farming-code 2.2.6 → 2.2.7

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.
@@ -23,6 +23,7 @@ const DEFAULT_CANCEL_TIMEOUT_MS = 15_000;
23
23
  const DEFAULT_HISTORY_REPLAY_MIN_WAIT_MS = 350;
24
24
  const DEFAULT_HISTORY_REPLAY_QUIET_MS = 150;
25
25
  const DEFAULT_HISTORY_REPLAY_MAX_WAIT_MS = 5_000;
26
+ const CODEX_SET_SESSION_MODEL_METHOD = 'session/set_model';
26
27
 
27
28
  let sdkPromise;
28
29
  const runtimeRequire = createRequire(__filename);
@@ -386,6 +387,16 @@ class AcpRuntime extends EventEmitter {
386
387
  binding.configOptions = sessionResponse?.configOptions || [];
387
388
  binding.sessionState.currentModeId = String(binding.modes?.currentModeId || '');
388
389
  binding.sessionState.configOptions = JSON.parse(JSON.stringify(binding.configOptions));
390
+ if (provider === 'codex' && options.serviceTier && options.serviceTier !== 'config') {
391
+ const fastOption = binding.configOptions.find(option => (
392
+ option.type === 'boolean'
393
+ && /fast/i.test(`${option.id || ''} ${option.name || ''} ${option.category || ''}`)
394
+ ));
395
+ const fastEnabled = ['fast', 'priority'].includes(options.serviceTier);
396
+ if (fastOption && fastOption.currentValue !== fastEnabled) {
397
+ await this.applySessionConfigOption(binding, fastOption.id, fastEnabled, { emit: false });
398
+ }
399
+ }
389
400
  binding.state = 'idle';
390
401
  binding.updatedAt = new Date().toISOString();
391
402
  this.emitRuntime(binding);
@@ -857,6 +868,87 @@ class AcpRuntime extends EventEmitter {
857
868
 
858
869
  async setSessionConfigOption(agentId, configId, value) {
859
870
  const binding = this.requireBinding(agentId);
871
+ const option = binding.configOptions?.find(candidate => candidate.id === String(configId || ''));
872
+ if (
873
+ binding.provider === 'codex'
874
+ && option?.type === 'select'
875
+ && /(^|[\s_-])model([\s_-]|$)/i.test(`${option.id} ${option.name || ''} ${option.category || ''}`)
876
+ ) {
877
+ // Let the adapter choose a supported fallback effort from its current
878
+ // snapshot first. The refresh extension requires an explicit effort and
879
+ // would otherwise reject a valid model change (for example ultra -> a
880
+ // model that tops out at max).
881
+ await this.applySessionConfigOption(binding, configId, value, { emit: false });
882
+ const reasoning = binding.configOptions?.find(candidate => (
883
+ candidate.type === 'select'
884
+ && /(reasoning|thought)/i.test(`${candidate.id} ${candidate.name || ''} ${candidate.category || ''}`)
885
+ ));
886
+ if (typeof reasoning?.currentValue === 'string' && reasoning.currentValue) {
887
+ await this.refreshCodexSessionModel(binding, String(value ?? ''), reasoning.currentValue);
888
+ return this.applySessionConfigOption(binding, configId, value);
889
+ }
890
+ this.emitSession(binding);
891
+ return { sessionId: binding.sessionId, configOptions: binding.configOptions };
892
+ }
893
+ return this.applySessionConfigOption(binding, configId, value);
894
+ }
895
+
896
+ async setSessionConfigOptions(agentId, changes) {
897
+ const binding = this.requireBinding(agentId);
898
+ const normalized = Array.isArray(changes)
899
+ ? changes.filter(change => change && typeof change.configId === 'string' && Object.prototype.hasOwnProperty.call(change, 'value'))
900
+ : [];
901
+ if (normalized.length === 0) throw new Error('ACP config options are required');
902
+
903
+ const configById = new Map((binding.configOptions || []).map(option => [option.id, option]));
904
+ const modelChange = normalized.find(change => {
905
+ const option = configById.get(change.configId);
906
+ return option?.type === 'select'
907
+ && /(^|[\s_-])model([\s_-]|$)/i.test(`${option.id} ${option.name || ''} ${option.category || ''}`);
908
+ });
909
+ const reasoningChange = normalized.find(change => {
910
+ const option = configById.get(change.configId);
911
+ return option?.type === 'select'
912
+ && /(reasoning|thought)/i.test(`${option.id} ${option.name || ''} ${option.category || ''}`);
913
+ });
914
+
915
+ let response;
916
+ const handled = new Set();
917
+ if (binding.provider === 'codex' && modelChange && reasoningChange) {
918
+ await this.applySessionConfigOption(binding, modelChange.configId, modelChange.value, { emit: false });
919
+ await this.applySessionConfigOption(binding, reasoningChange.configId, reasoningChange.value, { emit: false });
920
+ const currentReasoning = binding.configOptions?.find(candidate => (
921
+ candidate.type === 'select'
922
+ && /(reasoning|thought)/i.test(`${candidate.id} ${candidate.name || ''} ${candidate.category || ''}`)
923
+ ));
924
+ await this.refreshCodexSessionModel(
925
+ binding,
926
+ String(modelChange.value ?? ''),
927
+ String(currentReasoning?.currentValue || reasoningChange.value || '')
928
+ );
929
+ response = await this.applySessionConfigOption(binding, modelChange.configId, modelChange.value);
930
+ handled.add(modelChange);
931
+ handled.add(reasoningChange);
932
+ }
933
+ for (const change of normalized) {
934
+ if (handled.has(change)) continue;
935
+ response = await this.setSessionConfigOption(agentId, change.configId, change.value);
936
+ }
937
+ return response;
938
+ }
939
+
940
+ async refreshCodexSessionModel(binding, model, effort) {
941
+ await withTimeout(
942
+ binding.connection.request(CODEX_SET_SESSION_MODEL_METHOD, {
943
+ sessionId: binding.sessionId,
944
+ modelId: `${model}[${effort}]`,
945
+ }),
946
+ this.requestTimeoutMs,
947
+ 'Codex ACP session/set_model capability refresh'
948
+ );
949
+ }
950
+
951
+ async applySessionConfigOption(binding, configId, value, options = {}) {
860
952
  const request = typeof value === 'boolean'
861
953
  ? { sessionId: binding.sessionId, configId: String(configId || ''), type: 'boolean', value }
862
954
  : { sessionId: binding.sessionId, configId: String(configId || ''), value: String(value ?? '') };
@@ -867,7 +959,7 @@ class AcpRuntime extends EventEmitter {
867
959
  );
868
960
  binding.configOptions = response?.configOptions || binding.configOptions;
869
961
  binding.sessionState.configOptions = JSON.parse(JSON.stringify(binding.configOptions));
870
- this.emitSession(binding);
962
+ if (options.emit !== false) this.emitSession(binding);
871
963
  return { sessionId: binding.sessionId, configOptions: binding.configOptions };
872
964
  }
873
965
 
@@ -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;
@@ -2189,6 +2212,7 @@ class AgentManager extends EventEmitter {
2189
2212
  providerSessionSource: providerSessionPlan.source || '',
2190
2213
  providerSessionResolvedAt: providerSessionPlan.temporary === true ? null : Date.now(),
2191
2214
  providerSessionTitle: typeof options.providerSessionTitle === 'string' ? options.providerSessionTitle.trim().slice(0, 160) : '',
2215
+ terminalInputReceived: false,
2192
2216
  codexRuntimeMode: providerSessionPlan.provider === 'codex'
2193
2217
  ? (useCodexAppServer ? 'app-server' : 'cli')
2194
2218
  : '',
@@ -2338,7 +2362,9 @@ class AgentManager extends EventEmitter {
2338
2362
  executable: spawnProgram,
2339
2363
  env: this.buildAgentEnv(agentId, agentRecord),
2340
2364
  cwd: workspace,
2341
- sessionId: agentRecord.providerSessionTemporary ? '' : agentRecord.providerSessionId,
2365
+ sessionId: options.acpStartFresh === true || agentRecord.providerSessionTemporary
2366
+ ? ''
2367
+ : agentRecord.providerSessionId,
2342
2368
  historyMode: options.acpHistoryMode === 'resume' ? 'resume' : 'load',
2343
2369
  approvalMode: agentRecord.launchPermissionMode || 'approve',
2344
2370
  model: this.configManager && this.configManager.getCodexModel
@@ -2721,6 +2747,11 @@ class AgentManager extends EventEmitter {
2721
2747
  return this.acpRuntime.setSessionConfigOption(agentId, configId, value);
2722
2748
  }
2723
2749
 
2750
+ setAcpSessionConfigOptions(agentId, changes) {
2751
+ this.getAcpSession(agentId);
2752
+ return this.acpRuntime.setSessionConfigOptions(agentId, changes);
2753
+ }
2754
+
2724
2755
  async setCodexAppServerGoal(agentId, patch = {}) {
2725
2756
  const agent = this.assertCodexAppServerGoalAgent(agentId);
2726
2757
  const objective = typeof patch.objective === 'string' ? patch.objective.trim().slice(0, 4000) : undefined;
@@ -2763,6 +2794,12 @@ class AgentManager extends EventEmitter {
2763
2794
  const engine = this.engineBridge.getEngine(agent.engineName);
2764
2795
  if (!engine) return;
2765
2796
 
2797
+ if (agent.terminalInputReceived !== true) {
2798
+ agent.terminalInputReceived = true;
2799
+ this.ensurePersistentAgentSession(agent);
2800
+ this.updateEngineProviderSessionMetadata(agent);
2801
+ }
2802
+
2766
2803
  for (let attempt = 0; ; attempt += 1) {
2767
2804
  try {
2768
2805
  await engine.sendInput(agentId, input);
@@ -3158,7 +3195,14 @@ class AgentManager extends EventEmitter {
3158
3195
  return { error: `Agent does not support the ${nextMode.toUpperCase()} runtime` };
3159
3196
  }
3160
3197
  const sessionId = String(agent.providerSessionId || '').trim();
3161
- if (!isSafeProviderSessionId(sessionId)) {
3198
+ const canStartFreshAcpSession = nextMode === 'acp'
3199
+ && agent.agentRuntimeMode === 'terminal'
3200
+ && agent.terminalInputReceived !== true
3201
+ && (
3202
+ agent.providerSessionTemporary === true
3203
+ || ['codex-temporary', 'claude-session-id', 'qoder-session-id'].includes(agent.providerSessionSource || '')
3204
+ );
3205
+ if (!isSafeProviderSessionId(sessionId) && !canStartFreshAcpSession) {
3162
3206
  return { error: 'Runtime switching requires a resumable provider session. Send the first message and try again.' };
3163
3207
  }
3164
3208
  // A live ACP binding is the authoritative owner of a newly-created
@@ -3177,15 +3221,19 @@ class AgentManager extends EventEmitter {
3177
3221
  }
3178
3222
  }
3179
3223
  const previouslyVerifiedSession = String(agent.runtimeSwitchVerifiedSessionId || '') === sessionId;
3180
- if (!liveAcpSession && !previouslyVerifiedSession) {
3224
+ let startsFreshAcpSession = canStartFreshAcpSession && !isSafeProviderSessionId(sessionId);
3225
+ if (!startsFreshAcpSession && !liveAcpSession && !previouslyVerifiedSession) {
3181
3226
  const providerSession = await this.findRuntimeSwitchSession(agent);
3182
3227
  if (!providerSession) {
3183
- return { error: 'The saved Agent session is no longer available in the selected Agent Home.' };
3228
+ if (canStartFreshAcpSession) startsFreshAcpSession = true;
3229
+ else return { error: 'The saved Agent session is no longer available in the selected Agent Home.' };
3184
3230
  }
3185
3231
  }
3186
- const command = buildAgentSessionResumeCommand(provider, sessionId, {
3187
- cwd: agent.cwd || agent.projectWorkspace || '',
3188
- });
3232
+ const command = startsFreshAcpSession
3233
+ ? (agent.forkCommand || agent.command)
3234
+ : buildAgentSessionResumeCommand(provider, sessionId, {
3235
+ cwd: agent.cwd || agent.projectWorkspace || '',
3236
+ });
3189
3237
  if (!command) return { error: 'Failed to build provider resume command' };
3190
3238
  const preserved = {
3191
3239
  pinned: agent.pinned === true,
@@ -3202,7 +3250,9 @@ class AgentManager extends EventEmitter {
3202
3250
  task: agent.task || agent.providerSessionTitle || '',
3203
3251
  workflowTemplate: agent.workflowTemplate || '',
3204
3252
  projectWorkspace: agent.projectWorkspace || agent.cwd || '',
3205
- source: resumedAgentSource(provider, sessionId, agent.providerHomeId || ''),
3253
+ source: startsFreshAcpSession
3254
+ ? 'ui-runtime-switch-fresh'
3255
+ : resumedAgentSource(provider, sessionId, agent.providerHomeId || ''),
3206
3256
  providerHomeId: agent.providerHomeId || '',
3207
3257
  providerHomePath: agent.providerHomePath || '',
3208
3258
  providerSessionTitle: agent.providerSessionTitle || '',
@@ -3215,16 +3265,18 @@ class AgentManager extends EventEmitter {
3215
3265
  projectOrder: preserved.projectOrder,
3216
3266
  pinnedOrder: preserved.pinnedOrder,
3217
3267
  agentRuntimeMode: nextMode,
3268
+ acpStartFresh: startsFreshAcpSession,
3218
3269
  codexRuntimeMode: 'cli',
3219
3270
  codexApprovalMode: agent.launchPermissionMode || undefined,
3220
3271
  jsonCliEvents: preserved.jsonCliEvents,
3221
- runtimeSwitchVerifiedSessionId: sessionId,
3272
+ runtimeSwitchVerifiedSessionId: startsFreshAcpSession ? '' : sessionId,
3222
3273
  };
3223
3274
  const originalMode = agent.agentRuntimeMode || 'terminal';
3224
3275
  const originalOptions = {
3225
3276
  ...restartOptions,
3226
3277
  agentRuntimeMode: originalMode,
3227
3278
  codexRuntimeMode: agent.codexRuntimeMode || 'cli',
3279
+ acpStartFresh: false,
3228
3280
  };
3229
3281
  const startReplacement = options => new Promise(resolve => {
3230
3282
  let settled = false;
@@ -3930,6 +3982,7 @@ class AgentManager extends EventEmitter {
3930
3982
  providerSessionSource: agent.providerSessionSource || '',
3931
3983
  providerSessionResolvedAt: agent.providerSessionResolvedAt || null,
3932
3984
  providerSessionTitle: agent.providerSessionTitle || '',
3985
+ terminalInputReceived: agent.terminalInputReceived === true,
3933
3986
  codexRuntimeMode: agent.codexRuntimeMode || '',
3934
3987
  agentRuntimeMode: agent.agentRuntimeMode || 'terminal',
3935
3988
  acpState: agent.acpState || '',
@@ -4095,6 +4148,7 @@ class AgentManager extends EventEmitter {
4095
4148
  providerSessionSource: agent.providerSessionSource || '',
4096
4149
  providerSessionResolvedAt: agent.providerSessionResolvedAt || null,
4097
4150
  providerSessionTitle: agent.providerSessionTitle || '',
4151
+ terminalInputReceived: agent.terminalInputReceived === true,
4098
4152
  codexRuntimeMode: agent.codexRuntimeMode || '',
4099
4153
  agentRuntimeMode: agent.agentRuntimeMode || 'terminal',
4100
4154
  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: {