weaver-work-cli 0.1.32 → 0.1.36
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/README.md +2 -2
- package/dist/chunks/{archive-UMGX72K6.js → archive-XBVH7JU7.js} +2 -2
- package/dist/chunks/{archive-oa-N2IYBLF3.js → archive-oa-MZM3EKFQ.js} +2 -2
- package/dist/chunks/{asset-GLEGKFG5.js → asset-4XO7UOUW.js} +2 -2
- package/dist/chunks/{attachment-content-AQNX5N3S.js → attachment-content-VXSWANAC.js} +3 -2
- package/dist/chunks/{blog-E44OTVDA.js → blog-PGZKOPSC.js} +2 -2
- package/dist/chunks/{caizhilian-DQUFZXZL.js → caizhilian-BH4ZVGUN.js} +2 -2
- package/dist/chunks/{calendar-JKPIRKFD.js → calendar-MXEMTIO4.js} +2 -2
- package/dist/chunks/{chunk-QWY6PGL6.js → chunk-2LV74IWP.js} +361 -56
- package/dist/chunks/chunk-3SO6QL6U.js +73 -0
- package/dist/chunks/chunk-5DQEEGCC.js +229 -0
- package/dist/chunks/{chunk-G3QJULO2.js → chunk-6LLJFKJE.js} +1 -1
- package/dist/chunks/{chunk-C6R3B2ML.js → chunk-BPE37FPB.js} +35 -7
- package/dist/chunks/chunk-BYATLLEF.js +68 -0
- package/dist/chunks/chunk-BYBSCN4X.js +932 -0
- package/dist/chunks/{chunk-CVZ2JOSW.js → chunk-F746NKNF.js} +1 -1
- package/dist/chunks/{chunk-GCG2NMWR.js → chunk-HXJQI5NM.js} +4 -2
- package/dist/chunks/{chunk-YRVDW4WN.js → chunk-I6QGIBDM.js} +2 -2
- package/dist/chunks/{chunk-H7J4DC5R.js → chunk-JCSN3JGM.js} +2 -2
- package/dist/chunks/{chunk-2GA5ZIQL.js → chunk-JNG42JKG.js} +30 -1
- package/dist/chunks/{chunk-DW6AUUKS.js → chunk-JUXVYKII.js} +9 -4
- package/dist/chunks/{chunk-6RKD4DRZ.js → chunk-JYKABYCV.js} +2 -2
- package/dist/chunks/{chunk-6EFWDL7Y.js → chunk-NCOVUAZK.js} +2 -2
- package/dist/chunks/{chunk-E4Z23GHM.js → chunk-QHFANTX7.js} +1114 -904
- package/dist/chunks/{ebuilder-form-V5FG2EHC.js → chunk-QYGO5EE2.js} +22 -1148
- package/dist/chunks/{chunk-IURUFGZK.js → chunk-T72N74ZT.js} +50 -33
- package/dist/chunks/{chunk-H6XSTSLM.js → chunk-TC6MZAO3.js} +137 -204
- package/dist/chunks/{chunk-WKI2FBIQ.js → chunk-VPUTVEMC.js} +737 -341
- package/dist/chunks/{chunk-WSKBSQ2U.js → chunk-VPXOPQJI.js} +2 -2
- package/dist/chunks/chunk-WXFPWCXG.js +774 -0
- package/dist/chunks/{chunk-4YC43QQ4.js → chunk-XMGSYD6S.js} +4 -4
- package/dist/chunks/conflict-JAB2LJEZ.js +21 -0
- package/dist/chunks/continuation-RN6PZN2M.js +11 -0
- package/dist/chunks/contract-XI2EEIZU.js +9110 -0
- package/dist/chunks/cowork-SCPGLBA4.js +2527 -0
- package/dist/chunks/ebuilder-form-EYLHNOTA.js +121 -0
- package/dist/chunks/{ehr-HMFY6VIL.js → ehr-EAP77IMT.js} +1344 -27
- package/dist/chunks/{esb-ZYSVB7SG.js → esb-2TGGXEE7.js} +4 -4
- package/dist/chunks/{fna-B7RKPAZD.js → fna-2X7ZE7BU.js} +13 -8
- package/dist/chunks/{host-OCH2BCCV.js → host-C6EL2RKL.js} +3 -3
- package/dist/chunks/{host-45WED2KK.js → host-I4NRK4MX.js} +3 -3
- package/dist/chunks/{host-I4Z6BFYX.js → host-Q4LZGCRU.js} +3 -3
- package/dist/chunks/host-UEI5FGDM.js +23 -0
- package/dist/chunks/{host-KNZ52LJZ.js → host-XCLDDXKQ.js} +3 -3
- package/dist/chunks/{hrm-2KAZGUHI.js → hrm-YXERJM76.js} +3 -3
- package/dist/chunks/{im-ACJJWH4U.js → im-A4VZN2TQ.js} +88 -12
- package/dist/chunks/{invoice-ZK3WCX2U.js → invoice-2Z6YPMIS.js} +3 -3
- package/dist/chunks/{jiuchuanhui-5PQD3IL2.js → jiuchuanhui-AR5GMKZB.js} +2 -2
- package/dist/chunks/{lazy-dispatch-JQXBW3GM.js → lazy-dispatch-QXW7P4X3.js} +4 -4
- package/dist/chunks/{mail-S2HDDXO2.js → mail-7GZ6CPBJ.js} +45 -24
- package/dist/chunks/{meeting-QVN3I37L.js → meeting-3NMGA3MB.js} +278 -43
- package/dist/chunks/{okr-QNG4VC2Q.js → okr-BZ6XUOAG.js} +2 -2
- package/dist/chunks/operations-5QL3AECO.js +11 -0
- package/dist/chunks/{plan-A25EMW6U.js → plan-DR5CMUYP.js} +2 -2
- package/dist/chunks/{project-G6XV66DZ.js → project-6J63MK33.js} +2 -2
- package/dist/chunks/{registry-DC6KR3NH.js → registry-KDD7DHNK.js} +4 -4
- package/dist/chunks/{registry-2VLUAESA.js → registry-OZSMDVPP.js} +2 -2
- package/dist/chunks/{registry-7C5UMIPL.js → registry-TKX2E7C7.js} +2 -2
- package/dist/chunks/{registry-TI3CIUXF.js → registry-VRTNC6UR.js} +8 -6
- package/dist/chunks/{room-recommend-WRTHO6Y2.js → room-recommend-XCWRMB5P.js} +4 -3
- package/dist/chunks/{schedule-KFIITGSK.js → schedule-IVBICP32.js} +4 -3
- package/dist/chunks/{workflow-DJ7SYWAF.js → workflow-D74NFK3C.js} +179 -6
- package/dist/index.js +35 -25
- package/dist/internal/e10/auth-recovery.js +14 -4
- package/dist/internal/e10/fetch-timing.js +31 -0
- package/dist/internal/e10/request-runtime.js +5 -0
- package/dist/shortcuts/contract/host.js +34 -59
- package/dist/shortcuts/cowork/continuation.js +70 -0
- package/dist/shortcuts/cowork/errors.js +59 -0
- package/dist/shortcuts/cowork/host.js +180 -0
- package/dist/shortcuts/cowork/index.js +166 -0
- package/dist/shortcuts/cowork/manifest.js +65 -0
- package/dist/shortcuts/cowork/operations/discuss-write.js +277 -0
- package/dist/shortcuts/cowork/operations/item-write.js +380 -0
- package/dist/shortcuts/cowork/operations/member-write.js +283 -0
- package/dist/shortcuts/cowork/operations/permission-write.js +286 -0
- package/dist/shortcuts/cowork/operations/read.js +423 -0
- package/dist/shortcuts/cowork/operations/registry.js +39 -0
- package/dist/shortcuts/cowork/operations/review-write.js +157 -0
- package/dist/shortcuts/cowork/operations/shared.js +215 -0
- package/dist/shortcuts/cowork/operations/types.js +39 -0
- package/dist/shortcuts/cowork/operations.js +19 -0
- package/dist/shortcuts/ehr/host.js +4 -1
- package/dist/shortcuts/ehr/operations/recruit-candidate-import.js +757 -0
- package/dist/shortcuts/ehr/operations/recruit-position.js +367 -0
- package/dist/shortcuts/ehr/operations/recruit-stats.js +133 -0
- package/dist/shortcuts/ehr/operations/recruit-write.js +43 -16
- package/dist/shortcuts/ehr/operations/recruit.js +182 -10
- package/dist/shortcuts/ehr/operations/registry.js +6 -0
- package/dist/shortcuts/fna/operations/expense-chart.js +19 -7
- package/dist/shortcuts/im/diagnostics.js +97 -0
- package/dist/shortcuts/im/host.js +58 -31
- package/dist/shortcuts/im/index.js +7 -2
- package/dist/shortcuts/im/operations/at-mention.js +127 -0
- package/dist/shortcuts/im/operations/file-user.js +10 -2
- package/dist/shortcuts/im/operations/group-read.js +26 -5
- package/dist/shortcuts/im/operations/group-write.js +35 -28
- package/dist/shortcuts/im/operations/msg-sync.js +7 -2
- package/dist/shortcuts/im/operations/msg-write.js +76 -10
- package/dist/shortcuts/im/operations/quote-mention.js +119 -0
- package/dist/shortcuts/im/operations/session.js +1 -1
- package/dist/shortcuts/im/operations/shared.js +10 -0
- package/dist/shortcuts/im/operations/types.js +13 -0
- package/dist/shortcuts/index.js +10 -0
- package/dist/shortcuts/mail/host.js +12 -1
- package/dist/shortcuts/mail/operations/types.js +17 -2
- package/dist/shortcuts/meeting/host.js +27 -34
- package/dist/shortcuts/meeting/index.js +79 -1
- package/dist/shortcuts/meeting/manifest.js +10 -0
- package/dist/shortcuts/meeting/operations/create.js +4 -1
- package/dist/shortcuts/meeting/operations/eb/catalog.js +10 -0
- package/dist/shortcuts/meeting/operations/eb/conflict.js +84 -8
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +1195 -140
- package/dist/shortcuts/meeting/operations/eb/factory.js +170 -8
- package/dist/shortcuts/meeting/operations/eb/index.js +16 -3
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +50 -5
- package/dist/shortcuts/meeting/operations/eb-fallback.js +163 -0
- package/dist/shortcuts/meeting/operations/env.js +99 -46
- package/dist/shortcuts/meeting/operations/form-fields.js +110 -0
- package/dist/shortcuts/meeting/operations/invitation.js +79 -20
- package/dist/shortcuts/meeting/operations/registry.js +2 -0
- package/dist/shortcuts/meeting/operations/standard-guard.js +71 -0
- package/dist/shortcuts/meeting/operations.js +16 -0
- package/dist/shortcuts/workflow/endpoints.js +8 -3
- package/dist/shortcuts/workflow/index.js +110 -1
- package/dist/shortcuts/workflow/operations/form.js +204 -36
- package/dist/shortcuts/workflow/operations/read.js +104 -23
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +448 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupStore.js +106 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/businessRules.js +47 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +30 -23
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +1 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +1 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +2 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +2 -4
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +138 -181
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +12 -7
- package/dist/shortcuts/workflow/operations.js +23 -1
- package/docs/e10-auth.md +1 -1
- package/docs/ehr.md +1 -1
- package/docs/fna.md +2 -1
- package/docs/im.md +8 -1
- package/docs/todo-smart-convert/README.md +1 -1
- package/docs/todo-smart-convert/data-contract.md +2 -2
- package/docs/todo-smart-convert/subgroup-rules.md +6 -0
- package/package.json +1 -1
- package/skill-template/business-info.json +18 -0
- package/skill-template/domains/cowork.md +14 -0
- package/skill-template/domains/yimiaoban.md +3 -3
- package/skill-template/skill-template.md +1 -1
- package/skills/weaver-e10-blog/SKILL.md +5 -5
- package/skills/weaver-e10-caizhilian/SKILL.md +5 -5
- package/skills/weaver-e10-calendar/SKILL.md +5 -5
- package/skills/weaver-e10-cowork/SKILL.md +101 -0
- package/skills/weaver-e10-cowork/references/cowork-read.md +90 -0
- package/skills/weaver-e10-cowork/references/cowork-write.md +131 -0
- package/skills/weaver-e10-cowork/references/safety-boundaries.md +45 -0
- package/skills/weaver-e10-esb/SKILL.md +5 -5
- package/skills/weaver-e10-hrm/SKILL.md +5 -5
- package/skills/weaver-e10-jinchengda/SKILL.md +2 -2
- package/skills/weaver-e10-jiuchuanhui/SKILL.md +5 -5
- package/skills/weaver-e10-jucailin/SKILL.md +38 -14
- package/skills/weaver-e10-jucailin/references/recruit-create.md +104 -0
- package/skills/weaver-e10-jucailin/references/recruit-read.md +25 -0
- package/skills/weaver-e10-jucailin/references/recruit-write.md +13 -0
- package/skills/weaver-e10-jucailin/references/salary.md +15 -11
- package/skills/weaver-e10-mail/SKILL.md +17 -14
- package/skills/weaver-e10-meeting/SKILL.md +442 -46
- package/skills/weaver-e10-meeting/references/meeting-eb.md +135 -3
- package/skills/weaver-e10-meeting/references/meeting-invitation.md +28 -2
- package/skills/weaver-e10-meeting/references/meeting-queries.md +9 -6
- package/skills/weaver-e10-meeting/references/meeting-verification.md +8 -6
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-okr/SKILL.md +5 -5
- package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
- package/skills/weaver-e10-plan/SKILL.md +5 -5
- package/skills/weaver-e10-qiyecheng/SKILL.md +5 -2
- package/skills/weaver-e10-qiyecheng/references/catalog.md +2 -2
- package/skills/weaver-e10-qiyecheng/references/expense-chart.md +15 -1
- package/skills/weaver-e10-qiyecheng/references/source-manifest.json +22 -21
- package/skills/weaver-e10-shared/SKILL.md +12 -12
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +4 -4
- package/skills/weaver-e10-wenshuding/SKILL.md +5 -5
- package/skills/weaver-e10-workflow/SKILL.md +29 -5
- package/skills/weaver-e10-workflow/aigroup/default.json +88 -0
- package/skills/weaver-e10-workflow/references/todo-smart-convert.md +66 -14
- package/skills/weaver-e10-workflow/references/workflow-agent-entry.md +1 -1
- package/skills/weaver-e10-workflow/references/workflow-memory.md +1 -1
- package/skills/weaver-e10-workflow/references/workflow-query.md +12 -10
- package/skills/weaver-e10-workflow/references/workflow-view.md +2 -2
- package/skills/weaver-e10-workflow/tools/todo-serve.cjs +239 -0
- package/skills/weaver-e10-workflow/{scripts → tools}/todo-summary.cjs +218 -28
- package/skills/weaver-e10-yepiaotong/SKILL.md +10 -8
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +7 -7
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +10 -10
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +40 -37
- package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +4 -4
- package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +27 -38
- package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +4 -4
- package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +37 -29
- package/skills/weaver-e10-yimiaoban/SKILL.md +156 -33
- package/skills/weaver-e10-yimiaoban/references/ding-write.md +1 -1
- package/skills/weaver-e10-yimiaoban/references/error-codes.md +5 -1
- package/skills/weaver-e10-yimiaoban/references/group-read.md +5 -1
- package/skills/weaver-e10-yimiaoban/references/group-write.md +6 -4
- package/skills/weaver-e10-yimiaoban/references/msg-read.md +1 -0
- package/skills/weaver-e10-yimiaoban/references/msg-write.md +34 -2
- package/skills/weaver-e10-yimiaoban/references/safety-boundaries.md +5 -2
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +79 -58
- package/skills/weaver-e10-yimiaoban/references/user-remind.md +2 -2
- package/skills/weaver-e10-ziguanjia/SKILL.md +5 -5
|
@@ -1717,9 +1717,9 @@ const workflowSearch = {
|
|
|
1717
1717
|
// workflow.search 执行模式路由与最终 Markdown(来源:Python resolve_workflow_search_mode /
|
|
1718
1718
|
// build_workflow_search_final_markdown / todo_probe_notice)
|
|
1719
1719
|
// ---------------------------------------------------------------------------
|
|
1720
|
-
/** 智能分组基线版本(来源:Python _SMART_GROUPING_MIN_OA_VERSION) */
|
|
1721
|
-
const SMART_GROUPING_MIN_OA_VERSION = [10, 0,
|
|
1722
|
-
/** 把版本元组格式化为 OA 习惯的四段式(尾两段补零:10.0.
|
|
1720
|
+
/** 智能分组基线版本(来源:Python _SMART_GROUPING_MIN_OA_VERSION;2026-10-29 用户口径下调到 10.0.9807.01) */
|
|
1721
|
+
const SMART_GROUPING_MIN_OA_VERSION = [10, 0, 9807, 1];
|
|
1722
|
+
/** 把版本元组格式化为 OA 习惯的四段式(尾两段补零:10.0.9807.01)。错误提示一律由常量派生,避免文案与判定漂移。 */
|
|
1723
1723
|
function formatOaVersion(tuple) {
|
|
1724
1724
|
const [a, b, c, d] = tuple;
|
|
1725
1725
|
return `${a}.${b}.${c}.${String(d).padStart(2, '0')}`;
|
|
@@ -1833,7 +1833,7 @@ function requireProbeSuccess(probe, label, title) {
|
|
|
1833
1833
|
//
|
|
1834
1834
|
// 来源:Python `execute_grouped_todo_search` / `normalize_todo_group_list` /
|
|
1835
1835
|
// `combine_todo_group_results` / `build_grouped_todo_summary_markdown`
|
|
1836
|
-
// (V11.2 查询逻辑,V11.3 由 legacy 分支沿用)。恢复版本门槛后(阈值 10.0.
|
|
1836
|
+
// (V11.2 查询逻辑,V11.3 由 legacy 分支沿用)。恢复版本门槛后(阈值 10.0.9807.01),
|
|
1837
1837
|
// 低版本 OA 环境将长期走这条分支,故必须与 V11.3 对齐。
|
|
1838
1838
|
//
|
|
1839
1839
|
// 未移植项(有意为之):V11.3 的 `frequentPreference` / `batchSuggestions` 两路依赖
|
|
@@ -5153,6 +5153,33 @@ function formatHistoryFieldValue(raw, field) {
|
|
|
5153
5153
|
}
|
|
5154
5154
|
return stringifyDetailValue(raw);
|
|
5155
5155
|
}
|
|
5156
|
+
/**
|
|
5157
|
+
* SaaS 公有云正式环境域名(2026-10-29 用户口径)。
|
|
5158
|
+
*
|
|
5159
|
+
* 只有这个环境下,历史取数要在 `getMineListData` 的 `params` 里**写死**带上
|
|
5160
|
+
* `disableCheckUserPermission: '1'`(放开用户权限校验);其余环境不带该键,行为与之前完全一致。
|
|
5161
|
+
*/
|
|
5162
|
+
const SAAS_COLOGY_BASE_URL = 'https://www.e-cology.com.cn';
|
|
5163
|
+
/** 比 baseUrl 时容忍末尾斜杠与大小写差异(`E10AuthSession.getBaseUrl()` 已去掉末尾斜杠)。 */
|
|
5164
|
+
function isSaasCologyBaseUrl(baseUrl) {
|
|
5165
|
+
const normalize = (value) => String(value ?? '').trim().replace(/\/+$/u, '').toLowerCase();
|
|
5166
|
+
return normalize(baseUrl) === SAAS_COLOGY_BASE_URL;
|
|
5167
|
+
}
|
|
5168
|
+
/**
|
|
5169
|
+
* 当前环境是否 SaaS 公有云(`https://www.e-cology.com.cn`)。
|
|
5170
|
+
*
|
|
5171
|
+
* 取不到会话时按"非 SaaS"处理并返回 `false`:这里只是决定要不要多加一个参数,真正的认证错误
|
|
5172
|
+
* 由紧随其后的列表请求自己抛出,不在这一步抢先报错。
|
|
5173
|
+
*/
|
|
5174
|
+
async function isSaasCologyEnv(env) {
|
|
5175
|
+
try {
|
|
5176
|
+
const session = await env.host.getSession();
|
|
5177
|
+
return isSaasCologyBaseUrl(session?.baseUrl);
|
|
5178
|
+
}
|
|
5179
|
+
catch {
|
|
5180
|
+
return false;
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5156
5183
|
/**
|
|
5157
5184
|
* 历史取数的**前半段**:只发那一次列表请求。
|
|
5158
5185
|
*
|
|
@@ -5169,6 +5196,7 @@ export async function fetchHistoryListSnapshot(env, options = {}) {
|
|
|
5169
5196
|
const now = new Date();
|
|
5170
5197
|
const start = new Date(now.getTime() - HISTORY_LOOKBACK_DAYS * 24 * 60 * 60 * 1000);
|
|
5171
5198
|
const historyWorkflowId = String(options.workflowId ?? '').trim();
|
|
5199
|
+
const addDisableCheckUserPermission = await isSaasCologyEnv(env);
|
|
5172
5200
|
const listResponse = await env.host.workflowRequest({
|
|
5173
5201
|
method: 'POST',
|
|
5174
5202
|
path: WORKFLOW_ENDPOINTS.MINE_LIST,
|
|
@@ -5181,6 +5209,8 @@ export async function fetchHistoryListSnapshot(env, options = {}) {
|
|
|
5181
5209
|
flowSpareType: 1,
|
|
5182
5210
|
outType: 0,
|
|
5183
5211
|
needIutData: false,
|
|
5212
|
+
// SaaS 公有云环境写死带上(见 `SAAS_COLOGY_BASE_URL`);其余环境不带此键。
|
|
5213
|
+
...(addDisableCheckUserPermission ? { disableCheckUserPermission: '1' } : {}),
|
|
5184
5214
|
},
|
|
5185
5215
|
// `needParseData: true`:让服务端把日期等字段直接解析成可读文本(`displayData`),
|
|
5186
5216
|
// 客户端不再自己转。
|
|
@@ -5301,12 +5331,55 @@ export async function pickUniqueMineWorkflow(candidates, env) {
|
|
|
5301
5331
|
return null;
|
|
5302
5332
|
}
|
|
5303
5333
|
}
|
|
5334
|
+
/**
|
|
5335
|
+
* 历史取数的**中间段**:只把「明细」那几次请求发出去(2026-10-29 用户口径)。
|
|
5336
|
+
*
|
|
5337
|
+
* 明细请求(`getWorkflowRequest`)的入参只有 `requestId`(来自列表)+ 会话 `userId` + 四个写死的
|
|
5338
|
+
* 布尔值,路径里也没有 `formId`/`layoutId` —— 与 `getFormLayout` **没有任何参数依赖**。原先它要等
|
|
5339
|
+
* layout 才发,纯粹是因为「发请求」和「按 layout 字段清单组装」写在同一段里。拆出本函数后,调用方
|
|
5340
|
+
* 可以在**列表一返回**就把它挂上去、与 `getFormLayout` 并行,组装逻辑仍按原样在 layout 之后做。
|
|
5341
|
+
*
|
|
5342
|
+
* 不在这里吞异常:认证/网络错误照旧由 `buildHistoryRequestData` 的 catch
|
|
5343
|
+
* (`rethrowAuthOrNetworkError`)决定去留。调用方只做"预取"时,须另行 `.catch(() => {})` 兜住,
|
|
5344
|
+
* 以免表单后续先抛错时留下无人 `await` 的未处理拒绝。
|
|
5345
|
+
*/
|
|
5346
|
+
export function prefetchHistoryDetails(env, listed) {
|
|
5347
|
+
const requestIds = listed?.requestIds ?? [];
|
|
5348
|
+
if (!requestIds.length)
|
|
5349
|
+
return Promise.resolve([]);
|
|
5350
|
+
return (async () => {
|
|
5351
|
+
const session = await env.host.getSession();
|
|
5352
|
+
return Promise.all(requestIds.map(async (requestId) => {
|
|
5353
|
+
const payload = {
|
|
5354
|
+
requestId,
|
|
5355
|
+
isGetRequestBaseInfo: false,
|
|
5356
|
+
isGetRequestLog: false,
|
|
5357
|
+
isGetFormData: true,
|
|
5358
|
+
isReadRequest: false,
|
|
5359
|
+
};
|
|
5360
|
+
if (session.userId)
|
|
5361
|
+
payload.userId = session.userId;
|
|
5362
|
+
const response = await env.host.workflowRequest({
|
|
5363
|
+
method: 'POST',
|
|
5364
|
+
path: WORKFLOW_ENDPOINTS.REQUEST_DETAIL,
|
|
5365
|
+
operation: 'workflow.createRequest.history.detail',
|
|
5366
|
+
body: payload,
|
|
5367
|
+
});
|
|
5368
|
+
return detailDataOf(response);
|
|
5369
|
+
}));
|
|
5370
|
+
})();
|
|
5371
|
+
}
|
|
5304
5372
|
export async function buildHistoryRequestData(env, fields, targets, options = {},
|
|
5305
5373
|
/**
|
|
5306
5374
|
* 已提前发起的列表请求(见 `fetchHistoryListSnapshot`)。传入即直接复用,不再重复发请求;
|
|
5307
5375
|
* 传 `null` / 不传时退回"本函数内自己发列表"的老口径。
|
|
5308
5376
|
*/
|
|
5309
|
-
prefetchedList
|
|
5377
|
+
prefetchedList,
|
|
5378
|
+
/**
|
|
5379
|
+
* 已提前发起的**明细**请求(见 `prefetchHistoryDetails`)。传入即直接复用,不再在这里重发;
|
|
5380
|
+
* 传 `null` / 不传时退回"本函数内自己发"的老口径。组装逻辑与返回值口径与老口径完全一致。
|
|
5381
|
+
*/
|
|
5382
|
+
prefetchedDetails) {
|
|
5310
5383
|
const targetList = toArray(targets)
|
|
5311
5384
|
.filter((item) => !item?.systemField && String(item?.id ?? '').trim())
|
|
5312
5385
|
.map((item) => ({ id: String(item.id).trim(), name: String(item.name ?? item.id) }));
|
|
@@ -5331,24 +5404,32 @@ prefetchedList) {
|
|
|
5331
5404
|
const items = listed.items;
|
|
5332
5405
|
const requestIds = listed.requestIds;
|
|
5333
5406
|
const session = await env.host.getSession();
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5407
|
+
// 明细请求可以由调用方提前发(`prefetchHistoryDetails`)—— 那时它的结果已经在飞了,
|
|
5408
|
+
// 这里只等它回来;后续「按本表单字段组装」的口径与老路径逐行相同。
|
|
5409
|
+
const details = prefetchedDetails
|
|
5410
|
+
? await prefetchedDetails
|
|
5411
|
+
: await Promise.all(requestIds.map(async (requestId) => {
|
|
5412
|
+
const payload = {
|
|
5413
|
+
requestId,
|
|
5414
|
+
isGetRequestBaseInfo: false,
|
|
5415
|
+
isGetRequestLog: false,
|
|
5416
|
+
isGetFormData: true,
|
|
5417
|
+
isReadRequest: false,
|
|
5418
|
+
};
|
|
5419
|
+
if (session.userId)
|
|
5420
|
+
payload.userId = session.userId;
|
|
5421
|
+
const response = await env.host.workflowRequest({
|
|
5422
|
+
method: 'POST',
|
|
5423
|
+
path: WORKFLOW_ENDPOINTS.REQUEST_DETAIL,
|
|
5424
|
+
operation: 'workflow.createRequest.history.detail',
|
|
5425
|
+
body: payload,
|
|
5426
|
+
});
|
|
5427
|
+
return detailDataOf(response);
|
|
5428
|
+
}));
|
|
5429
|
+
// 预取链在「没有列表结果」时会回 `null`(正常路径走不到这里,因为上面已经 `!listed` 早退);
|
|
5430
|
+
// 按老口径降级成"没有可参考的历史数据"。
|
|
5431
|
+
if (!details)
|
|
5432
|
+
return null;
|
|
5352
5433
|
const result = [];
|
|
5353
5434
|
for (let index = 0; index < details.length; index += 1) {
|
|
5354
5435
|
const data = details[index] || {};
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `aigroup` 规则库:把「ai二次分组」的个性化规则从代码挪到 JSON(2026-10-28 用户定稿)。
|
|
3
|
+
*
|
|
4
|
+
* 目录与文件:
|
|
5
|
+
* - `<skill>/aigroup/default.json` —— 系统默认规则(随 skill 发布,**不允许被覆盖**)
|
|
6
|
+
* - `<state>/aigroup/user-intents.json` —— 用户意图规则(会话里由模型生成、CLI 校验落盘,整条覆盖)
|
|
7
|
+
* 刻意放 CLI 状态目录而不是 skill 目录:本机产生的意图不能跟着 skill 被打包/安装到别人机器上。
|
|
8
|
+
*
|
|
9
|
+
* 本模块只负责**读、校验、匹配、条件求值**,不负责分组算法本身;
|
|
10
|
+
* 门槛 / 子组解散 / 漏分平铺 / 排序编号 / 错误码仍固定在代码里(全局规则,不进 JSON)。
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { stateRoot } from '../../../../core/state.js';
|
|
15
|
+
import { cacheRoots } from './summaryCache.js';
|
|
16
|
+
export const AIGROUP_DIR_NAME = 'aigroup';
|
|
17
|
+
export const DEFAULT_RULE_FILE = 'default.json';
|
|
18
|
+
export const USER_INTENT_RULE_FILE = 'user-intents.json';
|
|
19
|
+
/** 十种算子(2026-10-28 用户定稿;`介于` 左闭右闭)。 */
|
|
20
|
+
export const FOCUS_OPERATORS = [
|
|
21
|
+
'等于', '不等于', '是', '不是', '大于', '小于', '小于等于', '大于等于', '介于', '包含',
|
|
22
|
+
];
|
|
23
|
+
/** `groupingPrompt` 会原样进模型上下文,限长避免把规则文件当提示词工程仓库。 */
|
|
24
|
+
const MAX_GROUPING_PROMPT_CHARS = 500;
|
|
25
|
+
const EMPTY_GROUP_RULE = { focus: false };
|
|
26
|
+
export const FALLBACK_DEFAULTS = {
|
|
27
|
+
weight: 999,
|
|
28
|
+
needSummary: true,
|
|
29
|
+
requestSummaryPrompt: '',
|
|
30
|
+
summaryMaxChars: 150,
|
|
31
|
+
groupSummaryMaxChars: 100,
|
|
32
|
+
groupSummaryTail: '可展开逐条查看或直接汇总处理',
|
|
33
|
+
};
|
|
34
|
+
export function emptyRuleFile(error = null) {
|
|
35
|
+
return {
|
|
36
|
+
file: { schemaVersion: 2, defaults: FALLBACK_DEFAULTS, rules: [] },
|
|
37
|
+
error,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function asStringArray(value) {
|
|
41
|
+
if (typeof value === 'string')
|
|
42
|
+
return [value];
|
|
43
|
+
if (!Array.isArray(value))
|
|
44
|
+
return null;
|
|
45
|
+
const items = value.map((item) => String(item ?? '').trim()).filter((item) => item !== '');
|
|
46
|
+
return items.length > 0 ? items : null;
|
|
47
|
+
}
|
|
48
|
+
function isOperator(value) {
|
|
49
|
+
return typeof value === 'string' && FOCUS_OPERATORS.includes(value);
|
|
50
|
+
}
|
|
51
|
+
/** 校验一条 focusKeyword;不合法返回原因字符串。 */
|
|
52
|
+
function validateFocusKeyword(raw, index) {
|
|
53
|
+
if (!Array.isArray(raw))
|
|
54
|
+
return `focusKeyword[${index}] 必须是数组`;
|
|
55
|
+
const [metric, op] = raw;
|
|
56
|
+
if (typeof metric !== 'string' || !metric.trim())
|
|
57
|
+
return `focusKeyword[${index}] 的指标名必须是非空字符串`;
|
|
58
|
+
if (!isOperator(op))
|
|
59
|
+
return `focusKeyword[${index}] 的算子「${String(op)}」不支持(可用:${FOCUS_OPERATORS.join('/')})`;
|
|
60
|
+
if (op === '介于') {
|
|
61
|
+
if (raw.length !== 4)
|
|
62
|
+
return `focusKeyword[${index}] 用「介于」时必须给 [指标, 介于, 下界, 上界]`;
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
if (raw.length !== 3)
|
|
66
|
+
return `focusKeyword[${index}] 必须是 [指标, 算子, 值] 三元组`;
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
/** 校验整份规则文件;返回错误列表(空数组 = 合法)。 */
|
|
70
|
+
export function validateRuleFile(raw) {
|
|
71
|
+
const errors = [];
|
|
72
|
+
const file = raw;
|
|
73
|
+
if (!file || typeof file !== 'object')
|
|
74
|
+
return ['规则文件必须是 JSON 对象'];
|
|
75
|
+
if (!Array.isArray(file.rules)) {
|
|
76
|
+
errors.push('rules 必须是数组');
|
|
77
|
+
return errors;
|
|
78
|
+
}
|
|
79
|
+
const seen = new Set();
|
|
80
|
+
file.rules.forEach((rule, index) => {
|
|
81
|
+
if (!rule || typeof rule !== 'object') {
|
|
82
|
+
errors.push(`rules[${index}] 必须是对象`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const names = asStringArray(rule.workflowName);
|
|
86
|
+
if (!names) {
|
|
87
|
+
errors.push(`rules[${index}].workflowName 必须是非空字符串或非空数组`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (const name of names) {
|
|
91
|
+
if (seen.has(name))
|
|
92
|
+
errors.push(`workflowName「${name}」在 rules[${index}] 重复出现`);
|
|
93
|
+
seen.add(name);
|
|
94
|
+
}
|
|
95
|
+
if (rule.weight !== undefined && (typeof rule.weight !== 'number' || !Number.isFinite(rule.weight))) {
|
|
96
|
+
errors.push(`rules[${index}].weight 必须是数字`);
|
|
97
|
+
}
|
|
98
|
+
if (rule.summaryMaxChars !== undefined && (typeof rule.summaryMaxChars !== 'number' || rule.summaryMaxChars <= 0)) {
|
|
99
|
+
errors.push(`rules[${index}].summaryMaxChars 必须是正数`);
|
|
100
|
+
}
|
|
101
|
+
const groupRule = rule.aiGroup?.groupRule;
|
|
102
|
+
if (groupRule) {
|
|
103
|
+
if (typeof groupRule.focus !== 'boolean')
|
|
104
|
+
errors.push(`rules[${index}].aiGroup.groupRule.focus 必须是布尔`);
|
|
105
|
+
if (groupRule.focus) {
|
|
106
|
+
const keywords = groupRule.focusKeyword;
|
|
107
|
+
if (!Array.isArray(keywords) || keywords.length === 0) {
|
|
108
|
+
errors.push(`rules[${index}] focus=true 时必须给 focusKeyword`);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
keywords.forEach((keyword, k) => {
|
|
112
|
+
const reason = validateFocusKeyword(keyword, k);
|
|
113
|
+
if (reason)
|
|
114
|
+
errors.push(`rules[${index}].${reason}`);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else if (groupRule.focusKeyword !== undefined && groupRule.focusKeyword.length > 0) {
|
|
119
|
+
errors.push(`rules[${index}] focus=false 时不应给 focusKeyword`);
|
|
120
|
+
}
|
|
121
|
+
if (groupRule.focus === false && !groupRule.aiContext) {
|
|
122
|
+
errors.push(`rules[${index}] focus=false 时必须给 aiContext(流程标题 / 流程详情)`);
|
|
123
|
+
}
|
|
124
|
+
if (groupRule.groupingPrompt !== undefined) {
|
|
125
|
+
if (groupRule.focus === true) {
|
|
126
|
+
errors.push(`rules[${index}] focus=true 时不应给 groupingPrompt(它只作用于 focus=false 的语义分组)`);
|
|
127
|
+
}
|
|
128
|
+
else if (typeof groupRule.groupingPrompt !== 'string' || groupRule.groupingPrompt.trim() === '') {
|
|
129
|
+
errors.push(`rules[${index}].aiGroup.groupRule.groupingPrompt 必须是非空字符串`);
|
|
130
|
+
}
|
|
131
|
+
else if (groupRule.groupingPrompt.length > MAX_GROUPING_PROMPT_CHARS) {
|
|
132
|
+
errors.push(`rules[${index}].aiGroup.groupRule.groupingPrompt 超过 ${MAX_GROUPING_PROMPT_CHARS} 字`
|
|
133
|
+
+ '(它会原样进模型上下文,请精简)');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const groupSummary = rule.aiGroup?.groupSummary;
|
|
138
|
+
if (groupSummary) {
|
|
139
|
+
if (typeof groupSummary.needAI !== 'boolean')
|
|
140
|
+
errors.push(`rules[${index}].aiGroup.groupSummary.needAI 必须是布尔`);
|
|
141
|
+
if (groupSummary.needAI && !groupSummary.prompt)
|
|
142
|
+
errors.push(`rules[${index}] groupSummary.needAI=true 时必须给 prompt`);
|
|
143
|
+
if (!groupSummary.needAI && !groupSummary.template)
|
|
144
|
+
errors.push(`rules[${index}] groupSummary.needAI=false 时必须给 template`);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
return errors;
|
|
148
|
+
}
|
|
149
|
+
/** 读一份规则文件;文件不存在/坏 JSON/结构不合法都退回 `emptyRuleFile(原因)`。 */
|
|
150
|
+
export function loadRuleFile(file) {
|
|
151
|
+
if (!existsSync(file))
|
|
152
|
+
return emptyRuleFile(`规则文件不存在:${file}`);
|
|
153
|
+
let parsed;
|
|
154
|
+
try {
|
|
155
|
+
parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
return emptyRuleFile(`规则文件不是合法 JSON:${file}(${error.message})`);
|
|
159
|
+
}
|
|
160
|
+
const errors = validateRuleFile(parsed);
|
|
161
|
+
if (errors.length > 0)
|
|
162
|
+
return emptyRuleFile(`规则文件校验失败:${errors.join(';')}`);
|
|
163
|
+
return { file: parsed, error: null };
|
|
164
|
+
}
|
|
165
|
+
/** 默认规则目录(**随 skill 分发、只读**):`WEAVER_WORK_CLI_AIGROUP_DIR` → skill 目录下的 `aigroup/` → CLI 状态目录。 */
|
|
166
|
+
export function aigroupDir(ctx) {
|
|
167
|
+
const explicit = process.env.WEAVER_WORK_CLI_AIGROUP_DIR?.trim();
|
|
168
|
+
if (explicit)
|
|
169
|
+
return join(explicit);
|
|
170
|
+
const roots = cacheRoots(ctx);
|
|
171
|
+
return roots.skillDir ? join(roots.skillDir, AIGROUP_DIR_NAME) : join(stateRoot(ctx), AIGROUP_DIR_NAME);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* 用户意图目录(**运行时产物,绝不写进 skill 目录**):`WEAVER_WORK_CLI_AIGROUP_DIR` → CLI 状态目录。
|
|
175
|
+
*
|
|
176
|
+
* 刻意与 `aigroupDir()` 分开:意图文件是本机产生的,一旦落在 skill 目录里,
|
|
177
|
+
* 打包/安装时会跟着 skill 跑到别人机器上(既是泄漏,也会污染别人的分组口径)。
|
|
178
|
+
*/
|
|
179
|
+
export function aigroupUserDir(ctx) {
|
|
180
|
+
const explicit = process.env.WEAVER_WORK_CLI_AIGROUP_DIR?.trim();
|
|
181
|
+
if (explicit)
|
|
182
|
+
return join(explicit);
|
|
183
|
+
return join(stateRoot(ctx), AIGROUP_DIR_NAME);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 加载并合并规则:默认文件 +(可选)用户意图文件(**同 workflowName 整条覆盖**,系统默认不被覆盖)。
|
|
187
|
+
* 用户意图文件里的条目按 `workflowName` 粒度替换默认条目,缺字段由 defaults 补。
|
|
188
|
+
*/
|
|
189
|
+
export function loadRules(options = {}) {
|
|
190
|
+
const dir = options.dir ?? aigroupDir(options.ctx);
|
|
191
|
+
// 显式 `dir` 时(测试/现场)默认文件与意图文件放一起;否则意图文件只从状态目录读。
|
|
192
|
+
const userDir = options.dir ?? aigroupUserDir(options.ctx);
|
|
193
|
+
const warnings = [];
|
|
194
|
+
const base = loadRuleFile(join(dir, DEFAULT_RULE_FILE));
|
|
195
|
+
if (base.error)
|
|
196
|
+
warnings.push(base.error);
|
|
197
|
+
const rules = [...base.file.rules];
|
|
198
|
+
// 兼容迁移:意图文件原先写在 skill 目录里,改到状态目录后老文件不会被自动搬走;
|
|
199
|
+
// 状态目录没有时就回读老位置,避免用户已有的意图「凭空消失」。
|
|
200
|
+
const userIntentFile = join(userDir, USER_INTENT_RULE_FILE);
|
|
201
|
+
const legacyIntentFile = join(dir, USER_INTENT_RULE_FILE);
|
|
202
|
+
let intentFile = userIntentFile;
|
|
203
|
+
if (!options.dir && !existsSync(userIntentFile) && existsSync(legacyIntentFile)) {
|
|
204
|
+
intentFile = legacyIntentFile;
|
|
205
|
+
warnings.push(`用户意图仍读自 skill 目录(${legacyIntentFile}):新位置是 ${userIntentFile},重新执行 aigroup set 即可迁移`);
|
|
206
|
+
}
|
|
207
|
+
const intent = loadRuleFile(intentFile);
|
|
208
|
+
if (intent.error) {
|
|
209
|
+
// 用户意图文件缺失是常态(还没产生意图),只在结构坏掉时才告警
|
|
210
|
+
if (existsSync(intentFile))
|
|
211
|
+
warnings.push(intent.error);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
for (const rule of intent.file.rules) {
|
|
215
|
+
const names = new Set(asStringArray(rule.workflowName) ?? []);
|
|
216
|
+
const at = rules.findIndex((item) => (asStringArray(item.workflowName) ?? []).some((name) => names.has(name)));
|
|
217
|
+
if (at >= 0 && (asStringArray(rules[at].workflowName) ?? []).includes('*')) {
|
|
218
|
+
warnings.push(`用户意图不能覆盖通配条目「*」,已忽略:${[...names].join(',')}`);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (at >= 0)
|
|
222
|
+
rules[at] = rule;
|
|
223
|
+
else
|
|
224
|
+
rules.push(rule);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
file: { schemaVersion: base.file.schemaVersion, defaults: { ...FALLBACK_DEFAULTS, ...base.file.defaults }, rules },
|
|
229
|
+
warnings,
|
|
230
|
+
dir,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/** 名称匹配:精确(别名全等)优先;否则模糊(包含)按权重升序、同权重按配置顺序。 */
|
|
234
|
+
export function matchRule(rules, workflowName) {
|
|
235
|
+
const name = String(workflowName ?? '').trim();
|
|
236
|
+
const aliasesOf = (rule) => asStringArray(rule.workflowName) ?? [];
|
|
237
|
+
const indexOf = new Map();
|
|
238
|
+
rules.forEach((rule, index) => indexOf.set(rule, index));
|
|
239
|
+
if (name) {
|
|
240
|
+
const exact = rules.filter((rule) => aliasesOf(rule).includes(name));
|
|
241
|
+
if (exact.length > 0) {
|
|
242
|
+
const [rule] = exact;
|
|
243
|
+
return { rule, matchedBy: 'exact', index: indexOf.get(rule) ?? 0 };
|
|
244
|
+
}
|
|
245
|
+
const fuzzy = rules.filter((rule) => aliasesOf(rule).some((alias) => alias !== '*' && name.includes(alias)));
|
|
246
|
+
if (fuzzy.length > 0) {
|
|
247
|
+
const sorted = [...fuzzy].sort((a, b) => (a.weight ?? FALLBACK_DEFAULTS.weight) - (b.weight ?? FALLBACK_DEFAULTS.weight)
|
|
248
|
+
|| (indexOf.get(a) ?? 0) - (indexOf.get(b) ?? 0));
|
|
249
|
+
const rule = sorted[0];
|
|
250
|
+
return { rule, matchedBy: 'fuzzy', index: indexOf.get(rule) ?? 0 };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const fallback = rules.find((rule) => aliasesOf(rule).includes('*'));
|
|
254
|
+
if (fallback)
|
|
255
|
+
return { rule: fallback, matchedBy: 'default', index: indexOf.get(fallback) ?? 0 };
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
/** 合并 defaults,得到生效规则。 */
|
|
259
|
+
export function resolveRule(file, workflowName) {
|
|
260
|
+
const matched = matchRule(file.rules, workflowName);
|
|
261
|
+
if (!matched)
|
|
262
|
+
return null;
|
|
263
|
+
const defaults = { ...FALLBACK_DEFAULTS, ...file.defaults };
|
|
264
|
+
const raw = matched.rule;
|
|
265
|
+
const groupSummary = raw.aiGroup?.groupSummary;
|
|
266
|
+
const groupRule = raw.aiGroup?.groupRule;
|
|
267
|
+
return {
|
|
268
|
+
matchedBy: matched.matchedBy,
|
|
269
|
+
rule: {
|
|
270
|
+
workflowName: asStringArray(raw.workflowName) ?? [],
|
|
271
|
+
weight: raw.weight ?? defaults.weight,
|
|
272
|
+
needSummary: raw.needSummary ?? defaults.needSummary,
|
|
273
|
+
requestSummaryPrompt: raw.requestSummaryPrompt ?? defaults.requestSummaryPrompt,
|
|
274
|
+
summaryMaxChars: raw.summaryMaxChars ?? defaults.summaryMaxChars,
|
|
275
|
+
groupSummaryMaxChars: defaults.groupSummaryMaxChars,
|
|
276
|
+
groupSummaryTail: defaults.groupSummaryTail,
|
|
277
|
+
aiGroup: raw.aiGroup
|
|
278
|
+
? {
|
|
279
|
+
groupSummary: {
|
|
280
|
+
needAI: groupSummary?.needAI ?? false,
|
|
281
|
+
...(groupSummary?.prompt === undefined ? {} : { prompt: groupSummary.prompt }),
|
|
282
|
+
...(groupSummary?.template === undefined ? {} : { template: groupSummary.template }),
|
|
283
|
+
maxChars: defaults.groupSummaryMaxChars,
|
|
284
|
+
tail: defaults.groupSummaryTail,
|
|
285
|
+
},
|
|
286
|
+
groupRule: groupRule ?? EMPTY_GROUP_RULE,
|
|
287
|
+
}
|
|
288
|
+
: null,
|
|
289
|
+
ruleIndex: matched.index,
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
// focusKeyword 求值
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
/** 日期口径:只比到「日」,用本地时区(`2026-10-28`)。 */
|
|
297
|
+
export function dateOnly(value) {
|
|
298
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
299
|
+
return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;
|
|
300
|
+
}
|
|
301
|
+
const RELATIVE_DAYS = { 今天: 0, 昨天: -1, 明天: 1 };
|
|
302
|
+
/** 把「今天/昨天/明天」或 `2026-10-28 …` 归一成 `YYYY-MM-DD`;不是日期返回 null。 */
|
|
303
|
+
export function normalizeDateLike(value, now = new Date()) {
|
|
304
|
+
const text = value.trim();
|
|
305
|
+
if (text in RELATIVE_DAYS) {
|
|
306
|
+
const date = new Date(now.getTime());
|
|
307
|
+
date.setDate(date.getDate() + RELATIVE_DAYS[text]);
|
|
308
|
+
return dateOnly(date);
|
|
309
|
+
}
|
|
310
|
+
const matched = /^(\d{4})[-/](\d{1,2})[-/](\d{1,2})/u.exec(text);
|
|
311
|
+
if (!matched)
|
|
312
|
+
return null;
|
|
313
|
+
const [, y, m, d] = matched;
|
|
314
|
+
return `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
|
|
315
|
+
}
|
|
316
|
+
function toNumber(value) {
|
|
317
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
318
|
+
return value;
|
|
319
|
+
const text = String(value ?? '').replace(/[,¥¥\s]/gu, '');
|
|
320
|
+
if (!text)
|
|
321
|
+
return null;
|
|
322
|
+
const parsed = Number(text);
|
|
323
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
324
|
+
}
|
|
325
|
+
function comparableOf(value, now) {
|
|
326
|
+
if (value === null || value === undefined || value === '')
|
|
327
|
+
return null;
|
|
328
|
+
if (typeof value === 'number')
|
|
329
|
+
return Number.isFinite(value) ? { kind: 'number', value } : null;
|
|
330
|
+
const text = String(value).trim();
|
|
331
|
+
if (!text)
|
|
332
|
+
return null;
|
|
333
|
+
const date = normalizeDateLike(text, now);
|
|
334
|
+
if (date)
|
|
335
|
+
return { kind: 'date', value: date };
|
|
336
|
+
const num = toNumber(text);
|
|
337
|
+
// 纯数字优先按数值比,避免「100」被当文本做字典序
|
|
338
|
+
if (num !== null && /^[¥¥]?\s*-?\d[\d,]*(?:\.\d+)?$/.test(text.replace(/\s/gu, ''))) {
|
|
339
|
+
return { kind: 'number', value: num };
|
|
340
|
+
}
|
|
341
|
+
return { kind: 'text', value: text };
|
|
342
|
+
}
|
|
343
|
+
function compare(a, b) {
|
|
344
|
+
if (a.kind === 'number' && b.kind === 'number')
|
|
345
|
+
return a.value - b.value;
|
|
346
|
+
if (a.kind === 'date' && b.kind === 'date')
|
|
347
|
+
return a.value < b.value ? -1 : a.value > b.value ? 1 : 0;
|
|
348
|
+
if (a.kind === 'text' || b.kind === 'text') {
|
|
349
|
+
const left = String(a.value);
|
|
350
|
+
const right = String(b.value);
|
|
351
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
352
|
+
}
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* 单个条件求值。**取不到值(null/undefined/空串)一律判为不命中**(2026-10-28 用户定稿:不猜)。
|
|
357
|
+
*/
|
|
358
|
+
export function evaluateCondition(condition, actual, now = new Date()) {
|
|
359
|
+
const [metric, op, expected, upper] = condition;
|
|
360
|
+
const left = comparableOf(actual, now);
|
|
361
|
+
if (!left)
|
|
362
|
+
return false;
|
|
363
|
+
const right = comparableOf(expected, now);
|
|
364
|
+
if (!right)
|
|
365
|
+
return false;
|
|
366
|
+
switch (op) {
|
|
367
|
+
case '等于':
|
|
368
|
+
case '是':
|
|
369
|
+
return compare(left, right) === 0;
|
|
370
|
+
case '不等于':
|
|
371
|
+
case '不是':
|
|
372
|
+
return compare(left, right) !== 0;
|
|
373
|
+
case '大于': {
|
|
374
|
+
const diff = compare(left, right);
|
|
375
|
+
return diff !== null && diff > 0;
|
|
376
|
+
}
|
|
377
|
+
case '小于': {
|
|
378
|
+
const diff = compare(left, right);
|
|
379
|
+
return diff !== null && diff < 0;
|
|
380
|
+
}
|
|
381
|
+
case '大于等于': {
|
|
382
|
+
const diff = compare(left, right);
|
|
383
|
+
return diff !== null && diff >= 0;
|
|
384
|
+
}
|
|
385
|
+
case '小于等于': {
|
|
386
|
+
const diff = compare(left, right);
|
|
387
|
+
return diff !== null && diff <= 0;
|
|
388
|
+
}
|
|
389
|
+
case '介于': {
|
|
390
|
+
const top = comparableOf(upper, now);
|
|
391
|
+
if (!top)
|
|
392
|
+
return false;
|
|
393
|
+
const low = compare(left, right);
|
|
394
|
+
const high = compare(left, top);
|
|
395
|
+
return low !== null && high !== null && low >= 0 && high <= 0;
|
|
396
|
+
}
|
|
397
|
+
case '包含':
|
|
398
|
+
return String(left.value).includes(String(right.value));
|
|
399
|
+
default:
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* 一条规则的全部条件(AND)。任一条件取不到值即不命中。
|
|
405
|
+
* `resolve` 由调用方提供:sysArg=true 从列表条目属性取,false 从流程详情字段取。
|
|
406
|
+
*/
|
|
407
|
+
export function evaluateFocusKeywords(conditions, resolve, now = new Date()) {
|
|
408
|
+
if (conditions.length === 0)
|
|
409
|
+
return false;
|
|
410
|
+
return conditions.every((condition) => evaluateCondition(condition, resolve(condition[0]), now));
|
|
411
|
+
}
|
|
412
|
+
// ---------------------------------------------------------------------------
|
|
413
|
+
// 进程内生效规则(一次 operation 只加载一次;纯读,不缓存到磁盘)
|
|
414
|
+
// ---------------------------------------------------------------------------
|
|
415
|
+
let activeFile = null;
|
|
416
|
+
let activeDir = null;
|
|
417
|
+
/** 加载并激活规则(每个 operation 开头调一次)。返回加载告警,由调用方并入 warnings。 */
|
|
418
|
+
export function primeRules(ctx) {
|
|
419
|
+
const loaded = loadRules({ ctx });
|
|
420
|
+
activeFile = loaded.file;
|
|
421
|
+
activeDir = loaded.dir;
|
|
422
|
+
return loaded;
|
|
423
|
+
}
|
|
424
|
+
/** 当前生效的规则文件(未 prime 时按默认目录懒加载)。 */
|
|
425
|
+
export function activeRules() {
|
|
426
|
+
if (!activeFile) {
|
|
427
|
+
const loaded = loadRules();
|
|
428
|
+
activeFile = loaded.file;
|
|
429
|
+
activeDir = loaded.dir;
|
|
430
|
+
}
|
|
431
|
+
return activeFile;
|
|
432
|
+
}
|
|
433
|
+
/** 当前规则目录(测试/诊断用)。 */
|
|
434
|
+
export function activeRulesDir() {
|
|
435
|
+
return activeDir;
|
|
436
|
+
}
|
|
437
|
+
/** 某工作流名对应的生效规则(找不到返回 null)。 */
|
|
438
|
+
export function activeRuleFor(workflowName) {
|
|
439
|
+
const name = String(workflowName ?? '').trim();
|
|
440
|
+
if (!name)
|
|
441
|
+
return null;
|
|
442
|
+
return resolveRule(activeRules(), name)?.rule ?? null;
|
|
443
|
+
}
|
|
444
|
+
/** 清空进程内缓存(单测用)。 */
|
|
445
|
+
export function resetActiveRules() {
|
|
446
|
+
activeFile = null;
|
|
447
|
+
activeDir = null;
|
|
448
|
+
}
|