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.
Files changed (83) hide show
  1. package/README.md +27 -90
  2. package/README.zh_cn.md +17 -90
  3. package/THIRD_PARTY_NOTICES.md +25 -6
  4. package/backend/acp/packaged-claude-acp.js +13 -0
  5. package/backend/acp/packaged-codex-acp.js +2 -2
  6. package/backend/acp-runtime.js +38 -17
  7. package/backend/acp-session-state.js +78 -88
  8. package/backend/acp-transcript.js +174 -9
  9. package/backend/agent-manager.js +27 -2
  10. package/backend/agent-session-history.js +45 -12
  11. package/backend/codex-session-history.js +1 -1
  12. package/backend/codex-transcript.js +0 -202
  13. package/backend/config-manager.js +44 -16
  14. package/backend/data/LICENSE.agent-browser +201 -0
  15. package/backend/data/runtime-dependency-manifest.json +199 -0
  16. package/backend/data/runtime-dependency-sources.json +5 -0
  17. package/backend/executable-discovery.js +7 -0
  18. package/backend/farming-agent-bootstrap.zh_cn.md +4 -2
  19. package/backend/farming-app-cli.js +161 -65
  20. package/backend/farming-cli.js +7 -59
  21. package/backend/index-html.js +31 -0
  22. package/backend/main-agent-skills.js +7 -28
  23. package/backend/npm-update-helper.js +43 -6
  24. package/backend/provider-adapters.js +10 -0
  25. package/backend/runtime-dependency-manager.js +759 -0
  26. package/backend/runtime-executable-invocation.js +20 -0
  27. package/backend/server-process-identity.js +47 -0
  28. package/backend/server.js +145 -204
  29. package/backend/slash-command-discovery.js +138 -1
  30. package/backend/storage-layout.js +30 -0
  31. package/backend/update-service.js +60 -914
  32. package/config/farming.deploy.env.example +1 -1
  33. package/config/farming.install.env.example +5 -1
  34. package/dist/acp/LICENSE.claude-agent-acp +191 -0
  35. package/dist/acp/LICENSE.claude-agent-sdk +1 -0
  36. package/dist/acp/claude-agent-acp-0.59.0.mjs +41606 -0
  37. package/dist/acp/{codex-acp-1.1.4.js → codex-acp-1.1.4.mjs} +387 -7
  38. package/dist/assets/{App-CM7Cy-l4.css → App-BKNUILuL.css} +1 -1
  39. package/dist/assets/App-Z1N7T0eM.js +695 -0
  40. package/dist/assets/{FileEditorMarkdownPreview-DYOMm-b1.js → FileEditorMarkdownPreview-Cs9cLv8a.js} +1 -1
  41. package/dist/assets/FileEditorPane-CNtuGjQ3.js +2 -0
  42. package/dist/assets/IconGlyphs-BClgag6E.js +1 -0
  43. package/dist/assets/ProjectFilesSection-K3GHuQ4U.js +12 -0
  44. package/dist/assets/ReviewPage-BRuxFaH5.js +3 -0
  45. package/dist/assets/code-dark-kLsQLvpf.css +1 -0
  46. package/dist/assets/{index-BzQO3pUr.js → index-D-zetSol.js} +2 -2
  47. package/dist/assets/main-DUTz-Rd2.css +1 -0
  48. package/dist/assets/{workspace-editor-monaco-tXA-udGx.js → workspace-editor-monaco-CJNwbpCy.js} +1 -1
  49. package/dist/assets/workspace-editor-monaco-CO9w8YV0.js +1 -0
  50. package/dist/assets/workspace-view-state-BjKPYp0E.js +7 -0
  51. package/dist/index.html +33 -3
  52. package/extensions/browser/backend/agent-browser-runtime.js +1041 -0
  53. package/extensions/browser/backend/browser-mcp-server.js +352 -0
  54. package/extensions/browser/backend/browser-resource-manager.js +921 -114
  55. package/extensions/browser/backend/browser-resource-store.js +47 -6
  56. package/extensions/browser/backend/browser-router.js +15 -2
  57. package/extensions/browser/backend/executable-discovery.js +164 -17
  58. package/extensions/browser/backend/managed-chromium-installer.js +593 -0
  59. package/extensions/browser/bin/farming-browser +384 -30
  60. package/extensions/browser/frontend/BrowserSidebarPortals.tsx +17 -19
  61. package/extensions/browser/frontend/BrowserViewer.tsx +217 -46
  62. package/extensions/browser/frontend/browser.css +68 -17
  63. package/extensions/browser/frontend/types.ts +13 -0
  64. package/extensions/browser/frontend/useBrowserResources.ts +18 -3
  65. package/index.html +32 -2
  66. package/package.json +10 -6
  67. package/shared/backend-connection-status.d.ts +12 -0
  68. package/shared/backend-connection-status.js +35 -4
  69. package/backend/agent-activity.js +0 -35
  70. package/backend/agent-memory-report.js +0 -583
  71. package/backend/bundle-update-helper.js +0 -113
  72. package/backend/codex-rollout-follower.js +0 -326
  73. package/dist/assets/App-CPG4WcjO.js +0 -658
  74. package/dist/assets/FileEditorPane-DXXq82aM.js +0 -2
  75. package/dist/assets/IconGlyphs-DrdPjYXr.js +0 -1
  76. package/dist/assets/ProjectFilesSection-CP66XVPl.js +0 -12
  77. package/dist/assets/ReviewPage-w21MEPD6.js +0 -3
  78. package/dist/assets/code-dark-BDjwj7Um.css +0 -1
  79. package/dist/assets/main-BlVUXfxa.css +0 -1
  80. package/dist/assets/workspace-editor-monaco-CFEfFNcA.js +0 -1
  81. package/dist/assets/workspace-view-state-BS_V8JVs.js +0 -7
  82. package/extensions/browser/backend/cdp-browser-runtime.js +0 -648
  83. package/extensions/browser/backend/cdp-client.js +0 -167
@@ -3,14 +3,33 @@ const crypto = require('crypto');
3
3
  const { atomicWriteJson } = require('../../../backend/atomic-json-store');
4
4
  const storageLayout = require('../../../backend/storage-layout');
5
5
 
6
- const STORE_VERSION = 2;
6
+ const STORE_VERSION = 5;
7
7
  const RESOURCE_ID_RE = /^browser_[A-Za-z0-9_-]+$/;
8
+ const TAB_ID_RE = /^t\d+$/;
8
9
  const STATUSES = new Set(['stopped', 'starting', 'running', 'stopping', 'failed']);
9
10
 
10
11
  function createBrowserId() {
11
12
  return `browser_${Date.now().toString(36)}_${crypto.randomBytes(6).toString('hex')}`;
12
13
  }
13
14
 
15
+ function normalizeProcessIdentity(value) {
16
+ if (
17
+ !value
18
+ || Number(value.pid) <= 0
19
+ || !Number.isSafeInteger(Number(value.pid))
20
+ || Number(value.processGroupId) <= 0
21
+ || !Number.isSafeInteger(Number(value.processGroupId))
22
+ || !String(value.startedAt || '').trim()
23
+ || !String(value.format || '').trim()
24
+ ) return null;
25
+ return {
26
+ pid: Number(value.pid),
27
+ processGroupId: Number(value.processGroupId),
28
+ startedAt: String(value.startedAt).trim(),
29
+ format: String(value.format).trim(),
30
+ };
31
+ }
32
+
14
33
  function normalizeResource(value) {
15
34
  if (!value || typeof value !== 'object' || !RESOURCE_ID_RE.test(String(value.id || ''))) return null;
16
35
  const projectRootId = String(value.projectRootId || '').trim();
@@ -21,13 +40,21 @@ function normalizeResource(value) {
21
40
  projectRootId,
22
41
  workspace,
23
42
  name: String(value.name || 'Browser').trim().slice(0, 120) || 'Browser',
43
+ autoName: value.autoName === true,
24
44
  status: STATUSES.has(value.status) ? value.status : 'failed',
25
45
  generation: Number.isSafeInteger(value.generation) && value.generation >= 0 ? value.generation : 0,
26
46
  revision: Number.isSafeInteger(value.revision) && value.revision >= 0 ? value.revision : 0,
27
47
  url: String(value.url || 'about:blank').slice(0, 8_192) || 'about:blank',
28
48
  title: String(value.title || '').slice(0, 512),
29
49
  browserKind: String(value.browserKind || ''),
50
+ runtimeKind: String(value.runtimeKind || ''),
51
+ sessionId: RESOURCE_ID_RE.test(String(value.sessionId || '')) ? String(value.sessionId) : '',
52
+ sessionGeneration: Number.isSafeInteger(value.sessionGeneration) && value.sessionGeneration >= 0
53
+ ? value.sessionGeneration
54
+ : 0,
55
+ tabId: TAB_ID_RE.test(String(value.tabId || '')) ? String(value.tabId) : '',
30
56
  error: String(value.error || '').slice(0, 2_000),
57
+ processIdentity: normalizeProcessIdentity(value.processIdentity),
31
58
  createdAt: Number.isFinite(value.createdAt) ? value.createdAt : Date.now(),
32
59
  updatedAt: Number.isFinite(value.updatedAt) ? value.updatedAt : Date.now(),
33
60
  };
@@ -59,11 +86,6 @@ class BrowserResourceStore {
59
86
  for (const value of resources) {
60
87
  const resource = normalizeResource(value);
61
88
  if (!resource || this.resources.has(resource.id)) continue;
62
- if (resource.status === 'running' || resource.status === 'starting' || resource.status === 'stopping') {
63
- resource.status = 'failed';
64
- resource.error = 'Farming restarted before the Browser runtime could be reconciled';
65
- resource.updatedAt = Date.now();
66
- }
67
89
  this.resources.set(resource.id, resource);
68
90
  }
69
91
  this.commit();
@@ -86,12 +108,16 @@ class BrowserResourceStore {
86
108
  projectRootId: input.projectRootId,
87
109
  workspace: input.workspace,
88
110
  name: input.name || 'Browser',
111
+ autoName: input.autoName === true,
89
112
  status: 'stopped',
90
113
  generation: 0,
91
114
  revision: 0,
92
115
  url: input.url || 'about:blank',
93
116
  title: '',
94
117
  browserKind: '',
118
+ sessionId: input.sessionId || '',
119
+ sessionGeneration: input.sessionGeneration || 0,
120
+ tabId: input.tabId || '',
95
121
  error: '',
96
122
  createdAt: Date.now(),
97
123
  updatedAt: Date.now(),
@@ -102,6 +128,21 @@ class BrowserResourceStore {
102
128
  return { ...resource };
103
129
  }
104
130
 
131
+ createRunningTab(input) {
132
+ const resource = this.create(input);
133
+ return this.update(resource.id, {
134
+ status: 'running',
135
+ autoName: true,
136
+ generation: 1,
137
+ browserKind: input.browserKind,
138
+ runtimeKind: 'agent-browser',
139
+ sessionId: input.sessionId,
140
+ sessionGeneration: input.sessionGeneration,
141
+ tabId: input.tabId,
142
+ title: input.title || '',
143
+ });
144
+ }
145
+
105
146
  update(id, patch) {
106
147
  const current = this.resources.get(id);
107
148
  if (!current) return null;
@@ -11,8 +11,21 @@ function createBrowserRouter(manager, workspaceRootRegistry) {
11
11
  const router = express.Router();
12
12
  router.use(express.json({ limit: '2mb' }));
13
13
 
14
- router.get('/capability', (_req, res) => {
15
- res.json(manager.capability());
14
+ router.get('/capability', async (_req, res) => {
15
+ try {
16
+ await manager.refreshCapability();
17
+ res.json(manager.capability());
18
+ } catch (error) {
19
+ sendError(res, error);
20
+ }
21
+ });
22
+
23
+ router.post('/install', async (_req, res) => {
24
+ try {
25
+ res.json(await manager.installManagedChromium());
26
+ } catch (error) {
27
+ sendError(res, error);
28
+ }
16
29
  });
17
30
 
18
31
  router.get('/', (_req, res) => {
@@ -1,6 +1,8 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { execFileSync } = require('child_process');
4
+ const { verifyExecutable } = require('../../../backend/runtime-dependency-manager');
5
+ const { AGENT_BROWSER_VERSION } = require('./agent-browser-runtime');
4
6
 
5
7
  function executable(pathname, kind) {
6
8
  if (!pathname) return null;
@@ -20,30 +22,57 @@ function firstExecutable(candidates) {
20
22
  return null;
21
23
  }
22
24
 
25
+ function uniqueExecutables(candidates) {
26
+ const seen = new Set();
27
+ const result = [];
28
+ for (const candidate of candidates) {
29
+ const found = executable(candidate.path, candidate.kind);
30
+ if (!found || seen.has(found.path)) continue;
31
+ seen.add(found.path);
32
+ result.push(found);
33
+ }
34
+ return result;
35
+ }
36
+
23
37
  function which(command) {
24
38
  try {
25
- return execFileSync('which', [command], {
39
+ const program = process.platform === 'win32' ? 'where.exe' : 'which';
40
+ return execFileSync(program, [command], {
26
41
  encoding: 'utf8',
27
42
  timeout: 1_000,
28
43
  maxBuffer: 16_384,
29
- }).trim();
44
+ }).split(/\r?\n/).map(value => value.trim()).find(Boolean) || '';
30
45
  } catch {
31
46
  return '';
32
47
  }
33
48
  }
34
49
 
35
- function discoverMacBrowser() {
36
- return firstExecutable([
50
+ function managedAgentBrowserPath(options = {}) {
51
+ const env = options.env || process.env;
52
+ return String(
53
+ options.agentBrowserPath
54
+ || env.FARMING_AGENT_BROWSER_BIN
55
+ || env.FARMING_AGENT_BROWSER_EXECUTABLE
56
+ || '',
57
+ ).trim();
58
+ }
59
+
60
+ function macBrowserCandidates() {
61
+ return [
37
62
  { kind: 'chrome', path: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' },
38
63
  { kind: 'brave', path: '/Applications/Brave Browser.app/Contents/MacOS/Brave Browser' },
39
64
  { kind: 'edge', path: '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge' },
40
65
  { kind: 'chromium', path: '/Applications/Chromium.app/Contents/MacOS/Chromium' },
41
66
  { kind: 'chrome', path: path.join(process.env.HOME || '', 'Applications/Google Chrome.app/Contents/MacOS/Google Chrome') },
42
67
  { kind: 'brave', path: path.join(process.env.HOME || '', 'Applications/Brave Browser.app/Contents/MacOS/Brave Browser') },
43
- ]);
68
+ ];
44
69
  }
45
70
 
46
- function discoverLinuxBrowser() {
71
+ function discoverMacBrowser() {
72
+ return firstExecutable(macBrowserCandidates());
73
+ }
74
+
75
+ function linuxBrowserCandidates() {
47
76
  const commands = [
48
77
  ['google-chrome', 'chrome'],
49
78
  ['google-chrome-stable', 'chrome'],
@@ -52,14 +81,14 @@ function discoverLinuxBrowser() {
52
81
  ['chromium', 'chromium'],
53
82
  ['chromium-browser', 'chromium'],
54
83
  ];
55
- for (const [command, kind] of commands) {
56
- const found = executable(which(command), kind);
57
- if (found) return found;
58
- }
59
- return null;
84
+ return commands.map(([command, kind]) => ({ kind, path: which(command) }));
60
85
  }
61
86
 
62
- function discoverWindowsBrowser(env) {
87
+ function discoverLinuxBrowser() {
88
+ return firstExecutable(linuxBrowserCandidates());
89
+ }
90
+
91
+ function windowsBrowserCandidates(env) {
63
92
  const roots = [env.PROGRAMFILES, env['PROGRAMFILES(X86)'], env.LOCALAPPDATA].filter(Boolean);
64
93
  const candidates = [];
65
94
  for (const root of roots) {
@@ -70,22 +99,140 @@ function discoverWindowsBrowser(env) {
70
99
  { kind: 'chromium', path: path.join(root, 'Chromium', 'Application', 'chrome.exe') },
71
100
  );
72
101
  }
73
- return firstExecutable(candidates);
102
+ return candidates;
103
+ }
104
+
105
+ function discoverWindowsBrowser(env) {
106
+ return firstExecutable(windowsBrowserCandidates(env));
107
+ }
108
+
109
+ function discoverBrowserExecutables(options = {}) {
110
+ const env = options.env || process.env;
111
+ const platform = options.platform || process.platform;
112
+ let candidates = [];
113
+ if (platform === 'darwin') candidates = macBrowserCandidates();
114
+ if (platform === 'linux') candidates = linuxBrowserCandidates();
115
+ if (platform === 'win32') candidates = windowsBrowserCandidates(env);
116
+ const managed = executable(options.managedBrowserPath, 'managed-chromium');
117
+ return [
118
+ ...uniqueExecutables(candidates),
119
+ ...(managed ? [managed] : []),
120
+ ];
121
+ }
122
+
123
+ function normalizeExternalCdpUrl(value) {
124
+ const input = String(value || '').trim();
125
+ if (!input) return '';
126
+ try {
127
+ const url = new URL(input);
128
+ if (!['http:', 'https:', 'ws:', 'wss:'].includes(url.protocol)) return '';
129
+ if (url.username || url.password || url.search) return '';
130
+ const hostname = url.hostname.toLowerCase();
131
+ if (!['localhost', '127.0.0.1', '[::1]'].includes(hostname)) return '';
132
+ url.hash = '';
133
+ return url.href;
134
+ } catch {
135
+ return '';
136
+ }
74
137
  }
75
138
 
76
139
  function discoverBrowserExecutable(options = {}) {
77
140
  const env = options.env || process.env;
78
141
  const platform = options.platform || process.platform;
142
+ const source = String(options.source || '').trim();
143
+ if (source === 'external-cdp') {
144
+ const cdpUrl = normalizeExternalCdpUrl(options.externalCdpUrl);
145
+ return cdpUrl
146
+ ? { kind: 'external-cdp', path: '', cdpUrl }
147
+ : {
148
+ kind: 'external-cdp',
149
+ path: '',
150
+ cdpUrl: '',
151
+ error: 'External CDP must be a loopback http(s) or ws(s) endpoint without credentials or query parameters',
152
+ };
153
+ }
154
+ if (source === 'managed') {
155
+ return executable(options.managedBrowserPath, 'managed-chromium') || {
156
+ kind: 'managed-chromium',
157
+ path: String(options.managedBrowserPath || ''),
158
+ error: 'Install or update the Farming-managed Chromium for this agent-browser version',
159
+ };
160
+ }
161
+ if (source === 'system') {
162
+ const configuredPath = String(options.executablePath || '').trim();
163
+ if (configuredPath) {
164
+ const configuredKind = String(options.executableKind || 'custom').trim() || 'custom';
165
+ return executable(path.resolve(configuredPath), configuredKind) || {
166
+ kind: configuredKind,
167
+ path: path.resolve(configuredPath),
168
+ error: 'The selected Chromium browser is no longer available',
169
+ };
170
+ }
171
+ let systemBrowser = null;
172
+ if (platform === 'darwin') systemBrowser = discoverMacBrowser();
173
+ if (platform === 'linux') systemBrowser = discoverLinuxBrowser();
174
+ if (platform === 'win32') systemBrowser = discoverWindowsBrowser(env);
175
+ return systemBrowser || executable(options.managedBrowserPath, 'managed-chromium');
176
+ }
177
+ const externalCdpInput = String(options.externalCdpUrl || env.FARMING_BROWSER_CDP_URL || '').trim();
178
+ if (externalCdpInput) {
179
+ const cdpUrl = normalizeExternalCdpUrl(externalCdpInput);
180
+ return cdpUrl
181
+ ? { kind: 'external-cdp', path: '', cdpUrl }
182
+ : {
183
+ kind: 'external-cdp',
184
+ path: '',
185
+ cdpUrl: '',
186
+ error: 'FARMING_BROWSER_CDP_URL must be a loopback http(s) or ws(s) CDP endpoint without credentials or query parameters',
187
+ };
188
+ }
79
189
  const configured = String(options.executablePath || env.FARMING_BROWSER_EXECUTABLE || '').trim();
80
190
  if (configured) {
81
191
  return executable(path.resolve(configured), 'custom');
82
192
  }
83
- if (platform === 'darwin') return discoverMacBrowser();
84
- if (platform === 'linux') return discoverLinuxBrowser();
85
- if (platform === 'win32') return discoverWindowsBrowser(env);
86
- return null;
193
+ let systemBrowser = null;
194
+ if (platform === 'darwin') systemBrowser = discoverMacBrowser();
195
+ if (platform === 'linux') systemBrowser = discoverLinuxBrowser();
196
+ if (platform === 'win32') systemBrowser = discoverWindowsBrowser(env);
197
+ return systemBrowser || executable(options.managedBrowserPath, 'managed-chromium');
198
+ }
199
+
200
+ async function discoverBrowserRuntime(options = {}) {
201
+ const browser = discoverBrowserExecutable(options);
202
+ if (!browser || browser.error) return browser;
203
+ const agentBrowserPath = managedAgentBrowserPath(options);
204
+ if (!agentBrowserPath) {
205
+ return {
206
+ ...browser,
207
+ error: `agent-browser ${AGENT_BROWSER_VERSION} is required; restart Farming through its launcher to prepare startup dependencies`,
208
+ runtimeErrorCode: 'NOT_FOUND',
209
+ };
210
+ }
211
+ const verification = await verifyExecutable(agentBrowserPath, AGENT_BROWSER_VERSION, {
212
+ execFile: options.execFile,
213
+ env: options.env || process.env,
214
+ platform: options.platform || process.platform,
215
+ useConfiguredLoader: true,
216
+ });
217
+ if (!verification.valid) {
218
+ return {
219
+ ...browser,
220
+ error: `The Farming-managed agent-browser must be version ${AGENT_BROWSER_VERSION}`,
221
+ runtimeErrorCode: 'VERSION_MISMATCH',
222
+ };
223
+ }
224
+ return {
225
+ ...browser,
226
+ agentBrowserPath,
227
+ agentBrowserVersion: AGENT_BROWSER_VERSION,
228
+ agentBrowserSource: 'managed',
229
+ };
87
230
  }
88
231
 
89
232
  module.exports = {
90
233
  discoverBrowserExecutable,
234
+ discoverBrowserExecutables,
235
+ discoverBrowserRuntime,
236
+ managedAgentBrowserPath,
237
+ normalizeExternalCdpUrl,
91
238
  };