lark-to-codex 0.1.0
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 +141 -0
- package/README.zh.md +141 -0
- package/bin/lark-to-codex.mjs +2 -0
- package/dist/cli.js +5934 -0
- package/dist/index.d.ts +134 -0
- package/dist/index.js +879 -0
- package/package.json +56 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,879 @@
|
|
|
1
|
+
// src/card/tool-render.ts
|
|
2
|
+
var HEADER_SUMMARY_MAX = 80;
|
|
3
|
+
var BODY_FIELD_MAX = 600;
|
|
4
|
+
var OUTPUT_MAX = 1200;
|
|
5
|
+
var BODY_TOTAL_MAX = 2500;
|
|
6
|
+
function toolHeaderText(tool) {
|
|
7
|
+
const icon = tool.status === "done" ? "\u2705" : tool.status === "error" ? "\u274C" : "\u23F3";
|
|
8
|
+
const summary = summarizeInput(tool.name, tool.input);
|
|
9
|
+
return summary ? `${icon} **${tool.name}** \u2014 ${summary}` : `${icon} **${tool.name}**`;
|
|
10
|
+
}
|
|
11
|
+
function toolBodyMd(tool) {
|
|
12
|
+
const parts = [];
|
|
13
|
+
const inputMd = renderInput(tool);
|
|
14
|
+
if (inputMd) parts.push(inputMd);
|
|
15
|
+
if (tool.output) {
|
|
16
|
+
const truncated = truncate(tool.output, OUTPUT_MAX);
|
|
17
|
+
if (tool.status === "error") {
|
|
18
|
+
parts.push(`**Error**
|
|
19
|
+
\`\`\`
|
|
20
|
+
${truncated}
|
|
21
|
+
\`\`\``);
|
|
22
|
+
} else if (tool.name === "Bash") {
|
|
23
|
+
parts.push(renderBashOutput(truncated));
|
|
24
|
+
} else {
|
|
25
|
+
parts.push(`**Output**
|
|
26
|
+
\`\`\`
|
|
27
|
+
${truncated}
|
|
28
|
+
\`\`\``);
|
|
29
|
+
}
|
|
30
|
+
} else if (tool.status === "running") {
|
|
31
|
+
parts.push("_\u8FD0\u884C\u4E2D\u2026_");
|
|
32
|
+
}
|
|
33
|
+
const body = parts.join("\n\n");
|
|
34
|
+
if (body.length <= BODY_TOTAL_MAX) return body;
|
|
35
|
+
return `${body.slice(0, BODY_TOTAL_MAX)}\u2026
|
|
36
|
+
|
|
37
|
+
_\uFF08body \u5DF2\u622A\u65AD,\u5B8C\u6574\u5185\u5BB9\u67E5 \`/doctor\` \u6216\u65E5\u5FD7\uFF09_`;
|
|
38
|
+
}
|
|
39
|
+
function summarizeInput(name, input) {
|
|
40
|
+
if (!input || typeof input !== "object") return "";
|
|
41
|
+
const rec = input;
|
|
42
|
+
const pick = (key, max = HEADER_SUMMARY_MAX) => {
|
|
43
|
+
const v = rec[key];
|
|
44
|
+
if (typeof v !== "string") return "";
|
|
45
|
+
const oneLine = v.replace(/\s+/g, " ").trim();
|
|
46
|
+
return oneLine.length > max ? `${oneLine.slice(0, max)}\u2026` : oneLine;
|
|
47
|
+
};
|
|
48
|
+
switch (name) {
|
|
49
|
+
case "Bash":
|
|
50
|
+
return pick("command");
|
|
51
|
+
case "Read":
|
|
52
|
+
case "Edit":
|
|
53
|
+
case "Write":
|
|
54
|
+
case "NotebookEdit":
|
|
55
|
+
return shortenPath(pick("file_path"));
|
|
56
|
+
case "Grep": {
|
|
57
|
+
const pat = pick("pattern", 40);
|
|
58
|
+
const path = pick("path", 30);
|
|
59
|
+
return path ? `${pat} in ${shortenPath(path)}` : pat;
|
|
60
|
+
}
|
|
61
|
+
case "Glob":
|
|
62
|
+
return pick("pattern");
|
|
63
|
+
case "WebFetch":
|
|
64
|
+
return pick("url");
|
|
65
|
+
case "WebSearch":
|
|
66
|
+
return pick("query", 60);
|
|
67
|
+
case "Agent":
|
|
68
|
+
case "Task":
|
|
69
|
+
return pick("description") || pick("subagent_type");
|
|
70
|
+
default:
|
|
71
|
+
return pick("command") || pick("file_path") || pick("path") || pick("query");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function renderInput(tool) {
|
|
75
|
+
const input = tool.input;
|
|
76
|
+
if (!input || typeof input !== "object") return "";
|
|
77
|
+
const rec = input;
|
|
78
|
+
const str = (k) => typeof rec[k] === "string" ? rec[k] : "";
|
|
79
|
+
switch (tool.name) {
|
|
80
|
+
case "Bash": {
|
|
81
|
+
const cmd = str("command");
|
|
82
|
+
return cmd ? `**Command**
|
|
83
|
+
\`\`\`bash
|
|
84
|
+
${truncate(cmd, BODY_FIELD_MAX)}
|
|
85
|
+
\`\`\`` : "";
|
|
86
|
+
}
|
|
87
|
+
case "Read":
|
|
88
|
+
case "Edit":
|
|
89
|
+
case "Write":
|
|
90
|
+
case "NotebookEdit": {
|
|
91
|
+
const fp = str("file_path");
|
|
92
|
+
return fp ? `**File** \`${fp}\`` : "";
|
|
93
|
+
}
|
|
94
|
+
case "Grep": {
|
|
95
|
+
const lines = [];
|
|
96
|
+
if (str("pattern")) lines.push(`**Pattern** \`${str("pattern")}\``);
|
|
97
|
+
if (str("path")) lines.push(`**Path** \`${str("path")}\``);
|
|
98
|
+
return lines.join("\n");
|
|
99
|
+
}
|
|
100
|
+
case "WebFetch":
|
|
101
|
+
return str("url") ? `**URL** ${str("url")}` : "";
|
|
102
|
+
case "WebSearch":
|
|
103
|
+
return str("query") ? `**Query** \`${truncate(str("query"), BODY_FIELD_MAX)}\`` : "";
|
|
104
|
+
default:
|
|
105
|
+
return "";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function renderBashOutput(out) {
|
|
109
|
+
return `**Output**
|
|
110
|
+
\`\`\`
|
|
111
|
+
${out}
|
|
112
|
+
\`\`\``;
|
|
113
|
+
}
|
|
114
|
+
function shortenPath(p) {
|
|
115
|
+
if (!p) return p;
|
|
116
|
+
const home = process.env.HOME || "";
|
|
117
|
+
if (home && p.startsWith(home)) return `~${p.slice(home.length)}`;
|
|
118
|
+
return p;
|
|
119
|
+
}
|
|
120
|
+
function truncate(s, max) {
|
|
121
|
+
return s.length > max ? `${s.slice(0, max)}\u2026` : s;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/card/run-renderer.ts
|
|
125
|
+
var REASONING_MAX = 1500;
|
|
126
|
+
var COLLAPSE_TOOL_THRESHOLD = 3;
|
|
127
|
+
function renderCard(state) {
|
|
128
|
+
const elements = [];
|
|
129
|
+
if (state.terminal === "running" && state.reasoning.content) {
|
|
130
|
+
elements.push(reasoningPanel(state.reasoning.content, state.reasoning.active));
|
|
131
|
+
}
|
|
132
|
+
for (const group of groupBlocks(state.blocks)) {
|
|
133
|
+
if (group.kind === "text") {
|
|
134
|
+
if (group.content.trim()) {
|
|
135
|
+
elements.push(markdown(group.content));
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
elements.push(...renderToolGroup(group.tools, state.terminal !== "running"));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (state.terminal === "interrupted") {
|
|
142
|
+
elements.push(noteMd("_\u23F9 \u5DF2\u88AB\u4E2D\u65AD_"));
|
|
143
|
+
} else if (state.terminal === "idle_timeout") {
|
|
144
|
+
const mins = state.idleTimeoutMinutes ?? 0;
|
|
145
|
+
elements.push(noteMd(`_\u23F1 ${mins} \u5206\u949F\u65E0\u54CD\u5E94,\u5DF2\u81EA\u52A8\u7EC8\u6B62_`));
|
|
146
|
+
} else if (state.terminal === "error" && state.errorMsg) {
|
|
147
|
+
elements.push(noteMd(`\u26A0\uFE0F agent \u5931\u8D25\uFF1A${state.errorMsg}`));
|
|
148
|
+
} else if (state.terminal === "done" && elements.length === 0) {
|
|
149
|
+
elements.push(noteMd("_\uFF08\u672A\u8FD4\u56DE\u5185\u5BB9\uFF09_"));
|
|
150
|
+
}
|
|
151
|
+
if (state.terminal === "running") {
|
|
152
|
+
if (state.footer) elements.push(footerStatus(state.footer));
|
|
153
|
+
elements.push(stopButton());
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
schema: "2.0",
|
|
157
|
+
config: {
|
|
158
|
+
streaming_mode: state.terminal === "running",
|
|
159
|
+
summary: { content: summaryText(state) }
|
|
160
|
+
},
|
|
161
|
+
body: { elements }
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function* groupBlocks(blocks) {
|
|
165
|
+
let toolBuf = [];
|
|
166
|
+
for (const b of blocks) {
|
|
167
|
+
if (b.kind === "tool") {
|
|
168
|
+
toolBuf.push(b.tool);
|
|
169
|
+
} else {
|
|
170
|
+
if (toolBuf.length > 0) {
|
|
171
|
+
yield { kind: "tools", tools: toolBuf };
|
|
172
|
+
toolBuf = [];
|
|
173
|
+
}
|
|
174
|
+
yield { kind: "text", content: b.content };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (toolBuf.length > 0) yield { kind: "tools", tools: toolBuf };
|
|
178
|
+
}
|
|
179
|
+
function renderToolGroup(tools, finalized) {
|
|
180
|
+
if (tools.length === 0) return [];
|
|
181
|
+
if (tools.length < COLLAPSE_TOOL_THRESHOLD) {
|
|
182
|
+
return tools.map((t) => toolPanel(t, false));
|
|
183
|
+
}
|
|
184
|
+
if (finalized) {
|
|
185
|
+
return [collapsedToolSummary(tools, true)];
|
|
186
|
+
}
|
|
187
|
+
const prior = tools.slice(0, -1);
|
|
188
|
+
const latest = tools[tools.length - 1];
|
|
189
|
+
const out = [];
|
|
190
|
+
if (prior.length > 0) out.push(collapsedToolSummary(prior, false));
|
|
191
|
+
if (latest) out.push(toolPanel(latest, true));
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
function reasoningPanel(content, active) {
|
|
195
|
+
const title = active ? "\u{1F9E0} **\u601D\u8003\u4E2D**" : "\u{1F9E0} **\u601D\u8003\u5B8C\u6210\uFF0C\u70B9\u51FB\u67E5\u770B**";
|
|
196
|
+
return collapsiblePanel({
|
|
197
|
+
title,
|
|
198
|
+
expanded: active,
|
|
199
|
+
border: "grey",
|
|
200
|
+
body: truncate2(content, REASONING_MAX)
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
function toolPanel(tool, expanded) {
|
|
204
|
+
return collapsiblePanel({
|
|
205
|
+
title: toolHeaderText(tool),
|
|
206
|
+
expanded,
|
|
207
|
+
border: tool.status === "error" ? "red" : "grey",
|
|
208
|
+
body: toolBodyMd(tool) || "_\u65E0\u8F93\u51FA_"
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function collapsedToolSummary(tools, finalized) {
|
|
212
|
+
const suffix = finalized ? "\uFF08\u5DF2\u7ED3\u675F\uFF09" : "";
|
|
213
|
+
const title = `\u2615 **${tools.length} \u4E2A\u5DE5\u5177\u8C03\u7528${suffix}**`;
|
|
214
|
+
const headerList = tools.map((t) => `- ${toolHeaderText(t)}`).join("\n");
|
|
215
|
+
return {
|
|
216
|
+
tag: "collapsible_panel",
|
|
217
|
+
expanded: false,
|
|
218
|
+
header: panelHeader(title),
|
|
219
|
+
border: { color: "blue", corner_radius: "5px" },
|
|
220
|
+
vertical_spacing: "8px",
|
|
221
|
+
padding: "8px 8px 8px 8px",
|
|
222
|
+
elements: [{ tag: "markdown", content: headerList, text_size: "notation" }]
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function collapsiblePanel(opts) {
|
|
226
|
+
return {
|
|
227
|
+
tag: "collapsible_panel",
|
|
228
|
+
expanded: opts.expanded,
|
|
229
|
+
header: panelHeader(opts.title),
|
|
230
|
+
border: { color: opts.border, corner_radius: "5px" },
|
|
231
|
+
vertical_spacing: "8px",
|
|
232
|
+
padding: "8px 8px 8px 8px",
|
|
233
|
+
elements: [{ tag: "markdown", content: opts.body, text_size: "notation" }]
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function panelHeader(titleMd) {
|
|
237
|
+
return {
|
|
238
|
+
title: { tag: "markdown", content: titleMd },
|
|
239
|
+
vertical_align: "center",
|
|
240
|
+
icon: { tag: "standard_icon", token: "down-small-ccm_outlined", size: "16px 16px" },
|
|
241
|
+
icon_position: "follow_text",
|
|
242
|
+
icon_expanded_angle: -180
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function markdown(content) {
|
|
246
|
+
return { tag: "markdown", content };
|
|
247
|
+
}
|
|
248
|
+
function noteMd(content) {
|
|
249
|
+
return { tag: "markdown", content, text_size: "notation" };
|
|
250
|
+
}
|
|
251
|
+
function stopButton() {
|
|
252
|
+
return {
|
|
253
|
+
tag: "button",
|
|
254
|
+
text: { tag: "plain_text", content: "\u23F9 \u7EC8\u6B62" },
|
|
255
|
+
type: "danger",
|
|
256
|
+
behaviors: [{ type: "callback", value: { cmd: "stop" } }]
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function footerStatus(status) {
|
|
260
|
+
const text = status === "thinking" ? "\u{1F9E0} \u6B63\u5728\u601D\u8003" : status === "tool_running" ? "\u{1F9F0} \u6B63\u5728\u8C03\u7528\u5DE5\u5177" : "\u270D\uFE0F \u6B63\u5728\u8F93\u51FA";
|
|
261
|
+
return noteMd(text);
|
|
262
|
+
}
|
|
263
|
+
function summaryText(state) {
|
|
264
|
+
if (state.terminal === "interrupted") return "\u5DF2\u4E2D\u65AD";
|
|
265
|
+
if (state.terminal === "idle_timeout") return "\u5DF2\u8D85\u65F6";
|
|
266
|
+
if (state.terminal === "error") return "\u51FA\u9519";
|
|
267
|
+
if (state.terminal === "done") return "\u5DF2\u5B8C\u6210";
|
|
268
|
+
if (state.footer === "tool_running") return "\u6B63\u5728\u8C03\u7528\u5DE5\u5177";
|
|
269
|
+
if (state.footer === "streaming") return "\u6B63\u5728\u8F93\u51FA";
|
|
270
|
+
return "\u601D\u8003\u4E2D";
|
|
271
|
+
}
|
|
272
|
+
function truncate2(s, max) {
|
|
273
|
+
return s.length > max ? `${s.slice(0, max)}\u2026` : s;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/card/text-renderer.ts
|
|
277
|
+
function renderText(state) {
|
|
278
|
+
const parts = [];
|
|
279
|
+
for (const block of state.blocks) {
|
|
280
|
+
const piece = renderBlock(block);
|
|
281
|
+
if (piece) parts.push(piece);
|
|
282
|
+
}
|
|
283
|
+
if (state.terminal === "interrupted") {
|
|
284
|
+
parts.push("_\u23F9 \u5DF2\u88AB\u4E2D\u65AD_");
|
|
285
|
+
} else if (state.terminal === "idle_timeout") {
|
|
286
|
+
const mins = state.idleTimeoutMinutes ?? 0;
|
|
287
|
+
parts.push(`_\u23F1 ${mins} \u5206\u949F\u65E0\u54CD\u5E94,\u5DF2\u81EA\u52A8\u7EC8\u6B62_`);
|
|
288
|
+
} else if (state.terminal === "error" && state.errorMsg) {
|
|
289
|
+
parts.push(`\u26A0\uFE0F agent \u5931\u8D25:${state.errorMsg}`);
|
|
290
|
+
} else if (state.terminal === "running" && state.footer) {
|
|
291
|
+
parts.push(footerLine(state.footer));
|
|
292
|
+
}
|
|
293
|
+
return parts.join("\n\n");
|
|
294
|
+
}
|
|
295
|
+
function renderBlock(block) {
|
|
296
|
+
if (block.kind === "text") {
|
|
297
|
+
return block.content.trim();
|
|
298
|
+
}
|
|
299
|
+
return toolLine(block.tool);
|
|
300
|
+
}
|
|
301
|
+
function toolLine(tool) {
|
|
302
|
+
return `> ${toolHeaderText(tool)}`;
|
|
303
|
+
}
|
|
304
|
+
function footerLine(status) {
|
|
305
|
+
if (status === "thinking") return "_\u{1F9E0} \u6B63\u5728\u601D\u8003\u2026_";
|
|
306
|
+
if (status === "tool_running") return "_\u{1F9F0} \u6B63\u5728\u8C03\u7528\u5DE5\u5177\u2026_";
|
|
307
|
+
return "_\u270D\uFE0F \u6B63\u5728\u8F93\u51FA\u2026_";
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/agent/codex/adapter.ts
|
|
311
|
+
import { spawn } from "child_process";
|
|
312
|
+
import { createInterface } from "readline";
|
|
313
|
+
|
|
314
|
+
// src/core/logger.ts
|
|
315
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
316
|
+
import { createWriteStream, mkdirSync } from "fs";
|
|
317
|
+
import { open, readdir, rm, stat } from "fs/promises";
|
|
318
|
+
import { join as join2 } from "path";
|
|
319
|
+
|
|
320
|
+
// src/config/paths.ts
|
|
321
|
+
import { homedir } from "os";
|
|
322
|
+
import { join } from "path";
|
|
323
|
+
var appDir = join(homedir(), ".lark-channel");
|
|
324
|
+
var paths = {
|
|
325
|
+
appDir,
|
|
326
|
+
cacheDir: appDir,
|
|
327
|
+
configFile: join(appDir, "config.json"),
|
|
328
|
+
sessionsFile: join(appDir, "sessions.json"),
|
|
329
|
+
workspacesFile: join(appDir, "workspaces.json"),
|
|
330
|
+
processesFile: join(appDir, "processes.json"),
|
|
331
|
+
secretsFile: join(appDir, "secrets.enc"),
|
|
332
|
+
keystoreSaltFile: join(appDir, ".keystore.salt"),
|
|
333
|
+
/**
|
|
334
|
+
* Thin shell wrapper that lark-cli (and other openclaw-exec-protocol
|
|
335
|
+
* consumers) invoke to resolve secrets from the bridge's encrypted store.
|
|
336
|
+
* Written user-owned and non-symlinked so it passes lark-cli's
|
|
337
|
+
* AssertSecurePath audit on machines where `node` is a Homebrew/Volta
|
|
338
|
+
* symlink or root-owned (`/usr/bin/node`). Wrapper internals do the
|
|
339
|
+
* `node ... secrets get` invocation; lark-cli only audits the wrapper.
|
|
340
|
+
*/
|
|
341
|
+
secretsGetterScript: join(appDir, "secrets-getter"),
|
|
342
|
+
mediaDir: join(appDir, "media")
|
|
343
|
+
};
|
|
344
|
+
var legacyPaths = {
|
|
345
|
+
appDir: join(
|
|
346
|
+
process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"),
|
|
347
|
+
"lark-to-codex"
|
|
348
|
+
),
|
|
349
|
+
cacheDir: join(
|
|
350
|
+
process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache"),
|
|
351
|
+
"lark-to-codex"
|
|
352
|
+
)
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// src/core/logger.ts
|
|
356
|
+
var LOG_RETENTION_DAYS = Math.max(
|
|
357
|
+
1,
|
|
358
|
+
Number(process.env.LARK_CHANNEL_LOG_DAYS ?? 7) || 7
|
|
359
|
+
);
|
|
360
|
+
var STDOUT_INFO_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
361
|
+
"ws.connected",
|
|
362
|
+
"ws.reconnecting",
|
|
363
|
+
"ws.reconnected",
|
|
364
|
+
"intake.enter",
|
|
365
|
+
"card.final"
|
|
366
|
+
]);
|
|
367
|
+
var als = new AsyncLocalStorage();
|
|
368
|
+
var stream = null;
|
|
369
|
+
var currentDate = "";
|
|
370
|
+
function todayKey() {
|
|
371
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
372
|
+
}
|
|
373
|
+
function logsDir() {
|
|
374
|
+
return join2(paths.appDir, "logs");
|
|
375
|
+
}
|
|
376
|
+
function getStream() {
|
|
377
|
+
const today = todayKey();
|
|
378
|
+
if (stream && currentDate === today) return stream;
|
|
379
|
+
if (stream) {
|
|
380
|
+
try {
|
|
381
|
+
stream.end();
|
|
382
|
+
} catch {
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
mkdirSync(logsDir(), { recursive: true });
|
|
387
|
+
stream = createWriteStream(join2(logsDir(), `${today}.log`), { flags: "a" });
|
|
388
|
+
currentDate = today;
|
|
389
|
+
return stream;
|
|
390
|
+
} catch {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
var RESERVED_KEYS = /* @__PURE__ */ new Set([
|
|
395
|
+
"ts",
|
|
396
|
+
"level",
|
|
397
|
+
"phase",
|
|
398
|
+
"event",
|
|
399
|
+
"traceId",
|
|
400
|
+
"chatId",
|
|
401
|
+
"msgId"
|
|
402
|
+
]);
|
|
403
|
+
function emit(level, phase, event, fields = {}) {
|
|
404
|
+
const ctx = als.getStore() ?? {};
|
|
405
|
+
const entry = {
|
|
406
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
407
|
+
level,
|
|
408
|
+
phase,
|
|
409
|
+
event,
|
|
410
|
+
...ctx
|
|
411
|
+
};
|
|
412
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
413
|
+
if (RESERVED_KEYS.has(k)) {
|
|
414
|
+
entry[`_${k}`] = v;
|
|
415
|
+
} else {
|
|
416
|
+
entry[k] = v;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const s = getStream();
|
|
420
|
+
if (s) {
|
|
421
|
+
try {
|
|
422
|
+
s.write(`${JSON.stringify(entry)}
|
|
423
|
+
`);
|
|
424
|
+
} catch {
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
const showOnStdout = level !== "info" || STDOUT_INFO_ALLOWLIST.has(`${phase}.${event}`);
|
|
428
|
+
if (!showOnStdout) return;
|
|
429
|
+
const fn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
|
|
430
|
+
fn(formatStdout(level, phase, event, ctx, fields));
|
|
431
|
+
}
|
|
432
|
+
function formatStdout(level, phase, event, ctx, fields) {
|
|
433
|
+
if (phase === "ws") {
|
|
434
|
+
if (event === "connected") {
|
|
435
|
+
const bot = fields.bot ?? "-";
|
|
436
|
+
const appId = fields.appId ? ` (${fields.appId})` : "";
|
|
437
|
+
const agent = fields.agent ?? "-";
|
|
438
|
+
const proc = fields.procId ? ` \u8FDB\u7A0B: ${fields.procId}` : "";
|
|
439
|
+
return `\u2713 \u5DF2\u8FDE\u63A5 bot: ${bot}${appId} agent: ${agent}${proc}`;
|
|
440
|
+
}
|
|
441
|
+
if (event === "reconnecting") return "\u21BB \u6B63\u5728\u91CD\u8FDE\u2026";
|
|
442
|
+
if (event === "reconnected") return "\u2713 \u5DF2\u91CD\u8FDE";
|
|
443
|
+
if (event === "fail") return `\u2717 WS \u9519\u8BEF: ${fields.err ?? ""}`;
|
|
444
|
+
}
|
|
445
|
+
if (phase === "intake" && event === "enter") {
|
|
446
|
+
const c = ctx.chatId ? ctx.chatId.slice(-6) : "-";
|
|
447
|
+
const sender = fields.sender ?? "-";
|
|
448
|
+
const preview = fields.preview ?? "";
|
|
449
|
+
return `\u25B8 ${fields.chatType ?? "?"}/${c} ${sender}: ${preview}`;
|
|
450
|
+
}
|
|
451
|
+
if (phase === "card" && event === "final") {
|
|
452
|
+
const c = ctx.chatId ? ctx.chatId.slice(-6) : "-";
|
|
453
|
+
const t = fields.terminal;
|
|
454
|
+
const mark = t === "done" ? "\u2713" : t === "interrupted" ? "\u23F9" : "\u2717";
|
|
455
|
+
return ` ${mark} ${c} ${t}`;
|
|
456
|
+
}
|
|
457
|
+
const ctxBits = [];
|
|
458
|
+
if (ctx.traceId) ctxBits.push(`t=${ctx.traceId}`);
|
|
459
|
+
if (ctx.chatId) ctxBits.push(`c=${ctx.chatId.slice(-6)}`);
|
|
460
|
+
const ctxStr = ctxBits.length > 0 ? ` ${ctxBits.join(" ")}` : "";
|
|
461
|
+
const summary = formatFields(fields);
|
|
462
|
+
const tag = level === "error" ? "\u2717" : level === "warn" ? "\u26A0" : "\xB7";
|
|
463
|
+
return `${tag} [${phase}.${event}]${ctxStr}${summary ? ` ${summary}` : ""}`;
|
|
464
|
+
}
|
|
465
|
+
function formatFields(fields) {
|
|
466
|
+
const keys = Object.keys(fields);
|
|
467
|
+
if (keys.length === 0) return "";
|
|
468
|
+
const parts = [];
|
|
469
|
+
for (const k of keys) {
|
|
470
|
+
const v = fields[k];
|
|
471
|
+
if (v === void 0 || v === null) continue;
|
|
472
|
+
if (k === "stack") continue;
|
|
473
|
+
if (typeof v === "string") {
|
|
474
|
+
parts.push(`${k}=${v.length > 80 ? `${v.slice(0, 80)}\u2026` : v}`);
|
|
475
|
+
} else if (typeof v === "number" || typeof v === "boolean") {
|
|
476
|
+
parts.push(`${k}=${v}`);
|
|
477
|
+
} else {
|
|
478
|
+
try {
|
|
479
|
+
const s = JSON.stringify(v);
|
|
480
|
+
parts.push(`${k}=${s.length > 80 ? `${s.slice(0, 80)}\u2026` : s}`);
|
|
481
|
+
} catch {
|
|
482
|
+
parts.push(`${k}=?`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return parts.join(" ");
|
|
487
|
+
}
|
|
488
|
+
var log = {
|
|
489
|
+
info(phase, event, fields) {
|
|
490
|
+
emit("info", phase, event, fields);
|
|
491
|
+
},
|
|
492
|
+
warn(phase, event, fields) {
|
|
493
|
+
emit("warn", phase, event, fields);
|
|
494
|
+
},
|
|
495
|
+
fail(phase, err, fields) {
|
|
496
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
497
|
+
const stack = err instanceof Error ? err.stack : void 0;
|
|
498
|
+
const apiData = err?.response?.data;
|
|
499
|
+
const apiStatus = err?.response?.status;
|
|
500
|
+
emit("error", phase, "fail", {
|
|
501
|
+
...fields,
|
|
502
|
+
err: message,
|
|
503
|
+
apiStatus,
|
|
504
|
+
apiData,
|
|
505
|
+
stack
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
// src/agent/codex/stream-json.ts
|
|
511
|
+
function* translateEvent(raw, context = {}) {
|
|
512
|
+
if (!raw || typeof raw !== "object") return;
|
|
513
|
+
const evt = raw;
|
|
514
|
+
if (evt.type === "thread.started" && evt.thread_id) {
|
|
515
|
+
yield {
|
|
516
|
+
type: "system",
|
|
517
|
+
sessionId: evt.thread_id,
|
|
518
|
+
cwd: context.cwd,
|
|
519
|
+
model: context.model
|
|
520
|
+
};
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (evt.type === "item.started" && evt.item?.type === "command_execution") {
|
|
524
|
+
yield {
|
|
525
|
+
type: "tool_use",
|
|
526
|
+
id: evt.item.id ?? `cmd-${Date.now()}`,
|
|
527
|
+
name: "shell",
|
|
528
|
+
input: evt.item.command ?? {}
|
|
529
|
+
};
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (evt.type === "item.completed" && evt.item?.type === "command_execution") {
|
|
533
|
+
yield {
|
|
534
|
+
type: "tool_result",
|
|
535
|
+
id: evt.item.id ?? `cmd-${Date.now()}`,
|
|
536
|
+
output: evt.item.aggregated_output ?? "",
|
|
537
|
+
isError: typeof evt.item.exit_code === "number" && evt.item.exit_code !== 0
|
|
538
|
+
};
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (evt.type === "item.completed" && evt.item?.type === "agent_message") {
|
|
542
|
+
if (evt.item.text) yield { type: "text", delta: evt.item.text };
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
if (evt.type === "turn.completed") {
|
|
546
|
+
if (evt.usage) {
|
|
547
|
+
yield {
|
|
548
|
+
type: "usage",
|
|
549
|
+
inputTokens: evt.usage.input_tokens,
|
|
550
|
+
outputTokens: evt.usage.output_tokens
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
yield { type: "done" };
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
if (evt.type === "error") {
|
|
557
|
+
const message = typeof evt.error === "string" ? evt.error : evt.error?.message ?? evt.message ?? "unknown error";
|
|
558
|
+
yield { type: "error", message };
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// src/agent/codex/adapter.ts
|
|
563
|
+
var BRIDGE_SYSTEM_PROMPT = `# lark-to-codex runtime contract
|
|
564
|
+
|
|
565
|
+
You are Codex running inside lark-to-codex: a Feishu/Lark chat bridge to the local \`codex\` CLI.
|
|
566
|
+
|
|
567
|
+
## bridge_context
|
|
568
|
+
|
|
569
|
+
Every user message starts with a \`<bridge_context>\` block:
|
|
570
|
+
|
|
571
|
+
\`\`\`
|
|
572
|
+
<bridge_context>
|
|
573
|
+
chat_id: oc_xxx
|
|
574
|
+
chat_type: p2p
|
|
575
|
+
sender_id: ou_xxx
|
|
576
|
+
sender_name: ...
|
|
577
|
+
</bridge_context>
|
|
578
|
+
\`\`\`
|
|
579
|
+
|
|
580
|
+
This metadata is injected by the bridge and is not visible to the user. Use it for routing and tool calls, but do not quote or render the XML block in replies.
|
|
581
|
+
|
|
582
|
+
## quoted_message
|
|
583
|
+
|
|
584
|
+
When the user replies to a previous message, the bridge injects one or more \`<quoted_message>\` blocks after \`<bridge_context>\`. Treat quoted content as the object the user is pointing at; answer the actual request that follows it.
|
|
585
|
+
|
|
586
|
+
## interactive_card
|
|
587
|
+
|
|
588
|
+
When the user sends or quotes an interactive card, the bridge injects the real card JSON inside \`<interactive_card>\`. Parse the JSON to understand the card structure. Do not echo the XML wrapper.
|
|
589
|
+
|
|
590
|
+
## Sending Feishu/Lark cards
|
|
591
|
+
|
|
592
|
+
When you need to send a card yourself, use \`lark-cli\` and the current \`bridge_context.chat_id\`:
|
|
593
|
+
|
|
594
|
+
\`\`\`sh
|
|
595
|
+
lark-cli im send-card --chat-id <chat_id> --card '<json>'
|
|
596
|
+
\`\`\`
|
|
597
|
+
|
|
598
|
+
Use CardKit 2.0 cards (\`schema: "2.0"\`). If a button or form should call back into this same Codex session, include this marker in the callback value:
|
|
599
|
+
|
|
600
|
+
\`\`\`json
|
|
601
|
+
{ "__codex_cb": true, "choice": "a" }
|
|
602
|
+
\`\`\`
|
|
603
|
+
|
|
604
|
+
The bridge strips \`__codex_cb\` and sends the remaining payload back as a \`[card-click] {...}\` user message. Do not include the marker on display-only cards.
|
|
605
|
+
|
|
606
|
+
## Feishu/Lark OAuth via lark-cli
|
|
607
|
+
|
|
608
|
+
Only start \`lark-cli auth login\` from a p2p chat. In group or topic chats, tell the user to DM the bot first, because the device-flow URL can be claimed by the wrong person.
|
|
609
|
+
|
|
610
|
+
Use the two-step flow when possible:
|
|
611
|
+
|
|
612
|
+
1. Run \`lark-cli auth login --no-wait --json [--recommend | --domain ... | --scope ...]\`.
|
|
613
|
+
2. Send the returned \`verification_url\` to the user exactly, preferably in a code block.
|
|
614
|
+
3. In the same Codex turn, run \`lark-cli auth login --device-code <code>\` in the foreground and wait for completion.
|
|
615
|
+
|
|
616
|
+
Do not background the auth wait. The bridge queues new messages while Codex is blocked in the foreground tool call.`;
|
|
617
|
+
var CodexAdapter = class {
|
|
618
|
+
id = "codex";
|
|
619
|
+
displayName = "Codex";
|
|
620
|
+
binary;
|
|
621
|
+
constructor(opts = {}) {
|
|
622
|
+
this.binary = opts.binary ?? "codex";
|
|
623
|
+
}
|
|
624
|
+
async isAvailable() {
|
|
625
|
+
return new Promise((resolve) => {
|
|
626
|
+
const child = spawn(this.binary, ["--version"], { stdio: "ignore" });
|
|
627
|
+
child.on("error", () => resolve(false));
|
|
628
|
+
child.on("exit", (code) => resolve(code === 0));
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
run(opts) {
|
|
632
|
+
const args = buildArgs(opts);
|
|
633
|
+
const child = spawn(this.binary, args, {
|
|
634
|
+
cwd: opts.cwd,
|
|
635
|
+
env: { ...process.env, FEISHU_CODEX_BRIDGE: "1", LARK_CHANNEL: "1" },
|
|
636
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
637
|
+
});
|
|
638
|
+
log.info("agent", "spawn", {
|
|
639
|
+
pid: child.pid ?? null,
|
|
640
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
641
|
+
hasSession: Boolean(opts.sessionId),
|
|
642
|
+
promptChars: opts.prompt.length,
|
|
643
|
+
model: opts.model
|
|
644
|
+
});
|
|
645
|
+
const stderrChunks = [];
|
|
646
|
+
let stderrBuffer = "";
|
|
647
|
+
child.stderr.on("data", (chunk) => {
|
|
648
|
+
stderrChunks.push(chunk);
|
|
649
|
+
stderrBuffer += chunk.toString("utf8");
|
|
650
|
+
let nl = stderrBuffer.indexOf("\n");
|
|
651
|
+
while (nl !== -1) {
|
|
652
|
+
const line = stderrBuffer.slice(0, nl);
|
|
653
|
+
stderrBuffer = stderrBuffer.slice(nl + 1);
|
|
654
|
+
if (line.trim()) log.warn("agent", "stderr", { line });
|
|
655
|
+
nl = stderrBuffer.indexOf("\n");
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
let runtimeError = null;
|
|
659
|
+
child.on("error", (err) => {
|
|
660
|
+
runtimeError = err;
|
|
661
|
+
});
|
|
662
|
+
child.on("exit", (code, signal) => {
|
|
663
|
+
log.info("agent", "exit", { pid: child.pid ?? null, code, signal });
|
|
664
|
+
});
|
|
665
|
+
const stopGraceMs = opts.stopGraceMs ?? 5e3;
|
|
666
|
+
return {
|
|
667
|
+
events: createEventStream(child, opts.cwd, opts.model, stderrChunks, () => runtimeError),
|
|
668
|
+
async stop() {
|
|
669
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
670
|
+
log.info("agent", "stop-sigterm", { pid: child.pid ?? null, graceMs: stopGraceMs });
|
|
671
|
+
child.kill("SIGTERM");
|
|
672
|
+
await new Promise((resolve) => {
|
|
673
|
+
const timer = setTimeout(() => {
|
|
674
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
675
|
+
log.warn("agent", "stop-sigkill", {
|
|
676
|
+
pid: child.pid ?? null,
|
|
677
|
+
graceMs: stopGraceMs,
|
|
678
|
+
reason: "grace-period-expired"
|
|
679
|
+
});
|
|
680
|
+
child.kill("SIGKILL");
|
|
681
|
+
}
|
|
682
|
+
resolve();
|
|
683
|
+
}, stopGraceMs);
|
|
684
|
+
child.once("exit", () => {
|
|
685
|
+
clearTimeout(timer);
|
|
686
|
+
resolve();
|
|
687
|
+
});
|
|
688
|
+
});
|
|
689
|
+
},
|
|
690
|
+
waitForExit(timeoutMs) {
|
|
691
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
692
|
+
return Promise.resolve(true);
|
|
693
|
+
}
|
|
694
|
+
return new Promise((resolve) => {
|
|
695
|
+
const onExit = () => {
|
|
696
|
+
clearTimeout(timer);
|
|
697
|
+
resolve(true);
|
|
698
|
+
};
|
|
699
|
+
const timer = setTimeout(() => {
|
|
700
|
+
child.removeListener("exit", onExit);
|
|
701
|
+
resolve(false);
|
|
702
|
+
}, timeoutMs);
|
|
703
|
+
child.once("exit", onExit);
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
function buildArgs(opts) {
|
|
710
|
+
const execOnly = [
|
|
711
|
+
"-C",
|
|
712
|
+
opts.cwd ?? process.cwd()
|
|
713
|
+
];
|
|
714
|
+
const common = [
|
|
715
|
+
"--json",
|
|
716
|
+
"--skip-git-repo-check",
|
|
717
|
+
"--dangerously-bypass-approvals-and-sandbox",
|
|
718
|
+
"-c",
|
|
719
|
+
`experimental_instructions=${JSON.stringify(BRIDGE_SYSTEM_PROMPT)}`
|
|
720
|
+
];
|
|
721
|
+
if (opts.model) common.push("--model", opts.model);
|
|
722
|
+
if (opts.sessionId) {
|
|
723
|
+
return ["exec", "resume", ...common, opts.sessionId, opts.prompt];
|
|
724
|
+
}
|
|
725
|
+
return ["exec", ...common, ...execOnly, opts.prompt];
|
|
726
|
+
}
|
|
727
|
+
async function* createEventStream(child, cwd, model, stderrChunks, getError) {
|
|
728
|
+
if (!child.pid) {
|
|
729
|
+
const err = getError();
|
|
730
|
+
yield {
|
|
731
|
+
type: "error",
|
|
732
|
+
message: err ? `failed to spawn codex: ${err.message}` : "spawn returned no pid"
|
|
733
|
+
};
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const rl = createInterface({ input: child.stdout, crlfDelay: Infinity });
|
|
737
|
+
try {
|
|
738
|
+
for await (const line of rl) {
|
|
739
|
+
const trimmed = line.trim();
|
|
740
|
+
if (!trimmed || !trimmed.startsWith("{")) continue;
|
|
741
|
+
let parsed;
|
|
742
|
+
try {
|
|
743
|
+
parsed = JSON.parse(trimmed);
|
|
744
|
+
} catch {
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
yield* translateEvent(parsed, { cwd, model });
|
|
748
|
+
}
|
|
749
|
+
} finally {
|
|
750
|
+
rl.close();
|
|
751
|
+
}
|
|
752
|
+
const exitCode = await new Promise((resolve) => {
|
|
753
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
754
|
+
resolve(child.exitCode);
|
|
755
|
+
} else {
|
|
756
|
+
child.once("exit", (code) => resolve(code));
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
const runtimeError = getError();
|
|
760
|
+
if (exitCode !== 0 && exitCode !== null) {
|
|
761
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
762
|
+
const detail = stderr ? `: ${stderr.slice(0, 500)}` : "";
|
|
763
|
+
yield { type: "error", message: `codex exited with code ${exitCode}${detail}` };
|
|
764
|
+
} else if (runtimeError) {
|
|
765
|
+
yield { type: "error", message: `codex runtime error: ${runtimeError.message}` };
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/card/run-state.ts
|
|
770
|
+
var initialState = {
|
|
771
|
+
blocks: [],
|
|
772
|
+
reasoning: { content: "", active: false },
|
|
773
|
+
footer: "thinking",
|
|
774
|
+
terminal: "running"
|
|
775
|
+
};
|
|
776
|
+
function closeStreamingText(blocks) {
|
|
777
|
+
return blocks.map(
|
|
778
|
+
(b) => b.kind === "text" && b.streaming ? { ...b, streaming: false } : b
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
function reduce(state, evt) {
|
|
782
|
+
switch (evt.type) {
|
|
783
|
+
case "text": {
|
|
784
|
+
const last = state.blocks[state.blocks.length - 1];
|
|
785
|
+
if (last && last.kind === "text" && last.streaming) {
|
|
786
|
+
const next = { ...last, content: last.content + evt.delta };
|
|
787
|
+
return {
|
|
788
|
+
...state,
|
|
789
|
+
blocks: [...state.blocks.slice(0, -1), next],
|
|
790
|
+
reasoning: { ...state.reasoning, active: false },
|
|
791
|
+
footer: "streaming"
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
return {
|
|
795
|
+
...state,
|
|
796
|
+
blocks: [...state.blocks, { kind: "text", content: evt.delta, streaming: true }],
|
|
797
|
+
reasoning: { ...state.reasoning, active: false },
|
|
798
|
+
footer: "streaming"
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
case "thinking": {
|
|
802
|
+
return {
|
|
803
|
+
...state,
|
|
804
|
+
reasoning: { content: state.reasoning.content + evt.delta, active: true },
|
|
805
|
+
footer: "thinking"
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
case "tool_use": {
|
|
809
|
+
const tool = {
|
|
810
|
+
id: evt.id,
|
|
811
|
+
name: evt.name,
|
|
812
|
+
input: evt.input,
|
|
813
|
+
status: "running"
|
|
814
|
+
};
|
|
815
|
+
return {
|
|
816
|
+
...state,
|
|
817
|
+
blocks: [...closeStreamingText(state.blocks), { kind: "tool", tool }],
|
|
818
|
+
reasoning: { ...state.reasoning, active: false },
|
|
819
|
+
footer: "tool_running"
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
case "tool_result": {
|
|
823
|
+
const blocks = state.blocks.map((b) => {
|
|
824
|
+
if (b.kind !== "tool" || b.tool.id !== evt.id) return b;
|
|
825
|
+
return {
|
|
826
|
+
...b,
|
|
827
|
+
tool: {
|
|
828
|
+
...b.tool,
|
|
829
|
+
status: evt.isError ? "error" : "done",
|
|
830
|
+
output: evt.output
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
});
|
|
834
|
+
return { ...state, blocks };
|
|
835
|
+
}
|
|
836
|
+
case "error": {
|
|
837
|
+
return { ...state, terminal: "error", errorMsg: evt.message, footer: null };
|
|
838
|
+
}
|
|
839
|
+
case "done": {
|
|
840
|
+
return {
|
|
841
|
+
...state,
|
|
842
|
+
blocks: closeStreamingText(state.blocks),
|
|
843
|
+
reasoning: { ...state.reasoning, active: false },
|
|
844
|
+
terminal: "done",
|
|
845
|
+
footer: null
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
default:
|
|
849
|
+
return state;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
function markInterrupted(state) {
|
|
853
|
+
return {
|
|
854
|
+
...state,
|
|
855
|
+
blocks: closeStreamingText(state.blocks),
|
|
856
|
+
reasoning: { ...state.reasoning, active: false },
|
|
857
|
+
terminal: "interrupted",
|
|
858
|
+
footer: null
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
function finalizeIfRunning(state) {
|
|
862
|
+
if (state.terminal !== "running") return state;
|
|
863
|
+
return {
|
|
864
|
+
...state,
|
|
865
|
+
blocks: closeStreamingText(state.blocks),
|
|
866
|
+
reasoning: { ...state.reasoning, active: false },
|
|
867
|
+
terminal: "done",
|
|
868
|
+
footer: null
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
export {
|
|
872
|
+
CodexAdapter,
|
|
873
|
+
finalizeIfRunning,
|
|
874
|
+
initialState,
|
|
875
|
+
markInterrupted,
|
|
876
|
+
reduce,
|
|
877
|
+
renderCard,
|
|
878
|
+
renderText
|
|
879
|
+
};
|