shennian 0.3.86 → 0.4.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/README.md +3 -2
- package/dist/bin/shennian.js +1 -1
- package/dist/publish-build-manifest.json +32 -27
- package/dist/src/agents/adapter.js +1 -19
- package/dist/src/agents/claude-managed-permissions.js +1 -248
- package/dist/src/agents/claude.js +8 -393
- package/dist/src/agents/codex-managed-permissions.js +1 -294
- package/dist/src/agents/codex-managed-room-tools.js +1 -249
- package/dist/src/agents/codex-utils.js +7 -200
- package/dist/src/agents/codex.d.ts +7 -1
- package/dist/src/agents/codex.js +17 -1352
- package/dist/src/agents/config-status.js +1 -226
- package/dist/src/agents/cursor-managed-permissions.js +2 -299
- package/dist/src/agents/cursor.js +1 -314
- package/dist/src/agents/custom.js +4 -418
- package/dist/src/agents/external-channel-instructions.js +11 -94
- package/dist/src/agents/gemini.js +1 -180
- package/dist/src/agents/manager.js +18 -250
- package/dist/src/agents/model-registry/cache.js +1 -37
- package/dist/src/agents/model-registry/discovery.js +2 -188
- package/dist/src/agents/model-registry/parsers.js +4 -521
- package/dist/src/agents/model-registry/runner.js +1 -30
- package/dist/src/agents/model-registry/service.js +1 -115
- package/dist/src/agents/model-registry/types.js +1 -8
- package/dist/src/agents/model-registry.js +1 -19
- package/dist/src/agents/native-capabilities.js +1 -237
- package/dist/src/agents/openclaw.js +2 -293
- package/dist/src/agents/opencode-managed-permissions.js +2 -187
- package/dist/src/agents/opencode.js +1 -298
- package/dist/src/agents/platform-instructions.js +17 -46
- package/dist/src/agents/runtime-compatibility.js +1 -44
- package/dist/src/agents/workbuddy-managed-permissions.js +2 -329
- package/dist/src/agents/workbuddy.js +3 -444
- package/dist/src/commands/logout.d.ts +3 -0
- package/dist/src/commands/logout.js +1 -0
- package/dist/src/daemon-ipc/paths.js +1 -1
- package/dist/src/fs/boundary.js +1 -185
- package/dist/src/fs/handler.js +1 -130
- package/dist/src/fs/security.js +1 -32
- package/dist/src/fs/text-decoder.js +1 -110
- package/dist/src/index.js +3 -587
- package/dist/src/manager/prompt.js +29 -34
- package/dist/src/manager/registry.js +1 -317
- package/dist/src/manager/retired-external-channel-compat.js +1 -40
- package/dist/src/manager/runtime.js +23 -1514
- package/dist/src/native-fusion/codex-parser.d.ts +3 -1
- package/dist/src/native-fusion/codex-parser.js +4 -660
- package/dist/src/native-fusion/codex-user-input.js +5 -102
- package/dist/src/native-fusion/config.js +1 -5
- package/dist/src/native-fusion/file-watcher.js +1 -191
- package/dist/src/native-fusion/local-repository.js +2 -569
- package/dist/src/native-fusion/opencode-parser.js +3 -130
- package/dist/src/native-fusion/parser-common.d.ts +6 -1
- package/dist/src/native-fusion/parser-common.js +4 -4
- package/dist/src/native-fusion/parsers.d.ts +4 -2
- package/dist/src/native-fusion/parsers.js +3 -140
- package/dist/src/native-fusion/service.js +2 -739
- package/dist/src/native-fusion/session-header.js +2 -101
- package/dist/src/native-fusion/state.js +2 -104
- package/dist/src/native-fusion/types.js +1 -1
- package/dist/src/personal-sync/machine-authority.js +1 -34
- package/dist/src/room/activation-audit.js +2 -45
- package/dist/src/room/activation-policy.js +1 -30
- package/dist/src/room/activation-service.js +8 -408
- package/dist/src/room/invite-bootstrap.js +1 -156
- package/dist/src/room/managed-prompt-composer.d.ts +1 -1
- package/dist/src/room/managed-prompt-composer.js +17 -16
- package/dist/src/room/managed-room-binding.js +1 -167
- package/dist/src/room/managed-room-ipc.d.ts +4 -0
- package/dist/src/room/managed-room-ipc.js +2 -2
- package/dist/src/room/mirror-sync-service.d.ts +1 -0
- package/dist/src/room/mirror-sync-service.js +1 -183
- package/dist/src/room/room-binding-client.js +1 -151
- package/dist/src/session/archive-zip.js +1 -220
- package/dist/src/session/deletion-sync-state.js +2 -136
- package/dist/src/session/handlers/agent-capabilities.js +1 -14
- package/dist/src/session/handlers/agent-config.js +1 -150
- package/dist/src/session/handlers/agents.js +1 -55
- package/dist/src/session/handlers/chat.js +2 -1030
- package/dist/src/session/handlers/control.js +1 -61
- package/dist/src/session/handlers/fs.js +1 -594
- package/dist/src/session/handlers/message-status.js +1 -15
- package/dist/src/session/handlers/remote-access.js +1 -43
- package/dist/src/session/handlers/room-managed.js +1 -101
- package/dist/src/session/handlers/session-refresh.js +1 -35
- package/dist/src/session/handlers/title.js +1 -79
- package/dist/src/session/handlers/tool-detail.js +1 -149
- package/dist/src/session/handlers/voice-window.js +1 -62
- package/dist/src/session/history-subscriptions.js +1 -470
- package/dist/src/session/manager.d.ts +1 -0
- package/dist/src/session/manager.js +1 -658
- package/dist/src/session/native-cleanup-outbox.js +2 -287
- package/dist/src/session/native-session-lifecycle.js +6 -327
- package/dist/src/session/queue.d.ts +8 -0
- package/dist/src/session/queue.js +6 -6
- package/dist/src/upgrade/scheduler.js +1 -126
- package/node_modules/@shennian/wire/dist/agent-workspace.js +2 -2
- package/package.json +1 -1
package/dist/src/agents/codex.js
CHANGED
|
@@ -1,1352 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import { assertCodexManagedPermissionProfile, CODEX_MANAGED_RUNTIME_SECURITY, compileCodexManagedPermissions, extractCodexMcpServerNames, } from './codex-managed-permissions.js';
|
|
19
|
-
import { CODEX_APP_SERVER_CLIENT_INFO, extractAppServerErrorMessage, formatCodexErrorMessage, formatTransientCodexStatus, isCodexCollabAgentItem, isCodexCollabAgentToolName, isCodexLegacyCollabAgentItem, isCodexUnsupportedEffortError, isMissingCodexRolloutError, isTransientCodexErrorMessage, looksLikeCodexInteractiveAuthPrompt, looksLikeFatalCodexStderr, normalizeCodexModelId, normalizeCodexReasoningEffort, normalizeCodexStderr, normalizeTerminalText, safeStringify, stripGitDirectiveArtifacts, } from './codex-utils.js';
|
|
20
|
-
export { isCodexUnsupportedEffortError, isMissingCodexRolloutError, normalizeCodexModelId, normalizeCodexReasoningEffort, } from './codex-utils.js';
|
|
21
|
-
registerManagedAgentRuntimeSecurity('codex', CODEX_MANAGED_RUNTIME_SECURITY);
|
|
22
|
-
export function isCodexActiveWriterError(error) {
|
|
23
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
24
|
-
return /already has an active writer|thread-store conflict/i.test(message);
|
|
25
|
-
}
|
|
26
|
-
export function isCodexPaginatedHistoryUnsupportedError(error) {
|
|
27
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
28
|
-
return (/paginated(?:_threads|\s+(?:thread\s+)?history)?.*(?:not supported|unsupported|unavailable)/i.test(message) ||
|
|
29
|
-
/(?:not supported|unsupported|unavailable).*paginated(?:_threads|\s+(?:thread\s+)?history)?/i.test(message));
|
|
30
|
-
}
|
|
31
|
-
export function isCodexExcludeTurnsUnsupportedError(error) {
|
|
32
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
33
|
-
return (/unknown field [`'"]?excludeTurns/i.test(message) ||
|
|
34
|
-
/excludeTurns.*(?:not supported|unsupported|unavailable)/i.test(message));
|
|
35
|
-
}
|
|
36
|
-
export function isCodexProjectApiUnsupportedError(error) {
|
|
37
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
38
|
-
return (/(?:method not found|unknown method).*(?:project\/list|project\/create)/i.test(message) ||
|
|
39
|
-
/(?:project\/list|project\/create).*(?:not supported|unsupported|unavailable)/i.test(message));
|
|
40
|
-
}
|
|
41
|
-
export function selectCodexProjectIdForWorkDir(projects, workDir) {
|
|
42
|
-
const normalizedWorkDir = path.resolve(workDir);
|
|
43
|
-
let bestMatch;
|
|
44
|
-
for (const project of projects) {
|
|
45
|
-
if (!project.id)
|
|
46
|
-
continue;
|
|
47
|
-
for (const root of project.roots ?? []) {
|
|
48
|
-
if (!root.path)
|
|
49
|
-
continue;
|
|
50
|
-
const normalizedRoot = path.resolve(root.path);
|
|
51
|
-
const relative = path.relative(normalizedRoot, normalizedWorkDir);
|
|
52
|
-
const containsWorkDir = relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
53
|
-
if (!containsWorkDir || normalizedRoot.length <= (bestMatch?.rootLength ?? -1))
|
|
54
|
-
continue;
|
|
55
|
-
bestMatch = { id: project.id, rootLength: normalizedRoot.length };
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return bestMatch?.id;
|
|
59
|
-
}
|
|
60
|
-
function buildCodexTextInput(text, attachments) {
|
|
61
|
-
const images = attachments
|
|
62
|
-
?.flatMap((attachment) => codexLocalImageInputs(attachment))
|
|
63
|
-
.filter((attachment) => !/^https?:\/\//i.test(attachment.path))
|
|
64
|
-
.filter((attachment) => attachment.path);
|
|
65
|
-
const textWithFileAttachments = appendFileAttachmentHints(text, attachments);
|
|
66
|
-
return {
|
|
67
|
-
type: 'text',
|
|
68
|
-
text: textWithFileAttachments,
|
|
69
|
-
text_elements: [],
|
|
70
|
-
...(images?.length ? { local_images: images } : {}),
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
function codexLocalImageInputs(attachment) {
|
|
74
|
-
if (attachment.kind === 'image') {
|
|
75
|
-
if (attachment.previewData) {
|
|
76
|
-
return [
|
|
77
|
-
{ path: attachment.path, data: attachment.previewData, mimeType: attachment.mimeType },
|
|
78
|
-
];
|
|
79
|
-
}
|
|
80
|
-
return [{ path: attachment.path }];
|
|
81
|
-
}
|
|
82
|
-
if (attachment.mimeType.startsWith('video/')) {
|
|
83
|
-
const previewPath = extractVideoPreviewFrame(attachment.path);
|
|
84
|
-
return previewPath ? [{ path: previewPath }] : [];
|
|
85
|
-
}
|
|
86
|
-
return [];
|
|
87
|
-
}
|
|
88
|
-
function extractVideoPreviewFrame(videoPath) {
|
|
89
|
-
if (!videoPath || /^https?:\/\//i.test(videoPath))
|
|
90
|
-
return null;
|
|
91
|
-
const ext = path.extname(videoPath);
|
|
92
|
-
const stem = ext ? videoPath.slice(0, -ext.length) : videoPath;
|
|
93
|
-
const previewPath = `${stem}.preview.png`;
|
|
94
|
-
const result = spawnSync('ffmpeg', ['-y', '-ss', '0', '-i', videoPath, '-frames:v', '1', '-vf', 'scale=320:-1', previewPath], { stdio: 'ignore', timeout: 15_000 });
|
|
95
|
-
return result.status === 0 ? previewPath : null;
|
|
96
|
-
}
|
|
97
|
-
function appendFileAttachmentHints(text, attachments) {
|
|
98
|
-
const files = attachments
|
|
99
|
-
?.filter((attachment) => attachment.kind !== 'image')
|
|
100
|
-
.filter((attachment) => attachment.path && !/^https?:\/\//i.test(attachment.path));
|
|
101
|
-
if (!files?.length)
|
|
102
|
-
return text;
|
|
103
|
-
const alreadyVisible = files.every((attachment) => text.includes(attachment.path));
|
|
104
|
-
if (alreadyVisible)
|
|
105
|
-
return text;
|
|
106
|
-
const lines = files.map((attachment, index) => {
|
|
107
|
-
const label = attachment.name || `attachment-${index + 1}`;
|
|
108
|
-
const mime = attachment.mimeType ? ` (${attachment.mimeType})` : '';
|
|
109
|
-
return `[${escapeMarkdownLinkLabel(label)}${mime}](<${attachment.path}>)`;
|
|
110
|
-
});
|
|
111
|
-
return [text, '', ...lines].join('\n');
|
|
112
|
-
}
|
|
113
|
-
export function buildCodexQueuedText(text, attachments) {
|
|
114
|
-
const missing = attachments?.filter((attachment) => attachment.path && !/^https?:\/\//i.test(attachment.path) && !text.includes(attachment.path));
|
|
115
|
-
if (!missing?.length)
|
|
116
|
-
return text;
|
|
117
|
-
const lines = missing.map((attachment, index) => {
|
|
118
|
-
const label = escapeMarkdownLinkLabel(attachment.name || `attachment-${index + 1}`);
|
|
119
|
-
return attachment.kind === 'image'
|
|
120
|
-
? ``
|
|
121
|
-
: `[${label}](<${attachment.path}>)`;
|
|
122
|
-
});
|
|
123
|
-
return [text, '', ...lines].join('\n');
|
|
124
|
-
}
|
|
125
|
-
export function buildCodexQueueArgs(threadId, text, attachments) {
|
|
126
|
-
return ['queue', '--thread', threadId, '--message', buildCodexQueuedText(text, attachments)];
|
|
127
|
-
}
|
|
128
|
-
function escapeMarkdownLinkLabel(value) {
|
|
129
|
-
return value.replace(/[[\]\\]/g, '\\$&');
|
|
130
|
-
}
|
|
131
|
-
export class CodexAdapter extends AgentAdapter {
|
|
132
|
-
options;
|
|
133
|
-
type = 'codex';
|
|
134
|
-
process = null;
|
|
135
|
-
workDir = null;
|
|
136
|
-
seq = 0;
|
|
137
|
-
runId = '';
|
|
138
|
-
pendingUsage;
|
|
139
|
-
agentSessionId = null;
|
|
140
|
-
hasEmittedText = false;
|
|
141
|
-
textTail = '';
|
|
142
|
-
terminalState = 'open';
|
|
143
|
-
forceCloseTimer = null;
|
|
144
|
-
stderrBuf = '';
|
|
145
|
-
rpcSeq = 1;
|
|
146
|
-
pendingRequests = new Map();
|
|
147
|
-
appServerReady = null;
|
|
148
|
-
appServerStartupError = null;
|
|
149
|
-
activeTurnId = null;
|
|
150
|
-
deltaItemIds = new Set();
|
|
151
|
-
lastTransientStatus = '';
|
|
152
|
-
agentSystemPrompt = '';
|
|
153
|
-
managedAgentPolicy = null;
|
|
154
|
-
managedPermissionConfig = null;
|
|
155
|
-
configurationRestartRequired = false;
|
|
156
|
-
managedRoomTools = new CodexManagedRoomToolBinding();
|
|
157
|
-
configuredManagedRoomContextToken = null;
|
|
158
|
-
configuredManagedRoomExecutor = null;
|
|
159
|
-
managedRoomToolThreadId = null;
|
|
160
|
-
preparedExternalOwnerThreadId = null;
|
|
161
|
-
constructor(options = {}) {
|
|
162
|
-
super();
|
|
163
|
-
this.options = options;
|
|
164
|
-
}
|
|
165
|
-
externalChannel = null;
|
|
166
|
-
shennianSessionId = null;
|
|
167
|
-
extraEnv = {};
|
|
168
|
-
configure(options) {
|
|
169
|
-
const previousFingerprint = this.configurationFingerprint();
|
|
170
|
-
this.shennianSessionId = options.sessionId ?? null;
|
|
171
|
-
this.externalChannel = options.externalChannel ?? null;
|
|
172
|
-
this.extraEnv = options.env ?? {};
|
|
173
|
-
if ('managedAgentPolicy' in options) {
|
|
174
|
-
this.managedAgentPolicy = options.managedAgentPolicy ?? null;
|
|
175
|
-
}
|
|
176
|
-
if ('managedRoomContextToken' in options || 'managedRoomExecutor' in options) {
|
|
177
|
-
// Reconfiguration invalidates the previous turn immediately. The new
|
|
178
|
-
// token is activated only after any required pre-turn process restart.
|
|
179
|
-
this.managedRoomTools.deactivateTurn();
|
|
180
|
-
this.configuredManagedRoomContextToken = options.managedRoomContextToken?.trim() || null;
|
|
181
|
-
this.configuredManagedRoomExecutor = options.managedRoomExecutor ?? null;
|
|
182
|
-
}
|
|
183
|
-
if (this.managedAgentPolicy)
|
|
184
|
-
this.workDir = this.managedAgentPolicy.adapterPolicy.workDir;
|
|
185
|
-
const nextFingerprint = this.configurationFingerprint();
|
|
186
|
-
if (previousFingerprint !== nextFingerprint && this.process) {
|
|
187
|
-
this.configurationRestartRequired = true;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
configurationFingerprint() {
|
|
191
|
-
const sortedEnv = Object.keys(this.extraEnv)
|
|
192
|
-
.sort()
|
|
193
|
-
.map((key) => [key, this.extraEnv[key] ?? null]);
|
|
194
|
-
return createHash('sha256')
|
|
195
|
-
.update(JSON.stringify({
|
|
196
|
-
sessionId: this.shennianSessionId,
|
|
197
|
-
externalChannel: this.externalChannel,
|
|
198
|
-
env: sortedEnv,
|
|
199
|
-
managedAgentPolicy: this.managedAgentPolicy,
|
|
200
|
-
}))
|
|
201
|
-
.digest('hex');
|
|
202
|
-
}
|
|
203
|
-
buildProcessEnv(runtimeEnv = {}) {
|
|
204
|
-
if (!this.managedAgentPolicy) {
|
|
205
|
-
return buildAgentProcessEnv({ ...runtimeEnv, ...this.extraEnv });
|
|
206
|
-
}
|
|
207
|
-
const providerEnv = Object.fromEntries(Object.entries(this.extraEnv).filter(([key]) => key !== 'SHENNIAN_MANAGED_ROOM_CONTEXT' &&
|
|
208
|
-
key !== 'SHENNIAN_MANAGED_ROOM_IPC_ROOT'));
|
|
209
|
-
return buildManagedAgentProcessEnv({
|
|
210
|
-
agentType: 'codex',
|
|
211
|
-
providerEnv,
|
|
212
|
-
sessionEnv: providerEnv,
|
|
213
|
-
runtimeEnv,
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
async start(_sessionId, workDir, agentSessionId) {
|
|
217
|
-
this.workDir = this.managedAgentPolicy?.adapterPolicy.workDir ?? workDir;
|
|
218
|
-
this.seq = 0;
|
|
219
|
-
if (agentSessionId)
|
|
220
|
-
this.agentSessionId = agentSessionId;
|
|
221
|
-
}
|
|
222
|
-
async prepareSend(_text, modelId, _reasoningEffort, _attachments, _agentSystemPrompt, context) {
|
|
223
|
-
if (!this.agentSessionId || this.activeTurnId)
|
|
224
|
-
return;
|
|
225
|
-
this.preparedExternalOwnerThreadId = null;
|
|
226
|
-
try {
|
|
227
|
-
await this.ensureAppServer(normalizeCodexModelId(modelId));
|
|
228
|
-
}
|
|
229
|
-
catch (error) {
|
|
230
|
-
if (!isCodexActiveWriterError(error))
|
|
231
|
-
throw error;
|
|
232
|
-
if (context?.source === 'room_activation') {
|
|
233
|
-
throw new Error('codex_managed_thread_owned_externally', { cause: error });
|
|
234
|
-
}
|
|
235
|
-
if (this.isThreadTailInterrupted(this.agentSessionId)) {
|
|
236
|
-
throw new Error(CODEX_DESKTOP_SEND_UNAVAILABLE, { cause: error });
|
|
237
|
-
}
|
|
238
|
-
this.preparedExternalOwnerThreadId = this.agentSessionId;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
async send(text, modelId, reasoningEffort, attachments, agentSystemPrompt, context) {
|
|
242
|
-
if (context?.source === 'room_activation' && !this.managedAgentPolicy) {
|
|
243
|
-
throw new Error('managed_agent_policy_missing');
|
|
244
|
-
}
|
|
245
|
-
if (context?.source === 'room_activation' &&
|
|
246
|
-
(!this.configuredManagedRoomContextToken || !this.configuredManagedRoomExecutor)) {
|
|
247
|
-
throw new Error('codex_managed_room_tool_context_unavailable');
|
|
248
|
-
}
|
|
249
|
-
if (this.activeTurnId) {
|
|
250
|
-
await this.interruptActiveTurn().catch(() => { });
|
|
251
|
-
await this.killProcess(true);
|
|
252
|
-
}
|
|
253
|
-
if (this.configurationRestartRequired && this.process)
|
|
254
|
-
await this.killProcess(true);
|
|
255
|
-
this.configurationRestartRequired = false;
|
|
256
|
-
const codexModelId = normalizeCodexModelId(modelId);
|
|
257
|
-
const codexReasoningEffort = normalizeCodexReasoningEffort(reasoningEffort);
|
|
258
|
-
const nextSystemPrompt = agentSystemPrompt?.trim() ?? '';
|
|
259
|
-
if (nextSystemPrompt !== this.agentSystemPrompt) {
|
|
260
|
-
this.agentSystemPrompt = nextSystemPrompt;
|
|
261
|
-
if (this.process)
|
|
262
|
-
await this.killProcess(true);
|
|
263
|
-
}
|
|
264
|
-
if (context?.source === 'room_activation') {
|
|
265
|
-
this.managedRoomTools.configure(this.configuredManagedRoomContextToken, this.configuredManagedRoomExecutor);
|
|
266
|
-
}
|
|
267
|
-
this.runId = randomUUID();
|
|
268
|
-
this.seq = 0;
|
|
269
|
-
this.resetRunState();
|
|
270
|
-
if (this.agentSessionId && this.preparedExternalOwnerThreadId === this.agentSessionId) {
|
|
271
|
-
this.preparedExternalOwnerThreadId = null;
|
|
272
|
-
const queuedSubmissionId = await this.enqueueForExternalOwner(this.agentSessionId, text, attachments);
|
|
273
|
-
return { deliveryMode: 'external_owner_queue', queuedSubmissionId };
|
|
274
|
-
}
|
|
275
|
-
try {
|
|
276
|
-
await this.ensureAppServer(codexModelId);
|
|
277
|
-
}
|
|
278
|
-
catch (error) {
|
|
279
|
-
if (!this.agentSessionId || !isCodexActiveWriterError(error))
|
|
280
|
-
throw error;
|
|
281
|
-
if (context?.source === 'room_activation') {
|
|
282
|
-
throw new Error('codex_managed_thread_owned_externally', { cause: error });
|
|
283
|
-
}
|
|
284
|
-
if (this.isThreadTailInterrupted(this.agentSessionId)) {
|
|
285
|
-
throw new Error(CODEX_DESKTOP_SEND_UNAVAILABLE, { cause: error });
|
|
286
|
-
}
|
|
287
|
-
const queuedSubmissionId = await this.enqueueForExternalOwner(this.agentSessionId, text, attachments);
|
|
288
|
-
return { deliveryMode: 'external_owner_queue', queuedSubmissionId };
|
|
289
|
-
}
|
|
290
|
-
const threadId = this.agentSessionId;
|
|
291
|
-
if (!threadId)
|
|
292
|
-
throw new Error('codex app-server did not create a thread');
|
|
293
|
-
this.emitEvent({ state: 'start', agentSessionId: threadId });
|
|
294
|
-
const response = await this.startTurnWithRecovery(threadId, text, codexModelId, codexReasoningEffort, attachments);
|
|
295
|
-
const activeThreadId = this.agentSessionId ?? threadId;
|
|
296
|
-
this.activeTurnId = response.turn?.id ?? null;
|
|
297
|
-
if (this.managedPermissionConfig &&
|
|
298
|
-
this.activeTurnId &&
|
|
299
|
-
!this.managedRoomTools.bindTurn(activeThreadId, this.activeTurnId)) {
|
|
300
|
-
await this.killProcess();
|
|
301
|
-
throw new Error('codex_managed_room_turn_binding_failed');
|
|
302
|
-
}
|
|
303
|
-
return { deliveryMode: 'direct' };
|
|
304
|
-
}
|
|
305
|
-
isThreadTailInterrupted(threadId) {
|
|
306
|
-
return this.options.isThreadTailInterrupted?.(threadId) ?? isCodexThreadTailInterrupted(threadId);
|
|
307
|
-
}
|
|
308
|
-
async enqueueForExternalOwner(threadId, text, attachments) {
|
|
309
|
-
const spec = resolveBuiltinCommand('codex');
|
|
310
|
-
if (!spec)
|
|
311
|
-
throw new Error('Command "codex" not found. Is OpenAI Codex CLI installed?');
|
|
312
|
-
const args = buildCodexQueueArgs(threadId, text, attachments);
|
|
313
|
-
const proc = spawnAgentCommand(spec, args, {
|
|
314
|
-
cwd: this.workDir ?? undefined,
|
|
315
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
316
|
-
env: this.buildProcessEnv({ NO_COLOR: '1' }),
|
|
317
|
-
});
|
|
318
|
-
return await new Promise((resolve, reject) => {
|
|
319
|
-
let stdout = '';
|
|
320
|
-
let stderr = '';
|
|
321
|
-
let settled = false;
|
|
322
|
-
const finish = (error, queuedSubmissionId) => {
|
|
323
|
-
if (settled)
|
|
324
|
-
return;
|
|
325
|
-
settled = true;
|
|
326
|
-
clearTimeout(timer);
|
|
327
|
-
if (error)
|
|
328
|
-
reject(error);
|
|
329
|
-
else
|
|
330
|
-
resolve(queuedSubmissionId);
|
|
331
|
-
};
|
|
332
|
-
const timer = setTimeout(() => {
|
|
333
|
-
proc.kill('SIGTERM');
|
|
334
|
-
finish(new Error('Codex Desktop message queue timed out'));
|
|
335
|
-
}, 60_000);
|
|
336
|
-
timer.unref?.();
|
|
337
|
-
proc.stdout?.on('data', (chunk) => {
|
|
338
|
-
stdout += chunk.toString();
|
|
339
|
-
});
|
|
340
|
-
proc.stderr?.on('data', (chunk) => {
|
|
341
|
-
stderr += chunk.toString();
|
|
342
|
-
});
|
|
343
|
-
proc.once('error', (error) => finish(error));
|
|
344
|
-
proc.once('close', (code) => {
|
|
345
|
-
const output = `${stdout}\n${stderr}`.trim();
|
|
346
|
-
if (code !== 0) {
|
|
347
|
-
finish(new Error(output || `codex queue exited with code ${code ?? 'unknown'}`));
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
const submissionId = output.match(/Queued message\s+([^\s]+)\s+for thread/i)?.[1];
|
|
351
|
-
finish(undefined, submissionId);
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
async resume(agentSessionId) {
|
|
356
|
-
await this.killProcess();
|
|
357
|
-
this.agentSessionId = agentSessionId;
|
|
358
|
-
this.resetTextState();
|
|
359
|
-
}
|
|
360
|
-
async setTitle(agentSessionId, title, workDir) {
|
|
361
|
-
const threadId = agentSessionId.trim();
|
|
362
|
-
const name = title.replace(/\s+/g, ' ').trim();
|
|
363
|
-
if (!threadId || !name)
|
|
364
|
-
return;
|
|
365
|
-
this.agentSessionId = threadId;
|
|
366
|
-
if (workDir?.trim())
|
|
367
|
-
this.workDir = workDir;
|
|
368
|
-
await this.ensureAppServer();
|
|
369
|
-
await this.sendRpc('thread/name/set', { threadId, name });
|
|
370
|
-
}
|
|
371
|
-
async stop() {
|
|
372
|
-
this.managedRoomTools.deactivateTurn();
|
|
373
|
-
await this.interruptActiveTurn().catch(() => { });
|
|
374
|
-
await this.killProcess();
|
|
375
|
-
}
|
|
376
|
-
async getStatus() {
|
|
377
|
-
const threadId = this.agentSessionId;
|
|
378
|
-
const activeTurnId = this.activeTurnId;
|
|
379
|
-
if (!threadId || !this.process) {
|
|
380
|
-
return { active: false, runId: activeTurnId, runPhase: null, canStop: false };
|
|
381
|
-
}
|
|
382
|
-
let status = null;
|
|
383
|
-
try {
|
|
384
|
-
const response = (await this.sendRpc('thread/read', { threadId, includeTurns: false }, 5_000));
|
|
385
|
-
status = response.thread?.status ?? null;
|
|
386
|
-
}
|
|
387
|
-
catch {
|
|
388
|
-
// If the local control channel is still alive but the status read fails,
|
|
389
|
-
// fall back to activeTurnId. This keeps the stop button available for
|
|
390
|
-
// the running turn while avoiding false positives for idle sessions.
|
|
391
|
-
}
|
|
392
|
-
const active = status?.type === 'active' || Boolean(activeTurnId);
|
|
393
|
-
return {
|
|
394
|
-
active,
|
|
395
|
-
runId: activeTurnId,
|
|
396
|
-
runPhase: active ? this.mapThreadStatusToRunPhase(status) : null,
|
|
397
|
-
canStop: Boolean(active && activeTurnId && this.process),
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
spawnCodex(args) {
|
|
401
|
-
const spec = resolveBuiltinCommand('codex');
|
|
402
|
-
if (!spec) {
|
|
403
|
-
this.emit('error', new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
const proc = spawnAgentCommand(spec, args, {
|
|
407
|
-
cwd: this.workDir ?? undefined,
|
|
408
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
409
|
-
env: this.buildProcessEnv(),
|
|
410
|
-
});
|
|
411
|
-
this.process = proc;
|
|
412
|
-
const rl = createInterface({ input: proc.stdout });
|
|
413
|
-
rl.on('line', (line) => {
|
|
414
|
-
if (!line.trim())
|
|
415
|
-
return;
|
|
416
|
-
try {
|
|
417
|
-
const obj = JSON.parse(line);
|
|
418
|
-
this.handleStreamEvent(obj);
|
|
419
|
-
}
|
|
420
|
-
catch {
|
|
421
|
-
this.emitEvent({ state: 'delta', text: line });
|
|
422
|
-
}
|
|
423
|
-
});
|
|
424
|
-
let stderrBuf = '';
|
|
425
|
-
proc.stderr?.on('data', (chunk) => {
|
|
426
|
-
stderrBuf += chunk.toString();
|
|
427
|
-
});
|
|
428
|
-
proc.on('close', (code) => {
|
|
429
|
-
if (this.process !== proc)
|
|
430
|
-
return;
|
|
431
|
-
this.process = null;
|
|
432
|
-
this.clearForceCloseTimer();
|
|
433
|
-
this.handleProcessClose(code, stderrBuf);
|
|
434
|
-
});
|
|
435
|
-
proc.on('error', (err) => {
|
|
436
|
-
if (this.process !== proc)
|
|
437
|
-
return;
|
|
438
|
-
this.process = null;
|
|
439
|
-
if (err.code === 'ENOENT') {
|
|
440
|
-
this.emit('error', new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));
|
|
441
|
-
}
|
|
442
|
-
else {
|
|
443
|
-
this.emit('error', err);
|
|
444
|
-
}
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
spawnAppServer() {
|
|
448
|
-
const spec = resolveBuiltinCommand('codex');
|
|
449
|
-
if (!spec) {
|
|
450
|
-
this.emit('error', new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
const args = ['app-server', '--listen', 'stdio://'];
|
|
454
|
-
const modelInstructionsFile = this.options.modelInstructionsFile ??
|
|
455
|
-
ensurePlatformInstructionsFile(this.workDir ?? process.cwd(), this.externalChannel, this.shennianSessionId ?? undefined, this.agentSystemPrompt);
|
|
456
|
-
if (modelInstructionsFile) {
|
|
457
|
-
args.push('-c', `model_instructions_file=${JSON.stringify(modelInstructionsFile)}`);
|
|
458
|
-
}
|
|
459
|
-
const proc = spawnAgentCommand(spec, args, {
|
|
460
|
-
cwd: this.workDir ?? undefined,
|
|
461
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
462
|
-
env: this.buildProcessEnv({ NO_COLOR: '1' }),
|
|
463
|
-
});
|
|
464
|
-
this.process = proc;
|
|
465
|
-
this.stderrBuf = '';
|
|
466
|
-
this.appServerStartupError = null;
|
|
467
|
-
const rl = createInterface({ input: proc.stdout });
|
|
468
|
-
rl.on('line', (line) => {
|
|
469
|
-
if (!line.trim())
|
|
470
|
-
return;
|
|
471
|
-
let msg;
|
|
472
|
-
try {
|
|
473
|
-
msg = JSON.parse(line);
|
|
474
|
-
}
|
|
475
|
-
catch {
|
|
476
|
-
this.handleAppServerNonJsonStdout(line);
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
this.handleAppServerMessage(msg);
|
|
480
|
-
});
|
|
481
|
-
proc.stderr?.on('data', (chunk) => {
|
|
482
|
-
this.stderrBuf += chunk.toString();
|
|
483
|
-
});
|
|
484
|
-
proc.on('close', (code) => {
|
|
485
|
-
if (this.process !== proc)
|
|
486
|
-
return;
|
|
487
|
-
const hadActiveTurn = this.activeTurnId != null;
|
|
488
|
-
this.process = null;
|
|
489
|
-
this.appServerReady = null;
|
|
490
|
-
this.activeTurnId = null;
|
|
491
|
-
this.managedRoomTools.reset();
|
|
492
|
-
this.clearConfiguredManagedRoomContext();
|
|
493
|
-
this.rejectAllPending(new Error(normalizeCodexStderr(this.stderrBuf) || `codex app-server exited with code ${code}`));
|
|
494
|
-
if (this.terminalState === 'open' && hadActiveTurn) {
|
|
495
|
-
this.handleProcessClose(code, this.stderrBuf);
|
|
496
|
-
}
|
|
497
|
-
});
|
|
498
|
-
proc.on('error', (err) => {
|
|
499
|
-
if (this.process !== proc)
|
|
500
|
-
return;
|
|
501
|
-
this.process = null;
|
|
502
|
-
this.managedRoomTools.reset();
|
|
503
|
-
this.clearConfiguredManagedRoomContext();
|
|
504
|
-
this.rejectAllPending(err);
|
|
505
|
-
if (err.code === 'ENOENT') {
|
|
506
|
-
this.emit('error', new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));
|
|
507
|
-
}
|
|
508
|
-
else {
|
|
509
|
-
this.emit('error', err);
|
|
510
|
-
}
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
async ensureAppServer(modelId) {
|
|
514
|
-
if (this.appServerReady)
|
|
515
|
-
return this.appServerReady;
|
|
516
|
-
this.spawnAppServer();
|
|
517
|
-
this.appServerReady = this.initializeAppServer(modelId).catch(async (error) => {
|
|
518
|
-
this.appServerReady = null;
|
|
519
|
-
await this.killProcess();
|
|
520
|
-
throw error;
|
|
521
|
-
});
|
|
522
|
-
return this.appServerReady;
|
|
523
|
-
}
|
|
524
|
-
async initializeAppServer(modelId) {
|
|
525
|
-
const codexModelId = normalizeCodexModelId(modelId);
|
|
526
|
-
await this.sendRpc('initialize', {
|
|
527
|
-
clientInfo: CODEX_APP_SERVER_CLIENT_INFO,
|
|
528
|
-
capabilities: { experimentalApi: true },
|
|
529
|
-
});
|
|
530
|
-
this.managedPermissionConfig = this.managedAgentPolicy
|
|
531
|
-
? await this.buildManagedPermissionConfig()
|
|
532
|
-
: null;
|
|
533
|
-
if (this.managedPermissionConfig && !this.managedRoomTools.isConfigured()) {
|
|
534
|
-
this.managedRoomTools.configure(this.configuredManagedRoomContextToken, this.configuredManagedRoomExecutor);
|
|
535
|
-
if (!this.managedRoomTools.isConfigured()) {
|
|
536
|
-
throw new Error('codex_managed_room_tool_context_unavailable');
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
// Dynamic tools are persisted only when a thread is created. A legacy
|
|
540
|
-
// managed Codex thread therefore cannot be resumed safely: start a fresh
|
|
541
|
-
// native thread with the host tool surface instead of silently losing Room
|
|
542
|
-
// access or falling back to the CLI/socket transport.
|
|
543
|
-
if (this.agentSessionId &&
|
|
544
|
-
this.managedPermissionConfig &&
|
|
545
|
-
this.agentSessionId !== this.managedRoomToolThreadId) {
|
|
546
|
-
if (!this.managedRoomTools.isConfigured()) {
|
|
547
|
-
throw new Error('codex_managed_room_tool_context_unavailable');
|
|
548
|
-
}
|
|
549
|
-
this.agentSessionId = null;
|
|
550
|
-
}
|
|
551
|
-
if (this.agentSessionId) {
|
|
552
|
-
const resumeParams = {
|
|
553
|
-
threadId: this.agentSessionId,
|
|
554
|
-
cwd: this.workDir,
|
|
555
|
-
approvalPolicy: 'never',
|
|
556
|
-
...(this.managedPermissionConfig
|
|
557
|
-
? { config: this.managedPermissionConfig.threadConfig }
|
|
558
|
-
: { sandbox: 'danger-full-access' }),
|
|
559
|
-
persistExtendedHistory: true,
|
|
560
|
-
...(this.options.hidden ? { threadSource: 'subagent' } : {}),
|
|
561
|
-
...(codexModelId ? { model: codexModelId } : {}),
|
|
562
|
-
};
|
|
563
|
-
let response;
|
|
564
|
-
try {
|
|
565
|
-
response = (await this.sendRpc('thread/resume', {
|
|
566
|
-
...resumeParams,
|
|
567
|
-
excludeTurns: true,
|
|
568
|
-
}));
|
|
569
|
-
}
|
|
570
|
-
catch (error) {
|
|
571
|
-
if (!isCodexExcludeTurnsUnsupportedError(error))
|
|
572
|
-
throw error;
|
|
573
|
-
response = (await this.sendRpc('thread/resume', resumeParams));
|
|
574
|
-
}
|
|
575
|
-
this.agentSessionId = response.thread?.id ?? this.agentSessionId;
|
|
576
|
-
if (this.managedPermissionConfig) {
|
|
577
|
-
assertCodexManagedPermissionProfile(response, this.managedPermissionConfig.profileId);
|
|
578
|
-
this.managedRoomTools.bindThread(this.agentSessionId);
|
|
579
|
-
this.managedRoomToolThreadId = this.agentSessionId;
|
|
580
|
-
}
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
const projectId = this.options.hidden ? undefined : await this.resolveCodexProjectId();
|
|
584
|
-
const startParams = {
|
|
585
|
-
cwd: this.workDir,
|
|
586
|
-
approvalPolicy: 'never',
|
|
587
|
-
...(this.managedPermissionConfig
|
|
588
|
-
? { config: this.managedPermissionConfig.threadConfig }
|
|
589
|
-
: { sandbox: 'danger-full-access' }),
|
|
590
|
-
serviceName: 'Shennian',
|
|
591
|
-
threadSource: this.options.hidden ? 'subagent' : 'user',
|
|
592
|
-
experimentalRawEvents: false,
|
|
593
|
-
persistExtendedHistory: true,
|
|
594
|
-
...(this.managedPermissionConfig
|
|
595
|
-
? { dynamicTools: CODEX_MANAGED_ROOM_DYNAMIC_TOOLS }
|
|
596
|
-
: {}),
|
|
597
|
-
...(projectId ? { projectId } : {}),
|
|
598
|
-
...(codexModelId ? { model: codexModelId } : {}),
|
|
599
|
-
};
|
|
600
|
-
let response;
|
|
601
|
-
if (this.options.hidden && !this.managedPermissionConfig) {
|
|
602
|
-
response = (await this.sendRpc('thread/start', startParams));
|
|
603
|
-
}
|
|
604
|
-
else {
|
|
605
|
-
try {
|
|
606
|
-
response = (await this.sendRpc('thread/start', {
|
|
607
|
-
...startParams,
|
|
608
|
-
historyMode: 'paginated',
|
|
609
|
-
}));
|
|
610
|
-
}
|
|
611
|
-
catch (error) {
|
|
612
|
-
if (!isCodexPaginatedHistoryUnsupportedError(error))
|
|
613
|
-
throw error;
|
|
614
|
-
throw new Error('当前 Codex CLI 不支持创建可在 Codex Desktop 中继续的新会话,请手动升级 Codex CLI 后重试。神念不会自动升级,也不会降级创建不可见的 legacy 会话。', { cause: error });
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
const threadId = response.thread?.id;
|
|
618
|
-
if (!threadId)
|
|
619
|
-
throw new Error('codex app-server thread/start returned no thread id');
|
|
620
|
-
if (this.managedPermissionConfig) {
|
|
621
|
-
assertCodexManagedPermissionProfile(response, this.managedPermissionConfig.profileId);
|
|
622
|
-
this.managedRoomTools.bindThread(threadId);
|
|
623
|
-
this.managedRoomToolThreadId = threadId;
|
|
624
|
-
}
|
|
625
|
-
this.agentSessionId = threadId;
|
|
626
|
-
}
|
|
627
|
-
async buildManagedPermissionConfig() {
|
|
628
|
-
const compiled = this.managedAgentPolicy;
|
|
629
|
-
if (!compiled)
|
|
630
|
-
throw new Error('managed_agent_policy_missing');
|
|
631
|
-
const mcpServers = compiled.adapterPolicy.externalCapabilities.mcp
|
|
632
|
-
? []
|
|
633
|
-
: extractCodexMcpServerNames(await this.sendRpc('config/read', { cwd: compiled.adapterPolicy.workDir }));
|
|
634
|
-
return compileCodexManagedPermissions(resolveManagedAgentAdapterEnforcement(compiled), compiled.adapterPolicy.workDir, {
|
|
635
|
-
configuredMcpServerNames: mcpServers,
|
|
636
|
-
shellSubprocessPolicy: compiled.environmentPolicy.shellSubprocess,
|
|
637
|
-
});
|
|
638
|
-
}
|
|
639
|
-
async resolveCodexProjectId() {
|
|
640
|
-
const workDir = this.workDir?.trim();
|
|
641
|
-
if (!workDir)
|
|
642
|
-
return undefined;
|
|
643
|
-
try {
|
|
644
|
-
const projects = [];
|
|
645
|
-
let cursor;
|
|
646
|
-
do {
|
|
647
|
-
const response = (await this.sendRpc('project/list', {
|
|
648
|
-
limit: 100,
|
|
649
|
-
...(cursor ? { cursor } : {}),
|
|
650
|
-
}));
|
|
651
|
-
projects.push(...(response.data ?? []));
|
|
652
|
-
cursor = response.nextCursor;
|
|
653
|
-
} while (cursor);
|
|
654
|
-
const existingProjectId = selectCodexProjectIdForWorkDir(projects, workDir);
|
|
655
|
-
if (existingProjectId)
|
|
656
|
-
return existingProjectId;
|
|
657
|
-
const normalizedWorkDir = path.resolve(workDir);
|
|
658
|
-
const idempotencyKey = `shennian-project-${createHash('sha256')
|
|
659
|
-
.update(normalizedWorkDir)
|
|
660
|
-
.digest('hex')}`;
|
|
661
|
-
const response = (await this.sendRpc('project/create', {
|
|
662
|
-
idempotencyKey,
|
|
663
|
-
name: path.basename(normalizedWorkDir) || normalizedWorkDir,
|
|
664
|
-
roots: [{ path: normalizedWorkDir }],
|
|
665
|
-
metadata: { createdBy: 'Shennian' },
|
|
666
|
-
}));
|
|
667
|
-
const projectId = response.project?.id;
|
|
668
|
-
if (!projectId)
|
|
669
|
-
throw new Error('codex app-server project/create returned no project id');
|
|
670
|
-
return projectId;
|
|
671
|
-
}
|
|
672
|
-
catch (error) {
|
|
673
|
-
if (!isCodexProjectApiUnsupportedError(error))
|
|
674
|
-
throw error;
|
|
675
|
-
throw new Error('当前 Codex CLI 不支持创建可归属到 Codex Desktop 项目的新会话,请手动升级 Codex CLI 后重试。神念不会自动升级,也不会直接修改 Codex Desktop 的私有侧栏索引。', { cause: error });
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
async startTurnWithRecovery(threadId, text, codexModelId, reasoningEffort, attachments) {
|
|
679
|
-
try {
|
|
680
|
-
return await this.startTurn(threadId, text, codexModelId, reasoningEffort, attachments);
|
|
681
|
-
}
|
|
682
|
-
catch (error) {
|
|
683
|
-
if (!isMissingCodexRolloutError(error))
|
|
684
|
-
throw error;
|
|
685
|
-
if (this.managedPermissionConfig) {
|
|
686
|
-
// A binding may outlive its Codex history (for example after a local
|
|
687
|
-
// cleanup or migration). Recreate the managed thread and replay the
|
|
688
|
-
// same real message once; never manufacture a placeholder turn merely
|
|
689
|
-
// to materialize a rollout.
|
|
690
|
-
await this.killProcess(true);
|
|
691
|
-
this.agentSessionId = null;
|
|
692
|
-
this.managedRoomToolThreadId = null;
|
|
693
|
-
await this.ensureAppServer(codexModelId);
|
|
694
|
-
const replacementThreadId = this.agentSessionId;
|
|
695
|
-
if (!replacementThreadId) {
|
|
696
|
-
throw new Error('codex app-server did not recreate the managed thread', {
|
|
697
|
-
cause: error,
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
this.emitEvent({ state: 'start', agentSessionId: replacementThreadId });
|
|
701
|
-
return await this.startTurn(replacementThreadId, text, codexModelId, reasoningEffort, attachments);
|
|
702
|
-
}
|
|
703
|
-
await this.killProcess();
|
|
704
|
-
await this.ensureAppServer(codexModelId);
|
|
705
|
-
return await this.startTurn(threadId, text, codexModelId, reasoningEffort, attachments);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
async startTurn(threadId, text, codexModelId, reasoningEffort, attachments) {
|
|
709
|
-
try {
|
|
710
|
-
return (await this.sendRpc('turn/start', {
|
|
711
|
-
threadId,
|
|
712
|
-
input: [buildCodexTextInput(text, attachments)],
|
|
713
|
-
approvalPolicy: 'never',
|
|
714
|
-
...(this.managedPermissionConfig ? {} : { sandboxPolicy: { type: 'dangerFullAccess' } }),
|
|
715
|
-
...(codexModelId ? { model: codexModelId } : {}),
|
|
716
|
-
...(reasoningEffort ? { effort: reasoningEffort } : {}),
|
|
717
|
-
}));
|
|
718
|
-
}
|
|
719
|
-
catch (error) {
|
|
720
|
-
if (reasoningEffort && isCodexUnsupportedEffortError(error)) {
|
|
721
|
-
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 });
|
|
722
|
-
}
|
|
723
|
-
throw error;
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
async interruptActiveTurn() {
|
|
727
|
-
const threadId = this.agentSessionId;
|
|
728
|
-
const turnId = this.activeTurnId;
|
|
729
|
-
this.activeTurnId = null;
|
|
730
|
-
this.managedRoomTools.deactivateTurn();
|
|
731
|
-
if (!threadId || !turnId || !this.process)
|
|
732
|
-
return;
|
|
733
|
-
await this.sendRpc('turn/interrupt', { threadId, turnId }, 5_000);
|
|
734
|
-
}
|
|
735
|
-
sendRpc(method, params, timeoutMs = 60_000) {
|
|
736
|
-
if (this.appServerStartupError)
|
|
737
|
-
return Promise.reject(this.appServerStartupError);
|
|
738
|
-
const proc = this.process;
|
|
739
|
-
if (!proc?.stdin)
|
|
740
|
-
return Promise.reject(new Error('codex app-server is not running'));
|
|
741
|
-
const id = this.rpcSeq++;
|
|
742
|
-
const payload = JSON.stringify({ id, method, params });
|
|
743
|
-
return new Promise((resolve, reject) => {
|
|
744
|
-
const timer = setTimeout(() => {
|
|
745
|
-
this.pendingRequests.delete(id);
|
|
746
|
-
reject(new Error(`codex app-server request timed out: ${method}`));
|
|
747
|
-
}, timeoutMs);
|
|
748
|
-
this.pendingRequests.set(id, { resolve, reject, timer });
|
|
749
|
-
proc.stdin.write(`${payload}\n`, (error) => {
|
|
750
|
-
if (!error)
|
|
751
|
-
return;
|
|
752
|
-
clearTimeout(timer);
|
|
753
|
-
this.pendingRequests.delete(id);
|
|
754
|
-
reject(error);
|
|
755
|
-
});
|
|
756
|
-
});
|
|
757
|
-
}
|
|
758
|
-
handleAppServerMessage(msg) {
|
|
759
|
-
if (msg.method === 'item/tool/call' && msg.id != null) {
|
|
760
|
-
void this.handleManagedRoomToolCall(msg.id, msg.params ?? {});
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
if (msg.id != null) {
|
|
764
|
-
const pending = typeof msg.id === 'number' ? this.pendingRequests.get(msg.id) : undefined;
|
|
765
|
-
if (pending) {
|
|
766
|
-
clearTimeout(pending.timer);
|
|
767
|
-
this.pendingRequests.delete(msg.id);
|
|
768
|
-
if (msg.error) {
|
|
769
|
-
pending.reject(new Error(msg.error.message || `codex app-server error ${msg.error.code ?? ''}`.trim()));
|
|
770
|
-
}
|
|
771
|
-
else {
|
|
772
|
-
pending.resolve(msg.result);
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
if (!msg.method)
|
|
777
|
-
return;
|
|
778
|
-
const params = msg.params ?? {};
|
|
779
|
-
switch (msg.method) {
|
|
780
|
-
case 'turn/started': {
|
|
781
|
-
const turn = typeof params.turn === 'object' && params.turn !== null
|
|
782
|
-
? params.turn
|
|
783
|
-
: null;
|
|
784
|
-
const turnId = typeof turn?.id === 'string' ? turn.id : null;
|
|
785
|
-
const threadId = typeof params.threadId === 'string' ? params.threadId : '';
|
|
786
|
-
if (turnId) {
|
|
787
|
-
this.activeTurnId = turnId;
|
|
788
|
-
if (this.managedPermissionConfig &&
|
|
789
|
-
!this.managedRoomTools.bindTurn(threadId, turnId)) {
|
|
790
|
-
this.emitErrorIfOpen('codex_managed_room_turn_binding_failed');
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
break;
|
|
794
|
-
}
|
|
795
|
-
case 'thread/status/changed': {
|
|
796
|
-
const threadId = typeof params.threadId === 'string' ? params.threadId : '';
|
|
797
|
-
if (threadId && this.agentSessionId && threadId !== this.agentSessionId)
|
|
798
|
-
break;
|
|
799
|
-
const status = typeof params.status === 'object' && params.status !== null
|
|
800
|
-
? params.status
|
|
801
|
-
: null;
|
|
802
|
-
if (status?.type === 'active') {
|
|
803
|
-
this.emitEvent({ state: 'heartbeat', runPhase: this.mapThreadStatusToRunPhase(status) });
|
|
804
|
-
}
|
|
805
|
-
break;
|
|
806
|
-
}
|
|
807
|
-
case 'item/agentMessage/delta': {
|
|
808
|
-
const itemId = typeof params.itemId === 'string' ? params.itemId : '';
|
|
809
|
-
if (itemId)
|
|
810
|
-
this.deltaItemIds.add(itemId);
|
|
811
|
-
if (typeof params.delta === 'string')
|
|
812
|
-
this.emitText(params.delta);
|
|
813
|
-
break;
|
|
814
|
-
}
|
|
815
|
-
case 'item/completed':
|
|
816
|
-
this.handleAppServerCompletedItem(params);
|
|
817
|
-
break;
|
|
818
|
-
case 'thread/tokenUsage/updated':
|
|
819
|
-
this.handleAppServerUsage(params);
|
|
820
|
-
break;
|
|
821
|
-
case 'turn/completed': {
|
|
822
|
-
const turn = typeof params.turn === 'object' && params.turn !== null
|
|
823
|
-
? params.turn
|
|
824
|
-
: null;
|
|
825
|
-
const turnId = typeof turn?.id === 'string' ? turn.id : null;
|
|
826
|
-
if (!this.activeTurnId || turnId === this.activeTurnId) {
|
|
827
|
-
this.activeTurnId = null;
|
|
828
|
-
this.managedRoomTools.deactivateTurn();
|
|
829
|
-
this.emitFinalIfOpen();
|
|
830
|
-
}
|
|
831
|
-
break;
|
|
832
|
-
}
|
|
833
|
-
case 'error': {
|
|
834
|
-
const message = extractAppServerErrorMessage(params);
|
|
835
|
-
if (isTransientCodexErrorMessage(message)) {
|
|
836
|
-
this.emitTransientStatus(message);
|
|
837
|
-
return;
|
|
838
|
-
}
|
|
839
|
-
this.emitErrorIfOpen(message);
|
|
840
|
-
break;
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
async handleManagedRoomToolCall(id, params) {
|
|
845
|
-
const result = await this.managedRoomTools.execute(params);
|
|
846
|
-
this.writeAppServerResponse(id, result);
|
|
847
|
-
}
|
|
848
|
-
writeAppServerResponse(id, result) {
|
|
849
|
-
const proc = this.process;
|
|
850
|
-
if (!proc?.stdin)
|
|
851
|
-
return;
|
|
852
|
-
proc.stdin.write(`${JSON.stringify({ id, result })}\n`);
|
|
853
|
-
}
|
|
854
|
-
mapThreadStatusToRunPhase(status) {
|
|
855
|
-
const flags = status?.type === 'active' && Array.isArray(status.activeFlags) ? status.activeFlags : [];
|
|
856
|
-
if (flags.includes('waitingOnApproval'))
|
|
857
|
-
return 'waiting_approval';
|
|
858
|
-
if (flags.includes('waitingOnUserInput'))
|
|
859
|
-
return 'waiting_user_input';
|
|
860
|
-
return 'thinking';
|
|
861
|
-
}
|
|
862
|
-
handleAppServerNonJsonStdout(line) {
|
|
863
|
-
const normalized = normalizeTerminalText(line);
|
|
864
|
-
if (!looksLikeCodexInteractiveAuthPrompt(normalized))
|
|
865
|
-
return;
|
|
866
|
-
this.failAppServerStartup(new Error('Codex 未登录。请先在这台电脑的终端运行 `codex`,按提示完成 ChatGPT 登录或配置 API key,然后再回到神念发送消息。'));
|
|
867
|
-
}
|
|
868
|
-
failAppServerStartup(error) {
|
|
869
|
-
if (this.appServerStartupError)
|
|
870
|
-
return;
|
|
871
|
-
this.appServerStartupError = error;
|
|
872
|
-
this.rejectAllPending(error);
|
|
873
|
-
this.process?.kill('SIGTERM');
|
|
874
|
-
}
|
|
875
|
-
handleAppServerCompletedItem(params) {
|
|
876
|
-
const item = typeof params.item === 'object' && params.item !== null
|
|
877
|
-
? params.item
|
|
878
|
-
: null;
|
|
879
|
-
if (!item || typeof item.type !== 'string')
|
|
880
|
-
return;
|
|
881
|
-
if (isCodexCollabAgentItem(item))
|
|
882
|
-
return;
|
|
883
|
-
switch (item.type) {
|
|
884
|
-
case 'agentMessage': {
|
|
885
|
-
const id = typeof item.id === 'string' ? item.id : '';
|
|
886
|
-
if (id && this.deltaItemIds.has(id))
|
|
887
|
-
return;
|
|
888
|
-
if (typeof item.text === 'string')
|
|
889
|
-
this.emitText(item.text, 'block');
|
|
890
|
-
break;
|
|
891
|
-
}
|
|
892
|
-
case 'commandExecution': {
|
|
893
|
-
const command = typeof item.command === 'string' ? item.command : '';
|
|
894
|
-
const output = typeof item.aggregatedOutput === 'string' ? item.aggregatedOutput : '';
|
|
895
|
-
if (command) {
|
|
896
|
-
this.emitEvent({ state: 'tool-call', name: 'command_execution', args: { command } });
|
|
897
|
-
}
|
|
898
|
-
this.emitEvent({
|
|
899
|
-
state: 'tool-result',
|
|
900
|
-
name: 'command_execution',
|
|
901
|
-
result: [output, item.exitCode != null ? `\n(exit ${item.exitCode})` : '']
|
|
902
|
-
.join('')
|
|
903
|
-
.trim(),
|
|
904
|
-
});
|
|
905
|
-
break;
|
|
906
|
-
}
|
|
907
|
-
case 'mcpToolCall':
|
|
908
|
-
case 'dynamicToolCall': {
|
|
909
|
-
const tool = typeof item.tool === 'string' ? item.tool : item.type;
|
|
910
|
-
if (isCodexCollabAgentToolName(tool))
|
|
911
|
-
return;
|
|
912
|
-
const args = typeof item.arguments === 'object' && item.arguments !== null
|
|
913
|
-
? item.arguments
|
|
914
|
-
: {};
|
|
915
|
-
this.emitEvent({ state: 'tool-call', name: tool, args });
|
|
916
|
-
this.emitEvent({
|
|
917
|
-
state: 'tool-result',
|
|
918
|
-
name: tool,
|
|
919
|
-
result: safeStringify(item.error ?? item.result ?? item.contentItems ?? ''),
|
|
920
|
-
});
|
|
921
|
-
break;
|
|
922
|
-
}
|
|
923
|
-
case 'fileChange': {
|
|
924
|
-
// File-change telemetry is useful to the local Codex host, but not to a
|
|
925
|
-
// remote control surface. Drop it before it enters Shennian's event,
|
|
926
|
-
// persistence, and relay pipeline.
|
|
927
|
-
break;
|
|
928
|
-
}
|
|
929
|
-
case 'approvalRequest':
|
|
930
|
-
case 'approval_request':
|
|
931
|
-
case 'permissionRequest':
|
|
932
|
-
case 'permission_request':
|
|
933
|
-
case 'userInputRequired':
|
|
934
|
-
case 'user_input_required': {
|
|
935
|
-
this.emitApprovalPending(item);
|
|
936
|
-
break;
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
handleAppServerUsage(params) {
|
|
941
|
-
const tokenUsage = typeof params.tokenUsage === 'object' && params.tokenUsage !== null
|
|
942
|
-
? params.tokenUsage
|
|
943
|
-
: null;
|
|
944
|
-
const last = typeof tokenUsage?.last === 'object' && tokenUsage.last !== null
|
|
945
|
-
? tokenUsage.last
|
|
946
|
-
: null;
|
|
947
|
-
const total = typeof tokenUsage?.total === 'object' && tokenUsage.total !== null
|
|
948
|
-
? tokenUsage.total
|
|
949
|
-
: null;
|
|
950
|
-
const usage = last ?? total;
|
|
951
|
-
if (!usage)
|
|
952
|
-
return;
|
|
953
|
-
const input = typeof usage.inputTokens === 'number' ? usage.inputTokens : undefined;
|
|
954
|
-
const output = typeof usage.outputTokens === 'number' ? usage.outputTokens : undefined;
|
|
955
|
-
if (input != null || output != null) {
|
|
956
|
-
this.pendingUsage = {
|
|
957
|
-
inputTokens: input ?? 0,
|
|
958
|
-
outputTokens: output ?? 0,
|
|
959
|
-
};
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
handleCodexItemLine(obj) {
|
|
963
|
-
const item = obj.item;
|
|
964
|
-
if (!item?.type)
|
|
965
|
-
return;
|
|
966
|
-
if (isCodexLegacyCollabAgentItem(item))
|
|
967
|
-
return;
|
|
968
|
-
switch (item.type) {
|
|
969
|
-
case 'agent_message':
|
|
970
|
-
case 'reasoning': {
|
|
971
|
-
if (obj.type === 'item.completed' && item.text) {
|
|
972
|
-
this.emitText(item.text, 'block');
|
|
973
|
-
}
|
|
974
|
-
break;
|
|
975
|
-
}
|
|
976
|
-
case 'command_execution': {
|
|
977
|
-
if (obj.type === 'item.started' && item.command) {
|
|
978
|
-
this.emitEvent({
|
|
979
|
-
state: 'tool-call',
|
|
980
|
-
name: 'command_execution',
|
|
981
|
-
args: { command: item.command },
|
|
982
|
-
});
|
|
983
|
-
}
|
|
984
|
-
if (obj.type === 'item.completed') {
|
|
985
|
-
const parts = [
|
|
986
|
-
item.aggregated_output ?? '',
|
|
987
|
-
item.exit_code != null ? `\n(exit ${item.exit_code})` : '',
|
|
988
|
-
];
|
|
989
|
-
this.emitEvent({
|
|
990
|
-
state: 'tool-result',
|
|
991
|
-
name: 'command_execution',
|
|
992
|
-
result: parts.join('').trim() || String(item.status ?? ''),
|
|
993
|
-
});
|
|
994
|
-
}
|
|
995
|
-
break;
|
|
996
|
-
}
|
|
997
|
-
case 'mcp_tool_call': {
|
|
998
|
-
if (item.tool && isCodexCollabAgentToolName(item.tool))
|
|
999
|
-
return;
|
|
1000
|
-
if (obj.type === 'item.started') {
|
|
1001
|
-
this.emitEvent({
|
|
1002
|
-
state: 'tool-call',
|
|
1003
|
-
name: item.tool ?? 'mcp_tool',
|
|
1004
|
-
args: {
|
|
1005
|
-
server: item.server,
|
|
1006
|
-
...(item.arguments && typeof item.arguments === 'object' ? item.arguments : {}),
|
|
1007
|
-
},
|
|
1008
|
-
});
|
|
1009
|
-
}
|
|
1010
|
-
if (obj.type === 'item.completed') {
|
|
1011
|
-
const errMsg = item.error && typeof item.error === 'object' ? item.error.message : undefined;
|
|
1012
|
-
this.emitEvent({
|
|
1013
|
-
state: 'tool-result',
|
|
1014
|
-
name: item.tool ?? 'mcp_tool',
|
|
1015
|
-
result: errMsg ?? safeStringify(item.result),
|
|
1016
|
-
});
|
|
1017
|
-
}
|
|
1018
|
-
break;
|
|
1019
|
-
}
|
|
1020
|
-
case 'file_change': {
|
|
1021
|
-
// Keep legacy exec compatibility aligned with the managed App Server:
|
|
1022
|
-
// file-change telemetry never leaves the local Codex client.
|
|
1023
|
-
break;
|
|
1024
|
-
}
|
|
1025
|
-
case 'web_search': {
|
|
1026
|
-
if (obj.type === 'item.completed' && item.query) {
|
|
1027
|
-
this.emitText(`[web_search] ${item.query}`, 'block');
|
|
1028
|
-
}
|
|
1029
|
-
break;
|
|
1030
|
-
}
|
|
1031
|
-
case 'todo_list': {
|
|
1032
|
-
if (obj.type === 'item.completed' && item.items?.length) {
|
|
1033
|
-
const lines = item.items.map((t) => `- [${t.completed ? 'x' : ' '}] ${t.text}`).join('\n');
|
|
1034
|
-
this.emitText(lines, 'block');
|
|
1035
|
-
}
|
|
1036
|
-
break;
|
|
1037
|
-
}
|
|
1038
|
-
case 'approval_request':
|
|
1039
|
-
case 'approvalRequest':
|
|
1040
|
-
case 'permission_request':
|
|
1041
|
-
case 'permissionRequest':
|
|
1042
|
-
case 'user_input_required':
|
|
1043
|
-
case 'userInputRequired': {
|
|
1044
|
-
if (obj.type === 'item.started' || obj.type === 'item.completed') {
|
|
1045
|
-
this.emitApprovalPending(item);
|
|
1046
|
-
}
|
|
1047
|
-
break;
|
|
1048
|
-
}
|
|
1049
|
-
case 'error': {
|
|
1050
|
-
if (obj.type === 'item.completed' && item.message) {
|
|
1051
|
-
this.emitEvent({ state: 'error', message: item.message });
|
|
1052
|
-
}
|
|
1053
|
-
break;
|
|
1054
|
-
}
|
|
1055
|
-
default:
|
|
1056
|
-
break;
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
handleStreamEvent(obj) {
|
|
1060
|
-
const topType = obj.type ?? obj.event;
|
|
1061
|
-
if (topType?.startsWith('item.')) {
|
|
1062
|
-
this.handleCodexItemLine(obj);
|
|
1063
|
-
}
|
|
1064
|
-
switch (topType) {
|
|
1065
|
-
case 'turn.completed': {
|
|
1066
|
-
const u = obj.usage;
|
|
1067
|
-
if (u) {
|
|
1068
|
-
const input = u.input_tokens ?? u.prompt_tokens;
|
|
1069
|
-
const output = u.output_tokens ?? u.completion_tokens;
|
|
1070
|
-
if (input != null || output != null) {
|
|
1071
|
-
this.pendingUsage = {
|
|
1072
|
-
inputTokens: input ?? 0,
|
|
1073
|
-
outputTokens: output ?? 0,
|
|
1074
|
-
};
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
this.emitFinalIfOpen();
|
|
1078
|
-
return;
|
|
1079
|
-
}
|
|
1080
|
-
case 'turn.failed': {
|
|
1081
|
-
const msg = typeof obj.error === 'object' && obj.error?.message
|
|
1082
|
-
? obj.error.message
|
|
1083
|
-
: typeof obj.error === 'string'
|
|
1084
|
-
? obj.error
|
|
1085
|
-
: 'turn failed';
|
|
1086
|
-
this.emitErrorIfOpen(msg);
|
|
1087
|
-
return;
|
|
1088
|
-
}
|
|
1089
|
-
case 'error': {
|
|
1090
|
-
const m = typeof obj.message === 'string' ? obj.message : '';
|
|
1091
|
-
if (m.includes('Reconnecting'))
|
|
1092
|
-
return;
|
|
1093
|
-
this.emitErrorIfOpen(m || (typeof obj.error === 'string' ? obj.error : 'stream error'));
|
|
1094
|
-
return;
|
|
1095
|
-
}
|
|
1096
|
-
case 'thread.started': {
|
|
1097
|
-
const sid = obj.thread_id ??
|
|
1098
|
-
obj.session_id ??
|
|
1099
|
-
obj.id;
|
|
1100
|
-
if (typeof sid === 'string' && sid) {
|
|
1101
|
-
this.agentSessionId = sid;
|
|
1102
|
-
this.emitEvent({ state: 'start', agentSessionId: this.agentSessionId });
|
|
1103
|
-
}
|
|
1104
|
-
return;
|
|
1105
|
-
}
|
|
1106
|
-
case 'turn.started':
|
|
1107
|
-
return;
|
|
1108
|
-
}
|
|
1109
|
-
if (topType?.startsWith('item.'))
|
|
1110
|
-
return;
|
|
1111
|
-
const eventType = obj.type ?? obj.event;
|
|
1112
|
-
switch (eventType) {
|
|
1113
|
-
case 'message':
|
|
1114
|
-
case 'text':
|
|
1115
|
-
case 'content': {
|
|
1116
|
-
const text = obj.content ?? obj.text;
|
|
1117
|
-
if (text) {
|
|
1118
|
-
this.emitText(text);
|
|
1119
|
-
}
|
|
1120
|
-
break;
|
|
1121
|
-
}
|
|
1122
|
-
case 'function_call':
|
|
1123
|
-
case 'tool_call': {
|
|
1124
|
-
if (this.isApprovalLikeName(obj.name)) {
|
|
1125
|
-
this.emitApprovalPending(obj);
|
|
1126
|
-
break;
|
|
1127
|
-
}
|
|
1128
|
-
if (obj.name && isCodexCollabAgentToolName(obj.name))
|
|
1129
|
-
return;
|
|
1130
|
-
this.emitEvent({
|
|
1131
|
-
state: 'tool-call',
|
|
1132
|
-
name: obj.name,
|
|
1133
|
-
args: obj.arguments ?? obj.input,
|
|
1134
|
-
});
|
|
1135
|
-
break;
|
|
1136
|
-
}
|
|
1137
|
-
case 'function_call_output':
|
|
1138
|
-
case 'tool_result': {
|
|
1139
|
-
if (obj.name && isCodexCollabAgentToolName(obj.name))
|
|
1140
|
-
return;
|
|
1141
|
-
this.emitEvent({
|
|
1142
|
-
state: 'tool-result',
|
|
1143
|
-
name: obj.name,
|
|
1144
|
-
result: obj.output ?? obj.result,
|
|
1145
|
-
});
|
|
1146
|
-
break;
|
|
1147
|
-
}
|
|
1148
|
-
case 'approval_request':
|
|
1149
|
-
case 'approvalRequest':
|
|
1150
|
-
case 'permission_request':
|
|
1151
|
-
case 'permissionRequest':
|
|
1152
|
-
case 'user_input_required':
|
|
1153
|
-
case 'userInputRequired': {
|
|
1154
|
-
this.emitApprovalPending(obj);
|
|
1155
|
-
break;
|
|
1156
|
-
}
|
|
1157
|
-
case 'error': {
|
|
1158
|
-
break;
|
|
1159
|
-
}
|
|
1160
|
-
case 'done':
|
|
1161
|
-
case 'completed': {
|
|
1162
|
-
if (obj.usage) {
|
|
1163
|
-
this.pendingUsage = {
|
|
1164
|
-
inputTokens: obj.usage.prompt_tokens ?? obj.usage.input_tokens ?? 0,
|
|
1165
|
-
outputTokens: obj.usage.completion_tokens ?? obj.usage.output_tokens ?? 0,
|
|
1166
|
-
};
|
|
1167
|
-
}
|
|
1168
|
-
this.emitFinalIfOpen();
|
|
1169
|
-
break;
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
emitEvent(partial) {
|
|
1174
|
-
// Codex can finish a turn while a long-running command is still shutting
|
|
1175
|
-
// down, then deliver that command's item/completed notification after
|
|
1176
|
-
// turn/completed. Re-emitting it would make SessionManager mark the
|
|
1177
|
-
// already-finished run busy again, leaving every later message queued.
|
|
1178
|
-
if (this.terminalState !== 'open' &&
|
|
1179
|
-
partial.state !== 'final' &&
|
|
1180
|
-
partial.state !== 'error' &&
|
|
1181
|
-
partial.state !== 'aborted')
|
|
1182
|
-
return;
|
|
1183
|
-
const event = {
|
|
1184
|
-
...partial,
|
|
1185
|
-
runId: this.runId,
|
|
1186
|
-
seq: this.seq++,
|
|
1187
|
-
};
|
|
1188
|
-
this.emit('agentEvent', event);
|
|
1189
|
-
}
|
|
1190
|
-
isApprovalLikeName(name) {
|
|
1191
|
-
if (typeof name !== 'string')
|
|
1192
|
-
return false;
|
|
1193
|
-
return /approval|permission|confirm|user[_-]?input|request[_-]?user/i.test(name);
|
|
1194
|
-
}
|
|
1195
|
-
emitApprovalPending(source) {
|
|
1196
|
-
const command = typeof source.command === 'string' ? source.command : '';
|
|
1197
|
-
const title = typeof source.title === 'string' && source.title.trim() ? source.title : '需要用户确认';
|
|
1198
|
-
const rawContent = typeof source.prompt === 'string'
|
|
1199
|
-
? source.prompt
|
|
1200
|
-
: typeof source.reason === 'string'
|
|
1201
|
-
? source.reason
|
|
1202
|
-
: typeof source.message === 'string'
|
|
1203
|
-
? source.message
|
|
1204
|
-
: command
|
|
1205
|
-
? `Codex 请求执行命令:${command}`
|
|
1206
|
-
: 'Codex 正在等待用户确认后继续。';
|
|
1207
|
-
this.emitEvent({
|
|
1208
|
-
state: 'approval-pending',
|
|
1209
|
-
approval: {
|
|
1210
|
-
title,
|
|
1211
|
-
content: rawContent,
|
|
1212
|
-
source: 'codex',
|
|
1213
|
-
actionHint: '当前神念暂不支持远程确认,请回到运行 Codex 的电脑/终端完成确认。',
|
|
1214
|
-
},
|
|
1215
|
-
});
|
|
1216
|
-
}
|
|
1217
|
-
async killProcess(preserveConfiguredManagedRoomContext = false) {
|
|
1218
|
-
const proc = this.process;
|
|
1219
|
-
this.appServerReady = null;
|
|
1220
|
-
this.activeTurnId = null;
|
|
1221
|
-
this.managedRoomTools.reset();
|
|
1222
|
-
if (!preserveConfiguredManagedRoomContext)
|
|
1223
|
-
this.clearConfiguredManagedRoomContext();
|
|
1224
|
-
if (!proc) {
|
|
1225
|
-
this.rejectAllPending(new Error('codex app-server stopped'));
|
|
1226
|
-
return;
|
|
1227
|
-
}
|
|
1228
|
-
this.process = null;
|
|
1229
|
-
this.clearForceCloseTimer();
|
|
1230
|
-
proc.kill('SIGTERM');
|
|
1231
|
-
await new Promise((resolve) => {
|
|
1232
|
-
proc.on('close', resolve);
|
|
1233
|
-
setTimeout(() => {
|
|
1234
|
-
proc.kill('SIGKILL');
|
|
1235
|
-
resolve();
|
|
1236
|
-
}, 3000);
|
|
1237
|
-
});
|
|
1238
|
-
this.rejectAllPending(new Error('codex app-server stopped'));
|
|
1239
|
-
}
|
|
1240
|
-
rejectAllPending(error) {
|
|
1241
|
-
for (const [id, pending] of this.pendingRequests.entries()) {
|
|
1242
|
-
clearTimeout(pending.timer);
|
|
1243
|
-
pending.reject(error);
|
|
1244
|
-
this.pendingRequests.delete(id);
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
handleProcessClose(code, stderrBuf) {
|
|
1248
|
-
const stderr = normalizeCodexStderr(stderrBuf);
|
|
1249
|
-
if (code !== 0 && code !== null) {
|
|
1250
|
-
const msg = stderr || `codex exited with code ${code}`;
|
|
1251
|
-
this.emitErrorIfOpen(msg);
|
|
1252
|
-
}
|
|
1253
|
-
else if (looksLikeFatalCodexStderr(stderr)) {
|
|
1254
|
-
// Codex sometimes surfaces fatal API/auth errors on stderr without
|
|
1255
|
-
// emitting a protocol-level `turn.failed`, and may still exit 0.
|
|
1256
|
-
this.emitErrorIfOpen(stderr);
|
|
1257
|
-
}
|
|
1258
|
-
else {
|
|
1259
|
-
this.emitFinalIfOpen();
|
|
1260
|
-
}
|
|
1261
|
-
this.pendingUsage = undefined;
|
|
1262
|
-
}
|
|
1263
|
-
emitFinalIfOpen() {
|
|
1264
|
-
if (this.terminalState !== 'open')
|
|
1265
|
-
return;
|
|
1266
|
-
this.terminalState = 'final';
|
|
1267
|
-
this.managedRoomTools.deactivateTurn();
|
|
1268
|
-
this.clearConfiguredManagedRoomContext();
|
|
1269
|
-
this.emitEvent({
|
|
1270
|
-
state: 'final',
|
|
1271
|
-
usage: this.pendingUsage,
|
|
1272
|
-
agentSessionId: this.agentSessionId ?? undefined,
|
|
1273
|
-
});
|
|
1274
|
-
// A completed managed turn no longer needs to own Codex's per-thread writer
|
|
1275
|
-
// lock. Release the app-server immediately so Codex Desktop/TUI can resume
|
|
1276
|
-
// the same thread without requiring a daemon restart.
|
|
1277
|
-
void this.killProcess().catch(() => { });
|
|
1278
|
-
}
|
|
1279
|
-
emitErrorIfOpen(message) {
|
|
1280
|
-
if (this.terminalState !== 'open')
|
|
1281
|
-
return;
|
|
1282
|
-
this.terminalState = 'error';
|
|
1283
|
-
this.managedRoomTools.deactivateTurn();
|
|
1284
|
-
this.clearConfiguredManagedRoomContext();
|
|
1285
|
-
this.emitEvent({ state: 'error', message: formatCodexErrorMessage(message) });
|
|
1286
|
-
this.scheduleProcessClose();
|
|
1287
|
-
}
|
|
1288
|
-
resetRunState() {
|
|
1289
|
-
this.pendingUsage = undefined;
|
|
1290
|
-
this.terminalState = 'open';
|
|
1291
|
-
this.deltaItemIds.clear();
|
|
1292
|
-
this.lastTransientStatus = '';
|
|
1293
|
-
this.clearForceCloseTimer();
|
|
1294
|
-
this.resetTextState();
|
|
1295
|
-
}
|
|
1296
|
-
clearConfiguredManagedRoomContext() {
|
|
1297
|
-
this.configuredManagedRoomContextToken = null;
|
|
1298
|
-
this.configuredManagedRoomExecutor = null;
|
|
1299
|
-
}
|
|
1300
|
-
emitTransientStatus(message) {
|
|
1301
|
-
const formatted = formatTransientCodexStatus(message);
|
|
1302
|
-
if (!formatted || formatted === this.lastTransientStatus || this.terminalState !== 'open')
|
|
1303
|
-
return;
|
|
1304
|
-
this.lastTransientStatus = formatted;
|
|
1305
|
-
this.emitEvent({ state: 'delta', text: `${formatted}\n`, thinking: true });
|
|
1306
|
-
}
|
|
1307
|
-
resetTextState() {
|
|
1308
|
-
this.hasEmittedText = false;
|
|
1309
|
-
this.textTail = '';
|
|
1310
|
-
}
|
|
1311
|
-
emitText(text, mode = 'delta') {
|
|
1312
|
-
if (!text)
|
|
1313
|
-
return;
|
|
1314
|
-
let next = stripGitDirectiveArtifacts(text);
|
|
1315
|
-
if (!next)
|
|
1316
|
-
return;
|
|
1317
|
-
if (mode === 'block' && this.hasEmittedText && !next.startsWith('\n')) {
|
|
1318
|
-
if (this.textTail.endsWith('\n')) {
|
|
1319
|
-
next = `\n${next}`;
|
|
1320
|
-
}
|
|
1321
|
-
else if (!this.textTail.endsWith('\n\n')) {
|
|
1322
|
-
next = `\n\n${next}`;
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
this.hasEmittedText = true;
|
|
1326
|
-
this.textTail = (this.textTail + next).slice(-4);
|
|
1327
|
-
this.emitEvent({ state: 'delta', text: next });
|
|
1328
|
-
}
|
|
1329
|
-
scheduleProcessClose() {
|
|
1330
|
-
const proc = this.process;
|
|
1331
|
-
if (!proc || this.forceCloseTimer)
|
|
1332
|
-
return;
|
|
1333
|
-
this.forceCloseTimer = setTimeout(() => {
|
|
1334
|
-
this.forceCloseTimer = null;
|
|
1335
|
-
if (this.process !== proc)
|
|
1336
|
-
return;
|
|
1337
|
-
proc.kill('SIGTERM');
|
|
1338
|
-
setTimeout(() => {
|
|
1339
|
-
if (this.process === proc) {
|
|
1340
|
-
proc.kill('SIGKILL');
|
|
1341
|
-
}
|
|
1342
|
-
}, 1000);
|
|
1343
|
-
}, 250);
|
|
1344
|
-
}
|
|
1345
|
-
clearForceCloseTimer() {
|
|
1346
|
-
if (!this.forceCloseTimer)
|
|
1347
|
-
return;
|
|
1348
|
-
clearTimeout(this.forceCloseTimer);
|
|
1349
|
-
this.forceCloseTimer = null;
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
registerAgent('codex', () => new CodexAdapter());
|
|
1
|
+
import{spawnSync as O}from"node:child_process";import{createInterface as w}from"node:readline";import{createHash as S,randomUUID as b}from"node:crypto";import p from"node:path";import{AgentAdapter as q,registerAgent as D}from"./adapter.js";import{resolveBuiltinCommand as v,spawnAgentCommand as x}from"./command-spec.js";import{buildAgentProcessEnv as L,buildManagedAgentProcessEnv as N}from"../agent-env.js";import{ensurePlatformInstructionsFile as F}from"./platform-instructions.js";import{CODEX_DESKTOP_SEND_UNAVAILABLE as C}from"@shennian/wire";import{isCodexThreadTailInterrupted as $}from"../native-fusion/codex-files.js";import{resolveManagedAgentAdapterEnforcement as U}from"../security/managed-agent-policy.js";import{registerManagedAgentRuntimeSecurity as W}from"../security/managed-agent-runtime.js";import{CODEX_MANAGED_ROOM_DYNAMIC_TOOLS as z,CodexManagedRoomToolBinding as B}from"./codex-managed-room-tools.js";import{assertCodexManagedPermissionProfile as E,CODEX_MANAGED_RUNTIME_SECURITY as G,compileCodexManagedPermissions as J,extractCodexMcpServerNames as H}from"./codex-managed-permissions.js";import{CODEX_APP_SERVER_CLIENT_INFO as V,extractAppServerErrorMessage as R,formatCodexErrorMessage as X,formatTransientCodexStatus as K,isCodexCollabAgentItem as Q,isCodexCollabAgentToolName as y,isCodexLegacyCollabAgentItem as Y,isCodexUnsupportedEffortError as j,isMissingCodexRolloutError as T,isTransientCodexErrorMessage as Z,looksLikeCodexInteractiveAuthPrompt as ee,looksLikeFatalCodexStderr as te,normalizeCodexModelId as I,normalizeCodexReasoningEffort as re,normalizeCodexStderr as _,normalizeTerminalText as se,safeStringify as k,stripGitDirectiveArtifacts as ne}from"./codex-utils.js";import{isCodexUnsupportedEffortError as be,isMissingCodexRolloutError as qe,normalizeCodexModelId as De,normalizeCodexReasoningEffort as Le}from"./codex-utils.js";W("codex",G);function P(o){const e=o instanceof Error?o.message:String(o);return/already has an active writer|thread-store conflict/i.test(e)}function ie(o){const e=o instanceof Error?o.message:String(o);return/paginated(?:_threads|\s+(?:thread\s+)?history)?.*(?:not supported|unsupported|unavailable)/i.test(e)||/(?:not supported|unsupported|unavailable).*paginated(?:_threads|\s+(?:thread\s+)?history)?/i.test(e)}function oe(o){const e=o instanceof Error?o.message:String(o);return/unknown field [`'"]?excludeTurns/i.test(e)||/excludeTurns.*(?:not supported|unsupported|unavailable)/i.test(e)}function ae(o){const e=o instanceof Error?o.message:String(o);return/(?:method not found|unknown method).*(?:project\/list|project\/create)/i.test(e)||/(?:project\/list|project\/create).*(?:not supported|unsupported|unavailable)/i.test(e)}function de(o,e){const t=p.resolve(e);let r;for(const s of o)if(s.id)for(const i of s.roots??[]){if(!i.path)continue;const n=p.resolve(i.path),a=p.relative(n,t);!(a===""||!a.startsWith("..")&&!p.isAbsolute(a))||n.length<=(r?.rootLength??-1)||(r={id:s.id,rootLength:n.length})}return r?.id}function le(o,e){const t=e?.flatMap(s=>ce(s)).filter(s=>!/^https?:\/\//i.test(s.path)).filter(s=>s.path);return{type:"text",text:he(o,e),text_elements:[],...t?.length?{local_images:t}:{}}}function ce(o){if(o.kind==="image")return o.previewData?[{path:o.path,data:o.previewData,mimeType:o.mimeType}]:[{path:o.path}];if(o.mimeType.startsWith("video/")){const e=ue(o.path);return e?[{path:e}]:[]}return[]}function ue(o){if(!o||/^https?:\/\//i.test(o))return null;const e=p.extname(o),r=`${e?o.slice(0,-e.length):o}.preview.png`;return O("ffmpeg",["-y","-ss","0","-i",o,"-frames:v","1","-vf","scale=320:-1",r],{stdio:"ignore",timeout:15e3}).status===0?r:null}function he(o,e){const t=e?.filter(i=>i.kind!=="image").filter(i=>i.path&&!/^https?:\/\//i.test(i.path));if(!t?.length||t.every(i=>o.includes(i.path)))return o;const s=t.map((i,n)=>{const a=i.name||`attachment-${n+1}`,d=i.mimeType?` (${i.mimeType})`:"";return`[${A(a)}${d}](<${i.path}>)`});return[o,"",...s].join(`
|
|
2
|
+
`)}function pe(o,e){const t=e?.filter(s=>s.path&&!/^https?:\/\//i.test(s.path)&&!o.includes(s.path));if(!t?.length)return o;const r=t.map((s,i)=>{const n=A(s.name||`attachment-${i+1}`);return s.kind==="image"?``:`[${n}](<${s.path}>)`});return[o,"",...r].join(`
|
|
3
|
+
`)}function me(o,e,t){return["queue","--thread",o,"--message",pe(e,t)]}function A(o){return o.replace(/[[\]\\]/g,"\\$&")}class ge extends q{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;deltaItemIds=new Set;lastTransientStatus="";agentSystemPrompt="";managedAgentPolicy=null;managedPermissionConfig=null;configurationRestartRequired=!1;managedRoomTools=new B;configuredManagedRoomContextToken=null;configuredManagedRoomExecutor=null;managedRoomToolThreadId=null;preparedExternalOwnerThreadId=null;managedTurnReplay=null;managedAsyncRecovery=null;constructor(e={}){super(),this.options=e}externalChannel=null;shennianSessionId=null;extraEnv={};configure(e){const t=this.configurationFingerprint();this.shennianSessionId=e.sessionId??null,this.externalChannel=e.externalChannel??null,this.extraEnv=e.env??{},"managedAgentPolicy"in e&&(this.managedAgentPolicy=e.managedAgentPolicy??null),("managedRoomContextToken"in e||"managedRoomExecutor"in e)&&(this.managedRoomTools.deactivateTurn(),this.configuredManagedRoomContextToken=e.managedRoomContextToken?.trim()||null,this.configuredManagedRoomExecutor=e.managedRoomExecutor??null),this.managedAgentPolicy&&(this.workDir=this.managedAgentPolicy.adapterPolicy.workDir);const r=this.configurationFingerprint();t!==r&&this.process&&(this.configurationRestartRequired=!0)}configurationFingerprint(){const e=Object.keys(this.extraEnv).sort().map(t=>[t,this.extraEnv[t]??null]);return S("sha256").update(JSON.stringify({sessionId:this.shennianSessionId,externalChannel:this.externalChannel,env:e,managedAgentPolicy:this.managedAgentPolicy})).digest("hex")}buildProcessEnv(e={}){if(!this.managedAgentPolicy)return L({...e,...this.extraEnv});const t=Object.fromEntries(Object.entries(this.extraEnv).filter(([r])=>r!=="SHENNIAN_MANAGED_ROOM_CONTEXT"&&r!=="SHENNIAN_MANAGED_ROOM_IPC_ROOT"));return N({agentType:"codex",providerEnv:t,sessionEnv:t,runtimeEnv:e})}async start(e,t,r){this.workDir=this.managedAgentPolicy?.adapterPolicy.workDir??t,this.seq=0,r&&(this.agentSessionId=r)}async prepareSend(e,t,r,s,i,n){const a=i?.trim()??"";if(a!==this.agentSystemPrompt&&(this.agentSystemPrompt=a,this.process&&await this.killProcess(!0)),!(!this.agentSessionId||this.activeTurnId)){this.preparedExternalOwnerThreadId=null;try{await this.ensureAppServer(I(t))}catch(d){if(!P(d))throw d;if(n?.source==="room_activation")throw new Error("codex_managed_thread_owned_externally",{cause:d});if(this.isThreadTailInterrupted(this.agentSessionId))throw new Error(C,{cause:d});this.preparedExternalOwnerThreadId=this.agentSessionId}}}async send(e,t,r,s,i,n){if(n?.source==="room_activation"&&!this.managedAgentPolicy)throw new Error("managed_agent_policy_missing");if(n?.source==="room_activation"&&(!this.configuredManagedRoomContextToken||!this.configuredManagedRoomExecutor))throw new Error("codex_managed_room_tool_context_unavailable");this.activeTurnId&&(await this.interruptActiveTurn().catch(()=>{}),await this.killProcess(!0)),this.configurationRestartRequired&&this.process&&await this.killProcess(!0),this.configurationRestartRequired=!1;const a=I(t),d=re(r),c=i?.trim()??"";if(c!==this.agentSystemPrompt&&(this.agentSystemPrompt=c,this.process&&await this.killProcess(!0)),n?.source==="room_activation"&&this.managedRoomTools.configure(this.configuredManagedRoomContextToken,this.configuredManagedRoomExecutor),this.runId=b(),this.seq=0,this.resetRunState(),this.agentSessionId&&this.preparedExternalOwnerThreadId===this.agentSessionId)return this.preparedExternalOwnerThreadId=null,{deliveryMode:"external_owner_queue",queuedSubmissionId:await this.enqueueForExternalOwner(this.agentSessionId,e,s)};try{await this.ensureAppServer(a)}catch(h){if(!this.agentSessionId||!P(h))throw h;if(n?.source==="room_activation")throw new Error("codex_managed_thread_owned_externally",{cause:h});if(this.isThreadTailInterrupted(this.agentSessionId))throw new Error(C,{cause:h});return{deliveryMode:"external_owner_queue",queuedSubmissionId:await this.enqueueForExternalOwner(this.agentSessionId,e,s)}}const l=this.agentSessionId;if(!l)throw new Error("codex app-server did not create a thread");this.emitEvent({state:"start",agentSessionId:l}),this.managedTurnReplay=this.managedPermissionConfig?{text:e,modelId:a,reasoningEffort:d,attachments:s,recoveryCount:0}:null;const g=await this.startTurnWithRecovery(l,e,a,d,s),m=this.agentSessionId??l;if(this.activeTurnId=g.turn?.id??null,this.managedPermissionConfig&&this.activeTurnId&&!this.managedRoomTools.bindTurn(m,this.activeTurnId))throw await this.killProcess(),new Error("codex_managed_room_turn_binding_failed");return{deliveryMode:"direct"}}isThreadTailInterrupted(e){return this.options.isThreadTailInterrupted?.(e)??$(e)}async enqueueForExternalOwner(e,t,r){const s=v("codex");if(!s)throw new Error('Command "codex" not found. Is OpenAI Codex CLI installed?');const i=me(e,t,r),n=x(s,i,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:this.buildProcessEnv({NO_COLOR:"1"})});return await new Promise((a,d)=>{let c="",l="",g=!1;const m=(u,f)=>{g||(g=!0,clearTimeout(h),u?d(u):a(f))},h=setTimeout(()=>{n.kill("SIGTERM"),m(new Error("Codex Desktop message queue timed out"))},6e4);h.unref?.(),n.stdout?.on("data",u=>{c+=u.toString()}),n.stderr?.on("data",u=>{l+=u.toString()}),n.once("error",u=>m(u)),n.once("close",u=>{const f=`${c}
|
|
4
|
+
${l}`.trim();if(u!==0){m(new Error(f||`codex queue exited with code ${u??"unknown"}`));return}const M=f.match(/Queued message\s+([^\s]+)\s+for thread/i)?.[1];m(void 0,M)})})}async resume(e){await this.killProcess(),this.agentSessionId=e,this.resetTextState()}async setTitle(e,t,r){const s=e.trim(),i=t.replace(/\s+/g," ").trim();!s||!i||(this.agentSessionId=s,r?.trim()&&(this.workDir=r),await this.ensureAppServer(),await this.sendRpc("thread/name/set",{threadId:s,name:i}))}async stop(){this.managedRoomTools.deactivateTurn(),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 r=null;try{r=(await this.sendRpc("thread/read",{threadId:e,includeTurns:!1},5e3)).thread?.status??null}catch{}const s=r?.type==="active"||!!t;return{active:s,runId:t,runPhase:s?this.mapThreadStatusToRunPhase(r):null,canStop:!!(s&&t&&this.process)}}spawnCodex(e){const t=v("codex");if(!t){this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));return}const r=x(t,e,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:this.buildProcessEnv()});this.process=r,w({input:r.stdout}).on("line",n=>{if(n.trim())try{const a=JSON.parse(n);this.handleStreamEvent(a)}catch{this.emitEvent({state:"delta",text:n})}});let i="";r.stderr?.on("data",n=>{i+=n.toString()}),r.on("close",n=>{this.process===r&&(this.process=null,this.clearForceCloseTimer(),this.handleProcessClose(n,i))}),r.on("error",n=>{this.process===r&&(this.process=null,n.code==="ENOENT"?this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?')):this.emit("error",n))})}spawnAppServer(){const e=v("codex");if(!e){this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));return}const t=["app-server","--listen","stdio://"],r=this.options.modelInstructionsFile??F(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,this.agentSystemPrompt);r&&t.push("-c",`model_instructions_file=${JSON.stringify(r)}`);const s=x(e,t,{cwd:this.workDir??void 0,stdio:["pipe","pipe","pipe"],env:this.buildProcessEnv({NO_COLOR:"1"})});this.process=s,this.stderrBuf="",this.appServerStartupError=null,w({input:s.stdout}).on("line",n=>{if(!n.trim())return;let a;try{a=JSON.parse(n)}catch{this.handleAppServerNonJsonStdout(n);return}this.handleAppServerMessage(a)}),s.stderr?.on("data",n=>{this.stderrBuf+=n.toString()}),s.on("close",n=>{if(this.process!==s)return;const a=this.activeTurnId!=null;this.process=null,this.appServerReady=null,this.activeTurnId=null,this.managedRoomTools.reset(),this.clearConfiguredManagedRoomContext(),this.rejectAllPending(new Error(_(this.stderrBuf)||`codex app-server exited with code ${n}`)),this.terminalState==="open"&&a&&this.handleProcessClose(n,this.stderrBuf)}),s.on("error",n=>{this.process===s&&(this.process=null,this.managedRoomTools.reset(),this.clearConfiguredManagedRoomContext(),this.rejectAllPending(n),n.code==="ENOENT"?this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?')):this.emit("error",n))})}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=I(e);if(await this.sendRpc("initialize",{clientInfo:V,capabilities:{experimentalApi:!0}}),this.managedPermissionConfig=this.managedAgentPolicy?await this.buildManagedPermissionConfig():null,this.managedPermissionConfig&&!this.managedRoomTools.isConfigured()&&(this.managedRoomTools.configure(this.configuredManagedRoomContextToken,this.configuredManagedRoomExecutor),!this.managedRoomTools.isConfigured()))throw new Error("codex_managed_room_tool_context_unavailable");if(this.agentSessionId&&this.managedPermissionConfig&&this.agentSessionId!==this.managedRoomToolThreadId){if(!this.managedRoomTools.isConfigured())throw new Error("codex_managed_room_tool_context_unavailable");this.agentSessionId=null}if(this.agentSessionId){const a={threadId:this.agentSessionId,cwd:this.workDir,approvalPolicy:"never",...this.managedPermissionConfig?{config:this.managedPermissionConfig.threadConfig}:{sandbox:"danger-full-access"},persistExtendedHistory:!0,...this.options.hidden?{threadSource:"subagent"}:{},...t?{model:t}:{}};let d;try{d=await this.sendRpc("thread/resume",{...a,excludeTurns:!0})}catch(c){if(!oe(c))throw c;d=await this.sendRpc("thread/resume",a)}this.agentSessionId=d.thread?.id??this.agentSessionId,this.managedPermissionConfig&&(E(d,this.managedPermissionConfig.profileId),this.managedRoomTools.bindThread(this.agentSessionId),this.managedRoomToolThreadId=this.agentSessionId);return}const r=this.options.hidden?void 0:await this.resolveCodexProjectId(),s={cwd:this.workDir,approvalPolicy:"never",...this.managedPermissionConfig?{config:this.managedPermissionConfig.threadConfig}:{sandbox:"danger-full-access"},serviceName:"Shennian",threadSource:this.options.hidden?"subagent":"user",experimentalRawEvents:!1,persistExtendedHistory:!0,...this.managedPermissionConfig?{dynamicTools:z}:{},...r?{projectId:r}:{},...t?{model:t}:{}};let i;if(this.options.hidden&&!this.managedPermissionConfig)i=await this.sendRpc("thread/start",s);else try{i=await this.sendRpc("thread/start",{...s,historyMode:"paginated"})}catch(a){throw ie(a)?new Error("\u5F53\u524D Codex CLI \u4E0D\u652F\u6301\u521B\u5EFA\u53EF\u5728 Codex Desktop \u4E2D\u7EE7\u7EED\u7684\u65B0\u4F1A\u8BDD\uFF0C\u8BF7\u624B\u52A8\u5347\u7EA7 Codex CLI \u540E\u91CD\u8BD5\u3002\u795E\u5FF5\u4E0D\u4F1A\u81EA\u52A8\u5347\u7EA7\uFF0C\u4E5F\u4E0D\u4F1A\u964D\u7EA7\u521B\u5EFA\u4E0D\u53EF\u89C1\u7684 legacy \u4F1A\u8BDD\u3002",{cause:a}):a}const n=i.thread?.id;if(!n)throw new Error("codex app-server thread/start returned no thread id");this.managedPermissionConfig&&(E(i,this.managedPermissionConfig.profileId),this.managedRoomTools.bindThread(n),this.managedRoomToolThreadId=n),this.agentSessionId=n}async buildManagedPermissionConfig(){const e=this.managedAgentPolicy;if(!e)throw new Error("managed_agent_policy_missing");const t=e.adapterPolicy.externalCapabilities.mcp?[]:H(await this.sendRpc("config/read",{cwd:e.adapterPolicy.workDir}));return J(U(e),e.adapterPolicy.workDir,{configuredMcpServerNames:t,shellSubprocessPolicy:e.environmentPolicy.shellSubprocess})}async resolveCodexProjectId(){const e=this.workDir?.trim();if(e)try{const t=[];let r;do{const c=await this.sendRpc("project/list",{limit:100,...r?{cursor:r}:{}});t.push(...c.data??[]),r=c.nextCursor}while(r);const s=de(t,e);if(s)return s;const i=p.resolve(e),n=`shennian-project-${S("sha256").update(i).digest("hex")}`,d=(await this.sendRpc("project/create",{idempotencyKey:n,name:p.basename(i)||i,roots:[{path:i}],metadata:{createdBy:"Shennian"}})).project?.id;if(!d)throw new Error("codex app-server project/create returned no project id");return d}catch(t){throw ae(t)?new Error("\u5F53\u524D Codex CLI \u4E0D\u652F\u6301\u521B\u5EFA\u53EF\u5F52\u5C5E\u5230 Codex Desktop \u9879\u76EE\u7684\u65B0\u4F1A\u8BDD\uFF0C\u8BF7\u624B\u52A8\u5347\u7EA7 Codex CLI \u540E\u91CD\u8BD5\u3002\u795E\u5FF5\u4E0D\u4F1A\u81EA\u52A8\u5347\u7EA7\uFF0C\u4E5F\u4E0D\u4F1A\u76F4\u63A5\u4FEE\u6539 Codex Desktop \u7684\u79C1\u6709\u4FA7\u680F\u7D22\u5F15\u3002",{cause:t}):t}}async startTurnWithRecovery(e,t,r,s,i){try{return await this.startTurn(e,t,r,s,i)}catch(n){if(!T(n))throw n;if(this.managedPermissionConfig){this.logManagedThreadLifecycle("rollout.missing",{threadId:e,recovery:"recreate"}),await this.killProcess(!0),this.agentSessionId=null,this.managedRoomToolThreadId=null,await this.ensureAppServer(r);const a=this.agentSessionId;if(!a)throw new Error("codex app-server did not recreate the managed thread",{cause:n});return this.logManagedThreadLifecycle("thread.recreated",{previousThreadId:e,threadId:a}),this.emitEvent({state:"start",agentSessionId:a}),await this.startFreshManagedTurnWithReadinessRetry(a,t,r,s,i)}return await this.killProcess(),await this.ensureAppServer(r),await this.startTurn(e,t,r,s,i)}}async startFreshManagedTurnWithReadinessRetry(e,t,r,s,i){const n=[0,100,300,1e3];let a;for(let d=0;d<n.length;d+=1){const c=n[d];c>0&&await new Promise(l=>setTimeout(l,c));try{const l=await this.startTurn(e,t,r,s,i);return d>0&&this.logManagedThreadLifecycle("rollout.ready",{threadId:e,attempt:d+1}),l}catch(l){if(a=l,!T(l)||d===n.length-1)throw l;this.logManagedThreadLifecycle("rollout.retry",{threadId:e,attempt:d+2,delayMs:n[d+1]})}}throw a}logManagedThreadLifecycle(e,t){console.error(`[codex.managed] ${JSON.stringify({event:e,...t})}`)}async startTurn(e,t,r,s,i){try{return await this.sendRpc("turn/start",{threadId:e,input:[le(t,i)],approvalPolicy:"never",...this.managedPermissionConfig?{}:{sandboxPolicy:{type:"dangerFullAccess"}},...r?{model:r}:{},...s?{effort:s}:{}})}catch(n){throw s&&j(n)?new Error(`Codex app-server does not accept reasoning effort "${s}" for this turn. Refresh models or upgrade Codex CLI, then retry.`,{cause:n}):n}}async interruptActiveTurn(){const e=this.agentSessionId,t=this.activeTurnId;this.activeTurnId=null,this.managedRoomTools.deactivateTurn(),!(!e||!t||!this.process)&&await this.sendRpc("turn/interrupt",{threadId:e,turnId:t},5e3)}sendRpc(e,t,r=6e4){if(this.appServerStartupError)return Promise.reject(this.appServerStartupError);const s=this.process;if(!s?.stdin)return Promise.reject(new Error("codex app-server is not running"));const i=this.rpcSeq++,n=JSON.stringify({id:i,method:e,params:t});return new Promise((a,d)=>{const c=setTimeout(()=>{this.pendingRequests.delete(i),d(new Error(`codex app-server request timed out: ${e}`))},r);this.pendingRequests.set(i,{resolve:a,reject:d,timer:c}),s.stdin.write(`${n}
|
|
5
|
+
`,l=>{l&&(clearTimeout(c),this.pendingRequests.delete(i),d(l))})})}handleAppServerMessage(e){if(e.method==="item/tool/call"&&e.id!=null){this.handleManagedRoomToolCall(e.id,e.params??{});return}if(e.id!=null){const r=typeof e.id=="number"?this.pendingRequests.get(e.id):void 0;r&&(clearTimeout(r.timer),this.pendingRequests.delete(e.id),e.error?r.reject(new Error(e.error.message||`codex app-server error ${e.error.code??""}`.trim())):r.resolve(e.result))}if(!e.method)return;const t=e.params??{};switch(e.method){case"turn/started":{const r=typeof t.turn=="object"&&t.turn!==null?t.turn:null,s=typeof r?.id=="string"?r.id:null,i=typeof t.threadId=="string"?t.threadId:"";s&&(this.activeTurnId=s,this.managedPermissionConfig&&!this.managedRoomTools.bindTurn(i,s)&&this.emitErrorIfOpen("codex_managed_room_turn_binding_failed"));break}case"thread/status/changed":{const r=typeof t.threadId=="string"?t.threadId:"";if(r&&this.agentSessionId&&r!==this.agentSessionId)break;const s=typeof t.status=="object"&&t.status!==null?t.status:null;s?.type==="active"&&this.emitEvent({state:"heartbeat",runPhase:this.mapThreadStatusToRunPhase(s)});break}case"item/agentMessage/delta":{const r=typeof t.itemId=="string"?t.itemId:"";r&&this.deltaItemIds.add(r),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 r=typeof t.turn=="object"&&t.turn!==null?t.turn:null,s=typeof r?.id=="string"?r.id:null;(!this.activeTurnId||s===this.activeTurnId)&&(this.activeTurnId=null,this.managedRoomTools.deactivateTurn(),this.emitFinalIfOpen());break}case"turn/failed":{const r=R(t);if(this.scheduleManagedAsyncRolloutRecovery(r))return;this.emitErrorIfOpen(r||"turn failed");break}case"error":{const r=R(t);if(Z(r)){this.emitTransientStatus(r);return}if(this.scheduleManagedAsyncRolloutRecovery(r))return;this.emitErrorIfOpen(r);break}}}scheduleManagedAsyncRolloutRecovery(e){if(!this.managedPermissionConfig||!this.managedTurnReplay||!T(new Error(e)))return!1;if(this.managedAsyncRecovery)return!0;if(this.managedTurnReplay.recoveryCount>=2)return!1;this.managedTurnReplay.recoveryCount+=1;const t=this.managedTurnReplay.recoveryCount;return this.logManagedThreadLifecycle("rollout.failed-async",{threadId:this.agentSessionId,recoveryCount:t}),this.managedAsyncRecovery=this.recoverManagedTurnAfterAsyncRolloutFailure().catch(r=>{const s=T(r)?"no rollout found after managed thread recovery":r instanceof Error?r.message:"managed thread recovery failed";this.emitErrorIfOpen(s)}).finally(()=>{this.managedAsyncRecovery=null}),!0}async recoverManagedTurnAfterAsyncRolloutFailure(){const e=this.managedTurnReplay,t=this.agentSessionId;if(!e||!t||this.terminalState!=="open")return;this.activeTurnId=null,this.managedRoomTools.deactivateTurn(),await this.killProcess(!0),this.agentSessionId=null,this.managedRoomToolThreadId=null,await this.ensureAppServer(e.modelId);const r=this.agentSessionId;if(!r)throw new Error("codex app-server did not recreate the managed thread");this.logManagedThreadLifecycle("thread.recreated-async",{previousThreadId:t,threadId:r,recoveryCount:e.recoveryCount}),this.emitEvent({state:"start",agentSessionId:r});const s=await this.startFreshManagedTurnWithReadinessRetry(r,e.text,e.modelId,e.reasoningEffort,e.attachments);if(this.activeTurnId=s.turn?.id??null,this.activeTurnId&&!this.managedRoomTools.bindTurn(r,this.activeTurnId))throw new Error("codex_managed_room_turn_binding_failed")}async handleManagedRoomToolCall(e,t){const r=await this.managedRoomTools.execute(t);if(this.writeAppServerResponse(e,r),r.success&&t.tool==="shennian_room_send"){const s=typeof t.arguments=="object"&&t.arguments!==null?t.arguments:null,i=typeof s?.text=="string"?s.text.trim():"";i&&this.emitText(i,"block")}}writeAppServerResponse(e,t){const r=this.process;r?.stdin&&r.stdin.write(`${JSON.stringify({id:e,result:t})}
|
|
6
|
+
`)}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=se(e);ee(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")&&!Q(t))switch(t.type){case"agentMessage":{const r=typeof t.id=="string"?t.id:"";if(r&&this.deltaItemIds.has(r))return;typeof t.text=="string"&&this.emitText(t.text,"block");break}case"commandExecution":{const r=typeof t.command=="string"?t.command:"",s=typeof t.aggregatedOutput=="string"?t.aggregatedOutput:"";r&&this.emitEvent({state:"tool-call",name:"command_execution",args:{command:r}}),this.emitEvent({state:"tool-result",name:"command_execution",result:[s,t.exitCode!=null?`
|
|
7
|
+
(exit ${t.exitCode})`:""].join("").trim()});break}case"mcpToolCall":case"dynamicToolCall":{const r=typeof t.tool=="string"?t.tool:t.type;if(y(r))return;const s=typeof t.arguments=="object"&&t.arguments!==null?t.arguments:{};this.emitEvent({state:"tool-call",name:r,args:s}),this.emitEvent({state:"tool-result",name:r,result:k(t.error??t.result??t.contentItems??"")});break}case"fileChange":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,r=typeof t?.last=="object"&&t.last!==null?t.last:null,s=typeof t?.total=="object"&&t.total!==null?t.total:null,i=r??s;if(!i)return;const n=typeof i.inputTokens=="number"?i.inputTokens:void 0,a=typeof i.outputTokens=="number"?i.outputTokens:void 0;(n!=null||a!=null)&&(this.pendingUsage={inputTokens:n??0,outputTokens:a??0})}handleCodexItemLine(e){const t=e.item;if(t?.type&&!Y(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 r=[t.aggregated_output??"",t.exit_code!=null?`
|
|
8
|
+
(exit ${t.exit_code})`:""];this.emitEvent({state:"tool-result",name:"command_execution",result:r.join("").trim()||String(t.status??"")})}break}case"mcp_tool_call":{if(t.tool&&y(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 r=t.error&&typeof t.error=="object"?t.error.message:void 0;this.emitEvent({state:"tool-result",name:t.tool??"mcp_tool",result:r??k(t.result)})}break}case"file_change":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 r=t.items.map(s=>`- [${s.completed?"x":" "}] ${s.text}`).join(`
|
|
9
|
+
`);this.emitText(r,"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 s=e.usage;if(s){const i=s.input_tokens??s.prompt_tokens,n=s.output_tokens??s.completion_tokens;(i!=null||n!=null)&&(this.pendingUsage={inputTokens:i??0,outputTokens:n??0})}this.emitFinalIfOpen();return}case"turn.failed":{const s=typeof e.error=="object"&&e.error?.message?e.error.message:typeof e.error=="string"?e.error:"turn failed";this.emitErrorIfOpen(s);return}case"error":{const s=typeof e.message=="string"?e.message:"";if(s.includes("Reconnecting"))return;this.emitErrorIfOpen(s||(typeof e.error=="string"?e.error:"stream error"));return}case"thread.started":{const s=e.thread_id??e.session_id??e.id;typeof s=="string"&&s&&(this.agentSessionId=s,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 s=e.content??e.text;s&&this.emitText(s);break}case"function_call":case"tool_call":{if(this.isApprovalLikeName(e.name)){this.emitApprovalPending(e);break}if(e.name&&y(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&&y(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){if(this.terminalState!=="open"&&e.state!=="final"&&e.state!=="error"&&e.state!=="aborted")return;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:"",r=typeof e.title=="string"&&e.title.trim()?e.title:"\u9700\u8981\u7528\u6237\u786E\u8BA4",s=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:r,content:s,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(e=!1){const t=this.process;if(this.appServerReady=null,this.activeTurnId=null,this.managedRoomTools.reset(),e||this.clearConfiguredManagedRoomContext(),!t){this.rejectAllPending(new Error("codex app-server stopped"));return}this.process=null,this.clearForceCloseTimer(),t.kill("SIGTERM"),await new Promise(r=>{t.on("close",r),setTimeout(()=>{t.kill("SIGKILL"),r()},3e3)}),this.rejectAllPending(new Error("codex app-server stopped"))}rejectAllPending(e){for(const[t,r]of this.pendingRequests.entries())clearTimeout(r.timer),r.reject(e),this.pendingRequests.delete(t)}handleProcessClose(e,t){const r=_(t);if(e!==0&&e!==null){const s=r||`codex exited with code ${e}`;this.emitErrorIfOpen(s)}else te(r)?this.emitErrorIfOpen(r):this.emitFinalIfOpen();this.pendingUsage=void 0}emitFinalIfOpen(){this.terminalState==="open"&&(this.terminalState="final",this.managedTurnReplay=null,this.managedRoomTools.deactivateTurn(),this.clearConfiguredManagedRoomContext(),this.emitEvent({state:"final",usage:this.pendingUsage,agentSessionId:this.agentSessionId??void 0}),this.killProcess().catch(()=>{}))}emitErrorIfOpen(e){this.terminalState==="open"&&(this.terminalState="error",this.managedTurnReplay=null,this.managedRoomTools.deactivateTurn(),this.clearConfiguredManagedRoomContext(),this.emitEvent({state:"error",message:X(e)}),this.scheduleProcessClose())}resetRunState(){this.pendingUsage=void 0,this.terminalState="open",this.deltaItemIds.clear(),this.lastTransientStatus="",this.clearForceCloseTimer(),this.resetTextState()}clearConfiguredManagedRoomContext(){this.configuredManagedRoomContextToken=null,this.configuredManagedRoomExecutor=null}emitTransientStatus(e){const t=K(e);!t||t===this.lastTransientStatus||this.terminalState!=="open"||(this.lastTransientStatus=t,this.emitEvent({state:"delta",text:`${t}
|
|
10
|
+
`,thinking:!0}))}resetTextState(){this.hasEmittedText=!1,this.textTail=""}emitText(e,t="delta"){if(!e)return;let r=ne(e);r&&(t==="block"&&this.hasEmittedText&&!r.startsWith(`
|
|
11
|
+
`)&&(this.textTail.endsWith(`
|
|
12
|
+
`)?r=`
|
|
13
|
+
${r}`:this.textTail.endsWith(`
|
|
14
|
+
|
|
15
|
+
`)||(r=`
|
|
16
|
+
|
|
17
|
+
${r}`)),this.hasEmittedText=!0,this.textTail=(this.textTail+r).slice(-4),this.emitEvent({state:"delta",text:r}))}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)}}D("codex",()=>new ge);export{ge as CodexAdapter,me as buildCodexQueueArgs,pe as buildCodexQueuedText,P as isCodexActiveWriterError,oe as isCodexExcludeTurnsUnsupportedError,ie as isCodexPaginatedHistoryUnsupportedError,ae as isCodexProjectApiUnsupportedError,be as isCodexUnsupportedEffortError,qe as isMissingCodexRolloutError,De as normalizeCodexModelId,Le as normalizeCodexReasoningEffort,de as selectCodexProjectIdForWorkDir};
|