wtt-connect 0.2.51 → 0.2.53
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.
- package/package.json +1 -1
- package/src/main.js +10 -0
- package/src/service-manager.js +156 -8
package/package.json
CHANGED
package/src/main.js
CHANGED
|
@@ -20,6 +20,7 @@ export async function main(args) {
|
|
|
20
20
|
const argv = parseArgs(args.slice(1));
|
|
21
21
|
if (!argv.envFile && argv.profile) argv.envFile = resolveProfileEnvFile(argv.profile);
|
|
22
22
|
loadDefaultEnvFiles({ envFile: argv.envFile });
|
|
23
|
+
if (cmd === 'version' || cmd === '--version' || cmd === '-v') return printVersion();
|
|
23
24
|
if (cmd === 'help' || cmd === '--help' || cmd === '-h') return printHelp();
|
|
24
25
|
if (cmd === 'up' || cmd === 'link') return up(argv);
|
|
25
26
|
if (cmd === 'list') return listProfiles(argv);
|
|
@@ -109,6 +110,15 @@ function parseArgs(args) {
|
|
|
109
110
|
return out;
|
|
110
111
|
}
|
|
111
112
|
|
|
113
|
+
function printVersion() {
|
|
114
|
+
try {
|
|
115
|
+
const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
116
|
+
console.log(pkg.version || 'unknown');
|
|
117
|
+
} catch {
|
|
118
|
+
console.log('unknown');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
112
122
|
function printHelp() {
|
|
113
123
|
console.log(`wtt-connect
|
|
114
124
|
|
package/src/service-manager.js
CHANGED
|
@@ -87,12 +87,16 @@ export async function up(argv) {
|
|
|
87
87
|
|
|
88
88
|
export function listProfiles() {
|
|
89
89
|
const dir = profilesDir();
|
|
90
|
+
const sandboxEntries = discoverCloudSandboxEntries();
|
|
90
91
|
if (!fs.existsSync(dir)) {
|
|
91
|
-
console.log('no profiles');
|
|
92
|
+
if (!sandboxEntries.length) console.log('no profiles');
|
|
93
|
+
for (const entry of sandboxEntries) {
|
|
94
|
+
console.log(`${entry.profile}\t${entry.adapter || 'cloud-sandbox'}\t${entry.agentId}\tcloud-sandbox`);
|
|
95
|
+
}
|
|
92
96
|
return;
|
|
93
97
|
}
|
|
94
98
|
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.env')).sort();
|
|
95
|
-
if (!files.length) {
|
|
99
|
+
if (!files.length && !sandboxEntries.length) {
|
|
96
100
|
console.log('no profiles');
|
|
97
101
|
return;
|
|
98
102
|
}
|
|
@@ -101,23 +105,36 @@ export function listProfiles() {
|
|
|
101
105
|
const env = readEnv(path.join(dir, file));
|
|
102
106
|
console.log(`${profile}\t${env.WTT_CONNECT_ADAPTER || ''}\t${env.WTT_AGENT_ID || ''}`);
|
|
103
107
|
}
|
|
108
|
+
for (const entry of sandboxEntries) {
|
|
109
|
+
console.log(`${entry.profile}\t${entry.adapter || 'cloud-sandbox'}\t${entry.agentId}\tcloud-sandbox`);
|
|
110
|
+
}
|
|
104
111
|
}
|
|
105
112
|
|
|
106
113
|
export function status(argv) {
|
|
107
|
-
const profiles = targetProfiles(argv);
|
|
108
|
-
|
|
114
|
+
const profiles = targetProfiles(argv, { allowEmpty: true });
|
|
115
|
+
const sandboxEntries = targetCloudSandboxEntries(argv);
|
|
116
|
+
if (!profiles.length && !sandboxEntries.length) {
|
|
109
117
|
console.log('no profiles');
|
|
110
118
|
return;
|
|
111
119
|
}
|
|
112
120
|
for (const profile of profiles) {
|
|
113
121
|
console.log(`${profile}: ${serviceStatus(profile)}`);
|
|
114
122
|
}
|
|
123
|
+
for (const entry of sandboxEntries) {
|
|
124
|
+
console.log(`${entry.profile}: ${cloudSandboxStatus(entry)}`);
|
|
125
|
+
}
|
|
115
126
|
}
|
|
116
127
|
|
|
117
128
|
export function restart(argv) {
|
|
118
|
-
const profiles = targetProfiles(argv);
|
|
129
|
+
const profiles = targetProfiles(argv, { allowEmpty: true });
|
|
130
|
+
const sandboxEntries = targetCloudSandboxEntries(argv);
|
|
131
|
+
if (!profiles.length && !sandboxEntries.length) {
|
|
132
|
+
console.log('no profiles');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
119
135
|
for (const profile of profiles) restartService(profile);
|
|
120
|
-
|
|
136
|
+
for (const entry of sandboxEntries) restartCloudSandboxEntry(entry);
|
|
137
|
+
status({ _: [...profiles, ...sandboxEntries.map((entry) => entry.profile)] });
|
|
121
138
|
}
|
|
122
139
|
|
|
123
140
|
export function logs(argv) {
|
|
@@ -182,11 +199,17 @@ export function down(argv) {
|
|
|
182
199
|
console.log(`kept state dir: ${defaultStateDir(safe)}`);
|
|
183
200
|
}
|
|
184
201
|
|
|
185
|
-
function targetProfiles(argv) {
|
|
202
|
+
function targetProfiles(argv, options = {}) {
|
|
186
203
|
const requested = argv._ || [];
|
|
187
|
-
if (requested.length && requested[0] !== 'all')
|
|
204
|
+
if (requested.length && requested[0] !== 'all') {
|
|
205
|
+
const available = new Set(discoverProfiles());
|
|
206
|
+
const profiles = requested.map(sanitizeProfile).filter((profile) => available.has(profile));
|
|
207
|
+
if (profiles.length || options.allowEmpty) return profiles;
|
|
208
|
+
return requested.map(sanitizeProfile);
|
|
209
|
+
}
|
|
188
210
|
const profiles = discoverProfiles();
|
|
189
211
|
if (!profiles.length && requested[0] === 'all') return [];
|
|
212
|
+
if (!profiles.length && options.allowEmpty) return [];
|
|
190
213
|
if (!profiles.length) throw new Error('no wtt-connect profiles found');
|
|
191
214
|
return profiles;
|
|
192
215
|
}
|
|
@@ -197,6 +220,131 @@ function discoverProfiles() {
|
|
|
197
220
|
return fs.readdirSync(dir).filter((f) => f.endsWith('.env')).map((f) => sanitizeProfile(f.slice(0, -4))).sort();
|
|
198
221
|
}
|
|
199
222
|
|
|
223
|
+
function targetCloudSandboxEntries(argv) {
|
|
224
|
+
const requested = (argv._ || []).map((value) => String(value || '').trim()).filter(Boolean);
|
|
225
|
+
const entries = discoverCloudSandboxEntries();
|
|
226
|
+
if (!requested.length || requested[0] === 'all') return entries;
|
|
227
|
+
const wanted = new Set(requested.map(sanitizeProfile));
|
|
228
|
+
return entries.filter((entry) => wanted.has(entry.profile) || wanted.has(entry.agentId));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function discoverCloudSandboxEntries() {
|
|
232
|
+
const root = path.join(os.homedir(), '.wtt-connect', 'agent');
|
|
233
|
+
if (!fs.existsSync(root)) return [];
|
|
234
|
+
const entries = [];
|
|
235
|
+
for (const name of fs.readdirSync(root).sort()) {
|
|
236
|
+
const agentRoot = path.join(root, name);
|
|
237
|
+
const envFile = path.join(agentRoot, 'run', 'wtt-connect.env');
|
|
238
|
+
if (!fs.existsSync(envFile)) continue;
|
|
239
|
+
const env = readEnv(envFile);
|
|
240
|
+
const agentId = sanitizeProfile(env.WTT_AGENT_ID || name);
|
|
241
|
+
if (!agentId) continue;
|
|
242
|
+
entries.push({
|
|
243
|
+
profile: agentId,
|
|
244
|
+
agentId,
|
|
245
|
+
adapter: env.WTT_CONNECT_ADAPTER || env.WTT_CONNECT_ADAPTERS || '',
|
|
246
|
+
envFile,
|
|
247
|
+
agentRoot,
|
|
248
|
+
pidFile: path.join(agentRoot, 'run', 'wtt-connect.pid'),
|
|
249
|
+
logFile: path.join(agentRoot, 'logs', 'wtt-connect.log'),
|
|
250
|
+
workDir: env.WTT_CONNECT_WORKDIR || path.join(agentRoot, 'workspace'),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
return entries;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function cloudSandboxStatus(entry) {
|
|
257
|
+
const pid = readPid(entry.pidFile);
|
|
258
|
+
if (pid && processRunningWithAgent(pid, entry.agentId)) return `active pid=${pid}`;
|
|
259
|
+
const found = findPidByAgentEnv(entry.agentId);
|
|
260
|
+
if (found) return `active pid=${found}`;
|
|
261
|
+
return 'inactive';
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function restartCloudSandboxEntry(entry) {
|
|
265
|
+
stopCloudSandboxEntry(entry);
|
|
266
|
+
ensureDir(path.dirname(entry.pidFile));
|
|
267
|
+
ensureDir(path.dirname(entry.logFile));
|
|
268
|
+
ensureDir(entry.workDir);
|
|
269
|
+
const bin = path.join(packageRoot(), 'bin', 'wtt-connect.js');
|
|
270
|
+
const script = [
|
|
271
|
+
`set -a`,
|
|
272
|
+
`. ${shellQuote(entry.envFile)}`,
|
|
273
|
+
`set +a`,
|
|
274
|
+
`export PATH=${shellQuote(runtimePath())}`,
|
|
275
|
+
`mkdir -p ${shellQuote(entry.workDir)} ${shellQuote(path.dirname(entry.pidFile))} ${shellQuote(path.dirname(entry.logFile))}`,
|
|
276
|
+
`cd ${shellQuote(entry.workDir)}`,
|
|
277
|
+
`nohup ${shellQuote(process.execPath)} ${shellQuote(bin)} start --env-file ${shellQuote(entry.envFile)} >> ${shellQuote(entry.logFile)} 2>&1 < /dev/null & echo $! > ${shellQuote(entry.pidFile)}`,
|
|
278
|
+
].join('; ');
|
|
279
|
+
const result = spawnSync('bash', ['-lc', script], { encoding: 'utf8' });
|
|
280
|
+
if (result.status !== 0) {
|
|
281
|
+
throw new Error(`cloud sandbox restart failed for ${entry.agentId}: ${result.stderr || result.stdout}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function stopCloudSandboxEntry(entry) {
|
|
286
|
+
const pids = new Set();
|
|
287
|
+
const pid = readPid(entry.pidFile);
|
|
288
|
+
if (pid) pids.add(pid);
|
|
289
|
+
const envPid = findPidByAgentEnv(entry.agentId);
|
|
290
|
+
if (envPid) pids.add(envPid);
|
|
291
|
+
for (const target of pids) {
|
|
292
|
+
try { process.kill(target, 'SIGTERM'); } catch {}
|
|
293
|
+
}
|
|
294
|
+
const deadline = Date.now() + 2000;
|
|
295
|
+
while (Date.now() < deadline && Array.from(pids).some((target) => processAlive(target))) {
|
|
296
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
|
|
297
|
+
}
|
|
298
|
+
for (const target of pids) {
|
|
299
|
+
if (!processAlive(target)) continue;
|
|
300
|
+
try { process.kill(target, 'SIGKILL'); } catch {}
|
|
301
|
+
}
|
|
302
|
+
try { fs.rmSync(entry.pidFile); } catch {}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function readPid(file) {
|
|
306
|
+
try {
|
|
307
|
+
const pid = Number.parseInt(fs.readFileSync(file, 'utf8').trim(), 10);
|
|
308
|
+
return Number.isFinite(pid) && pid > 0 ? pid : 0;
|
|
309
|
+
} catch {
|
|
310
|
+
return 0;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function processAlive(pid) {
|
|
315
|
+
try {
|
|
316
|
+
process.kill(pid, 0);
|
|
317
|
+
return true;
|
|
318
|
+
} catch {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function processRunningWithAgent(pid, agentId) {
|
|
324
|
+
if (!processAlive(pid)) return false;
|
|
325
|
+
try {
|
|
326
|
+
const env = fs.readFileSync(`/proc/${pid}/environ`, 'utf8').split('\0');
|
|
327
|
+
return env.includes(`WTT_AGENT_ID=${agentId}`);
|
|
328
|
+
} catch {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function findPidByAgentEnv(agentId) {
|
|
334
|
+
if (process.platform !== 'linux') return 0;
|
|
335
|
+
for (const name of fs.readdirSync('/proc')) {
|
|
336
|
+
if (!/^\d+$/.test(name)) continue;
|
|
337
|
+
const pid = Number.parseInt(name, 10);
|
|
338
|
+
if (pid === process.pid) continue;
|
|
339
|
+
if (processRunningWithAgent(pid, agentId)) return pid;
|
|
340
|
+
}
|
|
341
|
+
return 0;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function shellQuote(value) {
|
|
345
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
346
|
+
}
|
|
347
|
+
|
|
200
348
|
function writeProfileEnv(file, values) {
|
|
201
349
|
const lines = [
|
|
202
350
|
'# wtt-connect profile',
|