farming-code 2.2.19 → 2.2.22

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 (53) hide show
  1. package/README.md +5 -5
  2. package/README.zh_cn.md +5 -5
  3. package/THIRD_PARTY_NOTICES.md +5 -8
  4. package/backend/acp/client-services.js +37 -5
  5. package/backend/acp/patch-decisions.js +19 -3
  6. package/backend/acp-runtime.js +1201 -198
  7. package/backend/acp-transcript.js +66 -0
  8. package/backend/agent-lifecycle-journal.js +133 -0
  9. package/backend/agent-manager.js +2715 -354
  10. package/backend/agent-order.js +8 -4
  11. package/backend/agent-runtime-binding.js +1 -0
  12. package/backend/bundle-update-helper.js +5 -1
  13. package/backend/config-manager.js +16 -0
  14. package/backend/control-api.js +146 -20
  15. package/backend/farming-app-cli.js +335 -10
  16. package/backend/farming-session-store.js +283 -36
  17. package/backend/json-cli-runtime.js +120 -6
  18. package/backend/npm-update-helper.js +97 -18
  19. package/backend/provider-adapters.js +18 -6
  20. package/backend/review-session-service.js +31 -17
  21. package/backend/server.js +337 -103
  22. package/backend/storage-layout.js +10 -0
  23. package/backend/terminal-screen-worker-pool.js +15 -6
  24. package/backend/theme-manager.js +23 -5
  25. package/backend/update-service.js +239 -27
  26. package/backend/usage-monitor.js +11 -0
  27. package/backend/workspace-file-router.js +9 -3
  28. package/backend/workspace-file-service.js +292 -62
  29. package/dist/acp/codex-acp-1.1.4.js +32 -1
  30. package/dist/assets/App-CmZdNavW.js +210 -0
  31. package/dist/assets/{FileEditorMarkdownPreview-R0jwwLME.js → FileEditorMarkdownPreview-KbnE8VC8.js} +1 -1
  32. package/dist/assets/FileEditorPane-5_HUejjd.js +2 -0
  33. package/dist/assets/IconGlyphs-C2WFcoJS.js +1 -0
  34. package/dist/assets/ProjectFilesSection-C1RAJWOE.js +12 -0
  35. package/dist/assets/{ReviewPage-DBKNOWsJ.js → ReviewPage-L1vh1Ggs.js} +3 -3
  36. package/dist/assets/code-dark-BKtvf0Xc.css +1 -0
  37. package/dist/assets/file-icons-CHWt5nSy.js +1 -0
  38. package/dist/assets/{index-DQdtPfCk.js → index-RvUiMpGP.js} +2 -2
  39. package/dist/assets/main-XMXgyJJt.css +1 -0
  40. package/dist/assets/workspace-view-state-BqB4HM7r.js +7 -0
  41. package/dist/index.html +1 -1
  42. package/frontend/skins/crt/app.js +365 -107
  43. package/frontend/skins/crt/index.html +5 -10
  44. package/frontend/skins/crt/styles/billing.css +6 -1
  45. package/package.json +9 -3
  46. package/dist/assets/App-CbXYBccJ.js +0 -210
  47. package/dist/assets/FileEditorPane-CJs6kfZO.js +0 -2
  48. package/dist/assets/IconGlyphs-CZzDr5o2.js +0 -1
  49. package/dist/assets/ProjectFilesSection-DWCV07lA.js +0 -12
  50. package/dist/assets/code-dark-qIBiLBWV.css +0 -1
  51. package/dist/assets/file-icons-DpbrD5ZP.js +0 -1
  52. package/dist/assets/main-yzZEfgft.css +0 -1
  53. package/dist/assets/workspace-view-state-BpPINu84.js +0 -7
@@ -83,14 +83,16 @@ function reorderedProjectAgentOrders(agents, agentId, beforeAgentId = '', afterA
83
83
  }
84
84
 
85
85
  const updates = new Map();
86
- const orderAt = index => finiteOrder(fullOrder[index] && fullOrder[index].projectOrder) ?? 0;
86
+ const orderAt = index => {
87
+ const agent = fullOrder[index];
88
+ return agent ? (updates.get(agent.id) ?? finiteOrder(agent.projectOrder) ?? 0) : 0;
89
+ };
87
90
  let upper = fullInsertIndex > 0 ? orderAt(fullInsertIndex - 1) : null;
88
91
  let lower = fullInsertIndex < fullOrder.length ? orderAt(fullInsertIndex) : null;
89
92
  if (upper !== null && lower !== null && upper - lower <= 1) {
90
93
  fullOrder.forEach((agent, index) => {
91
94
  const order = (fullOrder.length - index) * AGENT_ORDER_STEP;
92
95
  if (projectOrder(agent) !== order) updates.set(agent.id, order);
93
- agent.projectOrder = order;
94
96
  });
95
97
  upper = fullInsertIndex > 0 ? orderAt(fullInsertIndex - 1) : null;
96
98
  lower = fullInsertIndex < fullOrder.length ? orderAt(fullInsertIndex) : null;
@@ -127,14 +129,16 @@ function reorderedPinnedAgentOrders(agents, agentId, beforeAgentId = '', afterAg
127
129
  }
128
130
 
129
131
  const updates = new Map();
130
- const orderAt = index => finiteOrder(pinned[index] && pinned[index].pinnedOrder) ?? 0;
132
+ const orderAt = index => {
133
+ const agent = pinned[index];
134
+ return agent ? (updates.get(agent.id) ?? finiteOrder(agent.pinnedOrder) ?? 0) : 0;
135
+ };
131
136
  let previous = insertIndex > 0 ? orderAt(insertIndex - 1) : null;
132
137
  let next = insertIndex < pinned.length ? orderAt(insertIndex) : null;
133
138
  if (previous !== null && next !== null && next - previous <= 1) {
134
139
  pinned.forEach((agent, index) => {
135
140
  const order = (index + 1) * AGENT_ORDER_STEP;
136
141
  if (pinnedOrder(agent) !== order) updates.set(agent.id, order);
137
- agent.pinnedOrder = order;
138
142
  });
139
143
  previous = insertIndex > 0 ? orderAt(insertIndex - 1) : null;
140
144
  next = insertIndex < pinned.length ? orderAt(insertIndex) : null;
@@ -11,6 +11,7 @@ function acpBinding(source = {}) {
11
11
  error: source.error || source.acpError || '',
12
12
  stopReason: source.stopReason || source.acpStopReason || '',
13
13
  supportsSteer: source.supportsSteer === true,
14
+ supportsFork: source.supportsFork === true,
14
15
  pendingPermission: source.pendingPermission || source.acpPendingPermission || null,
15
16
  pendingPermissions: source.pendingPermissions || source.acpPendingPermissions || [],
16
17
  pendingElicitation: source.pendingElicitation || source.acpPendingElicitation || null,
@@ -71,17 +71,21 @@ function runInstaller(payload) {
71
71
 
72
72
  async function runBundleUpdate(rawPayload) {
73
73
  const payload = validatePayload(rawPayload);
74
- writeJsonAtomic(payload.stateFile, stateFor(payload, 'installing'));
74
+ writeJsonAtomic(payload.stateFile, stateFor(payload, 'restarting', {
75
+ preparedAt: payload.preparedAt,
76
+ }));
75
77
  await new Promise(resolve => setTimeout(resolve, 1_000));
76
78
  try {
77
79
  await runInstaller(payload);
78
80
  writeJsonAtomic(payload.stateFile, stateFor(payload, 'succeeded', {
81
+ preparedAt: payload.preparedAt,
79
82
  completedAt: new Date().toISOString(),
80
83
  }));
81
84
  appendLog(payload.logPath, `Farming updated to ${payload.version}`);
82
85
  } catch (error) {
83
86
  const message = error && error.message ? error.message : String(error);
84
87
  writeJsonAtomic(payload.stateFile, stateFor(payload, 'failed', {
88
+ preparedAt: payload.preparedAt,
85
89
  error: message,
86
90
  completedAt: new Date().toISOString(),
87
91
  }));
@@ -498,6 +498,22 @@ class ConfigManager {
498
498
  return normalized;
499
499
  }
500
500
 
501
+ setProjectName(workspace, name) {
502
+ const normalized = this.normalizeProjectNames({ [workspace]: name });
503
+ const entry = Object.entries(normalized)[0];
504
+ if (!entry) throw new Error('Project workspace and name are required');
505
+ const [normalizedWorkspace, normalizedName] = entry;
506
+ this.settings.projectNames = {
507
+ ...this.settings.projectNames,
508
+ [normalizedWorkspace]: normalizedName,
509
+ };
510
+ this.writeSettingsFile();
511
+ return {
512
+ workspace: normalizedWorkspace,
513
+ name: normalizedName,
514
+ };
515
+ }
516
+
501
517
  normalizeAppearance(appearance) {
502
518
  return ['system', 'light', 'dark'].includes(appearance) ? appearance : 'system';
503
519
  }
@@ -1,4 +1,5 @@
1
1
  const express = require('express');
2
+ const crypto = require('crypto');
2
3
  const { runtimeKind } = require('./agent-runtime-binding');
3
4
  const { terminalInputReady } = require('./terminal-status');
4
5
 
@@ -19,6 +20,21 @@ function normalizeTail(value, fallback = 4000) {
19
20
  return Math.max(0, Math.min(100000, Math.floor(tail)));
20
21
  }
21
22
 
23
+ function stableJsonValue(value) {
24
+ if (Array.isArray(value)) return value.map(stableJsonValue);
25
+ if (!value || typeof value !== 'object') return value;
26
+ return Object.keys(value).sort().reduce((result, key) => {
27
+ result[key] = stableJsonValue(value[key]);
28
+ return result;
29
+ }, {});
30
+ }
31
+
32
+ function requestSignature(value) {
33
+ return crypto.createHash('sha256')
34
+ .update(JSON.stringify(stableJsonValue(value)))
35
+ .digest('hex');
36
+ }
37
+
22
38
  function terminalReadinessOptions(agent) {
23
39
  return {
24
40
  command: agent.command,
@@ -104,6 +120,7 @@ function waitForTerminalInputReadiness(agentManager, agentId, options = {}) {
104
120
 
105
121
  function createControlRouter(agentManager, options = {}) {
106
122
  const router = express.Router();
123
+ const createRequestAdmissions = new Map();
107
124
  const notifyUpdate = typeof options.notifyUpdate === 'function' ? options.notifyUpdate : () => {};
108
125
  const initialInputTimeoutMs = Number.isFinite(options.initialInputTimeoutMs)
109
126
  ? Math.max(1, options.initialInputTimeoutMs)
@@ -138,13 +155,33 @@ function createControlRouter(agentManager, options = {}) {
138
155
  const workspace = typeof body.workspace === 'string' ? body.workspace : null;
139
156
  const task = typeof body.task === 'string' ? body.task.trim() : '';
140
157
  const initialInput = typeof body.initialInput === 'string' ? body.initialInput : task;
158
+ const createRequestId = typeof body.requestId === 'string' && body.requestId.trim()
159
+ ? body.requestId
160
+ : req.get('Idempotency-Key') || '';
161
+ const controlRequestSignature = requestSignature(body);
141
162
 
142
163
  if (!command) {
143
164
  res.status(400).json({ error: 'command is required' });
144
165
  return;
145
166
  }
146
167
 
147
- agentManager.startAgent(command, workspace, (agentId, error) => {
168
+ const pendingRequest = createRequestId
169
+ ? createRequestAdmissions.get(createRequestId)
170
+ : null;
171
+ if (pendingRequest) {
172
+ if (pendingRequest.signature !== controlRequestSignature) {
173
+ res.status(409).json({
174
+ error: `Create request ${createRequestId} is already in progress with different parameters`,
175
+ });
176
+ return;
177
+ }
178
+ void pendingRequest.promise.then(outcome => {
179
+ res.status(outcome.status).json(outcome.body);
180
+ });
181
+ return;
182
+ }
183
+
184
+ agentManager.startAgent(command, workspace, (agentId, error, metadata = {}) => {
148
185
  if (error) {
149
186
  res.status(400).json({ error });
150
187
  return;
@@ -155,11 +192,36 @@ function createControlRouter(agentManager, options = {}) {
155
192
  return;
156
193
  }
157
194
 
158
- void (async () => {
195
+ if (metadata.deduplicated === true) {
196
+ const recorded = metadata.createResult?.controlApi;
197
+ if (recorded && Number.isInteger(recorded.status) && recorded.body) {
198
+ res.status(recorded.status).json(recorded.body);
199
+ return;
200
+ }
201
+ const admitted = createRequestId
202
+ ? createRequestAdmissions.get(createRequestId)
203
+ : null;
204
+ if (admitted) {
205
+ void admitted.promise.then(outcome => {
206
+ res.status(outcome.status).json(outcome.body);
207
+ });
208
+ return;
209
+ }
210
+ res.status(409).json({
211
+ error: 'Create succeeded, but the initial-input outcome is unknown; input was not replayed',
212
+ code: 'create-result-unknown',
213
+ agentId,
214
+ });
215
+ return;
216
+ }
217
+
218
+ const deliveryPromise = (async () => {
159
219
  if (!initialInput) {
160
220
  notifyUpdate();
161
- res.status(201).json({ agentId, initialInputDelivered: false });
162
- return;
221
+ return {
222
+ status: 201,
223
+ body: { agentId, initialInputDelivered: false },
224
+ };
163
225
  }
164
226
 
165
227
  const started = findAgent(agentManager.getState(), agentId);
@@ -170,8 +232,10 @@ function createControlRouter(agentManager, options = {}) {
170
232
  if (structuredRuntime) {
171
233
  await agentManager.sendComposerMessage(agentId, initialInput);
172
234
  notifyUpdate();
173
- res.status(201).json({ agentId, initialInputDelivered: true, inputMode: 'structured' });
174
- return;
235
+ return {
236
+ status: 201,
237
+ body: { agentId, initialInputDelivered: true, inputMode: 'structured' },
238
+ };
175
239
  }
176
240
 
177
241
  const readiness = await waitForTerminalInputReadiness(agentManager, agentId, {
@@ -204,21 +268,70 @@ function createControlRouter(agentManager, options = {}) {
204
268
  throw Object.assign(new Error(`Initial Terminal task was not delivered: ${reason}`), { code: reason });
205
269
  }
206
270
  notifyUpdate();
207
- res.status(201).json({ agentId, initialInputDelivered: true, inputMode: 'terminal' });
208
- })().catch((deliveryError) => {
271
+ return {
272
+ status: 201,
273
+ body: { agentId, initialInputDelivered: true, inputMode: 'terminal' },
274
+ };
275
+ })().catch(deliveryError => {
209
276
  const status = deliveryError?.code === 'initial-input-timeout' ? 504 : 409;
210
- res.status(status).json({
211
- error: deliveryError?.message || 'Initial input delivery failed',
212
- code: deliveryError?.code || 'initial-input-failed',
213
- agentId,
214
- initialInputDelivered: false,
215
- });
277
+ return {
278
+ status,
279
+ body: {
280
+ error: deliveryError?.message || 'Initial input delivery failed',
281
+ code: deliveryError?.code || 'initial-input-failed',
282
+ agentId,
283
+ initialInputDelivered: false,
284
+ },
285
+ };
286
+ });
287
+ const durableDeliveryPromise = deliveryPromise.then(outcome => {
288
+ if (!createRequestId) return outcome;
289
+ let persisted;
290
+ try {
291
+ persisted = agentManager.recordCreateRequestResult(
292
+ agentId,
293
+ createRequestId,
294
+ { controlApi: outcome },
295
+ );
296
+ } catch (error) {
297
+ persisted = { error: error?.message || 'Create result persistence failed' };
298
+ }
299
+ if (persisted?.error) {
300
+ return {
301
+ status: 409,
302
+ body: {
303
+ error: `${persisted.error}; Create outcome was not persisted and initial input was not replayed`,
304
+ code: 'create-result-not-durable',
305
+ agentId,
306
+ initialInputDelivered: outcome.body?.initialInputDelivered === true,
307
+ createResultDurable: false,
308
+ },
309
+ };
310
+ }
311
+ return outcome;
312
+ });
313
+ if (createRequestId) {
314
+ const admission = {
315
+ signature: controlRequestSignature,
316
+ promise: durableDeliveryPromise,
317
+ };
318
+ createRequestAdmissions.set(createRequestId, admission);
319
+ void durableDeliveryPromise.finally(() => {
320
+ if (createRequestAdmissions.get(createRequestId) === admission) {
321
+ createRequestAdmissions.delete(createRequestId);
322
+ }
323
+ }).catch(() => {});
324
+ }
325
+ void durableDeliveryPromise.then(outcome => {
326
+ res.status(outcome.status).json(outcome.body);
216
327
  });
217
328
  }, {
218
329
  wantsMain: false,
219
330
  parentAgentId: typeof body.parentAgentId === 'string' ? body.parentAgentId : '',
220
331
  task,
221
332
  source: 'control-cli',
333
+ createRequestId,
334
+ createInitialInputSignature: requestSignature(initialInput),
222
335
  agentRuntimeMode: ['json', 'acp', 'chat'].includes(body.agentRuntimeMode) ? body.agentRuntimeMode : 'terminal',
223
336
  acpHistoryMode: body.acpHistoryMode === 'resume' ? 'resume' : 'load',
224
337
  providerSessionTitle: typeof body.providerSessionTitle === 'string' ? body.providerSessionTitle : '',
@@ -306,15 +419,28 @@ function createControlRouter(agentManager, options = {}) {
306
419
 
307
420
  router.delete('/agents/:agentId', async (req, res) => {
308
421
  const agentId = req.params.agentId;
309
- const state = agentManager.getState();
310
- if (!findAgent(state, agentId)) {
311
- res.status(404).json({ error: 'agent not found' });
422
+ let requested;
423
+ try {
424
+ requested = await agentManager.requestKillAgent(agentId);
425
+ } catch (error) {
426
+ res.status(503).json({
427
+ error: error?.message || 'Agent lifecycle recovery is unavailable',
428
+ retryable: true,
429
+ });
430
+ return;
431
+ }
432
+ const result = requested.result;
433
+ if (result?.error) {
434
+ res.status(409).json({ error: result.error });
435
+ return;
436
+ }
437
+ if (result?.accepted) {
438
+ void requested.completion.finally(() => notifyUpdate()).catch(() => {});
439
+ res.status(202).json(result);
312
440
  return;
313
441
  }
314
-
315
- await agentManager.killAgent(agentId);
316
442
  notifyUpdate();
317
- res.json({ success: true });
443
+ res.json({ success: true, agentId, ...result });
318
444
  });
319
445
 
320
446
  return router;