@ai-setting/roy-agent-core 1.7.53 → 1.7.55

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 (62) hide show
  1. package/dist/env/agent/index.js +3 -3
  2. package/dist/env/error-reply.d.ts +184 -0
  3. package/dist/env/event-source/event-source-handlers.d.ts +9 -23
  4. package/dist/env/event-source/index.js +6 -6
  5. package/dist/env/index.js +18 -18
  6. package/dist/env/llm/adapter-pi-ai.d.ts +147 -2
  7. package/dist/env/llm/adapter.d.ts +21 -1
  8. package/dist/env/llm/index.js +2 -2
  9. package/dist/env/llm/invoke.d.ts +41 -1
  10. package/dist/env/llm/llm.d.ts +25 -0
  11. package/dist/env/llm/reasoning-defense.d.ts +104 -0
  12. package/dist/env/llm/types.d.ts +26 -0
  13. package/dist/env/prompt/index.js +2 -2
  14. package/dist/env/prompt/prompts-index.d.ts +1 -1
  15. package/dist/env/session/index.js +4 -4
  16. package/dist/env/session/storage/index.js +3 -3
  17. package/dist/env/task/backend/index.js +0 -2
  18. package/dist/env/task/backend/types.d.ts +0 -11
  19. package/dist/env/task/delegate/index.js +2 -2
  20. package/dist/env/task/index.js +3 -3
  21. package/dist/env/task/plugins/card-chat-routing.d.ts +42 -89
  22. package/dist/env/task/plugins/index.js +4 -4
  23. package/dist/env/task/plugins/lark-card-patch-notifier.d.ts +0 -51
  24. package/dist/env/task/plugins/lark-cli-task-notify-plugin.d.ts +14 -31
  25. package/dist/env/task/plugins/per-task-group-chat-plugin.d.ts +4 -4
  26. package/dist/env/workflow/engine/engine.d.ts +65 -0
  27. package/dist/env/workflow/engine/index.js +2 -2
  28. package/dist/env/workflow/index.js +9 -9
  29. package/dist/env/workflow/nodes/index.js +1 -1
  30. package/dist/env/workflow/nodes/tool-node.d.ts +6 -0
  31. package/dist/env/workflow/plugins/index.js +2 -2
  32. package/dist/env/workflow/tools/index.js +6 -6
  33. package/dist/env/workflow/tools/workflow-validate-tool.d.ts +11 -2
  34. package/dist/env/workflow/types/run.d.ts +19 -0
  35. package/dist/env/workflow/utils/agent-session-metadata.d.ts +13 -0
  36. package/dist/env/workflow/utils/index.js +1 -1
  37. package/dist/env/workflow/utils/redact-command-secrets.d.ts +70 -0
  38. package/dist/env/workflow/utils/shell-dialect-lint.d.ts +75 -0
  39. package/dist/env/workflow/utils/validate-workflow-definition.d.ts +22 -0
  40. package/dist/index.js +20 -20
  41. package/dist/shared/@ai-setting/{roy-agent-core-stnddvx9.js → roy-agent-core-12d2xap8.js} +1 -1
  42. package/dist/shared/@ai-setting/{roy-agent-core-pk1b3kvf.js → roy-agent-core-2b8jtv7m.js} +1 -1
  43. package/dist/shared/@ai-setting/{roy-agent-core-x8n1r7h4.js → roy-agent-core-2bscfrr1.js} +188 -7
  44. package/dist/shared/@ai-setting/{roy-agent-core-gdsqp6j8.js → roy-agent-core-6masakz4.js} +45 -0
  45. package/dist/shared/@ai-setting/{roy-agent-core-vvxb9522.js → roy-agent-core-6qxh4xrm.js} +67 -6
  46. package/dist/shared/@ai-setting/{roy-agent-core-h93v6p6y.js → roy-agent-core-7qy3nffr.js} +2 -2
  47. package/dist/shared/@ai-setting/{roy-agent-core-4vpk40ss.js → roy-agent-core-8eqngasy.js} +2 -2
  48. package/dist/shared/@ai-setting/{roy-agent-core-brwzs8wf.js → roy-agent-core-9h72sk1n.js} +1 -1
  49. package/dist/shared/@ai-setting/{roy-agent-core-na73mym1.js → roy-agent-core-a4vzsn0g.js} +212 -1
  50. package/dist/shared/@ai-setting/{roy-agent-core-35s1w7tc.js → roy-agent-core-aer5kw1j.js} +142 -70
  51. package/dist/shared/@ai-setting/{roy-agent-core-j8e92gj8.js → roy-agent-core-b5ew8t0k.js} +0 -59
  52. package/dist/shared/@ai-setting/{roy-agent-core-4sx6sdb2.js → roy-agent-core-cd62e9p1.js} +1 -1
  53. package/dist/shared/@ai-setting/{roy-agent-core-39xejr39.js → roy-agent-core-e2z1etk4.js} +1 -1
  54. package/dist/shared/@ai-setting/{roy-agent-core-hch2geta.js → roy-agent-core-g36jevrk.js} +14 -183
  55. package/dist/shared/@ai-setting/{roy-agent-core-ph5qbqcx.js → roy-agent-core-j2nvedt6.js} +465 -128
  56. package/dist/shared/@ai-setting/{roy-agent-core-p2r27era.js → roy-agent-core-mxdc756a.js} +1 -1
  57. package/dist/shared/@ai-setting/{roy-agent-core-p8ak03s0.js → roy-agent-core-w527pcpe.js} +293 -42
  58. package/dist/shared/@ai-setting/{roy-agent-core-3tv1q6f2.js → roy-agent-core-ytr2w0vb.js} +2 -2
  59. package/dist/shared/@ai-setting/{roy-agent-core-yd21n6c7.js → roy-agent-core-zvemdfx5.js} +1 -1
  60. package/dist/test-utils/child-process-mock.d.ts +73 -0
  61. package/package.json +2 -2
  62. /package/dist/shared/@ai-setting/{roy-agent-core-zy3hqfwg.js → roy-agent-core-0hq21px2.js} +0 -0
@@ -1,72 +1,60 @@
1
1
  /**
2
- * @fileoverview 任务卡片「该发到哪个群」的路由决策(Task #4230 follow-up)
2
+ * @fileoverview 任务卡片「该发到哪个群」的路由决策(Task #4230 / #4394)
3
3
  *
4
- * ## 背景(真实 bug,2026-10-15)
4
+ * ## 现行规则(用户 2026-10-20 明令,Task #4394)
5
5
  *
6
- * Task #4226 的进展卡片被发进了 **「Task 3999 - 多模态ontology提取跟进」** 群。
7
- * 根因链:
8
- * 1. 触发消息来自 `oc_d083fc…`(= **task #3999 的跟进群**);
9
- * 2. `LarkCliTaskNotifyHook` 的 `replyChannel.chatId` 优先(Task #4220 修复:
10
- * 让 user 在触发群看到卡片)→ 卡片落到 `oc_d083fc…`;
11
- * 3. #4226 是 sub-task(parent #2066),`PerTaskGroupChatPlugin` 默认跳过建群
12
- * → #4226 从来没有自己的群。
6
+ * > 去掉专属群卡片逻辑,只保留**触发群的进度卡片**(可用户点击建群)
13
7
  *
14
- * 结果:#3999 的专属群里出现了一张讲 #4226 的卡片 —— 跨任务污染。
8
+ * 决策优先级:
9
+ * 1. **有触发群 → 就用触发群**。无条件,不判断它是不是"别的 task 的专属群"。
10
+ * 2. 没有触发群 → 用本 task(其次最近祖先)自己已有的专属群兜底(卡片不能丢)。
11
+ * 3. 两者都没有 → `none`(调用方静默 skip)。
15
12
  *
16
- * ## 决策规则(用户确认)
13
+ * ## 已删除的历史逻辑(Task #4394)
17
14
  *
18
- * - **触发群优先**(保持"在哪聊就在哪看到卡片")
19
- * - **但跳过「别 task 的专属群」**:若触发群被判定为某个**非本 task lineage**
20
- * 的 task 的专属群 → 卡片改发到本 task 自己的群(lineage 无群时新建)
21
- * - 改投时在触发群补一条**链接卡片**(不是普通消息),让 user 知道卡片去哪了
15
+ * 旧实现(Task #4230)会在"触发群被判定为别的 task 的专属群"时把卡片**改投**到
16
+ * 本 task / 祖先的 lineage 群,并在触发群补一张「📂 任务卡片已改投专属群 /
17
+ * 这里不是本任务的群,进展请看专属群」的指路卡。
22
18
  *
23
- * ## 「专属群」判定信号(多重信号,高精度优先)
19
+ * 用户实测(Task #4393)该行为是干扰:
20
+ * ```
21
+ * 触发群 oc_8947d9e2…(task 4377 的专属群)
22
+ * → 卡片被改投到 oc_d27157ac…(lineage 群)
23
+ * → 用户在触发群只看到一张"这里不是本任务的群"的灰卡
24
+ * ```
25
+ * 用户的诉求是「在哪聊就在哪看到进展」。**跨任务污染不再是需要解决的问题**,
26
+ * 因此整套「专属群归属判定 + 改投 + 指路卡」链路已彻底删除:
27
+ * - 删除 `ChatOwnership` / `mergeChatOwnership` / `analyzeContextOwnership`
28
+ * - 删除 `CardChatDecision.reroutedFrom`
29
+ * - 删除 `new-group` / `fallback-to-trigger` 两种 source(不再自动建群)
24
30
  *
25
- * - `chat-binding`:session 的 chat↔task 绑定(PerTaskGroupChatPlugin 建群时写入)
26
- * - `larkPerTaskChatId`:task.context 里插件写入的专属群字段
27
- * - `followup_chat`:`*followup_chat_id` / `*_group_chat_id` 之类字段(老任务手工建的跟进群)
28
- * - `chat-name`:群名里的 `Task 3999` / `#3999` 模式(对没留字段的手工群兜底)
31
+ * ## 建群能力仍在(**用户主动点击**才建)
29
32
  *
30
- * 注意:**不算** `feishu_chat_id` / `chat_id` 这类"会话群"字段 —— 它们表示
31
- * "这个 task 曾在哪个群聊过",把普通会话群误判成专属群会让新任务的卡片被
32
- * 错误地赶去新建群(假阳性比假阴性更烦人)。
33
+ * 卡片顶部按钮 `create_group` / `disband_group` 由
34
+ * `lark-card-action-handler.ts` 处理,与本文档的路由决策**完全解耦**:
35
+ * ```
36
+ * 用户点「为这个任务建群」
37
+ * → card.action.trigger 事件
38
+ * → handleCardAction → ports.createGroupChat (lark-cli im +chat-create)
39
+ * → 回写 task.context.larkPerTaskChatId
40
+ * → patch 卡片(按钮翻成「解散该群」+ 出现「进入专属群」链接)
41
+ * ```
42
+ * 建群后下一张进度卡会走本文件的优先级 1(仍进触发群),
43
+ * 但卡片上多了「进入专属群」链接 —— 这是用户可选的入口,不是自动改投。
33
44
  *
34
- * 本模块是**纯函数**(不碰 IO),IO(DB 反查 / lark-cli 取群名)由调用方
35
- * (EventSource handler)注入,便于单测覆盖所有分支。
45
+ * 本模块是**纯函数**(不碰 IO),便于单测覆盖所有分支。
36
46
  */
37
47
  /** 群名里隐含的 task id("Task 3999 - xxx" / "#3999 TMO 提取跟进") */
38
48
  export declare const GROUP_NAME_TASK_ID_RE: RegExp;
39
49
  /** 会把 chatId 记进 context 的字段名(用于 DB LIKE 反查) */
40
50
  export declare const CTX_FIELD_LARK_PER_TASK_CHAT_ID = "larkPerTaskChatId";
41
- /** 一个群可能的归属证据(多重信号并集) */
42
- export interface ChatOwnership {
43
- chatId: string;
44
- /**
45
- * 该群归属的 task id(可能多个:历史脏数据 / 多个 task 共用过一个群)
46
- *
47
- * 空数组 = 没有证据表明它是任何 task 的专属群。
48
- */
49
- ownerTaskIds: number[];
50
- /** 证据来源(诊断用):chat-binding / larkPerTaskChatId / followup_chat_id / chat-name */
51
- sources: string[];
52
- /** 群名(DB 记录或实时查询;可能没有) */
53
- chatName?: string;
54
- /** 是否被判为"专属群"(至少有一条**专属**证据) */
55
- dedicated: boolean;
56
- }
57
- export type CardChatSource = "trigger-chat" | "own-group" | "ancestor-group" | "new-group" | "fallback-to-trigger" | "none";
51
+ export type CardChatSource = "trigger-chat" | "own-group" | "ancestor-group" | "none";
58
52
  export interface CardChatDecision {
59
53
  chatId?: string;
60
54
  profile?: string;
61
- /** 目标群名(新建群时由 ensureGroupChat 回填,用于改投链接卡片渲染) */
55
+ /** 目标群名(诊断用) */
62
56
  chatName?: string;
63
57
  source: CardChatSource;
64
- /** 因触发群是"别 task 的专属群"而改投时记录(用于回补链接卡片) */
65
- reroutedFrom?: {
66
- chatId: string;
67
- ownerTaskIds: number[];
68
- sources: string[];
69
- };
70
58
  /** 人类可读的决策原因(写进 span / debug 日志) */
71
59
  reason: string;
72
60
  }
@@ -79,41 +67,17 @@ export declare function extractPerTaskChatProfile(contextStr?: string): string |
79
67
  /** 从 task.context JSON 解析记录下来的群名(含 `#3999` / `Task 3999` 模式) */
80
68
  export declare function extractRecordedChatNames(contextStr?: string): string[];
81
69
  /**
82
- * 分析「某个 task 的 context 是否主张自己拥有 chatId」
83
- *
84
- * 只认**专属**信号;`feishu_chat_id` / `chat_id` 这类会话群字段会被忽略
85
- * (否则会把普通会话群误判成专属群)。
86
- */
87
- export declare function analyzeContextOwnership(contextStr: string | undefined, chatId: string): {
88
- owned: boolean;
89
- sources: string[];
90
- impliedTaskIds: number[];
91
- };
92
- /**
93
- * 由多个 task(DB 反查命中)的 context 汇总出 `ChatOwnership`
70
+ * 决定卡片该发到哪个群(Task #4394 简化版)
94
71
  *
95
- * @param chatId 目标群
96
- * @param candidates DB 反查命中的 task(id + context)
97
- * @param extra 额外信号(chat↔task 绑定 / 实时群名),由调用方提供
98
- */
99
- export declare function mergeChatOwnership(chatId: string, candidates: Array<{
100
- id: number;
101
- context?: string;
102
- }>, extra?: {
103
- boundTaskId?: number | null;
104
- /** 该群的真实群名(若调用方查到了) */
105
- chatName?: string;
106
- }): ChatOwnership;
107
- /**
108
- * 决定卡片该发到哪个群
72
+ * 规则只有两条:
73
+ * 1. **有触发群 → 触发群**(无条件,不做任何"是不是别人的群"判定)
74
+ * 2. 没触发群 → 用本 task / 祖先已有的专属群兜底
109
75
  *
110
- * @param input.lineageIds 本 task 的 lineage(自己 + 所有祖先)task id
111
76
  * @param input.lineageChat lineage 上找到的专属群(自己优先,其次最近祖先)
112
- * @param input.triggerOwnership 触发群的归属分析(未做分析时传 undefined)
113
77
  */
114
78
  export declare function decideCardChatTarget(input: {
115
79
  taskId?: number;
116
- lineageIds: number[];
80
+ lineageIds?: number[];
117
81
  lineageChat?: {
118
82
  chatId: string;
119
83
  profile?: string;
@@ -121,15 +85,4 @@ export declare function decideCardChatTarget(input: {
121
85
  };
122
86
  triggerChatId?: string;
123
87
  triggerProfile?: string;
124
- triggerOwnership?: ChatOwnership;
125
- /**
126
- * ★ Task #4331:是否允许"new-group"路径(即触发群是别人的群 + 本 task 无群时建专属群)
127
- *
128
- * 默认 false(用户新要求:默认不建群)
129
- * - true:保留 #4230 的老行为(建专属群),用于向后兼容显式开启的场景
130
- * - false:触发群是别人的群 + 本 task 无群 → 退回触发群(不建群)
131
- *
132
- * 不影响其他 source(trigger-chat / own-group / ancestor-group / none)的决策。
133
- */
134
- allowNewGroup?: boolean;
135
88
  }): CardChatDecision;
@@ -10,12 +10,12 @@ import {
10
10
  formatExtractorInput,
11
11
  formatExtractorInputFromDescription,
12
12
  parseExtractorOutput
13
- } from "../../../shared/@ai-setting/roy-agent-core-hch2geta.js";
13
+ } from "../../../shared/@ai-setting/roy-agent-core-g36jevrk.js";
14
14
  import"../../../shared/@ai-setting/roy-agent-core-yhsssn86.js";
15
15
  import"../../../shared/@ai-setting/roy-agent-core-1pg0fepg.js";
16
- import"../../../shared/@ai-setting/roy-agent-core-gdsqp6j8.js";
17
- import"../../../shared/@ai-setting/roy-agent-core-j8e92gj8.js";
18
- import"../../../shared/@ai-setting/roy-agent-core-na73mym1.js";
16
+ import"../../../shared/@ai-setting/roy-agent-core-6masakz4.js";
17
+ import"../../../shared/@ai-setting/roy-agent-core-b5ew8t0k.js";
18
+ import"../../../shared/@ai-setting/roy-agent-core-a4vzsn0g.js";
19
19
  import"../../../shared/@ai-setting/roy-agent-core-8s9jd2qy.js";
20
20
  import"../../../shared/@ai-setting/roy-agent-core-jrj30vq3.js";
21
21
  import"../../../shared/@ai-setting/roy-agent-core-dv714bqh.js";
@@ -173,21 +173,6 @@ export interface FinishTaskOptions {
173
173
  finalStep?: string;
174
174
  progress?: number;
175
175
  }
176
- /** 改投链接卡片的入参(Task #4230 follow-up) */
177
- export interface RedirectNoticeOptions {
178
- /** 发到哪个群(通常是"触发群",即被判定为别 task 专属群的那个) */
179
- chatId: string;
180
- profile?: string;
181
- /** 本 task */
182
- taskId?: number | string;
183
- taskTitle?: string;
184
- /** 卡片实际落在哪个群 */
185
- targetChatId: string;
186
- /** 目标群名(渲染成可点击文本;缺省显示"专属群") */
187
- targetChatName?: string;
188
- /** 改投原因(诊断,写进卡片文案) */
189
- reason?: string;
190
- }
191
176
  export interface StartTaskResult {
192
177
  messageId: string;
193
178
  /** pin 是否成功(失败不影响卡片可见性,但 trace 里可查) */
@@ -418,24 +403,6 @@ export declare function buildTaskCard(opts: {
418
403
  export declare function buildChatAppLink(chatId: string, base?: string): string;
419
404
  /** 默认 applink 基址(飞书) */
420
405
  export declare const DEFAULT_APP_LINK_BASE = "https://applink.feishu.cn";
421
- /**
422
- * 构造"卡片已改投专属群"的链接卡片(Task #4230 follow-up)
423
- *
424
- * 为什么是卡片而不是普通消息:
425
- * - 用户明确要求「旧逻辑的普通消息通知不要」;
426
- * - 改投后触发群里需要一条**可点击**的入口,否则 user 不知道进展卡片去哪了。
427
- *
428
- * 注意:这张卡片**不携带任务进展内容**,只是一条"去哪看"的指路牌,避免把
429
- * 别的 task 的进展细节塞进不属于它的群。
430
- */
431
- export declare function buildRedirectCard(opts: {
432
- taskId?: number | string;
433
- taskTitle?: string;
434
- targetChatId: string;
435
- /** 目标群名(渲染成可点击文本;缺省显示"专属群") */
436
- targetChatName?: string;
437
- reason?: string;
438
- }): CardPayload;
439
406
  /**
440
407
  * 格式化耗时为人类可读文本(毫秒 → ms / s / m)
441
408
  *
@@ -496,14 +463,6 @@ export declare class LarkCardPatchNotifier {
496
463
  * unpin 基础设施,只是渲染的卡片与自持的时间线不同。
497
464
  */
498
465
  private readonly workflowStates;
499
- /**
500
- * ★ Task #4237 修复:改投链接卡片幂等表
501
- *
502
- * key = `${taskId}:${targetChatId}`;同一 (task, 目标群) 只允许发一张
503
- * 「任务卡片已改投专属群」卡片 —— 旧实现里两条决策路径各发一次,
504
- * 触发群收到两张文案不同的改投卡片。
505
- */
506
- private readonly redirectsSent;
507
466
  constructor(options?: LarkCardPatchNotifierOptions);
508
467
  /** 取(或创建)某 messageId 的 mutex —— 不同卡片互不阻塞 */
509
468
  private mutexFor;
@@ -514,16 +473,6 @@ export declare class LarkCardPatchNotifier {
514
473
  * `--chat-id/--message-id` 是非法 flag,导致 pin 永远失败 → 用户"还是没有 pin")。
515
474
  */
516
475
  startTask(opts: StartTaskOptions): Promise<StartTaskResult>;
517
- /**
518
- * 在"触发群"发一条**链接卡片**,告知任务进展卡片已改投本 task 的专属群
519
- *
520
- * Task #4230 follow-up:当触发群被判定为"别的 task 的专属群"时,卡片不会
521
- * 再落在触发群(避免跨任务污染),但 user 需要知道去哪里看 → 这里补一张
522
- * 不含任务进展内容的指路卡片。
523
- *
524
- * 失败非致命(触发群里发不出去不影响主流程)。
525
- */
526
- sendRedirectNotice(opts: RedirectNoticeOptions): Promise<boolean>;
527
476
  /**
528
477
  * Patch 同一卡片(状态/进度/当前步骤 + 累积里程碑与过程时间线)
529
478
  *
@@ -23,10 +23,8 @@
23
23
  * ```
24
24
  */
25
25
  import type { Hook, HookContext } from "../../hook/types";
26
- import type { Task } from "../types";
27
26
  import { getEnvContextOrEmpty } from "../../context";
28
27
  import { LarkCardPatchNotifier } from "./lark-card-patch-notifier";
29
- import { type ChatOwnership } from "./card-chat-routing";
30
28
  /**
31
29
  * 从 EnvContext 提取 lark 专属字段
32
30
  *
@@ -82,16 +80,6 @@ export interface TaskNotifyConfig {
82
80
  * 不传 → plugin 自己 new 一个(messageId 由 plugin 的 Map 跟踪)。
83
81
  */
84
82
  cardNotifier?: LarkCardPatchNotifier;
85
- /**
86
- * ★ Task #4331:是否允许路由决策走"new-group"路径(建专属群)
87
- *
88
- * 默认 false:触发群是别人的群 + 本 task 无群时,**不**建专属群,退回触发群
89
- * 适用:默认行为(用户新要求:默认不建群)
90
- *
91
- * 设为 true:保留 #4230 老行为(建专属群并改投)
92
- * 适用:显式开启(向后兼容)
93
- */
94
- allowNewGroup?: boolean;
95
83
  }
96
84
  /**
97
85
  * 卡片路由端口(Task #4230 follow-up)
@@ -101,25 +89,16 @@ export interface TaskNotifyConfig {
101
89
  */
102
90
  export interface CardRoutingPorts {
103
91
  /**
104
- * 反查「该群是否是某个 task 的专属群」(多重信号)
92
+ * task 查询(lineage 上溯:无触发群时找本 task / 祖先的专属群兜底)
105
93
  *
106
- * 实现建议(见 event-source-handlers.ts):
107
- * - session 的 chat↔task 绑定
108
- * - task.context.larkPerTaskChatId / *followup_chat_id
109
- * - 群名 "Task 3999" / "#3999"(DB 记录的群名,必要时实时查)
94
+ * ★ Task #4394:`lookupChatOwnership` / `ensureTaskGroupChat` 两个端口已删除
95
+ * —— 卡片不再做"触发群是不是别 task 专属群"的判定,也不再自动建群。
96
+ * 建群改由用户在卡片上点「为这个任务建群」按钮触发(见 lark-card-action-handler.ts)。
110
97
  */
111
- lookupChatOwnership?: (chatId: string) => Promise<ChatOwnership | undefined>;
112
- /** task 查询(lineage 上溯) */
113
98
  getTask?: (id: number) => Promise<{
114
99
  context?: string;
115
100
  parent_task_id?: number | null;
116
101
  } | undefined>;
117
- /** 为 task 建/取专属群(lineage 无群且触发群是别人的群时用) */
118
- ensureTaskGroupChat?: (task: Task) => Promise<{
119
- chatId: string;
120
- profile?: string;
121
- name?: string;
122
- } | undefined>;
123
102
  }
124
103
  export declare class LarkCliTaskNotifyHook implements Hook {
125
104
  readonly name: string;
@@ -156,8 +135,8 @@ export declare class LarkCliTaskNotifyHook implements Hook {
156
135
  *
157
136
  * 根因:`sendCardStart` 里 `ensureTaskGroupChat` 会写 task.context → 同步触发
158
137
  * `task:after.update` → 本 hook 重入 `sendCardUpdate`;此时首卡 messageId 还没写进
159
- * `cardMessageIds` → 触发 fallback 补一张卡 + 再发一张改投卡片(同一 task 3 张卡、
160
- * 触发群 2 张改投卡片)。
138
+ * `cardMessageIds` → 触发 fallback 补一张重复卡(同一 task 多张卡)。
139
+ * (Task #4394 已删除"再发一张改投卡片"那条路径,哨兵仍用于防重复补卡。)
161
140
  *
162
141
  * 修法:进入 `sendCardStart` 前先占位;重入的 update 看到哨兵直接跳过。
163
142
  */
@@ -189,16 +168,20 @@ export declare class LarkCliTaskNotifyHook implements Hook {
189
168
  /**
190
169
  * 解析「卡片该发到哪个群」(Task #4230 follow-up)
191
170
  *
192
- * 规则见 card-chat-routing.ts:触发群优先,但跳过"别的 task 的专属群";
193
- * 若是别人的群 → 用本 task(或祖先)的专属群;都没有 → 新建一个。
171
+ * 规则见 card-chat-routing.ts(Task #4394 简化版):
172
+ * - **有触发群 → 就用触发群**(无条件,不改投)
173
+ * - 没有触发群 → 用本 task(或祖先)已有的专属群兜底
174
+ * - 都没有 → none(caller skip)
175
+ *
176
+ * 已经不再"自动建群"——建群由用户在卡片上点按钮触发。
194
177
  *
195
- * @returns decision + 最终可用的 chatId/profile + 是否需要回补链接卡片
178
+ * @returns decision(chatId / profile / source / reason)
196
179
  */
197
180
  private resolveCardTarget;
198
181
  /**
199
182
  * 收集 task 的 lineage(自己 → 父 → 祖父…,最多 10 层)
200
183
  *
201
- * 用于:(a) 找最近的专属群;(b) 判断"某个群属于谁"时确认是否在自己 lineage 上。
184
+ * 用于:无触发群时找最近的专属群兜底(Task #4394:不再用于"是否别人的群"判定)。
202
185
  */
203
186
  private collectLineage;
204
187
  /** 发初始卡片(+ pin)并缓存 messageId */
@@ -193,10 +193,10 @@ export interface PerTaskGroupConfig {
193
193
  * - 用户在卡片顶部点"为这个任务建群"按钮 → 走 ensureGroupChat 按需建群
194
194
  * - 适用:减少群聊爆炸 / 集中讨论 / 减少噪音
195
195
  *
196
- * ★ 与 LarkCliTaskNotifyPlugin.cardRoutingPorts.ensureTaskGroupChat 协同:
197
- * - LarkCliTaskNotifyPlugin 在路由决策 source="new-group" 时也会调 ensureGroupChat
198
- * - 用户本次改造明确要求"默认不建群",所以 LarkCliTaskNotifyPlugin 的 new-group 路径
199
- * 也被改造为 fallback-to-trigger(不调 ensure)
196
+ * ★ Task #4394:`LarkCliTaskNotifyPlugin.cardRoutingPorts.ensureTaskGroupChat`
197
+ * 及其 `new-group` / `fallback-to-trigger` 两条 source 已删除 —— 卡片路由
198
+ * **不再自动建群**,`ensureGroupChat` 现在只剩一个调用方:用户点卡片上
199
+ * 「为这个任务建群」按钮(见 lark-card-action-handler.ts)。
200
200
  */
201
201
  autoCreateGroup?: boolean;
202
202
  /**
@@ -217,12 +217,61 @@ export declare class WorkflowEngine extends EventEmitter {
217
217
  * Main scheduling loop
218
218
  */
219
219
  private scheduleAndExecute;
220
+ /**
221
+ * Dispatch a node without waiting for it to finish (Task #4392 P0-1).
222
+ *
223
+ * Registers the node's promise in `sessionState.inFlight` **synchronously**
224
+ * so the scheduler loop can (a) join it and (b) never mistake "no tracked
225
+ * work" for "done". The returned promise is intentionally swallowed: node
226
+ * outcomes propagate through `scheduler.markFailed` and the `node.failed`
227
+ * event, exactly as they did when the dispatch path awaited them.
228
+ *
229
+ * ## Claim guard — why this is not just "call startNode"
230
+ *
231
+ * A node can be reachable from **two** dispatch paths:
232
+ * 1. `scheduleAvailableNodes()` → `scheduler.getReadyNodes()` (DAG readiness)
233
+ * 2. `processConditionalEdgesAfterCompletion()` → `edgeResult.toStart`
234
+ *
235
+ * Under the old serial loop this never mattered: path 2 awaited each target
236
+ * to completion, so path 1 always found them `completed` already. Once
237
+ * dispatch became concurrent the two paths race and the *same* node was
238
+ * executed twice (proven by `engine-parallel-scheduling.test.ts`:
239
+ * `parallelLimit=2` produced peak concurrency 3 with 4 of 6 leaves run twice).
240
+ *
241
+ * `scheduler.running` is the tie-breaker. `startNode` calls
242
+ * `scheduler.markStarted()` before its first `await`, so the claim is visible
243
+ * synchronously to the next dispatcher on the same tick — no interleaving
244
+ * window. Returns `true` only for the caller that actually won the claim.
245
+ *
246
+ * ⚠️ The guard deliberately keys off `scheduler.running` and **not** off
247
+ * `inFlight`, even though both look equivalent. A self-loop back-edge
248
+ * (`implement → implement`) re-dispatches the node *from inside its own
249
+ * completion handler*, i.e. while its own promise is still in `inFlight` but
250
+ * after `resetNodeForReEntry()` cleared `running`. Guarding on `inFlight`
251
+ * deadlocks exactly that case: the node waits for itself forever.
252
+ */
253
+ private dispatchNode;
220
254
  /**
221
255
  * Wait for next node event
222
256
  *
223
257
  * Optimization: When there are running nodes, use pure event-driven waiting
224
258
  * (no polling). When there are only pending nodes (dependencies not yet met),
225
259
  * use a small timeout to re-check scheduling conditions.
260
+ *
261
+ * Task #4392 P0-1 — two additions that concurrent dispatch makes necessary:
262
+ *
263
+ * 1. **`inFlight` also arms the timeout.** `scheduler.running` is cleared by
264
+ * `markCompleted` inside `validateAndExecute`, but the node's promise is
265
+ * still settling (AFTER_NODE hook, conditional edges, `checkAndFinalize`).
266
+ * In that window `hasRunningNodes` is false while real work continues, so
267
+ * we must keep a wakeup source armed or the loop could park forever.
268
+ *
269
+ * 2. **`node.scheduled` resolves the wait.** Concurrent dispatch publishes
270
+ * `node.scheduled` for sibling nodes (and for loop re-entries) *after* this
271
+ * waiter subscribed — with `node.completed`/`node.failed` as the only
272
+ * trigger, a round whose completion event fired during the synchronous
273
+ * dispatch section would leave the loop blocked until the 100ms poll.
274
+ * Subscribing to `node.scheduled` closes that lost-wakeup window.
226
275
  */
227
276
  private waitForNextNodeEvent;
228
277
  /**
@@ -368,6 +417,22 @@ export declare class WorkflowEngine extends EventEmitter {
368
417
  * Get session metadata
369
418
  */
370
419
  private getSessionMetadata;
420
+ /**
421
+ * Serialized session-metadata read-modify-write (Task #4392 P0-1).
422
+ *
423
+ * Every `node.completed` / `node.failed` handler and every terminal-state
424
+ * transition mutates metadata via `get → spread → update`. That is safe only
425
+ * if mutations never interleave. Now that nodes are dispatched concurrently,
426
+ * two completions can both `await get()` the same snapshot and the later
427
+ * `update()` silently drops the other's `agentSessions` marker.
428
+ *
429
+ * `mutate` receives the freshly-read metadata and must return the next
430
+ * metadata object. Mutations are chained onto `sessionState.sessionMetadataQueue`
431
+ * so they apply in a strict total order, and the queue never rejects (a
432
+ * failed mutation is logged and skipped) so one bad update cannot poison
433
+ * every later write.
434
+ */
435
+ private updateSessionMetadata;
371
436
  /**
372
437
  * Find workflow definition from repository
373
438
  */
@@ -5,10 +5,10 @@ import {
5
5
  Scheduler,
6
6
  WorkflowEngine,
7
7
  init_engine
8
- } from "../../../shared/@ai-setting/roy-agent-core-35s1w7tc.js";
8
+ } from "../../../shared/@ai-setting/roy-agent-core-aer5kw1j.js";
9
9
  import"../../../shared/@ai-setting/roy-agent-core-6atd905e.js";
10
10
  import"../../../shared/@ai-setting/roy-agent-core-6167phd4.js";
11
- import"../../../shared/@ai-setting/roy-agent-core-x8n1r7h4.js";
11
+ import"../../../shared/@ai-setting/roy-agent-core-2bscfrr1.js";
12
12
  import"../../../shared/@ai-setting/roy-agent-core-1db4vpc6.js";
13
13
  import"../../../shared/@ai-setting/roy-agent-core-ze068vc9.js";
14
14
  import"../../../shared/@ai-setting/roy-agent-core-fs36xgcs.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  WorkflowComponent
3
- } from "../../shared/@ai-setting/roy-agent-core-4vpk40ss.js";
3
+ } from "../../shared/@ai-setting/roy-agent-core-8eqngasy.js";
4
4
  import"../../shared/@ai-setting/roy-agent-core-vrsxhw9e.js";
5
5
  import {
6
6
  Edge,
@@ -14,21 +14,21 @@ import {
14
14
  DecoratorNode,
15
15
  init_decorator_node
16
16
  } from "../../shared/@ai-setting/roy-agent-core-1ce3fqrk.js";
17
- import"../../shared/@ai-setting/roy-agent-core-vvxb9522.js";
18
- import"../../shared/@ai-setting/roy-agent-core-yd21n6c7.js";
19
- import"../../shared/@ai-setting/roy-agent-core-h93v6p6y.js";
17
+ import"../../shared/@ai-setting/roy-agent-core-6qxh4xrm.js";
18
+ import"../../shared/@ai-setting/roy-agent-core-zvemdfx5.js";
19
+ import"../../shared/@ai-setting/roy-agent-core-7qy3nffr.js";
20
20
  import"../../shared/@ai-setting/roy-agent-core-hhrg314p.js";
21
21
  import"../../shared/@ai-setting/roy-agent-core-7evvpfwc.js";
22
- import"../../shared/@ai-setting/roy-agent-core-35s1w7tc.js";
22
+ import"../../shared/@ai-setting/roy-agent-core-aer5kw1j.js";
23
23
  import"../../shared/@ai-setting/roy-agent-core-6atd905e.js";
24
24
  import"../../shared/@ai-setting/roy-agent-core-6167phd4.js";
25
- import"../../shared/@ai-setting/roy-agent-core-x8n1r7h4.js";
25
+ import"../../shared/@ai-setting/roy-agent-core-2bscfrr1.js";
26
26
  import"../../shared/@ai-setting/roy-agent-core-1db4vpc6.js";
27
27
  import"../../shared/@ai-setting/roy-agent-core-ze068vc9.js";
28
28
  import"../../shared/@ai-setting/roy-agent-core-fs36xgcs.js";
29
29
  import"../../shared/@ai-setting/roy-agent-core-9csa89jx.js";
30
30
  import"../../shared/@ai-setting/roy-agent-core-7exqwax1.js";
31
- import"../../shared/@ai-setting/roy-agent-core-na73mym1.js";
31
+ import"../../shared/@ai-setting/roy-agent-core-a4vzsn0g.js";
32
32
  import"../../shared/@ai-setting/roy-agent-core-tjg71zqp.js";
33
33
  import"../../shared/@ai-setting/roy-agent-core-ddwa0n6h.js";
34
34
  import"../../shared/@ai-setting/roy-agent-core-8s9jd2qy.js";
@@ -85,8 +85,8 @@ import {
85
85
  unresolvedVariable
86
86
  } from "../../shared/@ai-setting/roy-agent-core-jrj30vq3.js";
87
87
  import"../../shared/@ai-setting/roy-agent-core-rvxg1wps.js";
88
- import"../../shared/@ai-setting/roy-agent-core-pk1b3kvf.js";
89
- import"../../shared/@ai-setting/roy-agent-core-zy3hqfwg.js";
88
+ import"../../shared/@ai-setting/roy-agent-core-2b8jtv7m.js";
89
+ import"../../shared/@ai-setting/roy-agent-core-0hq21px2.js";
90
90
  import"../../shared/@ai-setting/roy-agent-core-c1v263jn.js";
91
91
  import {
92
92
  AskUserError,
@@ -7,7 +7,7 @@ import {
7
7
  init_skill_node,
8
8
  init_tool_node,
9
9
  init_workflow_node
10
- } from "../../../shared/@ai-setting/roy-agent-core-x8n1r7h4.js";
10
+ } from "../../../shared/@ai-setting/roy-agent-core-2bscfrr1.js";
11
11
  import"../../../shared/@ai-setting/roy-agent-core-dsevjbck.js";
12
12
  import"../../../shared/@ai-setting/roy-agent-core-8seq65bp.js";
13
13
  import"../../../shared/@ai-setting/roy-agent-core-jnszp0jq.js";
@@ -52,6 +52,12 @@ export declare class ToolNode implements Node {
52
52
  * - value is not an object
53
53
  * - success === true (or success field is missing)
54
54
  * - wrapper has no error string
55
+ *
56
+ * Task #4392 P0-2: when the wrapper signals a non-zero `exit_code` but carries
57
+ * no stderr, the bare `Tool exited with code N` message is useless on its own
58
+ * (#4385 measured 27 such `exit 2` occurrences in the creator sessions, and
59
+ * 291 failed runs whose `session.metadata.error` was null). We therefore append
60
+ * the (redacted, single-line, truncated) command plus a stdout tail.
55
61
  */
56
62
  private extractErrorFromWrapper;
57
63
  }
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  LarkCliWorkflowNotifyHook,
3
3
  createLarkCliWorkflowNotifyHook
4
- } from "../../../shared/@ai-setting/roy-agent-core-brwzs8wf.js";
5
- import"../../../shared/@ai-setting/roy-agent-core-j8e92gj8.js";
4
+ } from "../../../shared/@ai-setting/roy-agent-core-9h72sk1n.js";
5
+ import"../../../shared/@ai-setting/roy-agent-core-b5ew8t0k.js";
6
6
  import"../../../shared/@ai-setting/roy-agent-core-9csa89jx.js";
7
7
  import"../../../shared/@ai-setting/roy-agent-core-qcspfszy.js";
8
8
  import"../../../shared/@ai-setting/roy-agent-core-4z3rq5gw.js";
@@ -24,9 +24,9 @@ import {
24
24
  getRunWorkflowTool,
25
25
  initRunWorkflowTool,
26
26
  resetRunWorkflowTool
27
- } from "../../../shared/@ai-setting/roy-agent-core-vvxb9522.js";
28
- import"../../../shared/@ai-setting/roy-agent-core-yd21n6c7.js";
29
- import"../../../shared/@ai-setting/roy-agent-core-h93v6p6y.js";
27
+ } from "../../../shared/@ai-setting/roy-agent-core-6qxh4xrm.js";
28
+ import"../../../shared/@ai-setting/roy-agent-core-zvemdfx5.js";
29
+ import"../../../shared/@ai-setting/roy-agent-core-7qy3nffr.js";
30
30
  import"../../../shared/@ai-setting/roy-agent-core-7evvpfwc.js";
31
31
  import {
32
32
  WORKFLOW_SUBMIT_OUTPUT_TOOL_NAME,
@@ -37,12 +37,12 @@ import {
37
37
  } from "../../../shared/@ai-setting/roy-agent-core-1db4vpc6.js";
38
38
  import"../../../shared/@ai-setting/roy-agent-core-ze068vc9.js";
39
39
  import"../../../shared/@ai-setting/roy-agent-core-7exqwax1.js";
40
- import"../../../shared/@ai-setting/roy-agent-core-na73mym1.js";
40
+ import"../../../shared/@ai-setting/roy-agent-core-a4vzsn0g.js";
41
41
  import"../../../shared/@ai-setting/roy-agent-core-8s9jd2qy.js";
42
42
  import"../../../shared/@ai-setting/roy-agent-core-jrj30vq3.js";
43
43
  import"../../../shared/@ai-setting/roy-agent-core-rvxg1wps.js";
44
- import"../../../shared/@ai-setting/roy-agent-core-pk1b3kvf.js";
45
- import"../../../shared/@ai-setting/roy-agent-core-zy3hqfwg.js";
44
+ import"../../../shared/@ai-setting/roy-agent-core-2b8jtv7m.js";
45
+ import"../../../shared/@ai-setting/roy-agent-core-0hq21px2.js";
46
46
  import"../../../shared/@ai-setting/roy-agent-core-c1v263jn.js";
47
47
  import"../../../shared/@ai-setting/roy-agent-core-e25xkv53.js";
48
48
  import"../../../shared/@ai-setting/roy-agent-core-7hh0brvs.js";
@@ -34,18 +34,27 @@ import type { WorkflowService } from "../service/workflow-service";
34
34
  /**
35
35
  * Input schema for workflow_validate tool.
36
36
  *
37
- * Accepts either `yaml` (string) or `definition` (object) — same flexibility
38
- * as workflow_add, for symmetric API design.
37
+ * Accepts `file` (path), `yaml`/`definition_yaml` (string) or `definition`
38
+ * (object). Precedence: `definition` > `file` > `yaml` / `definition_yaml`.
39
+ *
40
+ * Task #4392 P0-4: `file` was added because the tool performs **no shell
41
+ * expansion**, yet LLMs kept passing `yaml: "$(cat /tmp/x.yaml)"`. That string
42
+ * is literal, YAML.parse returns a *string*, and the tool replied
43
+ * `Parsed definition is not an object` — 30 of the 31 measured validate
44
+ * failures in #4385 §2 C2.
39
45
  */
40
46
  export declare const WorkflowValidateInputSchema: z.ZodObject<{
41
47
  definition: z.ZodOptional<z.ZodAny>;
42
48
  definition_yaml: z.ZodOptional<z.ZodString>;
43
49
  yaml: z.ZodOptional<z.ZodString>;
50
+ file: z.ZodOptional<z.ZodString>;
44
51
  }, "strip", z.ZodTypeAny, {
52
+ file?: string | undefined;
45
53
  definition?: any;
46
54
  definition_yaml?: string | undefined;
47
55
  yaml?: string | undefined;
48
56
  }, {
57
+ file?: string | undefined;
49
58
  definition?: any;
50
59
  definition_yaml?: string | undefined;
51
60
  yaml?: string | undefined;