@renseiai/agentfactory 0.8.21 → 0.8.22
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/src/config/repository-config.d.ts +3 -3
- package/dist/src/orchestrator/orchestrator.d.ts +8 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.js +96 -10
- package/dist/src/orchestrator/state-types.d.ts +3 -0
- package/dist/src/orchestrator/state-types.d.ts.map +1 -1
- package/dist/src/providers/codex-app-server-provider.d.ts +68 -0
- package/dist/src/providers/codex-app-server-provider.d.ts.map +1 -1
- package/dist/src/providers/codex-app-server-provider.integration.test.d.ts +14 -0
- package/dist/src/providers/codex-app-server-provider.integration.test.d.ts.map +1 -0
- package/dist/src/providers/codex-app-server-provider.integration.test.js +909 -0
- package/dist/src/providers/codex-app-server-provider.js +244 -52
- package/dist/src/providers/codex-app-server-provider.test.js +838 -10
- package/dist/src/providers/codex-provider.d.ts +2 -0
- package/dist/src/providers/codex-provider.d.ts.map +1 -1
- package/dist/src/providers/codex-provider.js +36 -6
- package/dist/src/providers/codex-provider.test.js +12 -3
- package/dist/src/providers/types.d.ts +11 -0
- package/dist/src/providers/types.d.ts.map +1 -1
- package/dist/src/workflow/workflow-types.d.ts +5 -5
- package/package.json +2 -2
|
@@ -25,12 +25,51 @@ import { spawn } from 'child_process';
|
|
|
25
25
|
import { createInterface } from 'readline';
|
|
26
26
|
import { classifyTool } from '../tools/tool-category.js';
|
|
27
27
|
import { evaluateCommandApproval, evaluateFileChangeApproval, } from './codex-approval-bridge.js';
|
|
28
|
+
function isServerRequest(msg) {
|
|
29
|
+
return 'id' in msg && 'method' in msg;
|
|
30
|
+
}
|
|
28
31
|
function isResponse(msg) {
|
|
29
|
-
return 'id' in msg &&
|
|
32
|
+
return 'id' in msg && !('method' in msg);
|
|
30
33
|
}
|
|
31
34
|
function isNotification(msg) {
|
|
32
35
|
return 'method' in msg && !('id' in msg);
|
|
33
36
|
}
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Codex model mapping (SUP-1749)
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
export const CODEX_MODEL_MAP = {
|
|
41
|
+
'opus': 'gpt-5-codex',
|
|
42
|
+
'sonnet': 'gpt-5.2-codex',
|
|
43
|
+
'haiku': 'gpt-5.3-codex',
|
|
44
|
+
};
|
|
45
|
+
export const CODEX_DEFAULT_MODEL = 'gpt-5-codex';
|
|
46
|
+
export function resolveCodexModel(config) {
|
|
47
|
+
if (config.model)
|
|
48
|
+
return config.model;
|
|
49
|
+
const tier = config.env.CODEX_MODEL_TIER;
|
|
50
|
+
if (tier && CODEX_MODEL_MAP[tier])
|
|
51
|
+
return CODEX_MODEL_MAP[tier];
|
|
52
|
+
if (config.env.CODEX_MODEL)
|
|
53
|
+
return config.env.CODEX_MODEL;
|
|
54
|
+
return CODEX_DEFAULT_MODEL;
|
|
55
|
+
}
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Codex pricing and cost calculation (SUP-1750)
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
/** Codex pricing per 1M tokens (USD). Update when pricing changes. */
|
|
60
|
+
export const CODEX_PRICING = {
|
|
61
|
+
'gpt-5-codex': { input: 2.00, cachedInput: 0.50, output: 8.00 },
|
|
62
|
+
'gpt-5.2-codex': { input: 1.00, cachedInput: 0.25, output: 4.00 },
|
|
63
|
+
'gpt-5.3-codex': { input: 0.50, cachedInput: 0.125, output: 2.00 },
|
|
64
|
+
};
|
|
65
|
+
export const CODEX_DEFAULT_PRICING = CODEX_PRICING['gpt-5-codex'];
|
|
66
|
+
export function calculateCostUsd(inputTokens, cachedInputTokens, outputTokens, model) {
|
|
67
|
+
const pricing = (model && CODEX_PRICING[model]) || CODEX_DEFAULT_PRICING;
|
|
68
|
+
const freshInputTokens = Math.max(0, inputTokens - cachedInputTokens);
|
|
69
|
+
return ((freshInputTokens / 1_000_000) * pricing.input +
|
|
70
|
+
(cachedInputTokens / 1_000_000) * pricing.cachedInput +
|
|
71
|
+
(outputTokens / 1_000_000) * pricing.output);
|
|
72
|
+
}
|
|
34
73
|
/**
|
|
35
74
|
* Manages a single long-lived `codex app-server` process.
|
|
36
75
|
*
|
|
@@ -87,7 +126,12 @@ export class AppServerProcessManager {
|
|
|
87
126
|
// Non-JSON output — ignore
|
|
88
127
|
return;
|
|
89
128
|
}
|
|
90
|
-
if (
|
|
129
|
+
if (isServerRequest(msg)) {
|
|
130
|
+
// Server requests have both `id` and `method` — Codex expects a response.
|
|
131
|
+
// Approval requests (commandExecution/requestApproval, etc.) come as server requests.
|
|
132
|
+
this.handleServerRequest(msg);
|
|
133
|
+
}
|
|
134
|
+
else if (isResponse(msg)) {
|
|
91
135
|
this.handleResponse(msg);
|
|
92
136
|
}
|
|
93
137
|
else if (isNotification(msg)) {
|
|
@@ -106,6 +150,16 @@ export class AppServerProcessManager {
|
|
|
106
150
|
});
|
|
107
151
|
// Perform initialization handshake
|
|
108
152
|
await this.initialize();
|
|
153
|
+
// Discover available models (best effort — older servers may not support model/list)
|
|
154
|
+
try {
|
|
155
|
+
const models = await this.listModels();
|
|
156
|
+
if (models.length > 0) {
|
|
157
|
+
console.error(`[CodexAppServer] Available models: ${models.map(m => m.id).join(', ')}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
console.error('[CodexAppServer] model/list not supported by this server version');
|
|
162
|
+
}
|
|
109
163
|
}
|
|
110
164
|
/**
|
|
111
165
|
* JSON-RPC 2.0 initialization handshake:
|
|
@@ -174,6 +228,44 @@ export class AppServerProcessManager {
|
|
|
174
228
|
pending.resolve(response.result);
|
|
175
229
|
}
|
|
176
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Handle an incoming JSON-RPC server request (has both `id` and `method`).
|
|
233
|
+
* Codex sends approval requests as server requests that expect a response.
|
|
234
|
+
* Route to the thread listener (as a notification-like object) and store
|
|
235
|
+
* the request ID so the handle can respond.
|
|
236
|
+
*/
|
|
237
|
+
handleServerRequest(request) {
|
|
238
|
+
const threadId = request.params?.threadId;
|
|
239
|
+
console.error(`[CodexAppServer] Server request: ${request.method} (id=${request.id}, thread=${threadId ?? 'none'})`);
|
|
240
|
+
// Wrap as a notification-compatible object for the thread listener,
|
|
241
|
+
// but include the id so the handle can respond.
|
|
242
|
+
const notificationLike = {
|
|
243
|
+
method: request.method,
|
|
244
|
+
params: { ...request.params, _serverRequestId: request.id },
|
|
245
|
+
};
|
|
246
|
+
if (threadId) {
|
|
247
|
+
const listener = this.threadListeners.get(threadId);
|
|
248
|
+
if (listener) {
|
|
249
|
+
listener(notificationLike);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// No thread listener — auto-accept to avoid hanging
|
|
254
|
+
console.error(`[CodexAppServer] No thread listener for ${request.method} — auto-accepting`);
|
|
255
|
+
this.respondToServerRequest(request.id, { decision: 'acceptForSession' });
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Send a JSON-RPC response to a server request.
|
|
259
|
+
*/
|
|
260
|
+
respondToServerRequest(requestId, result) {
|
|
261
|
+
if (!this.process?.stdin?.writable) {
|
|
262
|
+
console.error('[CodexAppServer] Cannot respond to server request: stdin not writable');
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
console.error(`[CodexAppServer] Responding to server request ${requestId}: ${JSON.stringify(result)}`);
|
|
266
|
+
const response = JSON.stringify({ jsonrpc: '2.0', id: requestId, result });
|
|
267
|
+
this.process.stdin.write(response + '\n');
|
|
268
|
+
}
|
|
177
269
|
/**
|
|
178
270
|
* Handle an incoming JSON-RPC notification.
|
|
179
271
|
* Routes to the appropriate thread listener based on threadId in params.
|
|
@@ -236,8 +328,8 @@ export class AppServerProcessManager {
|
|
|
236
328
|
}
|
|
237
329
|
try {
|
|
238
330
|
await this.request('config/batchWrite', {
|
|
239
|
-
|
|
240
|
-
{
|
|
331
|
+
edits: [
|
|
332
|
+
{ keyPath: 'mcpServers', mergeStrategy: 'replace', value: mcpServers },
|
|
241
333
|
],
|
|
242
334
|
});
|
|
243
335
|
this.mcpConfigured = true;
|
|
@@ -264,6 +356,13 @@ export class AppServerProcessManager {
|
|
|
264
356
|
return [];
|
|
265
357
|
}
|
|
266
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* Discover available models from the app-server via model/list.
|
|
361
|
+
*/
|
|
362
|
+
async listModels() {
|
|
363
|
+
const result = await this.request('model/list', {});
|
|
364
|
+
return result?.models ?? [];
|
|
365
|
+
}
|
|
267
366
|
/**
|
|
268
367
|
* Get the PID of the app-server process.
|
|
269
368
|
*/
|
|
@@ -359,6 +458,7 @@ export function mapAppServerNotification(notification, state) {
|
|
|
359
458
|
if (turn?.usage) {
|
|
360
459
|
state.totalInputTokens += turn.usage.input_tokens ?? 0;
|
|
361
460
|
state.totalOutputTokens += turn.usage.output_tokens ?? 0;
|
|
461
|
+
state.totalCachedInputTokens += turn.usage.cached_input_tokens ?? 0;
|
|
362
462
|
}
|
|
363
463
|
if (turnStatus === 'completed') {
|
|
364
464
|
return [{
|
|
@@ -367,6 +467,8 @@ export function mapAppServerNotification(notification, state) {
|
|
|
367
467
|
cost: {
|
|
368
468
|
inputTokens: state.totalInputTokens || undefined,
|
|
369
469
|
outputTokens: state.totalOutputTokens || undefined,
|
|
470
|
+
cachedInputTokens: state.totalCachedInputTokens || undefined,
|
|
471
|
+
totalCostUsd: calculateCostUsd(state.totalInputTokens, state.totalCachedInputTokens, state.totalOutputTokens, state.model ?? undefined),
|
|
370
472
|
numTurns: state.turnCount || undefined,
|
|
371
473
|
},
|
|
372
474
|
raw: notification,
|
|
@@ -381,6 +483,8 @@ export function mapAppServerNotification(notification, state) {
|
|
|
381
483
|
cost: {
|
|
382
484
|
inputTokens: state.totalInputTokens || undefined,
|
|
383
485
|
outputTokens: state.totalOutputTokens || undefined,
|
|
486
|
+
cachedInputTokens: state.totalCachedInputTokens || undefined,
|
|
487
|
+
totalCostUsd: calculateCostUsd(state.totalInputTokens, state.totalCachedInputTokens, state.totalOutputTokens, state.model ?? undefined),
|
|
384
488
|
numTurns: state.turnCount || undefined,
|
|
385
489
|
},
|
|
386
490
|
raw: notification,
|
|
@@ -408,7 +512,7 @@ export function mapAppServerNotification(notification, state) {
|
|
|
408
512
|
return mapAppServerItemEvent(method, params);
|
|
409
513
|
// --- Item deltas (streaming) ---
|
|
410
514
|
case 'item/agentMessage/delta': {
|
|
411
|
-
const text = params.text;
|
|
515
|
+
const text = (params.delta ?? params.text);
|
|
412
516
|
if (text) {
|
|
413
517
|
return [{
|
|
414
518
|
type: 'assistant_text',
|
|
@@ -435,7 +539,7 @@ export function mapAppServerNotification(notification, state) {
|
|
|
435
539
|
return [{
|
|
436
540
|
type: 'system',
|
|
437
541
|
subtype: 'command_progress',
|
|
438
|
-
message: (params.delta ?? params.output) ?? '',
|
|
542
|
+
message: stripAnsi((params.delta ?? params.output) ?? ''),
|
|
439
543
|
raw: notification,
|
|
440
544
|
}];
|
|
441
545
|
// --- Turn diff/plan ---
|
|
@@ -462,6 +566,16 @@ export function mapAppServerNotification(notification, state) {
|
|
|
462
566
|
}];
|
|
463
567
|
}
|
|
464
568
|
}
|
|
569
|
+
/**
|
|
570
|
+
* Strip ANSI escape codes from text.
|
|
571
|
+
* Codex shell commands produce raw terminal output with color codes,
|
|
572
|
+
* cursor movement, etc. that pollute logs and activity tracking.
|
|
573
|
+
*/
|
|
574
|
+
// eslint-disable-next-line no-control-regex
|
|
575
|
+
const ANSI_PATTERN = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\].*?\x07|\x1b[()][AB012]|\x1b\[[\d;]*m/g;
|
|
576
|
+
function stripAnsi(text) {
|
|
577
|
+
return text.replace(ANSI_PATTERN, '');
|
|
578
|
+
}
|
|
465
579
|
/**
|
|
466
580
|
* Map item/started and item/completed notifications to AgentEvents.
|
|
467
581
|
* Exported for unit testing.
|
|
@@ -507,7 +621,7 @@ export function mapAppServerItemEvent(method, params) {
|
|
|
507
621
|
type: 'tool_result',
|
|
508
622
|
toolName: 'shell',
|
|
509
623
|
toolUseId: item.id,
|
|
510
|
-
content: item.text ?? '',
|
|
624
|
+
content: stripAnsi(item.text ?? ''),
|
|
511
625
|
isError: item.status === 'failed' || (item.exitCode !== undefined && item.exitCode !== 0),
|
|
512
626
|
raw: { method, params },
|
|
513
627
|
}];
|
|
@@ -607,20 +721,75 @@ export function normalizeMcpToolName(server, tool) {
|
|
|
607
721
|
// Resolve approval policy from AgentSpawnConfig
|
|
608
722
|
// ---------------------------------------------------------------------------
|
|
609
723
|
function resolveApprovalPolicy(config) {
|
|
610
|
-
// SUP-1747: Use '
|
|
724
|
+
// SUP-1747: Use 'on-request' for autonomous agents so all tool executions
|
|
611
725
|
// flow through the approval bridge for safety evaluation. The bridge
|
|
612
726
|
// auto-approves safe commands and declines destructive patterns.
|
|
727
|
+
// Codex v0.117+ uses kebab-case: 'on-request' | 'untrusted' | 'on-failure' | 'never'
|
|
613
728
|
if (config.autonomous)
|
|
614
|
-
return '
|
|
615
|
-
return '
|
|
729
|
+
return 'on-request';
|
|
730
|
+
return 'untrusted';
|
|
616
731
|
}
|
|
617
|
-
|
|
732
|
+
/**
|
|
733
|
+
* Map AgentSpawnConfig sandbox settings to Codex App Server sandbox policy.
|
|
734
|
+
*
|
|
735
|
+
* Codex sandbox levels vs Claude sandbox:
|
|
736
|
+
* | Feature | Claude | Codex |
|
|
737
|
+
* |-----------------------|-------------------------|--------------------------------|
|
|
738
|
+
* | File write control | Per-file glob patterns | Workspace root only |
|
|
739
|
+
* | Network access | Per-domain allow-lists | All-or-nothing per level |
|
|
740
|
+
* | Tool-level permissions| Per-tool allow/deny | Not supported (approval policy)|
|
|
741
|
+
* | Custom writable paths | Multiple glob patterns | Single writableRoots array |
|
|
742
|
+
* | Process isolation | macOS sandbox-exec | Docker/firewall container |
|
|
743
|
+
*
|
|
744
|
+
* Key limitation: Codex cannot restrict writes to specific subdirectories within
|
|
745
|
+
* the workspace or allow network access to specific domains. The mapping is intent-based:
|
|
746
|
+
* "safe browsing/analysis" → readOnly
|
|
747
|
+
* "normal development" → workspaceWrite
|
|
748
|
+
* "install/deploy/admin" → dangerFullAccess
|
|
749
|
+
*/
|
|
750
|
+
/**
|
|
751
|
+
* Resolve sandbox policy as an object for turn/start (supports writableRoots).
|
|
752
|
+
* Codex v0.117+ turn/start accepts: { type: 'workspaceWrite', writableRoots: [...] }
|
|
753
|
+
*
|
|
754
|
+
* Network access is enabled by default for agents because they need to run
|
|
755
|
+
* commands like `gh`, `curl`, `pnpm install`, etc. The sandbox still restricts
|
|
756
|
+
* file writes to the workspace root.
|
|
757
|
+
*/
|
|
758
|
+
export function resolveSandboxPolicy(config) {
|
|
759
|
+
if (config.sandboxLevel) {
|
|
760
|
+
switch (config.sandboxLevel) {
|
|
761
|
+
case 'read-only':
|
|
762
|
+
return { type: 'readOnly', networkAccess: true };
|
|
763
|
+
case 'workspace-write':
|
|
764
|
+
return { type: 'workspaceWrite', writableRoots: [config.cwd], networkAccess: true };
|
|
765
|
+
case 'full-access':
|
|
766
|
+
return { type: 'dangerFullAccess' };
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
// Fallback: boolean sandboxEnabled → workspaceWrite with network
|
|
618
770
|
if (!config.sandboxEnabled)
|
|
619
771
|
return undefined;
|
|
620
|
-
return {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
772
|
+
return { type: 'workspaceWrite', writableRoots: [config.cwd], networkAccess: true };
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Resolve sandbox mode as a simple string for thread/start.
|
|
776
|
+
* Codex v0.117+ thread/start accepts: 'read-only' | 'workspace-write' | 'danger-full-access'
|
|
777
|
+
*/
|
|
778
|
+
export function resolveSandboxMode(config) {
|
|
779
|
+
if (config.sandboxLevel) {
|
|
780
|
+
switch (config.sandboxLevel) {
|
|
781
|
+
case 'read-only':
|
|
782
|
+
return 'read-only';
|
|
783
|
+
case 'workspace-write':
|
|
784
|
+
return 'workspace-write';
|
|
785
|
+
case 'full-access':
|
|
786
|
+
return 'danger-full-access';
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
// Fallback: boolean sandboxEnabled → workspace-write
|
|
790
|
+
if (!config.sandboxEnabled)
|
|
791
|
+
return undefined;
|
|
792
|
+
return 'workspace-write';
|
|
624
793
|
}
|
|
625
794
|
// ---------------------------------------------------------------------------
|
|
626
795
|
// Base Instructions Builder (SUP-1746)
|
|
@@ -663,8 +832,10 @@ class AppServerAgentHandle {
|
|
|
663
832
|
resumeThreadId;
|
|
664
833
|
mapperState = {
|
|
665
834
|
sessionId: null,
|
|
835
|
+
model: null,
|
|
666
836
|
totalInputTokens: 0,
|
|
667
837
|
totalOutputTokens: 0,
|
|
838
|
+
totalCachedInputTokens: 0,
|
|
668
839
|
turnCount: 0,
|
|
669
840
|
};
|
|
670
841
|
activeTurnId = null;
|
|
@@ -673,6 +844,8 @@ class AppServerAgentHandle {
|
|
|
673
844
|
streamEnded = false;
|
|
674
845
|
/** True while we're waiting for a possible injected turn between turns */
|
|
675
846
|
awaitingInjection = false;
|
|
847
|
+
/** Accumulated assistant text for the result message (completion comment) */
|
|
848
|
+
accumulatedText = '';
|
|
676
849
|
constructor(processManager, config, resumeThreadId) {
|
|
677
850
|
this.processManager = processManager;
|
|
678
851
|
this.config = config;
|
|
@@ -725,7 +898,7 @@ class AppServerAgentHandle {
|
|
|
725
898
|
}
|
|
726
899
|
await this.processManager.request('turn/steer', {
|
|
727
900
|
threadId: this.sessionId,
|
|
728
|
-
|
|
901
|
+
expectedTurnId: this.activeTurnId,
|
|
729
902
|
input: [{ type: 'text', text }],
|
|
730
903
|
});
|
|
731
904
|
}
|
|
@@ -738,9 +911,10 @@ class AppServerAgentHandle {
|
|
|
738
911
|
*
|
|
739
912
|
* Returns a system event if the request was declined, for observability.
|
|
740
913
|
*/
|
|
741
|
-
|
|
914
|
+
handleApprovalRequest(notification) {
|
|
742
915
|
const params = notification.params ?? {};
|
|
743
|
-
|
|
916
|
+
// Server requests pass _serverRequestId; fall back to requestId for backwards compat
|
|
917
|
+
const serverRequestId = params._serverRequestId;
|
|
744
918
|
const command = params.command;
|
|
745
919
|
const filePath = params.filePath;
|
|
746
920
|
let decision;
|
|
@@ -756,13 +930,14 @@ class AppServerAgentHandle {
|
|
|
756
930
|
// Unknown approval request — accept by default
|
|
757
931
|
decision = { action: 'acceptForSession' };
|
|
758
932
|
}
|
|
759
|
-
// Respond to the
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
933
|
+
// Respond to the server request with the approval decision.
|
|
934
|
+
// Codex sends approval requests as JSON-RPC server requests (with `id`),
|
|
935
|
+
// expecting a JSON-RPC response matching that id.
|
|
936
|
+
if (serverRequestId != null) {
|
|
937
|
+
this.processManager.respondToServerRequest(serverRequestId, {
|
|
938
|
+
decision: decision.action,
|
|
939
|
+
});
|
|
940
|
+
}
|
|
766
941
|
// Emit system event for declined approvals (observability)
|
|
767
942
|
if (decision.action === 'decline') {
|
|
768
943
|
const target = command ?? filePath ?? 'unknown';
|
|
@@ -789,9 +964,7 @@ class AppServerAgentHandle {
|
|
|
789
964
|
cwd: this.config.cwd,
|
|
790
965
|
approvalPolicy: resolveApprovalPolicy(this.config),
|
|
791
966
|
};
|
|
792
|
-
|
|
793
|
-
turnParams.maxTurns = this.config.maxTurns;
|
|
794
|
-
}
|
|
967
|
+
turnParams.model = resolveCodexModel(this.config);
|
|
795
968
|
const sandboxPolicy = resolveSandboxPolicy(this.config);
|
|
796
969
|
if (sandboxPolicy) {
|
|
797
970
|
turnParams.sandboxPolicy = sandboxPolicy;
|
|
@@ -818,7 +991,7 @@ class AppServerAgentHandle {
|
|
|
818
991
|
// Resume existing thread
|
|
819
992
|
const result = await this.processManager.request('thread/resume', {
|
|
820
993
|
threadId: this.resumeThreadId,
|
|
821
|
-
personality: '
|
|
994
|
+
personality: 'pragmatic',
|
|
822
995
|
});
|
|
823
996
|
threadId = result?.thread?.id ?? this.resumeThreadId;
|
|
824
997
|
}
|
|
@@ -829,15 +1002,17 @@ class AppServerAgentHandle {
|
|
|
829
1002
|
approvalPolicy: resolveApprovalPolicy(this.config),
|
|
830
1003
|
serviceName: 'agentfactory',
|
|
831
1004
|
};
|
|
832
|
-
// SUP-1746: Pass persistent system instructions via `
|
|
1005
|
+
// SUP-1746: Pass persistent system instructions via `baseInstructions` on thread/start.
|
|
833
1006
|
// Separates safety rules and project context from per-turn task input.
|
|
834
1007
|
const instructions = buildBaseInstructions(this.config);
|
|
835
1008
|
if (instructions) {
|
|
836
|
-
threadParams.
|
|
1009
|
+
threadParams.baseInstructions = instructions;
|
|
837
1010
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1011
|
+
threadParams.model = resolveCodexModel(this.config);
|
|
1012
|
+
// thread/start uses simple string sandbox mode (not object like turn/start)
|
|
1013
|
+
const sandboxMode = resolveSandboxMode(this.config);
|
|
1014
|
+
if (sandboxMode) {
|
|
1015
|
+
threadParams.sandbox = sandboxMode;
|
|
841
1016
|
}
|
|
842
1017
|
const result = await this.processManager.request('thread/start', threadParams);
|
|
843
1018
|
threadId = result?.thread?.id ?? '';
|
|
@@ -852,6 +1027,7 @@ class AppServerAgentHandle {
|
|
|
852
1027
|
}
|
|
853
1028
|
this.sessionId = threadId;
|
|
854
1029
|
this.mapperState.sessionId = threadId;
|
|
1030
|
+
this.mapperState.model = resolveCodexModel(this.config);
|
|
855
1031
|
// Subscribe to thread notifications
|
|
856
1032
|
this.processManager.subscribeThread(threadId, (notification) => {
|
|
857
1033
|
this.notificationQueue.push(notification);
|
|
@@ -873,9 +1049,7 @@ class AppServerAgentHandle {
|
|
|
873
1049
|
cwd: this.config.cwd,
|
|
874
1050
|
approvalPolicy: resolveApprovalPolicy(this.config),
|
|
875
1051
|
};
|
|
876
|
-
|
|
877
|
-
turnParams.maxTurns = this.config.maxTurns;
|
|
878
|
-
}
|
|
1052
|
+
turnParams.model = resolveCodexModel(this.config);
|
|
879
1053
|
const sandboxPolicy = resolveSandboxPolicy(this.config);
|
|
880
1054
|
if (sandboxPolicy) {
|
|
881
1055
|
turnParams.sandboxPolicy = sandboxPolicy;
|
|
@@ -905,9 +1079,11 @@ class AppServerAgentHandle {
|
|
|
905
1079
|
while (this.notificationQueue.length > 0) {
|
|
906
1080
|
const notification = this.notificationQueue.shift();
|
|
907
1081
|
// SUP-1747: Intercept approval requests before other processing.
|
|
908
|
-
//
|
|
909
|
-
|
|
910
|
-
|
|
1082
|
+
// Codex sends approvals as server requests with methods like:
|
|
1083
|
+
// item/commandExecution/requestApproval, item/fileChange/requestApproval,
|
|
1084
|
+
// item/permissions/requestApproval, applyPatchApproval, execCommandApproval
|
|
1085
|
+
if (notification.method.includes('pproval') || notification.method.includes('requestApproval')) {
|
|
1086
|
+
const deniedEvent = this.handleApprovalRequest(notification);
|
|
911
1087
|
if (deniedEvent) {
|
|
912
1088
|
yield deniedEvent;
|
|
913
1089
|
}
|
|
@@ -928,18 +1104,31 @@ class AppServerAgentHandle {
|
|
|
928
1104
|
}
|
|
929
1105
|
const events = mapAppServerNotification(notification, this.mapperState);
|
|
930
1106
|
for (const event of events) {
|
|
931
|
-
// Intercept turn/completed result events
|
|
932
|
-
//
|
|
933
|
-
//
|
|
1107
|
+
// Intercept turn/completed result events.
|
|
1108
|
+
// In autonomous mode (fleet), emit the result directly to end the session.
|
|
1109
|
+
// In interactive mode, convert to system event to keep the stream alive
|
|
1110
|
+
// for potential message injection.
|
|
1111
|
+
// Accumulate assistant text for the result message / completion comment
|
|
1112
|
+
if (event.type === 'assistant_text' && event.text) {
|
|
1113
|
+
this.accumulatedText += event.text;
|
|
1114
|
+
}
|
|
934
1115
|
if (event.type === 'result') {
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1116
|
+
if (this.config.autonomous) {
|
|
1117
|
+
// Autonomous: emit result with accumulated text and end stream
|
|
1118
|
+
yield { ...event, message: this.accumulatedText.trim() || undefined };
|
|
1119
|
+
this.streamEnded = true;
|
|
1120
|
+
}
|
|
1121
|
+
else {
|
|
1122
|
+
// Interactive: keep stream alive for injection
|
|
1123
|
+
lastTurnSuccess = event.success;
|
|
1124
|
+
lastTurnErrors = event.errors;
|
|
1125
|
+
yield {
|
|
1126
|
+
type: 'system',
|
|
1127
|
+
subtype: 'turn_result',
|
|
1128
|
+
message: `Turn ${event.success ? 'succeeded' : 'failed'}${event.errors?.length ? ': ' + event.errors[0] : ''}`,
|
|
1129
|
+
raw: event.raw,
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
943
1132
|
}
|
|
944
1133
|
else {
|
|
945
1134
|
yield event;
|
|
@@ -955,14 +1144,17 @@ class AppServerAgentHandle {
|
|
|
955
1144
|
catch {
|
|
956
1145
|
// Best effort
|
|
957
1146
|
}
|
|
958
|
-
// Emit the final result event when the stream ends
|
|
1147
|
+
// Emit the final result event when the stream ends (interactive mode / stop)
|
|
959
1148
|
yield {
|
|
960
1149
|
type: 'result',
|
|
961
1150
|
success: lastTurnSuccess,
|
|
1151
|
+
message: this.accumulatedText.trim() || undefined,
|
|
962
1152
|
errors: lastTurnErrors,
|
|
963
1153
|
cost: {
|
|
964
1154
|
inputTokens: this.mapperState.totalInputTokens || undefined,
|
|
965
1155
|
outputTokens: this.mapperState.totalOutputTokens || undefined,
|
|
1156
|
+
cachedInputTokens: this.mapperState.totalCachedInputTokens || undefined,
|
|
1157
|
+
totalCostUsd: calculateCostUsd(this.mapperState.totalInputTokens, this.mapperState.totalCachedInputTokens, this.mapperState.totalOutputTokens, this.mapperState.model ?? undefined),
|
|
966
1158
|
numTurns: this.mapperState.turnCount || undefined,
|
|
967
1159
|
},
|
|
968
1160
|
raw: null,
|