farming-code 2.2.28 → 2.2.30
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 +27 -90
- package/README.zh_cn.md +17 -90
- package/THIRD_PARTY_NOTICES.md +25 -6
- package/backend/acp/packaged-claude-acp.js +13 -0
- package/backend/acp/packaged-codex-acp.js +2 -2
- package/backend/acp-runtime.js +38 -17
- package/backend/acp-session-state.js +78 -88
- package/backend/acp-transcript.js +174 -9
- package/backend/agent-manager.js +27 -2
- package/backend/agent-session-history.js +45 -12
- package/backend/codex-session-history.js +1 -1
- package/backend/codex-transcript.js +0 -202
- package/backend/config-manager.js +44 -16
- package/backend/data/LICENSE.agent-browser +201 -0
- package/backend/data/runtime-dependency-manifest.json +199 -0
- package/backend/data/runtime-dependency-sources.json +5 -0
- package/backend/executable-discovery.js +7 -0
- package/backend/farming-agent-bootstrap.zh_cn.md +4 -2
- package/backend/farming-app-cli.js +161 -65
- package/backend/farming-cli.js +7 -59
- package/backend/index-html.js +31 -0
- package/backend/main-agent-skills.js +7 -28
- package/backend/npm-update-helper.js +43 -6
- package/backend/provider-adapters.js +10 -0
- package/backend/runtime-dependency-manager.js +759 -0
- package/backend/runtime-executable-invocation.js +20 -0
- package/backend/server-process-identity.js +47 -0
- package/backend/server.js +145 -204
- package/backend/slash-command-discovery.js +138 -1
- package/backend/storage-layout.js +30 -0
- package/backend/update-service.js +60 -914
- package/config/farming.deploy.env.example +1 -1
- package/config/farming.install.env.example +5 -1
- package/dist/acp/LICENSE.claude-agent-acp +191 -0
- package/dist/acp/LICENSE.claude-agent-sdk +1 -0
- package/dist/acp/claude-agent-acp-0.59.0.mjs +41606 -0
- package/dist/acp/{codex-acp-1.1.4.js → codex-acp-1.1.4.mjs} +387 -7
- package/dist/assets/{App-CM7Cy-l4.css → App-BKNUILuL.css} +1 -1
- package/dist/assets/App-Z1N7T0eM.js +695 -0
- package/dist/assets/{FileEditorMarkdownPreview-DYOMm-b1.js → FileEditorMarkdownPreview-Cs9cLv8a.js} +1 -1
- package/dist/assets/FileEditorPane-CNtuGjQ3.js +2 -0
- package/dist/assets/IconGlyphs-BClgag6E.js +1 -0
- package/dist/assets/ProjectFilesSection-K3GHuQ4U.js +12 -0
- package/dist/assets/ReviewPage-BRuxFaH5.js +3 -0
- package/dist/assets/code-dark-kLsQLvpf.css +1 -0
- package/dist/assets/{index-BzQO3pUr.js → index-D-zetSol.js} +2 -2
- package/dist/assets/main-DUTz-Rd2.css +1 -0
- package/dist/assets/{workspace-editor-monaco-tXA-udGx.js → workspace-editor-monaco-CJNwbpCy.js} +1 -1
- package/dist/assets/workspace-editor-monaco-CO9w8YV0.js +1 -0
- package/dist/assets/workspace-view-state-BjKPYp0E.js +7 -0
- package/dist/index.html +33 -3
- package/extensions/browser/backend/agent-browser-runtime.js +1041 -0
- package/extensions/browser/backend/browser-mcp-server.js +352 -0
- package/extensions/browser/backend/browser-resource-manager.js +921 -114
- package/extensions/browser/backend/browser-resource-store.js +47 -6
- package/extensions/browser/backend/browser-router.js +15 -2
- package/extensions/browser/backend/executable-discovery.js +164 -17
- package/extensions/browser/backend/managed-chromium-installer.js +593 -0
- package/extensions/browser/bin/farming-browser +384 -30
- package/extensions/browser/frontend/BrowserSidebarPortals.tsx +17 -19
- package/extensions/browser/frontend/BrowserViewer.tsx +217 -46
- package/extensions/browser/frontend/browser.css +68 -17
- package/extensions/browser/frontend/types.ts +13 -0
- package/extensions/browser/frontend/useBrowserResources.ts +18 -3
- package/index.html +32 -2
- package/package.json +10 -6
- package/shared/backend-connection-status.d.ts +12 -0
- package/shared/backend-connection-status.js +35 -4
- package/backend/agent-activity.js +0 -35
- package/backend/agent-memory-report.js +0 -583
- package/backend/bundle-update-helper.js +0 -113
- package/backend/codex-rollout-follower.js +0 -326
- package/dist/assets/App-CPG4WcjO.js +0 -658
- package/dist/assets/FileEditorPane-DXXq82aM.js +0 -2
- package/dist/assets/IconGlyphs-DrdPjYXr.js +0 -1
- package/dist/assets/ProjectFilesSection-CP66XVPl.js +0 -12
- package/dist/assets/ReviewPage-w21MEPD6.js +0 -3
- package/dist/assets/code-dark-BDjwj7Um.css +0 -1
- package/dist/assets/main-BlVUXfxa.css +0 -1
- package/dist/assets/workspace-editor-monaco-CFEfFNcA.js +0 -1
- package/dist/assets/workspace-view-state-BS_V8JVs.js +0 -7
- package/extensions/browser/backend/cdp-browser-runtime.js +0 -648
- package/extensions/browser/backend/cdp-client.js +0 -167
|
@@ -105,6 +105,18 @@ function compareAgentSessions(a, b) {
|
|
|
105
105
|
return timeDelta || agentSessionIdentity(a).localeCompare(agentSessionIdentity(b));
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
function dedupeAgentSessions(sessions) {
|
|
109
|
+
const sessionsByIdentity = new Map();
|
|
110
|
+
for (const session of sessions) {
|
|
111
|
+
const identity = agentSessionIdentity(session);
|
|
112
|
+
const current = sessionsByIdentity.get(identity);
|
|
113
|
+
if (!current || timestampMs(session?.updatedAt || session?.createdAt) > timestampMs(current?.updatedAt || current?.createdAt)) {
|
|
114
|
+
sessionsByIdentity.set(identity, session);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return Array.from(sessionsByIdentity.values());
|
|
118
|
+
}
|
|
119
|
+
|
|
108
120
|
function encodeAgentSessionCursor(session) {
|
|
109
121
|
if (!session?.provider || !session?.id) return '';
|
|
110
122
|
return Buffer.from(JSON.stringify({
|
|
@@ -140,7 +152,10 @@ function paginateAgentSessions(sessions, options = {}) {
|
|
|
140
152
|
if (cursorValue) {
|
|
141
153
|
const cursor = decodeAgentSessionCursor(cursorValue);
|
|
142
154
|
if (!cursor) return { sessions: [], nextCursor: '', hasMore: false, invalidCursor: true };
|
|
143
|
-
const exactIndex = sessions.findIndex(session =>
|
|
155
|
+
const exactIndex = sessions.findIndex(session => (
|
|
156
|
+
agentSessionIdentity(session) === agentSessionIdentity(cursor)
|
|
157
|
+
&& String(session.updatedAt || session.createdAt || '') === cursor.updatedAt
|
|
158
|
+
));
|
|
144
159
|
if (exactIndex >= 0) {
|
|
145
160
|
start = exactIndex + 1;
|
|
146
161
|
} else {
|
|
@@ -164,7 +179,7 @@ function searchAgentSessions(sessions, query, options = {}) {
|
|
|
164
179
|
? Math.max(1, Math.min(MAX_AGENT_SESSION_HISTORY_LIMIT, Math.floor(options.limit)))
|
|
165
180
|
: DEFAULT_LIMIT;
|
|
166
181
|
if (!normalizedQuery) {
|
|
167
|
-
return { sessions: [], total: 0, query: '', scope: 'title-project' };
|
|
182
|
+
return { sessions: [], total: 0, query: '', scope: 'id-title-project' };
|
|
168
183
|
}
|
|
169
184
|
|
|
170
185
|
const projectNames = options.projectNames && typeof options.projectNames === 'object'
|
|
@@ -173,6 +188,7 @@ function searchAgentSessions(sessions, query, options = {}) {
|
|
|
173
188
|
const matches = sessions.filter(session => {
|
|
174
189
|
const projectPaths = [session?.workspace, session?.cwd];
|
|
175
190
|
return [
|
|
191
|
+
session?.id,
|
|
176
192
|
session?.title,
|
|
177
193
|
...projectPaths,
|
|
178
194
|
...projectPaths.map(workspace => projectNames[String(workspace || '')]),
|
|
@@ -183,7 +199,7 @@ function searchAgentSessions(sessions, query, options = {}) {
|
|
|
183
199
|
sessions: matches.slice(0, limit),
|
|
184
200
|
total: matches.length,
|
|
185
201
|
query: normalizedQuery,
|
|
186
|
-
scope: 'title-project',
|
|
202
|
+
scope: 'id-title-project',
|
|
187
203
|
};
|
|
188
204
|
}
|
|
189
205
|
|
|
@@ -340,7 +356,13 @@ function scheduleFromClaudeEvent(event, sessionId) {
|
|
|
340
356
|
return null;
|
|
341
357
|
}
|
|
342
358
|
|
|
343
|
-
async function collectRecentFiles(
|
|
359
|
+
async function collectRecentFiles(
|
|
360
|
+
root,
|
|
361
|
+
extension,
|
|
362
|
+
limit,
|
|
363
|
+
acceptFile = () => true,
|
|
364
|
+
descendDirectory = () => true
|
|
365
|
+
) {
|
|
344
366
|
const directories = [root];
|
|
345
367
|
let files = [];
|
|
346
368
|
let visitedDirectories = 0;
|
|
@@ -362,7 +384,7 @@ async function collectRecentFiles(root, extension, limit, acceptFile = () => tru
|
|
|
362
384
|
for (const entry of entries) {
|
|
363
385
|
const fullPath = path.join(directory, entry.name);
|
|
364
386
|
if (entry.isDirectory()) {
|
|
365
|
-
directories.push(fullPath);
|
|
387
|
+
if (descendDirectory(fullPath)) directories.push(fullPath);
|
|
366
388
|
} else if (entry.isFile() && entry.name.endsWith(extension) && acceptFile(fullPath)) {
|
|
367
389
|
let mtimeMs = 0;
|
|
368
390
|
try {
|
|
@@ -508,7 +530,17 @@ async function listClaudeSessions(options = {}) {
|
|
|
508
530
|
? Math.max(limit, Math.min(MAX_AGENT_SESSION_SCAN_LIMIT, Math.floor(options.scanLimit)))
|
|
509
531
|
: DEFAULT_SCAN_LIMIT;
|
|
510
532
|
const promptHistory = await readClaudePromptHistory(claudeHome);
|
|
511
|
-
const
|
|
533
|
+
const projectsRoot = path.join(claudeHome, 'projects');
|
|
534
|
+
// Claude child-agent transcripts live below <project>/<session-id>/subagents
|
|
535
|
+
// and inherit the parent session id. Only direct project session files are
|
|
536
|
+
// resumable roots and should count as History entries.
|
|
537
|
+
const sessionFiles = await collectRecentFiles(
|
|
538
|
+
projectsRoot,
|
|
539
|
+
'.jsonl',
|
|
540
|
+
scanLimit,
|
|
541
|
+
filePath => path.relative(projectsRoot, filePath).split(path.sep).length === 2,
|
|
542
|
+
directoryPath => path.relative(projectsRoot, directoryPath).split(path.sep).length === 1
|
|
543
|
+
);
|
|
512
544
|
|
|
513
545
|
const sessions = [];
|
|
514
546
|
for (const { filePath, mtimeMs } of sessionFiles) {
|
|
@@ -542,7 +574,7 @@ async function listClaudeSessions(options = {}) {
|
|
|
542
574
|
}
|
|
543
575
|
}
|
|
544
576
|
|
|
545
|
-
return sessions
|
|
577
|
+
return dedupeAgentSessions(sessions)
|
|
546
578
|
.sort((a, b) => timestampMs(b.updatedAt) - timestampMs(a.updatedAt))
|
|
547
579
|
.slice(0, limit);
|
|
548
580
|
}
|
|
@@ -674,7 +706,8 @@ async function listQoderSessions(options = {}) {
|
|
|
674
706
|
projectsRoot,
|
|
675
707
|
'.jsonl',
|
|
676
708
|
scanLimit,
|
|
677
|
-
filePath => path.relative(projectsRoot, filePath).split(path.sep).length === 2
|
|
709
|
+
filePath => path.relative(projectsRoot, filePath).split(path.sep).length === 2,
|
|
710
|
+
directoryPath => path.relative(projectsRoot, directoryPath).split(path.sep).length === 1
|
|
678
711
|
);
|
|
679
712
|
|
|
680
713
|
const sessions = [];
|
|
@@ -709,7 +742,7 @@ async function listQoderSessions(options = {}) {
|
|
|
709
742
|
}
|
|
710
743
|
}
|
|
711
744
|
|
|
712
|
-
return sessions
|
|
745
|
+
return dedupeAgentSessions(sessions)
|
|
713
746
|
.sort((a, b) => timestampMs(b.updatedAt) - timestampMs(a.updatedAt))
|
|
714
747
|
.slice(0, limit);
|
|
715
748
|
}
|
|
@@ -755,7 +788,7 @@ async function listOpenCodeSessions(options = {}) {
|
|
|
755
788
|
}
|
|
756
789
|
if (!Array.isArray(rawSessions)) return [];
|
|
757
790
|
|
|
758
|
-
return rawSessions
|
|
791
|
+
return dedupeAgentSessions(rawSessions
|
|
759
792
|
.map(raw => {
|
|
760
793
|
const id = firstTrimmedString(raw?.id);
|
|
761
794
|
const cwd = normalizePathValue(raw?.directory || '');
|
|
@@ -777,7 +810,7 @@ async function listOpenCodeSessions(options = {}) {
|
|
|
777
810
|
capabilities: ['resume', 'fork'],
|
|
778
811
|
};
|
|
779
812
|
})
|
|
780
|
-
.filter(session => session && isVisibleAgentSession(session))
|
|
813
|
+
.filter(session => session && isVisibleAgentSession(session)))
|
|
781
814
|
.sort((a, b) => timestampMs(b.updatedAt) - timestampMs(a.updatedAt))
|
|
782
815
|
.slice(0, limit);
|
|
783
816
|
}
|
|
@@ -912,7 +945,7 @@ async function listAgentSessions(options = {}) {
|
|
|
912
945
|
})));
|
|
913
946
|
}
|
|
914
947
|
|
|
915
|
-
return sessions
|
|
948
|
+
return dedupeAgentSessions(sessions)
|
|
916
949
|
.sort(compareAgentSessions)
|
|
917
950
|
.slice(0, limit);
|
|
918
951
|
}
|
|
@@ -6,7 +6,7 @@ const { stripCodexInternalContextBlocks } = require('./codex-transcript-sanitize
|
|
|
6
6
|
|
|
7
7
|
const DEFAULT_LIMIT = 40;
|
|
8
8
|
const DEFAULT_SCAN_LIMIT = 400;
|
|
9
|
-
const MAX_SESSION_HISTORY_LIMIT =
|
|
9
|
+
const MAX_SESSION_HISTORY_LIMIT = 5000;
|
|
10
10
|
const MAX_SESSION_HISTORY_SCAN_LIMIT = 5000;
|
|
11
11
|
const MAX_SCAN_DIRECTORIES = 2000;
|
|
12
12
|
const SESSION_INDEX_TAIL_BYTES = 4 * 1024 * 1024;
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
-
const fsp = require('fs/promises');
|
|
3
|
-
const os = require('os');
|
|
4
2
|
const path = require('path');
|
|
5
|
-
const { fileURLToPath } = require('url');
|
|
6
|
-
const { findCodexRolloutFile } = require('./codex-rollout-follower');
|
|
7
3
|
const {
|
|
8
4
|
heartbeatAssistantMessage,
|
|
9
5
|
heartbeatUserMessage,
|
|
@@ -11,10 +7,8 @@ const {
|
|
|
11
7
|
stripCodexInternalContextBlocks,
|
|
12
8
|
} = require('./codex-transcript-sanitizer');
|
|
13
9
|
|
|
14
|
-
// @deprecated `readCodexTranscript` below is the legacy JSONL file reader.
|
|
15
10
|
// The generic event-to-turn projection remains for JSON compatibility feeds;
|
|
16
11
|
// live structured Chat is reduced from ACP session updates instead.
|
|
17
|
-
const DEFAULT_MAX_READ_BYTES = 32 * 1024 * 1024;
|
|
18
12
|
const DEFAULT_MAX_TURNS = 240;
|
|
19
13
|
const USER_MESSAGE_BEGIN = '## My request for Codex:';
|
|
20
14
|
const MAX_USER_IMAGES_PER_TURN = 6;
|
|
@@ -308,71 +302,6 @@ function stripRenderedComposerAttachmentBlocks(value, renderedKinds) {
|
|
|
308
302
|
.trim();
|
|
309
303
|
}
|
|
310
304
|
|
|
311
|
-
function codexHistoryImageTargets(value) {
|
|
312
|
-
const text = String(value || '');
|
|
313
|
-
const targets = [];
|
|
314
|
-
let match;
|
|
315
|
-
CODEX_HISTORY_IMAGE_LINK_PATTERN.lastIndex = 0;
|
|
316
|
-
while ((match = CODEX_HISTORY_IMAGE_LINK_PATTERN.exec(text))) {
|
|
317
|
-
const target = String(match[1] || '').trim();
|
|
318
|
-
if (!target || targets.includes(target)) continue;
|
|
319
|
-
targets.push(target);
|
|
320
|
-
if (targets.length >= MAX_USER_IMAGES_PER_TURN) break;
|
|
321
|
-
}
|
|
322
|
-
return targets;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function localImagePathFromTarget(value) {
|
|
326
|
-
const target = String(value || '').trim();
|
|
327
|
-
if (!target || target.includes('\0') || /^data:/i.test(target)) return '';
|
|
328
|
-
if (/^file:\/\//i.test(target)) {
|
|
329
|
-
try {
|
|
330
|
-
return fileURLToPath(target);
|
|
331
|
-
} catch {
|
|
332
|
-
return '';
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
return path.isAbsolute(target) ? target : '';
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
function localImagePathsFromUserContent(content) {
|
|
339
|
-
const paths = [];
|
|
340
|
-
const append = (value) => {
|
|
341
|
-
const filePath = localImagePathFromTarget(value);
|
|
342
|
-
if (!filePath || !LOCAL_IMAGE_MIME_BY_EXT[path.extname(filePath).toLowerCase()] || paths.includes(filePath)) return;
|
|
343
|
-
paths.push(filePath);
|
|
344
|
-
};
|
|
345
|
-
for (const part of Array.isArray(content) ? content : []) {
|
|
346
|
-
if (!part || typeof part !== 'object') continue;
|
|
347
|
-
if (['local_image', 'localImage'].includes(part.type)) append(part.path || part.file || part.url);
|
|
348
|
-
const text = typeof part.text === 'string' ? part.text : '';
|
|
349
|
-
for (const match of text.matchAll(/<image\b[^>]*\bpath=(['"])(.*?)\1/gi)) append(match[2]);
|
|
350
|
-
for (const match of text.matchAll(/^##\s+[^\n:]+:\s*(.+)$/gim)) append(match[1]);
|
|
351
|
-
}
|
|
352
|
-
return paths.slice(0, MAX_USER_IMAGES_PER_TURN);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function dataImageUrlsFromUserContent(content) {
|
|
356
|
-
const urls = [];
|
|
357
|
-
for (const part of Array.isArray(content) ? content : []) {
|
|
358
|
-
if (!part || typeof part !== 'object' || !['input_image', 'inputImage', 'image'].includes(part.type)) continue;
|
|
359
|
-
const value = String(part.image_url || part.imageUrl || part.url || part.data || '');
|
|
360
|
-
if (!/^data:image\/(?:gif|jpe?g|png|svg\+xml|webp);base64,/i.test(value)) continue;
|
|
361
|
-
if (value.length > MAX_USER_IMAGE_URL_LENGTH || urls.includes(value)) continue;
|
|
362
|
-
urls.push(value);
|
|
363
|
-
if (urls.length >= MAX_USER_IMAGES_PER_TURN) break;
|
|
364
|
-
}
|
|
365
|
-
return urls;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function appendHistoryImageDataFromContent(imageDataByPath, content) {
|
|
369
|
-
const paths = localImagePathsFromUserContent(content);
|
|
370
|
-
const urls = dataImageUrlsFromUserContent(content);
|
|
371
|
-
for (let index = 0; index < Math.min(paths.length, urls.length); index += 1) {
|
|
372
|
-
if (!imageDataByPath.has(paths[index])) imageDataByPath.set(paths[index], urls[index]);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
305
|
function stripRenderedCodexHistoryAttachmentLinks(value, renderedKinds) {
|
|
377
306
|
const text = normalizeText(value);
|
|
378
307
|
if (!text || !renderedKinds.has('image')) return text;
|
|
@@ -2416,133 +2345,6 @@ function buildTranscriptFromLines(lines, options = {}) {
|
|
|
2416
2345
|
.slice(-maxTurns);
|
|
2417
2346
|
}
|
|
2418
2347
|
|
|
2419
|
-
async function readTailLines(filePath, maxReadBytes = DEFAULT_MAX_READ_BYTES) {
|
|
2420
|
-
const stat = await fsp.stat(filePath);
|
|
2421
|
-
const start = Math.max(0, stat.size - maxReadBytes);
|
|
2422
|
-
const handle = await fsp.open(filePath, 'r');
|
|
2423
|
-
try {
|
|
2424
|
-
const buffer = Buffer.alloc(stat.size - start);
|
|
2425
|
-
const result = await handle.read(buffer, 0, buffer.length, start);
|
|
2426
|
-
let text = buffer.subarray(0, result.bytesRead).toString('utf8');
|
|
2427
|
-
if (start > 0) {
|
|
2428
|
-
const firstNewline = text.indexOf('\n');
|
|
2429
|
-
text = firstNewline >= 0 ? text.slice(firstNewline + 1) : '';
|
|
2430
|
-
}
|
|
2431
|
-
return {
|
|
2432
|
-
truncated: start > 0,
|
|
2433
|
-
lines: text.split('\n'),
|
|
2434
|
-
};
|
|
2435
|
-
} finally {
|
|
2436
|
-
await handle.close().catch(() => {});
|
|
2437
|
-
}
|
|
2438
|
-
}
|
|
2439
|
-
|
|
2440
|
-
function isUserMessageLine(line) {
|
|
2441
|
-
if (!line.trim()) return false;
|
|
2442
|
-
try {
|
|
2443
|
-
const event = JSON.parse(line);
|
|
2444
|
-
const { type: eventType, payload } = normalizeEventEnvelope(event);
|
|
2445
|
-
if (!payload) return false;
|
|
2446
|
-
if (eventType === 'event_msg') {
|
|
2447
|
-
if (payload.type !== 'user_message') return false;
|
|
2448
|
-
const text = textFromUserMessagePayload(payload);
|
|
2449
|
-
const images = imagesFromUserMessagePayload(payload);
|
|
2450
|
-
const files = extractComposerFileAttachments(text).files;
|
|
2451
|
-
return Boolean(visibleUserMessageText(text, {
|
|
2452
|
-
renderedAttachmentKinds: renderedAttachmentKindsForAttachments({ images, files }),
|
|
2453
|
-
}) || images.length > 0 || files.length > 0);
|
|
2454
|
-
}
|
|
2455
|
-
if (eventType === 'response_item') {
|
|
2456
|
-
if (payload.type !== 'message' || payload.role !== 'user') return false;
|
|
2457
|
-
const text = textFromContent(payload.content);
|
|
2458
|
-
const images = imagesFromContent(payload.content);
|
|
2459
|
-
const files = extractComposerFileAttachments(text).files;
|
|
2460
|
-
return Boolean(visibleUserMessageText(text, {
|
|
2461
|
-
renderedAttachmentKinds: renderedAttachmentKindsForAttachments({ images, files }),
|
|
2462
|
-
}) || images.length > 0 || files.length > 0);
|
|
2463
|
-
}
|
|
2464
|
-
if (eventType === 'item/started' || eventType === 'item/completed' || eventType === 'item.started' || eventType === 'item.completed') {
|
|
2465
|
-
if (turnItemType(payload.item) !== 'usermessage') return false;
|
|
2466
|
-
const text = textFromUserInput(payload.item?.content);
|
|
2467
|
-
const images = imagesFromContent(payload.item?.content);
|
|
2468
|
-
const audios = audiosFromContent(payload.item?.content);
|
|
2469
|
-
const files = extractComposerFileAttachments(text).files;
|
|
2470
|
-
return Boolean(visibleUserMessageText(text, {
|
|
2471
|
-
renderedAttachmentKinds: renderedAttachmentKindsForAttachments({ images, audios, files }),
|
|
2472
|
-
}) || images.length > 0 || audios.length > 0 || files.length > 0);
|
|
2473
|
-
}
|
|
2474
|
-
return false;
|
|
2475
|
-
} catch {
|
|
2476
|
-
return false;
|
|
2477
|
-
}
|
|
2478
|
-
}
|
|
2479
|
-
|
|
2480
|
-
function dropLeadingPartialTurn(lines) {
|
|
2481
|
-
const firstUserIndex = lines.findIndex(isUserMessageLine);
|
|
2482
|
-
return firstUserIndex > 0 ? lines.slice(firstUserIndex) : lines;
|
|
2483
|
-
}
|
|
2484
|
-
|
|
2485
|
-
async function readCodexTranscript(sessionId, options = {}) {
|
|
2486
|
-
const normalizedSessionId = String(sessionId || '').trim();
|
|
2487
|
-
if (!normalizedSessionId) {
|
|
2488
|
-
return { available: false, reason: 'missing-session-id', sessionId: '', turns: [] };
|
|
2489
|
-
}
|
|
2490
|
-
|
|
2491
|
-
const filePath = findCodexRolloutFile(normalizedSessionId, {
|
|
2492
|
-
codexHome: options.codexHome || path.join(os.homedir(), '.codex'),
|
|
2493
|
-
});
|
|
2494
|
-
if (!filePath) {
|
|
2495
|
-
return { available: false, reason: 'history-not-found', sessionId: normalizedSessionId, turns: [] };
|
|
2496
|
-
}
|
|
2497
|
-
|
|
2498
|
-
const tail = await readTailLines(filePath, options.maxReadBytes);
|
|
2499
|
-
const lines = tail.truncated ? dropLeadingPartialTurn(tail.lines) : tail.lines;
|
|
2500
|
-
const stat = fs.statSync(filePath);
|
|
2501
|
-
const maxTurns = Number.isFinite(options.maxTurns) ? Math.max(1, Math.floor(options.maxTurns)) : DEFAULT_MAX_TURNS;
|
|
2502
|
-
const allTurns = buildTranscriptFromLines(lines, { maxTurns: Number.MAX_SAFE_INTEGER });
|
|
2503
|
-
const turns = allTurns.slice(-maxTurns);
|
|
2504
|
-
return {
|
|
2505
|
-
available: true,
|
|
2506
|
-
sessionId: normalizedSessionId,
|
|
2507
|
-
filePath,
|
|
2508
|
-
updatedAt: new Date(stat.mtimeMs).toISOString(),
|
|
2509
|
-
source: 'codex-rollout-jsonl',
|
|
2510
|
-
hasMoreBefore: allTurns.length > maxTurns || tail.truncated,
|
|
2511
|
-
turnLimit: maxTurns,
|
|
2512
|
-
turns,
|
|
2513
|
-
};
|
|
2514
|
-
}
|
|
2515
|
-
|
|
2516
|
-
async function readCodexHistoryImageData(sessionId, options = {}) {
|
|
2517
|
-
const normalizedSessionId = String(sessionId || '').trim();
|
|
2518
|
-
if (!normalizedSessionId) return new Map();
|
|
2519
|
-
const filePath = findCodexRolloutFile(normalizedSessionId, {
|
|
2520
|
-
codexHome: options.codexHome || path.join(os.homedir(), '.codex'),
|
|
2521
|
-
});
|
|
2522
|
-
if (!filePath) return new Map();
|
|
2523
|
-
const tail = await readTailLines(filePath, options.maxReadBytes);
|
|
2524
|
-
const imageDataByPath = new Map();
|
|
2525
|
-
for (const line of tail.lines) {
|
|
2526
|
-
if (!line.trim()) continue;
|
|
2527
|
-
try {
|
|
2528
|
-
const event = JSON.parse(line);
|
|
2529
|
-
const { type: eventType, payload } = normalizeEventEnvelope(event);
|
|
2530
|
-
if (!payload) continue;
|
|
2531
|
-
if (eventType === 'response_item' && payload.type === 'message' && payload.role === 'user') {
|
|
2532
|
-
appendHistoryImageDataFromContent(imageDataByPath, payload.content);
|
|
2533
|
-
continue;
|
|
2534
|
-
}
|
|
2535
|
-
if (['item/started', 'item/completed', 'item.started', 'item.completed'].includes(eventType)) {
|
|
2536
|
-
const item = payload.item;
|
|
2537
|
-
if (turnItemType(item) === 'usermessage') appendHistoryImageDataFromContent(imageDataByPath, item?.content);
|
|
2538
|
-
}
|
|
2539
|
-
} catch {
|
|
2540
|
-
// Ignore malformed or partial tail records; history replay remains usable without image recovery.
|
|
2541
|
-
}
|
|
2542
|
-
}
|
|
2543
|
-
return imageDataByPath;
|
|
2544
|
-
}
|
|
2545
|
-
|
|
2546
2348
|
function buildTranscriptFromEvents(events, options = {}) {
|
|
2547
2349
|
const lines = Array.isArray(events)
|
|
2548
2350
|
? events.filter(event => event && typeof event === 'object').map(event => JSON.stringify(event))
|
|
@@ -2554,10 +2356,6 @@ module.exports = {
|
|
|
2554
2356
|
DEFAULT_MAX_TURNS,
|
|
2555
2357
|
buildTranscriptFromEvents,
|
|
2556
2358
|
buildTranscriptFromLines,
|
|
2557
|
-
codexHistoryImageTargets,
|
|
2558
|
-
dropLeadingPartialTurn,
|
|
2559
|
-
readCodexHistoryImageData,
|
|
2560
|
-
readCodexTranscript,
|
|
2561
2359
|
stripUserMessagePrefix,
|
|
2562
2360
|
textFromContent,
|
|
2563
2361
|
visibleUserMessageText,
|
|
@@ -59,9 +59,6 @@ const DEFAULT_AGENT_HOMES = {
|
|
|
59
59
|
qoder: [{ id: 'default', path: '~/.qoder' }],
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
-
const DEFAULT_UPDATE_URL = 'https://github.com/zhuwenzhuang/farming/releases/latest';
|
|
63
|
-
const LEGACY_DEFAULT_UPDATE_URL = 'https://github.com/zhuwenzhuang/farming/releases/latest/download/manifest.json';
|
|
64
|
-
const API_DEFAULT_UPDATE_URL = 'https://api.github.com/repos/zhuwenzhuang/farming/releases/latest';
|
|
65
62
|
const LEGACY_DEFAULT_WORKSPACE_FILE_SEARCH_TIMEOUT_MS = 3000;
|
|
66
63
|
const DEFAULT_SEARCH_TIMEOUT_MS = 15000;
|
|
67
64
|
const MIN_SEARCH_TIMEOUT_MS = 3000;
|
|
@@ -85,16 +82,19 @@ const PERSISTED_SETTING_KEYS = new Set([
|
|
|
85
82
|
'theme',
|
|
86
83
|
'appearance',
|
|
87
84
|
'language',
|
|
85
|
+
'restReminderIntervalSeconds',
|
|
88
86
|
'heartbeatInterval',
|
|
89
87
|
'dangerouslySkipAgentPermissionsByDefault',
|
|
90
88
|
'browserExtensionEnabled',
|
|
89
|
+
'browserSource',
|
|
90
|
+
'browserExecutablePath',
|
|
91
|
+
'browserExternalCdpUrl',
|
|
91
92
|
'crtSkinEffectsEnabled',
|
|
92
93
|
'crtDynamicHeatEnabled',
|
|
93
94
|
'crtTerminalFontSize',
|
|
94
95
|
'defaultLaunchAgent',
|
|
95
96
|
'agentLaunchProfiles',
|
|
96
97
|
'agentHomes',
|
|
97
|
-
'updateUrl',
|
|
98
98
|
'searchTimeoutMs',
|
|
99
99
|
'codexApprovalMode',
|
|
100
100
|
'codexModel',
|
|
@@ -317,9 +317,13 @@ class ConfigManager {
|
|
|
317
317
|
theme: 'terminal',
|
|
318
318
|
appearance: 'system',
|
|
319
319
|
language: 'en',
|
|
320
|
+
restReminderIntervalSeconds: null,
|
|
320
321
|
heartbeatInterval: 1000,
|
|
321
322
|
dangerouslySkipAgentPermissionsByDefault: false,
|
|
322
323
|
browserExtensionEnabled: false,
|
|
324
|
+
browserSource: process.env.FARMING_BROWSER_CDP_URL ? 'external-cdp' : 'system',
|
|
325
|
+
browserExecutablePath: process.env.FARMING_BROWSER_EXECUTABLE || '',
|
|
326
|
+
browserExternalCdpUrl: process.env.FARMING_BROWSER_CDP_URL || 'http://127.0.0.1:9222',
|
|
323
327
|
crtSkinEffectsEnabled: true,
|
|
324
328
|
crtDynamicHeatEnabled: false,
|
|
325
329
|
crtTerminalFontSize: DEFAULT_CRT_TERMINAL_FONT_SIZE,
|
|
@@ -329,7 +333,6 @@ class ConfigManager {
|
|
|
329
333
|
claude: cloneLaunchProfile(DEFAULT_CLAUDE_LAUNCH_PROFILE),
|
|
330
334
|
},
|
|
331
335
|
agentHomes: cloneAgentHomes(DEFAULT_AGENT_HOMES),
|
|
332
|
-
updateUrl: DEFAULT_UPDATE_URL,
|
|
333
336
|
searchTimeoutMs: DEFAULT_SEARCH_TIMEOUT_MS,
|
|
334
337
|
codexApprovalMode: 'approve',
|
|
335
338
|
codexModel: 'gpt-5.5',
|
|
@@ -355,9 +358,13 @@ class ConfigManager {
|
|
|
355
358
|
theme: 'terminal',
|
|
356
359
|
appearance: 'system',
|
|
357
360
|
language: 'en',
|
|
361
|
+
restReminderIntervalSeconds: null,
|
|
358
362
|
heartbeatInterval: 1000,
|
|
359
363
|
dangerouslySkipAgentPermissionsByDefault: false,
|
|
360
364
|
browserExtensionEnabled: false,
|
|
365
|
+
browserSource: process.env.FARMING_BROWSER_CDP_URL ? 'external-cdp' : 'system',
|
|
366
|
+
browserExecutablePath: process.env.FARMING_BROWSER_EXECUTABLE || '',
|
|
367
|
+
browserExternalCdpUrl: process.env.FARMING_BROWSER_CDP_URL || 'http://127.0.0.1:9222',
|
|
361
368
|
crtSkinEffectsEnabled: true,
|
|
362
369
|
crtDynamicHeatEnabled: false,
|
|
363
370
|
crtTerminalFontSize: DEFAULT_CRT_TERMINAL_FONT_SIZE,
|
|
@@ -367,7 +374,6 @@ class ConfigManager {
|
|
|
367
374
|
claude: cloneLaunchProfile(DEFAULT_CLAUDE_LAUNCH_PROFILE),
|
|
368
375
|
},
|
|
369
376
|
agentHomes: cloneAgentHomes(DEFAULT_AGENT_HOMES),
|
|
370
|
-
updateUrl: DEFAULT_UPDATE_URL,
|
|
371
377
|
searchTimeoutMs: DEFAULT_SEARCH_TIMEOUT_MS,
|
|
372
378
|
codexApprovalMode: 'approve',
|
|
373
379
|
codexModel: 'gpt-5.5',
|
|
@@ -405,10 +411,7 @@ class ConfigManager {
|
|
|
405
411
|
this.settings.projectOperations = this.normalizeProjectOperations(this.settings.projectOperations);
|
|
406
412
|
this.settings.instanceName = this.normalizeInstanceName(this.settings.instanceName);
|
|
407
413
|
this.settings.agentHomes = this.normalizeAgentHomes(this.settings.agentHomes);
|
|
408
|
-
|
|
409
|
-
this.settings.updateUrl = DEFAULT_UPDATE_URL;
|
|
410
|
-
}
|
|
411
|
-
this.settings.updateUrl = this.normalizeUpdateUrl(this.settings.updateUrl);
|
|
414
|
+
delete this.settings.updateUrl;
|
|
412
415
|
this.settings.searchTimeoutMs = this.normalizeSearchTimeoutMs(this.settings.searchTimeoutMs);
|
|
413
416
|
delete this.settings.codexRuntimeMode;
|
|
414
417
|
const legacyMainPageSessionKeys = this.normalizeMainPageSessionKeys(this.settings.mainPageSessionKeys);
|
|
@@ -419,7 +422,14 @@ class ConfigManager {
|
|
|
419
422
|
this.runHistoryStore.init({ legacyTaskHistory });
|
|
420
423
|
this.settings.appearance = this.normalizeAppearance(this.settings.appearance);
|
|
421
424
|
this.settings.language = this.normalizeLanguage(this.settings.language);
|
|
425
|
+
this.settings.restReminderIntervalSeconds = this.normalizeRestReminderIntervalSeconds(
|
|
426
|
+
this.settings.restReminderIntervalSeconds,
|
|
427
|
+
);
|
|
422
428
|
this.settings.browserExtensionEnabled = this.settings.browserExtensionEnabled === true;
|
|
429
|
+
this.settings.browserSource = this.normalizeBrowserSource(this.settings.browserSource);
|
|
430
|
+
this.settings.browserExecutablePath = this.normalizeBrowserSetting(this.settings.browserExecutablePath);
|
|
431
|
+
this.settings.browserExternalCdpUrl = this.normalizeBrowserSetting(this.settings.browserExternalCdpUrl)
|
|
432
|
+
|| 'http://127.0.0.1:9222';
|
|
423
433
|
this.settings.crtSkinEffectsEnabled = this.settings.crtSkinEffectsEnabled !== false;
|
|
424
434
|
this.settings.crtDynamicHeatEnabled = this.settings.crtDynamicHeatEnabled === true;
|
|
425
435
|
this.settings.crtTerminalFontSize = this.normalizeCrtTerminalFontSize(this.settings.crtTerminalFontSize);
|
|
@@ -452,6 +462,16 @@ class ConfigManager {
|
|
|
452
462
|
);
|
|
453
463
|
}
|
|
454
464
|
|
|
465
|
+
normalizeRestReminderIntervalSeconds(value) {
|
|
466
|
+
if (value === null || value === undefined || value === '') return null;
|
|
467
|
+
const seconds = Number(value);
|
|
468
|
+
if (!Number.isInteger(seconds)) return null;
|
|
469
|
+
if (seconds === 0 || seconds === 5) return seconds;
|
|
470
|
+
return seconds >= 60 && seconds <= 240 * 60 && seconds % 60 === 0
|
|
471
|
+
? seconds
|
|
472
|
+
: null;
|
|
473
|
+
}
|
|
474
|
+
|
|
455
475
|
normalizeCrtTerminalFontSize(value) {
|
|
456
476
|
const fontSize = Number(value);
|
|
457
477
|
if (!Number.isFinite(fontSize)) return DEFAULT_CRT_TERMINAL_FONT_SIZE;
|
|
@@ -579,10 +599,12 @@ class ConfigManager {
|
|
|
579
599
|
return ['en', 'zh'].includes(language) ? language : 'en';
|
|
580
600
|
}
|
|
581
601
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
602
|
+
normalizeBrowserSource(source) {
|
|
603
|
+
return ['external-cdp', 'managed'].includes(source) ? source : 'system';
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
normalizeBrowserSetting(value) {
|
|
607
|
+
return String(value || '').trim().slice(0, 2000);
|
|
586
608
|
}
|
|
587
609
|
|
|
588
610
|
normalizeClaudePermissionMode(mode) {
|
|
@@ -1057,7 +1079,7 @@ class ConfigManager {
|
|
|
1057
1079
|
nextSettings.projectOperations = this.normalizeProjectOperations(nextSettings.projectOperations);
|
|
1058
1080
|
nextSettings.instanceName = this.normalizeInstanceName(nextSettings.instanceName);
|
|
1059
1081
|
nextSettings.agentHomes = this.normalizeAgentHomes(nextSettings.agentHomes);
|
|
1060
|
-
|
|
1082
|
+
delete nextSettings.updateUrl;
|
|
1061
1083
|
nextSettings.searchTimeoutMs = this.normalizeSearchTimeoutMs(nextSettings.searchTimeoutMs);
|
|
1062
1084
|
delete nextSettings.codexRuntimeMode;
|
|
1063
1085
|
delete nextSettings.mainPageSessionKeys;
|
|
@@ -1070,7 +1092,14 @@ class ConfigManager {
|
|
|
1070
1092
|
}
|
|
1071
1093
|
nextSettings.appearance = this.normalizeAppearance(nextSettings.appearance);
|
|
1072
1094
|
nextSettings.language = this.normalizeLanguage(nextSettings.language);
|
|
1095
|
+
nextSettings.restReminderIntervalSeconds = this.normalizeRestReminderIntervalSeconds(
|
|
1096
|
+
nextSettings.restReminderIntervalSeconds,
|
|
1097
|
+
);
|
|
1073
1098
|
nextSettings.browserExtensionEnabled = nextSettings.browserExtensionEnabled === true;
|
|
1099
|
+
nextSettings.browserSource = this.normalizeBrowserSource(nextSettings.browserSource);
|
|
1100
|
+
nextSettings.browserExecutablePath = this.normalizeBrowserSetting(nextSettings.browserExecutablePath);
|
|
1101
|
+
nextSettings.browserExternalCdpUrl = this.normalizeBrowserSetting(nextSettings.browserExternalCdpUrl)
|
|
1102
|
+
|| 'http://127.0.0.1:9222';
|
|
1074
1103
|
nextSettings.crtSkinEffectsEnabled = nextSettings.crtSkinEffectsEnabled !== false;
|
|
1075
1104
|
nextSettings.crtDynamicHeatEnabled = nextSettings.crtDynamicHeatEnabled === true;
|
|
1076
1105
|
nextSettings.crtTerminalFontSize = this.normalizeCrtTerminalFontSize(nextSettings.crtTerminalFontSize);
|
|
@@ -1082,7 +1111,6 @@ class ConfigManager {
|
|
|
1082
1111
|
}
|
|
1083
1112
|
|
|
1084
1113
|
module.exports = ConfigManager;
|
|
1085
|
-
module.exports.DEFAULT_UPDATE_URL = DEFAULT_UPDATE_URL;
|
|
1086
1114
|
module.exports.DEFAULT_SEARCH_TIMEOUT_MS = DEFAULT_SEARCH_TIMEOUT_MS;
|
|
1087
1115
|
module.exports.DEFAULT_CRT_TERMINAL_FONT_SIZE = DEFAULT_CRT_TERMINAL_FONT_SIZE;
|
|
1088
1116
|
module.exports.MIN_CRT_TERMINAL_FONT_SIZE = MIN_CRT_TERMINAL_FONT_SIZE;
|