@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/compute",
3
- "version": "0.9.1-main.c7dcc2e112",
3
+ "version": "0.9.1-staging.ee54ba693a",
4
4
  "description": "Umbrella package for AI-aware DXOS compute primitives.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -18,6 +18,11 @@
18
18
  "types": "./dist/types/src/index.d.ts",
19
19
  "default": "./dist/lib/neutral/index.mjs"
20
20
  },
21
+ "./AgentService": {
22
+ "source": "./src/AgentService.ts",
23
+ "types": "./dist/types/src/AgentService.d.ts",
24
+ "default": "./dist/lib/neutral/AgentService.mjs"
25
+ },
21
26
  "./StorageService": {
22
27
  "source": "./src/StorageService.ts",
23
28
  "types": "./dist/types/src/StorageService.d.ts",
@@ -36,25 +41,26 @@
36
41
  ],
37
42
  "dependencies": {
38
43
  "@effect-atom/atom": "^0.5.3",
39
- "@effect/platform": "0.96.1",
40
- "@dxos/echo": "0.9.1-main.c7dcc2e112",
41
- "@dxos/effect": "0.9.1-main.c7dcc2e112",
42
- "@dxos/ai": "0.9.1-main.c7dcc2e112",
43
- "@dxos/errors": "0.9.1-main.c7dcc2e112",
44
- "@dxos/log": "0.9.1-main.c7dcc2e112",
45
- "@dxos/protocols": "0.9.1-main.c7dcc2e112",
46
- "@dxos/schema": "0.9.1-main.c7dcc2e112",
47
- "@dxos/invariant": "0.9.1-main.c7dcc2e112",
48
- "@dxos/keys": "0.9.1-main.c7dcc2e112",
49
- "@dxos/vendor-kbn-handlebars": "0.9.1-main.c7dcc2e112"
44
+ "@effect/platform": "0.96.2",
45
+ "@effect/rpc": "0.75.1",
46
+ "@dxos/ai": "0.9.1-staging.ee54ba693a",
47
+ "@dxos/echo": "0.9.1-staging.ee54ba693a",
48
+ "@dxos/effect": "0.9.1-staging.ee54ba693a",
49
+ "@dxos/invariant": "0.9.1-staging.ee54ba693a",
50
+ "@dxos/keys": "0.9.1-staging.ee54ba693a",
51
+ "@dxos/errors": "0.9.1-staging.ee54ba693a",
52
+ "@dxos/log": "0.9.1-staging.ee54ba693a",
53
+ "@dxos/vendor-kbn-handlebars": "0.9.1-staging.ee54ba693a",
54
+ "@dxos/schema": "0.9.1-staging.ee54ba693a",
55
+ "@dxos/protocols": "0.9.1-staging.ee54ba693a"
50
56
  },
51
57
  "devDependencies": {
52
- "effect": "3.21.3",
58
+ "effect": "3.21.4",
53
59
  "vitest": "4.1.8",
54
- "@dxos/echo-client": "0.9.1-main.c7dcc2e112"
60
+ "@dxos/echo-client": "0.9.1-staging.ee54ba693a"
55
61
  },
56
62
  "peerDependencies": {
57
- "effect": "3.21.3"
63
+ "effect": "3.21.4"
58
64
  },
59
65
  "publishConfig": {
60
66
  "access": "public"
@@ -0,0 +1,90 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ // @import-as-namespace
6
+
7
+ import * as Context from 'effect/Context';
8
+ import * as Effect from 'effect/Effect';
9
+ import type * as Stream from 'effect/Stream';
10
+
11
+ import type { Database, Feed, Obj, Ref } from '@dxos/echo';
12
+ import { DXN } from '@dxos/keys';
13
+
14
+ import type * as Trace from './Trace';
15
+
16
+ /**
17
+ * Service interface for the agent session manager.
18
+ * Implementation lives in @dxos/functions-runtime.
19
+ */
20
+ export interface Service {
21
+ /**
22
+ * Gets or creates a session for a feed.
23
+ */
24
+ getSession: (feed: Feed.Feed, options?: GetSessionOptions) => Effect.Effect<Session>;
25
+
26
+ /**
27
+ * Hydrates agent processes persisted by a previous session.
28
+ * Each record is rehydrated with a fresh process built from the layer options.
29
+ */
30
+ hydrate: () => Effect.Effect<void>;
31
+ }
32
+
33
+ export class AgentService extends Context.Tag('@dxos/functions-runtime/AgentService')<AgentService, Service>() {}
34
+
35
+ /**
36
+ * Handle to an agent session.
37
+ */
38
+ export interface Session {
39
+ /**
40
+ * The feed that the session is associated with.
41
+ */
42
+ readonly feed: Feed.Feed;
43
+
44
+ /**
45
+ * Gets the context objects from the agent.
46
+ */
47
+ getContext: () => Effect.Effect<Ref.Ref<Obj.Unknown>[], never, Database.Service>;
48
+
49
+ /**
50
+ * Adds context objects to the agent.
51
+ */
52
+ addContext: (context: Ref.Ref<Obj.Unknown>[]) => Effect.Effect<void, never, Database.Service>;
53
+
54
+ /**
55
+ * Submits a prompt to the agent.
56
+ */
57
+ submitPrompt: (prompt: string) => Effect.Effect<void>;
58
+
59
+ /**
60
+ * Wait until agent has completed its work.
61
+ */
62
+ waitForCompletion: () => Effect.Effect<void>;
63
+
64
+ /**
65
+ * Terminates the agent process and clears its durable storage.
66
+ */
67
+ terminate: () => Effect.Effect<void>;
68
+
69
+ /**
70
+ * Subscribe to ephemeral trace events (e.g., streaming partial messages).
71
+ * Replays buffered events, then streams new ones until the process ends.
72
+ *
73
+ * When forking a collector from a short-lived parent (e.g. `useEffect` +
74
+ * `runPromise(Effect.forEach(subscribe))`), use {@link Effect.forkDaemon} so the
75
+ * stream survives after the parent scope closes; interrupt it on dispose.
76
+ */
77
+ subscribeEphemeral: () => Stream.Stream<Trace.Message>;
78
+ }
79
+
80
+ export const getSession = Effect.serviceFunctionEffect(AgentService, (service) => service.getSession);
81
+
82
+ export const hydrate = Effect.serviceFunctionEffect(AgentService, (service) => service.hydrate);
83
+
84
+ export interface GetSessionOptions {
85
+ readonly model?: DXN.DXN;
86
+ // The catalog's shared model ids are served by several providers, so the provider must accompany
87
+ // the model into the agent process — the id alone does not identify a resolver.
88
+ readonly provider?: DXN.DXN;
89
+ readonly systemPrompt?: string;
90
+ }
package/src/Credential.ts CHANGED
@@ -19,6 +19,9 @@ export type ServiceCredential = {
19
19
 
20
20
  // TODO(dmaretskyi): Build out.
21
21
  apiKey?: string;
22
+
23
+ /** Non-secret secondary identifier carried alongside the credential (e.g. an account id or report id). */
24
+ account?: string;
22
25
  };
23
26
 
24
27
  export class CredentialsService extends Context.Tag('@dxos/functions/CredentialsService')<
@@ -0,0 +1,73 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ // @import-as-namespace
6
+
7
+ import * as Schema from 'effect/Schema';
8
+
9
+ import { Annotation, DXN, Format, JsonSchema, Obj, Ref, Type } from '@dxos/echo';
10
+ import { Text } from '@dxos/schema';
11
+
12
+ import * as Skill from './Skill';
13
+
14
+ /**
15
+ * Prompt-based operation.
16
+ * May reference skills and additional context.
17
+ * Hidden from the navtree and object picker — accessed only through Routines.
18
+ */
19
+ export class Instructions extends Type.makeObject<Instructions>(DXN.make('org.dxos.type.instructions', '0.1.0'))(
20
+ Schema.Struct({
21
+ name: Schema.optional(Schema.String),
22
+ description: Schema.optional(Schema.String),
23
+ input: JsonSchema.JsonSchema.pipe(Annotation.FormInputAnnotation.set(false)).annotations({
24
+ description: 'Input schema',
25
+ }),
26
+ output: JsonSchema.JsonSchema.pipe(Annotation.FormInputAnnotation.set(false)).annotations({
27
+ description: 'Output schema',
28
+ }),
29
+ text: Ref.Ref(Text.Text).pipe(
30
+ Format.FormatAnnotation.set(Format.TypeFormat.Markdown),
31
+ Schema.annotations({ title: 'Instructions', description: 'Describe what the agent should do in each session.' }),
32
+ ),
33
+ skills: Schema.Array(Ref.Ref(Skill.Skill)),
34
+ /**
35
+ * Context objects bound to the agent's session when this routine runs (sibling of `skills`).
36
+ * Generic `Ref.Ref(Obj.Unknown)` so any space object qualifies. Honored on every run path that
37
+ * executes a routine through the agent prompt, not only triggered automations.
38
+ */
39
+ objects: Schema.Array(Ref.Ref(Obj.Unknown)).pipe(Schema.annotations({ title: 'Objects' }), Schema.optional),
40
+ }).pipe(
41
+ Annotation.LabelAnnotation.set(['name']),
42
+ Annotation.IconAnnotation.set({ icon: 'ph--scroll--regular', hue: 'sky' }),
43
+ Annotation.HiddenAnnotation.set(true),
44
+ ),
45
+ ) {}
46
+
47
+ export type MakeProps = {
48
+ name?: string;
49
+ description?: string;
50
+ input?: Schema.Schema.AnyNoContext;
51
+ output?: Schema.Schema.AnyNoContext;
52
+ text?: string;
53
+ skills?: Ref.Ref<Skill.Skill>[];
54
+ objects?: Ref.Ref<Obj.Unknown>[];
55
+ };
56
+
57
+ /** Creates an Instructions object with an owned Markdown `text` body (parented so it cascades and deep-clones). */
58
+ export const make = ({ name, description, input, output, text, skills = [], objects }: MakeProps): Instructions => {
59
+ const body = Text.make({ content: text ?? '' });
60
+ const instructions = Obj.make(Instructions, {
61
+ name,
62
+ description,
63
+ input: JsonSchema.toJsonSchema(input ?? Schema.Void),
64
+ output: JsonSchema.toJsonSchema(output ?? Schema.Void),
65
+ text: Ref.make(body),
66
+ skills,
67
+ objects,
68
+ });
69
+ // The body is owned by the instructions: it cascade-deletes with it and is cloned alongside it under
70
+ // `Obj.clone(..., { deep: 'parent' })`.
71
+ Obj.setParent(body, instructions);
72
+ return instructions;
73
+ };
package/src/McpServer.ts CHANGED
@@ -9,7 +9,7 @@ import * as Schema from 'effect/Schema';
9
9
  /**
10
10
  * MCP server connection spec — the minimal fields needed to dial an MCP server.
11
11
  *
12
- * Used as a value type embedded in blueprints and passed to runtime session connect logic.
12
+ * Used as a value type embedded in skills and passed to runtime session connect logic.
13
13
  * The user-facing ECHO object (`@dxos/assistant-toolkit` `McpServer`) composes this struct
14
14
  * and adds instance-level fields (`name`, `enabled`) and ECHO annotations.
15
15
  */
@@ -0,0 +1,39 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+ import * as Schema from 'effect/Schema';
7
+ import { describe, test } from 'vitest';
8
+
9
+ import { DXN } from '@dxos/keys';
10
+
11
+ import * as Operation from './Operation';
12
+
13
+ const KEY = DXN.make('org.example.test.op');
14
+
15
+ const makeOp = () => Operation.make({ input: Schema.Void, output: Schema.String, meta: { key: KEY, name: 'Test Op' } });
16
+
17
+ describe('Operation visibility', () => {
18
+ test('operations are hidden by default', ({ expect }) => {
19
+ expect(Operation.isVisible(Operation.serialize(makeOp()))).toBe(false);
20
+ });
21
+
22
+ test('visible combinator marks an operation visible', ({ expect }) => {
23
+ expect(Operation.isVisible(Operation.serialize(makeOp().pipe(Operation.visible)))).toBe(true);
24
+ });
25
+
26
+ test('annotate does not mutate the input definition', ({ expect }) => {
27
+ const op = makeOp();
28
+ const annotated = op.pipe(Operation.visible);
29
+ expect(Operation.isVisible(Operation.serialize(annotated))).toBe(true);
30
+ // The original definition is untouched — combinators return a fresh value.
31
+ expect(Operation.isVisible(Operation.serialize(op))).toBe(false);
32
+ });
33
+
34
+ test('visible preserves the definition type so a handler still attaches', ({ expect }) => {
35
+ // Type-preservation is a compile-time guarantee; this asserts the value path also works.
36
+ const op = makeOp().pipe(Operation.visible, (op) => Operation.withHandler(op, () => Effect.succeed('ok')));
37
+ expect(Operation.isOperationWithHandler(op)).toBe(true);
38
+ });
39
+ });
package/src/Operation.ts CHANGED
@@ -9,10 +9,11 @@ import * as Effect from 'effect/Effect';
9
9
  import * as Layer from 'effect/Layer';
10
10
  import * as Option from 'effect/Option';
11
11
  import * as Pipeable from 'effect/Pipeable';
12
+ import * as Schema from 'effect/Schema';
12
13
  import * as Schema$ from 'effect/Schema';
13
14
  import type * as Types from 'effect/Types';
14
15
 
15
- import { DXN, Annotation, JsonSchema, Migration, Obj, Ref, Type, type Key } from '@dxos/echo';
16
+ import { Annotation, DXN, JsonSchema, type Key, Migration, Obj, Ref, Type } from '@dxos/echo';
16
17
  import type { URI } from '@dxos/keys';
17
18
 
18
19
  import type { NoHandlerError } from './errors';
@@ -299,46 +300,48 @@ export type InvokeRemote = <I, O, E>(
299
300
  * The registry `key` and `version` live in the object meta — access via
300
301
  * `Obj.getMeta(record).key` and `Obj.getMeta(record).version`.
301
302
  */
302
- export const PersistentOperation = Schema$.Struct({
303
- name: Schema$.NonEmptyString,
303
+ // TODO(dmaretskyi): Keep typename as 'org.dxos.type.function' (not 'operation') to maintain
304
+ // backward compatibility with existing data and avoid requiring data migration.
305
+ export class PersistentOperation extends Type.makeObject<PersistentOperation>(
306
+ DXN.make('org.dxos.type.function', '0.2.0'),
307
+ )(
308
+ Schema$.Struct({
309
+ name: Schema$.NonEmptyString,
304
310
 
305
- description: Schema$.optional(Schema$.String),
311
+ description: Schema$.optional(Schema$.String),
306
312
 
307
- /**
308
- * ISO date string of the last deployment.
309
- */
310
- updated: Schema$.optional(Schema$.String),
313
+ /**
314
+ * ISO date string of the last deployment.
315
+ */
316
+ updated: Schema$.optional(Schema$.String),
311
317
 
312
- // Reference to a source script if it exists within ECHO.
313
- // TODO(burdon): Don't ref ScriptType directly (core).
314
- source: Schema$.optional(Ref.Ref(Obj.Unknown)),
318
+ // Reference to a source script if it exists within ECHO.
319
+ // TODO(burdon): Don't ref ScriptType directly (core).
320
+ source: Schema$.optional(Ref.Ref(Obj.Unknown)),
315
321
 
316
- inputSchema: Schema$.optional(JsonSchema.JsonSchema),
317
- outputSchema: Schema$.optional(JsonSchema.JsonSchema),
322
+ inputSchema: Schema$.optional(JsonSchema.JsonSchema),
323
+ outputSchema: Schema$.optional(JsonSchema.JsonSchema),
318
324
 
319
- /**
320
- * List of required services.
321
- * Match the Context.Tag keys of the FunctionServices variants.
322
- */
323
- services: Schema$.optional(Schema$.Array(Schema$.String)),
325
+ /**
326
+ * List of required services.
327
+ * Match the Context.Tag keys of the FunctionServices variants.
328
+ */
329
+ services: Schema$.optional(Schema$.Array(Schema$.String)),
324
330
 
325
- // Local binding to a function name.
326
- // TODO(dmaretskyi): Add this field to Operation.Definition.
327
- binding: Schema$.optional(Schema$.String),
331
+ // Local binding to a function name.
332
+ // TODO(dmaretskyi): Add this field to Operation.Definition.
333
+ binding: Schema$.optional(Schema$.String),
328
334
 
329
- /**
330
- * Phosphor icon identifier in `ph--<name>--<variant>` format (e.g. `ph--file-text--regular`).
331
- */
332
- icon: Schema$.optional(Schema$.String),
333
- }).pipe(
334
- Annotation.LabelAnnotation.set(['name']),
335
- Annotation.IconAnnotation.set({ icon: 'ph--function--regular', hue: 'blue' }),
336
- Annotation.HiddenAnnotation.set(true),
337
- // TODO(dmaretskyi): Keep typename as 'org.dxos.type.function' (not 'operation') to maintain
338
- // backward compatibility with existing data and avoid requiring data migration.
339
- Type.makeObject(DXN.make('org.dxos.type.function', '0.2.0')),
340
- );
341
- export type PersistentOperation = Type.InstanceType<typeof PersistentOperation>;
335
+ /**
336
+ * Phosphor icon identifier in `ph--<name>--<variant>` format (e.g. `ph--file-text--regular`).
337
+ */
338
+ icon: Schema$.optional(Schema$.String),
339
+ }).pipe(
340
+ Annotation.LabelAnnotation.set(['name']),
341
+ Annotation.IconAnnotation.set({ icon: 'ph--function--regular', hue: 'blue' }),
342
+ Annotation.HiddenAnnotation.set(true),
343
+ ),
344
+ ) {}
342
345
 
343
346
  const FUNCTION_META_KEY = 'org.dxos.service.function';
344
347
 
@@ -429,20 +432,46 @@ export const setFrom = (target: PersistentOperation, source: PersistentOperation
429
432
  * Defined locally to avoid a core dependency on UI translation packages; structurally compatible with
430
433
  * the app-level `Label` type so values flow into UI toasts unchanged.
431
434
  */
432
- export type Label = string | [string, { ns: string | readonly string[]; count?: number; defaultValue?: string }];
435
+ export const Label = Schema.Union(
436
+ Schema.String,
437
+ // `Schema.mutable` mirrors the app-level `Label` (whose tuple is mutable), so decoded values are
438
+ // assignable to UI toast `title`/`label` slots without a readonly-vs-mutable tuple mismatch.
439
+ Schema.mutable(
440
+ Schema.Tuple(
441
+ Schema.String,
442
+ Schema.mutable(
443
+ Schema.Struct({
444
+ ns: Schema.String,
445
+ count: Schema.optional(Schema.Number),
446
+ defaultValue: Schema.optional(Schema.String),
447
+ }),
448
+ ),
449
+ ),
450
+ ),
451
+ );
452
+ export type Label = Schema.Schema.Type<typeof Label>;
433
453
 
434
454
  /**
435
455
  * Per-phase user notification messages for an invocation.
436
456
  * A phase is notified to the user iff its message is provided; messages are translatable {@link Label}s.
437
457
  */
438
- export interface NotifyOptions {
458
+ export const NotifyOptions = Schema.Struct({
439
459
  /** Shown when the invocation starts. */
440
- start?: Label;
460
+ start: Schema.optional(Label),
441
461
  /** Shown when the invocation succeeds. */
442
- success?: Label;
462
+ success: Schema.optional(Label),
443
463
  /** Shown when the invocation fails. */
444
- error?: Label;
445
- }
464
+ error: Schema.optional(Label),
465
+ });
466
+ export type NotifyOptions = Schema.Schema.Type<typeof NotifyOptions>;
467
+
468
+ /**
469
+ * Annotation that configures the process to notify the user at the given invocation phases.
470
+ */
471
+ export const NotifyOptionsAnnotation = Annotation.make({
472
+ id: 'org.dxos.operation.notify-options',
473
+ schema: NotifyOptions,
474
+ });
446
475
 
447
476
  /**
448
477
  * Options for operation invocation.
@@ -460,7 +489,7 @@ export interface InvokeOptions {
460
489
  * URI of the conversation feed (queue) — today always an EID, but typed as
461
490
  * `URI.URI` to accommodate future entity-kind extensions. Narrow with `EID.parse`
462
491
  * at the point of use.
463
- * Passed to the process environment so nested operations can resolve AiContext.Service and related services.
492
+ * Passed to the process environment so nested operations can resolve HarnessService and related services.
464
493
  */
465
494
  conversation?: URI.URI;
466
495
  /**
@@ -487,6 +516,50 @@ export const isIdempotent = (op: Definition.Any): boolean =>
487
516
  ? Option.getOrElse(Annotation.getDictionary(op.meta.annotations, IdempotentAnnotation), () => false)
488
517
  : false;
489
518
 
519
+ /**
520
+ * Attaches an annotation to an operation definition, returning a new definition.
521
+ * Combinators never mutate their input — operation definitions are module-level singletons
522
+ * shared across handler sets, skills, and the registry, so a fresh value keeps the
523
+ * annotated definition distinct from any other reference to the original.
524
+ *
525
+ * Type-preserving: an annotation does not change the operation's input/output/service types, so the
526
+ * returned definition keeps `Def` (preserving `withHandler` inference and downstream usage).
527
+ */
528
+ export const annotate =
529
+ <T>(annotation: Annotation.Annotation<T>, value: T) =>
530
+ <Def extends Definition.Any>(op: Def): Def => {
531
+ const annotations = { ...(op.meta.annotations ?? {}) };
532
+ Annotation.setDictionary(annotations, annotation, value);
533
+ // The spread reconstructs the same shape with only `meta.annotations` changed; the checker can't
534
+ // track the branded variance symbol through the untyped dictionary mutation, so reassert `Def`.
535
+ return { ...op, meta: { ...op.meta, annotations } } as Def;
536
+ };
537
+
538
+ /**
539
+ * Marks an operation as visible on user-facing operation surfaces (trigger/automation pickers,
540
+ * manual invocation). Absent ⇒ internal: invoked programmatically by plugins and hidden from pickers.
541
+ *
542
+ * Polarity is inverted from the schema-level `HiddenAnnotation` (default visible): operations are
543
+ * hidden by default, since most are internal plugin machinery and only a minority are user-facing.
544
+ */
545
+ export const VisibleAnnotation = Annotation.make({
546
+ id: 'org.dxos.operation.visible',
547
+ schema: Schema$.Boolean,
548
+ });
549
+
550
+ /**
551
+ * Pipeable combinator that marks an operation visible. Apply at the definition site:
552
+ * `Operation.make({ ... }).pipe(Operation.visible)`.
553
+ */
554
+ export const visible = annotate(VisibleAnnotation, true);
555
+
556
+ /**
557
+ * Returns true when an operation is annotated as visible on user-facing surfaces (trigger/automation
558
+ * pickers, manual invocation). Reads from the persisted operation — the form every consumer holds.
559
+ */
560
+ export const isVisible = (op: PersistentOperation): boolean =>
561
+ Option.getOrElse(Annotation.get(op, VisibleAnnotation), () => false);
562
+
490
563
  /**
491
564
  * Operation service interface - provides unified access to operation invocation and scheduling.
492
565
  * This service is automatically provided to operation handlers.
@@ -605,19 +678,22 @@ export const withInvocationOptions = (options: InvokeOptions): Layer.Layer<Servi
605
678
  * Persistent operation schema v0.1.0 — `key` and `version` are stored as data properties.
606
679
  * @deprecated Use {@link PersistentOperation} (v0.2.0) instead; the `key` and `version` now live in the object meta.
607
680
  */
608
- export const PersistentOperation_v0_1_0 = Schema$.Struct({
609
- key: Schema$.optional(Schema$.String),
610
- name: Schema$.NonEmptyString,
611
- version: Schema$.String,
612
- description: Schema$.optional(Schema$.String),
613
- updated: Schema$.optional(Schema$.String),
614
- source: Schema$.optional(Ref.Ref(Obj.Unknown)),
615
- inputSchema: Schema$.optional(JsonSchema.JsonSchema),
616
- outputSchema: Schema$.optional(JsonSchema.JsonSchema),
617
- services: Schema$.optional(Schema$.Array(Schema$.String)),
618
- binding: Schema$.optional(Schema$.String),
619
- }).pipe(Type.makeObject(DXN.make('org.dxos.type.function', '0.1.0')));
620
- export type PersistentOperation_v0_1_0 = Type.InstanceType<typeof PersistentOperation_v0_1_0>;
681
+ export class PersistentOperation_v0_1_0 extends Type.makeObject<PersistentOperation_v0_1_0>(
682
+ DXN.make('org.dxos.type.function', '0.1.0'),
683
+ )(
684
+ Schema$.Struct({
685
+ key: Schema$.optional(Schema$.String),
686
+ name: Schema$.NonEmptyString,
687
+ version: Schema$.String,
688
+ description: Schema$.optional(Schema$.String),
689
+ updated: Schema$.optional(Schema$.String),
690
+ source: Schema$.optional(Ref.Ref(Obj.Unknown)),
691
+ inputSchema: Schema$.optional(JsonSchema.JsonSchema),
692
+ outputSchema: Schema$.optional(JsonSchema.JsonSchema),
693
+ services: Schema$.optional(Schema$.Array(Schema$.String)),
694
+ binding: Schema$.optional(Schema$.String),
695
+ }),
696
+ ) {}
621
697
 
622
698
  /**
623
699
  * Migration from {@link PersistentOperation_v0_1_0} (v0.1.0) to {@link PersistentOperation} (v0.2.0).