weaver-work-cli 0.1.33 → 0.1.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{archive-FVFY6GZN.js → archive-PDV724YK.js} +5 -4
- package/dist/chunks/{archive-oa-NW3OD4UV.js → archive-oa-4XE4MNXQ.js} +4 -3
- package/dist/chunks/{asset-7QJKMIDV.js → asset-PDL2QE2N.js} +8 -7
- package/dist/chunks/{attachment-content-OSAVA6K3.js → attachment-content-MDDGOE5E.js} +3 -3
- package/dist/chunks/{blog-I7SDPNW4.js → blog-UYR3EUXW.js} +5 -4
- package/dist/chunks/{caizhilian-O37ZIQNY.js → caizhilian-LG2X5NMF.js} +5 -4
- package/dist/chunks/{calendar-7KXE4ED3.js → calendar-FG4BAZHE.js} +4 -3
- package/dist/chunks/{chunk-Q6LWB5HX.js → chunk-6FTTSA45.js} +4 -3
- package/dist/chunks/{chunk-MQ2I5AA3.js → chunk-6LTFIFLM.js} +3 -2
- package/dist/chunks/{chunk-WRRJ4GG2.js → chunk-CORFRD7Q.js} +4 -3
- package/dist/chunks/{chunk-WY74UBJ6.js → chunk-DMV2TOMH.js} +2194 -240
- package/dist/chunks/{chunk-MEEFGWTM.js → chunk-ELYDWBZJ.js} +135 -10
- package/dist/chunks/{chunk-EEWPQE3O.js → chunk-FUWVHMVV.js} +7 -6
- package/dist/chunks/{chunk-HEL5XTYI.js → chunk-GGJFMNJM.js} +26 -4
- package/dist/chunks/{chunk-KAS45KNR.js → chunk-IZUDA353.js} +1 -1
- package/dist/chunks/{chunk-2FLWXFVS.js → chunk-J7USS3ED.js} +4 -4
- package/dist/chunks/{chunk-IHUYP6LO.js → chunk-JSMFPO43.js} +1 -1
- package/dist/chunks/{chunk-RBGOMK4Y.js → chunk-KKRL3REV.js} +5 -4
- package/dist/chunks/{chunk-OCU4RLHC.js → chunk-NI6GIPIM.js} +195 -36
- package/dist/chunks/{chunk-PRHJGPMA.js → chunk-NR6RKXPA.js} +2 -2
- package/dist/chunks/{chunk-D223PTXU.js → chunk-OJQUJZQF.js} +33 -7
- package/dist/chunks/{chunk-ILMBNVSG.js → chunk-UJJD4QWF.js} +4 -3
- package/dist/chunks/{chunk-6ATGWRZM.js → chunk-UZZBA6PV.js} +177 -14
- package/dist/chunks/{chunk-GFPXQIY2.js → chunk-VRYHGG6O.js} +2 -2
- package/dist/chunks/{chunk-NHA62QD7.js → chunk-XJY7G4DD.js} +3 -3
- package/dist/chunks/{chunk-VBCFFFGT.js → chunk-Z32KWY6G.js} +4 -1
- package/dist/chunks/{conflict-SFNEVJY5.js → conflict-BQZEQ4SP.js} +2 -2
- package/dist/chunks/contract-XIRAFTEV.js +9111 -0
- package/dist/chunks/{cowork-CKWYHXYB.js → cowork-BLG62BL2.js} +46 -26
- package/dist/chunks/{ebuilder-form-KOLJRVZD.js → ebuilder-form-NUKG5QW4.js} +3 -3
- package/dist/chunks/{ehr-WFOC3B4D.js → ehr-2STW5ILH.js} +174 -10
- package/dist/chunks/{esb-LJZTN663.js → esb-UUDQUNKY.js} +4 -4
- package/dist/chunks/{fna-F7QRIIZC.js → fna-6RCY4NU5.js} +15 -9
- package/dist/chunks/{host-VQ6JJXGQ.js → host-7RXEDMAH.js} +3 -3
- package/dist/chunks/{host-KXPTEWFS.js → host-EJZFROU3.js} +3 -3
- package/dist/chunks/{host-BCGUVEWX.js → host-FWDOSRH5.js} +3 -3
- package/dist/chunks/{host-DGE2TZD4.js → host-RHM3NPXV.js} +3 -3
- package/dist/chunks/{host-BQBRHVR3.js → host-UFF33TWJ.js} +3 -3
- package/dist/chunks/{hrm-NI6XAKW5.js → hrm-PKJ6ELSH.js} +3 -3
- package/dist/chunks/{im-7TRTF3KT.js → im-YJAXS7CO.js} +3 -3
- package/dist/chunks/{invoice-AOXWJKYT.js → invoice-HWVSXGX6.js} +3 -3
- package/dist/chunks/{jiuchuanhui-7NQWEZAW.js → jiuchuanhui-L7VKILZB.js} +4 -3
- package/dist/chunks/{lazy-dispatch-RNS4EE4S.js → lazy-dispatch-NMYEIHRH.js} +4 -4
- package/dist/chunks/{mail-VDHJAB4O.js → mail-B42RRU7G.js} +46 -24
- package/dist/chunks/{meeting-GA7XCNNP.js → meeting-CFYVBJSS.js} +77 -12
- package/dist/chunks/{okr-TQIRERPQ.js → okr-GCLF6V7W.js} +4 -3
- package/dist/chunks/{plan-4RY4LN2P.js → plan-ZVL7GBRI.js} +4 -3
- package/dist/chunks/{project-D6XT2OC5.js → project-4YWAAE2K.js} +4 -3
- package/dist/chunks/{registry-QKMI43Q6.js → registry-EP4VTHZS.js} +8 -8
- package/dist/chunks/{registry-JHRIDQ3A.js → registry-FRJ6H7BT.js} +4 -4
- package/dist/chunks/{registry-VOGKFFTR.js → registry-SG2PGVCF.js} +2 -2
- package/dist/chunks/{room-recommend-D4EUOOWY.js → room-recommend-R3JVUUAC.js} +4 -4
- package/dist/chunks/{schedule-XPFBHPCV.js → schedule-NIJBTYDO.js} +4 -4
- package/dist/chunks/{workflow-HLBZRWIY.js → workflow-BB24DFBY.js} +11 -4
- package/dist/index.js +31 -26
- package/dist/internal/e10/auth-recovery.js +114 -1
- package/dist/internal/e10/client.js +16 -2
- package/dist/internal/e10/fetch-timing.js +31 -0
- package/dist/internal/e10/request-runtime.js +108 -7
- package/dist/shortcuts/archive/host.js +3 -2
- package/dist/shortcuts/archive-oa/host.js +2 -1
- package/dist/shortcuts/asset/host.js +6 -5
- package/dist/shortcuts/blog/host.js +3 -2
- package/dist/shortcuts/caizhilian/host.js +3 -2
- package/dist/shortcuts/calendar/host.js +2 -1
- package/dist/shortcuts/contract/host.js +2 -1
- package/dist/shortcuts/cowork/host.js +10 -1
- package/dist/shortcuts/cowork/operations/types.js +17 -2
- package/dist/shortcuts/ebuilder-form/host.js +3 -1
- package/dist/shortcuts/ehr/host.js +2 -1
- package/dist/shortcuts/ehr/operations/recruit-write.js +35 -0
- package/dist/shortcuts/ehr/operations/recruit.js +147 -0
- package/dist/shortcuts/esb/host.js +2 -1
- package/dist/shortcuts/fna/host.js +2 -1
- package/dist/shortcuts/fna/operations/expense-chart.js +19 -7
- package/dist/shortcuts/hrm/host.js +2 -1
- package/dist/shortcuts/im/host.js +3 -2
- package/dist/shortcuts/index.js +5 -0
- package/dist/shortcuts/invoice/host.js +5 -4
- package/dist/shortcuts/jiuchuanhui/host.js +2 -1
- package/dist/shortcuts/mail/host.js +10 -1
- package/dist/shortcuts/mail/operations/types.js +17 -2
- package/dist/shortcuts/meeting/host.js +3 -2
- package/dist/shortcuts/meeting/index.js +79 -1
- package/dist/shortcuts/meeting/invitation/text.js +71 -0
- package/dist/shortcuts/meeting/manifest.js +10 -0
- package/dist/shortcuts/meeting/operations/eb/catalog.js +5 -0
- package/dist/shortcuts/meeting/operations/eb/conflict.js +6 -1
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +2105 -169
- package/dist/shortcuts/meeting/operations/eb/crm-search.js +478 -0
- package/dist/shortcuts/meeting/operations/eb/factory.js +161 -8
- package/dist/shortcuts/meeting/operations/eb/index.js +2 -0
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +36 -2
- package/dist/shortcuts/meeting/operations/eb-fallback.js +4 -0
- package/dist/shortcuts/meeting/operations/env.js +6 -0
- package/dist/shortcuts/meeting/operations/invitation.js +200 -28
- package/dist/shortcuts/okr/host.js +2 -1
- package/dist/shortcuts/plan/host.js +2 -1
- package/dist/shortcuts/project/host.js +2 -1
- package/dist/shortcuts/workflow/endpoints.js +8 -3
- package/dist/shortcuts/workflow/operations/form.js +341 -36
- package/dist/shortcuts/workflow/operations.js +23 -1
- package/docs/ehr.md +1 -1
- package/docs/fna.md +2 -1
- package/package.json +1 -1
- package/skill-template/business-info.json +18 -0
- package/skill-template/domains/cowork.md +14 -0
- package/skills/weaver-e10-cowork/SKILL.md +101 -0
- package/skills/weaver-e10-cowork/references/cowork-read.md +90 -0
- package/skills/weaver-e10-cowork/references/cowork-write.md +131 -0
- package/skills/weaver-e10-cowork/references/safety-boundaries.md +45 -0
- package/skills/weaver-e10-jucailin/SKILL.md +21 -3
- package/skills/weaver-e10-jucailin/references/recruit-read.md +25 -0
- package/skills/weaver-e10-jucailin/references/recruit-write.md +13 -0
- package/skills/weaver-e10-jucailin/references/salary.md +15 -11
- package/skills/weaver-e10-mail/SKILL.md +12 -9
- package/skills/weaver-e10-meeting/SKILL.md +499 -300
- package/skills/weaver-e10-meeting/references/meeting-eb.md +334 -0
- package/skills/weaver-e10-meeting/references/meeting-invitation.md +64 -6
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
- package/skills/weaver-e10-qiyecheng/SKILL.md +5 -2
- package/skills/weaver-e10-qiyecheng/references/catalog.md +2 -2
- package/skills/weaver-e10-qiyecheng/references/expense-chart.md +15 -1
- package/skills/weaver-e10-qiyecheng/references/source-manifest.json +22 -21
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
|
@@ -26,6 +26,7 @@ import { continuationReplayError } from '../continuation.js';
|
|
|
26
26
|
import { writeStateFileBestEffort } from '../state.js';
|
|
27
27
|
import { buildHistoryRequestData, fetchHistoryListSnapshot, filterHistoryRequestData, pickUniqueMineWorkflow, prefetchHistoryDetails } from './read.js';
|
|
28
28
|
import { hasXiaoEEnv } from '../../../internal/e10/auth/xiaoe.js';
|
|
29
|
+
import { createFetchTimingSink, runWithFetchTiming } from '../../../internal/e10/fetch-timing.js';
|
|
29
30
|
import { buildChatSceneInput, formatLocalNow, requestFormFill } from './chatscene.js';
|
|
30
31
|
// ---------------------------------------------------------------------------
|
|
31
32
|
// 常量(与 Python 源逐条对齐)
|
|
@@ -592,7 +593,14 @@ function extractFields(layoutData, requiredMap, disableEditMap, displayMap) {
|
|
|
592
593
|
// (Python 的 `bool({})` 为 False)——直接照抄 Python 的 `bool(browser_type)` 会让**每个字段**
|
|
593
594
|
// 都被判成浏览按钮字段,文本/富文本字段随之走 name→ID 解析并报 BROWSER NOT_FOUND,
|
|
594
595
|
// 内容永远写不进去(2026-09-16「内部留言」留言内容字段实证)。必须判"非空对象"。
|
|
595
|
-
|
|
596
|
+
// 组件名有两种写法:eBuilder 表单里常带 `Component` 后缀(`CustomerComponent` /
|
|
597
|
+
// `EmployeeComponent`),标准流程表单里是不带的(`customer` / `employee`)。
|
|
598
|
+
// ⛔ 只认前者会漏判:2026-10-31 会议申请流程的「参会客户」是 `CustomerComponent`,
|
|
599
|
+
// 被当成普通文本字段 ⇒ 提交 `[{id,name}]` 报「文本字段只接受字符串、数字或布尔值」。
|
|
600
|
+
const componentBase = normalized.replace(/component$/, '');
|
|
601
|
+
const isBrowser = Object.keys(browserType).length > 0
|
|
602
|
+
|| BROWSER_COMPONENTS.has(normalized)
|
|
603
|
+
|| BROWSER_COMPONENTS.has(componentBase);
|
|
596
604
|
const isSelect = SELECT_COMPONENTS.has(normalized) || options.length > 0;
|
|
597
605
|
result.push({
|
|
598
606
|
id: fieldId,
|
|
@@ -1485,7 +1493,23 @@ async function resolveCreateWorkflow(rawWorkflow, env) {
|
|
|
1485
1493
|
// 澄清文案里回显的是**用户原话**(不是切分后的关键词)——用户要能认出自己给的条件
|
|
1486
1494
|
const queryText = rawTerms.join('、');
|
|
1487
1495
|
if (!rawTerms.length) {
|
|
1488
|
-
|
|
1496
|
+
// ★ 2026-10-30 用户裁定:这一档不是「名称没匹配上」,而是**必填入参 `workflow` 根本没给**
|
|
1497
|
+
// (缺键 / 空数组 / 空字符串都算)。原先与「只给了填充词」共用同一句澄清,模型看到的是
|
|
1498
|
+
// 「请提供完整名称」,于是键名写错(如写成 `workflows`、`name`)时不知道自己漏了 `workflow`,
|
|
1499
|
+
// 只会回头把锅甩给用户。现在单独标 `clarificationReason=workflow_required` 并下发
|
|
1500
|
+
// `missingInput` + `requiredInput`,让模型知道「是我少传了入参、要按这个形态补」。
|
|
1501
|
+
throw workflowClarificationError('workflow', '', '要发起哪个流程?请提供该流程的完整名称(或其它可用于检索的关键词)后再继续。', [], {
|
|
1502
|
+
clarificationReason: 'workflow_required',
|
|
1503
|
+
missingInput: ['workflow'],
|
|
1504
|
+
requiredInput: {
|
|
1505
|
+
workflow: {
|
|
1506
|
+
required: true,
|
|
1507
|
+
type: 'string | string[]',
|
|
1508
|
+
description: '要发起的工作流:完整名称、名称关键词,或「名称(ID)」;纯数字按工作流 ID 处理。',
|
|
1509
|
+
example: ['费用报销'],
|
|
1510
|
+
},
|
|
1511
|
+
},
|
|
1512
|
+
});
|
|
1489
1513
|
}
|
|
1490
1514
|
// 先做**纯本地**的解析:显式 ID(`名称(ID)` / 纯数字)与名称关键词(一格可含多个,见
|
|
1491
1515
|
// `splitCreateKeywords`)。这一步不依赖远端结果,于是能先判"到底有没有可用条件"——
|
|
@@ -1751,9 +1775,9 @@ function singleExplicitWorkflowId(rawWorkflow) {
|
|
|
1751
1775
|
return parsed.id ?? (/^[0-9]+$/.test(terms[0]) ? terms[0] : '');
|
|
1752
1776
|
}
|
|
1753
1777
|
/** 表单载入 + 该工作流的记忆:两者只共享 `workflowId`,可以并行。 */
|
|
1754
|
-
async function loadCreateFormPack(workflowId, env) {
|
|
1778
|
+
async function loadCreateFormPack(workflowId, env, options = {}) {
|
|
1755
1779
|
const [{ data }, workflowMemory] = await Promise.all([
|
|
1756
|
-
buildCreateFormData(workflowId, env),
|
|
1780
|
+
buildCreateFormData(workflowId, env, options),
|
|
1757
1781
|
// 记忆读取是 best-effort(内部全 catch),不会把表单载入的错误顶掉。
|
|
1758
1782
|
fetchWorkflowMemory(workflowId, env),
|
|
1759
1783
|
]);
|
|
@@ -1839,7 +1863,7 @@ async function resolveIntentFill(pack, userQuestion, baseUrl, env) {
|
|
|
1839
1863
|
* 现在返回的就是纯取数结果:`fields` + `historyRequestData` + `systemFields` + 记忆,
|
|
1840
1864
|
* 由模型自己拼 `data` 后交给 `createRequest`。
|
|
1841
1865
|
*/
|
|
1842
|
-
async function prepareCreateFormPayload(args, env) {
|
|
1866
|
+
async function prepareCreateFormPayload(args, env, options = {}) {
|
|
1843
1867
|
// 会话是**惰性**加载的(`ensureSession` 只在首次调用时填充)。本命令随后会并发发起多个请求,
|
|
1844
1868
|
// 先在这里显式取一次,避免并发首调各自走一遍会话初始化。
|
|
1845
1869
|
//
|
|
@@ -1858,11 +1882,11 @@ async function prepareCreateFormPayload(args, env) {
|
|
|
1858
1882
|
// `commonParam.authInfo` 给出;该 ID 不可用时由载入表单一侧的 `readCreateFormBlock`
|
|
1859
1883
|
// 抛出**可判别**的澄清(不是把它伪装成"没有匹配到可发起的工作流")。
|
|
1860
1884
|
target = { id: explicitId, name: '', source: 'workflowId' };
|
|
1861
|
-
pack = await loadCreateFormPack(explicitId, env);
|
|
1885
|
+
pack = await loadCreateFormPack(explicitId, env, options);
|
|
1862
1886
|
}
|
|
1863
1887
|
else {
|
|
1864
1888
|
target = await resolveCreateWorkflow(args.workflow, env);
|
|
1865
|
-
pack = await loadCreateFormPack(target.id, env);
|
|
1889
|
+
pack = await loadCreateFormPack(target.id, env, options);
|
|
1866
1890
|
}
|
|
1867
1891
|
// ★ 2026-10-15 用户裁定:`createForm` 改用**字符口径 20,000** 的**严格档**(见
|
|
1868
1892
|
// `CREATE_FORM_STDOUT_SAFE_CHARS` / `compactFormOutput` 的 `charBudget`)。
|
|
@@ -1954,7 +1978,21 @@ async function commandCreateForm(args, env) {
|
|
|
1954
1978
|
* 与旧版的差别只有一处:`workflowId` 由调用方(`resolveCreateWorkflow`)解析好后传进来,
|
|
1955
1979
|
* 不再是本函数的入参校验点。
|
|
1956
1980
|
*/
|
|
1957
|
-
async function buildCreateFormData(workflowId, env) {
|
|
1981
|
+
async function buildCreateFormData(workflowId, env, options = {}) {
|
|
1982
|
+
/**
|
|
1983
|
+
* 明细字段的标记形态(2026-10-30 用户裁定):
|
|
1984
|
+
* - 缺省 `subFormId` —— `createForm` 口径,明细字段带 `subFormId`(`data.details` 的键);
|
|
1985
|
+
* - `isDetail` —— `createRequestDirect` 独有的口径,明细字段带 `isDetail: true`、不再下发 `subFormId`。
|
|
1986
|
+
*/
|
|
1987
|
+
const detailMark = options.detailMark === 'isDetail' ? 'isDetail' : 'subFormId';
|
|
1988
|
+
/**
|
|
1989
|
+
* ★ 2026-10-30 用户裁定:`getHistory` 是 `createRequestDirect` 的**隐藏入参**(只在 CLI 实现层支持,
|
|
1990
|
+
* 不进 `inputSchema`、不进 skill)。传 `false` 时「历史发起流程」这一段整块不做 ——
|
|
1991
|
+
* 不发 `getMineListData`(列表)、不发明细,也不要组装;输出里 `historyRequestData` 直接给 `null`。
|
|
1992
|
+
*
|
|
1993
|
+
* 缺省(含 `createForm` 这一侧,它从不传)一律按 `true`,行为与从前一字不差。
|
|
1994
|
+
*/
|
|
1995
|
+
const getHistory = options.getHistory !== false;
|
|
1958
1996
|
const query = { workflowId, isCreate: 1, ...AJAX_QUERY };
|
|
1959
1997
|
const body = { workflowId, isCreate: 1, isMobile: 0 };
|
|
1960
1998
|
/**
|
|
@@ -1965,7 +2003,9 @@ async function buildCreateFormData(workflowId, env) {
|
|
|
1965
2003
|
* `.catch(() => null)` 是必须的:这个 Promise 可能因为表单后续抛错而**没人 await**,
|
|
1966
2004
|
* 兜住之后就不会留下未处理的拒绝;真要暴露的认证/网络错误由同批的载入请求抛出,不会被吞掉。
|
|
1967
2005
|
*/
|
|
1968
|
-
const historyListSnapshot =
|
|
2006
|
+
const historyListSnapshot = getHistory
|
|
2007
|
+
? fetchHistoryListSnapshot(env, { workflowId }).catch(() => null)
|
|
2008
|
+
: Promise.resolve(null);
|
|
1969
2009
|
/**
|
|
1970
2010
|
* 明细请求紧跟着**列表**走(2026-10-29 用户口径):它的入参只有 `requestId`(列表给的)与会话
|
|
1971
2011
|
* `userId`,与 `getFormLayout` **没有任何参数依赖**;原先排在 layout 之后纯属写法问题,白等约
|
|
@@ -1976,7 +2016,9 @@ async function buildCreateFormData(workflowId, env) {
|
|
|
1976
2016
|
* 空处理免得留下未处理的拒绝。真正的认证/网络错误仍由 `buildHistoryRequestData` 里那次 `await`
|
|
1977
2017
|
* 决定去留(口径不变)。
|
|
1978
2018
|
*/
|
|
1979
|
-
const historyDetails =
|
|
2019
|
+
const historyDetails = getHistory
|
|
2020
|
+
? historyListSnapshot.then(async (snapshot) => (snapshot && snapshot.requestIds.length ? prefetchHistoryDetails(env, snapshot) : null))
|
|
2021
|
+
: Promise.resolve(null);
|
|
1980
2022
|
historyDetails.catch(() => { });
|
|
1981
2023
|
// 2026-09-22:这四个接口**彼此没有数据依赖**(入参只需要 workflowId,见各自的 query/body),
|
|
1982
2024
|
// 真机实测各 171–429 ms、串行合计约 1.1 s;并行后只受最慢的一个约束。
|
|
@@ -2105,6 +2147,31 @@ async function buildCreateFormData(workflowId, env) {
|
|
|
2105
2147
|
continue;
|
|
2106
2148
|
visibleFields.push(f);
|
|
2107
2149
|
}
|
|
2150
|
+
/**
|
|
2151
|
+
* 明细表**整表被判不可见**时的显式告警(2026-10-31)。
|
|
2152
|
+
*
|
|
2153
|
+
* 场景:明细表被摆在了页面网格的**隐藏行 / 隐藏列**上(`pageLayouts[0].config.workbook.Sheet1.rows[n].visible=false`),
|
|
2154
|
+
* 于是它的列字段全部被 `hiddenCellIds` 过滤掉。此前是**静默丢弃** —— 模型只看到"表单里没有这张明细表",
|
|
2155
|
+
* 分不清「表单确实没配」和「配了但摆在隐藏行上」,只能回一句"没有这张表",用户无从下手。
|
|
2156
|
+
* 这里按 subFormId 汇总,整表字段全灭的才告警(部分列隐藏属正常设计)。
|
|
2157
|
+
*/
|
|
2158
|
+
const hiddenBySubform = new Map();
|
|
2159
|
+
for (const f of fields) {
|
|
2160
|
+
const fid = String(f.id ?? '');
|
|
2161
|
+
const sid = String(f.subFormId ?? '').trim();
|
|
2162
|
+
if (!sid || !hiddenCellIds.has(fid))
|
|
2163
|
+
continue;
|
|
2164
|
+
hiddenBySubform.set(sid, (hiddenBySubform.get(sid) || 0) + 1);
|
|
2165
|
+
}
|
|
2166
|
+
const survivingSubforms = new Set(visibleFields.map((f) => String(f.subFormId ?? '').trim()).filter(Boolean));
|
|
2167
|
+
const invisibleSubformWarnings = [];
|
|
2168
|
+
for (const [sid, count] of hiddenBySubform) {
|
|
2169
|
+
if (survivingSubforms.has(sid))
|
|
2170
|
+
continue;
|
|
2171
|
+
invisibleSubformWarnings.push(`明细表 ${sid} 的 ${count} 个字段全部被判为「不可见」(该明细表所在的行/列在表单布局里是隐藏的),`
|
|
2172
|
+
+ '因此没有出现在字段清单里。要么在表单设计器里把明细表挪到可见行,要么取消隐藏它所在的行/列,然后重新载入表单。');
|
|
2173
|
+
}
|
|
2174
|
+
let layoutWarning = invisibleSubformWarnings.length ? invisibleSubformWarnings.join(';') : null;
|
|
2108
2175
|
const defaults = normalizeDefaults(layoutData.defaultDetails);
|
|
2109
2176
|
// 计算必填/可编辑/明细必填(按默认值 + onLoadForm 联动)
|
|
2110
2177
|
const systemFieldValues = _normalizeSystemFieldValues(layoutData.businessOperatorObj);
|
|
@@ -2169,6 +2236,52 @@ async function buildCreateFormData(workflowId, env) {
|
|
|
2169
2236
|
}
|
|
2170
2237
|
}
|
|
2171
2238
|
const folderOptionValues = _mainValueMap(defaults);
|
|
2239
|
+
/**
|
|
2240
|
+
* 「条件显示」的明细表(2026-10-31,会议申请流程「参会客户明细」实证)
|
|
2241
|
+
*
|
|
2242
|
+
* 表单可以给明细表配**显示联动**:触发字段有值时 `show`、为空时 `hideLine`。
|
|
2243
|
+
* 会议申请流程的「参会客户明细」(subFormId `1332370499762806784`)就是这样 ——
|
|
2244
|
+
* 触发字段是「参会客户」(`crm_members`),空着时整表隐藏。
|
|
2245
|
+
*
|
|
2246
|
+
* 旧行为把它与「明细表被摆在隐藏行」一视同仁 ⇒ 整表字段从 `fields` 里消失,
|
|
2247
|
+
* 建会链路既解析不到明细表也写不进明细,模型只能回「表单里没有这张表」。
|
|
2248
|
+
* 现在:布局上确实存在、且有成对 `show` 规则的表,按**条件可见**处理 ——
|
|
2249
|
+
* 字段照常下发(标 `conditional: true`),并在 `layoutWarning` 里说明触发字段。
|
|
2250
|
+
*/
|
|
2251
|
+
const conditionalHiddenSubforms = new Set();
|
|
2252
|
+
const conditionalMainFields = new Set();
|
|
2253
|
+
const conditionalHints = collectDetailShowLinkages(layoutData.viewLinkages, fields);
|
|
2254
|
+
/**
|
|
2255
|
+
* 主表字段同理:「参会客户」(`crm_members`) 自己也有成对的 `show` / `hideLine` 规则
|
|
2256
|
+
* (空值时连自己都隐藏)。若不这样处理,建会链路的 `crm_members` 永远映射不到字段。
|
|
2257
|
+
*/
|
|
2258
|
+
const fieldShowIds = collectFieldShowLinkageIds(layoutData.viewLinkages);
|
|
2259
|
+
for (const fid of Array.from(mainHidden)) {
|
|
2260
|
+
if (!fieldShowIds.has(fid))
|
|
2261
|
+
continue;
|
|
2262
|
+
if (hiddenCellIds.has(fid))
|
|
2263
|
+
continue;
|
|
2264
|
+
const target = visibleFields.find((f) => String(f.id ?? '') === fid);
|
|
2265
|
+
if (!target)
|
|
2266
|
+
continue;
|
|
2267
|
+
conditionalMainFields.add(fid);
|
|
2268
|
+
mainHidden.delete(fid);
|
|
2269
|
+
invisibleSubformWarnings.push(`字段「${target.name}」(${fid}) 因显示联动隐藏(该字段本身是触发字段),已照常下发并标 conditional: true`);
|
|
2270
|
+
}
|
|
2271
|
+
for (const sid of Array.from(hiddenSubforms)) {
|
|
2272
|
+
const hint = conditionalHints.get(sid);
|
|
2273
|
+
if (!hint)
|
|
2274
|
+
continue;
|
|
2275
|
+
const usable = visibleFields.some((f) => String(f.subFormId ?? '') === sid && !hiddenCellIds.has(String(f.id ?? '')));
|
|
2276
|
+
if (!usable)
|
|
2277
|
+
continue;
|
|
2278
|
+
conditionalHiddenSubforms.add(sid);
|
|
2279
|
+
hiddenSubforms.delete(sid);
|
|
2280
|
+
invisibleSubformWarnings.push(`明细表 ${sid}${hint.tableName ? `(${hint.tableName})` : ''} 因显示联动隐藏,触发字段:`
|
|
2281
|
+
+ `${hint.triggerNames.join('、') || hint.triggerIds.join('、')}。`
|
|
2282
|
+
+ '该表字段已照常下发(标 conditional: true),把触发字段填上后表单里就会显示;不要回「表单里没有这张明细表」。');
|
|
2283
|
+
}
|
|
2284
|
+
layoutWarning = invisibleSubformWarnings.length ? invisibleSubformWarnings.join(';') : null;
|
|
2172
2285
|
for (const field of visibleFields) {
|
|
2173
2286
|
const fieldId = String(field.id ?? '');
|
|
2174
2287
|
const subFormId = String(field.subFormId ?? '');
|
|
@@ -2254,12 +2367,25 @@ async function buildCreateFormData(workflowId, env) {
|
|
|
2254
2367
|
// 不在当前布局上的字段不下发——模型既填不了也看不到。
|
|
2255
2368
|
if (!modelFieldIds.has(String(field.id ?? '')))
|
|
2256
2369
|
continue;
|
|
2257
|
-
//
|
|
2258
|
-
//
|
|
2370
|
+
// 明细标记**按命令分档**(2026-10-30 用户裁定:改成 isDetail 只对 `createRequestDirect` 生效):
|
|
2371
|
+
// - `createRequestDirect`:键序 `id|name|isDetail|componentKey`,明细字段带 `isDetail: true`,
|
|
2372
|
+
// 主表字段**整个省略**该键(缺键=主表;恒写 `isDetail:false` 白占体积),且**不下发** `subFormId`;
|
|
2373
|
+
// - `createForm`(默认档):维持原口径,明细字段带 `subFormId`(它就是 `data.details` 的键),
|
|
2374
|
+
// 主表字段省略该键。
|
|
2259
2375
|
const slimSubFormId = String(field.subFormId ?? '');
|
|
2260
2376
|
const item = { id: field.id, name: field.name };
|
|
2261
|
-
if (
|
|
2377
|
+
if (detailMark === 'isDetail') {
|
|
2378
|
+
if (slimSubFormId)
|
|
2379
|
+
item.isDetail = true;
|
|
2380
|
+
}
|
|
2381
|
+
else if (slimSubFormId) {
|
|
2262
2382
|
item.subFormId = slimSubFormId;
|
|
2383
|
+
}
|
|
2384
|
+
// 条件可见的明细表(见 conditionalHiddenSubforms 注释):字段可用,但当前触发字段没值时表单里不显示。
|
|
2385
|
+
if (slimSubFormId && conditionalHiddenSubforms.has(slimSubFormId))
|
|
2386
|
+
item.conditional = true;
|
|
2387
|
+
if (!slimSubFormId && conditionalMainFields.has(String(field.id ?? '')))
|
|
2388
|
+
item.conditional = true;
|
|
2263
2389
|
item.componentKey = field.componentKey;
|
|
2264
2390
|
// 表单初始化时就有的值(OA 预填 + onLoadForm 联动带出)——有才出现该键;**没有默认值的字段不带**。
|
|
2265
2391
|
// CLI 拼 `data` 时会自动并入,模型**不必显式传**;用户本次明确改写了它才按用户说的覆盖(见
|
|
@@ -2335,7 +2461,7 @@ async function buildCreateFormData(workflowId, env) {
|
|
|
2335
2461
|
...requiredFieldsList.map((f) => String(f?.id ?? '')),
|
|
2336
2462
|
...Object.values(subFormRequired).flatMap((list) => list.map((f) => String(f?.id ?? ''))),
|
|
2337
2463
|
].filter(Boolean))];
|
|
2338
|
-
const historyCache = fields.length
|
|
2464
|
+
const historyCache = getHistory && fields.length
|
|
2339
2465
|
? await buildHistoryRequestData(env, fields, historyRequiredFieldIds.map((id) => ({ id, name: String((fieldsById.get(id) || {}).name ?? id) })), { allFormFields: true, workflowId },
|
|
2340
2466
|
// 本命令开头就已并发发起的列表请求:这里只补「详情」那几步(P2)。
|
|
2341
2467
|
historyListSnapshot,
|
|
@@ -2407,7 +2533,9 @@ async function buildCreateFormData(workflowId, env) {
|
|
|
2407
2533
|
// 历史参考值(近一年、非草稿、最近 5 条「我发起的」)——填值前先看它,能直接照用的字段就别去
|
|
2408
2534
|
// 问用户。字段范围与 `fields` **同一口径**(见 modelFieldIds 注释);完整结果(本表单全部字段)
|
|
2409
2535
|
// 仍随句柄留给 `createRequest` 用(条件必填要到那边才现身)。
|
|
2410
|
-
|
|
2536
|
+
// `getHistory:false`(隐藏入参)时整段历史取数没做 ⇒ 这里给 `null`(不是空数组:空数组表示
|
|
2537
|
+
// "查了但没数据",两者含义不同)。
|
|
2538
|
+
historyRequestData: getHistory ? filterHistoryRequestData(historyCache, modelFieldIds) : null,
|
|
2411
2539
|
systemFields: {
|
|
2412
2540
|
permissions: minorInfo.permissions || {},
|
|
2413
2541
|
defaults: context.systemFields.defaults,
|
|
@@ -2416,6 +2544,8 @@ async function buildCreateFormData(workflowId, env) {
|
|
|
2416
2544
|
},
|
|
2417
2545
|
systemFieldWarning: minorWarning,
|
|
2418
2546
|
requiredInfoWarning: requiredInfoWarningText(context),
|
|
2547
|
+
// 明细表整表不可见时的告警(见 layoutWarning 注释):有值时**不要**回"表单里没有这张明细表"。
|
|
2548
|
+
layoutWarning,
|
|
2419
2549
|
// 原 Python 把完整 context 落盘;此处通过 continuation 承载,返回结构保持文档契约。
|
|
2420
2550
|
continuation: token,
|
|
2421
2551
|
},
|
|
@@ -3821,9 +3951,11 @@ async function runFieldLinkage(env, ctx, entries, actionTypes, allDetails, isEdi
|
|
|
3821
3951
|
}
|
|
3822
3952
|
if (!dataList.length && !runDataList.length)
|
|
3823
3953
|
return [];
|
|
3954
|
+
// 建模表单的联动接口 `module` 是 `ebuilderform`(与布局 / 保存的 `ebuildercard` 不同,真机请求体实证)。
|
|
3955
|
+
const linkageModule = formModuleName(ctx.frame && ctx.frame.relate_type, 'ebuilderform');
|
|
3824
3956
|
const executeParam = {
|
|
3825
3957
|
formId: ctx.layout.form_id,
|
|
3826
|
-
module:
|
|
3958
|
+
module: linkageModule,
|
|
3827
3959
|
dataList,
|
|
3828
3960
|
runDataList,
|
|
3829
3961
|
moduleDataList: [],
|
|
@@ -3867,9 +3999,10 @@ async function runFieldLinkage(env, ctx, entries, actionTypes, allDetails, isEdi
|
|
|
3867
3999
|
if (frame.layout_multi_id)
|
|
3868
4000
|
executeParam.layoutId = frame.layout_multi_id;
|
|
3869
4001
|
}
|
|
3870
|
-
const payload = { formId: ctx.layout.form_id, module:
|
|
4002
|
+
const payload = { formId: ctx.layout.form_id, module: linkageModule, executeParam };
|
|
3871
4003
|
const relateType = (ctx.frame && ctx.frame.relate_type) || '';
|
|
3872
|
-
|
|
4004
|
+
// 建模表单的联动接口在 `/api/ebuilder/form/` 下,与布局 / 保存的 `/api/ebuilder/flow/` 不同(真机实证)。
|
|
4005
|
+
const endpoint = formApiUrl(FORM_ENDPOINTS.fdLinkage, relateType, '/api/ebuilder/form/');
|
|
3873
4006
|
const response = await requestJson(env, 'POST', endpoint, AJAX_QUERY, payload);
|
|
3874
4007
|
// requestJson 内部已解包一层(responseData),此处不可再取 .data
|
|
3875
4008
|
const data = (response && typeof response === 'object') ? response : null;
|
|
@@ -3914,6 +4047,74 @@ function mergeLinkageDetails(merged, linkageDetails) {
|
|
|
3914
4047
|
function emptyLinkageVisibility() {
|
|
3915
4048
|
return [new Set(), {}, {}, {}];
|
|
3916
4049
|
}
|
|
4050
|
+
/**
|
|
4051
|
+
* 收集主表字段的 `show` 联动字段 id(`linkageObj=field` 且 `linkageProp=show`)。
|
|
4052
|
+
* 这类字段的隐藏是**条件性**的,按条件可见处理,见 `buildCreateFormData` 的 `conditionalMainFields`。
|
|
4053
|
+
*/
|
|
4054
|
+
export function collectFieldShowLinkageIds(linkages) {
|
|
4055
|
+
const out = new Set();
|
|
4056
|
+
if (!Array.isArray(linkages))
|
|
4057
|
+
return out;
|
|
4058
|
+
for (const rule of linkages) {
|
|
4059
|
+
const details = Array.isArray(rule?.linkageDetails) ? rule.linkageDetails : [];
|
|
4060
|
+
for (const detail of details) {
|
|
4061
|
+
if (!detail || typeof detail !== 'object')
|
|
4062
|
+
continue;
|
|
4063
|
+
if (String(detail.linkageObj ?? '') !== 'field')
|
|
4064
|
+
continue;
|
|
4065
|
+
if (String(detail.linkageProp ?? '') !== 'show')
|
|
4066
|
+
continue;
|
|
4067
|
+
const fid = String(detail.linkageValue ?? '').trim();
|
|
4068
|
+
if (fid)
|
|
4069
|
+
out.add(fid);
|
|
4070
|
+
}
|
|
4071
|
+
}
|
|
4072
|
+
return out;
|
|
4073
|
+
}
|
|
4074
|
+
/**
|
|
4075
|
+
* 收集「明细表的显示联动」——`linkageObj=detail` 且 `linkageProp=show` 的规则。
|
|
4076
|
+
*
|
|
4077
|
+
* 用途见 `buildCreateFormData` 里 `conditionalHiddenSubforms` 的注释:配了 `show` 规则的明细表
|
|
4078
|
+
* 是**条件可见**(另有配套的 `hideLine` 规则),不能因为当期条件不成立就把整表字段丢掉。
|
|
4079
|
+
* 返回 `subFormId → { triggerIds, triggerNames, tableName }`,triggerNames 供告警文本直接引用。
|
|
4080
|
+
*/
|
|
4081
|
+
export function collectDetailShowLinkages(linkages, fields) {
|
|
4082
|
+
const out = new Map();
|
|
4083
|
+
if (!Array.isArray(linkages))
|
|
4084
|
+
return out;
|
|
4085
|
+
const list = Array.isArray(fields) ? fields : [];
|
|
4086
|
+
const nameOf = (id) => {
|
|
4087
|
+
for (const f of list) {
|
|
4088
|
+
if (String(f?.id ?? '') === id)
|
|
4089
|
+
return String(f?.name ?? id);
|
|
4090
|
+
}
|
|
4091
|
+
return id;
|
|
4092
|
+
};
|
|
4093
|
+
for (const rule of linkages) {
|
|
4094
|
+
const details = Array.isArray(rule?.linkageDetails) ? rule.linkageDetails : [];
|
|
4095
|
+
for (const detail of details) {
|
|
4096
|
+
if (!detail || typeof detail !== 'object')
|
|
4097
|
+
continue;
|
|
4098
|
+
if (String(detail.linkageObj ?? '') !== 'detail')
|
|
4099
|
+
continue;
|
|
4100
|
+
if (String(detail.linkageProp ?? '') !== 'show')
|
|
4101
|
+
continue;
|
|
4102
|
+
const sid = String(detail.linkageValue ?? '').trim();
|
|
4103
|
+
if (!sid)
|
|
4104
|
+
continue;
|
|
4105
|
+
const conds = Array.isArray(rule?.condition?.conditionDetails) ? rule.condition.conditionDetails : [];
|
|
4106
|
+
const triggerIds = conds
|
|
4107
|
+
.map((c) => String(c?.targetId ?? '').trim())
|
|
4108
|
+
.filter(Boolean);
|
|
4109
|
+
out.set(sid, {
|
|
4110
|
+
triggerIds,
|
|
4111
|
+
triggerNames: triggerIds.map(nameOf),
|
|
4112
|
+
tableName: nameOf(sid) === sid ? '' : nameOf(sid),
|
|
4113
|
+
});
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
return out;
|
|
4117
|
+
}
|
|
3917
4118
|
function computeLinkageVisibility(linkages, entries, defaults, linkageDetails, systemFieldValues, fields) {
|
|
3918
4119
|
const hiddenSubforms = new Set();
|
|
3919
4120
|
const rowHidden = {};
|
|
@@ -4646,6 +4847,8 @@ function linkageResultNotice(linkageDetails, fields, hiddenSubforms, rowHidden,
|
|
|
4646
4847
|
* 的就是与普通链路同一段代码),它们不是必填判定。
|
|
4647
4848
|
* - 句柄**不消费**:本分支在任何写请求之前返回,同一个 `context` 之后仍可正常重发。
|
|
4648
4849
|
* - `title` 取**工作流名称**;`src` 与两处 `routerProps_*` 的 `initialEntries` 用**工作流 id**;
|
|
4850
|
+
* `src` 末尾另按 `&<字段id>=<值>` 拼上本次要填的字段(2026-10-30 用户裁定,与下面
|
|
4851
|
+
* `params.customParams` 同一份数据);
|
|
4649
4852
|
* `params.customParams` 取 `createRequest` **入参**经解析后的字段值(不含表单默认值、不含联动带出的值),
|
|
4650
4853
|
* 同段 `fieldAssignKeys` 列出这次要填的**全部字段 id**(`,` 连接)。
|
|
4651
4854
|
*/
|
|
@@ -4798,10 +5001,31 @@ function xiaoECustomParams(preview) {
|
|
|
4798
5001
|
}
|
|
4799
5002
|
return { values, keys: ordered };
|
|
4800
5003
|
}
|
|
5004
|
+
/**
|
|
5005
|
+
* `src` 末尾的字段参数(2026-10-30 用户裁定):`&<字段id>=<值>`,与 `params.customParams` 里那份
|
|
5006
|
+
* 字段值**同一份数据、同一顺序**(`fieldAssignKeys` 是元信息,不进 URL)。
|
|
5007
|
+
*
|
|
5008
|
+
* 值**原样拼、不做任何百分号编码**:客户端自己解析这段 query(按 `&` 切键值对、再按 `=` 切第一个
|
|
5009
|
+
* 等号),中文、空格、`,` / `;` / `#` 等都不需要转义,拼出来的字面值必须与 `params.customParams`
|
|
5010
|
+
* 里逐字相同。唯一代价是值本身含 `&` 时会截断——按用户口径以此为准,不在此处兜底改写。
|
|
5011
|
+
*/
|
|
5012
|
+
function xiaoESrcFieldQuery(params) {
|
|
5013
|
+
let query = '';
|
|
5014
|
+
for (const fieldId of params.keys) {
|
|
5015
|
+
const text = params.values[fieldId];
|
|
5016
|
+
if (!text)
|
|
5017
|
+
continue;
|
|
5018
|
+
query += `&${fieldId}=${text}`;
|
|
5019
|
+
}
|
|
5020
|
+
return query;
|
|
5021
|
+
}
|
|
4801
5022
|
/**
|
|
4802
5023
|
* 渲染 `<browsertool>` 块(该块**整体**就是小e 环境下 `createRequest` 的 `finalMarkdown`)。
|
|
4803
5024
|
*
|
|
4804
5025
|
* 除 `title` / `src` / 两处 `routerProps_*` 的 `initialEntries` / `params.customParams` 外全部写死。
|
|
5026
|
+
*
|
|
5027
|
+
* `src` 形如 `…/flowpage/create/<workflowId>?workflowId=<workflowId>&isCreate=1&<字段id>=<值>&…`
|
|
5028
|
+
* —— 末尾的字段参数与 `params.customParams` 同源(2026-10-30 用户裁定)。
|
|
4805
5029
|
*/
|
|
4806
5030
|
function renderXiaoEFillFormMarkdown(params) {
|
|
4807
5031
|
const base = String(params.baseUrl ?? '').replace(/\/+$/u, '');
|
|
@@ -4814,7 +5038,7 @@ function renderXiaoEFillFormMarkdown(params) {
|
|
|
4814
5038
|
arguments: {
|
|
4815
5039
|
type: 'e10',
|
|
4816
5040
|
title: String(params.title ?? ''),
|
|
4817
|
-
src: `${base}/sp/workflow${createPath}?workflowId=${workflowId}&isCreate=1`,
|
|
5041
|
+
src: `${base}/sp/workflow${createPath}?workflowId=${workflowId}&isCreate=1${xiaoESrcFieldQuery(params.customParams)}`,
|
|
4818
5042
|
sdkOptions: {
|
|
4819
5043
|
compName: XIAOE_FILL_FORM_SDK_OPTIONS.compName,
|
|
4820
5044
|
routerType: XIAOE_FILL_FORM_SDK_OPTIONS.routerType,
|
|
@@ -4840,7 +5064,8 @@ function renderXiaoEFillFormMarkdown(params) {
|
|
|
4840
5064
|
* - `durationMs` = 命令入口 → 结果产出;
|
|
4841
5065
|
* - `apiDurations` = 每个接口(并行批次合成一个 key,取墙钟、不叠加);
|
|
4842
5066
|
* - `apiSlowest` = 每个**真并行**批里谁最慢(`[{ batch, name, ms, items }]`,`ms` 是该接口自身耗时,
|
|
4843
|
-
* `items` 是批内每个成员的 `{ name, at, ms }`,按 `ms`
|
|
5067
|
+
* `items` 是批内每个成员的 `{ name, at, ms, fetchAt, fetchEnd, fetchCost }`,按 `ms` 降序;
|
|
5068
|
+
* `fetchAt`/`fetchEnd`/`fetchCost` 描述其中纯 fetch 那一段);
|
|
4844
5069
|
* - `authMs` = 登录取凭据(首次会话解析)的墙钟 —— 这一段走 auth 层自己的 fetch、不经
|
|
4845
5070
|
* `host.workflowRequest`,`apiDurations` 统计不到,单独给一个数才能对上账。
|
|
4846
5071
|
*
|
|
@@ -4995,7 +5220,12 @@ async function runCreateRequest(args, env, options = {}) {
|
|
|
4995
5220
|
* (`buildCreateFormData`,那边的联动结果要用来算字段的必填 / 可编辑清单);这里两者都不跑 ——
|
|
4996
5221
|
* 本档既不做必填判定、也不发写请求。非小e 环境行为不变。
|
|
4997
5222
|
*/
|
|
4998
|
-
|
|
5223
|
+
// ⛔ 2026-10-31 真机回归:调用方可以显式要求「小e 环境也走服务端提交」(meeting 建会直提用)。
|
|
5224
|
+
// 起因:小e 的浏览器填单页只给**文本类**字段赋值,浏览按钮(参会人员 / 参会客户)传过去整段是空 ——
|
|
5225
|
+
// 用户看到的就是"填了但表单里这两个字段没有值"。服务端提交这条路上这些字段走标准 JSON,
|
|
5226
|
+
// 能完整落库,所以建会这类「字段必须完整」的场景应走直提,浏览器填单只留给需要人工核对/补填的场合。
|
|
5227
|
+
const forceServerSubmit = args.forceServerSubmit === true;
|
|
5228
|
+
const isXiaoE = forceServerSubmit ? false : hasXiaoEEnv(env.ctx.env, env.ctx.getGlobalOptions?.());
|
|
4999
5229
|
const linkageDetails = isXiaoE
|
|
5000
5230
|
? []
|
|
5001
5231
|
: await runFieldLinkage(env, linkageContextFromPayload(context), entries, null, merged, false, context.defaults || []);
|
|
@@ -5469,15 +5699,27 @@ function apiShortName(path) {
|
|
|
5469
5699
|
* 给 host 套一层**逐接口计时**(只包请求,不改返回、不吞异常)。
|
|
5470
5700
|
*
|
|
5471
5701
|
* `finally` 里落片段:请求失败(超时 / 业务错误 / 抛错)同样要记 —— 恰恰是这类请求最需要看到耗时。
|
|
5702
|
+
*
|
|
5703
|
+
* 片段里除整段耗时外,还记**纯 fetch** 那一段(`fetchMs`,2026-10-30 用户要求):它由
|
|
5704
|
+
* `runWithFetchTiming` 开一个旁路 sink、`E10RequestRuntime` 在 fetch 前后各打一枪得到,
|
|
5705
|
+
* 两条链路都只读不改请求/返回值。
|
|
5472
5706
|
*/
|
|
5473
5707
|
function withApiTiming(host, spans) {
|
|
5474
5708
|
const record = async (path, run) => {
|
|
5475
5709
|
const start = Date.now();
|
|
5710
|
+
const fetchTiming = createFetchTimingSink();
|
|
5476
5711
|
try {
|
|
5477
|
-
return await run
|
|
5712
|
+
return await runWithFetchTiming(fetchTiming, run);
|
|
5478
5713
|
}
|
|
5479
5714
|
finally {
|
|
5480
|
-
spans.push({
|
|
5715
|
+
spans.push({
|
|
5716
|
+
name: apiShortName(path),
|
|
5717
|
+
start,
|
|
5718
|
+
end: Date.now(),
|
|
5719
|
+
fetchStart: fetchTiming.fetchStart,
|
|
5720
|
+
fetchEnd: fetchTiming.fetchEnd,
|
|
5721
|
+
fetchMs: fetchTiming.fetchMs,
|
|
5722
|
+
});
|
|
5481
5723
|
}
|
|
5482
5724
|
};
|
|
5483
5725
|
return {
|
|
@@ -5506,7 +5748,9 @@ function withApiTiming(host, spans) {
|
|
|
5506
5748
|
* - 批内**全是同一个接口**时也不产(如 5 个并发的 `getWorkflowRequest` 取历史详情):请求都一样,
|
|
5507
5749
|
* "谁最慢"没有区分意义,只有批的墙钟有用(2026-10-22 用户第二轮裁定);
|
|
5508
5750
|
* - `ms` 取该接口**自身**耗时(`end - start`),不是批墙钟;
|
|
5509
|
-
* - 每条另带 `items`:批内**每个**成员的 `{name, at, ms}`(`at`
|
|
5751
|
+
* - 每条另带 `items`:批内**每个**成员的 `{name, at, ms, fetchAt, fetchEnd, fetchCost}`(`at` 相对批
|
|
5752
|
+
* 起点;`ms` 自身耗时;`fetchAt`/`fetchEnd` 是其中**纯 fetch**(发出请求 → 收到响应头)的起止偏移、
|
|
5753
|
+
* `fetchCost` 是它的耗时,2026-10-30 用户要求),按 `ms` 降序。
|
|
5510
5754
|
* 2026-10-26 用户要求:只看最慢项判断不出「优化它值不值」——若批内多个接口耗时接近,
|
|
5511
5755
|
* 砍掉最慢那个的收益有限,得看整条关键路径的分布;
|
|
5512
5756
|
* - 顺序=批的时间顺序,与 `apiDurations` 的 key 一一对应。
|
|
@@ -5541,13 +5785,17 @@ function summarizeApiSpans(spans) {
|
|
|
5541
5785
|
// 批内全是同一个接口 ⇒ 不产最慢项("5 个 getWorkflowRequest 里谁最慢"没有区分意义)。
|
|
5542
5786
|
if (group.length > 1 && new Set(names).size > 1) {
|
|
5543
5787
|
const origin = Math.min(...group.map((item) => item.start));
|
|
5544
|
-
// 批内逐个成员都留一条(`at` 相对批起点,`ms`
|
|
5545
|
-
//
|
|
5788
|
+
// 批内逐个成员都留一条(`at` 相对批起点,`ms` 自身耗时;`fetchAt`/`fetchEnd` 是其中纯 fetch
|
|
5789
|
+
// 的起止偏移、`fetchCost` 是它自身的耗时),按耗时降序;并列取先开始的(`at` 升序),
|
|
5790
|
+
// 保证同一份输入每次输出一致。`name`/`ms` 就取这一项。
|
|
5546
5791
|
const items = group
|
|
5547
5792
|
.map((item) => ({
|
|
5548
5793
|
name: item.name,
|
|
5549
5794
|
at: Math.max(0, Math.round(item.start - origin)),
|
|
5550
5795
|
ms: Math.max(0, Math.round(item.end - item.start)),
|
|
5796
|
+
fetchAt: item.fetchStart === null ? null : Math.max(0, Math.round(item.fetchStart - origin)),
|
|
5797
|
+
fetchEnd: item.fetchEnd === null ? null : Math.max(0, Math.round(item.fetchEnd - origin)),
|
|
5798
|
+
fetchCost: Math.max(0, Math.round(item.fetchMs)),
|
|
5551
5799
|
}))
|
|
5552
5800
|
.sort((a, b) => (b.ms - a.ms) || (a.at - b.at));
|
|
5553
5801
|
slowest.push({ batch: key, name: items[0].name, ms: items[0].ms, items });
|
|
@@ -5555,6 +5803,26 @@ function summarizeApiSpans(spans) {
|
|
|
5555
5803
|
}
|
|
5556
5804
|
return { durations, slowest };
|
|
5557
5805
|
}
|
|
5806
|
+
/**
|
|
5807
|
+
* `createRequestDirect` 的**隐藏入参** `getHistory` 取值(2026-10-30 用户裁定)。
|
|
5808
|
+
*
|
|
5809
|
+
* 只在 CLI 实现层支持:**不写进** `inputSchema`、不写进 skill 文档,模型侧看不到、也没法从
|
|
5810
|
+
* `workflow schema` 里读到;需要时由调用方(桌面端 / 脚本)自己塞进 `--input-json`。
|
|
5811
|
+
*
|
|
5812
|
+
* 口径:缺省、`null`、`undefined`、空串、其它非 `false` 的真值 ⇒ `true`(与从前的行为完全一致);
|
|
5813
|
+
* 只有明确表达"不要"的写法才关掉:`false` / `0` / `"false"` / `"0"`(大小写、首尾空格都不敏感)。
|
|
5814
|
+
* 宽松接受字符串是为了调用方从环境变量或配置里透传时不必先转类型。
|
|
5815
|
+
*/
|
|
5816
|
+
function readHiddenGetHistory(value) {
|
|
5817
|
+
if (value === false || value === 0)
|
|
5818
|
+
return false;
|
|
5819
|
+
if (typeof value === 'string') {
|
|
5820
|
+
const text = value.trim().toLowerCase();
|
|
5821
|
+
if (text === 'false' || text === '0')
|
|
5822
|
+
return false;
|
|
5823
|
+
}
|
|
5824
|
+
return true;
|
|
5825
|
+
}
|
|
5558
5826
|
/**
|
|
5559
5827
|
* `workflow.createRequestDirect` —— **取数命令**(2026-10-22 用户裁定,语义已变)。
|
|
5560
5828
|
*
|
|
@@ -5576,6 +5844,10 @@ function summarizeApiSpans(spans) {
|
|
|
5576
5844
|
* 5. **输出降级**(按 20,000 **字符**,与 `createForm` 同一个宿主口径):
|
|
5577
5845
|
* 全量超线 ⇒ `historyRequestData` 留空数组(键还在);仍超 ⇒ `userMemory` 留空串(键还在;小e 档没有
|
|
5578
5846
|
* 这个键,不补);**`fields` 自己也超线时不再裁**(宁大不丢)—— 它是唯一不可省的,砍了这条命令就没有意义。
|
|
5847
|
+
* 6. **隐藏入参 `getHistory`**(2026-10-30 用户裁定):只在实现层支持,**不进 schema、不进 skill**。
|
|
5848
|
+
* 不传 = `true`(原行为);传 `false` 时整段「历史发起流程」取数(`getMineListData` 列表 + 明细)
|
|
5849
|
+
* 一次都不发,输出里 `historyRequestData` 直接是 `null`(不是空数组——空数组的意思是"查了但没数据")。
|
|
5850
|
+
* 这一档也不参与上面的输出降级。
|
|
5579
5851
|
*
|
|
5580
5852
|
* ## 耗时
|
|
5581
5853
|
* 与 `createForm` 完全同口径,全部收在 `timing` 一个桶里:`durationMs`(整条命令)/
|
|
@@ -5588,9 +5860,15 @@ async function commandCreateRequestDirect(args, env) {
|
|
|
5588
5860
|
// 计时从**命令入口**开始算(含会话解析、工作流消歧、表单载入)。
|
|
5589
5861
|
const spans = [];
|
|
5590
5862
|
const timedEnv = { ...env, host: withApiTiming(env.host, spans) };
|
|
5863
|
+
// 隐藏入参(不进 schema / skill):`false` 时不跑「历史发起流程」那段取数。
|
|
5864
|
+
const getHistory = readHiddenGetHistory(args.getHistory);
|
|
5591
5865
|
// 取数段本身已经不碰 `chatScene`(`createForm` 自 2026-10-23 起不再接 `userQuestion`),
|
|
5592
|
-
//
|
|
5593
|
-
|
|
5866
|
+
// 这里不必再置空任何入参。`detailMark` 也是**本命令独有**:明细字段标 `isDetail: true`、
|
|
5867
|
+
// 不再下发 `subFormId`(`createForm` 仍走老口径,见 `buildCreateFormData`)。
|
|
5868
|
+
const { payload, authMs } = await prepareCreateFormPayload(args, timedEnv, {
|
|
5869
|
+
getHistory,
|
|
5870
|
+
detailMark: 'isDetail',
|
|
5871
|
+
});
|
|
5594
5872
|
const contextId = String(payload.contextId ?? '');
|
|
5595
5873
|
if (!contextId) {
|
|
5596
5874
|
// `prepareCreateFormPayload` 正常返回时 contextId 必然存在;这里只用来判断
|
|
@@ -5625,8 +5903,10 @@ async function commandCreateRequestDirect(args, env) {
|
|
|
5625
5903
|
const overLine = () => finalStdoutChars({ ...data, ...reserve }, 'workflow.createRequestDirect') > CREATE_FORM_STDOUT_SAFE_CHARS;
|
|
5626
5904
|
// 两级降级:先砍历史(留空 key)→ 再砍记忆(留空 key)。`fields` 不参与裁剪。
|
|
5627
5905
|
// 小e 档没有 `userMemory` 这个键,第二级跳过(不能借"写空串"把它重新加回来)。
|
|
5906
|
+
// `getHistory:false` 时历史那一档本来就没查(值是 `null`)⇒ 不参与降级,否则会把"没查"说成"查了没数据"。
|
|
5628
5907
|
if (overLine()) {
|
|
5629
|
-
|
|
5908
|
+
if (getHistory)
|
|
5909
|
+
data.historyRequestData = [];
|
|
5630
5910
|
if ('userMemory' in data && overLine())
|
|
5631
5911
|
data.userMemory = '';
|
|
5632
5912
|
}
|
|
@@ -5649,10 +5929,30 @@ function formatRemark(value) {
|
|
|
5649
5929
|
return text;
|
|
5650
5930
|
return `_weaverMte_<p>${text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')}</p>`;
|
|
5651
5931
|
}
|
|
5932
|
+
/**
|
|
5933
|
+
* 表单所属模块名:建模表单(`relateType=WEA_EBUILDER`)按接口取自己的 service 名,其余是 `workflow`。
|
|
5934
|
+
*
|
|
5935
|
+
* 服务端按请求体里的 `module` 解析 `@Service("<module>_FormCoreService")`:建模表单传 `workflow`
|
|
5936
|
+
* 会直接 500「未找到名为 FormCoreService 的实例对象」(2026-10-29 e-cology 建模流程实测:只把该值
|
|
5937
|
+
* 改成 `ebuildercard` 即 200)。所以凡是请求体带 `module` 的表单接口(布局 / 附件 / 保存 / 联动)
|
|
5938
|
+
* 都必须按表单类型取值,不能硬编码。
|
|
5939
|
+
*
|
|
5940
|
+
* ⚠️ 建模表单内部**不是同一个值**,必须按接口分别给(均为真机请求体实证):
|
|
5941
|
+
* - `getFormLayout` / `saveFormData` / 附件上传 → `ebuildercard`
|
|
5942
|
+
* - `executeFdLinkage` / `getFormDataAndLayout` → `ebuilderform`
|
|
5943
|
+
*
|
|
5944
|
+
* 注意这两个值**不按目录/同族划分**:`getFormDataAndLayout` 与 `getFormLayout` 同在 `form/core` 下,
|
|
5945
|
+
* 前者是 `ebuilderform`、后者是 `ebuildercard`。所以只能按接口真机样例取值,不能靠推断。
|
|
5946
|
+
*/
|
|
5947
|
+
function formModuleName(relateType, ebModule = 'ebuildercard') {
|
|
5948
|
+
return String(relateType ?? '').trim() === 'WEA_EBUILDER' ? ebModule : 'workflow';
|
|
5949
|
+
}
|
|
5652
5950
|
function buildSaveBody(context, prepared) {
|
|
5653
5951
|
const runtime = context.runtime;
|
|
5952
|
+
// 顶层 `module` 与 `formData.module` 是同一条判据:建模表单的 saveFormData 也走 ebuilder 的服务。
|
|
5953
|
+
const moduleName = formModuleName(context.frame?.relate_type);
|
|
5654
5954
|
return {
|
|
5655
|
-
module:
|
|
5955
|
+
module: moduleName,
|
|
5656
5956
|
customParam: {
|
|
5657
5957
|
isCreate: true,
|
|
5658
5958
|
requestId: runtime.request_id,
|
|
@@ -5670,7 +5970,7 @@ function buildSaveBody(context, prepared) {
|
|
|
5670
5970
|
},
|
|
5671
5971
|
employeeId: runtime.employee_id,
|
|
5672
5972
|
formData: {
|
|
5673
|
-
module:
|
|
5973
|
+
module: moduleName,
|
|
5674
5974
|
presetDataId: runtime.request_id,
|
|
5675
5975
|
dataDetails: prepared.dataDetails,
|
|
5676
5976
|
client: 'pc',
|
|
@@ -6171,12 +6471,15 @@ async function runCreateNodeFieldVerification(env, context, switches) {
|
|
|
6171
6471
|
// ---------------------------------------------------------------------------
|
|
6172
6472
|
// editLoad / editPrepare / editSave
|
|
6173
6473
|
// ---------------------------------------------------------------------------
|
|
6174
|
-
function buildEditDataLayoutRequest(requestId, workflowId, nodeId, formId, dataId, layoutMultiId, viewFormNodeId, permissionType, runtime) {
|
|
6474
|
+
function buildEditDataLayoutRequest(requestId, workflowId, nodeId, formId, dataId, layoutMultiId, viewFormNodeId, permissionType, runtime, relateType) {
|
|
6175
6475
|
return {
|
|
6176
6476
|
terminalType: 'PC',
|
|
6177
6477
|
dataId,
|
|
6178
6478
|
formId,
|
|
6179
|
-
|
|
6479
|
+
// 建模表单取 `ebuilderform`(2026-10-30 真机请求体实证),**不是** `ebuildercard`:
|
|
6480
|
+
// 同为 `form/core` 下的接口,`getFormLayout` / `saveFormData` 是 `ebuildercard`,
|
|
6481
|
+
// 本接口与 `executeFdLinkage` 是 `ebuilderform` —— 建模表单内部不能按"同族"推定。
|
|
6482
|
+
module: formModuleName(relateType, 'ebuilderform'),
|
|
6180
6483
|
newLayout: true,
|
|
6181
6484
|
cacheUid: null,
|
|
6182
6485
|
layoutMultiId,
|
|
@@ -6250,7 +6553,7 @@ async function commandEditLoad(args, env) {
|
|
|
6250
6553
|
tenantSetting = {};
|
|
6251
6554
|
}
|
|
6252
6555
|
const dataId = requestId;
|
|
6253
|
-
const dlResp = await requestJson(env, 'POST', formApiUrl(FORM_ENDPOINTS.formDataAndLayout, frame.relate_type), { formId: frame.form_id, dataId, ...AJAX_QUERY }, buildEditDataLayoutRequest(requestId, workflowId, runtime.node_id, frame.form_id, dataId, frame.layout_multi_id, frame.view_form_node_id, frame.permission_type, runtime));
|
|
6556
|
+
const dlResp = await requestJson(env, 'POST', formApiUrl(FORM_ENDPOINTS.formDataAndLayout, frame.relate_type), { formId: frame.form_id, dataId, ...AJAX_QUERY }, buildEditDataLayoutRequest(requestId, workflowId, runtime.node_id, frame.form_id, dataId, frame.layout_multi_id, frame.view_form_node_id, frame.permission_type, runtime, frame.relate_type));
|
|
6254
6557
|
const dlData = dlResp;
|
|
6255
6558
|
if (!dlData || typeof dlData !== 'object')
|
|
6256
6559
|
throw workflowApiError('format_error', 'getFormDataAndLayout 未返回有效 data');
|
|
@@ -6558,8 +6861,10 @@ async function commandEditPrepare(args, env) {
|
|
|
6558
6861
|
}
|
|
6559
6862
|
function buildEditSaveBody(context, prepared) {
|
|
6560
6863
|
const runtime = context.runtime || {};
|
|
6864
|
+
// 同 `buildSaveBody`:改已有表单时也按表单类型取 module,建模表单要 `ebuildercard`。
|
|
6865
|
+
const moduleName = formModuleName(context.frame?.relate_type);
|
|
6561
6866
|
return {
|
|
6562
|
-
module:
|
|
6867
|
+
module: moduleName,
|
|
6563
6868
|
customParam: {
|
|
6564
6869
|
isCreate: false,
|
|
6565
6870
|
requestId: context.requestId ?? context.request_id,
|
|
@@ -6579,7 +6884,7 @@ function buildEditSaveBody(context, prepared) {
|
|
|
6579
6884
|
formData: {
|
|
6580
6885
|
id: context.data_id,
|
|
6581
6886
|
updateTimeStamp: context.update_time_stamp || '',
|
|
6582
|
-
module:
|
|
6887
|
+
module: moduleName,
|
|
6583
6888
|
dataDelRows: [],
|
|
6584
6889
|
dataDetails: prepared.dataDetails || [],
|
|
6585
6890
|
client: 'pc',
|