@coralai/sps-cli 0.65.33 → 0.65.35

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 (67) hide show
  1. package/dist/chat-protocol/events.d.ts +131 -0
  2. package/dist/chat-protocol/events.d.ts.map +1 -0
  3. package/dist/chat-protocol/events.js +134 -0
  4. package/dist/chat-protocol/events.js.map +1 -0
  5. package/dist/chat-protocol/form.d.ts +66 -0
  6. package/dist/chat-protocol/form.d.ts.map +1 -0
  7. package/dist/chat-protocol/form.js +25 -0
  8. package/dist/chat-protocol/form.js.map +1 -0
  9. package/dist/chat-protocol/formValues.d.ts +60 -0
  10. package/dist/chat-protocol/formValues.d.ts.map +1 -0
  11. package/dist/chat-protocol/formValues.js +114 -0
  12. package/dist/chat-protocol/formValues.js.map +1 -0
  13. package/dist/chat-protocol/index.d.ts +25 -0
  14. package/dist/chat-protocol/index.d.ts.map +1 -0
  15. package/dist/chat-protocol/index.js +19 -0
  16. package/dist/chat-protocol/index.js.map +1 -0
  17. package/dist/chat-protocol/reduce.d.ts +33 -0
  18. package/dist/chat-protocol/reduce.d.ts.map +1 -0
  19. package/dist/chat-protocol/reduce.js +204 -0
  20. package/dist/chat-protocol/reduce.js.map +1 -0
  21. package/dist/chat-protocol/selectors.d.ts +31 -0
  22. package/dist/chat-protocol/selectors.d.ts.map +1 -0
  23. package/dist/chat-protocol/selectors.js +40 -0
  24. package/dist/chat-protocol/selectors.js.map +1 -0
  25. package/dist/chat-protocol/types.d.ts +85 -0
  26. package/dist/chat-protocol/types.d.ts.map +1 -0
  27. package/dist/chat-protocol/types.js +9 -0
  28. package/dist/chat-protocol/types.js.map +1 -0
  29. package/dist/chat-protocol/typewriter.d.ts +25 -0
  30. package/dist/chat-protocol/typewriter.d.ts.map +1 -0
  31. package/dist/chat-protocol/typewriter.js +36 -0
  32. package/dist/chat-protocol/typewriter.js.map +1 -0
  33. package/dist/console/chatEventLog.d.ts +47 -0
  34. package/dist/console/chatEventLog.d.ts.map +1 -0
  35. package/dist/console/chatEventLog.js +79 -0
  36. package/dist/console/chatEventLog.js.map +1 -0
  37. package/dist/console/routes/chat.d.ts +3 -0
  38. package/dist/console/routes/chat.d.ts.map +1 -1
  39. package/dist/console/routes/chat.js +178 -114
  40. package/dist/console/routes/chat.js.map +1 -1
  41. package/dist/console-assets/assets/{ShowcasePage-NmXybDqC.js → ShowcasePage-DYxYvjDm.js} +1 -1
  42. package/dist/console-assets/assets/{index-DCAnGMiQ.js → index-DoXihmot.js} +219 -219
  43. package/dist/console-assets/assets/index-Q5jKI6ZL.css +1 -0
  44. package/dist/console-assets/index.html +2 -2
  45. package/dist/core/activityStream.d.ts +1 -1
  46. package/dist/core/activityStream.d.ts.map +1 -1
  47. package/dist/core/activityStream.js +7 -2
  48. package/dist/core/activityStream.js.map +1 -1
  49. package/dist/core/agents/AgentBackend.d.ts +11 -1
  50. package/dist/core/agents/AgentBackend.d.ts.map +1 -1
  51. package/dist/core/agents/AgentBackend.js.map +1 -1
  52. package/dist/core/interactions/formSpec.d.ts +57 -0
  53. package/dist/core/interactions/formSpec.d.ts.map +1 -0
  54. package/dist/core/interactions/formSpec.js +123 -0
  55. package/dist/core/interactions/formSpec.js.map +1 -0
  56. package/dist/openai-agent/interactions/broker.d.ts +13 -10
  57. package/dist/openai-agent/interactions/broker.d.ts.map +1 -1
  58. package/dist/openai-agent/interactions/broker.js +48 -154
  59. package/dist/openai-agent/interactions/broker.js.map +1 -1
  60. package/dist/openai-agent/runWorker.d.ts.map +1 -1
  61. package/dist/openai-agent/runWorker.js +0 -6
  62. package/dist/openai-agent/runWorker.js.map +1 -1
  63. package/dist/providers/mcp/spsMcpServer.d.ts.map +1 -1
  64. package/dist/providers/mcp/spsMcpServer.js +6 -4
  65. package/dist/providers/mcp/spsMcpServer.js.map +1 -1
  66. package/package.json +1 -1
  67. package/dist/console-assets/assets/index-cxN7rD-p.css +0 -1
@@ -0,0 +1,131 @@
1
+ /**
2
+ * @module chat-protocol/events
3
+ * @description 对话流的**唯一页面协议** `ChatStreamEvent`,以及现网 SSE 事件名 → 规范事件
4
+ * 的适配器。
5
+ *
6
+ * 为什么留适配器而不是直接改 SSE 事件名:现网三个前端都在监听 `chat.message.*`,
7
+ * 一次性改名会同时打断三端。适配器让**协议先统一、线上名后收敛**——先把三端搬到同一个
8
+ * reducer 上,线协议的清理是之后一个纯服务端动作。
9
+ *
10
+ * 零依赖规则见 `./types.ts` 头注释。
11
+ * @layer contract
12
+ * @boundedContext chat
13
+ */
14
+ import type { InteractionRespond } from './form.js';
15
+ import type { ChatBlock, ChatMessage } from './types.js';
16
+ /**
17
+ * 协议版本。**不认识的版本要大声报错,不许静默降级**——版本不齐必须是一个明确的错误,
18
+ * 而不是又一个"两个页面显示不一样"的幽灵。
19
+ */
20
+ export declare const CHAT_PROTOCOL_VERSION = 1;
21
+ /** 规范事件。名字按 `<名词>.<动作>`,名词与消息块类型一致。 */
22
+ export type ChatStreamEvent =
23
+ /** 回合开始(还没有任何内容)。 */
24
+ {
25
+ type: 'turn.pending';
26
+ sessionId: string;
27
+ turnId: string;
28
+ seq?: number;
29
+ }
30
+ /** 断线重连/切回时的在途快照:blocks 是当前完整状态,直接覆盖。 */
31
+ | {
32
+ type: 'turn.snapshot';
33
+ sessionId: string;
34
+ turnId: string;
35
+ blocks: ChatBlock[];
36
+ seq?: number;
37
+ } | {
38
+ type: 'text.delta';
39
+ sessionId: string;
40
+ turnId: string;
41
+ text: string;
42
+ seq?: number;
43
+ } | {
44
+ type: 'tool_use.started';
45
+ sessionId: string;
46
+ turnId: string;
47
+ id: string;
48
+ title: string;
49
+ kind: string;
50
+ status: string;
51
+ input?: unknown;
52
+ locations?: unknown;
53
+ seq?: number;
54
+ } | {
55
+ type: 'tool_use.updated';
56
+ sessionId: string;
57
+ turnId: string;
58
+ id: string;
59
+ status: string;
60
+ input?: unknown;
61
+ locations?: unknown;
62
+ seq?: number;
63
+ }
64
+ /** 需要用户交互(表单 / 选择器 / 审批)。schema 已在引擎侧完全展开。 */
65
+ | {
66
+ type: 'interaction.required';
67
+ sessionId: string;
68
+ turnId: string;
69
+ id: string;
70
+ kind: string;
71
+ title: string;
72
+ schema: unknown;
73
+ /** 答案怎么送回去(见 ./form 的 InteractionRespond)。 */
74
+ respond?: InteractionRespond;
75
+ revision: number;
76
+ seq?: number;
77
+ }
78
+ /** 交互有了结果。**同时作用于在途回合与已提交消息**——用户常在回合仍在跑时提交表单。 */
79
+ | {
80
+ type: 'interaction.updated';
81
+ sessionId: string;
82
+ interactionId: string;
83
+ status: 'answered' | 'cancelled';
84
+ revision: number;
85
+ answer?: unknown;
86
+ seq?: number;
87
+ }
88
+ /** 一条权威消息落库(用户消息,或补拉到的历史)。 */
89
+ | {
90
+ type: 'message.appended';
91
+ sessionId: string;
92
+ message: ChatMessage;
93
+ seq?: number;
94
+ }
95
+ /** 回合成功收尾,携带完整权威消息(增量丢失也能靠它自愈)。 */
96
+ | {
97
+ type: 'turn.completed';
98
+ sessionId: string;
99
+ turnId: string;
100
+ message: ChatMessage;
101
+ seq?: number;
102
+ }
103
+ /** 回合以错误/中断收尾。 */
104
+ | {
105
+ type: 'turn.failed';
106
+ sessionId: string;
107
+ turnId?: string;
108
+ message?: ChatMessage;
109
+ seq?: number;
110
+ }
111
+ /**
112
+ * 续传游标已失效(断点滚出服务端事件窗口)。**不能装作无缝续上** —— 客户端必须重新
113
+ * 拉取会话快照重建,否则会拿着一段有洞的序列以为自己是完整的。
114
+ */
115
+ | {
116
+ type: 'resync.required';
117
+ sessionId: string;
118
+ reason?: string;
119
+ seq?: number;
120
+ };
121
+ /** 现网 SSE 事件名清单(订阅用)。 */
122
+ export declare const WIRE_EVENT_NAMES: readonly ["chat.message", "chat.message.pending", "chat.message.snapshot", "chat.message.chunk.text", "chat.message.chunk.tool_use", "chat.message.chunk.tool_update", "chat.message.chunk.interaction", "chat.message.interaction.updated", "chat.message.complete", "chat.message.error", "chat.resync.required"];
123
+ /**
124
+ * 把一条现网 SSE 事件翻成规范事件。不认识 / 字段缺失 → 返回 null(调用方忽略即可)。
125
+ * 解析 JSON 失败由调用方处理。
126
+ *
127
+ * `streamSessionId` = 这条 SSE 流所属的会话。**流的归属以 URL 为准**(`/stream/chat/:id`),
128
+ * payload 里的 `sessionId` 只作交叉校验:两者都在且不一致 → 丢弃(串台防线)。
129
+ */
130
+ export declare function fromWireEvent(name: string, data: unknown, streamSessionId: string): ChatStreamEvent | null;
131
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/chat-protocol/events.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * 协议版本。**不认识的版本要大声报错,不许静默降级**——版本不齐必须是一个明确的错误,
3
+ * 而不是又一个"两个页面显示不一样"的幽灵。
4
+ */
5
+ export const CHAT_PROTOCOL_VERSION = 1;
6
+ // ── 现网 SSE 事件名 → 规范事件 ────────────────────────────────────────
7
+ /** 现网 SSE 事件名清单(订阅用)。 */
8
+ export const WIRE_EVENT_NAMES = [
9
+ 'chat.message',
10
+ 'chat.message.pending',
11
+ 'chat.message.snapshot',
12
+ 'chat.message.chunk.text',
13
+ 'chat.message.chunk.tool_use',
14
+ 'chat.message.chunk.tool_update',
15
+ 'chat.message.chunk.interaction',
16
+ 'chat.message.interaction.updated',
17
+ 'chat.message.complete',
18
+ 'chat.message.error',
19
+ 'chat.resync.required',
20
+ ];
21
+ function str(v) {
22
+ return typeof v === 'string' ? v : '';
23
+ }
24
+ function num(v, fallback) {
25
+ return typeof v === 'number' && Number.isFinite(v) ? v : fallback;
26
+ }
27
+ /**
28
+ * 把一条现网 SSE 事件翻成规范事件。不认识 / 字段缺失 → 返回 null(调用方忽略即可)。
29
+ * 解析 JSON 失败由调用方处理。
30
+ *
31
+ * `streamSessionId` = 这条 SSE 流所属的会话。**流的归属以 URL 为准**(`/stream/chat/:id`),
32
+ * payload 里的 `sessionId` 只作交叉校验:两者都在且不一致 → 丢弃(串台防线)。
33
+ */
34
+ export function fromWireEvent(name, data, streamSessionId) {
35
+ if (!data || typeof data !== 'object' || !streamSessionId)
36
+ return null;
37
+ const d = data;
38
+ const payloadSession = str(d.sessionId);
39
+ if (payloadSession && payloadSession !== streamSessionId)
40
+ return null;
41
+ const sessionId = streamSessionId;
42
+ const seq = typeof d.seq === 'number' ? d.seq : undefined;
43
+ const turnId = str(d.assistantId) || str(d.turnId);
44
+ switch (name) {
45
+ case 'chat.message':
46
+ return d.message ? { type: 'message.appended', sessionId, message: d.message, seq } : null;
47
+ case 'chat.message.pending':
48
+ return turnId ? { type: 'turn.pending', sessionId, turnId, seq } : null;
49
+ case 'chat.message.snapshot':
50
+ return turnId
51
+ ? { type: 'turn.snapshot', sessionId, turnId, blocks: d.blocks ?? [], seq }
52
+ : null;
53
+ case 'chat.message.chunk.text':
54
+ return turnId && typeof d.text === 'string'
55
+ ? { type: 'text.delta', sessionId, turnId, text: d.text, seq }
56
+ : null;
57
+ case 'chat.message.chunk.tool_use':
58
+ return turnId && str(d.id)
59
+ ? {
60
+ type: 'tool_use.started',
61
+ sessionId,
62
+ turnId,
63
+ id: str(d.id),
64
+ title: str(d.title),
65
+ kind: str(d.kind),
66
+ status: str(d.status),
67
+ input: d.input,
68
+ locations: d.locations,
69
+ seq,
70
+ }
71
+ : null;
72
+ case 'chat.message.chunk.tool_update':
73
+ return turnId && str(d.id)
74
+ ? {
75
+ type: 'tool_use.updated',
76
+ sessionId,
77
+ turnId,
78
+ id: str(d.id),
79
+ status: str(d.status),
80
+ input: d.input,
81
+ locations: d.locations,
82
+ seq,
83
+ }
84
+ : null;
85
+ case 'chat.message.chunk.interaction':
86
+ return turnId && str(d.id)
87
+ ? {
88
+ type: 'interaction.required',
89
+ sessionId,
90
+ turnId,
91
+ id: str(d.id),
92
+ kind: str(d.kind),
93
+ title: str(d.title),
94
+ schema: d.schema,
95
+ ...(d.respond === 'message' || d.respond === 'endpoint' ? { respond: d.respond } : {}),
96
+ revision: num(d.revision, 1),
97
+ seq,
98
+ }
99
+ : null;
100
+ case 'chat.message.interaction.updated':
101
+ return str(d.interactionId)
102
+ ? {
103
+ type: 'interaction.updated',
104
+ sessionId,
105
+ interactionId: str(d.interactionId),
106
+ status: d.status === 'cancelled' ? 'cancelled' : 'answered',
107
+ revision: num(d.revision, 1),
108
+ answer: d.answer,
109
+ seq,
110
+ }
111
+ : null;
112
+ case 'chat.message.complete':
113
+ return turnId && d.message
114
+ ? { type: 'turn.completed', sessionId, turnId, message: d.message, seq }
115
+ : null;
116
+ case 'chat.resync.required':
117
+ return {
118
+ type: 'resync.required',
119
+ sessionId,
120
+ ...(typeof d.reason === 'string' ? { reason: d.reason } : {}),
121
+ };
122
+ case 'chat.message.error':
123
+ return {
124
+ type: 'turn.failed',
125
+ sessionId,
126
+ ...(turnId ? { turnId } : {}),
127
+ ...(d.message ? { message: d.message } : {}),
128
+ seq,
129
+ };
130
+ default:
131
+ return null;
132
+ }
133
+ }
134
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/chat-protocol/events.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @module chat-protocol/form
3
+ * @description **规范表单描述** `FormSpec` —— 交互块(`kind: 'form'`)的 schema 就是它。
4
+ *
5
+ * 为什么要有:此前同一个 `present_form`,claude 路发的是 fields 数组 DSL、openai 路发的是
6
+ * JSON Schema,于是每个产品要写**两个**表单渲染器;更糟的是生图模板
7
+ * (`IMAGE_FORM_SPEC`)被复制在引擎和 console 两处,靠注释提醒手工同步——再接两个平台
8
+ * 就是四份。规范化成一种语言之后,产品只写一个渲染器,且**完全不需要认识任何模板名**:
9
+ * 展开发生在引擎侧,事件里永远是完整字段表。
10
+ *
11
+ * 选 fields DSL 而不是 JSON Schema:它能表达 slider / multiselect / image-ref / 分组 /
12
+ * 折叠高级项 / 条件显隐,JSON Schema 表达这些是有损的。
13
+ *
14
+ * 零依赖规则见 `./types.ts` 头注释。
15
+ * @layer contract
16
+ * @boundedContext chat
17
+ */
18
+ export type FormFieldType = 'text' | 'textarea' | 'number' | 'slider' | 'select' | 'radio' | 'multiselect' | 'checkbox' | 'toggle' | 'color' | 'image-ref' | 'file';
19
+ export interface FormFieldSpec {
20
+ /** 回流 JSON 的键。 */
21
+ id: string;
22
+ label: string;
23
+ /** 一次定死:渲染控件 + 返回值类型 + 校验。 */
24
+ type: FormFieldType;
25
+ default?: unknown;
26
+ required?: boolean;
27
+ /** 字段下方灰字说明。 */
28
+ hint?: string;
29
+ /** 分组名(同 group 归一栏)。 */
30
+ group?: string;
31
+ /** true = 默认折叠进「高级」。 */
32
+ advanced?: boolean;
33
+ options?: {
34
+ value: string;
35
+ label: string;
36
+ }[];
37
+ min?: number;
38
+ max?: number;
39
+ step?: number;
40
+ placeholder?: string;
41
+ /** file / image-ref:如 `image/*`。 */
42
+ accept?: string;
43
+ /** 条件显隐。 */
44
+ dependsOn?: {
45
+ field: string;
46
+ equals: unknown;
47
+ };
48
+ }
49
+ export interface FormSpec {
50
+ title: string;
51
+ description?: string;
52
+ submitLabel?: string;
53
+ skipLabel?: string;
54
+ fields: FormFieldSpec[];
55
+ }
56
+ /**
57
+ * 答案怎么送回去。**两条后端路径的往返语义本身不同**,与其让客户端去猜"这是哪个后端",
58
+ * 不如在块上显式说清楚:
59
+ * - `message` —— 作为一条**新的用户消息**发回(claude/MCP 路:工具立即返回,回合不挂起);
60
+ * - `endpoint` —— `POST /sessions/:id/interactions/:interactionId`(openai/broker 路:
61
+ * run 真的挂起,提交后恢复)。
62
+ */
63
+ export type InteractionRespond = 'message' | 'endpoint';
64
+ /** 运行时判别:交互块的 schema 是不是规范 FormSpec(而非旧的 JSON Schema)。 */
65
+ export declare function isFormSpec(schema: unknown): schema is FormSpec;
66
+ //# sourceMappingURL=form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../src/chat-protocol/form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,UAAU,GACV,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,aAAa,GACb,UAAU,GACV,QAAQ,GACR,OAAO,GACP,WAAW,GACX,MAAM,CAAC;AAEX,MAAM,WAAW,aAAa;IAC5B,kBAAkB;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wBAAwB;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY;IACZ,SAAS,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;CAChD;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,UAAU,CAAC;AAExD,0DAA0D;AAC1D,wBAAgB,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,QAAQ,CAI9D"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @module chat-protocol/form
3
+ * @description **规范表单描述** `FormSpec` —— 交互块(`kind: 'form'`)的 schema 就是它。
4
+ *
5
+ * 为什么要有:此前同一个 `present_form`,claude 路发的是 fields 数组 DSL、openai 路发的是
6
+ * JSON Schema,于是每个产品要写**两个**表单渲染器;更糟的是生图模板
7
+ * (`IMAGE_FORM_SPEC`)被复制在引擎和 console 两处,靠注释提醒手工同步——再接两个平台
8
+ * 就是四份。规范化成一种语言之后,产品只写一个渲染器,且**完全不需要认识任何模板名**:
9
+ * 展开发生在引擎侧,事件里永远是完整字段表。
10
+ *
11
+ * 选 fields DSL 而不是 JSON Schema:它能表达 slider / multiselect / image-ref / 分组 /
12
+ * 折叠高级项 / 条件显隐,JSON Schema 表达这些是有损的。
13
+ *
14
+ * 零依赖规则见 `./types.ts` 头注释。
15
+ * @layer contract
16
+ * @boundedContext chat
17
+ */
18
+ /** 运行时判别:交互块的 schema 是不是规范 FormSpec(而非旧的 JSON Schema)。 */
19
+ export function isFormSpec(schema) {
20
+ if (!schema || typeof schema !== 'object')
21
+ return false;
22
+ const s = schema;
23
+ return typeof s.title === 'string' && Array.isArray(s.fields);
24
+ }
25
+ //# sourceMappingURL=form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.js","sourceRoot":"","sources":["../../src/chat-protocol/form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AA0DH,0DAA0D;AAC1D,MAAM,UAAU,UAAU,CAAC,MAAe;IACxC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACxD,MAAM,CAAC,GAAG,MAA+C,CAAC;IAC1D,OAAO,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAChE,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @module chat-protocol/formValues
3
+ * @description `FormSpec` 的**取值语义**:初值、显隐、校验、答案组装。纯函数,不碰 UI。
4
+ *
5
+ * 🔴 为什么这层必须共享:光给产品一份 `FormSpec` 还不够 —— 从 spec 到最终提交的 JSON,
6
+ * 中间这些规则各写各的一定会散,而散掉的后果**不是难看,是数据形状不一致**:
7
+ * 同一张表单,A 端交上来 `{count: "3"}`、B 端 `{count: 3}`、C 端漏掉被隐藏的字段,
8
+ * **模型收到三种不同的 JSON**。这是正确性问题。
9
+ *
10
+ * 而且这个分叉在本仓已经发生过:`ConfigFormPanel` 有一份按类型定初值的实现,
11
+ * `MessageBlocks` 另有一份做数字转换的实现,规则并不完全一致。一个仓两份,
12
+ * 再接两个平台就是四份 —— 和生图模板当初一模一样的剧本。
13
+ *
14
+ * 产品端拿到这四个函数后,真正要写的只剩控件的样子和事件绑定。
15
+ *
16
+ * 零依赖规则见 `./types.ts` 头注释。
17
+ * @layer contract
18
+ * @boundedContext chat
19
+ */
20
+ import type { FormFieldSpec, FormSpec } from './form.js';
21
+ export type FormValues = Record<string, unknown>;
22
+ /**
23
+ * 单个字段的初值。**按类型定死**,不让各端自己发挥:
24
+ * 多值 = `[]`、开关 = `false`、数值 = `min ?? 0`、其余 = 空串。给了 `default` 就用它。
25
+ */
26
+ export declare function initialValue(field: FormFieldSpec): unknown;
27
+ /** 整张表单的初值。`preset`(只读回看已提交表单时)按字段覆盖。 */
28
+ export declare function initialValues(spec: FormSpec, preset?: FormValues): FormValues;
29
+ /**
30
+ * 当前可见的字段(处理 `dependsOn` 条件显隐)。
31
+ * 依赖的字段自己不可见时,本字段也不可见——否则会出现"看不见的必填项"卡住提交。
32
+ */
33
+ export declare function visibleFields(spec: FormSpec, values: FormValues): FormFieldSpec[];
34
+ /** 一个值算不算"没填"。空串 / null / undefined / 空数组都算。 */
35
+ export declare function isBlank(value: unknown): boolean;
36
+ /**
37
+ * 校验,返回**缺失的必填字段**(按 spec 顺序)。
38
+ * 只校验可见字段:被 `dependsOn` 隐藏的必填项不该挡住提交。
39
+ */
40
+ export declare function validate(spec: FormSpec, values: FormValues): FormFieldSpec[];
41
+ /**
42
+ * 组装最终答案。规则(定死,别各端发挥):
43
+ * - 只收**可见**字段——被条件隐藏的不进答案,否则模型会收到用户根本没看见的值;
44
+ * - 数值字段从输入框的字符串转回 `number`(转不动就原样保留,不猜);
45
+ * - 多值字段保证是数组;
46
+ * - 没填的字段**不进答案**(而不是塞空串)——"没填"和"填了空"对模型是两件事。
47
+ */
48
+ export declare function buildAnswer(spec: FormSpec, values: FormValues): FormValues;
49
+ /**
50
+ * 把答案渲染成**一句人话**,用作用户侧的指令文本。
51
+ *
52
+ * 🔴 为什么要共享:交互确认后内容会落进**用户气泡**——历史里读到的是"要!金块加时间 +
53
+ * 炸弹扣分",而不是一坨 JSON。三端各渲染各的,同一次确认在不同平台读起来就不一样,
54
+ * 而这是**同一段对话历史**。
55
+ *
56
+ * 规则:按 spec 顺序取可见且已填的字段,`选项标签`优先于原始值(用户看见的是标签),
57
+ * 多值用顿号连接,字段之间用 ` · ` 分隔。单字段(典型:选择器)不加标签前缀,直接就是那句话。
58
+ */
59
+ export declare function renderAnswer(spec: FormSpec, answer: FormValues): string;
60
+ //# sourceMappingURL=formValues.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formValues.d.ts","sourceRoot":"","sources":["../../src/chat-protocol/formValues.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAEzD,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAYjD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAM1D;AAED,yCAAyC;AACzC,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,UAAU,CAM7E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAG,aAAa,EAAE,CASjF;AAED,gDAAgD;AAChD,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAG/C;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAG,aAAa,EAAE,CAE5E;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAG,UAAU,CAe1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAcvE"}
@@ -0,0 +1,114 @@
1
+ /** 多值控件(值恒为数组)。 */
2
+ function isMultiValue(type) {
3
+ return type === 'multiselect' || type === 'checkbox';
4
+ }
5
+ /** 数值控件(提交前要从输入框的字符串转回 number)。 */
6
+ function isNumeric(type) {
7
+ return type === 'number' || type === 'slider';
8
+ }
9
+ /**
10
+ * 单个字段的初值。**按类型定死**,不让各端自己发挥:
11
+ * 多值 = `[]`、开关 = `false`、数值 = `min ?? 0`、其余 = 空串。给了 `default` 就用它。
12
+ */
13
+ export function initialValue(field) {
14
+ if (field.default !== undefined)
15
+ return field.default;
16
+ if (isMultiValue(field.type))
17
+ return [];
18
+ if (field.type === 'toggle')
19
+ return false;
20
+ if (isNumeric(field.type))
21
+ return field.min ?? 0;
22
+ return '';
23
+ }
24
+ /** 整张表单的初值。`preset`(只读回看已提交表单时)按字段覆盖。 */
25
+ export function initialValues(spec, preset) {
26
+ const out = {};
27
+ for (const field of spec.fields) {
28
+ out[field.id] = preset && field.id in preset ? preset[field.id] : initialValue(field);
29
+ }
30
+ return out;
31
+ }
32
+ /**
33
+ * 当前可见的字段(处理 `dependsOn` 条件显隐)。
34
+ * 依赖的字段自己不可见时,本字段也不可见——否则会出现"看不见的必填项"卡住提交。
35
+ */
36
+ export function visibleFields(spec, values) {
37
+ const byId = new Map(spec.fields.map((f) => [f.id, f]));
38
+ const visible = (field, depth) => {
39
+ if (!field.dependsOn || depth > 16)
40
+ return true;
41
+ const parent = byId.get(field.dependsOn.field);
42
+ if (!parent || !visible(parent, depth + 1))
43
+ return false;
44
+ return values[field.dependsOn.field] === field.dependsOn.equals;
45
+ };
46
+ return spec.fields.filter((f) => visible(f, 0));
47
+ }
48
+ /** 一个值算不算"没填"。空串 / null / undefined / 空数组都算。 */
49
+ export function isBlank(value) {
50
+ if (value === undefined || value === null || value === '')
51
+ return true;
52
+ return Array.isArray(value) && value.length === 0;
53
+ }
54
+ /**
55
+ * 校验,返回**缺失的必填字段**(按 spec 顺序)。
56
+ * 只校验可见字段:被 `dependsOn` 隐藏的必填项不该挡住提交。
57
+ */
58
+ export function validate(spec, values) {
59
+ return visibleFields(spec, values).filter((f) => f.required && isBlank(values[f.id]));
60
+ }
61
+ /**
62
+ * 组装最终答案。规则(定死,别各端发挥):
63
+ * - 只收**可见**字段——被条件隐藏的不进答案,否则模型会收到用户根本没看见的值;
64
+ * - 数值字段从输入框的字符串转回 `number`(转不动就原样保留,不猜);
65
+ * - 多值字段保证是数组;
66
+ * - 没填的字段**不进答案**(而不是塞空串)——"没填"和"填了空"对模型是两件事。
67
+ */
68
+ export function buildAnswer(spec, values) {
69
+ const answer = {};
70
+ for (const field of visibleFields(spec, values)) {
71
+ const raw = values[field.id];
72
+ if (isBlank(raw) && field.type !== 'toggle')
73
+ continue;
74
+ if (isNumeric(field.type) && typeof raw === 'string') {
75
+ const n = Number(raw);
76
+ answer[field.id] = Number.isFinite(n) ? n : raw;
77
+ }
78
+ else if (isMultiValue(field.type)) {
79
+ answer[field.id] = Array.isArray(raw) ? raw : [raw];
80
+ }
81
+ else {
82
+ answer[field.id] = raw;
83
+ }
84
+ }
85
+ return answer;
86
+ }
87
+ /**
88
+ * 把答案渲染成**一句人话**,用作用户侧的指令文本。
89
+ *
90
+ * 🔴 为什么要共享:交互确认后内容会落进**用户气泡**——历史里读到的是"要!金块加时间 +
91
+ * 炸弹扣分",而不是一坨 JSON。三端各渲染各的,同一次确认在不同平台读起来就不一样,
92
+ * 而这是**同一段对话历史**。
93
+ *
94
+ * 规则:按 spec 顺序取可见且已填的字段,`选项标签`优先于原始值(用户看见的是标签),
95
+ * 多值用顿号连接,字段之间用 ` · ` 分隔。单字段(典型:选择器)不加标签前缀,直接就是那句话。
96
+ */
97
+ export function renderAnswer(spec, answer) {
98
+ const parts = [];
99
+ for (const field of spec.fields) {
100
+ const raw = answer[field.id];
101
+ if (raw === undefined)
102
+ continue;
103
+ const label = (v) => {
104
+ const hit = field.options?.find((o) => o.value === v);
105
+ return hit ? hit.label : String(v);
106
+ };
107
+ const text = Array.isArray(raw) ? raw.map(label).join('、') : label(raw);
108
+ if (!text)
109
+ continue;
110
+ parts.push(spec.fields.length === 1 ? text : `${field.label}:${text}`);
111
+ }
112
+ return parts.join(' · ');
113
+ }
114
+ //# sourceMappingURL=formValues.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formValues.js","sourceRoot":"","sources":["../../src/chat-protocol/formValues.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @module chat-protocol
3
+ * @description 对话流协议 + 共享状态机的**唯一入口**。三个前端都从这里进。
4
+ *
5
+ * 🔴 零依赖叶子模块:本目录不许 import 目录外任何东西(含 node 内置与 npm 包)。
6
+ * 规则由 `chat-protocol.deps.test.ts` 卡死。将来抽成独立包 `@coralai/chat-protocol`
7
+ * 时只是移文件 + 加一个 package.json,代码零改。
8
+ *
9
+ * @layer contract
10
+ * @boundedContext chat
11
+ */
12
+ export { CHAT_PROTOCOL_VERSION, WIRE_EVENT_NAMES, fromWireEvent } from './events.js';
13
+ export type { ChatStreamEvent } from './events.js';
14
+ export { OPTIMISTIC_ID_PREFIX, initialChatStreamState } from './types.js';
15
+ export type { ChatBlock, ChatMessage, ChatStreamState, PendingTurn } from './types.js';
16
+ export { commitTurn, reduceChatEvent } from './reduce.js';
17
+ export { isFormSpec } from './form.js';
18
+ export { buildAnswer, initialValue, initialValues, isBlank, renderAnswer, validate, visibleFields, } from './formValues.js';
19
+ export type { FormValues } from './formValues.js';
20
+ export { activeInteraction, supersededInteractions } from './selectors.js';
21
+ export type { InteractionBlock } from './selectors.js';
22
+ export type { FormFieldSpec, FormFieldType, FormSpec, InteractionRespond } from './form.js';
23
+ export { advanceTypewriter, isDrained, renderBlocks } from './typewriter.js';
24
+ export type { TypewriterProgress } from './typewriter.js';
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/chat-protocol/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACrF,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAC1E,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACvF,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EACL,WAAW,EACX,YAAY,EACZ,aAAa,EACb,OAAO,EACP,YAAY,EACZ,QAAQ,EACR,aAAa,GACd,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC3E,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC5F,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC7E,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @module chat-protocol
3
+ * @description 对话流协议 + 共享状态机的**唯一入口**。三个前端都从这里进。
4
+ *
5
+ * 🔴 零依赖叶子模块:本目录不许 import 目录外任何东西(含 node 内置与 npm 包)。
6
+ * 规则由 `chat-protocol.deps.test.ts` 卡死。将来抽成独立包 `@coralai/chat-protocol`
7
+ * 时只是移文件 + 加一个 package.json,代码零改。
8
+ *
9
+ * @layer contract
10
+ * @boundedContext chat
11
+ */
12
+ export { CHAT_PROTOCOL_VERSION, WIRE_EVENT_NAMES, fromWireEvent } from './events.js';
13
+ export { OPTIMISTIC_ID_PREFIX, initialChatStreamState } from './types.js';
14
+ export { commitTurn, reduceChatEvent } from './reduce.js';
15
+ export { isFormSpec } from './form.js';
16
+ export { buildAnswer, initialValue, initialValues, isBlank, renderAnswer, validate, visibleFields, } from './formValues.js';
17
+ export { activeInteraction, supersededInteractions } from './selectors.js';
18
+ export { advanceTypewriter, isDrained, renderBlocks } from './typewriter.js';
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/chat-protocol/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAErF,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAE1E,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EACL,WAAW,EACX,YAAY,EACZ,aAAa,EACb,OAAO,EACP,YAAY,EACZ,QAAQ,EACR,aAAa,GACd,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAG3E,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}