farming-code 2.2.6 → 2.2.8

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.
@@ -328,6 +328,7 @@ class AgentManager extends EventEmitter {
328
328
  this.controlUrl = options.controlUrl || '';
329
329
  this.tokenFile = options.tokenFile || '';
330
330
  this.authDisabled = options.authDisabled === true;
331
+ this.skipExecutablePreflight = options.skipExecutablePreflight === true;
331
332
  this.cliBinDir = options.cliBinDir || path.join(__dirname, '..', 'bin');
332
333
  this.agentShellEnvProvider = typeof options.agentShellEnvProvider === 'function'
333
334
  ? options.agentShellEnvProvider
@@ -1041,6 +1042,7 @@ class AgentManager extends EventEmitter {
1041
1042
  providerSessionSource: metadata.providerSessionSource || '',
1042
1043
  providerSessionResolvedAt: metadata.providerSessionResolvedAt || null,
1043
1044
  providerSessionTitle: metadata.providerSessionTitle || '',
1045
+ terminalInputReceived: metadata.terminalInputReceived === true,
1044
1046
  // Older persisted sessions predate App Server mode. Also, a Codex
1045
1047
  // App Server record without its isolated runtime home is not actually
1046
1048
  // attachable; recover it as terminal-owned CLI instead of leaving the
@@ -1227,6 +1229,7 @@ class AgentManager extends EventEmitter {
1227
1229
  providerSessionSource: agent.providerSessionSource || '',
1228
1230
  providerSessionResolvedAt: agent.providerSessionResolvedAt || null,
1229
1231
  providerSessionTitle: agent.providerSessionTitle || '',
1232
+ terminalInputReceived: agent.terminalInputReceived === true,
1230
1233
  codexRuntimeMode: agent.codexRuntimeMode || '',
1231
1234
  agentRuntimeMode: agent.agentRuntimeMode || 'terminal',
1232
1235
  acpState: agent.acpState || '',
@@ -1881,6 +1884,7 @@ class AgentManager extends EventEmitter {
1881
1884
  providerSessionSource: agent.providerSessionSource,
1882
1885
  providerSessionResolvedAt: agent.providerSessionResolvedAt,
1883
1886
  providerSessionTitle: agent.providerSessionTitle,
1887
+ terminalInputReceived: agent.terminalInputReceived === true,
1884
1888
  codexRuntimeMode: agent.codexRuntimeMode,
1885
1889
  agentRuntimeMode: agent.agentRuntimeMode || 'terminal',
1886
1890
  jsonCliState: agent.jsonCliState || '',
@@ -2005,7 +2009,8 @@ class AgentManager extends EventEmitter {
2005
2009
  const launchPathEnv = typeof userShellEnv?.PATH === 'string' && userShellEnv.PATH.trim()
2006
2010
  ? userShellEnv.PATH
2007
2011
  : (process.env.PATH || '');
2008
- let spawnProgram = resolveAgentExecutable(program, launchPathEnv) || program;
2012
+ const resolvedExecutable = resolveAgentExecutable(program, launchPathEnv);
2013
+ let spawnProgram = resolvedExecutable || program;
2009
2014
  if (path.basename(program) === 'codex') {
2010
2015
  const codexResolution = resolveCompatibleCodexExecutable(options.requiredCliVersion || '', launchPathEnv);
2011
2016
  if (!codexResolution.compatible) {
@@ -2014,6 +2019,24 @@ class AgentManager extends EventEmitter {
2014
2019
  }
2015
2020
  spawnProgram = codexResolution.path || spawnProgram;
2016
2021
  }
2022
+ if (
2023
+ launch.spec
2024
+ && path.basename(program) === program
2025
+ && !resolvedExecutable
2026
+ && !this.skipExecutablePreflight
2027
+ && process.env.FARMING_E2E_FAKE_EXECUTABLES !== '1'
2028
+ ) {
2029
+ const displayName = launch.spec.name === 'opencode'
2030
+ ? 'OpenCode'
2031
+ : launch.spec.name.charAt(0).toUpperCase() + launch.spec.name.slice(1);
2032
+ if (callback) {
2033
+ callback(
2034
+ null,
2035
+ `${displayName} executable "${program}" was not found in the user shell PATH. Install it or refresh the Agent list, then try again.`
2036
+ );
2037
+ }
2038
+ return null;
2039
+ }
2017
2040
 
2018
2041
  const parentAgentId = typeof options.parentAgentId === 'string' ? options.parentAgentId : '';
2019
2042
  const parentAgent = parentAgentId ? this.agents.get(parentAgentId) : null;
@@ -2117,6 +2140,16 @@ class AgentManager extends EventEmitter {
2117
2140
  resolvedProviderHomeId = defaultCodexHome.id || 'default';
2118
2141
  }
2119
2142
  }
2143
+ const requestedAgentRuntimeMode = ['json', 'acp'].includes(options.agentRuntimeMode)
2144
+ ? options.agentRuntimeMode
2145
+ : 'terminal';
2146
+ // A fresh structured runtime does not need a provider CLI resume id yet:
2147
+ // ACP/JSON creates the provider session and writes the resulting id back
2148
+ // after connecting. OpenCode is the important case here because its
2149
+ // terminal CLI does not accept a pre-generated id for a fresh session.
2150
+ const structuredRuntimeProvider = ['json', 'acp'].includes(requestedAgentRuntimeMode)
2151
+ ? homeProvider
2152
+ : providerSessionPlan.provider;
2120
2153
  const requestedCodexRuntimeMode = options.codexRuntimeMode === 'app-server' || options.codexRuntimeMode === 'cli'
2121
2154
  ? options.codexRuntimeMode
2122
2155
  : (this.configManager && typeof this.configManager.getCodexRuntimeMode === 'function'
@@ -2131,15 +2164,12 @@ class AgentManager extends EventEmitter {
2131
2164
  // on the path they are designed to exercise.
2132
2165
  && process.env.FARMING_E2E_FAKE_EXECUTABLES !== '1'
2133
2166
  && normalizeCodexRuntimeMode(requestedCodexRuntimeMode) === 'app-server';
2134
- const requestedAgentRuntimeMode = ['json', 'acp'].includes(options.agentRuntimeMode)
2135
- ? options.agentRuntimeMode
2136
- : 'terminal';
2137
2167
  const useJsonCli = requestedAgentRuntimeMode === 'json'
2138
- && ['codex', 'opencode'].includes(providerSessionPlan.provider)
2168
+ && ['codex', 'opencode'].includes(structuredRuntimeProvider)
2139
2169
  && !useCodexAppServer
2140
2170
  && process.env.FARMING_E2E_FAKE_EXECUTABLES !== '1';
2141
2171
  const useAcp = requestedAgentRuntimeMode === 'acp'
2142
- && ['codex', 'claude', 'opencode', 'qoder'].includes(providerSessionPlan.provider)
2172
+ && ['codex', 'claude', 'opencode', 'qoder'].includes(structuredRuntimeProvider)
2143
2173
  && !useCodexAppServer
2144
2174
  && (
2145
2175
  process.env.FARMING_E2E_FAKE_EXECUTABLES !== '1'
@@ -2180,7 +2210,7 @@ class AgentManager extends EventEmitter {
2180
2210
  task: typeof options.task === 'string' ? options.task : '',
2181
2211
  workflowTemplate: typeof options.workflowTemplate === 'string' ? options.workflowTemplate : '',
2182
2212
  source: typeof options.source === 'string' ? options.source : 'ui',
2183
- providerSessionProvider: providerSessionPlan.provider || '',
2213
+ providerSessionProvider: useAcp || useJsonCli ? structuredRuntimeProvider : (providerSessionPlan.provider || ''),
2184
2214
  providerHomeId: resolvedProviderHomeId,
2185
2215
  providerHomePath,
2186
2216
  providerSessionId: providerSessionPlan.id || '',
@@ -2189,7 +2219,8 @@ class AgentManager extends EventEmitter {
2189
2219
  providerSessionSource: providerSessionPlan.source || '',
2190
2220
  providerSessionResolvedAt: providerSessionPlan.temporary === true ? null : Date.now(),
2191
2221
  providerSessionTitle: typeof options.providerSessionTitle === 'string' ? options.providerSessionTitle.trim().slice(0, 160) : '',
2192
- codexRuntimeMode: providerSessionPlan.provider === 'codex'
2222
+ terminalInputReceived: false,
2223
+ codexRuntimeMode: structuredRuntimeProvider === 'codex'
2193
2224
  ? (useCodexAppServer ? 'app-server' : 'cli')
2194
2225
  : '',
2195
2226
  agentRuntimeMode: useAcp ? 'acp' : (useJsonCli ? 'json' : 'terminal'),
@@ -2320,7 +2351,7 @@ class AgentManager extends EventEmitter {
2320
2351
  if (useJsonCli) {
2321
2352
  this.jsonCliRuntime.registerAgent({
2322
2353
  agentId,
2323
- provider: providerSessionPlan.provider,
2354
+ provider: structuredRuntimeProvider,
2324
2355
  executable: spawnProgram,
2325
2356
  env: this.buildAgentEnv(agentId, agentRecord),
2326
2357
  cwd: workspace,
@@ -2334,11 +2365,13 @@ class AgentManager extends EventEmitter {
2334
2365
  if (useAcp) {
2335
2366
  const prepared = await this.acpRuntime.prepareAgent({
2336
2367
  agentId,
2337
- provider: providerSessionPlan.provider,
2368
+ provider: structuredRuntimeProvider,
2338
2369
  executable: spawnProgram,
2339
2370
  env: this.buildAgentEnv(agentId, agentRecord),
2340
2371
  cwd: workspace,
2341
- sessionId: agentRecord.providerSessionTemporary ? '' : agentRecord.providerSessionId,
2372
+ sessionId: options.acpStartFresh === true || agentRecord.providerSessionTemporary
2373
+ ? ''
2374
+ : agentRecord.providerSessionId,
2342
2375
  historyMode: options.acpHistoryMode === 'resume' ? 'resume' : 'load',
2343
2376
  approvalMode: agentRecord.launchPermissionMode || 'approve',
2344
2377
  model: this.configManager && this.configManager.getCodexModel
@@ -2353,7 +2386,7 @@ class AgentManager extends EventEmitter {
2353
2386
  });
2354
2387
  agentRecord.providerSessionId = prepared.sessionId;
2355
2388
  agentRecord.providerSessionKey = this.providerSessionKey(
2356
- providerSessionPlan.provider,
2389
+ structuredRuntimeProvider,
2357
2390
  prepared.sessionId,
2358
2391
  agentRecord.providerHomeId || 'default'
2359
2392
  );
@@ -2721,6 +2754,11 @@ class AgentManager extends EventEmitter {
2721
2754
  return this.acpRuntime.setSessionConfigOption(agentId, configId, value);
2722
2755
  }
2723
2756
 
2757
+ setAcpSessionConfigOptions(agentId, changes) {
2758
+ this.getAcpSession(agentId);
2759
+ return this.acpRuntime.setSessionConfigOptions(agentId, changes);
2760
+ }
2761
+
2724
2762
  async setCodexAppServerGoal(agentId, patch = {}) {
2725
2763
  const agent = this.assertCodexAppServerGoalAgent(agentId);
2726
2764
  const objective = typeof patch.objective === 'string' ? patch.objective.trim().slice(0, 4000) : undefined;
@@ -2763,6 +2801,12 @@ class AgentManager extends EventEmitter {
2763
2801
  const engine = this.engineBridge.getEngine(agent.engineName);
2764
2802
  if (!engine) return;
2765
2803
 
2804
+ if (agent.terminalInputReceived !== true) {
2805
+ agent.terminalInputReceived = true;
2806
+ this.ensurePersistentAgentSession(agent);
2807
+ this.updateEngineProviderSessionMetadata(agent);
2808
+ }
2809
+
2766
2810
  for (let attempt = 0; ; attempt += 1) {
2767
2811
  try {
2768
2812
  await engine.sendInput(agentId, input);
@@ -3158,7 +3202,14 @@ class AgentManager extends EventEmitter {
3158
3202
  return { error: `Agent does not support the ${nextMode.toUpperCase()} runtime` };
3159
3203
  }
3160
3204
  const sessionId = String(agent.providerSessionId || '').trim();
3161
- if (!isSafeProviderSessionId(sessionId)) {
3205
+ const canStartFreshAcpSession = nextMode === 'acp'
3206
+ && agent.agentRuntimeMode === 'terminal'
3207
+ && agent.terminalInputReceived !== true
3208
+ && (
3209
+ agent.providerSessionTemporary === true
3210
+ || ['codex-temporary', 'claude-session-id', 'qoder-session-id'].includes(agent.providerSessionSource || '')
3211
+ );
3212
+ if (!isSafeProviderSessionId(sessionId) && !canStartFreshAcpSession) {
3162
3213
  return { error: 'Runtime switching requires a resumable provider session. Send the first message and try again.' };
3163
3214
  }
3164
3215
  // A live ACP binding is the authoritative owner of a newly-created
@@ -3177,15 +3228,19 @@ class AgentManager extends EventEmitter {
3177
3228
  }
3178
3229
  }
3179
3230
  const previouslyVerifiedSession = String(agent.runtimeSwitchVerifiedSessionId || '') === sessionId;
3180
- if (!liveAcpSession && !previouslyVerifiedSession) {
3231
+ let startsFreshAcpSession = canStartFreshAcpSession && !isSafeProviderSessionId(sessionId);
3232
+ if (!startsFreshAcpSession && !liveAcpSession && !previouslyVerifiedSession) {
3181
3233
  const providerSession = await this.findRuntimeSwitchSession(agent);
3182
3234
  if (!providerSession) {
3183
- return { error: 'The saved Agent session is no longer available in the selected Agent Home.' };
3235
+ if (canStartFreshAcpSession) startsFreshAcpSession = true;
3236
+ else return { error: 'The saved Agent session is no longer available in the selected Agent Home.' };
3184
3237
  }
3185
3238
  }
3186
- const command = buildAgentSessionResumeCommand(provider, sessionId, {
3187
- cwd: agent.cwd || agent.projectWorkspace || '',
3188
- });
3239
+ const command = startsFreshAcpSession
3240
+ ? (agent.forkCommand || agent.command)
3241
+ : buildAgentSessionResumeCommand(provider, sessionId, {
3242
+ cwd: agent.cwd || agent.projectWorkspace || '',
3243
+ });
3189
3244
  if (!command) return { error: 'Failed to build provider resume command' };
3190
3245
  const preserved = {
3191
3246
  pinned: agent.pinned === true,
@@ -3202,7 +3257,9 @@ class AgentManager extends EventEmitter {
3202
3257
  task: agent.task || agent.providerSessionTitle || '',
3203
3258
  workflowTemplate: agent.workflowTemplate || '',
3204
3259
  projectWorkspace: agent.projectWorkspace || agent.cwd || '',
3205
- source: resumedAgentSource(provider, sessionId, agent.providerHomeId || ''),
3260
+ source: startsFreshAcpSession
3261
+ ? 'ui-runtime-switch-fresh'
3262
+ : resumedAgentSource(provider, sessionId, agent.providerHomeId || ''),
3206
3263
  providerHomeId: agent.providerHomeId || '',
3207
3264
  providerHomePath: agent.providerHomePath || '',
3208
3265
  providerSessionTitle: agent.providerSessionTitle || '',
@@ -3215,16 +3272,18 @@ class AgentManager extends EventEmitter {
3215
3272
  projectOrder: preserved.projectOrder,
3216
3273
  pinnedOrder: preserved.pinnedOrder,
3217
3274
  agentRuntimeMode: nextMode,
3275
+ acpStartFresh: startsFreshAcpSession,
3218
3276
  codexRuntimeMode: 'cli',
3219
3277
  codexApprovalMode: agent.launchPermissionMode || undefined,
3220
3278
  jsonCliEvents: preserved.jsonCliEvents,
3221
- runtimeSwitchVerifiedSessionId: sessionId,
3279
+ runtimeSwitchVerifiedSessionId: startsFreshAcpSession ? '' : sessionId,
3222
3280
  };
3223
3281
  const originalMode = agent.agentRuntimeMode || 'terminal';
3224
3282
  const originalOptions = {
3225
3283
  ...restartOptions,
3226
3284
  agentRuntimeMode: originalMode,
3227
3285
  codexRuntimeMode: agent.codexRuntimeMode || 'cli',
3286
+ acpStartFresh: false,
3228
3287
  };
3229
3288
  const startReplacement = options => new Promise(resolve => {
3230
3289
  let settled = false;
@@ -3930,6 +3989,7 @@ class AgentManager extends EventEmitter {
3930
3989
  providerSessionSource: agent.providerSessionSource || '',
3931
3990
  providerSessionResolvedAt: agent.providerSessionResolvedAt || null,
3932
3991
  providerSessionTitle: agent.providerSessionTitle || '',
3992
+ terminalInputReceived: agent.terminalInputReceived === true,
3933
3993
  codexRuntimeMode: agent.codexRuntimeMode || '',
3934
3994
  agentRuntimeMode: agent.agentRuntimeMode || 'terminal',
3935
3995
  acpState: agent.acpState || '',
@@ -4095,6 +4155,7 @@ class AgentManager extends EventEmitter {
4095
4155
  providerSessionSource: agent.providerSessionSource || '',
4096
4156
  providerSessionResolvedAt: agent.providerSessionResolvedAt || null,
4097
4157
  providerSessionTitle: agent.providerSessionTitle || '',
4158
+ terminalInputReceived: agent.terminalInputReceived === true,
4098
4159
  codexRuntimeMode: agent.codexRuntimeMode || '',
4099
4160
  agentRuntimeMode: agent.agentRuntimeMode || 'terminal',
4100
4161
  jsonCliState: agent.jsonCliState || '',
@@ -417,7 +417,7 @@ function resolveLaunchCommand(command, options = {}) {
417
417
  if (effort && effort !== 'config' && shouldApplyModelProfile && !hasCodexEffortOverride) {
418
418
  launchArgs.unshift('-c', `model_reasoning_effort="${effort}"`);
419
419
  }
420
- if (codexServiceTier && codexServiceTier !== 'default' && codexServiceTier !== 'config' && shouldApplyModelProfile && !hasCodexServiceTierOverride) {
420
+ if (codexServiceTier && codexServiceTier !== 'config' && shouldApplyModelProfile && !hasCodexServiceTierOverride) {
421
421
  launchArgs.unshift('-c', `service_tier="${codexServiceTier}"`);
422
422
  }
423
423
  }
@@ -15,6 +15,7 @@ const NATIVE_PTY_HOST_ARG = '--native-pty-host';
15
15
  const DEFAULT_PORT = '6694';
16
16
  const DEFAULT_BASE_PATH = '/farming';
17
17
  const DEFAULT_SERVER_START_TIMEOUT_MS = 30_000;
18
+ const DEFAULT_SERVER_START_STABILITY_MS = 1_500;
18
19
  const SERVER_COMMANDS = new Set(['start', 'serve', 'daemon', 'stop', 'status', 'logs', 'url', 'help']);
19
20
  const CONTROL_COMMANDS = new Set(['skills', 'memory', 'report', 'list', 'spawn', 'output', 'send', 'kill']);
20
21
  const SERVER_BACKED_CONTROL_COMMANDS = new Set(['list', 'spawn', 'output', 'send', 'kill']);
@@ -416,7 +417,14 @@ function childInvocation(env = process.env) {
416
417
  if (process.pkg) {
417
418
  return { command: '/bin/sh', args: ['-c', buildCleanEnvExecCommand(env, process.execPath, ['--'])] };
418
419
  }
419
- return { command: env.FARMING_NODE_BIN || process.execPath, args: [__filename] };
420
+ const nodePath = env.FARMING_NODE_BIN || process.execPath;
421
+ if (env.FARMING_NODE_LD && env.FARMING_NODE_LIBRARY_PATH) {
422
+ return {
423
+ command: env.FARMING_NODE_LD,
424
+ args: ['--library-path', env.FARMING_NODE_LIBRARY_PATH, nodePath, __filename],
425
+ };
426
+ }
427
+ return { command: nodePath, args: [__filename] };
420
428
  }
421
429
 
422
430
  function ensureConfigDir(configDir) {
@@ -500,6 +508,44 @@ function serverStartTimeoutMs(env) {
500
508
  return DEFAULT_SERVER_START_TIMEOUT_MS;
501
509
  }
502
510
 
511
+ function serverStartStabilityMs(env) {
512
+ const parsed = Number(env.FARMING_START_STABILITY_MS || env.FARMING_SERVER_START_STABILITY_MS);
513
+ if (Number.isFinite(parsed) && parsed >= 0) return Math.min(parsed, 60_000);
514
+ return DEFAULT_SERVER_START_STABILITY_MS;
515
+ }
516
+
517
+ function waitForProcessStability(pid, durationMs = DEFAULT_SERVER_START_STABILITY_MS) {
518
+ const startedAt = Date.now();
519
+ return new Promise((resolve, reject) => {
520
+ const tick = () => {
521
+ if (!isRunning(pid)) {
522
+ reject(new Error('server process exited during startup stability check'));
523
+ return;
524
+ }
525
+ const remainingMs = durationMs - (Date.now() - startedAt);
526
+ if (remainingMs <= 0) {
527
+ resolve();
528
+ return;
529
+ }
530
+ setTimeout(tick, Math.min(100, remainingMs));
531
+ };
532
+ tick();
533
+ });
534
+ }
535
+
536
+ function cleanupFailedDaemonStart(configDir, childPid) {
537
+ if (isRunning(childPid)) {
538
+ try {
539
+ process.kill(childPid, 'SIGTERM');
540
+ } catch {
541
+ // The child may exit between the liveness check and the signal.
542
+ }
543
+ }
544
+ if (readPid(configDir) !== childPid) return;
545
+ fs.rmSync(pidFile(configDir), { force: true });
546
+ fs.rmSync(serverStateFile(configDir), { force: true });
547
+ }
548
+
503
549
  function waitForServer(env, timeoutMs = serverStartTimeoutMs(env), childPid = 0) {
504
550
  const startedAt = Date.now();
505
551
  const port = Number(env.PORT || DEFAULT_PORT);
@@ -628,7 +674,10 @@ async function startDaemon(parsed) {
628
674
 
629
675
  try {
630
676
  await waitForServer(env, serverStartTimeoutMs(env), child.pid);
677
+ await waitForProcessStability(child.pid, serverStartStabilityMs(env));
678
+ await waitForServer(env, Math.min(serverStartTimeoutMs(env), 5_000), child.pid);
631
679
  } catch (error) {
680
+ cleanupFailedDaemonStart(configDir, child.pid);
632
681
  console.error(error.message);
633
682
  const logs = tailFile(logFile(configDir), 80);
634
683
  if (logs) console.error(logs);
@@ -773,6 +822,7 @@ module.exports = {
773
822
  NATIVE_PTY_HOST_ARG,
774
823
  buildCleanEnvExecCommand,
775
824
  childInvocation,
825
+ cleanupFailedDaemonStart,
776
826
  buildControlEnv,
777
827
  buildServerEnv,
778
828
  computeNodeHeapMb,
@@ -784,6 +834,7 @@ module.exports = {
784
834
  reviewUrl,
785
835
  readServerState,
786
836
  serverStartTimeoutMs,
837
+ serverStartStabilityMs,
787
838
  splitControlArgs,
788
839
  run,
789
840
  serverStateFile,
@@ -166,6 +166,7 @@ class FarmingSessionStore {
166
166
  providerSessionSource: typeof agent.providerSessionSource === 'string' ? agent.providerSessionSource : '',
167
167
  providerSessionResolvedAt: typeof agent.providerSessionResolvedAt === 'number' ? agent.providerSessionResolvedAt : null,
168
168
  providerSessionTitle: typeof agent.providerSessionTitle === 'string' ? agent.providerSessionTitle : '',
169
+ terminalInputReceived: agent.terminalInputReceived === true,
169
170
  codexRuntimeMode: typeof agent.codexRuntimeMode === 'string' ? agent.codexRuntimeMode : '',
170
171
  agentRuntimeMode: typeof agent.agentRuntimeMode === 'string' ? agent.agentRuntimeMode : 'terminal',
171
172
  acpState: typeof agent.acpState === 'string' ? agent.acpState : '',
@@ -102,6 +102,8 @@ function startArguments(payload) {
102
102
  function commandEnvironment() {
103
103
  const env = { ...process.env };
104
104
  delete env.FARMING_NPM_UPDATE_PAYLOAD;
105
+ delete env.FARMING_RUN_SERVER;
106
+ delete env.FARMING_RUN_NATIVE_PTY_HOST;
105
107
  return env;
106
108
  }
107
109
 
@@ -112,6 +114,7 @@ async function installPackage(payload, version) {
112
114
  if (payload.npmPrefix) args.push('--prefix', payload.npmPrefix);
113
115
  args.push(packageSpec, '--no-audit', '--no-fund');
114
116
  await runCommand(payload.npmCommand || 'npm', args, {
117
+ cwd: payload.configDir,
115
118
  env: commandEnvironment(),
116
119
  logPath: payload.logPath,
117
120
  });
@@ -120,6 +123,7 @@ async function installPackage(payload, version) {
120
123
  async function startServer(payload, version = payload.targetVersion) {
121
124
  appendLog(payload.logPath, `Starting Farming ${version}`);
122
125
  await runCommand(payload.nodePath, startArguments(payload), {
126
+ cwd: payload.configDir,
123
127
  env: commandEnvironment(),
124
128
  logPath: payload.logPath,
125
129
  });
package/backend/server.js CHANGED
@@ -1168,6 +1168,10 @@ app.patch(routePath(BASE_PATH, '/api/agents/:agentId/acp-session'), express.json
1168
1168
  res.json(await agentManager.setAcpSessionMode(req.params.agentId, req.body.modeId));
1169
1169
  return;
1170
1170
  }
1171
+ if (Array.isArray(req.body?.configOptions)) {
1172
+ res.json(await agentManager.setAcpSessionConfigOptions(req.params.agentId, req.body.configOptions));
1173
+ return;
1174
+ }
1171
1175
  if (typeof req.body?.configId === 'string' && Object.prototype.hasOwnProperty.call(req.body, 'value')) {
1172
1176
  res.json(await agentManager.setAcpSessionConfigOption(
1173
1177
  req.params.agentId,
@@ -1176,7 +1180,7 @@ app.patch(routePath(BASE_PATH, '/api/agents/:agentId/acp-session'), express.json
1176
1180
  ));
1177
1181
  return;
1178
1182
  }
1179
- res.status(400).json({ error: 'ACP modeId or configId/value is required' });
1183
+ res.status(400).json({ error: 'ACP modeId, configOptions, or configId/value is required' });
1180
1184
  } catch (error) {
1181
1185
  const message = error && error.message ? error.message : 'Failed to update ACP session';
1182
1186
  res.status(message === 'Agent not found' ? 404 : 409).json({ error: message });
@@ -666,6 +666,16 @@ function releaseInstallDir(rootDir, env = process.env) {
666
666
  return path.join(os.homedir(), 'farming');
667
667
  }
668
668
 
669
+ function nodeScriptInvocation(nodePath, scriptPath, env = process.env) {
670
+ if (env.FARMING_NODE_LD && env.FARMING_NODE_LIBRARY_PATH) {
671
+ return {
672
+ command: env.FARMING_NODE_LD,
673
+ args: ['--library-path', env.FARMING_NODE_LIBRARY_PATH, nodePath, scriptPath],
674
+ };
675
+ }
676
+ return { command: nodePath, args: [scriptPath] };
677
+ }
678
+
669
679
  function npmPackageMetadataUrl(registryUrl, packageName) {
670
680
  const registry = String(registryUrl || DEFAULT_NPM_REGISTRY).replace(/\/+$/, '');
671
681
  return `${registry}/${encodeURIComponent(packageName).replace(/^%40/, '@')}`;
@@ -739,7 +749,7 @@ class FarmingUpdateService {
739
749
  compatibilityProfile: String(release.compatibilityProfile || ''),
740
750
  bundledGlibcRuntime: release.bundledGlibcRuntime === true,
741
751
  type: this.installMethod,
742
- installDir: releaseInstallDir(this.rootDir),
752
+ installDir: this.installMethod === 'npm' ? this.rootDir : releaseInstallDir(this.rootDir),
743
753
  };
744
754
  }
745
755
 
@@ -1081,7 +1091,9 @@ class FarmingUpdateService {
1081
1091
  serverHome: process.env.FARMING_SERVER_HOME || '',
1082
1092
  disableAuth: /^(1|true|yes|on)$/i.test(String(process.env.FARMING_DISABLE_AUTH || '')),
1083
1093
  };
1084
- const child = this.spawn(nodePath, [helperPath], {
1094
+ const helperInvocation = nodeScriptInvocation(nodePath, helperPath);
1095
+ const child = this.spawn(helperInvocation.command, helperInvocation.args, {
1096
+ cwd: this.configDir,
1085
1097
  detached: true,
1086
1098
  stdio: 'ignore',
1087
1099
  env: {