@dxos/plugin-automation 0.8.4-staging.60fe92afc8 → 0.9.1-main.c7dcc2e112

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 (131) hide show
  1. package/dist/lib/neutral/{AutomationArticle-GN36NUX2.mjs → AutomationArticle-CG4ZML3C.mjs} +3 -2
  2. package/dist/lib/neutral/{AutomationCompanion-M26WR6VP.mjs → AutomationCompanion-67LW2WZS.mjs} +12 -13
  3. package/dist/lib/neutral/AutomationCompanion-67LW2WZS.mjs.map +7 -0
  4. package/dist/lib/neutral/AutomationPlugin.mjs +1 -1
  5. package/dist/lib/neutral/AutomationPlugin.node.mjs +4 -4
  6. package/dist/lib/neutral/AutomationPlugin.node.mjs.map +3 -3
  7. package/dist/lib/neutral/{AutomationSettings-YXUJDRQA.mjs → AutomationSettings-2XCCFX6X.mjs} +5 -5
  8. package/dist/lib/neutral/{AutomationSettings-YXUJDRQA.mjs.map → AutomationSettings-2XCCFX6X.mjs.map} +3 -3
  9. package/dist/lib/neutral/{TriggerSettings-XCHIZPOR.mjs → TriggerSettings-ABOTKRUA.mjs} +2 -2
  10. package/dist/lib/neutral/{app-graph-builder-BTTHS4VK.mjs → app-graph-builder-VX54SXD6.mjs} +6 -6
  11. package/dist/lib/neutral/app-graph-builder-VX54SXD6.mjs.map +7 -0
  12. package/dist/lib/neutral/capabilities/index.mjs +3 -3
  13. package/dist/lib/neutral/capabilities/node.mjs +1 -1
  14. package/dist/lib/neutral/{chunk-2JP77CMN.mjs → chunk-73DGSL37.mjs} +2 -2
  15. package/dist/lib/neutral/chunk-73DGSL37.mjs.map +7 -0
  16. package/dist/lib/neutral/{chunk-HPUHQ3L6.mjs → chunk-77QU5RSC.mjs} +3 -3
  17. package/dist/lib/neutral/chunk-77QU5RSC.mjs.map +7 -0
  18. package/dist/lib/neutral/chunk-D4RCXOP4.mjs +13 -0
  19. package/dist/lib/neutral/chunk-D4RCXOP4.mjs.map +7 -0
  20. package/dist/lib/neutral/{chunk-SONGOJRB.mjs → chunk-EJ5J22XS.mjs} +2 -2
  21. package/dist/lib/neutral/chunk-HHIFH3N3.mjs +45 -0
  22. package/dist/lib/neutral/chunk-HHIFH3N3.mjs.map +7 -0
  23. package/dist/lib/neutral/chunk-HQU5QWF4.mjs +349 -0
  24. package/dist/lib/neutral/chunk-HQU5QWF4.mjs.map +7 -0
  25. package/dist/lib/neutral/{chunk-FE7YFBX7.mjs → chunk-LVUL7GIN.mjs} +151 -60
  26. package/dist/lib/neutral/chunk-LVUL7GIN.mjs.map +7 -0
  27. package/dist/lib/neutral/{chunk-GARB7S5R.mjs → chunk-YHK7FWGV.mjs} +5 -3
  28. package/dist/lib/neutral/chunk-YHK7FWGV.mjs.map +7 -0
  29. package/dist/lib/neutral/components/index.mjs +36 -2
  30. package/dist/lib/neutral/components/index.mjs.map +3 -3
  31. package/dist/lib/neutral/containers/index.mjs +4 -4
  32. package/dist/lib/neutral/{create-automation-OE3TNXYE.mjs → create-automation-AGYTF62E.mjs} +5 -9
  33. package/dist/lib/neutral/create-automation-AGYTF62E.mjs.map +7 -0
  34. package/dist/lib/neutral/{create-trigger-from-template-TERHKWJM.mjs → create-trigger-from-template-TYGCSR3Z.mjs} +5 -5
  35. package/dist/lib/neutral/create-trigger-from-template-TYGCSR3Z.mjs.map +7 -0
  36. package/dist/lib/neutral/index.mjs +10 -2
  37. package/dist/lib/neutral/meta.json +1 -1
  38. package/dist/lib/neutral/meta.mjs +1 -1
  39. package/dist/lib/neutral/{navigation-resolver-I3L5FHJO.mjs → navigation-resolver-FSJNF3DQ.mjs} +3 -3
  40. package/dist/lib/neutral/navigation-resolver-FSJNF3DQ.mjs.map +7 -0
  41. package/dist/lib/neutral/operations/index.mjs +1 -1
  42. package/dist/lib/neutral/plugin.mjs +2 -2
  43. package/dist/lib/neutral/{react-surface-IBRWUKPC.mjs → react-surface-W3J3CDHA.mjs} +2 -2
  44. package/dist/lib/neutral/{react-surface-IBRWUKPC.mjs.map → react-surface-W3J3CDHA.mjs.map} +3 -3
  45. package/dist/lib/neutral/testing.mjs +1 -1
  46. package/dist/lib/neutral/translations.mjs +6 -2
  47. package/dist/lib/neutral/translations.mjs.map +3 -3
  48. package/dist/lib/neutral/types/index.mjs +1 -1
  49. package/dist/types/dx.config.d.ts +28 -0
  50. package/dist/types/dx.config.d.ts.map +1 -0
  51. package/dist/types/src/capabilities/index.d.ts +8 -13
  52. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  53. package/dist/types/src/capabilities/navigation-resolver.d.ts.map +1 -1
  54. package/dist/types/src/capabilities/node.d.ts +5 -5
  55. package/dist/types/src/capabilities/node.d.ts.map +1 -1
  56. package/dist/types/src/capabilities/react-surface.d.ts +2 -2
  57. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  58. package/dist/types/src/components/CronBuilder/CronBuilder.d.ts +16 -0
  59. package/dist/types/src/components/CronBuilder/CronBuilder.d.ts.map +1 -0
  60. package/dist/types/src/components/CronBuilder/CronBuilder.stories.d.ts +194 -0
  61. package/dist/types/src/components/CronBuilder/CronBuilder.stories.d.ts.map +1 -0
  62. package/dist/types/src/components/CronBuilder/cron.d.ts +17 -0
  63. package/dist/types/src/components/CronBuilder/cron.d.ts.map +1 -0
  64. package/dist/types/src/components/CronBuilder/cron.test.d.ts +2 -0
  65. package/dist/types/src/components/CronBuilder/cron.test.d.ts.map +1 -0
  66. package/dist/types/src/components/CronBuilder/index.d.ts +4 -0
  67. package/dist/types/src/components/CronBuilder/index.d.ts.map +1 -0
  68. package/dist/types/src/components/CronBuilder/schema.d.ts +96 -0
  69. package/dist/types/src/components/CronBuilder/schema.d.ts.map +1 -0
  70. package/dist/types/src/components/index.d.ts +1 -0
  71. package/dist/types/src/components/index.d.ts.map +1 -1
  72. package/dist/types/src/containers/AutomationArticle/AutomationArticle.d.ts +6 -0
  73. package/dist/types/src/containers/AutomationArticle/AutomationArticle.d.ts.map +1 -1
  74. package/dist/types/src/containers/AutomationArticle/AutomationArticle.stories.d.ts +120 -0
  75. package/dist/types/src/containers/AutomationArticle/AutomationArticle.stories.d.ts.map +1 -0
  76. package/dist/types/src/containers/AutomationCompanion/AutomationCompanion.d.ts.map +1 -1
  77. package/dist/types/src/index.d.ts +1 -0
  78. package/dist/types/src/index.d.ts.map +1 -1
  79. package/dist/types/src/meta.d.ts +28 -2
  80. package/dist/types/src/meta.d.ts.map +1 -1
  81. package/dist/types/src/paths.d.ts +2 -0
  82. package/dist/types/src/paths.d.ts.map +1 -1
  83. package/dist/types/src/translations.d.ts +9 -1
  84. package/dist/types/src/translations.d.ts.map +1 -1
  85. package/dist/types/src/types/AutomationCapabilities.d.ts +6 -1
  86. package/dist/types/src/types/AutomationCapabilities.d.ts.map +1 -1
  87. package/dist/types/tsconfig.tsbuildinfo +1 -1
  88. package/dx.config.ts +34 -0
  89. package/package.json +40 -38
  90. package/src/AutomationPlugin.test.ts +1 -1
  91. package/src/AutomationPlugin.tsx +1 -1
  92. package/src/capabilities/app-graph-builder.ts +5 -5
  93. package/src/capabilities/navigation-resolver.ts +2 -2
  94. package/src/capabilities/react-surface.tsx +1 -1
  95. package/src/commands/trigger/create/queue.ts +2 -2
  96. package/src/commands/trigger/update/queue.ts +2 -2
  97. package/src/components/CreateAutomationPanel/CreateAutomationPanel.tsx +1 -1
  98. package/src/components/CronBuilder/CronBuilder.stories.tsx +72 -0
  99. package/src/components/CronBuilder/CronBuilder.tsx +94 -0
  100. package/src/components/CronBuilder/cron.test.ts +131 -0
  101. package/src/components/CronBuilder/cron.ts +138 -0
  102. package/src/components/CronBuilder/index.ts +7 -0
  103. package/src/components/CronBuilder/schema.ts +119 -0
  104. package/src/components/index.ts +1 -0
  105. package/src/containers/AutomationArticle/AutomationArticle.stories.tsx +73 -0
  106. package/src/containers/AutomationArticle/AutomationArticle.tsx +186 -56
  107. package/src/containers/AutomationCompanion/AutomationCompanion.tsx +79 -72
  108. package/src/containers/AutomationSettings/AutomationSettings.tsx +3 -3
  109. package/src/containers/TriggerSettings/TriggerSettings.tsx +1 -1
  110. package/src/index.ts +1 -0
  111. package/src/meta.ts +2 -26
  112. package/src/operations/create-trigger-from-template.ts +3 -3
  113. package/src/paths.ts +7 -2
  114. package/src/translations.ts +7 -2
  115. package/src/types/AutomationCapabilities.ts +9 -1
  116. package/src/types/AutomationOperation.ts +1 -1
  117. package/src/types/schema.ts +1 -1
  118. package/dist/lib/neutral/AutomationCompanion-M26WR6VP.mjs.map +0 -7
  119. package/dist/lib/neutral/app-graph-builder-BTTHS4VK.mjs.map +0 -7
  120. package/dist/lib/neutral/chunk-2JP77CMN.mjs.map +0 -7
  121. package/dist/lib/neutral/chunk-DUGOIM7G.mjs +0 -36
  122. package/dist/lib/neutral/chunk-DUGOIM7G.mjs.map +0 -7
  123. package/dist/lib/neutral/chunk-FE7YFBX7.mjs.map +0 -7
  124. package/dist/lib/neutral/chunk-GARB7S5R.mjs.map +0 -7
  125. package/dist/lib/neutral/chunk-HPUHQ3L6.mjs.map +0 -7
  126. package/dist/lib/neutral/create-automation-OE3TNXYE.mjs.map +0 -7
  127. package/dist/lib/neutral/create-trigger-from-template-TERHKWJM.mjs.map +0 -7
  128. package/dist/lib/neutral/navigation-resolver-I3L5FHJO.mjs.map +0 -7
  129. /package/dist/lib/neutral/{AutomationArticle-GN36NUX2.mjs.map → AutomationArticle-CG4ZML3C.mjs.map} +0 -0
  130. /package/dist/lib/neutral/{TriggerSettings-XCHIZPOR.mjs.map → TriggerSettings-ABOTKRUA.mjs.map} +0 -0
  131. /package/dist/lib/neutral/{chunk-SONGOJRB.mjs.map → chunk-EJ5J22XS.mjs.map} +0 -0
package/dx.config.ts ADDED
@@ -0,0 +1,34 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Config2 } from '@dxos/app-framework/config';
6
+ import { trim } from '@dxos/util';
7
+
8
+ export default Config2.make({
9
+ plugin: {
10
+ key: 'org.dxos.plugin.automation',
11
+ name: 'Automation',
12
+ author: 'DXOS',
13
+ description: trim`
14
+ Event-driven workflow automation engine for DXOS Composer.
15
+ An Automation is a user-facing object pairing a trigger ("when" — a timer schedule or an
16
+ incoming feed) with an action ("then" — a persistent compute operation or routine), enabling
17
+ automated pipelines that react to changes in real time without manual intervention.
18
+
19
+ Automations are configured in their own article and surfaced on a per-object "Automations"
20
+ companion that lists the automations connected to each object. A per-space TriggerDispatcher
21
+ manages execution: running locally in the browser when computeEnvironment is "local", or
22
+ delegating to the DXOS edge for server-side reliability; the space settings page chooses the
23
+ runtime location.
24
+
25
+ Operation handlers and blueprints contributed by any plugin in the application are
26
+ automatically merged and made available to every space's OperationRegistry, so new
27
+ capabilities registered by other plugins become instantly invocable from automations.
28
+ `,
29
+ icon: { key: 'ph--atom--regular' },
30
+ source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-automation',
31
+ spec: 'PLUGIN.mdl',
32
+ tags: ['system'],
33
+ },
34
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-automation",
3
- "version": "0.8.4-staging.60fe92afc8",
3
+ "version": "0.9.1-main.c7dcc2e112",
4
4
  "description": "Prompt chain plugin",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -102,6 +102,7 @@
102
102
  "files": [
103
103
  "assets",
104
104
  "dist",
105
+ "dx.config.ts",
105
106
  "src",
106
107
  "PLUGIN.mdl"
107
108
  ],
@@ -109,37 +110,38 @@
109
110
  "@effect-atom/atom": "^0.5.3",
110
111
  "@effect/cli": "0.75.2",
111
112
  "@effect/printer-ansi": "0.49.0",
112
- "@dxos/ai": "0.8.4-staging.60fe92afc8",
113
- "@dxos/app-framework": "0.8.4-staging.60fe92afc8",
114
- "@dxos/app-toolkit": "0.8.4-staging.60fe92afc8",
115
- "@dxos/cli-util": "0.8.4-staging.60fe92afc8",
116
- "@dxos/client-protocol": "0.8.4-staging.60fe92afc8",
117
- "@dxos/client": "0.8.4-staging.60fe92afc8",
118
- "@dxos/compute-runtime": "0.8.4-staging.60fe92afc8",
119
- "@dxos/compute": "0.8.4-staging.60fe92afc8",
120
- "@dxos/conductor": "0.8.4-staging.60fe92afc8",
121
- "@dxos/context": "0.8.4-staging.60fe92afc8",
122
- "@dxos/echo": "0.8.4-staging.60fe92afc8",
123
- "@dxos/echo-react": "0.8.4-staging.60fe92afc8",
124
- "@dxos/edge-client": "0.8.4-staging.60fe92afc8",
125
- "@dxos/effect": "0.8.4-staging.60fe92afc8",
126
- "@dxos/invariant": "0.8.4-staging.60fe92afc8",
127
- "@dxos/keys": "0.8.4-staging.60fe92afc8",
128
- "@dxos/functions-runtime": "0.8.4-staging.60fe92afc8",
129
- "@dxos/log": "0.8.4-staging.60fe92afc8",
130
- "@dxos/plugin-graph": "0.8.4-staging.60fe92afc8",
131
- "@dxos/plugin-client": "0.8.4-staging.60fe92afc8",
132
- "@dxos/plugin-space": "0.8.4-staging.60fe92afc8",
133
- "@dxos/random": "0.8.4-staging.60fe92afc8",
134
- "@dxos/react-client": "0.8.4-staging.60fe92afc8",
135
- "@dxos/react-ui-components": "0.8.4-staging.60fe92afc8",
136
- "@dxos/react-ui-form": "0.8.4-staging.60fe92afc8",
137
- "@dxos/react-ui-list": "0.8.4-staging.60fe92afc8",
138
- "@dxos/react-ui-attention": "0.8.4-staging.60fe92afc8",
139
- "@dxos/react-ui-search": "0.8.4-staging.60fe92afc8",
140
- "@dxos/schema": "0.8.4-staging.60fe92afc8",
141
- "@dxos/types": "0.8.4-staging.60fe92afc8",
142
- "@dxos/util": "0.8.4-staging.60fe92afc8"
113
+ "cronstrue": "^2.56.0",
114
+ "@dxos/ai": "0.9.1-main.c7dcc2e112",
115
+ "@dxos/app-toolkit": "0.9.1-main.c7dcc2e112",
116
+ "@dxos/app-framework": "0.9.1-main.c7dcc2e112",
117
+ "@dxos/cli-util": "0.9.1-main.c7dcc2e112",
118
+ "@dxos/client-protocol": "0.9.1-main.c7dcc2e112",
119
+ "@dxos/compute-runtime": "0.9.1-main.c7dcc2e112",
120
+ "@dxos/compute": "0.9.1-main.c7dcc2e112",
121
+ "@dxos/context": "0.9.1-main.c7dcc2e112",
122
+ "@dxos/conductor": "0.9.1-main.c7dcc2e112",
123
+ "@dxos/echo": "0.9.1-main.c7dcc2e112",
124
+ "@dxos/edge-client": "0.9.1-main.c7dcc2e112",
125
+ "@dxos/echo-react": "0.9.1-main.c7dcc2e112",
126
+ "@dxos/functions-runtime": "0.9.1-main.c7dcc2e112",
127
+ "@dxos/invariant": "0.9.1-main.c7dcc2e112",
128
+ "@dxos/effect": "0.9.1-main.c7dcc2e112",
129
+ "@dxos/keys": "0.9.1-main.c7dcc2e112",
130
+ "@dxos/client": "0.9.1-main.c7dcc2e112",
131
+ "@dxos/log": "0.9.1-main.c7dcc2e112",
132
+ "@dxos/plugin-client": "0.9.1-main.c7dcc2e112",
133
+ "@dxos/plugin-graph": "0.9.1-main.c7dcc2e112",
134
+ "@dxos/plugin-space": "0.9.1-main.c7dcc2e112",
135
+ "@dxos/random": "0.9.1-main.c7dcc2e112",
136
+ "@dxos/react-ui-attention": "0.9.1-main.c7dcc2e112",
137
+ "@dxos/react-client": "0.9.1-main.c7dcc2e112",
138
+ "@dxos/react-ui-components": "0.9.1-main.c7dcc2e112",
139
+ "@dxos/react-ui-form": "0.9.1-main.c7dcc2e112",
140
+ "@dxos/react-ui-search": "0.9.1-main.c7dcc2e112",
141
+ "@dxos/schema": "0.9.1-main.c7dcc2e112",
142
+ "@dxos/react-ui-list": "0.9.1-main.c7dcc2e112",
143
+ "@dxos/types": "0.9.1-main.c7dcc2e112",
144
+ "@dxos/util": "0.9.1-main.c7dcc2e112"
143
145
  },
144
146
  "devDependencies": {
145
147
  "@effect-atom/atom-react": "^0.5.0",
@@ -150,10 +152,10 @@
150
152
  "react": "~19.2.3",
151
153
  "react-dom": "~19.2.3",
152
154
  "vite": "^8.0.16",
153
- "@dxos/plugin-testing": "0.8.4-staging.60fe92afc8",
154
- "@dxos/react-ui": "0.8.4-staging.60fe92afc8",
155
- "@dxos/storybook-utils": "0.8.4-staging.60fe92afc8",
156
- "@dxos/ui-theme": "0.8.4-staging.60fe92afc8"
155
+ "@dxos/plugin-testing": "0.9.1-main.c7dcc2e112",
156
+ "@dxos/storybook-utils": "0.9.1-main.c7dcc2e112",
157
+ "@dxos/ui-theme": "0.9.1-main.c7dcc2e112",
158
+ "@dxos/react-ui": "0.9.1-main.c7dcc2e112"
157
159
  },
158
160
  "peerDependencies": {
159
161
  "@effect-atom/atom-react": "^0.5.0",
@@ -161,8 +163,8 @@
161
163
  "effect": "3.21.3",
162
164
  "react": "~19.2.3",
163
165
  "react-dom": "~19.2.3",
164
- "@dxos/ui-theme": "0.8.4-staging.60fe92afc8",
165
- "@dxos/react-ui": "0.8.4-staging.60fe92afc8"
166
+ "@dxos/react-ui": "0.9.1-main.c7dcc2e112",
167
+ "@dxos/ui-theme": "0.9.1-main.c7dcc2e112"
166
168
  },
167
169
  "publishConfig": {
168
170
  "access": "public"
@@ -11,7 +11,7 @@ import { AutomationPlugin } from '#plugin';
11
11
 
12
12
  import { meta } from './meta';
13
13
 
14
- const moduleId = (name: string) => `${meta.id}.module.${name}`;
14
+ const moduleId = (name: string) => `${meta.profile.key}.module.${name}`;
15
15
 
16
16
  describe('AutomationPlugin', () => {
17
17
  test('modules activate on the expected events', async ({ expect }) => {
@@ -50,7 +50,7 @@ export const AutomationPlugin = Plugin.define(meta).pipe(
50
50
  activate: TriggerRuntimeController,
51
51
  }),
52
52
  AppPlugin.addPluginAssetModule({
53
- asset: { pluginId: meta.id, path: 'PLUGIN.mdl', content: pluginSpec, mimeType: 'application/x-mdl' },
53
+ asset: { pluginId: meta.profile.key, path: 'PLUGIN.mdl', content: pluginSpec, mimeType: 'application/x-mdl' },
54
54
  }),
55
55
  Plugin.make,
56
56
  );
@@ -6,7 +6,7 @@ import * as Effect from 'effect/Effect';
6
6
  import * as Option from 'effect/Option';
7
7
 
8
8
  import { Capability } from '@dxos/app-framework';
9
- import { AppCapabilities, AppNode, LayoutOperation, createTypeSectionExtension } from '@dxos/app-toolkit';
9
+ import { AppCapabilities, AppNode, LayoutOperation, TypeSection } from '@dxos/app-toolkit';
10
10
  import { isSpace } from '@dxos/client/echo';
11
11
  import { Operation } from '@dxos/compute';
12
12
  import { Type } from '@dxos/echo';
@@ -22,7 +22,7 @@ import { blank } from '../templates';
22
22
  export default Capability.makeModule(
23
23
  Effect.fnUntraced(function* () {
24
24
  const extensions = yield* Effect.all([
25
- createTypeSectionExtension(Automation.Automation),
25
+ TypeSection.createTypeSectionExtension(Automation.Automation),
26
26
  GraphBuilder.createExtension({
27
27
  id: 'automationsSectionActions',
28
28
  match: (node) => {
@@ -61,8 +61,8 @@ export default Capability.makeModule(
61
61
  Effect.succeed([
62
62
  AppNode.makeSettingsPanel({
63
63
  id: 'automations',
64
- type: `${meta.id}.space-settings-automation`,
65
- label: ['automation-panel.label', { ns: meta.id }],
64
+ type: `${meta.profile.key}.space-settings-automation`,
65
+ label: ['automation-panel.label', { ns: meta.profile.key }],
66
66
  icon: 'ph--lightning--regular',
67
67
  iconHue: 'indigo',
68
68
  position: 'last',
@@ -76,7 +76,7 @@ export default Capability.makeModule(
76
76
  Effect.succeed([
77
77
  AppNode.makeCompanion({
78
78
  id: linkedSegment('automation'),
79
- label: ['automation-companion.label', { ns: meta.id }],
79
+ label: ['automation-companion.label', { ns: meta.profile.key }],
80
80
  icon: 'ph--lightning--regular',
81
81
  data: 'automation',
82
82
  position: 'last',
@@ -5,7 +5,7 @@
5
5
  import * as Effect from 'effect/Effect';
6
6
 
7
7
  import { Capability } from '@dxos/app-framework';
8
- import { AppCapabilities, createTypeSectionPathResolver } from '@dxos/app-toolkit';
8
+ import { AppCapabilities, TypeSection } from '@dxos/app-toolkit';
9
9
 
10
10
  import { Automation } from '#types';
11
11
 
@@ -13,7 +13,7 @@ export default Capability.makeModule(
13
13
  Effect.fnUntraced(function* () {
14
14
  return Capability.contributes(
15
15
  AppCapabilities.NavigationPathResolver,
16
- createTypeSectionPathResolver(Automation.Automation),
16
+ TypeSection.createTypeSectionPathResolver(Automation.Automation),
17
17
  );
18
18
  }),
19
19
  );
@@ -26,7 +26,7 @@ export default Capability.makeModule(() =>
26
26
  }),
27
27
  Surface.create({
28
28
  id: 'spaceSettingsAutomation',
29
- filter: AppSurface.literal(AppSurface.Article, `${meta.id}.space-settings-automation`),
29
+ filter: AppSurface.literal(AppSurface.Article, `${meta.profile.key}.space-settings-automation`),
30
30
  component: () => {
31
31
  const space = useActiveSpace();
32
32
  if (!space) {
@@ -14,7 +14,7 @@ import { CommandConfig } from '@dxos/cli-util';
14
14
  import { flushAndSync, print, spaceLayer, withTypes } from '@dxos/cli-util';
15
15
  import { Common } from '@dxos/cli-util';
16
16
  import { Operation, Trigger } from '@dxos/compute';
17
- import { Database, Feed as FeedNs, Filter, JsonSchema, Ref } from '@dxos/echo';
17
+ import { Database, Feed as Feed$, Filter, JsonSchema, Ref } from '@dxos/echo';
18
18
  import { EID } from '@dxos/keys';
19
19
 
20
20
  import { Enabled, Feed, Input } from '../options';
@@ -45,7 +45,7 @@ export const queue = Command.make(
45
45
 
46
46
  const feed = yield* Option.match(options.feed, {
47
47
  onNone: () => selectFeed(),
48
- onSome: (uri) => Database.resolve(EID.parse(uri), FeedNs.Feed),
48
+ onSome: (uri) => Database.resolve(EID.parse(uri), Feed$.Feed),
49
49
  });
50
50
 
51
51
  const input = yield* Option.match(options.input, {
@@ -13,7 +13,7 @@ import { CommandConfig } from '@dxos/cli-util';
13
13
  import { flushAndSync, print, spaceLayer, withTypes } from '@dxos/cli-util';
14
14
  import { Common } from '@dxos/cli-util';
15
15
  import { Operation, Trigger } from '@dxos/compute';
16
- import { Database, Feed as FeedNs, Filter, JsonSchema, Obj, Ref } from '@dxos/echo';
16
+ import { Database, Feed as Feed$, Filter, JsonSchema, Obj, Ref } from '@dxos/echo';
17
17
  import { EID, type EntityId } from '@dxos/keys';
18
18
 
19
19
  import { Enabled, Feed, Input, TriggerId } from '../options';
@@ -124,7 +124,7 @@ const updateFeed = Effect.fn(function* (trigger: Trigger.Trigger, feedOption: Op
124
124
  if (shouldChangeFeed) {
125
125
  const feed = yield* Option.match(feedOption, {
126
126
  onNone: () => selectFeed(),
127
- onSome: (uri) => Database.resolve(EID.parse(uri), FeedNs.Feed),
127
+ onSome: (uri) => Database.resolve(EID.parse(uri), Feed$.Feed),
128
128
  });
129
129
  Obj.update(trigger, (trigger) => {
130
130
  if (trigger.spec?.kind === 'feed') {
@@ -23,7 +23,7 @@ export type CreateAutomationPanelProps = SpaceCapabilities.CreateObjectCustomPan
23
23
  * template's `scaffold`.
24
24
  */
25
25
  export const CreateAutomationPanel = ({ onCreateObject, templates: templatesProp }: CreateAutomationPanelProps) => {
26
- const { t } = useTranslation(meta.id);
26
+ const { t } = useTranslation(meta.profile.key);
27
27
  const capabilityTemplates = useCapabilities(AutomationCapabilities.Template);
28
28
  const templates = templatesProp ?? capabilityTemplates;
29
29
  // The global create dialog has no subject, so subject-required templates (e.g. CRM, which needs a
@@ -0,0 +1,72 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React, { useState } from 'react';
7
+
8
+ import { translations as formTranslations } from '@dxos/react-ui-form/translations';
9
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
10
+
11
+ import { translations } from '#translations';
12
+
13
+ import { describeCron, toCron } from './cron';
14
+ import { CronBuilder } from './CronBuilder';
15
+ import { FrequencyDefaults, type CronSpecType } from './schema';
16
+
17
+ const DefaultStory = () => {
18
+ const [value, setValue] = useState<CronSpecType>(FrequencyDefaults.daily);
19
+ const cronString = toCron(value);
20
+ const description = describeCron(cronString);
21
+
22
+ return (
23
+ <div className='grid grid-cols-[1fr_1fr] gap-4 p-4'>
24
+ <div className='dx-container bg-card-surface rounded-md'>
25
+ <CronBuilder value={value} onChange={(v) => setValue(v)} />
26
+ </div>
27
+ <div className='dx-container bg-card-surface rounded-md p-4 flex flex-col gap-3'>
28
+ <div>
29
+ <p className='text-xs text-subdued mb-1'>Cron expression</p>
30
+ <code className='font-mono text-sm bg-baseSurface rounded px-2 py-1'>{cronString}</code>
31
+ </div>
32
+ <div>
33
+ <p className='text-xs text-subdued mb-1'>Description</p>
34
+ <p className='text-sm'>{description}</p>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ );
39
+ };
40
+
41
+ const ReadonlyStory = () => {
42
+ const value = FrequencyDefaults.weekly;
43
+ return (
44
+ <div className='p-4'>
45
+ <div className='dx-container bg-card-surface rounded-md max-w-sm'>
46
+ <CronBuilder value={value} readonly />
47
+ </div>
48
+ </div>
49
+ );
50
+ };
51
+
52
+ const meta = {
53
+ title: 'plugins/plugin-automation/components/CronBuilder',
54
+ component: CronBuilder,
55
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
56
+ parameters: {
57
+ layout: 'fullscreen',
58
+ translations: [...formTranslations, ...translations],
59
+ },
60
+ } satisfies Meta<typeof CronBuilder>;
61
+
62
+ export default meta;
63
+
64
+ type Story = StoryObj<typeof meta>;
65
+
66
+ export const Default: Story = {
67
+ render: DefaultStory,
68
+ };
69
+
70
+ export const Readonly: Story = {
71
+ render: ReadonlyStory,
72
+ };
@@ -0,0 +1,94 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import type * as SchemaAST from 'effect/SchemaAST';
6
+ import React, { useCallback, useMemo } from 'react';
7
+
8
+ import {
9
+ Form,
10
+ type FormFieldRendererProps,
11
+ type FormFieldMap,
12
+ type FormRootProps,
13
+ SelectField,
14
+ useFormFieldState,
15
+ } from '@dxos/react-ui-form';
16
+
17
+ import { toCron } from './cron';
18
+ import {
19
+ CronBuilderSchema,
20
+ type CronBuilderSchemaType,
21
+ type CronSpecType,
22
+ FrequencyDefaults,
23
+ FrequencyLabels,
24
+ Frequencies,
25
+ } from './schema';
26
+
27
+ export type CronBuilderProps = {
28
+ value?: CronSpecType;
29
+ onChange?: (value: CronSpecType, cron: string) => void;
30
+ } & Pick<FormRootProps, 'readonly'>;
31
+
32
+ export const CronBuilder = ({ value, onChange, readonly }: CronBuilderProps) => {
33
+ const fieldMap = useFrequencyFieldMap();
34
+
35
+ const defaultValues = useMemo<CronBuilderSchemaType>(() => ({ spec: value ?? FrequencyDefaults.daily }), []);
36
+
37
+ const handleValuesChanged = useCallback(
38
+ (values: Partial<CronBuilderSchemaType>) => {
39
+ if (values.spec) {
40
+ onChange?.(values.spec, toCron(values.spec));
41
+ }
42
+ },
43
+ [onChange],
44
+ );
45
+
46
+ return (
47
+ <Form.Root<CronBuilderSchemaType>
48
+ schema={CronBuilderSchema}
49
+ defaultValues={defaultValues}
50
+ readonly={readonly}
51
+ fieldMap={fieldMap}
52
+ onValuesChanged={handleValuesChanged}
53
+ >
54
+ <Form.Viewport>
55
+ <Form.Content>
56
+ <Form.FieldSet />
57
+ </Form.Content>
58
+ </Form.Viewport>
59
+ </Form.Root>
60
+ );
61
+ };
62
+
63
+ CronBuilder.displayName = 'CronBuilder';
64
+
65
+ const useFrequencyFieldMap = (): FormFieldMap => {
66
+ return useMemo(
67
+ (): FormFieldMap => ({
68
+ 'spec.frequency': (props) => <FrequencySelector {...props} />,
69
+ }),
70
+ [],
71
+ );
72
+ };
73
+
74
+ const FrequencySelector = (props: FormFieldRendererProps) => {
75
+ // Access the parent `spec` field so switching frequency replaces the entire variant object.
76
+ const specState = useFormFieldState(FrequencySelector.displayName, ['spec']);
77
+
78
+ const handleChange = useCallback(
79
+ (_type: SchemaAST.AST, newFrequency: string) => {
80
+ const freq = Frequencies.find((f) => f === newFrequency);
81
+ if (!freq) {
82
+ return;
83
+ }
84
+ specState.onValueChange(props.type, FrequencyDefaults[freq]);
85
+ },
86
+ [props.type, specState],
87
+ );
88
+
89
+ const options = useMemo(() => Frequencies.map((freq) => ({ value: freq, label: FrequencyLabels[freq] })), []);
90
+
91
+ return <SelectField {...props} options={options} onValueChange={handleChange} />;
92
+ };
93
+
94
+ FrequencySelector.displayName = 'Form.FrequencySelector';
@@ -0,0 +1,131 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { describe, test } from 'vitest';
6
+
7
+ import { fromCron, toCron } from './cron';
8
+
9
+ describe('toCron', () => {
10
+ test('minutely: every 15 minutes', ({ expect }) => {
11
+ expect(toCron({ frequency: 'minutely', interval: 15 })).toBe('*/15 * * * *');
12
+ });
13
+
14
+ test('minutely: every 1 minute', ({ expect }) => {
15
+ expect(toCron({ frequency: 'minutely', interval: 1 })).toBe('*/1 * * * *');
16
+ });
17
+
18
+ test('hourly: every hour at minute 0', ({ expect }) => {
19
+ expect(toCron({ frequency: 'hourly', interval: 1, minute: 0 })).toBe('0 */1 * * *');
20
+ });
21
+
22
+ test('hourly: every 6 hours at minute 30', ({ expect }) => {
23
+ expect(toCron({ frequency: 'hourly', interval: 6, minute: 30 })).toBe('30 */6 * * *');
24
+ });
25
+
26
+ test('daily: at 09:00', ({ expect }) => {
27
+ expect(toCron({ frequency: 'daily', hour: 9, minute: 0 })).toBe('0 9 * * *');
28
+ });
29
+
30
+ test('daily: at 22:30', ({ expect }) => {
31
+ expect(toCron({ frequency: 'daily', hour: 22, minute: 30 })).toBe('30 22 * * *');
32
+ });
33
+
34
+ test('weekly: Monday at 09:00', ({ expect }) => {
35
+ expect(toCron({ frequency: 'weekly', daysOfWeek: ['mon'], hour: 9, minute: 0 })).toBe('0 9 * * 1');
36
+ });
37
+
38
+ test('weekly: Mon, Wed, Fri at 08:00', ({ expect }) => {
39
+ expect(toCron({ frequency: 'weekly', daysOfWeek: ['mon', 'wed', 'fri'], hour: 8, minute: 0 })).toBe(
40
+ '0 8 * * 1,3,5',
41
+ );
42
+ });
43
+
44
+ test('weekly: Sunday (DOW=0) at midnight', ({ expect }) => {
45
+ expect(toCron({ frequency: 'weekly', daysOfWeek: ['sun'], hour: 0, minute: 0 })).toBe('0 0 * * 0');
46
+ });
47
+
48
+ test('weekly: empty daysOfWeek falls back to wildcard', ({ expect }) => {
49
+ expect(toCron({ frequency: 'weekly', daysOfWeek: [], hour: 9, minute: 0 })).toBe('0 9 * * *');
50
+ });
51
+
52
+ test('monthly: 1st at 09:00', ({ expect }) => {
53
+ expect(toCron({ frequency: 'monthly', daysOfMonth: [1], hour: 9, minute: 0 })).toBe('0 9 1 * *');
54
+ });
55
+
56
+ test('monthly: 1st and 15th at 12:00', ({ expect }) => {
57
+ expect(toCron({ frequency: 'monthly', daysOfMonth: [1, 15], hour: 12, minute: 0 })).toBe('0 12 1,15 * *');
58
+ });
59
+
60
+ test('monthly: empty daysOfMonth falls back to wildcard', ({ expect }) => {
61
+ expect(toCron({ frequency: 'monthly', daysOfMonth: [], hour: 9, minute: 0 })).toBe('0 9 * * *');
62
+ });
63
+
64
+ test('custom: pass-through full cron expression', ({ expect }) => {
65
+ expect(toCron({ frequency: 'custom', cronExpression: '0 9 * * MON-FRI' })).toBe('0 9 * * MON-FRI');
66
+ });
67
+
68
+ test('custom: empty cronExpression defaults to wildcard', ({ expect }) => {
69
+ expect(toCron({ frequency: 'custom', cronExpression: '' })).toBe('* * * * *');
70
+ });
71
+
72
+ test('custom: step expression preserved', ({ expect }) => {
73
+ expect(toCron({ frequency: 'custom', cronExpression: '0/15 * * * *' })).toBe('0/15 * * * *');
74
+ });
75
+ });
76
+
77
+ describe('fromCron', () => {
78
+ test('minutely', ({ expect }) => {
79
+ expect(fromCron('*/15 * * * *')).toEqual({ frequency: 'minutely', interval: 15 });
80
+ });
81
+
82
+ test('hourly', ({ expect }) => {
83
+ expect(fromCron('30 */6 * * *')).toEqual({ frequency: 'hourly', interval: 6, minute: 30 });
84
+ });
85
+
86
+ test('daily', ({ expect }) => {
87
+ expect(fromCron('0 9 * * *')).toEqual({ frequency: 'daily', hour: 9, minute: 0 });
88
+ });
89
+
90
+ test('weekly single day', ({ expect }) => {
91
+ expect(fromCron('0 9 * * 1')).toEqual({ frequency: 'weekly', daysOfWeek: ['mon'], hour: 9, minute: 0 });
92
+ });
93
+
94
+ test('weekly multiple days', ({ expect }) => {
95
+ expect(fromCron('0 8 * * 1,3,5')).toEqual({
96
+ frequency: 'weekly',
97
+ daysOfWeek: ['mon', 'wed', 'fri'],
98
+ hour: 8,
99
+ minute: 0,
100
+ });
101
+ });
102
+
103
+ test('monthly single day', ({ expect }) => {
104
+ expect(fromCron('0 9 1 * *')).toEqual({ frequency: 'monthly', daysOfMonth: [1], hour: 9, minute: 0 });
105
+ });
106
+
107
+ test('monthly multiple days', ({ expect }) => {
108
+ expect(fromCron('0 12 1,15 * *')).toEqual({ frequency: 'monthly', daysOfMonth: [1, 15], hour: 12, minute: 0 });
109
+ });
110
+
111
+ test('custom: unknown pattern', ({ expect }) => {
112
+ expect(fromCron('0 9 * * MON-FRI')).toEqual({ frequency: 'custom', cronExpression: '0 9 * * MON-FRI' });
113
+ });
114
+
115
+ test('custom: wrong field count', ({ expect }) => {
116
+ expect(fromCron('0 9 *')).toEqual({ frequency: 'custom', cronExpression: '0 9 *' });
117
+ });
118
+
119
+ test('round-trip: toCron → fromCron', ({ expect }) => {
120
+ const specs = [
121
+ { frequency: 'minutely' as const, interval: 15 },
122
+ { frequency: 'hourly' as const, interval: 4, minute: 30 },
123
+ { frequency: 'daily' as const, hour: 22, minute: 30 },
124
+ { frequency: 'weekly' as const, daysOfWeek: ['mon', 'wed'] as const, hour: 9, minute: 0 },
125
+ { frequency: 'monthly' as const, daysOfMonth: [1, 15], hour: 12, minute: 0 },
126
+ ];
127
+ for (const spec of specs) {
128
+ expect(fromCron(toCron(spec))).toEqual(spec);
129
+ }
130
+ });
131
+ });