wtt-connect 0.2.41 → 0.2.43
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/config.js +1 -0
- package/src/main.js +90 -16
- package/src/runner.js +2 -2
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -96,6 +96,7 @@ export function loadConfig(argv = {}) {
|
|
|
96
96
|
openDesignSkillsDir: process.env.WTT_CONNECT_OPENDESIGN_SKILLS_DIR || fileConfig.openDesignSkillsDir || resolveOpenDesignSkillsDir(),
|
|
97
97
|
inboxDir: process.env.WTT_CONNECT_INBOX_DIR || fileConfig.inboxDir || path.join(stateDir, 'inbox'),
|
|
98
98
|
uploadArtifacts: parseBool(process.env.WTT_CONNECT_UPLOAD_ARTIFACTS, fileConfig.uploadArtifacts ?? false),
|
|
99
|
+
previewKeepLast: parseIntEnv(process.env.WTT_CONNECT_PREVIEW_KEEP_LAST, fileConfig.previewKeepLast ?? 3),
|
|
99
100
|
setupDisplayName: process.env.WTT_CONNECT_DISPLAY_NAME || fileConfig.setupDisplayName || 'wtt-connect',
|
|
100
101
|
agentAliases: parseListEnv(process.env.WTT_CONNECT_AGENT_ALIASES || fileConfig.agentAliases || ''),
|
|
101
102
|
stt: {
|
package/src/main.js
CHANGED
|
@@ -148,7 +148,7 @@ async function previewPort(config, argv) {
|
|
|
148
148
|
}
|
|
149
149
|
const cleanup = cleanupOldPreviews(config, {
|
|
150
150
|
currentPort: port,
|
|
151
|
-
keepLast:
|
|
151
|
+
keepLast: previewKeepLast(config, argv),
|
|
152
152
|
});
|
|
153
153
|
const preview = await createSandboxPreviewFromOutbox(config, port, {
|
|
154
154
|
name: String(argv.previewName || argv.title || '').trim(),
|
|
@@ -185,6 +185,11 @@ async function cleanupPreviewsCommand(config, argv) {
|
|
|
185
185
|
console.log(JSON.stringify({ ok: true, cleanup }, null, 2));
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
function previewKeepLast(config, argv = {}) {
|
|
189
|
+
if (Number.isFinite(Number(argv.keepLast))) return Math.max(0, Number(argv.keepLast));
|
|
190
|
+
return Math.max(0, Number(config.previewKeepLast ?? 3) || 0);
|
|
191
|
+
}
|
|
192
|
+
|
|
188
193
|
function cleanupOldPreviews(config, { currentPort = 0, keepLast = 0 } = {}) {
|
|
189
194
|
const registry = readPreviewRegistry(config);
|
|
190
195
|
const entries = registry
|
|
@@ -237,21 +242,90 @@ function writePreviewRegistry(config, registry) {
|
|
|
237
242
|
|
|
238
243
|
function stopPreviewPort(port) {
|
|
239
244
|
if (!Number.isInteger(port) || port < 1024 || port > 65535) return { killed: [], reason: 'invalid_port' };
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
245
|
+
const pids = listenerPidsForPort(port);
|
|
246
|
+
const killed = [];
|
|
247
|
+
for (const pid of pids) {
|
|
248
|
+
try {
|
|
249
|
+
process.kill(pid, 'SIGTERM');
|
|
250
|
+
killed.push(String(pid));
|
|
251
|
+
} catch {}
|
|
252
|
+
}
|
|
253
|
+
const deadline = Date.now() + 500;
|
|
254
|
+
while (Date.now() < deadline && pids.some((pid) => isProcessAlive(pid))) {
|
|
255
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);
|
|
256
|
+
}
|
|
257
|
+
for (const pid of pids) {
|
|
258
|
+
if (!isProcessAlive(pid)) continue;
|
|
259
|
+
try {
|
|
260
|
+
process.kill(pid, 'SIGKILL');
|
|
261
|
+
if (!killed.includes(String(pid))) killed.push(String(pid));
|
|
262
|
+
} catch {}
|
|
263
|
+
}
|
|
264
|
+
return { killed, ok: true };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function listenerPidsForPort(port) {
|
|
268
|
+
const inodes = listenerInodesForPort(port);
|
|
269
|
+
if (!inodes.size) return [];
|
|
270
|
+
const pids = [];
|
|
271
|
+
for (const name of safeReadDir('/proc')) {
|
|
272
|
+
if (!/^\d+$/.test(name)) continue;
|
|
273
|
+
const fdDir = `/proc/${name}/fd`;
|
|
274
|
+
for (const fd of safeReadDir(fdDir)) {
|
|
275
|
+
let target = '';
|
|
276
|
+
try {
|
|
277
|
+
target = fs.readlinkSync(path.join(fdDir, fd));
|
|
278
|
+
} catch {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const match = target.match(/^socket:\[(\d+)\]$/);
|
|
282
|
+
if (match && inodes.has(match[1])) {
|
|
283
|
+
pids.push(Number(name));
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return [...new Set(pids)];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function listenerInodesForPort(port) {
|
|
292
|
+
const targetHex = Number(port).toString(16).toUpperCase().padStart(4, '0');
|
|
293
|
+
const inodes = new Set();
|
|
294
|
+
for (const file of ['/proc/net/tcp', '/proc/net/tcp6']) {
|
|
295
|
+
let text = '';
|
|
296
|
+
try {
|
|
297
|
+
text = fs.readFileSync(file, 'utf8');
|
|
298
|
+
} catch {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
for (const line of text.split('\n').slice(1)) {
|
|
302
|
+
const parts = line.trim().split(/\s+/);
|
|
303
|
+
if (parts.length < 10) continue;
|
|
304
|
+
const local = parts[1] || '';
|
|
305
|
+
const state = parts[3] || '';
|
|
306
|
+
const inode = parts[9] || '';
|
|
307
|
+
const localPort = local.split(':')[1] || '';
|
|
308
|
+
if (state === '0A' && localPort.toUpperCase() === targetHex && inode) inodes.add(inode);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return inodes;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function safeReadDir(dir) {
|
|
315
|
+
try {
|
|
316
|
+
return fs.readdirSync(dir);
|
|
317
|
+
} catch {
|
|
318
|
+
return [];
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function isProcessAlive(pid) {
|
|
323
|
+
try {
|
|
324
|
+
process.kill(pid, 0);
|
|
325
|
+
return true;
|
|
326
|
+
} catch {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
255
329
|
}
|
|
256
330
|
|
|
257
331
|
async function createSandboxPreviewFromOutbox(config, port, { name = '', token = '', timeoutMs = 15000 } = {}) {
|
package/src/runner.js
CHANGED
|
@@ -939,11 +939,11 @@ function renderCloudSandboxStorageInstruction(config, topicId = '') {
|
|
|
939
939
|
'- If a generated user-facing file is stored in R2/persistent output storage and should appear in WTT chat, still publish it with `wtt-connect upload-file` or a WTT artifact marker.',
|
|
940
940
|
'- If the user asks for a visual page, HTML artifact, chart, dashboard, animation, or browser preview, build it in the workspace, start a local web server, create a Cloudflare Sandbox preview URL, and return that preview URL to WTT.',
|
|
941
941
|
'- The Cloud Sandbox preview URL feature is only for WTT Cloud Sandbox agents. Do not use WTT backend artifact/media preview flows for live sandbox web servers.',
|
|
942
|
-
'- Before starting a new preview server, run `wtt-connect cleanup-previews` to stop older preview servers for this agent and free sandbox resources.',
|
|
943
942
|
'- Preview servers must keep running after your command finishes. Start them in the background with `nohup ... >/tmp/<name>.log 2>&1 &` or an equivalent long-lived process, and bind to `0.0.0.0`.',
|
|
944
943
|
'- Before publishing a preview URL, verify the server is actually listening and serving content with `curl -fsS http://127.0.0.1:<port>/ >/dev/null`.',
|
|
945
944
|
'- If local curl fails, fix or restart the web server first. Never publish a preview URL for a dead port.',
|
|
946
|
-
'- `wtt-connect preview-port` automatically
|
|
945
|
+
'- `wtt-connect preview-port` automatically keeps the most recent preview servers for this agent and stops older registered previews beyond the retention limit. Default retention is 3 live previews; use `--keep-last <n>` only when the user asks.',
|
|
946
|
+
'- For historical results that should remain available after preview cleanup, also save static HTML, screenshots, reports, or build artifacts under the persistent R2 output directory and publish them as files/artifacts. Live preview URLs are for recent interactive previews, not permanent archival storage.',
|
|
947
947
|
'- If you start a web server in the sandbox and the user should preview it, call the Cloudflare Sandbox outbound Worker directly with curl and include the returned `preview_url` in your reply as `[preview_url:Short Title](<preview_url>)`.',
|
|
948
948
|
'- Preview ports must be 1024-65535 and cannot be 3000. For Vite/Next-style dev servers prefer 5173, 4173, or 8080.',
|
|
949
949
|
`- Preview curl rule: curl -sS -X POST "\${WTT_SANDBOX_OUTBOX_URL:-http://wtt.preview}/preview-port" -H 'content-type: application/json' -d '{"agent_id":"'\${WTT_AGENT_ID:-cloud-agent}'","port":<port>}'`,
|