@jian-ai/remote-control 0.8.1 → 0.8.2

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.
@@ -21,7 +21,7 @@ const option = (name) => {
21
21
  * Stamped to the release tag on npm publish and when the gateway serves this script.
22
22
  * Compatibility with the gateway requires the same major.minor (see versionsCompatible).
23
23
  */
24
- export const PACKAGE_VERSION = '0.8.1';
24
+ export const PACKAGE_VERSION = '0.8.2';
25
25
 
26
26
  /** Soft stdout/stderr caps while capturing a command (chars). Gateway schema matches these. */
27
27
  const STDOUT_CAP = 400_000;
@@ -136,6 +136,9 @@ export function buildMacosAppInfoPlist() {
136
136
  }
137
137
 
138
138
  export function buildLaunchAgentPlist({ label, executable, script, logPath }) {
139
+ // ProcessType=Interactive opts the worker out of App Nap. Without it, macOS can freeze the
140
+ // Node poll loop while the screen is locked or idle; lastSeenAt ages out and the gateway
141
+ // shows Offline even though the process is still "running".
139
142
  return `<?xml version="1.0" encoding="UTF-8"?>
140
143
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
141
144
  <plist version="1.0"><dict>
@@ -148,6 +151,7 @@ export function buildLaunchAgentPlist({ label, executable, script, logPath }) {
148
151
  <string>${xmlEscape(script)}</string>
149
152
  <string>run</string>
150
153
  </array>
154
+ <key>ProcessType</key><string>Interactive</string>
151
155
  <key>RunAtLoad</key><true/>
152
156
  <key>KeepAlive</key><true/>
153
157
  <key>StandardOutPath</key><string>${xmlEscape(logPath)}</string>
@@ -486,19 +490,26 @@ async function materializeWorkerScript(sourceBytes) {
486
490
  if (source !== installedScript) await copyFile(source, installedScript);
487
491
  }
488
492
  if (process.platform !== 'win32') await chmod(installedScript, 0o700);
489
- // Companion modules + macOS helper source (computer use). Best-effort beside the worker.
493
+ // Companion modules + OS helpers (computer use). Best-effort beside the worker.
490
494
  try {
491
495
  const here = dirname(fileURLToPath(import.meta.url));
492
- const computerSrc = join(here, 'computer-macos.mjs');
493
- const computerDest = join(configDir, 'computer-macos.mjs');
494
- if (existsSync(computerSrc) && computerSrc !== computerDest) {
495
- await copyFile(computerSrc, computerDest);
496
- if (process.platform !== 'win32') await chmod(computerDest, 0o700);
497
- }
498
- if (process.platform === 'darwin') {
499
- const { materializeComputerHelperSources } = await import('./computer-macos.mjs');
500
- await materializeComputerHelperSources(configDir);
496
+ const companions = [
497
+ 'computer.mjs',
498
+ 'computer-shared.mjs',
499
+ 'computer-macos.mjs',
500
+ 'computer-linux.mjs',
501
+ 'computer-windows.mjs',
502
+ ];
503
+ for (const name of companions) {
504
+ const src = join(here, name);
505
+ const dest = join(configDir, name);
506
+ if (existsSync(src) && src !== dest) {
507
+ await copyFile(src, dest);
508
+ if (process.platform !== 'win32') await chmod(dest, 0o700);
509
+ }
501
510
  }
511
+ const { materializeComputerHelperSources } = await import('./computer.mjs');
512
+ await materializeComputerHelperSources(configDir);
502
513
  } catch {
503
514
  // Install still succeeds; computer-use probe will explain if the helper is missing.
504
515
  }
@@ -816,17 +827,7 @@ async function executeShell(task) {
816
827
 
817
828
  async function execute(task) {
818
829
  if (task.kind === 'computer') {
819
- if (process.platform !== 'darwin') {
820
- return {
821
- taskId: task.id,
822
- exitCode: 1,
823
- stdout: '',
824
- stderr: 'Computer use is supported on macOS Jian Remote only.',
825
- timedOut: false,
826
- computer: { ok: false, error: 'Computer use is supported on macOS Jian Remote only.' },
827
- };
828
- }
829
- const { executeComputerTask } = await import('./computer-macos.mjs');
830
+ const { executeComputerTask } = await import('./computer.mjs');
830
831
  return executeComputerTask(task);
831
832
  }
832
833
  return executeShell(task);
@@ -940,20 +941,35 @@ async function postResult(base, headers, result) {
940
941
  async function run() {
941
942
  const config = await loadConfig();
942
943
  const capabilities = [];
944
+ let probeMacUiState = () => 'unknown';
945
+ try {
946
+ const { probeComputerCapability, computerCapabilityName } = await import('./computer.mjs');
947
+ const name = computerCapabilityName();
948
+ if (name && (await probeComputerCapability())) capabilities.push(name);
949
+ } catch {
950
+ // Computer use unavailable until helper is ready; shell tasks still work.
951
+ }
943
952
  if (process.platform === 'darwin') {
944
953
  try {
945
- const { probeComputerCapability, COMPUTER_CAPABILITY } = await import('./computer-macos.mjs');
946
- if (await probeComputerCapability()) capabilities.push(COMPUTER_CAPABILITY);
954
+ const mac = await import('./computer-macos.mjs');
955
+ mac.startWorkerIdleAssertion(process.pid);
956
+ probeMacUiState = () => mac.probeMacUiState();
957
+ for (const signal of ['SIGINT', 'SIGTERM']) {
958
+ process.once(signal, () => {
959
+ mac.stopWorkerIdleAssertion();
960
+ });
961
+ }
947
962
  } catch {
948
- // Computer use unavailable until helper builds; shell tasks still work.
963
+ // Idle assertion / uiState probe optional; shell still works.
949
964
  }
950
965
  }
951
- const headers = {
966
+ const pollHeaders = () => ({
952
967
  authorization: `Bearer ${config.token}`,
953
968
  'x-jian-platform': `${process.platform}-${process.arch}`,
954
969
  'x-jian-remote-version': PACKAGE_VERSION,
970
+ 'x-jian-remote-ui-state': probeMacUiState(),
955
971
  ...(capabilities.length ? { 'x-jian-remote-capabilities': capabilities.join(',') } : {}),
956
- };
972
+ });
957
973
  const base = `${config.gateway}/v1/remote/worker/${config.machineId}`;
958
974
  // Fail fast before the poll loop so an incompatible build does not look "online then stuck".
959
975
  const gatewayVersion = await assertCompatibleWithGateway(config.gateway);
@@ -978,6 +994,7 @@ async function run() {
978
994
  };
979
995
  while (true) {
980
996
  try {
997
+ const headers = pollHeaders();
981
998
  if (Date.now() >= nextUpdateCheck) {
982
999
  await ensureCompatible();
983
1000
  await updateIfNeeded(base, headers);