weaver-work-cli 0.1.7 → 0.1.20
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 +11 -11
- package/dist/cmd/action.js +76 -0
- package/dist/cmd/index.js +2 -0
- package/dist/cmd/refs/index.js +103 -0
- package/dist/cmd/register.js +31 -0
- package/dist/cmd/root.js +1 -0
- package/dist/cmd/setup/index.js +25 -5
- package/dist/cmd/skills/index.js +37 -5
- package/dist/core/connector.js +54 -0
- package/dist/core/context.js +2 -1
- package/dist/core/input.js +48 -0
- package/dist/core/output.js +298 -3
- package/dist/core/refs.js +432 -0
- package/dist/core/state.js +26 -0
- package/dist/internal/e10/auth/commands.js +2 -2
- package/dist/internal/e10/auth/session.js +14 -0
- package/dist/internal/e10/auth/xiaoe.js +116 -51
- package/dist/internal/e10/request-runtime.js +5 -0
- package/dist/internal/skills/install.js +148 -28
- package/dist/internal/skills/reader.js +12 -9
- package/dist/shortcuts/blog/continuation.js +74 -0
- package/dist/shortcuts/blog/errors.js +60 -0
- package/dist/shortcuts/blog/host.js +175 -0
- package/dist/shortcuts/blog/index.js +324 -0
- package/dist/shortcuts/blog/manifest.js +415 -0
- package/dist/shortcuts/blog/operations/comment.js +125 -0
- package/dist/shortcuts/blog/operations/comments.js +31 -0
- package/dist/shortcuts/blog/operations/edit.js +131 -0
- package/dist/shortcuts/blog/operations/export.js +46 -0
- package/dist/shortcuts/blog/operations/like.js +80 -0
- package/dist/shortcuts/blog/operations/publish.js +157 -0
- package/dist/shortcuts/blog/operations/query-user.js +58 -0
- package/dist/shortcuts/blog/operations/query.js +96 -0
- package/dist/shortcuts/blog/operations/registry.js +54 -0
- package/dist/shortcuts/blog/operations/remind.js +176 -0
- package/dist/shortcuts/blog/operations/shared.js +330 -0
- package/dist/shortcuts/blog/operations/types.js +16 -0
- package/dist/shortcuts/blog/operations.js +9 -0
- package/dist/shortcuts/caizhilian/continuation.js +70 -0
- package/dist/shortcuts/caizhilian/errors.js +53 -0
- package/dist/shortcuts/caizhilian/host.js +190 -0
- package/dist/shortcuts/caizhilian/index.js +186 -0
- package/dist/shortcuts/caizhilian/manifest.js +42 -0
- package/dist/shortcuts/caizhilian/operations/api.js +801 -0
- package/dist/shortcuts/caizhilian/operations/confirm.js +75 -0
- package/dist/shortcuts/caizhilian/operations/files.js +306 -0
- package/dist/shortcuts/caizhilian/operations/form.js +170 -0
- package/dist/shortcuts/caizhilian/operations/handlers/collab.js +611 -0
- package/dist/shortcuts/caizhilian/operations/handlers/document.js +890 -0
- package/dist/shortcuts/caizhilian/operations/handlers/file-collection.js +346 -0
- package/dist/shortcuts/caizhilian/operations/handlers/file-upload.js +192 -0
- package/dist/shortcuts/caizhilian/operations/handlers/fileops.js +570 -0
- package/dist/shortcuts/caizhilian/operations/handlers/folder.js +969 -0
- package/dist/shortcuts/caizhilian/operations/handlers/search.js +318 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-attach.js +294 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-backup.js +423 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-encrypt.js +238 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-storage.js +364 -0
- package/dist/shortcuts/caizhilian/operations/handlers/system.js +106 -0
- package/dist/shortcuts/caizhilian/operations/registry.js +151 -0
- package/dist/shortcuts/caizhilian/operations/shared.js +408 -0
- package/dist/shortcuts/caizhilian/operations/specs.js +623 -0
- package/dist/shortcuts/caizhilian/operations/types.js +57 -0
- package/dist/shortcuts/caizhilian/operations.js +21 -0
- package/dist/shortcuts/contract/continuation.js +70 -0
- package/dist/shortcuts/contract/errors.js +62 -0
- package/dist/shortcuts/contract/host.js +205 -0
- package/dist/shortcuts/contract/index.js +318 -0
- package/dist/shortcuts/contract/manifest.js +596 -0
- package/dist/shortcuts/contract/operations/clause-reference.js +78 -0
- package/dist/shortcuts/contract/operations/form-info.js +371 -0
- package/dist/shortcuts/contract/operations/read.js +118 -0
- package/dist/shortcuts/contract/operations/registry.js +43 -0
- package/dist/shortcuts/contract/operations/request.js +300 -0
- package/dist/shortcuts/contract/operations/shared.js +133 -0
- package/dist/shortcuts/contract/operations/subject.js +157 -0
- package/dist/shortcuts/contract/operations/template.js +81 -0
- package/dist/shortcuts/contract/operations/types.js +7 -0
- package/dist/shortcuts/contract/operations/workflow.js +52 -0
- package/dist/shortcuts/contract/operations/write.js +131 -0
- package/dist/shortcuts/contract/operations.js +8 -0
- package/dist/shortcuts/hrm/index.js +83 -2
- package/dist/shortcuts/hrm/operations/employee.js +160 -30
- package/dist/shortcuts/hrm/operations/org.js +17 -3
- package/dist/shortcuts/hrm/operations/position.js +18 -2
- package/dist/shortcuts/index.js +5 -1
- package/dist/shortcuts/invoice/index.js +74 -36
- package/dist/shortcuts/invoice/manifest.js +674 -4
- package/dist/shortcuts/invoice/markdown.js +172 -0
- package/dist/shortcuts/invoice/operations/add.js +3 -3
- package/dist/shortcuts/invoice/operations/delete.js +2 -2
- package/dist/shortcuts/invoice/operations/get.js +10 -2
- package/dist/shortcuts/invoice/operations/import.js +2 -2
- package/dist/shortcuts/invoice/operations/registry.js +8 -1
- package/dist/shortcuts/invoice/operations/reim-auto.js +719 -0
- package/dist/shortcuts/invoice/operations/reim.js +85 -2
- package/dist/shortcuts/invoice/operations/shared.js +75 -8
- package/dist/shortcuts/invoice/operations/update.js +2 -2
- package/dist/shortcuts/invoice/operations/upload-and-validate.js +93 -0
- package/dist/shortcuts/invoice/operations/validate-preview.js +2 -1
- package/dist/shortcuts/invoice/projection.js +328 -0
- package/dist/shortcuts/meeting/host.js +18 -1
- package/dist/shortcuts/meeting/manifest.js +30 -18
- package/dist/shortcuts/meeting/operations/eb/catalog.js +390 -0
- package/dist/shortcuts/meeting/operations/eb/conflict.js +389 -0
- package/dist/shortcuts/meeting/operations/eb/factory.js +389 -0
- package/dist/shortcuts/meeting/operations/eb/index.js +22 -0
- package/dist/shortcuts/meeting/operations/env.js +8 -1
- package/dist/shortcuts/meeting/operations/registry.js +2 -0
- package/dist/shortcuts/meeting/operations/signs.js +216 -2
- package/dist/shortcuts/workflow/endpoints.js +2 -1
- package/dist/shortcuts/workflow/errors.js +8 -2
- package/dist/shortcuts/workflow/host.js +6 -3
- package/dist/shortcuts/workflow/operations/form.js +931 -533
- package/dist/shortcuts/workflow/operations/read.js +462 -81
- package/dist/shortcuts/workflow/operations/shared.js +44 -0
- package/dist/shortcuts/workflow/operations/write.js +760 -56
- package/dist/shortcuts/workflow/operations.js +36 -10
- package/docs/_catalog.md +6 -0
- package/docs/agent-invoice.md +5 -1
- package/docs/agent-io-channel-design.md +468 -0
- package/docs/agent-skill-install.md +9 -4
- package/docs/blog.md +123 -0
- package/docs/caizhilian.md +304 -0
- package/docs/contract.md +91 -0
- package/docs/e10-auth.md +2 -2
- package/docs/hrm.md +19 -9
- package/docs/invoice.md +5 -1
- package/docs/meeting.md +1 -1
- package/docs/operation-manual.md +15 -3
- package/docs/skill-review-2026-09-19.md +2 -2
- package/package.json +2 -2
- package/scripts/package-skill.mjs +33 -20
- package/skill-template/business-info.json +113 -14
- package/skill-template/domains/blog.md +24 -0
- package/skill-template/domains/caizhilian.md +22 -0
- package/skill-template/domains/jinchengda.md +23 -0
- package/skill-template/domains/meeting.md +1 -1
- package/skill-template/domains/workflow.md +1 -1
- package/skill-template/skill-template.md +1 -1
- package/skills/weaver-e10-blog/SKILL.md +111 -0
- package/skills/weaver-e10-blog/references/blog-comment.md +51 -0
- package/skills/weaver-e10-blog/references/blog-comments.md +39 -0
- package/skills/weaver-e10-blog/references/blog-default-permission.md +38 -0
- package/skills/weaver-e10-blog/references/blog-disabled-capabilities.md +11 -0
- package/skills/weaver-e10-blog/references/blog-edit.md +51 -0
- package/skills/weaver-e10-blog/references/blog-export.md +42 -0
- package/skills/weaver-e10-blog/references/blog-like.md +50 -0
- package/skills/weaver-e10-blog/references/blog-publish.md +52 -0
- package/skills/weaver-e10-blog/references/blog-query-user.md +45 -0
- package/skills/weaver-e10-blog/references/blog-query.md +49 -0
- package/skills/weaver-e10-blog/references/blog-remind.md +51 -0
- package/skills/weaver-e10-blog/references/blog-resolve-users.md +38 -0
- package/skills/weaver-e10-blog/references/source-manifest.json +1540 -0
- package/skills/weaver-e10-caizhilian/SKILL.md +216 -0
- package/skills/weaver-e10-caizhilian/references/agent-entry.md +106 -0
- package/skills/weaver-e10-caizhilian/references/collaboration.md +112 -0
- package/skills/weaver-e10-caizhilian/references/document.md +111 -0
- package/skills/weaver-e10-caizhilian/references/file-collection.md +136 -0
- package/skills/weaver-e10-caizhilian/references/file-upload.md +309 -0
- package/skills/weaver-e10-caizhilian/references/files.md +120 -0
- package/skills/weaver-e10-caizhilian/references/folder.md +118 -0
- package/skills/weaver-e10-caizhilian/references/org-browser.md +104 -0
- package/skills/weaver-e10-caizhilian/references/setting-attach.md +410 -0
- package/skills/weaver-e10-caizhilian/references/setting-backup.md +372 -0
- package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +399 -0
- package/skills/weaver-e10-caizhilian/references/setting-storage.md +603 -0
- package/skills/weaver-e10-caizhilian/references/source-manifest.json +5079 -0
- package/skills/weaver-e10-caizhilian/references/withheld.md +93 -0
- package/skills/weaver-e10-caizhilian/references/write-protocol.md +120 -0
- package/skills/weaver-e10-calendar/SKILL.md +4 -3
- package/skills/weaver-e10-ebuilder-form/SKILL.md +4 -9
- package/skills/weaver-e10-ebuilder-form/references/context-routing.md +0 -9
- package/skills/weaver-e10-ebuilder-form/references/custom-api.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/fields-browser.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/list-nlist.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/openapi-read.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/openapi-write.md +1 -5
- package/skills/weaver-e10-esb/SKILL.md +2 -2
- package/skills/weaver-e10-esb/references/source-manifest.json +9 -9
- package/skills/weaver-e10-hrm/SKILL.md +54 -6
- package/skills/weaver-e10-hrm/references/employee.md +89 -15
- package/skills/weaver-e10-hrm/references/org.md +7 -4
- package/skills/weaver-e10-hrm/references/position.md +14 -7
- package/skills/weaver-e10-hrm/references/source-manifest.json +1 -1
- package/skills/weaver-e10-jinchengda/SKILL.md +166 -0
- package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +82 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +69 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +78 -0
- package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +27 -0
- package/skills/weaver-e10-jinchengda/references/contract-request-status.md +71 -0
- package/skills/weaver-e10-jinchengda/references/contract-request-write.md +94 -0
- package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +89 -0
- package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +77 -0
- package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +66 -0
- package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +94 -0
- package/skills/weaver-e10-jinchengda/references/source-manifest.json +259 -0
- package/skills/weaver-e10-jiuchuanhui/SKILL.md +5 -11
- package/skills/weaver-e10-jucailin/SKILL.md +4 -3
- package/skills/weaver-e10-mail/SKILL.md +4 -11
- package/skills/weaver-e10-meeting/SKILL.md +66 -32
- package/skills/weaver-e10-meeting/references/meeting-change.md +5 -2
- package/skills/weaver-e10-meeting/references/meeting-create.md +2 -1
- package/skills/weaver-e10-meeting/references/meeting-eb.md +114 -0
- package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +9 -8
- package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +35 -7
- package/skills/weaver-e10-meeting/references/meeting-update.md +1 -1
- package/skills/weaver-e10-okr/SKILL.md +6 -11
- package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
- package/skills/weaver-e10-plan/SKILL.md +3 -3
- package/skills/weaver-e10-plan/references/plan-collect-and-compose.md +1 -1
- package/skills/weaver-e10-plan/references/plan-overview-and-routing.md +1 -1
- package/skills/weaver-e10-plan/references/source-manifest.json +13 -13
- package/skills/weaver-e10-qiyecheng/SKILL.md +4 -10
- package/skills/weaver-e10-qiyecheng/references/source-manifest.json +9 -9
- package/skills/weaver-e10-shared/SKILL.md +36 -25
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +10 -31
- package/skills/weaver-e10-shared/references/json-output-contract.md +20 -11
- package/skills/weaver-e10-shijingran/SKILL.md +2 -1
- package/skills/weaver-e10-shijingran/references/source-manifest.json +9 -9
- package/skills/weaver-e10-skill-maker/SKILL.md +4 -10
- package/skills/weaver-e10-skill-maker/references/business-skill-generation.md +5 -3
- package/skills/weaver-e10-skill-maker/references/module-cli-generation.md +12 -27
- package/skills/weaver-e10-skill-maker/references/module-gap-audit-and-repair.md +17 -5
- package/skills/weaver-e10-skill-maker/references/source-update-detection.md +4 -27
- package/skills/weaver-e10-wenshuding/SKILL.md +5 -16
- package/skills/weaver-e10-wenshuding/references/source-manifest.json +7 -7
- package/skills/weaver-e10-workflow/SKILL.md +32 -37
- package/skills/weaver-e10-workflow/references/source-manifest.json +40 -77
- package/skills/weaver-e10-workflow/references/workflow-batch.md +90 -29
- package/skills/weaver-e10-workflow/references/workflow-create.md +79 -109
- package/skills/weaver-e10-workflow/references/workflow-memory.md +19 -9
- package/skills/weaver-e10-workflow/references/workflow-operate.md +2 -2
- package/skills/weaver-e10-workflow/references/workflow-query.md +37 -20
- package/skills/weaver-e10-workflow/references/workflow-share.md +18 -2
- package/skills/weaver-e10-workflow/references/workflow-view.md +4 -4
- package/skills/weaver-e10-yepiaotong/SKILL.md +75 -39
- package/skills/weaver-e10-yepiaotong/references/invoice-detail.md +22 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +3 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing.md +5 -5
- package/skills/weaver-e10-yepiaotong/references/invoice-output-view.md +135 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +15 -3
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +1 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-summarize.py +217 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-upload-and-validate.md +88 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-validation-preview.md +7 -2
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +90 -10
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +1 -0
- package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +70 -49
- package/skills/weaver-e10-yepiaotong/references/reim-fast-path.md +119 -0
- package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +55 -36
- package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +4 -0
- package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +149 -0
- package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +125 -5
- package/skills/weaver-e10-yepiaotong/references/source-manifest.json +430 -63
- package/skills/weaver-e10-yimiaoban/SKILL.md +4 -12
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
- package/skills/weaver-e10-ziguanjia/SKILL.md +2 -1
- package/skills/weaver-e10-ziguanjia/references/ziguanjia-agent-entry.md +3 -3
- package/tools/weaver-skill-detector-1.0.13/SKILL.md +9 -9
- package/tools/weaver-skill-detector-1.0.13/references/checklist.md +3 -3
- package/tools/weaver-skill-detector-1.0.13/scripts/__pycache__/validate_skill.cpython-313.pyc +0 -0
- package/tools/weaver-skill-detector-1.0.13/scripts/validate_skill.py +98 -91
- package/scripts/sync-connector-skills.mjs +0 -172
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// (saveSearchIndex / findIndexedItem,由 CLI 托管,本文件不读写索引文件);
|
|
9
9
|
// 查看/操作类 operation 允许用 requestId 或 group+index 定位(findIndexedItem)。
|
|
10
10
|
import { workflowSuccess, schemaOf, SCHEMA_REQUEST_ID, SCHEMA_CATEGORY, SCHEMA_INDEX, SCHEMA_PAGE_SIZE, } from './types.js';
|
|
11
|
-
import { asString, asInt, asStringArray, asBool, compact, responseData, toArray, resolveMemoryAppId, withLeadingMeta, STDOUT_SAFE_BYTES, stdoutBytesOf, } from './shared.js';
|
|
11
|
+
import { asString, asInt, asStringArray, asBool, compact, responseData, detailDataOf, toArray, resolveMemoryAppId, withLeadingMeta, STDOUT_SAFE_BYTES, stdoutBytesOf, parseNameWithId, } from './shared.js';
|
|
12
12
|
import { workflowValidationError, WorkflowCliError, workflowClarificationError, CLARIFICATION_SUBTYPE, } from '../errors.js';
|
|
13
13
|
import { WORKFLOW_ENDPOINTS } from '../endpoints.js';
|
|
14
14
|
import { loadSearchIndex, saveSearchIndex, findIndexedItem, readStateFile, writeStateFileBestEffort, } from '../state.js';
|
|
@@ -225,21 +225,6 @@ function cardWorkflowOpenUrl(card, baseUrl) {
|
|
|
225
225
|
? `${base}?secondEmpId=${encodeURIComponent(userMasterId)}&requestId=${encodeURIComponent(requestId)}`
|
|
226
226
|
: base;
|
|
227
227
|
}
|
|
228
|
-
/** 解析 "名称(ID)" 或纯 ID / 纯名称 → (name, id)。ID 必须是纯数字才认定为 ID。 */
|
|
229
|
-
function parseNameWithId(value) {
|
|
230
|
-
const text = (value || '').trim();
|
|
231
|
-
if (!text)
|
|
232
|
-
return { name: '' };
|
|
233
|
-
const match = /^(.+?)[((]([^()()]*)[))]\s*$/.exec(text);
|
|
234
|
-
if (match) {
|
|
235
|
-
const name = match[1].trim();
|
|
236
|
-
const idPart = match[2].trim();
|
|
237
|
-
if (name && idPart && /^[0-9]+$/.test(idPart)) {
|
|
238
|
-
return { name, id: idPart };
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return { name: text };
|
|
242
|
-
}
|
|
243
228
|
function splitCsv(value) {
|
|
244
229
|
if (value === undefined || value === null)
|
|
245
230
|
return [];
|
|
@@ -1178,6 +1163,54 @@ function isActionableRemark(value) {
|
|
|
1178
1163
|
return false;
|
|
1179
1164
|
return Number(text) < 100;
|
|
1180
1165
|
}
|
|
1166
|
+
/**
|
|
1167
|
+
* `isremark` 码位 → **当前用户在该节点上的参与身份名称**(列表卡片用)。
|
|
1168
|
+
*
|
|
1169
|
+
* 为什么需要本地映射:**待办列表接口(`getTodoListData`)的原始记录只有数字 `isremark`
|
|
1170
|
+
* (外加 `isremarktype` 等辅助位),没有身份名称**(2026-09-20 用 fetch 探针抓原始响应确认:
|
|
1171
|
+
* 一行 100+ 字段里只有 `isremark = 0`)。而分组计数接口(`getTodoGroupCount`,即 todoStat)
|
|
1172
|
+
* **会返回 `isRemarkName`** —— 本表就是按那套名称词表建的。
|
|
1173
|
+
*
|
|
1174
|
+
* 码位语义来源:V11.3 `scripts/e10_workflow.py` 的 `is_actionable_remark` 注释(逐条对齐)——
|
|
1175
|
+
* `0/1` 正常提交、`10/11` 意见征询(走 consulReply)、`20/21` 传阅需批示、`30/31` 抄送需提交、
|
|
1176
|
+
* `40/41` 转发需批注、`60/61` 传阅不需批示、`70/71` 抄送不需提交、`80/81` 转发不需批注;
|
|
1177
|
+
* **尾位 +1 即「(委托)」变体**(V11.3 全部以 `n/n+1` 成对书写);`100` 不可操作(非身份)。
|
|
1178
|
+
* 名称词表来源:V11.3 `references/workflow-search.md`「参与身份说明」的 20 条身份,并与真机
|
|
1179
|
+
* `isRemarkName` 实测值逐条一致(本租户 2026-09-20 实测:`0`=节点操作者、`30`=抄送接收人、
|
|
1180
|
+
* `40`=转发接收人、`70`=抄送接收人不需反馈)。
|
|
1181
|
+
*
|
|
1182
|
+
* ⚠️ **只覆盖有据可查的码位**:「超时指定干预人」「异常处理指定干预人」两族身份的码位在
|
|
1183
|
+
* V11.3 注释与实测里都没有出现,**不猜**——**未覆盖的码位一律返回空串**(模型据此不得臆断身份,
|
|
1184
|
+
* 需要身份时以 todoStat 的 `isRemarkName` 为准)。
|
|
1185
|
+
*/
|
|
1186
|
+
const REMARK_IDENTITY_NAMES = {
|
|
1187
|
+
'0': '节点操作者',
|
|
1188
|
+
'1': '节点操作者(委托)',
|
|
1189
|
+
'10': '意见征询接收人',
|
|
1190
|
+
'11': '意见征询接收人(委托)',
|
|
1191
|
+
'20': '传阅接收人',
|
|
1192
|
+
'21': '传阅接收人(委托)',
|
|
1193
|
+
'30': '抄送接收人',
|
|
1194
|
+
'31': '抄送接收人(委托)',
|
|
1195
|
+
'40': '转发接收人',
|
|
1196
|
+
'41': '转发接收人(委托)',
|
|
1197
|
+
'60': '传阅接收人不需反馈',
|
|
1198
|
+
'61': '传阅接收人不需反馈(委托)',
|
|
1199
|
+
'70': '抄送接收人不需反馈',
|
|
1200
|
+
'71': '抄送接收人不需反馈(委托)',
|
|
1201
|
+
'80': '转发接收人不需反馈',
|
|
1202
|
+
'81': '转发接收人不需反馈(委托)',
|
|
1203
|
+
};
|
|
1204
|
+
/** 取参与身份名称:优先用服务端下发的名称(若该接口将来下发),否则按码位表映射;未覆盖码位返回空串 */
|
|
1205
|
+
function remarkIdentityName(rawName, remarkValue) {
|
|
1206
|
+
const fromServer = asString(rawName) ?? '';
|
|
1207
|
+
if (fromServer)
|
|
1208
|
+
return fromServer;
|
|
1209
|
+
const text = remarkValue === undefined || remarkValue === null ? '' : String(remarkValue).trim();
|
|
1210
|
+
if (!/^-?\d+$/.test(text))
|
|
1211
|
+
return '';
|
|
1212
|
+
return REMARK_IDENTITY_NAMES[String(Number(text))] ?? '';
|
|
1213
|
+
}
|
|
1181
1214
|
function normalizeListResponse(response, category, categoryName, baseUrl = '') {
|
|
1182
1215
|
const record = responseData(response);
|
|
1183
1216
|
const data = record?.data && typeof record.data === 'object' ? record.data : {};
|
|
@@ -1214,6 +1247,9 @@ function normalizeListResponse(response, category, categoryName, baseUrl = '') {
|
|
|
1214
1247
|
flowStatus,
|
|
1215
1248
|
requestMark: pickText(recordObj, 'requestmark', 'requestMark', 'requestCode'),
|
|
1216
1249
|
isRemark: isRemarkRaw === undefined || isRemarkRaw === null ? '' : String(isRemarkRaw).trim(),
|
|
1250
|
+
// 参与身份名称:列表接口原始记录没有该字段 ⇒ 按 isremark 码位本地映射(映射出处见
|
|
1251
|
+
// REMARK_IDENTITY_NAMES);若将来服务端在该接口下发 `isremarkname`,以服务端值为准。
|
|
1252
|
+
isRemarkName: remarkIdentityName(pickText(recordObj, 'isremarkname', 'isRemarkName'), isRemarkRaw),
|
|
1217
1253
|
canSubmitRejectTransfer: isActionableRemark(isRemarkRaw),
|
|
1218
1254
|
stayStartAt,
|
|
1219
1255
|
stayDuration: category === 'todo' ? humanizeDurationSince(stayStartAt) : '',
|
|
@@ -1302,6 +1338,7 @@ const SLIM_INDEX_ITEM_KEYS = [
|
|
|
1302
1338
|
'workflowName',
|
|
1303
1339
|
'canSubmitRejectTransfer',
|
|
1304
1340
|
'isRemark',
|
|
1341
|
+
'isRemarkName',
|
|
1305
1342
|
'finished',
|
|
1306
1343
|
'overdue',
|
|
1307
1344
|
];
|
|
@@ -1448,7 +1485,6 @@ const workflowSearch = {
|
|
|
1448
1485
|
tabid: { type: 'string', description: '细分子类 tabid' },
|
|
1449
1486
|
tabName: { type: 'string', description: '细分子类名称' },
|
|
1450
1487
|
subCategory: { type: 'string', description: '细分子类名称或 tabid' },
|
|
1451
|
-
keyword: { type: 'array', items: { type: 'string' }, description: '实体关键词(辅助解析)' },
|
|
1452
1488
|
creator: { type: 'string', description: '发起人姓名,支持 "名称(ID)";纯名称多命中返回澄清候选,0 命中报错(对齐 V11.3)' },
|
|
1453
1489
|
creatorDept: { type: 'string', description: '发起人部门;纯名称多命中返回澄清候选,0 命中报错' },
|
|
1454
1490
|
creatorSubcom: { type: 'string', description: '发起人分部;纯名称多命中返回澄清候选,0 命中报错' },
|
|
@@ -1481,7 +1517,6 @@ const workflowSearch = {
|
|
|
1481
1517
|
pageSize: SCHEMA_PAGE_SIZE,
|
|
1482
1518
|
conditionType: { type: 'string', enum: ['and', 'or'], default: 'and' },
|
|
1483
1519
|
totalOnly: { type: 'boolean', default: false },
|
|
1484
|
-
mode: { type: 'string', enum: ['auto', 'legacy', 'smart'], default: 'auto', description: '执行模式强制覆盖,仅回归验证用;默认按 OA 版本自动路由' },
|
|
1485
1520
|
batchType: {
|
|
1486
1521
|
type: 'string',
|
|
1487
1522
|
enum: ['submit', 'urge', 'read', 'share', 'forward', 'reject'],
|
|
@@ -1495,16 +1530,18 @@ const workflowSearch = {
|
|
|
1495
1530
|
if (plan && typeof plan === 'object') {
|
|
1496
1531
|
// plan 批量编排仅智能分组模式支持(来源:Python cmd_workflow_search_plan 首行的
|
|
1497
1532
|
// resolve_workflow_search_mode 门禁)——该模式下 todoStat 的统计口径才成立;低版本 OA 上
|
|
1498
|
-
//
|
|
1499
|
-
|
|
1533
|
+
// 直接拒绝,并引导改用单条查询,避免静默按错口径分组。
|
|
1534
|
+
// 门禁与 memoryPrompt 的 oaContext.smartGrouping 同源(同一常量 + 同一比较函数),并且**没有
|
|
1535
|
+
// 强制覆盖开关**:模型拿到的布尔值就是这里的判据,不会出现「模型以为能用、这里却拒绝」的错位。
|
|
1536
|
+
const planMode = await resolveSearchMode(env);
|
|
1500
1537
|
if (planMode !== 'smart') {
|
|
1501
|
-
throw workflowValidationError('plan_requires_smart_mode', `plan 批量编排模式仅支持智能分组模式(OA ≥ ${formatOaVersion(SMART_GROUPING_MIN_OA_VERSION)});低版本请改用单条 workflow.search`, { retryAllowed: false
|
|
1538
|
+
throw workflowValidationError('plan_requires_smart_mode', `plan 批量编排模式仅支持智能分组模式(OA ≥ ${formatOaVersion(SMART_GROUPING_MIN_OA_VERSION)});低版本请改用单条 workflow.search`, { retryAllowed: false });
|
|
1502
1539
|
}
|
|
1503
1540
|
// 顶层 pageSize 即 plan 的展示窗口 page_cap(来源:Python cmd_workflow_search_plan 的 page_cap)
|
|
1504
1541
|
return executePlanSearch(env, plan, asInt(args.pageSize) ?? 10);
|
|
1505
1542
|
}
|
|
1506
1543
|
const category = (asString(args.category) || 'todo');
|
|
1507
|
-
const searchMode = await resolveSearchMode(env
|
|
1544
|
+
const searchMode = await resolveSearchMode(env);
|
|
1508
1545
|
const batchType = asString(args.batchType);
|
|
1509
1546
|
const totalOnly = asBool(args.totalOnly, false);
|
|
1510
1547
|
// legacy 待办查询走 CLI 侧分组(V11.2 语义;来源:Python execute_workflow_search_legacy 的
|
|
@@ -1577,7 +1614,6 @@ const workflowSearch = {
|
|
|
1577
1614
|
if (totalOnly) {
|
|
1578
1615
|
return workflowSuccess('workflow.search', {
|
|
1579
1616
|
type: 'workflowSearch',
|
|
1580
|
-
renderMode: searchMode,
|
|
1581
1617
|
category,
|
|
1582
1618
|
categoryName: CATEGORY_NAMES[category],
|
|
1583
1619
|
total,
|
|
@@ -1589,7 +1625,6 @@ const workflowSearch = {
|
|
|
1589
1625
|
const index = await saveListIndex(env, normalized);
|
|
1590
1626
|
return workflowSuccess('workflow.search', await enforceSearchStdoutBudget({
|
|
1591
1627
|
type: 'workflowSearch',
|
|
1592
|
-
renderMode: searchMode,
|
|
1593
1628
|
category,
|
|
1594
1629
|
categoryName: CATEGORY_NAMES[category],
|
|
1595
1630
|
total,
|
|
@@ -1630,11 +1665,13 @@ function isVersionAtLeast(actual, min) {
|
|
|
1630
1665
|
}
|
|
1631
1666
|
return true;
|
|
1632
1667
|
}
|
|
1633
|
-
/**
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1668
|
+
/**
|
|
1669
|
+
* 解析查询执行模式:版本 ≥ 基线走 smart;版本未知/运行时不可用 → legacy 兜底。
|
|
1670
|
+
* **不接受任何参数强制覆盖**——与 `memoryPrompt` 的 `oaContext.smartGrouping` 完全同源(同一常量 +
|
|
1671
|
+
* 同一比较函数),这样模型读到的布尔值与服务端门禁不可能漂移。
|
|
1672
|
+
* (09-21 用户裁定移除 `mode` 强制开关:它只是临时调试用,暴露出去反而会诱导模型「试一试」。)
|
|
1673
|
+
*/
|
|
1674
|
+
async function resolveSearchMode(env) {
|
|
1638
1675
|
try {
|
|
1639
1676
|
const session = await env.host.getSession();
|
|
1640
1677
|
const version = asString(session.version) ?? '';
|
|
@@ -2038,7 +2075,6 @@ async function executeLegacyGroupedTodoSearch(env, args) {
|
|
|
2038
2075
|
.join('\n\n');
|
|
2039
2076
|
return workflowSuccess('workflow.search', await enforceSearchStdoutBudget({
|
|
2040
2077
|
type: 'workflowSearch',
|
|
2041
|
-
renderMode: 'legacy',
|
|
2042
2078
|
category: 'todo',
|
|
2043
2079
|
categoryName: CATEGORY_NAMES.todo,
|
|
2044
2080
|
displayMode: 'groupedCards',
|
|
@@ -2884,9 +2920,9 @@ const workflowTodoStat = {
|
|
|
2884
2920
|
// 来源:Python 无独立 todoStat operation,只有 cmd_workflow_search_plan 的 smart 门禁 +
|
|
2885
2921
|
// execute_workflow_search_legacy 的分组输出——低版本待办统计由 workflow.search 的 legacy 分组输出承担。
|
|
2886
2922
|
// 低版本直接拒绝,避免模型拿到"全部(0)"口径统计(与 legacy"待处理(7)"口径矛盾)后按错误口径规划分组。
|
|
2887
|
-
const mode = await resolveSearchMode(env
|
|
2923
|
+
const mode = await resolveSearchMode(env);
|
|
2888
2924
|
if (mode !== 'smart') {
|
|
2889
|
-
throw workflowValidationError('todo_stat_requires_smart_mode', `workflow.todoStat 仅支持智能分组模式(OA ≥ ${formatOaVersion(SMART_GROUPING_MIN_OA_VERSION)});低版本请直接走 workflow.search(CLI 会自动输出分组统计)`, { retryAllowed: false
|
|
2925
|
+
throw workflowValidationError('todo_stat_requires_smart_mode', `workflow.todoStat 仅支持智能分组模式(OA ≥ ${formatOaVersion(SMART_GROUPING_MIN_OA_VERSION)});低版本请直接走 workflow.search(CLI 会自动输出分组统计)`, { retryAllowed: false });
|
|
2890
2926
|
}
|
|
2891
2927
|
const { query } = await buildSearchQuery(env, { ...args, category: 'todo' }, 'stat');
|
|
2892
2928
|
const response = await env.host.workflowRequest({
|
|
@@ -2984,7 +3020,7 @@ const workflowDetail = {
|
|
|
2984
3020
|
'表单内容区只展示核心业务字段,跳过空值、技术字段、可编辑、操作按钮、岗位、部门、最后反馈时间等非必要字段',
|
|
2985
3021
|
'表单字段值必须原样展示 value,不能摘要、改写、删减;字段含 valueHtml/links 时优先渲染为可点击超链接,不要把长 URL 直接作为普通文本换行展示',
|
|
2986
3022
|
'displayMode=longTextBlock 或 fullWidth=true 的表单字段必须全宽展示,保留原文、标点和换行;不要保留接口富文本里的背景色、字号和大段空白',
|
|
2987
|
-
'
|
|
3023
|
+
'签字意见是查看流程内容的固定组成部分,必须以意见块展示,不要压成表格或和表单字段混在一起;每条按两行展示(操作人/方式/时间、签字意见内容)',
|
|
2988
3024
|
'签字意见区标题写成「签字意见({{paginationText}})」;只有 requestLog.hasMore=true 时才显示 nextPagePrompt',
|
|
2989
3025
|
'不要展示 requestId;不要展示 workflowName,用户明确询问所属工作流时才展示为「工作流名称」;不要翻译、音译或改写接口返回文本',
|
|
2990
3026
|
'详情面板后不要输出固定建议列表或「如果想进一步操作」类尾巴;用户要求摘要/总结时改为围绕表单业务内容提炼,不要渲染详情表',
|
|
@@ -2993,8 +3029,12 @@ const workflowDetail = {
|
|
|
2993
3029
|
},
|
|
2994
3030
|
};
|
|
2995
3031
|
function normalizeDetailResponse(response) {
|
|
2996
|
-
|
|
2997
|
-
|
|
3032
|
+
// ★ 层级:`detailDataOf` 负责"`responseData()` 已解一层 `data`"这件事(真机实测响应为
|
|
3033
|
+
// `{message, data:{workflowBaseInfo, otherInfo, formData}}`、**无内层 `data`**,与 V11.3 同口径)。
|
|
3034
|
+
// 旧实现在这里又取了一次 `.data` ⇒ 实际拿到 `response.data.data`(不存在)⇒ `workflow.detail` 与
|
|
3035
|
+
// `workflow.summary` **恒空**(basicInfo 全空、formData.available=false),`workflow.write` 的操作
|
|
3036
|
+
// 门禁也因同源写法读不到 `mapItem` ⇒ 拦截失效。详见 `detailDataOf` 的注释与 PITFALLS A.50。
|
|
3037
|
+
const data = detailDataOf(response);
|
|
2998
3038
|
const base = data.workflowBaseInfo && typeof data.workflowBaseInfo === 'object' ? data.workflowBaseInfo : {};
|
|
2999
3039
|
const other = data.otherInfo && typeof data.otherInfo === 'object' ? data.otherInfo : {};
|
|
3000
3040
|
const currentNodes = toArray(base.currentNodeList ?? []);
|
|
@@ -3010,11 +3050,40 @@ function normalizeDetailResponse(response) {
|
|
|
3010
3050
|
creatorName: pickText(base, 'creatorLastName', 'createrName', 'creatorName'),
|
|
3011
3051
|
createdAt: pickText(base, 'createDateTime', 'createTime'),
|
|
3012
3052
|
currentNode: currentNode || pickText(base, 'currentNodeName', 'nodeName'),
|
|
3013
|
-
|
|
3014
|
-
flowStatus
|
|
3053
|
+
// 这两项的口径与列表侧(本文件 `:1396`/`:1432`)一致,也与 V11.3 `normalize_workflow_detail_response`
|
|
3054
|
+
// (`:3716`/`:3721`)逐字对齐:接口的 `flowStatus` 是 `{code,desc,...}` 对象、`requestLevel` 是
|
|
3055
|
+
// `0/1/2` 编码,**必须先过归一化函数**。旧写法直接 `asString(...)` ⇒ 详情面板里流程状态显示成
|
|
3056
|
+
// "[object Object]"、紧急程度显示成裸数字。
|
|
3057
|
+
// 这两项的口径与列表侧(本文件 `:1396`/`:1432`)一致,也与 V11.3 `normalize_workflow_detail_response`
|
|
3058
|
+
// (`:3716`/`:3721`)逐字对齐:接口的 `flowStatus` 是 `{code,desc,...}` 对象、`requestLevel` 是
|
|
3059
|
+
// `0/1/2` 编码,**必须先过归一化函数**。旧写法直接 `asString(...)` ⇒ 详情面板里流程状态显示成
|
|
3060
|
+
// "[object Object]"、紧急程度显示成裸数字。
|
|
3061
|
+
requestLevel: requestLevelText(pickText(base, 'requestLevelShow', 'requestlevelShow', 'requestLevelTitle', 'requestlevelTitle')
|
|
3062
|
+
|| base.requestLevel),
|
|
3063
|
+
flowStatus: flowStatusText(base.flowStatus) || pickText(base, 'flowstatusShow', 'status'),
|
|
3015
3064
|
};
|
|
3016
3065
|
const formFields = extractFormFields(data);
|
|
3017
3066
|
const formLogs = extractRequestLogs(data);
|
|
3067
|
+
// ★ 静默失败防线:接口成功但什么都没返回时,不能产出 `warnings: []` + 全空 basicInfo ——
|
|
3068
|
+
// 模型看到的是一个"成功但空白"的结果,既不知道是查不到、没权限还是参数错,容易当"流程是空的"。
|
|
3069
|
+
// (2026-09-19 追查:当时线上"detail 恒空"的**真根因**是 `responseData()` 之后又多解了一层
|
|
3070
|
+
// `data`(见 `detailDataOf`),已修;本 warnings 保留,作为**真的取不到**时的兜底。)
|
|
3071
|
+
const detailEmpty = !basicInfo.requestTitle
|
|
3072
|
+
&& !basicInfo.workflowName
|
|
3073
|
+
&& !basicInfo.requestMark
|
|
3074
|
+
&& !basicInfo.createdAt
|
|
3075
|
+
&& !basicInfo.creatorName
|
|
3076
|
+
&& !basicInfo.currentNode
|
|
3077
|
+
&& formFields.length === 0;
|
|
3078
|
+
const warnings = [];
|
|
3079
|
+
if (detailEmpty) {
|
|
3080
|
+
warnings.push('接口未返回该流程的任何详情内容(流程标题/编号/发起人/发起时间/当前节点/表单字段全为空)。'
|
|
3081
|
+
+ '这不代表"流程是空的",也不代表流程一定不存在——最可能是这个 requestId 在本环境查不到对应流程'
|
|
3082
|
+
+ '(编号抄错、来自其他环境、已被删除)。'
|
|
3083
|
+
+ '请先用 `workflow.search` 在本环境确认该流程可见、并直接从那一次的'
|
|
3084
|
+
+ '`cards[].requestId` 取值,再重试;若确认流程存在却仍取不到,请如实告知用户"详情不可见",'
|
|
3085
|
+
+ '不要编造流程内容。');
|
|
3086
|
+
}
|
|
3018
3087
|
return {
|
|
3019
3088
|
basicInfo,
|
|
3020
3089
|
formData: {
|
|
@@ -3023,7 +3092,7 @@ function normalizeDetailResponse(response) {
|
|
|
3023
3092
|
fields: formFields,
|
|
3024
3093
|
},
|
|
3025
3094
|
requestLog: { available: formLogs.length > 0, items: formLogs },
|
|
3026
|
-
warnings
|
|
3095
|
+
warnings,
|
|
3027
3096
|
};
|
|
3028
3097
|
}
|
|
3029
3098
|
// 去 HTML 标签 + 反转义(来源:Python strip_html,L681)。模块级共用。
|
|
@@ -3164,85 +3233,365 @@ function extractFormFields(data) {
|
|
|
3164
3233
|
}
|
|
3165
3234
|
return fields;
|
|
3166
3235
|
}
|
|
3236
|
+
// ---------------------------------------------------------------------------
|
|
3237
|
+
// 签字意见取值工具族(★ 2026-09-19 按 V11.3 逐函数对齐)
|
|
3238
|
+
//
|
|
3239
|
+
// V11.3 的链路:`extract_request_log_items(response)` → 取 `response["data"]` → 交给
|
|
3240
|
+
// `collect_detail_logs_from_obj` **递归收集**(list 逐项递归;dict 归一化后继续下钻 15 个候选键)。
|
|
3241
|
+
// V12 早期版本把"递归"退化成"枚举 4 条固定路径"(`data.list` / `data.records` …),而
|
|
3242
|
+
// `getRequestLog` 的真实响应是 `{message:{errcode,errmsg}, data:[…]}` —— **`data` 直接是数组**
|
|
3243
|
+
// ⇒ 四条路径全 miss ⇒ 签字意见恒为空。真机探针实测同一份响应:V11.3 解出 8 条、V12 解出 0 条。
|
|
3244
|
+
// 归一化侧的 `display_text_from_value` 一族(对象取值 / 时间戳格式化)V12 同样缺失 ⇒ 一并补齐。
|
|
3245
|
+
// (接收人解析链已于 2026-09-20 按要求整体删除,不再取 `receivedPersonIds` / `userDataMap`。)
|
|
3246
|
+
// ---------------------------------------------------------------------------
|
|
3247
|
+
/** 展示文本候选键(来源:V11.3 `DISPLAY_TEXT_KEYS`)。 */
|
|
3248
|
+
const LOG_DISPLAY_TEXT_KEYS = [
|
|
3249
|
+
'showValue', 'displayValue', 'name', 'showName', 'lastName', 'lastname', 'userName',
|
|
3250
|
+
'username', 'label', 'title', 'text', 'content', 'value',
|
|
3251
|
+
];
|
|
3252
|
+
/** 日志容器候选键(来源:V11.3 `collect_detail_logs_from_obj`)。 */
|
|
3253
|
+
const LOG_CONTAINER_KEYS = [
|
|
3254
|
+
'requestLogList', 'requestLogs', 'logs', 'logList', 'operatorList', 'signList', 'remarkList',
|
|
3255
|
+
'opinionList', 'records', 'rows', 'dataList', 'datas', 'children', 'items', 'list',
|
|
3256
|
+
];
|
|
3257
|
+
/** 详情里"日志容器"的键名特征(来源:V11.3 `DETAIL_LOG_CONTAINER_PATTERN`)。 */
|
|
3258
|
+
const LOG_CONTAINER_KEY_PATTERN = /log|日志|remark|opinion|sign|operate|审批|签字|流转/iu;
|
|
3259
|
+
const LOG_NODE_KEYS = [
|
|
3260
|
+
'nodeName', 'node', 'activityName', 'activityname', 'activity', 'activityInfo', 'operateNode',
|
|
3261
|
+
'operatenode', 'operateNodeName', 'operatenodename', 'nodeNameShow', 'nodeShow',
|
|
3262
|
+
];
|
|
3263
|
+
const LOG_OPERATOR_KEYS = [
|
|
3264
|
+
'operatorName', 'operator', 'operatorUser', 'operatorUserName', 'operatorLastName',
|
|
3265
|
+
'operatorLastname', 'operatorShow', 'operateUser', 'operateUserName', 'operateusername',
|
|
3266
|
+
'operateUserShow', 'operateBy', 'operateByName', 'operaterName', 'operater', 'handlerUser',
|
|
3267
|
+
'handlerUserName', 'userName', 'username', 'handlerName', 'handler', 'handleUser',
|
|
3268
|
+
'handleUserName', 'receiverName', 'receiver', 'receiverUser', 'receiverUserName', 'resourceName',
|
|
3269
|
+
'resource', 'createUser', 'createUserName', 'creator', 'creatorName', 'creatorLastName',
|
|
3270
|
+
'lastName', 'lastname',
|
|
3271
|
+
];
|
|
3272
|
+
const LOG_TIME_KEYS = [
|
|
3273
|
+
'operateTime', 'operatetime', 'operateDateTime', 'operatedatetime', 'operatorTime',
|
|
3274
|
+
'handlerTime', 'handleTime', 'receiveTime', 'createTime', 'createdTime', 'createdatetime',
|
|
3275
|
+
'createDateTime', 'dateTime', 'datetime', 'time',
|
|
3276
|
+
];
|
|
3277
|
+
const LOG_ACTION_KEYS = [
|
|
3278
|
+
'commentName', 'commentname', 'operateType', 'operateTypeName', 'operateTypeShow', 'operateName',
|
|
3279
|
+
'operateNameShow', 'operation', 'operationName', 'operationType', 'operationTypeName', 'action',
|
|
3280
|
+
'actionName', 'actionType', 'dealType', 'dealTypeName', 'handleType', 'handleTypeName',
|
|
3281
|
+
'submitType', 'submitTypeName', 'typeName', 'typeShow', 'status', 'statusName', 'statusShow',
|
|
3282
|
+
'logType', 'requestLogType',
|
|
3283
|
+
];
|
|
3284
|
+
const LOG_OPINION_KEYS = [
|
|
3285
|
+
'opinion', 'remark', 'remarks', 'signRemark', 'signremark', 'content', 'comment', 'message',
|
|
3286
|
+
'requestLogContent', 'commentContent', 'commentcontent',
|
|
3287
|
+
];
|
|
3288
|
+
/** 宽松取键:先精确匹配、再忽略大小写(来源:V11.3 `pick_raw`)。 */
|
|
3289
|
+
function pickRawLoose(source, ...keys) {
|
|
3290
|
+
if (!source || typeof source !== 'object' || Array.isArray(source))
|
|
3291
|
+
return undefined;
|
|
3292
|
+
const record = source;
|
|
3293
|
+
for (const key of keys) {
|
|
3294
|
+
if (key in record)
|
|
3295
|
+
return record[key];
|
|
3296
|
+
}
|
|
3297
|
+
const lowered = new Map();
|
|
3298
|
+
for (const [key, value] of Object.entries(record))
|
|
3299
|
+
lowered.set(key.toLowerCase(), value);
|
|
3300
|
+
for (const key of keys) {
|
|
3301
|
+
const hit = lowered.get(key.toLowerCase());
|
|
3302
|
+
if (hit !== undefined)
|
|
3303
|
+
return hit;
|
|
3304
|
+
}
|
|
3305
|
+
return undefined;
|
|
3306
|
+
}
|
|
3307
|
+
/**
|
|
3308
|
+
* 任意值 → 展示文本(来源:V11.3 `display_text_from_value`)。
|
|
3309
|
+
*
|
|
3310
|
+
* 这是"签名里放的是对象"也能读出来的关键:先按 `DISPLAY_TEXT_KEYS` 取(`commentor` 对象因此
|
|
3311
|
+
* 取到 `name`),再下钻常见包裹键,最后才 `stripHtml`;深度上限 4,避免环。
|
|
3312
|
+
*/
|
|
3313
|
+
function displayTextFromValue(value, depth = 0) {
|
|
3314
|
+
if (value === null || value === undefined)
|
|
3315
|
+
return '';
|
|
3316
|
+
if (typeof value === 'string' && !value.trim())
|
|
3317
|
+
return '';
|
|
3318
|
+
if (Array.isArray(value) && value.length === 0)
|
|
3319
|
+
return '';
|
|
3320
|
+
if (depth > 4)
|
|
3321
|
+
return '';
|
|
3322
|
+
if (Array.isArray(value)) {
|
|
3323
|
+
return value.map((item) => displayTextFromValue(item, depth + 1)).filter(Boolean).join('、');
|
|
3324
|
+
}
|
|
3325
|
+
if (typeof value === 'object') {
|
|
3326
|
+
const record = value;
|
|
3327
|
+
if (Object.keys(record).length === 0)
|
|
3328
|
+
return '';
|
|
3329
|
+
// ★ 必须走 `pickRawLoose`(大小写不敏感):实测服务端回的对象键常与候选键大小写不一致
|
|
3330
|
+
// (如 `UserName` 对上候选键 `userName`)—— 用 `in` 精确匹配会取不到值。
|
|
3331
|
+
for (const key of LOG_DISPLAY_TEXT_KEYS) {
|
|
3332
|
+
const raw = pickRawLoose(record, key);
|
|
3333
|
+
if (raw === undefined)
|
|
3334
|
+
continue;
|
|
3335
|
+
const text = displayTextFromValue(raw, depth + 1);
|
|
3336
|
+
if (text)
|
|
3337
|
+
return text;
|
|
3338
|
+
}
|
|
3339
|
+
for (const key of ['browserDatas', 'resource', 'resourceInfo', 'hrmResource', 'user', 'userInfo', 'userBaseInfo', 'baseInfo', 'person', 'identity', 'operator', 'handler', 'receiver', 'creator', 'data']) {
|
|
3340
|
+
const raw = pickRawLoose(record, key);
|
|
3341
|
+
if (raw === undefined)
|
|
3342
|
+
continue;
|
|
3343
|
+
const text = displayTextFromValue(raw, depth + 1);
|
|
3344
|
+
if (text)
|
|
3345
|
+
return text;
|
|
3346
|
+
}
|
|
3347
|
+
return '';
|
|
3348
|
+
}
|
|
3349
|
+
return stripHtml(value);
|
|
3350
|
+
}
|
|
3351
|
+
/** 按候选键取展示文本(来源:V11.3 `pick_display_text`)。 */
|
|
3352
|
+
function pickDisplayText(source, ...keys) {
|
|
3353
|
+
for (const key of keys) {
|
|
3354
|
+
const text = displayTextFromValue(pickRawLoose(source, key));
|
|
3355
|
+
if (text)
|
|
3356
|
+
return text;
|
|
3357
|
+
}
|
|
3358
|
+
return '';
|
|
3359
|
+
}
|
|
3360
|
+
/** 数字串 → 本地可读时间(`YYYY-MM-DD HH:mm:ss`)。 */
|
|
3361
|
+
function formatLogTimestamp(ms) {
|
|
3362
|
+
const date = new Date(ms);
|
|
3363
|
+
if (Number.isNaN(date.getTime()))
|
|
3364
|
+
return String(ms);
|
|
3365
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
3366
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} `
|
|
3367
|
+
+ `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
3368
|
+
}
|
|
3369
|
+
/**
|
|
3370
|
+
* 时间文本归一化(来源:V11.3 `normalize_log_time_text`)。
|
|
3371
|
+
*
|
|
3372
|
+
* 接口给的是**毫秒时间戳**(如 `1743082059000`)—— 不处理就会在签字意见里裸显一串数字。
|
|
3373
|
+
*/
|
|
3374
|
+
function normalizeLogTimeText(value) {
|
|
3375
|
+
const clean = stripHtml(value);
|
|
3376
|
+
if (/^\d{13}$/u.test(clean))
|
|
3377
|
+
return formatLogTimestamp(Number(clean));
|
|
3378
|
+
if (/^\d{10}$/u.test(clean))
|
|
3379
|
+
return formatLogTimestamp(Number(clean) * 1000);
|
|
3380
|
+
return clean;
|
|
3381
|
+
}
|
|
3382
|
+
/** 去掉 `_weaverMte_` 系统标记(来源:V11.3 `strip_weaver_mte_tokens`)。 */
|
|
3383
|
+
function stripWeaverMteTokens(text) {
|
|
3384
|
+
if (!text)
|
|
3385
|
+
return text;
|
|
3386
|
+
return text.replace(/_weaverMte_\S+/gu, ' ').replace(/\s+/gu, ' ').trim();
|
|
3387
|
+
}
|
|
3388
|
+
/**
|
|
3389
|
+
* 递归收集日志条目(来源:V11.3 `collect_detail_logs_from_obj`)。
|
|
3390
|
+
*
|
|
3391
|
+
* ★ 关键在"递归":`data` 可能是数组、也可能是对象里某个键下面挂着数组。V12 早期只枚举四条固定路径,
|
|
3392
|
+
* 遇到 `getRequestLog` 的"`data` 是数组"形态就整条归零。
|
|
3393
|
+
*/
|
|
3394
|
+
function collectDetailLogsFromObj(obj, logs, limit) {
|
|
3395
|
+
if (logs.length >= limit)
|
|
3396
|
+
return;
|
|
3397
|
+
if (Array.isArray(obj)) {
|
|
3398
|
+
for (const item of obj) {
|
|
3399
|
+
collectDetailLogsFromObj(item, logs, limit);
|
|
3400
|
+
if (logs.length >= limit)
|
|
3401
|
+
break;
|
|
3402
|
+
}
|
|
3403
|
+
return;
|
|
3404
|
+
}
|
|
3405
|
+
if (!obj || typeof obj !== 'object')
|
|
3406
|
+
return;
|
|
3407
|
+
const record = obj;
|
|
3408
|
+
const normalized = normalizeLogItem(record);
|
|
3409
|
+
if (detailLogHasContent(normalized))
|
|
3410
|
+
logs.push(normalized);
|
|
3411
|
+
for (const key of LOG_CONTAINER_KEYS) {
|
|
3412
|
+
if (key in record)
|
|
3413
|
+
collectDetailLogsFromObj(record[key], logs, limit);
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
/**
|
|
3417
|
+
* 从接口响应里取出签字意见条目(来源:V11.3 `extract_request_log_items`)。
|
|
3418
|
+
*
|
|
3419
|
+
* 取 `response.data` 作为源(为空则回退整个 response),递归收集后按
|
|
3420
|
+
* `node/operator/time/action/opinion` 五元组去重。
|
|
3421
|
+
*/
|
|
3422
|
+
function extractRequestLogItems(response, limit = 500) {
|
|
3423
|
+
const wrapper = response && typeof response === 'object' && !Array.isArray(response)
|
|
3424
|
+
? response
|
|
3425
|
+
: {};
|
|
3426
|
+
let source = wrapper.data;
|
|
3427
|
+
const emptySource = source === null || source === undefined || source === ''
|
|
3428
|
+
|| (Array.isArray(source) && source.length === 0)
|
|
3429
|
+
|| (typeof source === 'object' && !Array.isArray(source) && Object.keys(source).length === 0);
|
|
3430
|
+
if (emptySource)
|
|
3431
|
+
source = response;
|
|
3432
|
+
const logs = [];
|
|
3433
|
+
collectDetailLogsFromObj(source, logs, limit);
|
|
3434
|
+
const seen = new Set();
|
|
3435
|
+
const unique = [];
|
|
3436
|
+
for (const log of logs) {
|
|
3437
|
+
const key = [log.node, log.operator, log.time, log.action, log.opinion]
|
|
3438
|
+
.map((part) => String(part ?? ''))
|
|
3439
|
+
.join('\u0001');
|
|
3440
|
+
if (seen.has(key))
|
|
3441
|
+
continue;
|
|
3442
|
+
seen.add(key);
|
|
3443
|
+
unique.push(log);
|
|
3444
|
+
}
|
|
3445
|
+
return unique;
|
|
3446
|
+
}
|
|
3447
|
+
/**
|
|
3448
|
+
* 详情响应里的签字意见(来源:V11.3 `extract_detail_logs`)。
|
|
3449
|
+
* 只认"键名像日志容器"的键,再走同一套递归收集 —— 不再枚举固定键名。
|
|
3450
|
+
*/
|
|
3167
3451
|
function extractRequestLogs(data) {
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
if (
|
|
3173
|
-
|
|
3452
|
+
if (!data || typeof data !== 'object')
|
|
3453
|
+
return [];
|
|
3454
|
+
const logs = [];
|
|
3455
|
+
for (const [key, value] of Object.entries(data)) {
|
|
3456
|
+
if (!LOG_CONTAINER_KEY_PATTERN.test(key))
|
|
3457
|
+
continue;
|
|
3458
|
+
if (value === null || value === undefined || value === '')
|
|
3459
|
+
continue;
|
|
3460
|
+
if (Array.isArray(value) && value.length === 0)
|
|
3461
|
+
continue;
|
|
3462
|
+
collectDetailLogsFromObj(value, logs, 40);
|
|
3174
3463
|
}
|
|
3175
|
-
return
|
|
3464
|
+
return logs;
|
|
3176
3465
|
}
|
|
3177
3466
|
function detailLogHasContent(log) {
|
|
3178
|
-
return Boolean(log && (log.
|
|
3467
|
+
return Boolean(log && (log.node || log.operator || log.action || log.time || log.opinion));
|
|
3179
3468
|
}
|
|
3180
3469
|
// ---------------------------------------------------------------------------
|
|
3181
3470
|
// 签字意见归一化(来源:normalize_detail_log_item / fetch_request_log_page)
|
|
3182
3471
|
// ---------------------------------------------------------------------------
|
|
3472
|
+
/** 响应里的显式 hasMore 标志(来源:V11.3 `response_has_more_flag`)。 */
|
|
3473
|
+
function responseHasMoreFlag(response) {
|
|
3474
|
+
const wrapper = response && typeof response === 'object' && !Array.isArray(response)
|
|
3475
|
+
? response
|
|
3476
|
+
: {};
|
|
3477
|
+
const data = wrapper.data && typeof wrapper.data === 'object' && !Array.isArray(wrapper.data)
|
|
3478
|
+
? wrapper.data
|
|
3479
|
+
: {};
|
|
3480
|
+
const candidates = [];
|
|
3481
|
+
for (const key of ['hasMore', 'hasNext', 'more', 'next']) {
|
|
3482
|
+
if (key in wrapper)
|
|
3483
|
+
candidates.push(wrapper[key]);
|
|
3484
|
+
if (key in data)
|
|
3485
|
+
candidates.push(data[key]);
|
|
3486
|
+
}
|
|
3487
|
+
for (const value of candidates) {
|
|
3488
|
+
if (typeof value === 'boolean')
|
|
3489
|
+
return value;
|
|
3490
|
+
if (typeof value === 'string') {
|
|
3491
|
+
const lowered = value.trim().toLowerCase();
|
|
3492
|
+
if (['true', '1', 'yes'].includes(lowered))
|
|
3493
|
+
return true;
|
|
3494
|
+
if (['false', '0', 'no'].includes(lowered))
|
|
3495
|
+
return false;
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
return null;
|
|
3499
|
+
}
|
|
3183
3500
|
async function fetchRequestLogPage(env, requestId, current, pageSize) {
|
|
3184
3501
|
const session = await env.host.getSession();
|
|
3185
|
-
const
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3502
|
+
const normalizedCurrent = Math.max(1, current || 1);
|
|
3503
|
+
const normalizedPageSize = Math.max(1, Math.min(50, pageSize || 10));
|
|
3504
|
+
const loadPage = async (page) => {
|
|
3505
|
+
const body = { requestId, current: page, pageSize: normalizedPageSize };
|
|
3506
|
+
if (session.userId)
|
|
3507
|
+
body.userId = session.userId;
|
|
3508
|
+
return env.host.workflowRequest({
|
|
3509
|
+
method: 'POST',
|
|
3510
|
+
path: WORKFLOW_ENDPOINTS.REQUEST_LOG,
|
|
3511
|
+
operation: 'workflow.requestLog',
|
|
3512
|
+
body,
|
|
3513
|
+
});
|
|
3514
|
+
};
|
|
3515
|
+
const response = await loadPage(normalizedCurrent);
|
|
3516
|
+
// ★ 取数按 V11.3 口径:把整个响应交给 `extractRequestLogItems`(取 `response.data` —— 实测是
|
|
3517
|
+
// **数组** —— 再递归收集)。V12 早期写死 `data.list`/`data.records` 四个路径,数组形态全部 miss。
|
|
3518
|
+
const items = extractRequestLogItems(response, normalizedPageSize * 2).slice(0, normalizedPageSize);
|
|
3198
3519
|
const total = extractTotal(response);
|
|
3199
3520
|
const totalReliable = total !== null;
|
|
3200
|
-
const
|
|
3201
|
-
|
|
3521
|
+
const hasMoreFlag = responseHasMoreFlag(response);
|
|
3522
|
+
let hasMore;
|
|
3523
|
+
if (hasMoreFlag !== null) {
|
|
3524
|
+
hasMore = hasMoreFlag;
|
|
3525
|
+
}
|
|
3526
|
+
else if (totalReliable) {
|
|
3527
|
+
hasMore = normalizedCurrent * normalizedPageSize < total;
|
|
3528
|
+
}
|
|
3529
|
+
else if (items.length < normalizedPageSize) {
|
|
3530
|
+
hasMore = false;
|
|
3531
|
+
}
|
|
3532
|
+
else {
|
|
3533
|
+
// 本页已满且接口没给 total ⇒ 探测下一页(来源:V11.3 `fetch_request_log_page` 的 next_page_probe)。
|
|
3534
|
+
// 直接假定 hasMore=true 会在最后一页误报"可继续查看第 N 页"。
|
|
3535
|
+
hasMore = extractRequestLogItems(await loadPage(normalizedCurrent + 1), 1).length > 0;
|
|
3536
|
+
}
|
|
3202
3537
|
return {
|
|
3203
3538
|
available: items.length > 0,
|
|
3204
|
-
current,
|
|
3205
|
-
pageSize,
|
|
3539
|
+
current: normalizedCurrent,
|
|
3540
|
+
pageSize: normalizedPageSize,
|
|
3206
3541
|
currentPageCount: items.length,
|
|
3207
3542
|
total,
|
|
3208
3543
|
totalReliable,
|
|
3209
3544
|
hasMore,
|
|
3210
|
-
nextCurrent: hasMore ?
|
|
3211
|
-
nextPagePrompt: hasMore ? `可继续查看第 ${
|
|
3545
|
+
nextCurrent: hasMore ? normalizedCurrent + 1 : null,
|
|
3546
|
+
nextPagePrompt: hasMore ? `可继续查看第 ${normalizedCurrent + 1} 页签字意见` : '',
|
|
3212
3547
|
items,
|
|
3213
|
-
paginationText: `第 ${
|
|
3548
|
+
paginationText: `第 ${normalizedCurrent} 页,每页 ${normalizedPageSize} 条`,
|
|
3214
3549
|
paginationNote: '签字意见按页展示;只有 hasMore=true 时才提示查看下一页',
|
|
3215
3550
|
};
|
|
3216
3551
|
}
|
|
3552
|
+
/**
|
|
3553
|
+
* 签字意见单条归一化(来源:V11.3 `normalize_detail_log_item`)。
|
|
3554
|
+
*
|
|
3555
|
+
* 两处「V11.3 有、V12 缺」的处理,缺一个就会显示成空白或乱码:
|
|
3556
|
+
* ① **操作人取 `commentor`** —— 真机实测该字段是**对象**(`{id, name:'孙一一', username}`),
|
|
3557
|
+
* 必须走 `displayTextFromValue`;旧写法用 `pickText` 只会把对象 `String()` 成 `[object Object]`。
|
|
3558
|
+
* ② **时间走 `normalizeLogTimeText`** —— 接口给的是 **毫秒时间戳**(`1743082059000`)。
|
|
3559
|
+
*
|
|
3560
|
+
* 接收人(`receivedPersonIds` + `userDataMap` 解析)已按要求整体移除:解析链(`resolveReceivedPersons` /
|
|
3561
|
+
* `userNameFromMap` / `collectUserDataMap` / `valueToIdList` 与 `LOG_RECEIVER_*_KEYS`)一并删除。
|
|
3562
|
+
*/
|
|
3217
3563
|
function normalizeLogItem(item) {
|
|
3218
3564
|
if (!item || typeof item !== 'object')
|
|
3219
3565
|
return {};
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
const
|
|
3566
|
+
let node = pickDisplayText(item, ...LOG_NODE_KEYS);
|
|
3567
|
+
if (/^\d{12,}$/u.test(stripHtml(node)))
|
|
3568
|
+
node = '';
|
|
3569
|
+
const commentorText = displayTextFromValue(pickRawLoose(item, 'commentor', 'commentorId', 'commentorid', 'commentorInfo'));
|
|
3570
|
+
const operator = commentorText || pickDisplayText(item, ...LOG_OPERATOR_KEYS);
|
|
3571
|
+
const time = normalizeLogTimeText(pickDisplayText(item, ...LOG_TIME_KEYS));
|
|
3572
|
+
const action = pickDisplayText(item, ...LOG_ACTION_KEYS);
|
|
3573
|
+
const opinion = stripWeaverMteTokens(pickDisplayText(item, ...LOG_OPINION_KEYS));
|
|
3225
3574
|
const operatorDisplay = operator || '未返回';
|
|
3226
3575
|
const actionDisplay = action || '未返回';
|
|
3227
3576
|
const timeDisplay = time || '未返回';
|
|
3228
3577
|
const opinionDisplay = opinion || '未填写意见';
|
|
3229
3578
|
return {
|
|
3579
|
+
node,
|
|
3230
3580
|
operator,
|
|
3231
3581
|
operatorDisplay,
|
|
3232
3582
|
time,
|
|
3233
3583
|
timeDisplay,
|
|
3234
3584
|
action,
|
|
3235
3585
|
actionDisplay,
|
|
3586
|
+
operatorAction: `${operatorDisplay} · ${actionDisplay}`,
|
|
3236
3587
|
operatorActionTime: `${operatorDisplay} · ${actionDisplay} · ${timeDisplay}`,
|
|
3237
|
-
receiverDisplay: receivers || '未返回',
|
|
3238
3588
|
opinion,
|
|
3239
3589
|
opinionDisplay,
|
|
3240
3590
|
displayLines: {
|
|
3241
3591
|
line1: `${operatorDisplay} · ${actionDisplay} · ${timeDisplay}`,
|
|
3242
|
-
line2: `接收人:${receivers || '未返回'}`,
|
|
3243
3592
|
line3: opinionDisplay,
|
|
3244
3593
|
},
|
|
3245
|
-
displayLayout: '
|
|
3594
|
+
displayLayout: 'twoLineSignatureOpinion',
|
|
3246
3595
|
};
|
|
3247
3596
|
}
|
|
3248
3597
|
/** 详情字段取值:有 links 时优先渲染为可点击超链接(来源:Python detail_field_markdown_value) */
|
|
@@ -3308,7 +3657,6 @@ function renderDetailMarkdown(normalized) {
|
|
|
3308
3657
|
for (const it of logItems) {
|
|
3309
3658
|
const dl = it.displayLines || {};
|
|
3310
3659
|
let line1 = asString(dl.line1) ?? '';
|
|
3311
|
-
let line2 = asString(dl.line2) ?? '';
|
|
3312
3660
|
let line3 = asString(dl.line3) ?? '';
|
|
3313
3661
|
if (!line1) {
|
|
3314
3662
|
const operator = asString(it.operatorDisplay) ?? '未返回';
|
|
@@ -3316,13 +3664,11 @@ function renderDetailMarkdown(normalized) {
|
|
|
3316
3664
|
const timeText = asString(it.timeDisplay) ?? asString(it.createdAt) ?? '未返回';
|
|
3317
3665
|
line1 = `${operator} · ${action} · ${timeText}`;
|
|
3318
3666
|
}
|
|
3319
|
-
if (!line2)
|
|
3320
|
-
line2 = `接收人:${asString(it.receiverDisplay) ?? '未返回'}`;
|
|
3321
3667
|
if (!line3) {
|
|
3322
3668
|
const opinion = asString(it.opinionDisplay) ?? asString(it.contentDisplay) ?? asString(it.content) ?? '未填写意见';
|
|
3323
3669
|
line3 = `签字意见:${opinion}`;
|
|
3324
3670
|
}
|
|
3325
|
-
lines.push(`> **${line1}**`, '>', `> ${
|
|
3671
|
+
lines.push(`> **${line1}**`, '>', `> ${line3}`, '>');
|
|
3326
3672
|
}
|
|
3327
3673
|
if (lines[lines.length - 1] === '>')
|
|
3328
3674
|
lines.pop();
|
|
@@ -3734,8 +4080,8 @@ const workflowMemoryPrompt = {
|
|
|
3734
4080
|
};
|
|
3735
4081
|
if (!result.available)
|
|
3736
4082
|
result.diagnosis = { code: body?.code, status: body?.status, fail: body?.fail, dataEmpty: !promptText };
|
|
3737
|
-
// oaContext:OA 版本 + 当前用户身份(岗位/部门/分部)。原 Python
|
|
3738
|
-
// 提供;CLI 侧等价信息由会话自身承载,无需额外请求。
|
|
4083
|
+
// oaContext:OA 版本 + 当前用户身份(岗位/部门/分部)。原 Python 由旧登录链路
|
|
4084
|
+
// 的 whoami 提供;CLI 侧等价信息由会话自身承载,无需额外请求。
|
|
3739
4085
|
const session = await env.host.getSession();
|
|
3740
4086
|
const user = {};
|
|
3741
4087
|
const userCandidates = [
|
|
@@ -3755,7 +4101,14 @@ const workflowMemoryPrompt = {
|
|
|
3755
4101
|
oaContext.version = version;
|
|
3756
4102
|
if (Object.keys(user).length)
|
|
3757
4103
|
oaContext.user = user;
|
|
3758
|
-
|
|
4104
|
+
// ★ 当前环境是否支持「智能分组」查询 —— 由 CLI 按 OA 版本判定(与 todoStat / planJson 的门禁同源)。
|
|
4105
|
+
// 模型直接按它分流即可:true ⇒ 先 todoStat 统计再带 planJson 取数;false ⇒ 直接 workflow.search,
|
|
4106
|
+
// CLI 自身输出分组统计与卡片。**不必自己比版本号**(比法容易出错),也**不需要"先空跑一次
|
|
4107
|
+
// workflow.search 看模式"**。版本未知(会话取不到)⇒ 按不支持处理。
|
|
4108
|
+
oaContext.smartGrouping = Boolean(version)
|
|
4109
|
+
&& isVersionAtLeast(oaVersionTuple(version), SMART_GROUPING_MIN_OA_VERSION);
|
|
4110
|
+
// 注意:判据只看「有没有版本/身份」——smartGrouping 恒有值,不能让它把 oaContextMissing 变成永远 false。
|
|
4111
|
+
const hasOaContext = Boolean(version) || Object.keys(user).length > 0;
|
|
3759
4112
|
result.oaContext = hasOaContext ? oaContext : null;
|
|
3760
4113
|
result.oaContextMissing = !hasOaContext;
|
|
3761
4114
|
if (!hasOaContext) {
|
|
@@ -3776,3 +4129,31 @@ export const workflowReadOperations = [
|
|
|
3776
4129
|
workflowPageOpen,
|
|
3777
4130
|
workflowMemoryPrompt,
|
|
3778
4131
|
];
|
|
4132
|
+
/**
|
|
4133
|
+
* 供**批量范围驱动**复用:该大分类的「全部」子分类 tabid(todo=0 / done=50 / mine=100 / all=300)。
|
|
4134
|
+
*
|
|
4135
|
+
* 为什么批量范围驱动需要它:legacy(低版本)模式下 `workflow.search` 对未显式指定子分类的 `todo`
|
|
4136
|
+
* 默认查「待处理(7)」(见 `buildSearchQuery` 步骤 2.5,V11.2 语义)。而**批量过滤列表端点**
|
|
4137
|
+
* (`…/list/data/getTodoListData/batch/batch<X>`)在 tabid=7 下只返待处理 ⇒ 当范围里全是
|
|
4138
|
+
* 抄送/传阅/转发类流程时**恒 0 条**,模型会把"取不到"报成"范围内没有数据"(2026-09-20 真实事故)。
|
|
4139
|
+
* 范围驱动的语义是「该 scope 下的全部流程,再由 `batchType` 过滤可批量操作性」,故未指定子分类时
|
|
4140
|
+
* 显式取「全部」;scope 显式给了 `subCategory`/`tabName`/`tabid` 则原样尊重。
|
|
4141
|
+
*/
|
|
4142
|
+
export function defaultTabIdOf(category) {
|
|
4143
|
+
return DEFAULT_TABID[category] ?? DEFAULT_TABID.todo;
|
|
4144
|
+
}
|
|
4145
|
+
/**
|
|
4146
|
+
* 供**批量操作的范围驱动**复用:以与 `workflow.search` **完全相同的口径**取数(返回 `data`)。
|
|
4147
|
+
*
|
|
4148
|
+
* 为什么要复用同一个 operation,而不是在批量里另写一条取数路径:批量门禁的放行依据是
|
|
4149
|
+
* 「最近一次查询结果」的快照(`search-index.json`,由 search 内部**无条件**刷新)——批量自己
|
|
4150
|
+
* 另写一条取数路径就会造成"查一个口径、门禁另一个口径"的分叉(V12 早期真实事故:模型按文档
|
|
4151
|
+
* 查出来的 requestId 全被门禁判成"不在最近一次查询结果里",只能反复重查反复试)。
|
|
4152
|
+
*
|
|
4153
|
+
* 调用方(`write.ts` 的 `runScopeDrivenBatch`)只消费 `total` 与 `cards[].requestId`;超线时
|
|
4154
|
+
* `cards` 会被体积守卫移出 stdout,调用方需回退到刚刷新的索引快照取 ID。
|
|
4155
|
+
*/
|
|
4156
|
+
export async function runWorkflowSearch(args, env) {
|
|
4157
|
+
const envelope = await workflowSearch.handler(args, env);
|
|
4158
|
+
return (envelope?.data ?? {});
|
|
4159
|
+
}
|