@myagentroam/node 0.9.134 → 0.9.136
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/capabilities.js +1 -0
- package/dist/codex-app-server.js +14 -0
- package/dist/connector.js +4 -2
- package/dist/node-execution-environment.js +8 -0
- package/dist/runner/abstract-runner.js +35 -4
- package/dist/runner/claude/managed-run-controller.js +8 -1
- package/dist/runner/codex/conversation-parser.js +194 -13
- package/dist/runner/codex/managed-run-controller.js +2 -1
- package/dist/runner/codex-runner.js +113 -10
- package/dist/runner/mar-agent/computer-use.js +1 -0
- package/dist/runner/mar-agent/conversation-normalizer.js +8 -6
- package/dist/runner/mar-agent/hosted-conversation-runtime.js +3 -2
- package/dist/runner/mar-agent/managed-run-controller.js +46 -8
- package/dist/runner/mar-agent/mcp-client-pool.js +253 -29
- package/dist/runner/mar-agent/runtime-model-factory.js +7 -2
- package/dist/runner/mar-agent-runner.js +3 -0
- package/dist/runner/opencode/managed-run-controller.js +2 -1
- package/dist/runtime-state.js +2 -2
- package/dist/service/continuous-run-coordinator.js +3 -1
- package/dist/service/conversation-history-service.js +29 -40
- package/dist/service/external-session-resume-service.js +2 -0
- package/dist/service/hosted-conversation-node-service.js +1 -1
- package/dist/service/native-session-projection-service.js +2 -0
- package/dist/service/native-session-watch-service.js +13 -4
- package/dist/service/run-event-service.js +13 -0
- package/dist/service/session-catalog-service.js +3 -4
- package/dist/service/session-message-service.js +4 -2
- package/dist/util/node-operation-parsers.js +1 -3
- package/package.json +6 -5
package/dist/capabilities.js
CHANGED
package/dist/codex-app-server.js
CHANGED
|
@@ -237,6 +237,9 @@ export class CodexAppServerClient {
|
|
|
237
237
|
listThreads() {
|
|
238
238
|
return this.request('thread/list', {});
|
|
239
239
|
}
|
|
240
|
+
readThread(threadId) {
|
|
241
|
+
return this.request('thread/read', { threadId, includeTurns: false });
|
|
242
|
+
}
|
|
240
243
|
listCollaborationModes() {
|
|
241
244
|
return this.request('collaborationMode/list', {});
|
|
242
245
|
}
|
|
@@ -251,6 +254,17 @@ export class CodexAppServerClient {
|
|
|
251
254
|
...codexThreadConfiguration(cwd, configuration)
|
|
252
255
|
});
|
|
253
256
|
}
|
|
257
|
+
revertThread(threadId, beforeTurnId) {
|
|
258
|
+
return this.request('thread/revert', { threadId, beforeTurnId });
|
|
259
|
+
}
|
|
260
|
+
listThreadTurns(threadId, input) {
|
|
261
|
+
return this.request('thread/turns/list', {
|
|
262
|
+
threadId,
|
|
263
|
+
...input,
|
|
264
|
+
itemsView: 'full',
|
|
265
|
+
sortDirection: 'desc'
|
|
266
|
+
});
|
|
267
|
+
}
|
|
254
268
|
setThreadName(threadId, name) {
|
|
255
269
|
return this.request('thread/name/set', { threadId, name });
|
|
256
270
|
}
|
package/dist/connector.js
CHANGED
|
@@ -1075,12 +1075,14 @@ export class NodeConnector {
|
|
|
1075
1075
|
const session = this.runtime.getAgentSession(sessionId);
|
|
1076
1076
|
return session?.channelToken ?? undefined;
|
|
1077
1077
|
},
|
|
1078
|
-
goalMessage: (session, content) => {
|
|
1078
|
+
goalMessage: (session, content, intent) => {
|
|
1079
1079
|
const state = this.commandStates.get(session.id, 'goal');
|
|
1080
1080
|
if (state?.active !== true)
|
|
1081
1081
|
return undefined;
|
|
1082
1082
|
const current = this.goalRuntime.get(session.id);
|
|
1083
1083
|
if (current?.status === 'active') {
|
|
1084
|
+
if (intent !== 'REPLACE_CURRENT')
|
|
1085
|
+
return undefined;
|
|
1084
1086
|
this.invalidateGoalContinuation(session.id);
|
|
1085
1087
|
const resumed = this.goalRuntime.resume(session.id);
|
|
1086
1088
|
if (resumed !== current)
|
|
@@ -1111,7 +1113,7 @@ export class NodeConnector {
|
|
|
1111
1113
|
prepareGoalInsertion: (sessionId, submissionId, goal) => {
|
|
1112
1114
|
if (!this.goalGenerationCurrent(sessionId, goal))
|
|
1113
1115
|
return false;
|
|
1114
|
-
this.continuousRunCoordinator.
|
|
1116
|
+
this.continuousRunCoordinator.removeGoalQueue(sessionId, undefined, submissionId);
|
|
1115
1117
|
return true;
|
|
1116
1118
|
},
|
|
1117
1119
|
goalPreparationFailed: (session, goal, activated) => this.handleGoalPreparationFailed(session, goal, activated)
|
|
@@ -6,6 +6,14 @@ export function nodeExecutionEnvironment(provided = {}, inherited = process.env,
|
|
|
6
6
|
const environment = Object.fromEntries(Object.entries(inherited).filter((entry) => typeof entry[1] === 'string'));
|
|
7
7
|
Object.assign(environment, provided);
|
|
8
8
|
environment['MAR_NODE_CONTEXT'] = '1';
|
|
9
|
+
// Never identify a new execution as the user that launched the Node service.
|
|
10
|
+
const username = provided['MAR_USERNAME'] ?? '';
|
|
11
|
+
if (username.length > 128 || /[\r\n\0]/u.test(username))
|
|
12
|
+
throw new Error('SECRET_ENVIRONMENT_INVALID');
|
|
13
|
+
for (const key of Object.keys(environment))
|
|
14
|
+
if (key.toUpperCase() === 'MAR_USERNAME')
|
|
15
|
+
delete environment[key];
|
|
16
|
+
environment['MAR_USERNAME'] = username;
|
|
9
17
|
const platform = options.platform ?? process.platform;
|
|
10
18
|
const packageRoot = options.packageRoot ?? resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
11
19
|
const cliBins = [join(packageRoot, 'node_modules', '.bin')];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { mcpRuntimeDescriptorSchema } from '@myagentroam/protocol';
|
|
2
3
|
import { nodeExecutionEnvironment } from '../node-execution-environment.js';
|
|
3
4
|
/**
|
|
@@ -78,8 +79,12 @@ export class AbstractRunner {
|
|
|
78
79
|
void [session, boundary, title];
|
|
79
80
|
throw new RunnerCapabilityError(this.name, 'session.rewind');
|
|
80
81
|
}
|
|
81
|
-
|
|
82
|
-
void
|
|
82
|
+
supportsRollback(session) {
|
|
83
|
+
void session;
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
async rollbackNative(session, boundary, targetTurnBoundary) {
|
|
87
|
+
void [session, boundary, targetTurnBoundary];
|
|
83
88
|
throw new RunnerCapabilityError(this.name, 'session.rollback');
|
|
84
89
|
}
|
|
85
90
|
async clearNative(session) {
|
|
@@ -108,8 +113,8 @@ export class AbstractRunner {
|
|
|
108
113
|
void [session, nativeSessionId];
|
|
109
114
|
return undefined;
|
|
110
115
|
}
|
|
111
|
-
async readOfficialConversation(session, input, managedTurn) {
|
|
112
|
-
void [session, input, managedTurn];
|
|
116
|
+
async readOfficialConversation(session, input, managedTurn, registerImages) {
|
|
117
|
+
void [session, input, managedTurn, registerImages];
|
|
113
118
|
return undefined;
|
|
114
119
|
}
|
|
115
120
|
async readConversationHistory(readers) {
|
|
@@ -168,6 +173,7 @@ function resolvedMcpServers(raw, secrets) {
|
|
|
168
173
|
if (!Array.isArray(raw))
|
|
169
174
|
return [];
|
|
170
175
|
const marmgrEnvironment = {
|
|
176
|
+
MAR_USERNAME: secrets['MAR_USERNAME'] ?? '',
|
|
171
177
|
...(secrets['MAR_MGR_SERVER_URL'] === undefined
|
|
172
178
|
? {}
|
|
173
179
|
: { MAR_MGR_SERVER_URL: secrets['MAR_MGR_SERVER_URL'] }),
|
|
@@ -196,6 +202,16 @@ function resolvedMcpServers(raw, secrets) {
|
|
|
196
202
|
result.push({
|
|
197
203
|
name: record['serverName'],
|
|
198
204
|
lifecycle: 'EXECUTION',
|
|
205
|
+
origin: 'BUILTIN',
|
|
206
|
+
allowedToolCallers: record['__marGoal'] === true
|
|
207
|
+
? { mar_get_goal: ['direct', 'programmatic'], mar_update_goal: ['direct'] }
|
|
208
|
+
: record['__marScheduled'] === true
|
|
209
|
+
? {
|
|
210
|
+
mar_get_scheduled_task: ['direct', 'programmatic'],
|
|
211
|
+
mar_create_scheduled_task: ['direct'],
|
|
212
|
+
mar_update_scheduled_task: ['direct']
|
|
213
|
+
}
|
|
214
|
+
: { mar_send_email_notification: ['direct'] },
|
|
199
215
|
transport: 'STDIO',
|
|
200
216
|
command: record['command'],
|
|
201
217
|
args: record['args'],
|
|
@@ -221,6 +237,15 @@ function resolvedMcpServers(raw, secrets) {
|
|
|
221
237
|
if (!runtime.success)
|
|
222
238
|
continue;
|
|
223
239
|
const configuration = isRecord(record['configuration']) ? record['configuration'] : {};
|
|
240
|
+
const sourceIdentity = createHash('sha256')
|
|
241
|
+
.update(JSON.stringify({
|
|
242
|
+
id: record['id'] ?? null,
|
|
243
|
+
name: record['serverName'],
|
|
244
|
+
runtime: runtime.data,
|
|
245
|
+
configuration
|
|
246
|
+
}))
|
|
247
|
+
.digest('hex');
|
|
248
|
+
const authorizationIdentity = secrets['MAR_USERNAME']?.trim() || undefined;
|
|
224
249
|
const resolveReferences = (candidate) => {
|
|
225
250
|
if (!isRecord(candidate))
|
|
226
251
|
return {};
|
|
@@ -242,6 +267,9 @@ function resolvedMcpServers(raw, secrets) {
|
|
|
242
267
|
continue;
|
|
243
268
|
result.push({
|
|
244
269
|
name: record['serverName'],
|
|
270
|
+
origin: 'USER',
|
|
271
|
+
sourceIdentity,
|
|
272
|
+
...(authorizationIdentity === undefined ? {} : { authorizationIdentity }),
|
|
245
273
|
transport: 'STDIO',
|
|
246
274
|
command: runtime.data.command,
|
|
247
275
|
args: [
|
|
@@ -263,6 +291,9 @@ function resolvedMcpServers(raw, secrets) {
|
|
|
263
291
|
continue;
|
|
264
292
|
result.push({
|
|
265
293
|
name: record['serverName'],
|
|
294
|
+
origin: 'USER',
|
|
295
|
+
sourceIdentity,
|
|
296
|
+
...(authorizationIdentity === undefined ? {} : { authorizationIdentity }),
|
|
266
297
|
transport: 'HTTP',
|
|
267
298
|
url: runtime.data.url,
|
|
268
299
|
headers: {
|
|
@@ -180,7 +180,8 @@ export class ClaudeManagedRunController {
|
|
|
180
180
|
: {}),
|
|
181
181
|
mcpServers: this.runner.mcpConfiguration(payload.mcpInstallations, {
|
|
182
182
|
...secretEnvironment,
|
|
183
|
-
...mcpSecretEnvironment
|
|
183
|
+
...mcpSecretEnvironment,
|
|
184
|
+
MAR_USERNAME: secretEnvironment['MAR_USERNAME'] ?? ''
|
|
184
185
|
}),
|
|
185
186
|
...(attachments.length === 0
|
|
186
187
|
? {}
|
|
@@ -331,6 +332,12 @@ export class ClaudeManagedRunController {
|
|
|
331
332
|
}
|
|
332
333
|
}
|
|
333
334
|
if (record.type === 'result') {
|
|
335
|
+
const nativeTtft = [record.ttft_ms, record.ttft_stream_ms].find((value) => typeof value === 'number' &&
|
|
336
|
+
Number.isFinite(value) &&
|
|
337
|
+
value >= 0 &&
|
|
338
|
+
Number.isSafeInteger(Math.round(value)));
|
|
339
|
+
if (nativeTtft !== undefined)
|
|
340
|
+
this.host.emit(runId, 'run.performance', { ttftMs: Math.round(nativeTtft) });
|
|
334
341
|
const subtype = typeof record.subtype === 'string' ? record.subtype : 'unknown';
|
|
335
342
|
const completedNativeSessionId = this.runner.execution.runs.get(runId)?.nativeSessionId ?? nativeSessionId;
|
|
336
343
|
this.host.emit(runId, 'turn.completed', {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { basename, extname } from 'node:path';
|
|
2
|
+
import { runnerErrorProjection } from '../../util/runner-error.js';
|
|
3
|
+
import { conversationImageAttachmentSchema } from '@myagentroam/protocol';
|
|
1
4
|
import { parseCodexComposerImageMarkers } from './composer-image-markers.js';
|
|
2
5
|
import { normalizeCodexTool } from './tool-normalizer.js';
|
|
3
6
|
import { stripGoalStartPrompt } from '../../service/goal-runtime.js';
|
|
@@ -5,6 +8,68 @@ const MAX_COMPOSER_ATTACHMENT_BYTES = 5 * 1024 * 1024;
|
|
|
5
8
|
const MAX_COMPOSER_ATTACHMENTS_BYTES = 10 * 1024 * 1024;
|
|
6
9
|
const MAX_ASSISTANT_MESSAGE_CHARACTERS = 64 * 1024;
|
|
7
10
|
export * from '../../util/runner-native-session-parsers.js';
|
|
11
|
+
/** Only the official thread descriptor can declare history-only Revert support. */
|
|
12
|
+
export function codexThreadHistoryModes(result) {
|
|
13
|
+
if (!isPlainRecord(result))
|
|
14
|
+
return [];
|
|
15
|
+
const descriptors = Array.isArray(result.data) ? result.data : [result.thread];
|
|
16
|
+
return descriptors.flatMap((value) => isPlainRecord(value) && typeof value.id === 'string'
|
|
17
|
+
? [{ id: value.id, paginated: value.historyMode === 'paginated' }]
|
|
18
|
+
: []);
|
|
19
|
+
}
|
|
20
|
+
export function codexPaginatedThread(result) {
|
|
21
|
+
return (isPlainRecord(result) &&
|
|
22
|
+
isPlainRecord(result.thread) &&
|
|
23
|
+
result.thread.historyMode === 'paginated');
|
|
24
|
+
}
|
|
25
|
+
export function codexPaginatedCursor(sessionId, cursor) {
|
|
26
|
+
if (cursor === undefined)
|
|
27
|
+
return undefined;
|
|
28
|
+
try {
|
|
29
|
+
const value = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
|
|
30
|
+
if (isPlainRecord(value) &&
|
|
31
|
+
value.source === 'codex-paginated' &&
|
|
32
|
+
value.sessionId === sessionId &&
|
|
33
|
+
typeof value.cursor === 'string' &&
|
|
34
|
+
value.cursor.length > 0)
|
|
35
|
+
return value.cursor;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Invalid or foreign cursors never fall back to JSONL.
|
|
39
|
+
}
|
|
40
|
+
throw new Error('EVENT_CURSOR_INVALID');
|
|
41
|
+
}
|
|
42
|
+
export function codexPaginatedConversationPage(session, result, earlierTurnId, managedTurn, registerImages) {
|
|
43
|
+
if (!isPlainRecord(result) || !Array.isArray(result.data))
|
|
44
|
+
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
45
|
+
const values = result.data;
|
|
46
|
+
if (values.some((value) => !isPlainRecord(value) ||
|
|
47
|
+
typeof value.id !== 'string' ||
|
|
48
|
+
!Array.isArray(value.items) ||
|
|
49
|
+
value.itemsView !== 'full') ||
|
|
50
|
+
(result.nextCursor !== undefined &&
|
|
51
|
+
result.nextCursor !== null &&
|
|
52
|
+
typeof result.nextCursor !== 'string'))
|
|
53
|
+
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
54
|
+
const turns = values.flatMap((value, index) => {
|
|
55
|
+
const native = value;
|
|
56
|
+
const turn = codexOfficialTurn(session, native, index, managedTurn(native.id), registerImages);
|
|
57
|
+
if (turn === undefined)
|
|
58
|
+
return [];
|
|
59
|
+
const earlier = values[index + 1];
|
|
60
|
+
return [{ ...turn, rewind: { before: earlier?.id ?? earlierTurnId, after: native.id } }];
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
turns: turns.reverse(),
|
|
64
|
+
nextCursor: typeof result.nextCursor === 'string'
|
|
65
|
+
? Buffer.from(JSON.stringify({
|
|
66
|
+
source: 'codex-paginated',
|
|
67
|
+
sessionId: session.id,
|
|
68
|
+
cursor: result.nextCursor
|
|
69
|
+
})).toString('base64url')
|
|
70
|
+
: null
|
|
71
|
+
};
|
|
72
|
+
}
|
|
8
73
|
export function codexOfficialConversationPage(session, result, input, managedTurnForNative) {
|
|
9
74
|
if (!isPlainRecord(result) || !isPlainRecord(result.thread))
|
|
10
75
|
return undefined;
|
|
@@ -34,12 +99,36 @@ export function codexOfficialConversationPage(session, result, input, managedTur
|
|
|
34
99
|
: null
|
|
35
100
|
};
|
|
36
101
|
}
|
|
37
|
-
export function codexOfficialTurn(session, value, sequence, managedTurn) {
|
|
102
|
+
export function codexOfficialTurn(session, value, sequence, managedTurn, registerImages) {
|
|
38
103
|
if (!isPlainRecord(value) || typeof value.id !== 'string' || !Array.isArray(value.items))
|
|
39
104
|
return undefined;
|
|
105
|
+
const status = value.status === 'inProgress'
|
|
106
|
+
? 'RUNNING'
|
|
107
|
+
: value.status === 'failed'
|
|
108
|
+
? 'FAILED'
|
|
109
|
+
: value.status === 'interrupted'
|
|
110
|
+
? 'INTERRUPTED'
|
|
111
|
+
: 'SUCCEEDED';
|
|
40
112
|
const startedAt = codexTimestamp(value.startedAt);
|
|
41
113
|
const completedAt = codexTimestamp(value.completedAt) ?? startedAt;
|
|
42
|
-
const items = codexOfficialItems(session, value.id, value.items, sequence, startedAt, completedAt);
|
|
114
|
+
const items = codexOfficialItems(session, value.id, value.items, sequence, startedAt, completedAt, registerImages);
|
|
115
|
+
if ((status === 'FAILED' || status === 'INTERRUPTED') && isPlainRecord(value.error)) {
|
|
116
|
+
const projected = runnerErrorProjection(value.error, 'CODEX_RUN_FAILED');
|
|
117
|
+
items.push({
|
|
118
|
+
id: `${session.id}:official:${value.id}:error`,
|
|
119
|
+
sessionId: session.id,
|
|
120
|
+
turnId: `${session.id}:official:${value.id}`,
|
|
121
|
+
runId: null,
|
|
122
|
+
parentItemId: null,
|
|
123
|
+
sourceSequence: sequence * 10_000 + value.items.length + 1,
|
|
124
|
+
revision: 0,
|
|
125
|
+
kind: 'error',
|
|
126
|
+
status: 'FAILED',
|
|
127
|
+
startedAt,
|
|
128
|
+
completedAt,
|
|
129
|
+
payload: { ...projected, recoverable: true, retryAfterMs: null }
|
|
130
|
+
});
|
|
131
|
+
}
|
|
43
132
|
if (items.length === 0)
|
|
44
133
|
return undefined;
|
|
45
134
|
if (managedTurn !== undefined) {
|
|
@@ -98,6 +187,12 @@ export function codexOfficialTurn(session, value, sequence, managedTurn) {
|
|
|
98
187
|
let replacement;
|
|
99
188
|
if (managedItem.kind === 'user_message')
|
|
100
189
|
replacement = officialUser;
|
|
190
|
+
else if (managedItem.kind === 'error' && isPlainRecord(managedItem.payload))
|
|
191
|
+
replacement = officialItems.find((item) => item.kind === 'error' &&
|
|
192
|
+
isPlainRecord(item.payload) &&
|
|
193
|
+
isPlainRecord(managedItem.payload) &&
|
|
194
|
+
item.payload.code === managedItem.payload.code &&
|
|
195
|
+
item.payload.message === managedItem.payload.message);
|
|
101
196
|
else if (managedItem.kind === 'context_compaction')
|
|
102
197
|
replacement = officialContextCompactions.find((item) => !consumedOfficialIds.has(item.id));
|
|
103
198
|
else {
|
|
@@ -123,7 +218,7 @@ export function codexOfficialTurn(session, value, sequence, managedTurn) {
|
|
|
123
218
|
reconciledItems.push(item);
|
|
124
219
|
return {
|
|
125
220
|
...managedTurn,
|
|
126
|
-
status: officialComplete ?
|
|
221
|
+
status: officialComplete ? status : managedTurn.status,
|
|
127
222
|
// Reconcile against the managed runtime order. Official items replace
|
|
128
223
|
// matching live projections in place; newly visible terminal items such
|
|
129
224
|
// as a plan are appended after the commentary and tools that preceded it.
|
|
@@ -137,13 +232,13 @@ export function codexOfficialTurn(session, value, sequence, managedTurn) {
|
|
|
137
232
|
sessionId: session.id,
|
|
138
233
|
runId: null,
|
|
139
234
|
userItemId: items.find((item) => item.kind === 'user_message')?.id ?? null,
|
|
140
|
-
status
|
|
235
|
+
status,
|
|
141
236
|
model: null,
|
|
142
237
|
effort: null,
|
|
143
238
|
access: null,
|
|
144
239
|
items,
|
|
145
240
|
startedAt,
|
|
146
|
-
completedAt
|
|
241
|
+
completedAt: status === 'RUNNING' ? null : completedAt
|
|
147
242
|
};
|
|
148
243
|
}
|
|
149
244
|
/**
|
|
@@ -197,15 +292,21 @@ export function conversationItemNativeId(itemId) {
|
|
|
197
292
|
return markerIndex < 0 ? undefined : itemId.slice(markerIndex + marker.length);
|
|
198
293
|
}
|
|
199
294
|
/** Keep a custom tool invocation and its output in one Workbench card. */
|
|
200
|
-
export function codexOfficialItems(session, nativeTurnId, values, turnSequence, startedAt, completedAt) {
|
|
295
|
+
export function codexOfficialItems(session, nativeTurnId, values, turnSequence, startedAt, completedAt, registerImages) {
|
|
201
296
|
const items = [];
|
|
202
297
|
const pairedToolIndexes = new Map();
|
|
203
298
|
// App Server preserves execution order within a Turn. Keep that order while
|
|
204
299
|
// merging a paired tool invocation with its output into one Workbench card.
|
|
205
300
|
for (const [itemSequence, value] of values.entries()) {
|
|
206
|
-
|
|
301
|
+
let item = codexOfficialItem(session, nativeTurnId, value, turnSequence, itemSequence, startedAt, completedAt);
|
|
207
302
|
if (item === undefined)
|
|
208
303
|
continue;
|
|
304
|
+
if (isPlainRecord(value)) {
|
|
305
|
+
const sources = codexOfficialImageSources(value, item.id);
|
|
306
|
+
const attachments = sources.map((source) => registerImages?.([source])[0] ?? { name: source.name, mime: source.mime });
|
|
307
|
+
if (attachments.length > 0 && isPlainRecord(item.payload))
|
|
308
|
+
item = { ...item, payload: { ...item.payload, attachments } };
|
|
309
|
+
}
|
|
209
310
|
// Codex persists one compaction as both `compacted` state and a
|
|
210
311
|
// `context_compacted` event. App Server normalizes both records to
|
|
211
312
|
// consecutive `contextCompaction` items without preserving their source.
|
|
@@ -229,7 +330,73 @@ export function codexOfficialItems(session, nativeTurnId, values, turnSequence,
|
|
|
229
330
|
const result = invocation === item ? previous : item;
|
|
230
331
|
items[existingIndex] = mergeCodexCustomToolItems(invocation, result);
|
|
231
332
|
}
|
|
232
|
-
return items;
|
|
333
|
+
return [...coalesceImageGenerationItems(items)];
|
|
334
|
+
}
|
|
335
|
+
function codexLocalImageSource(path, name = basename(path)) {
|
|
336
|
+
const mime = {
|
|
337
|
+
'.png': 'image/png',
|
|
338
|
+
'.jpg': 'image/jpeg',
|
|
339
|
+
'.jpeg': 'image/jpeg',
|
|
340
|
+
'.gif': 'image/gif',
|
|
341
|
+
'.webp': 'image/webp'
|
|
342
|
+
}[extname(path).toLowerCase()];
|
|
343
|
+
return mime === undefined ? { name, mime: 'image/unknown' } : { name, mime, localPath: path };
|
|
344
|
+
}
|
|
345
|
+
function codexOfficialImageSources(value, sourceId) {
|
|
346
|
+
if (value.type !== 'userMessage') {
|
|
347
|
+
const path = value.type === 'imageView'
|
|
348
|
+
? value.path
|
|
349
|
+
: value.type === 'imageGeneration'
|
|
350
|
+
? value.savedPath
|
|
351
|
+
: undefined;
|
|
352
|
+
return typeof path === 'string' ? [codexLocalImageSource(path)] : [];
|
|
353
|
+
}
|
|
354
|
+
const images = [];
|
|
355
|
+
const paths = new Set();
|
|
356
|
+
let totalBytes = 0;
|
|
357
|
+
const content = Array.isArray(value.content) ? value.content : [];
|
|
358
|
+
for (const part of content) {
|
|
359
|
+
if (!isPlainRecord(part) || images.length >= 5)
|
|
360
|
+
continue;
|
|
361
|
+
const name = `Image #${images.length + 1}`;
|
|
362
|
+
if (part.type === 'localImage' && typeof part.path === 'string') {
|
|
363
|
+
if (!paths.has(part.path)) {
|
|
364
|
+
paths.add(part.path);
|
|
365
|
+
images.push(codexLocalImageSource(part.path, name));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
else if (part.type === 'image') {
|
|
369
|
+
const image = codexImageAttachments([{ type: 'input_image', image_url: part.url }])[0];
|
|
370
|
+
const publicMime = image !== undefined &&
|
|
371
|
+
conversationImageAttachmentSchema.safeParse({ name, mime: image.mime }).success;
|
|
372
|
+
const byteSize = image === undefined ? 0 : Buffer.byteLength(image.dataBase64, 'base64');
|
|
373
|
+
const readable = image !== undefined &&
|
|
374
|
+
publicMime &&
|
|
375
|
+
totalBytes + byteSize <= MAX_COMPOSER_ATTACHMENTS_BYTES;
|
|
376
|
+
if (readable)
|
|
377
|
+
totalBytes += byteSize;
|
|
378
|
+
images.push(!readable || image === undefined
|
|
379
|
+
? { name, mime: publicMime ? image.mime : 'image/unknown' }
|
|
380
|
+
: {
|
|
381
|
+
name,
|
|
382
|
+
mime: image.mime,
|
|
383
|
+
byteSize,
|
|
384
|
+
sourceId: `${sourceId}:image:${images.length}`,
|
|
385
|
+
read: async () => ({ ...image, name })
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
else if (part.type === 'text' && typeof part.text === 'string') {
|
|
389
|
+
for (const marker of parseCodexComposerImageMarkers(part.text).images) {
|
|
390
|
+
if (images.length >= 5)
|
|
391
|
+
break;
|
|
392
|
+
if (!paths.has(marker.localPath)) {
|
|
393
|
+
paths.add(marker.localPath);
|
|
394
|
+
images.push(codexLocalImageSource(marker.localPath, `Image #${marker.index}`));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return images;
|
|
233
400
|
}
|
|
234
401
|
export function codexPairedToolCallId(value) {
|
|
235
402
|
if (!isPlainRecord(value))
|
|
@@ -292,7 +459,8 @@ export function coalesceImageGenerationItems(items) {
|
|
|
292
459
|
if (item.kind !== 'tool_call' ||
|
|
293
460
|
payload?.toolName !== 'imagegen' ||
|
|
294
461
|
previous?.kind !== 'tool_call' ||
|
|
295
|
-
previousPayload?.toolName !== 'imagegen'
|
|
462
|
+
previousPayload?.toolName !== 'imagegen' ||
|
|
463
|
+
previous.id !== item.id) {
|
|
296
464
|
merged.push(item);
|
|
297
465
|
continue;
|
|
298
466
|
}
|
|
@@ -302,8 +470,14 @@ export function coalesceImageGenerationItems(items) {
|
|
|
302
470
|
];
|
|
303
471
|
merged[merged.length - 1] = {
|
|
304
472
|
...previous,
|
|
473
|
+
status: item.status,
|
|
305
474
|
completedAt: item.completedAt ?? previous.completedAt,
|
|
306
|
-
payload: {
|
|
475
|
+
payload: {
|
|
476
|
+
...previousPayload,
|
|
477
|
+
...payload,
|
|
478
|
+
inputSummary: previousPayload.inputSummary ?? payload.inputSummary,
|
|
479
|
+
attachments
|
|
480
|
+
}
|
|
307
481
|
};
|
|
308
482
|
}
|
|
309
483
|
return merged;
|
|
@@ -348,15 +522,22 @@ export function codexOfficialItem(session, nativeTurnId, value, turnSequence, it
|
|
|
348
522
|
turnId: `${session.id}:official:${nativeTurnId}`,
|
|
349
523
|
runId: null,
|
|
350
524
|
parentItemId: null,
|
|
351
|
-
sourceSequence: turnSequence * 10_000 + itemSequence,
|
|
525
|
+
sourceSequence: turnSequence * 10_000 + itemSequence + 1,
|
|
352
526
|
revision: 0,
|
|
353
|
-
status: '
|
|
527
|
+
status: codexItemStatus(value.status, value.status !== 'inProgress'),
|
|
354
528
|
startedAt,
|
|
355
529
|
completedAt
|
|
356
530
|
};
|
|
357
531
|
if (value.type === 'userMessage') {
|
|
358
532
|
const text = stripGoalStartPrompt(codexContentText(value.content));
|
|
359
|
-
|
|
533
|
+
const hasImage = Array.isArray(value.content) &&
|
|
534
|
+
value.content.some((part) => isPlainRecord(part) &&
|
|
535
|
+
(part.type === 'image' ||
|
|
536
|
+
(part.type === 'localImage' && typeof part.path === 'string') ||
|
|
537
|
+
(part.type === 'text' &&
|
|
538
|
+
typeof part.text === 'string' &&
|
|
539
|
+
parseCodexComposerImageMarkers(part.text).images.length > 0)));
|
|
540
|
+
return text.length === 0 && !hasImage
|
|
360
541
|
? undefined
|
|
361
542
|
: {
|
|
362
543
|
...base,
|
|
@@ -409,7 +409,8 @@ export class CodexManagedRunController {
|
|
|
409
409
|
: {}),
|
|
410
410
|
mcpServers: this.runner.mcpConfiguration(payload.mcpInstallations, {
|
|
411
411
|
...secretEnvironment,
|
|
412
|
-
...mcpSecretEnvironment
|
|
412
|
+
...mcpSecretEnvironment,
|
|
413
|
+
MAR_USERNAME: secretEnvironment['MAR_USERNAME'] ?? ''
|
|
413
414
|
})
|
|
414
415
|
}, attachments);
|
|
415
416
|
}
|