wtt-connect 0.2.20 → 0.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 (3) hide show
  1. package/package.json +1 -1
  2. package/src/runner.js +107 -24
  3. package/src/store.js +16 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wtt-connect",
3
- "version": "0.2.20",
3
+ "version": "0.2.22",
4
4
  "private": false,
5
5
  "description": "WTT-native connector daemon for Codex, Claude Code, Cursor, Gemini, ACP, and other coding agent surfaces.",
6
6
  "type": "module",
package/src/runner.js CHANGED
@@ -2,6 +2,7 @@ import { WTTClient } from './wtt-client.js';
2
2
  import { execFile } from 'node:child_process';
3
3
  import fs from 'node:fs';
4
4
  import fsp from 'node:fs/promises';
5
+ import os from 'node:os';
5
6
  import path from 'node:path';
6
7
  import { promisify } from 'node:util';
7
8
  import { WTTApi } from './wtt-api.js';
@@ -572,11 +573,11 @@ function parseUpgradeArgs(argsText = '') {
572
573
  }
573
574
 
574
575
  async function upgradeToolchain(config, targets) {
575
- const specs = [];
576
- if (targets.includes('wtt-connect')) specs.push('wtt-connect@latest');
577
- if (targets.includes('codex')) specs.push('@openai/codex@latest');
578
- if (targets.includes('claude-code')) specs.push('@anthropic-ai/claude-code@latest');
579
- if (!specs.length) return { message: upgradeHelp(), summary: {} };
576
+ const packageSpecs = [];
577
+ if (targets.includes('wtt-connect')) packageSpecs.push({ target: 'wtt-connect', spec: 'wtt-connect@latest' });
578
+ if (targets.includes('codex')) packageSpecs.push({ target: 'codex', spec: '@openai/codex@latest' });
579
+ if (targets.includes('claude-code')) packageSpecs.push({ target: 'claude-code', spec: '@anthropic-ai/claude-code@latest' });
580
+ if (!packageSpecs.length) return { message: upgradeHelp(), summary: {} };
580
581
 
581
582
  const prefix = await resolveNpmPrefix(config);
582
583
  const cache = process.env.NPM_CONFIG_CACHE
@@ -595,27 +596,29 @@ async function upgradeToolchain(config, targets) {
595
596
  npm_config_cache: cache,
596
597
  npm_config_nodedir: process.env.npm_config_nodedir || '/usr/local',
597
598
  };
598
- const install = await execFileAsync('npm', [
599
- 'install',
600
- '-g',
601
- '--prefix',
602
- prefix,
603
- '--cache',
604
- cache,
605
- `--registry=${registry}`,
606
- ...specs,
607
- ], {
608
- cwd: config.workDir,
609
- env,
610
- timeout: 10 * 60_000,
611
- maxBuffer: 2 * 1024 * 1024,
612
- });
599
+ const installOutputs = [];
600
+ for (const pkg of packageSpecs) {
601
+ try {
602
+ const install = await installNpmGlobal(config, pkg.spec, { prefix, cache, registry, env });
603
+ installOutputs.push({ spec: pkg.spec, method: 'npm', stdout: install.stdout, stderr: install.stderr });
604
+ } catch (err) {
605
+ if (pkg.target !== 'wtt-connect') throw err;
606
+ const fallback = await installWttConnectFromRegistry(prefix, registry);
607
+ installOutputs.push({
608
+ spec: pkg.spec,
609
+ method: 'registry-tarball',
610
+ stdout: fallback.stdout,
611
+ stderr: `npm install failed; used registry tarball fallback.\n${err.stderr || err.message || err}`,
612
+ });
613
+ }
614
+ }
613
615
  const after = await toolVersions(config, prefix);
614
616
  const lines = [
615
617
  'Toolchain upgrade completed.',
616
618
  `- npm prefix: ${prefix}`,
617
619
  `- npm cache: ${cache}`,
618
- `- packages: ${specs.join(', ')}`,
620
+ `- packages: ${packageSpecs.map((pkg) => pkg.spec).join(', ')}`,
621
+ `- install methods: ${installOutputs.map((out) => `${out.spec}:${out.method}`).join(', ')}`,
619
622
  '',
620
623
  'Versions:',
621
624
  `- wtt-connect: ${before.wtt_connect || 'unknown'} -> ${after.wtt_connect || 'unknown'}`,
@@ -625,14 +628,18 @@ async function upgradeToolchain(config, targets) {
625
628
  if (targets.includes('wtt-connect')) {
626
629
  lines.push('', 'Note: `wtt-connect` was upgraded on disk. Restart this agent process to run the new connector version; Claude Code and Codex upgrades are used on the next run because they are spawned per request.');
627
630
  }
628
- const stdout = String(install.stdout || '').trim();
629
- if (stdout) lines.push('', 'npm output:', truncate(stdout, 4000));
631
+ const outputText = installOutputs
632
+ .map((out) => [`[${out.spec} via ${out.method}]`, out.stdout, out.stderr].filter(Boolean).join('\n'))
633
+ .join('\n\n')
634
+ .trim();
635
+ if (outputText) lines.push('', 'install output:', truncate(outputText, 4000));
630
636
  return {
631
637
  message: lines.join('\n'),
632
638
  summary: {
633
639
  prefix,
634
640
  cache,
635
- packages: specs,
641
+ packages: packageSpecs.map((pkg) => pkg.spec),
642
+ install_methods: Object.fromEntries(installOutputs.map((out) => [out.spec, out.method])),
636
643
  before,
637
644
  after,
638
645
  installed_at: new Date().toISOString(),
@@ -640,6 +647,82 @@ async function upgradeToolchain(config, targets) {
640
647
  };
641
648
  }
642
649
 
650
+ async function installNpmGlobal(config, spec, { prefix, cache, registry, env }) {
651
+ return execFileAsync('npm', [
652
+ 'install',
653
+ '-g',
654
+ '--prefix',
655
+ prefix,
656
+ '--cache',
657
+ cache,
658
+ `--registry=${registry}`,
659
+ spec,
660
+ ], {
661
+ cwd: config.workDir,
662
+ env,
663
+ timeout: 10 * 60_000,
664
+ maxBuffer: 2 * 1024 * 1024,
665
+ });
666
+ }
667
+
668
+ async function installWttConnectFromRegistry(prefix, registry) {
669
+ const registryBase = String(registry || 'https://registry.npmjs.org').replace(/\/+$/, '');
670
+ const metadataUrl = `${registryBase}/wtt-connect`;
671
+ const metadata = await fetchJson(metadataUrl);
672
+ const version = metadata?.['dist-tags']?.latest;
673
+ const tarball = version ? metadata?.versions?.[version]?.dist?.tarball : '';
674
+ if (!version || !tarball) throw new Error(`wtt-connect metadata missing latest tarball from ${metadataUrl}`);
675
+
676
+ const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'wtt-connect-upgrade-'));
677
+ const archive = path.join(tmpDir, `wtt-connect-${version}.tgz`);
678
+ const extractDir = path.join(tmpDir, 'extract');
679
+ const libDir = path.join(prefix, 'lib', 'node_modules');
680
+ const binDir = path.join(prefix, 'bin');
681
+ const targetDir = path.join(libDir, 'wtt-connect');
682
+ const nextDir = path.join(libDir, `.wtt-connect-${version}-${process.pid}-${Date.now()}`);
683
+ try {
684
+ await execFileAsync('curl', ['-fsSL', tarball, '-o', archive], { timeout: 5 * 60_000, maxBuffer: 512 * 1024 });
685
+ await fsp.mkdir(extractDir, { recursive: true });
686
+ await execFileAsync('tar', ['-xzf', archive, '-C', extractDir], { timeout: 2 * 60_000, maxBuffer: 512 * 1024 });
687
+ await fsp.mkdir(libDir, { recursive: true });
688
+ await fsp.mkdir(binDir, { recursive: true });
689
+ await fsp.rm(nextDir, { recursive: true, force: true });
690
+ await fsp.rename(path.join(extractDir, 'package'), nextDir);
691
+ await fsp.rm(targetDir, { recursive: true, force: true });
692
+ await fsp.rename(nextDir, targetDir);
693
+ const entry = path.join(targetDir, 'bin', 'wtt-connect.js');
694
+ await fsp.chmod(entry, 0o755).catch(() => {});
695
+ const link = path.join(binDir, 'wtt-connect');
696
+ await fsp.rm(link, { force: true });
697
+ await fsp.symlink('../lib/node_modules/wtt-connect/bin/wtt-connect.js', link);
698
+ await writeToolVersion(prefix, 'wtt_connect', version);
699
+ return { stdout: `installed wtt-connect@${version} from ${tarball}`, stderr: '' };
700
+ } finally {
701
+ await fsp.rm(tmpDir, { recursive: true, force: true }).catch(() => {});
702
+ }
703
+ }
704
+
705
+ async function fetchJson(url) {
706
+ const res = await fetch(url);
707
+ if (!res.ok) throw new Error(`GET ${url} failed: ${res.status} ${res.statusText}`);
708
+ return res.json();
709
+ }
710
+
711
+ async function writeToolVersion(prefix, key, version) {
712
+ const toolchainDir = path.dirname(prefix);
713
+ const file = path.join(toolchainDir, 'tool-versions.json');
714
+ let current = {};
715
+ try {
716
+ current = JSON.parse(await fsp.readFile(file, 'utf8'));
717
+ } catch {
718
+ current = {};
719
+ }
720
+ current[key] = version;
721
+ current.updated_at = new Date().toISOString();
722
+ await fsp.mkdir(toolchainDir, { recursive: true });
723
+ await fsp.writeFile(file, `${JSON.stringify(current, null, 2)}\n`);
724
+ }
725
+
643
726
  async function resolveNpmPrefix(config) {
644
727
  const envPrefix = process.env.NPM_CONFIG_PREFIX || process.env.npm_config_prefix || '';
645
728
  if (envPrefix) return path.resolve(envPrefix);
package/src/store.js CHANGED
@@ -20,10 +20,22 @@ export class DurableStore {
20
20
  }
21
21
 
22
22
  save() {
23
- fs.mkdirSync(path.dirname(this.file), { recursive: true });
24
- const tmp = `${this.file}.tmp`;
25
- fs.writeFileSync(tmp, JSON.stringify(this.data, null, 2));
26
- fs.renameSync(tmp, this.file);
23
+ const dir = path.dirname(this.file);
24
+ fs.mkdirSync(dir, { recursive: true });
25
+ const tmp = path.join(dir, `.${path.basename(this.file)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
26
+ try {
27
+ fs.writeFileSync(tmp, JSON.stringify(this.data, null, 2));
28
+ fs.mkdirSync(dir, { recursive: true });
29
+ fs.renameSync(tmp, this.file);
30
+ } catch (error) {
31
+ try { fs.rmSync(tmp, { force: true }); } catch {}
32
+ if (error && error.code === 'ENOENT') {
33
+ fs.mkdirSync(dir, { recursive: true });
34
+ fs.writeFileSync(this.file, JSON.stringify(this.data, null, 2));
35
+ return;
36
+ }
37
+ throw error;
38
+ }
27
39
  }
28
40
 
29
41
  getSession(sessionKey) {