farming-code 2.2.8 → 2.2.12
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 +42 -28
- package/README.zh_cn.md +42 -28
- package/THIRD_PARTY_NOTICES.md +11 -2
- package/backend/acp-checkpoint-store.js +198 -0
- package/backend/acp-runtime.js +306 -83
- package/backend/acp-session-state.js +202 -6
- package/backend/acp-transcript.js +112 -0
- package/backend/agent-activity.js +6 -157
- package/backend/agent-manager.js +1592 -741
- package/backend/agent-provider-session.js +17 -242
- package/backend/agent-runtime-binding.js +219 -0
- package/backend/agent-session-history.js +66 -1
- package/backend/auth.js +79 -6
- package/backend/codex-models.js +81 -84
- package/backend/codex-session-archive.js +54 -0
- package/backend/codex-terminal-profile.js +500 -0
- package/backend/codex-transcript-sanitizer.js +12 -0
- package/backend/codex-transcript.js +230 -8
- package/backend/config-manager.js +35 -0
- package/backend/control-api.js +192 -17
- package/backend/executable-discovery.js +2 -0
- package/backend/farming-net-pass.js +285 -0
- package/backend/farming-net-registry.js +112 -0
- package/backend/farming-net-server.js +298 -0
- package/backend/farming-session-store.js +5 -13
- package/backend/git-worktree-info.js +181 -0
- package/backend/local-session-engine.js +411 -186
- package/backend/main-page-session.js +5 -2
- package/backend/native-pty-controller-generation.js +106 -0
- package/backend/native-pty-host-client.js +275 -7
- package/backend/native-pty-host-identity.js +86 -0
- package/backend/native-pty-host.js +813 -114
- package/backend/native-session-engine.js +100 -28
- package/backend/packaged-node-pty.js +22 -2
- package/backend/provider-adapters.js +253 -0
- package/backend/provider-session-service.js +241 -0
- package/backend/runtime-observation.js +81 -0
- package/backend/server.js +354 -84
- package/backend/session-engine-bridge.js +21 -2
- package/backend/session-engine-router.js +1 -1
- package/backend/session-engine.js +1 -1
- package/backend/session-stream-protocol.js +185 -0
- package/backend/storage-layout.js +55 -0
- package/backend/terminal-attach-checkpoint.js +74 -0
- package/backend/terminal-exit-quiescence.js +39 -0
- package/backend/terminal-reducer-flow-control.js +97 -0
- package/backend/terminal-screen-state.js +11 -2
- package/backend/terminal-screen-worker-pool.js +59 -6
- package/backend/terminal-screen-worker-thread.js +97 -57
- package/backend/terminal-screen-worker.js +133 -51
- package/backend/terminal-state-serialization.js +127 -0
- package/backend/terminal-status.js +23 -4
- package/backend/usage-monitor.js +176 -17
- package/backend/workspace-directory.js +232 -0
- package/backend/workspace-file-router.js +182 -76
- package/backend/workspace-file-service.js +319 -4
- package/backend/workspace-root-registry.js +164 -0
- package/dist/assets/App-DWsa7DXG.js +206 -0
- package/dist/assets/{FileEditorMarkdownPreview-elKWc8Im.js → FileEditorMarkdownPreview-UlVd0O4D.js} +92 -92
- package/dist/assets/FileEditorPane-Dxx4rKOg.js +2 -0
- package/dist/assets/IconGlyphs-AU22gPDY.js +1 -0
- package/dist/assets/ProjectFilesSection-DE7Nb9Jk.js +12 -0
- package/dist/assets/ReviewPage-CeDKDyZh.js +3 -0
- package/dist/assets/code-dark-DPiuyeSp.css +1 -0
- package/dist/assets/file-icons-Bw2qd5iT.js +1 -0
- package/dist/assets/{index-BrbljRqn.js → index-CUFPJoa5.js} +3 -3
- package/dist/assets/main-Bqtb5kuz.css +1 -0
- package/dist/assets/{monaco.contribution-CyLosfZI.js → monaco.contribution-BXz-lfFB.js} +2 -2
- package/dist/assets/{tsMode-DGZTlTj8.js → tsMode-B0oUTcXQ.js} +1 -1
- package/dist/assets/workspace-editor-model-BQol4qbA.js +1 -0
- package/dist/assets/workspace-editor-monaco-BICJESG0.js +4 -0
- package/dist/assets/workspace-editor-monaco-DRJ7IaXk.js +1 -0
- package/dist/assets/workspace-view-state-DvYG_9PH.js +7 -0
- package/dist/assets/workspace-working-copy-D8-s_Sgh.js +1 -0
- package/dist/farming-2/site.webmanifest +2 -0
- package/dist/index.html +1 -1
- package/frontend/farming-net/app.css +625 -0
- package/frontend/farming-net/app.js +268 -0
- package/frontend/farming-net/index.html +86 -0
- package/frontend/reading-anchor.js +198 -0
- package/frontend/session-bridge.js +12 -3
- package/frontend/session-modal-bridge.js +5 -12
- package/frontend/skins/crt/app.js +1978 -793
- package/frontend/skins/crt/index.html +313 -23
- package/frontend/skins/crt/styles/billing.css +294 -223
- package/frontend/skins/crt/styles/monochrome-green.css +7 -2
- package/frontend/terminal-replay.js +372 -0
- package/package.json +10 -3
- package/shared/browser-protocol.d.ts +5 -0
- package/shared/browser-protocol.js +130 -0
- package/dist/assets/App-8dYAM6ql.js +0 -124
- package/dist/assets/FileEditorPane-RWiFD2cq.js +0 -5
- package/dist/assets/IconGlyphs-DfL0EBnj.js +0 -1
- package/dist/assets/ProjectFilesSection-Q4PDsWmM.js +0 -12
- package/dist/assets/ReviewPage-BaXu1ZdX.js +0 -3
- package/dist/assets/code-dark-CDkOQAtK.css +0 -1
- package/dist/assets/file-icons-EFUGSSwf.js +0 -1
- package/dist/assets/main-D073SnW4.css +0 -1
- package/dist/assets/qoder-C9LmmOSf.svg +0 -1
- package/dist/assets/qoder-Cf9gl0Y5.svg +0 -1
- package/dist/assets/qoder-gHCinseV.svg +0 -1
- package/dist/assets/workspace-view-state-CTyDzk2D.js +0 -1
- package/dist/assets/zsh-CLpveKlF.svg +0 -1
- package/dist/assets/zsh-FxSpMPbz.svg +0 -1
- /package/dist/assets/{api-D1lyBYIQ.js → api-D8nyOEbz.js} +0 -0
- /package/dist/assets/{core-ZlAPicox.js → core-D0LFJkDt.js} +0 -0
- /package/dist/assets/{useWorkspaceMenuKeyboard-CneKAZUJ.js → useWorkspaceMenuKeyboard-Brws6Ar9.js} +0 -0
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const fsp = require('fs/promises');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const { fileURLToPath } = require('url');
|
|
5
6
|
const { findCodexRolloutFile } = require('./codex-rollout-follower');
|
|
6
7
|
const {
|
|
7
8
|
heartbeatAssistantMessage,
|
|
@@ -18,8 +19,12 @@ const USER_MESSAGE_BEGIN = '## My request for Codex:';
|
|
|
18
19
|
const MAX_USER_IMAGES_PER_TURN = 6;
|
|
19
20
|
const MAX_USER_IMAGE_URL_LENGTH = 5 * 1024 * 1024;
|
|
20
21
|
const MAX_LOCAL_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
22
|
+
const MAX_USER_AUDIOS_PER_TURN = 6;
|
|
23
|
+
const MAX_USER_AUDIO_URL_LENGTH = 10 * 1024 * 1024;
|
|
24
|
+
const MAX_LOCAL_AUDIO_BYTES = 10 * 1024 * 1024;
|
|
21
25
|
const MAX_USER_FILES_PER_TURN = 6;
|
|
22
26
|
const MAX_USER_FILE_CONTENT_CHARS = 50_000;
|
|
27
|
+
const CODEX_HISTORY_IMAGE_LINK_PATTERN = /\[@(?:image|[^\]]+\.(?:gif|jpe?g|png|svg|webp))\]\(([^)\n]+)\)/gi;
|
|
23
28
|
|
|
24
29
|
const LOCAL_IMAGE_MIME_BY_EXT = {
|
|
25
30
|
'.gif': 'image/gif',
|
|
@@ -30,6 +35,18 @@ const LOCAL_IMAGE_MIME_BY_EXT = {
|
|
|
30
35
|
'.webp': 'image/webp',
|
|
31
36
|
};
|
|
32
37
|
|
|
38
|
+
const LOCAL_AUDIO_MIME_BY_EXT = {
|
|
39
|
+
'.aac': 'audio/aac',
|
|
40
|
+
'.flac': 'audio/flac',
|
|
41
|
+
'.m4a': 'audio/mp4',
|
|
42
|
+
'.mp3': 'audio/mpeg',
|
|
43
|
+
'.oga': 'audio/ogg',
|
|
44
|
+
'.ogg': 'audio/ogg',
|
|
45
|
+
'.opus': 'audio/ogg',
|
|
46
|
+
'.wav': 'audio/wav',
|
|
47
|
+
'.webm': 'audio/webm',
|
|
48
|
+
};
|
|
49
|
+
|
|
33
50
|
function normalizeText(value) {
|
|
34
51
|
return String(value || '')
|
|
35
52
|
.replace(/\r\n/g, '\n')
|
|
@@ -290,6 +307,81 @@ function stripRenderedComposerAttachmentBlocks(value, renderedKinds) {
|
|
|
290
307
|
.trim();
|
|
291
308
|
}
|
|
292
309
|
|
|
310
|
+
function codexHistoryImageTargets(value) {
|
|
311
|
+
const text = String(value || '');
|
|
312
|
+
const targets = [];
|
|
313
|
+
let match;
|
|
314
|
+
CODEX_HISTORY_IMAGE_LINK_PATTERN.lastIndex = 0;
|
|
315
|
+
while ((match = CODEX_HISTORY_IMAGE_LINK_PATTERN.exec(text))) {
|
|
316
|
+
const target = String(match[1] || '').trim();
|
|
317
|
+
if (!target || targets.includes(target)) continue;
|
|
318
|
+
targets.push(target);
|
|
319
|
+
if (targets.length >= MAX_USER_IMAGES_PER_TURN) break;
|
|
320
|
+
}
|
|
321
|
+
return targets;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function localImagePathFromTarget(value) {
|
|
325
|
+
const target = String(value || '').trim();
|
|
326
|
+
if (!target || target.includes('\0') || /^data:/i.test(target)) return '';
|
|
327
|
+
if (/^file:\/\//i.test(target)) {
|
|
328
|
+
try {
|
|
329
|
+
return fileURLToPath(target);
|
|
330
|
+
} catch {
|
|
331
|
+
return '';
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return path.isAbsolute(target) ? target : '';
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function localImagePathsFromUserContent(content) {
|
|
338
|
+
const paths = [];
|
|
339
|
+
const append = (value) => {
|
|
340
|
+
const filePath = localImagePathFromTarget(value);
|
|
341
|
+
if (!filePath || !LOCAL_IMAGE_MIME_BY_EXT[path.extname(filePath).toLowerCase()] || paths.includes(filePath)) return;
|
|
342
|
+
paths.push(filePath);
|
|
343
|
+
};
|
|
344
|
+
for (const part of Array.isArray(content) ? content : []) {
|
|
345
|
+
if (!part || typeof part !== 'object') continue;
|
|
346
|
+
if (['local_image', 'localImage'].includes(part.type)) append(part.path || part.file || part.url);
|
|
347
|
+
const text = typeof part.text === 'string' ? part.text : '';
|
|
348
|
+
for (const match of text.matchAll(/<image\b[^>]*\bpath=(['"])(.*?)\1/gi)) append(match[2]);
|
|
349
|
+
for (const match of text.matchAll(/^##\s+[^\n:]+:\s*(.+)$/gim)) append(match[1]);
|
|
350
|
+
}
|
|
351
|
+
return paths.slice(0, MAX_USER_IMAGES_PER_TURN);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function dataImageUrlsFromUserContent(content) {
|
|
355
|
+
const urls = [];
|
|
356
|
+
for (const part of Array.isArray(content) ? content : []) {
|
|
357
|
+
if (!part || typeof part !== 'object' || !['input_image', 'inputImage', 'image'].includes(part.type)) continue;
|
|
358
|
+
const value = String(part.image_url || part.imageUrl || part.url || part.data || '');
|
|
359
|
+
if (!/^data:image\/(?:gif|jpe?g|png|svg\+xml|webp);base64,/i.test(value)) continue;
|
|
360
|
+
if (value.length > MAX_USER_IMAGE_URL_LENGTH || urls.includes(value)) continue;
|
|
361
|
+
urls.push(value);
|
|
362
|
+
if (urls.length >= MAX_USER_IMAGES_PER_TURN) break;
|
|
363
|
+
}
|
|
364
|
+
return urls;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function appendHistoryImageDataFromContent(imageDataByPath, content) {
|
|
368
|
+
const paths = localImagePathsFromUserContent(content);
|
|
369
|
+
const urls = dataImageUrlsFromUserContent(content);
|
|
370
|
+
for (let index = 0; index < Math.min(paths.length, urls.length); index += 1) {
|
|
371
|
+
if (!imageDataByPath.has(paths[index])) imageDataByPath.set(paths[index], urls[index]);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function stripRenderedCodexHistoryAttachmentLinks(value, renderedKinds) {
|
|
376
|
+
const text = normalizeText(value);
|
|
377
|
+
if (!text || !renderedKinds.has('image')) return text;
|
|
378
|
+
CODEX_HISTORY_IMAGE_LINK_PATTERN.lastIndex = 0;
|
|
379
|
+
return text
|
|
380
|
+
.replace(CODEX_HISTORY_IMAGE_LINK_PATTERN, '')
|
|
381
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
382
|
+
.trim();
|
|
383
|
+
}
|
|
384
|
+
|
|
293
385
|
function visibleUserMessageText(value, options = {}) {
|
|
294
386
|
const rawText = stripUserMessagePrefix(value);
|
|
295
387
|
const heartbeatMessage = heartbeatUserMessage(rawText);
|
|
@@ -297,19 +389,24 @@ function visibleUserMessageText(value, options = {}) {
|
|
|
297
389
|
const text = stripCodexInternalContextBlocks(rawText);
|
|
298
390
|
if (isInjectedContextMessage(text)) return '';
|
|
299
391
|
const renderedKinds = renderedAttachmentKindSet(options);
|
|
300
|
-
return
|
|
392
|
+
return stripRenderedCodexHistoryAttachmentLinks(
|
|
393
|
+
stripRenderedComposerAttachmentBlocks(stripRenderedAttachmentTagBlocks(text, renderedKinds), renderedKinds),
|
|
394
|
+
renderedKinds,
|
|
395
|
+
);
|
|
301
396
|
}
|
|
302
397
|
|
|
303
398
|
function renderedAttachmentKindsForTurn(turn) {
|
|
304
399
|
const kinds = [];
|
|
305
400
|
if (turn && Array.isArray(turn.userImages) && turn.userImages.length > 0) kinds.push('image');
|
|
401
|
+
if (turn && Array.isArray(turn.userAudios) && turn.userAudios.length > 0) kinds.push('audio');
|
|
306
402
|
if (turn && Array.isArray(turn.userFiles) && turn.userFiles.length > 0) kinds.push('file');
|
|
307
403
|
return kinds;
|
|
308
404
|
}
|
|
309
405
|
|
|
310
|
-
function renderedAttachmentKindsForAttachments({ images = [], files = [] } = {}) {
|
|
406
|
+
function renderedAttachmentKindsForAttachments({ images = [], audios = [], files = [] } = {}) {
|
|
311
407
|
const kinds = [];
|
|
312
408
|
if (Array.isArray(images) && images.length > 0) kinds.push('image');
|
|
409
|
+
if (Array.isArray(audios) && audios.length > 0) kinds.push('audio');
|
|
313
410
|
if (Array.isArray(files) && files.length > 0) kinds.push('file');
|
|
314
411
|
return kinds;
|
|
315
412
|
}
|
|
@@ -409,6 +506,79 @@ function imagesFromContent(content) {
|
|
|
409
506
|
return images;
|
|
410
507
|
}
|
|
411
508
|
|
|
509
|
+
function audioMimeFromUrl(url) {
|
|
510
|
+
const dataMime = String(url || '').match(/^data:(audio\/[^;,]+)[;,]/i)?.[1];
|
|
511
|
+
if (dataMime) return dataMime.toLowerCase();
|
|
512
|
+
try {
|
|
513
|
+
return LOCAL_AUDIO_MIME_BY_EXT[path.extname(new URL(url).pathname).toLowerCase()] || '';
|
|
514
|
+
} catch {
|
|
515
|
+
return '';
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function audioNameFromPart(part, index) {
|
|
520
|
+
const explicit = compactInline(part?.name || part?.filename || '');
|
|
521
|
+
if (explicit) return explicit;
|
|
522
|
+
const target = String(part?.path || part?.url || '');
|
|
523
|
+
if (target && !/^data:/i.test(target)) {
|
|
524
|
+
try {
|
|
525
|
+
const basename = path.basename(new URL(target, 'file:///').pathname);
|
|
526
|
+
if (basename) return basename;
|
|
527
|
+
} catch {
|
|
528
|
+
const basename = path.basename(target);
|
|
529
|
+
if (basename) return basename;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return `Audio ${index + 1}`;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function audioFromLocalPath(filePath, index) {
|
|
536
|
+
const normalized = String(filePath || '').trim();
|
|
537
|
+
if (!normalized || normalized.includes('\0')) return null;
|
|
538
|
+
const mimeType = LOCAL_AUDIO_MIME_BY_EXT[path.extname(normalized).toLowerCase()];
|
|
539
|
+
if (!mimeType) return null;
|
|
540
|
+
try {
|
|
541
|
+
const stat = fs.statSync(normalized);
|
|
542
|
+
if (!stat.isFile() || stat.size <= 0 || stat.size > MAX_LOCAL_AUDIO_BYTES) return null;
|
|
543
|
+
return {
|
|
544
|
+
id: `local-audio-${index + 1}`,
|
|
545
|
+
url: `data:${mimeType};base64,${fs.readFileSync(normalized).toString('base64')}`,
|
|
546
|
+
mimeType,
|
|
547
|
+
name: path.basename(normalized) || `Audio ${index + 1}`,
|
|
548
|
+
};
|
|
549
|
+
} catch {
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function audiosFromContent(content) {
|
|
555
|
+
if (!Array.isArray(content)) return [];
|
|
556
|
+
const audios = [];
|
|
557
|
+
for (const part of content) {
|
|
558
|
+
if (!part || typeof part !== 'object') continue;
|
|
559
|
+
let audio = null;
|
|
560
|
+
if (part.type === 'localAudio') {
|
|
561
|
+
audio = audioFromLocalPath(part.path, audios.length);
|
|
562
|
+
} else if (part.type === 'audio') {
|
|
563
|
+
const url = String(part.url || '');
|
|
564
|
+
if (
|
|
565
|
+
url.length <= MAX_USER_AUDIO_URL_LENGTH
|
|
566
|
+
&& (/^data:audio\//i.test(url) || /^https?:\/\//i.test(url))
|
|
567
|
+
) {
|
|
568
|
+
audio = {
|
|
569
|
+
id: `audio-${audios.length + 1}`,
|
|
570
|
+
url,
|
|
571
|
+
mimeType: audioMimeFromUrl(url),
|
|
572
|
+
name: audioNameFromPart(part, audios.length),
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (audio && !audios.some(existing => existing.url === audio.url)) audios.push(audio);
|
|
577
|
+
if (audios.length >= MAX_USER_AUDIOS_PER_TURN) break;
|
|
578
|
+
}
|
|
579
|
+
return audios;
|
|
580
|
+
}
|
|
581
|
+
|
|
412
582
|
function inlineImageFromEntry(entry, index) {
|
|
413
583
|
if (typeof entry === 'string') {
|
|
414
584
|
return isDisplayableImageUrl(entry)
|
|
@@ -967,7 +1137,7 @@ function detailForPlanSteps(steps) {
|
|
|
967
1137
|
.join('\n');
|
|
968
1138
|
}
|
|
969
1139
|
|
|
970
|
-
function applyUserMessageToTurn(turn, { id = '', message = '', images = [] } = {}) {
|
|
1140
|
+
function applyUserMessageToTurn(turn, { id = '', message = '', images = [], audios = [] } = {}) {
|
|
971
1141
|
if (!turn) return;
|
|
972
1142
|
const subagentNotification = parseSubagentNotification(message);
|
|
973
1143
|
if (subagentNotification) {
|
|
@@ -981,6 +1151,7 @@ function applyUserMessageToTurn(turn, { id = '', message = '', images = [] } = {
|
|
|
981
1151
|
return;
|
|
982
1152
|
}
|
|
983
1153
|
const userImages = Array.isArray(images) ? images.filter(Boolean) : [];
|
|
1154
|
+
const userAudios = Array.isArray(audios) ? audios.filter(Boolean) : [];
|
|
984
1155
|
const pastedTranscript = extractReferencedPastedTranscriptContext(message);
|
|
985
1156
|
const approvalTranscript = extractCodexApprovalTranscriptContext(pastedTranscript.text);
|
|
986
1157
|
const userFiles = [
|
|
@@ -989,13 +1160,18 @@ function applyUserMessageToTurn(turn, { id = '', message = '', images = [] } = {
|
|
|
989
1160
|
...extractComposerFileAttachments(approvalTranscript.text).files,
|
|
990
1161
|
].slice(0, MAX_USER_FILES_PER_TURN);
|
|
991
1162
|
const text = visibleUserMessageText(approvalTranscript.text, {
|
|
992
|
-
renderedAttachmentKinds: renderedAttachmentKindsForAttachments({
|
|
1163
|
+
renderedAttachmentKinds: renderedAttachmentKindsForAttachments({
|
|
1164
|
+
images: userImages,
|
|
1165
|
+
audios: userAudios,
|
|
1166
|
+
files: userFiles,
|
|
1167
|
+
}),
|
|
993
1168
|
});
|
|
994
|
-
if (!text && userImages.length <= 0 && userFiles.length <= 0) return;
|
|
1169
|
+
if (!text && userImages.length <= 0 && userAudios.length <= 0 && userFiles.length <= 0) return;
|
|
995
1170
|
|
|
996
1171
|
if (!turn.userMessage) {
|
|
997
1172
|
if (text) turn.userMessage = text;
|
|
998
1173
|
if (userImages.length) turn.userImages = userImages;
|
|
1174
|
+
if (userAudios.length) turn.userAudios = userAudios;
|
|
999
1175
|
if (userFiles.length) turn.userFiles = userFiles;
|
|
1000
1176
|
return;
|
|
1001
1177
|
}
|
|
@@ -1004,6 +1180,7 @@ function applyUserMessageToTurn(turn, { id = '', message = '', images = [] } = {
|
|
|
1004
1180
|
text &&
|
|
1005
1181
|
turn.userMessage === text &&
|
|
1006
1182
|
userImages.length === 0 &&
|
|
1183
|
+
userAudios.length === 0 &&
|
|
1007
1184
|
userFiles.length === 0
|
|
1008
1185
|
) {
|
|
1009
1186
|
return;
|
|
@@ -1015,6 +1192,7 @@ function applyUserMessageToTurn(turn, { id = '', message = '', images = [] } = {
|
|
|
1015
1192
|
title: text || 'User added context',
|
|
1016
1193
|
detail: text,
|
|
1017
1194
|
images: userImages,
|
|
1195
|
+
audios: userAudios,
|
|
1018
1196
|
files: userFiles,
|
|
1019
1197
|
status: 'completed',
|
|
1020
1198
|
});
|
|
@@ -1025,10 +1203,12 @@ function appendTurnItem(turn, item, status = '') {
|
|
|
1025
1203
|
if (!type) return false;
|
|
1026
1204
|
if (type === 'usermessage') {
|
|
1027
1205
|
const images = imagesFromContent(item.content);
|
|
1206
|
+
const audios = audiosFromContent(item.content);
|
|
1028
1207
|
applyUserMessageToTurn(turn, {
|
|
1029
1208
|
id: item.id,
|
|
1030
1209
|
message: textFromUserInput(item.content),
|
|
1031
1210
|
images,
|
|
1211
|
+
audios,
|
|
1032
1212
|
});
|
|
1033
1213
|
return true;
|
|
1034
1214
|
}
|
|
@@ -1383,6 +1563,7 @@ function newTurn(id = '') {
|
|
|
1383
1563
|
id: id || `turn-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
1384
1564
|
userMessage: '',
|
|
1385
1565
|
userImages: [],
|
|
1566
|
+
userAudios: [],
|
|
1386
1567
|
userFiles: [],
|
|
1387
1568
|
finalMessage: '',
|
|
1388
1569
|
startedAt: null,
|
|
@@ -1399,6 +1580,7 @@ function newTurn(id = '') {
|
|
|
1399
1580
|
function isEmptyTurn(turn) {
|
|
1400
1581
|
return !turn.userMessage &&
|
|
1401
1582
|
(!Array.isArray(turn.userImages) || turn.userImages.length === 0) &&
|
|
1583
|
+
(!Array.isArray(turn.userAudios) || turn.userAudios.length === 0) &&
|
|
1402
1584
|
(!Array.isArray(turn.userFiles) || turn.userFiles.length === 0) &&
|
|
1403
1585
|
!turn.finalMessage &&
|
|
1404
1586
|
turn.processItems.length === 0;
|
|
@@ -1444,7 +1626,13 @@ function sanitizeProcessItemForOutput(item) {
|
|
|
1444
1626
|
if (!item || typeof item !== 'object') return null;
|
|
1445
1627
|
const title = stripCodexInternalContextBlocks(item.title);
|
|
1446
1628
|
const detail = stripCodexInternalContextBlocks(item.detail);
|
|
1447
|
-
if (
|
|
1629
|
+
if (
|
|
1630
|
+
!title
|
|
1631
|
+
&& !detail
|
|
1632
|
+
&& (!Array.isArray(item.images) || item.images.length <= 0)
|
|
1633
|
+
&& (!Array.isArray(item.audios) || item.audios.length <= 0)
|
|
1634
|
+
&& (!Array.isArray(item.files) || item.files.length <= 0)
|
|
1635
|
+
) {
|
|
1448
1636
|
return null;
|
|
1449
1637
|
}
|
|
1450
1638
|
return {
|
|
@@ -2276,10 +2464,11 @@ function isUserMessageLine(line) {
|
|
|
2276
2464
|
if (turnItemType(payload.item) !== 'usermessage') return false;
|
|
2277
2465
|
const text = textFromUserInput(payload.item?.content);
|
|
2278
2466
|
const images = imagesFromContent(payload.item?.content);
|
|
2467
|
+
const audios = audiosFromContent(payload.item?.content);
|
|
2279
2468
|
const files = extractComposerFileAttachments(text).files;
|
|
2280
2469
|
return Boolean(visibleUserMessageText(text, {
|
|
2281
|
-
renderedAttachmentKinds: renderedAttachmentKindsForAttachments({ images, files }),
|
|
2282
|
-
}) || images.length > 0 || files.length > 0);
|
|
2470
|
+
renderedAttachmentKinds: renderedAttachmentKindsForAttachments({ images, audios, files }),
|
|
2471
|
+
}) || images.length > 0 || audios.length > 0 || files.length > 0);
|
|
2283
2472
|
}
|
|
2284
2473
|
return false;
|
|
2285
2474
|
} catch {
|
|
@@ -2323,6 +2512,36 @@ async function readCodexTranscript(sessionId, options = {}) {
|
|
|
2323
2512
|
};
|
|
2324
2513
|
}
|
|
2325
2514
|
|
|
2515
|
+
async function readCodexHistoryImageData(sessionId, options = {}) {
|
|
2516
|
+
const normalizedSessionId = String(sessionId || '').trim();
|
|
2517
|
+
if (!normalizedSessionId) return new Map();
|
|
2518
|
+
const filePath = findCodexRolloutFile(normalizedSessionId, {
|
|
2519
|
+
codexHome: options.codexHome || path.join(os.homedir(), '.codex'),
|
|
2520
|
+
});
|
|
2521
|
+
if (!filePath) return new Map();
|
|
2522
|
+
const tail = await readTailLines(filePath, options.maxReadBytes);
|
|
2523
|
+
const imageDataByPath = new Map();
|
|
2524
|
+
for (const line of tail.lines) {
|
|
2525
|
+
if (!line.trim()) continue;
|
|
2526
|
+
try {
|
|
2527
|
+
const event = JSON.parse(line);
|
|
2528
|
+
const { type: eventType, payload } = normalizeEventEnvelope(event);
|
|
2529
|
+
if (!payload) continue;
|
|
2530
|
+
if (eventType === 'response_item' && payload.type === 'message' && payload.role === 'user') {
|
|
2531
|
+
appendHistoryImageDataFromContent(imageDataByPath, payload.content);
|
|
2532
|
+
continue;
|
|
2533
|
+
}
|
|
2534
|
+
if (['item/started', 'item/completed', 'item.started', 'item.completed'].includes(eventType)) {
|
|
2535
|
+
const item = payload.item;
|
|
2536
|
+
if (turnItemType(item) === 'usermessage') appendHistoryImageDataFromContent(imageDataByPath, item?.content);
|
|
2537
|
+
}
|
|
2538
|
+
} catch {
|
|
2539
|
+
// Ignore malformed or partial tail records; history replay remains usable without image recovery.
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
return imageDataByPath;
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2326
2545
|
function buildTranscriptFromEvents(events, options = {}) {
|
|
2327
2546
|
const lines = Array.isArray(events)
|
|
2328
2547
|
? events.filter(event => event && typeof event === 'object').map(event => JSON.stringify(event))
|
|
@@ -2334,8 +2553,11 @@ module.exports = {
|
|
|
2334
2553
|
DEFAULT_MAX_TURNS,
|
|
2335
2554
|
buildTranscriptFromEvents,
|
|
2336
2555
|
buildTranscriptFromLines,
|
|
2556
|
+
codexHistoryImageTargets,
|
|
2337
2557
|
dropLeadingPartialTurn,
|
|
2558
|
+
readCodexHistoryImageData,
|
|
2338
2559
|
readCodexTranscript,
|
|
2339
2560
|
stripUserMessagePrefix,
|
|
2340
2561
|
textFromContent,
|
|
2562
|
+
visibleUserMessageText,
|
|
2341
2563
|
};
|
|
@@ -72,6 +72,8 @@ const PERSISTED_SETTING_KEYS = new Set([
|
|
|
72
72
|
'workspace',
|
|
73
73
|
'lastMainWorkspace',
|
|
74
74
|
'workspaceHistory',
|
|
75
|
+
'projectWorkspaces',
|
|
76
|
+
'pinnedProjectWorkspaces',
|
|
75
77
|
'projectNames',
|
|
76
78
|
'theme',
|
|
77
79
|
'appearance',
|
|
@@ -196,6 +198,31 @@ class ConfigManager {
|
|
|
196
198
|
return result.slice(0, 5);
|
|
197
199
|
}
|
|
198
200
|
|
|
201
|
+
normalizeProjectWorkspaces(projects) {
|
|
202
|
+
const entries = Array.isArray(projects) ? projects : [];
|
|
203
|
+
const result = [];
|
|
204
|
+
const seen = new Set();
|
|
205
|
+
|
|
206
|
+
for (const entry of entries) {
|
|
207
|
+
const expanded = this.expandWorkspacePath(entry);
|
|
208
|
+
if (!expanded) continue;
|
|
209
|
+
const resolved = path.resolve(expanded);
|
|
210
|
+
if (resolved === path.parse(resolved).root || this.isInternalWorkspace(resolved)) continue;
|
|
211
|
+
let canonical;
|
|
212
|
+
try {
|
|
213
|
+
canonical = fs.realpathSync(resolved);
|
|
214
|
+
if (!fs.statSync(canonical).isDirectory()) continue;
|
|
215
|
+
} catch {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (seen.has(canonical)) continue;
|
|
219
|
+
seen.add(canonical);
|
|
220
|
+
result.push(canonical);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return result.slice(0, 200);
|
|
224
|
+
}
|
|
225
|
+
|
|
199
226
|
normalizeMainPageSessionKeys(keys) {
|
|
200
227
|
const entries = Array.isArray(keys) ? keys : [];
|
|
201
228
|
const result = [];
|
|
@@ -260,6 +287,8 @@ class ConfigManager {
|
|
|
260
287
|
workspace: this.farmingDir,
|
|
261
288
|
lastMainWorkspace: this.farmingDir,
|
|
262
289
|
workspaceHistory: [],
|
|
290
|
+
projectWorkspaces: [],
|
|
291
|
+
pinnedProjectWorkspaces: [],
|
|
263
292
|
projectNames: {},
|
|
264
293
|
theme: 'terminal',
|
|
265
294
|
appearance: 'light',
|
|
@@ -294,6 +323,8 @@ class ConfigManager {
|
|
|
294
323
|
workspace: this.farmingDir,
|
|
295
324
|
lastMainWorkspace: this.farmingDir,
|
|
296
325
|
workspaceHistory: [],
|
|
326
|
+
projectWorkspaces: [],
|
|
327
|
+
pinnedProjectWorkspaces: [],
|
|
297
328
|
projectNames: {},
|
|
298
329
|
theme: 'terminal',
|
|
299
330
|
appearance: 'light',
|
|
@@ -342,6 +373,8 @@ class ConfigManager {
|
|
|
342
373
|
this.settings.workspace = this.farmingDir;
|
|
343
374
|
this.settings.lastMainWorkspace = this.normalizeMainWorkspace(this.settings.lastMainWorkspace, this.farmingDir);
|
|
344
375
|
this.settings.workspaceHistory = this.normalizeWorkspaceHistory(this.settings.workspaceHistory);
|
|
376
|
+
this.settings.projectWorkspaces = this.normalizeProjectWorkspaces(this.settings.projectWorkspaces);
|
|
377
|
+
this.settings.pinnedProjectWorkspaces = this.normalizeProjectWorkspaces(this.settings.pinnedProjectWorkspaces);
|
|
345
378
|
this.settings.projectNames = this.normalizeProjectNames(this.settings.projectNames);
|
|
346
379
|
this.settings.agentHomes = this.normalizeAgentHomes(this.settings.agentHomes);
|
|
347
380
|
if (this.settings.updateUrl === LEGACY_DEFAULT_UPDATE_URL || this.settings.updateUrl === API_DEFAULT_UPDATE_URL) {
|
|
@@ -783,6 +816,8 @@ class ConfigManager {
|
|
|
783
816
|
};
|
|
784
817
|
this.settings.lastMainWorkspace = this.normalizeMainWorkspace(this.settings.lastMainWorkspace, previousMainWorkspace);
|
|
785
818
|
this.settings.workspaceHistory = this.normalizeWorkspaceHistory(this.settings.workspaceHistory);
|
|
819
|
+
this.settings.projectWorkspaces = this.normalizeProjectWorkspaces(this.settings.projectWorkspaces);
|
|
820
|
+
this.settings.pinnedProjectWorkspaces = this.normalizeProjectWorkspaces(this.settings.pinnedProjectWorkspaces);
|
|
786
821
|
this.settings.projectNames = this.normalizeProjectNames(this.settings.projectNames);
|
|
787
822
|
this.settings.agentHomes = this.normalizeAgentHomes(this.settings.agentHomes);
|
|
788
823
|
this.settings.updateUrl = this.normalizeUpdateUrl(this.settings.updateUrl);
|