aitable-workflow-cli 0.1.23 → 0.1.24

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 (77) hide show
  1. package/README.md +1 -6
  2. package/dist/{chunk-IGB6WDLR.js → chunk-UNPV6DTX.js} +1 -1
  3. package/dist/cli.js +47 -47
  4. package/dist/config-set-FBOQBRSJ.js +13 -0
  5. package/dist/{config-ui-server-CORDAOYI.js → config-ui-server-IUSACAOT.js} +1 -1
  6. package/package.json +3 -3
  7. package/dist/config-set-EGLLIMVJ.js +0 -13
  8. package/templates/support-qa/.env.example +0 -16
  9. package/templates/support-qa/aitable-workflow.config.yml +0 -46
  10. package/templates/support-qa/coding-agents.config.json +0 -18
  11. package/templates/support-qa/lib/__tests__/declarative-matcher.test.ts +0 -240
  12. package/templates/support-qa/lib/__tests__/dingtalk-group-membership.test.ts +0 -144
  13. package/templates/support-qa/lib/__tests__/memory-events.test.ts +0 -90
  14. package/templates/support-qa/lib/__tests__/sandbox-agent.test.ts +0 -382
  15. package/templates/support-qa/lib/__tests__/thread-adapter.test.ts +0 -242
  16. package/templates/support-qa/lib/__tests__/thread-observe-step.test.ts +0 -391
  17. package/templates/support-qa/lib/__tests__/thread-observer-handler.test.ts +0 -274
  18. package/templates/support-qa/lib/action-dispatch.ts +0 -262
  19. package/templates/support-qa/lib/contract.ts +0 -253
  20. package/templates/support-qa/lib/declarative-matcher.ts +0 -398
  21. package/templates/support-qa/lib/dingtalk-group-membership.ts +0 -158
  22. package/templates/support-qa/lib/guards.ts +0 -18
  23. package/templates/support-qa/lib/memory-events.ts +0 -113
  24. package/templates/support-qa/lib/owner-resolver.ts +0 -94
  25. package/templates/support-qa/lib/preflight.ts +0 -294
  26. package/templates/support-qa/lib/profile.ts +0 -51
  27. package/templates/support-qa/lib/sandbox-agent.ts +0 -653
  28. package/templates/support-qa/lib/thread-adapter.ts +0 -281
  29. package/templates/support-qa/lib/thread-digest.ts +0 -144
  30. package/templates/support-qa/npmrc +0 -1
  31. package/templates/support-qa/package.json +0 -27
  32. package/templates/support-qa/profiles/aitable/profile/escalation/contacts.json +0 -37
  33. package/templates/support-qa/profiles/aitable/profile/escalation/owner-map.json +0 -18
  34. package/templates/support-qa/profiles/aitable/profile/fastpath/known-answers.yml +0 -561
  35. package/templates/support-qa/profiles/aitable/profile/profile.yml +0 -219
  36. package/templates/support-qa/profiles/aitable/profile/prompts/kb-editor.md +0 -3
  37. package/templates/support-qa/profiles/aitable/profile/prompts/persona.md +0 -49
  38. package/templates/support-qa/profiles/aitable/profile/prompts/troubleshooter.md +0 -5
  39. package/templates/support-qa/profiles/aitable/profile/troubleshoot/classifiers.yml +0 -36
  40. package/templates/support-qa/profiles/aitable/skills/troubleshooting/SKILL.md +0 -58
  41. package/templates/support-qa/profiles/aitable/skills/troubleshooting/aitable-import-troubleshooter/SKILL.md +0 -485
  42. package/templates/support-qa/profiles/aitable/skills/troubleshooting/aitable-openapi-troubleshooter/SKILL.md +0 -183
  43. package/templates/support-qa/profiles/aitable/skills/troubleshooting/import-openapi-e2e-troubleshooter/SKILL.md +0 -459
  44. package/templates/support-qa/profiles/aitable/skills/troubleshooting/notable-datasource-troubleshooter/SKILL.md +0 -1286
  45. package/templates/support-qa/profiles/aitable/skills/troubleshooting/notable-field-troubleshooter/SKILL.md +0 -673
  46. package/templates/support-qa/profiles/aitable/skills/troubleshooting/spreadsheet-datasync-troubleshooter/SKILL.md +0 -322
  47. package/templates/support-qa/profiles/default/profile/fastpath/known-answers.yml +0 -32
  48. package/templates/support-qa/profiles/default/profile/profile.yml +0 -217
  49. package/templates/support-qa/profiles/default/profile/troubleshoot/classifiers.yml +0 -37
  50. package/templates/support-qa/profiles/default/wiki/support//345/270/270/350/247/201/351/227/256/351/242/230.md +0 -10
  51. package/templates/support-qa/tsconfig.json +0 -16
  52. package/templates/support-qa/vitest.config.ts +0 -9
  53. package/templates/support-qa/workflows/admin-kb-update/recipes/kb-refine.recipe.ts +0 -137
  54. package/templates/support-qa/workflows/admin-kb-update/recipes/kb_sync_handler.recipe.ts +0 -119
  55. package/templates/support-qa/workflows/admin-kb-update/workflow.yml +0 -96
  56. package/templates/support-qa/workflows/config-sync/recipes/config_sync.recipe.ts +0 -668
  57. package/templates/support-qa/workflows/config-sync/recipes/config_sync_handler.recipe.ts +0 -16
  58. package/templates/support-qa/workflows/config-sync/workflow.yml +0 -62
  59. package/templates/support-qa/workflows/daily-review/recipes/daily_review.recipe.ts +0 -602
  60. package/templates/support-qa/workflows/daily-review/recipes/daily_review_handler.recipe.ts +0 -26
  61. package/templates/support-qa/workflows/daily-review/workflow.yml +0 -65
  62. package/templates/support-qa/workflows/group-qa/recipes/answer.recipe.ts +0 -231
  63. package/templates/support-qa/workflows/group-qa/recipes/conversation_handler.recipe.ts +0 -394
  64. package/templates/support-qa/workflows/group-qa/recipes/fastpath.recipe.ts +0 -72
  65. package/templates/support-qa/workflows/group-qa/recipes/on-action.recipe.ts +0 -225
  66. package/templates/support-qa/workflows/group-qa/recipes/reply-to-group.recipe.ts +0 -202
  67. package/templates/support-qa/workflows/group-qa/recipes/troubleshoot.recipe.ts +0 -273
  68. package/templates/support-qa/workflows/group-qa/workflow.yml +0 -268
  69. package/templates/support-qa/workflows/thread-observer/README.md +0 -96
  70. package/templates/support-qa/workflows/thread-observer/recipes/thread_observe.recipe.ts +0 -415
  71. package/templates/support-qa/workflows/thread-observer/recipes/thread_observer_handler.recipe.ts +0 -213
  72. package/templates/support-qa/workflows/thread-observer/workflow.yml +0 -185
  73. package/templates/support-qa/workspaces/daily-review/AGENTS.md +0 -233
  74. package/templates/support-qa/workspaces/kb-editor/AGENTS.md +0 -32
  75. package/templates/support-qa/workspaces/support/AGENTS.md +0 -156
  76. package/templates/support-qa/workspaces/thread-observer/AGENTS.md +0 -156
  77. package/templates/support-qa/workspaces/troubleshooter/AGENTS.md +0 -71
@@ -1,262 +0,0 @@
1
- import type { SupportAction } from "./contract.js";
2
- import type { ActionPolicy } from "./profile.js";
3
- import { speaksInGroup } from "./profile.js";
4
- import type { OwnerResolution } from "./owner-resolver.js";
5
-
6
- /**
7
- * Action 处置合成(纯函数层,spec §6/§7)
8
- *
9
- * 职责:按 action + 策略 + Owner 解析结果,确定性合成——
10
- * 1. 最终群消息 finalReply(Agent 主体 + Runtime Owner 尾注;主体缺失用内部兜底文案);
11
- * 2. 渠道无关的自动化通知字段(AI 表格自动化选做 TODO/IM,Runtime 不发送)。
12
- *
13
- * 两个正交轴(重构后):
14
- * 轴1 群内可见性:profile 显式配 `group: answer | silent`(见 profile.speaksInGroup);
15
- * 轴2 Owner 指引:Runtime **派生**——`needsOwnerResolution(action)` 且 Owner 解析成功且群内说话。
16
- * 所以 `answer` 也能带 Owner 尾注,不需要单独的「通知 Owner」档位。
17
- *
18
- * 内部默认文案(spec §7.2):本期不向 Profile 暴露模板,先把行为做稳。
19
- * Owner 提及形态由运行期的 (conversationId, Owner) 判定结果驱动:
20
- * 能确认当前群可用 userId 才真实 @,否则只渲染花名。
21
- */
22
-
23
- // ─── 轴2:需要找人的 action(单点判定) ───────────────
24
-
25
- /**
26
- * 需要尝试解析并附加 Owner 指引的 action:
27
- * bug/feature 找人排查或评估;kb_gap 找模块负责人补充文档。
28
- */
29
- const OWNER_MENTION_ACTIONS = new Set<SupportAction>(["bug", "feature", "kb_gap"]);
30
-
31
- /**
32
- * 是否需要解析 Owner(on-action 决定是否 resolveOwner、本模块决定是否附尾注,
33
- * 两侧共用本判定,不再各自拄一份「哪些 action + 哪些档位才找人」的表达式)。
34
- *
35
- * 只看 action,**不看 group 档位、也不看 module 是否为空**:
36
- * - 不看 group:群内静默时 Owner 仍需进通知字段(供自动化找人);
37
- * - 不看 module:module 缺失要让 resolveOwner 报 missing_module 才能保留
38
- * 「Agent 该给 module 却没给」的监控信号(见 shouldRecordOwnerFailure)。
39
- */
40
- export function needsOwnerResolution(action: SupportAction): boolean {
41
- return OWNER_MENTION_ACTIONS.has(action);
42
- }
43
-
44
- /**
45
- * Owner 解析失败是否值得记 owner_resolution_failed 事件:
46
- * - bug/feature:必须找到人(module 缺失也是 Agent 该给没给的信号)→ 恒记;
47
- * - kb_gap:Owner 是加分项,仅当给了 module 却查不到人(真配置缺口)才记,
48
- * 避免大量无 module 的常态 kb_gap 刷满事件。
49
- */
50
- export function shouldRecordOwnerFailure(action: SupportAction, module: string): boolean {
51
- if (!needsOwnerResolution(action)) return false;
52
- if (action === "bug" || action === "feature") return true;
53
- return Boolean(String(module ?? "").trim());
54
- }
55
-
56
- /**
57
- * 渲染尾注中的 Owner 提及文本:
58
- * - ownerMentionUserId 非空 → ` @工号 `,由发送层配合 mentionUserIds 真实 @;
59
- * - 空 → 花名纯文本,消息中绝不出现数字 uid。
60
- */
61
- export function renderOwnerMention(
62
- owner: { ownerName: string; ownerUserId: string },
63
- ownerMentionUserId?: string,
64
- ): string {
65
- const s = String(ownerMentionUserId ?? "").trim();
66
- if (s) {
67
- return ` @${s.length < 6 ? s.padStart(6, "0") : s} `;
68
- }
69
- return ` **${owner.ownerName}** `;
70
- }
71
-
72
- // ─── 内部文案(不进 Profile 配置面) ─────────────────────
73
-
74
- // Owner 尾注(追加在主体之后)。话术用引导式口吻:
75
- // 「建议在群内 @X 帮你看一下」——不谎称已通知(是否真实 @ 到人由发送层 atUserIds 决定);
76
- // mention 两侧已带空格,模板不再额外加空格
77
- const BUG_FOOTER = (mention: string, module: string) =>
78
- `建议在群内${mention}帮你看一下,他负责${module}模块 🔧`;
79
- const FEATURE_FOOTER = (mention: string, module: string) =>
80
- `建议在群内${mention}关注评估下这个诉求,他负责${module}模块。`;
81
-
82
- // 主体兜底文案(**不含**尾注;尾注由统一路径按需追加,避免两套拼接逻辑)
83
- const BUG_BODY_FALLBACK = (sender: string) =>
84
- `Hi ${sender || "用户"},从你描述的现象看,这个行为不太符合预期,还需要进一步排查。`;
85
- const FEATURE_BODY_FALLBACK = (sender: string) =>
86
- `Hi ${sender || "用户"},这个需求目前帮助文档中还没有明确说明。`;
87
- // kb_gap 契约禁止 Agent 携带 reply → 主体恒由内部合成(坦白无据,不静默也不编造)
88
- const KB_GAP_BODY = (sender: string) =>
89
- `Hi ${sender || "用户"},这个问题帮助文档暂时没有覆盖,我先记录为知识缺口,待文档补充后再帮你解答 🙏`;
90
- // Owner 未解析且 Agent 主体也缺失(bug/feature):不写假 @、不静默——引导用户补充定位信息
91
- const OWNER_FAILED_CLARIFY_FALLBACK = (sender: string) =>
92
- `Hi ${sender || "用户"},从你的描述还不能确定问题属于哪个功能模块。方便补充下:在哪个页面、做了什么操作、实际结果和预期分别是什么?有报错的话带下截图,方便进一步定位。`;
93
-
94
- /** 按 action 选 Owner 尾注(kb_gap 复用 Bug 口吻「帮你看一下」——已拍板) */
95
- function ownerFooter(action: SupportAction, mention: string, module: string): string {
96
- return action === "feature" ? FEATURE_FOOTER(mention, module) : BUG_FOOTER(mention, module);
97
- }
98
-
99
- /** Owner 已解析且 Agent 主体缺失时的内部主体(随后由统一路径追加尾注) */
100
- function resolvedBodyFallback(action: SupportAction, sender: string): string {
101
- if (action === "feature") return FEATURE_BODY_FALLBACK(sender);
102
- if (action === "kb_gap") return KB_GAP_BODY(sender);
103
- return BUG_BODY_FALLBACK(sender);
104
- }
105
-
106
- /** Owner 未解析且 Agent 主体缺失时的内部主体(不追加尾注) */
107
- function unresolvedBodyFallback(action: SupportAction, sender: string): string {
108
- return action === "kb_gap" ? KB_GAP_BODY(sender) : OWNER_FAILED_CLARIFY_FALLBACK(sender);
109
- }
110
-
111
- const NOTIFICATION_TITLES: Partial<Record<SupportAction, (module: string, brief: string) => string>> = {
112
- bug: (module, brief) => `【Bug 待跟进】【${module || "未知模块"}】${brief}`,
113
- feature: (module, brief) => `【需求建议】【${module || "未知模块"}】${brief}`,
114
- kb_gap: (_module, brief) => `【知识缺口待补充】${brief}`,
115
- };
116
-
117
- /** 问题摘要归一:折叠空白、去掉本地图片标记、截断 80 字 */
118
- export function questionBrief(content: string): string {
119
- return content
120
- .replace(/!\[[^\]]*\]\([^)]*\)/g, " ")
121
- .replace(/\s+/g, " ")
122
- .trim()
123
- .slice(0, 80);
124
- }
125
-
126
- /**
127
- * Owner 尾注拼接(评审修正):尾注插在正文末尾的来源引用块(`> 来源:…`)之前,
128
- * 保持来源链接始终在最末一行(与 handoff 基线的阅读顺序一致:正文 → 找人 → 出处);
129
- * 末尾无引用块时直接追加。
130
- */
131
- export function appendOwnerFooter(body: string, footer: string): string {
132
- const lines = body.split("\n");
133
- let end = lines.length;
134
- while (end > 0 && lines[end - 1]!.trim() === "") end--;
135
- let start = end;
136
- while (start > 0 && lines[start - 1]!.trimStart().startsWith(">")) start--;
137
- if (start < end && start > 0) {
138
- const before = lines.slice(0, start).join("\n").trimEnd();
139
- const quote = lines.slice(start, end).join("\n");
140
- return `${before}\n\n${footer}\n\n${quote}`;
141
- }
142
- return `${body}\n\n${footer}`;
143
- }
144
-
145
- export type ActionDispatch = {
146
- /** 最终发送到群的正文;空串 = 群内不说话 */
147
- finalReply: string;
148
- resolvedOwnerName: string;
149
- resolvedOwnerUserId: string;
150
- ownerResolutionStatus: "not_needed" | "resolved" | "failed";
151
- notificationTitle: string;
152
- notificationContent: string;
153
- notificationTargetUserId: string;
154
- /** 仅 Owner 已确认可在当前群真实 @ 时非空 */
155
- mentionUserId: string;
156
- /** bug/feature 但 Agent 未产出合法 reply(用了内部兜底文案)——监控 Prompt/Schema 稳定性 */
157
- agentReplyMissing: boolean;
158
- };
159
-
160
- export type BuildActionDispatchArgs = {
161
- action: SupportAction;
162
- policy: ActionPolicy;
163
- /** Agent 生成的用户可见回复主体(可空) */
164
- reply: string;
165
- module: string;
166
- reason: string;
167
- /** 原始用户消息(通知摘要用) */
168
- content: string;
169
- senderName: string;
170
- conversationId: string;
171
- /** 仅 needsOwnerResolution(action) 时由调用方解析后传入;其余传 null(误传也会被归一为 not_needed) */
172
- owner: OwnerResolution | null;
173
- /** Owner 已确认可在当前群真实 @ 时的 userId;空则渲染花名 */
174
- ownerMentionUserId?: string;
175
- };
176
-
177
- function buildNotification(args: BuildActionDispatchArgs, ownerUserId: string): {
178
- notificationTitle: string;
179
- notificationContent: string;
180
- notificationTargetUserId: string;
181
- } {
182
- const title = NOTIFICATION_TITLES[args.action];
183
- if (!title) return { notificationTitle: "", notificationContent: "", notificationTargetUserId: "" };
184
- const brief = questionBrief(args.content);
185
- const lines = [
186
- `原始问题:${brief}`,
187
- `发送人:${args.senderName || "未知"}`,
188
- `处置动作:${args.action}`,
189
- ...(args.module ? [`模块:${args.module}`] : []),
190
- ...(args.reason ? [`判定理由:${args.reason}`] : []),
191
- ...(args.conversationId ? [`来源群:${args.conversationId}`] : []),
192
- ...(ownerUserId ? [`Owner userid:${ownerUserId}`] : []),
193
- ];
194
- return {
195
- notificationTitle: title(args.module, brief),
196
- notificationContent: lines.join("\n"),
197
- notificationTargetUserId: ownerUserId,
198
- };
199
- }
200
-
201
- /**
202
- * 合成最终处置。两个正交轴分开判定:
203
- *
204
- * 轴1 群内可见性(profile.group):
205
- * - `silent` → 群里一句不说,仅留痕 + 通知字段(Owner 解析结果仍进通知 target);
206
- * - `answer` → 群里发正文。
207
- *
208
- * 轴2 Owner 指引(Runtime 派生,与轴1 无关):
209
- * - needsOwnerResolution(action) 且 Owner 已解析 → 主体 + Owner 尾注(主体缺失用内部主体);
210
- * - needsOwnerResolution(action) 但 Owner 未解析 → 主体照发不加尾注、不写假 @;
211
- * - 其余 action(reply 等)→ 透传 Agent 正文。
212
- */
213
- export function buildActionDispatch(args: BuildActionDispatchArgs): ActionDispatch {
214
- const base: ActionDispatch = {
215
- finalReply: "",
216
- resolvedOwnerName: "",
217
- resolvedOwnerUserId: "",
218
- ownerResolutionStatus: "not_needed",
219
- notificationTitle: "",
220
- notificationContent: "",
221
- notificationTargetUserId: "",
222
- mentionUserId: "",
223
- agentReplyMissing: false,
224
- };
225
-
226
- // 轴2:Owner 解析结果归一(不需要找人的 action 一律视为 not_needed,即使调用方误传 owner)
227
- const ownerNeeded = needsOwnerResolution(args.action);
228
- const owner = ownerNeeded && args.owner?.status === "resolved" ? args.owner : null;
229
- const ownerFields = {
230
- resolvedOwnerName: owner?.ownerName ?? "",
231
- resolvedOwnerUserId: owner?.ownerUserId ?? "",
232
- ownerResolutionStatus: (!ownerNeeded ? "not_needed" : owner ? "resolved" : "failed") as ActionDispatch["ownerResolutionStatus"],
233
- };
234
- const notification = buildNotification(args, owner?.ownerUserId ?? "");
235
- const mentionUserId = owner ? String(args.ownerMentionUserId ?? "").trim() : "";
236
-
237
- // 轴1:群内不说话 → 只出通知字段(kb_gap 的知识收件箱、bug/feature 的自动化找人都不丢)
238
- if (!speaksInGroup(args.policy)) {
239
- return { ...base, ...ownerFields, ...notification };
240
- }
241
-
242
- // 轴1:群内说话
243
- const body = args.reply.trim();
244
- // 契约禁止 kb_gap 携带 reply → 主体缺失是预期而非异常,不计 agent_reply_missing
245
- const agentReplyMissing = !body && (args.action === "bug" || args.action === "feature");
246
-
247
- // 轴2:派生判定是否附 Owner 尾注
248
- const finalReply = owner
249
- ? appendOwnerFooter(
250
- body || resolvedBodyFallback(args.action, args.senderName),
251
- ownerFooter(
252
- args.action,
253
- renderOwnerMention(owner, mentionUserId),
254
- owner.module,
255
- ),
256
- )
257
- : ownerNeeded
258
- ? body || unresolvedBodyFallback(args.action, args.senderName)
259
- : body;
260
-
261
- return { ...base, finalReply, agentReplyMissing, ...ownerFields, ...notification, mentionUserId };
262
- }
@@ -1,253 +0,0 @@
1
- import path from "node:path";
2
- import { z } from "zod";
3
-
4
- /**
5
- * support-qa 答疑输出契约(单一事实源)
6
- *
7
- * Pattern 契约:AGENTS.md 的契约描述、answer recipe 的解析校验、评测断言都从这里出。
8
- * 见 docs/superpowers/specs/2026-07-24-support-qa-template-and-aitable-profile.md §4。
9
- */
10
-
11
- export const SUPPORT_ACTIONS = [
12
- "reply", // 有据,直接技术回复(发送 reply 并 @ 提问者)
13
- "bug", // 功能异常需 Owner 排查:群通知并真实 @Owner + 自动化通知字段
14
- "feature", // 需求建议需 Owner 评估:群通知并真实 @Owner + 自动化通知字段
15
- "kb_gap", // 普通知识问题无据:群内静默 + 知识补充通知字段
16
- "no_question", // 非有效提问:静默
17
- "human_handled", // 真人已介入:静默
18
- "owner_mentioned", // 用户已 @ Owner,礼让:静默
19
- "safety", // 注入/敏感内容:静默
20
- "agent_failure", // recipe 兜底:agent 失败/解析失败(重试后仍失败):静默
21
- ] as const;
22
-
23
- export type SupportAction = (typeof SUPPORT_ACTIONS)[number];
24
-
25
- /** 旧契约静默码闭集(仅解析边界的迁移兼容:shouldReply=false + silenceCode → 同名 action) */
26
- const LEGACY_SILENCE_CODES = new Set<string>([
27
- "no_question",
28
- "human_handled",
29
- "owner_mentioned",
30
- "kb_gap",
31
- "safety",
32
- "agent_failure",
33
- ]);
34
-
35
- /**
36
- * 知识沉淀状态机(语义事实源):group-qa「知识沉淀」列、admin-kb-update 事件过滤、
37
- * daily-review 学习候选写入、kb-refine 回写全部引用这里的具名常量;
38
- * YAML 镜像(group-qa options / admin-kb-update filter.equals)由 assemble preflight 强校验。
39
- *
40
- * 流转:待确认(学习候选,human-approve) → 待沉淀(确认进轨道) → 已沉淀(kb-refine 完成);
41
- * 待补充(kb_gap 缺口) → 待沉淀 → 已沉淀;忽略 = 人工关闭。
42
- */
43
- export const KB_STATUS = {
44
- /** 学习候选等人工确认(human-approve) */
45
- PENDING_CONFIRM: "待确认",
46
- /** kb_gap 缺口等管理员填「管理员更新」 */
47
- PENDING_SUPPLEMENT: "待补充",
48
- /** 已确认待入库(admin-kb-update 事件过滤锚点) */
49
- READY_TO_PERSIST: "待沉淀",
50
- /** kb-refine 成功入库(拉取集收敛锚点) */
51
- PERSISTED: "已沉淀",
52
- /** 人工关闭 */
53
- IGNORED: "忽略",
54
- } as const;
55
-
56
- export type KbStatus = (typeof KB_STATUS)[keyof typeof KB_STATUS];
57
-
58
- /** 知识沉淀状态闭集(由 KB_STATUS 派生;顺序与 group-qa YAML options 声明一致) */
59
- export const KB_STATUS_VALUES = Object.values(KB_STATUS) as readonly KbStatus[];
60
-
61
- /** 知识沉淀目标文件缺省值(仅字段缺失时使用) */
62
- export const DEFAULT_CURATION_TARGET = "FAQ.md";
63
-
64
- /**
65
- * 解析 knowledge.curation_target(唯一事实源,评审 P1):assemble 定点回写与 kb-refine
66
- * 运行期写入共用同一套规则,杜绝“assemble 放行、recipe 又改写”的双源分家。
67
- *
68
- * 规则:字段缺失(undefined) → 缺省 FAQ.md;显式值必须是非空字符串、不含换行、
69
- * 非绝对路径、不含 `..` 路径段、且以 .md 结尾;否则返回 error(不自动修正、不静默回退)。
70
- */
71
- export function resolveCurationTarget(raw: unknown): { value: string; error: string | null } {
72
- if (raw === undefined) return { value: DEFAULT_CURATION_TARGET, error: null };
73
- if (typeof raw !== "string" || !raw.trim()) {
74
- return { value: "", error: `knowledge.curation_target 必须是非空字符串(省略整项则缺省 ${DEFAULT_CURATION_TARGET}),实际: ${JSON.stringify(raw)}` };
75
- }
76
- const target = raw.trim();
77
- if (/[\r\n]/.test(raw) || path.isAbsolute(target) || target.split(/[\\/]/).includes("..")) {
78
- return { value: "", error: `knowledge.curation_target 必须是不含 .. 和换行的相对文件路径: ${target}` };
79
- }
80
- if (!target.endsWith(".md")) {
81
- return { value: "", error: `knowledge.curation_target 必须以 .md 结尾(不自动补扩展名): ${target}` };
82
- }
83
- return { value: target, error: null };
84
- }
85
-
86
- export type SupportReply = {
87
- /** 唯一的最终处置结果(下游只按 action 路由,无第二事实源) */
88
- action: SupportAction;
89
- /** 判定理由简述(审计用,不发给用户);强制非空——空审计理由视为非法输出 */
90
- reason: string;
91
- /**
92
- * action=reply 必填(完整技术回答);action=bug/feature 应有(针对本次问题的用户可见回复主体,
93
- * 缺失不判非法——由 on_action 用内部兜底文案并记 agent_reply_missing,见 spec §5.3/§6.4);
94
- * 其余 action 禁止携带(避免“一边静默一边回答”)。
95
- */
96
- reply?: string;
97
- /** bug/feature/kb_gap 尽量给出:必须是 owner-map.json 的精确 key;不确定省略,禁止猜测 */
98
- module?: string;
99
- };
100
-
101
- /** 可携带 reply 的 action(reply 强制、bug/feature 可选) */
102
- const REPLY_BEARING_ACTIONS = new Set<string>(["reply", "bug", "feature"]);
103
-
104
- export const supportReplySchema = z
105
- .object({
106
- action: z.enum(SUPPORT_ACTIONS),
107
- // 强制非空审计理由:旧输出(含 legacy 转换)缺 reason 会解析失败,
108
- // 由现有 retry 与 agent_failure 兜底;不为兼容旧脏输出接受空理由。
109
- reason: z.string().trim().min(1),
110
- reply: z.string().optional(),
111
- module: z.string().optional(),
112
- })
113
- .superRefine((v, ctx) => {
114
- if (v.action === "reply" && !(v.reply ?? "").trim()) {
115
- ctx.addIssue({ code: "custom", message: "action=reply 时 reply 必填" });
116
- }
117
- if (!REPLY_BEARING_ACTIONS.has(v.action) && (v.reply ?? "").trim()) {
118
- ctx.addIssue({ code: "custom", message: `action=${v.action} 不得携带 reply` });
119
- }
120
- });
121
-
122
- /**
123
- * 尝试解析 JSON 字符串;如果 JSON.parse 失败,尝试修复字符串值中未转义的双引号。
124
- * 典型场景:模型在 reply 字段中原样引用了用户消息里的中文双引号 "xxx",
125
- * 导致 JSON 结构被破坏。
126
- */
127
- function safeParseJson(jsonStr: string): unknown {
128
- try {
129
- return JSON.parse(jsonStr);
130
- } catch {
131
- // noop — fall through to repair
132
- }
133
- // Repair: find string values and escape unescaped inner quotes.
134
- // Strategy: walk through the JSON char-by-char, track whether we're inside a string,
135
- // and fix unescaped quotes that don't terminate a value properly.
136
- try {
137
- const repaired = repairJsonStringValues(jsonStr);
138
- return JSON.parse(repaired);
139
- } catch {
140
- return null;
141
- }
142
- }
143
-
144
- /**
145
- * Attempt to repair unescaped double quotes inside JSON string values.
146
- * Works by scanning char-by-char: when inside a string, if we hit a `"` that is
147
- * followed by characters that can't legally follow a closed string (not `:`, `,`, `}`, `]`, whitespace),
148
- * we treat it as an unescaped quote and escape it.
149
- */
150
- function repairJsonStringValues(json: string): string {
151
- const result: string[] = [];
152
- let i = 0;
153
- let inString = false;
154
-
155
- while (i < json.length) {
156
- const ch = json[i];
157
-
158
- if (!inString) {
159
- result.push(ch);
160
- if (ch === '"') inString = true;
161
- i++;
162
- } else {
163
- // Inside a string
164
- if (ch === '\\') {
165
- // Escaped char — take both
166
- result.push(ch, json[i + 1] ?? '');
167
- i += 2;
168
- } else if (ch === '"') {
169
- // Could be end-of-string or an unescaped inner quote.
170
- // Look ahead: if the next non-whitespace char is one of : , } ] then this is a real close quote.
171
- let lookAhead = i + 1;
172
- while (lookAhead < json.length && (json[lookAhead] === ' ' || json[lookAhead] === '\t' || json[lookAhead] === '\n' || json[lookAhead] === '\r')) {
173
- lookAhead++;
174
- }
175
- const nextCh = json[lookAhead];
176
- if (nextCh === ':' || nextCh === ',' || nextCh === '}' || nextCh === ']' || nextCh === undefined) {
177
- // Legitimate string close
178
- result.push(ch);
179
- inString = false;
180
- i++;
181
- } else {
182
- // Unescaped inner quote — escape it
183
- result.push('\\"');
184
- i++;
185
- }
186
- } else {
187
- result.push(ch);
188
- i++;
189
- }
190
- }
191
- }
192
-
193
- return result.join('');
194
- }
195
-
196
- /**
197
- * 从 agent 自由文本中稳健提取契约 JSON;失败返回 null(由调用方决定重试/兜底)。
198
- *
199
- * 迁移兼容(spec §14.1):旧 {shouldReply, silenceCode} 输出只在本解析边界转换为 action,
200
- * 返回对象不再暴露 shouldReply/silenceCode;旧静默输出若误带 reply 一律丢弃。
201
- */
202
- export function extractSupportReply(raw: string): SupportReply | null {
203
- if (!raw) return null;
204
- let s = raw.trim();
205
- const fence = s.match(/```(?:json)?\s*([\s\S]*?)```/i);
206
- if (fence?.[1]) s = fence[1].trim();
207
- const start = s.indexOf("{");
208
- const end = s.lastIndexOf("}");
209
- if (start === -1 || end <= start) return null;
210
- const jsonStr = s.slice(start, end + 1);
211
- const parsed = safeParseJson(jsonStr);
212
- if (!parsed || typeof parsed !== "object") return null;
213
- try {
214
- const legacy = parsed as Record<string, unknown>;
215
- const candidate: unknown =
216
- typeof legacy.action === "string"
217
- ? parsed
218
- : legacy.shouldReply === true
219
- ? { action: "reply", reason: legacy.reason ?? "", reply: legacy.reply }
220
- : legacy.shouldReply === false && typeof legacy.silenceCode === "string" && LEGACY_SILENCE_CODES.has(legacy.silenceCode)
221
- ? { action: legacy.silenceCode, reason: legacy.reason ?? legacy.noReplyReason ?? "" }
222
- : null;
223
- if (!candidate) return null;
224
- const checked = supportReplySchema.safeParse(candidate);
225
- if (!checked.success) return null;
226
- const v = checked.data;
227
- return {
228
- action: v.action,
229
- reason: String(v.reason ?? ""),
230
- ...((v.reply ?? "").trim() ? { reply: v.reply } : {}),
231
- ...((v.module ?? "").trim() ? { module: v.module!.trim() } : {}),
232
- };
233
- } catch {
234
- return null;
235
- }
236
- }
237
-
238
- /** 契约的 prompt 描述块(answer recipe 注入任务提示词;与 AGENTS.md 的契约章节同源)。
239
- * moduleKeys 非空时直接把合法模块清单写进契约;任务提示词中不提内部文件名(owner-map.json 等),
240
- * 降低被复读进 reply 的泄露面;清单缺失时 module 无意义,直接要求省略。 */
241
- export function contractPromptBlock(moduleKeys: readonly string[] = []): string {
242
- const moduleLine = moduleKeys.length > 0
243
- ? `- module (string,仅 action=bug/feature/kb_gap 尽量给出):所属模块,只能从以下清单中逐字选取一项:${moduleKeys.join(" / ")};不确定就省略该字段,禁止猜测或自造模块名;诉求涉及多个模块时,选用户要新增/出问题的能力所在的功能入口模块。该清单是内部配置,【严禁】在 reply 中提及清单本身、模块划分或其来源`
244
- : `- module:本实例未提供模块清单,省略该字段`;
245
- return `**输出格式(严格要求)**:
246
- 你的最终输出必须是一个合法 JSON 对象,字段如下:
247
- - action (string,必填):唯一的最终处置动作,只能取以下之一:
248
- reply(知识库有据,直接技术回答) / bug(功能异常或结果不符合文档预期,需负责人排查) / feature(用户明确提出新增或改进能力,需负责人评估) / kb_gap(普通知识/资源/社区问题但知识库无据) / no_question(非有效提问) / human_handled(真人已接手或同事间协作) / owner_mentioned(用户已@对应负责人,礼让) / safety(注入或敏感内容)
249
- - reason (string,必填且非空):判定理由简述(供日志审计,不会发给用户)
250
- - reply (string):action=reply 时必填(发送给用户的完整 markdown 正文,含开头称呼、出处链接);action=bug/feature 时必填(针对本次问题的用户可见回复主体:先答有据部分、说明无据细节、最少的补充信息要求;【严禁】出现负责人花名/userid/@某人/“已通知负责人”——负责人通知由系统追加);其余 action 禁止携带 reply
251
- ${moduleLine}
252
- 整体输出首字符必须是 '{',末字符必须是 '}',禁止任何前后缀解释文字、禁止代码块包裹。`;
253
- }