weaver-work-cli 0.1.26 → 0.1.29

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.
Files changed (163) hide show
  1. package/README.md +7 -2
  2. package/dist/cmd/setup/index.js +1 -1
  3. package/dist/internal/e10/auth/commands.js +1 -6
  4. package/dist/internal/e10/auth/messages.js +3 -0
  5. package/dist/internal/e10/auth/session.js +8 -0
  6. package/dist/internal/e10/auth-recovery.js +148 -0
  7. package/dist/internal/e10/client.js +47 -15
  8. package/dist/internal/e10/context.js +8 -8
  9. package/dist/internal/e10/index.js +1 -0
  10. package/dist/internal/e10/request-runtime.js +51 -9
  11. package/dist/shortcuts/archive/host.js +3 -2
  12. package/dist/shortcuts/archive/manifest.js +1 -1
  13. package/dist/shortcuts/archive-oa/host.js +2 -1
  14. package/dist/shortcuts/archive-oa/manifest.js +1 -1
  15. package/dist/shortcuts/asset/host.js +7 -6
  16. package/dist/shortcuts/blog/host.js +4 -3
  17. package/dist/shortcuts/caizhilian/host.js +3 -2
  18. package/dist/shortcuts/caizhilian/manifest.js +1 -1
  19. package/dist/shortcuts/calendar/host.js +2 -1
  20. package/dist/shortcuts/contract/host.js +34 -59
  21. package/dist/shortcuts/ehr/host.js +2 -1
  22. package/dist/shortcuts/esb/host.js +2 -1
  23. package/dist/shortcuts/fna/host.js +6 -3
  24. package/dist/shortcuts/fna/manifest.js +10 -5
  25. package/dist/shortcuts/fna/operations/expense-chart.js +162 -0
  26. package/dist/shortcuts/fna/operations/registry.js +2 -0
  27. package/dist/shortcuts/hrm/host.js +2 -1
  28. package/dist/shortcuts/im/host.js +3 -2
  29. package/dist/shortcuts/invoice/host.js +9 -7
  30. package/dist/shortcuts/invoice/manifest.js +23 -142
  31. package/dist/shortcuts/invoice/operations/issuing-qys.js +266 -203
  32. package/dist/shortcuts/invoice/operations/registry.js +1 -6
  33. package/dist/shortcuts/jiuchuanhui/host.js +2 -1
  34. package/dist/shortcuts/mail/host.js +2 -1
  35. package/dist/shortcuts/meeting/host.js +2 -1
  36. package/dist/shortcuts/meeting/operations/eb/conflict.js +78 -12
  37. package/dist/shortcuts/okr/host.js +2 -1
  38. package/dist/shortcuts/plan/host.js +2 -1
  39. package/dist/shortcuts/project/host.js +2 -1
  40. package/dist/shortcuts/workflow/host.js +3 -5
  41. package/dist/shortcuts/workflow/index.js +4 -1
  42. package/dist/shortcuts/workflow/operations/chatscene.js +4 -22
  43. package/dist/shortcuts/workflow/operations/form.js +281 -166
  44. package/dist/shortcuts/workflow/operations/read.js +39 -8
  45. package/dist/shortcuts/workflow/operations/registry.js +2 -0
  46. package/dist/shortcuts/workflow/operations/todoSmartConvert/businessRules.js +331 -0
  47. package/dist/shortcuts/workflow/operations/todoSmartConvert/detailFieldRules.js +133 -0
  48. package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +326 -0
  49. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +113 -0
  50. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +259 -0
  51. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoListRows.js +64 -0
  52. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoOutputPlanner.js +28 -0
  53. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.aggregate.js +70 -0
  54. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +267 -0
  55. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +392 -0
  56. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +165 -0
  57. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +448 -0
  58. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +348 -0
  59. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +222 -0
  60. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.pipeline.js +66 -0
  61. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +502 -0
  62. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +540 -0
  63. package/dist/shortcuts/workflow/stdout-compact.js +7 -0
  64. package/docs/SKILL.md +1 -1
  65. package/docs/_catalog.md +2 -1
  66. package/docs/agent-invoice.md +1 -1
  67. package/docs/caizhilian.md +1 -1
  68. package/docs/e10-auth.md +1 -1
  69. package/docs/fna.md +7 -3
  70. package/docs/invoice.md +4 -6
  71. package/docs/todo-smart-convert/README.md +171 -0
  72. package/docs/todo-smart-convert/data-contract.md +236 -0
  73. package/docs/todo-smart-convert/mock/ai-todo-query-target.json +209 -0
  74. package/docs/todo-smart-convert/subgroup-rules.md +245 -0
  75. package/package.json +1 -1
  76. package/scripts/package-skill.mjs +1 -1
  77. package/skill-template/business-info.json +4 -1
  78. package/skill-template/domains/caizhilian.md +2 -1
  79. package/skill-template/domains/qiyecheng.md +1 -0
  80. package/skill-template/domains/yepiaotong.md +6 -2
  81. package/skill-template/skill-template.md +1 -0
  82. package/skills/weaver-e10-blog/SKILL.md +6 -1
  83. package/skills/weaver-e10-blog/references/blog-query.md +3 -1
  84. package/skills/weaver-e10-caizhilian/SKILL.md +18 -9
  85. package/skills/weaver-e10-caizhilian/references/action-flow.md +70 -0
  86. package/skills/weaver-e10-caizhilian/references/agent-entry.md +6 -2
  87. package/skills/weaver-e10-caizhilian/references/auth-and-install.md +207 -0
  88. package/skills/weaver-e10-caizhilian/references/file-collection.md +56 -54
  89. package/skills/weaver-e10-caizhilian/references/file-upload.md +4 -4
  90. package/skills/weaver-e10-caizhilian/references/host-executor-prompt.md +32 -0
  91. package/skills/weaver-e10-caizhilian/references/host-platform-integration.md +46 -0
  92. package/skills/weaver-e10-caizhilian/references/rule-index.md +56 -0
  93. package/skills/weaver-e10-caizhilian/references/setting-attach.md +5 -16
  94. package/skills/weaver-e10-caizhilian/references/setting-backup.md +4 -13
  95. package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +6 -15
  96. package/skills/weaver-e10-caizhilian/references/setting-storage-docs-summary.md +126 -0
  97. package/skills/weaver-e10-caizhilian/references/setting-storage-field-map.md +57 -0
  98. package/skills/weaver-e10-caizhilian/references/setting-storage.md +142 -482
  99. package/skills/weaver-e10-caizhilian/references/source-manifest.json +7 -7
  100. package/skills/weaver-e10-caizhilian/references/task-workflow.md +87 -0
  101. package/skills/weaver-e10-caizhilian/references/withheld.md +2 -2
  102. package/skills/weaver-e10-caizhilian/references/write-protocol.md +7 -0
  103. package/skills/weaver-e10-calendar/SKILL.md +6 -1
  104. package/skills/weaver-e10-esb/SKILL.md +8 -0
  105. package/skills/weaver-e10-esb/references/input-format.md +3 -1
  106. package/skills/weaver-e10-esb/references/source-manifest.json +5 -5
  107. package/skills/weaver-e10-hrm/SKILL.md +6 -1
  108. package/skills/weaver-e10-hrm/references/employee.md +1 -1
  109. package/skills/weaver-e10-hrm/references/org.md +1 -1
  110. package/skills/weaver-e10-hrm/references/position.md +1 -1
  111. package/skills/weaver-e10-jinchengda/SKILL.md +2 -1
  112. package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +1 -1
  113. package/skills/weaver-e10-jiuchuanhui/SKILL.md +6 -1
  114. package/skills/weaver-e10-jucailin/SKILL.md +6 -1
  115. package/skills/weaver-e10-jucailin/references/hr-contact.md +1 -1
  116. package/skills/weaver-e10-jucailin/references/salary.md +1 -1
  117. package/skills/weaver-e10-mail/SKILL.md +6 -1
  118. package/skills/weaver-e10-meeting/SKILL.md +11 -6
  119. package/skills/weaver-e10-meeting/references/meeting-eb.md +7 -3
  120. package/skills/weaver-e10-meeting/references/meeting-queries.md +3 -1
  121. package/skills/weaver-e10-meeting/references/meeting-verification.md +4 -2
  122. package/skills/weaver-e10-okr/SKILL.md +6 -1
  123. package/skills/weaver-e10-okr/references/source-manifest.json +5 -5
  124. package/skills/weaver-e10-plan/SKILL.md +6 -1
  125. package/skills/weaver-e10-plan/references/plan-overview-and-routing.md +3 -1
  126. package/skills/weaver-e10-plan/references/plan-report-query.md +3 -1
  127. package/skills/weaver-e10-plan/references/source-manifest.json +7 -7
  128. package/skills/weaver-e10-qiyecheng/SKILL.md +16 -6
  129. package/skills/weaver-e10-qiyecheng/references/catalog.md +6 -3
  130. package/skills/weaver-e10-qiyecheng/references/expense-center.md +2 -0
  131. package/skills/weaver-e10-qiyecheng/references/expense-chart.md +78 -0
  132. package/skills/weaver-e10-qiyecheng/references/source-manifest.json +87 -33
  133. package/skills/weaver-e10-shared/SKILL.md +37 -14
  134. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +11 -8
  135. package/skills/weaver-e10-shared/references/non-interactive-environment.md +4 -4
  136. package/skills/weaver-e10-shared/references/weaver-e10-installation.md +3 -1
  137. package/skills/weaver-e10-wenshuding/SKILL.md +6 -1
  138. package/skills/weaver-e10-wenshuding/references/source-manifest.json +3 -3
  139. package/skills/weaver-e10-workflow/SKILL.md +23 -1
  140. package/skills/weaver-e10-workflow/references/source-manifest.json +1 -2
  141. package/skills/weaver-e10-workflow/references/todo-smart-convert.md +282 -0
  142. package/skills/weaver-e10-workflow/references/workflow-create.md +30 -55
  143. package/skills/weaver-e10-workflow/scripts/todo-summary.cjs +695 -0
  144. package/skills/weaver-e10-yepiaotong/SKILL.md +19 -17
  145. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +75 -78
  146. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-form-fill-rules.md +179 -0
  147. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-workflow.md +238 -0
  148. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +7 -7
  149. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +11 -5
  150. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +55 -33
  151. package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +102 -23
  152. package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +1 -1
  153. package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +53 -42
  154. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +94 -214
  155. package/skills/weaver-e10-yimiaoban/SKILL.md +9 -2
  156. package/skills/weaver-e10-yimiaoban/references/file-user-i18n.md +1 -1
  157. package/skills/weaver-e10-yimiaoban/references/msg-read.md +1 -1
  158. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +9 -9
  159. package/skills/weaver-e10-ziguanjia/SKILL.md +6 -1
  160. package/dist/shortcuts/invoice/operations/issuing.js +0 -198
  161. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-field-rules.md +0 -161
  162. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-qys-ledger.md +0 -66
  163. package/skills/weaver-e10-yepiaotong/references/invoice-issuing.md +0 -107
@@ -1749,7 +1749,12 @@ async function loadCreateFormPack(workflowId, env) {
1749
1749
  /**
1750
1750
  * 智能填参:把「用户意图 + 表单字段上下文」交给独立智能体(`chatScene`),换回可直接使用的 `data`。
1751
1751
  *
1752
- * ## 什么时候调
1752
+ * ★ **2026-10-23 起本函数没有调用方**(用户裁定:`createForm` 不再接 `userQuestion`、内部不再调
1753
+ * `chatScene`,发起流程改回「`createForm` 取数 → 模型自己拼 `data` → `createRequest`」)。
1754
+ * 连同 `chatscene.ts` 的 `requestFormFill` 一起**保留不删**,只是暂时不接回链路;要恢复时把
1755
+ * `prepareCreateFormPayload` 里那句调用加回来即可。
1756
+ *
1757
+ * ## 什么时候调(恢复时生效)
1753
1758
  *
1754
1759
  * 两个条件**同时**成立才调,缺一就原样返回(= 老行为):
1755
1760
  * 1. 调用方给了 `userQuestion`(非空)—— 空串时智能体没有可依据的意图,接口也回不出有效结果,
@@ -1790,7 +1795,6 @@ async function resolveIntentFill(pack, userQuestion, baseUrl, env) {
1790
1795
  },
1791
1796
  fields,
1792
1797
  historyRequestData: pack.data.historyRequestData ?? null,
1793
- systemFields: pack.data.systemFields ?? {},
1794
1798
  userMemory: String(pack.workflowMemory ?? ''),
1795
1799
  now: formatLocalNow(),
1796
1800
  });
@@ -1815,20 +1819,22 @@ async function resolveIntentFill(pack, userQuestion, baseUrl, env) {
1815
1819
  /**
1816
1820
  * `createForm` 的**取数阶段**(原 `commandCreateForm` 前半段,逐行搬过来,行为一字不改)。
1817
1821
  *
1818
- * 抽出来的唯一目的:`createRequestDirect` 需要「未经压缩、未经落盘」的原始 payload —— 那条命令要把
1819
- * `contextId` 与智能体算出的 `data` 直接喂给 `createRequest`,而 `commandCreateForm` 的输出可能
1820
- * 因为超线只剩一句"结果已落盘"的提示,取不到这两个键。
1822
+ * 抽出来的唯一目的:`createRequestDirect` 需要「未经压缩、未经落盘」的原始 payload ——
1823
+ * `commandCreateForm` 的输出可能因为超线只剩一句"结果已落盘"的提示,取不到键。
1824
+ *
1825
+ * 2026-10-23 用户裁定:本函数**不再**调 `chatScene`(`createForm` 不再接 `userQuestion`)。
1826
+ * 现在返回的就是纯取数结果:`fields` + `historyRequestData` + `systemFields` + 记忆,
1827
+ * 由模型自己拼 `data` 后交给 `createRequest`。
1821
1828
  */
1822
1829
  async function prepareCreateFormPayload(args, env) {
1823
1830
  // 会话是**惰性**加载的(`ensureSession` 只在首次调用时填充)。本命令随后会并发发起多个请求,
1824
1831
  // 先在这里显式取一次,避免并发首调各自走一遍会话初始化。
1825
- // 顺带留住返回的上下文:智能填参要用它的 `baseUrl` 判「是不是真连了 OA」。
1826
1832
  //
1827
1833
  // 顺带计时:登录取凭据(小e 环境下是 `tokens.py` 换 eteamId + `teamsCheck` 换登录人这一整套)
1828
1834
  // 走的是 auth 层自己的 fetch,不经 `host.workflowRequest` ⇒ 天然落在 `apiDurations` 之外。
1829
1835
  // 这一段的耗时单独带出去(`authMs`),否则「总耗时 − 各接口」会凭空多出几百毫秒对不上账。
1830
1836
  const authStartedAt = Date.now();
1831
- const sessionCtx = await env.host.getSession();
1837
+ await env.host.getSession();
1832
1838
  const authMs = Date.now() - authStartedAt;
1833
1839
  let target;
1834
1840
  let pack;
@@ -1875,24 +1881,16 @@ async function prepareCreateFormPayload(args, env) {
1875
1881
  // 该工作流的记忆:**一段 Markdown 原文**,读不到就不出这个键(模型见到"没有这个键"就按新用户处理)。
1876
1882
  if (pack.workflowMemory)
1877
1883
  fullPayload.workflowMemory = pack.workflowMemory;
1878
- // ★ 2026-10-19 用户裁定:`createForm` 在返回前调 `chatScene`,由独立智能体把用户意图直接算成
1879
- // `data` 参数。成功时返回面收窄为「记忆(workflowMemory)+ fields + data」:
1880
- // - `historyRequestData` 删除(历史值已由智能体消化进 data,再发一份只会让模型二选一)
1881
- // - `systemFields` / `systemFieldWarning` 删除(智能体已按默认值 + 选项约束算过标题与紧急程度)
1882
- // - `requiredInfoWarning` **保留**:它不是 systemFields 的内容,而是「OA 没下发静态字段权限,
1883
- // 照常按 missingFields 补齐」这条**行为指引**;删掉会让模型在未下发权限的表单上误判。
1884
- // 失败(含小e 档、未传 userQuestion)⇒ 一个字都不改,原样走老口径。
1885
- const fill = await resolveIntentFill(pack, String(args.userQuestion ?? ''), sessionCtx.baseUrl, env);
1886
- if (fill.applied) {
1887
- delete fullPayload.historyRequestData;
1888
- delete fullPayload.systemFields;
1889
- delete fullPayload.systemFieldWarning;
1890
- fullPayload.data = fill.data;
1891
- // 智能体只在「用户明确要求标题」时才给非空值;它给了才覆盖 createForm 的默认标题
1892
- if (fill.requestName)
1893
- fullPayload.requestName = fill.requestName;
1894
- }
1895
- return { payload: fullPayload, fill, authMs };
1884
+ // ★ 2026-10-23 用户裁定:`createForm` **不再调 `chatScene`**(入参里的 `userQuestion` 也一并取消)。
1885
+ //
1886
+ // 起因:智能填参(2026-10-19 引入)让 `createForm` 在返回前把意图算成 `data`,成功时还会删掉
1887
+ // `historyRequestData` / `systemFields` —— 而现在的流程是「`createForm` 只负责取数(字段 + 历史 +
1888
+ // 系统字段 + 记忆)→ 模型自己拼 `data` → 调 `createRequest`」。中间再跑一次模型既多一笔开销,
1889
+ // 又会让返回面在两套结构之间切换。
1890
+ //
1891
+ // `resolveIntentFill` / `chatscene.ts` 的 `requestFormFill` **保留不删**(万一要恢复这条路),
1892
+ // 只是这里不再调用 —— 因此 `fillDurationMs`(智能体那一段的耗时)也不会再出现。
1893
+ return { payload: fullPayload, authMs };
1896
1894
  }
1897
1895
  async function commandCreateForm(args, env) {
1898
1896
  // ★ 2026-10-16 用户裁定:返回值里固定带上**本命令自身的耗时**(毫秒)。
@@ -1901,27 +1899,25 @@ async function commandCreateForm(args, env) {
1901
1899
  const startedAt = Date.now();
1902
1900
  // ★ 2026-10-21 用户裁定:耗时口径与 `createRequestDirect` **完全一致** —— 除总耗时外再给
1903
1901
  // 「每个接口多久」(`apiDurations`)与「登录取凭据多久」(`authMs`)。所以这里也把 host 包一层
1904
- // 逐接口计时,整条命令(含智能填参那一趟)的请求都会被记上。
1902
+ // 逐接口计时,整条命令的请求都会被记上。
1905
1903
  const spans = [];
1906
1904
  const timedEnv = { ...env, host: withApiTiming(env.host, spans) };
1907
- const { payload: fullPayload, fill, authMs } = await prepareCreateFormPayload(args, timedEnv);
1905
+ const { payload: fullPayload, authMs } = await prepareCreateFormPayload(args, timedEnv);
1908
1906
  // 取数阶段已全部结束 ⇒ 这里拿到的是完整的一份;它既是判线要预留的键,也是最终返回的值。
1909
- const apiDurations = summarizeApiSpans(spans);
1910
- // 判线的位数预留:`durationMs` / `apiDurations` / `authMs` 一定会有;`fillDurationMs` **只有走通
1911
- // chatScene 才有**,所以只在它会出现的这一轮才占位 —— 否则没走智能体时也白占十几个字符,边界上
1912
- // 会让本可内联的表单提前落盘。`apiDurations` 此刻已经求出,直接按真值占(不必用哨兵估)。
1907
+ const { durations: apiDurations, slowest: apiSlowest } = summarizeApiSpans(spans);
1908
+ // 判线的位数预留:`durationMs` / `apiDurations` / `apiSlowest` / `authMs` 一定会有。
1909
+ // `apiDurations` 此刻已经求出,直接按真值占(不必用哨兵估)。
1913
1910
  const reserve = {
1914
1911
  durationMs: DURATION_RESERVE_SENTINEL,
1915
1912
  apiDurations,
1913
+ apiSlowest,
1916
1914
  authMs: DURATION_RESERVE_SENTINEL,
1917
1915
  };
1918
- if (fill.durationMs !== undefined)
1919
- reserve.fillDurationMs = DURATION_RESERVE_SENTINEL;
1920
1916
  const compacted = await compactFormOutput(fullPayload, timedEnv, 'last_create_form.json', [], {
1921
1917
  safeChars: CREATE_FORM_STDOUT_SAFE_CHARS,
1922
1918
  operation: 'workflow.createForm',
1923
- // 判线要量"最终打出去的那份文本",而 `durationMs` / `fillDurationMs` 的真值只能在压缩**之后**求
1924
- // (它们统计的正是这段时间),故此处按位数用哨兵值先把它算进判线 —— 高估只让边界上的表单提前落盘
1919
+ // 判线要量"最终打出去的那份文本",而 `durationMs` 的真值只能在压缩**之后**求
1920
+ // (它统计的正是这段时间),故此处按位数用哨兵值先把它算进判线 —— 高估只让边界上的表单提前落盘
1925
1921
  // (安全方向)。
1926
1922
  reserve,
1927
1923
  });
@@ -1931,12 +1927,9 @@ async function commandCreateForm(args, env) {
1931
1927
  ...compacted,
1932
1928
  durationMs: Date.now() - startedAt,
1933
1929
  apiDurations,
1930
+ apiSlowest,
1934
1931
  authMs,
1935
1932
  };
1936
- // `fillDurationMs`(生成表单参数的耗时)**只在真走通 chatScene 时出现**:失败降级走的是老口径,
1937
- // 返回面必须一个字都不多(对应需求「不符合预期则仍按原本逻辑执行和返回」)。
1938
- if (fill.durationMs !== undefined)
1939
- result.fillDurationMs = fill.durationMs;
1940
1933
  return workflowSuccess('workflow.createForm', result);
1941
1934
  }
1942
1935
  // ---------------------------------------------------------------------------
@@ -1966,26 +1959,36 @@ async function buildCreateFormData(workflowId, env) {
1966
1959
  // 结果对象再判定 —— 不能让它们的失败经由 Promise.all 把整个命令带崩(后者语义与串行版一致)。
1967
1960
  // `loadBaseParam` / `loadFrameParam` 也改成"先收着、后判定":目的是让 base 的权限结论
1968
1961
  // **优先于** frame 的泛化错误浮出(理由见下面的判定顺序)。
1969
- const [baseOutcome, frameOutcome, minorOutcome, tenantOutcome] = await Promise.all([
1970
- // `tolerateBusinessError`:该接口用 `status:false` + `commonParam.authInfo` 表达
1971
- // 「不存在 / 无发起权限」,走带业务过滤的通道只会拿到"接口返回状态异常,code=200"。
1972
- requestJson(env, 'POST', FORM_ENDPOINTS.loadBase, query, body, { tolerateBusinessError: true })
1973
- .then((value) => ({ ok: true, value }))
1974
- .catch((error) => ({ ok: false, error })),
1975
- requestJson(env, 'POST', FORM_ENDPOINTS.loadFrame, query, body)
1976
- .then((value) => ({ ok: true, value }))
1977
- .catch((error) => ({ ok: false, error })),
1978
- requestJson(env, 'POST', FORM_ENDPOINTS.loadMinor, query, { workflowId, isCreate: 1, isMobile: 0 })
1979
- .then((value) => ({ ok: true, value }))
1980
- .catch(() => ({ ok: false, value: null })),
1981
- requestJson(env, 'POST', FORM_ENDPOINTS.tenantSetting, undefined, { setType: [] })
1962
+ //
1963
+ // 2026-10-22(用户裁定;非小e 行为不变):
1964
+ // ① 小e 环境**不发** `loadMinorInfo`:这一档不做系统字段(流程标题/紧急程度/密级)的必填校验,
1965
+ // 它的 `permissions` / 选项清单用不上,省掉真机 429 ms 的一趟。`tenant/setting` 仍照发
1966
+ // (`fileUploadParams` 的 sizeLimit 从它来)。
1967
+ // ② `getFormLayout` **挂到 base+frame 之后立刻发**,与 minor/tenant 并行:它只用 runtime 与
1968
+ // frame(见 `buildLayoutRequest`),原先要等四个都回来才发,白等最慢的那一个。
1969
+ const isXiaoE = hasXiaoEEnv(env.ctx.env, env.ctx.getGlobalOptions?.());
1970
+ // `tolerateBusinessError`:loadBase 用 `status:false` + `commonParam.authInfo` 表达
1971
+ // 「不存在 / 无发起权限」,走带业务过滤的通道只会拿到"接口返回状态异常,code=200"。
1972
+ const basePromise = requestJson(env, 'POST', FORM_ENDPOINTS.loadBase, query, body, { tolerateBusinessError: true })
1973
+ .then((value) => ({ ok: true, value }))
1974
+ .catch((error) => ({ ok: false, error }));
1975
+ const framePromise = requestJson(env, 'POST', FORM_ENDPOINTS.loadFrame, query, body)
1976
+ .then((value) => ({ ok: true, value }))
1977
+ .catch((error) => ({ ok: false, error }));
1978
+ // 小e:不发请求,直接给一个"没有 minorInfo"的结果,下游静默降级(不产生告警文本)。
1979
+ const minorPromise = isXiaoE
1980
+ ? Promise.resolve({ ok: false, value: null })
1981
+ : requestJson(env, 'POST', FORM_ENDPOINTS.loadMinor, query, { workflowId, isCreate: 1, isMobile: 0 })
1982
1982
  .then((value) => ({ ok: true, value }))
1983
- .catch(() => ({ ok: false, value: null })),
1984
- ]);
1983
+ .catch(() => ({ ok: false, value: null }));
1984
+ const tenantPromise = requestJson(env, 'POST', FORM_ENDPOINTS.tenantSetting, undefined, { setType: [] })
1985
+ .then((value) => ({ ok: true, value }))
1986
+ .catch(() => ({ ok: false, value: null }));
1985
1987
  // 「为什么进不去这张表单」必须**先于**其它接口的错误判定:
1986
1988
  // ID 不存在 / 无发起权限时,真机上 `loadFrameParam`、`loadMinorInfo` 会返回
1987
1989
  // `code:500「无流程查看权限」`——那是**后果**不是**原因**。若让它先外溢,模型只能对着
1988
1990
  // "无流程查看权限"猜,说不出到底是「ID 不存在」还是「存在但没权限」。
1991
+ const [baseOutcome, frameOutcome] = await Promise.all([basePromise, framePromise]);
1989
1992
  if (baseOutcome.ok) {
1990
1993
  const blocked = readCreateFormBlock(baseOutcome.value);
1991
1994
  if (blocked)
@@ -1995,10 +1998,25 @@ async function buildCreateFormData(workflowId, env) {
1995
1998
  throw baseOutcome.error;
1996
1999
  if (!frameOutcome.ok)
1997
2000
  throw frameOutcome.error;
2001
+ const base = baseOutcome.value;
2002
+ const frameData = frameOutcome.value;
2003
+ const runtime = parseRuntime(base, workflowId);
2004
+ const frame = parseFrame(frameData, runtime.node_id);
2005
+ // 表单布局只依赖上面这两个结果 —— 拿到就发,不陪 minor/tenant 一起等。
2006
+ const layoutPromise = requestJson(env, 'POST', formApiUrl(FORM_ENDPOINTS.formLayout, frame.relate_type), { formId: frame.form_id, ...AJAX_QUERY }, buildLayoutRequest(workflowId, runtime, frame))
2007
+ .then((value) => ({ ok: true, value }))
2008
+ .catch((error) => ({ ok: false, error }));
2009
+ const [layoutOutcome, minorOutcome, tenantOutcome] = await Promise.all([layoutPromise, minorPromise, tenantPromise]);
2010
+ if (!layoutOutcome.ok)
2011
+ throw layoutOutcome.error;
1998
2012
  const minorWarningText = 'loadMinorInfo 获取系统字段权限失败,跳过系统字段(流程标题/紧急程度/密级)必填校验';
1999
2013
  let minorInfo = {};
2000
2014
  let minorWarning = null;
2001
- if (minorOutcome.ok) {
2015
+ if (isXiaoE) {
2016
+ // 小e:本来就没发这个请求 ⇒ 按"没有系统字段权限"静默降级,不把告警塞给模型。
2017
+ minorWarning = null;
2018
+ }
2019
+ else if (minorOutcome.ok) {
2002
2020
  try {
2003
2021
  minorInfo = parseMinorInfo(minorOutcome.value);
2004
2022
  }
@@ -2010,10 +2028,6 @@ async function buildCreateFormData(workflowId, env) {
2010
2028
  else {
2011
2029
  minorWarning = minorWarningText;
2012
2030
  }
2013
- const base = baseOutcome.value;
2014
- const frameData = frameOutcome.value;
2015
- const runtime = parseRuntime(base, workflowId);
2016
- const frame = parseFrame(frameData, runtime.node_id);
2017
2031
  // tenantSetting 取不到时按 {} 处理(与串行版的 catch 分支同口径)。
2018
2032
  let tenantSetting = {};
2019
2033
  if (tenantOutcome.ok) {
@@ -2024,7 +2038,7 @@ async function buildCreateFormData(workflowId, env) {
2024
2038
  tenantSetting = {};
2025
2039
  }
2026
2040
  }
2027
- const layoutResp = await requestJson(env, 'POST', formApiUrl(FORM_ENDPOINTS.formLayout, frame.relate_type), { formId: frame.form_id, ...AJAX_QUERY }, buildLayoutRequest(workflowId, runtime, frame));
2041
+ const layoutResp = layoutOutcome.value;
2028
2042
  const layoutData = layoutResp;
2029
2043
  if (!layoutData || typeof layoutData !== 'object')
2030
2044
  throw workflowApiError('format_error', 'getFormLayout 未返回有效 data');
@@ -2087,7 +2101,7 @@ async function buildCreateFormData(workflowId, env) {
2087
2101
  * 小e 环境下两处都不跑(`createRequest` 同档也跳过字段联动与显示属性联动,由浏览器页面自己算)。
2088
2102
  * 非小e 环境行为不变。
2089
2103
  */
2090
- if (!hasXiaoEEnv(env.ctx.env, env.ctx.getGlobalOptions?.()) && linkageActions.length) {
2104
+ if (!isXiaoE && linkageActions.length) {
2091
2105
  const loadEntries = _entriesFromDefaults(defaults);
2092
2106
  try {
2093
2107
  onLoadLinkage = await runFieldLinkage(env, buildLinkageContext({ runtime, frame, layout: { form_id: frame.form_id }, linkageActions }), loadEntries, new Set(['onLoadForm']), defaults);
@@ -2432,12 +2446,18 @@ function bindInputFields(main, details, fields) {
2432
2446
  if (field)
2433
2447
  entries.push({ field, value: main[key] });
2434
2448
  }
2435
- const rowSeed = Date.now() * 100000 + Math.floor(Math.random() * 70000) + 10000;
2449
+ // ★ 2026-10-26 修复:`rowId` 必须**每行唯一**,服务端就是按 `(subForm.id, rowId)` 分行的
2450
+ // (同一行所有字段共用一个 rowId,不同行 rowId 不同)。原来用
2451
+ // `Date.now() * 100000 + ...` 得到 ≈1.79e17,已越过 `Number.MAX_SAFE_INTEGER`(9.0e15),
2452
+ // double 在该量级的精度间距是 32 ⇒ `+1/+2/+3` 全被舍回同一个数,4 行明细塌成 1 行。
2453
+ // 改用 BigInt 生成,保持同一套 18 位数值形态但不再丢精度。
2454
+ const rowSeed = BigInt(Date.now()) * 100000n
2455
+ + BigInt(Math.floor(Math.random() * 70000) + 10000);
2436
2456
  let rowOffset = 0;
2437
2457
  for (const subFormId of Object.keys(details)) {
2438
2458
  const rows = details[subFormId];
2439
2459
  rows.forEach((row, index) => {
2440
- const rowId = String(rowSeed + rowOffset);
2460
+ const rowId = String(rowSeed + BigInt(rowOffset));
2441
2461
  rowOffset += 1;
2442
2462
  const rowMeta = { dataIndex: index + 1, rowId, subForm: { id: subFormId } };
2443
2463
  for (const key of Object.keys(row)) {
@@ -2833,6 +2853,7 @@ function serializeValue(field, value, mappings, row, browserHints = {}) {
2833
2853
  if (!item || typeof item !== 'object') {
2834
2854
  throw new ValueAmbiguity({ kind: 'EMPLOYEE_SCOPE', fieldId: field.id, fieldName: field.name, reason: '人员范围字段的值必须是对象数组,每项含 name 和 type', resolution: '格式如 [{"name":"袁湘云","type":"user"}]' });
2835
2855
  }
2856
+ // 入参键名:`id`/`name` 是现行口径(智能体侧),`optionId`/`content` 是历史写法,两套都收。
2836
2857
  const optionId = String(item.optionId ?? item.id ?? '').trim();
2837
2858
  const content = String(item.content ?? item.name ?? '').trim();
2838
2859
  const scopeType = String(item.type ?? '').trim();
@@ -2844,7 +2865,7 @@ function serializeValue(field, value, mappings, row, browserHints = {}) {
2844
2865
  continue;
2845
2866
  }
2846
2867
  if (!content || !scopeType) {
2847
- throw new ValueAmbiguity({ kind: 'EMPLOYEE_SCOPE', fieldId: field.id, fieldName: field.name, input: item, reason: '缺少名称或类型', resolution: '每项必须含 name/content 与 type(user/dept/subcompany/group/role/position/external/all/allExternal)' });
2868
+ throw new ValueAmbiguity({ kind: 'EMPLOYEE_SCOPE', fieldId: field.id, fieldName: field.name, input: item, reason: '缺少名称或类型', resolution: '每项必须含 name 与 type(user/dept/subcompany/group/role/position/external/all/allExternal)' });
2848
2869
  }
2849
2870
  // 名称/类型齐了却没拿到 ID:说明名称→ID 解析没成功(预解析失败或未走预解析)。
2850
2871
  // **绝不静默丢弃**——空 optionId 会让 hasDetailValue 判为无值,整条值从保存体里消失,
@@ -3014,6 +3035,9 @@ function serializeValue(field, value, mappings, row, browserHints = {}) {
3014
3035
  * `{"optionId":"<解析出的ID>","content":"刘艳","type":"user","optionObj":<接口原始对象>}`,
3015
3036
  * 交给 `serializeValue` 的"已解析"分支直接透传。解析不到(0 命中/多命中)时把歧义挂到该条目上,
3016
3037
  * 由主循环原样报给模型(带候选),**不允许退化成一个空 optionId 被静默丢掉**。
3038
+ *
3039
+ * ★ 2026-10-22 用户裁定:智能体入参统一成 `{"type":…,"id":…,"name":…}`(不再用 `optionId`/`content`)。
3040
+ * 入参侧三个键名都收(`id`/`optionId`、`name`/`content`),内部与服务端仍用 `optionId`/`content`。
3017
3041
  */
3018
3042
  async function preResolveEmployeeScopeEntries(env, entries) {
3019
3043
  const failures = new Map();
@@ -3051,7 +3075,7 @@ async function preResolveEmployeeScopeEntries(env, entries) {
3051
3075
  reason: selected.length ? 'MULTIPLE_MATCHES' : 'NOT_FOUND',
3052
3076
  candidates: selected.map((c) => ({ id: c.id, name: c.name })),
3053
3077
  resolution: selected.length
3054
- ? `把 candidates 原样交给用户确认;用户选定后由你取该项的 id / name 拼出 {"optionId":…,"content":…,"type":"${scopeType}"} 重发同一条命令(不要让用户提供 ID)。`
3078
+ ? `把 candidates 原样交给用户确认;用户选定后由你取该项的 id / name 拼出 {"id":…,"name":…,"type":"${scopeType}"} 重发同一条命令(不要让用户提供 ID)。`
3055
3079
  : '该名称在系统里没有匹配。请让用户给出该对象的完整名称,或其它可用于检索的关键词(不要向用户索取 ID)。',
3056
3080
  };
3057
3081
  break;
@@ -4788,6 +4812,8 @@ function renderXiaoEFillFormMarkdown(params) {
4788
4812
  * (2026-10-21 用户裁定:`createRequest` 也要给耗时,且与 `createRequestDirect` 一致):
4789
4813
  * - `durationMs` = 命令入口 → 结果产出;
4790
4814
  * - `apiDurations` = 每个接口(并行批次合成一个 key,取墙钟、不叠加);
4815
+ * - `apiSlowest` = 每个**真并行**批里谁最慢(`[{ batch, name, ms, items }]`,`ms` 是该接口自身耗时,
4816
+ * `items` 是批内每个成员的 `{ name, at, ms }`,按 `ms` 降序);
4791
4817
  * - `authMs` = 登录取凭据(首次会话解析)的墙钟 —— 这一段走 auth 层自己的 fetch、不经
4792
4818
  * `host.workflowRequest`,`apiDurations` 统计不到,单独给一个数才能对上账。
4793
4819
  *
@@ -4798,11 +4824,70 @@ function stampCommandTimings(envelope, timing) {
4798
4824
  if (!data || typeof data !== 'object' || Array.isArray(data))
4799
4825
  return envelope;
4800
4826
  const mutable = data;
4827
+ const { durations, slowest } = summarizeApiSpans(timing.spans);
4801
4828
  mutable.durationMs = Date.now() - timing.startedAt;
4802
- mutable.apiDurations = summarizeApiSpans(timing.spans);
4829
+ mutable.apiDurations = durations;
4830
+ mutable.apiSlowest = slowest;
4803
4831
  mutable.authMs = timing.authMs;
4804
4832
  return envelope;
4805
4833
  }
4834
+ /**
4835
+ * 取第一个**非空**字符串。
4836
+ *
4837
+ * 系统字段的兜底链原来是 `a ?? b ?? c`,但 `??` 挡不住空串:`systemFields.defaults.requestName`
4838
+ * 的构造是 `runtime.default_request_name || ''`,一旦 OA 没给默认标题,这条链就停在 `''` 上、
4839
+ * 后面的 `context.workflow.name` / `'新建流程'` 全被短路掉。提示词改成「用户没说就不传 key」之后,
4840
+ * 这条链路会真的走到(以前是智能体拿默认值补的),所以按"跳过空串"取值。
4841
+ */
4842
+ function firstNonEmptyString(values) {
4843
+ for (const value of values) {
4844
+ const text = String(value ?? '').trim();
4845
+ if (text)
4846
+ return text;
4847
+ }
4848
+ return '';
4849
+ }
4850
+ /**
4851
+ * 系统字段「文本口径」归一(2026-10-22 用户裁定):提示词里紧急程度 / 密级已改成**文本字段**,
4852
+ * 智能体给的是用户原话(「紧急」「机密」「加急」…),而服务端要的是 `levelOptions[].code` /
4853
+ * `secLevelOptions[].id`。这里做一次「原话 → 编码」的映射:
4854
+ *
4855
+ * 1. 原样命中编码(`code` / `id`)⇒ 用编码;
4856
+ * 2. 命中选项**名称**(忽略空白与大小写)⇒ 换成编码;
4857
+ * 3. 兜底「宽松包含」(用户说「加急」、选项名「紧急加急」)⇒ 换成编码;
4858
+ * 4. 三层都命中不了 ⇒ **原样透传**:不静默丢掉用户说的内容,也不编造编码。
4859
+ *
4860
+ * 选项清单来自 `loadMinorInfo`;取不到(小e 档、老句柄)时清单为空 ⇒ 全部走第 4 条,
4861
+ * 行为与改造前完全一致(改造前就是原样透传)。
4862
+ */
4863
+ function normalizeSystemChoice(value, options, codeKey) {
4864
+ const text = String(value ?? '').trim();
4865
+ if (!text)
4866
+ return '';
4867
+ const list = (Array.isArray(options) ? options : []).filter((item) => item && typeof item === 'object');
4868
+ const norm = (raw) => String(raw ?? '').replace(/\s+/gu, '').toLowerCase();
4869
+ const target = norm(text);
4870
+ if (!target)
4871
+ return '';
4872
+ const codeOf = (item) => String(item?.[codeKey] ?? '').trim();
4873
+ for (const item of list) {
4874
+ const code = codeOf(item);
4875
+ if (code && norm(code) === target)
4876
+ return code;
4877
+ }
4878
+ for (const item of list) {
4879
+ const code = codeOf(item);
4880
+ if (code && norm(item?.name) === target)
4881
+ return code;
4882
+ }
4883
+ for (const item of list) {
4884
+ const name = norm(item?.name);
4885
+ const code = codeOf(item);
4886
+ if (name && code && (name.includes(target) || target.includes(name)))
4887
+ return code;
4888
+ }
4889
+ return text;
4890
+ }
4806
4891
  /**
4807
4892
  * `createRequest` 的耗时外壳:建计时通道 + 量登录耗时 + 把三个耗时键并回结果。
4808
4893
  *
@@ -4913,8 +4998,13 @@ async function runCreateRequest(args, env, options = {}) {
4913
4998
  const missing = missingRequiredFields(fields, entries, merged2, hiddenSubforms, rowHidden, rowExtraRequired, rowDisabled, hiddenFieldIds, compsFieldIds, layoutSubformIds);
4914
4999
  const sysPermissions = (context.systemFields && context.systemFields.permissions) || {};
4915
5000
  const sysDefaults = (context.systemFields && context.systemFields.defaults) || {};
4916
- const requestLevelArg = String(args.requestLevel ?? '').trim();
4917
- const secLevelArg = String(args.secLevel ?? '').trim();
5001
+ // 选项清单的键名随版本变过一次:`createForm` 句柄里是 snake_case,别处可能给 camelCase,两种都收
5002
+ // (与下方 `systemFilledRows` 同一口径)。
5003
+ const levelOptions = (context.systemFields && (context.systemFields.level_options || context.systemFields.levelOptions)) || [];
5004
+ const secLevelOptions = (context.systemFields && (context.systemFields.sec_level_options || context.systemFields.secLevelOptions)) || [];
5005
+ // 紧急程度 / 密级:提示词里是文本字段 ⇒ 入参可能是用户原话,先归一成编码(取不到选项就原样透传)。
5006
+ const requestLevelArg = normalizeSystemChoice(args.requestLevel, levelOptions, 'code');
5007
+ const secLevelArg = normalizeSystemChoice(args.secLevel, secLevelOptions, 'id');
4918
5008
  const requestNameValue = String(args.requestName ?? sysDefaults.requestName ?? '').trim();
4919
5009
  const requestLevelValue = requestLevelArg || String(sysDefaults.requestLevel ?? '').trim();
4920
5010
  const secLevelValue = secLevelArg || String(sysDefaults.secLevel ?? '').trim();
@@ -4980,8 +5070,8 @@ async function runCreateRequest(args, env, options = {}) {
4980
5070
  secLevel: secLevelValue,
4981
5071
  permissions: sysPermissions,
4982
5072
  // 老句柄可能没有这两个选项清单(键名也随版本变过一次),取不到就退化成"不列系统字段"。
4983
- levelOptions: (context.systemFields && (context.systemFields.level_options || context.systemFields.levelOptions)) || [],
4984
- secLevelOptions: (context.systemFields && (context.systemFields.sec_level_options || context.systemFields.secLevelOptions)) || [],
5073
+ levelOptions,
5074
+ secLevelOptions,
4985
5075
  });
4986
5076
  if (valueAmbiguities.length) {
4987
5077
  return workflowSuccess('workflow.createRequest', {
@@ -5013,7 +5103,7 @@ async function runCreateRequest(args, env, options = {}) {
5013
5103
  *
5014
5104
  * **stdout 形态**(2026-10-21 用户裁定,**改口径**):这一档同样**走 JSON 信封**,标签块放在
5015
5105
  * `data.finalMarkdown` 里 —— 与 `createRequestDirect` 统一,模型侧只有一条读法;`durationMs` /
5016
- * `apiDurations` / `authMs` 三个耗时键也因此在这一档可见。原来的「stdout 直接就是标签块、不套
5106
+ * `apiDurations` / `apiSlowest` / `authMs` 四个耗时键也因此在这一档可见。原来的「stdout 直接就是标签块、不套
5017
5107
  * 信封」已作废(`shortcuts/workflow/index.ts` 的直出通道随之删除)。
5018
5108
  * 所以下面的 `message` 要明确告诉模型:从 `data.finalMarkdown` 取内容、原样作为本轮回复正文。
5019
5109
  *
@@ -5055,7 +5145,11 @@ async function runCreateRequest(args, env, options = {}) {
5055
5145
  // 同时给一份可直接原样粘贴的渲染档(V12.1.102):结论 + 主表「已填内容」两列表(含流程标题与
5056
5146
  // 表单默认值,2026-10-14 起)+ 明细 Excel 列 + 待补必填项清单。模型不必自己拼这些表格,也不必再
5057
5147
  // 遍历 `resolvedValues` 拼待补清单;因此这里**不再返回** `resolvedValues`(渲染素材只在命令内部用)。
5058
- const pendingRequestName = String(args.requestName ?? sysDefaults.requestName ?? context.runtime.default_request_name ?? '').trim();
5148
+ const pendingRequestName = firstNonEmptyString([
5149
+ args.requestName,
5150
+ sysDefaults.requestName,
5151
+ context.runtime.default_request_name,
5152
+ ]);
5059
5153
  // ★ 2026-10-20 用户裁定:**有 `finalMarkdown` 的状态不再返回 `message`**。
5060
5154
  // 这条路一定有渲染档(`renderCreatePendingMarkdown` 至少输出结论句 + 待补清单),
5061
5155
  // 而 `message` 说的是同一件事、口气还是"对模型下指令"("带同一 context 重发 action=submit"),
@@ -5076,7 +5170,13 @@ async function runCreateRequest(args, env, options = {}) {
5076
5170
  ...folderPayload,
5077
5171
  });
5078
5172
  }
5079
- const requestName = String(args.requestName ?? sysDefaults.requestName ?? context.runtime.default_request_name ?? context.workflow.name ?? '新建流程').trim();
5173
+ const requestName = firstNonEmptyString([
5174
+ args.requestName,
5175
+ sysDefaults.requestName,
5176
+ context.runtime.default_request_name,
5177
+ context.workflow.name,
5178
+ '新建流程',
5179
+ ]);
5080
5180
  if (!requestName)
5081
5181
  throw workflowValidationError('input_invalid', 'request_name 不能为空');
5082
5182
  const inputFieldIds = [...new Set(entries.map((e) => String((e.field && e.field.id) ?? '')).filter(Boolean))];
@@ -5371,6 +5471,18 @@ function withApiTiming(host, spans) {
5371
5471
  *
5372
5472
  * 重叠判定用「区间扫描」而不是推断代码里的并发结构:只要两个请求在时间上真重叠就算一批,
5373
5473
  * 批量接口内部再起并发也照样能反映出来。
5474
+ *
5475
+ * ★ 2026-10-22 用户裁定:**并行批还要记「谁最慢」**(`apiSlowest`)。批的墙钟只说明"这一批花了多久",
5476
+ * 看不出是哪个接口把它拖住的;而优化恰恰只对最慢的那个有用。判定口径:
5477
+ * - 只对**真并行**(批内 ≥ 2 个请求)的批产出一条,单一请求的批不产 —— 那种情况下"谁最慢"就是它自己,
5478
+ * 记了只是噪声;
5479
+ * - 批内**全是同一个接口**时也不产(如 5 个并发的 `getWorkflowRequest` 取历史详情):请求都一样,
5480
+ * "谁最慢"没有区分意义,只有批的墙钟有用(2026-10-22 用户第二轮裁定);
5481
+ * - `ms` 取该接口**自身**耗时(`end - start`),不是批墙钟;
5482
+ * - 每条另带 `items`:批内**每个**成员的 `{name, at, ms}`(`at` 相对批起点),按 `ms` 降序。
5483
+ * 2026-10-26 用户要求:只看最慢项判断不出「优化它值不值」——若批内多个接口耗时接近,
5484
+ * 砍掉最慢那个的收益有限,得看整条关键路径的分布;
5485
+ * - 顺序=批的时间顺序,与 `apiDurations` 的 key 一一对应。
5374
5486
  */
5375
5487
  function summarizeApiSpans(spans) {
5376
5488
  const sorted = [...spans].sort((a, b) => (a.start - b.start) || (a.end - b.end));
@@ -5392,88 +5504,109 @@ function summarizeApiSpans(spans) {
5392
5504
  }
5393
5505
  if (current.length)
5394
5506
  groups.push(current);
5395
- const out = {};
5507
+ const durations = {};
5508
+ const slowest = [];
5396
5509
  for (const group of groups) {
5397
5510
  const names = group.map((item) => item.name);
5398
5511
  const key = names.length === 1 ? names[0] : [...names].sort().join('_');
5399
5512
  const cost = Math.max(0, Math.round(Math.max(...group.map((item) => item.end)) - Math.min(...group.map((item) => item.start))));
5400
- out[key] = (out[key] ?? 0) + cost;
5401
- }
5402
- return out;
5513
+ durations[key] = (durations[key] ?? 0) + cost;
5514
+ // 批内全是同一个接口 ⇒ 不产最慢项("5 个 getWorkflowRequest 里谁最慢"没有区分意义)。
5515
+ if (group.length > 1 && new Set(names).size > 1) {
5516
+ const origin = Math.min(...group.map((item) => item.start));
5517
+ // 批内逐个成员都留一条(`at` 相对批起点,`ms` 自身耗时),按耗时降序;并列取先开始的
5518
+ // (`at` 升序),保证同一份输入每次输出一致。`name`/`ms` 就取这一项。
5519
+ const items = group
5520
+ .map((item) => ({
5521
+ name: item.name,
5522
+ at: Math.max(0, Math.round(item.start - origin)),
5523
+ ms: Math.max(0, Math.round(item.end - item.start)),
5524
+ }))
5525
+ .sort((a, b) => (b.ms - a.ms) || (a.at - b.at));
5526
+ slowest.push({ batch: key, name: items[0].name, ms: items[0].ms, items });
5527
+ }
5528
+ }
5529
+ return { durations, slowest };
5403
5530
  }
5404
5531
  /**
5405
- * `workflow.createRequestDirect` —— **临时命令**:把「载入表单 + 智能填参」和「发起」串成一步。
5406
- *
5407
- * 等价于「先跑 `createForm`(带 `userQuestion`),再把它的 `contextId` 与 `data` 原样交给
5408
- * `createRequest`」,区别只在一次进程内完成、不给模型中间态。
5409
- *
5410
- * ## 四条口径
5411
- * 1. **取数阶段复用** `prepareCreateFormPayload`(`createForm` 的同一段代码),所以澄清 / 表单不可发起
5412
- * 这类错误**原样上抛**,新命令一条都不会吞,也不会在澄清时硬着头皮发写请求。
5413
- * 2. 智能填参走通 ⇒ 用它算出的 `data`;**没走通(超时 / 返回体不合法 / 未传 userQuestion)⇒ 按
5414
- * `data` 为空继续** —— 交给 `createRequest` 自己的必填校验去判定(`submit` 缺必填不写库、
5415
- * `draft` 直接建草稿),与"模型没填任何字段就调 createRequest"完全等价。
5416
- * 3. **小e 档**:不跑字段联动(页面自己会重算),但**跑显示属性联动并把不可编辑字段过滤掉**
5417
- * (`xiaoEViewLinkageFilter`),交出去的值只含页面真能填的字段。
5418
- * 4. 最终返回 **`createRequest` 的结果**(`data` 与各状态语义完全一致,`action` 仍是
5419
- * `workflow.createRequest`),只额外挂三个耗时键(见下)。JSON 信封的 `operation` 是
5420
- * `workflow.createRequestDirect`(由命令层盖,见 `successEnvelope`)。
5421
- *
5422
- * ## 小e 档也走 JSON(2026-10-21 用户裁定,已推广到原命令)
5423
- * 原命令在小e 下曾直出 `<browsertool>`,那条路会把耗时藏掉。现在**两条命令统一走 JSON 信封**:
5424
- * `data.finalMarkdown` 仍是同一段标签块,`data.durationMs` / `data.apiDurations` / `data.authMs`
5425
- * 一并可见;`message` 告知模型从 `finalMarkdown` 取内容原样回复。
5532
+ * `workflow.createRequestDirect` —— **取数命令**(2026-10-22 用户裁定,语义已变)。
5533
+ *
5534
+ * 原先是「载入表单 + 智能填参 + 发起」一步到位;现在**只返回填参原料**,由调用方(桌面端 agent
5535
+ * 自己的模型)拼好 `data` 再调 `createRequest`。理由:那一侧本身就是模型,再往 CLI 里跑一趟
5536
+ * `chatScene` 只是白白多一次模型调用与一轮上下文。
5537
+ *
5538
+ * ## 五条口径
5539
+ * 1. **不调 `chatScene`**:它复用的取数段(`prepareCreateFormPayload`)自 2026-10-23 起就不再碰
5540
+ * 这条链路(`createForm` 已不接 `userQuestion`),本命令无需再做什么就能保证「一个请求都不发」。
5541
+ * 2. **不发起、不写库**:不调用 `commandCreateRequest`,一条 `saveFormData` / `flow/create` 都不会发。
5542
+ * 这条命令的 `risk` 因此从 `high-risk-write` 降为 `read`。
5543
+ * 3. **只返回填参原料**:`fields`(含 `initialValue`)、`historyRequestData`、`userMemory`
5544
+ * (=该工作流的记忆 `workflowMemory`),外加 `status` / `workflow` 两个元信息。
5545
+ * 4. **不返回系统字段**:`systemFields` / `systemFieldWarning` 整块不出现(标题/紧急程度/密级的默认值
5546
+ * 与选项清单都不给)。
5547
+ * 5. **输出降级**(按 20,000 **字符**,与 `createForm` 同一个宿主口径):
5548
+ * 全量超线 ⇒ `historyRequestData` 留空数组(键还在);仍超 ⇒ `userMemory` 留空串(键还在);
5549
+ * **`fields` 自己也超线时不再裁**(宁大不丢)—— 它是唯一不可省的,砍了这条命令就没有意义。
5426
5550
  *
5427
5551
  * ## 耗时
5428
- * - `data.durationMs` = 命令总耗时;
5429
- * - `data.apiDurations` = 每个接口(并行批次合并)的耗时;
5430
- * - `data.authMs` = 登录取凭据(首次会话解析)的耗时 —— 这段走 auth 层自己的 fetch,不经
5431
- * `host.workflowRequest`,`apiDurations` 统计不到,单独给一个数才能对上账
5432
- * (`durationMs` ≈ `apiDurations` 合计 + `authMs` + 本地解析/渲染的零头)。
5433
- * 三者都只在 `data` 是普通对象时挂;本命令与 `createRequest` 用的是**同一个** `stampCommandTimings`,
5434
- * 差别只在时间区间(这里从命令入口起算、覆盖表单载入那一段)。
5552
+ * 与 `createForm` 完全同口径,全部收在 `timing` 一个桶里:`durationMs`(整条命令)/
5553
+ * `apiDurations`(按并行批合并的接口耗时)/ `apiSlowest`(每个真并行批的最慢项,`items` 里带批内
5554
+ * 每个接口各自的耗时)/
5555
+ * `authMs`(会话解析)。本命令不会出现 `fillDurationMs` —— 那一项统计的是 `chatScene`,不再有这一段。
5435
5556
  */
5436
5557
  async function commandCreateRequestDirect(args, env) {
5437
5558
  const startedAt = Date.now();
5438
- const action = String(args.action ?? '').trim();
5439
- // 先校验 action 再取数:参数错就没必要白跑一趟表单载入(那是最贵的一段)。
5440
- if (action !== 'draft' && action !== 'submit') {
5441
- throw workflowValidationError('action_required', 'action 必填,且只能是 draft(仅存草稿)或 submit(提交审批)');
5442
- }
5443
- // 计时从**命令入口**开始算(含会话解析、工作流消歧、表单载入、智能填参、写库、校验)。
5559
+ // 计时从**命令入口**开始算(含会话解析、工作流消歧、表单载入)。
5444
5560
  const spans = [];
5445
5561
  const timedEnv = { ...env, host: withApiTiming(env.host, spans) };
5446
- const { payload, fill, authMs } = await prepareCreateFormPayload(args, timedEnv);
5562
+ // 取数段本身已经不碰 `chatScene`(`createForm` 自 2026-10-23 起不再接 `userQuestion`),
5563
+ // 这里不必再置空任何入参。
5564
+ const { payload, authMs } = await prepareCreateFormPayload(args, timedEnv);
5447
5565
  const contextId = String(payload.contextId ?? '');
5448
5566
  if (!contextId) {
5449
- // `prepareCreateFormPayload` 正常返回时 contextId 必然存在;这里只是不带着空句柄去调写接口。
5450
- throw workflowValidationError('context_missing', '表单载入未产出可用句柄,本次未发起任何写入,请重试');
5451
- }
5452
- // ★ 智能体没给出可用 data ⇒ `{}`(不是老口径的 historyRequestData:本命令不做二次推断,
5453
- // 有值就用、没有就交给必填校验)。
5454
- const data = (fill.applied && fill.data && typeof fill.data === 'object')
5455
- ? fill.data
5456
- : {};
5457
- const systemData = fill.systemData ?? {};
5458
- const requestName = String(payload.requestName ?? '').trim();
5459
- const requestLevel = String(systemData.requestLevel ?? '').trim();
5460
- const secLevel = String(systemData.secLevel ?? '').trim();
5461
- const remark = String(systemData.remark ?? '').trim();
5462
- const envelope = await commandCreateRequest({
5463
- context: contextId,
5464
- data,
5465
- action,
5466
- // ★ 空串一律**不传键**:`createRequest` 的兜底链是 `args.x ?? sysDefaults.x ?? 句柄里的默认值`,
5467
- // 而 `??` 挡不住空串 —— 传 `''` 会把后面的默认值全部短路掉(`request_name 不能为空` 就是这么来的)。
5468
- ...(requestName ? { requestName } : {}),
5469
- ...(requestLevel ? { requestLevel } : {}),
5470
- ...(secLevel ? { secLevel } : {}),
5471
- ...(remark ? { remark } : {}),
5472
- verificationConfirmed: args.verificationConfirmed,
5473
- }, timedEnv, { xiaoEViewLinkageFilter: true, apiSpans: spans });
5474
- // 把耗时并回结果:与 `createRequest` 同一个函数、同一个口径,只是时间区间是整条命令
5475
- // (含会话解析、工作流消歧、表单载入、智能填参、写库、校验)。
5476
- return stampCommandTimings(envelope, { startedAt, spans, authMs });
5567
+ // `prepareCreateFormPayload` 正常返回时 contextId 必然存在;这里只用来判断
5568
+ // "表单到底载入成功没有"(句柄本身不再交给调用方)。
5569
+ throw workflowValidationError('context_missing', '表单载入未产出可用句柄,请重试');
5570
+ }
5571
+ const { durations: apiDurations, slowest: apiSlowest } = summarizeApiSpans(spans);
5572
+ // 返回面只留「模型真正要用的」:状态 + 工作流 + 填参三样 + 一个耗时桶。
5573
+ // 句柄类(contextId/requestId/requestName)、`form`、`continuation`、`requiredInfoWarning` 全部不出
5574
+ // —— 本命令不发起,这些字段没有下游(2026-10-22 用户裁定)。`contextId` 仍在上面的守卫里用于
5575
+ // 判断"表单到底载入成功没有",只是不再交出去。
5576
+ const data = {
5577
+ status: payload.status,
5578
+ workflow: payload.workflow,
5579
+ fields: payload.fields,
5580
+ historyRequestData: payload.historyRequestData,
5581
+ userMemory: String(payload.workflowMemory ?? ''),
5582
+ };
5583
+ // 判线必须把「稍后才求得出」的耗时键先按位数占住(与 `createForm` 同一个哨兵口径,宁可高估),
5584
+ // 否则边界上的表单会被判成"没超"、然后在宿主那儿被真截断。
5585
+ const reserve = {
5586
+ timing: {
5587
+ durationMs: DURATION_RESERVE_SENTINEL,
5588
+ apiDurations,
5589
+ apiSlowest,
5590
+ authMs: DURATION_RESERVE_SENTINEL,
5591
+ },
5592
+ };
5593
+ const overLine = () => finalStdoutChars({ ...data, ...reserve }, 'workflow.createRequestDirect') > CREATE_FORM_STDOUT_SAFE_CHARS;
5594
+ // 两级降级:先砍历史(留空 key)→ 再砍记忆(留空 key)。`fields` 不参与裁剪。
5595
+ if (overLine()) {
5596
+ data.historyRequestData = [];
5597
+ if (overLine())
5598
+ data.userMemory = '';
5599
+ }
5600
+ return workflowSuccess('workflow.createRequestDirect', {
5601
+ ...data,
5602
+ // 全部耗时口径收进一个桶:总耗时、逐接口耗时、并行批最慢项、登录耗时。
5603
+ timing: {
5604
+ durationMs: Date.now() - startedAt,
5605
+ apiDurations,
5606
+ apiSlowest,
5607
+ authMs,
5608
+ },
5609
+ });
5477
5610
  }
5478
5611
  function formatRemark(value) {
5479
5612
  const text = String(value ?? '').trim();
@@ -6527,7 +6660,7 @@ const workflowCreateForm = {
6527
6660
  resource: 'workflow',
6528
6661
  method: 'createForm',
6529
6662
  action: 'workflow.createForm',
6530
- summary: '按工作流参数解析可发起的工作流并载入发起表单:loadBase/loadFrame/loadMinor/formLayout 合并为 runtime 与 frame,产出 context 句柄 + 字段清单 + fileUploadParams + 该工作流的发起偏好。传了 userQuestion 时额外调智能体把意图算成 data 参数(此时不返回 historyRequestData / systemFields)',
6663
+ summary: '按工作流参数解析可发起的工作流并载入发起表单:loadBase/loadFrame/loadMinor/formLayout 合并为 runtime 与 frame,产出 context 句柄 + 字段清单 + fileUploadParams + 该工作流的发起偏好。只取数、不填参(不再调智能体):模型据此自己拼 data 后交给 createRequest',
6531
6664
  capability: 'create',
6532
6665
  risk: 'read',
6533
6666
  requiresConfirmation: false,
@@ -6537,14 +6670,6 @@ const workflowCreateForm = {
6537
6670
  items: { type: 'string' },
6538
6671
  description: '工作流条件:支持 "名称(ID)"、名称(可传多个,用中英文逗号分隔,如 "请假,请假申请")、纯数字 ID;按当前用户**可发起**的工作流模糊匹配;未能唯一确定目标时返回澄清(候选见 candidates),确认后由你按「名称(ID)」重发(只问用户完整名称或其它可用于检索的关键词,不要向用户索取 ID)',
6539
6672
  },
6540
- relatesTo: { type: 'string', description: '可选关联对象 id(仅透传,不影响表单初始化)' },
6541
- userQuestion: {
6542
- type: 'string',
6543
- description: '用户的填单意图(用一句话概括,可以是多轮对话汇总后的最终需求)。'
6544
- + '**传了才启用智能填参**:命令会在返回前把它连同字段清单交给智能体,换回已填好的 data 参数'
6545
- + '(此时不再返回 historyRequestData / systemFields)。'
6546
- + '不传 = 关闭这条链路,按原口径返回 historyRequestData 等参考值(老调用方零改动)',
6547
- },
6548
6673
  }, { required: ['workflow'] }),
6549
6674
  handler: (args, env) => commandCreateForm(args, env),
6550
6675
  };
@@ -6566,8 +6691,8 @@ const workflowCreateRequest = {
6566
6691
  },
6567
6692
  action: { type: 'string', enum: ['draft', 'submit'], description: 'draft=仅保存草稿(必填缺失不阻断,缺失项随结果作为提醒返回);submit=提交审批(仍有必填缺失时不调用创建接口,改为一次性回报全部缺失项)' },
6568
6693
  requestName: { type: 'string', description: '流程标题(form 返回 requestName 非空时直接用其值;为空时由模型按内容生成)' },
6569
- requestLevel: { type: 'string', description: '紧急程度(取 systemFields.levelOptions 的 code)' },
6570
- secLevel: { type: 'string', description: '密级(取 systemFields.secLevelOptions 的 id)' },
6694
+ requestLevel: { type: 'string', description: '紧急程度:用户原话即可(如「紧急」「普通」),CLI 按选项名归一成编码;说不出就不传,走表单默认值' },
6695
+ secLevel: { type: 'string', description: '密级:用户原话即可(如「机密」「公开」),CLI 按选项名归一成密级 id;说不出就不传,走表单默认值' },
6571
6696
  remark: { type: 'string', description: '签字意见' },
6572
6697
  verificationConfirmed: { type: 'boolean', description: '节点字段校验需用户确认时显式确认继续提交' },
6573
6698
  }, { required: ['context', 'data', 'action'] }),
@@ -6578,27 +6703,17 @@ const workflowCreateRequestDirect = {
6578
6703
  resource: 'workflow',
6579
6704
  method: 'createRequestDirect',
6580
6705
  action: 'workflow.createRequestDirect',
6581
- summary: '一步发起(直接给 workflow,不必先调 createForm 取句柄;仍按 requiresConfirmation 先取得用户确认):先按 createForm 解析工作流并载入表单、把 userQuestion 交给智能体算成 data,然后直接接着执行 createRequest(保存 → 创建 → 可选提交);智能体没给出可用 data 时按 data 为空继续。小e 环境不跑字段联动但照跑显示属性联动、只交可编辑字段。返回结果与 createRequest 一致,另带 durationMs(总耗时)、apiDurations(每接口耗时)与 authMs(登录取凭据耗时)',
6582
- capability: 'create',
6583
- risk: 'high-risk-write',
6584
- requiresConfirmation: true,
6706
+ summary: '取数(不发起、不调智能体、不写库):载入发起表单后直接返回 fields(含 initialValue)、historyRequestData、userMemory,供你拼好 data 后再调 createRequest。不返回系统字段(标题/紧急程度/密级的默认值与选项),也不返回句柄、form、continuation。输出超过 2 万字符时依次降级:先让 historyRequestData 变成空数组,仍超再让 userMemory 变成空串(键都保留)。耗时全部收在 timing 一个键里:{durationMs, apiDurations, apiSlowest, authMs}',
6707
+ capability: 'read',
6708
+ risk: 'read',
6709
+ requiresConfirmation: false,
6585
6710
  inputSchema: schemaOf({
6586
6711
  workflow: {
6587
6712
  type: 'array',
6588
6713
  items: { type: 'string' },
6589
6714
  description: '工作流条件:支持 "名称(ID)"、名称(可传多个,用英文逗号分隔)、纯数字 ID;语义与 createForm 的 workflow 完全一致(含澄清规则)',
6590
6715
  },
6591
- userQuestion: {
6592
- type: 'string',
6593
- description: '用户的填单意图(一句话概括,可多轮汇总)。命令会把它交给智能体算出要填的字段值后直接发起;为空或智能体没给出可用结果时,按"一个字段都没填"继续发起',
6594
- },
6595
- action: {
6596
- type: 'string',
6597
- enum: ['draft', 'submit'],
6598
- description: 'draft=仅保存草稿;submit=提交审批(必填缺失时不写库、一次性回报缺失项)',
6599
- },
6600
- verificationConfirmed: { type: 'boolean', description: '节点字段校验需用户确认时显式确认继续提交' },
6601
- }, { required: ['workflow', 'userQuestion', 'action'] }),
6716
+ }, { required: ['workflow'] }),
6602
6717
  handler: (args, env) => commandCreateRequestDirect(args, env),
6603
6718
  };
6604
6719
  const workflowEditLoad = {