weaver-work-cli 0.1.8 → 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 -589
- package/dist/shortcuts/workflow/operations/read.js +128 -205
- package/dist/shortcuts/workflow/operations/shared.js +21 -0
- package/dist/shortcuts/workflow/operations/write.js +750 -52
- 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 +32 -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 +39 -75
- 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 +38 -20
- package/skills/weaver-e10-workflow/references/workflow-share.md +18 -2
- package/skills/weaver-e10-workflow/references/workflow-view.md +3 -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-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 +89 -9
- 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, detailDataOf, 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
|
];
|
|
@@ -1480,7 +1517,6 @@ const workflowSearch = {
|
|
|
1480
1517
|
pageSize: SCHEMA_PAGE_SIZE,
|
|
1481
1518
|
conditionType: { type: 'string', enum: ['and', 'or'], default: 'and' },
|
|
1482
1519
|
totalOnly: { type: 'boolean', default: false },
|
|
1483
|
-
mode: { type: 'string', enum: ['auto', 'legacy', 'smart'], default: 'auto', description: '执行模式强制覆盖,仅回归验证用;默认按 OA 版本自动路由' },
|
|
1484
1520
|
batchType: {
|
|
1485
1521
|
type: 'string',
|
|
1486
1522
|
enum: ['submit', 'urge', 'read', 'share', 'forward', 'reject'],
|
|
@@ -1494,16 +1530,18 @@ const workflowSearch = {
|
|
|
1494
1530
|
if (plan && typeof plan === 'object') {
|
|
1495
1531
|
// plan 批量编排仅智能分组模式支持(来源:Python cmd_workflow_search_plan 首行的
|
|
1496
1532
|
// resolve_workflow_search_mode 门禁)——该模式下 todoStat 的统计口径才成立;低版本 OA 上
|
|
1497
|
-
//
|
|
1498
|
-
|
|
1533
|
+
// 直接拒绝,并引导改用单条查询,避免静默按错口径分组。
|
|
1534
|
+
// 门禁与 memoryPrompt 的 oaContext.smartGrouping 同源(同一常量 + 同一比较函数),并且**没有
|
|
1535
|
+
// 强制覆盖开关**:模型拿到的布尔值就是这里的判据,不会出现「模型以为能用、这里却拒绝」的错位。
|
|
1536
|
+
const planMode = await resolveSearchMode(env);
|
|
1499
1537
|
if (planMode !== 'smart') {
|
|
1500
|
-
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 });
|
|
1501
1539
|
}
|
|
1502
1540
|
// 顶层 pageSize 即 plan 的展示窗口 page_cap(来源:Python cmd_workflow_search_plan 的 page_cap)
|
|
1503
1541
|
return executePlanSearch(env, plan, asInt(args.pageSize) ?? 10);
|
|
1504
1542
|
}
|
|
1505
1543
|
const category = (asString(args.category) || 'todo');
|
|
1506
|
-
const searchMode = await resolveSearchMode(env
|
|
1544
|
+
const searchMode = await resolveSearchMode(env);
|
|
1507
1545
|
const batchType = asString(args.batchType);
|
|
1508
1546
|
const totalOnly = asBool(args.totalOnly, false);
|
|
1509
1547
|
// legacy 待办查询走 CLI 侧分组(V11.2 语义;来源:Python execute_workflow_search_legacy 的
|
|
@@ -1576,7 +1614,6 @@ const workflowSearch = {
|
|
|
1576
1614
|
if (totalOnly) {
|
|
1577
1615
|
return workflowSuccess('workflow.search', {
|
|
1578
1616
|
type: 'workflowSearch',
|
|
1579
|
-
renderMode: searchMode,
|
|
1580
1617
|
category,
|
|
1581
1618
|
categoryName: CATEGORY_NAMES[category],
|
|
1582
1619
|
total,
|
|
@@ -1588,7 +1625,6 @@ const workflowSearch = {
|
|
|
1588
1625
|
const index = await saveListIndex(env, normalized);
|
|
1589
1626
|
return workflowSuccess('workflow.search', await enforceSearchStdoutBudget({
|
|
1590
1627
|
type: 'workflowSearch',
|
|
1591
|
-
renderMode: searchMode,
|
|
1592
1628
|
category,
|
|
1593
1629
|
categoryName: CATEGORY_NAMES[category],
|
|
1594
1630
|
total,
|
|
@@ -1629,11 +1665,13 @@ function isVersionAtLeast(actual, min) {
|
|
|
1629
1665
|
}
|
|
1630
1666
|
return true;
|
|
1631
1667
|
}
|
|
1632
|
-
/**
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1668
|
+
/**
|
|
1669
|
+
* 解析查询执行模式:版本 ≥ 基线走 smart;版本未知/运行时不可用 → legacy 兜底。
|
|
1670
|
+
* **不接受任何参数强制覆盖**——与 `memoryPrompt` 的 `oaContext.smartGrouping` 完全同源(同一常量 +
|
|
1671
|
+
* 同一比较函数),这样模型读到的布尔值与服务端门禁不可能漂移。
|
|
1672
|
+
* (09-21 用户裁定移除 `mode` 强制开关:它只是临时调试用,暴露出去反而会诱导模型「试一试」。)
|
|
1673
|
+
*/
|
|
1674
|
+
async function resolveSearchMode(env) {
|
|
1637
1675
|
try {
|
|
1638
1676
|
const session = await env.host.getSession();
|
|
1639
1677
|
const version = asString(session.version) ?? '';
|
|
@@ -2037,7 +2075,6 @@ async function executeLegacyGroupedTodoSearch(env, args) {
|
|
|
2037
2075
|
.join('\n\n');
|
|
2038
2076
|
return workflowSuccess('workflow.search', await enforceSearchStdoutBudget({
|
|
2039
2077
|
type: 'workflowSearch',
|
|
2040
|
-
renderMode: 'legacy',
|
|
2041
2078
|
category: 'todo',
|
|
2042
2079
|
categoryName: CATEGORY_NAMES.todo,
|
|
2043
2080
|
displayMode: 'groupedCards',
|
|
@@ -2883,9 +2920,9 @@ const workflowTodoStat = {
|
|
|
2883
2920
|
// 来源:Python 无独立 todoStat operation,只有 cmd_workflow_search_plan 的 smart 门禁 +
|
|
2884
2921
|
// execute_workflow_search_legacy 的分组输出——低版本待办统计由 workflow.search 的 legacy 分组输出承担。
|
|
2885
2922
|
// 低版本直接拒绝,避免模型拿到"全部(0)"口径统计(与 legacy"待处理(7)"口径矛盾)后按错误口径规划分组。
|
|
2886
|
-
const mode = await resolveSearchMode(env
|
|
2923
|
+
const mode = await resolveSearchMode(env);
|
|
2887
2924
|
if (mode !== 'smart') {
|
|
2888
|
-
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 });
|
|
2889
2926
|
}
|
|
2890
2927
|
const { query } = await buildSearchQuery(env, { ...args, category: 'todo' }, 'stat');
|
|
2891
2928
|
const response = await env.host.workflowRequest({
|
|
@@ -2983,7 +3020,7 @@ const workflowDetail = {
|
|
|
2983
3020
|
'表单内容区只展示核心业务字段,跳过空值、技术字段、可编辑、操作按钮、岗位、部门、最后反馈时间等非必要字段',
|
|
2984
3021
|
'表单字段值必须原样展示 value,不能摘要、改写、删减;字段含 valueHtml/links 时优先渲染为可点击超链接,不要把长 URL 直接作为普通文本换行展示',
|
|
2985
3022
|
'displayMode=longTextBlock 或 fullWidth=true 的表单字段必须全宽展示,保留原文、标点和换行;不要保留接口富文本里的背景色、字号和大段空白',
|
|
2986
|
-
'
|
|
3023
|
+
'签字意见是查看流程内容的固定组成部分,必须以意见块展示,不要压成表格或和表单字段混在一起;每条按两行展示(操作人/方式/时间、签字意见内容)',
|
|
2987
3024
|
'签字意见区标题写成「签字意见({{paginationText}})」;只有 requestLog.hasMore=true 时才显示 nextPagePrompt',
|
|
2988
3025
|
'不要展示 requestId;不要展示 workflowName,用户明确询问所属工作流时才展示为「工作流名称」;不要翻译、音译或改写接口返回文本',
|
|
2989
3026
|
'详情面板后不要输出固定建议列表或「如果想进一步操作」类尾巴;用户要求摘要/总结时改为围绕表单业务内容提炼,不要渲染详情表',
|
|
@@ -3043,7 +3080,7 @@ function normalizeDetailResponse(response) {
|
|
|
3043
3080
|
warnings.push('接口未返回该流程的任何详情内容(流程标题/编号/发起人/发起时间/当前节点/表单字段全为空)。'
|
|
3044
3081
|
+ '这不代表"流程是空的",也不代表流程一定不存在——最可能是这个 requestId 在本环境查不到对应流程'
|
|
3045
3082
|
+ '(编号抄错、来自其他环境、已被删除)。'
|
|
3046
|
-
+ '请先用 `workflow.search`
|
|
3083
|
+
+ '请先用 `workflow.search` 在本环境确认该流程可见、并直接从那一次的'
|
|
3047
3084
|
+ '`cards[].requestId` 取值,再重试;若确认流程存在却仍取不到,请如实告知用户"详情不可见",'
|
|
3048
3085
|
+ '不要编造流程内容。');
|
|
3049
3086
|
}
|
|
@@ -3204,19 +3241,14 @@ function extractFormFields(data) {
|
|
|
3204
3241
|
// V12 早期版本把"递归"退化成"枚举 4 条固定路径"(`data.list` / `data.records` …),而
|
|
3205
3242
|
// `getRequestLog` 的真实响应是 `{message:{errcode,errmsg}, data:[…]}` —— **`data` 直接是数组**
|
|
3206
3243
|
// ⇒ 四条路径全 miss ⇒ 签字意见恒为空。真机探针实测同一份响应:V11.3 解出 8 条、V12 解出 0 条。
|
|
3207
|
-
// 归一化侧的 `display_text_from_value` 一族(对象取值 /
|
|
3208
|
-
//
|
|
3244
|
+
// 归一化侧的 `display_text_from_value` 一族(对象取值 / 时间戳格式化)V12 同样缺失 ⇒ 一并补齐。
|
|
3245
|
+
// (接收人解析链已于 2026-09-20 按要求整体删除,不再取 `receivedPersonIds` / `userDataMap`。)
|
|
3209
3246
|
// ---------------------------------------------------------------------------
|
|
3210
3247
|
/** 展示文本候选键(来源:V11.3 `DISPLAY_TEXT_KEYS`)。 */
|
|
3211
3248
|
const LOG_DISPLAY_TEXT_KEYS = [
|
|
3212
3249
|
'showValue', 'displayValue', 'name', 'showName', 'lastName', 'lastname', 'userName',
|
|
3213
3250
|
'username', 'label', 'title', 'text', 'content', 'value',
|
|
3214
3251
|
];
|
|
3215
|
-
/** 用户 ID 候选键(来源:V11.3 `USER_ID_KEYS`)。 */
|
|
3216
|
-
const LOG_USER_ID_KEYS = [
|
|
3217
|
-
'id', 'userId', 'userid', 'uid', 'resourceId', 'resourceid', 'masterId', 'masterid',
|
|
3218
|
-
'identityId', 'identityid',
|
|
3219
|
-
];
|
|
3220
3252
|
/** 日志容器候选键(来源:V11.3 `collect_detail_logs_from_obj`)。 */
|
|
3221
3253
|
const LOG_CONTAINER_KEYS = [
|
|
3222
3254
|
'requestLogList', 'requestLogs', 'logs', 'logList', 'operatorList', 'signList', 'remarkList',
|
|
@@ -3253,13 +3285,6 @@ const LOG_OPINION_KEYS = [
|
|
|
3253
3285
|
'opinion', 'remark', 'remarks', 'signRemark', 'signremark', 'content', 'comment', 'message',
|
|
3254
3286
|
'requestLogContent', 'commentContent', 'commentcontent',
|
|
3255
3287
|
];
|
|
3256
|
-
const LOG_RECEIVER_ID_KEYS = [
|
|
3257
|
-
'receivedPersonIds', 'receivedpersonids', 'receivedPersonId', 'receivePersonIds', 'receiverIds',
|
|
3258
|
-
'recipientIds',
|
|
3259
|
-
];
|
|
3260
|
-
const LOG_RECEIVER_TEXT_KEYS = [
|
|
3261
|
-
'receivedPersons', 'receivedPersonNames', 'receiverNames', 'receivers', 'recipients',
|
|
3262
|
-
];
|
|
3263
3288
|
/** 宽松取键:先精确匹配、再忽略大小写(来源:V11.3 `pick_raw`)。 */
|
|
3264
3289
|
function pickRawLoose(source, ...keys) {
|
|
3265
3290
|
if (!source || typeof source !== 'object' || Array.isArray(source))
|
|
@@ -3301,8 +3326,8 @@ function displayTextFromValue(value, depth = 0) {
|
|
|
3301
3326
|
const record = value;
|
|
3302
3327
|
if (Object.keys(record).length === 0)
|
|
3303
3328
|
return '';
|
|
3304
|
-
// ★ 必须走 `pickRawLoose
|
|
3305
|
-
//
|
|
3329
|
+
// ★ 必须走 `pickRawLoose`(大小写不敏感):实测服务端回的对象键常与候选键大小写不一致
|
|
3330
|
+
// (如 `UserName` 对上候选键 `userName`)—— 用 `in` 精确匹配会取不到值。
|
|
3306
3331
|
for (const key of LOG_DISPLAY_TEXT_KEYS) {
|
|
3307
3332
|
const raw = pickRawLoose(record, key);
|
|
3308
3333
|
if (raw === undefined)
|
|
@@ -3332,125 +3357,6 @@ function pickDisplayText(source, ...keys) {
|
|
|
3332
3357
|
}
|
|
3333
3358
|
return '';
|
|
3334
3359
|
}
|
|
3335
|
-
/** 值 → 去重 ID 列表(来源:V11.3 `value_to_id_list`,ID 串按 `,,;;空白` 切分)。 */
|
|
3336
|
-
function valueToIdList(value) {
|
|
3337
|
-
const ids = [];
|
|
3338
|
-
const pushText = (text) => {
|
|
3339
|
-
for (const part of stripHtml(text).split(/[,,;;\s]+/u)) {
|
|
3340
|
-
if (part)
|
|
3341
|
-
ids.push(part);
|
|
3342
|
-
}
|
|
3343
|
-
};
|
|
3344
|
-
const push = (input) => {
|
|
3345
|
-
if (input === null || input === undefined)
|
|
3346
|
-
return;
|
|
3347
|
-
if (Array.isArray(input)) {
|
|
3348
|
-
input.forEach(push);
|
|
3349
|
-
return;
|
|
3350
|
-
}
|
|
3351
|
-
if (typeof input === 'object') {
|
|
3352
|
-
const record = input;
|
|
3353
|
-
for (const key of [...LOG_USER_ID_KEYS, 'value']) {
|
|
3354
|
-
const raw = pickRawLoose(record, key);
|
|
3355
|
-
if (raw !== null && raw !== undefined && raw !== '') {
|
|
3356
|
-
push(raw);
|
|
3357
|
-
return;
|
|
3358
|
-
}
|
|
3359
|
-
}
|
|
3360
|
-
return;
|
|
3361
|
-
}
|
|
3362
|
-
pushText(String(input));
|
|
3363
|
-
};
|
|
3364
|
-
push(value);
|
|
3365
|
-
return [...new Set(ids)];
|
|
3366
|
-
}
|
|
3367
|
-
/** 用户映射里按 id 查姓名(来源:V11.3 `user_name_from_map`)。 */
|
|
3368
|
-
function userNameFromMap(map, userId) {
|
|
3369
|
-
if (!map || !userId)
|
|
3370
|
-
return '';
|
|
3371
|
-
const direct = displayTextFromValue(map[userId]);
|
|
3372
|
-
if (direct)
|
|
3373
|
-
return direct;
|
|
3374
|
-
for (const [key, candidate] of Object.entries(map)) {
|
|
3375
|
-
if (String(key) === String(userId)) {
|
|
3376
|
-
const text = displayTextFromValue(candidate);
|
|
3377
|
-
if (text)
|
|
3378
|
-
return text;
|
|
3379
|
-
}
|
|
3380
|
-
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate))
|
|
3381
|
-
continue;
|
|
3382
|
-
const candidateIds = LOG_USER_ID_KEYS.flatMap((idKey) => valueToIdList(pickRawLoose(candidate, idKey)));
|
|
3383
|
-
if (candidateIds.some((id) => String(id) === String(userId))) {
|
|
3384
|
-
const text = displayTextFromValue(candidate);
|
|
3385
|
-
if (text)
|
|
3386
|
-
return text;
|
|
3387
|
-
}
|
|
3388
|
-
}
|
|
3389
|
-
return '';
|
|
3390
|
-
}
|
|
3391
|
-
/**
|
|
3392
|
-
* 收集 `userDataMap` 并建 `id → 用户对象` 索引
|
|
3393
|
-
* (来源:V11.3 `extract_user_data_map` / `normalize_user_data_map` / `add_user_map_item`)。
|
|
3394
|
-
*
|
|
3395
|
-
* 真机实测:`getRequestLog` 的 `userDataMap`(`[{UserName, userId}, …]`,90+ 条)挂在**每条日志项内部**
|
|
3396
|
-
* 而不是响应顶层 ⇒ 除了遍历响应,还要逐项收一遍,否则接收人只能显示裸 ID。
|
|
3397
|
-
*/
|
|
3398
|
-
function collectUserDataMap(source) {
|
|
3399
|
-
const mapped = {};
|
|
3400
|
-
const addItem = (item, fallbackKey) => {
|
|
3401
|
-
if (fallbackKey !== undefined && fallbackKey !== '')
|
|
3402
|
-
mapped[String(fallbackKey)] = item;
|
|
3403
|
-
if (!item || typeof item !== 'object' || Array.isArray(item))
|
|
3404
|
-
return;
|
|
3405
|
-
const record = item;
|
|
3406
|
-
for (const key of LOG_USER_ID_KEYS) {
|
|
3407
|
-
const rawId = pickRawLoose(record, key);
|
|
3408
|
-
if (rawId !== null && rawId !== undefined && rawId !== '')
|
|
3409
|
-
mapped[String(rawId)] = item;
|
|
3410
|
-
}
|
|
3411
|
-
for (const nestedKey of ['user', 'userInfo', 'userBaseInfo', 'resource', 'resourceInfo', 'hrmResource', 'person', 'identity']) {
|
|
3412
|
-
const nested = record[nestedKey];
|
|
3413
|
-
if (nested && typeof nested === 'object' && !Array.isArray(nested))
|
|
3414
|
-
addItem(nested);
|
|
3415
|
-
}
|
|
3416
|
-
};
|
|
3417
|
-
const addMapLike = (value) => {
|
|
3418
|
-
if (Array.isArray(value)) {
|
|
3419
|
-
value.forEach((item) => addItem(item));
|
|
3420
|
-
return;
|
|
3421
|
-
}
|
|
3422
|
-
if (value && typeof value === 'object') {
|
|
3423
|
-
for (const [key, item] of Object.entries(value)) {
|
|
3424
|
-
if (Array.isArray(item))
|
|
3425
|
-
item.forEach((nested) => addItem(nested));
|
|
3426
|
-
else
|
|
3427
|
-
addItem(item, key);
|
|
3428
|
-
}
|
|
3429
|
-
}
|
|
3430
|
-
};
|
|
3431
|
-
const walk = (value, depth = 0) => {
|
|
3432
|
-
if (!value || typeof value !== 'object' || depth > 2)
|
|
3433
|
-
return;
|
|
3434
|
-
if (Array.isArray(value)) {
|
|
3435
|
-
value.forEach((item) => {
|
|
3436
|
-
if (item && typeof item === 'object') {
|
|
3437
|
-
const direct = pickRawLoose(item, 'userDataMap', 'userdataMap', 'userMap', 'userInfoMap', 'resourceDataMap');
|
|
3438
|
-
if (direct !== undefined)
|
|
3439
|
-
addMapLike(direct);
|
|
3440
|
-
}
|
|
3441
|
-
});
|
|
3442
|
-
return;
|
|
3443
|
-
}
|
|
3444
|
-
const record = value;
|
|
3445
|
-
const direct = pickRawLoose(record, 'userDataMap', 'userdataMap', 'userMap', 'userInfoMap', 'resourceDataMap');
|
|
3446
|
-
if (direct !== undefined)
|
|
3447
|
-
addMapLike(direct);
|
|
3448
|
-
for (const nestedKey of ['data', 'result', 'payload'])
|
|
3449
|
-
walk(record[nestedKey], depth + 1);
|
|
3450
|
-
};
|
|
3451
|
-
walk(source);
|
|
3452
|
-
return mapped;
|
|
3453
|
-
}
|
|
3454
3360
|
/** 数字串 → 本地可读时间(`YYYY-MM-DD HH:mm:ss`)。 */
|
|
3455
3361
|
function formatLogTimestamp(ms) {
|
|
3456
3362
|
const date = new Date(ms);
|
|
@@ -3479,26 +3385,18 @@ function stripWeaverMteTokens(text) {
|
|
|
3479
3385
|
return text;
|
|
3480
3386
|
return text.replace(/_weaverMte_\S+/gu, ' ').replace(/\s+/gu, ' ').trim();
|
|
3481
3387
|
}
|
|
3482
|
-
/** 接收人(ID 列表 + 显示串)。来源:V11.3 `resolve_received_persons`。 */
|
|
3483
|
-
function resolveReceivedPersons(item, userDataMap) {
|
|
3484
|
-
const ids = valueToIdList(pickRawLoose(item, ...LOG_RECEIVER_ID_KEYS));
|
|
3485
|
-
let display = ids.map((id) => userNameFromMap(userDataMap, id) || id).join('、');
|
|
3486
|
-
if (!display)
|
|
3487
|
-
display = pickDisplayText(item, ...LOG_RECEIVER_TEXT_KEYS);
|
|
3488
|
-
return { ids, display };
|
|
3489
|
-
}
|
|
3490
3388
|
/**
|
|
3491
3389
|
* 递归收集日志条目(来源:V11.3 `collect_detail_logs_from_obj`)。
|
|
3492
3390
|
*
|
|
3493
3391
|
* ★ 关键在"递归":`data` 可能是数组、也可能是对象里某个键下面挂着数组。V12 早期只枚举四条固定路径,
|
|
3494
3392
|
* 遇到 `getRequestLog` 的"`data` 是数组"形态就整条归零。
|
|
3495
3393
|
*/
|
|
3496
|
-
function collectDetailLogsFromObj(obj, logs,
|
|
3394
|
+
function collectDetailLogsFromObj(obj, logs, limit) {
|
|
3497
3395
|
if (logs.length >= limit)
|
|
3498
3396
|
return;
|
|
3499
3397
|
if (Array.isArray(obj)) {
|
|
3500
3398
|
for (const item of obj) {
|
|
3501
|
-
collectDetailLogsFromObj(item, logs,
|
|
3399
|
+
collectDetailLogsFromObj(item, logs, limit);
|
|
3502
3400
|
if (logs.length >= limit)
|
|
3503
3401
|
break;
|
|
3504
3402
|
}
|
|
@@ -3507,19 +3405,19 @@ function collectDetailLogsFromObj(obj, logs, userDataMap, limit) {
|
|
|
3507
3405
|
if (!obj || typeof obj !== 'object')
|
|
3508
3406
|
return;
|
|
3509
3407
|
const record = obj;
|
|
3510
|
-
const normalized = normalizeLogItem(record
|
|
3408
|
+
const normalized = normalizeLogItem(record);
|
|
3511
3409
|
if (detailLogHasContent(normalized))
|
|
3512
3410
|
logs.push(normalized);
|
|
3513
3411
|
for (const key of LOG_CONTAINER_KEYS) {
|
|
3514
3412
|
if (key in record)
|
|
3515
|
-
collectDetailLogsFromObj(record[key], logs,
|
|
3413
|
+
collectDetailLogsFromObj(record[key], logs, limit);
|
|
3516
3414
|
}
|
|
3517
3415
|
}
|
|
3518
3416
|
/**
|
|
3519
3417
|
* 从接口响应里取出签字意见条目(来源:V11.3 `extract_request_log_items`)。
|
|
3520
3418
|
*
|
|
3521
3419
|
* 取 `response.data` 作为源(为空则回退整个 response),递归收集后按
|
|
3522
|
-
* `node/operator/time/action/
|
|
3420
|
+
* `node/operator/time/action/opinion` 五元组去重。
|
|
3523
3421
|
*/
|
|
3524
3422
|
function extractRequestLogItems(response, limit = 500) {
|
|
3525
3423
|
const wrapper = response && typeof response === 'object' && !Array.isArray(response)
|
|
@@ -3531,13 +3429,12 @@ function extractRequestLogItems(response, limit = 500) {
|
|
|
3531
3429
|
|| (typeof source === 'object' && !Array.isArray(source) && Object.keys(source).length === 0);
|
|
3532
3430
|
if (emptySource)
|
|
3533
3431
|
source = response;
|
|
3534
|
-
const userDataMap = collectUserDataMap(response);
|
|
3535
3432
|
const logs = [];
|
|
3536
|
-
collectDetailLogsFromObj(source, logs,
|
|
3433
|
+
collectDetailLogsFromObj(source, logs, limit);
|
|
3537
3434
|
const seen = new Set();
|
|
3538
3435
|
const unique = [];
|
|
3539
3436
|
for (const log of logs) {
|
|
3540
|
-
const key = [log.node, log.operator, log.time, log.action, log.
|
|
3437
|
+
const key = [log.node, log.operator, log.time, log.action, log.opinion]
|
|
3541
3438
|
.map((part) => String(part ?? ''))
|
|
3542
3439
|
.join('\u0001');
|
|
3543
3440
|
if (seen.has(key))
|
|
@@ -3554,7 +3451,6 @@ function extractRequestLogItems(response, limit = 500) {
|
|
|
3554
3451
|
function extractRequestLogs(data) {
|
|
3555
3452
|
if (!data || typeof data !== 'object')
|
|
3556
3453
|
return [];
|
|
3557
|
-
const userDataMap = collectUserDataMap(data);
|
|
3558
3454
|
const logs = [];
|
|
3559
3455
|
for (const [key, value] of Object.entries(data)) {
|
|
3560
3456
|
if (!LOG_CONTAINER_KEY_PATTERN.test(key))
|
|
@@ -3563,12 +3459,12 @@ function extractRequestLogs(data) {
|
|
|
3563
3459
|
continue;
|
|
3564
3460
|
if (Array.isArray(value) && value.length === 0)
|
|
3565
3461
|
continue;
|
|
3566
|
-
collectDetailLogsFromObj(value, logs,
|
|
3462
|
+
collectDetailLogsFromObj(value, logs, 40);
|
|
3567
3463
|
}
|
|
3568
3464
|
return logs;
|
|
3569
3465
|
}
|
|
3570
3466
|
function detailLogHasContent(log) {
|
|
3571
|
-
return Boolean(log && (log.node || log.operator || log.action || log.time || log.opinion
|
|
3467
|
+
return Boolean(log && (log.node || log.operator || log.action || log.time || log.opinion));
|
|
3572
3468
|
}
|
|
3573
3469
|
// ---------------------------------------------------------------------------
|
|
3574
3470
|
// 签字意见归一化(来源:normalize_detail_log_item / fetch_request_log_page)
|
|
@@ -3656,17 +3552,17 @@ async function fetchRequestLogPage(env, requestId, current, pageSize) {
|
|
|
3656
3552
|
/**
|
|
3657
3553
|
* 签字意见单条归一化(来源:V11.3 `normalize_detail_log_item`)。
|
|
3658
3554
|
*
|
|
3659
|
-
*
|
|
3555
|
+
* 两处「V11.3 有、V12 缺」的处理,缺一个就会显示成空白或乱码:
|
|
3660
3556
|
* ① **操作人取 `commentor`** —— 真机实测该字段是**对象**(`{id, name:'孙一一', username}`),
|
|
3661
3557
|
* 必须走 `displayTextFromValue`;旧写法用 `pickText` 只会把对象 `String()` 成 `[object Object]`。
|
|
3662
3558
|
* ② **时间走 `normalizeLogTimeText`** —— 接口给的是 **毫秒时间戳**(`1743082059000`)。
|
|
3663
|
-
*
|
|
3664
|
-
*
|
|
3559
|
+
*
|
|
3560
|
+
* 接收人(`receivedPersonIds` + `userDataMap` 解析)已按要求整体移除:解析链(`resolveReceivedPersons` /
|
|
3561
|
+
* `userNameFromMap` / `collectUserDataMap` / `valueToIdList` 与 `LOG_RECEIVER_*_KEYS`)一并删除。
|
|
3665
3562
|
*/
|
|
3666
|
-
function normalizeLogItem(item
|
|
3563
|
+
function normalizeLogItem(item) {
|
|
3667
3564
|
if (!item || typeof item !== 'object')
|
|
3668
3565
|
return {};
|
|
3669
|
-
const mergedMap = { ...userDataMap, ...collectUserDataMap(item) };
|
|
3670
3566
|
let node = pickDisplayText(item, ...LOG_NODE_KEYS);
|
|
3671
3567
|
if (/^\d{12,}$/u.test(stripHtml(node)))
|
|
3672
3568
|
node = '';
|
|
@@ -3675,7 +3571,6 @@ function normalizeLogItem(item, userDataMap = {}) {
|
|
|
3675
3571
|
const time = normalizeLogTimeText(pickDisplayText(item, ...LOG_TIME_KEYS));
|
|
3676
3572
|
const action = pickDisplayText(item, ...LOG_ACTION_KEYS);
|
|
3677
3573
|
const opinion = stripWeaverMteTokens(pickDisplayText(item, ...LOG_OPINION_KEYS));
|
|
3678
|
-
const receivers = resolveReceivedPersons(item, mergedMap);
|
|
3679
3574
|
const operatorDisplay = operator || '未返回';
|
|
3680
3575
|
const actionDisplay = action || '未返回';
|
|
3681
3576
|
const timeDisplay = time || '未返回';
|
|
@@ -3690,17 +3585,13 @@ function normalizeLogItem(item, userDataMap = {}) {
|
|
|
3690
3585
|
actionDisplay,
|
|
3691
3586
|
operatorAction: `${operatorDisplay} · ${actionDisplay}`,
|
|
3692
3587
|
operatorActionTime: `${operatorDisplay} · ${actionDisplay} · ${timeDisplay}`,
|
|
3693
|
-
receiverIds: receivers.ids,
|
|
3694
|
-
receivers: receivers.display,
|
|
3695
|
-
receiverDisplay: receivers.display || '未返回',
|
|
3696
3588
|
opinion,
|
|
3697
3589
|
opinionDisplay,
|
|
3698
3590
|
displayLines: {
|
|
3699
3591
|
line1: `${operatorDisplay} · ${actionDisplay} · ${timeDisplay}`,
|
|
3700
|
-
line2: `接收人:${receivers.display || '未返回'}`,
|
|
3701
3592
|
line3: opinionDisplay,
|
|
3702
3593
|
},
|
|
3703
|
-
displayLayout: '
|
|
3594
|
+
displayLayout: 'twoLineSignatureOpinion',
|
|
3704
3595
|
};
|
|
3705
3596
|
}
|
|
3706
3597
|
/** 详情字段取值:有 links 时优先渲染为可点击超链接(来源:Python detail_field_markdown_value) */
|
|
@@ -3766,7 +3657,6 @@ function renderDetailMarkdown(normalized) {
|
|
|
3766
3657
|
for (const it of logItems) {
|
|
3767
3658
|
const dl = it.displayLines || {};
|
|
3768
3659
|
let line1 = asString(dl.line1) ?? '';
|
|
3769
|
-
let line2 = asString(dl.line2) ?? '';
|
|
3770
3660
|
let line3 = asString(dl.line3) ?? '';
|
|
3771
3661
|
if (!line1) {
|
|
3772
3662
|
const operator = asString(it.operatorDisplay) ?? '未返回';
|
|
@@ -3774,13 +3664,11 @@ function renderDetailMarkdown(normalized) {
|
|
|
3774
3664
|
const timeText = asString(it.timeDisplay) ?? asString(it.createdAt) ?? '未返回';
|
|
3775
3665
|
line1 = `${operator} · ${action} · ${timeText}`;
|
|
3776
3666
|
}
|
|
3777
|
-
if (!line2)
|
|
3778
|
-
line2 = `接收人:${asString(it.receiverDisplay) ?? '未返回'}`;
|
|
3779
3667
|
if (!line3) {
|
|
3780
3668
|
const opinion = asString(it.opinionDisplay) ?? asString(it.contentDisplay) ?? asString(it.content) ?? '未填写意见';
|
|
3781
3669
|
line3 = `签字意见:${opinion}`;
|
|
3782
3670
|
}
|
|
3783
|
-
lines.push(`> **${line1}**`, '>', `> ${
|
|
3671
|
+
lines.push(`> **${line1}**`, '>', `> ${line3}`, '>');
|
|
3784
3672
|
}
|
|
3785
3673
|
if (lines[lines.length - 1] === '>')
|
|
3786
3674
|
lines.pop();
|
|
@@ -4192,8 +4080,8 @@ const workflowMemoryPrompt = {
|
|
|
4192
4080
|
};
|
|
4193
4081
|
if (!result.available)
|
|
4194
4082
|
result.diagnosis = { code: body?.code, status: body?.status, fail: body?.fail, dataEmpty: !promptText };
|
|
4195
|
-
// oaContext:OA 版本 + 当前用户身份(岗位/部门/分部)。原 Python
|
|
4196
|
-
// 提供;CLI 侧等价信息由会话自身承载,无需额外请求。
|
|
4083
|
+
// oaContext:OA 版本 + 当前用户身份(岗位/部门/分部)。原 Python 由旧登录链路
|
|
4084
|
+
// 的 whoami 提供;CLI 侧等价信息由会话自身承载,无需额外请求。
|
|
4197
4085
|
const session = await env.host.getSession();
|
|
4198
4086
|
const user = {};
|
|
4199
4087
|
const userCandidates = [
|
|
@@ -4213,7 +4101,14 @@ const workflowMemoryPrompt = {
|
|
|
4213
4101
|
oaContext.version = version;
|
|
4214
4102
|
if (Object.keys(user).length)
|
|
4215
4103
|
oaContext.user = user;
|
|
4216
|
-
|
|
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;
|
|
4217
4112
|
result.oaContext = hasOaContext ? oaContext : null;
|
|
4218
4113
|
result.oaContextMissing = !hasOaContext;
|
|
4219
4114
|
if (!hasOaContext) {
|
|
@@ -4234,3 +4129,31 @@ export const workflowReadOperations = [
|
|
|
4234
4129
|
workflowPageOpen,
|
|
4235
4130
|
workflowMemoryPrompt,
|
|
4236
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
|
+
}
|
|
@@ -65,6 +65,27 @@ export function asStringArray(value, field) {
|
|
|
65
65
|
}
|
|
66
66
|
return value.map((item) => String(item));
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* 解析 `名称(ID)` / 纯 ID / 纯名称 → `{name, id}`。ID 必须是**纯数字**才认定为 ID。
|
|
70
|
+
*
|
|
71
|
+
* 查询侧(`read.ts` 的系统字段名称→ID)与发起侧(`form.ts` 的可发起工作流解析)共用同一
|
|
72
|
+
* 解析口径:澄清候选回传的就是 `名称(ID)`,两边若各写一份,一旦正则口径分叉就会出现
|
|
73
|
+
* 「查询侧认、发起侧不认」这类只有真机才暴露的错位。
|
|
74
|
+
*/
|
|
75
|
+
export function parseNameWithId(value) {
|
|
76
|
+
const text = (value || '').trim();
|
|
77
|
+
if (!text)
|
|
78
|
+
return { name: '' };
|
|
79
|
+
const match = /^(.+?)[((]([^()()]*)[))]\s*$/.exec(text);
|
|
80
|
+
if (match) {
|
|
81
|
+
const name = match[1].trim();
|
|
82
|
+
const idPart = match[2].trim();
|
|
83
|
+
if (name && idPart && /^[0-9]+$/.test(idPart)) {
|
|
84
|
+
return { name, id: idPart };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { name: text };
|
|
88
|
+
}
|
|
68
89
|
/** 请求 flag 是否开启(支持布尔与 0/1、"true"/"false")。 */
|
|
69
90
|
export function asBool(value, fallback = false) {
|
|
70
91
|
if (value === undefined || value === null || value === '')
|