@dxos/compute 0.9.1-main.c7dcc2e112 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/lib/neutral/AgentService.mjs +12 -0
- package/dist/lib/neutral/StorageService.mjs +3 -3
- package/dist/lib/neutral/{chunk-AFBNVFAY.mjs → chunk-2ZN64ZS7.mjs} +3 -3
- package/dist/lib/neutral/{chunk-JAU4E4EK.mjs → chunk-5VAXF6AH.mjs} +2 -2
- package/dist/lib/neutral/{chunk-QZS4G6PE.mjs → chunk-EVROJER7.mjs} +70 -25
- package/dist/lib/neutral/chunk-EVROJER7.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-XEVFBI6S.mjs → chunk-PF7XP7XP.mjs} +13 -13
- package/dist/lib/neutral/{chunk-XEVFBI6S.mjs.map → chunk-PF7XP7XP.mjs.map} +3 -3
- package/dist/lib/neutral/{chunk-MEPE4HHX.mjs → chunk-SJ37RWHU.mjs} +3 -3
- package/dist/lib/neutral/chunk-VSAZPWSJ.mjs +25 -0
- package/dist/lib/neutral/chunk-VSAZPWSJ.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-2B2KSJT6.mjs → chunk-WEO4FCQY.mjs} +3 -3
- package/dist/lib/neutral/fib-5XIYECL5.mjs +10 -0
- package/dist/lib/neutral/index.mjs +448 -399
- package/dist/lib/neutral/index.mjs.map +4 -4
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/reply-GCBRCUVM.mjs +10 -0
- package/dist/lib/neutral/sleep-XR6CL6W3.mjs +10 -0
- package/dist/lib/neutral/sleep-XR6CL6W3.mjs.map +7 -0
- package/dist/lib/neutral/testing/index.mjs +6 -6
- package/dist/types/src/AgentService.d.ts +71 -0
- package/dist/types/src/AgentService.d.ts.map +1 -0
- package/dist/types/src/Credential.d.ts +2 -0
- package/dist/types/src/Credential.d.ts.map +1 -1
- package/dist/types/src/Instructions.d.ts +33 -0
- package/dist/types/src/Instructions.d.ts.map +1 -0
- package/dist/types/src/McpServer.d.ts +1 -1
- package/dist/types/src/Operation.d.ts +92 -27
- package/dist/types/src/Operation.d.ts.map +1 -1
- package/dist/types/src/Operation.test.d.ts +2 -0
- package/dist/types/src/Operation.test.d.ts.map +1 -0
- package/dist/types/src/Process.d.ts +55 -21
- package/dist/types/src/Process.d.ts.map +1 -1
- package/dist/types/src/Runnable.d.ts +4 -0
- package/dist/types/src/Runnable.d.ts.map +1 -0
- package/dist/types/src/Script.d.ts +9 -11
- package/dist/types/src/Script.d.ts.map +1 -1
- package/dist/types/src/ServiceResolver.d.ts +1 -1
- package/dist/types/src/Skill.d.ts +157 -0
- package/dist/types/src/Skill.d.ts.map +1 -0
- package/dist/types/src/StorageService.d.ts +8 -8
- package/dist/types/src/StorageService.d.ts.map +1 -1
- package/dist/types/src/Template.d.ts +2 -4
- package/dist/types/src/Template.d.ts.map +1 -1
- package/dist/types/src/Trace.d.ts +16 -14
- package/dist/types/src/Trace.d.ts.map +1 -1
- package/dist/types/src/Trigger.d.ts +26 -61
- package/dist/types/src/Trigger.d.ts.map +1 -1
- package/dist/types/src/TriggerEvent.d.ts +3 -11
- package/dist/types/src/TriggerEvent.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +5 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +21 -15
- package/src/AgentService.ts +90 -0
- package/src/Credential.ts +3 -0
- package/src/Instructions.ts +73 -0
- package/src/McpServer.ts +1 -1
- package/src/Operation.test.ts +39 -0
- package/src/Operation.ts +130 -54
- package/src/Process.ts +96 -26
- package/src/Runnable.ts +12 -0
- package/src/Script.ts +14 -14
- package/src/ServiceResolver.ts +1 -1
- package/src/Skill.ts +218 -0
- package/src/StorageService.ts +15 -12
- package/src/Template.ts +2 -1
- package/src/Trace.ts +13 -11
- package/src/Trigger.ts +57 -52
- package/src/index.ts +5 -3
- package/src/umbrella.test.ts +8 -6
- package/dist/lib/neutral/chunk-QZS4G6PE.mjs.map +0 -7
- package/dist/lib/neutral/fib-HBDUGQW7.mjs +0 -10
- package/dist/lib/neutral/reply-RJIAJ6FV.mjs +0 -10
- package/dist/lib/neutral/sleep-3T2CZ7RE.mjs +0 -10
- package/dist/types/src/Blueprint.d.ts +0 -182
- package/dist/types/src/Blueprint.d.ts.map +0 -1
- package/dist/types/src/Routine.d.ts +0 -54
- package/dist/types/src/Routine.d.ts.map +0 -1
- package/src/Blueprint.ts +0 -224
- package/src/Routine.ts +0 -69
- /package/dist/lib/neutral/{fib-HBDUGQW7.mjs.map → AgentService.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-AFBNVFAY.mjs.map → chunk-2ZN64ZS7.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-JAU4E4EK.mjs.map → chunk-5VAXF6AH.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-MEPE4HHX.mjs.map → chunk-SJ37RWHU.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-2B2KSJT6.mjs.map → chunk-WEO4FCQY.mjs.map} +0 -0
- /package/dist/lib/neutral/{reply-RJIAJ6FV.mjs.map → fib-5XIYECL5.mjs.map} +0 -0
- /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,
|
|
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<
|
|
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,
|
|
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:
|
|
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,
|
|
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,
|
|
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
|
-
*
|
|
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
|
|
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<
|
|
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(
|
|
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<
|
|
232
|
+
export interface Variance<_Input, _Output, _Requirements, _Rpcs> {
|
|
192
233
|
readonly [ProcessTypeId]: {
|
|
193
|
-
readonly _Input: Types.Contravariant<
|
|
194
|
-
readonly _Output: Types.Covariant<
|
|
195
|
-
readonly _Requirements: Types.Covariant<
|
|
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,
|
|
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
|
|
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*
|
|
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*
|
|
360
|
+
yield* OperationStartedCell.set(true);
|
|
291
361
|
}
|
|
292
362
|
|
|
293
363
|
// Emit operation start event.
|
package/src/Runnable.ts
ADDED
|
@@ -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 {
|
|
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
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 =>
|
package/src/ServiceResolver.ts
CHANGED
|
@@ -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
|
|
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') {}
|
package/src/StorageService.ts
CHANGED
|
@@ -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
|
|
51
|
+
* Typed cell in a storage service.
|
|
52
52
|
*/
|
|
53
|
-
export interface
|
|
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
|
|
62
|
+
* Create a typed cell in a storage service.
|
|
63
63
|
*/
|
|
64
|
-
export const
|
|
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
|
|
80
|
+
* Typed cell in a storage service with a default value.
|
|
78
81
|
*/
|
|
79
|
-
export interface
|
|
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
|
|
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
|
-
(
|
|
94
|
+
(cell: Cell<T>): CellWithDefault<T, T> => {
|
|
92
95
|
return {
|
|
93
|
-
key:
|
|
94
|
-
get:
|
|
95
|
-
set: (value) =>
|
|
96
|
-
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 {
|
|
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
|
-
*
|
|
115
|
+
* Ref to the conversation feed object if present.
|
|
114
116
|
*/
|
|
115
|
-
|
|
117
|
+
conversation: Ref.Ref(Obj.Unknown).pipe(Schema.optional),
|
|
116
118
|
|
|
117
119
|
/**
|
|
118
|
-
*
|
|
120
|
+
* Ref to the trigger object if invocation resulted from a trigger.
|
|
119
121
|
*/
|
|
120
|
-
|
|
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
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
)
|
|
156
|
-
|
|
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.
|