@dxos/compute 0.9.1-main.c7dcc2e112 → 0.9.1-staging.ee54ba693a

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 (89) hide show
  1. package/README.md +4 -4
  2. package/dist/lib/neutral/AgentService.mjs +12 -0
  3. package/dist/lib/neutral/StorageService.mjs +3 -3
  4. package/dist/lib/neutral/{chunk-AFBNVFAY.mjs → chunk-2ZN64ZS7.mjs} +3 -3
  5. package/dist/lib/neutral/{chunk-JAU4E4EK.mjs → chunk-5VAXF6AH.mjs} +2 -2
  6. package/dist/lib/neutral/{chunk-QZS4G6PE.mjs → chunk-EVROJER7.mjs} +70 -25
  7. package/dist/lib/neutral/chunk-EVROJER7.mjs.map +7 -0
  8. package/dist/lib/neutral/{chunk-XEVFBI6S.mjs → chunk-PF7XP7XP.mjs} +13 -13
  9. package/dist/lib/neutral/{chunk-XEVFBI6S.mjs.map → chunk-PF7XP7XP.mjs.map} +3 -3
  10. package/dist/lib/neutral/{chunk-MEPE4HHX.mjs → chunk-SJ37RWHU.mjs} +3 -3
  11. package/dist/lib/neutral/chunk-VSAZPWSJ.mjs +25 -0
  12. package/dist/lib/neutral/chunk-VSAZPWSJ.mjs.map +7 -0
  13. package/dist/lib/neutral/{chunk-2B2KSJT6.mjs → chunk-WEO4FCQY.mjs} +3 -3
  14. package/dist/lib/neutral/fib-5XIYECL5.mjs +10 -0
  15. package/dist/lib/neutral/index.mjs +448 -399
  16. package/dist/lib/neutral/index.mjs.map +4 -4
  17. package/dist/lib/neutral/meta.json +1 -1
  18. package/dist/lib/neutral/reply-GCBRCUVM.mjs +10 -0
  19. package/dist/lib/neutral/sleep-XR6CL6W3.mjs +10 -0
  20. package/dist/lib/neutral/sleep-XR6CL6W3.mjs.map +7 -0
  21. package/dist/lib/neutral/testing/index.mjs +6 -6
  22. package/dist/types/src/AgentService.d.ts +71 -0
  23. package/dist/types/src/AgentService.d.ts.map +1 -0
  24. package/dist/types/src/Credential.d.ts +2 -0
  25. package/dist/types/src/Credential.d.ts.map +1 -1
  26. package/dist/types/src/Instructions.d.ts +33 -0
  27. package/dist/types/src/Instructions.d.ts.map +1 -0
  28. package/dist/types/src/McpServer.d.ts +1 -1
  29. package/dist/types/src/Operation.d.ts +92 -27
  30. package/dist/types/src/Operation.d.ts.map +1 -1
  31. package/dist/types/src/Operation.test.d.ts +2 -0
  32. package/dist/types/src/Operation.test.d.ts.map +1 -0
  33. package/dist/types/src/Process.d.ts +55 -21
  34. package/dist/types/src/Process.d.ts.map +1 -1
  35. package/dist/types/src/Runnable.d.ts +4 -0
  36. package/dist/types/src/Runnable.d.ts.map +1 -0
  37. package/dist/types/src/Script.d.ts +9 -11
  38. package/dist/types/src/Script.d.ts.map +1 -1
  39. package/dist/types/src/ServiceResolver.d.ts +1 -1
  40. package/dist/types/src/Skill.d.ts +157 -0
  41. package/dist/types/src/Skill.d.ts.map +1 -0
  42. package/dist/types/src/StorageService.d.ts +8 -8
  43. package/dist/types/src/StorageService.d.ts.map +1 -1
  44. package/dist/types/src/Template.d.ts +2 -4
  45. package/dist/types/src/Template.d.ts.map +1 -1
  46. package/dist/types/src/Trace.d.ts +16 -14
  47. package/dist/types/src/Trace.d.ts.map +1 -1
  48. package/dist/types/src/Trigger.d.ts +26 -61
  49. package/dist/types/src/Trigger.d.ts.map +1 -1
  50. package/dist/types/src/TriggerEvent.d.ts +3 -11
  51. package/dist/types/src/TriggerEvent.d.ts.map +1 -1
  52. package/dist/types/src/index.d.ts +5 -3
  53. package/dist/types/src/index.d.ts.map +1 -1
  54. package/dist/types/tsconfig.tsbuildinfo +1 -1
  55. package/package.json +21 -15
  56. package/src/AgentService.ts +90 -0
  57. package/src/Credential.ts +3 -0
  58. package/src/Instructions.ts +73 -0
  59. package/src/McpServer.ts +1 -1
  60. package/src/Operation.test.ts +39 -0
  61. package/src/Operation.ts +130 -54
  62. package/src/Process.ts +96 -26
  63. package/src/Runnable.ts +12 -0
  64. package/src/Script.ts +14 -14
  65. package/src/ServiceResolver.ts +1 -1
  66. package/src/Skill.ts +218 -0
  67. package/src/StorageService.ts +15 -12
  68. package/src/Template.ts +2 -1
  69. package/src/Trace.ts +13 -11
  70. package/src/Trigger.ts +57 -52
  71. package/src/index.ts +5 -3
  72. package/src/umbrella.test.ts +8 -6
  73. package/dist/lib/neutral/chunk-QZS4G6PE.mjs.map +0 -7
  74. package/dist/lib/neutral/fib-HBDUGQW7.mjs +0 -10
  75. package/dist/lib/neutral/reply-RJIAJ6FV.mjs +0 -10
  76. package/dist/lib/neutral/sleep-3T2CZ7RE.mjs +0 -10
  77. package/dist/types/src/Blueprint.d.ts +0 -182
  78. package/dist/types/src/Blueprint.d.ts.map +0 -1
  79. package/dist/types/src/Routine.d.ts +0 -54
  80. package/dist/types/src/Routine.d.ts.map +0 -1
  81. package/src/Blueprint.ts +0 -224
  82. package/src/Routine.ts +0 -69
  83. /package/dist/lib/neutral/{fib-HBDUGQW7.mjs.map → AgentService.mjs.map} +0 -0
  84. /package/dist/lib/neutral/{chunk-AFBNVFAY.mjs.map → chunk-2ZN64ZS7.mjs.map} +0 -0
  85. /package/dist/lib/neutral/{chunk-JAU4E4EK.mjs.map → chunk-5VAXF6AH.mjs.map} +0 -0
  86. /package/dist/lib/neutral/{chunk-MEPE4HHX.mjs.map → chunk-SJ37RWHU.mjs.map} +0 -0
  87. /package/dist/lib/neutral/{chunk-2B2KSJT6.mjs.map → chunk-WEO4FCQY.mjs.map} +0 -0
  88. /package/dist/lib/neutral/{reply-RJIAJ6FV.mjs.map → fib-5XIYECL5.mjs.map} +0 -0
  89. /package/dist/lib/neutral/{sleep-3T2CZ7RE.mjs.map → reply-GCBRCUVM.mjs.map} +0 -0
package/src/Process.ts CHANGED
@@ -5,6 +5,8 @@
5
5
  // @import-as-namespace
6
6
 
7
7
  import type { Atom } from '@effect-atom/atom';
8
+ import * as Rpc from '@effect/rpc/Rpc';
9
+ import * as RpcGroup from '@effect/rpc/RpcGroup';
8
10
  import * as Context from 'effect/Context';
9
11
  import * as Effect from 'effect/Effect';
10
12
  import type * as Exit from 'effect/Exit';
@@ -13,8 +15,9 @@ import * as Schema from 'effect/Schema';
13
15
  import * as Scope from 'effect/Scope';
14
16
  import type * as Types from 'effect/Types';
15
17
 
18
+ import { Annotation } from '@dxos/echo';
16
19
  import { assertArgument } from '@dxos/invariant';
17
- import { DXN, type URI } from '@dxos/keys';
20
+ import { DXN, URI } from '@dxos/keys';
18
21
  import { log } from '@dxos/log';
19
22
 
20
23
  import * as Operation from './Operation';
@@ -40,7 +43,7 @@ export type ID = Schema.Schema.Type<typeof ID>;
40
43
  * - onAlarm -> called for processes scheduling alarms.
41
44
  * - onChildEvent -> called when child process produces output or exits.
42
45
  */
43
- export interface Callbacks<I, O, R> {
46
+ export interface Callbacks<_Input, _Output, _Requirements, _Rpcs extends Rpc.Any> {
44
47
  /**
45
48
  * Called when the process is spawned.
46
49
  * Not called for processes that are resumed from a previously suspended state.
@@ -50,7 +53,7 @@ export interface Callbacks<I, O, R> {
50
53
  *
51
54
  * Note: This function should aim to complete in under 5 seconds to avoid exceeding limits in serverless environments.
52
55
  */
53
- onSpawn(): Effect.Effect<void, never, R | BaseServices>;
56
+ onSpawn(): Effect.Effect<void, never, _Requirements | BaseServices>;
54
57
 
55
58
  /**
56
59
  * Called when there's input available to process.
@@ -62,21 +65,26 @@ export interface Callbacks<I, O, R> {
62
65
  *
63
66
  * Note: This function should aim to complete in under 5 seconds to avoid exceeding limits in serverless environments.
64
67
  */
65
- onInput(input: I): Effect.Effect<void, never, R | BaseServices>;
68
+ onInput(input: _Input): Effect.Effect<void, never, _Requirements | BaseServices>;
66
69
 
67
70
  /**
68
71
  * Called when the process's alarm is triggered.
69
72
  *
70
73
  * @throws Throwing in the handler will terminate the process with an error.
71
74
  */
72
- onAlarm(): Effect.Effect<void, never, R | BaseServices>;
75
+ onAlarm(): Effect.Effect<void, never, _Requirements | BaseServices>;
73
76
 
74
77
  /**
75
78
  * Called when the process's child process produces output or exits.
76
79
  *
77
80
  * This allows the parent process to hibernate while a long-running child process is running.
78
81
  */
79
- onChildEvent(event: ChildEvent<unknown>): Effect.Effect<void, never, R | BaseServices>;
82
+ onChildEvent(event: ChildEvent<unknown>): Effect.Effect<void, never, _Requirements | BaseServices>;
83
+
84
+ /**
85
+ * Handlers for the RPCs provided by the process.
86
+ */
87
+ rpcHandlers: Context.Context<Rpc.ToHandler<_Rpcs>>;
80
88
  }
81
89
 
82
90
  /**
@@ -141,17 +149,36 @@ export interface Params {
141
149
  readonly name: string | null;
142
150
 
143
151
  /**
144
- * URI of the target this process is assigned to.
145
- */
146
- readonly target: URI.URI | null;
147
-
148
- /**
149
- * User-facing notifications requested for this process's lifecycle phases.
150
- * Surfaced on {@link Info} so a notification tracker can subscribe to the process monitor.
152
+ * User-defined annotations for the process.
153
+ * Can only be set when the process is spawned.
151
154
  */
152
- readonly notify?: Operation.NotifyOptions;
155
+ readonly annotations: Annotation.Dictionary;
153
156
  }
154
157
 
158
+ /**
159
+ * Attaches the process to a target object.
160
+ */
161
+ export const TargetAnnotation = Annotation.make({
162
+ id: 'org.dxos.process.target',
163
+ schema: URI.Schema,
164
+ });
165
+
166
+ /**
167
+ * Notification descriptor for surfacing process lifecycle events to the user.
168
+ */
169
+ export const NotifyAnnotation = Annotation.make({
170
+ id: 'org.dxos.process.notify',
171
+ schema: Operation.NotifyOptions,
172
+ });
173
+
174
+ /**
175
+ * Marks a process as the harness host for its conversation (discovery substrate).
176
+ */
177
+ export const HarnessHostAnnotation = Annotation.make({
178
+ id: 'org.dxos.process.harnessHost',
179
+ schema: Schema.Boolean,
180
+ });
181
+
155
182
  //
156
183
  // Executable.
157
184
  //
@@ -165,7 +192,12 @@ export type ProcessTypeId = typeof ProcessTypeId;
165
192
  * `create` is used to instantiate a new process.
166
193
  * Can store runtime state in scope of `create` function.
167
194
  */
168
- export interface Process<I, O, R> extends Process.Variance<I, O, R> {
195
+ export interface Process<
196
+ _Input,
197
+ _Output,
198
+ _Requirements = never,
199
+ _Rpcs extends Rpc.Any = never,
200
+ > extends Process.Variance<_Input, _Output, _Requirements, _Rpcs> {
169
201
  /**
170
202
  * Unique identifier for the executable in the reverse DNS format.
171
203
  */
@@ -178,25 +210,38 @@ export interface Process<I, O, R> extends Process.Variance<I, O, R> {
178
210
 
179
211
  readonly services: readonly Context.Tag<any, any>[];
180
212
 
213
+ // Runtime RPC group, stored as `any`. `RpcGroup`/`RpcClient` are invariant in their type
214
+ // argument (and `Callbacks.rpcHandlers` is contravariant in it), so referencing `_Rpcs` in the
215
+ // structural fields would block `Process<…, never>` from being assignable to `Process.Any`.
216
+ // The precise group is carried by the covariant `Variance` phantom and recovered at `spawn`.
217
+ // See design spec §4.4.
218
+ readonly rpcs: RpcGroup.RpcGroup<any>;
219
+
181
220
  /**
182
221
  * Create a new instance of the process.
183
222
  */
184
- create(ctx: ProcessContext<I, O>): Effect.Effect<Callbacks<I, O, R>, never, R | BaseServices | Scope.Scope>;
223
+ create(
224
+ ctx: ProcessContext<_Input, _Output>,
225
+ ): Effect.Effect<Callbacks<_Input, _Output, _Requirements, any>, never, _Requirements | BaseServices | Scope.Scope>;
185
226
  }
186
227
 
187
228
  export const isProcess = (executable: unknown): executable is Process.Any =>
188
229
  typeof executable === 'object' && executable !== null && ProcessTypeId in executable;
189
230
 
190
231
  export namespace Process {
191
- export interface Variance<I, O, R> {
232
+ export interface Variance<_Input, _Output, _Requirements, _Rpcs> {
192
233
  readonly [ProcessTypeId]: {
193
- readonly _Input: Types.Contravariant<I>;
194
- readonly _Output: Types.Covariant<O>;
195
- readonly _Requirements: Types.Covariant<R>;
234
+ readonly _Input: Types.Contravariant<_Input>;
235
+ readonly _Output: Types.Covariant<_Output>;
236
+ readonly _Requirements: Types.Covariant<_Requirements>;
237
+
238
+ // Phantom-covariant: lets `never`-RPC processes stay assignable to `Process.Any` while
239
+ // `spawn` still recovers the precise group from this slot. See design spec §4.4.
240
+ readonly _Rpcs: Types.Covariant<_Rpcs>;
196
241
  };
197
242
  }
198
243
 
199
- export type Any = Process<any, any, never>;
244
+ export type Any = Process<any, any, any, any>;
200
245
  }
201
246
 
202
247
  export interface MakeProcessOpts {
@@ -208,6 +253,7 @@ export interface MakeProcessOpts {
208
253
  readonly input: Schema.Schema.AnyNoContext;
209
254
  readonly output: Schema.Schema.AnyNoContext;
210
255
  readonly services: readonly Context.Tag<any, any>[];
256
+ readonly rpcs?: RpcGroup.RpcGroup<any>;
211
257
  }
212
258
 
213
259
  export const make = <const Opts extends Types.NoExcessProperties<MakeProcessOpts, Opts>>(
@@ -219,7 +265,8 @@ export const make = <const Opts extends Types.NoExcessProperties<MakeProcessOpts
219
265
  Callbacks<
220
266
  Schema.Schema.Type<Opts['input']>,
221
267
  Schema.Schema.Type<Opts['output']>,
222
- Context.Tag.Identifier<NonNullable<Opts['services']>[number]>
268
+ Context.Tag.Identifier<NonNullable<Opts['services']>[number]>,
269
+ RpcGroup.Rpcs<Opts['rpcs']>
223
270
  >
224
271
  >,
225
272
  never,
@@ -228,12 +275,14 @@ export const make = <const Opts extends Types.NoExcessProperties<MakeProcessOpts
228
275
  ): Process<
229
276
  Schema.Schema.Type<Opts['input']>,
230
277
  Schema.Schema.Type<Opts['output']>,
231
- Context.Tag.Identifier<NonNullable<Opts['services']>[number]>
278
+ Context.Tag.Identifier<NonNullable<Opts['services']>[number]>,
279
+ RpcGroup.Rpcs<Opts['rpcs']>
232
280
  > => {
233
281
  assertArgument(/^[a-z0-9]([a-z0-9.\-/]*[a-z0-9])?$/i.test(opts.key), 'key', 'Invalid key');
234
282
  return {
235
283
  [ProcessTypeId]: {} as any,
236
284
  ...opts,
285
+ rpcs: opts.rpcs ?? RpcGroup.make(),
237
286
  create: (ctx) =>
238
287
  create(ctx).pipe(
239
288
  Effect.map((partial) => ({
@@ -242,18 +291,39 @@ export const make = <const Opts extends Types.NoExcessProperties<MakeProcessOpts
242
291
  onAlarm: () => Effect.void,
243
292
  onChildEvent: () => Effect.void,
244
293
  ...partial,
294
+ rpcHandlers: sanitizeRpcs(opts.rpcs, partial.rpcHandlers),
245
295
  })),
246
296
  ),
247
297
  };
248
298
  };
249
299
 
300
+ // Returns `Context.Context<any>`: the runtime handler bag is stored untyped because
301
+ // `Callbacks.rpcHandlers` is contravariant in `_Rpcs` (see design spec §4.4); the precise
302
+ // handler contract is enforced by `make`'s `create` parameter, not by this internal helper.
303
+ const sanitizeRpcs = <Rpcs extends Rpc.Any>(
304
+ defined: RpcGroup.RpcGroup<Rpcs> | undefined,
305
+ provided: Context.Context<Rpc.ToHandler<Rpcs>> | undefined,
306
+ ): Context.Context<any> => {
307
+ // Handlers are required only when a non-empty RPC group is declared.
308
+ const needsRpcs = defined !== undefined && defined.requests.size > 0;
309
+ if (!needsRpcs) {
310
+ // `Context.empty()` is `Context<never>`; `Context`'s requirement parameter is contravariant,
311
+ // so the empty (no-handler) context needs widening to the untyped bag.
312
+ return provided ?? (Context.empty() as Context.Context<any>);
313
+ }
314
+ if (!provided) {
315
+ throw new TypeError('Process declared RPCs but did not provide any handlers');
316
+ }
317
+ return provided;
318
+ };
319
+
250
320
  /**
251
321
  * Durable marker recording that an operation's input handler has begun executing.
252
322
  * Persisted before the handler runs so that, after an interruption (suspend/restart), a
253
323
  * re-delivered input can tell that the previous attempt was in-flight. Cleared automatically
254
324
  * when the process reaches a terminal state (the runtime clears the process's storage).
255
325
  */
256
- const OperationStartedKey = StorageService.key(Schema.parseJson(Schema.Boolean), 'operation/started').pipe(
326
+ const OperationStartedCell = StorageService.cell(Schema.parseJson(Schema.Boolean), 'operation/started').pipe(
257
327
  StorageService.withDefault(() => false),
258
328
  );
259
329
 
@@ -281,13 +351,13 @@ export const fromOperation = <const Op extends Operation.Definition.Any>(
281
351
  onInput: (input: Operation.Definition.Input<Op>) =>
282
352
  Effect.gen(function* () {
283
353
  if (!idempotent) {
284
- const started = yield* OperationStartedKey.get;
354
+ const started = yield* OperationStartedCell.get;
285
355
  if (started) {
286
356
  return yield* Effect.die(
287
357
  new Error(`non-idempotent operation "${op.meta.key}" was interrupted; cannot retry safely`),
288
358
  );
289
359
  }
290
- yield* OperationStartedKey.set(true);
360
+ yield* OperationStartedCell.set(true);
291
361
  }
292
362
 
293
363
  // Emit operation start event.
@@ -0,0 +1,12 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ // @import-as-namespace
6
+
7
+ import * as Operation from './Operation';
8
+
9
+ // TODO(wittjosiah): Currently just Operation. Widen to a union (Operation | ComputeGraph | …) once the
10
+ // dispatcher/EDGE can run non-Operation runnables as a Process.
11
+ export type Runnable = Operation.PersistentOperation;
12
+ export const Runnable = Operation.PersistentOperation;
package/src/Script.ts CHANGED
@@ -6,27 +6,27 @@
6
6
 
7
7
  import * as Schema from 'effect/Schema';
8
8
 
9
- import { DXN, Annotation, Obj, Ref, Type } from '@dxos/echo';
9
+ import { Annotation, DXN, Obj, Ref, Type } from '@dxos/echo';
10
10
  import { FormInputAnnotation } from '@dxos/echo/Annotation';
11
11
  import { Text } from '@dxos/schema';
12
12
 
13
13
  /**
14
14
  * Source script.
15
15
  */
16
- export const Script = Schema.Struct({
17
- name: Schema.String.pipe(Schema.optional),
18
- description: Schema.String.pipe(Schema.optional),
19
- // TODO(burdon): Change to hash of deployed content.
20
- // Whether source has changed since last deploy.
21
- changed: Schema.Boolean.pipe(FormInputAnnotation.set(false), Schema.optional),
22
- source: Ref.Ref(Text.Text).pipe(FormInputAnnotation.set(false)),
23
- }).pipe(
24
- Annotation.LabelAnnotation.set(['name']),
25
- Annotation.IconAnnotation.set({ icon: 'ph--code--regular', hue: 'sky' }),
26
- Type.makeObject(DXN.make('org.dxos.type.script', '0.1.0')),
27
- );
16
+ export class Script extends Type.makeObject<Script>(DXN.make('org.dxos.type.script', '0.1.0'))(
17
+ Schema.Struct({
18
+ name: Schema.String.pipe(Schema.optional),
19
+ description: Schema.String.pipe(Schema.optional),
20
+ // TODO(burdon): Change to hash of deployed content.
21
+ // Whether source has changed since last deploy.
22
+ changed: Schema.Boolean.pipe(FormInputAnnotation.set(false), Schema.optional),
23
+ source: Ref.Ref(Text.Text).pipe(FormInputAnnotation.set(false)),
24
+ }).pipe(
25
+ Annotation.LabelAnnotation.set(['name']),
26
+ Annotation.IconAnnotation.set({ icon: 'ph--code--regular', hue: 'sky' }),
27
+ ),
28
+ ) {}
28
29
 
29
- export type Script = Type.InstanceType<typeof Script>;
30
30
  type Props = Omit<Obj.MakeProps<typeof Script>, 'source'> & { source?: string };
31
31
 
32
32
  export const make = ({ source = '', ...props }: Props = {}): Script =>
@@ -164,7 +164,7 @@ export const layerRequirements = <const Tags extends readonly Context.Tag<any, a
164
164
  * @example
165
165
  * ```ts
166
166
  * processManagerRuntime.runPromise(
167
- * myEffect.pipe(Effect.provide(ServiceResolver.provide({ space }, Database.Service, Feed.FeedService))),
167
+ * myEffect.pipe(Effect.provide(ServiceResolver.provide({ space }, Database.Service))),
168
168
  * );
169
169
  * ```
170
170
  */
package/src/Skill.ts ADDED
@@ -0,0 +1,218 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ // @import-as-namespace
6
+
7
+ import * as Effect from 'effect/Effect';
8
+ import * as Schema from 'effect/Schema';
9
+
10
+ import { ToolId } from '@dxos/ai';
11
+ import { Annotation, Database, DXN, Filter, Obj, Ref, Registry, Type, URI } from '@dxos/echo';
12
+ import { BaseError } from '@dxos/errors';
13
+ // Text is referenced in the inferred type of Skill (via Template.Template → Ref.Ref(Text.Text));
14
+ // the import lets TypeScript name it in the emitted .d.ts.
15
+ // eslint-disable-next-line unused-imports/no-unused-imports
16
+ import { type Text } from '@dxos/schema';
17
+
18
+ import * as McpServer from './McpServer';
19
+ import * as Operation from './Operation';
20
+ import * as Template from './Template';
21
+ import * as Trigger from './Trigger';
22
+
23
+ /**
24
+ * Skill schema defines the structure for AI assistant skills.
25
+ * Skills contain instructions, tools, and artifacts that guide the AI's behavior.
26
+ * Skills may use tools to create and read artifacts, which are managed by the assistant.
27
+ *
28
+ * The registry `key` and `version` are stored in the object meta — access them via
29
+ * `Obj.getMeta(skill).key` and `Obj.getMeta(skill).version`.
30
+ */
31
+ export class Skill extends Type.makeObject<Skill>(DXN.make('org.dxos.type.skill', '0.2.0'))(
32
+ Schema.Struct({
33
+ /**
34
+ * Human-readable name of the skill.
35
+ */
36
+ name: Schema.String.annotations({
37
+ description: 'Human-readable name of the skill',
38
+ }),
39
+
40
+ /**
41
+ * Description of the skill's purpose and functionality.
42
+ */
43
+ description: Schema.optional(Schema.String).annotations({
44
+ description: "Description of the skill's purpose and functionality",
45
+ }),
46
+
47
+ /**
48
+ * Instructions that guide the AI assistant's behavior and responses.
49
+ * These are system prompts or guidelines that the AI should follow.
50
+ */
51
+ instructions: Template.Template.annotations({
52
+ description: "Instructions that guide the AI assistant's behavior and responses",
53
+ }),
54
+
55
+ /**
56
+ * Array of tools that the AI assistant can use when this skill is active.
57
+ */
58
+ tools: Schema.Array(ToolId).annotations({
59
+ description: 'Array of tools that the AI assistant can use when this skill is active',
60
+ }),
61
+
62
+ /**
63
+ * Whether an agent is allowed to auto-enable this skill in a conversation.
64
+ */
65
+ agentCanEnable: Schema.optional(Schema.Boolean).annotations({
66
+ description: 'Whether an agent is allowed to auto-enable this skill in a conversation.',
67
+ }),
68
+
69
+ /**
70
+ * Array of MCP servers that the AI assistant can use when this skill is active.
71
+ */
72
+ mcpServers: Schema.optional(Schema.Array(McpServer.McpServer)),
73
+
74
+ /**
75
+ * Hooks triggered automatically at certain points in the agent's lifecycle.
76
+ */
77
+ hooks: Schema.optional(Schema.Array(Schema.suspend(() => Hook))),
78
+ }).pipe(
79
+ Annotation.LabelAnnotation.set(['name']),
80
+ Annotation.IconAnnotation.set({ icon: 'ph--blueprint--regular', hue: 'sky' }),
81
+ ),
82
+ ) {}
83
+
84
+ /**
85
+ * Controls when the hook is triggered.
86
+ */
87
+ export const HookSpec = Schema.Union(
88
+ /**
89
+ * Triggered when the agent is about to start a request.
90
+ * A request is a series of agent/tool turns that the model drives.
91
+ */
92
+ Schema.TaggedStruct('begin-request', {}),
93
+ /**
94
+ * Triggered when the agent has completed a request.
95
+ * A request is a series of agent/tool turns that the model drives.
96
+ */
97
+ Schema.TaggedStruct('end-request', {}),
98
+ );
99
+
100
+ /**
101
+ * Allows hooking into the agent's lifecycle.
102
+ * NOTE: Intentionally similar to Trigger, perhaps we should merge them.
103
+ */
104
+ export const Hook = Schema.Struct({
105
+ /**
106
+ * What to do when the hook is triggered.
107
+ *
108
+ * Can be a Ref to a PersistentOperation.
109
+ */
110
+ function: Schema.optional(Ref.Ref(Obj.Unknown).annotations({ title: 'Function' })),
111
+
112
+ /**
113
+ * Controls when the hook is triggered.
114
+ */
115
+ spec: HookSpec,
116
+
117
+ /**
118
+ * Passed as the input data to the function.
119
+ * Must match the function's input schema.
120
+ */
121
+ input: Trigger.InputTemplate.pipe(Annotation.FormInputAnnotation.set(false), Schema.optional),
122
+ });
123
+
124
+ /**
125
+ * Create a new Skill.
126
+ * The `key` (and optional `version`) are stored in the object meta.
127
+ * `key` must be a valid DXN name (e.g. `org.dxos.skill.mySkill`).
128
+ */
129
+ export const make: {
130
+ <T extends string>(
131
+ props: {
132
+ key: [DXN.Name<T>] extends [never] ? `Invalid DXN name "${T}": final segment must be camelCase (no hyphens)` : T;
133
+ version?: string;
134
+ name: string;
135
+ } & Partial<Skill>,
136
+ ): Skill;
137
+ } = ({ key, version, tools = [], instructions = Template.make(), ...props }) =>
138
+ Obj.make(Skill, {
139
+ [Obj.Meta]: { key, version },
140
+ tools,
141
+ instructions,
142
+ ...props,
143
+ });
144
+
145
+ /**
146
+ * Get the registry key for a skill.
147
+ */
148
+ export const getKey = (skill: Skill): string => {
149
+ const key = Obj.getMeta(skill).key;
150
+ if (key === undefined) {
151
+ throw new Error('Skill is missing the meta key.');
152
+ }
153
+ return key;
154
+ };
155
+
156
+ /**
157
+ * Get the registry version for a skill, if any.
158
+ */
159
+ export const getVersion = (skill: Skill): string | undefined => Obj.getMeta(skill).version;
160
+
161
+ /**
162
+ * Util to create tool definitions for a skill.
163
+ */
164
+ export const toolDefinitions = ({
165
+ tools = [],
166
+ operations = [],
167
+ }: {
168
+ tools?: string[];
169
+ operations?: Operation.Definition.Any[];
170
+ }) => [...operations.map((op) => ToolId.make(DXN.getName(op.meta.key))), ...tools.map((tool) => ToolId.make(tool))];
171
+
172
+ /**
173
+ * Factory for the skills.
174
+ */
175
+ export type Definition = {
176
+ key: DXN.Name<string>;
177
+ make: () => Skill;
178
+ };
179
+
180
+ /**
181
+ * Returns the canonical URI used to reference this skill in the registry.
182
+ * Use this URI with `Ref.fromURI` to bind a skill without cloning it to the DB.
183
+ *
184
+ * TODO(wittjosiah): Should use Obj.getURI instead once it supports options to prefer meta key over EID.
185
+ */
186
+ export const registryURI = (key: DXN.Name<string>): URI.URI => (DXN.tryMake(`dxn:${key}`) ?? URI.make(key)) as URI.URI;
187
+
188
+ /**
189
+ * Resolves a skill from the registry by its meta key.
190
+ * Does not check the local database for the skill.
191
+ */
192
+ export const resolve = (key: string): Effect.Effect<Skill, NotFoundError, Registry.Service> =>
193
+ Effect.gen(function* () {
194
+ const results = yield* Registry.runQuery(Filter.and(Filter.type(Skill), Filter.key(key)));
195
+ const skill = results[0];
196
+ if (!skill) {
197
+ return yield* Effect.fail(new NotFoundError({ context: { key } }));
198
+ }
199
+ return skill;
200
+ });
201
+
202
+ /**
203
+ * Upserts a skill into the database.
204
+ * If the skill already exists in the database, the local (possibly forked) copy is returned as-is.
205
+ * Otherwise, a fresh copy is cloned from the registry and added.
206
+ * @deprecated Since we're using a registry we no longer need to store skills in the database.
207
+ */
208
+ // TODO(dmaretskyi): Remove.
209
+ export const upsert = (key: string): Effect.Effect<Skill, NotFoundError, Registry.Service | Database.Service> =>
210
+ Effect.gen(function* () {
211
+ const local = yield* Database.query(Filter.and(Filter.type(Skill), Filter.key(key))).run;
212
+ if (local.length > 0) {
213
+ return local[0];
214
+ }
215
+ return yield* Database.add(Obj.clone(yield* resolve(key), { deep: 'all' }));
216
+ });
217
+
218
+ export class NotFoundError extends BaseError.extend('SkillNotFound', 'Skill not found') {}
@@ -48,9 +48,9 @@ export const list = Effect.serviceFunctionEffect(StorageService, (_) => _.list);
48
48
  export const clear = Effect.serviceFunctionEffect(StorageService, (_) => _.clear);
49
49
 
50
50
  /**
51
- * Typed key in a storage service.
51
+ * Typed cell in a storage service.
52
52
  */
53
- export interface Key<T> extends Pipeable.Pipeable {
53
+ export interface Cell<T> extends Pipeable.Pipeable {
54
54
  readonly key: string;
55
55
 
56
56
  get: Effect.Effect<Option.Option<T>, never, StorageService>;
@@ -59,9 +59,12 @@ export interface Key<T> extends Pipeable.Pipeable {
59
59
  }
60
60
 
61
61
  /**
62
- * Create a typed key in a storage service.
62
+ * Create a typed cell in a storage service.
63
63
  */
64
- export const key = <S extends Schema.Schema<any, string, any>>(schema: S, key: string): Key<Schema.Schema.Type<S>> => {
64
+ export const cell = <S extends Schema.Schema<any, string, any>>(
65
+ schema: S,
66
+ key: string,
67
+ ): Cell<Schema.Schema.Type<S>> => {
65
68
  return {
66
69
  key,
67
70
  get: get(schema, key),
@@ -74,9 +77,9 @@ export const key = <S extends Schema.Schema<any, string, any>>(schema: S, key: s
74
77
  };
75
78
 
76
79
  /**
77
- * Typed key in a storage service with a default value.
80
+ * Typed cell in a storage service with a default value.
78
81
  */
79
- export interface KeyWithDefault<T, U> extends Pipeable.Pipeable {
82
+ export interface CellWithDefault<T, U> extends Pipeable.Pipeable {
80
83
  readonly key: string;
81
84
  get: Effect.Effect<T | U, never, StorageService>;
82
85
  set(value: U): Effect.Effect<void, never, StorageService>;
@@ -84,16 +87,16 @@ export interface KeyWithDefault<T, U> extends Pipeable.Pipeable {
84
87
  }
85
88
 
86
89
  /**
87
- * Assign a default value to a key if it is not present.
90
+ * Assign a default value to a cell if it is not present.
88
91
  */
89
92
  export const withDefault =
90
93
  <T>(getDefault: () => NoInfer<T>) =>
91
- (key: Key<T>): KeyWithDefault<T, T> => {
94
+ (cell: Cell<T>): CellWithDefault<T, T> => {
92
95
  return {
93
- key: key.key,
94
- get: key.get.pipe(Effect.map(Option.getOrElse(() => getDefault()))),
95
- set: (value) => key.set(value),
96
- delete: () => key.delete(),
96
+ key: cell.key,
97
+ get: cell.get.pipe(Effect.map(Option.getOrElse(() => getDefault()))),
98
+ set: (value) => cell.set(value),
99
+ delete: () => cell.delete(),
97
100
  pipe(...args: any) {
98
101
  return Pipeable.pipeArguments(this, arguments);
99
102
  },
package/src/Template.ts CHANGED
@@ -63,6 +63,7 @@ export const Template = Schema.Struct({
63
63
  });
64
64
 
65
65
  export type Template = Schema.Schema.Type<typeof Template>;
66
+
66
67
  export type MakeProps = Partial<{ id: string; source: string; inputs: Input[] }>;
67
68
 
68
69
  export const make = ({ id, source, inputs = [] }: MakeProps = {}): Template => ({
@@ -104,9 +105,9 @@ export const processTemplate = (
104
105
  if (results.length === 0) {
105
106
  return yield* Effect.fail(new FunctionNotFoundError(input.operation));
106
107
  }
107
- const fn = Operation.deserialize(results[0]);
108
108
 
109
109
  // NOTE: Operations referenced by template inputs must accept void input — see `Input.operation`.
110
+ const fn = Operation.deserialize(results[0]);
110
111
  const result = yield* Operation.invoke(fn, undefined as any).pipe(Effect.orDie);
111
112
  return [input.name, result] as const;
112
113
  }
package/src/Trace.ts CHANGED
@@ -9,9 +9,11 @@ import * as Effect from 'effect/Effect';
9
9
  import * as Layer from 'effect/Layer';
10
10
  import * as Schema from 'effect/Schema';
11
11
 
12
- import { DXN, Annotation, Obj, Type } from '@dxos/echo';
12
+ import { Annotation, DXN, Obj, Ref, Type } from '@dxos/echo';
13
13
  import { log } from '@dxos/log';
14
14
 
15
+ import * as Trigger from './Trigger';
16
+
15
17
  /**
16
18
  * Writes ephemeral or persistent events to the trace.
17
19
  * Exposed to processes and operations to record events to the trace.
@@ -110,14 +112,14 @@ export const Meta = Schema.Struct({
110
112
  space: Schema.optional(Schema.String),
111
113
 
112
114
  /**
113
- * ID of the conversation feed object if present.
115
+ * Ref to the conversation feed object if present.
114
116
  */
115
- conversationId: Schema.optional(Obj.ID),
117
+ conversation: Ref.Ref(Obj.Unknown).pipe(Schema.optional),
116
118
 
117
119
  /**
118
- * ID of the trigger object if invocation resulted from a trigger.
120
+ * Ref to the trigger object if invocation resulted from a trigger.
119
121
  */
120
- triggerId: Schema.optional(Obj.ID),
122
+ trigger: Ref.Ref(Trigger.Trigger).pipe(Schema.optional),
121
123
 
122
124
  /**
123
125
  * ID of the tool call that created the current process.
@@ -148,12 +150,12 @@ export const MessageData = Schema.Struct({
148
150
  events: Schema.Array(Event),
149
151
  });
150
152
  export type MessageData = Schema.Schema.Type<typeof MessageData>;
151
- export const Message = MessageData.pipe(
152
- Annotation.IconAnnotation.set({ icon: 'ph--note--regular', hue: 'rose' }),
153
- Annotation.HiddenAnnotation.set(true),
154
- Type.makeObject(DXN.make('org.dxos.type.traceMessage', '0.1.0')),
155
- );
156
- export type Message = Type.InstanceType<typeof Message>;
153
+ export class Message extends Type.makeObject<Message>(DXN.make('org.dxos.type.traceMessage', '0.1.0'))(
154
+ MessageData.pipe(
155
+ Annotation.IconAnnotation.set({ icon: 'ph--note--regular', hue: 'rose' }),
156
+ Annotation.HiddenAnnotation.set(true),
157
+ ),
158
+ ) {}
157
159
  /**
158
160
  * Flattened representation of a signle event in a trace message.
159
161
  * Events are stored in batched messages for efficiency, but flat representation is more convenient for consumption.