tellrun 1.3.4 → 1.3.5
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/README.md +0 -4
- package/README.zh-CN.md +0 -4
- package/dist/agents/codex-interaction.js +81 -12
- package/dist/agents/codex-interaction.js.map +1 -1
- package/dist/agents/codex.js +151 -107
- package/dist/agents/codex.js.map +1 -1
- package/dist/core/relay.js +54 -4
- package/dist/core/relay.js.map +1 -1
- package/dist/im/telegram.js +18 -10
- package/dist/im/telegram.js.map +1 -1
- package/dist/storage/schema.js +12 -4
- package/dist/storage/schema.js.map +1 -1
- package/dist/storage/store.js +24 -9
- package/dist/storage/store.js.map +1 -1
- package/docs/architecture.md +12 -8
- package/docs/prd.md +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,12 +64,8 @@ At startup, Tellrun registers an English command menu. Use the menu button besid
|
|
|
64
64
|
|
|
65
65
|
Wait for the current task to finish before changing directories or starting a new conversation. After `/stop`, check the final status, then send `/resume` followed by your next task.
|
|
66
66
|
|
|
67
|
-
In `/status`, `Model` shows the current selection, or `Default` when using the Codex configuration. If the active task uses a different model, `Task model` is also shown.
|
|
68
|
-
|
|
69
67
|
`/permissions` offers the four presets from Codex CLI 0.165.0: Ask for approval (default), Approve for me (Codex automatic review), Full Access (no Codex sandbox or execution approvals), and Read Only (approval required for edits or network access). Full Access remains subject to operating system permissions.
|
|
70
68
|
|
|
71
|
-
Selections apply to subsequently submitted tasks; the current task and its follow-up messages retain their original mode. `/status` shows both modes when they differ. Selections survive service restarts and `/new`; changing the working directory resets the mode to its default. Menus expire after 10 minutes and are invalidated when reopened, on a new conversation, on a directory change, or after a restart. Codex validates the selected mode when starting a task. If it rejects the configuration or returns different settings, the task does not start.
|
|
72
|
-
|
|
73
69
|
## Terminal commands
|
|
74
70
|
|
|
75
71
|
Run these commands in the terminal on the computer or server running Tellrun.
|
package/README.zh-CN.md
CHANGED
|
@@ -64,12 +64,8 @@ tellrun setup
|
|
|
64
64
|
|
|
65
65
|
切换目录或新建会话前,需要等待当前任务结束。使用 `/stop` 后,先检查最终状态,再发送 `/resume` 和新的任务内容。
|
|
66
66
|
|
|
67
|
-
`/status` 的 `Model` 显示当前选择的模型,未选择时显示 `Default`,表示沿用 Codex 配置。执行中的任务使用不同模型时,额外显示 `Task model`。
|
|
68
|
-
|
|
69
67
|
`/permissions` 提供与 Codex CLI 0.165.0 对应的四种模式:Ask for approval(默认,额外权限由你审批)、Approve for me(由 Codex 自动审查)、Full Access(不使用 Codex 沙箱和执行审批)、Read Only(只读,修改或联网时请求审批)。Full Access 仍受系统用户权限约束。
|
|
70
68
|
|
|
71
|
-
选择在下一次提交任务时使用,当前任务及其补充消息保留原权限;`/status` 会在两者不同时分别显示。选择跨服务重启保留,`/new` 沿用选择,切换工作目录恢复默认。菜单有效期为 10 分钟,重新打开、新建会话、切换目录或重启后旧按钮失效。模式受本机 Codex 版本与托管策略限制,启动任务时由 Codex 校验;被拒绝或返回设置不符时不启动任务。
|
|
72
|
-
|
|
73
69
|
## 终端命令
|
|
74
70
|
|
|
75
71
|
在运行 Tellrun 的电脑或服务器上执行以下命令。
|
|
@@ -6,19 +6,51 @@ export const fileChangesSchema = z.array(z.object({
|
|
|
6
6
|
kind: z.object({ type: z.enum(["add", "delete", "update"]), move_path: z.string().nullish() }),
|
|
7
7
|
diff: z.string(),
|
|
8
8
|
}));
|
|
9
|
+
/** 只接受已知命令审批格式;规则内容必须来自当前请求,不能由按钮构造。 */
|
|
10
|
+
const commandDecisionSchema = z.union([
|
|
11
|
+
z.enum(["accept", "acceptForSession", "decline", "cancel"]),
|
|
12
|
+
z.strictObject({
|
|
13
|
+
acceptWithExecpolicyAmendment: z.strictObject({
|
|
14
|
+
execpolicy_amendment: z.array(z.string()).min(1),
|
|
15
|
+
}),
|
|
16
|
+
}),
|
|
17
|
+
]);
|
|
9
18
|
/** 转换审批与提问请求;格式错误抛出异常,中断原因交给调用方应答并清理任务。 */
|
|
10
19
|
export function parseCodexInteraction(method, value, cwd, fileChanges) {
|
|
11
20
|
const interaction = {
|
|
12
21
|
id: randomUUID(),
|
|
13
22
|
kind: "approval",
|
|
14
23
|
text: "",
|
|
15
|
-
expiresAt:
|
|
24
|
+
expiresAt: null,
|
|
16
25
|
blocking: true,
|
|
17
|
-
choices: [
|
|
26
|
+
choices: [],
|
|
18
27
|
questions: [],
|
|
19
28
|
};
|
|
20
|
-
|
|
29
|
+
const approvals = new Map();
|
|
21
30
|
let fileReview;
|
|
31
|
+
/** 展示选项与完整响应同步登记,核心层只传回选项标识。 */
|
|
32
|
+
function offer(id, label, result, approved) {
|
|
33
|
+
if (approvals.has(id))
|
|
34
|
+
return;
|
|
35
|
+
interaction.choices.push({ id, label });
|
|
36
|
+
approvals.set(id, { result, approved });
|
|
37
|
+
}
|
|
38
|
+
/** 原生取消直接回传 Codex,不转换为 Tellrun 的停止和暂停接收。 */
|
|
39
|
+
function offerDecision(decision, index) {
|
|
40
|
+
if (typeof decision === "string") {
|
|
41
|
+
const labels = {
|
|
42
|
+
accept: "Allow once",
|
|
43
|
+
acceptForSession: "Allow for session",
|
|
44
|
+
decline: "Decline",
|
|
45
|
+
cancel: "Cancel",
|
|
46
|
+
};
|
|
47
|
+
offer(decision, labels[decision], { decision }, decision.startsWith("accept"));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
interaction.text += `\nCommand rule ${index + 1}: ${JSON.stringify(decision.acceptWithExecpolicyAmendment.execpolicy_amendment)}`;
|
|
51
|
+
offer(`rule-${index}`, `Allow & remember rule ${index + 1}`, { decision }, true);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
22
54
|
if (method === "item/commandExecution/requestApproval") {
|
|
23
55
|
const params = z
|
|
24
56
|
.object({
|
|
@@ -30,11 +62,38 @@ export function parseCodexInteraction(method, value, cwd, fileChanges) {
|
|
|
30
62
|
networkApprovalContext: z.object({ host: z.string(), protocol: z.string() }).nullish(),
|
|
31
63
|
additionalPermissions: z.unknown().optional(),
|
|
32
64
|
availableDecisions: z.array(z.unknown()).nullish(),
|
|
65
|
+
proposedExecpolicyAmendment: z.array(z.string()).nullish(),
|
|
33
66
|
})
|
|
34
67
|
.parse(value);
|
|
35
68
|
interaction.text = `${params.kind === "writeStdin" ? "Terminal input" : "Command"}\nDirectory: ${params.cwd ?? cwd}\n${params.command ?? "Command unavailable."}${params.reason ? `\nReason: ${params.reason}` : ""}${params.networkApprovalContext ? `\nNetwork: ${params.networkApprovalContext.protocol}://${params.networkApprovalContext.host}` : ""}${params.additionalPermissions ? `\nPermissions: ${JSON.stringify(params.additionalPermissions, null, 2)}` : ""}`;
|
|
36
|
-
|
|
37
|
-
|
|
69
|
+
/** 显式选项具有优先权;旧服务未返回列表时使用协议的基本决定与所提规则。 */
|
|
70
|
+
const decisions = params.availableDecisions ?? [
|
|
71
|
+
"accept",
|
|
72
|
+
"acceptForSession",
|
|
73
|
+
...(params.proposedExecpolicyAmendment?.length
|
|
74
|
+
? [
|
|
75
|
+
{
|
|
76
|
+
acceptWithExecpolicyAmendment: {
|
|
77
|
+
execpolicy_amendment: params.proposedExecpolicyAmendment,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
]
|
|
81
|
+
: []),
|
|
82
|
+
"decline",
|
|
83
|
+
"cancel",
|
|
84
|
+
];
|
|
85
|
+
for (const [index, value] of decisions.entries()) {
|
|
86
|
+
const parsed = commandDecisionSchema.safeParse(value);
|
|
87
|
+
if (!parsed.success)
|
|
88
|
+
continue;
|
|
89
|
+
const decision = parsed.data;
|
|
90
|
+
if (decision !== "decline" &&
|
|
91
|
+
decision !== "cancel" &&
|
|
92
|
+
!params.command &&
|
|
93
|
+
!params.networkApprovalContext)
|
|
94
|
+
continue;
|
|
95
|
+
offerDecision(decision, index);
|
|
96
|
+
}
|
|
38
97
|
}
|
|
39
98
|
else if (method === "item/fileChange/requestApproval") {
|
|
40
99
|
const params = z
|
|
@@ -48,9 +107,12 @@ export function parseCodexInteraction(method, value, cwd, fileChanges) {
|
|
|
48
107
|
const changes = fileChanges.get(params.itemId);
|
|
49
108
|
if (changes?.length)
|
|
50
109
|
fileReview = { itemId: params.itemId, changes: JSON.stringify(changes) };
|
|
51
|
-
interaction.text = `File changes\nDirectory: ${cwd}${params.reason ? `\nReason: ${params.reason}` : ""}${params.grantRoot ? `\nWrite access: ${params.grantRoot}` : ""}\n${changes?.length ? changes.map((change) => `${change.kind.type}: ${change.path}${change.kind.move_path ? ` → ${change.kind.move_path}` : ""}\n${change.diff}`).join("\n\n") : "Details unavailable. Decline or
|
|
52
|
-
|
|
53
|
-
|
|
110
|
+
interaction.text = `File changes\nDirectory: ${cwd}${params.reason ? `\nReason: ${params.reason}` : ""}${params.grantRoot ? `\nWrite access: ${params.grantRoot}` : ""}\n${changes?.length ? changes.map((change) => `${change.kind.type}: ${change.path}${change.kind.move_path ? ` → ${change.kind.move_path}` : ""}\n${change.diff}`).join("\n\n") : "Details unavailable. Decline or cancel."}`;
|
|
111
|
+
for (const decision of ["accept", "acceptForSession", "decline", "cancel"]) {
|
|
112
|
+
if (!changes?.length && decision.startsWith("accept"))
|
|
113
|
+
continue;
|
|
114
|
+
offerDecision(decision, interaction.choices.length);
|
|
115
|
+
}
|
|
54
116
|
}
|
|
55
117
|
else if (method === "item/permissions/requestApproval") {
|
|
56
118
|
const params = z
|
|
@@ -71,8 +133,11 @@ export function parseCodexInteraction(method, value, cwd, fileChanges) {
|
|
|
71
133
|
}),
|
|
72
134
|
})
|
|
73
135
|
.parse(value);
|
|
74
|
-
permissions = Object.fromEntries(Object.entries(params.permissions).filter(([, value]) => value != null));
|
|
75
|
-
interaction.text = `Permissions
|
|
136
|
+
const permissions = Object.fromEntries(Object.entries(params.permissions).filter(([, value]) => value != null));
|
|
137
|
+
interaction.text = `Permissions\nDirectory: ${params.cwd}${params.reason ? `\nReason: ${params.reason}` : ""}\n${JSON.stringify(permissions, null, 2)}`;
|
|
138
|
+
offer("accept", "Allow this turn", { permissions, scope: "turn" }, true);
|
|
139
|
+
offer("acceptForSession", "Allow for session", { permissions, scope: "session" }, true);
|
|
140
|
+
offer("decline", "Decline", { permissions: {}, scope: "turn" }, false);
|
|
76
141
|
}
|
|
77
142
|
else if (method === "item/tool/requestUserInput") {
|
|
78
143
|
const params = z
|
|
@@ -103,7 +168,9 @@ export function parseCodexInteraction(method, value, cwd, fileChanges) {
|
|
|
103
168
|
interaction.kind = "question";
|
|
104
169
|
interaction.blocking = params.isBlocking;
|
|
105
170
|
interaction.choices = [];
|
|
106
|
-
|
|
171
|
+
/** 只有提问设置本地期限;审批持续等待用户决定。 */
|
|
172
|
+
interaction.expiresAt =
|
|
173
|
+
Date.now() + Math.min(10 * 60_000, params.autoResolutionMs ?? 10 * 60_000);
|
|
107
174
|
interaction.questions = params.questions.map((question) => ({
|
|
108
175
|
id: question.id,
|
|
109
176
|
header: question.header,
|
|
@@ -117,9 +184,11 @@ export function parseCodexInteraction(method, value, cwd, fileChanges) {
|
|
|
117
184
|
}
|
|
118
185
|
if (JSON.stringify(interaction).length > 64_000)
|
|
119
186
|
return { error: "Request too large. Handle locally, then /resume." };
|
|
187
|
+
if (interaction.kind === "approval" && !interaction.choices.length)
|
|
188
|
+
return { error: "No supported approval options. Handle locally, then /resume." };
|
|
120
189
|
return {
|
|
121
190
|
request: interaction,
|
|
122
|
-
|
|
191
|
+
approvals,
|
|
123
192
|
...(fileReview ? { fileReview } : {}),
|
|
124
193
|
};
|
|
125
194
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex-interaction.js","sourceRoot":"","sources":["../../src/agents/codex-interaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,uCAAuC;AACvC,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CACtC,CAAC,CAAC,MAAM,CAAC;IACP,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC;IAC9F,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;CACjB,CAAC,CACH,CAAC;AAEF,2CAA2C;AAC3C,MAAM,UAAU,qBAAqB,CACnC,MAAc,EACd,KAAc,EACd,GAAW,EACX,WAAmE;IAQnE,MAAM,WAAW,GAAqB;QACpC,EAAE,EAAE,UAAU,EAAE;QAChB,IAAI,EAAE,UAAU;QAChB,IAAI,EAAE,EAAE;QACR,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM
|
|
1
|
+
{"version":3,"file":"codex-interaction.js","sourceRoot":"","sources":["../../src/agents/codex-interaction.ts"],"names":[],"mappings":"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"}
|
package/dist/agents/codex.js
CHANGED
|
@@ -6,7 +6,7 @@ import { promisify } from "node:util";
|
|
|
6
6
|
import { JSONRPCErrorException } from "json-rpc-2.0";
|
|
7
7
|
import WebSocket from "ws";
|
|
8
8
|
import { z } from "zod";
|
|
9
|
-
import { fileChangesSchema, parseCodexInteraction } from "./codex-interaction.js";
|
|
9
|
+
import { fileChangesSchema, parseCodexInteraction, } from "./codex-interaction.js";
|
|
10
10
|
import { createCodexRpc } from "./codex-rpc.js";
|
|
11
11
|
/** 由 Codex 0.165.0 的 generate-ts 核对;只校验实际消费的协议字段。 */
|
|
12
12
|
const turnSchema = z.object({
|
|
@@ -101,61 +101,92 @@ export async function createCodexAgent(command = "codex") {
|
|
|
101
101
|
}
|
|
102
102
|
throw new Error("Codex daemon unavailable.");
|
|
103
103
|
}
|
|
104
|
+
/** 模型菜单与状态共用只读连接;按需读取目录配置,不恢复会话或发起轮次。 */
|
|
105
|
+
async function readModelData(cwd, signal, includeConfig = false) {
|
|
106
|
+
signal = AbortSignal.any([signal, AbortSignal.timeout(15_000)]);
|
|
107
|
+
const rpc = createCodexRpc(await connectServer(cwd, signal));
|
|
108
|
+
/** 取消仅关闭目录查询,不能向正在运行的轮次发送中断。 */
|
|
109
|
+
const abort = () => rpc.close("Codex model query cancelled.");
|
|
110
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
111
|
+
try {
|
|
112
|
+
signal.throwIfAborted();
|
|
113
|
+
await rpc.initialize();
|
|
114
|
+
const config = includeConfig
|
|
115
|
+
? z
|
|
116
|
+
.object({
|
|
117
|
+
config: z.object({
|
|
118
|
+
model: z.string().min(1).nullish(),
|
|
119
|
+
model_reasoning_effort: z.string().min(1).nullish(),
|
|
120
|
+
}),
|
|
121
|
+
})
|
|
122
|
+
.parse(await rpc.request("config/read", { cwd, includeLayers: false })).config
|
|
123
|
+
: undefined;
|
|
124
|
+
const models = [];
|
|
125
|
+
let defaultModel;
|
|
126
|
+
let cursor = null;
|
|
127
|
+
do {
|
|
128
|
+
signal.throwIfAborted();
|
|
129
|
+
const page = z
|
|
130
|
+
.object({
|
|
131
|
+
data: z.array(z.object({
|
|
132
|
+
model: z.string().min(1),
|
|
133
|
+
displayName: z.string(),
|
|
134
|
+
hidden: z.boolean(),
|
|
135
|
+
isDefault: z.boolean().optional(),
|
|
136
|
+
supportedReasoningEfforts: z.array(z.object({
|
|
137
|
+
reasoningEffort: z.string().min(1),
|
|
138
|
+
description: z.string(),
|
|
139
|
+
})),
|
|
140
|
+
defaultReasoningEffort: z.string().min(1),
|
|
141
|
+
})),
|
|
142
|
+
nextCursor: z.string().nullable(),
|
|
143
|
+
})
|
|
144
|
+
.parse(await rpc.request("model/list", { limit: 100, includeHidden: includeConfig, cursor }));
|
|
145
|
+
for (const model of page.data) {
|
|
146
|
+
if (model.isDefault)
|
|
147
|
+
defaultModel = model.model;
|
|
148
|
+
if ((includeConfig || !model.hidden) &&
|
|
149
|
+
!models.some((option) => option.id === model.model))
|
|
150
|
+
models.push({
|
|
151
|
+
id: model.model,
|
|
152
|
+
label: model.displayName || model.model,
|
|
153
|
+
reasoningEfforts: model.supportedReasoningEfforts.map((effort) => ({
|
|
154
|
+
id: effort.reasoningEffort,
|
|
155
|
+
label: effort.description,
|
|
156
|
+
})),
|
|
157
|
+
defaultReasoningEffort: model.defaultReasoningEffort,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
cursor = page.nextCursor;
|
|
161
|
+
} while (cursor);
|
|
162
|
+
return { models, defaultModel, config };
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
throw new Error("Codex model query failed. Check the local configuration and connection.");
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
signal.removeEventListener("abort", abort);
|
|
169
|
+
rpc.close();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
104
172
|
return {
|
|
105
173
|
name: "Codex",
|
|
106
174
|
version: stdout.trim().replace(/^codex-cli /, ""),
|
|
107
175
|
permissionModes: permissionModes.map(({ id, label }) => ({ id, label })),
|
|
108
|
-
/** 分页读取模型目录;不恢复会话、不发起轮次,也不影响任务连接。 */
|
|
109
176
|
async listModels(cwd, signal) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
.object({
|
|
124
|
-
data: z.array(z.object({
|
|
125
|
-
model: z.string().min(1),
|
|
126
|
-
displayName: z.string(),
|
|
127
|
-
hidden: z.boolean(),
|
|
128
|
-
supportedReasoningEfforts: z.array(z.object({
|
|
129
|
-
reasoningEffort: z.string().min(1),
|
|
130
|
-
description: z.string(),
|
|
131
|
-
})),
|
|
132
|
-
defaultReasoningEffort: z.string().min(1),
|
|
133
|
-
})),
|
|
134
|
-
nextCursor: z.string().nullable(),
|
|
135
|
-
})
|
|
136
|
-
.parse(await rpc.request("model/list", { limit: 100, includeHidden: false, cursor }));
|
|
137
|
-
for (const model of page.data)
|
|
138
|
-
if (!model.hidden && !models.some((option) => option.id === model.model))
|
|
139
|
-
models.push({
|
|
140
|
-
id: model.model,
|
|
141
|
-
label: model.displayName || model.model,
|
|
142
|
-
reasoningEfforts: model.supportedReasoningEfforts.map((effort) => ({
|
|
143
|
-
id: effort.reasoningEffort,
|
|
144
|
-
label: effort.description,
|
|
145
|
-
})),
|
|
146
|
-
defaultReasoningEffort: model.defaultReasoningEffort,
|
|
147
|
-
});
|
|
148
|
-
cursor = page.nextCursor;
|
|
149
|
-
} while (cursor);
|
|
150
|
-
return models;
|
|
151
|
-
}
|
|
152
|
-
catch {
|
|
153
|
-
throw new Error("Codex model query failed. Check the local configuration and connection.");
|
|
154
|
-
}
|
|
155
|
-
finally {
|
|
156
|
-
signal.removeEventListener("abort", abort);
|
|
157
|
-
rpc.close();
|
|
158
|
-
}
|
|
177
|
+
return (await readModelData(cwd, signal)).models;
|
|
178
|
+
},
|
|
179
|
+
async describeModel(cwd, selection, signal) {
|
|
180
|
+
/** 已选模型可能已从菜单隐藏,状态仍需查询完整目录中的展示名称。 */
|
|
181
|
+
const { models, defaultModel, config } = await readModelData(cwd, signal, true);
|
|
182
|
+
const id = selection.model ?? config?.model ?? defaultModel;
|
|
183
|
+
const model = models.find((option) => option.id === id);
|
|
184
|
+
const reasoningEffort = selection.reasoningEffort ??
|
|
185
|
+
config?.model_reasoning_effort ??
|
|
186
|
+
model?.defaultReasoningEffort;
|
|
187
|
+
if (!id || !reasoningEffort)
|
|
188
|
+
throw new Error("Codex model settings unavailable.");
|
|
189
|
+
return { id, label: model?.label ?? id, reasoningEffort };
|
|
159
190
|
},
|
|
160
191
|
async run(input) {
|
|
161
192
|
if (active)
|
|
@@ -183,6 +214,7 @@ export async function createCodexAgent(command = "codex") {
|
|
|
183
214
|
};
|
|
184
215
|
}
|
|
185
216
|
const completed = Promise.withResolvers();
|
|
217
|
+
/** 按首次出现顺序保存全部回复与文件变更标题,完整事件覆盖同一条增量。 */
|
|
186
218
|
const messages = new Map();
|
|
187
219
|
/** 应用随机 ID 隔离每次连接;已见 RPC 编号保留到任务结束,重复请求不生成新按钮。 */
|
|
188
220
|
const interactions = new Map();
|
|
@@ -195,6 +227,8 @@ export async function createCodexAgent(command = "codex") {
|
|
|
195
227
|
let runId;
|
|
196
228
|
let dispatched = Boolean(input.runId);
|
|
197
229
|
let settled = false;
|
|
230
|
+
/** 原生 Cancel 的已确认中断视为用户取消,不能误报异常并暂停聊天。 */
|
|
231
|
+
let cancelledByApproval = false;
|
|
198
232
|
let stopTimer;
|
|
199
233
|
/** 只接受第一个终态,避免退出事件覆盖已经确认的执行结果。 */
|
|
200
234
|
function finish(result) {
|
|
@@ -204,15 +238,25 @@ export async function createCodexAgent(command = "codex") {
|
|
|
204
238
|
for (const interaction of interactions.values())
|
|
205
239
|
clearTimeout(interaction.timer);
|
|
206
240
|
interactions.clear();
|
|
207
|
-
completed.resolve(result
|
|
241
|
+
completed.resolve(result
|
|
242
|
+
? {
|
|
243
|
+
...result,
|
|
244
|
+
text: [formatOutput(), result.text].filter(Boolean).join("\n\n") ||
|
|
245
|
+
"Codex finished with no text output.",
|
|
246
|
+
}
|
|
247
|
+
: undefined);
|
|
208
248
|
}
|
|
209
|
-
/**
|
|
210
|
-
function
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
249
|
+
/** 预览和结果共用完整内容;保留过程说明、最终回复及文件差异,由 IM 分段发送。 */
|
|
250
|
+
function formatOutput() {
|
|
251
|
+
return [...messages]
|
|
252
|
+
.map(([id, text]) => [
|
|
253
|
+
text,
|
|
254
|
+
...(fileChanges.get(id) ?? []).map((change) => `${change.kind.type}: ${change.path}${change.kind.move_path ? ` → ${change.kind.move_path}` : ""}\n${change.diff}`),
|
|
255
|
+
]
|
|
256
|
+
.filter(Boolean)
|
|
257
|
+
.join("\n\n"))
|
|
258
|
+
.filter(Boolean)
|
|
259
|
+
.join("\n\n");
|
|
216
260
|
}
|
|
217
261
|
/** 通信故障不等于执行失败;保留不确定状态并结束当前连接。 */
|
|
218
262
|
function fail(text) {
|
|
@@ -321,25 +365,27 @@ export async function createCodexAgent(command = "codex") {
|
|
|
321
365
|
interactions.set(interaction.id, {
|
|
322
366
|
requestId: message.id,
|
|
323
367
|
...parsed,
|
|
324
|
-
timer:
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
interactions.delete(interaction.id);
|
|
333
|
-
/** 非阻塞问题过期只返回空答案,不替用户选择,也不中断正在执行的任务。 */
|
|
334
|
-
try {
|
|
335
|
-
input.onInteractionClosed(interaction.id, "Question expired. Task continues.");
|
|
336
|
-
rpc.write({ id: pending.requestId, result: { answers: {} } });
|
|
368
|
+
timer: interaction.expiresAt === null
|
|
369
|
+
? undefined
|
|
370
|
+
: setTimeout(() => {
|
|
371
|
+
const pending = interactions.get(interaction.id);
|
|
372
|
+
if (!pending)
|
|
373
|
+
return;
|
|
374
|
+
if (interaction.blocking) {
|
|
375
|
+
fail("Request timed out. Check /last, then /resume.");
|
|
337
376
|
}
|
|
338
|
-
|
|
339
|
-
|
|
377
|
+
else {
|
|
378
|
+
interactions.delete(interaction.id);
|
|
379
|
+
/** 非阻塞问题过期只返回空答案,不替用户选择,也不中断正在执行的任务。 */
|
|
380
|
+
try {
|
|
381
|
+
input.onInteractionClosed(interaction.id, "Question expired. Task continues.");
|
|
382
|
+
rpc.write({ id: pending.requestId, result: { answers: {} } });
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
fail("Cannot save expired question state. Task interrupted.");
|
|
386
|
+
}
|
|
340
387
|
}
|
|
341
|
-
}
|
|
342
|
-
}, Math.max(0, interaction.expiresAt - Date.now())),
|
|
388
|
+
}, Math.max(0, interaction.expiresAt - Date.now())),
|
|
343
389
|
});
|
|
344
390
|
input.onInteraction(interaction);
|
|
345
391
|
}
|
|
@@ -367,8 +413,12 @@ export async function createCodexAgent(command = "codex") {
|
|
|
367
413
|
changes: fileChangesSchema,
|
|
368
414
|
})
|
|
369
415
|
.parse(message.params);
|
|
370
|
-
if (event.threadId === sessionId && event.turnId === runId)
|
|
416
|
+
if (event.threadId === sessionId && event.turnId === runId) {
|
|
371
417
|
fileChanges.set(event.itemId, event.changes);
|
|
418
|
+
if (!messages.has(event.itemId))
|
|
419
|
+
messages.set(event.itemId, "File changes");
|
|
420
|
+
input.onText(formatOutput());
|
|
421
|
+
}
|
|
372
422
|
}
|
|
373
423
|
else if (message.method === "item/agentMessage/delta") {
|
|
374
424
|
const event = z
|
|
@@ -381,10 +431,8 @@ export async function createCodexAgent(command = "codex") {
|
|
|
381
431
|
.parse(message.params);
|
|
382
432
|
if (event.threadId !== sessionId || !recordRun(event.turnId))
|
|
383
433
|
return;
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
messages.set(event.itemId, item);
|
|
387
|
-
publishText(item.text);
|
|
434
|
+
messages.set(event.itemId, (messages.get(event.itemId) ?? "") + event.delta);
|
|
435
|
+
input.onText(formatOutput());
|
|
388
436
|
}
|
|
389
437
|
else if (message.method === "item/started" || message.method === "item/completed") {
|
|
390
438
|
const event = z
|
|
@@ -395,18 +443,22 @@ export async function createCodexAgent(command = "codex") {
|
|
|
395
443
|
type: z.string(),
|
|
396
444
|
id: z.string(),
|
|
397
445
|
text: z.string().optional(),
|
|
398
|
-
|
|
446
|
+
status: z.string().optional(),
|
|
399
447
|
changes: fileChangesSchema.optional(),
|
|
400
448
|
}),
|
|
401
449
|
})
|
|
402
450
|
.parse(message.params);
|
|
403
451
|
if (event.threadId !== sessionId || !recordRun(event.turnId))
|
|
404
452
|
return;
|
|
405
|
-
if (event.item.type === "fileChange"
|
|
406
|
-
|
|
453
|
+
if (event.item.type === "fileChange") {
|
|
454
|
+
if (event.item.changes)
|
|
455
|
+
fileChanges.set(event.item.id, event.item.changes);
|
|
456
|
+
messages.set(event.item.id, `File changes${event.item.status ? ` (${event.item.status})` : ""}`);
|
|
457
|
+
input.onText(formatOutput());
|
|
458
|
+
}
|
|
407
459
|
if (event.item.type === "agentMessage" && event.item.text !== undefined) {
|
|
408
|
-
messages.set(event.item.id,
|
|
409
|
-
|
|
460
|
+
messages.set(event.item.id, event.item.text);
|
|
461
|
+
input.onText(formatOutput());
|
|
410
462
|
}
|
|
411
463
|
}
|
|
412
464
|
else if (message.method === "turn/started" || message.method === "turn/completed") {
|
|
@@ -416,23 +468,19 @@ export async function createCodexAgent(command = "codex") {
|
|
|
416
468
|
if (event.threadId !== sessionId || !recordRun(event.turn.id))
|
|
417
469
|
return;
|
|
418
470
|
if (message.method === "turn/completed") {
|
|
419
|
-
const final = [...messages.values()].filter((item) => item.phase === "final_answer");
|
|
420
471
|
finish({
|
|
421
472
|
status: event.turn.status === "completed"
|
|
422
473
|
? "completed"
|
|
423
474
|
: event.turn.status === "failed"
|
|
424
475
|
? "failed"
|
|
425
|
-
: controller.signal.aborted
|
|
476
|
+
: controller.signal.aborted || cancelledByApproval
|
|
426
477
|
? "cancelled"
|
|
427
478
|
: "interrupted",
|
|
428
|
-
text:
|
|
429
|
-
|
|
430
|
-
.
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
: event.turn.status === "failed"
|
|
434
|
-
? "Codex task failed. Check sign-in, model access, and network."
|
|
435
|
-
: "Task interrupted. File changes were not reverted."),
|
|
479
|
+
text: event.turn.status === "completed"
|
|
480
|
+
? ""
|
|
481
|
+
: event.turn.status === "failed"
|
|
482
|
+
? "Codex task failed. Check sign-in, model access, and network."
|
|
483
|
+
: "Task interrupted. File changes were not reverted.",
|
|
436
484
|
});
|
|
437
485
|
}
|
|
438
486
|
}
|
|
@@ -460,12 +508,13 @@ export async function createCodexAgent(command = "codex") {
|
|
|
460
508
|
if (!interaction ||
|
|
461
509
|
settled ||
|
|
462
510
|
controller.signal.aborted ||
|
|
463
|
-
interaction.request.expiresAt <= Date.now())
|
|
511
|
+
(interaction.request.expiresAt !== null && interaction.request.expiresAt <= Date.now()))
|
|
464
512
|
return false;
|
|
513
|
+
const approval = "decision" in answer ? interaction.approvals.get(answer.decision) : undefined;
|
|
465
514
|
if (interaction.request.kind === "approval") {
|
|
466
|
-
if (!("decision" in answer) || !
|
|
515
|
+
if (!("decision" in answer) || !approval)
|
|
467
516
|
return false;
|
|
468
|
-
if (
|
|
517
|
+
if (approval.approved &&
|
|
469
518
|
interaction.fileReview &&
|
|
470
519
|
JSON.stringify(fileChanges.get(interaction.fileReview.itemId)) !==
|
|
471
520
|
interaction.fileReview.changes) {
|
|
@@ -486,16 +535,11 @@ export async function createCodexAgent(command = "codex") {
|
|
|
486
535
|
/** 在写入前移除映射,失败也不重发批准;任务终态负责说明不确定结果。 */
|
|
487
536
|
interactions.delete(id);
|
|
488
537
|
clearTimeout(interaction.timer);
|
|
538
|
+
if (approval?.result.decision === "cancel")
|
|
539
|
+
cancelledByApproval = true;
|
|
489
540
|
rpc.write({
|
|
490
541
|
id: interaction.requestId,
|
|
491
|
-
result:
|
|
492
|
-
? {
|
|
493
|
-
permissions: "decision" in answer && answer.decision === "accept"
|
|
494
|
-
? interaction.permissions
|
|
495
|
-
: {},
|
|
496
|
-
scope: "turn",
|
|
497
|
-
}
|
|
498
|
-
: answer,
|
|
542
|
+
result: approval ? approval.result : answer,
|
|
499
543
|
});
|
|
500
544
|
return !settled;
|
|
501
545
|
};
|