@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.
- package/LICENSE +21 -0
- package/NOTICE.md +224 -0
- package/README.i18n.yaml +4 -0
- package/README.md +109 -0
- package/README.zh.md +109 -0
- package/cordis.patch.yml +10 -0
- package/docs/architecture.i18n.yaml +4 -0
- package/docs/architecture.md +177 -0
- package/docs/architecture.zh.md +177 -0
- package/docs/testing.i18n.yaml +4 -0
- package/docs/testing.md +125 -0
- package/docs/testing.zh.md +125 -0
- package/docs/user-guide.i18n.yaml +4 -0
- package/docs/user-guide.md +351 -0
- package/docs/user-guide.zh.md +351 -0
- package/lib/client-types/WorkflowMemberInspector.d.ts +28 -0
- package/lib/client-types/WorkflowMemberInspector.d.ts.map +1 -0
- package/lib/client-types/WorkflowMemberInspector.js +181 -0
- package/lib/client-types/WorkflowMemberInspector.js.map +1 -0
- package/lib/client-types/WorkflowRunPanel.d.ts +43 -0
- package/lib/client-types/WorkflowRunPanel.d.ts.map +1 -0
- package/lib/client-types/WorkflowRunPanel.js +130 -0
- package/lib/client-types/WorkflowRunPanel.js.map +1 -0
- package/lib/client-types/WorkflowsDashboard.d.ts +49 -0
- package/lib/client-types/WorkflowsDashboard.d.ts.map +1 -0
- package/lib/client-types/WorkflowsDashboard.js +942 -0
- package/lib/client-types/WorkflowsDashboard.js.map +1 -0
- package/lib/client-types/adapter.d.ts +60 -0
- package/lib/client-types/adapter.d.ts.map +1 -0
- package/lib/client-types/adapter.js +117 -0
- package/lib/client-types/adapter.js.map +1 -0
- package/lib/client-types/chat-renderer.d.ts +10 -0
- package/lib/client-types/chat-renderer.d.ts.map +1 -0
- package/lib/client-types/chat-renderer.js +11 -0
- package/lib/client-types/chat-renderer.js.map +1 -0
- package/lib/client-types/contract.d.ts +210 -0
- package/lib/client-types/contract.d.ts.map +1 -0
- package/lib/client-types/contract.js +37 -0
- package/lib/client-types/contract.js.map +1 -0
- package/lib/client-types/controller.d.ts +83 -0
- package/lib/client-types/controller.d.ts.map +1 -0
- package/lib/client-types/controller.js +705 -0
- package/lib/client-types/controller.js.map +1 -0
- package/lib/client-types/index.d.ts +19 -0
- package/lib/client-types/index.d.ts.map +1 -0
- package/lib/client-types/index.js +730 -0
- package/lib/client-types/index.js.map +1 -0
- package/lib/client-types/locales.d.ts +257 -0
- package/lib/client-types/locales.d.ts.map +1 -0
- package/lib/client-types/locales.js +270 -0
- package/lib/client-types/locales.js.map +1 -0
- package/lib/client-types/slot-components.d.ts +38 -0
- package/lib/client-types/slot-components.d.ts.map +1 -0
- package/lib/client-types/slot-components.js +22 -0
- package/lib/client-types/slot-components.js.map +1 -0
- package/lib/client-types/store.d.ts +69 -0
- package/lib/client-types/store.d.ts.map +1 -0
- package/lib/client-types/store.js +135 -0
- package/lib/client-types/store.js.map +1 -0
- package/lib/client-types/workflow-definition.d.ts +52 -0
- package/lib/client-types/workflow-definition.d.ts.map +1 -0
- package/lib/client-types/workflow-definition.js +135 -0
- package/lib/client-types/workflow-definition.js.map +1 -0
- package/lib/client.js +11064 -0
- package/lib/client.js.map +1 -0
- package/lib/compat-engine/index.js +1926 -0
- package/lib/compat-engine/index.js.map +1 -0
- package/lib/compat-engine/worker.cjs +1500 -0
- package/lib/compat-engine/worker.cjs.map +1 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +1709 -0
- package/lib/typert.remote-client.d.ts +57 -0
- package/lib/typert.remote-client.js +1662 -0
- package/lib/types/commands/aliases.d.ts +4 -0
- package/lib/types/commands/aliases.d.ts.map +1 -0
- package/lib/types/commands/aliases.js +25 -0
- package/lib/types/commands/aliases.js.map +1 -0
- package/lib/types/commands/index.d.ts +66 -0
- package/lib/types/commands/index.d.ts.map +1 -0
- package/lib/types/commands/index.js +550 -0
- package/lib/types/commands/index.js.map +1 -0
- package/lib/types/commands/parser.d.ts +17 -0
- package/lib/types/commands/parser.d.ts.map +1 -0
- package/lib/types/commands/parser.js +45 -0
- package/lib/types/commands/parser.js.map +1 -0
- package/lib/types/config.d.ts +71 -0
- package/lib/types/config.d.ts.map +1 -0
- package/lib/types/config.js +109 -0
- package/lib/types/config.js.map +1 -0
- package/lib/types/index.d.ts +41 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +483 -0
- package/lib/types/index.js.map +1 -0
- package/lib/types/invariant.d.ts +21 -0
- package/lib/types/invariant.d.ts.map +1 -0
- package/lib/types/invariant.js +85 -0
- package/lib/types/invariant.js.map +1 -0
- package/lib/types/registry/definition.d.ts +24 -0
- package/lib/types/registry/definition.d.ts.map +1 -0
- package/lib/types/registry/definition.js +189 -0
- package/lib/types/registry/definition.js.map +1 -0
- package/lib/types/registry/index.d.ts +100 -0
- package/lib/types/registry/index.d.ts.map +1 -0
- package/lib/types/registry/index.js +899 -0
- package/lib/types/registry/index.js.map +1 -0
- package/lib/types/registry/names.d.ts +19 -0
- package/lib/types/registry/names.d.ts.map +1 -0
- package/lib/types/registry/names.js +69 -0
- package/lib/types/registry/names.js.map +1 -0
- package/lib/types/registry/remote.d.ts +15 -0
- package/lib/types/registry/remote.d.ts.map +1 -0
- package/lib/types/registry/remote.js +128 -0
- package/lib/types/registry/remote.js.map +1 -0
- package/lib/types/registry/roots.d.ts +44 -0
- package/lib/types/registry/roots.d.ts.map +1 -0
- package/lib/types/registry/roots.js +118 -0
- package/lib/types/registry/roots.js.map +1 -0
- package/lib/types/registry/types.d.ts +62 -0
- package/lib/types/registry/types.d.ts.map +1 -0
- package/lib/types/registry/types.js +2 -0
- package/lib/types/registry/types.js.map +1 -0
- package/lib/types/registry/watchers.d.ts +70 -0
- package/lib/types/registry/watchers.d.ts.map +1 -0
- package/lib/types/registry/watchers.js +152 -0
- package/lib/types/registry/watchers.js.map +1 -0
- package/lib/types/remote-events.d.ts +42 -0
- package/lib/types/remote-events.d.ts.map +1 -0
- package/lib/types/remote-events.js +21 -0
- package/lib/types/remote-events.js.map +1 -0
- package/lib/types/run-recorder.d.ts +44 -0
- package/lib/types/run-recorder.d.ts.map +1 -0
- package/lib/types/run-recorder.js +356 -0
- package/lib/types/run-recorder.js.map +1 -0
- package/lib/types/supervisor/canned-validate.d.ts +25 -0
- package/lib/types/supervisor/canned-validate.d.ts.map +1 -0
- package/lib/types/supervisor/canned-validate.js +122 -0
- package/lib/types/supervisor/canned-validate.js.map +1 -0
- package/lib/types/supervisor/completion-notice.d.ts +49 -0
- package/lib/types/supervisor/completion-notice.d.ts.map +1 -0
- package/lib/types/supervisor/completion-notice.js +281 -0
- package/lib/types/supervisor/completion-notice.js.map +1 -0
- package/lib/types/supervisor/cursors.d.ts +68 -0
- package/lib/types/supervisor/cursors.d.ts.map +1 -0
- package/lib/types/supervisor/cursors.js +158 -0
- package/lib/types/supervisor/cursors.js.map +1 -0
- package/lib/types/supervisor/engine-compat.d.ts +48 -0
- package/lib/types/supervisor/engine-compat.d.ts.map +1 -0
- package/lib/types/supervisor/engine-compat.js +116 -0
- package/lib/types/supervisor/engine-compat.js.map +1 -0
- package/lib/types/supervisor/index.d.ts +183 -0
- package/lib/types/supervisor/index.d.ts.map +1 -0
- package/lib/types/supervisor/index.js +2040 -0
- package/lib/types/supervisor/index.js.map +1 -0
- package/lib/types/supervisor/parallel-compat.d.ts +36 -0
- package/lib/types/supervisor/parallel-compat.d.ts.map +1 -0
- package/lib/types/supervisor/parallel-compat.js +180 -0
- package/lib/types/supervisor/parallel-compat.js.map +1 -0
- package/lib/types/supervisor/remote.d.ts +25 -0
- package/lib/types/supervisor/remote.d.ts.map +1 -0
- package/lib/types/supervisor/remote.js +478 -0
- package/lib/types/supervisor/remote.js.map +1 -0
- package/lib/types/supervisor/storage/bounded-file.d.ts +41 -0
- package/lib/types/supervisor/storage/bounded-file.d.ts.map +1 -0
- package/lib/types/supervisor/storage/bounded-file.js +186 -0
- package/lib/types/supervisor/storage/bounded-file.js.map +1 -0
- package/lib/types/supervisor/storage/details-codec.d.ts +14 -0
- package/lib/types/supervisor/storage/details-codec.d.ts.map +1 -0
- package/lib/types/supervisor/storage/details-codec.js +359 -0
- package/lib/types/supervisor/storage/details-codec.js.map +1 -0
- package/lib/types/supervisor/storage/index.d.ts +26 -0
- package/lib/types/supervisor/storage/index.d.ts.map +1 -0
- package/lib/types/supervisor/storage/index.js +100 -0
- package/lib/types/supervisor/storage/index.js.map +1 -0
- package/lib/types/supervisor/storage/lease.d.ts +34 -0
- package/lib/types/supervisor/storage/lease.d.ts.map +1 -0
- package/lib/types/supervisor/storage/lease.js +289 -0
- package/lib/types/supervisor/storage/lease.js.map +1 -0
- package/lib/types/supervisor/storage/manifest-codec.d.ts +10 -0
- package/lib/types/supervisor/storage/manifest-codec.d.ts.map +1 -0
- package/lib/types/supervisor/storage/manifest-codec.js +335 -0
- package/lib/types/supervisor/storage/manifest-codec.js.map +1 -0
- package/lib/types/supervisor/storage/manifest-store.d.ts +99 -0
- package/lib/types/supervisor/storage/manifest-store.d.ts.map +1 -0
- package/lib/types/supervisor/storage/manifest-store.js +1606 -0
- package/lib/types/supervisor/storage/manifest-store.js.map +1 -0
- package/lib/types/supervisor/storage/manifest-types.d.ts +269 -0
- package/lib/types/supervisor/storage/manifest-types.d.ts.map +1 -0
- package/lib/types/supervisor/storage/manifest-types.js +2 -0
- package/lib/types/supervisor/storage/manifest-types.js.map +1 -0
- package/lib/types/supervisor/storage/private-root.d.ts +143 -0
- package/lib/types/supervisor/storage/private-root.d.ts.map +1 -0
- package/lib/types/supervisor/storage/private-root.js +860 -0
- package/lib/types/supervisor/storage/private-root.js.map +1 -0
- package/lib/types/supervisor/storage/recovery.d.ts +14 -0
- package/lib/types/supervisor/storage/recovery.d.ts.map +1 -0
- package/lib/types/supervisor/storage/recovery.js +126 -0
- package/lib/types/supervisor/storage/recovery.js.map +1 -0
- package/lib/types/supervisor/storage/run-files.d.ts +67 -0
- package/lib/types/supervisor/storage/run-files.d.ts.map +1 -0
- package/lib/types/supervisor/storage/run-files.js +346 -0
- package/lib/types/supervisor/storage/run-files.js.map +1 -0
- package/lib/types/supervisor/types.d.ts +295 -0
- package/lib/types/supervisor/types.d.ts.map +1 -0
- package/lib/types/supervisor/types.js +3 -0
- package/lib/types/supervisor/types.js.map +1 -0
- package/lib/types/supervisor/value-view.d.ts +19 -0
- package/lib/types/supervisor/value-view.d.ts.map +1 -0
- package/lib/types/supervisor/value-view.js +183 -0
- package/lib/types/supervisor/value-view.js.map +1 -0
- package/lib/types/tool/index.d.ts +86 -0
- package/lib/types/tool/index.d.ts.map +1 -0
- package/lib/types/tool/index.js +810 -0
- package/lib/types/tool/index.js.map +1 -0
- package/lib/types/tool/schema.d.ts +158 -0
- package/lib/types/tool/schema.d.ts.map +1 -0
- package/lib/types/tool/schema.js +137 -0
- package/lib/types/tool/schema.js.map +1 -0
- package/lib/types/types.d.ts +9 -0
- package/lib/types/types.d.ts.map +1 -0
- package/lib/types/types.js +3 -0
- package/lib/types/types.js.map +1 -0
- package/lib/types/user-questions.d.ts +9 -0
- package/lib/types/user-questions.d.ts.map +1 -0
- package/lib/types/user-questions.js +91 -0
- package/lib/types/user-questions.js.map +1 -0
- package/package.json +273 -0
- package/skills/create-workflow/SKILL.md +166 -0
- package/vendor/workflow-engine/LICENSE +21 -0
- 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
|