@zaalipro/dsh-workflows 0.1.0-rc.3

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 (229) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +224 -0
  3. package/README.i18n.yaml +4 -0
  4. package/README.md +109 -0
  5. package/README.zh.md +109 -0
  6. package/cordis.patch.yml +10 -0
  7. package/docs/architecture.i18n.yaml +4 -0
  8. package/docs/architecture.md +177 -0
  9. package/docs/architecture.zh.md +177 -0
  10. package/docs/testing.i18n.yaml +4 -0
  11. package/docs/testing.md +125 -0
  12. package/docs/testing.zh.md +125 -0
  13. package/docs/user-guide.i18n.yaml +4 -0
  14. package/docs/user-guide.md +351 -0
  15. package/docs/user-guide.zh.md +351 -0
  16. package/lib/client-types/WorkflowMemberInspector.d.ts +28 -0
  17. package/lib/client-types/WorkflowMemberInspector.d.ts.map +1 -0
  18. package/lib/client-types/WorkflowMemberInspector.js +181 -0
  19. package/lib/client-types/WorkflowMemberInspector.js.map +1 -0
  20. package/lib/client-types/WorkflowRunPanel.d.ts +43 -0
  21. package/lib/client-types/WorkflowRunPanel.d.ts.map +1 -0
  22. package/lib/client-types/WorkflowRunPanel.js +130 -0
  23. package/lib/client-types/WorkflowRunPanel.js.map +1 -0
  24. package/lib/client-types/WorkflowsDashboard.d.ts +49 -0
  25. package/lib/client-types/WorkflowsDashboard.d.ts.map +1 -0
  26. package/lib/client-types/WorkflowsDashboard.js +942 -0
  27. package/lib/client-types/WorkflowsDashboard.js.map +1 -0
  28. package/lib/client-types/adapter.d.ts +60 -0
  29. package/lib/client-types/adapter.d.ts.map +1 -0
  30. package/lib/client-types/adapter.js +117 -0
  31. package/lib/client-types/adapter.js.map +1 -0
  32. package/lib/client-types/chat-renderer.d.ts +10 -0
  33. package/lib/client-types/chat-renderer.d.ts.map +1 -0
  34. package/lib/client-types/chat-renderer.js +11 -0
  35. package/lib/client-types/chat-renderer.js.map +1 -0
  36. package/lib/client-types/contract.d.ts +210 -0
  37. package/lib/client-types/contract.d.ts.map +1 -0
  38. package/lib/client-types/contract.js +37 -0
  39. package/lib/client-types/contract.js.map +1 -0
  40. package/lib/client-types/controller.d.ts +83 -0
  41. package/lib/client-types/controller.d.ts.map +1 -0
  42. package/lib/client-types/controller.js +705 -0
  43. package/lib/client-types/controller.js.map +1 -0
  44. package/lib/client-types/index.d.ts +19 -0
  45. package/lib/client-types/index.d.ts.map +1 -0
  46. package/lib/client-types/index.js +730 -0
  47. package/lib/client-types/index.js.map +1 -0
  48. package/lib/client-types/locales.d.ts +257 -0
  49. package/lib/client-types/locales.d.ts.map +1 -0
  50. package/lib/client-types/locales.js +270 -0
  51. package/lib/client-types/locales.js.map +1 -0
  52. package/lib/client-types/slot-components.d.ts +38 -0
  53. package/lib/client-types/slot-components.d.ts.map +1 -0
  54. package/lib/client-types/slot-components.js +22 -0
  55. package/lib/client-types/slot-components.js.map +1 -0
  56. package/lib/client-types/store.d.ts +69 -0
  57. package/lib/client-types/store.d.ts.map +1 -0
  58. package/lib/client-types/store.js +135 -0
  59. package/lib/client-types/store.js.map +1 -0
  60. package/lib/client-types/workflow-definition.d.ts +52 -0
  61. package/lib/client-types/workflow-definition.d.ts.map +1 -0
  62. package/lib/client-types/workflow-definition.js +135 -0
  63. package/lib/client-types/workflow-definition.js.map +1 -0
  64. package/lib/client.js +11064 -0
  65. package/lib/client.js.map +1 -0
  66. package/lib/compat-engine/index.js +1926 -0
  67. package/lib/compat-engine/index.js.map +1 -0
  68. package/lib/compat-engine/worker.cjs +1500 -0
  69. package/lib/compat-engine/worker.cjs.map +1 -0
  70. package/lib/typert.host.d.ts +3 -0
  71. package/lib/typert.host.js +1709 -0
  72. package/lib/typert.remote-client.d.ts +57 -0
  73. package/lib/typert.remote-client.js +1662 -0
  74. package/lib/types/commands/aliases.d.ts +4 -0
  75. package/lib/types/commands/aliases.d.ts.map +1 -0
  76. package/lib/types/commands/aliases.js +25 -0
  77. package/lib/types/commands/aliases.js.map +1 -0
  78. package/lib/types/commands/index.d.ts +66 -0
  79. package/lib/types/commands/index.d.ts.map +1 -0
  80. package/lib/types/commands/index.js +550 -0
  81. package/lib/types/commands/index.js.map +1 -0
  82. package/lib/types/commands/parser.d.ts +17 -0
  83. package/lib/types/commands/parser.d.ts.map +1 -0
  84. package/lib/types/commands/parser.js +45 -0
  85. package/lib/types/commands/parser.js.map +1 -0
  86. package/lib/types/config.d.ts +71 -0
  87. package/lib/types/config.d.ts.map +1 -0
  88. package/lib/types/config.js +109 -0
  89. package/lib/types/config.js.map +1 -0
  90. package/lib/types/index.d.ts +41 -0
  91. package/lib/types/index.d.ts.map +1 -0
  92. package/lib/types/index.js +483 -0
  93. package/lib/types/index.js.map +1 -0
  94. package/lib/types/invariant.d.ts +21 -0
  95. package/lib/types/invariant.d.ts.map +1 -0
  96. package/lib/types/invariant.js +85 -0
  97. package/lib/types/invariant.js.map +1 -0
  98. package/lib/types/registry/definition.d.ts +24 -0
  99. package/lib/types/registry/definition.d.ts.map +1 -0
  100. package/lib/types/registry/definition.js +189 -0
  101. package/lib/types/registry/definition.js.map +1 -0
  102. package/lib/types/registry/index.d.ts +100 -0
  103. package/lib/types/registry/index.d.ts.map +1 -0
  104. package/lib/types/registry/index.js +899 -0
  105. package/lib/types/registry/index.js.map +1 -0
  106. package/lib/types/registry/names.d.ts +19 -0
  107. package/lib/types/registry/names.d.ts.map +1 -0
  108. package/lib/types/registry/names.js +69 -0
  109. package/lib/types/registry/names.js.map +1 -0
  110. package/lib/types/registry/remote.d.ts +15 -0
  111. package/lib/types/registry/remote.d.ts.map +1 -0
  112. package/lib/types/registry/remote.js +128 -0
  113. package/lib/types/registry/remote.js.map +1 -0
  114. package/lib/types/registry/roots.d.ts +44 -0
  115. package/lib/types/registry/roots.d.ts.map +1 -0
  116. package/lib/types/registry/roots.js +118 -0
  117. package/lib/types/registry/roots.js.map +1 -0
  118. package/lib/types/registry/types.d.ts +62 -0
  119. package/lib/types/registry/types.d.ts.map +1 -0
  120. package/lib/types/registry/types.js +2 -0
  121. package/lib/types/registry/types.js.map +1 -0
  122. package/lib/types/registry/watchers.d.ts +70 -0
  123. package/lib/types/registry/watchers.d.ts.map +1 -0
  124. package/lib/types/registry/watchers.js +152 -0
  125. package/lib/types/registry/watchers.js.map +1 -0
  126. package/lib/types/remote-events.d.ts +42 -0
  127. package/lib/types/remote-events.d.ts.map +1 -0
  128. package/lib/types/remote-events.js +21 -0
  129. package/lib/types/remote-events.js.map +1 -0
  130. package/lib/types/run-recorder.d.ts +44 -0
  131. package/lib/types/run-recorder.d.ts.map +1 -0
  132. package/lib/types/run-recorder.js +356 -0
  133. package/lib/types/run-recorder.js.map +1 -0
  134. package/lib/types/supervisor/canned-validate.d.ts +25 -0
  135. package/lib/types/supervisor/canned-validate.d.ts.map +1 -0
  136. package/lib/types/supervisor/canned-validate.js +122 -0
  137. package/lib/types/supervisor/canned-validate.js.map +1 -0
  138. package/lib/types/supervisor/completion-notice.d.ts +49 -0
  139. package/lib/types/supervisor/completion-notice.d.ts.map +1 -0
  140. package/lib/types/supervisor/completion-notice.js +281 -0
  141. package/lib/types/supervisor/completion-notice.js.map +1 -0
  142. package/lib/types/supervisor/cursors.d.ts +68 -0
  143. package/lib/types/supervisor/cursors.d.ts.map +1 -0
  144. package/lib/types/supervisor/cursors.js +158 -0
  145. package/lib/types/supervisor/cursors.js.map +1 -0
  146. package/lib/types/supervisor/engine-compat.d.ts +48 -0
  147. package/lib/types/supervisor/engine-compat.d.ts.map +1 -0
  148. package/lib/types/supervisor/engine-compat.js +116 -0
  149. package/lib/types/supervisor/engine-compat.js.map +1 -0
  150. package/lib/types/supervisor/index.d.ts +183 -0
  151. package/lib/types/supervisor/index.d.ts.map +1 -0
  152. package/lib/types/supervisor/index.js +2040 -0
  153. package/lib/types/supervisor/index.js.map +1 -0
  154. package/lib/types/supervisor/parallel-compat.d.ts +36 -0
  155. package/lib/types/supervisor/parallel-compat.d.ts.map +1 -0
  156. package/lib/types/supervisor/parallel-compat.js +180 -0
  157. package/lib/types/supervisor/parallel-compat.js.map +1 -0
  158. package/lib/types/supervisor/remote.d.ts +25 -0
  159. package/lib/types/supervisor/remote.d.ts.map +1 -0
  160. package/lib/types/supervisor/remote.js +478 -0
  161. package/lib/types/supervisor/remote.js.map +1 -0
  162. package/lib/types/supervisor/storage/bounded-file.d.ts +41 -0
  163. package/lib/types/supervisor/storage/bounded-file.d.ts.map +1 -0
  164. package/lib/types/supervisor/storage/bounded-file.js +186 -0
  165. package/lib/types/supervisor/storage/bounded-file.js.map +1 -0
  166. package/lib/types/supervisor/storage/details-codec.d.ts +14 -0
  167. package/lib/types/supervisor/storage/details-codec.d.ts.map +1 -0
  168. package/lib/types/supervisor/storage/details-codec.js +359 -0
  169. package/lib/types/supervisor/storage/details-codec.js.map +1 -0
  170. package/lib/types/supervisor/storage/index.d.ts +26 -0
  171. package/lib/types/supervisor/storage/index.d.ts.map +1 -0
  172. package/lib/types/supervisor/storage/index.js +100 -0
  173. package/lib/types/supervisor/storage/index.js.map +1 -0
  174. package/lib/types/supervisor/storage/lease.d.ts +34 -0
  175. package/lib/types/supervisor/storage/lease.d.ts.map +1 -0
  176. package/lib/types/supervisor/storage/lease.js +289 -0
  177. package/lib/types/supervisor/storage/lease.js.map +1 -0
  178. package/lib/types/supervisor/storage/manifest-codec.d.ts +10 -0
  179. package/lib/types/supervisor/storage/manifest-codec.d.ts.map +1 -0
  180. package/lib/types/supervisor/storage/manifest-codec.js +335 -0
  181. package/lib/types/supervisor/storage/manifest-codec.js.map +1 -0
  182. package/lib/types/supervisor/storage/manifest-store.d.ts +99 -0
  183. package/lib/types/supervisor/storage/manifest-store.d.ts.map +1 -0
  184. package/lib/types/supervisor/storage/manifest-store.js +1606 -0
  185. package/lib/types/supervisor/storage/manifest-store.js.map +1 -0
  186. package/lib/types/supervisor/storage/manifest-types.d.ts +269 -0
  187. package/lib/types/supervisor/storage/manifest-types.d.ts.map +1 -0
  188. package/lib/types/supervisor/storage/manifest-types.js +2 -0
  189. package/lib/types/supervisor/storage/manifest-types.js.map +1 -0
  190. package/lib/types/supervisor/storage/private-root.d.ts +143 -0
  191. package/lib/types/supervisor/storage/private-root.d.ts.map +1 -0
  192. package/lib/types/supervisor/storage/private-root.js +860 -0
  193. package/lib/types/supervisor/storage/private-root.js.map +1 -0
  194. package/lib/types/supervisor/storage/recovery.d.ts +14 -0
  195. package/lib/types/supervisor/storage/recovery.d.ts.map +1 -0
  196. package/lib/types/supervisor/storage/recovery.js +126 -0
  197. package/lib/types/supervisor/storage/recovery.js.map +1 -0
  198. package/lib/types/supervisor/storage/run-files.d.ts +67 -0
  199. package/lib/types/supervisor/storage/run-files.d.ts.map +1 -0
  200. package/lib/types/supervisor/storage/run-files.js +346 -0
  201. package/lib/types/supervisor/storage/run-files.js.map +1 -0
  202. package/lib/types/supervisor/types.d.ts +295 -0
  203. package/lib/types/supervisor/types.d.ts.map +1 -0
  204. package/lib/types/supervisor/types.js +3 -0
  205. package/lib/types/supervisor/types.js.map +1 -0
  206. package/lib/types/supervisor/value-view.d.ts +19 -0
  207. package/lib/types/supervisor/value-view.d.ts.map +1 -0
  208. package/lib/types/supervisor/value-view.js +183 -0
  209. package/lib/types/supervisor/value-view.js.map +1 -0
  210. package/lib/types/tool/index.d.ts +86 -0
  211. package/lib/types/tool/index.d.ts.map +1 -0
  212. package/lib/types/tool/index.js +810 -0
  213. package/lib/types/tool/index.js.map +1 -0
  214. package/lib/types/tool/schema.d.ts +158 -0
  215. package/lib/types/tool/schema.d.ts.map +1 -0
  216. package/lib/types/tool/schema.js +137 -0
  217. package/lib/types/tool/schema.js.map +1 -0
  218. package/lib/types/types.d.ts +9 -0
  219. package/lib/types/types.d.ts.map +1 -0
  220. package/lib/types/types.js +3 -0
  221. package/lib/types/types.js.map +1 -0
  222. package/lib/types/user-questions.d.ts +9 -0
  223. package/lib/types/user-questions.d.ts.map +1 -0
  224. package/lib/types/user-questions.js +91 -0
  225. package/lib/types/user-questions.js.map +1 -0
  226. package/package.json +273 -0
  227. package/skills/create-workflow/SKILL.md +166 -0
  228. package/vendor/workflow-engine/LICENSE +21 -0
  229. package/vendor/workflow-engine/README.md +1 -0
@@ -0,0 +1,730 @@
1
+ import { createElement, useSyncExternalStore } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import TYPERT_REMOTE from '@zaalipro/dsh-workflows/remote';
4
+ import { DashboardWorkflowRunsAdapter } from './adapter.js';
5
+ import { WorkflowRunsController } from './controller.js';
6
+ import { workflowRunDefinition } from './workflow-definition.js';
7
+ import { workflowMessageDefinition } from './chat-renderer.js';
8
+ import { createWorkflowsStore } from './store.js';
9
+ import { WorkflowsDashboardSlot, WorkflowRunChatSlot } from './slot-components.js';
10
+ import { WorkflowsDashboard } from './WorkflowsDashboard.js';
11
+ import { NS, dashboardLabelsFromLocale, workflowChatLabelsFromLocale, workflowLocaleFromBind, workflowLocales, } from './locales.js';
12
+ import { unwrapWorkflowRemoteResult } from './contract.js';
13
+ export * from './contract.js';
14
+ export * from './controller.js';
15
+ export * from './adapter.js';
16
+ export * from './store.js';
17
+ export * from './locales.js';
18
+ export * from './WorkflowsDashboard.js';
19
+ export * from './WorkflowRunPanel.js';
20
+ export * from './WorkflowMemberInspector.js';
21
+ export * from './workflow-definition.js';
22
+ /** Services consumed by the browser half of the package. */
23
+ export const inject = [
24
+ 'connection', 'remote', 'sessions', 'slots', 'conversationEvents', 'commandUi', 'inputTriggers', 'locale',
25
+ ];
26
+ function disposeValue(value) {
27
+ if (typeof value === 'function')
28
+ return value();
29
+ if (typeof value?.dispose === 'function') {
30
+ return value.dispose();
31
+ }
32
+ }
33
+ function asDisposer(value) {
34
+ if (typeof value === 'function')
35
+ return value;
36
+ if (typeof value?.dispose === 'function') {
37
+ return () => value.dispose();
38
+ }
39
+ }
40
+ /** Top-level Session ids only. Never union `byId` (addressed children). */
41
+ function sessionListIds(sessions) {
42
+ const list = sessions?.list?.getSnapshot?.();
43
+ if (list == null || typeof list !== 'object')
44
+ return undefined;
45
+ if (list.phase === 'pending' || list.status === 'pending')
46
+ return undefined;
47
+ if (!Array.isArray(list.ids))
48
+ return undefined;
49
+ const ids = list.ids.filter((value) => typeof value === 'string');
50
+ // A missing/non-ready phase with an empty id list is the pending-empty
51
+ // snapshot that must not mass-remove observed Sessions.
52
+ if (list.phase !== 'ready' && ids.length === 0)
53
+ return undefined;
54
+ return ids;
55
+ }
56
+ function directChildAvailable(sessions, parentSessionId, childSessionId) {
57
+ const catalog = sessions?.list?.getSnapshot?.()?.subagentsByParent?.[parentSessionId];
58
+ if (catalog?.state !== 'ready' || !Array.isArray(catalog.entries))
59
+ return false;
60
+ return catalog.entries.some((entry) => (entry?.kind === 'child'
61
+ && entry?.mode === 'one-shot'
62
+ && (entry.id ?? entry.childSessionId) === childSessionId
63
+ && (entry.parentSessionId ?? entry.parentId ?? parentSessionId) === parentSessionId));
64
+ }
65
+ const MAX_PICKER_DEFINITIONS = 4_096;
66
+ function requireCommandUi(commandUi) {
67
+ if (typeof commandUi !== 'object' || commandUi === null) {
68
+ throw new Error('workflow dashboard action registration is unavailable');
69
+ }
70
+ const register = commandUi.register;
71
+ const decorate = commandUi.decorate;
72
+ if (typeof register !== 'function' || typeof decorate !== 'function') {
73
+ throw new Error('workflow dashboard action registration is unavailable');
74
+ }
75
+ return commandUi;
76
+ }
77
+ /** Action-capable command UIs dispatch kind:action via runAction; stock RC2 always opens a popup. */
78
+ function commandUiDispatchesActions(commandUi) {
79
+ let current = commandUi;
80
+ while (current !== null) {
81
+ if (typeof current.runAction === 'function')
82
+ return true;
83
+ current = Object.getPrototypeOf(current);
84
+ if (current === Object.prototype)
85
+ break;
86
+ }
87
+ return false;
88
+ }
89
+ const PICKER_PAGE_LIMIT = 32;
90
+ const PICKER_TIMEOUT_MS = 2_500;
91
+ async function callDefinitionList(list, sessionId, request, signal) {
92
+ try {
93
+ return await list(sessionId, request, signal);
94
+ }
95
+ catch {
96
+ return await list(sessionId, signal);
97
+ }
98
+ }
99
+ async function callDefinitionListRpc(connection, sessionId, request, signal) {
100
+ const rpc = connection?.rpc?.call;
101
+ if (typeof rpc !== 'function')
102
+ return { items: [] };
103
+ return rpc.call(connection.rpc, '/api', 'workflowDefinitions/list', {
104
+ args: { agentId: sessionId, request },
105
+ }, signal);
106
+ }
107
+ /** Load the picker catalog; an absent or hung Remote must settle, never spin. */
108
+ async function loadPickerDefinitions(definitionsRemote, session, signal, connection) {
109
+ const list = definitionsRemote?.list ?? definitionsRemote?.['workflowDefinitions/list'];
110
+ const sessionId = String(session?.sessionId ?? '');
111
+ if (typeof list !== 'function' && typeof connection?.rpc?.call !== 'function')
112
+ return [];
113
+ const work = (async () => {
114
+ const items = [];
115
+ const seen = new Set();
116
+ let cursor;
117
+ for (let pageNo = 0; pageNo < PICKER_PAGE_LIMIT; pageNo += 1) {
118
+ signal.throwIfAborted();
119
+ const request = cursor === undefined ? { limit: 200 } : { limit: 200, cursor };
120
+ const raw = typeof list === 'function'
121
+ ? await callDefinitionList(list, sessionId, request, signal)
122
+ : await callDefinitionListRpc(connection, sessionId, request, signal);
123
+ const page = unwrapWorkflowRemoteResult(raw);
124
+ const pageItems = Array.isArray(page) ? page : Array.isArray(page?.items) ? page.items : [];
125
+ items.push(...pageItems);
126
+ if (items.length > MAX_PICKER_DEFINITIONS)
127
+ return items.slice(0, MAX_PICKER_DEFINITIONS);
128
+ const next = page?.nextCursor === undefined || page?.nextCursor === '' ? undefined : String(page.nextCursor);
129
+ if (next === undefined)
130
+ return items;
131
+ if (seen.has(next) || next === cursor)
132
+ return items;
133
+ seen.add(next);
134
+ cursor = next;
135
+ }
136
+ return items;
137
+ })();
138
+ const timeout = new Promise((_, reject) => {
139
+ const timer = setTimeout(() => reject(new Error('workflow definition picker timed out')), PICKER_TIMEOUT_MS);
140
+ const abort = () => {
141
+ clearTimeout(timer);
142
+ reject(signal.reason ?? new Error('workflow definition picker aborted'));
143
+ };
144
+ if (signal.aborted)
145
+ abort();
146
+ else
147
+ signal.addEventListener('abort', abort, { once: true });
148
+ void work.finally(() => clearTimeout(timer));
149
+ });
150
+ try {
151
+ return await Promise.race([work, timeout]);
152
+ }
153
+ catch {
154
+ return [];
155
+ }
156
+ }
157
+ function bindDashboardCatalog(remote, sessions, connection) {
158
+ return {
159
+ async listDefinitions(sessionId, signal) {
160
+ const items = await loadPickerDefinitions(remote, { sessionId }, signal ?? new AbortController().signal, connection);
161
+ const cards = [];
162
+ for (const item of items) {
163
+ const name = typeof item?.name === 'string' ? item.name : '';
164
+ if (name === '')
165
+ continue;
166
+ cards.push({
167
+ name,
168
+ description: typeof item.description === 'string' ? item.description : '',
169
+ ...(typeof item.whenToUse === 'string' ? { whenToUse: item.whenToUse } : {}),
170
+ ...(typeof item.scope === 'string' ? { scope: item.scope } : {}),
171
+ });
172
+ }
173
+ return cards;
174
+ },
175
+ async launchDefinition(sessionId, name, signal) {
176
+ signal?.throwIfAborted();
177
+ if (!/^[a-z](?:[a-z0-9]*)(?:-[a-z0-9]+)*$/u.test(name)) {
178
+ throw new Error('workflow name is invalid');
179
+ }
180
+ const live = sessions.binding?.(sessionId)?.session;
181
+ if (live === undefined || typeof live.command !== 'function') {
182
+ throw new Error('this session is not available');
183
+ }
184
+ const result = await live.command(`/workflow ${name}`);
185
+ if (result?.ok === false) {
186
+ const raw = result.error;
187
+ const message = typeof raw === 'string' && raw.length > 0
188
+ ? raw
189
+ : typeof raw === 'object' && raw !== null && typeof raw.message === 'string' && raw.message.length > 0
190
+ ? raw.message
191
+ : 'the host rejected /workflow';
192
+ throw new Error(message);
193
+ }
194
+ if (result?.value?.matched === false)
195
+ throw new Error('the host offers no /workflow command');
196
+ },
197
+ };
198
+ }
199
+ /** Read a namespace installed dynamically by Typert without traversing the
200
+ * injected `remote` aggregate. Cordis deliberately rejects that traversal
201
+ * unless the nested service was part of the plugin inject list; adding it to
202
+ * the list would deadlock because this plugin is also what mounts it. */
203
+ function mountedRemoteNamespace(root, remote, namespace) {
204
+ if (typeof root.get === 'function') {
205
+ return root.get(`remote.${namespace}`);
206
+ }
207
+ // Unit/minimal host contexts are ordinary objects rather than Cordis
208
+ // proxies, so retain their convenient nested fake shape.
209
+ return remote?.[namespace];
210
+ }
211
+ /**
212
+ * Register one complete browser aggregate. The generated Remote is mounted
213
+ * first; every consumer and listener is created in that mount's effect and
214
+ * is disposed before the contribution is unmounted.
215
+ */
216
+ export function apply(ctx) {
217
+ const root = ctx;
218
+ const startup = root.effect(async () => {
219
+ const cleanup = [];
220
+ const addCleanup = (value) => {
221
+ if (value !== undefined)
222
+ cleanup.push(value);
223
+ };
224
+ let dashboardActions;
225
+ let pendingOpen = false;
226
+ let liveAdapter;
227
+ let fallbackRoot;
228
+ let fallbackHost;
229
+ let fallbackOpen = false;
230
+ let fallbackSchedule = 0;
231
+ let slotDashboardVisible = false;
232
+ let dashboardLogicallyOpen = false;
233
+ let remoteDisposer;
234
+ let controller;
235
+ let adapter;
236
+ let rolledBack = false;
237
+ let overlayState = { invoker: null };
238
+ const overlayListeners = new Set();
239
+ const rollback = async () => {
240
+ if (rolledBack)
241
+ return;
242
+ rolledBack = true;
243
+ dashboardActions = undefined;
244
+ liveAdapter = undefined;
245
+ overlayListeners.clear();
246
+ fallbackOpen = false;
247
+ slotDashboardVisible = false;
248
+ dashboardLogicallyOpen = false;
249
+ fallbackSchedule += 1;
250
+ try {
251
+ fallbackRoot?.unmount();
252
+ }
253
+ catch { /* contained */ }
254
+ fallbackRoot = undefined;
255
+ fallbackHost?.remove();
256
+ fallbackHost = undefined;
257
+ // Reverse registration order: listeners/slots/consumers stop before
258
+ // the generated Remote namespace is unmounted.
259
+ for (const dispose of cleanup.splice(0).reverse()) {
260
+ try {
261
+ await dispose();
262
+ }
263
+ catch { /* one rollback cannot block the rest */ }
264
+ }
265
+ try {
266
+ adapter?.dispose();
267
+ }
268
+ catch { /* contained */ }
269
+ try {
270
+ controller?.dispose();
271
+ }
272
+ catch { /* contained */ }
273
+ adapter = undefined;
274
+ controller = undefined;
275
+ await Promise.resolve(disposeValue(remoteDisposer)).catch(() => undefined);
276
+ remoteDisposer = undefined;
277
+ };
278
+ const publishOverlay = (next) => {
279
+ overlayState = next;
280
+ for (const listener of [...overlayListeners])
281
+ listener();
282
+ };
283
+ const captureInvoker = (element) => {
284
+ const active = typeof document !== 'undefined' && document.activeElement instanceof HTMLElement
285
+ ? document.activeElement
286
+ : null;
287
+ publishOverlay({ invoker: element ?? active });
288
+ };
289
+ const currentSessionId = () => {
290
+ const id = root.sessions?.list?.getSnapshot?.()?.current;
291
+ return typeof id === 'string' && id.length > 0 ? id : undefined;
292
+ };
293
+ const fallbackObservationOwner = {};
294
+ const slotObservationOwner = {};
295
+ const scopedOperations = (owner) => {
296
+ if (liveAdapter === undefined)
297
+ return undefined;
298
+ return new Proxy(liveAdapter, {
299
+ get(target, property) {
300
+ if (property === 'observe') {
301
+ return (sessionId) => { target.observeFor(owner, sessionId); };
302
+ }
303
+ const value = Reflect.get(target, property, target);
304
+ // `source` is a callable external-store object whose own methods
305
+ // would be lost if Function#bind produced a replacement function.
306
+ if (property === 'source')
307
+ return value;
308
+ return typeof value === 'function' ? value.bind(target) : value;
309
+ },
310
+ });
311
+ };
312
+ let fallbackOperations;
313
+ let slotOperations;
314
+ const renderFallbackDashboard = () => {
315
+ if (typeof document === 'undefined' || liveAdapter === undefined || slotDashboardVisible)
316
+ return;
317
+ fallbackOperations ??= scopedOperations(fallbackObservationOwner);
318
+ if (fallbackHost === undefined) {
319
+ fallbackHost = document.createElement('div');
320
+ fallbackHost.id = 'dsh-workflows-overlay';
321
+ document.body.appendChild(fallbackHost);
322
+ }
323
+ fallbackRoot ??= createRoot(fallbackHost);
324
+ const sessionId = currentSessionId();
325
+ fallbackRoot.render(fallbackOpen
326
+ ? createElement(WorkflowsDashboard, {
327
+ operations: fallbackOperations,
328
+ sessionId,
329
+ open: true,
330
+ onClose: () => {
331
+ dashboardLogicallyOpen = false;
332
+ fallbackOpen = false;
333
+ fallbackSchedule += 1;
334
+ dashboardActions?.close?.();
335
+ renderFallbackDashboard();
336
+ },
337
+ labels: dashboardLabelsFromLocale(workflowLocaleFromBind(typeof root.locale?.bind === 'function' ? root.locale.bind(NS) : undefined)),
338
+ })
339
+ : createElement('div'));
340
+ };
341
+ const removeFallbackDashboard = () => {
342
+ fallbackOpen = false;
343
+ fallbackSchedule += 1;
344
+ const staleRoot = fallbackRoot;
345
+ fallbackRoot = undefined;
346
+ fallbackHost?.remove();
347
+ fallbackHost = undefined;
348
+ // Presence is reported from the shell root's layout effect. Detach the
349
+ // fallback DOM immediately (so two dialogs are never observable), then
350
+ // let React dispose its separate root after the current commit.
351
+ queueMicrotask(() => {
352
+ try {
353
+ staleRoot?.unmount();
354
+ }
355
+ catch { /* contained */ }
356
+ });
357
+ };
358
+ const reportSlotDashboardPresence = (visible) => {
359
+ slotDashboardVisible = visible;
360
+ if (visible) {
361
+ dashboardLogicallyOpen = true;
362
+ removeFallbackDashboard();
363
+ }
364
+ else {
365
+ // A committed closed state is authoritative and cancels any portal
366
+ // scheduled by the action which produced that state transition.
367
+ dashboardLogicallyOpen = false;
368
+ fallbackSchedule += 1;
369
+ }
370
+ };
371
+ const reportSlotDashboardUnmount = () => {
372
+ slotDashboardVisible = false;
373
+ // A shell remount/navigation must not make an open dashboard vanish.
374
+ // Restore the portal only while the store is still logically open;
375
+ // normal close first clears that flag through `close`/presence.
376
+ if (dashboardLogicallyOpen)
377
+ scheduleFallbackDashboard();
378
+ };
379
+ const scheduleFallbackDashboard = () => {
380
+ if (rolledBack || !dashboardLogicallyOpen || typeof document === 'undefined'
381
+ || liveAdapter === undefined || slotDashboardVisible)
382
+ return;
383
+ const scheduled = ++fallbackSchedule;
384
+ // Give the shell's external store update a microtask to commit its slot.
385
+ // A later slot mount still reports presence and atomically removes an
386
+ // already-created fallback; a shell which never renders gets the portal.
387
+ queueMicrotask(() => {
388
+ if (scheduled !== fallbackSchedule)
389
+ return;
390
+ if (rolledBack || !dashboardLogicallyOpen || slotDashboardVisible)
391
+ return;
392
+ fallbackOpen = true;
393
+ renderFallbackDashboard();
394
+ });
395
+ };
396
+ let dispatchesActions = false;
397
+ const openDashboard = () => {
398
+ if (dashboardActions !== undefined && typeof dashboardActions.open === 'function') {
399
+ const active = typeof document !== 'undefined' && document.activeElement instanceof HTMLElement
400
+ ? document.activeElement
401
+ : null;
402
+ captureInvoker(active);
403
+ dashboardActions.open();
404
+ dashboardLogicallyOpen = true;
405
+ pendingOpen = false;
406
+ scheduleFallbackDashboard();
407
+ return true;
408
+ }
409
+ pendingOpen = true;
410
+ if (liveAdapter === undefined || typeof document === 'undefined') {
411
+ return dashboardActions !== undefined && typeof dashboardActions.open === 'function';
412
+ }
413
+ dashboardLogicallyOpen = true;
414
+ fallbackOpen = true;
415
+ renderFallbackDashboard();
416
+ pendingOpen = false;
417
+ return fallbackHost !== undefined
418
+ || (dashboardActions !== undefined && typeof dashboardActions.open === 'function');
419
+ };
420
+ try {
421
+ addCleanup(root.locale?.register?.(NS, workflowLocales));
422
+ const commandUi = requireCommandUi(root.commandUi);
423
+ const translate = typeof root.locale?.bind === 'function' ? root.locale.bind(NS) : undefined;
424
+ const workflowsDescription = typeof translate === 'function'
425
+ ? String(translate('commandDescription'))
426
+ : workflowLocales.en.commandDescription;
427
+ // Stock dsh always opens popupSelect for client contributions, including
428
+ // kind:'action'. A Host /workflows command plus command/executed (and the
429
+ // durable command node) opens the overlay without colliding with that picker.
430
+ // Keep a contribution only when the runtime actually dispatches actions.
431
+ dispatchesActions = commandUiDispatchesActions(commandUi);
432
+ if (dispatchesActions) {
433
+ addCleanup(asDisposer(commandUi.register({
434
+ name: 'workflows',
435
+ description: workflowsDescription,
436
+ available: () => true,
437
+ ui: {
438
+ kind: 'action',
439
+ run: () => {
440
+ if (!openDashboard()) {
441
+ throw new Error('workflow dashboard overlay is not mounted');
442
+ }
443
+ },
444
+ },
445
+ })));
446
+ }
447
+ // Enter adjudication is independent of commandUi's picker/action plane.
448
+ // Even action-capable shells can submit an exact typed token through the
449
+ // composer before commandUi gets a chance to dispatch it. Keep one source
450
+ // in every runtime so bare Enter is claimed locally and can never fall
451
+ // through to a Host/model turn. In action-capable shells commandUi remains
452
+ // the sole menu candidate/pick owner, avoiding duplicate visible actions.
453
+ const registerSource = root.inputTriggers?.registerSource;
454
+ if (typeof registerSource !== 'function') {
455
+ throw new Error('workflow dashboard slash action registration is unavailable');
456
+ }
457
+ const actionSource = {
458
+ trigger: '/',
459
+ name: 'workflows',
460
+ order: -100,
461
+ showGroupTitle: false,
462
+ async candidates(_session, request) {
463
+ if (dispatchesActions || request.position !== 'leading')
464
+ return [];
465
+ const query = request.query.toLowerCase();
466
+ if (query !== '' && !'workflows'.includes(query))
467
+ return [];
468
+ return [{ name: 'workflows', description: workflowsDescription }];
469
+ },
470
+ onPick(pick) {
471
+ if (dispatchesActions || pick.position !== 'leading')
472
+ return undefined;
473
+ if (!openDashboard())
474
+ return undefined;
475
+ // The input-trigger pipeline applies this replacement through its
476
+ // span CAS, so a successful menu action consumes only the exact
477
+ // slash token and cannot append a Host command lifecycle row.
478
+ return { text: '' };
479
+ },
480
+ async matchEnter(_session, line, signal, envelope) {
481
+ if (line !== '/workflows') {
482
+ if (/^\/workflows\s/u.test(line)) {
483
+ throw new Error('the /workflows dashboard action accepts no arguments');
484
+ }
485
+ return undefined;
486
+ }
487
+ signal.throwIfAborted();
488
+ if (envelope.images > 0) {
489
+ throw new Error('the /workflows dashboard action does not accept images');
490
+ }
491
+ // A claim gives the conversation input machine an ordinary submit
492
+ // transaction: dashboard open is the commit and kind:success clears
493
+ // the exact bare-token draft. No Host Remote or model sink is called.
494
+ return {
495
+ claim: {
496
+ token: '/workflows',
497
+ async submit() {
498
+ if (!openDashboard()) {
499
+ return { kind: 'error', text: 'workflow dashboard overlay is not mounted' };
500
+ }
501
+ return { kind: 'success' };
502
+ },
503
+ },
504
+ };
505
+ },
506
+ };
507
+ addCleanup(asDisposer(registerSource.call(root.inputTriggers, actionSource)));
508
+ const remote = root.remote;
509
+ const sessions = root.sessions;
510
+ // Dynamic namespaces do not exist until the contribution is mounted. Use
511
+ // the RPC fallback until then rather than reading them via the traced
512
+ // aggregate (which is an undeclared-service error in real Cordis).
513
+ const liveController = new WorkflowRunsController({}, sessions, root.connection);
514
+ const adapterInstance = new DashboardWorkflowRunsAdapter(liveController);
515
+ let definitionsRemote;
516
+ const catalog = bindDashboardCatalog({
517
+ get list() { return definitionsRemote?.list; },
518
+ get ['workflowDefinitions/list']() { return definitionsRemote?.['workflowDefinitions/list']; },
519
+ }, sessions, root.connection);
520
+ adapterInstance.listDefinitions = catalog.listDefinitions;
521
+ adapterInstance.launchDefinition = catalog.launchDefinition;
522
+ liveAdapter = adapterInstance;
523
+ controller = liveController;
524
+ adapter = adapterInstance;
525
+ slotOperations = scopedOperations(slotObservationOwner);
526
+ if (pendingOpen)
527
+ openDashboard();
528
+ addCleanup(root.conversationEvents?.register?.(workflowMessageDefinition));
529
+ if (root.conversationEvents !== undefined && root.conversationEvents.register !== undefined
530
+ && workflowMessageDefinition !== workflowRunDefinition) {
531
+ // Keep the named definition visible to older consumers that inspect the
532
+ // package export rather than the keyed renderer registry.
533
+ addCleanup(root.conversationEvents.register(workflowRunDefinition));
534
+ }
535
+ const runChatComponent = (props) => {
536
+ const dict = workflowLocaleFromBind(typeof root.locale?.bind === 'function' ? root.locale.bind(NS) : undefined);
537
+ return createElement(WorkflowRunChatSlot, {
538
+ ...props,
539
+ operations: adapterInstance,
540
+ useSessions: props.useSessions,
541
+ labels: workflowChatLabelsFromLocale(dict),
542
+ childAvailable: (parent, child) => directChildAvailable(sessions, parent, child),
543
+ });
544
+ };
545
+ const chatInjection = root.slots?.inject?.('conversation.chat.node', () => root.slots.register({
546
+ name: 'conversation.chat.node',
547
+ key: 'workflow-run',
548
+ locale: NS,
549
+ inject: () => ({
550
+ operations: adapterInstance,
551
+ childAvailable: (parent, child) => directChildAvailable(sessions, parent, child),
552
+ }),
553
+ }, runChatComponent));
554
+ addCleanup(chatInjection);
555
+ /** Root-scoped overlay component; slot standard hooks remain framework-owned. */
556
+ function DashboardContribution(props) {
557
+ if (props.actions !== undefined)
558
+ dashboardActions = props.actions;
559
+ const list = sessions.list;
560
+ const sessionId = useSyncExternalStore(list.subscribe.bind(list), () => list.getSnapshot().current, () => list.getSnapshot().current);
561
+ const overlay = useSyncExternalStore(listener => { overlayListeners.add(listener); return () => { overlayListeners.delete(listener); }; }, () => overlayState, () => overlayState);
562
+ const source = useSyncExternalStore(adapterInstance.source.subscribe, adapterInstance.source.getSnapshot, adapterInstance.source.getSnapshot);
563
+ const dict = workflowLocaleFromBind(typeof root.locale?.bind === 'function' ? root.locale.bind(NS) : undefined);
564
+ const close = () => {
565
+ dashboardLogicallyOpen = false;
566
+ fallbackSchedule += 1;
567
+ publishOverlay({ invoker: overlay.invoker });
568
+ props.actions?.close?.();
569
+ };
570
+ return createElement(WorkflowsDashboardSlot, {
571
+ ...props,
572
+ useSessions: props.useSessions ?? ((selector) => selector({ current: sessionId })),
573
+ useStore: props.useStore ?? ((selector) => selector({
574
+ open: false,
575
+ selectedRunId: undefined,
576
+ selectedMemberId: undefined,
577
+ selectedArtifactName: undefined,
578
+ inspectorTab: 'members',
579
+ mobileView: 'runs',
580
+ })),
581
+ useWorkflowRuns: props.useWorkflowRuns ?? ((selector) => selector(source)),
582
+ actions: props.actions ?? {
583
+ open: () => undefined,
584
+ close: () => undefined,
585
+ selectRun: () => undefined,
586
+ reconcileRun: () => undefined,
587
+ selectMember: () => undefined,
588
+ selectArtifact: () => undefined,
589
+ selectTab: () => undefined,
590
+ showRuns: () => undefined,
591
+ showExecution: () => undefined,
592
+ showRun: () => undefined,
593
+ },
594
+ operations: slotOperations ?? adapterInstance,
595
+ invoker: overlay.invoker,
596
+ onClose: close,
597
+ onPresenceChange: reportSlotDashboardPresence,
598
+ onUnmount: reportSlotDashboardUnmount,
599
+ labels: dashboardLabelsFromLocale(dict),
600
+ });
601
+ }
602
+ const overlayInjection = root.slots?.inject?.('shell.overlay', () => root.slots.register({
603
+ name: 'shell.overlay',
604
+ id: 'workflows-dashboard',
605
+ order: 100,
606
+ locale: NS,
607
+ store: createWorkflowsStore,
608
+ inject: (actions) => {
609
+ if (actions !== undefined && typeof actions.open === 'function') {
610
+ dashboardActions = actions;
611
+ if (pendingOpen) {
612
+ pendingOpen = false;
613
+ actions.open();
614
+ }
615
+ }
616
+ return { operations: slotOperations ?? adapterInstance, hooks: { workflowRuns: adapterInstance.source } };
617
+ },
618
+ }, DashboardContribution));
619
+ addCleanup(overlayInjection);
620
+ // Overlay and command-row registration must complete before this await.
621
+ // $mount has to run on this fiber (not a detached then) so the generated
622
+ // workflowRuns namespace actually installs.
623
+ if (typeof remote?.$mount === 'function') {
624
+ try {
625
+ remoteDisposer = await remote.$mount(TYPERT_REMOTE);
626
+ }
627
+ catch {
628
+ remoteDisposer = undefined;
629
+ }
630
+ }
631
+ definitionsRemote = mountedRemoteNamespace(root, remote, 'workflowDefinitions');
632
+ const runsRemote = mountedRemoteNamespace(root, remote, 'workflowRuns');
633
+ if (runsRemote !== undefined)
634
+ liveController.setRemote(runsRemote);
635
+ if (pendingOpen)
636
+ openDashboard();
637
+ if (typeof definitionsRemote?.list === 'function')
638
+ addCleanup(asDisposer(commandUi.decorate({
639
+ name: 'workflow',
640
+ available: () => true,
641
+ ui: {
642
+ kind: 'popupSelect',
643
+ options: async (session, signal) => {
644
+ const definitions = await loadPickerDefinitions(definitionsRemote, session, signal, root.connection);
645
+ return definitions.map((definition) => ({
646
+ id: String(definition.name),
647
+ label: String(definition.name),
648
+ detail: `${String(definition.description ?? '')}${definition.whenToUse === undefined ? '' : ` — ${String(definition.whenToUse)}`} · ${String(definition.scope ?? '')}`,
649
+ }));
650
+ },
651
+ onSelect: async (option, session) => {
652
+ const binding = sessions.binding?.(session.sessionId);
653
+ const live = binding?.session;
654
+ if (live === undefined)
655
+ throw new Error('this session is not available');
656
+ const result = await live.command(`/workflow ${String(option.id)}`);
657
+ if (result?.ok === false) {
658
+ throw new Error(typeof result.error === 'string' && result.error.length > 0
659
+ ? result.error
660
+ : 'the host rejected /workflow');
661
+ }
662
+ if (result?.value?.matched === false)
663
+ throw new Error('the host offers no /workflow command');
664
+ },
665
+ },
666
+ })));
667
+ // The generated event transport is deliberately invalidation-only. Do
668
+ // not copy run heads from the event into browser state.
669
+ const remoteOn = remote.$on;
670
+ if (typeof remoteOn === 'function') {
671
+ addCleanup(remoteOn.call(remote, 'workflows/run-change', change => liveController.handleChange(change)));
672
+ }
673
+ const hostDescription = root.connection?.hostDescription;
674
+ if (hostDescription?.subscribe !== undefined) {
675
+ addCleanup(hostDescription.subscribe(() => {
676
+ if (hostDescription.getSnapshot?.() === undefined)
677
+ liveController.handleDisconnected();
678
+ else
679
+ liveController.handleConnected();
680
+ }));
681
+ if (hostDescription.getSnapshot?.() === undefined)
682
+ liveController.handleDisconnected();
683
+ }
684
+ if (typeof root.on === 'function') {
685
+ const registered = root.on('connection/reset', () => liveController.handleReset());
686
+ if (typeof registered === 'function')
687
+ addCleanup(registered);
688
+ }
689
+ if (sessions.list?.subscribe !== undefined) {
690
+ let previous = new Set(sessionListIds(sessions) ?? []);
691
+ addCleanup(sessions.list.subscribe(() => {
692
+ const current = sessionListIds(sessions);
693
+ if (current === undefined)
694
+ return;
695
+ const keys = new Set(current);
696
+ for (const id of previous)
697
+ if (!keys.has(id))
698
+ liveController.removeSession(id);
699
+ previous = keys;
700
+ }));
701
+ }
702
+ }
703
+ catch (error) {
704
+ // Every setup phase, including the pre-controller registrations, owns
705
+ // the same rollback transaction. Never swallow an unexpected setup
706
+ // failure: a resolved effect would otherwise make the plugin look active
707
+ // while its /workflows source had already been removed, allowing a typed
708
+ // token to fall through to a model turn. Expected optional failures
709
+ // (notably Remote projection mount) are handled at their call site.
710
+ await rollback();
711
+ throw error;
712
+ }
713
+ return rollback;
714
+ }, 'dsh-workflows: client aggregate');
715
+ // `ctx.effect()` starts immediately, but its callable return value is also
716
+ // a thenable that settles only after an async effect has finished setting
717
+ // up. The plugin callback must expose that setup barrier to Cordis. If it
718
+ // is discarded, Cordis can mark the plugin ACTIVE before (for example) the
719
+ // Typert mount resumes, and a later setup rejection merely tears down this
720
+ // nested effect without failing the plugin fiber.
721
+ //
722
+ // Do not return the disposer yielded by awaiting the thenable. The effect
723
+ // wrapper is already owned by this fiber and will be unloaded with it;
724
+ // returning that disposer as a second outer effect would duplicate
725
+ // ownership. `Promise.resolve` also preserves the minimal plain-object
726
+ // fixtures whose `effect()` shim returns `undefined` while separately
727
+ // recording the callback promise.
728
+ return Promise.resolve(startup).then(() => undefined);
729
+ }
730
+ //# sourceMappingURL=index.js.map