@dxos/plugin-automation 0.8.2-staging.7ac8446 → 0.8.3-main.672df60

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 (179) hide show
  1. package/dist/lib/browser/AutomationContainer-HQK7FTN6.mjs +40 -0
  2. package/dist/lib/browser/AutomationContainer-HQK7FTN6.mjs.map +7 -0
  3. package/dist/lib/browser/AutomationPanel-GU37N5LO.mjs +11 -0
  4. package/dist/lib/browser/AutomationPanel-GU37N5LO.mjs.map +7 -0
  5. package/dist/lib/browser/FunctionsContainer-B7RDWVMX.mjs +39 -0
  6. package/dist/lib/browser/FunctionsContainer-B7RDWVMX.mjs.map +7 -0
  7. package/dist/lib/browser/FunctionsPanel-LCCPDIT6.mjs +10 -0
  8. package/dist/lib/browser/FunctionsPanel-LCCPDIT6.mjs.map +7 -0
  9. package/dist/lib/browser/app-graph-builder-BYECL42X.mjs +80 -0
  10. package/dist/lib/browser/app-graph-builder-BYECL42X.mjs.map +7 -0
  11. package/dist/lib/browser/chunk-4E2KPMLB.mjs +39 -0
  12. package/dist/lib/browser/chunk-4E2KPMLB.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-4QTXMPBC.mjs +229 -0
  14. package/dist/lib/browser/chunk-4QTXMPBC.mjs.map +7 -0
  15. package/dist/lib/browser/chunk-D2ESAYQD.mjs +149 -0
  16. package/dist/lib/browser/chunk-D2ESAYQD.mjs.map +7 -0
  17. package/dist/lib/browser/chunk-ELQ2FX5G.mjs +14 -0
  18. package/dist/lib/browser/chunk-ELQ2FX5G.mjs.map +7 -0
  19. package/dist/lib/browser/{chunk-WKKQV4PC.mjs → chunk-IHAKPP5A.mjs} +2 -2
  20. package/dist/lib/browser/{chunk-WKKQV4PC.mjs.map → chunk-IHAKPP5A.mjs.map} +1 -1
  21. package/dist/lib/browser/chunk-ZTRYR6RJ.mjs +94 -0
  22. package/dist/lib/browser/chunk-ZTRYR6RJ.mjs.map +7 -0
  23. package/dist/lib/browser/index.mjs +26 -20
  24. package/dist/lib/browser/index.mjs.map +3 -3
  25. package/dist/lib/browser/intent-resolver-ZRBBYZUX.mjs +77 -0
  26. package/dist/lib/browser/intent-resolver-ZRBBYZUX.mjs.map +7 -0
  27. package/dist/lib/browser/meta.json +1 -1
  28. package/dist/lib/browser/react-surface-VKO6PA2P.mjs +68 -0
  29. package/dist/lib/browser/react-surface-VKO6PA2P.mjs.map +7 -0
  30. package/dist/lib/browser/types.mjs +8 -0
  31. package/dist/lib/browser/types.mjs.map +7 -0
  32. package/dist/lib/node/AutomationContainer-5J7LJK7L.cjs +68 -0
  33. package/dist/lib/node/AutomationContainer-5J7LJK7L.cjs.map +7 -0
  34. package/dist/lib/node/AutomationPanel-NPDBUQ7D.cjs +32 -0
  35. package/dist/lib/node/AutomationPanel-NPDBUQ7D.cjs.map +7 -0
  36. package/dist/lib/node/FunctionsContainer-LNLDZUZ6.cjs +67 -0
  37. package/dist/lib/node/FunctionsContainer-LNLDZUZ6.cjs.map +7 -0
  38. package/dist/lib/node/FunctionsPanel-7AOXIQMA.cjs +31 -0
  39. package/dist/lib/node/FunctionsPanel-7AOXIQMA.cjs.map +7 -0
  40. package/dist/lib/node/app-graph-builder-DUKR2BRB.cjs +96 -0
  41. package/dist/lib/node/app-graph-builder-DUKR2BRB.cjs.map +7 -0
  42. package/dist/lib/node/{chunk-7GXNXMSM.cjs → chunk-3EF7MLFX.cjs} +5 -5
  43. package/dist/lib/node/{chunk-7GXNXMSM.cjs.map → chunk-3EF7MLFX.cjs.map} +1 -1
  44. package/dist/lib/node/chunk-4O627QZU.cjs +175 -0
  45. package/dist/lib/node/chunk-4O627QZU.cjs.map +7 -0
  46. package/dist/lib/node/{chunk-AGJ6XTDN.cjs → chunk-DOLMQUQ5.cjs} +16 -7
  47. package/dist/lib/node/chunk-DOLMQUQ5.cjs.map +7 -0
  48. package/dist/lib/node/chunk-GIIVTK4O.cjs +58 -0
  49. package/dist/lib/node/chunk-GIIVTK4O.cjs.map +7 -0
  50. package/dist/lib/node/chunk-O42NQYQT.cjs +252 -0
  51. package/dist/lib/node/chunk-O42NQYQT.cjs.map +7 -0
  52. package/dist/lib/node/chunk-WXQAVUBJ.cjs +122 -0
  53. package/dist/lib/node/chunk-WXQAVUBJ.cjs.map +7 -0
  54. package/dist/lib/node/index.cjs +37 -31
  55. package/dist/lib/node/index.cjs.map +3 -3
  56. package/dist/lib/node/intent-resolver-JC4Q4TYU.cjs +93 -0
  57. package/dist/lib/node/intent-resolver-JC4Q4TYU.cjs.map +7 -0
  58. package/dist/lib/node/meta.json +1 -1
  59. package/dist/lib/node/{react-surface-52M54VWV.cjs → react-surface-HB3MYTCK.cjs} +41 -17
  60. package/dist/lib/node/react-surface-HB3MYTCK.cjs.map +7 -0
  61. package/dist/lib/node/types.cjs +30 -0
  62. package/dist/lib/node/types.cjs.map +7 -0
  63. package/dist/lib/node-esm/AutomationContainer-OJGH76X2.mjs +41 -0
  64. package/dist/lib/node-esm/AutomationContainer-OJGH76X2.mjs.map +7 -0
  65. package/dist/lib/node-esm/AutomationPanel-HIWEJUWL.mjs +12 -0
  66. package/dist/lib/node-esm/AutomationPanel-HIWEJUWL.mjs.map +7 -0
  67. package/dist/lib/node-esm/FunctionsContainer-PPR6XNNR.mjs +40 -0
  68. package/dist/lib/node-esm/FunctionsContainer-PPR6XNNR.mjs.map +7 -0
  69. package/dist/lib/node-esm/FunctionsPanel-SBXKWTHR.mjs +11 -0
  70. package/dist/lib/node-esm/FunctionsPanel-SBXKWTHR.mjs.map +7 -0
  71. package/dist/lib/node-esm/app-graph-builder-UAXHKKGW.mjs +81 -0
  72. package/dist/lib/node-esm/app-graph-builder-UAXHKKGW.mjs.map +7 -0
  73. package/dist/lib/node-esm/chunk-5IAHBEHR.mjs +95 -0
  74. package/dist/lib/node-esm/chunk-5IAHBEHR.mjs.map +7 -0
  75. package/dist/lib/node-esm/chunk-6JOJ2NN4.mjs +40 -0
  76. package/dist/lib/node-esm/chunk-6JOJ2NN4.mjs.map +7 -0
  77. package/dist/lib/node-esm/chunk-D5HK4XLC.mjs +230 -0
  78. package/dist/lib/node-esm/chunk-D5HK4XLC.mjs.map +7 -0
  79. package/dist/lib/node-esm/{chunk-DZ44LGYT.mjs → chunk-HCCLRNMJ.mjs} +2 -2
  80. package/dist/lib/node-esm/{chunk-DZ44LGYT.mjs.map → chunk-HCCLRNMJ.mjs.map} +1 -1
  81. package/dist/lib/node-esm/chunk-P2FKMPRE.mjs +150 -0
  82. package/dist/lib/node-esm/chunk-P2FKMPRE.mjs.map +7 -0
  83. package/dist/lib/node-esm/chunk-Z7VAQDEE.mjs +16 -0
  84. package/dist/lib/node-esm/chunk-Z7VAQDEE.mjs.map +7 -0
  85. package/dist/lib/node-esm/index.mjs +26 -20
  86. package/dist/lib/node-esm/index.mjs.map +3 -3
  87. package/dist/lib/node-esm/intent-resolver-KRCXJEDR.mjs +78 -0
  88. package/dist/lib/node-esm/intent-resolver-KRCXJEDR.mjs.map +7 -0
  89. package/dist/lib/node-esm/meta.json +1 -1
  90. package/dist/lib/node-esm/react-surface-TRKCIZAB.mjs +69 -0
  91. package/dist/lib/node-esm/react-surface-TRKCIZAB.mjs.map +7 -0
  92. package/dist/lib/node-esm/types.mjs +9 -0
  93. package/dist/lib/node-esm/types.mjs.map +7 -0
  94. package/dist/types/src/AutomationPlugin.d.ts.map +1 -1
  95. package/dist/types/src/capabilities/app-graph-builder.d.ts +2 -179
  96. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  97. package/dist/types/src/capabilities/index.d.ts +2 -177
  98. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  99. package/dist/types/src/capabilities/intent-resolver.d.ts +4 -0
  100. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -0
  101. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  102. package/dist/types/src/components/AutomationContainer.d.ts +7 -0
  103. package/dist/types/src/components/AutomationContainer.d.ts.map +1 -0
  104. package/dist/types/src/components/AutomationPanel/AutomationPanel.d.ts +6 -3
  105. package/dist/types/src/components/AutomationPanel/AutomationPanel.d.ts.map +1 -1
  106. package/dist/types/src/components/AutomationPanel/AutomationPanel.stories.d.ts.map +1 -1
  107. package/dist/types/src/components/AutomationPanel/index.d.ts +1 -0
  108. package/dist/types/src/components/AutomationPanel/index.d.ts.map +1 -1
  109. package/dist/types/src/components/FunctionsContainer.d.ts +7 -0
  110. package/dist/types/src/components/FunctionsContainer.d.ts.map +1 -0
  111. package/dist/types/src/components/FunctionsPanel/FunctionsPanel.d.ts +7 -0
  112. package/dist/types/src/components/FunctionsPanel/FunctionsPanel.d.ts.map +1 -0
  113. package/dist/types/src/components/FunctionsPanel/index.d.ts +4 -0
  114. package/dist/types/src/components/FunctionsPanel/index.d.ts.map +1 -0
  115. package/dist/types/src/components/TriggerEditor/FunctionInputEditor.d.ts +12 -0
  116. package/dist/types/src/components/TriggerEditor/FunctionInputEditor.d.ts.map +1 -0
  117. package/dist/types/src/components/TriggerEditor/SpecSelector.d.ts +5 -0
  118. package/dist/types/src/components/TriggerEditor/SpecSelector.d.ts.map +1 -0
  119. package/dist/types/src/components/TriggerEditor/TriggerEditor.d.ts +1 -1
  120. package/dist/types/src/components/TriggerEditor/TriggerEditor.d.ts.map +1 -1
  121. package/dist/types/src/components/TriggerEditor/TriggerEditor.stories.d.ts.map +1 -1
  122. package/dist/types/src/components/index.d.ts +8 -1
  123. package/dist/types/src/components/index.d.ts.map +1 -1
  124. package/dist/types/src/testing/test-functions.d.ts.map +1 -1
  125. package/dist/types/src/translations.d.ts +8 -0
  126. package/dist/types/src/translations.d.ts.map +1 -1
  127. package/dist/types/src/types.d.ts +25 -0
  128. package/dist/types/src/types.d.ts.map +1 -0
  129. package/dist/types/tsconfig.tsbuildinfo +1 -1
  130. package/package.json +38 -25
  131. package/src/AutomationPlugin.tsx +5 -10
  132. package/src/capabilities/app-graph-builder.ts +72 -17
  133. package/src/capabilities/index.ts +1 -0
  134. package/src/capabilities/intent-resolver.ts +73 -0
  135. package/src/capabilities/react-surface.tsx +32 -8
  136. package/src/components/AutomationContainer.tsx +31 -0
  137. package/src/components/AutomationPanel/AutomationPanel.stories.tsx +6 -5
  138. package/src/components/AutomationPanel/AutomationPanel.tsx +77 -73
  139. package/src/components/AutomationPanel/index.ts +2 -0
  140. package/src/components/FunctionsContainer.tsx +31 -0
  141. package/src/components/FunctionsPanel/FunctionsPanel.tsx +95 -0
  142. package/src/components/FunctionsPanel/index.ts +8 -0
  143. package/src/components/TriggerEditor/FunctionInputEditor.tsx +77 -0
  144. package/src/components/TriggerEditor/SpecSelector.tsx +59 -0
  145. package/src/components/TriggerEditor/TriggerEditor.stories.tsx +19 -10
  146. package/src/components/TriggerEditor/TriggerEditor.tsx +64 -102
  147. package/src/components/index.ts +3 -0
  148. package/src/meta.ts +1 -1
  149. package/src/testing/test-functions.ts +23 -9
  150. package/src/translations.ts +10 -1
  151. package/src/types.ts +33 -0
  152. package/dist/lib/browser/AutomationPanel-YAHFXQX6.mjs +0 -139
  153. package/dist/lib/browser/AutomationPanel-YAHFXQX6.mjs.map +0 -7
  154. package/dist/lib/browser/app-graph-builder-K3BIQFWW.mjs +0 -40
  155. package/dist/lib/browser/app-graph-builder-K3BIQFWW.mjs.map +0 -7
  156. package/dist/lib/browser/chunk-FALBBJNO.mjs +0 -138
  157. package/dist/lib/browser/chunk-FALBBJNO.mjs.map +0 -7
  158. package/dist/lib/browser/chunk-MT3FZH4V.mjs +0 -8
  159. package/dist/lib/browser/chunk-MT3FZH4V.mjs.map +0 -7
  160. package/dist/lib/browser/react-surface-4QQSJR4A.mjs +0 -42
  161. package/dist/lib/browser/react-surface-4QQSJR4A.mjs.map +0 -7
  162. package/dist/lib/node/AutomationPanel-ZKAMIU6O.cjs +0 -161
  163. package/dist/lib/node/AutomationPanel-ZKAMIU6O.cjs.map +0 -7
  164. package/dist/lib/node/app-graph-builder-HO4FPGZ5.cjs +0 -56
  165. package/dist/lib/node/app-graph-builder-HO4FPGZ5.cjs.map +0 -7
  166. package/dist/lib/node/chunk-AGJ6XTDN.cjs.map +0 -7
  167. package/dist/lib/node/chunk-FTEDH5Q6.cjs +0 -167
  168. package/dist/lib/node/chunk-FTEDH5Q6.cjs.map +0 -7
  169. package/dist/lib/node/react-surface-52M54VWV.cjs.map +0 -7
  170. package/dist/lib/node-esm/AutomationPanel-XF7YPSKM.mjs +0 -140
  171. package/dist/lib/node-esm/AutomationPanel-XF7YPSKM.mjs.map +0 -7
  172. package/dist/lib/node-esm/app-graph-builder-XCJR33VS.mjs +0 -41
  173. package/dist/lib/node-esm/app-graph-builder-XCJR33VS.mjs.map +0 -7
  174. package/dist/lib/node-esm/chunk-M4QXMIIB.mjs +0 -139
  175. package/dist/lib/node-esm/chunk-M4QXMIIB.mjs.map +0 -7
  176. package/dist/lib/node-esm/chunk-OA75PSGH.mjs +0 -10
  177. package/dist/lib/node-esm/chunk-OA75PSGH.mjs.map +0 -7
  178. package/dist/lib/node-esm/react-surface-MGKM3OO3.mjs +0 -43
  179. package/dist/lib/node-esm/react-surface-MGKM3OO3.mjs.map +0 -7
@@ -2,30 +2,85 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Capabilities, contributes, type PluginsContext } from '@dxos/app-framework';
5
+ import { Rx } from '@effect-rx/rx-react';
6
+ import { Option, pipe } from 'effect';
7
+
8
+ import { Capabilities, contributes, type PluginContext } from '@dxos/app-framework';
6
9
  import { isInstanceOf } from '@dxos/echo-schema';
7
10
  import { ScriptType } from '@dxos/functions';
8
11
  import { PLANK_COMPANION_TYPE, ATTENDABLE_PATH_SEPARATOR } from '@dxos/plugin-deck/types';
9
- import { createExtension, type Node } from '@dxos/plugin-graph';
10
- import { SCRIPT_PLUGIN } from '@dxos/plugin-script/types';
12
+ import { createExtension } from '@dxos/plugin-graph';
13
+ import { SPACE_PLUGIN } from '@dxos/plugin-space';
11
14
 
12
15
  import { meta } from '../meta';
13
16
 
14
- export default (context: PluginsContext) =>
17
+ export default (context: PluginContext) =>
15
18
  contributes(Capabilities.AppGraphBuilder, [
16
19
  createExtension({
17
- id: `${SCRIPT_PLUGIN}/script-companion`,
18
- filter: (node): node is Node<ScriptType> => isInstanceOf(ScriptType, node.data),
19
- connector: ({ node }) => [
20
- {
21
- id: [node.id, 'automation'].join(ATTENDABLE_PATH_SEPARATOR),
22
- type: PLANK_COMPANION_TYPE,
23
- data: 'automation',
24
- properties: {
25
- label: ['script automation label', { ns: meta.id }],
26
- icon: 'ph--lightning--regular',
27
- },
28
- },
29
- ],
20
+ id: `${meta.id}/space-settings-automation`,
21
+ connector: (node) =>
22
+ Rx.make((get) =>
23
+ pipe(
24
+ get(node),
25
+ Option.flatMap((node) => (node.type === `${SPACE_PLUGIN}/settings` ? Option.some(node) : Option.none())),
26
+ Option.map((node) => [
27
+ {
28
+ id: `automation-${node.id}`,
29
+ type: `${meta.id}/space-settings-automation`,
30
+ data: `${meta.id}/space-settings-automation`,
31
+ properties: {
32
+ label: ['automation panel label', { ns: meta.id }],
33
+ icon: 'ph--lightning--regular',
34
+ },
35
+ },
36
+ ]),
37
+ Option.getOrElse(() => []),
38
+ ),
39
+ ),
40
+ }),
41
+ createExtension({
42
+ id: `${meta.id}/space-settings-functions`,
43
+ connector: (node) =>
44
+ Rx.make((get) =>
45
+ pipe(
46
+ get(node),
47
+ Option.flatMap((node) => (node.type === `${SPACE_PLUGIN}/settings` ? Option.some(node) : Option.none())),
48
+ Option.map((node) => [
49
+ {
50
+ id: `functions-${node.id}`,
51
+ type: `${meta.id}/space-settings-functions`,
52
+ data: `${meta.id}/space-settings-functions`,
53
+ properties: {
54
+ label: ['functions panel label', { ns: meta.id }],
55
+ icon: 'ph--function--regular',
56
+ },
57
+ },
58
+ ]),
59
+ Option.getOrElse(() => []),
60
+ ),
61
+ ),
62
+ }),
63
+ createExtension({
64
+ id: `${meta.id}/script-companion`,
65
+ connector: (node) =>
66
+ Rx.make((get) =>
67
+ pipe(
68
+ get(node),
69
+ Option.flatMap((node) => (isInstanceOf(ScriptType, node.data) ? Option.some(node) : Option.none())),
70
+ Option.map((node) => [
71
+ {
72
+ id: [node.id, 'automation'].join(ATTENDABLE_PATH_SEPARATOR),
73
+ type: PLANK_COMPANION_TYPE,
74
+ data: 'automation',
75
+ properties: {
76
+ label: ['script automation label', { ns: meta.id }],
77
+ icon: 'ph--lightning--regular',
78
+ disposition: 'hidden',
79
+ },
80
+ },
81
+ ]),
82
+ Option.getOrElse(() => []),
83
+ ),
84
+ ),
30
85
  }),
31
86
  ]);
@@ -5,4 +5,5 @@
5
5
  import { lazy } from '@dxos/app-framework';
6
6
 
7
7
  export const AppGraphBuilder = lazy(() => import('./app-graph-builder'));
8
+ export const IntentResolver = lazy(() => import('./intent-resolver'));
8
9
  export const ReactSurface = lazy(() => import('./react-surface'));
@@ -0,0 +1,73 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import {
6
+ contributes,
7
+ Capabilities,
8
+ createResolver,
9
+ type PluginContext,
10
+ createIntent,
11
+ LayoutAction,
12
+ } from '@dxos/app-framework';
13
+ import { Ref } from '@dxos/echo';
14
+ import { FunctionTrigger, FunctionType, ScriptType, TriggerKind } from '@dxos/functions';
15
+ import { type DXN } from '@dxos/keys';
16
+ import { live } from '@dxos/live-object';
17
+ import { ATTENDABLE_PATH_SEPARATOR } from '@dxos/plugin-deck/types';
18
+ import { SpaceAction } from '@dxos/plugin-space/types';
19
+ import { Filter } from '@dxos/react-client/echo';
20
+
21
+ import { AutomationAction } from '../types';
22
+
23
+ export default (context: PluginContext) =>
24
+ contributes(Capabilities.IntentResolver, [
25
+ createResolver({
26
+ intent: AutomationAction.CreateTriggerFromTemplate,
27
+ resolve: async ({ space, template, enabled = false, scriptName, input }) => {
28
+ const trigger = live(FunctionTrigger, { enabled, input });
29
+
30
+ // TODO(wittjosiah): Factor out function lookup by script name?
31
+ if (scriptName) {
32
+ const {
33
+ objects: [script],
34
+ } = await space.db.query(Filter.type(ScriptType, { name: scriptName })).run();
35
+ if (script) {
36
+ const {
37
+ objects: [fn],
38
+ } = await space.db.query(Filter.type(FunctionType, { source: Ref.make(script) })).run();
39
+ if (fn) {
40
+ trigger.function = Ref.make(fn);
41
+ }
42
+ }
43
+ }
44
+
45
+ switch (template.type) {
46
+ case 'timer': {
47
+ trigger.spec = { kind: TriggerKind.Timer, cron: template.cron };
48
+ break;
49
+ }
50
+ case 'queue': {
51
+ trigger.spec = { kind: TriggerKind.Queue, queue: (template.queueDXN as DXN).toString() };
52
+ break;
53
+ }
54
+ default: {
55
+ break;
56
+ }
57
+ }
58
+
59
+ return {
60
+ intents: [
61
+ createIntent(SpaceAction.AddObject, { object: trigger, target: space }),
62
+ createIntent(LayoutAction.Open, {
63
+ part: 'main',
64
+ subject: [`automation-settings${ATTENDABLE_PATH_SEPARATOR}${space.id}`],
65
+ options: {
66
+ workspace: space.id,
67
+ },
68
+ }),
69
+ ],
70
+ };
71
+ },
72
+ }),
73
+ ]);
@@ -4,22 +4,46 @@
4
4
 
5
5
  import React from 'react';
6
6
 
7
- import { Capabilities, contributes, createSurface } from '@dxos/app-framework';
7
+ import { Capabilities, contributes, createSurface, useLayout } from '@dxos/app-framework';
8
8
  import { isInstanceOf } from '@dxos/echo-schema';
9
- import { ScriptType } from '@dxos/functions/types';
10
- import { getSpace, isSpace, type Space } from '@dxos/react-client/echo';
9
+ import { ScriptType } from '@dxos/functions';
10
+ import { getSpace, parseId, useSpace } from '@dxos/react-client/echo';
11
11
  import { StackItem } from '@dxos/react-ui-stack';
12
12
 
13
- import { AutomationPanel } from '../components';
13
+ import { AutomationContainer, AutomationPanel, FunctionsContainer } from '../components';
14
14
  import { meta } from '../meta';
15
15
 
16
16
  export default () =>
17
17
  contributes(Capabilities.ReactSurface, [
18
18
  createSurface({
19
- id: `${meta.id}/automation`,
20
- role: 'space-settings--automation',
21
- filter: (data): data is { subject: Space } => isSpace(data.subject),
22
- component: ({ data }) => <AutomationPanel space={data.subject} />,
19
+ id: `${meta.id}/space-settings-automation`,
20
+ role: 'article',
21
+ filter: (data): data is { subject: string } => data.subject === `${meta.id}/space-settings-automation`,
22
+ component: () => {
23
+ const layout = useLayout();
24
+ const { spaceId } = parseId(layout.workspace);
25
+ const space = useSpace(spaceId);
26
+ if (!space || !spaceId) {
27
+ return null;
28
+ }
29
+
30
+ return <AutomationContainer space={space} />;
31
+ },
32
+ }),
33
+ createSurface({
34
+ id: `${meta.id}/space-settings-functions`,
35
+ role: 'article',
36
+ filter: (data): data is { subject: string } => data.subject === `${meta.id}/space-settings-functions`,
37
+ component: () => {
38
+ const layout = useLayout();
39
+ const { spaceId } = parseId(layout.workspace);
40
+ const space = useSpace(spaceId);
41
+ if (!space || !spaceId) {
42
+ return null;
43
+ }
44
+
45
+ return <FunctionsContainer space={space} />;
46
+ },
23
47
  }),
24
48
  createSurface({
25
49
  id: `${meta.id}/companion/automation`,
@@ -0,0 +1,31 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { type Space } from '@dxos/react-client/echo';
8
+ import { useTranslation } from '@dxos/react-ui';
9
+ import { ControlSection, ControlPage } from '@dxos/react-ui-form';
10
+ import { StackItem } from '@dxos/react-ui-stack';
11
+
12
+ import { AutomationPanel } from './AutomationPanel';
13
+ import { AUTOMATION_PLUGIN } from '../meta';
14
+
15
+ export const AutomationContainer = ({ space }: { space: Space }) => {
16
+ const { t } = useTranslation(AUTOMATION_PLUGIN);
17
+ return (
18
+ <StackItem.Content classNames='block overflow-y-auto'>
19
+ <ControlPage>
20
+ <ControlSection
21
+ title={t('automation verbose label', { ns: AUTOMATION_PLUGIN })}
22
+ description={t('automation description', { ns: AUTOMATION_PLUGIN })}
23
+ >
24
+ <AutomationPanel space={space} />
25
+ </ControlSection>
26
+ </ControlPage>
27
+ </StackItem.Content>
28
+ );
29
+ };
30
+
31
+ export default AutomationContainer;
@@ -7,8 +7,8 @@ import '@dxos-theme';
7
7
  import { type Meta } from '@storybook/react';
8
8
  import React from 'react';
9
9
 
10
- import { FunctionType, FunctionTrigger } from '@dxos/functions/types';
11
- import { create, useSpaces } from '@dxos/react-client/echo';
10
+ import { FunctionType, FunctionTrigger } from '@dxos/functions';
11
+ import { live, useSpaces } from '@dxos/react-client/echo';
12
12
  import { withClientProvider } from '@dxos/react-client/testing';
13
13
  import { withLayout, withTheme } from '@dxos/storybook-utils';
14
14
 
@@ -21,7 +21,7 @@ const DefaultStory = () => {
21
21
  const space = spaces[1];
22
22
 
23
23
  return (
24
- <div role='none' className='flex w-[350px] border border-separator overflow-hidden'>
24
+ <div className='w-96'>
25
25
  <AutomationPanel space={space} />
26
26
  </div>
27
27
  );
@@ -38,14 +38,15 @@ const meta: Meta = {
38
38
  types: [FunctionType, FunctionTrigger],
39
39
  onSpaceCreated: ({ space }) => {
40
40
  for (const fn of functions) {
41
- space.db.add(create(FunctionType, fn));
41
+ space.db.add(live(FunctionType, fn));
42
42
  }
43
43
  },
44
44
  }),
45
- withLayout({ fullscreen: true, tooltips: true, classNames: 'flex justify-center m-2' }),
45
+ withLayout(),
46
46
  withTheme,
47
47
  ],
48
48
  parameters: {
49
+ layout: 'centered',
49
50
  translations,
50
51
  },
51
52
  };
@@ -2,9 +2,9 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { Schema } from 'effect';
5
6
  import React, { useState } from 'react';
6
7
 
7
- import { S } from '@dxos/echo-schema';
8
8
  import {
9
9
  FunctionType,
10
10
  FunctionTrigger,
@@ -12,13 +12,12 @@ import {
12
12
  TriggerKind,
13
13
  type FunctionTriggerType,
14
14
  ScriptType,
15
- } from '@dxos/functions/types';
15
+ } from '@dxos/functions';
16
16
  import { type Client, useClient } from '@dxos/react-client';
17
- import { create, Filter, useQuery, type Space, type ReactiveObject, getSpace } from '@dxos/react-client/echo';
17
+ import { live, Filter, useQuery, type Space, type Live, getSpace } from '@dxos/react-client/echo';
18
18
  import { Clipboard, IconButton, Input, Separator, useTranslation } from '@dxos/react-ui';
19
19
  import { ControlItem, controlItemClasses } from '@dxos/react-ui-form';
20
20
  import { List } from '@dxos/react-ui-list';
21
- import { StackItem } from '@dxos/react-ui-stack';
22
21
  import { ghostHover, mx } from '@dxos/react-ui-theme';
23
22
 
24
23
  import { AUTOMATION_PLUGIN } from '../../meta';
@@ -28,18 +27,20 @@ const grid = 'grid grid-cols-[40px_1fr_32px] min-bs-[2.5rem]';
28
27
 
29
28
  export type AutomationPanelProps = {
30
29
  space: Space;
31
- object?: ReactiveObject<any>;
30
+ object?: Live<any>;
31
+ initialTrigger?: FunctionTriggerType;
32
+ onDone?: () => void;
32
33
  };
33
34
 
34
35
  // TODO(burdon): Factor out common layout with ViewEditor.
35
- export const AutomationPanel = ({ space, object }: AutomationPanelProps) => {
36
+ export const AutomationPanel = ({ space, object, initialTrigger, onDone }: AutomationPanelProps) => {
36
37
  const { t } = useTranslation(AUTOMATION_PLUGIN);
37
38
  const client = useClient();
38
- const triggers = useQuery(space, Filter.schema(FunctionTrigger));
39
- const functions = useQuery(space, Filter.schema(FunctionType));
40
- const scripts = useQuery(space, Filter.schema(ScriptType));
39
+ const triggers = useQuery(space, Filter.type(FunctionTrigger));
40
+ const functions = useQuery(space, Filter.type(FunctionType));
41
+ const scripts = useQuery(space, Filter.type(ScriptType));
41
42
 
42
- const [trigger, setTrigger] = useState<FunctionTriggerType>();
43
+ const [trigger, setTrigger] = useState<FunctionTriggerType | undefined>(initialTrigger);
43
44
  const [selected, setSelected] = useState<FunctionTrigger>();
44
45
 
45
46
  const handleSelect = (trigger: FunctionTrigger) => {
@@ -49,7 +50,7 @@ export const AutomationPanel = ({ space, object }: AutomationPanelProps) => {
49
50
  };
50
51
 
51
52
  const handleAdd = () => {
52
- setTrigger(create(FunctionTriggerSchema, {}));
53
+ setTrigger(live(FunctionTriggerSchema, {}));
53
54
  setSelected(undefined);
54
55
  };
55
56
 
@@ -63,85 +64,87 @@ export const AutomationPanel = ({ space, object }: AutomationPanelProps) => {
63
64
  if (selected) {
64
65
  Object.assign(selected, trigger);
65
66
  } else {
66
- space.db.add(create(FunctionTrigger, trigger));
67
+ space.db.add(live(FunctionTrigger, trigger));
67
68
  }
68
69
 
69
70
  setTrigger(undefined);
70
71
  setSelected(undefined);
72
+ onDone?.();
71
73
  };
72
74
 
73
75
  const handleCancel: TriggerEditorProps['onCancel'] = () => {
74
76
  setTrigger(undefined);
77
+ onDone?.();
75
78
  };
76
79
 
80
+ if (trigger) {
81
+ return (
82
+ <ControlItem title={t('trigger editor title')}>
83
+ <TriggerEditor space={space} trigger={trigger} onSave={handleSave} onCancel={handleCancel} />
84
+ </ControlItem>
85
+ );
86
+ }
87
+
77
88
  return (
78
- <Clipboard.Provider>
79
- <StackItem.Content classNames='block overflow-y-auto'>
80
- <div className='flex flex-col w-full'>
81
- {trigger ? (
82
- <ControlItem title={t('trigger editor title')}>
83
- <TriggerEditor space={space} trigger={trigger} onSave={handleSave} onCancel={handleCancel} />
84
- </ControlItem>
85
- ) : (
86
- <div role='none' className={controlItemClasses}>
87
- <List.Root<FunctionTrigger> items={triggers} isItem={S.is(FunctionTrigger)} getId={(field) => field.id}>
88
- {({ items: triggers }) => (
89
- <div role='list' className='flex flex-col w-full'>
90
- {triggers?.map((trigger) => {
91
- const copyAction = getCopyAction(client, trigger);
92
- return (
93
- <List.Item<FunctionTrigger>
94
- key={trigger.id}
95
- item={trigger}
96
- classNames={mx(grid, ghostHover, 'items-center', 'px-2')}
89
+ <div className='flex flex-col w-full'>
90
+ <div role='none' className={controlItemClasses}>
91
+ {triggers.length > 0 && (
92
+ <List.Root<FunctionTrigger> items={triggers} isItem={Schema.is(FunctionTrigger)} getId={(field) => field.id}>
93
+ {({ items: triggers }) => (
94
+ <div role='list' className='flex flex-col w-full'>
95
+ {triggers?.map((trigger) => {
96
+ const copyAction = getCopyAction(client, trigger);
97
+ return (
98
+ <List.Item<FunctionTrigger>
99
+ key={trigger.id}
100
+ item={trigger}
101
+ classNames={mx(grid, ghostHover, 'items-center', 'px-2')}
102
+ >
103
+ <Input.Root>
104
+ <Input.Switch
105
+ checked={trigger.enabled}
106
+ onCheckedChange={(checked) => (trigger.enabled = checked)}
107
+ />
108
+ </Input.Root>
109
+
110
+ <div className={'flex'}>
111
+ <List.ItemTitle
112
+ classNames='px-1 cursor-pointer w-0 shrink truncate'
113
+ onClick={() => handleSelect(trigger)}
97
114
  >
98
- <Input.Root>
99
- <Input.Switch
100
- checked={trigger.enabled}
101
- onCheckedChange={(checked) => (trigger.enabled = checked)}
102
- />
103
- </Input.Root>
104
-
105
- <div className={'flex'}>
106
- <List.ItemTitle
107
- classNames='px-1 cursor-pointer w-0 shrink truncate'
108
- onClick={() => handleSelect(trigger)}
109
- >
110
- {getFunctionName(scripts, functions, trigger) ?? '∅'}
111
- </List.ItemTitle>
112
-
113
- {/* TODO: a better way to expose copy action */}
114
- {copyAction && (
115
- <Clipboard.IconButton
116
- label={t(copyAction.translationKey)}
117
- value={copyAction.contentProvider()}
118
- />
119
- )}
120
- </div>
121
-
122
- <List.ItemDeleteButton onClick={() => handleDelete(trigger)} />
123
- </List.Item>
124
- );
125
- })}
126
- </div>
127
- )}
128
- </List.Root>
129
- {triggers.length > 0 && <Separator classNames='mlb-4' />}
130
- <IconButton icon='ph--plus--regular' label={t('new trigger label')} onClick={handleAdd} />
131
- </div>
132
- )}
133
- </div>
134
- </StackItem.Content>
135
- </Clipboard.Provider>
115
+ {getFunctionName(scripts, functions, trigger) ?? '∅'}
116
+ </List.ItemTitle>
117
+
118
+ {/* TODO: a better way to expose copy action */}
119
+ {copyAction && (
120
+ <Clipboard.IconButton
121
+ label={t(copyAction.translationKey)}
122
+ value={copyAction.contentProvider()}
123
+ />
124
+ )}
125
+ </div>
126
+
127
+ <List.ItemDeleteButton onClick={() => handleDelete(trigger)} />
128
+ </List.Item>
129
+ );
130
+ })}
131
+ </div>
132
+ )}
133
+ </List.Root>
134
+ )}
135
+ {triggers.length > 0 && <Separator classNames='mlb-4' />}
136
+ <IconButton icon='ph--plus--regular' label={t('new trigger label')} onClick={handleAdd} />
137
+ </div>
138
+ </div>
136
139
  );
137
140
  };
138
141
 
139
142
  const getCopyAction = (client: Client, trigger: FunctionTrigger | undefined) => {
140
- if (trigger?.spec?.type === TriggerKind.Email) {
143
+ if (trigger?.spec?.kind === TriggerKind.Email) {
141
144
  return { translationKey: 'trigger copy email', contentProvider: () => `${getSpace(trigger)!.id}@dxos.network` };
142
145
  }
143
146
 
144
- if (trigger?.spec?.type === TriggerKind.Webhook) {
147
+ if (trigger?.spec?.kind === TriggerKind.Webhook) {
145
148
  return { translationKey: 'trigger copy url', contentProvider: () => getWebhookUrl(client, trigger) };
146
149
  }
147
150
 
@@ -159,10 +162,11 @@ const getWebhookUrl = (client: Client, trigger: FunctionTrigger) => {
159
162
  const getFunctionName = (scripts: ScriptType[], functions: FunctionType[], trigger: FunctionTriggerType) => {
160
163
  // TODO(wittjosiah): Truncation should be done in the UI.
161
164
  // Warning that the List component is currently a can of worms.
162
- const shortId = trigger.function && `${trigger.function?.slice(0, 16)}…`;
163
- const functionObject = functions.find((fn) => `dxn:worker:${fn.name}` === trigger.function);
165
+ const shortId = trigger.function && `${trigger.function.dxn.toString().slice(0, 16)}…`;
166
+ const functionObject = functions.find((fn) => fn === trigger.function?.target);
164
167
  if (!functionObject) {
165
168
  return shortId;
166
169
  }
170
+
167
171
  return scripts.find((s) => functionObject.source?.target?.id === s.id)?.name ?? shortId;
168
172
  };
@@ -4,4 +4,6 @@
4
4
 
5
5
  import { AutomationPanel } from './AutomationPanel';
6
6
 
7
+ export * from './AutomationPanel';
8
+
7
9
  export default AutomationPanel;
@@ -0,0 +1,31 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { type Space } from '@dxos/react-client/echo';
8
+ import { useTranslation } from '@dxos/react-ui';
9
+ import { ControlPage, ControlSection } from '@dxos/react-ui-form';
10
+ import { StackItem } from '@dxos/react-ui-stack';
11
+
12
+ import { FunctionsPanel } from './FunctionsPanel';
13
+ import { AUTOMATION_PLUGIN } from '../meta';
14
+
15
+ export const FunctionsContainer = ({ space }: { space: Space }) => {
16
+ const { t } = useTranslation(AUTOMATION_PLUGIN);
17
+ return (
18
+ <StackItem.Content classNames='block overflow-y-auto'>
19
+ <ControlPage>
20
+ <ControlSection
21
+ title={t('functions verbose label', { ns: AUTOMATION_PLUGIN })}
22
+ description={t('functions description', { ns: AUTOMATION_PLUGIN })}
23
+ >
24
+ <FunctionsPanel space={space} />
25
+ </ControlSection>
26
+ </ControlPage>
27
+ </StackItem.Content>
28
+ );
29
+ };
30
+
31
+ export default FunctionsContainer;
@@ -0,0 +1,95 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Schema } from 'effect';
6
+ import React, { useCallback, useMemo } from 'react';
7
+
8
+ import { createIntent, LayoutAction, useIntentDispatcher } from '@dxos/app-framework';
9
+ import { FunctionType, ScriptType } from '@dxos/functions';
10
+ import { Filter, fullyQualifiedId, useQuery, type Space } from '@dxos/react-client/echo';
11
+ import { Button, useTranslation } from '@dxos/react-ui';
12
+ import { controlItemClasses } from '@dxos/react-ui-form';
13
+ import { List } from '@dxos/react-ui-list';
14
+ import { ghostHover, mx } from '@dxos/react-ui-theme';
15
+
16
+ import { AUTOMATION_PLUGIN } from '../../meta';
17
+
18
+ const grid = 'grid grid-cols-[1fr_auto] min-bs-[2.5rem]';
19
+
20
+ export type FunctionsPanelProps = {
21
+ space: Space;
22
+ };
23
+
24
+ export const FunctionsPanel = ({ space }: FunctionsPanelProps) => {
25
+ const { t } = useTranslation(AUTOMATION_PLUGIN);
26
+ const functions = useQuery(space, Filter.type(FunctionType));
27
+ const scripts = useQuery(space, Filter.type(ScriptType));
28
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
29
+
30
+ const functionToScriptMap = useMemo(
31
+ () =>
32
+ functions.reduce(
33
+ (map, func) => {
34
+ const scriptId = func.source?.target?.id;
35
+ if (scriptId) {
36
+ const script = scripts.find((s) => s.id === scriptId);
37
+ if (script) {
38
+ map[func.id] = script;
39
+ }
40
+ }
41
+ return map;
42
+ },
43
+ {} as Record<string, ScriptType>,
44
+ ),
45
+ [functions, scripts],
46
+ );
47
+
48
+ const getScriptName = useCallback(
49
+ (func: FunctionType) => {
50
+ const script = functionToScriptMap[func.id];
51
+ return script?.name;
52
+ },
53
+ [functionToScriptMap],
54
+ );
55
+
56
+ const handleGoToScript = useCallback(
57
+ (func: FunctionType) => {
58
+ const script = functionToScriptMap[func.id];
59
+ if (script) {
60
+ void dispatch(createIntent(LayoutAction.Open, { part: 'main', subject: [fullyQualifiedId(script)] }));
61
+ }
62
+ },
63
+ [functionToScriptMap, dispatch],
64
+ );
65
+
66
+ return (
67
+ <div role='none' className={mx(controlItemClasses)}>
68
+ <List.Root<FunctionType> items={functions} isItem={Schema.is(FunctionType)} getId={(func) => func.id}>
69
+ {({ items }) => (
70
+ <div role='list' className='flex flex-col w-full'>
71
+ {items?.map((func) => (
72
+ <List.Item<FunctionType>
73
+ key={func.id}
74
+ item={func}
75
+ classNames={mx(grid, ghostHover, 'items-center', 'pli-2', 'min-bs-[3rem]')}
76
+ >
77
+ <div className='flex flex-col truncate'>
78
+ <List.ItemTitle classNames='truncate'>{func.name}</List.ItemTitle>
79
+ {getScriptName(func) && (
80
+ <div className='text-xs text-description truncate'>{getScriptName(func)}</div>
81
+ )}
82
+ </div>
83
+ {functionToScriptMap[func.id] && (
84
+ <Button onClick={() => handleGoToScript(func)}>{t('go to function source button label')}</Button>
85
+ )}
86
+ </List.Item>
87
+ ))}
88
+ </div>
89
+ )}
90
+ </List.Root>
91
+
92
+ {functions.length === 0 && <div className='text-center plb-4 text-gray-500'>{t('no functions found')}</div>}
93
+ </div>
94
+ );
95
+ };