@keyou007/agent-bot 0.1.20-alpha.0 → 0.1.20
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/CHANGELOG.md +17 -0
- package/README.md +18 -1
- package/README.zh.md +9 -0
- package/dist/cli/AgentPrerequisites.js +11 -47
- package/dist/cli/AgentPrerequisites.js.map +1 -1
- package/dist/cli/DefaultAgentSelection.js +1 -1
- package/dist/cli/DefaultAgentSelection.js.map +1 -1
- package/dist/cli.js +15 -0
- package/dist/cli.js.map +1 -1
- package/dist/codex/CodexLocalActivityDetector.js +32 -10
- package/dist/codex/CodexLocalActivityDetector.js.map +1 -1
- package/dist/codex/CodexProcessManager.js +53 -7
- package/dist/codex/CodexProcessManager.js.map +1 -1
- package/dist/codex/CodexRuntime.js +225 -164
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/codex/CodexVersion.js +18 -0
- package/dist/codex/CodexVersion.js.map +1 -0
- package/dist/codex/ProviderSwitch.js +32 -0
- package/dist/codex/ProviderSwitch.js.map +1 -0
- package/dist/config/AgentExecutionDefaultsStore.js +1 -1
- package/dist/config/AgentExecutionDefaultsStore.js.map +1 -1
- package/dist/feishu/CardRenderer.js +36 -11
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuConnector.js +17 -5
- package/dist/feishu/FeishuConnector.js.map +1 -1
- package/dist/feishu/InboundText.js +11 -0
- package/dist/feishu/InboundText.js.map +1 -1
- package/dist/feishu/MergedForwardMessage.js +17 -5
- package/dist/feishu/MergedForwardMessage.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/local-files/LocalFileViewerServer.js +25 -7
- package/dist/local-files/LocalFileViewerServer.js.map +1 -1
- package/dist/local-files/MarkdownFileLinks.js +65 -0
- package/dist/local-files/MarkdownFileLinks.js.map +1 -0
- package/dist/proxy/ProxySessionController.js +400 -259
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +99 -65
- package/dist/state/StateStore.js.map +1 -1
- package/dist/utils/semanticVersion.js +39 -0
- package/dist/utils/semanticVersion.js.map +1 -0
- package/docs/technical-reference.md +5 -3
- package/docs/technical-reference.zh.md +5 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/agent-bot/SKILL.md +7 -1
- package/src/cli/AgentPrerequisites.ts +14 -40
- package/src/cli/DefaultAgentSelection.ts +1 -1
- package/src/cli.ts +13 -0
- package/src/codex/CodexLocalActivityDetector.ts +41 -9
- package/src/codex/CodexProcessManager.ts +44 -7
- package/src/codex/CodexRuntime.ts +244 -195
- package/src/codex/CodexVersion.ts +18 -0
- package/src/codex/ProviderSwitch.ts +36 -0
- package/src/config/AgentExecutionDefaultsStore.ts +1 -1
- package/src/feishu/CardRenderer.ts +37 -27
- package/src/feishu/FeishuConnector.ts +15 -5
- package/src/feishu/InboundText.ts +11 -0
- package/src/feishu/MergedForwardMessage.ts +15 -5
- package/src/index.ts +1 -1
- package/src/local-files/LocalFileViewerServer.ts +26 -7
- package/src/local-files/MarkdownFileLinks.ts +64 -0
- package/src/proxy/ProxySessionController.ts +370 -240
- package/src/runtime/types.ts +15 -2
- package/src/state/StateStore.ts +117 -90
- package/src/utils/semanticVersion.ts +28 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodexProcessManager.js","sourceRoot":"","sources":["../../src/codex/CodexProcessManager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,QAAQ,MAAM,eAAe,CAAC;AAErC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D,OAAO,EACL,mBAAmB,GAEpB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"CodexProcessManager.js","sourceRoot":"","sources":["../../src/codex/CodexProcessManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,7 @@ import { mapCodexNotification } from "./CodexEventMapper.js";
|
|
|
5
5
|
import { CodexLocalActivityDetector } from "./CodexLocalActivityDetector.js";
|
|
6
6
|
import { detectProjectlessWorkspace } from "./ProjectlessWorkspace.js";
|
|
7
7
|
import { threadWriterLockPath } from "./ThreadWriterProcess.js";
|
|
8
|
+
import { assertProviderSettingsApplied, PROVIDER_SWITCH_BUSY, providerSwitchFailure } from "./ProviderSwitch.js";
|
|
8
9
|
const WINDOWS_SCREENSHOT_DEVELOPER_INSTRUCTIONS = [
|
|
9
10
|
"When capturing any screenshot on Windows, use one fresh process and make it Per-Monitor DPI Aware V2 before loading System.Windows.Forms, System.Drawing, or UI Automation, and before calling any screen, window, or bounds API.",
|
|
10
11
|
"Call user32!SetProcessDpiAwarenessContext((IntPtr)-4) in that same process and verify that it succeeded or that the process is already PMv2 before continuing.",
|
|
@@ -16,10 +17,10 @@ const WINDOWS_SCREENSHOT_DEVELOPER_INSTRUCTIONS = [
|
|
|
16
17
|
const SESSION_REQUEST_TIMEOUT_MS = 60_000;
|
|
17
18
|
const CONTROL_REQUEST_TIMEOUT_MS = 10_000;
|
|
18
19
|
const SYNC_REQUEST_TIMEOUT_MS = 5_000;
|
|
19
|
-
const THREAD_TURN_PAGE_SIZE = 100;
|
|
20
20
|
const FORK_SOURCE_TURN_PAGE_SIZE = 20;
|
|
21
21
|
const FORK_SOURCE_REQUEST_TIMEOUT_MS = 15_000;
|
|
22
22
|
const USER_RESUMABLE_THREAD_SOURCE_KINDS = ["cli", "vscode", "exec", "appServer"];
|
|
23
|
+
const BUILT_IN_CODEX_PROVIDER_ID = "openai";
|
|
23
24
|
// A timed-out fork keeps running in App Server and can create an orphan thread.
|
|
24
25
|
// Wait for its response; connection closure still rejects the request.
|
|
25
26
|
const FORK_REQUEST_TIMEOUT_MS = 0;
|
|
@@ -34,9 +35,9 @@ export class CodexRuntime {
|
|
|
34
35
|
unsubscribe;
|
|
35
36
|
unsubscribeDisconnect;
|
|
36
37
|
sessionSyncs = new Map();
|
|
38
|
+
sessionOperations = new Map();
|
|
37
39
|
localActivityDetector;
|
|
38
40
|
codexHome;
|
|
39
|
-
threadListSortKey = "recency_at";
|
|
40
41
|
releaseInFlight;
|
|
41
42
|
constructor(provider, logger) {
|
|
42
43
|
this.provider = provider;
|
|
@@ -56,6 +57,9 @@ export class CodexRuntime {
|
|
|
56
57
|
return this.codexHome ? threadWriterLockPath(this.codexHome, remoteSessionId) : undefined;
|
|
57
58
|
}
|
|
58
59
|
async createSession(input) {
|
|
60
|
+
return this.runSessionOperation(input.localSessionId, () => this.createSessionNow(input));
|
|
61
|
+
}
|
|
62
|
+
async createSessionNow(input) {
|
|
59
63
|
const client = await this.client();
|
|
60
64
|
const response = await client.request("thread/start", {
|
|
61
65
|
cwd: input.cwd,
|
|
@@ -80,6 +84,9 @@ export class CodexRuntime {
|
|
|
80
84
|
return session;
|
|
81
85
|
}
|
|
82
86
|
async resumeSession(input) {
|
|
87
|
+
return this.runSessionOperation(input.localSessionId, () => this.resumeSessionNow(input));
|
|
88
|
+
}
|
|
89
|
+
async resumeSessionNow(input) {
|
|
83
90
|
const client = await this.client();
|
|
84
91
|
const response = await client.request("thread/resume", {
|
|
85
92
|
threadId: input.remoteSessionId,
|
|
@@ -96,6 +103,9 @@ export class CodexRuntime {
|
|
|
96
103
|
return session;
|
|
97
104
|
}
|
|
98
105
|
async forkSession(input) {
|
|
106
|
+
return this.runSessionOperation(input.localSessionId, () => this.forkSessionNow(input));
|
|
107
|
+
}
|
|
108
|
+
async forkSessionNow(input) {
|
|
99
109
|
const client = await this.client();
|
|
100
110
|
const forkParams = {
|
|
101
111
|
threadId: input.remoteSessionId,
|
|
@@ -107,19 +117,10 @@ export class CodexRuntime {
|
|
|
107
117
|
...threadLifecycleParams(input.cwd),
|
|
108
118
|
...permissionParams(input.permissionMode),
|
|
109
119
|
};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
excludeTurns: true,
|
|
115
|
-
}, FORK_REQUEST_TIMEOUT_MS);
|
|
116
|
-
}
|
|
117
|
-
catch (error) {
|
|
118
|
-
if (!isUnsupportedExcludeTurnsError(error))
|
|
119
|
-
throw error;
|
|
120
|
-
this.logger.warn({ error }, "App Server does not support thread/fork excludeTurns; retrying without it.");
|
|
121
|
-
response = await client.request("thread/fork", forkParams, FORK_REQUEST_TIMEOUT_MS);
|
|
122
|
-
}
|
|
120
|
+
const response = await client.request("thread/fork", {
|
|
121
|
+
...forkParams,
|
|
122
|
+
excludeTurns: true,
|
|
123
|
+
}, FORK_REQUEST_TIMEOUT_MS);
|
|
123
124
|
const requestedTitle = normalizeTaskTitle(input.title);
|
|
124
125
|
if (requestedTitle) {
|
|
125
126
|
await client.request("thread/name/set", {
|
|
@@ -135,9 +136,13 @@ export class CodexRuntime {
|
|
|
135
136
|
return session;
|
|
136
137
|
}
|
|
137
138
|
async startTurn(sessionId, prompt) {
|
|
139
|
+
return this.runSessionOperation(sessionId, () => this.startTurnNow(sessionId, prompt));
|
|
140
|
+
}
|
|
141
|
+
async startTurnNow(sessionId, prompt) {
|
|
138
142
|
const session = this.requireSession(sessionId);
|
|
139
143
|
const client = await this.client();
|
|
140
144
|
await this.ensureSessionResumed(session, client);
|
|
145
|
+
session.canReplaceEmptyThread = false;
|
|
141
146
|
const start = () => client.request("turn/start", {
|
|
142
147
|
threadId: session.remoteSessionId,
|
|
143
148
|
input: codexUserInput(prompt),
|
|
@@ -172,24 +177,14 @@ export class CodexRuntime {
|
|
|
172
177
|
}
|
|
173
178
|
async listRemoteSessions(input = {}) {
|
|
174
179
|
const client = await this.client();
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
response = await this.requestThreadList(client, input, this.threadListSortKey);
|
|
178
|
-
}
|
|
179
|
-
catch (error) {
|
|
180
|
-
if (this.threadListSortKey !== "recency_at" || !isUnsupportedRecencySortError(error))
|
|
181
|
-
throw error;
|
|
182
|
-
this.threadListSortKey = "updated_at";
|
|
183
|
-
this.logger.warn({ error }, "App Server does not support thread/list recency_at sorting; retrying with updated_at.");
|
|
184
|
-
response = await this.requestThreadList(client, input, this.threadListSortKey);
|
|
185
|
-
}
|
|
180
|
+
const response = await this.requestThreadList(client, input);
|
|
186
181
|
const sessions = await Promise.all(response.data.map(async (thread) => {
|
|
187
182
|
const listed = remoteSessionSummary(thread);
|
|
188
183
|
if (listed.status === "active" || listed.lastTurnStatus === "inProgress")
|
|
189
184
|
return listed;
|
|
190
185
|
try {
|
|
191
|
-
const
|
|
192
|
-
return mergeRemoteSessionSummary(listed, remoteSessionSummary(
|
|
186
|
+
const turns = await this.readLatestThreadTurns(client, thread.id, "summary");
|
|
187
|
+
return mergeRemoteSessionSummary(listed, remoteSessionSummary({ ...thread, turns }));
|
|
193
188
|
}
|
|
194
189
|
catch {
|
|
195
190
|
return listed;
|
|
@@ -203,35 +198,39 @@ export class CodexRuntime {
|
|
|
203
198
|
nextCursor: response.nextCursor ?? undefined,
|
|
204
199
|
};
|
|
205
200
|
}
|
|
206
|
-
requestThreadList(client, input
|
|
201
|
+
requestThreadList(client, input) {
|
|
207
202
|
return client.request("thread/list", {
|
|
208
203
|
cursor: input.cursor,
|
|
209
204
|
limit: input.limit ?? 20,
|
|
210
|
-
sortKey,
|
|
205
|
+
sortKey: this.provider.getAgentFamily?.() === "traex" ? "updated_at" : "recency_at",
|
|
211
206
|
sortDirection: "desc",
|
|
212
207
|
sourceKinds: USER_RESUMABLE_THREAD_SOURCE_KINDS,
|
|
213
208
|
archived: false,
|
|
214
209
|
searchTerm: input.searchTerm,
|
|
215
210
|
}, CONTROL_REQUEST_TIMEOUT_MS);
|
|
216
211
|
}
|
|
217
|
-
async readRemoteSession(remoteSessionId) {
|
|
212
|
+
async readRemoteSession(remoteSessionId, view = "latest") {
|
|
218
213
|
const client = await this.client();
|
|
219
|
-
const response = await
|
|
220
|
-
|
|
214
|
+
const response = await client.request("thread/read", { threadId: remoteSessionId, includeTurns: false }, SYNC_REQUEST_TIMEOUT_MS);
|
|
215
|
+
const turns = view === "metadata" ? []
|
|
216
|
+
: await this.readLatestThreadTurns(client, remoteSessionId, view === "latest-full" ? "full" : "summary");
|
|
217
|
+
return this.decorateRemoteSession(remoteSessionId, remoteSessionSummary({ ...response.thread, turns }));
|
|
218
|
+
}
|
|
219
|
+
async listRemoteTurnSummaries(remoteSessionId, input) {
|
|
220
|
+
const response = await this.readTurnPage(await this.client(), remoteSessionId, {
|
|
221
|
+
cursor: input.cursor,
|
|
222
|
+
limit: Math.max(1, Math.min(100, Math.trunc(input.limit))),
|
|
223
|
+
itemsView: "summary",
|
|
224
|
+
});
|
|
225
|
+
return {
|
|
226
|
+
turns: remoteSessionSummary({ id: remoteSessionId, turns: response.data }).completedTurns ?? [],
|
|
227
|
+
nextCursor: response.nextCursor ?? undefined,
|
|
228
|
+
};
|
|
221
229
|
}
|
|
222
230
|
async readRemoteForkSource(remoteSessionId) {
|
|
223
231
|
const client = await this.client();
|
|
224
232
|
const metadata = await client.request("thread/read", { threadId: remoteSessionId, includeTurns: false }, FORK_SOURCE_REQUEST_TIMEOUT_MS);
|
|
225
|
-
|
|
226
|
-
try {
|
|
227
|
-
turns = await this.listRecentThreadTurnsThroughCompleted(client, remoteSessionId);
|
|
228
|
-
}
|
|
229
|
-
catch (error) {
|
|
230
|
-
if (!isUnsupportedThreadTurnsListError(error))
|
|
231
|
-
throw error;
|
|
232
|
-
this.logger.warn({ error, remoteSessionId }, "App Server does not support lightweight Fork source reads; falling back to complete thread history.");
|
|
233
|
-
return this.readRemoteSession(remoteSessionId);
|
|
234
|
-
}
|
|
233
|
+
const turns = await this.listRecentThreadTurnsThroughCompleted(client, remoteSessionId);
|
|
235
234
|
return this.decorateRemoteSession(remoteSessionId, remoteSessionSummary({
|
|
236
235
|
...metadata.thread,
|
|
237
236
|
turns,
|
|
@@ -251,12 +250,8 @@ export class CodexRuntime {
|
|
|
251
250
|
const seenCursors = new Set();
|
|
252
251
|
let cursor;
|
|
253
252
|
do {
|
|
254
|
-
const response = await
|
|
255
|
-
|
|
256
|
-
...(cursor ? { cursor } : {}),
|
|
257
|
-
limit: FORK_SOURCE_TURN_PAGE_SIZE,
|
|
258
|
-
sortDirection: "desc",
|
|
259
|
-
itemsView: "summary",
|
|
253
|
+
const response = await this.readTurnPage(client, remoteSessionId, {
|
|
254
|
+
cursor, limit: FORK_SOURCE_TURN_PAGE_SIZE, itemsView: "summary",
|
|
260
255
|
}, FORK_SOURCE_REQUEST_TIMEOUT_MS);
|
|
261
256
|
turns.push(...response.data);
|
|
262
257
|
const completedIndex = turns.findIndex((turn) => turn.status === "completed");
|
|
@@ -276,49 +271,25 @@ export class CodexRuntime {
|
|
|
276
271
|
} while (cursor);
|
|
277
272
|
return turns.reverse();
|
|
278
273
|
}
|
|
279
|
-
async
|
|
274
|
+
async readLatestThreadTurns(client, remoteSessionId, itemsView) {
|
|
275
|
+
const response = await this.readTurnPage(client, remoteSessionId, { limit: 1, itemsView });
|
|
276
|
+
return response.data;
|
|
277
|
+
}
|
|
278
|
+
async readTurnPage(client, remoteSessionId, input, timeoutMs = SYNC_REQUEST_TIMEOUT_MS) {
|
|
280
279
|
try {
|
|
281
|
-
return await client.request("thread/
|
|
280
|
+
return await client.request("thread/turns/list", {
|
|
281
|
+
threadId: remoteSessionId,
|
|
282
|
+
...(input.cursor ? { cursor: input.cursor } : {}),
|
|
283
|
+
limit: input.limit, sortDirection: "desc", itemsView: input.itemsView,
|
|
284
|
+
}, timeoutMs);
|
|
282
285
|
}
|
|
283
286
|
catch (error) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const metadata = await client.request("thread/read", { threadId: remoteSessionId, includeTurns: false }, SYNC_REQUEST_TIMEOUT_MS);
|
|
288
|
-
if (!paginated)
|
|
289
|
-
return { thread: { ...metadata.thread, turns: [] } };
|
|
290
|
-
return {
|
|
291
|
-
thread: {
|
|
292
|
-
...metadata.thread,
|
|
293
|
-
turns: await this.listThreadTurns(client, remoteSessionId),
|
|
294
|
-
},
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
async listThreadTurns(client, remoteSessionId) {
|
|
299
|
-
const turns = [];
|
|
300
|
-
const seenCursors = new Set();
|
|
301
|
-
let cursor;
|
|
302
|
-
do {
|
|
303
|
-
const response = await client.request("thread/turns/list", {
|
|
304
|
-
threadId: remoteSessionId,
|
|
305
|
-
...(cursor ? { cursor } : {}),
|
|
306
|
-
limit: THREAD_TURN_PAGE_SIZE,
|
|
307
|
-
sortDirection: "asc",
|
|
308
|
-
itemsView: "full",
|
|
309
|
-
}, SYNC_REQUEST_TIMEOUT_MS);
|
|
310
|
-
turns.push(...response.data);
|
|
311
|
-
const nextCursor = response.nextCursor ?? undefined;
|
|
312
|
-
if (!nextCursor)
|
|
313
|
-
break;
|
|
314
|
-
if (seenCursors.has(nextCursor)) {
|
|
315
|
-
this.logger.warn({ remoteSessionId, cursor: nextCursor }, "App Server repeated a thread Turn pagination cursor; stopping pagination.");
|
|
316
|
-
break;
|
|
287
|
+
if (error instanceof AppServerRequestError && error.method === "thread/turns/list"
|
|
288
|
+
&& /not materialized yet; thread\/turns\/list is unavailable before first user message/i.test(error.serverMessage)) {
|
|
289
|
+
return { data: [], nextCursor: null };
|
|
317
290
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
} while (cursor);
|
|
321
|
-
return turns;
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
322
293
|
}
|
|
323
294
|
async inspectRemoteSessionActivity(remoteSessionId) {
|
|
324
295
|
const [response, activeThreads] = await Promise.all([
|
|
@@ -341,7 +312,7 @@ export class CodexRuntime {
|
|
|
341
312
|
const existing = this.sessionSyncs.get(sessionId);
|
|
342
313
|
if (existing)
|
|
343
314
|
return existing;
|
|
344
|
-
const synchronization = this.synchronizeSessionNow(sessionId);
|
|
315
|
+
const synchronization = this.runSessionOperation(sessionId, () => this.synchronizeSessionNow(sessionId));
|
|
345
316
|
this.sessionSyncs.set(sessionId, synchronization);
|
|
346
317
|
try {
|
|
347
318
|
return await synchronization;
|
|
@@ -368,8 +339,10 @@ export class CodexRuntime {
|
|
|
368
339
|
this.logger.info({ ...(sessionId ? { sessionId } : {}), threadId: remoteSessionId, turnId }, "App Server accepted the turn interrupt request.");
|
|
369
340
|
}
|
|
370
341
|
async closeSession(sessionId) {
|
|
371
|
-
|
|
372
|
-
|
|
342
|
+
await this.runSessionOperation(sessionId, async () => {
|
|
343
|
+
const session = this.requireSession(sessionId);
|
|
344
|
+
await this.archiveRemoteSession(session.remoteSessionId);
|
|
345
|
+
});
|
|
373
346
|
}
|
|
374
347
|
async archiveRemoteSession(remoteSessionId) {
|
|
375
348
|
await (await this.client()).request("thread/archive", { threadId: remoteSessionId }, CONTROL_REQUEST_TIMEOUT_MS);
|
|
@@ -379,6 +352,9 @@ export class CodexRuntime {
|
|
|
379
352
|
}
|
|
380
353
|
}
|
|
381
354
|
async setTitle(sessionId, title) {
|
|
355
|
+
return this.runSessionOperation(sessionId, () => this.setTitleNow(sessionId, title));
|
|
356
|
+
}
|
|
357
|
+
async setTitleNow(sessionId, title) {
|
|
382
358
|
const session = this.requireSession(sessionId);
|
|
383
359
|
const normalizedTitle = normalizeTaskTitle(title);
|
|
384
360
|
if (!normalizedTitle)
|
|
@@ -387,6 +363,9 @@ export class CodexRuntime {
|
|
|
387
363
|
session.title = normalizedTitle;
|
|
388
364
|
}
|
|
389
365
|
async getGoal(sessionId) {
|
|
366
|
+
return this.runSessionOperation(sessionId, () => this.getGoalNow(sessionId));
|
|
367
|
+
}
|
|
368
|
+
async getGoalNow(sessionId) {
|
|
390
369
|
const session = this.requireSession(sessionId);
|
|
391
370
|
const client = await this.client();
|
|
392
371
|
await this.ensureSessionResumed(session, client);
|
|
@@ -394,13 +373,20 @@ export class CodexRuntime {
|
|
|
394
373
|
return response.goal ?? undefined;
|
|
395
374
|
}
|
|
396
375
|
async setGoal(sessionId, update) {
|
|
376
|
+
return this.runSessionOperation(sessionId, () => this.setGoalNow(sessionId, update));
|
|
377
|
+
}
|
|
378
|
+
async setGoalNow(sessionId, update) {
|
|
397
379
|
const session = this.requireSession(sessionId);
|
|
398
380
|
const client = await this.client();
|
|
399
381
|
await this.ensureSessionResumed(session, client);
|
|
382
|
+
session.canReplaceEmptyThread = false;
|
|
400
383
|
const response = await client.request("thread/goal/set", { threadId: session.remoteSessionId, ...update }, CONTROL_REQUEST_TIMEOUT_MS);
|
|
401
384
|
return response.goal;
|
|
402
385
|
}
|
|
403
386
|
async clearGoal(sessionId) {
|
|
387
|
+
return this.runSessionOperation(sessionId, () => this.clearGoalNow(sessionId));
|
|
388
|
+
}
|
|
389
|
+
async clearGoalNow(sessionId) {
|
|
404
390
|
const session = this.requireSession(sessionId);
|
|
405
391
|
const client = await this.client();
|
|
406
392
|
await this.ensureSessionResumed(session, client);
|
|
@@ -408,32 +394,132 @@ export class CodexRuntime {
|
|
|
408
394
|
return response.cleared;
|
|
409
395
|
}
|
|
410
396
|
async setModel(sessionId, model) {
|
|
411
|
-
this.requireSession(sessionId).model = model;
|
|
397
|
+
await this.runSessionOperation(sessionId, async () => { this.requireSession(sessionId).model = model; });
|
|
412
398
|
}
|
|
413
399
|
async setReasoningEffort(sessionId, effort) {
|
|
414
|
-
this.requireSession(sessionId).reasoningEffort = effort;
|
|
400
|
+
await this.runSessionOperation(sessionId, async () => { this.requireSession(sessionId).reasoningEffort = effort; });
|
|
415
401
|
}
|
|
416
402
|
async setPermissionMode(sessionId, mode) {
|
|
417
|
-
this.requireSession(sessionId).permissionMode = mode;
|
|
403
|
+
await this.runSessionOperation(sessionId, async () => { this.requireSession(sessionId).permissionMode = mode; });
|
|
404
|
+
}
|
|
405
|
+
async setExecutionSettings(sessionId, settings, persist) {
|
|
406
|
+
return this.runSessionOperation(sessionId, () => this.setExecutionSettingsNow(sessionId, settings, persist));
|
|
418
407
|
}
|
|
419
|
-
async
|
|
408
|
+
async setExecutionSettingsNow(sessionId, settings, persist) {
|
|
420
409
|
const session = this.requireSession(sessionId);
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
session.
|
|
433
|
-
|
|
434
|
-
|
|
410
|
+
if (session.activeTurnId)
|
|
411
|
+
throw new Error(PROVIDER_SWITCH_BUSY);
|
|
412
|
+
const client = await this.client();
|
|
413
|
+
const metadata = await client.request("thread/read", {
|
|
414
|
+
threadId: session.remoteSessionId, includeTurns: false,
|
|
415
|
+
}, SYNC_REQUEST_TIMEOUT_MS);
|
|
416
|
+
const localActive = await this.localActivityDetector?.activeThreads([session.remoteSessionId]);
|
|
417
|
+
if (metadata.thread.status?.type === "active" || localActive?.has(session.remoteSessionId) || session.activeTurnId) {
|
|
418
|
+
throw new Error(PROVIDER_SWITCH_BUSY);
|
|
419
|
+
}
|
|
420
|
+
let empty = false;
|
|
421
|
+
if (session.canReplaceEmptyThread && !metadata.thread.forkedFromId && !metadata.thread.preview?.trim()) {
|
|
422
|
+
try {
|
|
423
|
+
const turns = await this.readLatestThreadTurns(client, session.remoteSessionId, "summary");
|
|
424
|
+
empty = turns.length === 0;
|
|
425
|
+
if (!empty)
|
|
426
|
+
session.canReplaceEmptyThread = false;
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
if (!(error instanceof AppServerRequestError) || error.method !== "thread/turns/list"
|
|
430
|
+
|| !/missing source rollout|no rollout found/iu.test(error.serverMessage))
|
|
431
|
+
throw error;
|
|
432
|
+
empty = true;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
const previous = { ...session };
|
|
436
|
+
let replacementId;
|
|
437
|
+
let detached = false;
|
|
438
|
+
try {
|
|
439
|
+
const params = {
|
|
440
|
+
cwd: session.cwd, modelProvider: settings.modelProvider, model: settings.model,
|
|
441
|
+
...threadLifecycleParams(session.cwd), ...permissionParams(settings.permissionMode),
|
|
442
|
+
};
|
|
443
|
+
if (!empty) {
|
|
444
|
+
await this.detachThreadForSettings(client, session);
|
|
445
|
+
detached = true;
|
|
446
|
+
}
|
|
447
|
+
const response = empty
|
|
448
|
+
? await client.request("thread/start", {
|
|
449
|
+
...params, threadSource: "user", allowProviderModelFallback: false,
|
|
450
|
+
}, SESSION_REQUEST_TIMEOUT_MS)
|
|
451
|
+
: await client.request("thread/resume", {
|
|
452
|
+
...params, threadId: session.remoteSessionId, excludeTurns: true,
|
|
453
|
+
}, SESSION_REQUEST_TIMEOUT_MS);
|
|
454
|
+
if (empty)
|
|
455
|
+
replacementId = response.thread.id;
|
|
456
|
+
assertProviderSettingsApplied(response, settings);
|
|
457
|
+
if (!empty && response.thread.id !== session.remoteSessionId)
|
|
458
|
+
throw new Error("App Server 返回了不同的任务 ID,未保存设置。");
|
|
459
|
+
if (empty && session.title)
|
|
460
|
+
await client.request("thread/name/set", {
|
|
461
|
+
threadId: response.thread.id, name: session.title,
|
|
462
|
+
}, SESSION_REQUEST_TIMEOUT_MS);
|
|
463
|
+
const candidate = { ...session, ...settings, remoteSessionId: response.thread.id,
|
|
464
|
+
needsResume: false, settingsRecoveryError: undefined };
|
|
465
|
+
await persist?.(candidate);
|
|
466
|
+
Object.assign(session, candidate);
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
if (replacementId)
|
|
470
|
+
await this.discardUnusedThread(client, replacementId);
|
|
471
|
+
let recovery = "原任务设置未更改。";
|
|
472
|
+
if (detached) {
|
|
473
|
+
try {
|
|
474
|
+
await this.detachThreadForSettings(client, previous);
|
|
475
|
+
const restored = await client.request("thread/resume", {
|
|
476
|
+
threadId: previous.remoteSessionId, excludeTurns: true, cwd: previous.cwd,
|
|
477
|
+
modelProvider: previous.modelProvider, model: previous.model,
|
|
478
|
+
...threadLifecycleParams(previous.cwd), ...permissionParams(previous.permissionMode),
|
|
479
|
+
}, SESSION_REQUEST_TIMEOUT_MS);
|
|
480
|
+
if (previous.modelProvider && previous.model)
|
|
481
|
+
assertProviderSettingsApplied(restored, {
|
|
482
|
+
modelProvider: previous.modelProvider, model: previous.model,
|
|
483
|
+
});
|
|
484
|
+
if (restored.thread.id !== previous.remoteSessionId)
|
|
485
|
+
throw new Error("恢复时返回了不同的任务 ID。");
|
|
486
|
+
session.needsResume = false;
|
|
487
|
+
session.settingsRecoveryError = undefined;
|
|
488
|
+
}
|
|
489
|
+
catch (restoreError) {
|
|
490
|
+
this.logger.warn({ error: restoreError, sessionId }, "Failed to restore Provider after a rejected settings change.");
|
|
491
|
+
recovery = "原设置仍保留,但远端恢复失败;请重新切换 Provider,确认成功后再发送消息。";
|
|
492
|
+
session.settingsRecoveryError = recovery;
|
|
493
|
+
session.needsResume = true;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
throw new Error(`Provider 切换失败:${providerSwitchFailure(error)} ${recovery}`, { cause: error });
|
|
497
|
+
}
|
|
498
|
+
if (replacementId)
|
|
499
|
+
await this.discardUnusedThread(client, previous.remoteSessionId);
|
|
435
500
|
return session;
|
|
436
501
|
}
|
|
502
|
+
async detachThreadForSettings(client, session) {
|
|
503
|
+
const current = await client.request("thread/read", {
|
|
504
|
+
threadId: session.remoteSessionId, includeTurns: false,
|
|
505
|
+
}, SYNC_REQUEST_TIMEOUT_MS);
|
|
506
|
+
if (session.activeTurnId || current.thread.status?.type === "active")
|
|
507
|
+
throw new Error(PROVIDER_SWITCH_BUSY);
|
|
508
|
+
const result = await client.request("thread/unsubscribe", {
|
|
509
|
+
threadId: session.remoteSessionId,
|
|
510
|
+
}, CONTROL_REQUEST_TIMEOUT_MS);
|
|
511
|
+
if (result.status !== "unsubscribed" && result.status !== "notLoaded") {
|
|
512
|
+
throw new Error("当前任务仍由其他客户端加载,无法安全切换 Provider。请先在原客户端释放任务。");
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
async discardUnusedThread(client, threadId) {
|
|
516
|
+
try {
|
|
517
|
+
await client.request("thread/unsubscribe", { threadId }, CONTROL_REQUEST_TIMEOUT_MS);
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
this.logger.warn({ error, threadId }, "Failed to release an unused empty Thread after changing Provider.");
|
|
521
|
+
}
|
|
522
|
+
}
|
|
437
523
|
async respondToApproval(sessionId, requestId, decision) {
|
|
438
524
|
const pending = this.approvals.get(requestId);
|
|
439
525
|
if (!pending || pending.sessionId !== sessionId)
|
|
@@ -471,6 +557,13 @@ export class CodexRuntime {
|
|
|
471
557
|
...(id === defaultProvider ? { isDefault: true } : {}),
|
|
472
558
|
});
|
|
473
559
|
}
|
|
560
|
+
if (this.provider.getAgentFamily?.() === "codex" && !providers.has(BUILT_IN_CODEX_PROVIDER_ID)) {
|
|
561
|
+
providers.set(BUILT_IN_CODEX_PROVIDER_ID, {
|
|
562
|
+
id: BUILT_IN_CODEX_PROVIDER_ID,
|
|
563
|
+
displayName: "OpenAI",
|
|
564
|
+
...(!defaultProvider ? { isDefault: true } : {}),
|
|
565
|
+
});
|
|
566
|
+
}
|
|
474
567
|
if (defaultProvider && !providers.has(defaultProvider)) {
|
|
475
568
|
providers.set(defaultProvider, { id: defaultProvider, isDefault: true });
|
|
476
569
|
}
|
|
@@ -484,7 +577,7 @@ export class CodexRuntime {
|
|
|
484
577
|
if (this.releaseInFlight)
|
|
485
578
|
return this.releaseInFlight;
|
|
486
579
|
const activeSessionIds = [...this.sessions.values()]
|
|
487
|
-
.filter((session) => Boolean(session.activeTurnId))
|
|
580
|
+
.filter((session) => Boolean(session.activeTurnId) || this.sessionOperations.has(session.localSessionId))
|
|
488
581
|
.map((session) => session.localSessionId);
|
|
489
582
|
if (activeSessionIds.length > 0 && options.force !== true) {
|
|
490
583
|
return { status: "busy", activeSessionIds };
|
|
@@ -731,6 +824,8 @@ export class CodexRuntime {
|
|
|
731
824
|
?? models.find((item) => item.isDefault)?.defaultReasoningEffort;
|
|
732
825
|
}
|
|
733
826
|
async ensureSessionResumed(session, client) {
|
|
827
|
+
if (session.settingsRecoveryError)
|
|
828
|
+
throw new Error(session.settingsRecoveryError);
|
|
734
829
|
if (!session.needsResume)
|
|
735
830
|
return;
|
|
736
831
|
await this.resumeAppServerSession(session, client);
|
|
@@ -749,9 +844,12 @@ export class CodexRuntime {
|
|
|
749
844
|
}
|
|
750
845
|
async synchronizeSessionNow(sessionId) {
|
|
751
846
|
const session = this.requireSession(sessionId);
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
|
|
847
|
+
const client = await this.client();
|
|
848
|
+
const response = await client.request("thread/read", { threadId: session.remoteSessionId, includeTurns: false }, SYNC_REQUEST_TIMEOUT_MS);
|
|
849
|
+
const turns = await this.readLatestThreadTurns(client, session.remoteSessionId, "full");
|
|
850
|
+
const thread = { ...response.thread, turns };
|
|
851
|
+
this.reconcileThreadSnapshot(session, thread);
|
|
852
|
+
return { ...session, remoteSummary: await this.decorateRemoteSession(session.remoteSessionId, remoteSessionSummary(thread)) };
|
|
755
853
|
}
|
|
756
854
|
reconcileThreadSnapshot(session, thread) {
|
|
757
855
|
const turns = Array.isArray(thread.turns) ? thread.turns : [];
|
|
@@ -796,6 +894,7 @@ export class CodexRuntime {
|
|
|
796
894
|
this.finishSnapshotTurn(session, latest);
|
|
797
895
|
}
|
|
798
896
|
adoptTurn(session, turnId, startedAt) {
|
|
897
|
+
session.canReplaceEmptyThread = false;
|
|
799
898
|
session.activeTurnId = turnId;
|
|
800
899
|
session.activeTurnStartedAt = startedAt;
|
|
801
900
|
session.finalText = "";
|
|
@@ -873,6 +972,7 @@ export class CodexRuntime {
|
|
|
873
972
|
generatedImagePaths: [],
|
|
874
973
|
messagePhases: new Map(),
|
|
875
974
|
needsResume: false,
|
|
975
|
+
canReplaceEmptyThread: !("remoteSessionId" in input),
|
|
876
976
|
};
|
|
877
977
|
}
|
|
878
978
|
requireSession(sessionId) {
|
|
@@ -881,6 +981,18 @@ export class CodexRuntime {
|
|
|
881
981
|
throw new Error(`Unknown App Server session: ${sessionId}`);
|
|
882
982
|
return session;
|
|
883
983
|
}
|
|
984
|
+
async runSessionOperation(sessionId, operation) {
|
|
985
|
+
const previous = this.sessionOperations.get(sessionId);
|
|
986
|
+
const current = (previous ?? Promise.resolve()).catch(() => undefined).then(operation);
|
|
987
|
+
this.sessionOperations.set(sessionId, current);
|
|
988
|
+
try {
|
|
989
|
+
return await current;
|
|
990
|
+
}
|
|
991
|
+
finally {
|
|
992
|
+
if (this.sessionOperations.get(sessionId) === current)
|
|
993
|
+
this.sessionOperations.delete(sessionId);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
884
996
|
requireActiveTurn(sessionId, turnId) {
|
|
885
997
|
const session = this.requireSession(sessionId);
|
|
886
998
|
if (session.activeTurnId !== turnId)
|
|
@@ -976,62 +1088,11 @@ function extractGeneratedImagePaths(turn) {
|
|
|
976
1088
|
function uniqueStrings(values) {
|
|
977
1089
|
return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
|
|
978
1090
|
}
|
|
979
|
-
function isUnsupportedExcludeTurnsError(error) {
|
|
980
|
-
if (!(error instanceof AppServerRequestError) || error.method !== "thread/fork")
|
|
981
|
-
return false;
|
|
982
|
-
const data = typeof error.data === "string"
|
|
983
|
-
? error.data
|
|
984
|
-
: error.data === undefined
|
|
985
|
-
? ""
|
|
986
|
-
: JSON.stringify(error.data);
|
|
987
|
-
const details = `${error.serverMessage} ${data}`;
|
|
988
|
-
if (!details.toLowerCase().replaceAll(/[^a-z]/g, "").includes("excludeturns"))
|
|
989
|
-
return false;
|
|
990
|
-
return error.code === -32602
|
|
991
|
-
|| /unknown|unrecognized|unexpected|unsupported|not supported|experimental/i.test(details);
|
|
992
|
-
}
|
|
993
1091
|
function isInvalidWorkingDirectoryError(error) {
|
|
994
1092
|
return error instanceof AppServerRequestError
|
|
995
1093
|
&& error.method === "turn/start"
|
|
996
1094
|
&& /invalid cwd/i.test(`${error.serverMessage} ${error.data === undefined ? "" : JSON.stringify(error.data)}`);
|
|
997
1095
|
}
|
|
998
|
-
function isUnsupportedRecencySortError(error) {
|
|
999
|
-
if (!(error instanceof AppServerRequestError) || error.method !== "thread/list")
|
|
1000
|
-
return false;
|
|
1001
|
-
const data = typeof error.data === "string"
|
|
1002
|
-
? error.data
|
|
1003
|
-
: error.data === undefined
|
|
1004
|
-
? ""
|
|
1005
|
-
: JSON.stringify(error.data);
|
|
1006
|
-
const details = `${error.serverMessage} ${data}`;
|
|
1007
|
-
return /recency_at/i.test(details)
|
|
1008
|
-
&& /unknown|unrecognized|unexpected|unsupported|not supported|invalid/i.test(details);
|
|
1009
|
-
}
|
|
1010
|
-
function isPaginatedThreadReadError(error) {
|
|
1011
|
-
const details = appServerErrorDetails(error);
|
|
1012
|
-
return /paginated threads?[^\n]*do(?:es)? not support[^\n]*thread\/read[^\n]*includeTurns\s*=\s*true/i.test(details);
|
|
1013
|
-
}
|
|
1014
|
-
function isUnsupportedThreadTurnsListError(error) {
|
|
1015
|
-
if (!(error instanceof AppServerRequestError) || error.method !== "thread/turns/list")
|
|
1016
|
-
return false;
|
|
1017
|
-
return error.code === -32601
|
|
1018
|
-
|| /method not found|unknown method|unsupported|not supported/i.test(appServerErrorDetails(error));
|
|
1019
|
-
}
|
|
1020
|
-
function isUnmaterializedThreadReadError(error) {
|
|
1021
|
-
const details = appServerErrorDetails(error);
|
|
1022
|
-
return /thread[^\n]*(?:not materialized|not loaded)[^\n]*includeTurns[^\n]*(?:unavailable|before first user message)/i
|
|
1023
|
-
.test(details);
|
|
1024
|
-
}
|
|
1025
|
-
function appServerErrorDetails(error) {
|
|
1026
|
-
if (!(error instanceof AppServerRequestError))
|
|
1027
|
-
return error instanceof Error ? error.message : String(error);
|
|
1028
|
-
const data = typeof error.data === "string"
|
|
1029
|
-
? error.data
|
|
1030
|
-
: error.data === undefined
|
|
1031
|
-
? ""
|
|
1032
|
-
: JSON.stringify(error.data);
|
|
1033
|
-
return `${error.message} ${error.serverMessage} ${data}`;
|
|
1034
|
-
}
|
|
1035
1096
|
function remoteSessionSummary(thread) {
|
|
1036
1097
|
const lastTurn = thread.turns?.at(-1);
|
|
1037
1098
|
const lastCompletedTurn = [...(thread.turns ?? [])]
|