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.
Files changed (113) hide show
  1. package/.env.example +101 -0
  2. package/.feishu-codex-policy.example.json +11 -0
  3. package/.feishu-codex-runbooks.example.json +36 -0
  4. package/CHANGELOG.md +129 -0
  5. package/CODE_OF_CONDUCT.md +7 -0
  6. package/CONTRIBUTING.md +52 -0
  7. package/LICENSE +21 -0
  8. package/README.en.md +81 -0
  9. package/README.md +398 -0
  10. package/ROADMAP.md +40 -0
  11. package/SECURITY.md +53 -0
  12. package/dist/account-quota-card.js +233 -0
  13. package/dist/account-quota.js +125 -0
  14. package/dist/app-server-client.js +281 -0
  15. package/dist/card-session.js +166 -0
  16. package/dist/codex-events.js +1 -0
  17. package/dist/codex-runner.js +875 -0
  18. package/dist/config.js +198 -0
  19. package/dist/confirmation-card.js +135 -0
  20. package/dist/control-card.js +345 -0
  21. package/dist/conversation-turn-session.js +209 -0
  22. package/dist/data-maintenance.js +71 -0
  23. package/dist/device-card.js +460 -0
  24. package/dist/device-health.js +94 -0
  25. package/dist/diagnostics.js +253 -0
  26. package/dist/doctor.js +250 -0
  27. package/dist/fallback-card-session.js +37 -0
  28. package/dist/health-file.js +75 -0
  29. package/dist/index.js +4330 -0
  30. package/dist/lark-cli.js +558 -0
  31. package/dist/lark-retry.js +34 -0
  32. package/dist/maintenance.js +140 -0
  33. package/dist/model-capabilities.js +31 -0
  34. package/dist/onboarding-card.js +312 -0
  35. package/dist/permission-lease.js +22 -0
  36. package/dist/policy.js +506 -0
  37. package/dist/progress.js +267 -0
  38. package/dist/project-card.js +303 -0
  39. package/dist/project-overview-card.js +182 -0
  40. package/dist/project-overview.js +278 -0
  41. package/dist/project-policy.js +160 -0
  42. package/dist/project-registry.js +259 -0
  43. package/dist/project-status.js +45 -0
  44. package/dist/project-workspace.js +55 -0
  45. package/dist/quota-card.js +94 -0
  46. package/dist/recovery-policy.js +26 -0
  47. package/dist/redaction.js +67 -0
  48. package/dist/remote-ready.js +112 -0
  49. package/dist/response-card.js +139 -0
  50. package/dist/result-card.js +166 -0
  51. package/dist/review-card.js +452 -0
  52. package/dist/runbook-card.js +272 -0
  53. package/dist/runbooks.js +191 -0
  54. package/dist/runtime-card.js +337 -0
  55. package/dist/session-card.js +128 -0
  56. package/dist/session-naming.js +14 -0
  57. package/dist/smoke.js +28 -0
  58. package/dist/state-backup.js +302 -0
  59. package/dist/state-store.js +874 -0
  60. package/dist/task-card.js +640 -0
  61. package/dist/task-center-card.js +176 -0
  62. package/dist/task-failure.js +43 -0
  63. package/dist/task-intent.js +76 -0
  64. package/dist/task-queue.js +187 -0
  65. package/dist/task-reconciliation.js +80 -0
  66. package/dist/task-review.js +497 -0
  67. package/dist/team-card.js +275 -0
  68. package/dist/team-directory.js +54 -0
  69. package/dist/team-policy.js +93 -0
  70. package/dist/types.js +1 -0
  71. package/dist/version.js +9 -0
  72. package/dist/workspace-session.js +64 -0
  73. package/docs/ARCHITECTURE.md +54 -0
  74. package/docs/COMPATIBILITY.md +55 -0
  75. package/docs/CONFIGURATION.md +88 -0
  76. package/docs/DEMO.md +45 -0
  77. package/docs/GOOD_FIRST_ISSUES.md +23 -0
  78. package/docs/INSTALLATION.md +207 -0
  79. package/docs/OPEN_SOURCE_PRODUCT_PLAN.md +113 -0
  80. package/docs/PRODUCT_REQUIREMENTS_MAP.md +591 -0
  81. package/docs/RELEASE_CHECKLIST.md +65 -0
  82. package/docs/TEAM_DEPLOYMENT.md +35 -0
  83. package/docs/TROUBLESHOOTING.md +130 -0
  84. package/docs/V4_WORKSPACE_SESSION_FLOW.md +232 -0
  85. package/docs/requirements/D10_MAINTENANCE_AND_ECOSYSTEM.md +103 -0
  86. package/docs/requirements/D1_INSTALLATION_AND_FIRST_CONNECTION.md +479 -0
  87. package/docs/requirements/D2_DEVICE_AND_CONNECTIVITY.md +54 -0
  88. package/docs/requirements/D3_PROJECTS_AND_SESSIONS.md +107 -0
  89. package/docs/requirements/D4_REMOTE_TASK_EXECUTION.md +102 -0
  90. package/docs/requirements/D5_CODEX_NATIVE_INTERACTIONS.md +99 -0
  91. package/docs/requirements/D6_RESULTS_AND_CODE_REVIEW.md +100 -0
  92. package/docs/requirements/D7_SECURITY_GOVERNANCE.md +106 -0
  93. package/docs/requirements/D8_RELIABILITY_AND_RECOVERY.md +182 -0
  94. package/docs/requirements/D9_TEAM_COLLABORATION.md +129 -0
  95. package/package.json +76 -0
  96. package/scripts/capability-probe.mjs +113 -0
  97. package/scripts/cli.mjs +919 -0
  98. package/scripts/config-file.mjs +137 -0
  99. package/scripts/discovery-lib.mjs +78 -0
  100. package/scripts/install-card.mjs +37 -0
  101. package/scripts/install-detection.mjs +126 -0
  102. package/scripts/install-state.mjs +107 -0
  103. package/scripts/launchd.mjs +161 -0
  104. package/scripts/migrate-legacy.mjs +97 -0
  105. package/scripts/package-smoke.mjs +163 -0
  106. package/scripts/release-dist-tag.mjs +7 -0
  107. package/scripts/runbook-template.mjs +36 -0
  108. package/scripts/service-health.mjs +110 -0
  109. package/scripts/service.mjs +24 -0
  110. package/scripts/setup-lib.mjs +118 -0
  111. package/scripts/systemd.mjs +96 -0
  112. package/scripts/upgrade-lib.mjs +99 -0
  113. package/scripts/verify-release.mjs +37 -0
@@ -0,0 +1,337 @@
1
+ import { redactSensitiveText } from "./redaction.js";
2
+ export function renderRuntimeApprovalCard(requestId, request, projectLabel, state = "pending", ttlMinutes = 10) {
3
+ const pending = state === "pending";
4
+ const kindLabel = approvalKindLabel(request.kind);
5
+ const elements = [
6
+ {
7
+ tag: "column_set",
8
+ element_id: "approval_detail",
9
+ flex_mode: "none",
10
+ columns: [
11
+ {
12
+ tag: "column",
13
+ width: "weighted",
14
+ weight: 1,
15
+ background_style: pending ? "orange-50" : "grey-50",
16
+ padding: "12px 12px 12px 12px",
17
+ vertical_spacing: "6px",
18
+ elements: [
19
+ {
20
+ tag: "markdown",
21
+ content: `**${safe(request.title)}**\n${safe(truncate(request.detail, 2_000))}`,
22
+ },
23
+ {
24
+ tag: "markdown",
25
+ text_size: "notation",
26
+ content: `<font color='grey'>类型:${kindLabel}\n项目:${safe(projectLabel)}${request.cwd ? `\n目录:${safe(request.cwd)}` : ""}${request.reason ? `\n原因:${safe(request.reason)}` : ""}\n范围:允许一次仅用于当前请求;本会话允许可能放行后续同类请求</font>`,
27
+ },
28
+ ],
29
+ },
30
+ ],
31
+ },
32
+ ];
33
+ if (pending) {
34
+ elements.push({
35
+ tag: "column_set",
36
+ element_id: "approval_actions",
37
+ flex_mode: "none",
38
+ horizontal_spacing: "8px",
39
+ columns: [
40
+ actionButton("allow_once", "允许一次", "primary_filled", requestId, "approve_runtime_once"),
41
+ actionButton("allow_session", "本会话允许", "default", requestId, "approve_runtime_session", {
42
+ title: "本会话持续允许?",
43
+ text: "同类权限在当前 Codex 会话中可能不再询问。",
44
+ }),
45
+ actionButton("deny", "拒绝", "default", requestId, "reject_runtime"),
46
+ ],
47
+ });
48
+ }
49
+ else {
50
+ elements.push({
51
+ tag: "markdown",
52
+ element_id: "approval_result",
53
+ text_align: "center",
54
+ content: state === "accepted"
55
+ ? "<font color='green'>已允许一次</font>"
56
+ : state === "session"
57
+ ? "<font color='green'>已在本会话允许</font>"
58
+ : state === "expired"
59
+ ? "<font color='grey'>请求已过期,Codex 未获得权限。发送“任务”查看最新任务。</font>"
60
+ : "<font color='grey'>已拒绝,Codex 未获得权限</font>",
61
+ });
62
+ }
63
+ return {
64
+ schema: "2.0",
65
+ config: {
66
+ update_multi: true,
67
+ width_mode: "default",
68
+ enable_forward: false,
69
+ summary: { content: `Codex 运行时确认 · ${projectLabel}` },
70
+ },
71
+ header: {
72
+ title: { tag: "plain_text", content: "Codex 需要你的确认" },
73
+ subtitle: {
74
+ tag: "plain_text",
75
+ content: `这是本地 Codex 的实时权限请求;${ttlMinutes} 分钟内有效`,
76
+ },
77
+ template: pending ? "orange" : state === "accepted" || state === "session" ? "green" : "grey",
78
+ icon: { tag: "standard_icon", token: "warning_colorful" },
79
+ text_tag_list: [
80
+ {
81
+ tag: "text_tag",
82
+ text: { tag: "plain_text", content: "运行时权限" },
83
+ color: pending ? "orange" : "neutral",
84
+ },
85
+ {
86
+ tag: "text_tag",
87
+ text: { tag: "plain_text", content: kindLabel },
88
+ color: "neutral",
89
+ },
90
+ ],
91
+ },
92
+ body: {
93
+ direction: "vertical",
94
+ padding: "12px 12px 20px 12px",
95
+ vertical_spacing: "12px",
96
+ elements,
97
+ },
98
+ };
99
+ }
100
+ export function renderRuntimeQuestionCard(requestId, question, position, state = "pending", answer = "", ttlMinutes = 10) {
101
+ const pending = state === "pending";
102
+ const answerAllowed = !question.isSecret;
103
+ const elements = [
104
+ {
105
+ tag: "column_set",
106
+ element_id: "question_detail",
107
+ flex_mode: "none",
108
+ columns: [
109
+ {
110
+ tag: "column",
111
+ width: "weighted",
112
+ weight: 1,
113
+ background_style: pending ? "blue-50" : "grey-50",
114
+ padding: "12px 12px 12px 12px",
115
+ vertical_spacing: "6px",
116
+ elements: [
117
+ {
118
+ tag: "markdown",
119
+ text_size: "notation",
120
+ content: `<font color='blue'>${safe(question.header)}</font>`,
121
+ },
122
+ { tag: "markdown", content: `**${safe(question.question)}**` },
123
+ ],
124
+ },
125
+ ],
126
+ },
127
+ ];
128
+ if (pending && answerAllowed && question.options.length > 0 && question.options.length <= 3) {
129
+ elements.push({
130
+ tag: "column_set",
131
+ element_id: "question_options",
132
+ flex_mode: "none",
133
+ horizontal_spacing: "8px",
134
+ columns: question.options.map((option, index) => answerButton(`answer_${index}`, option.label, index === 0 ? "primary_filled" : "default", requestId, question.id, option.label)),
135
+ });
136
+ const descriptions = question.options
137
+ .filter((option) => option.description)
138
+ .map((option) => `- **${safe(option.label)}**:${safe(option.description)}`);
139
+ if (descriptions.length > 0) {
140
+ elements.push({
141
+ tag: "markdown",
142
+ element_id: "option_descriptions",
143
+ text_size: "notation",
144
+ content: descriptions.join("\n"),
145
+ });
146
+ }
147
+ }
148
+ else if (pending && answerAllowed && question.options.length > 0) {
149
+ elements.push({
150
+ tag: "select_static",
151
+ element_id: "question_select",
152
+ name: "runtime_answer",
153
+ width: "fill",
154
+ placeholder: { tag: "plain_text", content: "选择答案" },
155
+ options: question.options.map((option) => ({
156
+ text: { tag: "plain_text", content: truncatePlain(option.label, 70) },
157
+ value: option.label,
158
+ })),
159
+ behaviors: [
160
+ {
161
+ type: "callback",
162
+ value: {
163
+ bridge: "feishu-codex-v4",
164
+ action: "answer_runtime",
165
+ request_id: requestId,
166
+ question_id: question.id,
167
+ },
168
+ },
169
+ ],
170
+ });
171
+ }
172
+ if (pending) {
173
+ elements.push({
174
+ tag: "markdown",
175
+ element_id: "answer_hint",
176
+ text_size: "notation",
177
+ content: question.isSecret
178
+ ? "<font color='red'>安全保护:此桥接器不会通过飞书收集或转发密码、令牌、密钥等敏感答案。请回本机处理,或点击“取消回答”。</font>"
179
+ : question.isOther || question.options.length === 0
180
+ ? "<font color='grey'>也可以直接发送下一条飞书消息作为答案。</font>"
181
+ : "<font color='grey'>点击选项,或直接发送下一条飞书消息作为答案。</font>",
182
+ });
183
+ elements.push({
184
+ tag: "column_set",
185
+ element_id: "question_actions",
186
+ flex_mode: "none",
187
+ columns: [
188
+ {
189
+ tag: "column",
190
+ width: "weighted",
191
+ weight: 1,
192
+ elements: [
193
+ {
194
+ tag: "button",
195
+ element_id: "cancel_answer",
196
+ text: { tag: "plain_text", content: "取消回答" },
197
+ type: "default",
198
+ width: "fill",
199
+ behaviors: [
200
+ {
201
+ type: "callback",
202
+ value: {
203
+ bridge: "feishu-codex-v4",
204
+ action: "cancel_runtime_question",
205
+ request_id: requestId,
206
+ question_id: question.id,
207
+ },
208
+ },
209
+ ],
210
+ },
211
+ ],
212
+ },
213
+ ],
214
+ });
215
+ }
216
+ else {
217
+ elements.push({
218
+ tag: "markdown",
219
+ element_id: "answer_result",
220
+ text_align: "center",
221
+ content: state === "answered"
222
+ ? `<font color='green'>已回答:${safe(truncate(answer, 300))}</font>`
223
+ : state === "cancelled"
224
+ ? "<font color='grey'>已取消回答,Codex 将继续处理</font>"
225
+ : "<font color='grey'>问题已过期,Codex 将继续处理。发送“任务”查看最新任务。</font>",
226
+ });
227
+ }
228
+ return {
229
+ schema: "2.0",
230
+ config: {
231
+ update_multi: true,
232
+ width_mode: "default",
233
+ enable_forward: false,
234
+ summary: { content: `Codex 等待你的输入 · ${question.header}` },
235
+ },
236
+ header: {
237
+ title: { tag: "plain_text", content: "Codex 在等你回答" },
238
+ subtitle: {
239
+ tag: "plain_text",
240
+ content: `问题 ${position.index} / ${position.total} · ${ttlMinutes} 分钟内有效 · 回答后任务会自动继续`,
241
+ },
242
+ template: pending ? "blue" : state === "answered" ? "green" : "grey",
243
+ icon: { tag: "standard_icon", token: "myai_colorful" },
244
+ text_tag_list: [
245
+ {
246
+ tag: "text_tag",
247
+ text: { tag: "plain_text", content: "实时追问" },
248
+ color: pending ? "blue" : "neutral",
249
+ },
250
+ ],
251
+ },
252
+ body: {
253
+ direction: "vertical",
254
+ padding: "12px 12px 20px 12px",
255
+ vertical_spacing: "12px",
256
+ elements,
257
+ },
258
+ };
259
+ }
260
+ function actionButton(elementId, label, type, requestId, action, confirm) {
261
+ const button = {
262
+ tag: "button",
263
+ element_id: elementId,
264
+ text: { tag: "plain_text", content: label },
265
+ type,
266
+ width: "fill",
267
+ behaviors: [
268
+ {
269
+ type: "callback",
270
+ value: { bridge: "feishu-codex-v4", action, request_id: requestId },
271
+ },
272
+ ],
273
+ };
274
+ if (confirm) {
275
+ button.confirm = {
276
+ title: { tag: "plain_text", content: confirm.title },
277
+ text: { tag: "plain_text", content: confirm.text },
278
+ };
279
+ }
280
+ return { tag: "column", width: "weighted", weight: 1, elements: [button] };
281
+ }
282
+ function answerButton(elementId, label, type, requestId, questionId, answer) {
283
+ return {
284
+ tag: "column",
285
+ width: "weighted",
286
+ weight: 1,
287
+ elements: [
288
+ {
289
+ tag: "button",
290
+ element_id: elementId,
291
+ text: { tag: "plain_text", content: truncatePlain(label, 40) },
292
+ type,
293
+ width: "fill",
294
+ behaviors: [
295
+ {
296
+ type: "callback",
297
+ value: {
298
+ bridge: "feishu-codex-v4",
299
+ action: "answer_runtime",
300
+ request_id: requestId,
301
+ question_id: questionId,
302
+ answer,
303
+ },
304
+ },
305
+ ],
306
+ },
307
+ ],
308
+ };
309
+ }
310
+ function safe(value) {
311
+ return redactSensitiveText(value)
312
+ .replaceAll("&", "&")
313
+ .replaceAll("<", "<")
314
+ .replaceAll(">", ">")
315
+ .replaceAll("*", "*")
316
+ .replaceAll("_", "_")
317
+ .replaceAll("`", "`")
318
+ .replaceAll("<at", "<at")
319
+ .replaceAll("</at>", "</at>");
320
+ }
321
+ function approvalKindLabel(kind) {
322
+ if (kind === "command")
323
+ return "命令执行";
324
+ if (kind === "file_change")
325
+ return "越界写入";
326
+ return "临时权限";
327
+ }
328
+ function truncate(value, max) {
329
+ if (value.length <= max)
330
+ return value;
331
+ return `${value.slice(0, max - 1)}…`;
332
+ }
333
+ function truncatePlain(value, max) {
334
+ if (value.length <= max)
335
+ return value;
336
+ return `${value.slice(0, max - 1)}…`;
337
+ }
@@ -0,0 +1,128 @@
1
+ export function renderSessionCenterCard(snapshot) {
2
+ const currentExists = snapshot.sessions.some((session) => session.id === snapshot.currentThreadId);
3
+ return {
4
+ schema: "2.0",
5
+ config: {
6
+ update_multi: true,
7
+ width_mode: "default",
8
+ enable_forward: false,
9
+ summary: { content: `Codex 会话中心 · ${safe(snapshot.projectName)}` },
10
+ },
11
+ header: {
12
+ title: { tag: "plain_text", content: "Codex 会话中心" },
13
+ subtitle: { tag: "plain_text", content: `恢复 ${snapshot.projectName} 的历史上下文` },
14
+ template: "blue",
15
+ icon: { tag: "standard_icon", token: "history_colorful" },
16
+ text_tag_list: [
17
+ {
18
+ tag: "text_tag",
19
+ text: { tag: "plain_text", content: `${snapshot.sessions.length} 个最近会话` },
20
+ color: "blue",
21
+ },
22
+ ],
23
+ },
24
+ body: {
25
+ direction: "vertical",
26
+ padding: "12px 12px 18px 12px",
27
+ vertical_spacing: "12px",
28
+ elements: [
29
+ ...(snapshot.feedback ? [notice(snapshot.feedback)] : []),
30
+ snapshot.sessions.length > 0
31
+ ? {
32
+ tag: "select_static",
33
+ element_id: "session_select",
34
+ name: "thread_id",
35
+ width: "fill",
36
+ placeholder: { tag: "plain_text", content: "选择要恢复的会话" },
37
+ ...(currentExists ? { initial_option: snapshot.currentThreadId } : {}),
38
+ options: snapshot.sessions.slice(0, 20).map((session) => ({
39
+ text: {
40
+ tag: "plain_text",
41
+ content: `${sessionTitle(session)} · ${relativeTime(session.updatedAt)}`.slice(0, 90),
42
+ },
43
+ value: session.id,
44
+ })),
45
+ behaviors: [callback("select_session")],
46
+ }
47
+ : {
48
+ tag: "markdown",
49
+ element_id: "session_empty",
50
+ content: "**还没有历史会话**\n<font color='grey'>发送第一条开发任务后,会话会自动保存在这里。</font>",
51
+ },
52
+ ...snapshot.sessions.slice(0, 5).map((session, index) => sessionRow(session, index)),
53
+ actionGrid("session_actions", [
54
+ button("压缩当前会话", "session_compact", "default"),
55
+ button("新会话", "session_new", "primary"),
56
+ button("返回设置", "session_settings", "default"),
57
+ button("刷新", "session_refresh", "default"),
58
+ ]),
59
+ ],
60
+ },
61
+ };
62
+ }
63
+ function sessionRow(session, index) {
64
+ return {
65
+ tag: "markdown",
66
+ element_id: `session_row_${index}`,
67
+ content: `**${safe(sessionTitle(session))}** ${statusTag(session.status)}\n<font color='grey'>${safe(session.preview || "暂无摘要").slice(0, 160)}</font>`,
68
+ };
69
+ }
70
+ function notice(text) {
71
+ return {
72
+ tag: "markdown",
73
+ element_id: "session_feedback",
74
+ content: `<font color='blue'>${safe(text)}</font>`,
75
+ };
76
+ }
77
+ function callback(action) {
78
+ return { type: "callback", value: { bridge: "feishu-codex-v5", action } };
79
+ }
80
+ function button(text, action, type) {
81
+ return {
82
+ tag: "button",
83
+ element_id: action.slice(0, 20),
84
+ text: { tag: "plain_text", content: text },
85
+ type: type === "primary" ? "primary_filled" : "default",
86
+ width: "fill",
87
+ behaviors: [callback(action)],
88
+ };
89
+ }
90
+ function actionGrid(id, actions) {
91
+ return {
92
+ tag: "column_set",
93
+ element_id: id,
94
+ flex_mode: "none",
95
+ horizontal_spacing: "8px",
96
+ columns: actions.map((action) => ({
97
+ tag: "column",
98
+ width: "weighted",
99
+ weight: 1,
100
+ elements: [action],
101
+ })),
102
+ };
103
+ }
104
+ function sessionTitle(session) {
105
+ return session.name || session.preview || session.id.slice(0, 8);
106
+ }
107
+ function relativeTime(timestamp) {
108
+ if (!timestamp)
109
+ return "未知时间";
110
+ const seconds = Math.max(0, Math.floor(Date.now() / 1_000 - timestamp));
111
+ if (seconds < 60)
112
+ return "刚刚";
113
+ if (seconds < 3_600)
114
+ return `${Math.floor(seconds / 60)} 分钟前`;
115
+ if (seconds < 86_400)
116
+ return `${Math.floor(seconds / 3_600)} 小时前`;
117
+ return `${Math.floor(seconds / 86_400)} 天前`;
118
+ }
119
+ function statusTag(status) {
120
+ if (status === "active")
121
+ return "<text_tag color='green'>活跃</text_tag>";
122
+ if (status === "error")
123
+ return "<text_tag color='red'>异常</text_tag>";
124
+ return "<text_tag color='neutral'>已保存</text_tag>";
125
+ }
126
+ function safe(value) {
127
+ return value.replaceAll("<", "<").replaceAll(">", ">").replaceAll("*", "*").replaceAll("`", "`");
128
+ }
@@ -0,0 +1,14 @@
1
+ export function sessionNameFromPrompt(prompt, projectName, maxLength = 56) {
2
+ const firstMeaningfulLine = prompt
3
+ .split(/\r?\n/)
4
+ .map((line) => line.trim())
5
+ .find(Boolean)
6
+ ?.replace(/^[-*#>\s]+/, "")
7
+ .replace(/\s+/g, " ")
8
+ .trim();
9
+ const fallback = `${projectName} 新任务`;
10
+ const selected = firstMeaningfulLine || fallback;
11
+ if (selected.length <= maxLength)
12
+ return selected;
13
+ return `${selected.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…`;
14
+ }
package/dist/smoke.js ADDED
@@ -0,0 +1,28 @@
1
+ import "dotenv/config";
2
+ import { existsSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { CodexRunner } from "./codex-runner.js";
5
+ import { loadConfig } from "./config.js";
6
+ async function main() {
7
+ const config = loadConfig();
8
+ const runner = new CodexRunner({
9
+ ...config,
10
+ sandboxMode: "read-only",
11
+ networkAccessEnabled: false,
12
+ webSearchMode: "disabled",
13
+ codexTimeoutMs: 120_000,
14
+ });
15
+ try {
16
+ const result = await runner.run("smoke-test", "smoke", "Reply with exactly BRIDGE_SMOKE_OK. Do not run commands and do not modify files.", config.workdir, existsSync(path.join(config.workdir, ".git")));
17
+ process.stdout.write(`${result.finalResponse.trim()}\n`);
18
+ if (result.finalResponse.trim() !== "BRIDGE_SMOKE_OK")
19
+ process.exitCode = 1;
20
+ }
21
+ finally {
22
+ await runner.close();
23
+ }
24
+ }
25
+ main().catch((error) => {
26
+ console.error(error);
27
+ process.exitCode = 1;
28
+ });