@coda-console/codex 1.8.4 → 1.8.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/agent/entry.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // The Codex agent as Coda starts it (the plugin's `contributes.agents`): the locked codex-acp, run in this process with Coda's
2
2
  // patches, which fail the start once the adapter changes. Between it and Coda the entry speaks Coda's protocol for it
3
- // (waits.mjs, docs/agent-protocol.md): its session instructions become the thread's developer instructions (instructions.mjs),
3
+ // (waits.mjs, docs/agent-protocol.md), and of its own check of a call as that call's (reviews.mjs): its session instructions become the thread's developer instructions (instructions.mjs),
4
4
  // and a steer that finds no turn going is answered `promptRequired` (steering.mjs); the models of the model service chosen in
5
5
  // Coda join Codex's own (providers.mjs). What Coda wants of Codex beyond ACP is set here: the mode a session starts in, and
6
6
  // which Codex program runs, through the account bridge when another plugin chooses the account (accounts.mjs).
@@ -22,6 +22,7 @@ import { promptRequiredCodexSteering } from './steering.mjs';
22
22
  import { servedCodexModels } from './providers.mjs';
23
23
  import { input, listen, relayedCodexStreams } from './relay.mjs';
24
24
  import { CodexWaits } from './waits.mjs';
25
+ import { CodexReviews } from './reviews.mjs';
25
26
  import { explainCodexError, reportedCodexFailures } from './errors.mjs';
26
27
  import { reportedCodexSleeps } from './sleep.mjs';
27
28
 
@@ -48,8 +49,8 @@ if (!env.CODEX_PATH && accountSource(env) && env.CODA_PLUGIN_STATE) {
48
49
 
49
50
  const version = codexVersion();
50
51
  const waits = new CodexWaits({ agentInfo: version ? { name: 'codex', version } : null }), parse = line => { try { return JSON.parse(line); } catch { return null; } };
51
- const say = message => process.stdout.write(`${JSON.stringify(message)}\n`);
52
- listen(line => { for (const message of waits.fromAgent(parse(line))) say(explainCodexError(message)); });
52
+ const reviews = new CodexReviews(), say = message => process.stdout.write(`${JSON.stringify(message)}\n`);
53
+ listen(line => { for (const message of waits.fromAgent(parse(line))) for (const each of reviews.fromAgent(message)) say(explainCodexError(each)); });
53
54
  readLines(process.stdin).on('line', line => {
54
55
  const message = parse(line);
55
56
  if (!message || typeof message !== 'object') return;
@@ -0,0 +1,55 @@
1
+ // Codex's own check of a call before running it (its automatic approval review), said to Coda as part of that call
2
+ // (docs/agent-protocol.md「工具调用的审核」). codex-acp makes each check a call of its own, `Guardian Review`
3
+ // (`guardian_assessment:<id>`), which names the call it checks (`targetItemId`); here it becomes `_meta.coda.review` of that
4
+ // call. A check can begin before codex-acp tells of the call: the call is told then, pending, and codex-acp's own telling of
5
+ // it later is an update of it.
6
+ const record = value => value && typeof value === 'object' && !Array.isArray(value);
7
+ const CHECK = 'guardian_assessment:';
8
+ const STATE = { inProgress: 'reviewing', approved: 'approved', denied: 'denied', aborted: 'denied', timedOut: 'denied' };
9
+ const RISKS = new Set(['low', 'medium', 'high', 'critical']);
10
+ // What the call is, as far as the check says (each kind of action codex-acp 1.12.0 names), until codex-acp tells it. ACP
11
+ // requires a call's title: one the action does not give is said plainly.
12
+ const PLACEHOLDER = {
13
+ command: a => [a.command, 'execute'],
14
+ execve: a => [(a.argv?.length ? a.argv : [a.program]).join(' '), 'execute'],
15
+ writeStdin: a => [`向进程 ${a.processId} 写入输入`, 'execute'],
16
+ applyPatch: a => [a.files?.length === 1 ? `修改 ${a.files[0]}` : `修改 ${a.files?.length ?? 0} 个文件`, 'edit'],
17
+ networkAccess: a => [`访问 ${a.target || a.host}`, 'fetch'],
18
+ mcpToolCall: a => [`MCP ${a.toolName}(${a.connectorName ?? a.server})`],
19
+ requestPermissions: a => [a.reason || '申请更多权限'],
20
+ };
21
+ const placeholder = action => {
22
+ const [title, kind] = PLACEHOLDER[action?.type]?.(action) ?? [];
23
+ return { title: typeof title === 'string' && title.trim() ? title : '待审核的操作', ...(kind ? { kind } : {}) };
24
+ };
25
+
26
+ export class CodexReviews {
27
+ constructor() {
28
+ // The calls told of, and those told here before codex-acp did, by session.
29
+ this.told = new Set(); this.early = new Set();
30
+ }
31
+ // What codex-acp says, as said to Coda: the same, but a check is said of the call it checks.
32
+ fromAgent(message) {
33
+ const u = message?.params?.update;
34
+ if (message?.method !== 'session/update' || !record(u) || !['tool_call', 'tool_call_update'].includes(u.sessionUpdate)
35
+ || typeof u.toolCallId !== 'string') return [message];
36
+ const { sessionId } = message.params, key = id => `${sessionId}\n${id}`;
37
+ const said = update => ({ ...message, params: { ...message.params, update } });
38
+ if (!u.toolCallId.startsWith(CHECK)) {
39
+ if (u.sessionUpdate !== 'tool_call') return [message];
40
+ if (this.early.delete(key(u.toolCallId))) return [said({ ...u, sessionUpdate: 'tool_call_update' })];
41
+ this.told.add(key(u.toolCallId));
42
+ return [message];
43
+ }
44
+ // The started event, then the completed one: the later is the last word.
45
+ const check = record(u.rawOutput) ? u.rawOutput : u.rawInput, target = check?.targetItemId, state = STATE[check?.review?.status];
46
+ if (typeof target !== 'string' || !target || !state) return [message];
47
+ const { rationale, riskLevel } = check.review;
48
+ const review = { state, ...(typeof rationale === 'string' && rationale.trim() ? { reason: rationale.slice(0, 4000) } : {}),
49
+ ...(RISKS.has(riskLevel) ? { risk: riskLevel } : {}) };
50
+ const meta = { toolCallId: target, _meta: { coda: { review } } };
51
+ if (this.told.has(key(target)) || this.early.has(key(target))) return [said({ sessionUpdate: 'tool_call_update', ...meta })];
52
+ this.early.add(key(target)); this.told.add(key(target));
53
+ return [said({ sessionUpdate: 'tool_call', ...meta, ...placeholder(check.action), status: 'pending' })];
54
+ }
55
+ }
package/package.json CHANGED
@@ -2,8 +2,8 @@
2
2
  "name": "@coda-console/codex",
3
3
  "id": "codex",
4
4
  "displayName": "Codex",
5
- "description": "Codex Agent:Coda 自带的入口运行 codex-acp,报告子 Agent 与后台命令。额度与登录由账号来源插件接管;未接入账号来源时使用 Codex 自己的登录面板。",
6
- "version": "1.8.4",
5
+ "description": "Codex Agent:Coda 自带的入口运行 codex-acp,报告子 Agent、后台命令与命令的自动审核。额度与登录由账号来源插件接管;未接入账号来源时使用 Codex 自己的登录面板。",
6
+ "version": "1.8.5",
7
7
  "categories": ["Agent"],
8
8
  "dependencies": {
9
9
  "@agentclientprotocol/codex-acp": "1.12.0",