@sumicom/quicksave 0.8.16 → 0.8.18
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/dist/ai/codexAppServer/cardAdapter.d.ts +29 -5
- package/dist/ai/codexAppServer/cardAdapter.d.ts.map +1 -1
- package/dist/ai/codexAppServer/cardAdapter.js +35 -14
- package/dist/ai/codexAppServer/cardAdapter.js.map +1 -1
- package/dist/ai/codexAppServer/provider.d.ts +24 -3
- package/dist/ai/codexAppServer/provider.d.ts.map +1 -1
- package/dist/ai/codexAppServer/provider.js +320 -34
- package/dist/ai/codexAppServer/provider.js.map +1 -1
- package/dist/ai/provider.d.ts +5 -1
- package/dist/ai/provider.d.ts.map +1 -1
- package/dist/ai/sessionManager.d.ts +3 -1
- package/dist/ai/sessionManager.d.ts.map +1 -1
- package/dist/ai/sessionManager.js +18 -0
- package/dist/ai/sessionManager.js.map +1 -1
- package/dist/handlers/messageHandler.d.ts +4 -0
- package/dist/handlers/messageHandler.d.ts.map +1 -1
- package/dist/handlers/messageHandler.js +85 -8
- package/dist/handlers/messageHandler.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/service/ipcClient.js +1 -1
- package/dist/service/run.js +1 -1
- package/dist/service/types.js +1 -1
- package/package.json +3 -3
|
@@ -5,7 +5,7 @@ import { buildSandboxMcpServerConfig, SANDBOX_MCP_NAME } from '../sandboxMcp.js'
|
|
|
5
5
|
import { normalizePermissionLevelForAgent } from '../provider.js';
|
|
6
6
|
import { makeQueuedUserPrompt, queueStateFor } from '../queuedUserPrompts.js';
|
|
7
7
|
import { getEventStore } from '../../storage/eventStore.js';
|
|
8
|
-
import {
|
|
8
|
+
import { createCodexTurnStreamConsumer, } from './cardAdapter.js';
|
|
9
9
|
import { codexApprovalResponse, codexApprovalToPermissionPrompt, } from './approvalMapping.js';
|
|
10
10
|
import { detectCodexVersion, spawnAppServer } from './processManager.js';
|
|
11
11
|
import { RuntimeOverrideStore } from './overrideStore.js';
|
|
@@ -14,6 +14,13 @@ import { codexProtocolPreview } from './protocolLog.js';
|
|
|
14
14
|
import { codexServerRequestInputId } from './serverRequestIds.js';
|
|
15
15
|
const __ownDir = dirname(fileURLToPath(import.meta.url));
|
|
16
16
|
const __aiDir = dirname(__ownDir);
|
|
17
|
+
const CODEX_BUILT_IN_SLASH_COMMANDS = [
|
|
18
|
+
{
|
|
19
|
+
name: 'goal',
|
|
20
|
+
description: 'Manage Codex goal mode',
|
|
21
|
+
argumentHint: 'pause | resume | clear | set <objective>',
|
|
22
|
+
},
|
|
23
|
+
];
|
|
17
24
|
/**
|
|
18
25
|
* Codex provider driving the JSON-RPC v2 `app-server` protocol.
|
|
19
26
|
* Phase 2 of the migration — see
|
|
@@ -114,6 +121,29 @@ export class CodexAppServerProvider {
|
|
|
114
121
|
scheduleInitialTurn(session, opts.prompt, opts.attachments);
|
|
115
122
|
return { sessionId: response.thread.id, session };
|
|
116
123
|
}
|
|
124
|
+
async listNativeSessions(opts) {
|
|
125
|
+
const handle = await spawnCodexNativeListAppServer();
|
|
126
|
+
try {
|
|
127
|
+
const [activeThreads, archivedThreads] = await Promise.all([
|
|
128
|
+
listCodexThreads(handle, opts?.cwd, false),
|
|
129
|
+
listCodexThreads(handle, opts?.cwd, true),
|
|
130
|
+
]);
|
|
131
|
+
const byId = new Map();
|
|
132
|
+
for (const item of [
|
|
133
|
+
...activeThreads.map((thread) => codexThreadToNativeSession(thread, false)),
|
|
134
|
+
...archivedThreads.map((thread) => codexThreadToNativeSession(thread, true)),
|
|
135
|
+
]) {
|
|
136
|
+
const existing = byId.get(item.sessionId);
|
|
137
|
+
if (!existing || item.lastInteractionAt > existing.lastInteractionAt) {
|
|
138
|
+
byId.set(item.sessionId, item);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return Array.from(byId.values()).sort((a, b) => b.lastInteractionAt - a.lastInteractionAt);
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
await handle.shutdown().catch(() => { });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
117
147
|
}
|
|
118
148
|
function scheduleInitialTurn(session, prompt, attachments) {
|
|
119
149
|
// Give SessionManager and messageHandler one macrotask to register the
|
|
@@ -135,8 +165,13 @@ export class CodexAppServerSession {
|
|
|
135
165
|
currentTurnId = null;
|
|
136
166
|
pendingTurns = [];
|
|
137
167
|
running = false;
|
|
168
|
+
startingRunTurn = false;
|
|
169
|
+
prestartedRunTurnId = null;
|
|
138
170
|
exited = false;
|
|
171
|
+
turnConsumers = new Map();
|
|
172
|
+
settledTurnIds = new Set();
|
|
139
173
|
unsubscribeSessionNotifications = null;
|
|
174
|
+
unsubscribeTransportClose = null;
|
|
140
175
|
constructor(args) {
|
|
141
176
|
this.handle = args.handle;
|
|
142
177
|
this.tokens = args.tokens;
|
|
@@ -152,6 +187,9 @@ export class CodexAppServerSession {
|
|
|
152
187
|
this.unsubscribeSessionNotifications = this.handle.rpc.onNotification((notification) => {
|
|
153
188
|
this.handleSessionNotification(notification);
|
|
154
189
|
});
|
|
190
|
+
this.unsubscribeTransportClose = this.handle.rpc.onClose(() => {
|
|
191
|
+
this.handleTransportClosed();
|
|
192
|
+
});
|
|
155
193
|
// If the child exits unexpectedly, mark us dead and notify SessionManager.
|
|
156
194
|
this.handle.child.once('exit', () => {
|
|
157
195
|
if (this.exited)
|
|
@@ -164,9 +202,9 @@ export class CodexAppServerSession {
|
|
|
164
202
|
return !this.exited;
|
|
165
203
|
}
|
|
166
204
|
sendUserMessage(prompt, attachments) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
this.
|
|
205
|
+
const activeTurnId = this.currentTurnId;
|
|
206
|
+
if (activeTurnId) {
|
|
207
|
+
void this.steerOrQueue(prompt, attachments, activeTurnId);
|
|
170
208
|
return;
|
|
171
209
|
}
|
|
172
210
|
void this.runTurn(prompt, attachments);
|
|
@@ -174,12 +212,7 @@ export class CodexAppServerSession {
|
|
|
174
212
|
interruptThenSendUserMessage(prompt, attachments) {
|
|
175
213
|
if (this.currentTurnId)
|
|
176
214
|
this.interrupt();
|
|
177
|
-
|
|
178
|
-
this.pendingTurns.push(makeQueuedUserPrompt(prompt, attachments));
|
|
179
|
-
this.callbacks.onQueueStateChange?.(this.threadId);
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
void this.runTurn(prompt, attachments);
|
|
215
|
+
this.enqueueOrRunTurn(prompt, attachments);
|
|
183
216
|
}
|
|
184
217
|
getQueueState() {
|
|
185
218
|
return queueStateFor(this.pendingTurns, this.currentTurnId !== null);
|
|
@@ -230,16 +263,20 @@ export class CodexAppServerSession {
|
|
|
230
263
|
cwds: opts?.cwd ? [opts.cwd] : [],
|
|
231
264
|
forceReload: opts?.forceReload === true,
|
|
232
265
|
});
|
|
233
|
-
|
|
266
|
+
const skills = codexSkillsToSlashCommands(response, opts?.cwd)
|
|
267
|
+
.filter((command) => !CODEX_BUILT_IN_SLASH_COMMANDS.some((builtin) => builtin.name === command.name));
|
|
268
|
+
return [...CODEX_BUILT_IN_SLASH_COMMANDS, ...skills];
|
|
234
269
|
}
|
|
235
270
|
interrupt() {
|
|
236
|
-
|
|
271
|
+
const turnId = this.currentTurnId;
|
|
272
|
+
if (!turnId)
|
|
237
273
|
return;
|
|
238
274
|
void this.handle.rpc
|
|
239
|
-
.request('turn/interrupt', { threadId: this.threadId, turnId
|
|
275
|
+
.request('turn/interrupt', { threadId: this.threadId, turnId })
|
|
240
276
|
.catch(() => {
|
|
241
277
|
/* best-effort */
|
|
242
278
|
});
|
|
279
|
+
this.closeTurnAsInterrupted(turnId);
|
|
243
280
|
}
|
|
244
281
|
async kill() {
|
|
245
282
|
if (this.exited)
|
|
@@ -248,6 +285,9 @@ export class CodexAppServerSession {
|
|
|
248
285
|
this.pendingTurns = [];
|
|
249
286
|
this.unsubscribeSessionNotifications?.();
|
|
250
287
|
this.unsubscribeSessionNotifications = null;
|
|
288
|
+
this.unsubscribeTransportClose?.();
|
|
289
|
+
this.unsubscribeTransportClose = null;
|
|
290
|
+
this.closeTurnConsumersAsInterrupted();
|
|
251
291
|
try {
|
|
252
292
|
await this.cardBuilder.persistCards();
|
|
253
293
|
}
|
|
@@ -267,8 +307,8 @@ export class CodexAppServerSession {
|
|
|
267
307
|
// best-effort
|
|
268
308
|
}
|
|
269
309
|
}
|
|
270
|
-
/** Run a single turn end-to-end: cb.startNewTurn → cb.userMessage
|
|
271
|
-
* turn/start →
|
|
310
|
+
/** Run a single explicit turn end-to-end: cb.startNewTurn → cb.userMessage
|
|
311
|
+
* → turn/start → wait for the session-routed turn consumer to settle. */
|
|
272
312
|
async runTurn(prompt, attachments) {
|
|
273
313
|
if (this.exited)
|
|
274
314
|
return;
|
|
@@ -317,6 +357,22 @@ export class CodexAppServerSession {
|
|
|
317
357
|
this.callbacks.onQueueStateChange?.(this.threadId);
|
|
318
358
|
return true;
|
|
319
359
|
}
|
|
360
|
+
async steerOrQueue(prompt, attachments, expectedTurnId) {
|
|
361
|
+
const steered = await this.steerCurrentTurn(prompt, attachments, expectedTurnId);
|
|
362
|
+
if (steered)
|
|
363
|
+
return;
|
|
364
|
+
this.enqueueOrRunTurn(prompt, attachments);
|
|
365
|
+
}
|
|
366
|
+
enqueueOrRunTurn(prompt, attachments) {
|
|
367
|
+
if (this.exited)
|
|
368
|
+
return;
|
|
369
|
+
if (this.running || this.currentTurnId) {
|
|
370
|
+
this.pendingTurns.push(makeQueuedUserPrompt(prompt, attachments));
|
|
371
|
+
this.callbacks.onQueueStateChange?.(this.threadId);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
void this.runTurn(prompt, attachments);
|
|
375
|
+
}
|
|
320
376
|
async steerCurrentTurn(prompt, attachments, expectedTurnId, opts) {
|
|
321
377
|
try {
|
|
322
378
|
if (attachments && attachments.length > 0) {
|
|
@@ -372,17 +428,24 @@ export class CodexAppServerSession {
|
|
|
372
428
|
input: attachmentsToCodexUserInput(prompt, attachments),
|
|
373
429
|
...drained,
|
|
374
430
|
};
|
|
375
|
-
|
|
431
|
+
this.startingRunTurn = true;
|
|
432
|
+
this.prestartedRunTurnId = null;
|
|
433
|
+
let response;
|
|
434
|
+
try {
|
|
435
|
+
response = await this.handle.rpc.request('turn/start', params);
|
|
436
|
+
}
|
|
437
|
+
finally {
|
|
438
|
+
this.startingRunTurn = false;
|
|
439
|
+
}
|
|
376
440
|
this.overrideStore.commit();
|
|
377
441
|
turnId = response.turn.id;
|
|
378
442
|
this.currentTurnId = turnId;
|
|
379
443
|
cb.setCurrentTurnId(turnId);
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}, this.callbacks);
|
|
444
|
+
const prestartedTurnId = this.prestartedRunTurnId;
|
|
445
|
+
if (prestartedTurnId && prestartedTurnId !== turnId) {
|
|
446
|
+
console.warn(`[codex-app] turn/start response mismatch session=${this.threadId.slice(0, 8)} response=${turnId.slice(0, 8)} notification=${prestartedTurnId.slice(0, 8)}`);
|
|
447
|
+
}
|
|
448
|
+
const result = await this.beginTurnConsumer(turnId, { managedByRunTurn: true }).result;
|
|
386
449
|
void result; // settled; stream-end already emitted by adapter
|
|
387
450
|
}
|
|
388
451
|
catch (err) {
|
|
@@ -396,15 +459,10 @@ export class CodexAppServerSession {
|
|
|
396
459
|
});
|
|
397
460
|
}
|
|
398
461
|
finally {
|
|
399
|
-
this.
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
catch {
|
|
404
|
-
// best-effort
|
|
405
|
-
}
|
|
406
|
-
cb.clearCards();
|
|
407
|
-
this.callbacks.onTurnSettled?.(this.threadId);
|
|
462
|
+
this.startingRunTurn = false;
|
|
463
|
+
const lifecycleTurnId = turnId ?? this.prestartedRunTurnId;
|
|
464
|
+
this.prestartedRunTurnId = null;
|
|
465
|
+
await this.finishTurnLifecycle(lifecycleTurnId);
|
|
408
466
|
}
|
|
409
467
|
}
|
|
410
468
|
async refreshGoalConfigAndReturn() {
|
|
@@ -431,8 +489,96 @@ export class CodexAppServerSession {
|
|
|
431
489
|
}
|
|
432
490
|
return out;
|
|
433
491
|
}
|
|
492
|
+
beginTurnConsumer(turnId, opts) {
|
|
493
|
+
const existing = this.turnConsumers.get(turnId);
|
|
494
|
+
if (existing)
|
|
495
|
+
return existing;
|
|
496
|
+
this.settledTurnIds.delete(turnId);
|
|
497
|
+
const consumer = createCodexTurnStreamConsumer(this.cardBuilder, {
|
|
498
|
+
sessionId: this.threadId,
|
|
499
|
+
threadId: this.threadId,
|
|
500
|
+
turnId,
|
|
501
|
+
tokens: this.tokens,
|
|
502
|
+
}, this.callbacks);
|
|
503
|
+
this.turnConsumers.set(turnId, consumer);
|
|
504
|
+
void consumer.result.then(() => {
|
|
505
|
+
void this.handleTurnConsumerSettled(turnId, consumer, opts.managedByRunTurn).catch((err) => {
|
|
506
|
+
console.warn(`[codex-app] failed to settle turn consumer session=${this.threadId.slice(0, 8)} turn=${turnId.slice(0, 8)} error=${err instanceof Error ? err.message : String(err)}`);
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
return consumer;
|
|
510
|
+
}
|
|
511
|
+
async handleTurnConsumerSettled(turnId, consumer, managedByRunTurn) {
|
|
512
|
+
if (this.turnConsumers.get(turnId) === consumer) {
|
|
513
|
+
this.turnConsumers.delete(turnId);
|
|
514
|
+
}
|
|
515
|
+
this.rememberSettledTurn(turnId);
|
|
516
|
+
consumer.dispose();
|
|
517
|
+
if (managedByRunTurn)
|
|
518
|
+
return;
|
|
519
|
+
await this.finishTurnLifecycle(turnId);
|
|
520
|
+
this.drainPendingTurnsAfterObservedTurn();
|
|
521
|
+
}
|
|
522
|
+
async finishTurnLifecycle(turnId) {
|
|
523
|
+
const builderTurnId = this.cardBuilder.getCurrentTurnId();
|
|
524
|
+
const shouldClearCards = !turnId || !builderTurnId || builderTurnId === turnId;
|
|
525
|
+
if (!turnId || this.currentTurnId === turnId) {
|
|
526
|
+
this.currentTurnId = null;
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
529
|
+
await this.cardBuilder.persistCards();
|
|
530
|
+
}
|
|
531
|
+
catch {
|
|
532
|
+
// best-effort
|
|
533
|
+
}
|
|
534
|
+
if (shouldClearCards) {
|
|
535
|
+
this.cardBuilder.clearCards();
|
|
536
|
+
}
|
|
537
|
+
this.callbacks.onTurnSettled?.(this.threadId);
|
|
538
|
+
}
|
|
539
|
+
drainPendingTurnsAfterObservedTurn() {
|
|
540
|
+
if (this.exited || this.running || this.currentTurnId || this.pendingTurns.length === 0) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
const next = this.pendingTurns.shift();
|
|
544
|
+
this.callbacks.onQueueStateChange?.(this.threadId);
|
|
545
|
+
void this.runTurn(next.prompt, next.attachments);
|
|
546
|
+
}
|
|
547
|
+
closeTurnConsumersAsInterrupted() {
|
|
548
|
+
for (const consumer of this.turnConsumers.values()) {
|
|
549
|
+
consumer.closeAsInterrupted();
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
closeTurnAsInterrupted(turnId) {
|
|
553
|
+
const consumer = this.turnConsumers.get(turnId);
|
|
554
|
+
if (consumer) {
|
|
555
|
+
consumer.closeAsInterrupted();
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
this.callbacks.emitStreamEnd({
|
|
559
|
+
sessionId: this.threadId,
|
|
560
|
+
turnId,
|
|
561
|
+
success: false,
|
|
562
|
+
interrupted: true,
|
|
563
|
+
});
|
|
564
|
+
void this.finishTurnLifecycle(turnId);
|
|
565
|
+
}
|
|
566
|
+
handleTransportClosed() {
|
|
567
|
+
this.closeTurnConsumersAsInterrupted();
|
|
568
|
+
}
|
|
569
|
+
rememberSettledTurn(turnId) {
|
|
570
|
+
this.settledTurnIds.add(turnId);
|
|
571
|
+
if (this.settledTurnIds.size <= 100)
|
|
572
|
+
return;
|
|
573
|
+
const oldest = this.settledTurnIds.values().next().value;
|
|
574
|
+
if (oldest)
|
|
575
|
+
this.settledTurnIds.delete(oldest);
|
|
576
|
+
}
|
|
434
577
|
handleSessionNotification(notification) {
|
|
435
578
|
try {
|
|
579
|
+
this.observeTokenUsageNotification(notification);
|
|
580
|
+
const turnId = this.ensureTurnConsumerForNotification(notification);
|
|
581
|
+
this.dispatchTurnNotification(notification, turnId);
|
|
436
582
|
switch (notification.method) {
|
|
437
583
|
case 'thread/goal/updated': {
|
|
438
584
|
const params = notification.params;
|
|
@@ -456,6 +602,53 @@ export class CodexAppServerSession {
|
|
|
456
602
|
console.warn(`[codex-app] failed to handle session notification method=${notification.method} session=${this.threadId.slice(0, 8)} params=${codexProtocolPreview(notification.params)} error=${err instanceof Error ? err.message : String(err)}`);
|
|
457
603
|
}
|
|
458
604
|
}
|
|
605
|
+
observeTokenUsageNotification(notification) {
|
|
606
|
+
if (notification.method !== 'thread/tokenUsage/updated')
|
|
607
|
+
return;
|
|
608
|
+
if (!notificationBelongsToThread(notification.params, this.threadId))
|
|
609
|
+
return;
|
|
610
|
+
this.tokens.observe(notification.params);
|
|
611
|
+
}
|
|
612
|
+
ensureTurnConsumerForNotification(notification) {
|
|
613
|
+
if (!notificationBelongsToThread(notification.params, this.threadId))
|
|
614
|
+
return null;
|
|
615
|
+
const turnId = notificationTurnId(notification);
|
|
616
|
+
if (!turnId)
|
|
617
|
+
return null;
|
|
618
|
+
if (this.settledTurnIds.has(turnId))
|
|
619
|
+
return turnId;
|
|
620
|
+
const existing = this.turnConsumers.get(turnId);
|
|
621
|
+
if (existing)
|
|
622
|
+
return turnId;
|
|
623
|
+
if (!shouldCreateTurnConsumerForNotification(notification.method))
|
|
624
|
+
return turnId;
|
|
625
|
+
const managedByRunTurn = this.startingRunTurn || this.currentTurnId === turnId;
|
|
626
|
+
if (this.startingRunTurn) {
|
|
627
|
+
this.prestartedRunTurnId = turnId;
|
|
628
|
+
}
|
|
629
|
+
if (managedByRunTurn) {
|
|
630
|
+
this.currentTurnId = turnId;
|
|
631
|
+
this.cardBuilder.setCurrentTurnId(turnId);
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
this.cardBuilder.startNewTurn(turnId);
|
|
635
|
+
this.currentTurnId = turnId;
|
|
636
|
+
this.callbacks.onQueueStateChange?.(this.threadId);
|
|
637
|
+
}
|
|
638
|
+
this.beginTurnConsumer(turnId, { managedByRunTurn });
|
|
639
|
+
return turnId;
|
|
640
|
+
}
|
|
641
|
+
dispatchTurnNotification(notification, turnId) {
|
|
642
|
+
if (!notificationBelongsToThread(notification.params, this.threadId))
|
|
643
|
+
return;
|
|
644
|
+
if (turnId) {
|
|
645
|
+
this.turnConsumers.get(turnId)?.dispatch(notification);
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
for (const consumer of this.turnConsumers.values()) {
|
|
649
|
+
consumer.dispatch(notification);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
459
652
|
emitGoalConfig(goal) {
|
|
460
653
|
this.callbacks.onSessionConfigPatch?.(this.threadId, codexGoalToConfigPatch(goal));
|
|
461
654
|
}
|
|
@@ -563,6 +756,57 @@ export class CodexAppServerSession {
|
|
|
563
756
|
};
|
|
564
757
|
}
|
|
565
758
|
}
|
|
759
|
+
// ── helpers ──
|
|
760
|
+
function notificationBelongsToThread(params, threadId) {
|
|
761
|
+
if (typeof params !== 'object' || params === null)
|
|
762
|
+
return true;
|
|
763
|
+
const candidate = params.threadId;
|
|
764
|
+
return typeof candidate !== 'string' || candidate === threadId;
|
|
765
|
+
}
|
|
766
|
+
function notificationTurnId(notification) {
|
|
767
|
+
switch (notification.method) {
|
|
768
|
+
case 'turn/started': {
|
|
769
|
+
const params = notification.params;
|
|
770
|
+
return params.turn?.id ?? null;
|
|
771
|
+
}
|
|
772
|
+
case 'turn/completed': {
|
|
773
|
+
const params = notification.params;
|
|
774
|
+
return params.turn?.id ?? null;
|
|
775
|
+
}
|
|
776
|
+
default: {
|
|
777
|
+
if (typeof notification.params !== 'object' || notification.params === null)
|
|
778
|
+
return null;
|
|
779
|
+
const candidate = notification.params.turnId;
|
|
780
|
+
return typeof candidate === 'string' && candidate.length > 0 ? candidate : null;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
function shouldCreateTurnConsumerForNotification(method) {
|
|
785
|
+
switch (method) {
|
|
786
|
+
case 'turn/started':
|
|
787
|
+
case 'turn/completed':
|
|
788
|
+
case 'turn/plan/updated':
|
|
789
|
+
case 'item/started':
|
|
790
|
+
case 'item/autoApprovalReview/started':
|
|
791
|
+
case 'item/autoApprovalReview/completed':
|
|
792
|
+
case 'item/completed':
|
|
793
|
+
case 'item/agentMessage/delta':
|
|
794
|
+
case 'item/plan/delta':
|
|
795
|
+
case 'item/commandExecution/outputDelta':
|
|
796
|
+
case 'item/fileChange/outputDelta':
|
|
797
|
+
case 'item/fileChange/patchUpdated':
|
|
798
|
+
case 'item/mcpToolCall/progress':
|
|
799
|
+
case 'item/reasoning/summaryTextDelta':
|
|
800
|
+
case 'item/reasoning/summaryPartAdded':
|
|
801
|
+
case 'item/reasoning/textDelta':
|
|
802
|
+
case 'model/rerouted':
|
|
803
|
+
case 'model/verification':
|
|
804
|
+
case 'error':
|
|
805
|
+
return true;
|
|
806
|
+
default:
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
566
810
|
function codexToolQuestionToPromptQuestion(question) {
|
|
567
811
|
return {
|
|
568
812
|
id: question.id,
|
|
@@ -780,15 +1024,21 @@ function clearedGoalConfigPatch() {
|
|
|
780
1024
|
codexGoalUpdatedAt: null,
|
|
781
1025
|
};
|
|
782
1026
|
}
|
|
783
|
-
function
|
|
784
|
-
return
|
|
1027
|
+
function codexAppServerInit() {
|
|
1028
|
+
return {
|
|
785
1029
|
clientInfo: {
|
|
786
1030
|
name: 'quicksave-agent',
|
|
787
1031
|
title: 'Quicksave Agent',
|
|
788
1032
|
version: '0.0.0',
|
|
789
1033
|
},
|
|
790
1034
|
capabilities: { experimentalApi: true, requestAttestation: false, optOutNotificationMethods: null },
|
|
791
|
-
}
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
function spawnCodexNativeListAppServer() {
|
|
1038
|
+
return spawnAppServer(codexAppServerInit());
|
|
1039
|
+
}
|
|
1040
|
+
function spawnCodexAppServer(opts) {
|
|
1041
|
+
return spawnAppServer(codexAppServerInit(), {
|
|
792
1042
|
extraArgs: buildCodexSandboxMcpConfigArgs({
|
|
793
1043
|
cwd: opts.cwd,
|
|
794
1044
|
sessionId: 'sessionId' in opts ? opts.sessionId : undefined,
|
|
@@ -796,6 +1046,42 @@ function spawnCodexAppServer(opts) {
|
|
|
796
1046
|
}),
|
|
797
1047
|
});
|
|
798
1048
|
}
|
|
1049
|
+
async function listCodexThreads(handle, cwd, archived) {
|
|
1050
|
+
const threads = [];
|
|
1051
|
+
let cursor = null;
|
|
1052
|
+
do {
|
|
1053
|
+
const params = {
|
|
1054
|
+
cursor,
|
|
1055
|
+
limit: 100,
|
|
1056
|
+
sortKey: 'updated_at',
|
|
1057
|
+
sortDirection: 'desc',
|
|
1058
|
+
cwd: cwd ?? null,
|
|
1059
|
+
archived,
|
|
1060
|
+
};
|
|
1061
|
+
const response = await handle.rpc.request('thread/list', params);
|
|
1062
|
+
threads.push(...response.data.filter((thread) => !thread.ephemeral &&
|
|
1063
|
+
thread.parentThreadId === null &&
|
|
1064
|
+
(!cwd || thread.cwd === cwd)));
|
|
1065
|
+
cursor = response.nextCursor;
|
|
1066
|
+
} while (cursor);
|
|
1067
|
+
return threads;
|
|
1068
|
+
}
|
|
1069
|
+
function codexThreadToNativeSession(thread, archived) {
|
|
1070
|
+
return {
|
|
1071
|
+
sessionId: thread.id,
|
|
1072
|
+
cwd: thread.cwd,
|
|
1073
|
+
agent: 'codex',
|
|
1074
|
+
title: thread.name ?? undefined,
|
|
1075
|
+
firstPrompt: thread.preview || undefined,
|
|
1076
|
+
createdAt: codexSecondsToMs(thread.createdAt),
|
|
1077
|
+
lastInteractionAt: codexSecondsToMs(thread.updatedAt),
|
|
1078
|
+
gitBranch: thread.gitInfo?.branch ?? undefined,
|
|
1079
|
+
archived,
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
function codexSecondsToMs(value) {
|
|
1083
|
+
return value < 10_000_000_000 ? value * 1000 : value;
|
|
1084
|
+
}
|
|
799
1085
|
export function buildCodexSandboxMcpConfigArgs(opts) {
|
|
800
1086
|
const config = buildSandboxMcpServerConfig({
|
|
801
1087
|
ownDir: __aiDir,
|