farming-code 2.2.29 → 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 +25 -66
- package/README.zh_cn.md +15 -66
- package/backend/acp-runtime.js +1 -15
- package/backend/acp-session-state.js +78 -88
- 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 +3 -19
- package/backend/farming-app-cli.js +1 -1
- package/backend/farming-cli.js +3 -57
- package/backend/index-html.js +21 -3
- package/backend/main-agent-skills.js +1 -18
- package/backend/server.js +6 -48
- package/backend/storage-layout.js +15 -0
- package/backend/update-service.js +55 -935
- package/config/farming.deploy.env.example +1 -1
- package/config/farming.install.env.example +1 -1
- package/dist/acp/codex-acp-1.1.4.mjs +387 -7
- package/dist/assets/App-Z1N7T0eM.js +695 -0
- package/dist/assets/{ProjectFilesSection-Cf2BEcAN.js → ProjectFilesSection-K3GHuQ4U.js} +2 -2
- package/dist/assets/code-dark-kLsQLvpf.css +1 -0
- package/dist/assets/{index-CQNaZc3l.js → index-D-zetSol.js} +2 -2
- package/dist/assets/main-DUTz-Rd2.css +1 -0
- package/dist/index.html +2 -1
- package/extensions/browser/backend/browser-resource-manager.js +27 -5
- package/extensions/browser/backend/browser-router.js +8 -0
- package/extensions/browser/backend/executable-discovery.js +26 -12
- package/extensions/browser/backend/managed-chromium-installer.js +593 -0
- package/extensions/browser/frontend/types.ts +8 -1
- package/extensions/browser/frontend/useBrowserResources.ts +8 -0
- package/index.html +1 -0
- package/package.json +1 -1
- 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-Ex2oVVXO.js +0 -693
- package/dist/assets/code-dark-BEZTQwYZ.css +0 -1
- package/dist/assets/main-DK9Sy7-c.css +0 -1
|
@@ -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;
|
|
@@ -98,7 +95,6 @@ const PERSISTED_SETTING_KEYS = new Set([
|
|
|
98
95
|
'defaultLaunchAgent',
|
|
99
96
|
'agentLaunchProfiles',
|
|
100
97
|
'agentHomes',
|
|
101
|
-
'updateUrl',
|
|
102
98
|
'searchTimeoutMs',
|
|
103
99
|
'codexApprovalMode',
|
|
104
100
|
'codexModel',
|
|
@@ -337,7 +333,6 @@ class ConfigManager {
|
|
|
337
333
|
claude: cloneLaunchProfile(DEFAULT_CLAUDE_LAUNCH_PROFILE),
|
|
338
334
|
},
|
|
339
335
|
agentHomes: cloneAgentHomes(DEFAULT_AGENT_HOMES),
|
|
340
|
-
updateUrl: DEFAULT_UPDATE_URL,
|
|
341
336
|
searchTimeoutMs: DEFAULT_SEARCH_TIMEOUT_MS,
|
|
342
337
|
codexApprovalMode: 'approve',
|
|
343
338
|
codexModel: 'gpt-5.5',
|
|
@@ -379,7 +374,6 @@ class ConfigManager {
|
|
|
379
374
|
claude: cloneLaunchProfile(DEFAULT_CLAUDE_LAUNCH_PROFILE),
|
|
380
375
|
},
|
|
381
376
|
agentHomes: cloneAgentHomes(DEFAULT_AGENT_HOMES),
|
|
382
|
-
updateUrl: DEFAULT_UPDATE_URL,
|
|
383
377
|
searchTimeoutMs: DEFAULT_SEARCH_TIMEOUT_MS,
|
|
384
378
|
codexApprovalMode: 'approve',
|
|
385
379
|
codexModel: 'gpt-5.5',
|
|
@@ -417,10 +411,7 @@ class ConfigManager {
|
|
|
417
411
|
this.settings.projectOperations = this.normalizeProjectOperations(this.settings.projectOperations);
|
|
418
412
|
this.settings.instanceName = this.normalizeInstanceName(this.settings.instanceName);
|
|
419
413
|
this.settings.agentHomes = this.normalizeAgentHomes(this.settings.agentHomes);
|
|
420
|
-
|
|
421
|
-
this.settings.updateUrl = DEFAULT_UPDATE_URL;
|
|
422
|
-
}
|
|
423
|
-
this.settings.updateUrl = this.normalizeUpdateUrl(this.settings.updateUrl);
|
|
414
|
+
delete this.settings.updateUrl;
|
|
424
415
|
this.settings.searchTimeoutMs = this.normalizeSearchTimeoutMs(this.settings.searchTimeoutMs);
|
|
425
416
|
delete this.settings.codexRuntimeMode;
|
|
426
417
|
const legacyMainPageSessionKeys = this.normalizeMainPageSessionKeys(this.settings.mainPageSessionKeys);
|
|
@@ -609,19 +600,13 @@ class ConfigManager {
|
|
|
609
600
|
}
|
|
610
601
|
|
|
611
602
|
normalizeBrowserSource(source) {
|
|
612
|
-
return
|
|
603
|
+
return ['external-cdp', 'managed'].includes(source) ? source : 'system';
|
|
613
604
|
}
|
|
614
605
|
|
|
615
606
|
normalizeBrowserSetting(value) {
|
|
616
607
|
return String(value || '').trim().slice(0, 2000);
|
|
617
608
|
}
|
|
618
609
|
|
|
619
|
-
normalizeUpdateUrl(value) {
|
|
620
|
-
const url = String(value || '').trim();
|
|
621
|
-
if (!url) return DEFAULT_UPDATE_URL;
|
|
622
|
-
return /^https?:\/\//i.test(url) ? url.slice(0, 2000) : DEFAULT_UPDATE_URL;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
610
|
normalizeClaudePermissionMode(mode) {
|
|
626
611
|
return ['acceptEdits', 'auto', 'bypassPermissions', 'default', 'dontAsk', 'plan'].includes(mode)
|
|
627
612
|
? mode
|
|
@@ -1094,7 +1079,7 @@ class ConfigManager {
|
|
|
1094
1079
|
nextSettings.projectOperations = this.normalizeProjectOperations(nextSettings.projectOperations);
|
|
1095
1080
|
nextSettings.instanceName = this.normalizeInstanceName(nextSettings.instanceName);
|
|
1096
1081
|
nextSettings.agentHomes = this.normalizeAgentHomes(nextSettings.agentHomes);
|
|
1097
|
-
|
|
1082
|
+
delete nextSettings.updateUrl;
|
|
1098
1083
|
nextSettings.searchTimeoutMs = this.normalizeSearchTimeoutMs(nextSettings.searchTimeoutMs);
|
|
1099
1084
|
delete nextSettings.codexRuntimeMode;
|
|
1100
1085
|
delete nextSettings.mainPageSessionKeys;
|
|
@@ -1126,7 +1111,6 @@ class ConfigManager {
|
|
|
1126
1111
|
}
|
|
1127
1112
|
|
|
1128
1113
|
module.exports = ConfigManager;
|
|
1129
|
-
module.exports.DEFAULT_UPDATE_URL = DEFAULT_UPDATE_URL;
|
|
1130
1114
|
module.exports.DEFAULT_SEARCH_TIMEOUT_MS = DEFAULT_SEARCH_TIMEOUT_MS;
|
|
1131
1115
|
module.exports.DEFAULT_CRT_TERMINAL_FONT_SIZE = DEFAULT_CRT_TERMINAL_FONT_SIZE;
|
|
1132
1116
|
module.exports.MIN_CRT_TERMINAL_FONT_SIZE = MIN_CRT_TERMINAL_FONT_SIZE;
|
|
@@ -27,7 +27,7 @@ const DEFAULT_SERVER_START_STABILITY_MS = 1_500;
|
|
|
27
27
|
const DEFAULT_SERVER_STOP_TIMEOUT_MS = 30_000;
|
|
28
28
|
const SERVER_STOP_POLL_MS = 100;
|
|
29
29
|
const SERVER_COMMANDS = new Set(['start', 'serve', 'daemon', 'stop', 'status', 'logs', 'url', 'help']);
|
|
30
|
-
const CONTROL_COMMANDS = new Set(['skills', 'capabilities', '
|
|
30
|
+
const CONTROL_COMMANDS = new Set(['skills', 'capabilities', 'list', 'spawn', 'output', 'send', 'kill']);
|
|
31
31
|
const SERVER_BACKED_CONTROL_COMMANDS = new Set(['capabilities', 'list', 'spawn', 'output', 'send', 'kill']);
|
|
32
32
|
|
|
33
33
|
function defaultConfigDir(env = process.env) {
|
package/backend/farming-cli.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const http = require('http');
|
|
3
3
|
const https = require('https');
|
|
4
|
-
const { buildMemoryReport, formatMemoryReport } = require('./agent-memory-report');
|
|
5
4
|
const { renderMainAgentSkills } = require('./main-agent-skills');
|
|
6
5
|
const storageLayout = require('./storage-layout');
|
|
7
6
|
|
|
@@ -9,7 +8,6 @@ function usage() {
|
|
|
9
8
|
return `Usage:
|
|
10
9
|
farming skills
|
|
11
10
|
farming capabilities [--json]
|
|
12
|
-
farming memory report [--period today|yesterday|week] [--since <time>] [--until <time>] [--home <path>] [--json]
|
|
13
11
|
farming list [--json] [--parent <agentId>]
|
|
14
12
|
farming spawn [--workspace <path>] [--task <text>] [--parent <agentId>] [--json] -- <command...>
|
|
15
13
|
farming output <agentId> [--tail <chars>]
|
|
@@ -18,8 +16,6 @@ function usage() {
|
|
|
18
16
|
|
|
19
17
|
Examples:
|
|
20
18
|
farming spawn --workspace /repo --task "Inspect this module for bugs" -- claude
|
|
21
|
-
farming memory report --period today
|
|
22
|
-
farming memory report --period week --json
|
|
23
19
|
farming skills
|
|
24
20
|
farming capabilities
|
|
25
21
|
farming list --parent "$FARMING_AGENT_ID"
|
|
@@ -85,43 +81,6 @@ function parseArgs(argv) {
|
|
|
85
81
|
return { command, options: { json: rest.includes('--json') } };
|
|
86
82
|
}
|
|
87
83
|
|
|
88
|
-
if (command === 'memory' || command === 'report') {
|
|
89
|
-
const subcommand = command === 'memory' ? rest.shift() : 'report';
|
|
90
|
-
if (subcommand !== 'report') {
|
|
91
|
-
throw new Error('memory requires the report subcommand');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const options = {
|
|
95
|
-
period: 'today',
|
|
96
|
-
since: '',
|
|
97
|
-
until: '',
|
|
98
|
-
homeDir: '',
|
|
99
|
-
json: false,
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
for (let i = 0; i < rest.length; i++) {
|
|
103
|
-
if (rest[i] === '--period') {
|
|
104
|
-
options.period = splitOptionValue(rest, i, '--period');
|
|
105
|
-
i++;
|
|
106
|
-
} else if (rest[i] === '--since') {
|
|
107
|
-
options.since = splitOptionValue(rest, i, '--since');
|
|
108
|
-
i++;
|
|
109
|
-
} else if (rest[i] === '--until') {
|
|
110
|
-
options.until = splitOptionValue(rest, i, '--until');
|
|
111
|
-
i++;
|
|
112
|
-
} else if (rest[i] === '--home') {
|
|
113
|
-
options.homeDir = splitOptionValue(rest, i, '--home');
|
|
114
|
-
i++;
|
|
115
|
-
} else if (rest[i] === '--json') {
|
|
116
|
-
options.json = true;
|
|
117
|
-
} else {
|
|
118
|
-
throw new Error(`Unknown option: ${rest[i]}`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return { command: 'memory-report', options };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
84
|
if (command === 'list') {
|
|
126
85
|
const options = { json: false, parent: '' };
|
|
127
86
|
for (let i = 0; i < rest.length; i++) {
|
|
@@ -316,7 +275,9 @@ function farmingCapabilities(browser) {
|
|
|
316
275
|
summary: state === 'available'
|
|
317
276
|
? (browser?.browser?.kind === 'external-cdp'
|
|
318
277
|
? 'Default browser path for web tasks. An externally managed CDP Browser can be created or attached as a shared, user-visible Farming Resource.'
|
|
319
|
-
:
|
|
278
|
+
: browser?.browser?.kind === 'managed-chromium'
|
|
279
|
+
? 'Default browser path for web tasks. Farming-managed Chromium can be created or attached as a shared, user-visible Farming Resource.'
|
|
280
|
+
: 'Default browser path for web tasks. An installed system Chromium Browser can be created or attached as a shared, user-visible Farming Resource.')
|
|
320
281
|
: (browser?.message || 'Browser integration is unavailable in Farming.'),
|
|
321
282
|
commands: state === 'available'
|
|
322
283
|
? {
|
|
@@ -361,21 +322,6 @@ async function run(argv = process.argv.slice(2), io = process) {
|
|
|
361
322
|
return 0;
|
|
362
323
|
}
|
|
363
324
|
|
|
364
|
-
if (parsed.command === 'memory-report') {
|
|
365
|
-
const report = buildMemoryReport({
|
|
366
|
-
period: parsed.options.period,
|
|
367
|
-
since: parsed.options.since,
|
|
368
|
-
until: parsed.options.until,
|
|
369
|
-
homeDir: parsed.options.homeDir || undefined,
|
|
370
|
-
});
|
|
371
|
-
if (parsed.options.json) {
|
|
372
|
-
io.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
373
|
-
} else {
|
|
374
|
-
io.stdout.write(`${formatMemoryReport(report)}\n`);
|
|
375
|
-
}
|
|
376
|
-
return 0;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
325
|
if (parsed.command === 'list') {
|
|
380
326
|
const query = parsed.options.parent ? `?parent=${encodeURIComponent(parsed.options.parent)}` : '';
|
|
381
327
|
const state = await request(`/api/control/agents${query}`);
|
package/backend/index-html.js
CHANGED
|
@@ -43,14 +43,32 @@ function appendIndexHtmlAssetToken(html, token) {
|
|
|
43
43
|
|
|
44
44
|
function applyIndexHtmlAppearance(html, appearance) {
|
|
45
45
|
const normalizedAppearance = ['light', 'dark'].includes(appearance) ? appearance : 'system';
|
|
46
|
-
|
|
46
|
+
let source = String(html || '');
|
|
47
47
|
if (/\bdata-appearance-preference="[^"]*"/i.test(source)) {
|
|
48
|
-
|
|
48
|
+
source = source.replace(
|
|
49
49
|
/\bdata-appearance-preference="[^"]*"/i,
|
|
50
50
|
`data-appearance-preference="${normalizedAppearance}"`
|
|
51
51
|
);
|
|
52
|
+
} else {
|
|
53
|
+
source = source.replace(
|
|
54
|
+
/<html\b/i,
|
|
55
|
+
`<html data-appearance-preference="${normalizedAppearance}"`
|
|
56
|
+
);
|
|
52
57
|
}
|
|
53
|
-
|
|
58
|
+
|
|
59
|
+
const colorScheme = normalizedAppearance === 'system'
|
|
60
|
+
? 'light dark'
|
|
61
|
+
: normalizedAppearance;
|
|
62
|
+
const themeColor = normalizedAppearance === 'dark' ? '#181818' : '#ffffff';
|
|
63
|
+
return source
|
|
64
|
+
.replace(
|
|
65
|
+
/(<meta\s+name="color-scheme"\s+content=")[^"]*(")/i,
|
|
66
|
+
`$1${colorScheme}$2`
|
|
67
|
+
)
|
|
68
|
+
.replace(
|
|
69
|
+
/(<meta\s+name="theme-color"\s+content=")[^"]*(")/i,
|
|
70
|
+
`$1${themeColor}$2`
|
|
71
|
+
);
|
|
54
72
|
}
|
|
55
73
|
|
|
56
74
|
module.exports = {
|
|
@@ -4,7 +4,7 @@ const path = require('path');
|
|
|
4
4
|
const MANAGED_BLOCK_START = '<!-- FARMING_MAIN_AGENT_SKILLS:START -->';
|
|
5
5
|
const MANAGED_BLOCK_END = '<!-- FARMING_MAIN_AGENT_SKILLS:END -->';
|
|
6
6
|
|
|
7
|
-
const REMOVED_MAIN_AGENT_SKILL_IDS = [];
|
|
7
|
+
const REMOVED_MAIN_AGENT_SKILL_IDS = ['memory-report'];
|
|
8
8
|
|
|
9
9
|
const MAIN_AGENT_OPERATING_GUIDE = [
|
|
10
10
|
{
|
|
@@ -19,7 +19,6 @@ const MAIN_AGENT_OPERATING_GUIDE = [
|
|
|
19
19
|
lines: [
|
|
20
20
|
'On startup or resume, orient yourself with `farming list --json` and inspect `FARMING_PROJECT_WORKSPACE` / `FARMING_MAIN_WORKSPACE` before proposing work.',
|
|
21
21
|
'Do not spawn child agents just because you can. Wait for a concrete user goal, or ask one short clarification if the goal is too vague to route safely.',
|
|
22
|
-
'Use `farming memory report` only when recent local context would change what you do next.',
|
|
23
22
|
],
|
|
24
23
|
},
|
|
25
24
|
{
|
|
@@ -69,18 +68,6 @@ const MAIN_AGENT_SKILLS = [
|
|
|
69
68
|
'farming browser help <lifecycle|navigation|interaction|inspection|debugging|state|files>',
|
|
70
69
|
],
|
|
71
70
|
},
|
|
72
|
-
{
|
|
73
|
-
id: 'memory-report',
|
|
74
|
-
name: '记忆读取总结',
|
|
75
|
-
trigger: '用户要求总结今日、昨日、本周做过什么,或需要先了解这台机器上各 agent 的近期工作记忆时',
|
|
76
|
-
summary: '只读扫描本机 Claude/Qwen/Codex 本地历史线索,按时间段生成 Farming 日报或周报。',
|
|
77
|
-
commands: [
|
|
78
|
-
'farming memory report --period today',
|
|
79
|
-
'farming memory report --period yesterday',
|
|
80
|
-
'farming memory report --period week',
|
|
81
|
-
'farming memory report --since <YYYY-MM-DD> --until <YYYY-MM-DD>',
|
|
82
|
-
],
|
|
83
|
-
},
|
|
84
71
|
{
|
|
85
72
|
id: 'pest-control',
|
|
86
73
|
name: '牧场除虫计划',
|
|
@@ -150,7 +137,6 @@ function renderMainAgentSkills() {
|
|
|
150
137
|
|
|
151
138
|
lines.push('Rules:');
|
|
152
139
|
lines.push('- Use `farming browser` only for Browser Resources already in the user-selected Project scope; follow `farming browser help workflow`, and reveal only the help topic or command needed for the current step.');
|
|
153
|
-
lines.push('- Use `farming memory report` when you need recent context from local agent memories.');
|
|
154
140
|
lines.push('- Use “牧场除虫计划” when the user asks for systematic bug hunting across a directory or module tree.');
|
|
155
141
|
lines.push('- Before spawning child agents for pest control, map modules and module protocols first; do not send overlapping or vague tasks.');
|
|
156
142
|
lines.push('- Keep child agents scoped to their assigned module and require evidence, reproduction notes, or tests for each bug claim.');
|
|
@@ -174,9 +160,6 @@ function renderCanonicalAgentsFile() {
|
|
|
174
160
|
'',
|
|
175
161
|
'Useful commands:',
|
|
176
162
|
'- `farming skills`',
|
|
177
|
-
'- `farming memory report --period today`',
|
|
178
|
-
'- `farming memory report --period yesterday`',
|
|
179
|
-
'- `farming memory report --period week`',
|
|
180
163
|
'- `farming spawn --workspace <repo> --task "<module bug hunt task>" -- <coding-agent-command>`',
|
|
181
164
|
'- `farming list --parent "$FARMING_AGENT_ID"`',
|
|
182
165
|
'- `farming output <agent-id> --tail 2000`',
|