@ai-setting/roy-agent-core 1.7.64 → 1.7.65

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 (31) hide show
  1. package/dist/env/environment.d.ts +27 -0
  2. package/dist/env/event-source/event-source-handlers.js +3 -2
  3. package/dist/env/event-source/index.js +4 -3
  4. package/dist/env/index.js +7 -6
  5. package/dist/env/task/delegate/delegate-tool.d.ts +2 -2
  6. package/dist/env/task/hooks/contexts.d.ts +34 -0
  7. package/dist/env/task/hooks/verdict.d.ts +170 -0
  8. package/dist/env/task/index.js +3 -2
  9. package/dist/env/task/mcp/index.js +2 -1
  10. package/dist/env/task/plugins/__e2e__/laya-chain-validate.d.ts +15 -0
  11. package/dist/env/task/plugins/__e2e__/laya-decision-branches.d.ts +18 -0
  12. package/dist/env/task/plugins/__e2e__/laya-decision-e2e-live.d.ts +27 -0
  13. package/dist/env/task/plugins/__e2e__/laya-e2e-verify.d.ts +17 -0
  14. package/dist/env/task/plugins/decision-hint-plugin.d.ts +144 -0
  15. package/dist/env/task/plugins/decision-port.d.ts +119 -0
  16. package/dist/env/task/plugins/decision-renderers.d.ts +33 -0
  17. package/dist/env/task/plugins/index.d.ts +1 -0
  18. package/dist/env/task/plugins/index.js +8 -1
  19. package/dist/env/task/plugins/laya-decision-plugin.d.ts +136 -0
  20. package/dist/env/task/plugins/laya-port.d.ts +143 -0
  21. package/dist/env/task/plugins/laya-render.d.ts +100 -0
  22. package/dist/env/task/tools/index.js +2 -1
  23. package/dist/index.js +7 -6
  24. package/dist/shared/@ai-setting/{roy-agent-core-1h5bbqcg.js → roy-agent-core-18am1h07.js} +1 -1
  25. package/dist/shared/@ai-setting/{roy-agent-core-vncwfzw5.js → roy-agent-core-6z412atw.js} +39 -2
  26. package/dist/shared/@ai-setting/{roy-agent-core-vd9zak15.js → roy-agent-core-e42v9bcq.js} +1 -1
  27. package/dist/shared/@ai-setting/{roy-agent-core-ry2eztft.js → roy-agent-core-f6c0nadn.js} +958 -94
  28. package/dist/shared/@ai-setting/{roy-agent-core-e4hehcdr.js → roy-agent-core-hmdcz3g9.js} +3 -2
  29. package/dist/shared/@ai-setting/{roy-agent-core-xw4wsj38.js → roy-agent-core-t0ktaevq.js} +48 -2
  30. package/dist/shared/@ai-setting/roy-agent-core-tr3ep81v.js +397 -0
  31. package/package.json +1 -1
@@ -0,0 +1,119 @@
1
+ /**
2
+ * @fileoverview Task #4467 — decision model 载体适配层(DecisionPort)
3
+ *
4
+ * 契约:/tmp/task-create-verdict-contract-v2.md §7
5
+ *
6
+ * 载体实测事实(#4107 踩过的坑,务必照抄):
7
+ * - baseURL 本身就是请求地址,**不是** `{baseURL}/chat/completions`
8
+ * - **不能用标准 chat/completions 格式**:`state` / `questions` 必须是**顶层键**
9
+ * (标准 `messages:[...]` → 400 `Field required: state & Field required: questions`)
10
+ * - 响应形态:`{ answers: { <key>: { type, choice, confidence, probabilities } }, usage, latency_ms }`
11
+ * - API 硬限:`questions.<key>.criteria` 最多 255 个 choices
12
+ * - 超时是真实风险:实测 100ms ~ 13452ms
13
+ */
14
+ export interface DecisionRequest {
15
+ /**
16
+ * 决策上下文。
17
+ *
18
+ * ⚠️ Task #4620:**可以是一段文本**。
19
+ * - systemone 载体(阿里百炼):JSON 对象 `{task_to_route, similar_tasks}`
20
+ * - laya 载体(#4614 的微调模型):中文文本
21
+ * `【新任务标题】…\n【新任务描述】…\n【候选任务】…`
22
+ *
23
+ * 两种形态都合法 —— 载体**只在训练分布内**才判得准(实测差 32.5 个百分点),
24
+ * 见 `laya-train/scripts/format_probe.py`。
25
+ *
26
+ * ⚠️ 类型放宽到 `unknown` 而非 `Record<string, unknown>` —— 因为文本 state
27
+ * 不是对象。**运行时按原样透传**(JSON.stringify 会把它序列化成字符串)。
28
+ */
29
+ state: unknown;
30
+ questions: Record<string, unknown>;
31
+ }
32
+ export interface DecisionAnswer {
33
+ choice: string;
34
+ confidence: number;
35
+ probabilities?: Record<string, number>;
36
+ /**
37
+ * Task #4627 — **Jev 公式置信度**(#302 的核心字段)。
38
+ *
39
+ * ⚠️ `confidence` 是**归一化熵**,**不是** Jev 公式置信度。
40
+ * laya 同时返回两个字段,60 条实测里 20 条差异 > 0.05。
41
+ * 门禁**必须**读 `x_jev_confidence`(缺失时回退 `answer_confidence` → `confidence`)。
42
+ *
43
+ * `LayaDecisionPort` 已在 normalize() 里把 `confidence` 覆盖成选源后的值,
44
+ * 但那是**载体侧**的归一化 —— 判定层不能依赖它(换载体就失效)。
45
+ * 所以判定层自己再选一次源,两层都指向 `x_jev_confidence`。
46
+ */
47
+ x_jev_confidence?: number;
48
+ /** Jev 公式置信度的另一个别名(laya 返回) */
49
+ answer_confidence?: number;
50
+ }
51
+ export interface DecisionResponse {
52
+ answers: Record<string, DecisionAnswer>;
53
+ usage?: {
54
+ input_tokens?: number;
55
+ };
56
+ latency_ms?: number;
57
+ }
58
+ export interface DecisionPort {
59
+ /** 执行一次决策。失败抛错,调用方按契约 §8 降级。 */
60
+ decide(req: DecisionRequest): Promise<DecisionResponse>;
61
+ /** 供测试/熔断查询当前是否已自我禁用 */
62
+ isDisabled(): boolean;
63
+ }
64
+ export interface SystemOneDecisionPortOptions {
65
+ /** 端点(**本身就是请求地址**,不要拼 /chat/completions) */
66
+ baseURL: string;
67
+ apiKey: string;
68
+ model: string;
69
+ /** 硬超时(默认 2000ms,契约 P8) */
70
+ timeoutMs?: number;
71
+ /** 熔断计数的 session 维度(契约 P9) */
72
+ sessionId?: string;
73
+ /** 连续超时多少次后自我禁用(默认 3) */
74
+ maxStrikes?: number;
75
+ /** 注入 fetch(测试用) */
76
+ fetchImpl?: typeof globalThis.fetch;
77
+ }
78
+ export declare class SystemOneDecisionPort implements DecisionPort {
79
+ private readonly baseURL;
80
+ private readonly apiKey;
81
+ private readonly model;
82
+ private readonly timeoutMs;
83
+ private readonly sessionId;
84
+ private readonly maxStrikes;
85
+ private readonly fetchImpl;
86
+ /** 同 session 连续超时计数 */
87
+ private timeoutStrikes;
88
+ private disabled;
89
+ constructor(options: SystemOneDecisionPortOptions);
90
+ isDisabled(): boolean;
91
+ /**
92
+ * 执行一次决策。
93
+ *
94
+ * Task #4494 trace 插桩 —— **这是整条链路最关键的可观测点**(真调模型)。
95
+ *
96
+ * ⚠️ 安全硬约束:`paramFilter` 只输出**结构摘要**,绝不输出
97
+ * - `req.state` 全量(200 候选可达 47KB)
98
+ * - `req.questions` 的 criteria 全量(每个候选一条渲染串)
99
+ * - **apiKey**(它在 `this.apiKey` 上,args 里本来就没有,但绝不能
100
+ * 顺手把 `this` 记录下来 —— paramFilter 只用 args,不碰 this)
101
+ *
102
+ * ⚠️ 延迟观测:`resultFilter` 记录 `latency_ms`(载体自己返回)+
103
+ * `answers` 的 choice/confidence。P50/P95 统计靠这个字段。
104
+ */
105
+ decide(req: DecisionRequest): Promise<DecisionResponse>;
106
+ }
107
+ export interface DecisionAuthConfig {
108
+ baseURL: string;
109
+ apiKey: string;
110
+ model: string;
111
+ }
112
+ /**
113
+ * 从 `~/.roy-agent/auth.json` 的 `providers.ali-decision` 读取端点配置。
114
+ *
115
+ * 路径与字段(实测):
116
+ * ~/.roy-agent/auth.json → providers["ali-decision"].baseURL
117
+ * → providers["ali-decision"].apiKey
118
+ */
119
+ export declare function loadSystemOneAuth(authPath?: string): Promise<DecisionAuthConfig | null>;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @fileoverview Task #4620 — DecisionRenderer 的两个实现
3
+ *
4
+ * `DecisionHintPlugin`(#4481)的判定逻辑是载体无关的;**渲染**不是。
5
+ * 不同载体的模型是在各自的渲染分布上训出来的,把 state/criteria 发成
6
+ * 另一种形态,模型就落到分布外 —— 实测代价高达 32.5 个百分点。
7
+ *
8
+ * 实测(200 条 held-out,laya-train/scripts/format_probe.py):
9
+ * laya 原样 state + laya 原样 key → 79.00%
10
+ * roy JSON state + laya key → 56.50%
11
+ * laya state + roy key → 63.00%
12
+ * roy JSON state + roy key → 46.50%
13
+ */
14
+ import { layaCandidateKey, layaCandidateValue, LAYA_NEW_TASK_DESC } from "./laya-render";
15
+ import type { DecisionRenderer } from "./decision-hint-plugin";
16
+ /** Stage A 的 instructions —— 两个载体各自训练时用的措辞 */
17
+ export declare const SYSTEMONE_STAGE_A_INSTRUCTIONS = "\u5224\u65AD\u672C\u6B21\u300E\u65B0\u4EFB\u52A1\u8BF7\u6C42\u300F\u5E94\u8BE5\u65B0\u5EFA\u8FD8\u662F\u590D\u7528\u67D0\u4E2A\u5DF2\u6709\u4EFB\u52A1\u3002\u82E5\u5E94\u590D\u7528\uFF0C\u9009\u8BE5\u4EFB\u52A1\u7684 #id\uFF1B\u82E5\u5E94\u65B0\u5EFA\uFF0C\u9009 NEW_TASK\u3002\u8F93\u51FA choice/confidence/probabilities\u3002";
18
+ export declare const LAYA_STAGE_A_INSTRUCTIONS = "\u4F18\u5148\u590D\u7528\uFF1A\u82E5\u5019\u9009\u4E0E\u8BF7\u6C42\u5C5E\u4E8E\u540C\u4E00\u5DE5\u4F5C\u6D41/\u540C\u4E00\u7CFB\u5217/\u76EE\u6807\u91CD\u53E0\uFF0C\u5FC5\u987B\u9009\u8BE5\u5019\u9009\u7684 #id\uFF1B\u4EC5\u5F53\u5B8C\u5168\u65E0\u5173\u65F6\u624D\u9009 NEW_TASK\u3002";
19
+ /**
20
+ * systemone 渲染器 —— 阿里百炼 systemone / #4107 的默认形态。
21
+ *
22
+ * state 是 JSON 对象(`task_to_route` + `similar_tasks`),
23
+ * criteria key = `#<id> [<type>] <title[:40]>`。
24
+ */
25
+ export declare const systemoneRenderer: DecisionRenderer;
26
+ /**
27
+ * laya 渲染器 —— #4614 的 Laya 微调模型的训练格式。
28
+ *
29
+ * state 是中文文本,候选**重复**在 state 与 criteria 里(训练时的形态)。
30
+ * criteria key = `#<id> <title[:50]>`(无 `[type]` 段)。
31
+ */
32
+ export declare const layaRenderer: DecisionRenderer;
33
+ export { LAYA_NEW_TASK_DESC, layaCandidateKey, layaCandidateValue };
@@ -2,6 +2,7 @@
2
2
  * @fileoverview Task Plugins Module
3
3
  */
4
4
  export { TaskTagPlugin } from "./task-tag-plugin";
5
+ export { LayaDecisionPlugin, createLayaDecisionPlugin, LAYA_REUSE_THRESHOLD, type LayaDecisionPluginOptions, } from "./laya-decision-plugin";
5
6
  export { createAutoTaskPlugin, type AutoTaskPluginConfig, } from "./auto-task-plugin";
6
7
  export { WORKFLOW_EXTRACTOR_PROMPT, formatExtractorInput, formatExtractorInputFromDescription, parseExtractorOutput, } from "./workflow-extractor-agent";
7
8
  export { LarkCliTaskNotifyHook, createLarkCliTaskNotifyHook, type TaskNotifyConfig, NotifyType, } from "./lark-cli-task-notify-plugin";
@@ -1,20 +1,24 @@
1
1
  import {
2
+ LAYA_REUSE_THRESHOLD,
2
3
  LarkCliTaskNotifyHook,
4
+ LayaDecisionPlugin,
3
5
  NotifyType,
4
6
  PerTaskGroupChatPlugin,
5
7
  TaskTagPlugin,
6
8
  WORKFLOW_EXTRACTOR_PROMPT,
7
9
  createAutoTaskPlugin,
8
10
  createLarkCliTaskNotifyHook,
11
+ createLayaDecisionPlugin,
9
12
  createPerTaskGroupChatPlugin,
10
13
  formatExtractorInput,
11
14
  formatExtractorInputFromDescription,
12
15
  parseExtractorOutput
13
- } from "../../../shared/@ai-setting/roy-agent-core-ry2eztft.js";
16
+ } from "../../../shared/@ai-setting/roy-agent-core-f6c0nadn.js";
14
17
  import"../../../shared/@ai-setting/roy-agent-core-na18zwj0.js";
15
18
  import"../../../shared/@ai-setting/roy-agent-core-9bs6xrav.js";
16
19
  import"../../../shared/@ai-setting/roy-agent-core-1pg0fepg.js";
17
20
  import"../../../shared/@ai-setting/roy-agent-core-6masakz4.js";
21
+ import"../../../shared/@ai-setting/roy-agent-core-tr3ep81v.js";
18
22
  import {
19
23
  DEFAULT_APP_LINK_BASE,
20
24
  LarkCardPatchNotifier,
@@ -63,6 +67,7 @@ export {
63
67
  formatDurationMs,
64
68
  formatDateTimeForDisplay,
65
69
  createPerTaskGroupChatPlugin,
70
+ createLayaDecisionPlugin,
66
71
  createLarkCliTaskNotifyHook,
67
72
  createAutoTaskPlugin,
68
73
  buildWorkflowCard,
@@ -72,7 +77,9 @@ export {
72
77
  TaskTagPlugin,
73
78
  PerTaskGroupChatPlugin,
74
79
  NotifyType,
80
+ LayaDecisionPlugin,
75
81
  LarkCliTaskNotifyHook,
76
82
  LarkCardPatchNotifier,
83
+ LAYA_REUSE_THRESHOLD,
77
84
  DEFAULT_APP_LINK_BASE
78
85
  };
@@ -0,0 +1,136 @@
1
+ /**
2
+ * @fileoverview Task #4620 — Laya 决策插件:把 #4467 的 decision 检测接到本地 Laya 服务
3
+ *
4
+ * 与 `DecisionHintPlugin`(#4481,阿里百炼 provider)的关系:
5
+ * - `DecisionHintPlugin` 是**载体无关**的判定逻辑(Stage A/B、门槛、hint 渲染)
6
+ * - 本文件是**载体接入层**:读 `~/.roy-agent/auth.json` 的 `providers["laya-decision"]`,
7
+ * 构造 `LayaDecisionPort`,再把 port 注入 `DecisionHintPlugin`
8
+ *
9
+ * 形态:**只提示,不拦截**(与 #4467 契约一致)。
10
+ *
11
+ * ── fail-open 总原则(硬要求) ────────────────────────────────────────────────
12
+ *
13
+ * 本插件挂在 `task:before.create` 上,**任何**异常都不得影响 task_create:
14
+ * - auth.json 读不到 / provider 不存在 → 不装 port → 插件直接跳过
15
+ * - 网络错误 / 超时 / HTTP 非 2xx / 响应结构不对 → 判定降级为 null → 不出 hint
16
+ * - 熔断后 → isDisabled() → 跳过
17
+ * - 候选为空 → 不调模型
18
+ *
19
+ * 因此这里的**每一个** await 都有 try/catch 包裹,且 catch 里只记日志、绝不 rethrow。
20
+ *
21
+ * ── 置信度门禁 ──────────────────────────────────────────────────────────────
22
+ *
23
+ * 复用建议("应复用 #X")是**破坏性建议**(用户可能因此不建新任务),
24
+ * 按 #4467 的 Q4 拍板用高门槛(`REUSE_ADOPT_THRESHOLD` 之上单独一档;
25
+ * Task #4643 起默认 **≥ 0.92**)。
26
+ * laya 的 `x_jev_confidence` 在 896 条实测中:thr=0.85 → 提示精确率 80.5%、
27
+ * thr=0.90 → 84.9%、thr=0.95 → 87.0%(coverage 28.7% / 25.9% / 18.9%)。
28
+ * ⚠️ Task #4643 live 实测(25 条 gold unseen):0.85~0.92 区间零 case,
29
+ * 误报的 jev 全部 ≥0.938 —— 该模型「错得自信」,单纯提阈值滤不掉误报。
30
+ */
31
+ import type { DecisionPort } from "./decision-port";
32
+ import type { TaskCreateContext } from "../hooks/contexts";
33
+ /** 复用建议的置信度门禁(#4467 Q4:破坏性动作门槛;Task #4643 提到 0.92) */
34
+ export declare const LAYA_REUSE_THRESHOLD = 0.92;
35
+ export interface LayaDecisionPluginOptions {
36
+ /** auth.json 路径(默认 ~/.roy-agent/auth.json) */
37
+ authPath?: string;
38
+ /** 硬超时(默认 2000ms) */
39
+ timeoutMs?: number;
40
+ /** 连续超时多少次后自我禁用(默认 3) */
41
+ maxStrikes?: number;
42
+ /** 置信度来源(默认 auto:x_jev_confidence 优先) */
43
+ confidencePref?: "auto" | "jev" | "entropy";
44
+ /** 注入 fetch(测试用) */
45
+ fetchImpl?: typeof globalThis.fetch;
46
+ /** 注入 port(测试用;给了就不读 auth.json) */
47
+ decisionPort?: DecisionPort;
48
+ /** 注入上下文读取(生产用 getEnvContextOrEmpty().parentTaskId) */
49
+ envParentTaskIdProvider?: () => number | undefined | null;
50
+ /**
51
+ * 「建议复用」硬提示的置信度门禁(默认 LAYA_REUSE_THRESHOLD = 0.92)。
52
+ * 896 条实测:0.85 → 精确率 80.5%(coverage 28.7%)、0.9 → 84.9%、0.95 → 87.0%。
53
+ */
54
+ reuseThreshold?: number;
55
+ /** 低于此置信度完全不出提示(默认与 reuseThreshold 同值 = 0.92) */
56
+ softHintThreshold?: number;
57
+ /**
58
+ * 单候选快捷路径(默认 false)。
59
+ * 开启后「只有 1 个候选 → 不调模型直接提示」—— 该路径**没有置信度**,
60
+ * 与「必须门禁 ≥0.92」冲突,所以默认关掉。
61
+ */
62
+ singleCandidateShortcut?: boolean;
63
+ }
64
+ /**
65
+ * Laya 决策插件 —— 挂在 `task:before.create` 上。
66
+ *
67
+ * 用法:
68
+ * ```ts
69
+ * const plugin = new LayaDecisionPlugin();
70
+ * await plugin.execute(ctx); // ctx: TaskCreateContext | { data: TaskCreateContext }
71
+ * ```
72
+ *
73
+ * ⚠️ `execute()` **永远不抛**。
74
+ */
75
+ export declare class LayaDecisionPlugin {
76
+ readonly name = "laya-decision";
77
+ readonly version = "1.0.0";
78
+ readonly description = "Task #4620 \u2014 task:before.create \u51B3\u7B56\u68C0\u6D4B\uFF08\u672C\u5730 Laya \u670D\u52A1\u8F7D\u4F53\uFF0C\u53EA\u63D0\u793A\u4E0D\u62E6\u622A\uFF09";
79
+ private readonly options;
80
+ private port;
81
+ private portResolved;
82
+ private inner;
83
+ constructor(options?: LayaDecisionPluginOptions);
84
+ /** 当前是否已自我禁用(熔断)—— 供观测 */
85
+ isDisabled(): boolean;
86
+ /**
87
+ * hook 入口 —— **永不抛**。
88
+ *
89
+ * 失败路径一律是「不出 hint」,不影响 task_create 的成功语义。
90
+ */
91
+ execute(ctx: TaskCreateContext | {
92
+ data: TaskCreateContext;
93
+ }): Promise<void>;
94
+ /** 懒加载 + 缓存 port(读 auth.json 只做一次) */
95
+ private ensureInner;
96
+ private resolvePort;
97
+ }
98
+ /** 取进程级共享实例(仅生产路径用;传了 options 就新建,避免测试串味) */
99
+ export declare function getSharedLayaDecisionPlugin(options?: LayaDecisionPluginOptions): LayaDecisionPlugin;
100
+ /**
101
+ * Task #4627 — 把 LayaDecisionPlugin 包成**可注册到 task hook** 的形态。
102
+ *
103
+ * ⚠️ 为什么不是 Agent `Plugin`:
104
+ * - `AgentComponent.registerPlugin(agentName, plugin)` 只把 plugin 放进
105
+ * `agent.plugins` Map,**不写 globalHookManager**。
106
+ * - `agent.plugins` 只由 `executePluginHooks()` 派发,且只在 **agent 作用域**
107
+ * 的 hook 点被调用(`agent:before.start` / `agent:before.llm` / `agent:before.tool`
108
+ * / `agent:after.tool` / `agent:on.iteration` / `agent:after.react`
109
+ * / `agent:after.complete` / `agent:on.error` / `on.error`)
110
+ * —— **不含 `task:before.create`**。
111
+ * - `task:before.create` 真正的派发路径是:
112
+ * `TaskComponent.createTask()` → `executeHook(TaskHookPoints.BEFORE_CREATE, createCtx)`
113
+ * → `globalHookManager.execute("task:before.create", data, metadata)`
114
+ * - 所以正确注册方式是 `taskComponent.registerHook(TaskHookPoints.BEFORE_CREATE, {...})`。
115
+ *
116
+ * 附带类型问题(Agent `Plugin` 这条路本身走不通):
117
+ * - `agent/types.ts` 的 `HookPoint` 联合类型里**没有** `"task:before.create"`
118
+ * (旧代码用 `as HookPoint` 强转绕过 → 运行期不报错但永远不触发)
119
+ * - `hook/types.ts` 的 `HookResult` 是 `{stopped?, action?, results?}`,
120
+ * **没有** `continue` 字段(旧代码 `return { continue: true }` 不匹配)
121
+ *
122
+ * 本工厂返回的是 `Hook` 形态(`hook/types.ts`),由
123
+ * `registerLayaDecisionTaskHook()` 负责注册到 task hook 通道。
124
+ *
125
+ * ⚠️ fail-open:注册失败不影响 agent 启动(调用方自行 try/catch)。
126
+ */
127
+ export declare function createLayaDecisionPlugin(options?: LayaDecisionPluginOptions): {
128
+ /** Hook 唯一名称(用于幂等去重 / unregister) */
129
+ name: string;
130
+ /** 执行优先级 */
131
+ priority: number;
132
+ /** hook 执行体 —— 永不抛 */
133
+ execute: (ctx: unknown) => Promise<void>;
134
+ /** 内层插件实例(供观测 / 测试) */
135
+ plugin: LayaDecisionPlugin;
136
+ };
@@ -0,0 +1,143 @@
1
+ /**
2
+ * @fileoverview Task #4620 — Laya 自托管决策服务的 DecisionPort 实现
3
+ *
4
+ * 背景:#4467 的 decision 检测最初接的是阿里百炼的 systemone(`ali-decision`
5
+ * provider)。#4614 把一个 Laya 微调模型训到严格 held-out 81.8%,本文件把它
6
+ * 封装成**标准 Jev 语义服务**后接进同一套 `DecisionPort` 抽象。
7
+ *
8
+ * ── 协议对照(与阿里百炼 systemone 同构,服务端实测) ────────────────────────
9
+ *
10
+ * | 维度 | 阿里百炼 systemone | Laya laya-serve |
11
+ * |----------------|-------------------------------|----------------------------|
12
+ * | 请求 URL | baseURL 本身就是请求地址 | `{baseURL}/v1/systemone` |
13
+ * | 请求体顶层键 | state / questions | state / questions |
14
+ * | body.model | decision-model-preview | task-decision |
15
+ * | 鉴权 header | Authorization: Bearer <key> | Authorization: Bearer <key>|
16
+ * | 响应 answers | { qid: {choice, confidence, | 同构 + 额外字段 |
17
+ * | | probabilities} } | x_jev_confidence |
18
+ * | | | answer_confidence |
19
+ * | | | action.act_probability |
20
+ * | usage | { input_tokens } | { input_tokens, |
21
+ * | | | state_tokens, |
22
+ * | | | state_tokens_dropped, |
23
+ * | | | truncated, ... } |
24
+ * | routing | 无 | { model, repo } |
25
+ *
26
+ * ⚠️ **confidence 语义陷阱(#302)**:laya 返回两个置信度:
27
+ * - `confidence` = 归一化熵(不是 Jev 公式置信度)
28
+ * - `x_jev_confidence` = Jev 公式算出的置信度
29
+ * 按 laya 源码注释,**从 Jev 移植阈值的客户端应读 `x_jev_confidence`**。
30
+ * 本 port 默认用 `x_jev_confidence`,缺失时回退 `answer_confidence` → `confidence`。
31
+ * 用 `confidencePref` 可强制指定来源(便于 A/B 对比)。
32
+ *
33
+ * ⚠️ **state 截断**:laya 的 usage 会报 `state_tokens_dropped` / `truncated`。
34
+ * 本 port **不静默吞掉**这个信号 —— 它把 `truncated` 抬到 answer 的 `truncated`
35
+ * 字段上,让调用方(插件)能降级或记录。截断意味着候选描述被部分丢弃,
36
+ * 模型的判别力会下降,属于**必须观测**的状态。
37
+ *
38
+ * ── 与 `SystemOneDecisionPort` 的关系 ──────────────────────────────────────
39
+ *
40
+ * 二者实现同一套超时 / 熔断 / 泄漏断言语义,但:
41
+ * - SystemOne: baseURL **就是**请求地址;响应无 x_jev_confidence
42
+ * Laya: baseURL 是**服务根**,请求地址是 `{baseURL}/v1/systemone`;
43
+ * 多一层 x_jev_confidence 的解析与截断信号
44
+ * 抽出一个共用的基类会引入"URL 拼不拼"的分支混乱,所以这里独立实现,
45
+ * 共享的纯逻辑(超时判定、熔断计数)保持一致的语义约定。
46
+ */
47
+ import type { DecisionPort, DecisionRequest, DecisionResponse } from "./decision-port";
48
+ /** laya 的 answers 里多出来的字段(父接口只声明了 choice/confidence/probabilities) */
49
+ export interface LayaDecisionAnswer {
50
+ choice: string;
51
+ confidence: number;
52
+ /** Jev 公式置信度 —— #302:从 Jev 移植阈值的客户端应读这个 */
53
+ x_jev_confidence?: number;
54
+ /** 温度缩放后的答案置信度 */
55
+ answer_confidence?: number;
56
+ probabilities?: Record<string, number>;
57
+ action?: {
58
+ act_probability?: number;
59
+ };
60
+ /** laya 报 state 被截断时为 true —— 本 port 从 usage 抬升上来 */
61
+ truncated?: boolean;
62
+ }
63
+ export interface LayaDecisionResponse extends DecisionResponse {
64
+ answers: Record<string, LayaDecisionAnswer>;
65
+ usage?: {
66
+ input_tokens?: number;
67
+ output_tokens?: number;
68
+ state_tokens?: number;
69
+ state_tokens_dropped?: number;
70
+ truncated?: boolean;
71
+ truncated_questions?: string[];
72
+ };
73
+ routing?: {
74
+ model?: string;
75
+ repo?: string;
76
+ };
77
+ }
78
+ export interface LayaDecisionPortOptions {
79
+ /** 服务根地址,例如 http://127.0.0.1:8901(**不带** /v1/systemone) */
80
+ baseURL: string;
81
+ apiKey: string;
82
+ /** 注册在服务上的模型名(默认 task-decision) */
83
+ model?: string;
84
+ /** 硬超时(默认 2000ms) */
85
+ timeoutMs?: number;
86
+ /** 连续超时多少次后自我禁用(默认 3) */
87
+ maxStrikes?: number;
88
+ /** 注入 fetch(测试用) */
89
+ fetchImpl?: typeof globalThis.fetch;
90
+ /**
91
+ * 置信度来源:auto(默认,优先 x_jev_confidence)/ jev / entropy
92
+ * - auto:x_jev_confidence → answer_confidence → confidence
93
+ * - jev:只用 x_jev_confidence(缺失则视为 0)
94
+ * - entropy:只用 confidence(归一化熵)
95
+ */
96
+ confidencePref?: "auto" | "jev" | "entropy";
97
+ }
98
+ export declare class LayaDecisionPort implements DecisionPort {
99
+ private readonly baseURL;
100
+ private readonly apiKey;
101
+ private readonly model;
102
+ private readonly timeoutMs;
103
+ private readonly maxStrikes;
104
+ private readonly fetchImpl;
105
+ private readonly confidencePref;
106
+ /** 同 session 连续超时计数 */
107
+ private timeoutStrikes;
108
+ private disabled;
109
+ constructor(options: LayaDecisionPortOptions);
110
+ isDisabled(): boolean;
111
+ /** 请求地址:baseURL 是服务根 → 拼 /v1/systemone */
112
+ get endpoint(): string;
113
+ decide(req: DecisionRequest): Promise<LayaDecisionResponse>;
114
+ /**
115
+ * 归一化响应:
116
+ * 1. 把 usage 里的截断信号抬到每个 answer 的 `truncated` 上
117
+ * 2. 统一置信度字段(`confidence` 保持原义,另给 `jevConfidence`)
118
+ */
119
+ private normalize;
120
+ /**
121
+ * 置信度选取(#302 的核心)。
122
+ *
123
+ * auto 顺序:x_jev_confidence → answer_confidence → confidence
124
+ * jev 顺序:只用 x_jev_confidence(没有则 0)
125
+ * entropy 顺序:只用 confidence
126
+ */
127
+ private pickConfidence;
128
+ }
129
+ export interface LayaAuthConfig {
130
+ baseURL: string;
131
+ apiKey: string;
132
+ model: string;
133
+ }
134
+ /**
135
+ * 从 `~/.roy-agent/auth.json` 的 `providers["laya-decision"]` 读取端点配置。
136
+ *
137
+ * 路径:~/.roy-agent/auth.json → providers["laya-decision"].baseURL
138
+ * → providers["laya-decision"].apiKey
139
+ * → providers["laya-decision"].model(可选)
140
+ *
141
+ * ⚠️ 读不到就返回 null —— 调用方必须 fail-open(不提示、不报错)。
142
+ */
143
+ export declare function loadLayaAuth(authPath?: string): Promise<LayaAuthConfig | null>;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * @fileoverview Task #4620 — Laya 专用的 state / criteria 渲染
3
+ *
4
+ * ── 为什么需要这个文件 ──────────────────────────────────────────────────────
5
+ *
6
+ * `decision-hint-plugin.ts`(#4481)里的 `buildStageARequest` 是按**阿里百炼
7
+ * systemone** 的习惯写的:state 传一个 JSON 对象,criteria key 用
8
+ * `#<id> [<type>] <title>`。
9
+ *
10
+ * 但 #4614 训出来的 Laya 模型是在**完全不同的渲染格式**上训练的
11
+ * (`laya-train/scripts/build_dataset.py`):
12
+ *
13
+ * state = "【新任务标题】{title}\n"
14
+ * "【新任务描述】{desc[:600]}\n"
15
+ * "【候选任务】\n"
16
+ * "{key}\n {value}\n..." ← 候选也**重复**在 state 里
17
+ * key = "#{id} {title[:50]}" ← 没有 [type] 段
18
+ * value = "{status_letter}|{ago}|{desc[:45]} [tags: a,b,c,d]" ← 与 roy-agent 相同
19
+ *
20
+ * Task #4620 实测(200 条 held-out,scripts/format_probe.py):
21
+ *
22
+ * | state 格式 | key 格式 | 准确率 |
23
+ * |-------------|----------|----------|
24
+ * | laya 原样 | laya 原样 | **79.00%** |
25
+ * | roy JSON | laya 原样 | 56.50% |
26
+ * | laya 原样 | roy | 63.00% |
27
+ * | roy JSON | roy | 46.50% |
28
+ *
29
+ * → **两个格式都要对齐**:state 换成 laya 的中文文本形态,key 换成 laya 的
30
+ * `#{id} {title[:50]}`(去掉 `[type]` 段)。这是把 46.5% 拉回 79% 的关键。
31
+ *
32
+ * ── 与roy-agent 的 key 映射 ─────────────────────────────────────────────────
33
+ *
34
+ * laya 的 key 是 `#{id} {title[:50]}`,模型回的 choice 也是这个形态。
35
+ * 插件要把 choice 映射回 task id —— 见 `matchChoiceToCandidate`。
36
+ */
37
+ /** laya 训练时的候选 key 长度上限(build_dataset.py: `t["title"][:50]`) */
38
+ export declare const LAYA_CANDIDATE_TITLE_MAX = 50;
39
+ /** laya 训练时的候选描述长度上限(build_dataset.py: `t["desc"][:45]`) */
40
+ export declare const LAYA_CANDIDATE_DESC_MAX = 45;
41
+ /** laya 训练时的 tags 展示上限(build_dataset.py: `t["tags"][:4]`) */
42
+ export declare const LAYA_CANDIDATE_TAGS_MAX = 4;
43
+ /** 新任务描述在 state 里的长度上限(build_dataset.py: `req_desc[:600]`) */
44
+ export declare const LAYA_STATE_DESC_MAX = 600;
45
+ /** laya 训练时的 NEW_TASK 选项描述(必须逐字一致,见 build_dataset.py) */
46
+ export declare const LAYA_NEW_TASK_DESC = "\u65B0\u5EFA\u4E3A\u72EC\u7ACB\u4EFB\u52A1\uFF08\u4E0E\u6240\u6709\u73B0\u6709\u5019\u9009\u5747\u65E0\u660E\u786E\u5173\u8054\uFF09";
47
+ export interface LayaCandidate {
48
+ id: number;
49
+ title: string;
50
+ status: string;
51
+ /** 相对时间,形如 "3d" / "5h" / "12m" */
52
+ ago: string;
53
+ description: string;
54
+ tags: string[];
55
+ }
56
+ export interface LayaStateInput {
57
+ title: string;
58
+ description: string;
59
+ /** Task #4669 — req 任务自身的 tags(渲染成【请求标签】行;无 tags 时省略) */
60
+ tags?: string[];
61
+ candidates: LayaCandidate[];
62
+ }
63
+ /** laya key = `#{id} {title[:50]}` —— 注意**没有** `[type]` 段 */
64
+ export declare function layaCandidateKey(c: {
65
+ id: number;
66
+ title: string;
67
+ }): string;
68
+ /**
69
+ * laya value = `{status_letter}|{ago}|{desc[:45]} [tags: a,b,c,d]`
70
+ *
71
+ * 与 roy-agent 的 `renderCandidateValue`(#4107 模板)**结构一致**,
72
+ * 只有 status 字母大小写不同(laya 训练用大写)。
73
+ */
74
+ export declare function layaCandidateValue(c: {
75
+ status: string;
76
+ ago: string;
77
+ description: string;
78
+ tags?: string[];
79
+ }): string;
80
+ /**
81
+ * 渲染 laya 训练格式的 state。
82
+ *
83
+ * ⚠️ 候选**同时**出现在 state 文本里和 criteria 里 —— 这是训练时的形态,
84
+ * 不是冗余:模型是从 state 的自然语言里读候选的。
85
+ */
86
+ export declare function renderLayaState(input: LayaStateInput): string;
87
+ /**
88
+ * 渲染 laya 训练格式的 criteria。
89
+ *
90
+ * NEW_TASK 的**描述必须逐字**用 `LAYA_NEW_TASK_DESC` —— 训练集里每个 case
91
+ * 都是这句话,换字会让模型对「新建」这个选项的先验偏移。
92
+ */
93
+ export declare function renderLayaCriteria(candidates: LayaCandidate[]): Record<string, string>;
94
+ /**
95
+ * 把模型回的 choice 映射回候选 task id。
96
+ *
97
+ * laya 的 choice 是完整的 key(`#1234 标题...`),但也可能被截断成裸 `#1234`。
98
+ * 两种都要认。
99
+ */
100
+ export declare function matchChoiceToCandidate(choice: string, candidates: LayaCandidate[]): number | undefined;
@@ -9,7 +9,8 @@ import {
9
9
  searchTasksTool,
10
10
  treeTasksTool,
11
11
  updateTaskTool
12
- } from "../../../shared/@ai-setting/roy-agent-core-xw4wsj38.js";
12
+ } from "../../../shared/@ai-setting/roy-agent-core-t0ktaevq.js";
13
+ import"../../../shared/@ai-setting/roy-agent-core-tr3ep81v.js";
13
14
  import"../../../shared/@ai-setting/roy-agent-core-7cbpmk4q.js";
14
15
  import"../../../shared/@ai-setting/roy-agent-core-qq96p0tj.js";
15
16
  import"../../../shared/@ai-setting/roy-agent-core-7f7t9wwp.js";
package/dist/index.js CHANGED
@@ -61,21 +61,21 @@ import {
61
61
  BaseEnvironment,
62
62
  generateDescendingId,
63
63
  generateId
64
- } from "./shared/@ai-setting/roy-agent-core-vncwfzw5.js";
64
+ } from "./shared/@ai-setting/roy-agent-core-6z412atw.js";
65
65
  import {
66
66
  TaskComponent
67
- } from "./shared/@ai-setting/roy-agent-core-e4hehcdr.js";
67
+ } from "./shared/@ai-setting/roy-agent-core-hmdcz3g9.js";
68
68
  import"./shared/@ai-setting/roy-agent-core-amkqnyzy.js";
69
69
  import"./shared/@ai-setting/roy-agent-core-jm136jzh.js";
70
70
  import"./shared/@ai-setting/roy-agent-core-hjejpcg5.js";
71
71
  import"./shared/@ai-setting/roy-agent-core-fttgm52a.js";
72
72
  import"./shared/@ai-setting/roy-agent-core-gxzxq1sf.js";
73
73
  import"./shared/@ai-setting/roy-agent-core-8gxth0eh.js";
74
- import"./shared/@ai-setting/roy-agent-core-xw4wsj38.js";
74
+ import"./shared/@ai-setting/roy-agent-core-t0ktaevq.js";
75
75
  import"./shared/@ai-setting/roy-agent-core-rn57psp0.js";
76
76
  import {
77
77
  EventSourceComponent
78
- } from "./shared/@ai-setting/roy-agent-core-vd9zak15.js";
78
+ } from "./shared/@ai-setting/roy-agent-core-e42v9bcq.js";
79
79
  import {
80
80
  BountyIMInstance,
81
81
  TimerInstance,
@@ -87,7 +87,7 @@ import {
87
87
  larkCliHandler,
88
88
  resolveBountyIMSystemPrompt,
89
89
  timerHandler
90
- } from "./shared/@ai-setting/roy-agent-core-1h5bbqcg.js";
90
+ } from "./shared/@ai-setting/roy-agent-core-18am1h07.js";
91
91
  import {
92
92
  BUILT_IN_EVENT_SOURCE_TYPES,
93
93
  BUILT_IN_EVENT_SOURCE_TYPE_LIST,
@@ -112,7 +112,7 @@ import {
112
112
  import {
113
113
  TaskTagPlugin,
114
114
  createAutoTaskPlugin
115
- } from "./shared/@ai-setting/roy-agent-core-ry2eztft.js";
115
+ } from "./shared/@ai-setting/roy-agent-core-f6c0nadn.js";
116
116
  import {
117
117
  EventStoreImpl,
118
118
  getSharedEventStore,
@@ -130,6 +130,7 @@ import {
130
130
  getBuiltInPrompt,
131
131
  getBuiltInPromptNames
132
132
  } from "./shared/@ai-setting/roy-agent-core-6masakz4.js";
133
+ import"./shared/@ai-setting/roy-agent-core-tr3ep81v.js";
133
134
  import"./shared/@ai-setting/roy-agent-core-0ys85x46.js";
134
135
  import"./shared/@ai-setting/roy-agent-core-zk2jg0v7.js";
135
136
  import {
@@ -13,7 +13,7 @@ import {
13
13
  createPerTaskGroupChatPlugin,
14
14
  extractPerTaskChatId,
15
15
  extractPerTaskChatName
16
- } from "./roy-agent-core-ry2eztft.js";
16
+ } from "./roy-agent-core-f6c0nadn.js";
17
17
  import {
18
18
  getSharedEventStore
19
19
  } from "./roy-agent-core-na18zwj0.js";