shennian 0.2.89 → 0.2.90

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 (118) hide show
  1. package/dist/assets/wechat-channel/macos/manifest.json +13 -4
  2. package/dist/assets/wechat-channel/macos/shennian-wechat-channel-helper +0 -0
  3. package/dist/bin/shennian.js +1 -1
  4. package/dist/publish-build-manifest.json +548 -0
  5. package/dist/scripts/wechat-rpa-confirmation.mjs +5 -97
  6. package/dist/src/agent-env.js +4 -105
  7. package/dist/src/agents/adapter.js +1 -19
  8. package/dist/src/agents/claude.js +8 -305
  9. package/dist/src/agents/codex-control.js +2 -188
  10. package/dist/src/agents/codex-utils.js +7 -200
  11. package/dist/src/agents/codex.js +15 -916
  12. package/dist/src/agents/command-spec.js +2 -413
  13. package/dist/src/agents/config-status.js +1 -226
  14. package/dist/src/agents/cursor.js +1 -249
  15. package/dist/src/agents/custom.js +4 -271
  16. package/dist/src/agents/detect.js +1 -56
  17. package/dist/src/agents/external-channel-instructions.js +10 -94
  18. package/dist/src/agents/gemini.js +1 -173
  19. package/dist/src/agents/manager.js +13 -157
  20. package/dist/src/agents/model-registry/cache.js +1 -37
  21. package/dist/src/agents/model-registry/discovery.js +2 -187
  22. package/dist/src/agents/model-registry/parsers.js +4 -447
  23. package/dist/src/agents/model-registry/runner.js +1 -30
  24. package/dist/src/agents/model-registry/service.js +1 -78
  25. package/dist/src/agents/model-registry/types.js +1 -8
  26. package/dist/src/agents/model-registry.js +1 -18
  27. package/dist/src/agents/openclaw.js +2 -275
  28. package/dist/src/agents/opencode.js +1 -231
  29. package/dist/src/agents/pi-context.js +12 -217
  30. package/dist/src/agents/pi.js +14 -723
  31. package/dist/src/agents/platform-instructions.js +9 -54
  32. package/dist/src/channels/base.js +1 -3
  33. package/dist/src/channels/registry.js +1 -30
  34. package/dist/src/channels/reply-split.js +10 -89
  35. package/dist/src/channels/runtime.js +5 -564
  36. package/dist/src/channels/secret-registry.js +1 -46
  37. package/dist/src/channels/websocket.js +8 -378
  38. package/dist/src/channels/wechat-channel/anchor.js +1 -65
  39. package/dist/src/channels/wechat-channel/client.js +1 -96
  40. package/dist/src/channels/wechat-channel/cooldown.js +1 -38
  41. package/dist/src/channels/wechat-channel/fingerprint.js +1 -71
  42. package/dist/src/channels/wechat-channel/helper-assets.d.ts +10 -1
  43. package/dist/src/channels/wechat-channel/helper-assets.js +1 -68
  44. package/dist/src/channels/wechat-channel/helper-client.js +3 -149
  45. package/dist/src/channels/wechat-channel/helper-protocol.d.ts +1 -1
  46. package/dist/src/channels/wechat-channel/helper-protocol.js +1 -115
  47. package/dist/src/channels/wechat-channel/index.d.ts +1 -0
  48. package/dist/src/channels/wechat-channel/index.js +1 -19
  49. package/dist/src/channels/wechat-channel/ledger.js +1 -54
  50. package/dist/src/channels/wechat-channel/media-resolver.js +1 -181
  51. package/dist/src/channels/wechat-channel/message-key.js +1 -105
  52. package/dist/src/channels/wechat-channel/observer.js +1 -118
  53. package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +3 -0
  54. package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -112
  55. package/dist/src/channels/wechat-channel/outbound-sender.d.ts +26 -0
  56. package/dist/src/channels/wechat-channel/outbound-sender.js +1 -0
  57. package/dist/src/channels/wechat-channel/preflight.js +1 -48
  58. package/dist/src/channels/wechat-channel/runner.js +1 -84
  59. package/dist/src/channels/wechat-channel/runtime.js +1 -66
  60. package/dist/src/channels/wechat-channel/scheduler.d.ts +5 -0
  61. package/dist/src/channels/wechat-channel/scheduler.js +1 -152
  62. package/dist/src/channels/wechat-rpa/macos-flow.js +1 -96
  63. package/dist/src/channels/wechat-rpa/macos.js +6 -48
  64. package/dist/src/channels/wechat-rpa/normalizer.js +7 -127
  65. package/dist/src/channels/wechat-rpa.js +6 -1028
  66. package/dist/src/channels/wecom.js +4 -357
  67. package/dist/src/commands/agent.js +6 -131
  68. package/dist/src/commands/daemon-windows.js +8 -48
  69. package/dist/src/commands/daemon.js +19 -1013
  70. package/dist/src/commands/external-attachments.js +1 -51
  71. package/dist/src/commands/external.js +1 -137
  72. package/dist/src/commands/manager.js +2 -391
  73. package/dist/src/commands/pair-qr.js +1 -6
  74. package/dist/src/commands/pair.js +9 -287
  75. package/dist/src/commands/tools.js +1 -34
  76. package/dist/src/commands/upgrade.js +1 -198
  77. package/dist/src/config/index.js +1 -35
  78. package/dist/src/daemon-log.js +6 -58
  79. package/dist/src/env-path.js +1 -64
  80. package/dist/src/fs/boundary.js +1 -126
  81. package/dist/src/fs/handler.js +1 -130
  82. package/dist/src/fs/security.js +1 -32
  83. package/dist/src/fs/text-decoder.js +1 -110
  84. package/dist/src/index.js +2 -404
  85. package/dist/src/log-reporter.js +1 -16
  86. package/dist/src/manager/prompt.js +29 -34
  87. package/dist/src/manager/registry.js +2 -269
  88. package/dist/src/manager/runtime.js +19 -1007
  89. package/dist/src/native-fusion/config.js +1 -5
  90. package/dist/src/native-fusion/opencode-parser.js +3 -123
  91. package/dist/src/native-fusion/parser-common.js +8 -264
  92. package/dist/src/native-fusion/parsers.js +8 -729
  93. package/dist/src/native-fusion/service.js +2 -225
  94. package/dist/src/native-fusion/state.js +1 -22
  95. package/dist/src/native-fusion/types.js +1 -1
  96. package/dist/src/region.js +1 -88
  97. package/dist/src/relay/client.js +1 -343
  98. package/dist/src/session/archive-zip.js +1 -220
  99. package/dist/src/session/handlers/agent-config.js +1 -150
  100. package/dist/src/session/handlers/agents.js +1 -55
  101. package/dist/src/session/handlers/chat.js +2 -751
  102. package/dist/src/session/handlers/control.js +1 -55
  103. package/dist/src/session/handlers/fs.js +1 -783
  104. package/dist/src/session/handlers/session-refresh.js +1 -47
  105. package/dist/src/session/handlers/skills.js +1 -121
  106. package/dist/src/session/handlers/title.js +1 -60
  107. package/dist/src/session/handlers/tool-detail.js +1 -218
  108. package/dist/src/session/manager.js +1 -319
  109. package/dist/src/session/projection.js +1 -54
  110. package/dist/src/session/queue.js +4 -317
  111. package/dist/src/session/remote-attachments.js +1 -72
  112. package/dist/src/session/store.js +3 -109
  113. package/dist/src/session/types.js +1 -4
  114. package/dist/src/skills/registry.js +15 -148
  115. package/dist/src/skills/setup.js +1 -101
  116. package/dist/src/tools/markdown-to-pdf.js +10 -346
  117. package/dist/src/upgrade/engine.js +3 -347
  118. package/package.json +3 -2
@@ -1,916 +1,15 @@
1
- // @arch docs/architecture/cli/agent-adapters.md
2
- // @test src/__tests__/codex-adapter.test.ts
3
- import { createInterface } from 'node:readline';
4
- import { randomUUID } from 'node:crypto';
5
- import { AgentAdapter, registerAgent } from './adapter.js';
6
- import { resolveBuiltinCommand, spawnAgentCommand } from './command-spec.js';
7
- import { buildAgentProcessEnv } from '../agent-env.js';
8
- import { ensurePlatformInstructionsFile } from './platform-instructions.js';
9
- import { CODEX_APP_SERVER_CLIENT_INFO, extractAppServerErrorMessage, formatCodexErrorMessage, formatTransientCodexStatus, isCodexCollabAgentItem, isCodexCollabAgentToolName, isCodexLegacyCollabAgentItem, isCodexUnsupportedEffortError, isMissingCodexRolloutError, isTransientCodexErrorMessage, looksLikeCodexInteractiveAuthPrompt, looksLikeFatalCodexStderr, makeThreadTitle, normalizeCodexModelId, normalizeCodexReasoningEffort, normalizeCodexStderr, normalizeTerminalText, safeStringify, stripGitDirectiveArtifacts, } from './codex-utils.js';
10
- export { isCodexUnsupportedEffortError, isMissingCodexRolloutError, normalizeCodexModelId, normalizeCodexReasoningEffort, } from './codex-utils.js';
11
- function buildCodexTextInput(text, attachments) {
12
- const images = attachments
13
- ?.filter((attachment) => attachment.kind === 'image')
14
- .filter((attachment) => !/^https?:\/\//i.test(attachment.path))
15
- .map((attachment) => {
16
- if (attachment.previewData) {
17
- return {
18
- path: attachment.path,
19
- data: attachment.previewData,
20
- mimeType: attachment.mimeType,
21
- };
22
- }
23
- return { path: attachment.path };
24
- })
25
- .filter((attachment) => attachment.path);
26
- return {
27
- type: 'text',
28
- text,
29
- text_elements: [],
30
- ...(images?.length ? { local_images: images } : {}),
31
- };
32
- }
33
- export class CodexAdapter extends AgentAdapter {
34
- options;
35
- type = 'codex';
36
- process = null;
37
- workDir = null;
38
- seq = 0;
39
- runId = '';
40
- pendingUsage;
41
- agentSessionId = null;
42
- hasEmittedText = false;
43
- textTail = '';
44
- terminalState = 'open';
45
- forceCloseTimer = null;
46
- stderrBuf = '';
47
- rpcSeq = 1;
48
- pendingRequests = new Map();
49
- appServerReady = null;
50
- appServerStartupError = null;
51
- activeTurnId = null;
52
- namedThread = false;
53
- deltaItemIds = new Set();
54
- lastTransientStatus = '';
55
- constructor(options = {}) {
56
- super();
57
- this.options = options;
58
- }
59
- externalChannel = null;
60
- shennianSessionId = null;
61
- extraEnv = {};
62
- configure(options) {
63
- this.shennianSessionId = options.sessionId ?? null;
64
- this.externalChannel = options.externalChannel ?? null;
65
- this.extraEnv = options.env ?? {};
66
- }
67
- async start(_sessionId, workDir, agentSessionId) {
68
- this.workDir = workDir;
69
- this.seq = 0;
70
- if (agentSessionId)
71
- this.agentSessionId = agentSessionId;
72
- }
73
- async send(text, modelId, reasoningEffort, attachments) {
74
- if (this.activeTurnId) {
75
- await this.interruptActiveTurn().catch(() => { });
76
- await this.killProcess();
77
- }
78
- const codexModelId = normalizeCodexModelId(modelId);
79
- const codexReasoningEffort = normalizeCodexReasoningEffort(reasoningEffort);
80
- this.runId = randomUUID();
81
- this.seq = 0;
82
- this.resetRunState();
83
- await this.ensureAppServer(codexModelId);
84
- const threadId = this.agentSessionId;
85
- if (!threadId)
86
- throw new Error('codex app-server did not create a thread');
87
- this.emitEvent({ state: 'start', agentSessionId: threadId });
88
- if (!this.namedThread) {
89
- await this.sendRpc('thread/name/set', {
90
- threadId,
91
- name: makeThreadTitle(text),
92
- }).catch(() => { });
93
- this.namedThread = true;
94
- }
95
- const response = await this.startTurnWithRecovery(threadId, text, codexModelId, codexReasoningEffort, attachments);
96
- this.activeTurnId = response.turn?.id ?? null;
97
- }
98
- async resume(agentSessionId) {
99
- await this.killProcess();
100
- this.agentSessionId = agentSessionId;
101
- this.namedThread = true;
102
- this.resetTextState();
103
- }
104
- async setTitle(agentSessionId, title, workDir) {
105
- const threadId = agentSessionId.trim();
106
- const name = title.replace(/\s+/g, ' ').trim();
107
- if (!threadId || !name)
108
- return;
109
- this.agentSessionId = threadId;
110
- if (workDir?.trim())
111
- this.workDir = workDir;
112
- await this.ensureAppServer();
113
- await this.sendRpc('thread/name/set', { threadId, name });
114
- this.namedThread = true;
115
- }
116
- async stop() {
117
- await this.interruptActiveTurn().catch(() => { });
118
- await this.killProcess();
119
- }
120
- async getStatus() {
121
- const threadId = this.agentSessionId;
122
- const activeTurnId = this.activeTurnId;
123
- if (!threadId || !this.process) {
124
- return { active: false, runId: activeTurnId, runPhase: null, canStop: false };
125
- }
126
- let status = null;
127
- try {
128
- const response = await this.sendRpc('thread/read', { threadId, includeTurns: false }, 5_000);
129
- status = response.thread?.status ?? null;
130
- }
131
- catch {
132
- // If the local control channel is still alive but the status read fails,
133
- // fall back to activeTurnId. This keeps the stop button available for
134
- // the running turn while avoiding false positives for idle sessions.
135
- }
136
- const active = status?.type === 'active' || Boolean(activeTurnId);
137
- return {
138
- active,
139
- runId: activeTurnId,
140
- runPhase: active ? this.mapThreadStatusToRunPhase(status) : null,
141
- canStop: Boolean(active && activeTurnId && this.process),
142
- };
143
- }
144
- spawnCodex(args) {
145
- const spec = resolveBuiltinCommand('codex');
146
- if (!spec) {
147
- this.emit('error', new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));
148
- return;
149
- }
150
- const proc = spawnAgentCommand(spec, args, {
151
- cwd: this.workDir ?? undefined,
152
- stdio: ['ignore', 'pipe', 'pipe'],
153
- env: buildAgentProcessEnv(this.extraEnv),
154
- });
155
- this.process = proc;
156
- const rl = createInterface({ input: proc.stdout });
157
- rl.on('line', (line) => {
158
- if (!line.trim())
159
- return;
160
- try {
161
- const obj = JSON.parse(line);
162
- this.handleStreamEvent(obj);
163
- }
164
- catch {
165
- this.emitEvent({ state: 'delta', text: line });
166
- }
167
- });
168
- let stderrBuf = '';
169
- proc.stderr?.on('data', (chunk) => {
170
- stderrBuf += chunk.toString();
171
- });
172
- proc.on('close', (code) => {
173
- if (this.process !== proc)
174
- return;
175
- this.process = null;
176
- this.clearForceCloseTimer();
177
- this.handleProcessClose(code, stderrBuf);
178
- });
179
- proc.on('error', (err) => {
180
- if (this.process !== proc)
181
- return;
182
- this.process = null;
183
- if (err.code === 'ENOENT') {
184
- this.emit('error', new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));
185
- }
186
- else {
187
- this.emit('error', err);
188
- }
189
- });
190
- }
191
- spawnAppServer() {
192
- const spec = resolveBuiltinCommand('codex');
193
- if (!spec) {
194
- this.emit('error', new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));
195
- return;
196
- }
197
- const args = ['app-server', '--listen', 'stdio://'];
198
- const modelInstructionsFile = this.options.modelInstructionsFile ?? ensurePlatformInstructionsFile(this.workDir ?? process.cwd(), this.externalChannel, this.shennianSessionId ?? undefined);
199
- if (modelInstructionsFile) {
200
- args.push('-c', `model_instructions_file=${JSON.stringify(modelInstructionsFile)}`);
201
- }
202
- const proc = spawnAgentCommand(spec, args, {
203
- cwd: this.workDir ?? undefined,
204
- stdio: ['pipe', 'pipe', 'pipe'],
205
- env: buildAgentProcessEnv({ NO_COLOR: '1', ...this.extraEnv }),
206
- });
207
- this.process = proc;
208
- this.stderrBuf = '';
209
- this.appServerStartupError = null;
210
- const rl = createInterface({ input: proc.stdout });
211
- rl.on('line', (line) => {
212
- if (!line.trim())
213
- return;
214
- let msg;
215
- try {
216
- msg = JSON.parse(line);
217
- }
218
- catch {
219
- this.handleAppServerNonJsonStdout(line);
220
- return;
221
- }
222
- this.handleAppServerMessage(msg);
223
- });
224
- proc.stderr?.on('data', (chunk) => {
225
- this.stderrBuf += chunk.toString();
226
- });
227
- proc.on('close', (code) => {
228
- if (this.process !== proc)
229
- return;
230
- const hadActiveTurn = this.activeTurnId != null;
231
- this.process = null;
232
- this.appServerReady = null;
233
- this.activeTurnId = null;
234
- this.rejectAllPending(new Error(normalizeCodexStderr(this.stderrBuf) || `codex app-server exited with code ${code}`));
235
- if (this.terminalState === 'open' && hadActiveTurn) {
236
- this.handleProcessClose(code, this.stderrBuf);
237
- }
238
- });
239
- proc.on('error', (err) => {
240
- if (this.process !== proc)
241
- return;
242
- this.process = null;
243
- this.rejectAllPending(err);
244
- if (err.code === 'ENOENT') {
245
- this.emit('error', new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));
246
- }
247
- else {
248
- this.emit('error', err);
249
- }
250
- });
251
- }
252
- async ensureAppServer(modelId) {
253
- if (this.appServerReady)
254
- return this.appServerReady;
255
- this.spawnAppServer();
256
- this.appServerReady = this.initializeAppServer(modelId).catch(async (error) => {
257
- this.appServerReady = null;
258
- await this.killProcess();
259
- throw error;
260
- });
261
- return this.appServerReady;
262
- }
263
- async initializeAppServer(modelId) {
264
- const codexModelId = normalizeCodexModelId(modelId);
265
- await this.sendRpc('initialize', {
266
- clientInfo: CODEX_APP_SERVER_CLIENT_INFO,
267
- capabilities: { experimentalApi: true },
268
- });
269
- if (this.agentSessionId) {
270
- const response = await this.sendRpc('thread/resume', {
271
- threadId: this.agentSessionId,
272
- cwd: this.workDir,
273
- approvalPolicy: 'never',
274
- sandbox: 'danger-full-access',
275
- persistExtendedHistory: true,
276
- ...(this.options.hidden ? { threadSource: 'subagent' } : {}),
277
- ...(codexModelId ? { model: codexModelId } : {}),
278
- });
279
- this.agentSessionId = response.thread?.id ?? this.agentSessionId;
280
- this.namedThread = !!response.thread?.name;
281
- return;
282
- }
283
- const response = await this.sendRpc('thread/start', {
284
- cwd: this.workDir,
285
- approvalPolicy: 'never',
286
- sandbox: 'danger-full-access',
287
- serviceName: 'Shennian',
288
- threadSource: this.options.hidden ? 'subagent' : 'user',
289
- experimentalRawEvents: false,
290
- persistExtendedHistory: true,
291
- ...(codexModelId ? { model: codexModelId } : {}),
292
- });
293
- const threadId = response.thread?.id;
294
- if (!threadId)
295
- throw new Error('codex app-server thread/start returned no thread id');
296
- this.agentSessionId = threadId;
297
- this.namedThread = !!response.thread?.name;
298
- }
299
- async startTurnWithRecovery(threadId, text, codexModelId, reasoningEffort, attachments) {
300
- try {
301
- return await this.startTurn(threadId, text, codexModelId, reasoningEffort, attachments);
302
- }
303
- catch (error) {
304
- if (!isMissingCodexRolloutError(error))
305
- throw error;
306
- await this.killProcess();
307
- await this.ensureAppServer(codexModelId);
308
- return await this.startTurn(threadId, text, codexModelId, reasoningEffort, attachments);
309
- }
310
- }
311
- async startTurn(threadId, text, codexModelId, reasoningEffort, attachments) {
312
- try {
313
- return await this.sendRpc('turn/start', {
314
- threadId,
315
- input: [buildCodexTextInput(text, attachments)],
316
- approvalPolicy: 'never',
317
- sandboxPolicy: { type: 'dangerFullAccess' },
318
- ...(codexModelId ? { model: codexModelId } : {}),
319
- ...(reasoningEffort ? { effort: reasoningEffort } : {}),
320
- });
321
- }
322
- catch (error) {
323
- if (reasoningEffort && isCodexUnsupportedEffortError(error)) {
324
- throw new Error(`Codex app-server does not accept reasoning effort "${reasoningEffort}" for this turn. Refresh models or upgrade Codex CLI, then retry.`, { cause: error });
325
- }
326
- throw error;
327
- }
328
- }
329
- async interruptActiveTurn() {
330
- const threadId = this.agentSessionId;
331
- const turnId = this.activeTurnId;
332
- if (!threadId || !turnId || !this.process)
333
- return;
334
- await this.sendRpc('turn/interrupt', { threadId, turnId }, 5_000);
335
- this.activeTurnId = null;
336
- }
337
- sendRpc(method, params, timeoutMs = 60_000) {
338
- if (this.appServerStartupError)
339
- return Promise.reject(this.appServerStartupError);
340
- const proc = this.process;
341
- if (!proc?.stdin)
342
- return Promise.reject(new Error('codex app-server is not running'));
343
- const id = this.rpcSeq++;
344
- const payload = JSON.stringify({ id, method, params });
345
- return new Promise((resolve, reject) => {
346
- const timer = setTimeout(() => {
347
- this.pendingRequests.delete(id);
348
- reject(new Error(`codex app-server request timed out: ${method}`));
349
- }, timeoutMs);
350
- this.pendingRequests.set(id, { resolve, reject, timer });
351
- proc.stdin.write(`${payload}\n`, (error) => {
352
- if (!error)
353
- return;
354
- clearTimeout(timer);
355
- this.pendingRequests.delete(id);
356
- reject(error);
357
- });
358
- });
359
- }
360
- handleAppServerMessage(msg) {
361
- if (msg.id != null) {
362
- const pending = this.pendingRequests.get(msg.id);
363
- if (pending) {
364
- clearTimeout(pending.timer);
365
- this.pendingRequests.delete(msg.id);
366
- if (msg.error) {
367
- pending.reject(new Error(msg.error.message || `codex app-server error ${msg.error.code ?? ''}`.trim()));
368
- }
369
- else {
370
- pending.resolve(msg.result);
371
- }
372
- }
373
- }
374
- if (!msg.method)
375
- return;
376
- const params = msg.params ?? {};
377
- switch (msg.method) {
378
- case 'turn/started': {
379
- const turn = typeof params.turn === 'object' && params.turn !== null
380
- ? params.turn
381
- : null;
382
- const turnId = typeof turn?.id === 'string' ? turn.id : null;
383
- if (turnId)
384
- this.activeTurnId = turnId;
385
- break;
386
- }
387
- case 'thread/status/changed': {
388
- const threadId = typeof params.threadId === 'string' ? params.threadId : '';
389
- if (threadId && this.agentSessionId && threadId !== this.agentSessionId)
390
- break;
391
- const status = typeof params.status === 'object' && params.status !== null
392
- ? params.status
393
- : null;
394
- if (status?.type === 'active') {
395
- this.emitEvent({ state: 'heartbeat', runPhase: this.mapThreadStatusToRunPhase(status) });
396
- }
397
- break;
398
- }
399
- case 'item/agentMessage/delta': {
400
- const itemId = typeof params.itemId === 'string' ? params.itemId : '';
401
- if (itemId)
402
- this.deltaItemIds.add(itemId);
403
- if (typeof params.delta === 'string')
404
- this.emitText(params.delta);
405
- break;
406
- }
407
- case 'item/completed':
408
- this.handleAppServerCompletedItem(params);
409
- break;
410
- case 'thread/tokenUsage/updated':
411
- this.handleAppServerUsage(params);
412
- break;
413
- case 'turn/completed': {
414
- const turn = typeof params.turn === 'object' && params.turn !== null
415
- ? params.turn
416
- : null;
417
- const turnId = typeof turn?.id === 'string' ? turn.id : null;
418
- if (!this.activeTurnId || turnId === this.activeTurnId) {
419
- this.activeTurnId = null;
420
- this.emitFinalIfOpen();
421
- }
422
- break;
423
- }
424
- case 'error': {
425
- const message = extractAppServerErrorMessage(params);
426
- if (isTransientCodexErrorMessage(message)) {
427
- this.emitTransientStatus(message);
428
- return;
429
- }
430
- this.emitErrorIfOpen(message);
431
- break;
432
- }
433
- }
434
- }
435
- mapThreadStatusToRunPhase(status) {
436
- const flags = status?.type === 'active' && Array.isArray(status.activeFlags) ? status.activeFlags : [];
437
- if (flags.includes('waitingOnApproval'))
438
- return 'waiting_approval';
439
- if (flags.includes('waitingOnUserInput'))
440
- return 'waiting_user_input';
441
- return 'thinking';
442
- }
443
- handleAppServerNonJsonStdout(line) {
444
- const normalized = normalizeTerminalText(line);
445
- if (!looksLikeCodexInteractiveAuthPrompt(normalized))
446
- return;
447
- this.failAppServerStartup(new Error('Codex 未登录。请先在这台电脑的终端运行 `codex`,按提示完成 ChatGPT 登录或配置 API key,然后再回到神念发送消息。'));
448
- }
449
- failAppServerStartup(error) {
450
- if (this.appServerStartupError)
451
- return;
452
- this.appServerStartupError = error;
453
- this.rejectAllPending(error);
454
- this.process?.kill('SIGTERM');
455
- }
456
- handleAppServerCompletedItem(params) {
457
- const item = typeof params.item === 'object' && params.item !== null
458
- ? params.item
459
- : null;
460
- if (!item || typeof item.type !== 'string')
461
- return;
462
- if (isCodexCollabAgentItem(item))
463
- return;
464
- switch (item.type) {
465
- case 'agentMessage': {
466
- const id = typeof item.id === 'string' ? item.id : '';
467
- if (id && this.deltaItemIds.has(id))
468
- return;
469
- if (typeof item.text === 'string')
470
- this.emitText(item.text, 'block');
471
- break;
472
- }
473
- case 'commandExecution': {
474
- const command = typeof item.command === 'string' ? item.command : '';
475
- const output = typeof item.aggregatedOutput === 'string' ? item.aggregatedOutput : '';
476
- if (command) {
477
- this.emitEvent({ state: 'tool-call', name: 'command_execution', args: { command } });
478
- }
479
- this.emitEvent({
480
- state: 'tool-result',
481
- name: 'command_execution',
482
- result: [output, item.exitCode != null ? `\n(exit ${item.exitCode})` : ''].join('').trim(),
483
- });
484
- break;
485
- }
486
- case 'mcpToolCall':
487
- case 'dynamicToolCall': {
488
- const tool = typeof item.tool === 'string' ? item.tool : item.type;
489
- if (isCodexCollabAgentToolName(tool))
490
- return;
491
- const args = typeof item.arguments === 'object' && item.arguments !== null
492
- ? item.arguments
493
- : {};
494
- this.emitEvent({ state: 'tool-call', name: tool, args });
495
- this.emitEvent({
496
- state: 'tool-result',
497
- name: tool,
498
- result: safeStringify(item.error ?? item.result ?? item.contentItems ?? ''),
499
- });
500
- break;
501
- }
502
- case 'fileChange': {
503
- const changes = Array.isArray(item.changes) ? item.changes : [];
504
- const summary = changes
505
- .map((change) => {
506
- if (typeof change !== 'object' || change === null)
507
- return '';
508
- const c = change;
509
- const path = typeof c.path === 'string' ? c.path : typeof c.file === 'string' ? c.file : '';
510
- const kind = typeof c.kind === 'string' ? c.kind : typeof c.type === 'string' ? c.type : 'change';
511
- return path ? `${kind}: ${path}` : '';
512
- })
513
- .filter(Boolean)
514
- .join('\n');
515
- if (summary)
516
- this.emitText(summary, 'block');
517
- break;
518
- }
519
- case 'approvalRequest':
520
- case 'approval_request':
521
- case 'permissionRequest':
522
- case 'permission_request':
523
- case 'userInputRequired':
524
- case 'user_input_required': {
525
- this.emitApprovalPending(item);
526
- break;
527
- }
528
- }
529
- }
530
- handleAppServerUsage(params) {
531
- const tokenUsage = typeof params.tokenUsage === 'object' && params.tokenUsage !== null
532
- ? params.tokenUsage
533
- : null;
534
- const last = typeof tokenUsage?.last === 'object' && tokenUsage.last !== null
535
- ? tokenUsage.last
536
- : null;
537
- const total = typeof tokenUsage?.total === 'object' && tokenUsage.total !== null
538
- ? tokenUsage.total
539
- : null;
540
- const usage = last ?? total;
541
- if (!usage)
542
- return;
543
- const input = typeof usage.inputTokens === 'number' ? usage.inputTokens : undefined;
544
- const output = typeof usage.outputTokens === 'number' ? usage.outputTokens : undefined;
545
- if (input != null || output != null) {
546
- this.pendingUsage = {
547
- inputTokens: input ?? 0,
548
- outputTokens: output ?? 0,
549
- };
550
- }
551
- }
552
- handleCodexItemLine(obj) {
553
- const item = obj.item;
554
- if (!item?.type)
555
- return;
556
- if (isCodexLegacyCollabAgentItem(item))
557
- return;
558
- switch (item.type) {
559
- case 'agent_message':
560
- case 'reasoning': {
561
- if (obj.type === 'item.completed' && item.text) {
562
- this.emitText(item.text, 'block');
563
- }
564
- break;
565
- }
566
- case 'command_execution': {
567
- if (obj.type === 'item.started' && item.command) {
568
- this.emitEvent({
569
- state: 'tool-call',
570
- name: 'command_execution',
571
- args: { command: item.command },
572
- });
573
- }
574
- if (obj.type === 'item.completed') {
575
- const parts = [
576
- item.aggregated_output ?? '',
577
- item.exit_code != null ? `\n(exit ${item.exit_code})` : '',
578
- ];
579
- this.emitEvent({
580
- state: 'tool-result',
581
- name: 'command_execution',
582
- result: parts.join('').trim() || String(item.status ?? ''),
583
- });
584
- }
585
- break;
586
- }
587
- case 'mcp_tool_call': {
588
- if (item.tool && isCodexCollabAgentToolName(item.tool))
589
- return;
590
- if (obj.type === 'item.started') {
591
- this.emitEvent({
592
- state: 'tool-call',
593
- name: item.tool ?? 'mcp_tool',
594
- args: {
595
- server: item.server,
596
- ...(item.arguments && typeof item.arguments === 'object' ? item.arguments : {}),
597
- },
598
- });
599
- }
600
- if (obj.type === 'item.completed') {
601
- const errMsg = item.error && typeof item.error === 'object' ? item.error.message : undefined;
602
- this.emitEvent({
603
- state: 'tool-result',
604
- name: item.tool ?? 'mcp_tool',
605
- result: errMsg ?? safeStringify(item.result),
606
- });
607
- }
608
- break;
609
- }
610
- case 'file_change': {
611
- if (obj.type === 'item.completed' && item.changes?.length) {
612
- const summary = item.changes.map((c) => `${c.kind}: ${c.path}`).join('\n');
613
- this.emitText(summary, 'block');
614
- }
615
- break;
616
- }
617
- case 'web_search': {
618
- if (obj.type === 'item.completed' && item.query) {
619
- this.emitText(`[web_search] ${item.query}`, 'block');
620
- }
621
- break;
622
- }
623
- case 'todo_list': {
624
- if (obj.type === 'item.completed' && item.items?.length) {
625
- const lines = item.items.map((t) => `- [${t.completed ? 'x' : ' '}] ${t.text}`).join('\n');
626
- this.emitText(lines, 'block');
627
- }
628
- break;
629
- }
630
- case 'approval_request':
631
- case 'approvalRequest':
632
- case 'permission_request':
633
- case 'permissionRequest':
634
- case 'user_input_required':
635
- case 'userInputRequired': {
636
- if (obj.type === 'item.started' || obj.type === 'item.completed') {
637
- this.emitApprovalPending(item);
638
- }
639
- break;
640
- }
641
- case 'error': {
642
- if (obj.type === 'item.completed' && item.message) {
643
- this.emitEvent({ state: 'error', message: item.message });
644
- }
645
- break;
646
- }
647
- default:
648
- break;
649
- }
650
- }
651
- handleStreamEvent(obj) {
652
- const topType = obj.type ?? obj.event;
653
- if (topType?.startsWith('item.')) {
654
- this.handleCodexItemLine(obj);
655
- }
656
- switch (topType) {
657
- case 'turn.completed': {
658
- const u = obj.usage;
659
- if (u) {
660
- const input = u.input_tokens ?? u.prompt_tokens;
661
- const output = u.output_tokens ?? u.completion_tokens;
662
- if (input != null || output != null) {
663
- this.pendingUsage = {
664
- inputTokens: input ?? 0,
665
- outputTokens: output ?? 0,
666
- };
667
- }
668
- }
669
- this.emitFinalIfOpen();
670
- return;
671
- }
672
- case 'turn.failed': {
673
- const msg = typeof obj.error === 'object' && obj.error?.message
674
- ? obj.error.message
675
- : typeof obj.error === 'string'
676
- ? obj.error
677
- : 'turn failed';
678
- this.emitErrorIfOpen(msg);
679
- return;
680
- }
681
- case 'error': {
682
- const m = typeof obj.message === 'string' ? obj.message : '';
683
- if (m.includes('Reconnecting'))
684
- return;
685
- this.emitErrorIfOpen(m || (typeof obj.error === 'string' ? obj.error : 'stream error'));
686
- return;
687
- }
688
- case 'thread.started': {
689
- const sid = obj.thread_id ?? obj.session_id ?? obj.id;
690
- if (typeof sid === 'string' && sid) {
691
- this.agentSessionId = sid;
692
- this.emitEvent({ state: 'start', agentSessionId: this.agentSessionId });
693
- }
694
- return;
695
- }
696
- case 'turn.started':
697
- return;
698
- }
699
- if (topType?.startsWith('item.'))
700
- return;
701
- const eventType = obj.type ?? obj.event;
702
- switch (eventType) {
703
- case 'message':
704
- case 'text':
705
- case 'content': {
706
- const text = obj.content ?? obj.text;
707
- if (text) {
708
- this.emitText(text);
709
- }
710
- break;
711
- }
712
- case 'function_call':
713
- case 'tool_call': {
714
- if (this.isApprovalLikeName(obj.name)) {
715
- this.emitApprovalPending(obj);
716
- break;
717
- }
718
- if (obj.name && isCodexCollabAgentToolName(obj.name))
719
- return;
720
- this.emitEvent({
721
- state: 'tool-call',
722
- name: obj.name,
723
- args: obj.arguments ?? obj.input,
724
- });
725
- break;
726
- }
727
- case 'function_call_output':
728
- case 'tool_result': {
729
- if (obj.name && isCodexCollabAgentToolName(obj.name))
730
- return;
731
- this.emitEvent({
732
- state: 'tool-result',
733
- name: obj.name,
734
- result: obj.output ?? obj.result,
735
- });
736
- break;
737
- }
738
- case 'approval_request':
739
- case 'approvalRequest':
740
- case 'permission_request':
741
- case 'permissionRequest':
742
- case 'user_input_required':
743
- case 'userInputRequired': {
744
- this.emitApprovalPending(obj);
745
- break;
746
- }
747
- case 'error': {
748
- break;
749
- }
750
- case 'done':
751
- case 'completed': {
752
- if (obj.usage) {
753
- this.pendingUsage = {
754
- inputTokens: obj.usage.prompt_tokens ?? obj.usage.input_tokens ?? 0,
755
- outputTokens: obj.usage.completion_tokens ?? obj.usage.output_tokens ?? 0,
756
- };
757
- }
758
- this.emitFinalIfOpen();
759
- break;
760
- }
761
- }
762
- }
763
- emitEvent(partial) {
764
- const event = {
765
- ...partial,
766
- runId: this.runId,
767
- seq: this.seq++,
768
- };
769
- this.emit('agentEvent', event);
770
- }
771
- isApprovalLikeName(name) {
772
- if (typeof name !== 'string')
773
- return false;
774
- return /approval|permission|confirm|user[_-]?input|request[_-]?user/i.test(name);
775
- }
776
- emitApprovalPending(source) {
777
- const command = typeof source.command === 'string' ? source.command : '';
778
- const title = typeof source.title === 'string' && source.title.trim()
779
- ? source.title
780
- : '需要用户确认';
781
- const rawContent = typeof source.prompt === 'string' ? source.prompt
782
- : typeof source.reason === 'string' ? source.reason
783
- : typeof source.message === 'string' ? source.message
784
- : command ? `Codex 请求执行命令:${command}`
785
- : 'Codex 正在等待用户确认后继续。';
786
- this.emitEvent({
787
- state: 'approval-pending',
788
- approval: {
789
- title,
790
- content: rawContent,
791
- source: 'codex',
792
- actionHint: '当前神念暂不支持远程确认,请回到运行 Codex 的电脑/终端完成确认。',
793
- },
794
- });
795
- }
796
- async killProcess() {
797
- const proc = this.process;
798
- this.appServerReady = null;
799
- this.activeTurnId = null;
800
- if (!proc) {
801
- this.rejectAllPending(new Error('codex app-server stopped'));
802
- return;
803
- }
804
- this.process = null;
805
- this.clearForceCloseTimer();
806
- proc.kill('SIGTERM');
807
- await new Promise((resolve) => {
808
- proc.on('close', resolve);
809
- setTimeout(() => {
810
- proc.kill('SIGKILL');
811
- resolve();
812
- }, 3000);
813
- });
814
- this.rejectAllPending(new Error('codex app-server stopped'));
815
- }
816
- rejectAllPending(error) {
817
- for (const [id, pending] of this.pendingRequests.entries()) {
818
- clearTimeout(pending.timer);
819
- pending.reject(error);
820
- this.pendingRequests.delete(id);
821
- }
822
- }
823
- handleProcessClose(code, stderrBuf) {
824
- const stderr = normalizeCodexStderr(stderrBuf);
825
- if (code !== 0 && code !== null) {
826
- const msg = stderr || `codex exited with code ${code}`;
827
- this.emitErrorIfOpen(msg);
828
- }
829
- else if (looksLikeFatalCodexStderr(stderr)) {
830
- // Codex sometimes surfaces fatal API/auth errors on stderr without
831
- // emitting a protocol-level `turn.failed`, and may still exit 0.
832
- this.emitErrorIfOpen(stderr);
833
- }
834
- else {
835
- this.emitFinalIfOpen();
836
- }
837
- this.pendingUsage = undefined;
838
- }
839
- emitFinalIfOpen() {
840
- if (this.terminalState !== 'open')
841
- return;
842
- this.terminalState = 'final';
843
- this.emitEvent({
844
- state: 'final',
845
- usage: this.pendingUsage,
846
- agentSessionId: this.agentSessionId ?? undefined,
847
- });
848
- }
849
- emitErrorIfOpen(message) {
850
- if (this.terminalState !== 'open')
851
- return;
852
- this.terminalState = 'error';
853
- this.emitEvent({ state: 'error', message: formatCodexErrorMessage(message) });
854
- this.scheduleProcessClose();
855
- }
856
- resetRunState() {
857
- this.pendingUsage = undefined;
858
- this.terminalState = 'open';
859
- this.deltaItemIds.clear();
860
- this.lastTransientStatus = '';
861
- this.clearForceCloseTimer();
862
- this.resetTextState();
863
- }
864
- emitTransientStatus(message) {
865
- const formatted = formatTransientCodexStatus(message);
866
- if (!formatted || formatted === this.lastTransientStatus || this.terminalState !== 'open')
867
- return;
868
- this.lastTransientStatus = formatted;
869
- this.emitEvent({ state: 'delta', text: `${formatted}\n`, thinking: true });
870
- }
871
- resetTextState() {
872
- this.hasEmittedText = false;
873
- this.textTail = '';
874
- }
875
- emitText(text, mode = 'delta') {
876
- if (!text)
877
- return;
878
- let next = stripGitDirectiveArtifacts(text);
879
- if (!next)
880
- return;
881
- if (mode === 'block' && this.hasEmittedText && !next.startsWith('\n')) {
882
- if (this.textTail.endsWith('\n')) {
883
- next = `\n${next}`;
884
- }
885
- else if (!this.textTail.endsWith('\n\n')) {
886
- next = `\n\n${next}`;
887
- }
888
- }
889
- this.hasEmittedText = true;
890
- this.textTail = (this.textTail + next).slice(-4);
891
- this.emitEvent({ state: 'delta', text: next });
892
- }
893
- scheduleProcessClose() {
894
- const proc = this.process;
895
- if (!proc || this.forceCloseTimer)
896
- return;
897
- this.forceCloseTimer = setTimeout(() => {
898
- this.forceCloseTimer = null;
899
- if (this.process !== proc)
900
- return;
901
- proc.kill('SIGTERM');
902
- setTimeout(() => {
903
- if (this.process === proc) {
904
- proc.kill('SIGKILL');
905
- }
906
- }, 1000);
907
- }, 250);
908
- }
909
- clearForceCloseTimer() {
910
- if (!this.forceCloseTimer)
911
- return;
912
- clearTimeout(this.forceCloseTimer);
913
- this.forceCloseTimer = null;
914
- }
915
- }
916
- registerAgent('codex', () => new CodexAdapter());
1
+ import{createInterface as u}from"node:readline";import{randomUUID as y}from"node:crypto";import{AgentAdapter as I,registerAgent as T}from"./adapter.js";import{resolveBuiltinCommand as h,spawnAgentCommand as m}from"./command-spec.js";import{buildAgentProcessEnv as f}from"../agent-env.js";import{ensurePlatformInstructionsFile as S}from"./platform-instructions.js";import{CODEX_APP_SERVER_CLIENT_INFO as k,extractAppServerErrorMessage as E,formatCodexErrorMessage as w,formatTransientCodexStatus as C,isCodexCollabAgentItem as _,isCodexCollabAgentToolName as l,isCodexLegacyCollabAgentItem as A,isCodexUnsupportedEffortError as R,isMissingCodexRolloutError as P,isTransientCodexErrorMessage as q,looksLikeCodexInteractiveAuthPrompt as O,looksLikeFatalCodexStderr as $,makeThreadTitle as U,normalizeCodexModelId as g,normalizeCodexReasoningEffort as L,normalizeCodexStderr as v,normalizeTerminalText as b,safeStringify as x,stripGitDirectiveArtifacts as F}from"./codex-utils.js";import{isCodexUnsupportedEffortError as X,isMissingCodexRolloutError as Q,normalizeCodexModelId as Y,normalizeCodexReasoningEffort as Z}from"./codex-utils.js";function N(p,e){const t=e?.filter(s=>s.kind==="image").filter(s=>!/^https?:\/\//i.test(s.path)).map(s=>s.previewData?{path:s.path,data:s.previewData,mimeType:s.mimeType}:{path:s.path}).filter(s=>s.path);return{type:"text",text:p,text_elements:[],...t?.length?{local_images:t}:{}}}class M extends I{options;type="codex";process=null;workDir=null;seq=0;runId="";pendingUsage;agentSessionId=null;hasEmittedText=!1;textTail="";terminalState="open";forceCloseTimer=null;stderrBuf="";rpcSeq=1;pendingRequests=new Map;appServerReady=null;appServerStartupError=null;activeTurnId=null;namedThread=!1;deltaItemIds=new Set;lastTransientStatus="";constructor(e={}){super(),this.options=e}externalChannel=null;shennianSessionId=null;extraEnv={};configure(e){this.shennianSessionId=e.sessionId??null,this.externalChannel=e.externalChannel??null,this.extraEnv=e.env??{}}async start(e,t,s){this.workDir=t,this.seq=0,s&&(this.agentSessionId=s)}async send(e,t,s,r){this.activeTurnId&&(await this.interruptActiveTurn().catch(()=>{}),await this.killProcess());const n=g(t),i=L(s);this.runId=y(),this.seq=0,this.resetRunState(),await this.ensureAppServer(n);const a=this.agentSessionId;if(!a)throw new Error("codex app-server did not create a thread");this.emitEvent({state:"start",agentSessionId:a}),this.namedThread||(await this.sendRpc("thread/name/set",{threadId:a,name:U(e)}).catch(()=>{}),this.namedThread=!0);const o=await this.startTurnWithRecovery(a,e,n,i,r);this.activeTurnId=o.turn?.id??null}async resume(e){await this.killProcess(),this.agentSessionId=e,this.namedThread=!0,this.resetTextState()}async setTitle(e,t,s){const r=e.trim(),n=t.replace(/\s+/g," ").trim();!r||!n||(this.agentSessionId=r,s?.trim()&&(this.workDir=s),await this.ensureAppServer(),await this.sendRpc("thread/name/set",{threadId:r,name:n}),this.namedThread=!0)}async stop(){await this.interruptActiveTurn().catch(()=>{}),await this.killProcess()}async getStatus(){const e=this.agentSessionId,t=this.activeTurnId;if(!e||!this.process)return{active:!1,runId:t,runPhase:null,canStop:!1};let s=null;try{s=(await this.sendRpc("thread/read",{threadId:e,includeTurns:!1},5e3)).thread?.status??null}catch{}const r=s?.type==="active"||!!t;return{active:r,runId:t,runPhase:r?this.mapThreadStatusToRunPhase(s):null,canStop:!!(r&&t&&this.process)}}spawnCodex(e){const t=h("codex");if(!t){this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));return}const s=m(t,e,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:f(this.extraEnv)});this.process=s,u({input:s.stdout}).on("line",i=>{if(i.trim())try{const a=JSON.parse(i);this.handleStreamEvent(a)}catch{this.emitEvent({state:"delta",text:i})}});let n="";s.stderr?.on("data",i=>{n+=i.toString()}),s.on("close",i=>{this.process===s&&(this.process=null,this.clearForceCloseTimer(),this.handleProcessClose(i,n))}),s.on("error",i=>{this.process===s&&(this.process=null,i.code==="ENOENT"?this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?')):this.emit("error",i))})}spawnAppServer(){const e=h("codex");if(!e){this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));return}const t=["app-server","--listen","stdio://"],s=this.options.modelInstructionsFile??S(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0);s&&t.push("-c",`model_instructions_file=${JSON.stringify(s)}`);const r=m(e,t,{cwd:this.workDir??void 0,stdio:["pipe","pipe","pipe"],env:f({NO_COLOR:"1",...this.extraEnv})});this.process=r,this.stderrBuf="",this.appServerStartupError=null,u({input:r.stdout}).on("line",i=>{if(!i.trim())return;let a;try{a=JSON.parse(i)}catch{this.handleAppServerNonJsonStdout(i);return}this.handleAppServerMessage(a)}),r.stderr?.on("data",i=>{this.stderrBuf+=i.toString()}),r.on("close",i=>{if(this.process!==r)return;const a=this.activeTurnId!=null;this.process=null,this.appServerReady=null,this.activeTurnId=null,this.rejectAllPending(new Error(v(this.stderrBuf)||`codex app-server exited with code ${i}`)),this.terminalState==="open"&&a&&this.handleProcessClose(i,this.stderrBuf)}),r.on("error",i=>{this.process===r&&(this.process=null,this.rejectAllPending(i),i.code==="ENOENT"?this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?')):this.emit("error",i))})}async ensureAppServer(e){return this.appServerReady?this.appServerReady:(this.spawnAppServer(),this.appServerReady=this.initializeAppServer(e).catch(async t=>{throw this.appServerReady=null,await this.killProcess(),t}),this.appServerReady)}async initializeAppServer(e){const t=g(e);if(await this.sendRpc("initialize",{clientInfo:k,capabilities:{experimentalApi:!0}}),this.agentSessionId){const n=await this.sendRpc("thread/resume",{threadId:this.agentSessionId,cwd:this.workDir,approvalPolicy:"never",sandbox:"danger-full-access",persistExtendedHistory:!0,...this.options.hidden?{threadSource:"subagent"}:{},...t?{model:t}:{}});this.agentSessionId=n.thread?.id??this.agentSessionId,this.namedThread=!!n.thread?.name;return}const s=await this.sendRpc("thread/start",{cwd:this.workDir,approvalPolicy:"never",sandbox:"danger-full-access",serviceName:"Shennian",threadSource:this.options.hidden?"subagent":"user",experimentalRawEvents:!1,persistExtendedHistory:!0,...t?{model:t}:{}}),r=s.thread?.id;if(!r)throw new Error("codex app-server thread/start returned no thread id");this.agentSessionId=r,this.namedThread=!!s.thread?.name}async startTurnWithRecovery(e,t,s,r,n){try{return await this.startTurn(e,t,s,r,n)}catch(i){if(!P(i))throw i;return await this.killProcess(),await this.ensureAppServer(s),await this.startTurn(e,t,s,r,n)}}async startTurn(e,t,s,r,n){try{return await this.sendRpc("turn/start",{threadId:e,input:[N(t,n)],approvalPolicy:"never",sandboxPolicy:{type:"dangerFullAccess"},...s?{model:s}:{},...r?{effort:r}:{}})}catch(i){throw r&&R(i)?new Error(`Codex app-server does not accept reasoning effort "${r}" for this turn. Refresh models or upgrade Codex CLI, then retry.`,{cause:i}):i}}async interruptActiveTurn(){const e=this.agentSessionId,t=this.activeTurnId;!e||!t||!this.process||(await this.sendRpc("turn/interrupt",{threadId:e,turnId:t},5e3),this.activeTurnId=null)}sendRpc(e,t,s=6e4){if(this.appServerStartupError)return Promise.reject(this.appServerStartupError);const r=this.process;if(!r?.stdin)return Promise.reject(new Error("codex app-server is not running"));const n=this.rpcSeq++,i=JSON.stringify({id:n,method:e,params:t});return new Promise((a,o)=>{const d=setTimeout(()=>{this.pendingRequests.delete(n),o(new Error(`codex app-server request timed out: ${e}`))},s);this.pendingRequests.set(n,{resolve:a,reject:o,timer:d}),r.stdin.write(`${i}
2
+ `,c=>{c&&(clearTimeout(d),this.pendingRequests.delete(n),o(c))})})}handleAppServerMessage(e){if(e.id!=null){const s=this.pendingRequests.get(e.id);s&&(clearTimeout(s.timer),this.pendingRequests.delete(e.id),e.error?s.reject(new Error(e.error.message||`codex app-server error ${e.error.code??""}`.trim())):s.resolve(e.result))}if(!e.method)return;const t=e.params??{};switch(e.method){case"turn/started":{const s=typeof t.turn=="object"&&t.turn!==null?t.turn:null,r=typeof s?.id=="string"?s.id:null;r&&(this.activeTurnId=r);break}case"thread/status/changed":{const s=typeof t.threadId=="string"?t.threadId:"";if(s&&this.agentSessionId&&s!==this.agentSessionId)break;const r=typeof t.status=="object"&&t.status!==null?t.status:null;r?.type==="active"&&this.emitEvent({state:"heartbeat",runPhase:this.mapThreadStatusToRunPhase(r)});break}case"item/agentMessage/delta":{const s=typeof t.itemId=="string"?t.itemId:"";s&&this.deltaItemIds.add(s),typeof t.delta=="string"&&this.emitText(t.delta);break}case"item/completed":this.handleAppServerCompletedItem(t);break;case"thread/tokenUsage/updated":this.handleAppServerUsage(t);break;case"turn/completed":{const s=typeof t.turn=="object"&&t.turn!==null?t.turn:null,r=typeof s?.id=="string"?s.id:null;(!this.activeTurnId||r===this.activeTurnId)&&(this.activeTurnId=null,this.emitFinalIfOpen());break}case"error":{const s=E(t);if(q(s)){this.emitTransientStatus(s);return}this.emitErrorIfOpen(s);break}}}mapThreadStatusToRunPhase(e){const t=e?.type==="active"&&Array.isArray(e.activeFlags)?e.activeFlags:[];return t.includes("waitingOnApproval")?"waiting_approval":t.includes("waitingOnUserInput")?"waiting_user_input":"thinking"}handleAppServerNonJsonStdout(e){const t=b(e);O(t)&&this.failAppServerStartup(new Error("Codex \u672A\u767B\u5F55\u3002\u8BF7\u5148\u5728\u8FD9\u53F0\u7535\u8111\u7684\u7EC8\u7AEF\u8FD0\u884C `codex`\uFF0C\u6309\u63D0\u793A\u5B8C\u6210 ChatGPT \u767B\u5F55\u6216\u914D\u7F6E API key\uFF0C\u7136\u540E\u518D\u56DE\u5230\u795E\u5FF5\u53D1\u9001\u6D88\u606F\u3002"))}failAppServerStartup(e){this.appServerStartupError||(this.appServerStartupError=e,this.rejectAllPending(e),this.process?.kill("SIGTERM"))}handleAppServerCompletedItem(e){const t=typeof e.item=="object"&&e.item!==null?e.item:null;if(!(!t||typeof t.type!="string")&&!_(t))switch(t.type){case"agentMessage":{const s=typeof t.id=="string"?t.id:"";if(s&&this.deltaItemIds.has(s))return;typeof t.text=="string"&&this.emitText(t.text,"block");break}case"commandExecution":{const s=typeof t.command=="string"?t.command:"",r=typeof t.aggregatedOutput=="string"?t.aggregatedOutput:"";s&&this.emitEvent({state:"tool-call",name:"command_execution",args:{command:s}}),this.emitEvent({state:"tool-result",name:"command_execution",result:[r,t.exitCode!=null?`
3
+ (exit ${t.exitCode})`:""].join("").trim()});break}case"mcpToolCall":case"dynamicToolCall":{const s=typeof t.tool=="string"?t.tool:t.type;if(l(s))return;const r=typeof t.arguments=="object"&&t.arguments!==null?t.arguments:{};this.emitEvent({state:"tool-call",name:s,args:r}),this.emitEvent({state:"tool-result",name:s,result:x(t.error??t.result??t.contentItems??"")});break}case"fileChange":{const r=(Array.isArray(t.changes)?t.changes:[]).map(n=>{if(typeof n!="object"||n===null)return"";const i=n,a=typeof i.path=="string"?i.path:typeof i.file=="string"?i.file:"",o=typeof i.kind=="string"?i.kind:typeof i.type=="string"?i.type:"change";return a?`${o}: ${a}`:""}).filter(Boolean).join(`
4
+ `);r&&this.emitText(r,"block");break}case"approvalRequest":case"approval_request":case"permissionRequest":case"permission_request":case"userInputRequired":case"user_input_required":{this.emitApprovalPending(t);break}}}handleAppServerUsage(e){const t=typeof e.tokenUsage=="object"&&e.tokenUsage!==null?e.tokenUsage:null,s=typeof t?.last=="object"&&t.last!==null?t.last:null,r=typeof t?.total=="object"&&t.total!==null?t.total:null,n=s??r;if(!n)return;const i=typeof n.inputTokens=="number"?n.inputTokens:void 0,a=typeof n.outputTokens=="number"?n.outputTokens:void 0;(i!=null||a!=null)&&(this.pendingUsage={inputTokens:i??0,outputTokens:a??0})}handleCodexItemLine(e){const t=e.item;if(t?.type&&!A(t))switch(t.type){case"agent_message":case"reasoning":{e.type==="item.completed"&&t.text&&this.emitText(t.text,"block");break}case"command_execution":{if(e.type==="item.started"&&t.command&&this.emitEvent({state:"tool-call",name:"command_execution",args:{command:t.command}}),e.type==="item.completed"){const s=[t.aggregated_output??"",t.exit_code!=null?`
5
+ (exit ${t.exit_code})`:""];this.emitEvent({state:"tool-result",name:"command_execution",result:s.join("").trim()||String(t.status??"")})}break}case"mcp_tool_call":{if(t.tool&&l(t.tool))return;if(e.type==="item.started"&&this.emitEvent({state:"tool-call",name:t.tool??"mcp_tool",args:{server:t.server,...t.arguments&&typeof t.arguments=="object"?t.arguments:{}}}),e.type==="item.completed"){const s=t.error&&typeof t.error=="object"?t.error.message:void 0;this.emitEvent({state:"tool-result",name:t.tool??"mcp_tool",result:s??x(t.result)})}break}case"file_change":{if(e.type==="item.completed"&&t.changes?.length){const s=t.changes.map(r=>`${r.kind}: ${r.path}`).join(`
6
+ `);this.emitText(s,"block")}break}case"web_search":{e.type==="item.completed"&&t.query&&this.emitText(`[web_search] ${t.query}`,"block");break}case"todo_list":{if(e.type==="item.completed"&&t.items?.length){const s=t.items.map(r=>`- [${r.completed?"x":" "}] ${r.text}`).join(`
7
+ `);this.emitText(s,"block")}break}case"approval_request":case"approvalRequest":case"permission_request":case"permissionRequest":case"user_input_required":case"userInputRequired":{(e.type==="item.started"||e.type==="item.completed")&&this.emitApprovalPending(t);break}case"error":{e.type==="item.completed"&&t.message&&this.emitEvent({state:"error",message:t.message});break}default:break}}handleStreamEvent(e){const t=e.type??e.event;switch(t?.startsWith("item.")&&this.handleCodexItemLine(e),t){case"turn.completed":{const r=e.usage;if(r){const n=r.input_tokens??r.prompt_tokens,i=r.output_tokens??r.completion_tokens;(n!=null||i!=null)&&(this.pendingUsage={inputTokens:n??0,outputTokens:i??0})}this.emitFinalIfOpen();return}case"turn.failed":{const r=typeof e.error=="object"&&e.error?.message?e.error.message:typeof e.error=="string"?e.error:"turn failed";this.emitErrorIfOpen(r);return}case"error":{const r=typeof e.message=="string"?e.message:"";if(r.includes("Reconnecting"))return;this.emitErrorIfOpen(r||(typeof e.error=="string"?e.error:"stream error"));return}case"thread.started":{const r=e.thread_id??e.session_id??e.id;typeof r=="string"&&r&&(this.agentSessionId=r,this.emitEvent({state:"start",agentSessionId:this.agentSessionId}));return}case"turn.started":return}if(t?.startsWith("item."))return;switch(e.type??e.event){case"message":case"text":case"content":{const r=e.content??e.text;r&&this.emitText(r);break}case"function_call":case"tool_call":{if(this.isApprovalLikeName(e.name)){this.emitApprovalPending(e);break}if(e.name&&l(e.name))return;this.emitEvent({state:"tool-call",name:e.name,args:e.arguments??e.input});break}case"function_call_output":case"tool_result":{if(e.name&&l(e.name))return;this.emitEvent({state:"tool-result",name:e.name,result:e.output??e.result});break}case"approval_request":case"approvalRequest":case"permission_request":case"permissionRequest":case"user_input_required":case"userInputRequired":{this.emitApprovalPending(e);break}case"error":break;case"done":case"completed":{e.usage&&(this.pendingUsage={inputTokens:e.usage.prompt_tokens??e.usage.input_tokens??0,outputTokens:e.usage.completion_tokens??e.usage.output_tokens??0}),this.emitFinalIfOpen();break}}}emitEvent(e){const t={...e,runId:this.runId,seq:this.seq++};this.emit("agentEvent",t)}isApprovalLikeName(e){return typeof e!="string"?!1:/approval|permission|confirm|user[_-]?input|request[_-]?user/i.test(e)}emitApprovalPending(e){const t=typeof e.command=="string"?e.command:"",s=typeof e.title=="string"&&e.title.trim()?e.title:"\u9700\u8981\u7528\u6237\u786E\u8BA4",r=typeof e.prompt=="string"?e.prompt:typeof e.reason=="string"?e.reason:typeof e.message=="string"?e.message:t?`Codex \u8BF7\u6C42\u6267\u884C\u547D\u4EE4\uFF1A${t}`:"Codex \u6B63\u5728\u7B49\u5F85\u7528\u6237\u786E\u8BA4\u540E\u7EE7\u7EED\u3002";this.emitEvent({state:"approval-pending",approval:{title:s,content:r,source:"codex",actionHint:"\u5F53\u524D\u795E\u5FF5\u6682\u4E0D\u652F\u6301\u8FDC\u7A0B\u786E\u8BA4\uFF0C\u8BF7\u56DE\u5230\u8FD0\u884C Codex \u7684\u7535\u8111/\u7EC8\u7AEF\u5B8C\u6210\u786E\u8BA4\u3002"}})}async killProcess(){const e=this.process;if(this.appServerReady=null,this.activeTurnId=null,!e){this.rejectAllPending(new Error("codex app-server stopped"));return}this.process=null,this.clearForceCloseTimer(),e.kill("SIGTERM"),await new Promise(t=>{e.on("close",t),setTimeout(()=>{e.kill("SIGKILL"),t()},3e3)}),this.rejectAllPending(new Error("codex app-server stopped"))}rejectAllPending(e){for(const[t,s]of this.pendingRequests.entries())clearTimeout(s.timer),s.reject(e),this.pendingRequests.delete(t)}handleProcessClose(e,t){const s=v(t);if(e!==0&&e!==null){const r=s||`codex exited with code ${e}`;this.emitErrorIfOpen(r)}else $(s)?this.emitErrorIfOpen(s):this.emitFinalIfOpen();this.pendingUsage=void 0}emitFinalIfOpen(){this.terminalState==="open"&&(this.terminalState="final",this.emitEvent({state:"final",usage:this.pendingUsage,agentSessionId:this.agentSessionId??void 0}))}emitErrorIfOpen(e){this.terminalState==="open"&&(this.terminalState="error",this.emitEvent({state:"error",message:w(e)}),this.scheduleProcessClose())}resetRunState(){this.pendingUsage=void 0,this.terminalState="open",this.deltaItemIds.clear(),this.lastTransientStatus="",this.clearForceCloseTimer(),this.resetTextState()}emitTransientStatus(e){const t=C(e);!t||t===this.lastTransientStatus||this.terminalState!=="open"||(this.lastTransientStatus=t,this.emitEvent({state:"delta",text:`${t}
8
+ `,thinking:!0}))}resetTextState(){this.hasEmittedText=!1,this.textTail=""}emitText(e,t="delta"){if(!e)return;let s=F(e);s&&(t==="block"&&this.hasEmittedText&&!s.startsWith(`
9
+ `)&&(this.textTail.endsWith(`
10
+ `)?s=`
11
+ ${s}`:this.textTail.endsWith(`
12
+
13
+ `)||(s=`
14
+
15
+ ${s}`)),this.hasEmittedText=!0,this.textTail=(this.textTail+s).slice(-4),this.emitEvent({state:"delta",text:s}))}scheduleProcessClose(){const e=this.process;!e||this.forceCloseTimer||(this.forceCloseTimer=setTimeout(()=>{this.forceCloseTimer=null,this.process===e&&(e.kill("SIGTERM"),setTimeout(()=>{this.process===e&&e.kill("SIGKILL")},1e3))},250))}clearForceCloseTimer(){this.forceCloseTimer&&(clearTimeout(this.forceCloseTimer),this.forceCloseTimer=null)}}T("codex",()=>new M);export{M as CodexAdapter,X as isCodexUnsupportedEffortError,Q as isMissingCodexRolloutError,Y as normalizeCodexModelId,Z as normalizeCodexReasoningEffort};