@rytass/bpm-core-shared 0.3.0 → 0.5.0
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/CHANGELOG.md +12 -0
- package/eslint.config.mjs +19 -0
- package/jest.config.cts +10 -0
- package/package.json +2 -3
- package/project.json +49 -0
- package/src/lib/condition.ts +55 -0
- package/src/lib/form.ts +77 -0
- package/src/lib/identity.ts +25 -0
- package/src/lib/organization.ts +52 -0
- package/src/lib/shared.spec.ts +13 -0
- package/src/lib/status.ts +29 -0
- package/src/lib/workflow-command.spec.ts +278 -0
- package/src/lib/workflow-command.ts +848 -0
- package/src/lib/workflow-graph.ts +974 -0
- package/src/lib/workflow-toolset.spec.ts +230 -0
- package/src/lib/workflow-toolset.ts +1083 -0
- package/src/lib/workflow.ts +216 -0
- package/tsconfig.json +23 -0
- package/tsconfig.lib.json +16 -0
- package/tsconfig.spec.json +16 -0
- package/src/index.js +0 -13
- package/src/index.js.map +0 -1
- package/src/lib/condition.d.ts +0 -43
- package/src/lib/condition.js +0 -3
- package/src/lib/condition.js.map +0 -1
- package/src/lib/form.d.ts +0 -48
- package/src/lib/form.js +0 -3
- package/src/lib/form.js.map +0 -1
- package/src/lib/identity.d.ts +0 -24
- package/src/lib/identity.js +0 -3
- package/src/lib/identity.js.map +0 -1
- package/src/lib/organization.d.ts +0 -47
- package/src/lib/organization.js +0 -3
- package/src/lib/organization.js.map +0 -1
- package/src/lib/status.d.ts +0 -7
- package/src/lib/status.js +0 -3
- package/src/lib/status.js.map +0 -1
- package/src/lib/workflow-command.d.ts +0 -156
- package/src/lib/workflow-command.js +0 -292
- package/src/lib/workflow-command.js.map +0 -1
- package/src/lib/workflow-graph.d.ts +0 -96
- package/src/lib/workflow-graph.js +0 -605
- package/src/lib/workflow-graph.js.map +0 -1
- package/src/lib/workflow-toolset.d.ts +0 -105
- package/src/lib/workflow-toolset.js +0 -702
- package/src/lib/workflow-toolset.js.map +0 -1
- package/src/lib/workflow.d.ts +0 -160
- package/src/lib/workflow.js +0 -3
- package/src/lib/workflow.js.map +0 -1
- /package/src/{index.d.ts → index.ts} +0 -0
|
@@ -1,702 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WORKFLOW_TOOL_BY_NAME = exports.WORKFLOW_TOOLSET = void 0;
|
|
4
|
-
exports.readWorkflowSnapshot = readWorkflowSnapshot;
|
|
5
|
-
exports.executeWorkflowTool = executeWorkflowTool;
|
|
6
|
-
const tslib_1 = require("tslib");
|
|
7
|
-
const workflow_graph_1 = require("./workflow-graph");
|
|
8
|
-
const workflow_command_1 = require("./workflow-command");
|
|
9
|
-
// ── Reusable schema fragments ──────────────────────────────────────────────
|
|
10
|
-
const NODE_TYPE_ENUM = ['userTask', 'serviceTask', 'exclusiveGateway'];
|
|
11
|
-
const TRIGGER_MODE_ENUM = ['AND', 'OR'];
|
|
12
|
-
const RESUBMIT_STRATEGY_ENUM = [
|
|
13
|
-
'FROM_RETURN_POINT',
|
|
14
|
-
'RESTART',
|
|
15
|
-
];
|
|
16
|
-
const CONDITION_OPERATOR_ENUM = workflow_graph_1.CONDITION_OPERATOR_OPTIONS.map((option) => option.id);
|
|
17
|
-
const APPROVER_RESOLVER_SCHEMA = {
|
|
18
|
-
description: '簽核人員解析策略,以 type 區分。DIRECT: { type, memberIds[] };POSITION: { type, positionId };' +
|
|
19
|
-
'ORG_UNIT_MEMBER: { type, orgUnitId, includeDescendants? };ORG_UNIT_POSITION: { type, orgUnitId, positionId, includeDescendants? };' +
|
|
20
|
-
'ORG_MANAGER: { type, baseFromInitiator, levelsUp, fallback? };ORG_UNIT_MANAGER: { type, orgUnitId, fallback? };' +
|
|
21
|
-
'DYNAMIC_FORM: { type, formPath };EXPRESSION: { type, expression }。',
|
|
22
|
-
properties: {
|
|
23
|
-
type: {
|
|
24
|
-
enum: [
|
|
25
|
-
'DIRECT',
|
|
26
|
-
'POSITION',
|
|
27
|
-
'ORG_UNIT_MEMBER',
|
|
28
|
-
'ORG_UNIT_POSITION',
|
|
29
|
-
'ORG_MANAGER',
|
|
30
|
-
'ORG_UNIT_MANAGER',
|
|
31
|
-
'DYNAMIC_FORM',
|
|
32
|
-
'EXPRESSION',
|
|
33
|
-
],
|
|
34
|
-
type: 'string',
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
required: ['type'],
|
|
38
|
-
type: 'object',
|
|
39
|
-
};
|
|
40
|
-
const SERVICE_ACTION_SCHEMA = {
|
|
41
|
-
description: '系統節點動作,以 type 區分。NOTIFY: { type, channels[], recipients(ApproverResolver), template? };' +
|
|
42
|
-
'WEBHOOK: { type, url, headers?, payload? };SET_FORM_FIELD: { type, fieldPath, value }。',
|
|
43
|
-
properties: {
|
|
44
|
-
type: { enum: ['NOTIFY', 'WEBHOOK', 'SET_FORM_FIELD'], type: 'string' },
|
|
45
|
-
},
|
|
46
|
-
required: ['type'],
|
|
47
|
-
type: 'object',
|
|
48
|
-
};
|
|
49
|
-
function objectSchema(properties, required) {
|
|
50
|
-
return { additionalProperties: false, properties, required, type: 'object' };
|
|
51
|
-
}
|
|
52
|
-
// ── Tool catalog ───────────────────────────────────────────────────────────
|
|
53
|
-
exports.WORKFLOW_TOOLSET = [
|
|
54
|
-
{
|
|
55
|
-
name: 'add_node',
|
|
56
|
-
description: '在流程中新增一個節點(簽核/系統/條件分流)。可選擇插入錨點:anchorEdgeId 會把節點插入該連線中間;anchorAfterNodeId 會接在該節點之後;皆未指定時沿用目前選取或追加到末端。',
|
|
57
|
-
inputSchema: objectSchema({
|
|
58
|
-
nodeType: { enum: [...NODE_TYPE_ENUM], type: 'string' },
|
|
59
|
-
anchorEdgeId: { type: ['string', 'null'] },
|
|
60
|
-
anchorAfterNodeId: { type: ['string', 'null'] },
|
|
61
|
-
}, ['nodeType']),
|
|
62
|
-
kind: 'mutation',
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
name: 'rename_node',
|
|
66
|
-
description: '修改節點顯示名稱。',
|
|
67
|
-
inputSchema: objectSchema({ nodeId: { type: 'string' }, label: { type: 'string' } }, ['nodeId', 'label']),
|
|
68
|
-
kind: 'mutation',
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
name: 'delete_node',
|
|
72
|
-
description: '刪除節點及其相連的連線(開始/結束節點不可刪除)。',
|
|
73
|
-
inputSchema: objectSchema({ nodeId: { type: 'string' } }, ['nodeId']),
|
|
74
|
-
kind: 'mutation',
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
name: 'set_node_trigger_mode',
|
|
78
|
-
description: '設定節點觸發模式:AND=所有前置完成才觸發;OR=任一前置完成即觸發(僅多重輸入時有意義)。',
|
|
79
|
-
inputSchema: objectSchema({
|
|
80
|
-
nodeId: { type: 'string' },
|
|
81
|
-
triggerMode: { enum: [...TRIGGER_MODE_ENUM], type: 'string' },
|
|
82
|
-
}, ['nodeId', 'triggerMode']),
|
|
83
|
-
kind: 'mutation',
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: 'set_user_task_approver',
|
|
87
|
-
description: '設定簽核節點的簽核人員(會將決策政策重設為 SINGLE)。approverResolver 可省略或不完整 —— 此時預設為「直屬主管」(ORG_MANAGER,往上 1 層)。',
|
|
88
|
-
inputSchema: objectSchema({ nodeId: { type: 'string' }, approverResolver: APPROVER_RESOLVER_SCHEMA }, ['nodeId']),
|
|
89
|
-
kind: 'mutation',
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
name: 'set_user_task_options',
|
|
93
|
-
description: '設定簽核節點的選項開關與描述:是否允許駁回/轉簽/加簽,以及節點描述。只更新有提供的欄位。',
|
|
94
|
-
inputSchema: objectSchema({
|
|
95
|
-
nodeId: { type: 'string' },
|
|
96
|
-
allowReject: { type: 'boolean' },
|
|
97
|
-
allowTransfer: { type: 'boolean' },
|
|
98
|
-
allowAddSigner: { type: 'boolean' },
|
|
99
|
-
description: { type: 'string' },
|
|
100
|
-
}, ['nodeId']),
|
|
101
|
-
kind: 'mutation',
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
name: 'set_user_task_return_resubmit_strategy',
|
|
105
|
-
description: '設定簽核節點退回後的重送策略:RESTART=從頭重跑;FROM_RETURN_POINT=回到退回節點。',
|
|
106
|
-
inputSchema: objectSchema({
|
|
107
|
-
nodeId: { type: 'string' },
|
|
108
|
-
resubmitStrategy: { enum: [...RESUBMIT_STRATEGY_ENUM], type: 'string' },
|
|
109
|
-
}, ['nodeId', 'resubmitStrategy']),
|
|
110
|
-
kind: 'mutation',
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
name: 'set_service_action',
|
|
114
|
-
description: '設定系統節點的動作(NOTIFY 知會 / WEBHOOK / SET_FORM_FIELD)。',
|
|
115
|
-
inputSchema: objectSchema({ nodeId: { type: 'string' }, action: SERVICE_ACTION_SCHEMA }, ['nodeId', 'action']),
|
|
116
|
-
kind: 'mutation',
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
name: 'set_end_state',
|
|
120
|
-
description: '設定結束節點的最終狀態(APPROVED / REJECTED)。',
|
|
121
|
-
inputSchema: objectSchema({
|
|
122
|
-
nodeId: { type: 'string' },
|
|
123
|
-
endState: { enum: ['APPROVED', 'REJECTED'], type: 'string' },
|
|
124
|
-
}, ['nodeId', 'endState']),
|
|
125
|
-
kind: 'mutation',
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
name: 'connect_edge',
|
|
129
|
-
description: '在兩個節點之間建立連線(source → target)。若 source 不可輸出或 target 不可輸入則拒絕。',
|
|
130
|
-
inputSchema: objectSchema({ source: { type: 'string' }, target: { type: 'string' } }, ['source', 'target']),
|
|
131
|
-
kind: 'mutation',
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
name: 'delete_edge',
|
|
135
|
-
description: '刪除一條連線。',
|
|
136
|
-
inputSchema: objectSchema({ edgeId: { type: 'string' } }, ['edgeId']),
|
|
137
|
-
kind: 'mutation',
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
name: 'set_edge_condition',
|
|
141
|
-
description: '設定條件分流輸出連線的條件(依綁定表單欄位)。fieldKey 為表單欄位、operator 為比較運算子、value 為比較值;系統會自動編譯為 CEL 條件並產生標籤。需先綁定表單。',
|
|
142
|
-
inputSchema: objectSchema({
|
|
143
|
-
edgeId: { type: 'string' },
|
|
144
|
-
fieldKey: { type: ['string', 'null'] },
|
|
145
|
-
operator: { enum: [...CONDITION_OPERATOR_ENUM, null], type: ['string', 'null'] },
|
|
146
|
-
value: { type: ['string', 'null'] },
|
|
147
|
-
}, ['edgeId']),
|
|
148
|
-
kind: 'mutation',
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
name: 'set_edge_default',
|
|
152
|
-
description: '將條件分流的某條輸出連線設為(或取消)預設路徑;設為預設時會自動取消同源其他連線的預設。',
|
|
153
|
-
inputSchema: objectSchema({ edgeId: { type: 'string' }, isDefault: { type: 'boolean' } }, ['edgeId', 'isDefault']),
|
|
154
|
-
kind: 'mutation',
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
name: 'set_initiator_policy_cel',
|
|
158
|
-
description: '直接設定發起人政策的 CEL 條件式(傳 null 代表清除/不限制)。',
|
|
159
|
-
inputSchema: objectSchema({ cel: { type: ['string', 'null'] } }, ['cel']),
|
|
160
|
-
kind: 'mutation',
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
name: 'auto_layout',
|
|
164
|
-
description: '對整個流程套用自動排版(由前端 dagre 重新計算節點座標)。',
|
|
165
|
-
inputSchema: objectSchema({}, []),
|
|
166
|
-
kind: 'mutation',
|
|
167
|
-
},
|
|
168
|
-
// ── Macros ──
|
|
169
|
-
{
|
|
170
|
-
name: 'insert_approval_step',
|
|
171
|
-
description: '高階:新增一個簽核關卡。approverResolver 可省略 —— 省略或資料不全時,預設為「直屬主管」(ORG_MANAGER,從申請人往上 1 層),不需要真實會員 id。畫初稿時建議直接省略簽核人、先把流程結構建出來。',
|
|
172
|
-
inputSchema: objectSchema({
|
|
173
|
-
approverResolver: APPROVER_RESOLVER_SCHEMA,
|
|
174
|
-
label: { type: 'string' },
|
|
175
|
-
anchorEdgeId: { type: ['string', 'null'] },
|
|
176
|
-
anchorAfterNodeId: { type: ['string', 'null'] },
|
|
177
|
-
}, []),
|
|
178
|
-
kind: 'macro',
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
name: 'insert_notification',
|
|
182
|
-
description: '高階:新增一個系統通知關卡。action 可省略 —— 省略或資料不全時,預設為站內通知(NOTIFY / IN_APP / 收件者為直屬主管)。',
|
|
183
|
-
inputSchema: objectSchema({
|
|
184
|
-
action: SERVICE_ACTION_SCHEMA,
|
|
185
|
-
label: { type: 'string' },
|
|
186
|
-
anchorEdgeId: { type: ['string', 'null'] },
|
|
187
|
-
anchorAfterNodeId: { type: ['string', 'null'] },
|
|
188
|
-
}, []),
|
|
189
|
-
kind: 'macro',
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
name: 'insert_conditional_branch',
|
|
193
|
-
description: '高階:新增一個條件分流節點(可選 label 與插入錨點)。',
|
|
194
|
-
inputSchema: objectSchema({
|
|
195
|
-
label: { type: 'string' },
|
|
196
|
-
anchorEdgeId: { type: ['string', 'null'] },
|
|
197
|
-
anchorAfterNodeId: { type: ['string', 'null'] },
|
|
198
|
-
}, []),
|
|
199
|
-
kind: 'macro',
|
|
200
|
-
},
|
|
201
|
-
// ── Queries ──
|
|
202
|
-
{
|
|
203
|
-
name: 'get_workflow_snapshot',
|
|
204
|
-
description: '讀取目前流程的精簡快照:所有節點、連線、選取狀態、表單綁定、發起人政策、以及驗證問題。動作前應先呼叫以了解現況。',
|
|
205
|
-
inputSchema: objectSchema({}, []),
|
|
206
|
-
kind: 'query',
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
name: 'describe_node_types',
|
|
210
|
-
description: '取得可用節點型別與其資料結構說明。',
|
|
211
|
-
inputSchema: objectSchema({}, []),
|
|
212
|
-
kind: 'query',
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
name: 'list_form_fields',
|
|
216
|
-
description: '列出目前綁定表單的欄位(fieldKey、型別、標籤),供設定條件分流條件時參考。',
|
|
217
|
-
inputSchema: objectSchema({}, []),
|
|
218
|
-
kind: 'query',
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
name: 'validate_workflow',
|
|
222
|
-
description: '檢查目前流程設定是否完整,回傳問題描述(無問題則為 null)。',
|
|
223
|
-
inputSchema: objectSchema({}, []),
|
|
224
|
-
kind: 'query',
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
name: 'list_org_units',
|
|
228
|
-
description: '列出組織單位(id、名稱),供指定 ORG_UNIT_MEMBER / ORG_UNIT_POSITION / ORG_UNIT_MANAGER 簽核人時取得真實 orgUnitId。',
|
|
229
|
-
inputSchema: objectSchema({}, []),
|
|
230
|
-
kind: 'query',
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
name: 'list_positions',
|
|
234
|
-
description: '列出職位(id、名稱),供指定 POSITION / ORG_UNIT_POSITION 簽核人時取得真實 positionId。',
|
|
235
|
-
inputSchema: objectSchema({}, []),
|
|
236
|
-
kind: 'query',
|
|
237
|
-
},
|
|
238
|
-
{
|
|
239
|
-
name: 'search_members',
|
|
240
|
-
description: '依關鍵字搜尋會員(回傳 id、姓名、email),供指定 DIRECT 簽核人或通知收件者時取得真實 memberId。使用者點名某人時先用這個查到 id 再設定。',
|
|
241
|
-
inputSchema: objectSchema({ query: { type: 'string' } }, ['query']),
|
|
242
|
-
kind: 'query',
|
|
243
|
-
},
|
|
244
|
-
];
|
|
245
|
-
/** Quick lookup of a tool definition by name. */
|
|
246
|
-
exports.WORKFLOW_TOOL_BY_NAME = new Map(exports.WORKFLOW_TOOLSET.map((tool) => [tool.name, tool]));
|
|
247
|
-
function readWorkflowSnapshot(state) {
|
|
248
|
-
return {
|
|
249
|
-
edges: state.definition.edges.map((edge) => readEdgeSnapshot(edge, state.definition.nodes)),
|
|
250
|
-
formDefinitionVersionId: state.formDefinitionVersionId,
|
|
251
|
-
initiatorPolicyCel: state.initiatorPolicyCel,
|
|
252
|
-
issue: readWorkflowIssue(state),
|
|
253
|
-
nodes: state.definition.nodes.map(readNodeSnapshot),
|
|
254
|
-
selectedEdgeIds: state.selectedEdgeIds,
|
|
255
|
-
selectedNodeId: state.selectedNodeId,
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
function readWorkflowIssue(state) {
|
|
259
|
-
return (0, workflow_graph_1.readWorkflowDefinitionIssue)(state.definition);
|
|
260
|
-
}
|
|
261
|
-
function readNodeSnapshot(node) {
|
|
262
|
-
return {
|
|
263
|
-
id: node.id,
|
|
264
|
-
label: node.data.label,
|
|
265
|
-
summary: readNodeSummary(node),
|
|
266
|
-
type: node.type,
|
|
267
|
-
typeLabel: workflow_graph_1.WORKFLOW_NODE_TYPE_LABELS[node.type],
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
function readNodeSummary(node) {
|
|
271
|
-
var _a, _b;
|
|
272
|
-
if (node.type === 'userTask') {
|
|
273
|
-
return `簽核人=${node.data.approverResolver.type};觸發=${(_a = node.data.triggerMode) !== null && _a !== void 0 ? _a : 'AND'}`;
|
|
274
|
-
}
|
|
275
|
-
if (node.type === 'serviceTask') {
|
|
276
|
-
return `動作=${node.data.action.type}`;
|
|
277
|
-
}
|
|
278
|
-
if (node.type === 'endEvent') {
|
|
279
|
-
return `結束狀態=${(_b = node.data.endState) !== null && _b !== void 0 ? _b : 'APPROVED'}`;
|
|
280
|
-
}
|
|
281
|
-
if (node.type === 'exclusiveGateway' || node.type === 'parallelGateway') {
|
|
282
|
-
return `方向=${node.data.direction}`;
|
|
283
|
-
}
|
|
284
|
-
return '開始';
|
|
285
|
-
}
|
|
286
|
-
function readEdgeSnapshot(edge, nodes) {
|
|
287
|
-
var _a, _b;
|
|
288
|
-
return {
|
|
289
|
-
condition: (_a = edge.data.condition) !== null && _a !== void 0 ? _a : null,
|
|
290
|
-
conditionLabel: (_b = edge.data.label) !== null && _b !== void 0 ? _b : null,
|
|
291
|
-
id: edge.id,
|
|
292
|
-
isDefault: Boolean(edge.data.isDefault && (0, workflow_graph_1.isExclusiveGatewaySourceEdge)(edge, nodes)),
|
|
293
|
-
source: edge.source,
|
|
294
|
-
target: edge.target,
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Execute a single LLM tool call against the designer state. Mutations and
|
|
299
|
-
* macros return the new state + a fresh snapshot; queries return read-only data.
|
|
300
|
-
* Async because directory query tools may fetch from the host (GraphQL).
|
|
301
|
-
*/
|
|
302
|
-
function executeWorkflowTool(state_1, toolName_1, rawInput_1) {
|
|
303
|
-
return tslib_1.__awaiter(this, arguments, void 0, function* (state, toolName, rawInput, options = {}) {
|
|
304
|
-
const tool = exports.WORKFLOW_TOOL_BY_NAME.get(toolName);
|
|
305
|
-
if (!tool) {
|
|
306
|
-
return { error: `未知的工具:${toolName}`, ok: false };
|
|
307
|
-
}
|
|
308
|
-
const input = isRecord(rawInput) ? rawInput : {};
|
|
309
|
-
try {
|
|
310
|
-
if (tool.kind === 'query') {
|
|
311
|
-
return {
|
|
312
|
-
data: yield runQueryTool(state, toolName, input, options),
|
|
313
|
-
kind: 'query',
|
|
314
|
-
ok: true,
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
if (tool.kind === 'macro') {
|
|
318
|
-
const macro = buildMacroCommand(toolName, input);
|
|
319
|
-
const result = (0, workflow_command_1.applyWorkflowMacroCommand)(state, macro, options);
|
|
320
|
-
return finalizeMutation('macro', result);
|
|
321
|
-
}
|
|
322
|
-
const command = buildPrimitiveCommand(toolName, input);
|
|
323
|
-
const result = (0, workflow_command_1.applyWorkflowCommand)(state, command, options);
|
|
324
|
-
return finalizeMutation('mutation', result);
|
|
325
|
-
}
|
|
326
|
-
catch (error) {
|
|
327
|
-
return { error: readToolError(error), ok: false };
|
|
328
|
-
}
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
function finalizeMutation(kind, result) {
|
|
332
|
-
if (result.error) {
|
|
333
|
-
return { error: result.error, ok: false };
|
|
334
|
-
}
|
|
335
|
-
return {
|
|
336
|
-
kind,
|
|
337
|
-
ok: true,
|
|
338
|
-
result,
|
|
339
|
-
snapshot: readWorkflowSnapshot(result.state),
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
function runQueryTool(state, toolName, input, options) {
|
|
343
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
344
|
-
var _a, _b, _c;
|
|
345
|
-
if (toolName === 'get_workflow_snapshot') {
|
|
346
|
-
return readWorkflowSnapshot(state);
|
|
347
|
-
}
|
|
348
|
-
if (toolName === 'describe_node_types') {
|
|
349
|
-
return readNodeTypeCatalog();
|
|
350
|
-
}
|
|
351
|
-
if (toolName === 'list_form_fields') {
|
|
352
|
-
return readFormFieldCatalog(state.formSchema);
|
|
353
|
-
}
|
|
354
|
-
if (toolName === 'list_org_units') {
|
|
355
|
-
return readDirectoryResult((_a = options.directory) === null || _a === void 0 ? void 0 : _a.listOrgUnits());
|
|
356
|
-
}
|
|
357
|
-
if (toolName === 'list_positions') {
|
|
358
|
-
return readDirectoryResult((_b = options.directory) === null || _b === void 0 ? void 0 : _b.listPositions());
|
|
359
|
-
}
|
|
360
|
-
if (toolName === 'search_members') {
|
|
361
|
-
const query = typeof input['query'] === 'string' ? input['query'] : '';
|
|
362
|
-
return readDirectoryResult((_c = options.directory) === null || _c === void 0 ? void 0 : _c.searchMembers(query));
|
|
363
|
-
}
|
|
364
|
-
// validate_workflow
|
|
365
|
-
return { issue: readWorkflowIssue(state) };
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
function readDirectoryResult(pending) {
|
|
369
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
370
|
-
if (!pending) {
|
|
371
|
-
return { available: false, items: [] };
|
|
372
|
-
}
|
|
373
|
-
return { available: true, items: yield pending };
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
// ── Command builders (parse LLM input → typed command) ──────────────────────
|
|
377
|
-
function buildPrimitiveCommand(toolName, input) {
|
|
378
|
-
switch (toolName) {
|
|
379
|
-
case 'add_node':
|
|
380
|
-
return {
|
|
381
|
-
anchor: readAnchor(input),
|
|
382
|
-
nodeType: readEnum(input, 'nodeType', NODE_TYPE_ENUM),
|
|
383
|
-
type: 'addNode',
|
|
384
|
-
};
|
|
385
|
-
case 'rename_node':
|
|
386
|
-
return {
|
|
387
|
-
label: readString(input, 'label'),
|
|
388
|
-
nodeId: readString(input, 'nodeId'),
|
|
389
|
-
type: 'renameNode',
|
|
390
|
-
};
|
|
391
|
-
case 'delete_node':
|
|
392
|
-
return { nodeId: readString(input, 'nodeId'), type: 'deleteNode' };
|
|
393
|
-
case 'set_node_trigger_mode':
|
|
394
|
-
return {
|
|
395
|
-
nodeId: readString(input, 'nodeId'),
|
|
396
|
-
triggerMode: readEnum(input, 'triggerMode', TRIGGER_MODE_ENUM),
|
|
397
|
-
type: 'setNodeTriggerMode',
|
|
398
|
-
};
|
|
399
|
-
case 'set_user_task_approver':
|
|
400
|
-
return {
|
|
401
|
-
approverResolver: parseApproverResolver(input['approverResolver']),
|
|
402
|
-
nodeId: readString(input, 'nodeId'),
|
|
403
|
-
type: 'setUserTaskApprover',
|
|
404
|
-
};
|
|
405
|
-
case 'set_user_task_options':
|
|
406
|
-
return Object.assign(Object.assign(Object.assign(Object.assign({ nodeId: readString(input, 'nodeId'), type: 'setUserTaskOptions' }, readOptionalBoolean(input, 'allowReject', 'allowReject')), readOptionalBoolean(input, 'allowTransfer', 'allowTransfer')), readOptionalBoolean(input, 'allowAddSigner', 'allowAddSigner')), readOptionalString(input, 'description', 'description'));
|
|
407
|
-
case 'set_user_task_return_resubmit_strategy':
|
|
408
|
-
return {
|
|
409
|
-
nodeId: readString(input, 'nodeId'),
|
|
410
|
-
resubmitStrategy: readEnum(input, 'resubmitStrategy', RESUBMIT_STRATEGY_ENUM),
|
|
411
|
-
type: 'setUserTaskReturnResubmitStrategy',
|
|
412
|
-
};
|
|
413
|
-
case 'set_service_action':
|
|
414
|
-
return {
|
|
415
|
-
action: parseServiceAction(input['action']),
|
|
416
|
-
nodeId: readString(input, 'nodeId'),
|
|
417
|
-
type: 'setServiceAction',
|
|
418
|
-
};
|
|
419
|
-
case 'set_end_state':
|
|
420
|
-
return {
|
|
421
|
-
endState: readEnum(input, 'endState', ['APPROVED', 'REJECTED']),
|
|
422
|
-
nodeId: readString(input, 'nodeId'),
|
|
423
|
-
type: 'setEndState',
|
|
424
|
-
};
|
|
425
|
-
case 'connect_edge':
|
|
426
|
-
return {
|
|
427
|
-
source: readString(input, 'source'),
|
|
428
|
-
target: readString(input, 'target'),
|
|
429
|
-
type: 'connectEdge',
|
|
430
|
-
};
|
|
431
|
-
case 'delete_edge':
|
|
432
|
-
return { edgeId: readString(input, 'edgeId'), type: 'deleteEdge' };
|
|
433
|
-
case 'set_edge_condition':
|
|
434
|
-
return {
|
|
435
|
-
edgeId: readString(input, 'edgeId'),
|
|
436
|
-
type: 'setEdgeCondition',
|
|
437
|
-
fieldKey: readNullableString(input, 'fieldKey'),
|
|
438
|
-
operator: readNullableEnum(input, 'operator', CONDITION_OPERATOR_ENUM),
|
|
439
|
-
value: readNullableString(input, 'value'),
|
|
440
|
-
};
|
|
441
|
-
case 'set_edge_default':
|
|
442
|
-
return {
|
|
443
|
-
edgeId: readString(input, 'edgeId'),
|
|
444
|
-
isDefault: readBoolean(input, 'isDefault'),
|
|
445
|
-
type: 'setEdgeDefault',
|
|
446
|
-
};
|
|
447
|
-
case 'set_initiator_policy_cel':
|
|
448
|
-
return {
|
|
449
|
-
cel: readNullableString(input, 'cel'),
|
|
450
|
-
type: 'setInitiatorPolicyCel',
|
|
451
|
-
};
|
|
452
|
-
case 'auto_layout':
|
|
453
|
-
return { type: 'autoLayout' };
|
|
454
|
-
default:
|
|
455
|
-
throw new Error(`工具 ${toolName} 尚未支援。`);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
function buildMacroCommand(toolName, input) {
|
|
459
|
-
if (toolName === 'insert_approval_step') {
|
|
460
|
-
return Object.assign({ anchor: readAnchor(input), approverResolver: parseApproverResolver(input['approverResolver']), type: 'insertApprovalStep' }, readOptionalString(input, 'label', 'label'));
|
|
461
|
-
}
|
|
462
|
-
if (toolName === 'insert_notification') {
|
|
463
|
-
return Object.assign({ action: parseServiceAction(input['action']), anchor: readAnchor(input), type: 'insertNotification' }, readOptionalString(input, 'label', 'label'));
|
|
464
|
-
}
|
|
465
|
-
return Object.assign({ anchor: readAnchor(input), type: 'insertConditionalBranch' }, readOptionalString(input, 'label', 'label'));
|
|
466
|
-
}
|
|
467
|
-
// ── Static catalogs for query tools ─────────────────────────────────────────
|
|
468
|
-
function readNodeTypeCatalog() {
|
|
469
|
-
return {
|
|
470
|
-
nodeTypes: [
|
|
471
|
-
{
|
|
472
|
-
dataShape: 'label, approverResolver, decisionPolicy, returnBehavior, allowReject, allowTransfer, allowAddSigner, triggerMode, sla?, notification?',
|
|
473
|
-
label: workflow_graph_1.WORKFLOW_NODE_TYPE_LABELS.userTask,
|
|
474
|
-
type: 'userTask',
|
|
475
|
-
},
|
|
476
|
-
{
|
|
477
|
-
dataShape: 'label, action(NOTIFY|WEBHOOK|SET_FORM_FIELD), triggerMode',
|
|
478
|
-
label: workflow_graph_1.WORKFLOW_NODE_TYPE_LABELS.serviceTask,
|
|
479
|
-
type: 'serviceTask',
|
|
480
|
-
},
|
|
481
|
-
{
|
|
482
|
-
dataShape: 'label, direction(split|join), triggerMode;輸出連線需設定條件',
|
|
483
|
-
label: workflow_graph_1.WORKFLOW_NODE_TYPE_LABELS.exclusiveGateway,
|
|
484
|
-
type: 'exclusiveGateway',
|
|
485
|
-
},
|
|
486
|
-
{
|
|
487
|
-
dataShape: 'label(系統內建,固定存在)',
|
|
488
|
-
label: workflow_graph_1.WORKFLOW_NODE_TYPE_LABELS.startEvent,
|
|
489
|
-
type: 'startEvent',
|
|
490
|
-
},
|
|
491
|
-
{
|
|
492
|
-
dataShape: 'label, endState(APPROVED|REJECTED)',
|
|
493
|
-
label: workflow_graph_1.WORKFLOW_NODE_TYPE_LABELS.endEvent,
|
|
494
|
-
type: 'endEvent',
|
|
495
|
-
},
|
|
496
|
-
],
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
function readFormFieldCatalog(schema) {
|
|
500
|
-
var _a;
|
|
501
|
-
return {
|
|
502
|
-
fields: (_a = schema === null || schema === void 0 ? void 0 : schema.fields.map((field) => ({
|
|
503
|
-
fieldKey: field.fieldKey,
|
|
504
|
-
label: field.label,
|
|
505
|
-
required: field.required,
|
|
506
|
-
type: field.type,
|
|
507
|
-
}))) !== null && _a !== void 0 ? _a : [],
|
|
508
|
-
hasBoundForm: Boolean(schema),
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
// ── Input parsing helpers (no `any`) ────────────────────────────────────────
|
|
512
|
-
function isRecord(value) {
|
|
513
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
514
|
-
}
|
|
515
|
-
function readString(input, key) {
|
|
516
|
-
const value = input[key];
|
|
517
|
-
if (typeof value !== 'string' || value.length === 0) {
|
|
518
|
-
throw new Error(`參數 ${key} 必須為非空字串。`);
|
|
519
|
-
}
|
|
520
|
-
return value;
|
|
521
|
-
}
|
|
522
|
-
function readBoolean(input, key) {
|
|
523
|
-
const value = input[key];
|
|
524
|
-
if (typeof value !== 'boolean') {
|
|
525
|
-
throw new Error(`參數 ${key} 必須為布林值。`);
|
|
526
|
-
}
|
|
527
|
-
return value;
|
|
528
|
-
}
|
|
529
|
-
function readEnum(input, key, allowed) {
|
|
530
|
-
const value = input[key];
|
|
531
|
-
if (typeof value !== 'string' || !allowed.includes(value)) {
|
|
532
|
-
throw new Error(`參數 ${key} 必須為:${allowed.join(' / ')}。`);
|
|
533
|
-
}
|
|
534
|
-
return value;
|
|
535
|
-
}
|
|
536
|
-
function readNullableString(input, key) {
|
|
537
|
-
const value = input[key];
|
|
538
|
-
if (value === undefined || value === null) {
|
|
539
|
-
return null;
|
|
540
|
-
}
|
|
541
|
-
if (typeof value !== 'string') {
|
|
542
|
-
throw new Error(`參數 ${key} 必須為字串或 null。`);
|
|
543
|
-
}
|
|
544
|
-
return value;
|
|
545
|
-
}
|
|
546
|
-
function readNullableEnum(input, key, allowed) {
|
|
547
|
-
const value = input[key];
|
|
548
|
-
if (value === undefined || value === null) {
|
|
549
|
-
return null;
|
|
550
|
-
}
|
|
551
|
-
if (typeof value !== 'string' || !allowed.includes(value)) {
|
|
552
|
-
throw new Error(`參數 ${key} 必須為:${allowed.join(' / ')} 或 null。`);
|
|
553
|
-
}
|
|
554
|
-
return value;
|
|
555
|
-
}
|
|
556
|
-
function readOptionalBoolean(input, key, outKey) {
|
|
557
|
-
const value = input[key];
|
|
558
|
-
if (value === undefined) {
|
|
559
|
-
return {};
|
|
560
|
-
}
|
|
561
|
-
if (typeof value !== 'boolean') {
|
|
562
|
-
throw new Error(`參數 ${key} 必須為布林值。`);
|
|
563
|
-
}
|
|
564
|
-
return { [outKey]: value };
|
|
565
|
-
}
|
|
566
|
-
function readOptionalString(input, key, outKey) {
|
|
567
|
-
const value = input[key];
|
|
568
|
-
if (value === undefined) {
|
|
569
|
-
return {};
|
|
570
|
-
}
|
|
571
|
-
if (typeof value !== 'string') {
|
|
572
|
-
throw new Error(`參數 ${key} 必須為字串。`);
|
|
573
|
-
}
|
|
574
|
-
return { [outKey]: value };
|
|
575
|
-
}
|
|
576
|
-
function readAnchor(input) {
|
|
577
|
-
return {
|
|
578
|
-
afterNodeId: readNullableString(input, 'anchorAfterNodeId'),
|
|
579
|
-
edgeId: readNullableString(input, 'anchorEdgeId'),
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
/**
|
|
583
|
-
* The always-valid default approver: the initiator's direct manager (one level
|
|
584
|
-
* up). Needs no member/org id, so a draft built purely from this passes
|
|
585
|
-
* validation — letting the assistant draw a flow from one sentence.
|
|
586
|
-
*/
|
|
587
|
-
const DEFAULT_MANAGER_RESOLVER = {
|
|
588
|
-
baseFromInitiator: true,
|
|
589
|
-
levelsUp: 1,
|
|
590
|
-
type: 'ORG_MANAGER',
|
|
591
|
-
};
|
|
592
|
-
const DEFAULT_NOTIFY_ACTION = {
|
|
593
|
-
channels: ['IN_APP'],
|
|
594
|
-
recipients: DEFAULT_MANAGER_RESOLVER,
|
|
595
|
-
type: 'NOTIFY',
|
|
596
|
-
};
|
|
597
|
-
/**
|
|
598
|
-
* Coerce an LLM-supplied approver resolver into a valid one, **never throwing**.
|
|
599
|
-
* When required ids are missing (the model can't know real member/org ids), we
|
|
600
|
-
* fall back to {@link DEFAULT_MANAGER_RESOLVER} so the operation still succeeds;
|
|
601
|
-
* `validate_workflow` surfaces anything that still needs the user's attention.
|
|
602
|
-
*/
|
|
603
|
-
function parseApproverResolver(value) {
|
|
604
|
-
if (!isRecord(value)) {
|
|
605
|
-
return DEFAULT_MANAGER_RESOLVER;
|
|
606
|
-
}
|
|
607
|
-
switch (value['type']) {
|
|
608
|
-
case 'DIRECT': {
|
|
609
|
-
const memberIds = readStringArray(value['memberIds']);
|
|
610
|
-
return memberIds.length > 0
|
|
611
|
-
? { memberIds, type: 'DIRECT' }
|
|
612
|
-
: DEFAULT_MANAGER_RESOLVER;
|
|
613
|
-
}
|
|
614
|
-
case 'POSITION': {
|
|
615
|
-
const positionId = readNonEmptyString(value['positionId']);
|
|
616
|
-
return positionId
|
|
617
|
-
? { positionId, type: 'POSITION' }
|
|
618
|
-
: DEFAULT_MANAGER_RESOLVER;
|
|
619
|
-
}
|
|
620
|
-
case 'ORG_UNIT_MEMBER': {
|
|
621
|
-
const orgUnitId = readNonEmptyString(value['orgUnitId']);
|
|
622
|
-
return orgUnitId
|
|
623
|
-
? Object.assign({ orgUnitId, type: 'ORG_UNIT_MEMBER' }, readOptionalBoolean(value, 'includeDescendants', 'includeDescendants')) : DEFAULT_MANAGER_RESOLVER;
|
|
624
|
-
}
|
|
625
|
-
case 'ORG_UNIT_POSITION': {
|
|
626
|
-
const orgUnitId = readNonEmptyString(value['orgUnitId']);
|
|
627
|
-
const positionId = readNonEmptyString(value['positionId']);
|
|
628
|
-
return orgUnitId && positionId
|
|
629
|
-
? Object.assign({ orgUnitId,
|
|
630
|
-
positionId, type: 'ORG_UNIT_POSITION' }, readOptionalBoolean(value, 'includeDescendants', 'includeDescendants')) : DEFAULT_MANAGER_RESOLVER;
|
|
631
|
-
}
|
|
632
|
-
case 'ORG_MANAGER':
|
|
633
|
-
return {
|
|
634
|
-
baseFromInitiator: value['baseFromInitiator'] !== false,
|
|
635
|
-
levelsUp: readPositiveInteger(value['levelsUp'], 1),
|
|
636
|
-
type: 'ORG_MANAGER',
|
|
637
|
-
};
|
|
638
|
-
case 'ORG_UNIT_MANAGER': {
|
|
639
|
-
const orgUnitId = readNonEmptyString(value['orgUnitId']);
|
|
640
|
-
return orgUnitId
|
|
641
|
-
? { orgUnitId, type: 'ORG_UNIT_MANAGER' }
|
|
642
|
-
: DEFAULT_MANAGER_RESOLVER;
|
|
643
|
-
}
|
|
644
|
-
case 'DYNAMIC_FORM': {
|
|
645
|
-
const formPath = readNonEmptyString(value['formPath']);
|
|
646
|
-
return formPath
|
|
647
|
-
? { formPath, type: 'DYNAMIC_FORM' }
|
|
648
|
-
: DEFAULT_MANAGER_RESOLVER;
|
|
649
|
-
}
|
|
650
|
-
case 'EXPRESSION': {
|
|
651
|
-
const expression = readNonEmptyString(value['expression']);
|
|
652
|
-
return expression
|
|
653
|
-
? { expression, type: 'EXPRESSION' }
|
|
654
|
-
: DEFAULT_MANAGER_RESOLVER;
|
|
655
|
-
}
|
|
656
|
-
default:
|
|
657
|
-
return DEFAULT_MANAGER_RESOLVER;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
/** Coerce an LLM-supplied service action, **never throwing** (defaults to NOTIFY). */
|
|
661
|
-
function parseServiceAction(value) {
|
|
662
|
-
if (!isRecord(value)) {
|
|
663
|
-
return DEFAULT_NOTIFY_ACTION;
|
|
664
|
-
}
|
|
665
|
-
if (value['type'] === 'WEBHOOK') {
|
|
666
|
-
const url = readNonEmptyString(value['url']);
|
|
667
|
-
return url
|
|
668
|
-
? Object.assign({ type: 'WEBHOOK', url }, readOptionalString(value, 'payload', 'payload')) : DEFAULT_NOTIFY_ACTION;
|
|
669
|
-
}
|
|
670
|
-
if (value['type'] === 'SET_FORM_FIELD') {
|
|
671
|
-
const fieldPath = readNonEmptyString(value['fieldPath']);
|
|
672
|
-
const fieldValue = readNonEmptyString(value['value']);
|
|
673
|
-
return fieldPath && fieldValue
|
|
674
|
-
? { fieldPath, type: 'SET_FORM_FIELD', value: fieldValue }
|
|
675
|
-
: DEFAULT_NOTIFY_ACTION;
|
|
676
|
-
}
|
|
677
|
-
// NOTIFY (default for any other / missing type).
|
|
678
|
-
return Object.assign({ channels: readNotifyChannels(value['channels']), recipients: parseApproverResolver(value['recipients']), type: 'NOTIFY' }, readOptionalString(value, 'template', 'template'));
|
|
679
|
-
}
|
|
680
|
-
function readStringArray(value) {
|
|
681
|
-
return Array.isArray(value)
|
|
682
|
-
? value.filter((item) => typeof item === 'string' && item.length > 0)
|
|
683
|
-
: [];
|
|
684
|
-
}
|
|
685
|
-
function readNonEmptyString(value) {
|
|
686
|
-
return typeof value === 'string' && value.trim().length > 0 ? value : null;
|
|
687
|
-
}
|
|
688
|
-
function readPositiveInteger(value, fallback) {
|
|
689
|
-
return typeof value === 'number' && Number.isInteger(value) && value > 0
|
|
690
|
-
? value
|
|
691
|
-
: fallback;
|
|
692
|
-
}
|
|
693
|
-
function readNotifyChannels(value) {
|
|
694
|
-
const channels = Array.isArray(value)
|
|
695
|
-
? value.filter((item) => item === 'IN_APP' || item === 'EMAIL')
|
|
696
|
-
: [];
|
|
697
|
-
return channels.length > 0 ? channels : ['IN_APP'];
|
|
698
|
-
}
|
|
699
|
-
function readToolError(error) {
|
|
700
|
-
return error instanceof Error ? error.message : '工具執行發生未知錯誤。';
|
|
701
|
-
}
|
|
702
|
-
//# sourceMappingURL=workflow-toolset.js.map
|