master
Colby Knox Publish 0.17.0 1573f81 2mo ago
#!/usr/bin/env node
// Desktop-bridge adapter (hero-studio#31): the Adom Bridge v2 rename replaced
// the `adom-desktop` binary with `adom-bridge-cli` and moved pup's verbs to a
// `pup_*` prefix -- and the relay now refuses raw HTTP posts entirely. This
// module is the ONE place that knows which binary and which verb names a box
// has: it prefers adom-bridge-cli, resolves the live verb names from
// bridge_list output (manifests have claimed prefixes the box does not
// answer, so never hardcode), and falls back to legacy adom-desktop.
import { execFile } from 'child_process';

const run = (bin, args, timeout, target, thread) => new Promise((res) => {
  let a = target ? [...args, '--target', target] : [...args];
  // Caller identity rides every call (#42.2): adom-bridge-cli takes the
  // global --ai-thread flag; legacy adom-desktop reads ADOM_AI_THREAD env.
  if (thread && bin === 'adom-bridge-cli') a = ['--ai-thread', thread, ...a];
  const env = thread ? Object.assign({}, process.env, { ADOM_AI_THREAD: thread }) : process.env;
  execFile(bin, a, { timeout, env }, (err, stdout, stderr) => res({
    ok: !err,
    out: String(stdout || ''),
    errOut: String(stderr || ''),
    fail: err ? (err.killed ? 'timed out after ' + timeout + 'ms' : 'exit ' + (err.code ?? '?')) : '',
  }));
});

const which = (bin) => new Promise((res) => {
  execFile('sh', ['-c', 'command -v ' + bin], { timeout: 4000 }, (err) => res(!err));
});

// The verbs the studio uses, by suffix; the adapter resolves each suffix to
// whatever full name the box actually answers on (pup_*, browser_*, ...).
const SUFFIXES = ['open_window', 'navigate', 'screenshot', 'eval', 'status', 'alert_window'];

export async function makeBridge(target, thread) {
  thread = thread || process.env.ADOM_AI_THREAD || '';
  const hasNew = await which('adom-bridge-cli');
  const bin = hasNew ? 'adom-bridge-cli' : 'adom-desktop';
  const verbs = {};
  if (hasNew) {
    // Resolve live verb names from bridge_list; a suffix seen there wins.
    const bl = await run(bin, ['bridge_list'], 15000, target, thread);
    for (const sfx of SUFFIXES) {
      const m = (bl.out + bl.errOut).match(new RegExp('\\b([a-z][a-z0-9]*)_' + sfx + '\\b'));
      if (m) verbs[sfx] = m[0];
    }
    // pup_* is the known-good prefix on renamed boxes; fill unresolved
    // suffixes with it rather than the legacy browser_* names.
    for (const sfx of SUFFIXES) if (!verbs[sfx]) verbs[sfx] = 'pup_' + sfx;
  } else {
    for (const sfx of SUFFIXES) verbs[sfx] = 'browser_' + sfx;
  }
  return {
    bin,
    generation: hasNew ? 'adom-bridge-cli' : 'adom-desktop (legacy)',
    verbs,
    /// call('open_window', {...}) -> {ok, out, errOut, fail}; args JSON-encoded
    call: (suffix, args, timeout = 20000) =>
      run(bin, [verbs[suffix], JSON.stringify(args ?? {})], timeout, target, thread),
    /// plain verb (no JSON args), e.g. bridge_list itself
    raw: (verb, timeout = 15000) => run(bin, [verb], timeout, target, thread),
  };
}