feishu-codex-console 1.0.0-beta.6
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/.env.example +101 -0
- package/.feishu-codex-policy.example.json +11 -0
- package/.feishu-codex-runbooks.example.json +36 -0
- package/CHANGELOG.md +129 -0
- package/CODE_OF_CONDUCT.md +7 -0
- package/CONTRIBUTING.md +52 -0
- package/LICENSE +21 -0
- package/README.en.md +81 -0
- package/README.md +398 -0
- package/ROADMAP.md +40 -0
- package/SECURITY.md +53 -0
- package/dist/account-quota-card.js +233 -0
- package/dist/account-quota.js +125 -0
- package/dist/app-server-client.js +281 -0
- package/dist/card-session.js +166 -0
- package/dist/codex-events.js +1 -0
- package/dist/codex-runner.js +875 -0
- package/dist/config.js +198 -0
- package/dist/confirmation-card.js +135 -0
- package/dist/control-card.js +345 -0
- package/dist/conversation-turn-session.js +209 -0
- package/dist/data-maintenance.js +71 -0
- package/dist/device-card.js +460 -0
- package/dist/device-health.js +94 -0
- package/dist/diagnostics.js +253 -0
- package/dist/doctor.js +250 -0
- package/dist/fallback-card-session.js +37 -0
- package/dist/health-file.js +75 -0
- package/dist/index.js +4330 -0
- package/dist/lark-cli.js +558 -0
- package/dist/lark-retry.js +34 -0
- package/dist/maintenance.js +140 -0
- package/dist/model-capabilities.js +31 -0
- package/dist/onboarding-card.js +312 -0
- package/dist/permission-lease.js +22 -0
- package/dist/policy.js +506 -0
- package/dist/progress.js +267 -0
- package/dist/project-card.js +303 -0
- package/dist/project-overview-card.js +182 -0
- package/dist/project-overview.js +278 -0
- package/dist/project-policy.js +160 -0
- package/dist/project-registry.js +259 -0
- package/dist/project-status.js +45 -0
- package/dist/project-workspace.js +55 -0
- package/dist/quota-card.js +94 -0
- package/dist/recovery-policy.js +26 -0
- package/dist/redaction.js +67 -0
- package/dist/remote-ready.js +112 -0
- package/dist/response-card.js +139 -0
- package/dist/result-card.js +166 -0
- package/dist/review-card.js +452 -0
- package/dist/runbook-card.js +272 -0
- package/dist/runbooks.js +191 -0
- package/dist/runtime-card.js +337 -0
- package/dist/session-card.js +128 -0
- package/dist/session-naming.js +14 -0
- package/dist/smoke.js +28 -0
- package/dist/state-backup.js +302 -0
- package/dist/state-store.js +874 -0
- package/dist/task-card.js +640 -0
- package/dist/task-center-card.js +176 -0
- package/dist/task-failure.js +43 -0
- package/dist/task-intent.js +76 -0
- package/dist/task-queue.js +187 -0
- package/dist/task-reconciliation.js +80 -0
- package/dist/task-review.js +497 -0
- package/dist/team-card.js +275 -0
- package/dist/team-directory.js +54 -0
- package/dist/team-policy.js +93 -0
- package/dist/types.js +1 -0
- package/dist/version.js +9 -0
- package/dist/workspace-session.js +64 -0
- package/docs/ARCHITECTURE.md +54 -0
- package/docs/COMPATIBILITY.md +55 -0
- package/docs/CONFIGURATION.md +88 -0
- package/docs/DEMO.md +45 -0
- package/docs/GOOD_FIRST_ISSUES.md +23 -0
- package/docs/INSTALLATION.md +207 -0
- package/docs/OPEN_SOURCE_PRODUCT_PLAN.md +113 -0
- package/docs/PRODUCT_REQUIREMENTS_MAP.md +591 -0
- package/docs/RELEASE_CHECKLIST.md +65 -0
- package/docs/TEAM_DEPLOYMENT.md +35 -0
- package/docs/TROUBLESHOOTING.md +130 -0
- package/docs/V4_WORKSPACE_SESSION_FLOW.md +232 -0
- package/docs/requirements/D10_MAINTENANCE_AND_ECOSYSTEM.md +103 -0
- package/docs/requirements/D1_INSTALLATION_AND_FIRST_CONNECTION.md +479 -0
- package/docs/requirements/D2_DEVICE_AND_CONNECTIVITY.md +54 -0
- package/docs/requirements/D3_PROJECTS_AND_SESSIONS.md +107 -0
- package/docs/requirements/D4_REMOTE_TASK_EXECUTION.md +102 -0
- package/docs/requirements/D5_CODEX_NATIVE_INTERACTIONS.md +99 -0
- package/docs/requirements/D6_RESULTS_AND_CODE_REVIEW.md +100 -0
- package/docs/requirements/D7_SECURITY_GOVERNANCE.md +106 -0
- package/docs/requirements/D8_RELIABILITY_AND_RECOVERY.md +182 -0
- package/docs/requirements/D9_TEAM_COLLABORATION.md +129 -0
- package/package.json +76 -0
- package/scripts/capability-probe.mjs +113 -0
- package/scripts/cli.mjs +919 -0
- package/scripts/config-file.mjs +137 -0
- package/scripts/discovery-lib.mjs +78 -0
- package/scripts/install-card.mjs +37 -0
- package/scripts/install-detection.mjs +126 -0
- package/scripts/install-state.mjs +107 -0
- package/scripts/launchd.mjs +161 -0
- package/scripts/migrate-legacy.mjs +97 -0
- package/scripts/package-smoke.mjs +163 -0
- package/scripts/release-dist-tag.mjs +7 -0
- package/scripts/runbook-template.mjs +36 -0
- package/scripts/service-health.mjs +110 -0
- package/scripts/service.mjs +24 -0
- package/scripts/setup-lib.mjs +118 -0
- package/scripts/systemd.mjs +96 -0
- package/scripts/upgrade-lib.mjs +99 -0
- package/scripts/verify-release.mjs +37 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import { renderAccountQuotaSummary } from "./account-quota-card.js";
|
|
2
|
+
export function renderDeviceCard(snapshot) {
|
|
3
|
+
const presentation = statePresentation(snapshot.availability.state);
|
|
4
|
+
const elements = [
|
|
5
|
+
availabilityPanel(snapshot),
|
|
6
|
+
remoteReadyPanel(snapshot),
|
|
7
|
+
healthMetrics(snapshot),
|
|
8
|
+
...(snapshot.accountQuota
|
|
9
|
+
? [
|
|
10
|
+
renderAccountQuotaSummary(snapshot.accountQuota, "dq", snapshot.availability.canInteract
|
|
11
|
+
? {
|
|
12
|
+
bridge: "feishu-codex-v4",
|
|
13
|
+
action: "device_quota",
|
|
14
|
+
}
|
|
15
|
+
: undefined),
|
|
16
|
+
]
|
|
17
|
+
: []),
|
|
18
|
+
currentProjectPanel(snapshot),
|
|
19
|
+
];
|
|
20
|
+
if (snapshot.feedback) {
|
|
21
|
+
elements.push({
|
|
22
|
+
tag: "column_set",
|
|
23
|
+
element_id: "feedback_panel",
|
|
24
|
+
flex_mode: "none",
|
|
25
|
+
columns: [
|
|
26
|
+
{
|
|
27
|
+
tag: "column",
|
|
28
|
+
width: "weighted",
|
|
29
|
+
weight: 1,
|
|
30
|
+
background_style: "blue-50",
|
|
31
|
+
padding: "10px 12px 10px 12px",
|
|
32
|
+
elements: [
|
|
33
|
+
{
|
|
34
|
+
tag: "markdown",
|
|
35
|
+
element_id: "device_feedback",
|
|
36
|
+
text_size: "notation",
|
|
37
|
+
content: `<font color='blue'>${safe(snapshot.feedback)}</font>`,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
elements.push(safetyNote(snapshot));
|
|
45
|
+
if (!snapshot.availability.canInteract) {
|
|
46
|
+
elements.push(connectionNotice(snapshot));
|
|
47
|
+
}
|
|
48
|
+
else if (snapshot.availability.canExecute) {
|
|
49
|
+
elements.push(primaryActions(), workspaceActions());
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
elements.push(recoveryActions(snapshot));
|
|
53
|
+
}
|
|
54
|
+
if (snapshot.availability.canInteract) {
|
|
55
|
+
const remote = remoteActions(snapshot);
|
|
56
|
+
if (remote)
|
|
57
|
+
elements.push(remote);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
schema: "2.0",
|
|
61
|
+
config: {
|
|
62
|
+
update_multi: true,
|
|
63
|
+
width_mode: "default",
|
|
64
|
+
enable_forward: false,
|
|
65
|
+
summary: {
|
|
66
|
+
content: `本地 Codex 控制台 · ${truncatePlain(snapshot.deviceName, 40)} · ${presentation.label}`,
|
|
67
|
+
},
|
|
68
|
+
style: {
|
|
69
|
+
text_size: {
|
|
70
|
+
device_title: { default: "heading-4", pc: "heading-4", mobile: "normal" },
|
|
71
|
+
device_caption: { default: "notation", pc: "notation", mobile: "notation" },
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
header: {
|
|
76
|
+
title: { tag: "plain_text", content: "本地 Codex 控制台" },
|
|
77
|
+
subtitle: {
|
|
78
|
+
tag: "plain_text",
|
|
79
|
+
content: `${truncatePlain(snapshot.deviceName, 32)} · ${truncatePlain(snapshot.project.name, 36)}`,
|
|
80
|
+
},
|
|
81
|
+
template: presentation.template,
|
|
82
|
+
icon: { tag: "standard_icon", token: "myai_colorful" },
|
|
83
|
+
text_tag_list: [
|
|
84
|
+
{
|
|
85
|
+
tag: "text_tag",
|
|
86
|
+
text: { tag: "plain_text", content: presentation.tag },
|
|
87
|
+
color: presentation.color,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
tag: "text_tag",
|
|
91
|
+
text: {
|
|
92
|
+
tag: "plain_text",
|
|
93
|
+
content: !snapshot.remoteReady.supported
|
|
94
|
+
? "唤醒不支持"
|
|
95
|
+
: snapshot.remoteReady.active
|
|
96
|
+
? "持续唤醒"
|
|
97
|
+
: "按需唤醒",
|
|
98
|
+
},
|
|
99
|
+
color: snapshot.remoteReady.active ? "blue" : "neutral",
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
body: {
|
|
104
|
+
direction: "vertical",
|
|
105
|
+
padding: "12px 12px 20px 12px",
|
|
106
|
+
vertical_spacing: "12px",
|
|
107
|
+
elements,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function availabilityPanel(snapshot) {
|
|
112
|
+
const presentation = statePresentation(snapshot.availability.state);
|
|
113
|
+
const lastSuccess = snapshot.availability.lastSuccessfulTaskAt
|
|
114
|
+
? formatTime(snapshot.availability.lastSuccessfulTaskAt)
|
|
115
|
+
: "暂无成功任务";
|
|
116
|
+
return {
|
|
117
|
+
tag: "column_set",
|
|
118
|
+
element_id: "device_state_panel",
|
|
119
|
+
flex_mode: "none",
|
|
120
|
+
columns: [
|
|
121
|
+
{
|
|
122
|
+
tag: "column",
|
|
123
|
+
width: "weighted",
|
|
124
|
+
weight: 1,
|
|
125
|
+
background_style: presentation.background,
|
|
126
|
+
padding: "12px 12px 12px 12px",
|
|
127
|
+
vertical_spacing: "4px",
|
|
128
|
+
elements: [
|
|
129
|
+
{
|
|
130
|
+
tag: "markdown",
|
|
131
|
+
element_id: "device_state_title",
|
|
132
|
+
text_size: "device_title",
|
|
133
|
+
content: `**<font color='${presentation.textColor}'>${safe(snapshot.availability.title)}</font>**`,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
tag: "markdown",
|
|
137
|
+
element_id: "device_state_detail",
|
|
138
|
+
text_size: "device_caption",
|
|
139
|
+
content: safe(snapshot.availability.detail),
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
tag: "markdown",
|
|
143
|
+
element_id: "device_state_next",
|
|
144
|
+
text_size: "device_caption",
|
|
145
|
+
content: `<font color='grey'>${safe(snapshot.availability.nextAction)}\n状态采样 ${safe(formatTime(snapshot.availability.sampledAt))} · 本人最后成功 ${safe(lastSuccess)}</font>`,
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function remoteReadyPanel(snapshot) {
|
|
153
|
+
const status = !snapshot.remoteReady.supported
|
|
154
|
+
? "当前系统不支持远程就绪"
|
|
155
|
+
: snapshot.remoteReady.active
|
|
156
|
+
? "远程就绪已开启"
|
|
157
|
+
: snapshot.remoteReady.enabled
|
|
158
|
+
? "远程就绪启动失败"
|
|
159
|
+
: "远程就绪未开启";
|
|
160
|
+
const color = snapshot.remoteReady.active
|
|
161
|
+
? "green"
|
|
162
|
+
: snapshot.remoteReady.enabled
|
|
163
|
+
? "red"
|
|
164
|
+
: "grey";
|
|
165
|
+
const background = snapshot.remoteReady.active
|
|
166
|
+
? "blue-50"
|
|
167
|
+
: snapshot.remoteReady.enabled
|
|
168
|
+
? "red-50"
|
|
169
|
+
: "grey-50";
|
|
170
|
+
const detail = !snapshot.remoteReady.supported
|
|
171
|
+
? "Remote Ready 目前仅支持 macOS;仍需自行保证电源、网络和系统唤醒"
|
|
172
|
+
: snapshot.remoteReady.lastError
|
|
173
|
+
? safe(truncate(snapshot.remoteReady.lastError, 240))
|
|
174
|
+
: snapshot.remoteReady.active
|
|
175
|
+
? "桥接服务运行期间,macOS 不会因空闲自动睡眠"
|
|
176
|
+
: "开启后可降低人在外面时本机因空闲而离线的概率";
|
|
177
|
+
return {
|
|
178
|
+
tag: "column_set",
|
|
179
|
+
element_id: "remote_ready_panel",
|
|
180
|
+
flex_mode: "none",
|
|
181
|
+
columns: [
|
|
182
|
+
{
|
|
183
|
+
tag: "column",
|
|
184
|
+
width: "weighted",
|
|
185
|
+
weight: 1,
|
|
186
|
+
background_style: background,
|
|
187
|
+
padding: "12px 12px 12px 12px",
|
|
188
|
+
vertical_spacing: "4px",
|
|
189
|
+
elements: [
|
|
190
|
+
{
|
|
191
|
+
tag: "markdown",
|
|
192
|
+
element_id: "remote_ready_status",
|
|
193
|
+
text_size: "device_title",
|
|
194
|
+
content: `**<font color='${color}'>${status}</font>**`,
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
tag: "markdown",
|
|
198
|
+
element_id: "remote_ready_detail",
|
|
199
|
+
text_size: "device_caption",
|
|
200
|
+
content: `<font color='grey'>${safe(snapshot.powerLabel)} · ${detail}</font>`,
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function healthMetrics(snapshot) {
|
|
208
|
+
const codexLabel = snapshot.codex.state === "online"
|
|
209
|
+
? "在线"
|
|
210
|
+
: snapshot.codex.state === "error"
|
|
211
|
+
? "异常"
|
|
212
|
+
: "待命";
|
|
213
|
+
const taskLabel = snapshot.activeTask
|
|
214
|
+
? `运行 1 · 等待 ${snapshot.queuedForConversation}`
|
|
215
|
+
: snapshot.queuedForConversation > 0
|
|
216
|
+
? `等待 ${snapshot.queuedForConversation}`
|
|
217
|
+
: "空闲";
|
|
218
|
+
const listenerLabel = statePresentation(snapshot.availability.state).metric;
|
|
219
|
+
return {
|
|
220
|
+
tag: "column_set",
|
|
221
|
+
element_id: "health_metrics",
|
|
222
|
+
flex_mode: "none",
|
|
223
|
+
horizontal_spacing: "8px",
|
|
224
|
+
columns: [
|
|
225
|
+
metricColumn("codex_metric", codexLabel, "Codex 引擎"),
|
|
226
|
+
metricColumn("task_metric", taskLabel, "当前聊天"),
|
|
227
|
+
metricColumn("listen_metric", listenerLabel, "飞书连接"),
|
|
228
|
+
],
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function metricColumn(elementId, value, label) {
|
|
232
|
+
return {
|
|
233
|
+
tag: "column",
|
|
234
|
+
width: "weighted",
|
|
235
|
+
weight: 1,
|
|
236
|
+
background_style: "grey-50",
|
|
237
|
+
padding: "10px 4px 10px 4px",
|
|
238
|
+
vertical_spacing: "2px",
|
|
239
|
+
elements: [
|
|
240
|
+
{
|
|
241
|
+
tag: "markdown",
|
|
242
|
+
element_id: `${elementId}_value`,
|
|
243
|
+
text_align: "center",
|
|
244
|
+
content: `**${safe(truncatePlain(value, 24))}**`,
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
tag: "markdown",
|
|
248
|
+
element_id: `${elementId}_label`,
|
|
249
|
+
text_align: "center",
|
|
250
|
+
text_size: "device_caption",
|
|
251
|
+
content: `<font color='grey'>${label}</font>`,
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function currentProjectPanel(snapshot) {
|
|
257
|
+
const taskLine = snapshot.activeTask
|
|
258
|
+
? `任务 ${safe(snapshot.activeTask)} 正在执行`
|
|
259
|
+
: `全局 ${snapshot.activeTasks} 个运行中 · ${snapshot.queuedTasks} 个排队中`;
|
|
260
|
+
return {
|
|
261
|
+
tag: "column_set",
|
|
262
|
+
element_id: "device_project_panel",
|
|
263
|
+
flex_mode: "none",
|
|
264
|
+
columns: [
|
|
265
|
+
{
|
|
266
|
+
tag: "column",
|
|
267
|
+
width: "weighted",
|
|
268
|
+
weight: 1,
|
|
269
|
+
background_style: "grey-50",
|
|
270
|
+
padding: "12px 12px 12px 12px",
|
|
271
|
+
vertical_spacing: "4px",
|
|
272
|
+
elements: [
|
|
273
|
+
{
|
|
274
|
+
tag: "markdown",
|
|
275
|
+
element_id: "device_project_label",
|
|
276
|
+
text_size: "device_caption",
|
|
277
|
+
content: "<font color='grey'>当前项目</font>",
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
tag: "markdown",
|
|
281
|
+
element_id: "device_project_name",
|
|
282
|
+
content: `**${safe(snapshot.project.name)}** ${snapshot.project.isGitRepository ? "<text_tag color='blue'>Git</text_tag>" : "<text_tag color='neutral'>文件夹</text_tag>"}`,
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
tag: "markdown",
|
|
286
|
+
element_id: "device_project_path",
|
|
287
|
+
text_size: "device_caption",
|
|
288
|
+
content: `<font color='grey'>${safe(snapshot.project.displayPath)}\n${taskLine} · 已保存 ${snapshot.threadCount} 个会话\n${safe(snapshot.osLabel)} · 已运行 ${safe(snapshot.uptimeLabel)}</font>`,
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function safetyNote(snapshot) {
|
|
296
|
+
const network = snapshot.networkEnabled ? "网络已开启" : "网络已关闭";
|
|
297
|
+
return {
|
|
298
|
+
tag: "markdown",
|
|
299
|
+
element_id: "device_safety_note",
|
|
300
|
+
text_size: "device_caption",
|
|
301
|
+
content: `<font color='grey'>${safe(snapshot.sandboxLabel)} · ${network} · 提交、推送、部署和 PR 仍需单独确认。合盖后 macOS 仍可能休眠。</font>`,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function primaryActions() {
|
|
305
|
+
return {
|
|
306
|
+
tag: "column_set",
|
|
307
|
+
element_id: "primary_actions",
|
|
308
|
+
flex_mode: "none",
|
|
309
|
+
horizontal_spacing: "8px",
|
|
310
|
+
columns: [
|
|
311
|
+
actionColumn("device_refresh", "刷新状态", "primary_filled", "device_refresh"),
|
|
312
|
+
actionColumn("device_projects", "切换项目", "default", "device_projects"),
|
|
313
|
+
actionColumn("device_new_session", "新会话", "default", "device_new_session", {
|
|
314
|
+
title: "开启新会话?",
|
|
315
|
+
text: "会清除当前聊天保存的 Codex 上下文,并停止未完成任务。",
|
|
316
|
+
}),
|
|
317
|
+
],
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function workspaceActions() {
|
|
321
|
+
return {
|
|
322
|
+
tag: "column_set",
|
|
323
|
+
element_id: "workspace_actions",
|
|
324
|
+
flex_mode: "none",
|
|
325
|
+
horizontal_spacing: "8px",
|
|
326
|
+
columns: [
|
|
327
|
+
actionColumn("device_settings", "模型设置", "default", "device_settings"),
|
|
328
|
+
actionColumn("device_sessions", "历史会话", "default", "device_sessions"),
|
|
329
|
+
actionColumn("device_tasks", "任务中心", "default", "device_tasks"),
|
|
330
|
+
],
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
function recoveryActions(snapshot) {
|
|
334
|
+
const columns = [
|
|
335
|
+
actionColumn("device_refresh", "重新检查", "primary_filled", "device_refresh"),
|
|
336
|
+
];
|
|
337
|
+
if (snapshot.availability.state === "error") {
|
|
338
|
+
columns.push(actionColumn("device_reconnect", "重连 Codex", "default", "device_reconnect"));
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
columns.push(actionColumn("device_tasks", "查看任务", "default", "device_tasks"));
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
tag: "column_set",
|
|
345
|
+
element_id: "recovery_actions",
|
|
346
|
+
flex_mode: "none",
|
|
347
|
+
horizontal_spacing: "8px",
|
|
348
|
+
columns,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function connectionNotice(snapshot) {
|
|
352
|
+
return {
|
|
353
|
+
tag: "markdown",
|
|
354
|
+
element_id: "connection_notice",
|
|
355
|
+
text_size: "device_caption",
|
|
356
|
+
content: `<font color='grey'>控制按钮已暂时隐藏,避免把无法送达的操作显示成可执行。${safe(snapshot.availability.nextAction)}</font>`,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function remoteActions(snapshot) {
|
|
360
|
+
const remoteAction = snapshot.remoteReady.active
|
|
361
|
+
? "remote_ready_disable"
|
|
362
|
+
: "remote_ready_enable";
|
|
363
|
+
const remoteLabel = snapshot.remoteReady.active ? "关闭远程就绪" : "开启远程就绪";
|
|
364
|
+
const columns = [];
|
|
365
|
+
if (snapshot.remoteReady.supported) {
|
|
366
|
+
columns.push(actionColumn("remote_ready_toggle", remoteLabel, snapshot.remoteReady.active ? "default" : "primary_filled", remoteAction, snapshot.remoteReady.active
|
|
367
|
+
? undefined
|
|
368
|
+
: {
|
|
369
|
+
title: "开启远程就绪?",
|
|
370
|
+
text: "桥接服务运行期间会阻止 macOS 因空闲自动睡眠,电池耗电可能增加。",
|
|
371
|
+
}));
|
|
372
|
+
}
|
|
373
|
+
if (snapshot.activeTask) {
|
|
374
|
+
columns.push(actionColumn("device_stop", "停止当前任务", "default", "device_stop", {
|
|
375
|
+
title: "停止当前任务?",
|
|
376
|
+
text: "已经完成的文件修改不会自动撤销。",
|
|
377
|
+
}));
|
|
378
|
+
}
|
|
379
|
+
return columns.length === 0 ? null : {
|
|
380
|
+
tag: "column_set",
|
|
381
|
+
element_id: "remote_actions",
|
|
382
|
+
flex_mode: "none",
|
|
383
|
+
horizontal_spacing: "8px",
|
|
384
|
+
columns,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function actionColumn(elementId, label, type, action, confirm) {
|
|
388
|
+
const button = {
|
|
389
|
+
tag: "button",
|
|
390
|
+
element_id: elementId,
|
|
391
|
+
text: { tag: "plain_text", content: label },
|
|
392
|
+
type,
|
|
393
|
+
width: "fill",
|
|
394
|
+
behaviors: [
|
|
395
|
+
{
|
|
396
|
+
type: "callback",
|
|
397
|
+
value: { bridge: "feishu-codex-v4", action },
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
};
|
|
401
|
+
if (confirm) {
|
|
402
|
+
button.confirm = {
|
|
403
|
+
title: { tag: "plain_text", content: confirm.title },
|
|
404
|
+
text: { tag: "plain_text", content: confirm.text },
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
tag: "column",
|
|
409
|
+
width: "weighted",
|
|
410
|
+
weight: 1,
|
|
411
|
+
elements: [button],
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
function statePresentation(state) {
|
|
415
|
+
switch (state) {
|
|
416
|
+
case "online":
|
|
417
|
+
return { label: "在线", tag: "可以执行", metric: "已连接", template: "blue", color: "green", textColor: "green", background: "blue-50" };
|
|
418
|
+
case "degraded":
|
|
419
|
+
return { label: "部分可用", tag: "部分可用", metric: "部分连接", template: "orange", color: "orange", textColor: "orange", background: "orange-50" };
|
|
420
|
+
case "error":
|
|
421
|
+
return { label: "异常", tag: "需要修复", metric: "连接正常", template: "red", color: "red", textColor: "red", background: "red-50" };
|
|
422
|
+
case "maintenance":
|
|
423
|
+
return { label: "维护中", tag: "暂停任务", metric: "维护中", template: "grey", color: "neutral", textColor: "grey", background: "grey-50" };
|
|
424
|
+
case "offline":
|
|
425
|
+
return { label: "离线", tag: "设备离线", metric: "已离线", template: "grey", color: "neutral", textColor: "grey", background: "grey-50" };
|
|
426
|
+
case "connecting":
|
|
427
|
+
return { label: "连接中", tag: "正在连接", metric: "连接中", template: "orange", color: "orange", textColor: "orange", background: "orange-50" };
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function formatTime(value) {
|
|
431
|
+
const date = new Date(value);
|
|
432
|
+
if (!Number.isFinite(date.getTime()))
|
|
433
|
+
return "时间未知";
|
|
434
|
+
return date.toLocaleString("zh-CN", {
|
|
435
|
+
month: "2-digit",
|
|
436
|
+
day: "2-digit",
|
|
437
|
+
hour: "2-digit",
|
|
438
|
+
minute: "2-digit",
|
|
439
|
+
hour12: false,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
function safe(value) {
|
|
443
|
+
return value
|
|
444
|
+
.replaceAll("&", "&")
|
|
445
|
+
.replaceAll("<", "<")
|
|
446
|
+
.replaceAll(">", ">")
|
|
447
|
+
.replaceAll("*", "*")
|
|
448
|
+
.replaceAll("_", "_")
|
|
449
|
+
.replaceAll("`", "`")
|
|
450
|
+
.replaceAll("[", "[")
|
|
451
|
+
.replaceAll("]", "]");
|
|
452
|
+
}
|
|
453
|
+
function truncate(value, maxLength) {
|
|
454
|
+
if (value.length <= maxLength)
|
|
455
|
+
return value;
|
|
456
|
+
return `${value.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
457
|
+
}
|
|
458
|
+
function truncatePlain(value, maxLength) {
|
|
459
|
+
return truncate(value.replaceAll("<", "<").replaceAll(">", ">"), maxLength);
|
|
460
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export function deriveDeviceAvailability({ consumers, codex, api, sampledAt = new Date().toISOString(), serviceHeartbeatAt, heartbeatTimeoutMs = 45_000, lastSuccessfulTaskAt, maintenance = false, }) {
|
|
2
|
+
const common = {
|
|
3
|
+
sampledAt,
|
|
4
|
+
...(lastSuccessfulTaskAt ? { lastSuccessfulTaskAt } : {}),
|
|
5
|
+
};
|
|
6
|
+
if (maintenance) {
|
|
7
|
+
return {
|
|
8
|
+
state: "maintenance",
|
|
9
|
+
title: "设备正在维护",
|
|
10
|
+
detail: "服务在线,但暂不接受新的 Codex 任务。",
|
|
11
|
+
nextAction: "维护结束后刷新状态。",
|
|
12
|
+
canExecute: false,
|
|
13
|
+
canInteract: true,
|
|
14
|
+
...common,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (serviceHeartbeatAt) {
|
|
18
|
+
const sampled = Date.parse(sampledAt);
|
|
19
|
+
const heartbeat = Date.parse(serviceHeartbeatAt);
|
|
20
|
+
if (!Number.isFinite(heartbeat) || sampled - heartbeat > heartbeatTimeoutMs) {
|
|
21
|
+
return {
|
|
22
|
+
state: "offline",
|
|
23
|
+
title: "本地设备已离线",
|
|
24
|
+
detail: "服务健康心跳已经过期,当前卡片不能证明设备仍可执行任务。",
|
|
25
|
+
nextAction: "检查电脑电源、网络和后台服务;恢复后重新发送“状态”。",
|
|
26
|
+
canExecute: false,
|
|
27
|
+
canInteract: false,
|
|
28
|
+
...common,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const messages = consumers.find((consumer) => consumer.eventKey === "im.message.receive_v1");
|
|
33
|
+
const cards = consumers.find((consumer) => consumer.eventKey === "card.action.trigger");
|
|
34
|
+
if (!messages?.ready) {
|
|
35
|
+
return {
|
|
36
|
+
state: "connecting",
|
|
37
|
+
title: "飞书连接正在恢复",
|
|
38
|
+
detail: "消息事件尚未就绪,此时发送的新任务可能无法被本机接收。",
|
|
39
|
+
nextAction: "等待连接恢复后重新发送“状态”。",
|
|
40
|
+
canExecute: false,
|
|
41
|
+
canInteract: false,
|
|
42
|
+
...common,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (!cards?.ready) {
|
|
46
|
+
return {
|
|
47
|
+
state: "degraded",
|
|
48
|
+
title: "设备部分可用",
|
|
49
|
+
detail: "文字消息可用,但卡片按钮连接正在恢复。",
|
|
50
|
+
nextAction: "可以直接发送文字任务;需要按钮操作时稍后刷新状态。",
|
|
51
|
+
canExecute: true,
|
|
52
|
+
canInteract: false,
|
|
53
|
+
...common,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (api?.state === "degraded") {
|
|
57
|
+
return {
|
|
58
|
+
state: "degraded",
|
|
59
|
+
title: "飞书发送链路正在恢复",
|
|
60
|
+
detail: `消息接收正常,但最近 ${api.consecutiveFailures} 次飞书 API 请求失败;可靠文本回复会继续重试。`,
|
|
61
|
+
nextAction: "任务仍可发送;若长时间没有回复,请检查网络后刷新状态。",
|
|
62
|
+
canExecute: true,
|
|
63
|
+
canInteract: true,
|
|
64
|
+
...common,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (codex.lastError && !codex.ready) {
|
|
68
|
+
return {
|
|
69
|
+
state: "error",
|
|
70
|
+
title: "Codex 引擎异常",
|
|
71
|
+
detail: truncate(codex.lastError, 240),
|
|
72
|
+
nextAction: "点击“重连 Codex”验证登录和本地引擎。",
|
|
73
|
+
canExecute: false,
|
|
74
|
+
canInteract: true,
|
|
75
|
+
...common,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
state: "online",
|
|
80
|
+
title: "可以开始任务",
|
|
81
|
+
detail: codex.ready
|
|
82
|
+
? "飞书消息、卡片和 Codex 引擎均已连接。"
|
|
83
|
+
: "飞书连接正常;Codex 引擎将在首个任务时按需启动。",
|
|
84
|
+
nextAction: "直接发送完整任务,或先切换项目与模型。",
|
|
85
|
+
canExecute: true,
|
|
86
|
+
canInteract: true,
|
|
87
|
+
...common,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function truncate(value, maxLength) {
|
|
91
|
+
if (value.length <= maxLength)
|
|
92
|
+
return value;
|
|
93
|
+
return `${value.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
94
|
+
}
|