twinny 0.0.0-dev.260525150705
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +264 -0
- package/README.zh-CN.md +252 -0
- package/configs/banner.png +0 -0
- package/configs/logo.png +0 -0
- package/dist/app/caffeinate.d.ts +28 -0
- package/dist/app/caffeinate.js +96 -0
- package/dist/app/caffeinate.js.map +1 -0
- package/dist/app/daemon.d.ts +1 -0
- package/dist/app/daemon.js +44 -0
- package/dist/app/daemon.js.map +1 -0
- package/dist/app/lark-assets.d.ts +25 -0
- package/dist/app/lark-assets.js +108 -0
- package/dist/app/lark-assets.js.map +1 -0
- package/dist/app/startup-probe.d.ts +17 -0
- package/dist/app/startup-probe.js +90 -0
- package/dist/app/startup-probe.js.map +1 -0
- package/dist/app/wiring.d.ts +122 -0
- package/dist/app/wiring.js +694 -0
- package/dist/app/wiring.js.map +1 -0
- package/dist/cli/commands.d.ts +1 -0
- package/dist/cli/commands.js +47 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/install-wizard.d.ts +41 -0
- package/dist/cli/install-wizard.js +629 -0
- package/dist/cli/install-wizard.js.map +1 -0
- package/dist/codex/appserver.d.ts +109 -0
- package/dist/codex/appserver.js +308 -0
- package/dist/codex/appserver.js.map +1 -0
- package/dist/codex/goal.d.ts +64 -0
- package/dist/codex/goal.js +433 -0
- package/dist/codex/goal.js.map +1 -0
- package/dist/codex/index.d.ts +6 -0
- package/dist/codex/index.js +7 -0
- package/dist/codex/index.js.map +1 -0
- package/dist/codex/protocol.d.ts +95 -0
- package/dist/codex/protocol.js +205 -0
- package/dist/codex/protocol.js.map +1 -0
- package/dist/codex/thread-name.d.ts +3 -0
- package/dist/codex/thread-name.js +27 -0
- package/dist/codex/thread-name.js.map +1 -0
- package/dist/codex/thread.d.ts +76 -0
- package/dist/codex/thread.js +80 -0
- package/dist/codex/thread.js.map +1 -0
- package/dist/codex/turn.d.ts +166 -0
- package/dist/codex/turn.js +746 -0
- package/dist/codex/turn.js.map +1 -0
- package/dist/config/bootstrap.d.ts +14 -0
- package/dist/config/bootstrap.js +56 -0
- package/dist/config/bootstrap.js.map +1 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.js +5 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +49 -0
- package/dist/config/loader.js +467 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/paths.d.ts +11 -0
- package/dist/config/paths.js +43 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/secrets.d.ts +33 -0
- package/dist/config/secrets.js +85 -0
- package/dist/config/secrets.js.map +1 -0
- package/dist/conversation/manager.d.ts +701 -0
- package/dist/conversation/manager.js +7673 -0
- package/dist/conversation/manager.js.map +1 -0
- package/dist/conversation/queue.d.ts +8 -0
- package/dist/conversation/queue.js +28 -0
- package/dist/conversation/queue.js.map +1 -0
- package/dist/conversation/routing.d.ts +11 -0
- package/dist/conversation/routing.js +55 -0
- package/dist/conversation/routing.js.map +1 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +17 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/lark/auth.d.ts +41 -0
- package/dist/lark/auth.js +132 -0
- package/dist/lark/auth.js.map +1 -0
- package/dist/lark/browser-auth.d.ts +68 -0
- package/dist/lark/browser-auth.js +258 -0
- package/dist/lark/browser-auth.js.map +1 -0
- package/dist/lark/cards.d.ts +140 -0
- package/dist/lark/cards.js +1150 -0
- package/dist/lark/cards.js.map +1 -0
- package/dist/lark/contact.d.ts +41 -0
- package/dist/lark/contact.js +122 -0
- package/dist/lark/contact.js.map +1 -0
- package/dist/lark/events.d.ts +65 -0
- package/dist/lark/events.js +218 -0
- package/dist/lark/events.js.map +1 -0
- package/dist/lark/files.d.ts +36 -0
- package/dist/lark/files.js +191 -0
- package/dist/lark/files.js.map +1 -0
- package/dist/lark/filters.d.ts +73 -0
- package/dist/lark/filters.js +678 -0
- package/dist/lark/filters.js.map +1 -0
- package/dist/lark/index.d.ts +10 -0
- package/dist/lark/index.js +11 -0
- package/dist/lark/index.js.map +1 -0
- package/dist/lark/messages.d.ts +87 -0
- package/dist/lark/messages.js +428 -0
- package/dist/lark/messages.js.map +1 -0
- package/dist/lark/openapi.d.ts +58 -0
- package/dist/lark/openapi.js +206 -0
- package/dist/lark/openapi.js.map +1 -0
- package/dist/lark/redactor.d.ts +5 -0
- package/dist/lark/redactor.js +68 -0
- package/dist/lark/redactor.js.map +1 -0
- package/dist/lark/types.d.ts +49 -0
- package/dist/lark/types.js +18 -0
- package/dist/lark/types.js.map +1 -0
- package/dist/launchd/install.d.ts +22 -0
- package/dist/launchd/install.js +114 -0
- package/dist/launchd/install.js.map +1 -0
- package/dist/launchd/plist.d.ts +10 -0
- package/dist/launchd/plist.js +61 -0
- package/dist/launchd/plist.js.map +1 -0
- package/dist/lock/index.d.ts +20 -0
- package/dist/lock/index.js +74 -0
- package/dist/lock/index.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +11 -0
- package/dist/main.js.map +1 -0
- package/dist/markdown.d.ts +12 -0
- package/dist/markdown.js +149 -0
- package/dist/markdown.js.map +1 -0
- package/dist/observability/health.d.ts +25 -0
- package/dist/observability/health.js +187 -0
- package/dist/observability/health.js.map +1 -0
- package/dist/observability/logs.d.ts +10 -0
- package/dist/observability/logs.js +34 -0
- package/dist/observability/logs.js.map +1 -0
- package/dist/observability/system-notifications.d.ts +25 -0
- package/dist/observability/system-notifications.js +33 -0
- package/dist/observability/system-notifications.js.map +1 -0
- package/dist/profiles/guest.d.ts +19 -0
- package/dist/profiles/guest.js +241 -0
- package/dist/profiles/guest.js.map +1 -0
- package/dist/profiles/index.d.ts +5 -0
- package/dist/profiles/index.js +14 -0
- package/dist/profiles/index.js.map +1 -0
- package/dist/profiles/owner.d.ts +1 -0
- package/dist/profiles/owner.js +6 -0
- package/dist/profiles/owner.js.map +1 -0
- package/dist/store/db.d.ts +10 -0
- package/dist/store/db.js +29 -0
- package/dist/store/db.js.map +1 -0
- package/dist/store/index.d.ts +3 -0
- package/dist/store/index.js +4 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/migrations.d.ts +13 -0
- package/dist/store/migrations.js +79 -0
- package/dist/store/migrations.js.map +1 -0
- package/dist/store/repositories.d.ts +227 -0
- package/dist/store/repositories.js +1384 -0
- package/dist/store/repositories.js.map +1 -0
- package/dist/telemetry/client.d.ts +60 -0
- package/dist/telemetry/client.js +204 -0
- package/dist/telemetry/client.js.map +1 -0
- package/dist/telemetry/hash.d.ts +1 -0
- package/dist/telemetry/hash.js +8 -0
- package/dist/telemetry/hash.js.map +1 -0
- package/dist/telemetry/index.d.ts +4 -0
- package/dist/telemetry/index.js +5 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/posthog.d.ts +27 -0
- package/dist/telemetry/posthog.js +45 -0
- package/dist/telemetry/posthog.js.map +1 -0
- package/dist/telemetry/reporter.d.ts +13 -0
- package/dist/telemetry/reporter.js +29 -0
- package/dist/telemetry/reporter.js.map +1 -0
- package/dist/types.d.ts +330 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/dist/version.js.map +1 -0
- package/dist/version.json +3 -0
- package/dist/workspace/index.d.ts +2 -0
- package/dist/workspace/index.js +3 -0
- package/dist/workspace/index.js.map +1 -0
- package/dist/workspace/manager.d.ts +14 -0
- package/dist/workspace/manager.js +69 -0
- package/dist/workspace/manager.js.map +1 -0
- package/dist/workspace/slug.d.ts +8 -0
- package/dist/workspace/slug.js +59 -0
- package/dist/workspace/slug.js.map +1 -0
- package/migrations/0001_initial.sql +102 -0
- package/package.json +85 -0
|
@@ -0,0 +1,1150 @@
|
|
|
1
|
+
import { isPositionInTextRanges, markdownCodeRanges, markdownLines } from "../markdown.js";
|
|
2
|
+
export const PLAN_IMPLEMENT_INSTRUCTION_FORM_NAME = "plan_implement_instruction";
|
|
3
|
+
const STATUS_HEADER = {
|
|
4
|
+
working: { title: "工作中...", template: "purple" },
|
|
5
|
+
finished: { title: "已完成", template: "green" },
|
|
6
|
+
interrupted: { title: "已中断", template: "grey" },
|
|
7
|
+
paused: { title: "工作中断", subtitle: "服务重启中,任务将在重启后自动恢复", template: "grey" },
|
|
8
|
+
failed: { title: "发生错误", template: "red" },
|
|
9
|
+
waiting_input: { title: "等待交互", template: "yellow" },
|
|
10
|
+
waiting_plan: { title: "确认计划", template: "wathet" },
|
|
11
|
+
interrupted_input: { title: "已跳过问题", template: "grey" },
|
|
12
|
+
interrupted_plan: { title: "计划被拒绝", template: "grey" },
|
|
13
|
+
accepted_plan: { title: "计划已确认", template: "turquoise" }
|
|
14
|
+
};
|
|
15
|
+
export function renderTwinnyAgentCard(options) {
|
|
16
|
+
const header = STATUS_HEADER[options.status];
|
|
17
|
+
const parsedPlan = options.waiting?.kind === "plan" ? parsePlanMarkdown(options.waiting.planText) : undefined;
|
|
18
|
+
const title = cardHeaderTitle(options, header.title, parsedPlan);
|
|
19
|
+
const subtitle = cardHeaderSubtitle(options, header, parsedPlan);
|
|
20
|
+
const summaryContent = cardListSummaryContent(options, title);
|
|
21
|
+
return {
|
|
22
|
+
schema: "2.0",
|
|
23
|
+
config: {
|
|
24
|
+
update_multi: true,
|
|
25
|
+
...(summaryContent === undefined
|
|
26
|
+
? {}
|
|
27
|
+
: {
|
|
28
|
+
summary: {
|
|
29
|
+
content: summaryContent
|
|
30
|
+
}
|
|
31
|
+
}),
|
|
32
|
+
style: {
|
|
33
|
+
text_size: {
|
|
34
|
+
normal_v2: {
|
|
35
|
+
default: "normal",
|
|
36
|
+
pc: "normal",
|
|
37
|
+
mobile: "heading"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
body: {
|
|
43
|
+
direction: "vertical",
|
|
44
|
+
horizontal_spacing: "8px",
|
|
45
|
+
vertical_spacing: "8px",
|
|
46
|
+
horizontal_align: "left",
|
|
47
|
+
vertical_align: "top",
|
|
48
|
+
padding: "12px 12px 12px 12px",
|
|
49
|
+
elements: bodyElements(options, parsedPlan)
|
|
50
|
+
},
|
|
51
|
+
header: {
|
|
52
|
+
title: {
|
|
53
|
+
tag: "plain_text",
|
|
54
|
+
content: title
|
|
55
|
+
},
|
|
56
|
+
subtitle: {
|
|
57
|
+
tag: "plain_text",
|
|
58
|
+
content: subtitle
|
|
59
|
+
},
|
|
60
|
+
template: header.template,
|
|
61
|
+
...(options.iconImageKey
|
|
62
|
+
? {
|
|
63
|
+
icon: {
|
|
64
|
+
tag: "custom_icon",
|
|
65
|
+
img_key: options.iconImageKey
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
: {}),
|
|
69
|
+
padding: "12px 12px 12px 12px"
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function renderTwinnyStatusCard(options) {
|
|
74
|
+
const elements = [
|
|
75
|
+
...statusSection("话题", "command_outlined", [
|
|
76
|
+
["ID", options.topic.id ?? "未创建"],
|
|
77
|
+
["名称", options.topic.name ?? "未创建"],
|
|
78
|
+
["模式", options.topic.mode],
|
|
79
|
+
["模型", options.topic.model],
|
|
80
|
+
["上下文窗口", formatStatusContext(options.topic.contextTokens, options.topic.contextWindow)],
|
|
81
|
+
["用户消息数", formatInteger(options.topic.userMessageCount)],
|
|
82
|
+
["输入 Tokens", formatStatusInputTokens(options.topic.inputTokens, options.topic.cachedInputTokens)],
|
|
83
|
+
["输出 Tokens", formatStatusOutputTokens(options.topic.outputTokens, options.topic.reasoningOutputTokens)],
|
|
84
|
+
["总工作时长", formatElapsed(options.topic.totalWorkDurationMs)]
|
|
85
|
+
]),
|
|
86
|
+
...statusSection("工作区", "home_outlined", [
|
|
87
|
+
["ID", options.workspace.id],
|
|
88
|
+
["类型", formatConversationType(options.workspace.type)],
|
|
89
|
+
["响应模式", formatResponseMode(options.workspace.responseMode)],
|
|
90
|
+
["配置", options.workspace.profile ?? "未创建"],
|
|
91
|
+
["路径", options.workspace.path ?? "未创建"],
|
|
92
|
+
["话题数", formatInteger(options.workspace.topicCount)],
|
|
93
|
+
["用户消息数", formatInteger(options.workspace.userMessageCount)],
|
|
94
|
+
["总输入 Token", formatStatusInputTokens(options.workspace.inputTokens, options.workspace.cachedInputTokens)],
|
|
95
|
+
["总输出 Token", formatStatusOutputTokens(options.workspace.outputTokens, options.workspace.reasoningOutputTokens)],
|
|
96
|
+
["总工作时长", formatElapsed(options.workspace.totalWorkDurationMs)]
|
|
97
|
+
]),
|
|
98
|
+
...statusSection("用户", "member_outlined", [
|
|
99
|
+
["ID", options.user.openId],
|
|
100
|
+
["身份", options.user.identity]
|
|
101
|
+
])
|
|
102
|
+
];
|
|
103
|
+
if (options.system) {
|
|
104
|
+
elements.push(...statusSection("系统", "setting_outlined", [
|
|
105
|
+
["Twinny Home", options.system.twinnyHome],
|
|
106
|
+
["Twinny 版本", options.system.twinnyVersion],
|
|
107
|
+
["CodeX 版本", options.system.codexVersion],
|
|
108
|
+
["Lark App ID", options.system.larkAppId],
|
|
109
|
+
["剩余 5h 限额", options.system.fiveHourRemainingLimit],
|
|
110
|
+
["剩余 7d 限额", options.system.sevenDayRemainingLimit]
|
|
111
|
+
]));
|
|
112
|
+
}
|
|
113
|
+
if (options.hideAction) {
|
|
114
|
+
elements.push(statusHideButtonElement(options.hideAction));
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
schema: "2.0",
|
|
118
|
+
config: {
|
|
119
|
+
update_multi: true,
|
|
120
|
+
style: {
|
|
121
|
+
text_size: {
|
|
122
|
+
normal_v2: {
|
|
123
|
+
default: "normal",
|
|
124
|
+
pc: "normal",
|
|
125
|
+
mobile: "heading"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
body: {
|
|
131
|
+
direction: "vertical",
|
|
132
|
+
horizontal_spacing: "8px",
|
|
133
|
+
vertical_spacing: "8px",
|
|
134
|
+
horizontal_align: "left",
|
|
135
|
+
vertical_align: "top",
|
|
136
|
+
padding: "12px 12px 12px 12px",
|
|
137
|
+
elements
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export function renderTwinnyThreadSummaryCard(options) {
|
|
142
|
+
return {
|
|
143
|
+
schema: "2.0",
|
|
144
|
+
config: {
|
|
145
|
+
update_multi: true,
|
|
146
|
+
style: {
|
|
147
|
+
text_size: {
|
|
148
|
+
normal_v2: {
|
|
149
|
+
default: "normal",
|
|
150
|
+
pc: "normal",
|
|
151
|
+
mobile: "heading"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
body: {
|
|
157
|
+
direction: "vertical",
|
|
158
|
+
horizontal_spacing: "8px",
|
|
159
|
+
vertical_spacing: "8px",
|
|
160
|
+
horizontal_align: "left",
|
|
161
|
+
vertical_align: "top",
|
|
162
|
+
padding: "12px 12px 12px 12px",
|
|
163
|
+
elements: [
|
|
164
|
+
{
|
|
165
|
+
tag: "column_set",
|
|
166
|
+
horizontal_spacing: "8px",
|
|
167
|
+
horizontal_align: "left",
|
|
168
|
+
columns: [
|
|
169
|
+
threadMetricColumn("输入", formatCompactTokenCount(options.inputTokens)),
|
|
170
|
+
threadMetricColumn("输出", formatCompactTokenCount(options.outputTokens)),
|
|
171
|
+
threadMetricColumn("时长", formatElapsed(options.totalWorkDurationMs))
|
|
172
|
+
],
|
|
173
|
+
margin: "0px 0px 0px 0px"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
tag: "div",
|
|
177
|
+
text: {
|
|
178
|
+
tag: "plain_text",
|
|
179
|
+
content: options.codexThreadId,
|
|
180
|
+
text_size: "notation",
|
|
181
|
+
text_align: "left",
|
|
182
|
+
text_color: "grey"
|
|
183
|
+
},
|
|
184
|
+
margin: "0px 0px 0px 0px"
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
header: {
|
|
189
|
+
title: {
|
|
190
|
+
tag: "plain_text",
|
|
191
|
+
content: compactCardTitle(options.name) || "新会话"
|
|
192
|
+
},
|
|
193
|
+
subtitle: {
|
|
194
|
+
tag: "plain_text",
|
|
195
|
+
content: ""
|
|
196
|
+
},
|
|
197
|
+
template: threadSummaryHeaderTemplate(options.status),
|
|
198
|
+
icon: threadSummaryHeaderIcon(options.iconImageKey),
|
|
199
|
+
padding: "12px 12px 12px 12px"
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
export function renderTwinnyBannerCard(options = {}) {
|
|
204
|
+
const version = options.twinnyVersion ?? "dev";
|
|
205
|
+
return {
|
|
206
|
+
schema: "2.0",
|
|
207
|
+
config: {
|
|
208
|
+
update_multi: true,
|
|
209
|
+
summary: {
|
|
210
|
+
content: `🐰 Twinny ${version}`
|
|
211
|
+
},
|
|
212
|
+
style: {
|
|
213
|
+
text_size: {
|
|
214
|
+
normal_v2: {
|
|
215
|
+
default: "normal",
|
|
216
|
+
pc: "normal",
|
|
217
|
+
mobile: "heading"
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
body: {
|
|
223
|
+
direction: "vertical",
|
|
224
|
+
horizontal_spacing: "8px",
|
|
225
|
+
vertical_spacing: "8px",
|
|
226
|
+
horizontal_align: "left",
|
|
227
|
+
vertical_align: "top",
|
|
228
|
+
padding: "0px 0px 12px 0px",
|
|
229
|
+
elements: [
|
|
230
|
+
...(options.bannerImageKey
|
|
231
|
+
? [
|
|
232
|
+
{
|
|
233
|
+
tag: "img",
|
|
234
|
+
img_key: options.bannerImageKey,
|
|
235
|
+
preview: true,
|
|
236
|
+
transparent: false,
|
|
237
|
+
scale_type: "fit_horizontal",
|
|
238
|
+
margin: "0px 0px 0px 0px"
|
|
239
|
+
}
|
|
240
|
+
]
|
|
241
|
+
: []),
|
|
242
|
+
{
|
|
243
|
+
tag: "markdown",
|
|
244
|
+
content: `### 🐰 Twinny - Turn chats into action\n${version} | 🌟 me on [Github](https://github.com/hachiwii/twinny)`,
|
|
245
|
+
text_align: "center",
|
|
246
|
+
text_size: "normal_v2",
|
|
247
|
+
margin: "0px 0px 0px 0px"
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function compactCardTitle(value) {
|
|
254
|
+
return value.replace(/\s+/g, " ").trim();
|
|
255
|
+
}
|
|
256
|
+
function threadSummaryHeaderTemplate(status) {
|
|
257
|
+
if (status === "working") {
|
|
258
|
+
return STATUS_HEADER.working.template;
|
|
259
|
+
}
|
|
260
|
+
if (status === "waiting") {
|
|
261
|
+
return STATUS_HEADER.waiting_input.template;
|
|
262
|
+
}
|
|
263
|
+
return "blue";
|
|
264
|
+
}
|
|
265
|
+
function threadMetricColumn(label, value) {
|
|
266
|
+
return {
|
|
267
|
+
tag: "column",
|
|
268
|
+
width: "weighted",
|
|
269
|
+
elements: [
|
|
270
|
+
markdownElement(`**${label}**\n${value}`, {
|
|
271
|
+
text_align: "center"
|
|
272
|
+
})
|
|
273
|
+
],
|
|
274
|
+
vertical_align: "top",
|
|
275
|
+
weight: 1
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function threadSummaryHeaderIcon(iconImageKey) {
|
|
279
|
+
return iconImageKey
|
|
280
|
+
? {
|
|
281
|
+
tag: "custom_icon",
|
|
282
|
+
img_key: iconImageKey
|
|
283
|
+
}
|
|
284
|
+
: {
|
|
285
|
+
tag: "standard_icon",
|
|
286
|
+
token: "table-group_outlined"
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function statusSection(title, iconToken, rows) {
|
|
290
|
+
return [
|
|
291
|
+
{
|
|
292
|
+
tag: "div",
|
|
293
|
+
text: {
|
|
294
|
+
tag: "plain_text",
|
|
295
|
+
content: title,
|
|
296
|
+
text_size: "heading",
|
|
297
|
+
text_align: "left",
|
|
298
|
+
text_color: "default"
|
|
299
|
+
},
|
|
300
|
+
icon: {
|
|
301
|
+
tag: "standard_icon",
|
|
302
|
+
token: iconToken,
|
|
303
|
+
color: "grey"
|
|
304
|
+
},
|
|
305
|
+
margin: "0px 0px 0px 0px"
|
|
306
|
+
},
|
|
307
|
+
markdownElement(statusMarkdownTable(rows))
|
|
308
|
+
];
|
|
309
|
+
}
|
|
310
|
+
function statusMarkdownTable(rows) {
|
|
311
|
+
return [
|
|
312
|
+
"| 字段 | 值 |",
|
|
313
|
+
"| -------- | -------- |",
|
|
314
|
+
...rows.map(([field, value]) => `| ${escapeTableCell(field)} | ${escapeTableCell(value)} |`)
|
|
315
|
+
].join("\n");
|
|
316
|
+
}
|
|
317
|
+
function escapeTableCell(value) {
|
|
318
|
+
return value.replace(/\r?\n/g, " ").replace(/\|/g, "\\|").trim();
|
|
319
|
+
}
|
|
320
|
+
function formatConversationType(type) {
|
|
321
|
+
switch (type) {
|
|
322
|
+
case "p2p":
|
|
323
|
+
return "单聊";
|
|
324
|
+
case "group":
|
|
325
|
+
return "群聊";
|
|
326
|
+
case "topic_group":
|
|
327
|
+
return "话题群";
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function formatResponseMode(mode) {
|
|
331
|
+
switch (mode) {
|
|
332
|
+
case "all":
|
|
333
|
+
return "全部用户,全部消息";
|
|
334
|
+
case "all_at":
|
|
335
|
+
return "全部用户,at 消息";
|
|
336
|
+
case "owner":
|
|
337
|
+
return "仅 owner,全部消息";
|
|
338
|
+
case "owner_at":
|
|
339
|
+
return "仅 owner,at 消息";
|
|
340
|
+
case "none":
|
|
341
|
+
return "未激活";
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function formatStatusContext(contextTokens, contextWindow) {
|
|
345
|
+
const context = Math.max(0, Math.trunc(contextTokens));
|
|
346
|
+
const window = Math.max(0, Math.trunc(contextWindow));
|
|
347
|
+
const percentage = window > 0 ? Math.min(100, Math.max(0, (context / window) * 100)) : 0;
|
|
348
|
+
return `${formatCompactTokenCount(context)} / ${formatCompactTokenCount(window)} (${Math.round(percentage)}%)`;
|
|
349
|
+
}
|
|
350
|
+
function formatStatusInputTokens(inputTokens, cachedInputTokens) {
|
|
351
|
+
const input = Math.max(0, Math.trunc(inputTokens));
|
|
352
|
+
const cacheRatio = formatCachedInputRatio(input, cachedInputTokens);
|
|
353
|
+
return cacheRatio
|
|
354
|
+
? `${formatCompactTokenCount(input)} (${cacheRatio} Cached)`
|
|
355
|
+
: formatCompactTokenCount(input);
|
|
356
|
+
}
|
|
357
|
+
function formatStatusOutputTokens(outputTokens, reasoningOutputTokens) {
|
|
358
|
+
const output = Math.max(0, Math.trunc(outputTokens));
|
|
359
|
+
const reasoningRatio = formatReasoningOutputRatio(output, reasoningOutputTokens);
|
|
360
|
+
return reasoningRatio
|
|
361
|
+
? `${formatCompactTokenCount(output)} (${reasoningRatio} Reasoning)`
|
|
362
|
+
: formatCompactTokenCount(output);
|
|
363
|
+
}
|
|
364
|
+
function formatReasoningOutputRatio(outputTokens, reasoningOutputTokens) {
|
|
365
|
+
if (outputTokens <= 0) {
|
|
366
|
+
return undefined;
|
|
367
|
+
}
|
|
368
|
+
const reasoning = Math.max(0, Math.trunc(reasoningOutputTokens));
|
|
369
|
+
const percentage = Math.min(100, Math.max(0, (reasoning / outputTokens) * 100));
|
|
370
|
+
return `${Math.round(percentage)}%`;
|
|
371
|
+
}
|
|
372
|
+
function formatInteger(value) {
|
|
373
|
+
return Math.max(0, Math.trunc(value)).toLocaleString("en-US");
|
|
374
|
+
}
|
|
375
|
+
export function markdownElement(content, extra = {}) {
|
|
376
|
+
return {
|
|
377
|
+
tag: "markdown",
|
|
378
|
+
content: normalizeIndentedCodeFenceBlocks(content),
|
|
379
|
+
text_align: "left",
|
|
380
|
+
text_size: "normal_v2",
|
|
381
|
+
margin: "0px 0px 0px 0px",
|
|
382
|
+
...extra
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
function normalizeIndentedCodeFenceBlocks(markdown) {
|
|
386
|
+
if (!markdown.includes("```") && !markdown.includes("~~~")) {
|
|
387
|
+
return markdown;
|
|
388
|
+
}
|
|
389
|
+
const lines = markdown.split("\n");
|
|
390
|
+
const normalized = [];
|
|
391
|
+
let fence;
|
|
392
|
+
for (const line of lines) {
|
|
393
|
+
if (!fence) {
|
|
394
|
+
const opening = parseMarkdownFenceOpening(line);
|
|
395
|
+
if (!opening) {
|
|
396
|
+
normalized.push(line);
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
fence = opening;
|
|
400
|
+
normalized.push(stripMarkdownFenceIndent(line, opening.indent));
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
normalized.push(stripMarkdownFenceIndent(line, fence.indent));
|
|
404
|
+
if (isMarkdownFenceClosing(line, fence)) {
|
|
405
|
+
fence = undefined;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return normalized.join("\n");
|
|
409
|
+
}
|
|
410
|
+
function parseMarkdownFenceOpening(line) {
|
|
411
|
+
const match = trimTrailingCarriageReturn(line).match(/^([ \t]*)(`{3,}|~{3,})/);
|
|
412
|
+
if (!match) {
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
415
|
+
const fenceMarker = match[2];
|
|
416
|
+
return {
|
|
417
|
+
indent: match[1],
|
|
418
|
+
marker: fenceMarker[0],
|
|
419
|
+
markerLength: fenceMarker.length
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function stripMarkdownFenceIndent(line, indent) {
|
|
423
|
+
if (!indent) {
|
|
424
|
+
return line;
|
|
425
|
+
}
|
|
426
|
+
return line.startsWith(indent) ? line.slice(indent.length) : line;
|
|
427
|
+
}
|
|
428
|
+
function isMarkdownFenceClosing(line, fence) {
|
|
429
|
+
const match = trimTrailingCarriageReturn(line).match(/^[ \t]*(`{3,}|~{3,})[ \t]*$/);
|
|
430
|
+
if (!match) {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
const fenceMarker = match[1];
|
|
434
|
+
return fenceMarker[0] === fence.marker && fenceMarker.length >= fence.markerLength;
|
|
435
|
+
}
|
|
436
|
+
function trimTrailingCarriageReturn(line) {
|
|
437
|
+
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
438
|
+
}
|
|
439
|
+
export function imageElement(imageKey) {
|
|
440
|
+
return {
|
|
441
|
+
tag: "img",
|
|
442
|
+
img_key: imageKey,
|
|
443
|
+
margin: "0px 0px 0px 0px"
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
export function mediaElement(fileKey) {
|
|
447
|
+
return {
|
|
448
|
+
tag: "media",
|
|
449
|
+
file_key: fileKey,
|
|
450
|
+
margin: "0px 0px 0px 0px"
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
function bodyElements(options, parsedPlan) {
|
|
454
|
+
if (options.status === "finished") {
|
|
455
|
+
return [
|
|
456
|
+
...finishedMentionElements(options.mentionOpenIds),
|
|
457
|
+
...finishedProcessPanelElements(options.messages),
|
|
458
|
+
...(options.finalElements?.length ? options.finalElements : [markdownElement("")]),
|
|
459
|
+
elapsedElement(options.elapsedMs, options.runtimeStats, options.mode)
|
|
460
|
+
];
|
|
461
|
+
}
|
|
462
|
+
if (options.status === "waiting_input" ||
|
|
463
|
+
options.status === "interrupted_input" ||
|
|
464
|
+
options.status === "waiting_plan" ||
|
|
465
|
+
options.status === "interrupted_plan" ||
|
|
466
|
+
options.status === "accepted_plan") {
|
|
467
|
+
const elements = [
|
|
468
|
+
...finishedMentionElements(options.mentionOpenIds),
|
|
469
|
+
...finishedProcessPanelElements(options.messages)
|
|
470
|
+
];
|
|
471
|
+
if (options.waiting?.kind === "request_user_input") {
|
|
472
|
+
if (options.status === "waiting_input") {
|
|
473
|
+
elements.push(requestUserInputFormElement(options));
|
|
474
|
+
return elements;
|
|
475
|
+
}
|
|
476
|
+
elements.push(...requestUserInputElements(options.waiting.questions, { includeControls: false }));
|
|
477
|
+
}
|
|
478
|
+
else if (options.waiting?.kind === "plan") {
|
|
479
|
+
elements.push(...planElements(parsedPlan ?? parsePlanMarkdown(options.waiting.planText), options.status));
|
|
480
|
+
}
|
|
481
|
+
elements.push(elapsedElement(options.elapsedMs, options.runtimeStats, options.mode));
|
|
482
|
+
if (options.status === "waiting_input") {
|
|
483
|
+
elements.push(waitingButtonsElement(options, "提交", "primary_filled", "request_input_submit", "跳过", "danger_filled", "request_input_interrupt"));
|
|
484
|
+
}
|
|
485
|
+
else if (options.status === "waiting_plan") {
|
|
486
|
+
elements.push(planImplementFormElement(options));
|
|
487
|
+
}
|
|
488
|
+
return elements;
|
|
489
|
+
}
|
|
490
|
+
const elements = workingProcessElements(workingMessagesWithError(options));
|
|
491
|
+
elements.push(elapsedElement(options.elapsedMs, options.runtimeStats, options.mode));
|
|
492
|
+
if (options.status === "working") {
|
|
493
|
+
elements.push(buttonsElement(options));
|
|
494
|
+
if (!options.hideQueueControls) {
|
|
495
|
+
elements.push(queueModeHintElement(options));
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return elements;
|
|
499
|
+
}
|
|
500
|
+
function workingMessagesWithError(options) {
|
|
501
|
+
if (options.status !== "failed" || !options.error) {
|
|
502
|
+
return options.messages;
|
|
503
|
+
}
|
|
504
|
+
return [
|
|
505
|
+
...options.messages,
|
|
506
|
+
{
|
|
507
|
+
id: "error",
|
|
508
|
+
text: `[ERROR] ${options.error}`,
|
|
509
|
+
processOnly: true
|
|
510
|
+
}
|
|
511
|
+
];
|
|
512
|
+
}
|
|
513
|
+
function finishedMentionElements(openIds) {
|
|
514
|
+
const mentions = uniqueNonEmpty(openIds).map((openId) => `<at id=${openId}></at>`);
|
|
515
|
+
return mentions.length > 0 ? [markdownElement(mentions.join(" "))] : [];
|
|
516
|
+
}
|
|
517
|
+
function finishedProcessPanelElements(messages) {
|
|
518
|
+
const rendered = renderProcessItems(messages);
|
|
519
|
+
return rendered.length > 0 ? [processPanel(rendered)] : [];
|
|
520
|
+
}
|
|
521
|
+
function workingProcessElements(messages) {
|
|
522
|
+
const rendered = renderProcessItems(messages);
|
|
523
|
+
if (rendered.length === 0) {
|
|
524
|
+
return [progressPlaceholderElement()];
|
|
525
|
+
}
|
|
526
|
+
const elements = [];
|
|
527
|
+
for (let index = 0; index < rendered.length; index += 1) {
|
|
528
|
+
if (index > 0) {
|
|
529
|
+
elements.push({ tag: "hr", margin: "4px 0px 4px 0px" });
|
|
530
|
+
}
|
|
531
|
+
elements.push(markdownElement(`- ${rendered[index]}`));
|
|
532
|
+
}
|
|
533
|
+
return elements;
|
|
534
|
+
}
|
|
535
|
+
function processPanel(renderedMessages) {
|
|
536
|
+
return {
|
|
537
|
+
tag: "collapsible_panel",
|
|
538
|
+
expanded: false,
|
|
539
|
+
header: {
|
|
540
|
+
title: {
|
|
541
|
+
tag: "plain_text",
|
|
542
|
+
content: "工作过程"
|
|
543
|
+
},
|
|
544
|
+
vertical_align: "center",
|
|
545
|
+
icon: {
|
|
546
|
+
tag: "standard_icon",
|
|
547
|
+
token: "down-small-ccm_outlined",
|
|
548
|
+
color: "",
|
|
549
|
+
size: "16px 16px"
|
|
550
|
+
},
|
|
551
|
+
icon_position: "right",
|
|
552
|
+
icon_expanded_angle: -180
|
|
553
|
+
},
|
|
554
|
+
border: {
|
|
555
|
+
color: "grey",
|
|
556
|
+
corner_radius: "5px"
|
|
557
|
+
},
|
|
558
|
+
vertical_spacing: "8px",
|
|
559
|
+
padding: "8px 8px 8px 8px",
|
|
560
|
+
elements: [markdownElement(renderedMessages.map((message) => `- ${message}`).join("\n"))]
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function progressPlaceholderElement() {
|
|
564
|
+
return {
|
|
565
|
+
tag: "div",
|
|
566
|
+
text: {
|
|
567
|
+
tag: "plain_text",
|
|
568
|
+
content: "暂无进度",
|
|
569
|
+
text_size: "notation",
|
|
570
|
+
text_align: "center",
|
|
571
|
+
text_color: "grey"
|
|
572
|
+
},
|
|
573
|
+
margin: "8px 0px 8px 0px"
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
function buttonsElement(options) {
|
|
577
|
+
const queueButtonType = options.queueNextMessage ? "primary" : "default";
|
|
578
|
+
const buttons = [
|
|
579
|
+
buttonElement("打断", "danger_filled", {
|
|
580
|
+
twinny: true,
|
|
581
|
+
action: "next",
|
|
582
|
+
stateKey: options.stateKey,
|
|
583
|
+
runId: options.runId
|
|
584
|
+
})
|
|
585
|
+
];
|
|
586
|
+
if (!options.hideQueueControls) {
|
|
587
|
+
buttons.push(buttonElement(options.queueNextMessage ? "关闭排队" : "开启排队", queueButtonType, {
|
|
588
|
+
twinny: true,
|
|
589
|
+
action: "queue",
|
|
590
|
+
stateKey: options.stateKey,
|
|
591
|
+
runId: options.runId
|
|
592
|
+
}));
|
|
593
|
+
}
|
|
594
|
+
return {
|
|
595
|
+
tag: "column_set",
|
|
596
|
+
horizontal_spacing: "8px",
|
|
597
|
+
horizontal_align: "left",
|
|
598
|
+
columns: buttons.map((button) => ({
|
|
599
|
+
tag: "column",
|
|
600
|
+
width: "auto",
|
|
601
|
+
elements: [button],
|
|
602
|
+
direction: "horizontal",
|
|
603
|
+
vertical_align: "top"
|
|
604
|
+
})),
|
|
605
|
+
margin: "0px 0px 0px 0px"
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
function statusHideButtonElement(action) {
|
|
609
|
+
const button = buttonElement("隐藏", "default", action);
|
|
610
|
+
return {
|
|
611
|
+
tag: "column_set",
|
|
612
|
+
horizontal_spacing: "8px",
|
|
613
|
+
horizontal_align: "left",
|
|
614
|
+
columns: [
|
|
615
|
+
{
|
|
616
|
+
tag: "column",
|
|
617
|
+
width: "auto",
|
|
618
|
+
elements: [button],
|
|
619
|
+
direction: "horizontal",
|
|
620
|
+
vertical_align: "top"
|
|
621
|
+
}
|
|
622
|
+
],
|
|
623
|
+
margin: "0px 0px 0px 0px"
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
function waitingButtonsElement(options, primaryLabel, primaryType, primaryAction, dangerLabel, dangerType, dangerAction, buttonOptions = {}) {
|
|
627
|
+
const buttons = [
|
|
628
|
+
buttonElement(primaryLabel, primaryType, {
|
|
629
|
+
twinny: true,
|
|
630
|
+
action: primaryAction,
|
|
631
|
+
stateKey: options.stateKey,
|
|
632
|
+
runId: options.runId
|
|
633
|
+
}, {
|
|
634
|
+
formSubmit: buttonOptions.primaryFormSubmit,
|
|
635
|
+
name: buttonOptions.namePrefix ? `${buttonOptions.namePrefix}_submit` : undefined
|
|
636
|
+
}),
|
|
637
|
+
buttonElement(dangerLabel, dangerType, {
|
|
638
|
+
twinny: true,
|
|
639
|
+
action: dangerAction,
|
|
640
|
+
stateKey: options.stateKey,
|
|
641
|
+
runId: options.runId
|
|
642
|
+
}, {
|
|
643
|
+
name: buttonOptions.namePrefix ? `${buttonOptions.namePrefix}_interrupt` : undefined
|
|
644
|
+
})
|
|
645
|
+
];
|
|
646
|
+
return {
|
|
647
|
+
tag: "column_set",
|
|
648
|
+
horizontal_spacing: "8px",
|
|
649
|
+
horizontal_align: "left",
|
|
650
|
+
columns: buttons.map((button) => ({
|
|
651
|
+
tag: "column",
|
|
652
|
+
width: "weighted",
|
|
653
|
+
weight: 1,
|
|
654
|
+
elements: [{ ...button, width: "fill" }],
|
|
655
|
+
direction: "horizontal",
|
|
656
|
+
vertical_align: "top"
|
|
657
|
+
})),
|
|
658
|
+
margin: "0px 0px 0px 0px"
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
function requestUserInputFormElement(options) {
|
|
662
|
+
const questions = options.waiting?.kind === "request_user_input" ? options.waiting.questions : [];
|
|
663
|
+
return {
|
|
664
|
+
tag: "form",
|
|
665
|
+
name: "request_user_input",
|
|
666
|
+
elements: [
|
|
667
|
+
...requestUserInputElements(questions),
|
|
668
|
+
formElapsedElement(options),
|
|
669
|
+
waitingButtonsElement(options, "提交", "primary_filled", "request_input_submit", "跳过", "danger_filled", "request_input_interrupt", {
|
|
670
|
+
primaryFormSubmit: true,
|
|
671
|
+
namePrefix: "request_user_input"
|
|
672
|
+
})
|
|
673
|
+
],
|
|
674
|
+
margin: "0px 0px 0px 0px"
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
function planImplementFormElement(options) {
|
|
678
|
+
return {
|
|
679
|
+
tag: "form",
|
|
680
|
+
name: "plan_implement",
|
|
681
|
+
elements: [
|
|
682
|
+
planImplementInstructionInputElement(),
|
|
683
|
+
waitingButtonsElement(options, "实现", "primary_filled", "plan_implement", "拒绝", "danger_filled", "plan_interrupt", {
|
|
684
|
+
primaryFormSubmit: true,
|
|
685
|
+
namePrefix: "plan_implement"
|
|
686
|
+
})
|
|
687
|
+
],
|
|
688
|
+
margin: "0px 0px 0px 0px"
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
function formElapsedElement(options) {
|
|
692
|
+
return {
|
|
693
|
+
tag: "column_set",
|
|
694
|
+
columns: [
|
|
695
|
+
{
|
|
696
|
+
tag: "column",
|
|
697
|
+
width: "weighted",
|
|
698
|
+
weight: 1,
|
|
699
|
+
elements: [elapsedElement(options.elapsedMs, options.runtimeStats, options.mode)],
|
|
700
|
+
direction: "vertical",
|
|
701
|
+
vertical_align: "top"
|
|
702
|
+
}
|
|
703
|
+
],
|
|
704
|
+
margin: "0px 0px 0px 0px"
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function queueModeHintElement(options) {
|
|
708
|
+
const hint = options.queueNextMessage
|
|
709
|
+
? "排队模式:新消息将等待当前任务完成后发送。"
|
|
710
|
+
: options.queueDepth > 0
|
|
711
|
+
? "追加模式:新消息将和排队消息一起发送。"
|
|
712
|
+
: "追加模式:新消息将被追加至当前任务。";
|
|
713
|
+
return {
|
|
714
|
+
tag: "div",
|
|
715
|
+
text: {
|
|
716
|
+
tag: "plain_text",
|
|
717
|
+
content: hint,
|
|
718
|
+
text_size: "notation",
|
|
719
|
+
text_align: "left",
|
|
720
|
+
text_color: "grey"
|
|
721
|
+
},
|
|
722
|
+
margin: "4px 0px 0px 0px"
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
function buttonElement(label, type, value, options = {}) {
|
|
726
|
+
const element = {
|
|
727
|
+
tag: "button",
|
|
728
|
+
text: {
|
|
729
|
+
tag: "plain_text",
|
|
730
|
+
content: label
|
|
731
|
+
},
|
|
732
|
+
type,
|
|
733
|
+
width: "default",
|
|
734
|
+
size: "medium"
|
|
735
|
+
};
|
|
736
|
+
if (options.name) {
|
|
737
|
+
element.name = options.name;
|
|
738
|
+
}
|
|
739
|
+
if (options.formSubmit) {
|
|
740
|
+
element.action_type = "form_submit";
|
|
741
|
+
element.value = value;
|
|
742
|
+
return element;
|
|
743
|
+
}
|
|
744
|
+
element.behaviors = [
|
|
745
|
+
{
|
|
746
|
+
type: "callback",
|
|
747
|
+
value
|
|
748
|
+
}
|
|
749
|
+
];
|
|
750
|
+
return element;
|
|
751
|
+
}
|
|
752
|
+
function elapsedElement(elapsedMs, runtimeStats, mode) {
|
|
753
|
+
return {
|
|
754
|
+
tag: "div",
|
|
755
|
+
text: {
|
|
756
|
+
tag: "plain_text",
|
|
757
|
+
content: elapsedText(elapsedMs, runtimeStats, mode),
|
|
758
|
+
text_size: "notation",
|
|
759
|
+
text_align: "left",
|
|
760
|
+
text_color: "grey"
|
|
761
|
+
},
|
|
762
|
+
margin: "4px 0px 4px 0px"
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
function elapsedText(elapsedMs, runtimeStats, mode) {
|
|
766
|
+
const parts = [`已工作 ${formatElapsed(elapsedMs)}`, ...runtimeStatParts(runtimeStats)];
|
|
767
|
+
if (mode === "plan") {
|
|
768
|
+
parts.push("Plan Mode");
|
|
769
|
+
}
|
|
770
|
+
return parts.join(" · ");
|
|
771
|
+
}
|
|
772
|
+
function requestUserInputElements(questions, options = {}) {
|
|
773
|
+
const includeControls = options.includeControls ?? true;
|
|
774
|
+
const elements = [];
|
|
775
|
+
for (let index = 0; index < questions.length; index += 1) {
|
|
776
|
+
const question = questions[index];
|
|
777
|
+
if (index > 0) {
|
|
778
|
+
elements.push({ tag: "hr", margin: "0px 0px 0px 0px" });
|
|
779
|
+
}
|
|
780
|
+
const title = `${index + 1}. ${question.header || question.question || question.id}`;
|
|
781
|
+
elements.push(markdownElement(`##### ${title}`, { margin: "8px 0px 8px 0px" }));
|
|
782
|
+
const body = question.question.trim();
|
|
783
|
+
if (body && body !== question.header.trim()) {
|
|
784
|
+
elements.push(markdownElement(body, { margin: "0px 0px 0px 0px" }));
|
|
785
|
+
}
|
|
786
|
+
for (const option of question.options ?? []) {
|
|
787
|
+
elements.push(markdownElement(`- **${escapeMarkdown(option.label)}**: ${escapeMarkdown(option.description)}`, { margin: "0px 0px 0px 0px" }));
|
|
788
|
+
}
|
|
789
|
+
if (includeControls && (question.options?.length ?? 0) > 0) {
|
|
790
|
+
elements.push(selectElement(question));
|
|
791
|
+
}
|
|
792
|
+
if (includeControls && (question.isOther || (question.options?.length ?? 0) === 0)) {
|
|
793
|
+
elements.push(inputElement(question));
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return elements.length > 0 ? elements : [progressPlaceholderElement()];
|
|
797
|
+
}
|
|
798
|
+
function planElements(plan, status) {
|
|
799
|
+
if (!plan.title) {
|
|
800
|
+
return [markdownElement(plan.rawText || "暂无计划")];
|
|
801
|
+
}
|
|
802
|
+
const parts = plan.parts.length > 0 ? plan.parts : [{ content: "暂无计划" }];
|
|
803
|
+
if (status === "waiting_plan") {
|
|
804
|
+
const elements = [];
|
|
805
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
806
|
+
if (index > 0) {
|
|
807
|
+
elements.push({ tag: "hr", margin: "0px 0px 0px 0px" });
|
|
808
|
+
}
|
|
809
|
+
elements.push(markdownElement(formatPlanPartMarkdown(parts[index])));
|
|
810
|
+
}
|
|
811
|
+
return elements;
|
|
812
|
+
}
|
|
813
|
+
const [firstPart, ...remainingParts] = parts;
|
|
814
|
+
const elements = [markdownElement(formatPlanPartMarkdown(firstPart))];
|
|
815
|
+
if (remainingParts.length > 0) {
|
|
816
|
+
elements.push(fullPlanPanel(remainingParts));
|
|
817
|
+
}
|
|
818
|
+
return elements;
|
|
819
|
+
}
|
|
820
|
+
function fullPlanPanel(parts) {
|
|
821
|
+
return {
|
|
822
|
+
tag: "collapsible_panel",
|
|
823
|
+
expanded: false,
|
|
824
|
+
header: {
|
|
825
|
+
title: {
|
|
826
|
+
tag: "plain_text",
|
|
827
|
+
content: "查看完整计划"
|
|
828
|
+
},
|
|
829
|
+
vertical_align: "center",
|
|
830
|
+
icon: {
|
|
831
|
+
tag: "standard_icon",
|
|
832
|
+
token: "down-small-ccm_outlined",
|
|
833
|
+
color: "",
|
|
834
|
+
size: "16px 16px"
|
|
835
|
+
},
|
|
836
|
+
icon_position: "right",
|
|
837
|
+
icon_expanded_angle: -180
|
|
838
|
+
},
|
|
839
|
+
border: {
|
|
840
|
+
color: "grey",
|
|
841
|
+
corner_radius: "5px"
|
|
842
|
+
},
|
|
843
|
+
vertical_spacing: "8px",
|
|
844
|
+
padding: "8px 8px 8px 8px",
|
|
845
|
+
elements: parts.map((part) => markdownElement(formatPlanPartMarkdown(part)))
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
function formatPlanPartMarkdown(part) {
|
|
849
|
+
const content = part.content.trim();
|
|
850
|
+
if (!part.title) {
|
|
851
|
+
return content || "暂无计划";
|
|
852
|
+
}
|
|
853
|
+
return content ? `#### ${part.title}\n${content}` : `#### ${part.title}`;
|
|
854
|
+
}
|
|
855
|
+
function parsePlanMarkdown(planText) {
|
|
856
|
+
const rawText = planText.trim();
|
|
857
|
+
const normalized = planText.replace(/\r\n?/g, "\n").trim();
|
|
858
|
+
if (!normalized) {
|
|
859
|
+
return { rawText, parts: [] };
|
|
860
|
+
}
|
|
861
|
+
const lines = normalized.split("\n");
|
|
862
|
+
let cursor = 0;
|
|
863
|
+
while (cursor < lines.length && lines[cursor].trim() === "") {
|
|
864
|
+
cursor += 1;
|
|
865
|
+
}
|
|
866
|
+
const title = parseMarkdownHeading(lines[cursor] ?? "", 1);
|
|
867
|
+
if (title !== undefined) {
|
|
868
|
+
cursor += 1;
|
|
869
|
+
}
|
|
870
|
+
const parts = [];
|
|
871
|
+
const preambleLines = [];
|
|
872
|
+
let currentPart;
|
|
873
|
+
let inFence = false;
|
|
874
|
+
const pushPreamble = () => {
|
|
875
|
+
const content = preambleLines.join("\n").trim();
|
|
876
|
+
if (content) {
|
|
877
|
+
parts.push({ content });
|
|
878
|
+
}
|
|
879
|
+
preambleLines.length = 0;
|
|
880
|
+
};
|
|
881
|
+
const pushCurrentPart = () => {
|
|
882
|
+
if (!currentPart) {
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
const content = currentPart.lines.join("\n").trim();
|
|
886
|
+
parts.push({ title: currentPart.title, content });
|
|
887
|
+
currentPart = undefined;
|
|
888
|
+
};
|
|
889
|
+
for (; cursor < lines.length; cursor += 1) {
|
|
890
|
+
const line = lines[cursor];
|
|
891
|
+
if (!inFence) {
|
|
892
|
+
const partTitle = parseMarkdownHeading(line, 2);
|
|
893
|
+
if (partTitle !== undefined) {
|
|
894
|
+
if (currentPart) {
|
|
895
|
+
pushCurrentPart();
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
pushPreamble();
|
|
899
|
+
}
|
|
900
|
+
currentPart = { title: partTitle, lines: [] };
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
if (currentPart) {
|
|
905
|
+
currentPart.lines.push(line);
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
preambleLines.push(line);
|
|
909
|
+
}
|
|
910
|
+
if (isMarkdownFenceBoundary(line)) {
|
|
911
|
+
inFence = !inFence;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
pushCurrentPart();
|
|
915
|
+
pushPreamble();
|
|
916
|
+
const parsed = { rawText, parts };
|
|
917
|
+
if (title !== undefined) {
|
|
918
|
+
parsed.title = title;
|
|
919
|
+
}
|
|
920
|
+
return parsed;
|
|
921
|
+
}
|
|
922
|
+
function parseMarkdownHeading(line, level) {
|
|
923
|
+
const match = line.match(/^\s{0,3}(#{1,6})\s+(.+?)\s*$/);
|
|
924
|
+
if (!match || match[1].length !== level) {
|
|
925
|
+
return undefined;
|
|
926
|
+
}
|
|
927
|
+
const heading = match[2].replace(/\s+#+\s*$/, "").trim();
|
|
928
|
+
return heading || undefined;
|
|
929
|
+
}
|
|
930
|
+
function isMarkdownFenceBoundary(line) {
|
|
931
|
+
return /^\s*(```+|~~~+)/.test(line);
|
|
932
|
+
}
|
|
933
|
+
function selectElement(question) {
|
|
934
|
+
return {
|
|
935
|
+
tag: "select_static",
|
|
936
|
+
name: formSelectName(question.id),
|
|
937
|
+
placeholder: {
|
|
938
|
+
tag: "plain_text",
|
|
939
|
+
content: "请选择"
|
|
940
|
+
},
|
|
941
|
+
options: (question.options ?? []).map((option) => ({
|
|
942
|
+
text: {
|
|
943
|
+
tag: "plain_text",
|
|
944
|
+
content: option.label
|
|
945
|
+
},
|
|
946
|
+
value: option.label,
|
|
947
|
+
icon: {
|
|
948
|
+
tag: "standard_icon",
|
|
949
|
+
token: "check_outlined"
|
|
950
|
+
}
|
|
951
|
+
})),
|
|
952
|
+
type: "default",
|
|
953
|
+
width: "fill",
|
|
954
|
+
initial_index: 1,
|
|
955
|
+
margin: "0px 0px 0px 0px"
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
function inputElement(question) {
|
|
959
|
+
return {
|
|
960
|
+
tag: "input",
|
|
961
|
+
name: formOtherName(question.id),
|
|
962
|
+
placeholder: {
|
|
963
|
+
tag: "plain_text",
|
|
964
|
+
content: question.options?.length ? "输入其它答案,选项将被忽略" : "请输入"
|
|
965
|
+
},
|
|
966
|
+
default_value: "",
|
|
967
|
+
width: "fill",
|
|
968
|
+
...(question.isSecret ? { input_type: "password" } : {}),
|
|
969
|
+
margin: "0px 0px 0px 0px"
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
function planImplementInstructionInputElement() {
|
|
973
|
+
return {
|
|
974
|
+
tag: "input",
|
|
975
|
+
name: PLAN_IMPLEMENT_INSTRUCTION_FORM_NAME,
|
|
976
|
+
placeholder: {
|
|
977
|
+
tag: "plain_text",
|
|
978
|
+
content: "补充提交指令(可选)"
|
|
979
|
+
},
|
|
980
|
+
default_value: "",
|
|
981
|
+
width: "fill",
|
|
982
|
+
margin: "0px 0px 0px 0px"
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
function cardHeaderTitle(options, fallback, parsedPlan) {
|
|
986
|
+
if (isPlanCardStatus(options.status) && options.waiting?.kind === "plan") {
|
|
987
|
+
return parsedPlan?.title ?? fallback;
|
|
988
|
+
}
|
|
989
|
+
if (options.title) {
|
|
990
|
+
return options.title;
|
|
991
|
+
}
|
|
992
|
+
return fallback;
|
|
993
|
+
}
|
|
994
|
+
function cardHeaderSubtitle(options, header, parsedPlan) {
|
|
995
|
+
if (options.subtitle) {
|
|
996
|
+
return options.subtitle;
|
|
997
|
+
}
|
|
998
|
+
if (options.status === "waiting_input" && options.waiting?.kind === "request_user_input") {
|
|
999
|
+
const count = options.waiting.questions.length;
|
|
1000
|
+
return `${count} 个问题待回答`;
|
|
1001
|
+
}
|
|
1002
|
+
if (isPlanCardStatus(options.status) && options.waiting?.kind === "plan" && parsedPlan?.title) {
|
|
1003
|
+
return header.title;
|
|
1004
|
+
}
|
|
1005
|
+
return header.subtitle ?? "";
|
|
1006
|
+
}
|
|
1007
|
+
function cardListSummaryContent(options, title) {
|
|
1008
|
+
if (options.status === "finished") {
|
|
1009
|
+
return cardSummaryContent(options.summaryText ?? "");
|
|
1010
|
+
}
|
|
1011
|
+
if (options.status === "waiting_plan" && options.waiting?.kind === "plan") {
|
|
1012
|
+
return `[待确认计划] ${title}`;
|
|
1013
|
+
}
|
|
1014
|
+
if (options.status === "waiting_input" && options.waiting?.kind === "request_user_input") {
|
|
1015
|
+
const count = options.waiting.questions.length;
|
|
1016
|
+
return `[需要交互] ${count} 个问题待回答`;
|
|
1017
|
+
}
|
|
1018
|
+
return undefined;
|
|
1019
|
+
}
|
|
1020
|
+
function isPlanCardStatus(status) {
|
|
1021
|
+
return status === "waiting_plan" || status === "interrupted_plan" || status === "accepted_plan";
|
|
1022
|
+
}
|
|
1023
|
+
function formSelectName(id) {
|
|
1024
|
+
return `answer_${safeFormKey(id)}_select`;
|
|
1025
|
+
}
|
|
1026
|
+
function formOtherName(id) {
|
|
1027
|
+
return `answer_${safeFormKey(id)}_other`;
|
|
1028
|
+
}
|
|
1029
|
+
function safeFormKey(value) {
|
|
1030
|
+
return value.replace(/[^A-Za-z0-9_]/g, "_");
|
|
1031
|
+
}
|
|
1032
|
+
function escapeMarkdown(value) {
|
|
1033
|
+
return value.replace(/\*/g, "\\*").replace(/_/g, "\\_");
|
|
1034
|
+
}
|
|
1035
|
+
function renderProcessItems(messages) {
|
|
1036
|
+
return messages
|
|
1037
|
+
.map((message) => sanitizeProcessText(message.text))
|
|
1038
|
+
.filter((message) => message.length > 0);
|
|
1039
|
+
}
|
|
1040
|
+
function sanitizeProcessText(text) {
|
|
1041
|
+
const codeRanges = markdownCodeRanges(text);
|
|
1042
|
+
return markdownLines(text)
|
|
1043
|
+
.filter((line) => {
|
|
1044
|
+
const firstNonWhitespace = line.text.search(/\S/);
|
|
1045
|
+
return firstNonWhitespace === -1 ||
|
|
1046
|
+
!line.text.slice(firstNonWhitespace).startsWith("SEND_TO_LARK:") ||
|
|
1047
|
+
isPositionInTextRanges(line.start + firstNonWhitespace, codeRanges);
|
|
1048
|
+
})
|
|
1049
|
+
.map((line) => line.text)
|
|
1050
|
+
.join(" ")
|
|
1051
|
+
.replace(/\s+/g, " ")
|
|
1052
|
+
.trim();
|
|
1053
|
+
}
|
|
1054
|
+
function cardSummaryContent(text) {
|
|
1055
|
+
return Array.from(sanitizeProcessText(text)).slice(0, 100).join("");
|
|
1056
|
+
}
|
|
1057
|
+
function formatElapsed(elapsedMs) {
|
|
1058
|
+
const totalSeconds = Math.max(0, Math.floor(elapsedMs / 1000));
|
|
1059
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
1060
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
1061
|
+
const seconds = totalSeconds % 60;
|
|
1062
|
+
if (hours > 0) {
|
|
1063
|
+
return `${hours}h${minutes}m${seconds}s`;
|
|
1064
|
+
}
|
|
1065
|
+
if (minutes > 0) {
|
|
1066
|
+
return `${minutes}m${seconds}s`;
|
|
1067
|
+
}
|
|
1068
|
+
return `${seconds}s`;
|
|
1069
|
+
}
|
|
1070
|
+
function runtimeStatParts(stats) {
|
|
1071
|
+
if (!stats) {
|
|
1072
|
+
return [];
|
|
1073
|
+
}
|
|
1074
|
+
const parts = [];
|
|
1075
|
+
const model = formatModelAndEffort(stats.model, stats.effort);
|
|
1076
|
+
if (model) {
|
|
1077
|
+
parts.push(model);
|
|
1078
|
+
}
|
|
1079
|
+
const context = formatContextPercentage(stats.contextTokens, stats.contextWindow);
|
|
1080
|
+
if (context) {
|
|
1081
|
+
parts.push(context);
|
|
1082
|
+
}
|
|
1083
|
+
const tokenUsage = formatCompactTokenUsage(stats);
|
|
1084
|
+
if (tokenUsage) {
|
|
1085
|
+
parts.push(tokenUsage);
|
|
1086
|
+
}
|
|
1087
|
+
return parts;
|
|
1088
|
+
}
|
|
1089
|
+
function formatModelAndEffort(model, effort) {
|
|
1090
|
+
const parts = [model, effort].map((value) => value?.trim()).filter((value) => Boolean(value));
|
|
1091
|
+
return parts.length > 0 ? parts.join(" ") : undefined;
|
|
1092
|
+
}
|
|
1093
|
+
function formatContextPercentage(contextTokens, contextWindow) {
|
|
1094
|
+
const tokens = Math.max(0, Math.trunc(contextTokens));
|
|
1095
|
+
const window = Math.max(0, Math.trunc(contextWindow));
|
|
1096
|
+
if (window <= 0) {
|
|
1097
|
+
return undefined;
|
|
1098
|
+
}
|
|
1099
|
+
return `${Math.round(Math.min(100, (tokens / window) * 100))}%`;
|
|
1100
|
+
}
|
|
1101
|
+
function formatCompactTokenUsage(stats) {
|
|
1102
|
+
const inputTokens = Math.max(0, Math.trunc(stats.inputTokens));
|
|
1103
|
+
const outputTokens = Math.max(0, Math.trunc(stats.outputTokens));
|
|
1104
|
+
if (inputTokens === 0 && outputTokens === 0) {
|
|
1105
|
+
return undefined;
|
|
1106
|
+
}
|
|
1107
|
+
const cachedRatio = formatCachedInputRatio(inputTokens, stats.cachedInputTokens);
|
|
1108
|
+
const input = cachedRatio
|
|
1109
|
+
? `${formatCompactTokenCount(inputTokens)} (${cachedRatio} Cached)`
|
|
1110
|
+
: formatCompactTokenCount(inputTokens);
|
|
1111
|
+
return `↑ ${input} ↓ ${formatCompactTokenCount(outputTokens)}`;
|
|
1112
|
+
}
|
|
1113
|
+
function formatCachedInputRatio(inputTokens, cachedInputTokens) {
|
|
1114
|
+
if (inputTokens <= 0) {
|
|
1115
|
+
return undefined;
|
|
1116
|
+
}
|
|
1117
|
+
const cached = Math.max(0, Math.trunc(cachedInputTokens));
|
|
1118
|
+
const percentage = Math.min(100, Math.max(0, (cached / inputTokens) * 100));
|
|
1119
|
+
return `${Math.round(percentage)}%`;
|
|
1120
|
+
}
|
|
1121
|
+
function formatCompactTokenCount(value) {
|
|
1122
|
+
const units = ["", "K", "M", "B"];
|
|
1123
|
+
let scaled = Math.max(0, Math.trunc(value));
|
|
1124
|
+
let unitIndex = 0;
|
|
1125
|
+
while (scaled >= 1000 && unitIndex < units.length - 1) {
|
|
1126
|
+
scaled /= 1000;
|
|
1127
|
+
unitIndex += 1;
|
|
1128
|
+
}
|
|
1129
|
+
let rounded = Number(scaled.toPrecision(3));
|
|
1130
|
+
if (rounded >= 1000 && unitIndex < units.length - 1) {
|
|
1131
|
+
unitIndex += 1;
|
|
1132
|
+
rounded = Number((Math.max(0, Math.trunc(value)) / 1000 ** unitIndex).toPrecision(3));
|
|
1133
|
+
}
|
|
1134
|
+
const formatted = String(rounded);
|
|
1135
|
+
return unitIndex === 0 ? formatted : `${formatted} ${units[unitIndex]}`;
|
|
1136
|
+
}
|
|
1137
|
+
function uniqueNonEmpty(values) {
|
|
1138
|
+
const seen = new Set();
|
|
1139
|
+
const result = [];
|
|
1140
|
+
for (const value of values ?? []) {
|
|
1141
|
+
const trimmed = value.trim();
|
|
1142
|
+
if (!trimmed || seen.has(trimmed)) {
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
seen.add(trimmed);
|
|
1146
|
+
result.push(trimmed);
|
|
1147
|
+
}
|
|
1148
|
+
return result;
|
|
1149
|
+
}
|
|
1150
|
+
//# sourceMappingURL=cards.js.map
|