farming-code 2.2.13 → 2.2.16

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 (119) hide show
  1. package/README.md +81 -105
  2. package/README.zh_cn.md +82 -106
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/backend/acp/packaged-codex-acp.js +13 -0
  5. package/backend/acp-runtime.js +192 -14
  6. package/backend/agent-manager.js +176 -510
  7. package/backend/agent-runtime-binding.js +9 -55
  8. package/backend/chat-runtime.js +28 -0
  9. package/backend/codex-transcript.js +2 -1
  10. package/backend/config-manager.js +22 -14
  11. package/backend/control-api.js +3 -1
  12. package/backend/farming-app-cli.js +13 -0
  13. package/backend/farming-session-store.js +2 -1
  14. package/backend/npm-update-helper.js +65 -7
  15. package/backend/provider-adapters.js +6 -2
  16. package/backend/server.js +89 -104
  17. package/backend/storage-layout.js +0 -16
  18. package/backend/update-service.js +94 -5
  19. package/dist/acp/LICENSE.codex-acp +190 -0
  20. package/dist/acp/codex-acp-1.1.4.js +30718 -0
  21. package/dist/assets/App-y6p2yRar.js +206 -0
  22. package/dist/assets/{FileEditorMarkdownPreview-UlVd0O4D.js → FileEditorMarkdownPreview-R0jwwLME.js} +4 -4
  23. package/dist/assets/FileEditorPane-DNy3LAqS.js +2 -0
  24. package/dist/assets/{IconGlyphs-AU22gPDY.js → IconGlyphs-BsUfqpKN.js} +1 -1
  25. package/dist/assets/ProjectFilesSection-BhDEUVHA.js +12 -0
  26. package/dist/assets/{ReviewPage-CeDKDyZh.js → ReviewPage-ChtByBe3.js} +1 -1
  27. package/dist/assets/{abnfDiagram-VRR7QNED-BgawzDQT.js → abnfDiagram-VRR7QNED-nnglrXFi.js} +1 -1
  28. package/dist/assets/{architectureDiagram-ZJ3FMSHR-DUShwPg0.js → architectureDiagram-ZJ3FMSHR-BEXMaFfY.js} +1 -1
  29. package/dist/assets/{blockDiagram-677ZJIJ3-CeGZviXR.js → blockDiagram-677ZJIJ3-1mCezSBk.js} +1 -1
  30. package/dist/assets/{c4Diagram-LMCZKHZV-CLhYht6S.js → c4Diagram-LMCZKHZV-DhTiI6Uv.js} +1 -1
  31. package/dist/assets/channel-BVWNatUB.js +1 -0
  32. package/dist/assets/{chunk-32BRIVSS-LFYpT-Cu.js → chunk-32BRIVSS-CCdsKd73.js} +1 -1
  33. package/dist/assets/{chunk-52WLFC77-BH6jDw9J.js → chunk-52WLFC77-Gncjk4LF.js} +1 -1
  34. package/dist/assets/{chunk-C7G6YPKG-B_mIUlb7.js → chunk-C7G6YPKG-CiDVUP82.js} +1 -1
  35. package/dist/assets/{chunk-EX3LRPZG-C63hVuAJ.js → chunk-EX3LRPZG-z6gXP2_N.js} +1 -1
  36. package/dist/assets/{chunk-FWX5IMBZ-CboX-WF_.js → chunk-FWX5IMBZ-DMZBccQa.js} +2 -2
  37. package/dist/assets/{chunk-HOUHSVGY-BOgSd6Un.js → chunk-HOUHSVGY-D8tIeoPk.js} +1 -1
  38. package/dist/assets/{chunk-ICXQ74PX-C0K-pgZt.js → chunk-ICXQ74PX-b6oQ2Q17.js} +1 -1
  39. package/dist/assets/{chunk-MOJQB5TN-CVMfwN_7.js → chunk-MOJQB5TN-DAVH_p2a.js} +1 -1
  40. package/dist/assets/{chunk-OGEWGWER-DLT3pxyw.js → chunk-OGEWGWER-DxoiMCI-.js} +1 -1
  41. package/dist/assets/{chunk-PUDLZKDR-Cd079yIQ.js → chunk-PUDLZKDR-NsOWh7HM.js} +1 -1
  42. package/dist/assets/{chunk-Q4XR5HBZ-D0jHa6SO.js → chunk-Q4XR5HBZ-Di4H7L21.js} +1 -1
  43. package/dist/assets/{chunk-V7JOEXUC-CVDtR75H.js → chunk-V7JOEXUC-DttpZg9Y.js} +1 -1
  44. package/dist/assets/{chunk-VAUOI2AC-B-W2_9Ot.js → chunk-VAUOI2AC-UHNr_yhX.js} +1 -1
  45. package/dist/assets/{chunk-VR4S4FIN-FD3kWh32.js → chunk-VR4S4FIN-D-Fmtd4Z.js} +1 -1
  46. package/dist/assets/{chunk-WYO6CB5R-C2bGaS5r.js → chunk-WYO6CB5R-CUxMtfK2.js} +4 -4
  47. package/dist/assets/{chunk-ZGVPDNZ5-ymgts0zo.js → chunk-ZGVPDNZ5-CoV2QEl0.js} +1 -1
  48. package/dist/assets/classDiagram-OUVF2IWQ-Db0ib5iE.js +1 -0
  49. package/dist/assets/classDiagram-v2-EOCWNBFH-Bkf69XF9.js +1 -0
  50. package/dist/assets/code-dark-DKBGxoPX.css +1 -0
  51. package/dist/assets/{cynefinDiagram-TSTJHNR4-CwpobXIl.js → cynefinDiagram-TSTJHNR4-0xPI92Lg.js} +1 -1
  52. package/dist/assets/{dagre-VKFMJZFB-Dkhlfjna.js → dagre-VKFMJZFB-DfuMG4gU.js} +1 -1
  53. package/dist/assets/{diagram-FQU43EPY-hA8dicp6.js → diagram-FQU43EPY-CT00bLZ6.js} +1 -1
  54. package/dist/assets/{diagram-G47NLZAW-BTY6JTzO.js → diagram-G47NLZAW-Xbh7zXUx.js} +1 -1
  55. package/dist/assets/{diagram-NH7WQ7WH-xkaHHX9M.js → diagram-NH7WQ7WH-SRDVPfn7.js} +1 -1
  56. package/dist/assets/{diagram-OA4YK3LP-gk_fg9UU.js → diagram-OA4YK3LP-CQRli4Np.js} +1 -1
  57. package/dist/assets/{diagram-WEI45ONY-BvC8xd6T.js → diagram-WEI45ONY-DMf17WQi.js} +1 -1
  58. package/dist/assets/{ebnfDiagram-CCIWWBDH-CR7RaNat.js → ebnfDiagram-CCIWWBDH-DhpFkgJ5.js} +1 -1
  59. package/dist/assets/{erDiagram-Q63AITRT-_M-JFPbj.js → erDiagram-Q63AITRT-1IwxWmJo.js} +1 -1
  60. package/dist/assets/file-icons-DpbrD5ZP.js +1 -0
  61. package/dist/assets/flowDiagram-23GEKE2U-DYFo2xmB.js +1 -0
  62. package/dist/assets/{ganttDiagram-NO4QXBWP-Bl8P1yf_.js → ganttDiagram-NO4QXBWP-BNCdfyr1.js} +1 -1
  63. package/dist/assets/{gitGraphDiagram-IHSO6WYX-DoHa5ysz.js → gitGraphDiagram-IHSO6WYX-pbyWZuiX.js} +1 -1
  64. package/dist/assets/{index-CYZKdoqi.js → index-iKBvPjWS.js} +2 -2
  65. package/dist/assets/{infoDiagram-FWYZ7A6U-DSFcSuUs.js → infoDiagram-FWYZ7A6U-BcTOgDbe.js} +1 -1
  66. package/dist/assets/{ishikawaDiagram-FXEZZL3T-DdkqullI.js → ishikawaDiagram-FXEZZL3T-DCdvGKrn.js} +1 -1
  67. package/dist/assets/{journeyDiagram-5HDEW3XC-B4hRnQJr.js → journeyDiagram-5HDEW3XC-BZ-1q_IT.js} +1 -1
  68. package/dist/assets/{kanban-definition-HUTT4EX6-tm2FZB2E.js → kanban-definition-HUTT4EX6-BIgejCjj.js} +1 -1
  69. package/dist/assets/{line-CmsjK4kn.js → line-CfptFMmX.js} +1 -1
  70. package/dist/assets/main-CdkLOXYl.css +1 -0
  71. package/dist/assets/{mermaid.core-C7UT_EEL.js → mermaid.core-gSpX30pV.js} +3 -3
  72. package/dist/assets/{mindmap-definition-LN4V7U3C-DplfjGms.js → mindmap-definition-LN4V7U3C-BKBaI4LW.js} +1 -1
  73. package/dist/assets/{pegDiagram-2B236MQR-lu3SdkQs.js → pegDiagram-2B236MQR-sBvDY3VX.js} +1 -1
  74. package/dist/assets/{pieDiagram-ENE6RG2P-3fHbmDf9.js → pieDiagram-ENE6RG2P-DWlWcu08.js} +1 -1
  75. package/dist/assets/{quadrantDiagram-ABIIQ3AL-a5qoWz-X.js → quadrantDiagram-ABIIQ3AL-DZutUd3J.js} +1 -1
  76. package/dist/assets/{railroadDiagram-RFXS5EU6-D2TOuXW9.js → railroadDiagram-RFXS5EU6-DQb3lXGM.js} +1 -1
  77. package/dist/assets/{requirementDiagram-TGXJPOKE-LQBWpUvd.js → requirementDiagram-TGXJPOKE-DqFGXu2e.js} +1 -1
  78. package/dist/assets/{sankeyDiagram-HTMAVEWB-B1ohYoyK.js → sankeyDiagram-HTMAVEWB-d7ECahnO.js} +1 -1
  79. package/dist/assets/{sequenceDiagram-DBY2YBRQ-C3vM-hMq.js → sequenceDiagram-DBY2YBRQ-DdsbKqct.js} +1 -1
  80. package/dist/assets/{stateDiagram-2N3HPSRC-BvFhOryI.js → stateDiagram-2N3HPSRC-P6iSrgwR.js} +1 -1
  81. package/dist/assets/stateDiagram-v2-6OUMAXLB-DIYSf_qh.js +1 -0
  82. package/dist/assets/{swimlanes-5IMT3BWC-DKF_b-2s.js → swimlanes-5IMT3BWC-BGnZJx1M.js} +1 -1
  83. package/dist/assets/swimlanesDiagram-G3AALYLV-BIkrlKrZ.js +8 -0
  84. package/dist/assets/{timeline-definition-FHXFAJF6-vAs11ux_.js → timeline-definition-FHXFAJF6-rA-r3aUR.js} +1 -1
  85. package/dist/assets/{vennDiagram-L72KCM5P-BybTX_9B.js → vennDiagram-L72KCM5P-DiVKUjA9.js} +1 -1
  86. package/dist/assets/{wardleyDiagram-EHGQE667-CicUmMBR.js → wardleyDiagram-EHGQE667-Cfxh7UOn.js} +1 -1
  87. package/dist/assets/workspace-editor-model-D0Im34S5.js +1 -0
  88. package/dist/assets/{workspace-editor-monaco-BICJESG0.js → workspace-editor-monaco-BIpqcWf1.js} +1 -1
  89. package/dist/assets/workspace-editor-monaco-DLlws5Ig.js +1 -0
  90. package/dist/assets/{workspace-view-state-DvYG_9PH.js → workspace-view-state-BpPINu84.js} +1 -1
  91. package/dist/assets/{xychartDiagram-FW5EYKEG-BhWDyCX-.js → xychartDiagram-FW5EYKEG--hmryQeL.js} +1 -1
  92. package/dist/crt-mermaid-renderer.js +313 -313
  93. package/dist/index.html +1 -1
  94. package/frontend/session-bridge.js +24 -2
  95. package/frontend/skins/crt/app.js +194 -82
  96. package/frontend/skins/crt/index.html +2 -11
  97. package/frontend/skins/crt/styles/effects.css +2 -34
  98. package/package.json +23 -9
  99. package/shared/browser-protocol.js +3 -3
  100. package/backend/app-server-api.js +0 -917
  101. package/backend/codex-app-server-home.js +0 -39
  102. package/backend/codex-app-server-runtime.js +0 -901
  103. package/dist/assets/App-xASd_Y0F.js +0 -206
  104. package/dist/assets/FileEditorPane-Dxx4rKOg.js +0 -2
  105. package/dist/assets/ProjectFilesSection-DE7Nb9Jk.js +0 -12
  106. package/dist/assets/channel-Uw90AO0b.js +0 -1
  107. package/dist/assets/classDiagram-OUVF2IWQ-DKW_dtku.js +0 -1
  108. package/dist/assets/classDiagram-v2-EOCWNBFH-C8Fi7-yv.js +0 -1
  109. package/dist/assets/code-dark-DPiuyeSp.css +0 -1
  110. package/dist/assets/code-mobile-3C2Rr-CN.css +0 -1
  111. package/dist/assets/file-icons-Bw2qd5iT.js +0 -1
  112. package/dist/assets/flowDiagram-23GEKE2U-DAiS5Y7G.js +0 -1
  113. package/dist/assets/main-Bqtb5kuz.css +0 -1
  114. package/dist/assets/stateDiagram-v2-6OUMAXLB-WMrz4PNo.js +0 -1
  115. package/dist/assets/swimlanesDiagram-G3AALYLV-BGswC9oC.js +0 -8
  116. package/dist/assets/workspace-editor-model-BQol4qbA.js +0 -1
  117. package/dist/assets/workspace-editor-monaco-DRJ7IaXk.js +0 -1
  118. package/scripts/sync-ghostty-vendor.js +0 -66
  119. /package/dist/assets/{core-D0LFJkDt.js → core-B9MwRjDE.js} +0 -0
package/backend/server.js CHANGED
@@ -50,7 +50,6 @@ const { discoverSlashCommands } = require('./slash-command-discovery');
50
50
  const { FarmingUpdateService } = require('./update-service');
51
51
  const { isRestartBlockingAgent } = require('./agent-activity');
52
52
  const { inputPartsFromMessage } = require('./input-parts');
53
- const { AppServerApiBridge, createAppServerApiRouter } = require('./app-server-api');
54
53
  const { cleanupTerminalRuntime } = require('./terminal-runtime-cleanup');
55
54
  const { QrShareTicketStore, SHARE_TICKET_TTL_MS } = require('./qr-share-tickets');
56
55
  const { ReviewStateStore } = require('./review-state-store');
@@ -123,7 +122,6 @@ const updateService = new FarmingUpdateService({
123
122
  packagedRuntime: Boolean(process.pkg || process.env.FARMING_PACKAGED_RUNTIME === '1'),
124
123
  getUpdateUrl: () => configManager.getSettings().updateUrl || '',
125
124
  });
126
- const appServerApiBridge = new AppServerApiBridge();
127
125
  const usageMonitor = new UsageMonitor({ agentManager, getProviderHomes: configuredProviderHomes });
128
126
  const codexContextWindowReader = new CodexContextWindowReader();
129
127
  const usageSummaryCache = new AsyncCache(() => usageMonitor.getUsageSummary(), {
@@ -531,10 +529,6 @@ app.use(routePath(BASE_PATH, '/api/control'), createControlRouter(agentManager,
531
529
  && process.env.FARMING_E2E_FAKE_EXECUTABLES === '1',
532
530
  }));
533
531
 
534
- app.use(routePath(BASE_PATH, '/api/app-server'), createAppServerApiRouter({
535
- bridge: appServerApiBridge,
536
- }));
537
-
538
532
  app.get([
539
533
  BASE_PATH || '/',
540
534
  `${BASE_PATH || ''}/`,
@@ -602,9 +596,21 @@ const IMAGE_ATTACHMENT_EXTENSIONS = {
602
596
  'image/gif': 'gif',
603
597
  'image/webp': 'webp',
604
598
  };
599
+ const AUDIO_ATTACHMENT_EXTENSIONS = {
600
+ 'audio/aac': 'aac',
601
+ 'audio/flac': 'flac',
602
+ 'audio/mp4': 'm4a',
603
+ 'audio/mpeg': 'mp3',
604
+ 'audio/ogg': 'ogg',
605
+ 'audio/wav': 'wav',
606
+ 'audio/wave': 'wav',
607
+ 'audio/webm': 'webm',
608
+ 'audio/x-wav': 'wav',
609
+ };
605
610
  const IMAGE_ATTACHMENT_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
606
611
  const IMAGE_ATTACHMENT_GC_INTERVAL_MS = 60 * 60 * 1000;
607
612
  const IMAGE_ATTACHMENT_FILENAME_RE = /^pasted-image-\d+-[a-f0-9]{8}\.(?:png|jpg|gif|webp)$/;
613
+ const AUDIO_ATTACHMENT_FILENAME_RE = /^pasted-audio-\d+-[a-f0-9]{8}\.(?:aac|flac|m4a|mp3|ogg|wav|webm)$/;
608
614
  let lastImageAttachmentGcAt = 0;
609
615
 
610
616
  function imageAttachmentExtension(contentType) {
@@ -612,6 +618,11 @@ function imageAttachmentExtension(contentType) {
612
618
  return IMAGE_ATTACHMENT_EXTENSIONS[normalized] || '';
613
619
  }
614
620
 
621
+ function audioAttachmentExtension(contentType) {
622
+ const normalized = String(contentType || '').split(';')[0].trim().toLowerCase();
623
+ return AUDIO_ATTACHMENT_EXTENSIONS[normalized] || '';
624
+ }
625
+
615
626
  function imageAttachmentsDir() {
616
627
  return path.join(configManager.farmingDir, 'attachments');
617
628
  }
@@ -634,7 +645,7 @@ async function cleanupExpiredImageAttachments(options = {}) {
634
645
 
635
646
  const cutoff = now - IMAGE_ATTACHMENT_RETENTION_MS;
636
647
  await Promise.all(entries.map(async (entry) => {
637
- if (!entry.isFile() || !IMAGE_ATTACHMENT_FILENAME_RE.test(entry.name)) return;
648
+ if (!entry.isFile() || (!IMAGE_ATTACHMENT_FILENAME_RE.test(entry.name) && !AUDIO_ATTACHMENT_FILENAME_RE.test(entry.name))) return;
638
649
 
639
650
  const filePath = path.join(attachmentsDir, entry.name);
640
651
  try {
@@ -684,6 +695,30 @@ app.post(
684
695
  }
685
696
  );
686
697
 
698
+ app.post(
699
+ routePath(BASE_PATH, '/api/attachments/audio'),
700
+ express.raw({ type: 'audio/*', limit: '25mb' }),
701
+ (req, res) => {
702
+ const contentType = String(req.headers['content-type'] || '').split(';')[0].trim().toLowerCase();
703
+ const extension = audioAttachmentExtension(contentType);
704
+ if (!extension) {
705
+ res.status(415).json({ error: 'unsupported audio type' });
706
+ return;
707
+ }
708
+ if (!Buffer.isBuffer(req.body) || req.body.length === 0) {
709
+ res.status(400).json({ error: 'empty audio attachment' });
710
+ return;
711
+ }
712
+ const attachmentsDir = imageAttachmentsDir();
713
+ fs.mkdirSync(attachmentsDir, { recursive: true });
714
+ const filename = `pasted-audio-${Date.now()}-${crypto.randomBytes(4).toString('hex')}.${extension}`;
715
+ const filePath = path.join(attachmentsDir, filename);
716
+ fs.writeFileSync(filePath, req.body);
717
+ void cleanupExpiredImageAttachments();
718
+ res.status(201).json({ path: filePath, name: filename, type: contentType, size: req.body.length });
719
+ }
720
+ );
721
+
687
722
  app.get(routePath(BASE_PATH, '/api/codex/models'), async (req, res) => {
688
723
  try {
689
724
  const catalog = await codexModelOptionsCache.get('catalog');
@@ -968,8 +1003,8 @@ app.get(routePath(BASE_PATH, '/api/agents/:agentId/session-view'), async (req, r
968
1003
  });
969
1004
 
970
1005
  app.get(routePath(BASE_PATH, '/api/agents/:agentId/codex-transcript'), async (req, res) => {
971
- // Legacy JSONL transcript reader. New App Server Agents use the dedicated
972
- // structured endpoint below; Terminal Agents stay in their terminal UI.
1006
+ // Legacy JSONL transcript reader. ACP Chat uses the dedicated ACP endpoint;
1007
+ // Terminal Agents stay in their terminal UI.
973
1008
  const providerSession = agentManager.getAgentProviderSession(req.params.agentId);
974
1009
  if (!providerSession) {
975
1010
  res.status(404).json({ error: 'Agent not found' });
@@ -999,9 +1034,7 @@ app.get(routePath(BASE_PATH, '/api/agents/:agentId/codex-transcript'), async (re
999
1034
  : DEFAULT_CODEX_TRANSCRIPT_MAX_TURNS;
1000
1035
  const transcript = await readCodexTranscript(providerSession.sessionId, {
1001
1036
  maxTurns,
1002
- codexHome: runtimeKind(providerSession) === 'app-server'
1003
- ? (providerSession.codexAppServerHomePath || providerSession.providerHomePath || '')
1004
- : (providerSession.providerHomePath || ''),
1037
+ codexHome: providerSession.providerHomePath || '',
1005
1038
  });
1006
1039
  res.json({ transcript });
1007
1040
  } catch (error) {
@@ -1010,20 +1043,6 @@ app.get(routePath(BASE_PATH, '/api/agents/:agentId/codex-transcript'), async (re
1010
1043
  }
1011
1044
  });
1012
1045
 
1013
- app.get(routePath(BASE_PATH, '/api/agents/:agentId/codex-app-server-transcript'), async (req, res) => {
1014
- try {
1015
- const requestedMaxTurns = Number.parseInt(String(req.query.maxTurns || ''), 10);
1016
- const maxTurns = Number.isFinite(requestedMaxTurns)
1017
- ? Math.min(MAX_CODEX_TRANSCRIPT_TURNS, Math.max(20, requestedMaxTurns))
1018
- : DEFAULT_CODEX_TRANSCRIPT_MAX_TURNS;
1019
- const transcript = agentManager.getCodexAppServerTranscript(req.params.agentId, { maxTurns });
1020
- res.json({ transcript });
1021
- } catch (error) {
1022
- const message = error && error.message ? error.message : 'Failed to read Codex App Server transcript';
1023
- res.status(/not using the Codex App Server runtime/i.test(message) ? 409 : 500).json({ error: message });
1024
- }
1025
- });
1026
-
1027
1046
  app.get(routePath(BASE_PATH, '/api/agents/:agentId/json-cli-transcript'), async (req, res) => {
1028
1047
  try {
1029
1048
  const transcript = agentManager.getJsonCliTranscript(req.params.agentId, {
@@ -1191,6 +1210,15 @@ app.post(routePath(BASE_PATH, '/api/agents/:agentId/acp-session/authenticate'),
1191
1210
  }
1192
1211
  });
1193
1212
 
1213
+ app.post(routePath(BASE_PATH, '/api/agents/:agentId/acp-session/logout'), async (req, res) => {
1214
+ try {
1215
+ res.json(await agentManager.logoutAcpAgent(req.params.agentId));
1216
+ } catch (error) {
1217
+ const message = error && error.message ? error.message : 'Failed to log out ACP Agent';
1218
+ res.status(message === 'Agent not found' ? 404 : 409).json({ error: message });
1219
+ }
1220
+ });
1221
+
1194
1222
  app.post(routePath(BASE_PATH, '/api/agents/:agentId/acp-session/fork'), express.json(), async (req, res) => {
1195
1223
  try {
1196
1224
  res.json(await agentManager.forkAcpSession(req.params.agentId, req.body || {}));
@@ -1243,41 +1271,6 @@ app.patch(routePath(BASE_PATH, '/api/agents/:agentId/acp-session'), express.json
1243
1271
  }
1244
1272
  });
1245
1273
 
1246
- app.get(routePath(BASE_PATH, '/api/agents/:agentId/codex-goal'), async (req, res) => {
1247
- try {
1248
- const goal = await agentManager.getCodexAppServerGoal(req.params.agentId);
1249
- res.json({ goal });
1250
- } catch (error) {
1251
- const message = error && error.message ? error.message : 'Failed to read Codex goal';
1252
- res.status(message === 'Agent not found' ? 404 : 400).json({ error: message });
1253
- }
1254
- });
1255
-
1256
- app.patch(routePath(BASE_PATH, '/api/agents/:agentId/codex-goal'), express.json(), async (req, res) => {
1257
- try {
1258
- const body = req.body || {};
1259
- const goal = await agentManager.setCodexAppServerGoal(req.params.agentId, {
1260
- objective: typeof body.objective === 'string' ? body.objective : undefined,
1261
- status: typeof body.status === 'string' ? body.status : undefined,
1262
- ...(Object.prototype.hasOwnProperty.call(body, 'tokenBudget') ? { tokenBudget: body.tokenBudget } : {}),
1263
- });
1264
- res.json({ goal });
1265
- } catch (error) {
1266
- const message = error && error.message ? error.message : 'Failed to update Codex goal';
1267
- res.status(message === 'Agent not found' ? 404 : 400).json({ error: message });
1268
- }
1269
- });
1270
-
1271
- app.delete(routePath(BASE_PATH, '/api/agents/:agentId/codex-goal'), async (req, res) => {
1272
- try {
1273
- await agentManager.clearCodexAppServerGoal(req.params.agentId);
1274
- res.json({ goal: null });
1275
- } catch (error) {
1276
- const message = error && error.message ? error.message : 'Failed to clear Codex goal';
1277
- res.status(message === 'Agent not found' ? 404 : 400).json({ error: message });
1278
- }
1279
- });
1280
-
1281
1274
  app.post(routePath(BASE_PATH, '/api/agents/:agentId/codex-terminal-profile'), express.json(), async (req, res) => {
1282
1275
  const requestAbort = new globalThis.AbortController();
1283
1276
  const abortRequest = () => requestAbort.abort(new Error('Terminal profile request was canceled'));
@@ -1710,7 +1703,7 @@ async function resumeAgentSessionById(provider, rawSessionId, options = {}) {
1710
1703
  requiredCliVersion: normalizedProvider === 'codex' && session ? session.cliVersion : '',
1711
1704
  projectWorkspace: session ? (session.workspace || session.cwd || '') : '',
1712
1705
  source: shouldFork ? resumeSource.replace('-history:', '-history-fork:') : resumeSource,
1713
- agentRuntimeMode: options.agentRuntimeMode === 'acp' ? 'acp' : 'terminal',
1706
+ agentRuntimeMode: ['chat', 'acp'].includes(options.agentRuntimeMode) ? 'chat' : 'terminal',
1714
1707
  acpHistoryMode: options.acpHistoryMode === 'resume' ? 'resume' : 'load',
1715
1708
  providerHomeId: resolvedProviderHomeId,
1716
1709
  providerHomePath: session ? (session.providerHomePath || '') : '',
@@ -1754,7 +1747,7 @@ async function startResumedAgentSession(req, res, provider, rawSessionId) {
1754
1747
  allowUnarchiveArchived,
1755
1748
  providerHomeId: req.body && typeof req.body.providerHomeId === 'string' ? req.body.providerHomeId : '',
1756
1749
  customTitle: req.body && typeof req.body.customTitle === 'string' ? req.body.customTitle : '',
1757
- agentRuntimeMode: req.body && req.body.agentRuntimeMode === 'acp' ? 'acp' : 'terminal',
1750
+ agentRuntimeMode: req.body && ['chat', 'acp'].includes(req.body.agentRuntimeMode) ? 'chat' : 'terminal',
1758
1751
  acpHistoryMode: req.body && req.body.acpHistoryMode === 'resume' ? 'resume' : 'load',
1759
1752
  });
1760
1753
 
@@ -2018,66 +2011,66 @@ async function sendInputMessage(ws, data) {
2018
2011
 
2019
2012
  async function sendComposerInputMessage(ws, data) {
2020
2013
  const targetAgentId = resolveInputTargetAgentId(ws, data);
2014
+ const requestId = typeof data.requestId === 'string' ? data.requestId : '';
2015
+ const responseAgentId = targetAgentId || (typeof data.agentId === 'string' ? data.agentId : '');
2016
+ const respond = (accepted, message = '') => {
2017
+ if (!requestId || !responseAgentId) return;
2018
+ ws.send(JSON.stringify({
2019
+ type: 'composer-input-result',
2020
+ requestId,
2021
+ agentId: responseAgentId,
2022
+ accepted,
2023
+ ...(message ? { message } : {}),
2024
+ }));
2025
+ };
2021
2026
  const message = typeof data.message === 'string' ? data.message : '';
2022
2027
  const content = [];
2023
2028
  if (message.trim()) content.push({ type: 'text', text: message });
2024
2029
  const attachmentsRoot = path.resolve(imageAttachmentsDir());
2025
2030
  const attachments = Array.isArray(data.attachments) ? data.attachments.slice(0, 8) : [];
2026
2031
  for (const attachment of attachments) {
2027
- if (attachment?.kind !== 'image' || typeof attachment.path !== 'string') continue;
2032
+ if (!['image', 'audio'].includes(attachment?.kind) || typeof attachment.path !== 'string') continue;
2028
2033
  const filePath = path.resolve(attachment.path);
2029
2034
  if (!filePath.startsWith(`${attachmentsRoot}${path.sep}`)) continue;
2030
- const mimeType = typeof attachment.type === 'string' && /^image\/(?:png|jpe?g|gif|webp)$/i.test(attachment.type)
2035
+ const mimeType = typeof attachment.type === 'string' && attachment.kind === 'image' && /^image\/(?:png|jpe?g|gif|webp)$/i.test(attachment.type)
2031
2036
  ? attachment.type.toLowerCase()
2032
- : '';
2037
+ : (typeof attachment.type === 'string' && attachment.kind === 'audio' && Object.hasOwn(AUDIO_ATTACHMENT_EXTENSIONS, attachment.type.toLowerCase())
2038
+ ? attachment.type.toLowerCase()
2039
+ : '');
2033
2040
  if (!mimeType) continue;
2034
2041
  try {
2035
2042
  const data = await fs.promises.readFile(filePath);
2036
2043
  if (data.length === 0 || data.length > 12 * 1024 * 1024) continue;
2037
2044
  content.push({
2038
- type: 'image',
2045
+ type: attachment.kind,
2039
2046
  data: data.toString('base64'),
2040
2047
  mimeType,
2048
+ path: filePath,
2041
2049
  uri: pathToFileURL(filePath).href,
2042
2050
  });
2043
2051
  } catch {
2044
2052
  // The text fallback already explains failed or unavailable uploads.
2045
2053
  }
2046
2054
  }
2047
- if (!targetAgentId || content.length === 0) return;
2055
+ if (!targetAgentId || content.length === 0) {
2056
+ respond(false, 'Composer message is empty');
2057
+ return;
2058
+ }
2048
2059
  const targetAgent = agentManager.getState().agents.find(agent => agent.id === targetAgentId);
2049
2060
  const structuredRuntime = targetAgent && runtimeKind(targetAgent) !== 'terminal';
2050
2061
  if (!structuredRuntime) {
2051
- ws.send(JSON.stringify({
2052
- type: 'error',
2053
- message: 'Terminal Composer input requires the active terminal owner',
2054
- }));
2062
+ const error = 'Terminal Composer input requires the active terminal owner';
2063
+ if (requestId) respond(false, error);
2064
+ else ws.send(JSON.stringify({ type: 'error', message: error }));
2055
2065
  return;
2056
2066
  }
2057
2067
  try {
2058
2068
  await agentManager.sendComposerMessage(targetAgentId, content);
2069
+ respond(true);
2059
2070
  } catch (error) {
2060
- ws.send(JSON.stringify({
2061
- type: 'error',
2062
- message: error && error.message ? error.message : 'Failed to send Composer message',
2063
- }));
2064
- }
2065
- }
2066
-
2067
- async function respondToAppServerRequest(ws, data) {
2068
- const targetAgentId = resolveInputTargetAgentId(ws, data);
2069
- const requestId = typeof data.requestId === 'string' ? data.requestId : '';
2070
- if (!targetAgentId || !requestId) return;
2071
- try {
2072
- agentManager.respondToCodexAppServerRequest(targetAgentId, requestId, data.result, {
2073
- reject: data.reject === true,
2074
- reason: typeof data.reason === 'string' ? data.reason : '',
2075
- });
2076
- } catch (error) {
2077
- ws.send(JSON.stringify({
2078
- type: 'error',
2079
- message: error && error.message ? error.message : 'Failed to respond to Codex App Server request',
2080
- }));
2071
+ const message = error && error.message ? error.message : 'Failed to send Composer message';
2072
+ if (requestId) respond(false, message);
2073
+ else ws.send(JSON.stringify({ type: 'error', message }));
2081
2074
  }
2082
2075
  }
2083
2076
 
@@ -2107,12 +2100,11 @@ function handleMessage(ws, data) {
2107
2100
  workflowTemplate: typeof data.workflowTemplate === 'string' ? data.workflowTemplate : '',
2108
2101
  customTitle: typeof data.customTitle === 'string' ? data.customTitle : '',
2109
2102
  codexApprovalMode: typeof data.codexApprovalMode === 'string' ? data.codexApprovalMode : undefined,
2110
- codexRuntimeMode: data.codexRuntimeMode === 'app-server' || data.codexRuntimeMode === 'cli'
2111
- ? data.codexRuntimeMode
2112
- : undefined,
2113
- agentRuntimeMode: ['json', 'acp'].includes(data.agentRuntimeMode) ? data.agentRuntimeMode : 'terminal',
2103
+ agentRuntimeMode: ['json', 'acp', 'chat'].includes(data.agentRuntimeMode) ? data.agentRuntimeMode : 'terminal',
2114
2104
  acpHistoryMode: data.acpHistoryMode === 'resume' ? 'resume' : 'load',
2115
2105
  providerHomeId: typeof data.providerHomeId === 'string' ? data.providerHomeId : '',
2106
+ ...(Array.isArray(data.additionalDirectories) ? { additionalDirectories: data.additionalDirectories } : {}),
2107
+ ...(Array.isArray(data.mcpServers) ? { mcpServers: data.mcpServers } : {}),
2116
2108
  ...(data.dangerouslySkipPermissions === true ? { dangerouslySkipPermissions: true } : {}),
2117
2109
  });
2118
2110
  break;
@@ -2129,12 +2121,6 @@ function handleMessage(ws, data) {
2129
2121
  }
2130
2122
  break;
2131
2123
 
2132
- case 'app-server-request-response':
2133
- {
2134
- void respondToAppServerRequest(ws, data);
2135
- }
2136
- break;
2137
-
2138
2124
  case 'acp-permission-response':
2139
2125
  try {
2140
2126
  agentManager.respondToAcpPermission(
@@ -2717,7 +2703,6 @@ module.exports = {
2717
2703
  server,
2718
2704
  wss,
2719
2705
  agentManager,
2720
- appServerApiBridge,
2721
2706
  workspaceFileService,
2722
2707
  handleMessage,
2723
2708
  resolveCliBinDir,
@@ -1,6 +1,5 @@
1
1
  const os = require('os');
2
2
  const path = require('path');
3
- const crypto = require('crypto');
4
3
 
5
4
  function farmingConfigDir(env = process.env) {
6
5
  return env.FARMING_CONFIG_DIR || path.join(env.HOME || os.homedir(), '.farming');
@@ -110,23 +109,8 @@ function farmingNetTrustFile(configDir) {
110
109
  return path.join(configDir, 'farming-net-trust.json');
111
110
  }
112
111
 
113
- function codexAppServerRuntimeDir(configDir) {
114
- return path.join(configDir, 'c');
115
- }
116
-
117
- function codexAppServerAgentHome(configDir, agentId) {
118
- const safeAgentId = String(agentId || '').trim();
119
- if (!/^agent-[A-Za-z0-9_-]+$/.test(safeAgentId)) {
120
- throw new Error('Invalid Codex App Server agent id');
121
- }
122
- const homeId = crypto.createHash('sha256').update(safeAgentId).digest('hex').slice(0, 16);
123
- return path.join(codexAppServerRuntimeDir(configDir), homeId);
124
- }
125
-
126
112
  module.exports = {
127
113
  acpCheckpointsDir,
128
- codexAppServerAgentHome,
129
- codexAppServerRuntimeDir,
130
114
  farmingNetInstancesFile,
131
115
  farmingNetServerLogFile,
132
116
  farmingNetServerStateFile,
@@ -681,6 +681,45 @@ function npmPackageMetadataUrl(registryUrl, packageName) {
681
681
  return `${registry}/${encodeURIComponent(packageName).replace(/^%40/, '@')}`;
682
682
  }
683
683
 
684
+ function normalizePathForCompare(filePath) {
685
+ try {
686
+ return fs.realpathSync(filePath);
687
+ } catch {
688
+ return path.resolve(filePath);
689
+ }
690
+ }
691
+
692
+ function npmPackageRoot(npmRoot, packageName) {
693
+ return path.join(npmRoot, ...String(packageName || '').split('/').filter(Boolean));
694
+ }
695
+
696
+ function npmPrefixForPackageRoot(packageRoot, packageName) {
697
+ const segments = String(packageName || '').split('/').filter(Boolean);
698
+ if (!path.isAbsolute(packageRoot) || segments.length === 0) return '';
699
+ let npmRoot = packageRoot;
700
+ for (let index = 0; index < segments.length; index += 1) npmRoot = path.dirname(npmRoot);
701
+ return path.dirname(path.dirname(npmRoot));
702
+ }
703
+
704
+ function readNpmGlobalRoot(npmCommand, npmPrefix, execFile = childProcess.execFile) {
705
+ const args = ['root', '--global'];
706
+ if (npmPrefix) args.push('--prefix', npmPrefix);
707
+ return new Promise((resolve, reject) => {
708
+ execFile(npmCommand, args, (error, stdout) => {
709
+ if (error) {
710
+ reject(error);
711
+ return;
712
+ }
713
+ const root = String(stdout || '').split(/\r?\n/).map(value => value.trim()).find(Boolean);
714
+ if (!root) {
715
+ reject(new Error('npm root --global returned no path'));
716
+ return;
717
+ }
718
+ resolve(root);
719
+ });
720
+ });
721
+ }
722
+
684
723
  function npmVersionsFromMetadata(metadata, currentVersion) {
685
724
  const versions = metadata && metadata.versions && typeof metadata.versions === 'object'
686
725
  ? Object.keys(metadata.versions)
@@ -711,6 +750,8 @@ class FarmingUpdateService {
711
750
  });
712
751
  this.npmPackageName = options.npmPackageName || NPM_PACKAGE_NAME;
713
752
  this.npmRegistryUrl = options.npmRegistryUrl || process.env.FARMING_NPM_REGISTRY || DEFAULT_NPM_REGISTRY;
753
+ this.npmPackageRoot = options.npmPackageRoot || process.env.FARMING_MANAGED_PACKAGE_ROOT || '';
754
+ this.npmPrefix = options.npmPrefix || process.env.FARMING_NPM_PREFIX || '';
714
755
  this.runtime = options.platform || options.arch
715
756
  ? {
716
757
  platform: normalizePlatform(options.platform || process.platform),
@@ -724,6 +765,8 @@ class FarmingUpdateService {
724
765
  this.downloadFile = options.downloadFile || downloadFile;
725
766
  this.listArchiveEntries = options.listArchiveEntries || listTarArchiveEntries;
726
767
  this.execFile = options.execFile || childProcess.execFile;
768
+ this.getNpmGlobalRoot = options.getNpmGlobalRoot
769
+ || ((npmCommand, npmPrefix) => readNpmGlobalRoot(npmCommand, npmPrefix, this.execFile));
727
770
  this.spawn = options.spawn || childProcess.spawn;
728
771
  this.latestCache = null;
729
772
  this.npmCache = null;
@@ -782,6 +825,7 @@ class FarmingUpdateService {
782
825
  const current = this.currentVersion();
783
826
  const currentVersion = normalizeVersion(current.releaseVersion || current.packageVersion);
784
827
  const metadata = await this.npmMetadata(options);
828
+ const target = await this.npmUpdateTarget();
785
829
  const versions = npmVersionsFromMetadata(metadata, currentVersion);
786
830
  const latestVersion = normalizeVersion(metadata && metadata['dist-tags'] && metadata['dist-tags'].latest)
787
831
  || versions[0]?.version
@@ -791,7 +835,8 @@ class FarmingUpdateService {
791
835
  || versions.find(version => version.version === latestVersion)
792
836
  || versions[0]
793
837
  || null;
794
- const available = Boolean(selected && selected.available);
838
+ const blockedReason = target.proven ? '' : target.error;
839
+ const available = Boolean(selected && selected.available && target.proven);
795
840
  return {
796
841
  method: 'npm',
797
842
  current,
@@ -802,24 +847,63 @@ class FarmingUpdateService {
802
847
  publishedAt: '',
803
848
  assetName: latestVersion,
804
849
  assetSize: 0,
805
- blockedReason: '',
850
+ blockedReason,
806
851
  source: npmPackageMetadataUrl(this.npmRegistryUrl, this.npmPackageName),
807
852
  },
808
853
  selected: {
809
854
  version: selected?.version || '',
810
855
  assetName: selected?.assetName || '',
811
856
  assetSize: selected?.assetSize || 0,
812
- blockedReason: selected?.blockedReason || '',
857
+ blockedReason: selected?.blockedReason || blockedReason,
813
858
  },
814
859
  versions,
815
860
  runtime: this.runtime,
861
+ target,
816
862
  available,
817
- installable: Boolean(selected),
863
+ installable: Boolean(selected && target.proven),
818
864
  checkedAt: new Date(this.now()).toISOString(),
819
865
  state: this.currentInstallState(),
820
866
  };
821
867
  }
822
868
 
869
+ async npmUpdateTarget() {
870
+ const runningPackageRoot = String(this.npmPackageRoot || '').trim();
871
+ if (!path.isAbsolute(runningPackageRoot)) {
872
+ return {
873
+ proven: false,
874
+ error: 'npm update target could not be proven: the running package has no managed package-root provenance',
875
+ };
876
+ }
877
+ const npmCommand = process.env.FARMING_NPM_COMMAND || 'npm';
878
+ const npmPrefix = this.npmPrefix || npmPrefixForPackageRoot(runningPackageRoot, this.npmPackageName);
879
+ if (!npmPrefix) {
880
+ return {
881
+ proven: false,
882
+ error: 'npm update target could not be proven: the running package has no npm prefix',
883
+ };
884
+ }
885
+ try {
886
+ const root = await this.getNpmGlobalRoot(npmCommand, npmPrefix);
887
+ const targetPackageRoot = npmPackageRoot(root, this.npmPackageName);
888
+ if (normalizePathForCompare(runningPackageRoot) !== normalizePathForCompare(targetPackageRoot)) {
889
+ return {
890
+ proven: false,
891
+ error: `npm update would target a different installation: running ${runningPackageRoot}; npm ${targetPackageRoot}`,
892
+ };
893
+ }
894
+ return {
895
+ proven: true,
896
+ npmPrefix,
897
+ packageRoot: targetPackageRoot,
898
+ };
899
+ } catch (error) {
900
+ return {
901
+ proven: false,
902
+ error: `npm update target could not be inspected: ${error.message || String(error)}`,
903
+ };
904
+ }
905
+ }
906
+
823
907
  unsupportedStatus() {
824
908
  const current = this.currentVersion();
825
909
  const reason = installMethodBlockedReason(this.installMethod);
@@ -1081,9 +1165,11 @@ class FarmingUpdateService {
1081
1165
  stateFile: this.updateStateFile,
1082
1166
  logPath: this.updateLogFile,
1083
1167
  cliPath: path.join(this.rootDir, 'bin', 'farming'),
1168
+ packageRoot: status.target.packageRoot,
1084
1169
  nodePath,
1085
1170
  npmCommand: process.env.FARMING_NPM_COMMAND || 'npm',
1086
- npmPrefix: process.env.FARMING_NPM_PREFIX || '',
1171
+ npmPrefix: status.target.npmPrefix,
1172
+ npmFallbackRegistryUrl: this.npmRegistryUrl,
1087
1173
  serverPid: process.pid,
1088
1174
  configDir: this.configDir,
1089
1175
  port: process.env.FARMING_PORT || process.env.PORT || '6694',
@@ -1181,7 +1267,10 @@ module.exports = {
1181
1267
  installMethodAllowsBundleUpdate,
1182
1268
  normalizeVersion,
1183
1269
  npmPackageMetadataUrl,
1270
+ npmPrefixForPackageRoot,
1271
+ npmPackageRoot,
1184
1272
  npmVersionsFromMetadata,
1273
+ readNpmGlobalRoot,
1185
1274
  releaseInstallDir,
1186
1275
  manifestAssetSafety,
1187
1276
  normalizeSha256,