@yu_robotics/remote-cli 1.6.170 → 1.6.173
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 +21 -0
- package/dist/client/GoalCommand.d.ts +15 -0
- package/dist/client/GoalCommand.d.ts.map +1 -0
- package/dist/client/GoalCommand.js +59 -0
- package/dist/client/GoalCommand.js.map +1 -0
- package/dist/client/MessageHandler.d.ts +3 -0
- package/dist/client/MessageHandler.d.ts.map +1 -1
- package/dist/client/MessageHandler.js +167 -43
- package/dist/client/MessageHandler.js.map +1 -1
- package/dist/client/TaskRecovery.d.ts +3 -1
- package/dist/client/TaskRecovery.d.ts.map +1 -1
- package/dist/client/TaskRecovery.js +9 -4
- package/dist/client/TaskRecovery.js.map +1 -1
- package/dist/client/WebSocketClient.d.ts +2 -1
- package/dist/client/WebSocketClient.d.ts.map +1 -1
- package/dist/client/WebSocketClient.js +8 -1
- package/dist/client/WebSocketClient.js.map +1 -1
- package/dist/executor/CodexAppServerClient.d.ts +1 -0
- package/dist/executor/CodexAppServerClient.d.ts.map +1 -1
- package/dist/executor/CodexAppServerClient.js +12 -4
- package/dist/executor/CodexAppServerClient.js.map +1 -1
- package/dist/executor/CodexAppServerExecutor.d.ts +28 -2
- package/dist/executor/CodexAppServerExecutor.d.ts.map +1 -1
- package/dist/executor/CodexAppServerExecutor.js +517 -30
- package/dist/executor/CodexAppServerExecutor.js.map +1 -1
- package/dist/executor/IExecutor.d.ts +24 -0
- package/dist/executor/IExecutor.d.ts.map +1 -1
- package/dist/executor/codex/CodexGoals.d.ts +6 -0
- package/dist/executor/codex/CodexGoals.d.ts.map +1 -0
- package/dist/executor/codex/CodexGoals.js +49 -0
- package/dist/executor/codex/CodexGoals.js.map +1 -0
- package/dist/executor/codex/CodexImages.d.ts +22 -0
- package/dist/executor/codex/CodexImages.d.ts.map +1 -0
- package/dist/executor/codex/CodexImages.js +201 -0
- package/dist/executor/codex/CodexImages.js.map +1 -0
- package/dist/maintenance/release-notes.json +1 -1
- package/dist/utils/ImageDelivery.d.ts +67 -0
- package/dist/utils/ImageDelivery.d.ts.map +1 -0
- package/dist/utils/ImageDelivery.js +270 -0
- package/dist/utils/ImageDelivery.js.map +1 -0
- package/dist/utils/ImageValidation.d.ts +11 -0
- package/dist/utils/ImageValidation.d.ts.map +1 -0
- package/dist/utils/ImageValidation.js +62 -0
- package/dist/utils/ImageValidation.js.map +1 -0
- package/dist/utils/LocalImageDetector.d.ts +12 -3
- package/dist/utils/LocalImageDetector.d.ts.map +1 -1
- package/dist/utils/LocalImageDetector.js +153 -55
- package/dist/utils/LocalImageDetector.js.map +1 -1
- package/package.json +1 -1
|
@@ -38,6 +38,8 @@ const fs = __importStar(require("fs"));
|
|
|
38
38
|
const os = __importStar(require("os"));
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const crypto_1 = require("crypto");
|
|
41
|
+
const CodexGoals_1 = require("./codex/CodexGoals");
|
|
42
|
+
const CodexImages_1 = require("./codex/CodexImages");
|
|
41
43
|
const ExecutionMetadata_1 = require("./ExecutionMetadata");
|
|
42
44
|
const CodexAppServerClient_1 = require("./CodexAppServerClient");
|
|
43
45
|
const CodexTaskNotifications_1 = require("./CodexTaskNotifications");
|
|
@@ -50,6 +52,8 @@ const CodexWebSearch_1 = require("./CodexWebSearch");
|
|
|
50
52
|
const DEFAULT_INACTIVITY_TIMEOUT_MS = 10 * 60 * 1000;
|
|
51
53
|
const DEFAULT_COMPACT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
52
54
|
const ACCOUNT_USAGE_TIMEOUT_MS = 10_000;
|
|
55
|
+
const GOAL_CONTROL_TIMEOUT_MS = 10_000;
|
|
56
|
+
const GOAL_RECOVERY_GUIDANCE = 'Use /goal resume to continue the saved native goal, or /goal pause or /goal clear before sending another task.';
|
|
53
57
|
function formatWindowDuration(minutes) {
|
|
54
58
|
if (!Number.isFinite(minutes) || !minutes || minutes <= 0)
|
|
55
59
|
return 'usage window';
|
|
@@ -142,6 +146,13 @@ class CodexAppServerExecutor {
|
|
|
142
146
|
delegationConfigured = false;
|
|
143
147
|
delegationWorker;
|
|
144
148
|
sessionResumeFailure = false;
|
|
149
|
+
goalManaged = false;
|
|
150
|
+
goalNeedsResume = false;
|
|
151
|
+
goalRun = null;
|
|
152
|
+
goalControl = null;
|
|
153
|
+
goalControlClear = false;
|
|
154
|
+
goalTransportStops = 0;
|
|
155
|
+
threadLoads = 0;
|
|
145
156
|
async configureDelegation(connection) {
|
|
146
157
|
if (this.delegationConfigured && (0, contract_1.sameConnection)(this.delegation, connection))
|
|
147
158
|
return;
|
|
@@ -196,11 +207,13 @@ class CodexAppServerExecutor {
|
|
|
196
207
|
this.sessionResumeFailure = false;
|
|
197
208
|
if (this.destroyed)
|
|
198
209
|
throw new Error('Executor has been destroyed');
|
|
199
|
-
if (this.
|
|
210
|
+
if (this.isBusy()) {
|
|
200
211
|
return { success: false, error: 'Codex thread is already running a command' };
|
|
201
212
|
}
|
|
202
213
|
try {
|
|
203
214
|
await this.ensureThread();
|
|
215
|
+
if (this.isBusy())
|
|
216
|
+
return { success: false, error: 'Codex thread is already running a command' };
|
|
204
217
|
}
|
|
205
218
|
catch (error) {
|
|
206
219
|
return { success: false, error: this.errorMessage(error) };
|
|
@@ -211,6 +224,10 @@ class CodexAppServerExecutor {
|
|
|
211
224
|
// leave the executor busy without an actual app-server turn.
|
|
212
225
|
const sandboxOptions = this.sandbox.turnOptions(this.currentWorkingDirectory);
|
|
213
226
|
const input = await this.buildInput(prompt, options.attachments, temporaryFiles);
|
|
227
|
+
if (this.isBusy()) {
|
|
228
|
+
await this.cleanupTemporaryFiles(temporaryFiles);
|
|
229
|
+
return { success: false, error: 'Codex thread is already running a command' };
|
|
230
|
+
}
|
|
214
231
|
return await new Promise((resolve) => {
|
|
215
232
|
const active = {
|
|
216
233
|
options,
|
|
@@ -221,6 +238,7 @@ class CodexAppServerExecutor {
|
|
|
221
238
|
emittedTools: new Map(),
|
|
222
239
|
itemPhases: new Map(),
|
|
223
240
|
temporaryFiles,
|
|
241
|
+
images: new CodexImages_1.CodexImages(),
|
|
224
242
|
inactivityTimeoutMs: options.inactivityTimeout,
|
|
225
243
|
};
|
|
226
244
|
this.activityTracker.setCallback(options.onActivity);
|
|
@@ -269,6 +287,332 @@ class CodexAppServerExecutor {
|
|
|
269
287
|
getCurrentWorkingDirectory() {
|
|
270
288
|
return this.currentWorkingDirectory;
|
|
271
289
|
}
|
|
290
|
+
isGoalRunning() {
|
|
291
|
+
return this.goalRun !== null;
|
|
292
|
+
}
|
|
293
|
+
/** Reading a saved goal must never materialize or resume its native thread. */
|
|
294
|
+
async getGoal() {
|
|
295
|
+
if (this.destroyed)
|
|
296
|
+
throw new Error('Executor has been destroyed');
|
|
297
|
+
if (!this.codexThreadId)
|
|
298
|
+
return null;
|
|
299
|
+
const threadId = this.codexThreadId;
|
|
300
|
+
try {
|
|
301
|
+
await this.goalDeadline(this.client.start());
|
|
302
|
+
(0, CodexGoals_1.assertNativeGoalsVersion)(this.client.getServerVersion?.());
|
|
303
|
+
const response = await this.goalRequest('thread/goal/get', { threadId });
|
|
304
|
+
if (this.codexThreadId !== threadId || this.destroyed)
|
|
305
|
+
throw new Error('The native goal session changed while reading it.');
|
|
306
|
+
const goal = (0, CodexGoals_1.parseNativeGoal)(response?.goal, threadId);
|
|
307
|
+
if (goal && !this.goalManaged)
|
|
308
|
+
this.rememberGoalManagement();
|
|
309
|
+
return goal;
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
throw new Error((0, CodexGoals_1.nativeGoalError)(error));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async executeGoal(request, options) {
|
|
316
|
+
try {
|
|
317
|
+
(0, CodexGoals_1.validateGoalRequest)(request);
|
|
318
|
+
if (this.destroyed)
|
|
319
|
+
throw new Error('Executor has been destroyed');
|
|
320
|
+
if (this.isBusy())
|
|
321
|
+
throw new Error('Codex thread is already running a command');
|
|
322
|
+
if (this.delegationWorker || this.delegation)
|
|
323
|
+
throw new Error('Native goals require managed delegation to be off and cannot run in a delegated worker.');
|
|
324
|
+
if (request.objective === undefined && !this.codexThreadId)
|
|
325
|
+
throw new Error('There is no saved native goal to resume.');
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
return { success: false, error: (0, CodexGoals_1.nativeGoalError)(error) };
|
|
329
|
+
}
|
|
330
|
+
let resolve;
|
|
331
|
+
const done = new Promise(settle => { resolve = settle; });
|
|
332
|
+
const active = { options, resolve, turnId: null, output: [], sideEffectsStarted: false,
|
|
333
|
+
emittedTools: new Map(), itemPhases: new Map(), temporaryFiles: [], images: new CodexImages_1.CodexImages(), inactivityTimeoutMs: options.inactivityTimeout };
|
|
334
|
+
const run = { active, done, objective: request.objective, ready: false, inFlight: new Set(), retired: new Set(),
|
|
335
|
+
revision: 0, checking: false, controlling: false, stopping: false };
|
|
336
|
+
// Install ownership before any native loading or mutation can start a turn.
|
|
337
|
+
this.goalRun = run;
|
|
338
|
+
this.activeTurn = active;
|
|
339
|
+
this.activityTracker.setCallback(options.onActivity);
|
|
340
|
+
this.activityTracker.reset();
|
|
341
|
+
this.armInactivityTimer();
|
|
342
|
+
if (options.timeout && options.timeout > 0)
|
|
343
|
+
active.timeoutTimer = setTimeout(() => {
|
|
344
|
+
void this.failGoal(run, `Native goal timed out after ${options.timeout}ms. ${GOAL_RECOVERY_GUIDANCE}`);
|
|
345
|
+
}, options.timeout);
|
|
346
|
+
run.startup = this.startGoal(run, request).catch(error => this.failGoal(run, (0, CodexGoals_1.nativeGoalError)(error)));
|
|
347
|
+
return done;
|
|
348
|
+
}
|
|
349
|
+
pauseGoal() {
|
|
350
|
+
return this.controlGoal(false);
|
|
351
|
+
}
|
|
352
|
+
clearGoal() {
|
|
353
|
+
return this.controlGoal(true);
|
|
354
|
+
}
|
|
355
|
+
async startGoal(run, request) {
|
|
356
|
+
await this.goalDeadline(this.client.start());
|
|
357
|
+
this.assertGoalOwner(run);
|
|
358
|
+
(0, CodexGoals_1.assertNativeGoalsVersion)(this.client.getServerVersion?.());
|
|
359
|
+
let previous = null;
|
|
360
|
+
if (this.codexThreadId) {
|
|
361
|
+
previous = await this.getGoal();
|
|
362
|
+
this.assertGoalOwner(run);
|
|
363
|
+
}
|
|
364
|
+
if (request.objective === undefined && (!previous || previous.status === 'complete')) {
|
|
365
|
+
throw new Error('There is no unfinished native goal to resume.');
|
|
366
|
+
}
|
|
367
|
+
// Replacing an active saved goal must not briefly execute its old objective on load.
|
|
368
|
+
if (request.objective !== undefined && previous?.status === 'active' && !this.threadReady) {
|
|
369
|
+
throw new Error('Pause or clear the saved active goal before replacing its objective.');
|
|
370
|
+
}
|
|
371
|
+
run.goal = previous;
|
|
372
|
+
run.objective = request.objective ?? previous?.objective;
|
|
373
|
+
this.rememberGoalManagement();
|
|
374
|
+
if (previous?.status === 'active' && !this.threadReady) {
|
|
375
|
+
// Loading an active goal can dispatch before settings/update. Suspend it
|
|
376
|
+
// without loading, then restore activation only after native policy sync.
|
|
377
|
+
const response = await this.goalRequest('thread/goal/set', {
|
|
378
|
+
threadId: this.codexThreadId, status: 'paused', origin: 'user',
|
|
379
|
+
});
|
|
380
|
+
this.assertGoalOwner(run);
|
|
381
|
+
const paused = (0, CodexGoals_1.parseNativeGoal)(response?.goal, this.codexThreadId);
|
|
382
|
+
if (!paused || paused.status !== 'paused')
|
|
383
|
+
throw new Error('Codex did not pause the saved goal before loading its policies.');
|
|
384
|
+
run.goal = paused;
|
|
385
|
+
if (run.controlling)
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
await this.ensureThread();
|
|
389
|
+
this.assertGoalOwner(run);
|
|
390
|
+
this.rememberGoalManagement();
|
|
391
|
+
if (run.controlling)
|
|
392
|
+
return;
|
|
393
|
+
// Native continuations bypass turn/start, so their settings must be sticky.
|
|
394
|
+
const goalPolicy = this.sandbox.turnOptions(this.currentWorkingDirectory);
|
|
395
|
+
if (this.model || this.effort || Object.keys(goalPolicy).length) {
|
|
396
|
+
await this.goalRequest('thread/settings/update', { threadId: this.codexThreadId,
|
|
397
|
+
...(this.model ? { model: this.model } : {}), ...(this.effort ? { effort: this.effort } : {}), ...goalPolicy });
|
|
398
|
+
this.assertGoalOwner(run);
|
|
399
|
+
if (run.controlling)
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
// A resumed active goal may already have completed while the load request ran.
|
|
403
|
+
if (request.objective !== undefined || run.goal?.status !== 'complete') {
|
|
404
|
+
const revision = run.revision;
|
|
405
|
+
const response = await this.goalRequest('thread/goal/set', {
|
|
406
|
+
threadId: this.codexThreadId, origin: 'user',
|
|
407
|
+
...(request.objective !== undefined ? { objective: request.objective } : {}),
|
|
408
|
+
status: 'active', ...(request.tokenBudget !== undefined ? { tokenBudget: request.tokenBudget } : {}),
|
|
409
|
+
});
|
|
410
|
+
this.assertGoalOwner(run);
|
|
411
|
+
const goal = (0, CodexGoals_1.parseNativeGoal)(response?.goal, this.codexThreadId);
|
|
412
|
+
if (!goal)
|
|
413
|
+
throw new Error('Codex did not return the activated native goal.');
|
|
414
|
+
if (run.revision === revision)
|
|
415
|
+
run.goal = goal;
|
|
416
|
+
}
|
|
417
|
+
run.ready = true;
|
|
418
|
+
this.goalNeedsResume = false;
|
|
419
|
+
if (run.goal?.status === 'active') {
|
|
420
|
+
this.goalNotice(run, run.inFlight.size ? 'Native goal running' : 'Native goal active; waiting for native continuation');
|
|
421
|
+
}
|
|
422
|
+
this.maybeCompleteGoal(run);
|
|
423
|
+
if (this.goalRun === run && !run.inFlight.size)
|
|
424
|
+
void this.checkGoalAfterTurn(run);
|
|
425
|
+
}
|
|
426
|
+
assertGoalOwner(run) {
|
|
427
|
+
if (this.goalRun !== run || this.destroyed || run.stopping)
|
|
428
|
+
throw new Error('Native goal operation was cancelled.');
|
|
429
|
+
}
|
|
430
|
+
goalNotice(run, text) {
|
|
431
|
+
const bounded = Array.from(text).slice(0, 400).join('');
|
|
432
|
+
if (this.goalRun !== run || run.lastNotice === bounded)
|
|
433
|
+
return;
|
|
434
|
+
run.lastNotice = bounded;
|
|
435
|
+
try {
|
|
436
|
+
if (run.active.options.onActivity)
|
|
437
|
+
run.active.options.onActivity({ source: 'state', text: bounded });
|
|
438
|
+
else
|
|
439
|
+
run.active.options.onStream?.(`\n[${bounded}]\n`);
|
|
440
|
+
}
|
|
441
|
+
catch { /* A display failure cannot change native goal ownership. */ }
|
|
442
|
+
}
|
|
443
|
+
maybeCompleteGoal(run) {
|
|
444
|
+
if (this.goalRun !== run || !run.ready || run.controlling || run.stopping || run.inFlight.size
|
|
445
|
+
|| run.goal === undefined || run.goal?.status === 'active')
|
|
446
|
+
return;
|
|
447
|
+
const status = run.goal?.status ?? 'cleared';
|
|
448
|
+
this.goalNotice(run, `Native goal ${status}`);
|
|
449
|
+
const summary = `\n\nNative goal ${status}.\n`;
|
|
450
|
+
run.active.output.push(summary);
|
|
451
|
+
try {
|
|
452
|
+
run.active.options.onStream?.(summary);
|
|
453
|
+
}
|
|
454
|
+
catch { /* Native completion still releases ownership. */ }
|
|
455
|
+
try {
|
|
456
|
+
run.active.options.onDisplayText?.(summary);
|
|
457
|
+
}
|
|
458
|
+
catch { /* Native completion still releases ownership. */ }
|
|
459
|
+
this.completeActive({ success: status === 'complete' || status === 'paused' || status === 'cleared',
|
|
460
|
+
output: run.active.output.join(''), sessionAbbr: this.codexThreadId?.slice(0, 8),
|
|
461
|
+
...(['blocked', 'usageLimited', 'budgetLimited'].includes(status) ? { error: `Native goal stopped: ${status}.` } : {}) });
|
|
462
|
+
}
|
|
463
|
+
async checkGoalAfterTurn(run) {
|
|
464
|
+
if (this.goalRun !== run || run.checking || run.controlling || run.stopping)
|
|
465
|
+
return;
|
|
466
|
+
run.checking = true;
|
|
467
|
+
const revision = run.revision;
|
|
468
|
+
try {
|
|
469
|
+
const goal = await this.getGoal();
|
|
470
|
+
if (this.goalRun !== run || run.controlling || run.stopping)
|
|
471
|
+
return;
|
|
472
|
+
if (revision === run.revision)
|
|
473
|
+
run.goal = goal;
|
|
474
|
+
this.maybeCompleteGoal(run);
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
if (this.goalRun === run)
|
|
478
|
+
await this.failGoal(run, `Could not reconcile native goal: ${(0, CodexGoals_1.nativeGoalError)(error)}`);
|
|
479
|
+
}
|
|
480
|
+
finally {
|
|
481
|
+
run.checking = false;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
goalDeadline(promise) {
|
|
485
|
+
return new Promise((resolve, reject) => {
|
|
486
|
+
const timer = setTimeout(() => reject(new Error('Native goal control timed out.')), GOAL_CONTROL_TIMEOUT_MS);
|
|
487
|
+
promise.then(value => { clearTimeout(timer); resolve(value); }, error => { clearTimeout(timer); reject(error); });
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
goalRequest(method, params) {
|
|
491
|
+
if (this.destroyed)
|
|
492
|
+
return Promise.reject(new Error('Executor has been destroyed'));
|
|
493
|
+
return this.goalDeadline(this.client.request(method, params, GOAL_CONTROL_TIMEOUT_MS));
|
|
494
|
+
}
|
|
495
|
+
async stopGoalTransport() {
|
|
496
|
+
this.threadReady = false;
|
|
497
|
+
this.goalNeedsResume = this.goalManaged;
|
|
498
|
+
this.goalTransportStops++;
|
|
499
|
+
try {
|
|
500
|
+
await this.goalDeadline(this.client.stop());
|
|
501
|
+
if (this.client.waitForExit)
|
|
502
|
+
await this.goalDeadline(this.client.waitForExit());
|
|
503
|
+
else if (this.client.isRunning())
|
|
504
|
+
throw new Error('Codex goal process exit could not be confirmed.');
|
|
505
|
+
}
|
|
506
|
+
finally {
|
|
507
|
+
this.goalTransportStops--;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
async failGoal(run, error) {
|
|
511
|
+
if (this.goalRun !== run || run.stopping)
|
|
512
|
+
return;
|
|
513
|
+
run.stopping = true;
|
|
514
|
+
this.clearInactivityTimer();
|
|
515
|
+
if (run.active.timeoutTimer)
|
|
516
|
+
clearTimeout(run.active.timeoutTimer);
|
|
517
|
+
try {
|
|
518
|
+
await this.stopGoalTransport();
|
|
519
|
+
if (this.goalRun === run)
|
|
520
|
+
this.completeActive({ success: false, error });
|
|
521
|
+
}
|
|
522
|
+
catch (stopError) {
|
|
523
|
+
run.stopping = false;
|
|
524
|
+
this.goalNotice(run, `Native goal stop unconfirmed: ${(0, CodexGoals_1.nativeGoalError)(stopError)}. Retry /abort.`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
controlGoal(clear) {
|
|
528
|
+
if (this.goalControl) {
|
|
529
|
+
if (this.goalControlClear === clear)
|
|
530
|
+
return this.goalControl;
|
|
531
|
+
return this.goalControl.then(result => result.success ? this.controlGoal(clear) : result);
|
|
532
|
+
}
|
|
533
|
+
const control = this.stopGoalControl(this.goalRun, clear);
|
|
534
|
+
this.goalControl = control;
|
|
535
|
+
this.goalControlClear = clear;
|
|
536
|
+
void control.then(() => { if (this.goalControl === control)
|
|
537
|
+
this.goalControl = null; });
|
|
538
|
+
return control;
|
|
539
|
+
}
|
|
540
|
+
async stopGoalControl(run, clear) {
|
|
541
|
+
if (this.destroyed)
|
|
542
|
+
return { success: false, error: 'Executor has been destroyed' };
|
|
543
|
+
if (!run && (this.activeTurn || this.compactWaiter || this.threadLoads)) {
|
|
544
|
+
return { success: false, error: 'Wait for the current ordinary task before changing its goal.' };
|
|
545
|
+
}
|
|
546
|
+
if (!this.codexThreadId && !run)
|
|
547
|
+
return { success: true, output: 'No native goal is set.' };
|
|
548
|
+
if (run)
|
|
549
|
+
run.controlling = true;
|
|
550
|
+
try {
|
|
551
|
+
if (run?.startup)
|
|
552
|
+
await this.goalDeadline(run.startup);
|
|
553
|
+
if (run && this.goalRun !== run)
|
|
554
|
+
return { success: false, error: 'The native goal run already stopped.' };
|
|
555
|
+
await this.goalDeadline(this.client.start());
|
|
556
|
+
(0, CodexGoals_1.assertNativeGoalsVersion)(this.client.getServerVersion?.());
|
|
557
|
+
const threadId = this.codexThreadId;
|
|
558
|
+
if (!threadId)
|
|
559
|
+
throw new Error('No native goal session was created.');
|
|
560
|
+
this.rememberGoalManagement();
|
|
561
|
+
const existing = await this.getGoal();
|
|
562
|
+
let goal = existing;
|
|
563
|
+
if (existing && existing.status !== 'complete') {
|
|
564
|
+
const paused = await this.goalRequest('thread/goal/set', { threadId, status: 'paused', origin: 'user' });
|
|
565
|
+
goal = (0, CodexGoals_1.parseNativeGoal)(paused?.goal, threadId);
|
|
566
|
+
if (!goal || goal.status === 'active')
|
|
567
|
+
throw new Error('Codex did not confirm that the native goal is paused.');
|
|
568
|
+
}
|
|
569
|
+
if (clear && existing) {
|
|
570
|
+
const response = await this.goalRequest('thread/goal/clear', { threadId, origin: 'user' });
|
|
571
|
+
if (typeof response?.cleared !== 'boolean')
|
|
572
|
+
throw new Error('Codex did not confirm clearing the native goal.');
|
|
573
|
+
goal = null;
|
|
574
|
+
}
|
|
575
|
+
if (run) {
|
|
576
|
+
run.goal = goal;
|
|
577
|
+
for (const pending of this.pendingUserInputs.splice(0)) {
|
|
578
|
+
this.finishApproval(pending, 'expired');
|
|
579
|
+
this.client.respond(pending.id, pending.method === 'item/tool/requestUserInput' ? { answers: {} }
|
|
580
|
+
: pending.method === 'item/permissions/requestApproval' ? { permissions: {}, scope: 'turn' } : { decision: 'cancel' });
|
|
581
|
+
}
|
|
582
|
+
for (const turnId of [...run.inFlight])
|
|
583
|
+
await this.goalRequest('turn/interrupt', { threadId, turnId });
|
|
584
|
+
if (run.inFlight.size)
|
|
585
|
+
await this.goalDeadline(new Promise(resolve => { run.onIdle = resolve; }));
|
|
586
|
+
if (this.goalRun !== run)
|
|
587
|
+
throw new Error('The native goal stopped before its control operation finished.');
|
|
588
|
+
}
|
|
589
|
+
// A late dispatched continuation must not outlive a successful stop ACK.
|
|
590
|
+
// Reopening this process is safe because native state is now paused/cleared.
|
|
591
|
+
await this.stopGoalTransport();
|
|
592
|
+
this.goalNeedsResume = false;
|
|
593
|
+
if (run) {
|
|
594
|
+
if (this.goalRun !== run)
|
|
595
|
+
throw new Error('The native goal stopped before its control operation finished.');
|
|
596
|
+
run.inFlight.clear();
|
|
597
|
+
run.ready = true;
|
|
598
|
+
run.controlling = false;
|
|
599
|
+
this.maybeCompleteGoal(run);
|
|
600
|
+
const result = await this.goalDeadline(run.done);
|
|
601
|
+
if (!result.success)
|
|
602
|
+
throw new Error(result.error ?? 'Native goal did not stop successfully.');
|
|
603
|
+
}
|
|
604
|
+
return { success: true, output: clear ? 'Native goal cleared; execution stopped.'
|
|
605
|
+
: goal?.status === 'complete' ? 'Native goal was already complete; execution stopped.'
|
|
606
|
+
: goal ? 'Native goal paused; execution stopped.' : 'No native goal is set; execution stopped.' };
|
|
607
|
+
}
|
|
608
|
+
catch (error) {
|
|
609
|
+
if (run && this.goalRun === run) {
|
|
610
|
+
run.controlling = false;
|
|
611
|
+
await this.failGoal(run, `Native goal control failed: ${(0, CodexGoals_1.nativeGoalError)(error)}. ${GOAL_RECOVERY_GUIDANCE}`);
|
|
612
|
+
}
|
|
613
|
+
return { success: false, error: (0, CodexGoals_1.nativeGoalError)(error) };
|
|
614
|
+
}
|
|
615
|
+
}
|
|
272
616
|
getExecutionMetadata() {
|
|
273
617
|
const data = (0, ExecutionMetadata_1.configuredExecutionMetadata)(this.model, this.effort);
|
|
274
618
|
if (this.threadReady && this.client.isRunning() && this.reportedModel) {
|
|
@@ -288,7 +632,7 @@ class CodexAppServerExecutor {
|
|
|
288
632
|
this.reportedEffort = response.reasoningEffort;
|
|
289
633
|
}
|
|
290
634
|
isBusy() {
|
|
291
|
-
return this.activeTurn !== null || this.compactWaiter !== null;
|
|
635
|
+
return this.activeTurn !== null || this.compactWaiter !== null || this.goalControl !== null || this.threadLoads > 0;
|
|
292
636
|
}
|
|
293
637
|
async setWorkingDirectory(targetPath) {
|
|
294
638
|
const resolved = this.directoryGuard.resolveWorkingDirectory(targetPath, this.currentWorkingDirectory);
|
|
@@ -341,6 +685,8 @@ class CodexAppServerExecutor {
|
|
|
341
685
|
}
|
|
342
686
|
}
|
|
343
687
|
resetContext() {
|
|
688
|
+
if (this.goalRun)
|
|
689
|
+
throw new Error('Stop the native goal before clearing its conversation.');
|
|
344
690
|
this.taskNotifications.clear();
|
|
345
691
|
const previousId = this.codexThreadId;
|
|
346
692
|
if (this.activeTurn || this.compactWaiter) {
|
|
@@ -349,6 +695,8 @@ class CodexAppServerExecutor {
|
|
|
349
695
|
void this.client.stop();
|
|
350
696
|
}
|
|
351
697
|
this.codexThreadId = null;
|
|
698
|
+
this.goalManaged = false;
|
|
699
|
+
this.goalNeedsResume = false;
|
|
352
700
|
this.threadReady = false;
|
|
353
701
|
this.pendingUserInputs = [];
|
|
354
702
|
this.clearThreadId();
|
|
@@ -357,6 +705,8 @@ class CodexAppServerExecutor {
|
|
|
357
705
|
}
|
|
358
706
|
}
|
|
359
707
|
async abort() {
|
|
708
|
+
if (this.goalRun)
|
|
709
|
+
return (await this.pauseGoal()).success;
|
|
360
710
|
const active = this.activeTurn;
|
|
361
711
|
if (!active || !this.codexThreadId)
|
|
362
712
|
return false;
|
|
@@ -384,12 +734,17 @@ class CodexAppServerExecutor {
|
|
|
384
734
|
return true;
|
|
385
735
|
}
|
|
386
736
|
async destroy() {
|
|
387
|
-
if (this.destroyed)
|
|
737
|
+
if (this.destroyed && !this.goalRun)
|
|
388
738
|
return;
|
|
389
739
|
this.destroyed = true;
|
|
390
740
|
this.taskNotifications.clear();
|
|
391
741
|
this.unsubscribeClient?.();
|
|
392
742
|
this.unsubscribeClient = null;
|
|
743
|
+
if (this.goalRun) {
|
|
744
|
+
this.goalRun.stopping = true;
|
|
745
|
+
this.clearInactivityTimer();
|
|
746
|
+
await this.stopGoalTransport();
|
|
747
|
+
}
|
|
393
748
|
if (this.activeTurn)
|
|
394
749
|
this.completeActive({ success: false, error: 'Executor destroyed' });
|
|
395
750
|
this.completeCompact({ success: false, error: 'Executor destroyed' });
|
|
@@ -709,12 +1064,29 @@ class CodexAppServerExecutor {
|
|
|
709
1064
|
this.sandbox.deleteData();
|
|
710
1065
|
}
|
|
711
1066
|
async ensureThread() {
|
|
1067
|
+
this.threadLoads++;
|
|
1068
|
+
try {
|
|
1069
|
+
await this.loadThread();
|
|
1070
|
+
}
|
|
1071
|
+
finally {
|
|
1072
|
+
this.threadLoads--;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
async loadThread() {
|
|
712
1076
|
(0, WorkingDirectory_1.assertWorkingDirectoryExists)(this.currentWorkingDirectory);
|
|
1077
|
+
if (this.goalManaged && this.codexThreadId && !this.goalRun) {
|
|
1078
|
+
const goal = await this.getGoal();
|
|
1079
|
+
if (this.goalNeedsResume || goal?.status === 'active')
|
|
1080
|
+
throw new Error(GOAL_RECOVERY_GUIDANCE);
|
|
1081
|
+
}
|
|
713
1082
|
if (this.threadReady && this.client.isRunning())
|
|
714
1083
|
return;
|
|
1084
|
+
const goalOwner = this.goalRun;
|
|
715
1085
|
this.reportedModel = undefined;
|
|
716
1086
|
this.reportedEffort = undefined;
|
|
717
1087
|
await this.client.start();
|
|
1088
|
+
if (goalOwner)
|
|
1089
|
+
this.assertGoalOwner(goalOwner);
|
|
718
1090
|
const common = {
|
|
719
1091
|
...(this.model ? { model: this.model } : {}),
|
|
720
1092
|
cwd: this.currentWorkingDirectory,
|
|
@@ -724,6 +1096,9 @@ class CodexAppServerExecutor {
|
|
|
724
1096
|
} : {}),
|
|
725
1097
|
...this.sandbox.threadOptions(this.currentWorkingDirectory, this.delegationWorker || !!this.delegation),
|
|
726
1098
|
};
|
|
1099
|
+
// A saved active goal can dispatch during resume, before settings/update.
|
|
1100
|
+
if (goalOwner && this.effort)
|
|
1101
|
+
common.config = { ...common.config, model_reasoning_effort: this.effort };
|
|
727
1102
|
if (this.delegationConfigured) {
|
|
728
1103
|
common.config = { ...common.config,
|
|
729
1104
|
'mcp_servers.remote_cli_delegation': this.delegation
|
|
@@ -744,6 +1119,8 @@ class CodexAppServerExecutor {
|
|
|
744
1119
|
if (response?.thread?.id !== this.codexThreadId) {
|
|
745
1120
|
throw new Error('Codex app-server resumed an unexpected thread');
|
|
746
1121
|
}
|
|
1122
|
+
if (goalOwner)
|
|
1123
|
+
this.assertGoalOwner(goalOwner);
|
|
747
1124
|
this.captureReportedSettings(response);
|
|
748
1125
|
}
|
|
749
1126
|
catch (error) {
|
|
@@ -761,6 +1138,8 @@ class CodexAppServerExecutor {
|
|
|
761
1138
|
serviceName: 'remote_cli',
|
|
762
1139
|
});
|
|
763
1140
|
const id = response?.thread?.id;
|
|
1141
|
+
if (goalOwner)
|
|
1142
|
+
this.assertGoalOwner(goalOwner);
|
|
764
1143
|
if (typeof id !== 'string' || !id)
|
|
765
1144
|
throw new Error('Codex app-server returned no thread ID');
|
|
766
1145
|
this.captureReportedSettings(response);
|
|
@@ -769,6 +1148,8 @@ class CodexAppServerExecutor {
|
|
|
769
1148
|
this.threadReady = true;
|
|
770
1149
|
}
|
|
771
1150
|
handleMessage(message) {
|
|
1151
|
+
if (this.destroyed)
|
|
1152
|
+
return;
|
|
772
1153
|
const method = message.method;
|
|
773
1154
|
if (!method)
|
|
774
1155
|
return;
|
|
@@ -776,7 +1157,12 @@ class CodexAppServerExecutor {
|
|
|
776
1157
|
if (method === 'client/disconnected') {
|
|
777
1158
|
this.taskNotifications.clear();
|
|
778
1159
|
this.threadReady = false;
|
|
779
|
-
if (this.
|
|
1160
|
+
if (this.goalRun) {
|
|
1161
|
+
if (!this.goalTransportStops) {
|
|
1162
|
+
void this.failGoal(this.goalRun, `${params.error ?? 'Codex app-server disconnected'}. ${GOAL_RECOVERY_GUIDANCE}`);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
else if (this.activeTurn) {
|
|
780
1166
|
this.completeActive({ success: false, error: params.error ?? 'Codex app-server disconnected' });
|
|
781
1167
|
}
|
|
782
1168
|
this.completeCompact({ success: false, error: params.error ?? 'Codex app-server disconnected' });
|
|
@@ -788,6 +1174,29 @@ class CodexAppServerExecutor {
|
|
|
788
1174
|
}
|
|
789
1175
|
if (params.threadId && params.threadId !== this.codexThreadId)
|
|
790
1176
|
return;
|
|
1177
|
+
const goalRun = this.goalRun;
|
|
1178
|
+
if (goalRun?.stopping)
|
|
1179
|
+
return;
|
|
1180
|
+
// Goal changes may arrive after their turn completed, or without any turn ID.
|
|
1181
|
+
if (goalRun && (method === 'thread/goal/updated' || method === 'thread/goal/cleared')) {
|
|
1182
|
+
if (params.threadId !== this.codexThreadId)
|
|
1183
|
+
return;
|
|
1184
|
+
try {
|
|
1185
|
+
const goal = method === 'thread/goal/cleared' ? null : (0, CodexGoals_1.parseNativeGoal)(params.goal, this.codexThreadId);
|
|
1186
|
+
if (method === 'thread/goal/updated' && !goal)
|
|
1187
|
+
throw new Error('Codex returned an empty native goal update.');
|
|
1188
|
+
if (goal && (goalRun.objective && goal.objective !== goalRun.objective
|
|
1189
|
+
|| goalRun.goal && goal.updatedAt < goalRun.goal.updatedAt))
|
|
1190
|
+
return;
|
|
1191
|
+
goalRun.goal = goal;
|
|
1192
|
+
goalRun.revision++;
|
|
1193
|
+
this.maybeCompleteGoal(goalRun);
|
|
1194
|
+
}
|
|
1195
|
+
catch (error) {
|
|
1196
|
+
void this.failGoal(goalRun, (0, CodexGoals_1.nativeGoalError)(error));
|
|
1197
|
+
}
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
791
1200
|
if (method === 'serverRequest/resolved') {
|
|
792
1201
|
this.pendingUserInputs = this.pendingUserInputs.filter(pending => {
|
|
793
1202
|
if (pending.id !== params.requestId)
|
|
@@ -804,18 +1213,46 @@ class CodexAppServerExecutor {
|
|
|
804
1213
|
return;
|
|
805
1214
|
// Late background output must not become part of a subsequent turn.
|
|
806
1215
|
if (eventTurnId && !matchesActiveTurn && method !== 'turn/started'
|
|
1216
|
+
&& !(method === 'turn/completed' && goalRun?.inFlight.has(eventTurnId))
|
|
807
1217
|
&& params.item?.type !== 'contextCompaction')
|
|
808
1218
|
return;
|
|
809
1219
|
if (this.activeTurn)
|
|
810
1220
|
this.armInactivityTimer();
|
|
811
1221
|
switch (method) {
|
|
812
1222
|
case 'turn/started':
|
|
1223
|
+
if (goalRun) {
|
|
1224
|
+
const id = params.turn?.id;
|
|
1225
|
+
if (params.threadId !== this.codexThreadId || typeof id !== 'string' || !id || goalRun.retired.has(id))
|
|
1226
|
+
break;
|
|
1227
|
+
goalRun.inFlight.add(id);
|
|
1228
|
+
if (this.activeTurn?.turnId !== id) {
|
|
1229
|
+
if (goalRun.retired.size && goalRun.active.output.length) {
|
|
1230
|
+
goalRun.active.output.push('\n\n');
|
|
1231
|
+
goalRun.active.options.onStream?.('\n\n');
|
|
1232
|
+
goalRun.active.options.onDisplayText?.('\n\n');
|
|
1233
|
+
}
|
|
1234
|
+
goalRun.active.emittedTools.clear();
|
|
1235
|
+
goalRun.active.itemPhases.clear();
|
|
1236
|
+
goalRun.active.error = undefined;
|
|
1237
|
+
goalRun.active.errorCode = undefined;
|
|
1238
|
+
}
|
|
1239
|
+
this.goalNotice(goalRun, 'Native goal running');
|
|
1240
|
+
}
|
|
813
1241
|
if (this.activeTurn && typeof params.turn?.id === 'string')
|
|
814
1242
|
this.activeTurn.turnId = params.turn.id;
|
|
815
1243
|
break;
|
|
1244
|
+
case 'thread/status/changed':
|
|
1245
|
+
if (goalRun && params.status?.type === 'idle' && goalRun.goal?.status === 'active') {
|
|
1246
|
+
this.goalNotice(goalRun, 'Native goal active; waiting for native continuation or user control');
|
|
1247
|
+
}
|
|
1248
|
+
break;
|
|
816
1249
|
case 'item/agentMessage/delta':
|
|
817
1250
|
if (this.activeTurn && typeof params.delta === 'string') {
|
|
818
1251
|
this.activeTurn.output.push(params.delta);
|
|
1252
|
+
if (goalRun && this.activeTurn.output.reduce((size, part) => size + part.length, 0) > 65_536) {
|
|
1253
|
+
this.activeTurn.output = ['[Earlier native goal output omitted from this diagnostic buffer]\n',
|
|
1254
|
+
this.activeTurn.output.join('').slice(-65_536)];
|
|
1255
|
+
}
|
|
819
1256
|
this.activeTurn.options.onStream?.(params.delta);
|
|
820
1257
|
this.activeTurn.options.onDisplayText?.(params.delta);
|
|
821
1258
|
const phase = params.itemId ? this.activeTurn.itemPhases.get(params.itemId) : undefined;
|
|
@@ -933,6 +1370,11 @@ class CodexAppServerExecutor {
|
|
|
933
1370
|
catch { /* Rendering failures must not make an accepted request executable again. */ }
|
|
934
1371
|
}
|
|
935
1372
|
handleServerRequest(id, method, params) {
|
|
1373
|
+
if (this.goalRun && (this.goalRun.stopping || this.goalRun.controlling || !this.goalRun.inFlight.size
|
|
1374
|
+
|| params.turnId && !this.goalRun.inFlight.has(params.turnId))) {
|
|
1375
|
+
this.client.respondError(id, 'Request does not belong to a running native goal turn');
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
936
1378
|
if (params.threadId && params.threadId !== this.codexThreadId) {
|
|
937
1379
|
this.client.respondError(id, 'Request does not belong to this remote-cli thread');
|
|
938
1380
|
return;
|
|
@@ -1055,9 +1497,10 @@ class CodexAppServerExecutor {
|
|
|
1055
1497
|
case 'mcpToolCall':
|
|
1056
1498
|
case 'dynamicToolCall':
|
|
1057
1499
|
this.emitToolUse(active, id, item.tool ?? 'MCP', item.arguments ?? {});
|
|
1500
|
+
this.handleGeneratedImage(active, item);
|
|
1058
1501
|
active.options.onToolResult?.({
|
|
1059
1502
|
tool_use_id: id,
|
|
1060
|
-
content:
|
|
1503
|
+
content: (0, CodexImages_1.codexToolResultText)(item.result ?? item.contentItems ?? item.error ?? ''),
|
|
1061
1504
|
is_error: item.status !== 'completed' || item.success === false || Boolean(item.error),
|
|
1062
1505
|
});
|
|
1063
1506
|
break;
|
|
@@ -1081,6 +1524,35 @@ class CodexAppServerExecutor {
|
|
|
1081
1524
|
const active = this.activeTurn;
|
|
1082
1525
|
if (!active || !turn)
|
|
1083
1526
|
return;
|
|
1527
|
+
const run = this.goalRun;
|
|
1528
|
+
if (run) {
|
|
1529
|
+
if (!run.inFlight.has(turn.id) || run.retired.has(turn.id))
|
|
1530
|
+
return;
|
|
1531
|
+
run.inFlight.delete(turn.id);
|
|
1532
|
+
if (!run.inFlight.size)
|
|
1533
|
+
run.onIdle?.();
|
|
1534
|
+
run.retired.add(turn.id);
|
|
1535
|
+
if (run.retired.size > 256)
|
|
1536
|
+
run.retired.delete(run.retired.values().next().value);
|
|
1537
|
+
if (active.turnId === turn.id)
|
|
1538
|
+
active.turnId = null;
|
|
1539
|
+
this.pendingUserInputs = this.pendingUserInputs.filter(pending => {
|
|
1540
|
+
if (pending.params.turnId && pending.params.turnId !== turn.id)
|
|
1541
|
+
return true;
|
|
1542
|
+
this.finishApproval(pending, 'expired');
|
|
1543
|
+
return false;
|
|
1544
|
+
});
|
|
1545
|
+
if (turn.status !== 'completed' && !run.controlling) {
|
|
1546
|
+
void this.failGoal(run, turn.status === 'interrupted' ? 'Native goal interrupted. Resume explicitly to continue.'
|
|
1547
|
+
: turn.error?.message ?? active.error ?? 'Native goal turn failed.');
|
|
1548
|
+
}
|
|
1549
|
+
else {
|
|
1550
|
+
this.maybeCompleteGoal(run);
|
|
1551
|
+
if (run.ready && this.goalRun === run)
|
|
1552
|
+
void this.checkGoalAfterTurn(run);
|
|
1553
|
+
}
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1084
1556
|
if (active.turnId && turn.id && active.turnId !== turn.id)
|
|
1085
1557
|
return;
|
|
1086
1558
|
if (turn.status === 'completed') {
|
|
@@ -1126,27 +1598,27 @@ class CodexAppServerExecutor {
|
|
|
1126
1598
|
this.activityTracker.emitTool(name, input, description ?? title);
|
|
1127
1599
|
}
|
|
1128
1600
|
handleGeneratedImage(active, item) {
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
console.warn('[CodexAppServer] Ignoring imageGeneration result with unsupported format');
|
|
1140
|
-
return;
|
|
1601
|
+
const parsed = active.images.extract(item, active.turnId);
|
|
1602
|
+
for (const artifact of parsed.artifacts) {
|
|
1603
|
+
if ('image' in artifact)
|
|
1604
|
+
active.options.onImage?.(artifact.image);
|
|
1605
|
+
else
|
|
1606
|
+
active.options.onImageFile?.(artifact.path);
|
|
1607
|
+
}
|
|
1608
|
+
if (parsed.notice) {
|
|
1609
|
+
active.output.push(parsed.notice);
|
|
1610
|
+
active.options.onStream?.(parsed.notice);
|
|
1141
1611
|
}
|
|
1142
|
-
const image = { type: 'image', data, mimeType };
|
|
1143
|
-
active.options.onImage?.(image);
|
|
1144
1612
|
}
|
|
1145
1613
|
completeActive(result) {
|
|
1146
1614
|
const active = this.activeTurn;
|
|
1147
1615
|
if (!active)
|
|
1148
1616
|
return;
|
|
1149
1617
|
this.activeTurn = null;
|
|
1618
|
+
if (this.goalRun?.active === active) {
|
|
1619
|
+
this.goalRun.onIdle?.();
|
|
1620
|
+
this.goalRun = null;
|
|
1621
|
+
}
|
|
1150
1622
|
this.activityTracker.reset();
|
|
1151
1623
|
this.activityTracker.setCallback(undefined);
|
|
1152
1624
|
for (const pending of this.pendingUserInputs.splice(0))
|
|
@@ -1166,6 +1638,8 @@ class CodexAppServerExecutor {
|
|
|
1166
1638
|
waiter.resolve(result);
|
|
1167
1639
|
}
|
|
1168
1640
|
async failAndRestart(error) {
|
|
1641
|
+
if (this.goalRun)
|
|
1642
|
+
return this.failGoal(this.goalRun, error);
|
|
1169
1643
|
this.taskNotifications.clear();
|
|
1170
1644
|
this.threadReady = false;
|
|
1171
1645
|
this.completeActive({ success: false, error });
|
|
@@ -1222,15 +1696,19 @@ class CodexAppServerExecutor {
|
|
|
1222
1696
|
const session = JSON.parse(fs.readFileSync(this.sessionFilePath, 'utf8'));
|
|
1223
1697
|
if (typeof session.id === 'string' && session.id)
|
|
1224
1698
|
this.codexThreadId = session.id;
|
|
1699
|
+
this.goalManaged = !!this.codexThreadId && session.goalManaged === true;
|
|
1700
|
+
this.goalNeedsResume = this.goalManaged;
|
|
1225
1701
|
}
|
|
1226
1702
|
catch {
|
|
1227
1703
|
this.codexThreadId = null;
|
|
1704
|
+
this.goalManaged = false;
|
|
1705
|
+
this.goalNeedsResume = false;
|
|
1228
1706
|
}
|
|
1229
1707
|
}
|
|
1230
1708
|
setThreadId(id) {
|
|
1231
1709
|
this.codexThreadId = id;
|
|
1232
1710
|
const temporaryPath = `${this.sessionFilePath}.${process.pid}.tmp`;
|
|
1233
|
-
const data = JSON.stringify({ id, savedAt: new Date().toISOString() });
|
|
1711
|
+
const data = JSON.stringify({ id, savedAt: new Date().toISOString(), ...(this.goalManaged ? { goalManaged: true } : {}) });
|
|
1234
1712
|
try {
|
|
1235
1713
|
fs.writeFileSync(temporaryPath, data, { encoding: 'utf8', mode: 0o600 });
|
|
1236
1714
|
fs.renameSync(temporaryPath, this.sessionFilePath);
|
|
@@ -1252,6 +1730,25 @@ class CodexAppServerExecutor {
|
|
|
1252
1730
|
console.error('[CodexAppServerExecutor] Failed to delete session file:', error);
|
|
1253
1731
|
}
|
|
1254
1732
|
}
|
|
1733
|
+
/** Persist opt-in before a native mutation; a failed write must prevent activation. */
|
|
1734
|
+
rememberGoalManagement() {
|
|
1735
|
+
this.goalManaged = true;
|
|
1736
|
+
if (!this.codexThreadId)
|
|
1737
|
+
return;
|
|
1738
|
+
const temporaryPath = `${this.sessionFilePath}.${process.pid}.goal.tmp`;
|
|
1739
|
+
try {
|
|
1740
|
+
fs.writeFileSync(temporaryPath, JSON.stringify({ id: this.codexThreadId, goalManaged: true,
|
|
1741
|
+
savedAt: new Date().toISOString() }), { encoding: 'utf8', mode: 0o600 });
|
|
1742
|
+
fs.renameSync(temporaryPath, this.sessionFilePath);
|
|
1743
|
+
}
|
|
1744
|
+
catch {
|
|
1745
|
+
try {
|
|
1746
|
+
fs.unlinkSync(temporaryPath);
|
|
1747
|
+
}
|
|
1748
|
+
catch { /* Keep the previous binding intact. */ }
|
|
1749
|
+
throw new Error('Could not persist native goal ownership. No goal activation was requested.');
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1255
1752
|
extractErrorCode(info) {
|
|
1256
1753
|
if (typeof info === 'string')
|
|
1257
1754
|
return info;
|
|
@@ -1261,16 +1758,6 @@ class CodexAppServerExecutor {
|
|
|
1261
1758
|
return info.type;
|
|
1262
1759
|
return Object.keys(info)[0];
|
|
1263
1760
|
}
|
|
1264
|
-
stringifyResult(value) {
|
|
1265
|
-
if (typeof value === 'string')
|
|
1266
|
-
return value;
|
|
1267
|
-
try {
|
|
1268
|
-
return JSON.stringify(value).slice(0, 2_000);
|
|
1269
|
-
}
|
|
1270
|
-
catch {
|
|
1271
|
-
return String(value);
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
1761
|
errorMessage(error) {
|
|
1275
1762
|
return error instanceof Error ? error.message : String(error);
|
|
1276
1763
|
}
|