weaver-work-cli 0.1.29 → 0.1.31
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/dist/chunks/archive-U766FTFH.js +2537 -0
- package/dist/chunks/archive-oa-DMWEKX3L.js +1317 -0
- package/dist/chunks/asset-TCR7VDEV.js +2109 -0
- package/dist/chunks/attachment-content-AQNX5N3S.js +23 -0
- package/dist/chunks/blog-EVZWGDVM.js +2157 -0
- package/dist/chunks/caizhilian-JOWS6SL5.js +10087 -0
- package/dist/chunks/calendar-5WNCV66Q.js +1195 -0
- package/dist/chunks/chunk-2DUV44UX.js +15606 -0
- package/dist/chunks/chunk-324YPTC2.js +1607 -0
- package/dist/chunks/chunk-65QMVWHS.js +231 -0
- package/dist/chunks/chunk-6V7ZLF34.js +387 -0
- package/dist/chunks/chunk-AC7KJWC5.js +358 -0
- package/dist/chunks/chunk-AJ62BGAW.js +29 -0
- package/dist/chunks/chunk-BUSC6UAE.js +43 -0
- package/dist/chunks/chunk-CVZ2JOSW.js +62 -0
- package/dist/chunks/chunk-D4LTJSJD.js +477 -0
- package/dist/chunks/chunk-EG6ZU444.js +654 -0
- package/dist/chunks/chunk-EZALDOQH.js +383 -0
- package/dist/chunks/chunk-FMPJJYYI.js +106 -0
- package/dist/chunks/chunk-G2JSIUWF.js +769 -0
- package/dist/chunks/chunk-GCG2NMWR.js +703 -0
- package/dist/chunks/chunk-GPMFD4RD.js +75 -0
- package/dist/chunks/chunk-H3WDBYJW.js +508 -0
- package/dist/chunks/chunk-H6XSTSLM.js +2357 -0
- package/dist/chunks/chunk-J7RJTZSV.js +5556 -0
- package/dist/chunks/chunk-LUSPKZKY.js +53 -0
- package/dist/chunks/chunk-NIAJ5ZCG.js +73 -0
- package/dist/chunks/chunk-NOODWMCL.js +584 -0
- package/dist/chunks/chunk-OOUTBLZ4.js +51 -0
- package/dist/chunks/chunk-P26ZWSKU.js +73 -0
- package/dist/chunks/chunk-QWY6PGL6.js +3472 -0
- package/dist/chunks/chunk-R2IJAK7T.js +464 -0
- package/dist/chunks/chunk-R7O5PUWO.js +205 -0
- package/dist/chunks/chunk-RCGYCOMG.js +6962 -0
- package/dist/chunks/chunk-VZTXRG6X.js +403 -0
- package/dist/chunks/chunk-WHD4XMRP.js +161 -0
- package/dist/chunks/chunk-YLCYX7LE.js +371 -0
- package/dist/chunks/chunk-ZEDWSH32.js +68 -0
- package/dist/chunks/continuation-2CKSBLA6.js +11 -0
- package/dist/chunks/continuation-HZIZRZBX.js +13 -0
- package/dist/chunks/continuation-NLECEJPX.js +11 -0
- package/dist/chunks/ebuilder-form-NJ4YK6CC.js +2276 -0
- package/dist/chunks/ehr-MMYN36VA.js +5241 -0
- package/dist/chunks/employee-SAK2QYXX.js +11 -0
- package/dist/chunks/esb-HIWHOU7Q.js +141 -0
- package/dist/chunks/fna-KBACQTA5.js +1444 -0
- package/dist/chunks/host-3OHS5QUX.js +21 -0
- package/dist/chunks/host-IG6KEH5Q.js +19 -0
- package/dist/chunks/host-KQL5N4HO.js +23 -0
- package/dist/chunks/host-LLSZQCWR.js +17 -0
- package/dist/chunks/hrm-LYEC67ZQ.js +244 -0
- package/dist/chunks/im-RN2UKAEU.js +252 -0
- package/dist/chunks/invoice-EEPHLAF6.js +5179 -0
- package/dist/chunks/jiuchuanhui-WVVZYSJE.js +1940 -0
- package/dist/chunks/lazy-dispatch-MDOFFL2S.js +28 -0
- package/dist/chunks/mail-LNQLTF6L.js +2695 -0
- package/dist/chunks/meeting-XSD3BDVQ.js +504 -0
- package/dist/chunks/msg-parse-4GSQ2WTO.js +41 -0
- package/dist/chunks/okr-TMWTIU2K.js +2482 -0
- package/dist/chunks/plan-A2FSFA7T.js +2025 -0
- package/dist/chunks/project-T26QLBQF.js +1361 -0
- package/dist/chunks/registry-2VLUAESA.js +14 -0
- package/dist/chunks/registry-JCFEYXOE.js +18 -0
- package/dist/chunks/registry-NYPYMZXG.js +16 -0
- package/dist/chunks/registry-Q7OQJ3SU.js +19 -0
- package/dist/chunks/registry-ZJMJVWFA.js +19 -0
- package/dist/chunks/room-recommend-UR4ZDZS6.js +20 -0
- package/dist/chunks/schedule-5L2PM4M6.js +24 -0
- package/dist/chunks/state-Y2XBP6YE.js +15 -0
- package/dist/chunks/workflow-TR6PG2SL.js +245 -0
- package/dist/cmd/argv.js +45 -0
- package/dist/cmd/docs/index.js +3 -3
- package/dist/cmd/lazy-dispatch.js +20 -0
- package/dist/cmd/root.js +2 -4
- package/dist/cmd/setup/index.js +2 -2
- package/dist/cmd/skills/index.js +2 -2
- package/dist/core/package-paths.js +47 -0
- package/dist/index.js +163 -7
- package/dist/internal/e10/auth/xiaoe.js +3 -3
- package/dist/internal/e10/request-runtime.js +18 -62
- package/dist/internal/skills/detector.js +2 -2
- package/dist/shortcuts/contract/data/performance-catalog.js +1306 -0
- package/dist/shortcuts/contract/host.js +91 -35
- package/dist/shortcuts/contract/index.js +213 -0
- package/dist/shortcuts/contract/manifest.js +928 -0
- package/dist/shortcuts/contract/ooxml/docx.js +295 -0
- package/dist/shortcuts/contract/ooxml/xlsx.js +221 -0
- package/dist/shortcuts/contract/ooxml/zip.js +158 -0
- package/dist/shortcuts/contract/operations/assistant.js +1156 -0
- package/dist/shortcuts/contract/operations/clause-library.js +522 -0
- package/dist/shortcuts/contract/operations/compare.js +193 -0
- package/dist/shortcuts/contract/operations/credit-page.js +34 -0
- package/dist/shortcuts/contract/operations/flow.js +98 -0
- package/dist/shortcuts/contract/operations/page.js +66 -0
- package/dist/shortcuts/contract/operations/performance.js +733 -0
- package/dist/shortcuts/contract/operations/quote.js +557 -0
- package/dist/shortcuts/contract/operations/recommend.js +78 -0
- package/dist/shortcuts/contract/operations/registry.js +18 -0
- package/dist/shortcuts/contract/operations/review.js +83 -0
- package/dist/shortcuts/contract/operations/shared.js +37 -0
- package/dist/shortcuts/contract/operations/upload.js +108 -0
- package/dist/shortcuts/contract/operations/write.js +3 -2
- package/dist/shortcuts/index.js +140 -40
- package/dist/shortcuts/invoice/manifest.js +34 -11
- package/dist/shortcuts/invoice/operations/reim-auto.js +9 -0
- package/dist/shortcuts/invoice/operations/reim.js +81 -5
- package/dist/shortcuts/invoice/operations/shared.js +12 -5
- package/dist/shortcuts/meeting/host.js +45 -3
- package/dist/shortcuts/meeting/invitation/docx.js +442 -0
- package/dist/shortcuts/meeting/operations/attachment-content.js +418 -0
- package/dist/shortcuts/meeting/operations/attachment.js +442 -0
- package/dist/shortcuts/meeting/operations/eb/cache.js +57 -0
- package/dist/shortcuts/meeting/operations/eb/catalog.js +343 -28
- package/dist/shortcuts/meeting/operations/eb/conflict.js +372 -11
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +710 -0
- package/dist/shortcuts/meeting/operations/eb/decision.js +47 -1
- package/dist/shortcuts/meeting/operations/eb/factory.js +129 -11
- package/dist/shortcuts/meeting/operations/eb/index.js +122 -2
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +378 -0
- package/dist/shortcuts/meeting/operations/eb/room-summary.js +84 -0
- package/dist/shortcuts/meeting/operations/eb/schedule.js +450 -0
- package/dist/shortcuts/meeting/operations/from-group-chat.js +576 -0
- package/dist/shortcuts/meeting/operations/invitation.js +458 -0
- package/dist/shortcuts/meeting/operations/minute-content.js +266 -0
- package/dist/shortcuts/meeting/operations/pipeline.js +489 -0
- package/dist/shortcuts/meeting/operations/qrcode.js +131 -0
- package/dist/shortcuts/meeting/operations/receipts.js +1 -1
- package/dist/shortcuts/meeting/operations/registry.js +18 -0
- package/dist/shortcuts/meeting/operations/summary.js +276 -0
- package/dist/shortcuts/meeting/operations/task-notify.js +325 -0
- package/dist/shortcuts/meeting/operations.js +42 -1
- package/dist/shortcuts/meeting/qr/encoder.js +427 -0
- package/dist/shortcuts/meeting/qr/render.js +103 -0
- package/dist/shortcuts/workflow/operations/form.js +17 -5
- package/docs/contract.md +39 -4
- package/docs/invoice.md +5 -5
- package/package.json +3 -2
- package/scripts/bundle.mjs +63 -0
- package/skill-template/business-info.json +25 -4
- package/skill-template/domains/jinchengda.md +16 -3
- package/skill-template/domains/yepiaotong.md +1 -1
- package/skills/weaver-e10-jinchengda/SKILL.md +111 -31
- package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +7 -13
- package/skills/weaver-e10-jinchengda/references/contract-ai-assistant.md +150 -0
- package/skills/weaver-e10-jinchengda/references/contract-ai-review.md +87 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-library-crud.md +111 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +0 -15
- package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +0 -13
- package/skills/weaver-e10-jinchengda/references/contract-credit-page.md +50 -0
- package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +4 -2
- package/skills/weaver-e10-jinchengda/references/contract-doc-compare.md +74 -0
- package/skills/weaver-e10-jinchengda/references/contract-flow-launch.md +84 -0
- package/skills/weaver-e10-jinchengda/references/contract-performance-query.md +29 -0
- package/skills/weaver-e10-jinchengda/references/contract-quote-to-contract.md +28 -0
- package/skills/weaver-e10-jinchengda/references/contract-request-status.md +2 -21
- package/skills/weaver-e10-jinchengda/references/contract-request-write.md +0 -24
- package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +0 -15
- package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +0 -17
- package/skills/weaver-e10-jinchengda/references/contract-template-recommend.md +70 -0
- package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +0 -15
- package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +0 -15
- package/skills/weaver-e10-jinchengda/references/source-manifest.json +1168 -152
- package/skills/weaver-e10-meeting/SKILL.md +493 -17
- package/skills/weaver-e10-meeting/references/meeting-change.md +4 -4
- package/skills/weaver-e10-meeting/references/meeting-create.md +6 -6
- package/skills/weaver-e10-meeting/references/meeting-eb.md +503 -27
- package/skills/weaver-e10-meeting/references/meeting-invitation.md +113 -0
- package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +6 -6
- package/skills/weaver-e10-meeting/references/meeting-minute-file.md +425 -0
- package/skills/weaver-e10-meeting/references/meeting-queries.md +4 -4
- package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +15 -15
- package/skills/weaver-e10-meeting/references/meeting-verification.md +19 -11
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-yepiaotong/SKILL.md +9 -5
- package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +1 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +6 -5
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +14 -11
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +97 -56
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +84 -60
- package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +142 -105
- package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +53 -194
- package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +7 -7
- package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +164 -72
- package/skills/weaver-e10-yepiaotong/references/source-manifest.json +6 -0
- package/templates/meeting/invitation.docx +0 -0
|
@@ -0,0 +1,1156 @@
|
|
|
1
|
+
import { contractPolicyError, contractValidationError } from '../errors.js';
|
|
2
|
+
import { aiSettingPageUrl } from './page.js';
|
|
3
|
+
import { bodyFrom, fingerprintFields, idList, requireId, requireNonEmptyString, toJsonString } from './shared.js';
|
|
4
|
+
import { ContractWriteApplyOperation, ContractWritePrepareOperation } from './write.js';
|
|
5
|
+
const AI_BASE = '/api/ebd/contract/ai';
|
|
6
|
+
const AUDIT_BASE = '/api/ebd/contract/audit';
|
|
7
|
+
const CONFIG_BASE = `${AI_BASE}/config`;
|
|
8
|
+
const TIP_BASE = `${AI_BASE}/tip`;
|
|
9
|
+
const PERMISSION_BASE = '/api/ebd/contract/tool/permission';
|
|
10
|
+
/** 关联流程固定 toolId / componentId(源资料第十一节)。 */
|
|
11
|
+
const FLOW_TOOL_ID = '803297036482249914';
|
|
12
|
+
/** 关联流程删除必须携带 type,缺失会返回 success 但实际不删除。 */
|
|
13
|
+
const FLOW_DELETE_TYPE = 'contract_ai_workflow_data';
|
|
14
|
+
const AI_MARKS = [
|
|
15
|
+
'Chat', 'Summary', 'ProcurementRisk', 'Control', 'Review', 'Correct', 'Extract',
|
|
16
|
+
'Credit', 'Signature', 'Compare', 'Desensitization', 'RiskReview',
|
|
17
|
+
'riskRuleComponent', 'consultComponent', 'termComponent', 'keywordComponent',
|
|
18
|
+
];
|
|
19
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
20
|
+
function dataOf(value) {
|
|
21
|
+
return value?.data ?? {};
|
|
22
|
+
}
|
|
23
|
+
function rowsOf(value) {
|
|
24
|
+
const data = dataOf(value);
|
|
25
|
+
for (const candidate of [data.data, data.records, data.list, data.rows, data]) {
|
|
26
|
+
if (Array.isArray(candidate))
|
|
27
|
+
return candidate;
|
|
28
|
+
}
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
// ---------------------------------------------------------------- 串联链路
|
|
32
|
+
/**
|
|
33
|
+
* getEbData?type=ai_background_config 是助手本体列表的唯一合法 type。
|
|
34
|
+
* ai_component_config 是组件配置(实测能查出数据但答非所问),ai_compenent_config 不存在,均在此阻断。
|
|
35
|
+
*/
|
|
36
|
+
async function assistantMeta(host) {
|
|
37
|
+
const value = await host.contractRequest({
|
|
38
|
+
method: 'GET',
|
|
39
|
+
path: `${AI_BASE}/getEbData`,
|
|
40
|
+
query: { type: 'ai_background_config' },
|
|
41
|
+
});
|
|
42
|
+
const data = dataOf(value);
|
|
43
|
+
if (!data.formId || !data.viewId) {
|
|
44
|
+
throw contractValidationError('assistant_meta_missing', 'getEbData 未返回 formId/viewId,助手列表链路不可用;请先运行 contract.assistant.init 完成初始化');
|
|
45
|
+
}
|
|
46
|
+
return { ebFormId: String(data.formId), viewId: String(data.viewId), fieldsMap: data.fieldsMap ?? {} };
|
|
47
|
+
}
|
|
48
|
+
async function listAssistants(host, name) {
|
|
49
|
+
const meta = await assistantMeta(host);
|
|
50
|
+
const quickSearchFields = {};
|
|
51
|
+
if (name) {
|
|
52
|
+
const fieldId = meta.fieldsMap.ai_name;
|
|
53
|
+
if (!fieldId) {
|
|
54
|
+
throw contractValidationError('assistant_meta_missing', 'fieldsMap 缺少 ai_name 字段映射,无法按名称过滤;请省略 name 做全量查询');
|
|
55
|
+
}
|
|
56
|
+
quickSearchFields[`field_${fieldId}`] = name;
|
|
57
|
+
}
|
|
58
|
+
const value = await host.contractRequest({
|
|
59
|
+
method: 'POST',
|
|
60
|
+
path: '/api/ebuilder/form/list/getListDatas',
|
|
61
|
+
query: { cid: meta.ebFormId },
|
|
62
|
+
body: {
|
|
63
|
+
current: 1,
|
|
64
|
+
requiredMatch: 1,
|
|
65
|
+
tableuid: '',
|
|
66
|
+
mapResult: '1',
|
|
67
|
+
recheckQuery: '1',
|
|
68
|
+
searchParamData: {
|
|
69
|
+
searchType: '2',
|
|
70
|
+
quickSearchFields,
|
|
71
|
+
commonSearchFields: {},
|
|
72
|
+
type: 'and',
|
|
73
|
+
groupFields: {},
|
|
74
|
+
},
|
|
75
|
+
listId: meta.viewId,
|
|
76
|
+
isFollow: 0,
|
|
77
|
+
isShareToMe: 0,
|
|
78
|
+
filterCondition: {},
|
|
79
|
+
browser: 'Chrome',
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
return rowsOf(value);
|
|
83
|
+
}
|
|
84
|
+
async function queryAiDoc(host, aiId) {
|
|
85
|
+
const value = await host.contractRequest({ method: 'POST', path: `${CONFIG_BASE}/queryAiDoc`, body: { ai_id: aiId } });
|
|
86
|
+
return rowsOf(value);
|
|
87
|
+
}
|
|
88
|
+
async function queryTips(host, aiId) {
|
|
89
|
+
const value = await host.contractRequest({ method: 'POST', path: `${TIP_BASE}/queryTips`, body: { ai_id: aiId } });
|
|
90
|
+
return rowsOf(value);
|
|
91
|
+
}
|
|
92
|
+
async function queryClassificationList(host) {
|
|
93
|
+
const value = await host.contractRequest({
|
|
94
|
+
method: 'POST',
|
|
95
|
+
path: `${AI_BASE}/queryList`,
|
|
96
|
+
body: { type: 'contract_classification', condition: [{ fieldName: 'personal_config', conditionType: 'isNull' }] },
|
|
97
|
+
});
|
|
98
|
+
return rowsOf(value);
|
|
99
|
+
}
|
|
100
|
+
async function queryRuleList(host, flid, pageNo = 1, pageSize = 20) {
|
|
101
|
+
const condition = [{ fieldName: 'personal_config', conditionType: 'neq', value: '1' }];
|
|
102
|
+
if (flid)
|
|
103
|
+
condition.push({ fieldName: 'flid', conditionType: 'like', value: String(flid) });
|
|
104
|
+
const value = await host.contractRequest({
|
|
105
|
+
method: 'POST',
|
|
106
|
+
path: `${AI_BASE}/queryList`,
|
|
107
|
+
body: { type: 'contract_rule_datasource', condition, pageNo, pageSize },
|
|
108
|
+
});
|
|
109
|
+
return rowsOf(value);
|
|
110
|
+
}
|
|
111
|
+
async function queryAiConfig(host, aiId) {
|
|
112
|
+
const value = await host.contractRequest({ method: 'POST', path: `${CONFIG_BASE}/queryAiConfig`, body: { ai_id: aiId } });
|
|
113
|
+
return rowsOf(value);
|
|
114
|
+
}
|
|
115
|
+
async function queryFlowList(host, aiId, ruleConfigId, pageIndex = 1, pageSize = 50) {
|
|
116
|
+
const value = await host.contractRequest({
|
|
117
|
+
method: 'POST',
|
|
118
|
+
path: `${PERMISSION_BASE}/list/page`,
|
|
119
|
+
body: { appId: '-1', aiId, rule_config_id: ruleConfigId, componentId: FLOW_TOOL_ID, pageIndex, pageSize },
|
|
120
|
+
});
|
|
121
|
+
return rowsOf(value);
|
|
122
|
+
}
|
|
123
|
+
// ---------------------------------------------------------------- 查询类
|
|
124
|
+
/** 初始化状态:importStatus 非 already 时自动轮询 getInitStatus。 */
|
|
125
|
+
export const contractAssistantInitOperation = {
|
|
126
|
+
name: 'contract.assistant.init',
|
|
127
|
+
async execute(_input, { host }) {
|
|
128
|
+
let value = await host.contractRequest({ method: 'GET', path: `${AUDIT_BASE}/getAllEbConfig` });
|
|
129
|
+
let data = dataOf(value);
|
|
130
|
+
let polled = 0;
|
|
131
|
+
if (data.importStatus !== 'already' && data.traceId) {
|
|
132
|
+
for (let i = 0; i < 6; i += 1) {
|
|
133
|
+
await sleep(1500);
|
|
134
|
+
polled += 1;
|
|
135
|
+
await host.contractRequest({
|
|
136
|
+
method: 'GET',
|
|
137
|
+
path: `${AUDIT_BASE}/getInitStatus`,
|
|
138
|
+
query: { traceId: String(data.traceId) },
|
|
139
|
+
});
|
|
140
|
+
value = await host.contractRequest({ method: 'GET', path: `${AUDIT_BASE}/getAllEbConfig` });
|
|
141
|
+
data = dataOf(value);
|
|
142
|
+
if (data.importStatus === 'already')
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
status: 'COMPLETE',
|
|
148
|
+
data: {
|
|
149
|
+
importStatus: data.importStatus,
|
|
150
|
+
polled,
|
|
151
|
+
companyEbConfigMap: data.companyEbConfigMap,
|
|
152
|
+
...(data.importStatus === 'already' ? {} : {
|
|
153
|
+
warning: `初始化未完成(traceId=${data.traceId});稍后重新运行本命令,配置链路(getEbData/getListDatas)在完成前不可用`,
|
|
154
|
+
}),
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
/** 列表字段映射;type 固定 ai_background_config(组件配置是合法 type 但答非所问,在此固定掉)。 */
|
|
160
|
+
export const contractAssistantMetaOperation = {
|
|
161
|
+
name: 'contract.assistant.meta',
|
|
162
|
+
async execute(_input, { host }) {
|
|
163
|
+
const meta = await assistantMeta(host);
|
|
164
|
+
return { status: 'COMPLETE', data: { ...meta, note: '查询条件 key 格式为 field_<字段id>' } };
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
export const contractAssistantListOperation = {
|
|
168
|
+
name: 'contract.assistant.list',
|
|
169
|
+
async execute(input, { host }) {
|
|
170
|
+
const name = typeof input.name === 'string' && input.name.trim() ? input.name.trim() : undefined;
|
|
171
|
+
const rows = await listAssistants(host, name);
|
|
172
|
+
return {
|
|
173
|
+
status: 'COMPLETE',
|
|
174
|
+
data: {
|
|
175
|
+
assistants: rows.map((row) => ({
|
|
176
|
+
id: row.id,
|
|
177
|
+
ai_name: row.ai_name,
|
|
178
|
+
ai_desc: row.ai_desc,
|
|
179
|
+
type_name: row.type_name,
|
|
180
|
+
stand_name: row.stand_name,
|
|
181
|
+
})),
|
|
182
|
+
hint: rows.length === 1
|
|
183
|
+
? '列表只有一个助手,后续操作可默认使用它'
|
|
184
|
+
: '多个助手时后续操作必须明确指定 ai_id,否则列出全部让用户选择',
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
/** 存储目录 + 自动执行配置;无 base_type=存储目录,base_type=1=执行方式。 */
|
|
190
|
+
export const contractAssistantDocQueryOperation = {
|
|
191
|
+
name: 'contract.assistant.doc.query',
|
|
192
|
+
async execute(input, { host }) {
|
|
193
|
+
const aiId = requireId(input.ai_id ?? input.aiId, 'ai_id');
|
|
194
|
+
const rows = await queryAiDoc(host, aiId);
|
|
195
|
+
return {
|
|
196
|
+
status: 'COMPLETE',
|
|
197
|
+
data: {
|
|
198
|
+
storage: rows.filter((row) => row.base_type === undefined || row.base_type === ''),
|
|
199
|
+
execute: rows.filter((row) => row.base_type === '1' || row.base_type === 1),
|
|
200
|
+
hint: 'execute_type=2 手动执行,其他值为自动',
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
/** 16 智能体查询;自动挑出 Control(rule_config_id)与 Extract(config_id)。 */
|
|
206
|
+
export const contractAssistantConfigQueryOperation = {
|
|
207
|
+
name: 'contract.assistant.config.query',
|
|
208
|
+
async execute(input, { host }) {
|
|
209
|
+
const aiId = requireId(input.ai_id ?? input.aiId, 'ai_id');
|
|
210
|
+
const aiMark = typeof input.aiMark === 'string' && input.aiMark ? input.aiMark : undefined;
|
|
211
|
+
if (aiMark && !AI_MARKS.includes(aiMark)) {
|
|
212
|
+
throw contractValidationError('ai_mark_invalid', `未知 ai_mark:${aiMark};合法值 ${AI_MARKS.join('/')}`);
|
|
213
|
+
}
|
|
214
|
+
const rows = await queryAiConfig(host, aiId);
|
|
215
|
+
const agents = rows
|
|
216
|
+
.filter((row) => !aiMark || row.ai_mark === aiMark)
|
|
217
|
+
.map((row) => ({
|
|
218
|
+
id: row.id,
|
|
219
|
+
ai_mark: row.ai_mark,
|
|
220
|
+
ai_name: row.ai_name,
|
|
221
|
+
display: row.display,
|
|
222
|
+
status_info: row.status_info,
|
|
223
|
+
iorder: row.iorder,
|
|
224
|
+
}));
|
|
225
|
+
const control = rows.find((row) => row.ai_mark === 'Control');
|
|
226
|
+
const extract = rows.find((row) => row.ai_mark === 'Extract');
|
|
227
|
+
return {
|
|
228
|
+
status: 'COMPLETE',
|
|
229
|
+
data: {
|
|
230
|
+
agents,
|
|
231
|
+
controlAgentId: control?.id,
|
|
232
|
+
extractAgentId: extract?.id,
|
|
233
|
+
warning: 'display 是平台保留字段(前端卡片展示),启用/停用智能体只允许改 status_info',
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
export const contractAssistantTipsQueryOperation = {
|
|
239
|
+
name: 'contract.assistant.tips.query',
|
|
240
|
+
async execute(input, { host }) {
|
|
241
|
+
const aiId = requireId(input.ai_id ?? input.aiId, 'ai_id');
|
|
242
|
+
const rows = await queryTips(host, aiId);
|
|
243
|
+
return {
|
|
244
|
+
status: 'COMPLETE',
|
|
245
|
+
data: {
|
|
246
|
+
tips: rows.map((row) => ({ id: row.id, aizjid: row.aizjid, tsynr: row.tsynr })),
|
|
247
|
+
hint: '提示语无编辑接口,改动需先删后增;新建助手自带 4 条默认提示语',
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
export const contractAssistantDocFolderSearchOperation = {
|
|
253
|
+
name: 'contract.assistant.doc.folder.search',
|
|
254
|
+
async execute(input, { host }) {
|
|
255
|
+
const name = typeof input.name === 'string' && input.name.trim() ? input.name.trim() : '';
|
|
256
|
+
const value = await host.contractRequest({
|
|
257
|
+
method: 'POST',
|
|
258
|
+
path: '/api/doc/common/browser/data/folder',
|
|
259
|
+
body: {
|
|
260
|
+
browserMultiple: false,
|
|
261
|
+
dimension: 'all',
|
|
262
|
+
current: 1,
|
|
263
|
+
pageSize: 10,
|
|
264
|
+
formDatas: {},
|
|
265
|
+
quickSearchDatas: { name },
|
|
266
|
+
type: 'and',
|
|
267
|
+
useListViewType: false,
|
|
268
|
+
browserTreeDefaultExpandedLevel: '0',
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
return { status: 'COMPLETE', data: { name, folders: rowsOf(value) } };
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
export const contractAssistantKeygroupSearchOperation = {
|
|
275
|
+
name: 'contract.assistant.keygroup.search',
|
|
276
|
+
async execute(input, { host }) {
|
|
277
|
+
const name = typeof input.name === 'string' && input.name.trim() ? input.name.trim() : '';
|
|
278
|
+
const value = await host.contractRequest({
|
|
279
|
+
method: 'POST',
|
|
280
|
+
path: '/api/ebd/contract/common/browser/data/ebContractKeyGroupBrowser',
|
|
281
|
+
body: { browserMultiple: true, current: 1, pageSize: 10, formDatas: {}, quickSearchDatas: { name }, type: 'and' },
|
|
282
|
+
});
|
|
283
|
+
return { status: 'COMPLETE', data: { name, groups: rowsOf(value) } };
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
export const contractAssistantClassificationListOperation = {
|
|
287
|
+
name: 'contract.assistant.rule.classification.list',
|
|
288
|
+
async execute(_input, { host }) {
|
|
289
|
+
const rows = await queryClassificationList(host);
|
|
290
|
+
return { status: 'COMPLETE', data: { classifications: rows, hint: '该接口无模糊检索,全量返回后自行过滤' } };
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
export const contractAssistantRuleListOperation = {
|
|
294
|
+
name: 'contract.assistant.rule.list',
|
|
295
|
+
async execute(input, { host }) {
|
|
296
|
+
const flid = input.flid === undefined || input.flid === '' ? undefined : requireId(input.flid, 'flid');
|
|
297
|
+
const pageNo = typeof input.pageNo === 'number' ? input.pageNo : 1;
|
|
298
|
+
const pageSize = typeof input.pageSize === 'number' ? input.pageSize : 20;
|
|
299
|
+
const rows = await queryRuleList(host, flid, pageNo, pageSize);
|
|
300
|
+
return { status: 'COMPLETE', data: { rules: rows } };
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
export const contractAssistantRuleGroupQueryOperation = {
|
|
304
|
+
name: 'contract.assistant.rule.group.query',
|
|
305
|
+
async execute(input, { host }) {
|
|
306
|
+
const configId = requireId(input.config_id ?? input.configId, 'config_id(Control 智能体 id)');
|
|
307
|
+
const value = await host.contractRequest({
|
|
308
|
+
method: 'POST',
|
|
309
|
+
path: `${CONFIG_BASE}/queryCustomConfigRules`,
|
|
310
|
+
body: { config_id: configId },
|
|
311
|
+
});
|
|
312
|
+
return { status: 'COMPLETE', data: { groups: rowsOf(value) } };
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
export const contractAssistantElementQueryOperation = {
|
|
316
|
+
name: 'contract.assistant.element.query',
|
|
317
|
+
async execute(input, { host }) {
|
|
318
|
+
const configId = requireId(input.config_id ?? input.configId, 'config_id(Extract 智能体 id)');
|
|
319
|
+
const value = await host.contractRequest({
|
|
320
|
+
method: 'POST',
|
|
321
|
+
path: `${CONFIG_BASE}/queryElementConfigRules`,
|
|
322
|
+
body: { config_id: configId },
|
|
323
|
+
});
|
|
324
|
+
return { status: 'COMPLETE', data: { elementSets: rowsOf(value) } };
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
export const contractAssistantFlowListOperation = {
|
|
328
|
+
name: 'contract.assistant.flow.list',
|
|
329
|
+
async execute(input, { host }) {
|
|
330
|
+
const aiId = requireId(input.ai_id ?? input.aiId, 'ai_id');
|
|
331
|
+
const ruleConfigId = requireId(input.rule_config_id ?? input.ruleConfigId, 'rule_config_id(Control 智能体 id)');
|
|
332
|
+
const pageIndex = typeof input.pageIndex === 'number' ? input.pageIndex : 1;
|
|
333
|
+
const pageSize = typeof input.pageSize === 'number' ? input.pageSize : 50;
|
|
334
|
+
const rows = await queryFlowList(host, aiId, ruleConfigId, pageIndex, pageSize);
|
|
335
|
+
return {
|
|
336
|
+
status: 'COMPLETE',
|
|
337
|
+
data: {
|
|
338
|
+
bindings: rows,
|
|
339
|
+
hint: '一个流程全局只能绑定一次(跨助手唯一);绑定前先在这里排查是否已被占用',
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
// ---------------------------------------------------------------- 写操作公共
|
|
345
|
+
/** 读取并归一化 ai_id:驼峰 aiId 一律写回 ai_id,供 body 构造与指纹复用。 */
|
|
346
|
+
function requireAiId(input) {
|
|
347
|
+
const value = requireId(input.ai_id ?? input.aiId, 'ai_id(助手数据 id)');
|
|
348
|
+
input.ai_id = value;
|
|
349
|
+
return value;
|
|
350
|
+
}
|
|
351
|
+
function requireConfigId(input, label) {
|
|
352
|
+
const value = requireId(input.config_id ?? input.configId, `config_id(${label})`);
|
|
353
|
+
input.config_id = value;
|
|
354
|
+
return value;
|
|
355
|
+
}
|
|
356
|
+
/** ai-setting 功能页地址随写结果一并返回,供写后打开页面确认。 */
|
|
357
|
+
async function pageHint(host) {
|
|
358
|
+
const session = await host.getSession();
|
|
359
|
+
return { url: aiSettingPageUrl(session.baseUrl) };
|
|
360
|
+
}
|
|
361
|
+
function writePair(name, spec) {
|
|
362
|
+
return [
|
|
363
|
+
new ContractWritePrepareOperation(`${name}.prepare`, `${name}.apply`, spec),
|
|
364
|
+
new ContractWriteApplyOperation(`${name}.apply`, spec),
|
|
365
|
+
];
|
|
366
|
+
}
|
|
367
|
+
/** tips.create 需要在 prepare / apply 两端自动补算 aizjid,用专用子类注册。 */
|
|
368
|
+
function tipsWritePair(spec) {
|
|
369
|
+
return [
|
|
370
|
+
new TipsCreatePrepareOperation('contract.assistant.tips.create.prepare', 'contract.assistant.tips.create.apply', spec),
|
|
371
|
+
new TipsCreateApplyOperation('contract.assistant.tips.create.apply', spec),
|
|
372
|
+
];
|
|
373
|
+
}
|
|
374
|
+
// ---------------------------------------------------------------- 助手新建 / 删除
|
|
375
|
+
const assistantCreateSpec = {
|
|
376
|
+
method: 'POST',
|
|
377
|
+
path: `${CONFIG_BASE}/saveAiInfo`,
|
|
378
|
+
validate(input) {
|
|
379
|
+
requireNonEmptyString(input.ai_name, 'ai_name', 200);
|
|
380
|
+
if ((input.type === undefined) !== (input.type_name === undefined)) {
|
|
381
|
+
throw contractValidationError('type_pair_required', 'type 与 type_name(合同类型 id 与名称)必须成对提交');
|
|
382
|
+
}
|
|
383
|
+
if (input.type !== undefined)
|
|
384
|
+
requireId(input.type, 'type');
|
|
385
|
+
if (input.contract_stand !== undefined) {
|
|
386
|
+
const stand = String(input.contract_stand);
|
|
387
|
+
if (!['0', '1'].includes(stand)) {
|
|
388
|
+
throw contractValidationError('contract_stand_invalid', 'contract_stand 只能是 0(甲方)或 1(乙方)');
|
|
389
|
+
}
|
|
390
|
+
if (input.stand_name === undefined) {
|
|
391
|
+
throw contractValidationError('stand_pair_required', 'contract_stand 与 stand_name 必须成对提交');
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
body: (input) => bodyFrom(input, ['ai_name', 'ai_desc', 'type', 'type_name', 'contract_stand', 'stand_name']),
|
|
396
|
+
fingerprint: fingerprintFields(['ai_name', 'ai_desc', 'type', 'type_name', 'contract_stand', 'stand_name']),
|
|
397
|
+
preview: (input) => ({
|
|
398
|
+
action: '新建智能助手',
|
|
399
|
+
ai_name: input.ai_name,
|
|
400
|
+
type_name: input.type_name,
|
|
401
|
+
stand_name: input.stand_name ?? input.contract_stand,
|
|
402
|
+
warning: '助手基本信息(名称/说明/类型/立场)平台不支持修改,需调整时删除后重建',
|
|
403
|
+
}),
|
|
404
|
+
async readback(_result, input, host) {
|
|
405
|
+
const rows = await listAssistants(host, input.ai_name);
|
|
406
|
+
const hit = rows.find((row) => row.ai_name === input.ai_name);
|
|
407
|
+
if (!hit)
|
|
408
|
+
throw new Error('助手列表中未找到刚创建的助手');
|
|
409
|
+
return { id: hit.id, ai_name: hit.ai_name, ...(await pageHint(host)) };
|
|
410
|
+
},
|
|
411
|
+
};
|
|
412
|
+
const assistantDeleteSpec = {
|
|
413
|
+
method: 'POST',
|
|
414
|
+
path: `${CONFIG_BASE}/deleteAiInfo`,
|
|
415
|
+
validate(input) {
|
|
416
|
+
const ids = idList(input.ids ?? input.id, 'ids');
|
|
417
|
+
input.ids = ids.join(',');
|
|
418
|
+
},
|
|
419
|
+
body: (input) => ({ ids: String(input.ids) }),
|
|
420
|
+
fingerprint: fingerprintFields(['ids']),
|
|
421
|
+
preview: (input) => ({
|
|
422
|
+
action: '删除智能助手',
|
|
423
|
+
ids: input.ids,
|
|
424
|
+
warning: '删除接口对不存在的 id 也返回 success(假成功),apply 会强制回读复核',
|
|
425
|
+
}),
|
|
426
|
+
async precheck(input, host) {
|
|
427
|
+
const ids = String(input.ids).split(',');
|
|
428
|
+
const rows = await listAssistants(host);
|
|
429
|
+
const existing = rows.filter((row) => ids.includes(String(row.id)));
|
|
430
|
+
if (existing.length === 0) {
|
|
431
|
+
throw contractPolicyError('assistant_not_found', '待删除的助手 id 均不存在,未发起删除', { ids: input.ids });
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
found: existing.map((row) => ({ id: row.id, ai_name: row.ai_name })),
|
|
435
|
+
missing: ids.filter((id) => !existing.some((row) => String(row.id) === id)),
|
|
436
|
+
};
|
|
437
|
+
},
|
|
438
|
+
async readback(_result, input, host) {
|
|
439
|
+
const ids = String(input.ids).split(',');
|
|
440
|
+
const rows = await listAssistants(host);
|
|
441
|
+
const remaining = rows.filter((row) => ids.includes(String(row.id)));
|
|
442
|
+
if (remaining.length) {
|
|
443
|
+
throw new Error(`删除接口返回成功,但列表中仍能查到 ${remaining.length} 个助手(${remaining.map((r) => r.ai_name).join('/')})`);
|
|
444
|
+
}
|
|
445
|
+
return { removed: true, ...(await pageHint(host)) };
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
// ---------------------------------------------------------------- 存储目录 / 执行方式(saveAiDoc 同表覆盖防护)
|
|
449
|
+
const assistantDocSaveSpec = {
|
|
450
|
+
method: 'POST',
|
|
451
|
+
path: `${CONFIG_BASE}/saveAiDoc`,
|
|
452
|
+
validate(input) {
|
|
453
|
+
requireAiId(input);
|
|
454
|
+
const kind = input.kind;
|
|
455
|
+
if (!['storage', 'execute'].includes(kind)) {
|
|
456
|
+
throw contractValidationError('kind_required', "kind 必传且只能是 'storage'(存储目录)或 'execute'(执行方式)");
|
|
457
|
+
}
|
|
458
|
+
if (kind === 'storage') {
|
|
459
|
+
requireId(input.doc_id ?? input.docId, 'doc_id');
|
|
460
|
+
requireNonEmptyString(input.doc_name ?? input.docName, 'doc_name', 200);
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
const executeType = String(input.execute_type ?? input.executeType ?? '');
|
|
464
|
+
if (!['1', '2'].includes(executeType)) {
|
|
465
|
+
throw contractValidationError('execute_type_invalid', "execute_type 只能是 '1'(自动)或 '2'(手动)");
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
body: (input) => {
|
|
470
|
+
const base = { ai_id: requireAiId(input) };
|
|
471
|
+
if (input.id !== undefined)
|
|
472
|
+
base.id = requireId(input.id, '记录 id');
|
|
473
|
+
if (input.kind === 'storage') {
|
|
474
|
+
base.doc_id = String(input.doc_id ?? input.docId);
|
|
475
|
+
base.doc_name = String(input.doc_name ?? input.docName);
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
base.execute_type = String(input.execute_type ?? input.executeType);
|
|
479
|
+
base.base_type = '1';
|
|
480
|
+
}
|
|
481
|
+
return base;
|
|
482
|
+
},
|
|
483
|
+
fingerprint: fingerprintFields(['kind', 'ai_id', 'id', 'doc_id', 'doc_name', 'execute_type']),
|
|
484
|
+
preview: (input) => ({
|
|
485
|
+
action: input.kind === 'storage' ? '设置存储目录' : '设置自动执行方式',
|
|
486
|
+
kind: input.kind,
|
|
487
|
+
id: input.id ?? '(新增)',
|
|
488
|
+
doc_name: input.doc_name ?? input.docName,
|
|
489
|
+
execute_type: input.execute_type ?? input.executeType,
|
|
490
|
+
}),
|
|
491
|
+
async precheck(input, host) {
|
|
492
|
+
if (input.id === undefined)
|
|
493
|
+
return { mode: 'create' };
|
|
494
|
+
// saveAiDoc 同表按 id 覆盖:record-id 传错会把另一类记录改写污染且无法回退,必须前置校验类型。
|
|
495
|
+
const aiId = requireAiId(input);
|
|
496
|
+
const rows = await queryAiDoc(host, aiId);
|
|
497
|
+
const hit = rows.find((row) => String(row.id) === String(input.id));
|
|
498
|
+
if (!hit) {
|
|
499
|
+
throw contractPolicyError('record_not_found', '该 ai_id 下查不到此记录 id,拒绝覆盖(saveAiDoc 同表按 id 覆盖,传错会污染另一类记录)', { id: input.id });
|
|
500
|
+
}
|
|
501
|
+
const hitIsExecute = hit.base_type === '1' || hit.base_type === 1;
|
|
502
|
+
if (input.kind === 'storage' && hitIsExecute) {
|
|
503
|
+
throw contractPolicyError('record_type_mismatch', '目标记录是执行方式记录,不能用于存储目录写入', { id: input.id });
|
|
504
|
+
}
|
|
505
|
+
if (input.kind === 'execute' && !hitIsExecute) {
|
|
506
|
+
throw contractPolicyError('record_type_mismatch', '目标记录是存储目录记录,写入执行方式会使其语义丢失且无法回退', { id: input.id });
|
|
507
|
+
}
|
|
508
|
+
return { mode: 'overwrite', recordType: hitIsExecute ? 'execute' : 'storage' };
|
|
509
|
+
},
|
|
510
|
+
async readback(_result, input, host) {
|
|
511
|
+
const rows = await queryAiDoc(host, requireAiId(input));
|
|
512
|
+
if (input.kind === 'storage') {
|
|
513
|
+
const docId = String(input.doc_id ?? input.docId);
|
|
514
|
+
const hit = rows.find((row) => (row.base_type === undefined || row.base_type === '') && String(row.doc_id) === docId);
|
|
515
|
+
if (!hit)
|
|
516
|
+
throw new Error('回读未找到刚写入的存储目录记录');
|
|
517
|
+
return { id: hit.id, doc_id: hit.doc_id, doc_name: hit.doc_name, ...(await pageHint(host)) };
|
|
518
|
+
}
|
|
519
|
+
const executeType = String(input.execute_type ?? input.executeType);
|
|
520
|
+
const hit = rows.find((row) => (row.base_type === '1' || row.base_type === 1) && String(row.execute_type) === executeType);
|
|
521
|
+
if (!hit)
|
|
522
|
+
throw new Error('回读未找到刚写入的执行方式记录');
|
|
523
|
+
return { id: hit.id, execute_type: hit.execute_type, ...(await pageHint(host)) };
|
|
524
|
+
},
|
|
525
|
+
};
|
|
526
|
+
// ---------------------------------------------------------------- 智能体配置(updateAiConfig)
|
|
527
|
+
const assistantAgentUpdateSpec = {
|
|
528
|
+
method: 'POST',
|
|
529
|
+
path: `${CONFIG_BASE}/updateAiConfig`,
|
|
530
|
+
validate(input) {
|
|
531
|
+
if ('display' in input) {
|
|
532
|
+
throw contractPolicyError('display_forbidden', 'display 是平台保留字段(前端卡片展示),技术上会被静默落库导致卡片消失;启用/停用只允许改 status_info,请从输入中删除 display');
|
|
533
|
+
}
|
|
534
|
+
requireAiId(input);
|
|
535
|
+
requireId(input.id, '智能体 id');
|
|
536
|
+
if (input.ai_mark !== undefined && !AI_MARKS.includes(String(input.ai_mark))) {
|
|
537
|
+
throw contractValidationError('ai_mark_invalid', `未知 ai_mark:${input.ai_mark};合法值 ${AI_MARKS.join('/')}`);
|
|
538
|
+
}
|
|
539
|
+
if (input.status_info !== undefined && !['0', '1'].includes(String(input.status_info))) {
|
|
540
|
+
throw contractValidationError('status_info_invalid', "status_info 只能是 '0'(停用)或 '1'(启用)");
|
|
541
|
+
}
|
|
542
|
+
if (input.pzxx !== undefined)
|
|
543
|
+
input.pzxx = toJsonString(input.pzxx, 'pzxx');
|
|
544
|
+
},
|
|
545
|
+
body: (input) => bodyFrom(input, ['id', 'ai_id', 'ai_mark', 'ai_name', 'status_info', 'headline', 'prompt', 'ai_desc', 'iorder', 'pzxx']),
|
|
546
|
+
fingerprint: fingerprintFields(['id', 'ai_id', 'ai_mark', 'ai_name', 'status_info', 'headline', 'prompt', 'ai_desc', 'iorder', 'pzxx']),
|
|
547
|
+
preview: (input) => ({
|
|
548
|
+
action: '修改智能体配置',
|
|
549
|
+
id: input.id,
|
|
550
|
+
ai_mark: input.ai_mark,
|
|
551
|
+
status_info: input.status_info,
|
|
552
|
+
warning: '一次只改一个智能体;pzxx / ruleData 等附加字段会被服务端拒绝,已自动剔除 ruleData',
|
|
553
|
+
}),
|
|
554
|
+
body2: undefined,
|
|
555
|
+
async readback(_result, input, host) {
|
|
556
|
+
const rows = await queryAiConfig(host, requireAiId(input));
|
|
557
|
+
const hit = rows.find((row) => String(row.id) === String(input.id));
|
|
558
|
+
if (!hit)
|
|
559
|
+
throw new Error('回读未找到该智能体');
|
|
560
|
+
if (input.status_info !== undefined && String(hit.status_info) !== String(input.status_info)) {
|
|
561
|
+
throw new Error(`status_info 未落库:期望 ${input.status_info},实际 ${hit.status_info}`);
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
id: hit.id,
|
|
565
|
+
ai_mark: hit.ai_mark,
|
|
566
|
+
status_info: hit.status_info,
|
|
567
|
+
...(await pageHint(host)),
|
|
568
|
+
};
|
|
569
|
+
},
|
|
570
|
+
};
|
|
571
|
+
// ---------------------------------------------------------------- 提示语(aizjid 服务端不补,必须自算最大序号 + 1)
|
|
572
|
+
function nextAizjid(rows) {
|
|
573
|
+
let max = 0;
|
|
574
|
+
for (const row of rows) {
|
|
575
|
+
const n = Number(row.aizjid);
|
|
576
|
+
if (Number.isFinite(n) && n > max)
|
|
577
|
+
max = n;
|
|
578
|
+
}
|
|
579
|
+
return String(max + 1);
|
|
580
|
+
}
|
|
581
|
+
class TipsCreatePrepareOperation extends ContractWritePrepareOperation {
|
|
582
|
+
async execute(input, context) {
|
|
583
|
+
requireNonEmptyString(input.tsynr, 'tsynr', 2000);
|
|
584
|
+
requireAiId(input);
|
|
585
|
+
if (input.aizjid === undefined)
|
|
586
|
+
input.aizjid = nextAizjid(await queryTips(context.host, requireAiId(input)));
|
|
587
|
+
return super.execute(input, context);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
class TipsCreateApplyOperation extends ContractWriteApplyOperation {
|
|
591
|
+
async execute(input, context) {
|
|
592
|
+
requireNonEmptyString(input.tsynr, 'tsynr', 2000);
|
|
593
|
+
requireAiId(input);
|
|
594
|
+
// apply 阶段重算序号:prepare 与 apply 之间可能有其他人新增了提示语。
|
|
595
|
+
if (input.aizjid === undefined)
|
|
596
|
+
input.aizjid = nextAizjid(await queryTips(context.host, requireAiId(input)));
|
|
597
|
+
return super.execute(input, context);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
const tipsCreateSpec = {
|
|
601
|
+
method: 'POST',
|
|
602
|
+
path: `${TIP_BASE}/save`,
|
|
603
|
+
validate(input) {
|
|
604
|
+
requireNonEmptyString(input.tsynr, 'tsynr', 2000);
|
|
605
|
+
requireAiId(input);
|
|
606
|
+
if (input.aizjid === undefined) {
|
|
607
|
+
throw contractValidationError('aizjid_missing', 'aizjid 缺失(正常由 CLI 自动计算,请重新 prepare)');
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
body: (input) => ({ tsynr: input.tsynr, ai_id: requireAiId(input), aizjid: String(input.aizjid) }),
|
|
611
|
+
fingerprint: fingerprintFields(['tsynr', 'ai_id']),
|
|
612
|
+
preview: (input) => ({
|
|
613
|
+
action: '新增提示语',
|
|
614
|
+
tsynr: input.tsynr,
|
|
615
|
+
aizjid: input.aizjid,
|
|
616
|
+
warning: '提示语无编辑接口;不传 aizjid 会显示"序号undefined",CLI 已自动按当前最大序号 + 1 计算',
|
|
617
|
+
}),
|
|
618
|
+
async readback(_result, input, host) {
|
|
619
|
+
const rows = await queryTips(host, requireAiId(input));
|
|
620
|
+
const hit = rows.find((row) => row.tsynr === input.tsynr);
|
|
621
|
+
if (!hit)
|
|
622
|
+
throw new Error('回读未找到刚新增的提示语');
|
|
623
|
+
if (String(hit.aizjid) !== String(input.aizjid)) {
|
|
624
|
+
throw new Error(`序号未落库:期望 ${input.aizjid},实际 ${hit.aizjid}`);
|
|
625
|
+
}
|
|
626
|
+
return { id: hit.id, aizjid: hit.aizjid, ...(await pageHint(host)) };
|
|
627
|
+
},
|
|
628
|
+
};
|
|
629
|
+
const tipsDeleteSpec = {
|
|
630
|
+
method: 'POST',
|
|
631
|
+
path: `${TIP_BASE}/batchDelete`,
|
|
632
|
+
validate(input) {
|
|
633
|
+
input.ids = idList(input.ids ?? input.id, 'ids');
|
|
634
|
+
requireAiId(input);
|
|
635
|
+
},
|
|
636
|
+
body: (input) => ({ ids: input.ids, ai_id: requireAiId(input) }),
|
|
637
|
+
fingerprint: fingerprintFields(['ids', 'ai_id']),
|
|
638
|
+
preview: (input) => ({ action: '批量删除提示语', ids: input.ids, ai_id: requireAiId(input) }),
|
|
639
|
+
async readback(_result, input, host) {
|
|
640
|
+
const rows = await queryTips(host, requireAiId(input));
|
|
641
|
+
const remaining = rows.filter((row) => input.ids.includes(String(row.id)));
|
|
642
|
+
if (remaining.length)
|
|
643
|
+
throw new Error(`删除后仍能查到 ${remaining.length} 条提示语`);
|
|
644
|
+
return { removed: true, ...(await pageHint(host)) };
|
|
645
|
+
},
|
|
646
|
+
};
|
|
647
|
+
// ---------------------------------------------------------------- 内控规则台账(分类 → 规则 → 规则组,三步缺一不可)
|
|
648
|
+
const classificationCreateSpec = {
|
|
649
|
+
method: 'POST',
|
|
650
|
+
path: `${AI_BASE}/save`,
|
|
651
|
+
validate(input) {
|
|
652
|
+
requireNonEmptyString(input.htlx, 'htlx(分类名称)', 200);
|
|
653
|
+
},
|
|
654
|
+
body: (input) => ({ htlx: input.htlx, htlxlabelid: input.htlxlabelid ?? '', type: 'contract_classification' }),
|
|
655
|
+
fingerprint: fingerprintFields(['htlx', 'htlxlabelid']),
|
|
656
|
+
preview: (input) => ({ action: '新建内控规则分类', htlx: input.htlx }),
|
|
657
|
+
async precheck(input, host) {
|
|
658
|
+
const rows = await queryClassificationList(host);
|
|
659
|
+
if (rows.some((row) => row.htlx === input.htlx)) {
|
|
660
|
+
throw contractPolicyError('classification_already_exists', '已存在同名内控规则分类,禁止重复创建', { htlx: input.htlx });
|
|
661
|
+
}
|
|
662
|
+
return { duplicateCheck: 'passed' };
|
|
663
|
+
},
|
|
664
|
+
async readback(_result, input, host) {
|
|
665
|
+
const rows = await queryClassificationList(host);
|
|
666
|
+
const hit = rows.find((row) => row.htlx === input.htlx);
|
|
667
|
+
if (!hit)
|
|
668
|
+
throw new Error('分类列表中未找到刚创建的分类');
|
|
669
|
+
return { id: hit.id, htlx: hit.htlx, ...(await pageHint(host)) };
|
|
670
|
+
},
|
|
671
|
+
};
|
|
672
|
+
const classificationUpdateSpec = {
|
|
673
|
+
method: 'POST',
|
|
674
|
+
path: `${AI_BASE}/update`,
|
|
675
|
+
validate(input) {
|
|
676
|
+
requireId(input.id, '分类 id');
|
|
677
|
+
requireNonEmptyString(input.htlx, 'htlx(分类名称)', 200);
|
|
678
|
+
},
|
|
679
|
+
body: (input) => ({ id: String(input.id), htlx: input.htlx }),
|
|
680
|
+
fingerprint: fingerprintFields(['id', 'htlx']),
|
|
681
|
+
preview: (input) => ({ action: '修改内控规则分类', id: input.id, htlx: input.htlx }),
|
|
682
|
+
async readback(_result, input, host) {
|
|
683
|
+
const rows = await queryClassificationList(host);
|
|
684
|
+
const hit = rows.find((row) => String(row.id) === String(input.id));
|
|
685
|
+
if (!hit)
|
|
686
|
+
throw new Error('分类列表中未找到被修改的分类');
|
|
687
|
+
return { id: hit.id, htlx: hit.htlx, ...(await pageHint(host)) };
|
|
688
|
+
},
|
|
689
|
+
};
|
|
690
|
+
const classificationDeleteSpec = {
|
|
691
|
+
method: 'POST',
|
|
692
|
+
path: `${AI_BASE}/delete`,
|
|
693
|
+
validate(input) {
|
|
694
|
+
input.id = requireId(input.id, '分类 id');
|
|
695
|
+
},
|
|
696
|
+
body: (input) => ({ id: String(input.id) }),
|
|
697
|
+
fingerprint: fingerprintFields(['id']),
|
|
698
|
+
preview: (input) => ({ action: '删除内控规则分类', id: input.id }),
|
|
699
|
+
async precheck(input, host) {
|
|
700
|
+
const rows = await queryRuleList(host, String(input.id));
|
|
701
|
+
if (rows.length) {
|
|
702
|
+
throw contractPolicyError('rules_remaining', `该分类下仍有 ${rows.length} 条规则,必须先批量删除规则再删分类`, { id: input.id, rules: rows.map((row) => ({ id: row.id, gzmc: row.gzmc })) });
|
|
703
|
+
}
|
|
704
|
+
return { ruleCount: 0 };
|
|
705
|
+
},
|
|
706
|
+
async readback(_result, input, host) {
|
|
707
|
+
const rows = await queryClassificationList(host);
|
|
708
|
+
if (rows.some((row) => String(row.id) === String(input.id))) {
|
|
709
|
+
throw new Error('删除接口返回成功,但分类列表中仍能查到该分类');
|
|
710
|
+
}
|
|
711
|
+
return { removed: true, ...(await pageHint(host)) };
|
|
712
|
+
},
|
|
713
|
+
};
|
|
714
|
+
const ruleCreateSpec = {
|
|
715
|
+
method: 'POST',
|
|
716
|
+
path: `${AI_BASE}/save`,
|
|
717
|
+
validate(input) {
|
|
718
|
+
requireNonEmptyString(input.gzmc, 'gzmc(规则名称)', 200);
|
|
719
|
+
requireNonEmptyString(input.rule_content, 'rule_content(规则内容)', 8000);
|
|
720
|
+
requireId(input.flid, 'flid(所属分类 id)');
|
|
721
|
+
if (input.enable_info !== undefined && !['0', '1'].includes(String(input.enable_info))) {
|
|
722
|
+
throw contractValidationError('enable_info_invalid', "enable_info 只能是 '0' 或 '1'");
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
body: (input) => ({
|
|
726
|
+
gzmc: input.gzmc,
|
|
727
|
+
rule_content: input.rule_content,
|
|
728
|
+
flid: String(input.flid),
|
|
729
|
+
enable_info: input.enable_info === undefined ? '1' : String(input.enable_info),
|
|
730
|
+
type: 'contract_rule_datasource',
|
|
731
|
+
}),
|
|
732
|
+
fingerprint: fingerprintFields(['gzmc', 'rule_content', 'flid', 'enable_info']),
|
|
733
|
+
preview: (input) => ({
|
|
734
|
+
action: '新建内控规则',
|
|
735
|
+
gzmc: input.gzmc,
|
|
736
|
+
flid: input.flid,
|
|
737
|
+
warning: '只建规则助手里看不到;必须再建规则组关联 Control 智能体(三步缺一不可)',
|
|
738
|
+
}),
|
|
739
|
+
async precheck(input, host) {
|
|
740
|
+
const rows = await queryClassificationList(host);
|
|
741
|
+
if (!rows.some((row) => String(row.id) === String(input.flid))) {
|
|
742
|
+
throw contractPolicyError('classification_not_found', 'flid 对应的分类不存在,先用 rule.classification.list 获取', { flid: input.flid });
|
|
743
|
+
}
|
|
744
|
+
return { classificationExists: true };
|
|
745
|
+
},
|
|
746
|
+
async readback(_result, input, host) {
|
|
747
|
+
const rows = await queryRuleList(host, String(input.flid));
|
|
748
|
+
const hit = rows.find((row) => row.gzmc === input.gzmc);
|
|
749
|
+
if (!hit)
|
|
750
|
+
throw new Error('规则列表中未找到刚创建的规则');
|
|
751
|
+
return { id: hit.id, gzmc: hit.gzmc, ...(await pageHint(host)) };
|
|
752
|
+
},
|
|
753
|
+
};
|
|
754
|
+
const ruleUpdateSpec = {
|
|
755
|
+
method: 'POST',
|
|
756
|
+
path: `${AI_BASE}/update`,
|
|
757
|
+
validate(input) {
|
|
758
|
+
requireId(input.id, '规则 id');
|
|
759
|
+
if (input.flid !== undefined)
|
|
760
|
+
requireId(input.flid, 'flid');
|
|
761
|
+
},
|
|
762
|
+
body: (input) => bodyFrom(input, ['id', 'gzmc', 'rule_content', 'flid', 'enable_info'], { id: String(input.id) }),
|
|
763
|
+
fingerprint: fingerprintFields(['id', 'gzmc', 'rule_content', 'flid', 'enable_info']),
|
|
764
|
+
preview: (input) => ({ action: '修改内控规则', id: input.id, gzmc: input.gzmc }),
|
|
765
|
+
async readback(_result, input, host) {
|
|
766
|
+
const rows = await queryRuleList(host, input.flid === undefined ? undefined : String(input.flid));
|
|
767
|
+
const hit = rows.find((row) => String(row.id) === String(input.id));
|
|
768
|
+
if (!hit)
|
|
769
|
+
throw new Error('规则列表中未找到被修改的规则(未传 flid 时按分类过滤可能查不到,建议补传 flid)');
|
|
770
|
+
return { id: hit.id, gzmc: hit.gzmc, ...(await pageHint(host)) };
|
|
771
|
+
},
|
|
772
|
+
};
|
|
773
|
+
const ruleDeleteSpec = {
|
|
774
|
+
method: 'POST',
|
|
775
|
+
path: `${AI_BASE}/delete`,
|
|
776
|
+
validate(input) {
|
|
777
|
+
input.ids = idList(input.ids ?? input.id, 'ids').join(',');
|
|
778
|
+
},
|
|
779
|
+
body: (input) => ({ ids: String(input.ids) }),
|
|
780
|
+
fingerprint: fingerprintFields(['ids']),
|
|
781
|
+
preview: (input) => ({ action: '批量删除内控规则', ids: input.ids }),
|
|
782
|
+
async readback(_result, input, host) {
|
|
783
|
+
const ids = String(input.ids).split(',');
|
|
784
|
+
const rows = await queryRuleList(host);
|
|
785
|
+
const remaining = rows.filter((row) => ids.includes(String(row.id)));
|
|
786
|
+
if (remaining.length)
|
|
787
|
+
throw new Error(`删除后规则列表中仍能查到 ${remaining.length} 条规则`);
|
|
788
|
+
return { removed: true, ...(await pageHint(host)) };
|
|
789
|
+
},
|
|
790
|
+
};
|
|
791
|
+
function normalizeRuleSource(value, name) {
|
|
792
|
+
let items;
|
|
793
|
+
if (typeof value === 'string') {
|
|
794
|
+
try {
|
|
795
|
+
items = JSON.parse(value);
|
|
796
|
+
}
|
|
797
|
+
catch {
|
|
798
|
+
throw contractValidationError('json_invalid', `${name} 必须是可解析的 JSON 数组字符串`);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
items = value;
|
|
803
|
+
}
|
|
804
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
805
|
+
throw contractValidationError('input_invalid', `${name} 必须是非空数组(元素含 id 与 name)`);
|
|
806
|
+
}
|
|
807
|
+
for (const item of items) {
|
|
808
|
+
if (!item || typeof item !== 'object' || !item.id || !item.name) {
|
|
809
|
+
throw contractValidationError('input_invalid', `${name} 的每个元素必须包含 id 与 name`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return JSON.stringify(items);
|
|
813
|
+
}
|
|
814
|
+
const ruleGroupCreateSpec = {
|
|
815
|
+
method: 'POST',
|
|
816
|
+
path: `${AI_BASE}/saveCustomRules`,
|
|
817
|
+
validate(input) {
|
|
818
|
+
requireNonEmptyString(input.rule_flag, 'rule_flag(规则组名称)', 200);
|
|
819
|
+
requireConfigId(input, 'Control 智能体 id');
|
|
820
|
+
if (input.default_type !== undefined && !['0', '1'].includes(String(input.default_type))) {
|
|
821
|
+
throw contractValidationError('default_type_invalid', "default_type 只能是 0 或 1");
|
|
822
|
+
}
|
|
823
|
+
},
|
|
824
|
+
body: (input) => ({
|
|
825
|
+
rule_flag: input.rule_flag,
|
|
826
|
+
default_type: input.default_type === undefined ? 0 : Number(input.default_type),
|
|
827
|
+
contract_rule_source: normalizeRuleSource(input.contract_rule_source, 'contract_rule_source'),
|
|
828
|
+
config_id: requireConfigId(input, 'Control 智能体 id'),
|
|
829
|
+
}),
|
|
830
|
+
fingerprint: fingerprintFields(['rule_flag', 'default_type', 'contract_rule_source', 'config_id']),
|
|
831
|
+
preview: (input) => ({
|
|
832
|
+
action: '新建内控规则组',
|
|
833
|
+
rule_flag: input.rule_flag,
|
|
834
|
+
default_type: input.default_type ?? 0,
|
|
835
|
+
warning: 'default_type 服务端互斥:一个助手只能有一个默认组,后建/后改的会抢占默认标记(前面自动回落 0);要当默认的放最后建',
|
|
836
|
+
}),
|
|
837
|
+
async readback(_result, input, host) {
|
|
838
|
+
const value = await host.contractRequest({
|
|
839
|
+
method: 'POST',
|
|
840
|
+
path: `${CONFIG_BASE}/queryCustomConfigRules`,
|
|
841
|
+
body: { config_id: requireConfigId(input, 'Control 智能体 id') },
|
|
842
|
+
});
|
|
843
|
+
const hit = rowsOf(value).find((row) => row.rule_flag === input.rule_flag);
|
|
844
|
+
if (!hit)
|
|
845
|
+
throw new Error('规则组列表中未找到刚创建的规则组');
|
|
846
|
+
return { id: hit.id, rule_flag: hit.rule_flag, default_type: hit.default_type, ...(await pageHint(host)) };
|
|
847
|
+
},
|
|
848
|
+
};
|
|
849
|
+
const ruleGroupUpdateSpec = {
|
|
850
|
+
method: 'POST',
|
|
851
|
+
path: `${AI_BASE}/updateCustomRules`,
|
|
852
|
+
validate(input) {
|
|
853
|
+
requireId(input.id, '规则组 id');
|
|
854
|
+
if (input.rule_flag !== undefined)
|
|
855
|
+
requireNonEmptyString(input.rule_flag, 'rule_flag', 200);
|
|
856
|
+
if (input.default_type !== undefined && !['0', '1'].includes(String(input.default_type))) {
|
|
857
|
+
throw contractValidationError('default_type_invalid', "default_type 只能是 0 或 1");
|
|
858
|
+
}
|
|
859
|
+
},
|
|
860
|
+
body: (input) => bodyFrom(input, ['id', 'rule_flag', 'default_type'], { id: String(input.id) }),
|
|
861
|
+
fingerprint: fingerprintFields(['id', 'rule_flag', 'default_type']),
|
|
862
|
+
preview: (input) => ({ action: '修改内控规则组', id: input.id, rule_flag: input.rule_flag }),
|
|
863
|
+
async readback(_result, input, host) {
|
|
864
|
+
if (input.config_id === undefined && input.configId === undefined)
|
|
865
|
+
return { skipped: '未提供 config_id,无法回读' };
|
|
866
|
+
const value = await host.contractRequest({
|
|
867
|
+
method: 'POST',
|
|
868
|
+
path: `${CONFIG_BASE}/queryCustomConfigRules`,
|
|
869
|
+
body: { config_id: requireConfigId(input, 'Control 智能体 id') },
|
|
870
|
+
});
|
|
871
|
+
const hit = rowsOf(value).find((row) => String(row.id) === String(input.id));
|
|
872
|
+
if (!hit)
|
|
873
|
+
throw new Error('规则组列表中未找到被修改的规则组');
|
|
874
|
+
return { id: hit.id, rule_flag: hit.rule_flag, default_type: hit.default_type, ...(await pageHint(host)) };
|
|
875
|
+
},
|
|
876
|
+
};
|
|
877
|
+
const ruleGroupDeleteSpec = {
|
|
878
|
+
method: 'POST',
|
|
879
|
+
path: `${CONFIG_BASE}/batchDeleteCustomRules`,
|
|
880
|
+
validate(input) {
|
|
881
|
+
input.ids = idList(input.ids ?? input.id, 'ids');
|
|
882
|
+
},
|
|
883
|
+
body: (input) => ({ ids: input.ids }),
|
|
884
|
+
fingerprint: fingerprintFields(['ids']),
|
|
885
|
+
preview: (input) => ({ action: '批量删除内控规则组', ids: input.ids }),
|
|
886
|
+
async readback(_result, input, host) {
|
|
887
|
+
if (input.config_id === undefined && input.configId === undefined)
|
|
888
|
+
return { skipped: '未提供 config_id,无法回读' };
|
|
889
|
+
const value = await host.contractRequest({
|
|
890
|
+
method: 'POST',
|
|
891
|
+
path: `${CONFIG_BASE}/queryCustomConfigRules`,
|
|
892
|
+
body: { config_id: requireConfigId(input, 'Control 智能体 id') },
|
|
893
|
+
});
|
|
894
|
+
const remaining = rowsOf(value).filter((row) => input.ids.includes(String(row.id)));
|
|
895
|
+
if (remaining.length)
|
|
896
|
+
throw new Error(`删除后仍能查到 ${remaining.length} 个规则组`);
|
|
897
|
+
return { removed: true, ...(await pageHint(host)) };
|
|
898
|
+
},
|
|
899
|
+
};
|
|
900
|
+
// ---------------------------------------------------------------- 要素抽取(element_content 与 element_mxb 是两份重复数据,都要传)
|
|
901
|
+
const elementCreateSpec = {
|
|
902
|
+
method: 'POST',
|
|
903
|
+
path: `${AI_BASE}/element/saveElementRules`,
|
|
904
|
+
validate(input) {
|
|
905
|
+
requireNonEmptyString(input.element_name, 'element_name', 200);
|
|
906
|
+
requireConfigId(input, 'Extract 智能体 id');
|
|
907
|
+
if (input.default_type !== undefined && !['0', '1'].includes(String(input.default_type))) {
|
|
908
|
+
throw contractValidationError('default_type_invalid', "default_type 只能是 0 或 1");
|
|
909
|
+
}
|
|
910
|
+
let mxb;
|
|
911
|
+
if (typeof input.element_mxb === 'string') {
|
|
912
|
+
try {
|
|
913
|
+
mxb = JSON.parse(input.element_mxb);
|
|
914
|
+
}
|
|
915
|
+
catch {
|
|
916
|
+
throw contractValidationError('json_invalid', 'element_mxb 必须是可解析的 JSON 数组字符串');
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
mxb = input.element_mxb;
|
|
921
|
+
}
|
|
922
|
+
if (!Array.isArray(mxb) || mxb.length === 0) {
|
|
923
|
+
// 传空数组服务端报「系统错误」,必须前置拦截。
|
|
924
|
+
throw contractValidationError('input_invalid', 'element_mxb 必须是非空数组(元素含 name/fieldName/dbField)');
|
|
925
|
+
}
|
|
926
|
+
for (const item of mxb) {
|
|
927
|
+
if (!item || typeof item !== 'object' || !item.name || !item.fieldName || !item.dbField) {
|
|
928
|
+
throw contractValidationError('input_invalid', 'element_mxb 的每个元素必须包含 name、fieldName、dbField 三键');
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
input.element_mxb = JSON.stringify(mxb);
|
|
932
|
+
if (input.element_content === undefined || input.element_content === '') {
|
|
933
|
+
input.element_content = mxb.map((item) => item.name).join('\n');
|
|
934
|
+
}
|
|
935
|
+
},
|
|
936
|
+
body: (input) => ({
|
|
937
|
+
element_name: input.element_name,
|
|
938
|
+
element_content: String(input.element_content),
|
|
939
|
+
element_mxb: String(input.element_mxb),
|
|
940
|
+
default_type: input.default_type === undefined ? 1 : Number(input.default_type),
|
|
941
|
+
config_id: requireConfigId(input, 'Extract 智能体 id'),
|
|
942
|
+
}),
|
|
943
|
+
fingerprint: fingerprintFields(['element_name', 'element_content', 'element_mxb', 'default_type', 'config_id']),
|
|
944
|
+
preview: (input) => ({
|
|
945
|
+
action: '新增要素抽取集合',
|
|
946
|
+
element_name: input.element_name,
|
|
947
|
+
elementCount: JSON.parse(String(input.element_mxb)).length,
|
|
948
|
+
warning: 'element_content 与 element_mxb 是两份重复数据,必须都传(页面读 content,字段映射读 mxb);要素集合无更新接口,改动需先删后增',
|
|
949
|
+
}),
|
|
950
|
+
async readback(_result, input, host) {
|
|
951
|
+
const value = await host.contractRequest({
|
|
952
|
+
method: 'POST',
|
|
953
|
+
path: `${CONFIG_BASE}/queryElementConfigRules`,
|
|
954
|
+
body: { config_id: requireConfigId(input, 'Extract 智能体 id') },
|
|
955
|
+
});
|
|
956
|
+
const hit = rowsOf(value).find((row) => row.element_name === input.element_name);
|
|
957
|
+
if (!hit)
|
|
958
|
+
throw new Error('要素集合列表中未找到刚创建的集合');
|
|
959
|
+
return { id: hit.id, element_name: hit.element_name, ...(await pageHint(host)) };
|
|
960
|
+
},
|
|
961
|
+
};
|
|
962
|
+
const elementDeleteSpec = {
|
|
963
|
+
method: 'POST',
|
|
964
|
+
path: `${CONFIG_BASE}/batchDeleteElementRules`,
|
|
965
|
+
validate(input) {
|
|
966
|
+
input.ids = idList(input.ids ?? input.id, 'ids');
|
|
967
|
+
},
|
|
968
|
+
body: (input) => ({ ids: input.ids }),
|
|
969
|
+
fingerprint: fingerprintFields(['ids']),
|
|
970
|
+
preview: (input) => ({ action: '批量删除要素抽取集合', ids: input.ids }),
|
|
971
|
+
async readback(_result, input, host) {
|
|
972
|
+
if (input.config_id === undefined && input.configId === undefined)
|
|
973
|
+
return { skipped: '未提供 config_id,无法回读' };
|
|
974
|
+
const value = await host.contractRequest({
|
|
975
|
+
method: 'POST',
|
|
976
|
+
path: `${CONFIG_BASE}/queryElementConfigRules`,
|
|
977
|
+
body: { config_id: requireConfigId(input, 'Extract 智能体 id') },
|
|
978
|
+
});
|
|
979
|
+
const remaining = rowsOf(value).filter((row) => input.ids.includes(String(row.id)));
|
|
980
|
+
if (remaining.length)
|
|
981
|
+
throw new Error(`删除后仍能查到 ${remaining.length} 个要素集合`);
|
|
982
|
+
return { removed: true, ...(await pageHint(host)) };
|
|
983
|
+
},
|
|
984
|
+
};
|
|
985
|
+
// ---------------------------------------------------------------- 助手关联流程
|
|
986
|
+
function validateFlowInput(input, requireRecordId) {
|
|
987
|
+
requireAiId(input);
|
|
988
|
+
requireId(input.flowId, 'flowId(流程 id)');
|
|
989
|
+
requireId(input.rule_config_id ?? input.ruleConfigId, 'rule_config_id(Control 智能体 id)');
|
|
990
|
+
if (requireRecordId)
|
|
991
|
+
requireId(input.id, '记录 id');
|
|
992
|
+
const showPosition = input.showPosition === undefined ? undefined : String(input.showPosition);
|
|
993
|
+
if (showPosition !== undefined && !['1', '2', '3'].includes(showPosition)) {
|
|
994
|
+
throw contractValidationError('show_position_invalid', "showPosition 只能是 1(更多菜单)/ 2(右上角)/ 3(字段上)");
|
|
995
|
+
}
|
|
996
|
+
// 源资料 NEED-CONFIRM 三场景:不满足就直接拦截,让用户回补参数。
|
|
997
|
+
if (showPosition === '3' && (input.fieldId === undefined || input.fieldId === '')) {
|
|
998
|
+
throw contractValidationError('field_id_required', 'showPosition=3(字段上)时 fieldId 必传;请向用户确认来源字段后再 prepare');
|
|
999
|
+
}
|
|
1000
|
+
const termConfig = input.term_config;
|
|
1001
|
+
if (termConfig && typeof termConfig === 'object' && !Array.isArray(termConfig) && (termConfig.contractTypeField === undefined || termConfig.contractTypeField === '')) {
|
|
1002
|
+
throw contractValidationError('contract_type_field_required', 'term_config 缺少 contractTypeField(合同类型字段 id);请向用户确认后再 prepare');
|
|
1003
|
+
}
|
|
1004
|
+
if (input.key_config !== undefined) {
|
|
1005
|
+
let parsed = input.key_config;
|
|
1006
|
+
if (typeof parsed === 'string') {
|
|
1007
|
+
try {
|
|
1008
|
+
parsed = JSON.parse(parsed);
|
|
1009
|
+
}
|
|
1010
|
+
catch {
|
|
1011
|
+
throw contractValidationError('json_invalid', 'key_config 必须是可解析的 JSON');
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
const empty = parsed === null
|
|
1015
|
+
|| (Array.isArray(parsed) && parsed.length === 0)
|
|
1016
|
+
|| (typeof parsed === 'object' && !Array.isArray(parsed) && Object.keys(parsed).length === 0);
|
|
1017
|
+
if (empty) {
|
|
1018
|
+
throw contractValidationError('key_config_required', 'key_config 未指定任何关键词组;请先用 contract.assistant.keygroup.search 检索后确认');
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
const customRules = input.customRules;
|
|
1022
|
+
if (customRules && typeof customRules === 'object' && !Array.isArray(customRules) && String(customRules.type) === '0') {
|
|
1023
|
+
throw contractPolicyError('custom_rules_type_unsupported', 'customRules type=0(自定义内控规则)不被支持,请阻断用户并改用 type=1(指定规则)或 type=2(按合同类型过滤)');
|
|
1024
|
+
}
|
|
1025
|
+
if (input.showPosition === undefined)
|
|
1026
|
+
input.showPosition = '2';
|
|
1027
|
+
if (input.config !== undefined)
|
|
1028
|
+
input.config = toJsonString(input.config, 'config');
|
|
1029
|
+
for (const key of ['risk_review', 'customRules', 'key_config', 'consult_config', 'term_config', 'fieldMapping']) {
|
|
1030
|
+
if (input[key] !== undefined)
|
|
1031
|
+
input[key] = toJsonString(input[key], key);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
function flowBody(input, includeId) {
|
|
1035
|
+
const body = {
|
|
1036
|
+
toolId: FLOW_TOOL_ID,
|
|
1037
|
+
aiId: requireAiId(input),
|
|
1038
|
+
rule_config_id: String(input.rule_config_id ?? input.ruleConfigId),
|
|
1039
|
+
flowId: String(input.flowId),
|
|
1040
|
+
showPosition: String(input.showPosition),
|
|
1041
|
+
openMode: input.openMode === undefined ? 1 : input.openMode,
|
|
1042
|
+
config: input.config === undefined ? '[]' : String(input.config),
|
|
1043
|
+
};
|
|
1044
|
+
if (includeId)
|
|
1045
|
+
body.id = String(input.id);
|
|
1046
|
+
for (const key of ['risk_review', 'customRules', 'key_config', 'consult_config', 'term_config', 'fieldMapping']) {
|
|
1047
|
+
if (input[key] !== undefined)
|
|
1048
|
+
body[key] = input[key];
|
|
1049
|
+
}
|
|
1050
|
+
for (const key of ['elementExtractConfig', 'fieldId', 'fieldName']) {
|
|
1051
|
+
if (input[key] !== undefined)
|
|
1052
|
+
body[key] = input[key];
|
|
1053
|
+
}
|
|
1054
|
+
return body;
|
|
1055
|
+
}
|
|
1056
|
+
const flowSaveSpec = {
|
|
1057
|
+
method: 'POST',
|
|
1058
|
+
path: `${PERMISSION_BASE}/save`,
|
|
1059
|
+
validate(input) {
|
|
1060
|
+
validateFlowInput(input, false);
|
|
1061
|
+
},
|
|
1062
|
+
body: (input) => flowBody(input, false),
|
|
1063
|
+
fingerprint: fingerprintFields(['ai_id', 'aiId', 'flowId', 'rule_config_id', 'ruleConfigId', 'showPosition', 'openMode', 'config', 'risk_review', 'customRules', 'key_config', 'consult_config', 'term_config', 'fieldMapping', 'elementExtractConfig', 'fieldId', 'fieldName']),
|
|
1064
|
+
preview: (input) => ({
|
|
1065
|
+
action: '新增助手关联流程',
|
|
1066
|
+
aiId: input.ai_id ?? input.aiId,
|
|
1067
|
+
flowId: input.flowId,
|
|
1068
|
+
showPosition: input.showPosition,
|
|
1069
|
+
warning: '一个流程全局只能绑定一次(跨助手唯一);绑定前先用 contract.assistant.flow.list 排查',
|
|
1070
|
+
}),
|
|
1071
|
+
async readback(_result, input, host) {
|
|
1072
|
+
const rows = await queryFlowList(host, requireAiId(input), String(input.rule_config_id ?? input.ruleConfigId));
|
|
1073
|
+
const hit = rows.find((row) => String(row.flowId) === String(input.flowId));
|
|
1074
|
+
if (!hit)
|
|
1075
|
+
throw new Error('关联流程列表中未找到刚创建的绑定');
|
|
1076
|
+
return { id: hit.id, flowId: hit.flowId, ...(await pageHint(host)) };
|
|
1077
|
+
},
|
|
1078
|
+
};
|
|
1079
|
+
const flowUpdateSpec = {
|
|
1080
|
+
method: 'POST',
|
|
1081
|
+
path: `${PERMISSION_BASE}/modify`,
|
|
1082
|
+
pathOf: (input) => `${PERMISSION_BASE}/modify/${requireId(input.id, '记录 id')}`,
|
|
1083
|
+
validate(input) {
|
|
1084
|
+
validateFlowInput(input, true);
|
|
1085
|
+
},
|
|
1086
|
+
body: (input) => flowBody(input, true),
|
|
1087
|
+
fingerprint: fingerprintFields(['id', 'ai_id', 'aiId', 'flowId', 'rule_config_id', 'ruleConfigId', 'showPosition', 'openMode', 'config', 'risk_review', 'customRules', 'key_config', 'consult_config', 'term_config', 'fieldMapping', 'elementExtractConfig', 'fieldId', 'fieldName']),
|
|
1088
|
+
preview: (input) => ({ action: '修改助手关联流程', id: input.id, flowId: input.flowId, showPosition: input.showPosition }),
|
|
1089
|
+
async readback(_result, input, host) {
|
|
1090
|
+
const rows = await queryFlowList(host, requireAiId(input), String(input.rule_config_id ?? input.ruleConfigId));
|
|
1091
|
+
const hit = rows.find((row) => String(row.id) === String(input.id));
|
|
1092
|
+
if (!hit)
|
|
1093
|
+
throw new Error('关联流程列表中未找到被修改的绑定');
|
|
1094
|
+
return { id: hit.id, flowId: hit.flowId, showPosition: hit.showPosition, ...(await pageHint(host)) };
|
|
1095
|
+
},
|
|
1096
|
+
};
|
|
1097
|
+
const flowDeleteSpec = {
|
|
1098
|
+
method: 'POST',
|
|
1099
|
+
path: `${PERMISSION_BASE}/delete`,
|
|
1100
|
+
validate(input) {
|
|
1101
|
+
input.ids = idList(input.ids ?? input.id, 'ids').join(',');
|
|
1102
|
+
requireAiId(input);
|
|
1103
|
+
requireId(input.rule_config_id ?? input.ruleConfigId, 'rule_config_id(回读复核用)');
|
|
1104
|
+
},
|
|
1105
|
+
body: (input) => ({
|
|
1106
|
+
ids: String(input.ids),
|
|
1107
|
+
// 删除不带 type 会返回 success 但实际不删除,CLI 强制补上。
|
|
1108
|
+
type: FLOW_DELETE_TYPE,
|
|
1109
|
+
}),
|
|
1110
|
+
fingerprint: fingerprintFields(['ids', 'ai_id', 'aiId']),
|
|
1111
|
+
preview: (input) => ({ action: '删除助手关联流程', ids: input.ids, type: FLOW_DELETE_TYPE }),
|
|
1112
|
+
async readback(_result, input, host) {
|
|
1113
|
+
const ids = String(input.ids).split(',');
|
|
1114
|
+
const rows = await queryFlowList(host, requireAiId(input), String(input.rule_config_id ?? input.ruleConfigId));
|
|
1115
|
+
const remaining = rows.filter((row) => ids.includes(String(row.id)));
|
|
1116
|
+
if (remaining.length)
|
|
1117
|
+
throw new Error(`删除后关联流程列表中仍能查到 ${remaining.length} 条绑定(疑似缺 type 的假成功)`);
|
|
1118
|
+
return { removed: true, ...(await pageHint(host)) };
|
|
1119
|
+
},
|
|
1120
|
+
};
|
|
1121
|
+
// ---------------------------------------------------------------- 汇总
|
|
1122
|
+
export const contractAssistantOperations = [
|
|
1123
|
+
contractAssistantInitOperation,
|
|
1124
|
+
contractAssistantMetaOperation,
|
|
1125
|
+
contractAssistantListOperation,
|
|
1126
|
+
contractAssistantDocQueryOperation,
|
|
1127
|
+
contractAssistantConfigQueryOperation,
|
|
1128
|
+
contractAssistantTipsQueryOperation,
|
|
1129
|
+
contractAssistantDocFolderSearchOperation,
|
|
1130
|
+
contractAssistantKeygroupSearchOperation,
|
|
1131
|
+
contractAssistantClassificationListOperation,
|
|
1132
|
+
contractAssistantRuleListOperation,
|
|
1133
|
+
contractAssistantRuleGroupQueryOperation,
|
|
1134
|
+
contractAssistantElementQueryOperation,
|
|
1135
|
+
contractAssistantFlowListOperation,
|
|
1136
|
+
...writePair('contract.assistant.create', assistantCreateSpec),
|
|
1137
|
+
...writePair('contract.assistant.delete', assistantDeleteSpec),
|
|
1138
|
+
...writePair('contract.assistant.doc.save', assistantDocSaveSpec),
|
|
1139
|
+
...writePair('contract.assistant.agent.update', assistantAgentUpdateSpec),
|
|
1140
|
+
...tipsWritePair(tipsCreateSpec),
|
|
1141
|
+
...writePair('contract.assistant.tips.delete', tipsDeleteSpec),
|
|
1142
|
+
...writePair('contract.assistant.rule.classification.create', classificationCreateSpec),
|
|
1143
|
+
...writePair('contract.assistant.rule.classification.update', classificationUpdateSpec),
|
|
1144
|
+
...writePair('contract.assistant.rule.classification.delete', classificationDeleteSpec),
|
|
1145
|
+
...writePair('contract.assistant.rule.create', ruleCreateSpec),
|
|
1146
|
+
...writePair('contract.assistant.rule.update', ruleUpdateSpec),
|
|
1147
|
+
...writePair('contract.assistant.rule.delete', ruleDeleteSpec),
|
|
1148
|
+
...writePair('contract.assistant.rule.group.create', ruleGroupCreateSpec),
|
|
1149
|
+
...writePair('contract.assistant.rule.group.update', ruleGroupUpdateSpec),
|
|
1150
|
+
...writePair('contract.assistant.rule.group.delete', ruleGroupDeleteSpec),
|
|
1151
|
+
...writePair('contract.assistant.element.create', elementCreateSpec),
|
|
1152
|
+
...writePair('contract.assistant.element.delete', elementDeleteSpec),
|
|
1153
|
+
...writePair('contract.assistant.flow.save', flowSaveSpec),
|
|
1154
|
+
...writePair('contract.assistant.flow.update', flowUpdateSpec),
|
|
1155
|
+
...writePair('contract.assistant.flow.delete', flowDeleteSpec),
|
|
1156
|
+
];
|