weaver-work-cli 0.1.38 → 0.1.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/chunks/{archive-PDV724YK.js → archive-JHTONRST.js} +556 -66
- package/dist/chunks/{archive-oa-4XE4MNXQ.js → archive-oa-FCHLCPHC.js} +2 -2
- package/dist/chunks/{asset-PDL2QE2N.js → asset-RLIHC7DB.js} +2 -2
- package/dist/chunks/asset-idle-KVOFTLCP.js +670 -0
- package/dist/chunks/{attachment-content-MDDGOE5E.js → attachment-content-RZYDB3MB.js} +3 -3
- package/dist/chunks/{blog-UYR3EUXW.js → blog-42OZTJNP.js} +2 -2
- package/dist/chunks/{caizhilian-LG2X5NMF.js → caizhilian-KRK547K5.js} +2 -2
- package/dist/chunks/{calendar-FG4BAZHE.js → calendar-RTWAMKHD.js} +2 -2
- package/dist/chunks/{chunk-J7USS3ED.js → chunk-4CTESSKI.js} +4 -4
- package/dist/chunks/{chunk-OJQUJZQF.js → chunk-4TEDNCON.js} +45 -5
- package/dist/chunks/chunk-57IECMWX.js +487 -0
- package/dist/chunks/{chunk-ELYDWBZJ.js → chunk-7BRU4EFH.js} +13 -7
- package/dist/chunks/{chunk-6LTFIFLM.js → chunk-7E6MUOEK.js} +2 -2
- package/dist/chunks/{chunk-KKRL3REV.js → chunk-7U577Q7U.js} +2 -2
- package/dist/chunks/{chunk-UJJD4QWF.js → chunk-CAQNBBQA.js} +2 -2
- package/dist/chunks/{chunk-CORFRD7Q.js → chunk-CXRQWOZ5.js} +2 -2
- package/dist/chunks/{chunk-GGJFMNJM.js → chunk-FLS4DPGP.js} +110 -16
- package/dist/chunks/{chunk-JSMFPO43.js → chunk-I566ZYNF.js} +1 -1
- package/dist/chunks/{chunk-6FTTSA45.js → chunk-IZSSVYAG.js} +2 -2
- package/dist/chunks/{chunk-XJY7G4DD.js → chunk-JWRZM2H2.js} +3 -3
- package/dist/chunks/{chunk-NI6GIPIM.js → chunk-LFMSDBZN.js} +529 -395
- package/dist/chunks/{chunk-VRYHGG6O.js → chunk-LN447PY2.js} +92 -51
- package/dist/chunks/{chunk-NR6RKXPA.js → chunk-NETCCARD.js} +4 -2
- package/dist/chunks/{chunk-Z32KWY6G.js → chunk-NJKREDA3.js} +1 -1
- package/dist/chunks/{chunk-FUWVHMVV.js → chunk-SPRHHSVC.js} +2 -2
- package/dist/chunks/{chunk-IZUDA353.js → chunk-T6EGVBKU.js} +1 -1
- package/dist/chunks/{chunk-DMV2TOMH.js → chunk-VNBU55SL.js} +428 -145
- package/dist/chunks/{chunk-UZZBA6PV.js → chunk-WFFNM65B.js} +47 -6
- package/dist/chunks/{chunk-EG6ZU444.js → chunk-ZUX3P6BQ.js} +10 -5
- package/dist/chunks/{conflict-BQZEQ4SP.js → conflict-VR55AOOO.js} +2 -2
- package/dist/chunks/{contract-XIRAFTEV.js → contract-HI4FNPYE.js} +2 -2
- package/dist/chunks/{cowork-BLG62BL2.js → cowork-HJLI353D.js} +12 -5
- package/dist/chunks/{ebuilder-form-NUKG5QW4.js → ebuilder-form-HTGI63UU.js} +3 -3
- package/dist/chunks/{ehr-2STW5ILH.js → ehr-GYXIYH3A.js} +2 -2
- package/dist/chunks/env-DW3G4HUO.js +19 -0
- package/dist/chunks/{esb-UUDQUNKY.js → esb-I5INCEFE.js} +4 -4
- package/dist/chunks/factory-TKXGXS6L.js +30 -0
- package/dist/chunks/{fna-6RCY4NU5.js → fna-K2MHTQ24.js} +2 -2
- package/dist/chunks/group-chat-VYBNMJLG.js +18 -0
- package/dist/chunks/{host-FWDOSRH5.js → host-256GVCAH.js} +3 -3
- package/dist/chunks/{host-EJZFROU3.js → host-D5SERMAJ.js} +3 -3
- package/dist/chunks/{host-RHM3NPXV.js → host-JM4YVHFM.js} +3 -3
- package/dist/chunks/{host-7RXEDMAH.js → host-L3VLITMT.js} +3 -3
- package/dist/chunks/{host-UFF33TWJ.js → host-ZUQTJYYK.js} +3 -3
- package/dist/chunks/{hrm-PKJ6ELSH.js → hrm-VLRBKTZB.js} +3 -3
- package/dist/chunks/{im-YJAXS7CO.js → im-QK3VTLXK.js} +8 -8
- package/dist/chunks/{invoice-HWVSXGX6.js → invoice-T5JTA32H.js} +3 -3
- package/dist/chunks/{jiuchuanhui-L7VKILZB.js → jiuchuanhui-P2FNEQVZ.js} +2 -2
- package/dist/chunks/{lazy-dispatch-NMYEIHRH.js → lazy-dispatch-XRRI46M6.js} +5 -5
- package/dist/chunks/{mail-B42RRU7G.js → mail-3Y7MJ6PS.js} +9 -4
- package/dist/chunks/{meeting-CFYVBJSS.js → meeting-M7LTXGJE.js} +11 -10
- package/dist/chunks/{okr-GCLF6V7W.js → okr-OBC6S67L.js} +2 -2
- package/dist/chunks/{plan-ZVL7GBRI.js → plan-5H6I53SK.js} +2 -2
- package/dist/chunks/{project-4YWAAE2K.js → project-L75QZV4S.js} +2 -2
- package/dist/chunks/{registry-EP4VTHZS.js → registry-KMHCPAAD.js} +9 -8
- package/dist/chunks/{registry-OZSMDVPP.js → registry-QGHFNUWS.js} +2 -2
- package/dist/chunks/{registry-FRJ6H7BT.js → registry-RXPN6BBT.js} +4 -4
- package/dist/chunks/{registry-SG2PGVCF.js → registry-UGRBCBHO.js} +3 -3
- package/dist/chunks/{room-recommend-R3JVUUAC.js → room-recommend-A3UDC3CA.js} +4 -4
- package/dist/chunks/{schedule-NIJBTYDO.js → schedule-5GCACCK7.js} +4 -4
- package/dist/chunks/{workflow-BB24DFBY.js → workflow-M5ZJ7DWF.js} +11 -8
- package/dist/core/output.js +16 -6
- package/dist/index.js +33 -28
- package/dist/internal/e10/auth/commands.js +109 -56
- package/dist/internal/e10/auth/messages.js +1 -0
- package/dist/internal/e10/auth/session.js +11 -1
- package/dist/internal/e10/auth/xiaoe.js +29 -4
- package/dist/internal/e10/auth-recovery.js +13 -6
- package/dist/internal/e10/client.js +13 -0
- package/dist/internal/e10/context.js +24 -3
- package/dist/internal/e10/request-runtime.js +12 -1
- package/dist/shortcuts/archive/host.js +2 -0
- package/dist/shortcuts/archive/manifest.js +1 -1
- package/dist/shortcuts/archive/operations/borrow-list.js +13 -1
- package/dist/shortcuts/archive/operations/flow-url.js +1 -1
- package/dist/shortcuts/archive/operations/prelib.js +472 -0
- package/dist/shortcuts/archive/operations/registry.js +2 -0
- package/dist/shortcuts/archive/operations/upload.js +1 -1
- package/dist/shortcuts/asset-idle/errors.js +44 -0
- package/dist/shortcuts/asset-idle/host.js +161 -0
- package/dist/shortcuts/asset-idle/index.js +114 -0
- package/dist/shortcuts/asset-idle/manifest.js +159 -0
- package/dist/shortcuts/asset-idle/operations/idle.js +47 -0
- package/dist/shortcuts/asset-idle/operations/registry.js +33 -0
- package/dist/shortcuts/asset-idle/operations/shared.js +142 -0
- package/dist/shortcuts/asset-idle/operations/types.js +9 -0
- package/dist/shortcuts/asset-idle/operations.js +15 -0
- package/dist/shortcuts/cowork/operations/item-write.js +10 -2
- package/dist/shortcuts/index.js +5 -0
- package/dist/shortcuts/mail/operations/mail-send.js +8 -2
- package/dist/shortcuts/meeting/operations/eb/catalog.js +5 -1
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +381 -104
- package/dist/shortcuts/meeting/operations/eb/crm-search.js +87 -5
- package/dist/shortcuts/meeting/operations/eb/flow-memory.js +49 -0
- package/dist/shortcuts/meeting/operations/eb/group-chat.js +583 -0
- package/dist/shortcuts/meeting/operations/eb/index.js +2 -0
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +19 -3
- package/dist/shortcuts/meeting/operations/eb/room-summary.js +2 -0
- package/dist/shortcuts/meeting/operations/eb/usage-cache.js +99 -0
- package/dist/shortcuts/meeting/operations/from-group-chat.js +35 -12
- package/dist/shortcuts/workflow/host.js +3 -0
- package/dist/shortcuts/workflow/index.js +1 -1
- package/dist/shortcuts/workflow/operations/form.js +65 -171
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +129 -27
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupStore.js +6 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +60 -117
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +44 -7
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +37 -29
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +48 -8
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +1 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +22 -18
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +17 -9
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +34 -10
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +5 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +9 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +122 -20
- package/docs/_catalog.md +3 -0
- package/docs/asset-idle.md +45 -0
- package/docs/e10-auth.md +16 -1
- package/docs/todo-smart-convert/subgroup-rules.md +1 -1
- package/package.json +1 -1
- package/scripts/mcp-meeting.mjs +234 -0
- package/scripts/package-skill.mjs +2 -0
- package/skill-template/business-info.json +14 -1
- package/skill-template/domains/asset-idle.md +13 -0
- package/skill-template/domains/wenshuding.md +3 -0
- package/skills/weaver-e10-archive-oa/SKILL.md +1 -0
- package/skills/weaver-e10-archive-oa/references/my-borrow.md +49 -0
- package/skills/weaver-e10-asset-idle/SKILL.md +73 -0
- package/skills/weaver-e10-asset-idle/references/idle-list.md +62 -0
- package/skills/weaver-e10-asset-idle/references/idle-stats-and-charts.md +65 -0
- package/skills/weaver-e10-asset-idle/references/source-manifest.json +193 -0
- package/skills/weaver-e10-asset-idle/references/widget-style.md +205 -0
- package/skills/weaver-e10-caizhilian/SKILL.md +29 -11
- package/skills/weaver-e10-caizhilian/references/auth-and-install.md +12 -0
- package/skills/weaver-e10-caizhilian/references/files.md +1 -1
- package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +1 -1
- package/skills/weaver-e10-caizhilian/references/source-manifest.json +45 -45
- package/skills/weaver-e10-cowork/SKILL.md +2 -0
- package/skills/weaver-e10-cowork/references/cowork-write.md +1 -1
- package/skills/weaver-e10-jucailin/SKILL.md +2 -0
- package/skills/weaver-e10-jucailin/references/recruit-read.md +2 -0
- package/skills/weaver-e10-jucailin/references/recruit-write.md +1 -1
- package/skills/weaver-e10-mail/SKILL.md +3 -0
- package/skills/weaver-e10-mail/references/mail-send.md +2 -0
- package/skills/weaver-e10-meeting/SKILL.md +88 -25
- package/skills/weaver-e10-meeting/references/meeting-minute-file.md +25 -11
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-shared/SKILL.md +5 -5
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +3 -3
- package/skills/weaver-e10-wenshuding/SKILL.md +29 -6
- package/skills/weaver-e10-wenshuding/references/archive-borrow-list.md +14 -0
- package/skills/weaver-e10-wenshuding/references/archive-download.md +2 -0
- package/skills/weaver-e10-wenshuding/references/archive-flow.md +1 -1
- package/skills/weaver-e10-wenshuding/references/archive-prelib-arrange.md +76 -0
- package/skills/weaver-e10-wenshuding/references/archive-prelib-list.md +107 -0
- package/skills/weaver-e10-wenshuding/references/archive-prelib-open.md +59 -0
- package/skills/weaver-e10-wenshuding/references/archive-upload.md +4 -1
- package/skills/weaver-e10-wenshuding/references/source-manifest.json +108 -65
- package/skills/weaver-e10-workflow/SKILL.md +9 -2
- package/skills/weaver-e10-workflow/aigroup/default.json +166 -19
- package/skills/weaver-e10-workflow/references/todo-smart-convert.md +72 -34
- package/skills/weaver-e10-workflow/tools/todo-serve.cjs +113 -25
- package/skills/weaver-e10-workflow/tools/todo-summary.cjs +350 -50
- package/dist/chunks/{chunk-2LV74IWP.js → chunk-E7OK5WD5.js} +5 -5
|
@@ -3,13 +3,43 @@ import { workflowValidationError } from '../../errors.js';
|
|
|
3
3
|
import { asRecord, readBoundedInt } from './todoSmartConvert.core.js';
|
|
4
4
|
import { orderGroupsByIntent, readGroupOrder } from './todoSmartConvert.group.js';
|
|
5
5
|
import { detectBusinessKind, needsRequestSummary } from './businessRules.js';
|
|
6
|
-
import { openSummaryCache } from './summaryCache.js';
|
|
6
|
+
import { openSummaryCache, promptTagOf } from './summaryCache.js';
|
|
7
|
+
import { openDetailCache, detailMaterialTagOf } from './todoDetailCache.js';
|
|
7
8
|
import { loadDetailContextProvider, stampsOfRows } from './todoSmartConvert.detail.js';
|
|
8
9
|
import { requestItemsOf, rowsOfGroup, runSmartGroup, totalTodoCount, } from './todoSmartConvert.smartGroup.js';
|
|
9
10
|
/** 该组是否需要 AI 生成单条摘要(规则 needSummary 决定,默认需要)。 */
|
|
10
11
|
function needAiSummary(workflowName) {
|
|
11
12
|
return activeRuleFor(workflowName)?.needSummary ?? true;
|
|
12
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* 单条摘要的目标字数:**入参 > aigroup 规则(default.json / user-intents.json)> 内置兜底**。
|
|
16
|
+
*
|
|
17
|
+
* 规则是主要来源(2026-10-30 用户要求「摘要规则必须从 JSON 取」),入参只在用户当轮明确给了
|
|
18
|
+
* 口径(如「每条 30 字」)时覆盖它;两者都没有才用兜底 50。
|
|
19
|
+
*/
|
|
20
|
+
function requestMaxCharsFor(args, workflowName) {
|
|
21
|
+
return readBoundedInt(args.summaryMaxChars ?? activeRuleFor(workflowName)?.summaryMaxChars, 'summaryMaxChars', DEFAULT_REQUEST_SUMMARY_MAX, 10, 500);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 缓存**命中比对**用的字数口径:只在用户显式传了 `summaryMaxChars`(`--summary-chars`)时返回,
|
|
25
|
+
* 否则返回 undefined(= 不按字数过滤)。
|
|
26
|
+
* 2026-11-02 用户定稿:缓存失效只由 requestSummaryPrompt 调整触发;
|
|
27
|
+
* 规则/默认字数变化(如 50→150)不作废历史摘要,只有用户显式要求不同字数时才重写。
|
|
28
|
+
*/
|
|
29
|
+
function explicitMaxCharsFor(args, workflowName) {
|
|
30
|
+
const explicit = args.summaryMaxChars;
|
|
31
|
+
if (explicit === undefined || explicit === null || String(explicit).trim() === '')
|
|
32
|
+
return undefined;
|
|
33
|
+
return requestMaxCharsFor(args, workflowName);
|
|
34
|
+
}
|
|
35
|
+
/** 该组的单条摘要口径提示(规则 `requestSummaryPrompt`,空串表示没有)。 */
|
|
36
|
+
function requestPromptFor(workflowName) {
|
|
37
|
+
return (activeRuleFor(workflowName)?.requestSummaryPrompt ?? '').trim();
|
|
38
|
+
}
|
|
39
|
+
/** 批次里出现过的目标字数(去重、升序);用于在 meta 里表达「各组口径可能不同」。 */
|
|
40
|
+
function distinctBatchMaxChars(batches) {
|
|
41
|
+
return [...new Set(batches.map((batch) => batch.suggestedMaxChars))].sort((left, right) => left - right);
|
|
42
|
+
}
|
|
13
43
|
const DEFAULT_MAX_CHARS_PER_BATCH = 24_000;
|
|
14
44
|
const DEFAULT_MAX_ITEMS_PER_BATCH = 100;
|
|
15
45
|
const DEFAULT_REQUEST_SUMMARY_MAX = 50;
|
|
@@ -194,18 +224,42 @@ export async function runSummaryContext(args, env, prefetched) {
|
|
|
194
224
|
.reduce((sum, group) => sum + rowsOfGroup(group).length, 0);
|
|
195
225
|
// 优先组是叠加视图 ⇒ 同一条待办会在两个位置各出现一次,统计口径必须去重。
|
|
196
226
|
const requestIds = [...new Set(units.flatMap((unit) => unit.rows.map((row) => row.requestId)))].filter(Boolean);
|
|
197
|
-
// 单条摘要目标字数:用户给了口径(如「每条 30 字」)就用用户的,没给才用默认 50。
|
|
198
|
-
const summaryMaxChars = readBoundedInt(args.summaryMaxChars, 'summaryMaxChars', DEFAULT_REQUEST_SUMMARY_MAX, 10, 500);
|
|
199
227
|
// 摘要缓存命中时直接回填,未命中的条目才进入 AI 批次。
|
|
200
228
|
// 预填必须在「准备详情」之前做——已有摘要的条目根本不需要详情(详情只为写摘要服务)。
|
|
201
229
|
const summaryCache = await openSummaryCache(env.ctx);
|
|
230
|
+
// 摘要条目的素材特征值(dv):读详情缓存(本地文件,不发请求)算当前素材哈希,
|
|
231
|
+
// 与条目写入时的素材比对——素材变了 ⇒ 旧摘要失效重写(2026-11-01 根治)。
|
|
232
|
+
const detailCacheForTag = await openDetailCache(env.ctx);
|
|
233
|
+
const materialTagOfRow = (requestId, stamp) => {
|
|
234
|
+
const entry = detailCacheForTag.get(requestId, stamp);
|
|
235
|
+
return entry ? detailMaterialTagOf(entry) : undefined;
|
|
236
|
+
};
|
|
202
237
|
/** 已有摘要(缓存命中 / collect 已回填)而跳过 AI 的条数 */
|
|
203
238
|
let prefilled = 0;
|
|
204
239
|
const prefilledIds = new Set();
|
|
205
240
|
const needsDetail = [];
|
|
241
|
+
/**
|
|
242
|
+
* 优先组里的条目在**工作流组**里还有一份副本——它们的规则口径必须按**原工作流**解析,
|
|
243
|
+
* 而不是按「建议优先处理的流程」这个组名(它匹配不到规则 ⇒ pk/字数都取错)。
|
|
244
|
+
* 先建 requestId → 工作流组名 的映射,优先组的行查这张表。
|
|
245
|
+
*/
|
|
246
|
+
const workflowTitleByRequest = new Map();
|
|
206
247
|
for (const unit of units) {
|
|
248
|
+
if (unit.groupId === 'priority')
|
|
249
|
+
continue;
|
|
207
250
|
for (const row of unit.rows) {
|
|
208
|
-
|
|
251
|
+
if (row.requestId && !workflowTitleByRequest.has(row.requestId)) {
|
|
252
|
+
workflowTitleByRequest.set(row.requestId, unit.groupTitle);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const ruleNameForRow = (unit, row) => unit.groupId === 'priority'
|
|
257
|
+
? (workflowTitleByRequest.get(row.requestId ?? '') ?? unit.groupTitle)
|
|
258
|
+
: unit.groupTitle;
|
|
259
|
+
for (const unit of units) {
|
|
260
|
+
for (const row of unit.rows) {
|
|
261
|
+
const name = ruleNameForRow(unit, row);
|
|
262
|
+
const cached = summaryCache.get(row.requestId, row.lastFeedBackDateTime, explicitMaxCharsFor(args, name), promptTagOf(requestPromptFor(name)), materialTagOfRow(row.requestId, row.lastFeedBackDateTime));
|
|
209
263
|
if (cached) {
|
|
210
264
|
row.summary = cached;
|
|
211
265
|
prefilled += 1;
|
|
@@ -232,7 +286,10 @@ export async function runSummaryContext(args, env, prefetched) {
|
|
|
232
286
|
* 所以这里跳过不会让任何一份摘要空缺,只是省掉一次重复的上下文。
|
|
233
287
|
*/
|
|
234
288
|
const claimed = new Set();
|
|
235
|
-
|
|
289
|
+
// 优先组**最后**处理:它的条目在工作流批次里已经带着各自规则的口径(requestSummaryPrompt)
|
|
290
|
+
// 被写过一遍;若优先组先处理,口径会丢失(优先组标题匹配不到规则)。剩下的未认领条目才进优先批次。
|
|
291
|
+
const orderedUnits = [...units].sort((a, b) => Number(a.groupId === 'priority') - Number(b.groupId === 'priority'));
|
|
292
|
+
for (const unit of orderedUnits) {
|
|
236
293
|
let bucket = [];
|
|
237
294
|
let chars = 0;
|
|
238
295
|
let seq = 0;
|
|
@@ -240,6 +297,9 @@ export async function runSummaryContext(args, env, prefetched) {
|
|
|
240
297
|
if (bucket.length === 0)
|
|
241
298
|
return;
|
|
242
299
|
seq += 1;
|
|
300
|
+
// 批次口径按本批**第一条**所属工作流的规则取(正常批次不跨工作流;
|
|
301
|
+
// 优先组批次兜底时可能混多条工作流,此时以首条为准,逐条口径随 items 上下文仍可辨)
|
|
302
|
+
const firstName = ruleNameForRow(unit, { requestId: bucket[0].requestId });
|
|
243
303
|
batches.push({
|
|
244
304
|
batchId: `${unit.subGroupId}#${seq}`,
|
|
245
305
|
groupId: unit.groupId,
|
|
@@ -250,7 +310,8 @@ export async function runSummaryContext(args, env, prefetched) {
|
|
|
250
310
|
charCount: chars,
|
|
251
311
|
kind: unit.kind,
|
|
252
312
|
...(unit.kind === 'internal-message' ? { hint: INTERNAL_MESSAGE_SUMMARY_HINT } : {}),
|
|
253
|
-
|
|
313
|
+
...(requestPromptFor(firstName) ? { prompt: requestPromptFor(firstName) } : {}),
|
|
314
|
+
suggestedMaxChars: requestMaxCharsFor(args, firstName),
|
|
254
315
|
items: bucket,
|
|
255
316
|
});
|
|
256
317
|
bucket = [];
|
|
@@ -329,8 +390,10 @@ export async function runSummaryContext(args, env, prefetched) {
|
|
|
329
390
|
titleCopied,
|
|
330
391
|
/** 本地摘要命中、跳过 AI 的条数 */
|
|
331
392
|
prefilled,
|
|
332
|
-
/**
|
|
333
|
-
|
|
393
|
+
/** 单条摘要的目标字数:口径一致时给数字,各组口径不同时给 distinct 列表 */
|
|
394
|
+
...(distinctBatchMaxChars(batches).length === 1
|
|
395
|
+
? { suggestedMaxChars: distinctBatchMaxChars(batches)[0] }
|
|
396
|
+
: { suggestedMaxCharsList: distinctBatchMaxChars(batches) }),
|
|
334
397
|
/** 内部留言类批次:摘要要按「最低推理强度」写 */
|
|
335
398
|
internalMessageBatches: batches.filter((batch) => batch.kind === 'internal-message').length,
|
|
336
399
|
skippedSatisfaction,
|
|
@@ -388,16 +451,42 @@ export function readAiSummaries(value) {
|
|
|
388
451
|
export async function runSummary(args, env) {
|
|
389
452
|
const groups = await readFinalGroups(args, env);
|
|
390
453
|
const summaries = readAiSummaries(args.aiSummaries);
|
|
391
|
-
// 单条摘要目标字数:与 `runSummaryContext`
|
|
392
|
-
|
|
454
|
+
// 单条摘要目标字数:与 `runSummaryContext` 保持同一口径(入参 > 规则 > 兜底)。
|
|
455
|
+
/** 按工作流名缓存本组的字数口径,避免逐行解析规则。 */
|
|
456
|
+
const maxCharsByGroup = new Map();
|
|
457
|
+
const maxCharsFor = (workflowName) => {
|
|
458
|
+
const hit = maxCharsByGroup.get(workflowName);
|
|
459
|
+
if (hit !== undefined)
|
|
460
|
+
return hit;
|
|
461
|
+
const value = requestMaxCharsFor(args, workflowName);
|
|
462
|
+
maxCharsByGroup.set(workflowName, value);
|
|
463
|
+
return value;
|
|
464
|
+
};
|
|
393
465
|
// ★ 本次 AI 写出来的摘要顺手进缓存(key=requestId__lastFeedBackDateTime),
|
|
394
466
|
// 下次同一条流程的待办再来时可以复用摘要。
|
|
395
467
|
const summaryCache = await openSummaryCache(env.ctx);
|
|
468
|
+
// 素材特征值(与 summaryContext 同口径):写入时锚定当前详情素材,读取时比对失效
|
|
469
|
+
const detailCacheForTag = await openDetailCache(env.ctx);
|
|
470
|
+
const materialTagOfRow = (requestId, stamp) => {
|
|
471
|
+
const entry = detailCacheForTag.get(requestId, stamp);
|
|
472
|
+
return entry ? detailMaterialTagOf(entry) : undefined;
|
|
473
|
+
};
|
|
396
474
|
let cacheWritten = 0;
|
|
397
475
|
// 缓存预填(与 `summaryContext` 同口径):只跑 `summary` 阶段时也能拿到已写好的摘要,
|
|
398
476
|
// 否则重跑 finish(不带 aiSummaries)会把整份摘要清成空串。
|
|
399
477
|
// 满意度类不走 AI、也不读缓存:直接用标题当摘要,保证 `summary` 不为空。
|
|
400
478
|
let titleSummary = 0;
|
|
479
|
+
// 优先组的行按**原工作流**解析口径(组名「建议优先处理的流程」匹配不到规则)。
|
|
480
|
+
const wfTitleByRequest = new Map();
|
|
481
|
+
for (const group of groups) {
|
|
482
|
+
if (group.topPriority)
|
|
483
|
+
continue;
|
|
484
|
+
for (const row of rowsOfGroup(group)) {
|
|
485
|
+
if (row?.requestId && !wfTitleByRequest.has(row.requestId))
|
|
486
|
+
wfTitleByRequest.set(row.requestId, group.title);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
const ruleNameForGroup = (group, row) => group.topPriority ? (wfTitleByRequest.get(row?.requestId ?? '') ?? group.title) : group.title;
|
|
401
490
|
for (const group of groups) {
|
|
402
491
|
if (!needAiSummary(group.title)) {
|
|
403
492
|
for (const row of rowsOfGroup(group)) {
|
|
@@ -412,7 +501,8 @@ export async function runSummary(args, env) {
|
|
|
412
501
|
for (const row of rowsOfGroup(group)) {
|
|
413
502
|
if (!row?.requestId || (row.summary ?? '').trim())
|
|
414
503
|
continue;
|
|
415
|
-
const
|
|
504
|
+
const name = ruleNameForGroup(group, row);
|
|
505
|
+
const cached = summaryCache.get(row.requestId, row.lastFeedBackDateTime, explicitMaxCharsFor(args, name), promptTagOf(requestPromptFor(name)), materialTagOfRow(row.requestId, row.lastFeedBackDateTime));
|
|
416
506
|
if (cached) {
|
|
417
507
|
row.summary = cached;
|
|
418
508
|
}
|
|
@@ -440,7 +530,7 @@ export async function runSummary(args, env) {
|
|
|
440
530
|
let prefilledKept = 0;
|
|
441
531
|
/** 统计用:同一条待办在叠加视图里出现两次,计数只算一次 */
|
|
442
532
|
const countedIds = new Set();
|
|
443
|
-
const applyAll = (rows) => {
|
|
533
|
+
const applyAll = (rows, meta) => {
|
|
444
534
|
for (const row of rows) {
|
|
445
535
|
const summary = summaries.get(row.requestId) ?? '';
|
|
446
536
|
const firstTime = !row.requestId || !countedIds.has(row.requestId);
|
|
@@ -461,10 +551,10 @@ export async function runSummary(args, env) {
|
|
|
461
551
|
if (summary) {
|
|
462
552
|
if (firstTime) {
|
|
463
553
|
filled += 1;
|
|
464
|
-
if (summary.length >
|
|
554
|
+
if (summary.length > meta(row).maxChars)
|
|
465
555
|
tooLong += 1;
|
|
466
556
|
}
|
|
467
|
-
if (summaryCache.set(row.requestId, row.lastFeedBackDateTime, summary,
|
|
557
|
+
if (summaryCache.set(row.requestId, row.lastFeedBackDateTime, summary, meta(row).maxChars, meta(row).pk, materialTagOfRow(row.requestId, row.lastFeedBackDateTime)))
|
|
468
558
|
cacheWritten += 1;
|
|
469
559
|
}
|
|
470
560
|
else if (firstTime) {
|
|
@@ -478,18 +568,30 @@ export async function runSummary(args, env) {
|
|
|
478
568
|
if (!needAiSummary(group.title))
|
|
479
569
|
continue;
|
|
480
570
|
if (group.topPriority) {
|
|
481
|
-
applyAll(rowsOfGroup(group))
|
|
571
|
+
applyAll(rowsOfGroup(group), (row) => {
|
|
572
|
+
const name = ruleNameForGroup(group, row);
|
|
573
|
+
return { maxChars: maxCharsFor(name), pk: promptTagOf(requestPromptFor(name)) };
|
|
574
|
+
});
|
|
482
575
|
continue;
|
|
483
576
|
}
|
|
484
577
|
for (const item of group.displayItems) {
|
|
485
578
|
if (item.type === 'group') {
|
|
486
|
-
applyAll(item.requestDatas)
|
|
579
|
+
applyAll(item.requestDatas, (row) => {
|
|
580
|
+
const name = ruleNameForGroup(group, row);
|
|
581
|
+
return { maxChars: maxCharsFor(name), pk: promptTagOf(requestPromptFor(name)) };
|
|
582
|
+
});
|
|
487
583
|
continue;
|
|
488
584
|
}
|
|
489
585
|
// 单独展示的一条:只回填它自己
|
|
490
|
-
applyAll([item.requestData])
|
|
586
|
+
applyAll([item.requestData], (row) => {
|
|
587
|
+
const name = ruleNameForGroup(group, row);
|
|
588
|
+
return { maxChars: maxCharsFor(name), pk: promptTagOf(requestPromptFor(name)) };
|
|
589
|
+
});
|
|
491
590
|
}
|
|
492
591
|
}
|
|
592
|
+
/** 告警与 meta 里展示的口径:各组一致就给数字,不一致就给 `a/b` 形式。 */
|
|
593
|
+
const usedMaxChars = [...new Set(maxCharsByGroup.values())].sort((left, right) => left - right);
|
|
594
|
+
const maxCharsHint = usedMaxChars.length > 0 ? usedMaxChars.join('/') : String(DEFAULT_REQUEST_SUMMARY_MAX);
|
|
493
595
|
if (empty > 0) {
|
|
494
596
|
warnings.push(`有 ${empty} 条没有拿到 AI 摘要,summary 保持空串(未用标题兜底、未编造)`);
|
|
495
597
|
}
|
|
@@ -497,7 +599,7 @@ export async function runSummary(args, env) {
|
|
|
497
599
|
warnings.push(`有 ${titleKept} 条详情为空的条目保留 CLI 填充的标题摘要(不参与 AI 回填)`);
|
|
498
600
|
}
|
|
499
601
|
if (tooLong > 0) {
|
|
500
|
-
warnings.push(`有 ${tooLong} 条摘要超过目标长度 ${
|
|
602
|
+
warnings.push(`有 ${tooLong} 条摘要超过目标长度 ${maxCharsHint} 字(可按用户口径调整),已原样保留;`
|
|
501
603
|
+ '如需控制长度请重跑模型侧(CLI 不改写文案)');
|
|
502
604
|
}
|
|
503
605
|
if (prefilledKept > 0) {
|
|
@@ -527,8 +629,8 @@ export async function runSummary(args, env) {
|
|
|
527
629
|
meta: {
|
|
528
630
|
summarized: filled,
|
|
529
631
|
empty,
|
|
530
|
-
/**
|
|
531
|
-
suggestedMaxChars:
|
|
632
|
+
/** 单条摘要的目标字数:各组口径一致时给数字,不一致时给 distinct 列表 */
|
|
633
|
+
...(usedMaxChars.length === 1 ? { suggestedMaxChars: usedMaxChars[0] } : { suggestedMaxCharsList: usedMaxChars }),
|
|
532
634
|
titleCopied: titleKept,
|
|
533
635
|
/** 本地摘要缓存命中、未重新生成的条数 */
|
|
534
636
|
prefilled: prefilledKept,
|
package/docs/_catalog.md
CHANGED
|
@@ -50,3 +50,6 @@
|
|
|
50
50
|
| Agent 调用采知连文档中心 CLI 的入口路由 | `skills/weaver-e10-caizhilian/SKILL.md` |
|
|
51
51
|
| Agent 调用采知连文档中心 CLI 的具体 reference | `skills/weaver-e10-caizhilian/references/` |
|
|
52
52
|
| 将新业务模块文档或代码同步转换为 weaver-work-cli 业务 CLI 与 Skill、检测源资料更新 | `skills/weaver-e10-skill-maker/SKILL.md` |
|
|
53
|
+
| E10 闲置资产判定结果查询(分页获取/聚合统计/三张图口径)、asset-idle operation | `docs/asset-idle.md` |
|
|
54
|
+
| Agent 调用闲置资产统计 CLI 的入口路由 | `skills/weaver-e10-asset-idle/SKILL.md` |
|
|
55
|
+
| Agent 调用闲置资产统计 CLI 的具体 reference | `skills/weaver-e10-asset-idle/references/` |
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# asset-idle — E10 闲置资产判定结果查询 CLI
|
|
2
|
+
|
|
3
|
+
## 模块来源
|
|
4
|
+
|
|
5
|
+
源资料为用户级技能 `weaver-e10-ziguanjia-idle`(SKILL.md + references/api.md + references/widget-style.md,2026-10-27 起实测固化的闲置资产统计口径)。按 `weaver-e10-skill-maker` 流程同步生成 CLI 模块与 Skill `weaver-e10-asset-idle`(CLI 命令后缀与 Skill 后缀均为 `asset-idle`,2026-11-02 用户确认)。
|
|
6
|
+
|
|
7
|
+
- 源接口:`POST {baseurl}/api/esb/server/event/triggerActionFlow`(动作流「自用-skill获取闲置资产」)
|
|
8
|
+
- 源口径:闲置判定由服务端动作流完成,CLI 只取数与聚合,不重算
|
|
9
|
+
|
|
10
|
+
## 命令结构
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
weaver-work-cli asset-idle schema
|
|
14
|
+
weaver-work-cli asset-idle run <operation> --input-json '<json>'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## operation 表
|
|
18
|
+
|
|
19
|
+
| operation | 风险 | 说明 |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| `asset-idle.idle.list` | read | 触发动作流,按 `hasMore` 自动翻页(每页 1000 条,服务端固定),合并各页 `idleAssets` 为结果集 |
|
|
22
|
+
| `asset-idle.idle.stats` | read | 在合并结果上聚合:`czjyCounts`(0=待调拨/1=待调剂/2=待评估/缺失=无法判断)、`typeCounts`(zclx 降序)、`agingBuckets`(≤30/31–90/91–180/181–365/>365 天,边界归前一档,0 档不返回) |
|
|
23
|
+
|
|
24
|
+
## 何时用哪个 operation
|
|
25
|
+
|
|
26
|
+
- 只要统计出图 → `idle.stats`(一次拿到全部分组,不要先 list 再自己数)
|
|
27
|
+
- 要明细、逐条判定依据 → `idle.list`
|
|
28
|
+
- 两个 operation 输入一致:`esbFlowId`(必填,1-32 位数字字符串)、`maxPages`(默认 20,1-100)
|
|
29
|
+
|
|
30
|
+
## 输入与字段口径
|
|
31
|
+
|
|
32
|
+
- `esbFlowId` 必须由调用方传入;按名称解析动作流 ID 需要的 ESB 应用列表查询接口在源资料中只有旧版 e10-cli 命令名,未实现(schema `withheldOperations: asset-idle.flow.resolve`)
|
|
33
|
+
- 判定字段直接使用:`xzsz`(闲置时长/天)、`pdyj`(判定依据)、`zxd`(置信度)、`czjy`(处置建议编码);缺失标「无法判断」并排除出统计
|
|
34
|
+
- 恒为空展示字段(`czjy_obj`/`xzdj_obj`/`yjzczt_obj`/`czlc`/`czlc_obj`/`zcxzczrq`)不透出;禁止反推 `idle_date`/`stockindate`/`use_term`(`disabledCapabilities`)
|
|
35
|
+
|
|
36
|
+
## 已知限制
|
|
37
|
+
|
|
38
|
+
- 会话失效判据为接口返回 `{"error":"authentication required","code":-2}`(HTTP 200),与标准 401 并行识别
|
|
39
|
+
- `truncated=true`(达到 maxPages 且 hasMore 仍 true)时结果不是全集,调用方应增大 `maxPages` 重查
|
|
40
|
+
- 动作流数据规模随系统台账变化(2026-10-30 起为判定结果集,384 条;此前为原始台账 2885 条)
|
|
41
|
+
|
|
42
|
+
## 安全边界
|
|
43
|
+
|
|
44
|
+
- 只读模块,无写链;认证请求头由 `weaver-work-cli` 统一托管,模块代码不读取、不拼装任何凭据
|
|
45
|
+
- 两个 operation 均为 `risk: read`,Agent 可直接执行;失败处置见 `skills/weaver-e10-asset-idle/references/`
|
package/docs/e10-auth.md
CHANGED
|
@@ -59,6 +59,19 @@ Chrome/Edge 原生确认框。宿主需要返回特定会话时,可以传
|
|
|
59
59
|
结束后页面会自动退回浏览器自定义协议打开方式,不直接显示网络请求错误。
|
|
60
60
|
智能小 E 统一登录不会经过浏览器,也不会显示“返回智能小 E”弹层;该弹层只属于用户另行显式发起的 `auth login` 浏览器 OAuth。
|
|
61
61
|
|
|
62
|
+
HTTPS E10 授权页跳转到 HTTP loopback 是桌面 OAuth 的标准回调方式:它属于浏览器
|
|
63
|
+
顶层导航,不按 Mixed Content 子资源请求处理。但部分 E10 环境或浏览器安全策略会把
|
|
64
|
+
回调改成 `https://127.0.0.1:<port>`;由于 CLI 本地监听器是 HTTP,此时浏览器会显示
|
|
65
|
+
`ERR_SSL_PROTOCOL_ERROR`,而旧版本 CLI 会继续等待直到超时。CLI 现在会识别发到 HTTP
|
|
66
|
+
端口的 TLS ClientHello,在 WorkBuddy/终端立即给出协议错配提示,并保留 30 秒恢复窗口:
|
|
67
|
+
只把浏览器当前地址开头明确改成 `http://`,保留完整路径和查询参数后回车,即可让同一次
|
|
68
|
+
登录继续完成;未处理则返回 `authentication/oauth_callback_protocol_mismatch`,不再挂起
|
|
69
|
+
5 分钟。长期应在 E10 认证服务侧保留传入的 HTTP loopback 回调,不要改写为 HTTPS。
|
|
70
|
+
若仍收不到回调,再检查 CLI 与浏览器是否在同一台机器,以及 E10 侧的回调白名单。
|
|
71
|
+
该排查只适用于显式浏览器登录,
|
|
72
|
+
`auth xiaoe` 和业务命令的 XiaoE 自动取登录信息使用独立处理函数,不创建本地 HTTP
|
|
73
|
+
服务,也不校验或使用 callback 端口。
|
|
74
|
+
|
|
62
75
|
XiaoE 宿主身份有两种判定入口:桌面客户端由 Agent 根据“智能小 e 客户端”“小 E”、`XiaoE`、`xiaoe-client` 等明确上下文判断,并传入 `--runtime-agent xiaoe`;网页端沿用原有环境变量规则,成对的 `XIAOE_USER_TOKEN` + `XIAOE_DOMAIN` 会被识别为 XiaoE。工作目录、客户端文件、内置 Node.js、历史登录、路径变量或单个凭据变量都不能单独用于判定。判定为 XiaoE 后,Agent 显式执行 `weaver-work-cli auth xiaoe`。
|
|
63
76
|
|
|
64
77
|
CLI 依次尝试成对的平台凭据、npm 包自带的 `scripts/xiaoe_env.py` 兼容路径、宿主 `xiaoeUserData/tokens.py`、桌面 XiaoE 客户端本地会话和 `WEAVER_XIAOE_*` ETEAMSID 映射。客户端会话优先读取 `XIAOE_USER_SETTINGS_PATH`,再从 `XIAOE_ROBOT_DATA_ROOT` / `XIAOE_ROBOT_ROOT` 查找,最后回退 macOS `~/Library/Application Support/XiaoE/Robots/<id>/user_settings.json` 或 Windows `%LOCALAPPDATA%/XiaoE/Robots/<id>/user_settings.json`。这些变量和文件只用于定位凭据,不能用于识别宿主。`XIAOE_DOMAIN` 是 XiaoE/erpa 换票服务地址,不是 E10 登录地址。
|
|
@@ -69,7 +82,9 @@ E10 目标地址按“显式 `--base-url` → 专用 `WEAVER_XIAOE_*` E10 配置
|
|
|
69
82
|
|
|
70
83
|
`auth xiaoe` 会先打印不含凭证的 preflight 摘要,给出 `authMode=xiaoe`、`credentialSourceDetected`、`attempted`、各凭据源检测结果和目标 `baseUrl`。进入脚本分支时会打印 `Trying XiaoE token script credential...`;进入内置 Node 会话分支时会打印 `Trying built-in XiaoE client session credential...`。成功结果和后续 `auth status` 会显示 `credentialSourceStep`、`credentialSourceType`、`credentialSourceLabel`。
|
|
71
84
|
|
|
72
|
-
|
|
85
|
+
XiaoE 换票结果中的 `expiryTimes` 按 Unix 毫秒时间戳持久化。宿主脚本未返回该字段时,CLI 与 XiaoE `tokens.py` 保持一致,按换票成功时间加 2 小时生成。业务命令加载会话后或长进程每次请求前发现当前时间达到 `expiryTimes` 时,会先静默重新换票;换票失败则停止本次业务请求,不会继续发送已知过期的 ETEAMSID。
|
|
86
|
+
|
|
87
|
+
业务命令缺少可读取的本地会话时,若当前运行时已通过 `XIAOE_*`、`--runtime-agent xiaoe` 等标记识别为 XiaoE,公共会话加载层会先静默尝试统一登录,成功后继续业务操作。失败时返回 `authentication/xiaoe_credential_unavailable`,并明确提示已尝试自动获取。显式 `--auth` 不触发此恢复;未识别到 XiaoE 的首次认证仍需显式选择登录方式。`auth status --no-check` 等本地状态查询不会主动登录。已有 XiaoE 会话被 E10 接口明确拒绝为失效时,公共请求运行时会根据持久化会话的 `agentType` / `loginSource` 静默换票并重放原请求一次;这条路径不依赖 shared Skill、Agent、`XIAOE_*` 或 `--runtime-agent xiaoe`。它不会打印中间登录步骤,不会自动打开浏览器,也不会对流式请求、网络错误、`partial` / `write_uncertain` 做重放。`--runtime-agent xiaoe` 表示当前运行时身份,不携带凭据。WorkBuddy、Codex 及其他智能体首次认证使用 `auth login --base-url <url> --agent_type <agent_type>` 的浏览器 OAuth 流程。
|
|
73
88
|
|
|
74
89
|
无法使用浏览器回调时,可以用 `auth set` 直接写入浏览器中复制到的
|
|
75
90
|
`ETEAMSID`。命令会调用 `teamsCheck` 解析当前用户、租户和组织信息,并写入
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
> **⚠️ 2026-10-28 起:分组规则已外置到 JSON,代码不再按业务类型分支。**
|
|
8
8
|
> 唯一真源是 `skills/weaver-e10-workflow/aigroup/default.json`(`{defaults, rules[]}`,随 skill 分发、只读),用户意图另存 **CLI 状态目录**下的 `aigroup/user-intents.json`(`weaver-work-cli aigroup set` 写入,刻意不落 skill 目录,避免被打包带走)。
|
|
9
|
-
> 现行规则要点:①没有 `aiGroup` ⇒ 全平铺;②`focus:true` ⇒ 按 `focusKeyword`(`[指标, 算子, 值]`,十种算子)确定性成一组,`groupTitle` 为组名,`sysArg` 指明指标取自列表属性还是流程详情;③`focus:false` ⇒ `aiContext`(流程标题/流程详情)交 AI 分组,**必须**给 `aiGroups`;④`focus:true` 收到多个计划子组时**只保留条数最多的一个**,其余平铺;⑤`aiGroups.seq` 已删除,只认 `requestIds`;⑥组摘要 `groupSummary.needAI:false` 时由 CLI 按 JSON `template` 渲染,`true` 时交 AI;⑦单条摘要由 `needSummary` 决定(false ⇒
|
|
9
|
+
> 现行规则要点:①没有 `aiGroup` ⇒ 全平铺;②`focus:true` ⇒ 按 `focusKeyword`(`[指标, 算子, 值]`,十种算子)确定性成一组,`groupTitle` 为组名,`sysArg` 指明指标取自列表属性还是流程详情;③`focus:false` ⇒ `aiContext`(流程标题/流程详情)交 AI 分组,**必须**给 `aiGroups`;④`focus:true` 收到多个计划子组时**只保留条数最多的一个**,其余平铺;⑤`aiGroups.seq` 已删除,只认 `requestIds`;⑥组摘要 `groupSummary.needAI:false` 时由 CLI 按 JSON `template` 渲染,`true` 时交 AI;⑦单条摘要由 `needSummary` 决定(false ⇒ 用标题);⑧详情素材要排掉哪些 formData 字段由 **`excludeOriginFormDataFields`**(字段名清单,fieldName 精确匹配,2026-10-30 定稿)维护,过滤后 ≤200 个字段、单值 ≤500 字。
|
|
10
10
|
> 下面各节是按类型描述的**历史口径**,仅作背景参考;与本节冲突时以本节与 `default.json` 为准。
|
|
11
11
|
|
|
12
12
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* weaver-work-cli 会议 MCP 服务器(stdio 传输,零依赖)。
|
|
4
|
+
*
|
|
5
|
+
* 把 CLI 的会议 operation 封装成 MCP tools,供小E / WorkBuddy 等宿主以 MCP 方式调用:
|
|
6
|
+
* {"mcpServers": {"weaver-meeting": {"command": "node", "args": ["<本文件路径>"]}}}
|
|
7
|
+
*
|
|
8
|
+
* 设计原则(2026-11-03 与用户对齐):
|
|
9
|
+
* - **不重新实现业务**:tools/call 内部直接 spawn 现有 CLI(`meeting run <op> --input-json`),
|
|
10
|
+
* 防呆闸门(timeConfirmed/roomConfirmed/confirm)、登录态、占用缓存、流程记忆全部原样继承;
|
|
11
|
+
* - **工具少而清晰**:MCP 工具发现靠模型读 tool schema,工具越多越乱 —— 只暴露 3 个高频动作;
|
|
12
|
+
* - **零依赖**:MCP stdio 就是 JSON-RPC 2.0(每行一个 JSON),手写握手即可,不引 SDK
|
|
13
|
+
* (私服有坏包前科,bundle 链路也不动)。
|
|
14
|
+
*
|
|
15
|
+
* 手工自测:
|
|
16
|
+
* printf '%s\n%s\n%s\n' \
|
|
17
|
+
* '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"0"}}}' \
|
|
18
|
+
* '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
|
|
19
|
+
* '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | node scripts/mcp-meeting.mjs
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { spawn } from 'node:child_process';
|
|
23
|
+
import { createInterface } from 'node:readline';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import { existsSync } from 'node:fs';
|
|
27
|
+
|
|
28
|
+
const SERVER_INFO = { name: 'weaver-meeting', version: '1.0.0' };
|
|
29
|
+
const PROTOCOL_VERSION = '2024-11-05';
|
|
30
|
+
|
|
31
|
+
// CLI 入口:优先同目录的 dist(打包后 scripts/ 与 dist/ 同级),找不到再试仓库根
|
|
32
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
const candidates = [
|
|
34
|
+
path.resolve(here, '..', 'dist', 'index.js'),
|
|
35
|
+
path.resolve(here, '..', '..', 'dist', 'index.js'),
|
|
36
|
+
];
|
|
37
|
+
const CLI_ENTRY = candidates.find((p) => existsSync(p));
|
|
38
|
+
if (!CLI_ENTRY) {
|
|
39
|
+
console.error(JSON.stringify({ error: `找不到 CLI 入口,试过:${candidates.join(', ')}` }));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 调用现有 CLI:一行 JSON 进,一行 JSON 出。 */
|
|
44
|
+
async function callCli(operation, input) {
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
const child = spawn(process.execPath, [
|
|
47
|
+
CLI_ENTRY, '--json', 'meeting', 'run', operation, '--input-json', JSON.stringify(input ?? {}),
|
|
48
|
+
], { windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
49
|
+
let stdout = '';
|
|
50
|
+
let stderr = '';
|
|
51
|
+
child.stdout.on('data', (chunk) => { stdout += String(chunk); });
|
|
52
|
+
child.stderr.on('data', (chunk) => { stderr += String(chunk); });
|
|
53
|
+
child.on('close', () => {
|
|
54
|
+
try {
|
|
55
|
+
resolve(JSON.parse(stdout));
|
|
56
|
+
} catch {
|
|
57
|
+
resolve({
|
|
58
|
+
ok: false,
|
|
59
|
+
error: { type: 'mcp', message: `CLI 输出不是 JSON(stderr: ${stderr.slice(0, 200)};stdout: ${stdout.slice(0, 200)})` },
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
child.on('error', (error) => {
|
|
64
|
+
resolve({ ok: false, error: { type: 'mcp', message: `CLI 启动失败:${error.message}` } });
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** 把 CLI 的返回压成 MCP text content(大 JSON 直接塞给模型会撑上下文,这里挑关键域)。 */
|
|
70
|
+
function summarize(result) {
|
|
71
|
+
const data = result?.data ?? {};
|
|
72
|
+
const brief = {
|
|
73
|
+
ok: result?.ok === true,
|
|
74
|
+
...(result?.ok ? {} : { error: result?.error }),
|
|
75
|
+
status: data.status,
|
|
76
|
+
meetingId: data.meetingId ?? undefined,
|
|
77
|
+
requestId: data.requestId ?? undefined,
|
|
78
|
+
workflowPending: data.workflowPending,
|
|
79
|
+
message: typeof data.message === 'string' ? data.message.slice(0, 400) : undefined,
|
|
80
|
+
// 选项类返回(时段/流程/会议室挑选)必须原样带给模型 —— 用户要在选项里点选
|
|
81
|
+
options: Array.isArray(data.options) ? data.options.slice(0, 6) : undefined,
|
|
82
|
+
timings: data.timings,
|
|
83
|
+
};
|
|
84
|
+
return { content: [{ type: 'text', text: JSON.stringify(brief, null, 1) }] };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// 工具清单:3 个高频动作,schema 直接搬 CLI 入参的精简版(描述要自足,模型只看得到这里)
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
const TOOLS = [
|
|
91
|
+
{
|
|
92
|
+
name: 'meeting_book_room',
|
|
93
|
+
description: '预订会议室(泛微 E10 EB 版):发起建会流程并**服务端直提**,不弹流程表单。'
|
|
94
|
+
+ '时间/会议室必须来自用户原话或在候选里点选的结果——'
|
|
95
|
+
+ '时间要传 timeConfirmed: true、会议室要传 roomConfirmed: true 才会真正提交,否则返回候选让用户挑。'
|
|
96
|
+
+ 'submit=false 时只做预检与推荐(不提交、不弹表单);submit=true 直接提交流程(同样不弹表单)。',
|
|
97
|
+
inputSchema: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
properties: {
|
|
100
|
+
mt_subject: { type: 'string', description: '会议名称/主题(必填)' },
|
|
101
|
+
begin_datetime: { type: 'string', description: '开始时间 yyyy-MM-dd HH:mm(必填,须来自用户确认)' },
|
|
102
|
+
end_datetime: { type: 'string', description: '结束时间 yyyy-MM-dd HH:mm' },
|
|
103
|
+
timeConfirmed: { type: 'boolean', description: '用户原话明确了时间、或在候选中点选后才传 true' },
|
|
104
|
+
address: { type: 'string', description: '会议室 id 或名称' },
|
|
105
|
+
roomConfirmed: { type: 'boolean', description: '用户明确指定该会议室、或点选后才传 true' },
|
|
106
|
+
mt_type: { type: 'string', description: '会议类型名称(流程表单必填项)' },
|
|
107
|
+
extraFields: { type: 'object', description: '流程表单的其他必填字段(按字段标签传,如 {"会议内容":"..."})' },
|
|
108
|
+
submit: { type: 'boolean', description: 'false=只出方案不提交(默认);true=直接提交流程(不弹表单)' },
|
|
109
|
+
},
|
|
110
|
+
required: ['mt_subject', 'begin_datetime'],
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'meeting_room_list',
|
|
115
|
+
description: '查询会议室列表(EB 版,含状态)。可按人数/关键词过滤。',
|
|
116
|
+
inputSchema: {
|
|
117
|
+
type: 'object',
|
|
118
|
+
properties: {
|
|
119
|
+
keyword: { type: 'string', description: '会议室名称关键词' },
|
|
120
|
+
pageSize: { type: 'number', description: '每页条数,默认 20' },
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'meeting_room_usage',
|
|
126
|
+
description: '查询某天某会议室(或不指定则全部会议室)的占用明细,用于回答"这个会议室明天有没有空"。',
|
|
127
|
+
inputSchema: {
|
|
128
|
+
type: 'object',
|
|
129
|
+
properties: {
|
|
130
|
+
day: { type: 'string', description: '日期 yyyy-MM-dd(必填)' },
|
|
131
|
+
address: { type: 'string', description: '会议室 id;不传 = 返回当天全部会议室占用' },
|
|
132
|
+
},
|
|
133
|
+
required: ['day'],
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
async function handleToolCall(name, args) {
|
|
139
|
+
if (name === 'meeting_book_room') {
|
|
140
|
+
const input = {
|
|
141
|
+
// ⛔ CLI 的语义键是 mt_name(FIELD_ALIASES 里「会议名称/会议主题/主题」都挂在它下面),
|
|
142
|
+
// mt_subject 不是合法入参 —— MCP 层做一次转换,别把模型给的名称弄丢。
|
|
143
|
+
mt_name: args.mt_subject ?? args.mt_name,
|
|
144
|
+
begin_datetime: args.begin_datetime,
|
|
145
|
+
...(args.end_datetime ? { end_datetime: args.end_datetime } : {}),
|
|
146
|
+
...(args.timeConfirmed ? { timeConfirmed: true } : {}),
|
|
147
|
+
...(args.address ? { address: args.address } : {}),
|
|
148
|
+
...(args.roomConfirmed ? { roomConfirmed: true } : {}),
|
|
149
|
+
...(args.mt_type ? { mt_type: args.mt_type } : {}),
|
|
150
|
+
...(args.extraFields && typeof args.extraFields === 'object' ? { extraFields: args.extraFields } : {}),
|
|
151
|
+
// MCP 口径(2026-11-03 用户确认):预约会议 = 直提流程,**永不弹流程表单**;
|
|
152
|
+
// 提交必须 confirm + submit_mode:direct,预检则不带 confirm(CLI 返回方案,同样不弹表单)。
|
|
153
|
+
...(args.submit === true ? { confirm: true, submit_mode: 'direct' } : {}),
|
|
154
|
+
};
|
|
155
|
+
return summarize(await callCli('meeting.eb.meeting.create.flow.quick', input));
|
|
156
|
+
}
|
|
157
|
+
if (name === 'meeting_room_list') {
|
|
158
|
+
return summarize(await callCli('meeting.eb.room.list', {
|
|
159
|
+
...(args.keyword ? { keyword: args.keyword } : {}),
|
|
160
|
+
...(args.pageSize ? { pageSize: args.pageSize } : {}),
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
if (name === 'meeting_room_usage') {
|
|
164
|
+
const day = String(args.day ?? '').slice(0, 10);
|
|
165
|
+
const input = args.address
|
|
166
|
+
? { beginDatetime: `${day} 00:00`, endDatetime: `${day} 23:59`, address: args.address }
|
|
167
|
+
: { begindatetime: `${day} 00:00`, enddatetime: `${day} 23:59`, pageSize: 200 };
|
|
168
|
+
const op = args.address ? 'meeting.eb.conflict.room' : 'meeting.eb.roomUsage.list';
|
|
169
|
+
return summarize(await callCli(op, input));
|
|
170
|
+
}
|
|
171
|
+
return { content: [{ type: 'text', text: `未知工具:${name}` }], isError: true };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// JSON-RPC over stdio(每行一个消息)
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
const rl = createInterface({ input: process.stdin });
|
|
178
|
+
const send = (message) => process.stdout.write(`${JSON.stringify(message)}\n`);
|
|
179
|
+
// stdin 关闭(宿主不再发消息)时要等在途的 tools/call 跑完再退——CLI 调用要十几秒,不能被杀
|
|
180
|
+
let pendingCalls = 0;
|
|
181
|
+
let stdinClosed = false;
|
|
182
|
+
|
|
183
|
+
rl.on('line', (line) => {
|
|
184
|
+
const text = line.trim();
|
|
185
|
+
if (text === '') return;
|
|
186
|
+
let message;
|
|
187
|
+
try {
|
|
188
|
+
message = JSON.parse(text);
|
|
189
|
+
} catch {
|
|
190
|
+
return; // 非 JSON 行(人话日志)直接忽略
|
|
191
|
+
}
|
|
192
|
+
const { id, method, params } = message ?? {};
|
|
193
|
+
if (method === 'initialize') {
|
|
194
|
+
send({
|
|
195
|
+
jsonrpc: '2.0', id,
|
|
196
|
+
result: {
|
|
197
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
198
|
+
capabilities: { tools: { listChanged: false } },
|
|
199
|
+
serverInfo: SERVER_INFO,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (method === 'notifications/initialized' || (method ?? '').startsWith('notifications/')) return;
|
|
205
|
+
if (method === 'tools/list') {
|
|
206
|
+
send({ jsonrpc: '2.0', id, result: { tools: TOOLS } });
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (method === 'tools/call') {
|
|
210
|
+
pendingCalls += 1;
|
|
211
|
+
handleToolCall(String(params?.name ?? ''), params?.arguments ?? {})
|
|
212
|
+
.then((result) => send({ jsonrpc: '2.0', id, result }))
|
|
213
|
+
.catch((error) => send({
|
|
214
|
+
jsonrpc: '2.0', id,
|
|
215
|
+
result: { content: [{ type: 'text', text: `工具执行异常:${error.message}` }], isError: true },
|
|
216
|
+
}))
|
|
217
|
+
.finally(() => {
|
|
218
|
+
pendingCalls -= 1;
|
|
219
|
+
if (stdinClosed && pendingCalls === 0) process.exit(0);
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (method === 'ping') {
|
|
224
|
+
send({ jsonrpc: '2.0', id, result: {} });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (id !== undefined) {
|
|
228
|
+
send({ jsonrpc: '2.0', id, error: { code: -32601, message: `method not found: ${method}` } });
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
rl.on('close', () => {
|
|
232
|
+
if (pendingCalls === 0) process.exit(0);
|
|
233
|
+
stdinClosed = true; // 在途调用完成后自行退出(见 tools/call 的 finally)
|
|
234
|
+
});
|
|
@@ -77,6 +77,8 @@ function shouldSkip(entry, relativePath) {
|
|
|
77
77
|
if (entry.name === '.DS_Store' || entry.name.startsWith('.')) return true
|
|
78
78
|
if (relativePath === 'product.json') return true
|
|
79
79
|
if (relativePath === 'references/source-manifest.json') return true
|
|
80
|
+
// 用户意图是本机产物(2026-11-01 起与 default.json 同目录),不随包分发
|
|
81
|
+
if (relativePath.endsWith('user-intents.json')) return true
|
|
80
82
|
if (entry.isDirectory() && ['cache', 'assets', 'node_modules', 'scripts'].includes(entry.name)) return true
|
|
81
83
|
return false
|
|
82
84
|
}
|