farming-code 2.2.20 → 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 (49) 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 +332 -102
  22. package/backend/storage-layout.js +10 -0
  23. package/backend/theme-manager.js +23 -5
  24. package/backend/update-service.js +239 -27
  25. package/backend/workspace-file-router.js +9 -3
  26. package/backend/workspace-file-service.js +292 -62
  27. package/dist/acp/codex-acp-1.1.4.js +32 -1
  28. package/dist/assets/App-CmZdNavW.js +210 -0
  29. package/dist/assets/{FileEditorMarkdownPreview-R0jwwLME.js → FileEditorMarkdownPreview-KbnE8VC8.js} +1 -1
  30. package/dist/assets/FileEditorPane-5_HUejjd.js +2 -0
  31. package/dist/assets/IconGlyphs-C2WFcoJS.js +1 -0
  32. package/dist/assets/ProjectFilesSection-C1RAJWOE.js +12 -0
  33. package/dist/assets/{ReviewPage-DBKNOWsJ.js → ReviewPage-L1vh1Ggs.js} +3 -3
  34. package/dist/assets/code-dark-BKtvf0Xc.css +1 -0
  35. package/dist/assets/file-icons-CHWt5nSy.js +1 -0
  36. package/dist/assets/{index-DuMjesL2.js → index-RvUiMpGP.js} +2 -2
  37. package/dist/assets/main-XMXgyJJt.css +1 -0
  38. package/dist/assets/workspace-view-state-BqB4HM7r.js +7 -0
  39. package/dist/index.html +1 -1
  40. package/frontend/skins/crt/app.js +199 -81
  41. package/package.json +9 -3
  42. package/dist/assets/App-BupehIbc.js +0 -210
  43. package/dist/assets/FileEditorPane-CJs6kfZO.js +0 -2
  44. package/dist/assets/IconGlyphs-CZzDr5o2.js +0 -1
  45. package/dist/assets/ProjectFilesSection-DWCV07lA.js +0 -12
  46. package/dist/assets/code-dark-BAt0Q_Op.css +0 -1
  47. package/dist/assets/file-icons-DpbrD5ZP.js +0 -1
  48. package/dist/assets/main-y2aK_56c.css +0 -1
  49. package/dist/assets/workspace-view-state-BpPINu84.js +0 -7
@@ -18,6 +18,9 @@ const DEFAULT_PORT = '6694';
18
18
  const DEFAULT_BASE_PATH = '/farming';
19
19
  const DEFAULT_SERVER_START_TIMEOUT_MS = 30_000;
20
20
  const DEFAULT_SERVER_START_STABILITY_MS = 1_500;
21
+ const DEFAULT_SERVER_STOP_TIMEOUT_MS = 30_000;
22
+ const SERVER_STOP_POLL_MS = 100;
23
+ const SERVER_PROCESS_IDENTITY_FORMAT = 'ps-lstart-c-utc-v1';
21
24
  const SERVER_COMMANDS = new Set(['start', 'serve', 'daemon', 'stop', 'status', 'logs', 'url', 'help']);
22
25
  const CONTROL_COMMANDS = new Set(['skills', 'memory', 'report', 'list', 'spawn', 'output', 'send', 'kill']);
23
26
  const SERVER_BACKED_CONTROL_COMMANDS = new Set(['list', 'spawn', 'output', 'send', 'kill']);
@@ -42,14 +45,19 @@ function quoteShellArg(arg) {
42
45
  }
43
46
 
44
47
  function buildCleanEnvExecCommand(env, command, args = []) {
45
- const parts = ['/usr/bin/env', '-i'];
48
+ const parts = ['/usr/bin/env', ...buildCleanEnvExecArgs(env, command, args)];
49
+ return parts.map(quoteShellArg).join(' ');
50
+ }
51
+
52
+ function buildCleanEnvExecArgs(env, command, args = []) {
53
+ const parts = ['-i'];
46
54
  Object.entries(env || {}).forEach(([key, value]) => {
47
55
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) return;
48
56
  if (value === undefined || value === null) return;
49
57
  parts.push(`${key}=${String(value)}`);
50
58
  });
51
59
  parts.push(command, ...args);
52
- return parts.map(quoteShellArg).join(' ');
60
+ return parts;
53
61
  }
54
62
 
55
63
  function readMemoryLimitBytes() {
@@ -424,7 +432,13 @@ async function runServerBackedControlCli(argv) {
424
432
 
425
433
  function childInvocation(env = process.env) {
426
434
  if (process.pkg) {
427
- return { command: '/bin/sh', args: ['-c', buildCleanEnvExecCommand(env, process.execPath, ['--'])] };
435
+ // Spawn env directly so it execs the packaged binary in-place. A shell
436
+ // wrapper may keep its own PID while the listener belongs to a child,
437
+ // making daemon ownership impossible to prove on Linux.
438
+ return {
439
+ command: '/usr/bin/env',
440
+ args: buildCleanEnvExecArgs(env, process.execPath, ['--']),
441
+ };
428
442
  }
429
443
  const nodePath = env.FARMING_NODE_BIN || process.execPath;
430
444
  if (env.FARMING_NODE_LD && env.FARMING_NODE_LIBRARY_PATH) {
@@ -460,17 +474,271 @@ function readServerState(configDir) {
460
474
  }
461
475
  }
462
476
 
463
- function writeServerState(configDir, env, pid) {
477
+ function canonicalConfigDir(configDir) {
478
+ try {
479
+ return fs.realpathSync.native(configDir);
480
+ } catch {
481
+ return path.resolve(configDir);
482
+ }
483
+ }
484
+
485
+ async function readServerProcessIdentity(pid) {
486
+ const processId = Number(pid);
487
+ if (!Number.isSafeInteger(processId) || processId <= 0 || process.platform === 'win32') return null;
488
+ let stdout;
489
+ try {
490
+ stdout = execFileSync(
491
+ '/bin/ps',
492
+ ['-p', String(processId), '-o', 'pid=', '-o', 'pgid=', '-o', 'lstart='],
493
+ {
494
+ encoding: 'utf8',
495
+ timeout: 1_000,
496
+ maxBuffer: 16_384,
497
+ env: { ...process.env, LANG: 'C', LC_ALL: 'C', TZ: 'UTC' },
498
+ },
499
+ );
500
+ } catch (error) {
501
+ if (error?.status === 1 || error?.code === 'ESRCH') return null;
502
+ throw error;
503
+ }
504
+ const match = String(stdout || '').trim().match(/^(\d+)\s+(\d+)\s+(.+)$/);
505
+ if (!match || Number(match[1]) !== processId) return null;
506
+ return {
507
+ pid: processId,
508
+ processGroupId: Number(match[2]),
509
+ startedAt: match[3].trim(),
510
+ format: SERVER_PROCESS_IDENTITY_FORMAT,
511
+ };
512
+ }
513
+
514
+ function writeServerState(configDir, env, pid, processIdentity) {
464
515
  const state = {
465
516
  pid,
466
517
  port: Number(env.PORT || DEFAULT_PORT),
467
518
  basePath: env.FARMING_BASE_PATH || DEFAULT_BASE_PATH,
468
- configDir,
519
+ configDir: canonicalConfigDir(configDir),
520
+ processIdentity,
469
521
  updatedAt: new Date().toISOString(),
470
522
  };
471
523
  fs.writeFileSync(serverStateFile(configDir), `${JSON.stringify(state, null, 2)}\n`);
472
524
  }
473
525
 
526
+ function processLooksLikeFarmingServer(pid) {
527
+ let environment = '';
528
+ try {
529
+ environment = fs.readFileSync(`/proc/${pid}/environ`, 'utf8').replace(/\0/g, ' ');
530
+ } catch {
531
+ try {
532
+ environment = execFileSync(
533
+ '/bin/ps',
534
+ ['eww', '-p', String(pid), '-o', 'command='],
535
+ { encoding: 'utf8', timeout: 1_000, maxBuffer: 1024 * 1024 },
536
+ );
537
+ } catch {
538
+ environment = '';
539
+ }
540
+ }
541
+ if (/(?:^|\s)FARMING_RUN_SERVER=1(?:\s|$)/.test(environment)) return true;
542
+ return /(?:^|\s)(?:\S+[/\\])?backend[/\\]server\.js(?:\s|$)/.test(environment);
543
+ }
544
+
545
+ function processHasExactEnvironment(pid, expected) {
546
+ try {
547
+ const entries = fs.readFileSync(`/proc/${pid}/environ`, 'utf8')
548
+ .split('\0')
549
+ .filter(Boolean)
550
+ .map(entry => {
551
+ const separator = entry.indexOf('=');
552
+ return separator < 0 ? [entry, ''] : [entry.slice(0, separator), entry.slice(separator + 1)];
553
+ });
554
+ const environment = new Map(entries);
555
+ return Object.entries(expected).every(([key, value]) => environment.get(key) === String(value));
556
+ } catch {
557
+ if (Object.values(expected).some(value => /\s/.test(String(value)))) return false;
558
+ try {
559
+ const environment = execFileSync(
560
+ '/bin/ps',
561
+ ['eww', '-p', String(pid), '-o', 'command='],
562
+ { encoding: 'utf8', timeout: 1_000, maxBuffer: 1024 * 1024 },
563
+ );
564
+ return Object.entries(expected).every(([key, value]) => {
565
+ const escaped = `${key}=${value}`.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
566
+ return new RegExp(`(?:^|\\s)${escaped}(?:\\s|$)`).test(environment);
567
+ });
568
+ } catch {
569
+ return false;
570
+ }
571
+ }
572
+ }
573
+
574
+ function readLegacyServerSettings(configDir, state, pid) {
575
+ const authDisabled = processHasExactEnvironment(pid, { FARMING_DISABLE_AUTH: '1' });
576
+ const token = authDisabled ? '' : readTokenForEnv({ FARMING_CONFIG_DIR: configDir });
577
+ if (!authDisabled && !token) {
578
+ return Promise.reject(new Error('legacy Farming server identity probe requires this config directory token'));
579
+ }
580
+ const requestPath = routePath(state.basePath || DEFAULT_BASE_PATH, '/api/settings');
581
+ return new Promise((resolve, reject) => {
582
+ const req = http.request({
583
+ host: '127.0.0.1',
584
+ port: Number(state.port),
585
+ path: requestPath,
586
+ method: 'GET',
587
+ timeout: 1_000,
588
+ headers: token ? { Cookie: `farming_token=${encodeURIComponent(token)}` } : {},
589
+ }, res => {
590
+ let body = '';
591
+ res.setEncoding('utf8');
592
+ res.on('data', chunk => {
593
+ if (body.length <= 64 * 1024) body += chunk;
594
+ });
595
+ res.on('end', () => {
596
+ if (res.statusCode !== 200) {
597
+ reject(new Error(`legacy Farming server identity probe returned HTTP ${res.statusCode || 0}`));
598
+ return;
599
+ }
600
+ try {
601
+ const payload = JSON.parse(body);
602
+ if (!payload?.settings || typeof payload.settings !== 'object') {
603
+ reject(new Error('legacy Farming server identity probe did not return Farming settings'));
604
+ return;
605
+ }
606
+ resolve(payload.settings);
607
+ } catch (error) {
608
+ reject(new Error(`legacy Farming server identity probe was invalid: ${error.message || error}`));
609
+ }
610
+ });
611
+ });
612
+ req.on('error', reject);
613
+ req.on('timeout', () => req.destroy(new Error('legacy Farming server identity probe timed out')));
614
+ req.end();
615
+ });
616
+ }
617
+
618
+ function processOwnsListeningPort(pid, port) {
619
+ const processId = Number(pid);
620
+ const portNumber = Number(port);
621
+ if (!Number.isSafeInteger(processId) || processId <= 0 || !Number.isInteger(portNumber) || portNumber <= 0) {
622
+ return false;
623
+ }
624
+ if (process.platform === 'linux') {
625
+ try {
626
+ const socketInodes = new Set(fs.readdirSync(`/proc/${processId}/fd`).flatMap(entry => {
627
+ try {
628
+ const target = fs.readlinkSync(`/proc/${processId}/fd/${entry}`);
629
+ const match = target.match(/^socket:\[(\d+)\]$/);
630
+ return match ? [match[1]] : [];
631
+ } catch {
632
+ return [];
633
+ }
634
+ }));
635
+ const portHex = portNumber.toString(16).toUpperCase().padStart(4, '0');
636
+ for (const table of ['/proc/net/tcp', '/proc/net/tcp6']) {
637
+ let rows = [];
638
+ try {
639
+ rows = fs.readFileSync(table, 'utf8').trim().split(/\r?\n/).slice(1);
640
+ } catch {
641
+ continue;
642
+ }
643
+ if (rows.some(row => {
644
+ const fields = row.trim().split(/\s+/);
645
+ const localPort = String(fields[1] || '').split(':').at(-1);
646
+ return localPort === portHex && fields[3] === '0A' && socketInodes.has(fields[9]);
647
+ })) return true;
648
+ }
649
+ return false;
650
+ } catch {
651
+ return false;
652
+ }
653
+ }
654
+ try {
655
+ const lsof = fs.existsSync('/usr/sbin/lsof') ? '/usr/sbin/lsof' : 'lsof';
656
+ const stdout = execFileSync(
657
+ lsof,
658
+ ['-nP', '-a', '-p', String(processId), `-iTCP:${portNumber}`, '-sTCP:LISTEN', '-Fpfn'],
659
+ { encoding: 'utf8', timeout: 1_000, maxBuffer: 64 * 1024 },
660
+ );
661
+ return String(stdout || '').split(/\r?\n/).some(line => line.startsWith('n') && line.endsWith(`:${portNumber}`));
662
+ } catch {
663
+ return false;
664
+ }
665
+ }
666
+
667
+ function matchingProcessIdentity(expected, current) {
668
+ return Boolean(
669
+ current
670
+ && current.pid === Number(expected?.pid)
671
+ && current.processGroupId === Number(expected?.processGroupId)
672
+ && current.startedAt === String(expected?.startedAt || '')
673
+ );
674
+ }
675
+
676
+ async function assertServerProcessIdentity(configDir, pid, state) {
677
+ const expected = state?.processIdentity;
678
+ if (
679
+ Number(state?.pid) !== pid
680
+ || canonicalConfigDir(state?.configDir || '') !== canonicalConfigDir(configDir)
681
+ || !expected
682
+ || Number(expected.pid) !== pid
683
+ || !Number.isSafeInteger(Number(expected.processGroupId))
684
+ || Number(expected.processGroupId) <= 0
685
+ || !String(expected.startedAt || '').trim()
686
+ ) {
687
+ throw new Error(
688
+ `Refusing to stop Farming PID ${pid}: server control metadata does not contain a matching process identity`,
689
+ );
690
+ }
691
+ const current = await readServerProcessIdentity(pid);
692
+ if (!matchingProcessIdentity(expected, current)) {
693
+ throw new Error(
694
+ `Refusing to stop Farming PID ${pid}: the live process identity does not match the server control metadata`,
695
+ );
696
+ }
697
+ if (!processOwnsListeningPort(pid, Number(state.port))) {
698
+ throw new Error(`Refusing to stop Farming PID ${pid}: the process does not own listening port ${state.port}`);
699
+ }
700
+ return current;
701
+ }
702
+
703
+ async function migrateLegacyServerIdentity(configDir, pid, state) {
704
+ if (
705
+ Number(state?.pid) !== pid
706
+ || canonicalConfigDir(state?.configDir || '') !== canonicalConfigDir(configDir)
707
+ || !Number.isInteger(Number(state?.port))
708
+ || Number(state.port) <= 0
709
+ || Number(state.port) > 65535
710
+ || !processLooksLikeFarmingServer(pid)
711
+ || !processHasExactEnvironment(pid, {
712
+ FARMING_CONFIG_DIR: configDir,
713
+ PORT: String(state.port),
714
+ })
715
+ ) {
716
+ throw new Error(
717
+ `Refusing to stop Farming PID ${pid}: legacy server control metadata could not prove this process belongs to the config directory`,
718
+ );
719
+ }
720
+ const firstIdentity = await readServerProcessIdentity(pid);
721
+ if (!firstIdentity) {
722
+ throw new Error(`Refusing to stop Farming PID ${pid}: the legacy server process identity could not be read`);
723
+ }
724
+ if (!processOwnsListeningPort(pid, Number(state.port))) {
725
+ throw new Error(`Refusing to stop Farming PID ${pid}: the legacy process does not own listening port ${state.port}`);
726
+ }
727
+ await readLegacyServerSettings(configDir, state, pid);
728
+ const stableIdentity = await readServerProcessIdentity(pid);
729
+ if (!matchingProcessIdentity(firstIdentity, stableIdentity) || readPid(configDir) !== pid) {
730
+ throw new Error(`Refusing to stop Farming PID ${pid}: the legacy server process identity changed during verification`);
731
+ }
732
+ fs.writeFileSync(serverStateFile(configDir), `${JSON.stringify({
733
+ ...state,
734
+ pid,
735
+ configDir: canonicalConfigDir(configDir),
736
+ processIdentity: stableIdentity,
737
+ identityMigratedAt: new Date().toISOString(),
738
+ }, null, 2)}\n`);
739
+ return stableIdentity;
740
+ }
741
+
474
742
  function readPid(configDir) {
475
743
  try {
476
744
  return Number(fs.readFileSync(pidFile(configDir), 'utf8').trim());
@@ -523,6 +791,36 @@ function serverStartStabilityMs(env) {
523
791
  return DEFAULT_SERVER_START_STABILITY_MS;
524
792
  }
525
793
 
794
+ function serverStopTimeoutMs(env) {
795
+ const parsed = Number(env.FARMING_STOP_TIMEOUT_MS || env.FARMING_SERVER_STOP_TIMEOUT_MS);
796
+ if (Number.isFinite(parsed) && parsed > 0) return Math.min(parsed, 120_000);
797
+ return DEFAULT_SERVER_STOP_TIMEOUT_MS;
798
+ }
799
+
800
+ async function waitForDaemonStop(pid, port, options = {}) {
801
+ const timeoutMs = options.timeoutMs || DEFAULT_SERVER_STOP_TIMEOUT_MS;
802
+ const processRunning = options.isRunning || isRunning;
803
+ const portAvailable = options.canListenOnPort || canListenOnPort;
804
+ const now = options.now || Date.now;
805
+ const wait = options.wait || (durationMs => new Promise(resolve => setTimeout(resolve, durationMs)));
806
+ const startedAt = now();
807
+ let running = true;
808
+ let released = false;
809
+
810
+ while (true) {
811
+ running = processRunning(pid);
812
+ released = await portAvailable(port);
813
+ if (!running && released) return;
814
+ if (now() - startedAt >= timeoutMs) {
815
+ throw new Error(
816
+ `Farming server PID ${pid} did not stop and release port ${port} within ${timeoutMs}ms`
817
+ + ` (process ${running ? 'still running' : 'exited'}, port ${released ? 'released' : 'still in use'})`,
818
+ );
819
+ }
820
+ await wait(SERVER_STOP_POLL_MS);
821
+ }
822
+ }
823
+
526
824
  function waitForProcessStability(pid, durationMs = DEFAULT_SERVER_START_STABILITY_MS) {
527
825
  const startedAt = Date.now();
528
826
  return new Promise((resolve, reject) => {
@@ -682,9 +980,20 @@ async function startDaemon(parsed) {
682
980
  fs.writeFileSync(pidFile(configDir), String(child.pid));
683
981
 
684
982
  try {
983
+ const processIdentity = await readServerProcessIdentity(child.pid);
984
+ if (!processIdentity) throw new Error('server process identity could not be verified after launch');
685
985
  await waitForServer(env, serverStartTimeoutMs(env), child.pid);
686
986
  await waitForProcessStability(child.pid, serverStartStabilityMs(env));
687
987
  await waitForServer(env, Math.min(serverStartTimeoutMs(env), 5_000), child.pid);
988
+ const stableIdentity = await readServerProcessIdentity(child.pid);
989
+ if (
990
+ !stableIdentity
991
+ || stableIdentity.processGroupId !== processIdentity.processGroupId
992
+ || stableIdentity.startedAt !== processIdentity.startedAt
993
+ ) {
994
+ throw new Error('server process identity changed during startup');
995
+ }
996
+ writeServerState(configDir, env, child.pid, stableIdentity);
688
997
  } catch (error) {
689
998
  cleanupFailedDaemonStart(configDir, child.pid);
690
999
  console.error(error.message);
@@ -694,7 +1003,6 @@ async function startDaemon(parsed) {
694
1003
  }
695
1004
 
696
1005
  console.log(`Farming started (PID ${child.pid})`);
697
- writeServerState(configDir, env, child.pid);
698
1006
  const logs = tailFile(logFile(configDir), 40);
699
1007
  const urlLines = logs.split(/\r?\n/).filter(line => /Local:|Network:|Token:|Token style:|Token auth:/.test(line));
700
1008
  if (urlLines.length > 0) {
@@ -705,7 +1013,7 @@ async function startDaemon(parsed) {
705
1013
  return 0;
706
1014
  }
707
1015
 
708
- function stopDaemon(parsed) {
1016
+ async function stopDaemon(parsed) {
709
1017
  const env = buildServerEnv(parsed.env);
710
1018
  const configDir = env.FARMING_CONFIG_DIR;
711
1019
  const pid = readPid(configDir);
@@ -715,9 +1023,21 @@ function stopDaemon(parsed) {
715
1023
  console.log('Farming is not running.');
716
1024
  return 0;
717
1025
  }
718
- process.kill(pid, 'SIGTERM');
719
- fs.rmSync(pidFile(configDir), { force: true });
720
- fs.rmSync(serverStateFile(configDir), { force: true });
1026
+ const state = readServerState(configDir);
1027
+ const port = Number(state.port || env.PORT || DEFAULT_PORT);
1028
+ if (state.processIdentity?.format === SERVER_PROCESS_IDENTITY_FORMAT) {
1029
+ await assertServerProcessIdentity(configDir, pid, state);
1030
+ } else {
1031
+ await migrateLegacyServerIdentity(configDir, pid, state);
1032
+ }
1033
+ try {
1034
+ process.kill(pid, 'SIGTERM');
1035
+ } catch (error) {
1036
+ if (error?.code !== 'ESRCH') throw error;
1037
+ }
1038
+ await waitForDaemonStop(pid, port, { timeoutMs: serverStopTimeoutMs(env) });
1039
+ if (readPid(configDir) === pid) fs.rmSync(pidFile(configDir), { force: true });
1040
+ if (Number(readServerState(configDir).pid) === pid) fs.rmSync(serverStateFile(configDir), { force: true });
721
1041
  console.log(`Stopped Farming (PID ${pid})`);
722
1042
  return 0;
723
1043
  }
@@ -841,6 +1161,7 @@ if (require.main === module) {
841
1161
  module.exports = {
842
1162
  SERVER_MODE_ARG,
843
1163
  NATIVE_PTY_HOST_ARG,
1164
+ buildCleanEnvExecArgs,
844
1165
  buildCleanEnvExecCommand,
845
1166
  childInvocation,
846
1167
  cleanupFailedDaemonStart,
@@ -854,9 +1175,13 @@ module.exports = {
854
1175
  resolveReviewTarget,
855
1176
  reviewUrl,
856
1177
  readServerState,
1178
+ readServerProcessIdentity,
857
1179
  serverStartTimeoutMs,
858
1180
  serverStartStabilityMs,
1181
+ serverStopTimeoutMs,
859
1182
  splitControlArgs,
1183
+ stopDaemon,
860
1184
  run,
861
1185
  serverStateFile,
1186
+ waitForDaemonStop,
862
1187
  };