farming-code 2.2.28 → 2.2.29

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 (74) hide show
  1. package/README.md +9 -31
  2. package/README.zh_cn.md +8 -30
  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 +37 -2
  7. package/backend/acp-transcript.js +174 -9
  8. package/backend/agent-manager.js +27 -2
  9. package/backend/config-manager.js +44 -0
  10. package/backend/data/LICENSE.agent-browser +201 -0
  11. package/backend/data/runtime-dependency-manifest.json +199 -0
  12. package/backend/data/runtime-dependency-sources.json +5 -0
  13. package/backend/executable-discovery.js +7 -0
  14. package/backend/farming-agent-bootstrap.zh_cn.md +4 -2
  15. package/backend/farming-app-cli.js +160 -64
  16. package/backend/farming-cli.js +5 -3
  17. package/backend/index-html.js +13 -0
  18. package/backend/main-agent-skills.js +6 -10
  19. package/backend/npm-update-helper.js +43 -6
  20. package/backend/provider-adapters.js +10 -0
  21. package/backend/runtime-dependency-manager.js +759 -0
  22. package/backend/runtime-executable-invocation.js +20 -0
  23. package/backend/server-process-identity.js +47 -0
  24. package/backend/server.js +140 -157
  25. package/backend/slash-command-discovery.js +138 -1
  26. package/backend/storage-layout.js +15 -0
  27. package/backend/update-service.js +27 -1
  28. package/config/farming.install.env.example +4 -0
  29. package/dist/acp/LICENSE.claude-agent-acp +191 -0
  30. package/dist/acp/LICENSE.claude-agent-sdk +1 -0
  31. package/dist/acp/claude-agent-acp-0.59.0.mjs +41606 -0
  32. package/dist/assets/{App-CM7Cy-l4.css → App-BKNUILuL.css} +1 -1
  33. package/dist/assets/App-Ex2oVVXO.js +693 -0
  34. package/dist/assets/{FileEditorMarkdownPreview-DYOMm-b1.js → FileEditorMarkdownPreview-Cs9cLv8a.js} +1 -1
  35. package/dist/assets/FileEditorPane-CNtuGjQ3.js +2 -0
  36. package/dist/assets/IconGlyphs-BClgag6E.js +1 -0
  37. package/dist/assets/ProjectFilesSection-Cf2BEcAN.js +12 -0
  38. package/dist/assets/ReviewPage-BRuxFaH5.js +3 -0
  39. package/dist/assets/code-dark-BEZTQwYZ.css +1 -0
  40. package/dist/assets/{index-BzQO3pUr.js → index-CQNaZc3l.js} +2 -2
  41. package/dist/assets/main-DK9Sy7-c.css +1 -0
  42. package/dist/assets/{workspace-editor-monaco-tXA-udGx.js → workspace-editor-monaco-CJNwbpCy.js} +1 -1
  43. package/dist/assets/workspace-editor-monaco-CO9w8YV0.js +1 -0
  44. package/dist/assets/workspace-view-state-BjKPYp0E.js +7 -0
  45. package/dist/index.html +32 -3
  46. package/extensions/browser/backend/agent-browser-runtime.js +1041 -0
  47. package/extensions/browser/backend/browser-mcp-server.js +352 -0
  48. package/extensions/browser/backend/browser-resource-manager.js +899 -114
  49. package/extensions/browser/backend/browser-resource-store.js +47 -6
  50. package/extensions/browser/backend/browser-router.js +7 -2
  51. package/extensions/browser/backend/executable-discovery.js +146 -13
  52. package/extensions/browser/bin/farming-browser +384 -30
  53. package/extensions/browser/frontend/BrowserSidebarPortals.tsx +17 -19
  54. package/extensions/browser/frontend/BrowserViewer.tsx +217 -46
  55. package/extensions/browser/frontend/browser.css +68 -17
  56. package/extensions/browser/frontend/types.ts +6 -0
  57. package/extensions/browser/frontend/useBrowserResources.ts +10 -3
  58. package/index.html +31 -2
  59. package/package.json +10 -6
  60. package/shared/backend-connection-status.d.ts +12 -0
  61. package/shared/backend-connection-status.js +35 -4
  62. package/backend/agent-activity.js +0 -35
  63. package/dist/assets/App-CPG4WcjO.js +0 -658
  64. package/dist/assets/FileEditorPane-DXXq82aM.js +0 -2
  65. package/dist/assets/IconGlyphs-DrdPjYXr.js +0 -1
  66. package/dist/assets/ProjectFilesSection-CP66XVPl.js +0 -12
  67. package/dist/assets/ReviewPage-w21MEPD6.js +0 -3
  68. package/dist/assets/code-dark-BDjwj7Um.css +0 -1
  69. package/dist/assets/main-BlVUXfxa.css +0 -1
  70. package/dist/assets/workspace-editor-monaco-CFEfFNcA.js +0 -1
  71. package/dist/assets/workspace-view-state-BS_V8JVs.js +0 -7
  72. package/extensions/browser/backend/cdp-browser-runtime.js +0 -648
  73. package/extensions/browser/backend/cdp-client.js +0 -167
  74. /package/dist/acp/{codex-acp-1.1.4.js → codex-acp-1.1.4.mjs} +0 -0
@@ -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,13 @@ 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
+ }
16
21
  });
17
22
 
18
23
  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,12 +99,80 @@ 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
+ if (platform === 'darwin') return uniqueExecutables(macBrowserCandidates());
113
+ if (platform === 'linux') return uniqueExecutables(linuxBrowserCandidates());
114
+ if (platform === 'win32') return uniqueExecutables(windowsBrowserCandidates(env));
115
+ return [];
116
+ }
117
+
118
+ function normalizeExternalCdpUrl(value) {
119
+ const input = String(value || '').trim();
120
+ if (!input) return '';
121
+ try {
122
+ const url = new URL(input);
123
+ if (!['http:', 'https:', 'ws:', 'wss:'].includes(url.protocol)) return '';
124
+ if (url.username || url.password || url.search) return '';
125
+ const hostname = url.hostname.toLowerCase();
126
+ if (!['localhost', '127.0.0.1', '[::1]'].includes(hostname)) return '';
127
+ url.hash = '';
128
+ return url.href;
129
+ } catch {
130
+ return '';
131
+ }
74
132
  }
75
133
 
76
134
  function discoverBrowserExecutable(options = {}) {
77
135
  const env = options.env || process.env;
78
136
  const platform = options.platform || process.platform;
137
+ const source = String(options.source || '').trim();
138
+ if (source === 'external-cdp') {
139
+ const cdpUrl = normalizeExternalCdpUrl(options.externalCdpUrl);
140
+ return cdpUrl
141
+ ? { kind: 'external-cdp', path: '', cdpUrl }
142
+ : {
143
+ kind: 'external-cdp',
144
+ path: '',
145
+ cdpUrl: '',
146
+ error: 'External CDP must be a loopback http(s) or ws(s) endpoint without credentials or query parameters',
147
+ };
148
+ }
149
+ if (source === 'system') {
150
+ const configuredPath = String(options.executablePath || '').trim();
151
+ if (configuredPath) {
152
+ const configuredKind = String(options.executableKind || 'custom').trim() || 'custom';
153
+ return executable(path.resolve(configuredPath), configuredKind) || {
154
+ kind: configuredKind,
155
+ path: path.resolve(configuredPath),
156
+ error: 'The selected Chromium browser is no longer available',
157
+ };
158
+ }
159
+ if (platform === 'darwin') return discoverMacBrowser();
160
+ if (platform === 'linux') return discoverLinuxBrowser();
161
+ if (platform === 'win32') return discoverWindowsBrowser(env);
162
+ return null;
163
+ }
164
+ const externalCdpInput = String(options.externalCdpUrl || env.FARMING_BROWSER_CDP_URL || '').trim();
165
+ if (externalCdpInput) {
166
+ const cdpUrl = normalizeExternalCdpUrl(externalCdpInput);
167
+ return cdpUrl
168
+ ? { kind: 'external-cdp', path: '', cdpUrl }
169
+ : {
170
+ kind: 'external-cdp',
171
+ path: '',
172
+ cdpUrl: '',
173
+ error: 'FARMING_BROWSER_CDP_URL must be a loopback http(s) or ws(s) CDP endpoint without credentials or query parameters',
174
+ };
175
+ }
79
176
  const configured = String(options.executablePath || env.FARMING_BROWSER_EXECUTABLE || '').trim();
80
177
  if (configured) {
81
178
  return executable(path.resolve(configured), 'custom');
@@ -86,6 +183,42 @@ function discoverBrowserExecutable(options = {}) {
86
183
  return null;
87
184
  }
88
185
 
186
+ async function discoverBrowserRuntime(options = {}) {
187
+ const browser = discoverBrowserExecutable(options);
188
+ if (!browser || browser.error) return browser;
189
+ const agentBrowserPath = managedAgentBrowserPath(options);
190
+ if (!agentBrowserPath) {
191
+ return {
192
+ ...browser,
193
+ error: `agent-browser ${AGENT_BROWSER_VERSION} is required; restart Farming through its launcher to prepare startup dependencies`,
194
+ runtimeErrorCode: 'NOT_FOUND',
195
+ };
196
+ }
197
+ const verification = await verifyExecutable(agentBrowserPath, AGENT_BROWSER_VERSION, {
198
+ execFile: options.execFile,
199
+ env: options.env || process.env,
200
+ platform: options.platform || process.platform,
201
+ useConfiguredLoader: true,
202
+ });
203
+ if (!verification.valid) {
204
+ return {
205
+ ...browser,
206
+ error: `The Farming-managed agent-browser must be version ${AGENT_BROWSER_VERSION}`,
207
+ runtimeErrorCode: 'VERSION_MISMATCH',
208
+ };
209
+ }
210
+ return {
211
+ ...browser,
212
+ agentBrowserPath,
213
+ agentBrowserVersion: AGENT_BROWSER_VERSION,
214
+ agentBrowserSource: 'managed',
215
+ };
216
+ }
217
+
89
218
  module.exports = {
90
219
  discoverBrowserExecutable,
220
+ discoverBrowserExecutables,
221
+ discoverBrowserRuntime,
222
+ managedAgentBrowserPath,
223
+ normalizeExternalCdpUrl,
91
224
  };