wtt-connect 0.1.0
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/README.md +295 -0
- package/bin/wtt-connect.js +7 -0
- package/package.json +32 -0
- package/scripts/install-agent.sh +4 -0
- package/scripts/install-launchd.sh +51 -0
- package/scripts/install-systemd-user.sh +189 -0
- package/scripts/uninstall-launchd.sh +18 -0
- package/scripts/uninstall-systemd-user.sh +42 -0
- package/src/adapter-registry.js +58 -0
- package/src/adapters/acp.js +196 -0
- package/src/adapters/claude-code.js +161 -0
- package/src/adapters/codex.js +88 -0
- package/src/adapters/generic-cli.js +258 -0
- package/src/adapters/index.js +28 -0
- package/src/adapters/openclaw.js +32 -0
- package/src/artifacts.js +68 -0
- package/src/attachments.js +181 -0
- package/src/config.js +118 -0
- package/src/env.js +42 -0
- package/src/events.js +23 -0
- package/src/logger.js +18 -0
- package/src/main.js +143 -0
- package/src/mime.js +12 -0
- package/src/permissions.js +28 -0
- package/src/runner.js +562 -0
- package/src/runtime-info.js +71 -0
- package/src/service-manager.js +510 -0
- package/src/session-manager.js +31 -0
- package/src/setup.js +71 -0
- package/src/shell-runner.js +184 -0
- package/src/smoke.js +99 -0
- package/src/store.js +58 -0
- package/src/stt.js +146 -0
- package/src/terminal-session.js +152 -0
- package/src/tts.js +40 -0
- package/src/wtt-api.js +79 -0
- package/src/wtt-client.js +144 -0
package/src/wtt-api.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { apiUrl } from './config.js';
|
|
2
|
+
import { log } from './logger.js';
|
|
3
|
+
|
|
4
|
+
export class WTTApi {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
this.base = apiUrl(config.wttBaseUrl);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
headers(extra = {}) {
|
|
11
|
+
return { ...(this.config.httpToken ? { Authorization: `Bearer ${this.config.httpToken}` } : {}), ...extra };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
agentHeaders(extra = {}) {
|
|
15
|
+
return { ...(this.config.token ? { 'X-Agent-Token': this.config.token } : {}), ...extra };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async request(method, path, { json, headers } = {}) {
|
|
19
|
+
const r = await fetch(`${this.base}${path}`, {
|
|
20
|
+
method,
|
|
21
|
+
headers: this.headers(json ? { 'content-type': 'application/json', ...(headers || {}) } : (headers || {})),
|
|
22
|
+
body: json ? JSON.stringify(json) : undefined,
|
|
23
|
+
});
|
|
24
|
+
if (!r.ok) throw new Error(`${method} ${path} failed: ${r.status} ${await r.text()}`);
|
|
25
|
+
return r.status === 204 ? null : r.json();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async registerAgent(displayName = 'wtt-connect') {
|
|
29
|
+
return this.request('POST', '/agents/register', { json: { display_name: displayName, platform: 'wtt-connect' } });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async claimCode(agentId = this.config.agentId, token = this.config.token) {
|
|
33
|
+
try {
|
|
34
|
+
return await this.request('POST', `/agents/claim-code?agent_id=${encodeURIComponent(agentId)}`, {
|
|
35
|
+
headers: token ? { 'X-Agent-Token': token } : {},
|
|
36
|
+
});
|
|
37
|
+
} catch {
|
|
38
|
+
return this.request('POST', `/agents/claim-code?agent_id=${encodeURIComponent(agentId)}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async getTask(taskId) {
|
|
43
|
+
try {
|
|
44
|
+
return await this.request('GET', `/tasks/${taskId}`);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (String(err.message).includes(' 404 ')) return null;
|
|
47
|
+
log('warn', 'get_task failed', { taskId, error: err.message });
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async patchTask(taskId, payload) {
|
|
53
|
+
if (this.config.dryRun) {
|
|
54
|
+
log('info', 'dry-run patch task', { taskId, fields: Object.keys(payload).join(',') });
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
return await this.request('PATCH', `/tasks/${taskId}`, { json: payload });
|
|
59
|
+
} catch (err) {
|
|
60
|
+
log('warn', 'patch_task failed', { taskId, error: err.message, fields: Object.keys(payload).join(',') });
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async createTask(payload) {
|
|
66
|
+
return this.request('POST', '/tasks', { json: payload });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async runTask(taskId, payload = {}) {
|
|
70
|
+
return this.request('POST', `/tasks/${taskId}/run`, { json: payload });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async completeArenaJudgeJob(jobId, result) {
|
|
74
|
+
return this.request('POST', `/arena/judge/jobs/${encodeURIComponent(jobId)}/result`, {
|
|
75
|
+
headers: this.agentHeaders(),
|
|
76
|
+
json: { agent_id: this.config.agentId, result },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { log } from './logger.js';
|
|
2
|
+
|
|
3
|
+
export class WTTClient {
|
|
4
|
+
constructor(config, onEvent, runtimeInfoProvider = null) {
|
|
5
|
+
this.config = config;
|
|
6
|
+
this.onEvent = onEvent;
|
|
7
|
+
this.runtimeInfoProvider = runtimeInfoProvider;
|
|
8
|
+
this.ws = null;
|
|
9
|
+
this.pending = new Map();
|
|
10
|
+
this.closed = false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async connectForever() {
|
|
14
|
+
let backoff = 1000;
|
|
15
|
+
while (!this.closed) {
|
|
16
|
+
try {
|
|
17
|
+
await this.connectOnce();
|
|
18
|
+
backoff = 1000;
|
|
19
|
+
} catch (err) {
|
|
20
|
+
if (this.closed) return;
|
|
21
|
+
log('warn', 'WTT websocket disconnected', { error: err?.message || err, reconnectMs: backoff });
|
|
22
|
+
await sleep(backoff);
|
|
23
|
+
backoff = Math.min(backoff * 2, 30000);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async connectOnce() {
|
|
29
|
+
if (!globalThis.WebSocket) throw new Error('Node.js global WebSocket is unavailable; use Node >=22');
|
|
30
|
+
log('info', 'connecting WTT websocket', { url: this.config.wttWsUrl, agentId: this.config.agentId });
|
|
31
|
+
const ws = new WebSocket(this.config.wttWsUrl);
|
|
32
|
+
this.ws = ws;
|
|
33
|
+
await onceOpen(ws, 15000);
|
|
34
|
+
if (this.config.token) {
|
|
35
|
+
await this.sendJson({ action: 'auth', request_id: rid('auth'), token: this.config.token });
|
|
36
|
+
}
|
|
37
|
+
log('info', 'WTT websocket connected');
|
|
38
|
+
await this.sendHeartbeat();
|
|
39
|
+
const heartbeat = setInterval(() => {
|
|
40
|
+
if (ws.readyState !== WebSocket.OPEN) return;
|
|
41
|
+
const runtime = this.runtimeInfoProvider ? this.runtimeInfoProvider() : null;
|
|
42
|
+
if (runtime) {
|
|
43
|
+
ws.send(JSON.stringify({ action: 'heartbeat', runtime }));
|
|
44
|
+
} else {
|
|
45
|
+
ws.send('ping');
|
|
46
|
+
}
|
|
47
|
+
}, Math.max(5, this.config.heartbeatSeconds) * 1000);
|
|
48
|
+
try {
|
|
49
|
+
await new Promise((resolve, reject) => {
|
|
50
|
+
ws.addEventListener('message', (event) => this.handleMessage(String(event.data)).catch(reject));
|
|
51
|
+
ws.addEventListener('error', () => reject(new Error('websocket error')));
|
|
52
|
+
ws.addEventListener('close', () => resolve());
|
|
53
|
+
});
|
|
54
|
+
} finally {
|
|
55
|
+
clearInterval(heartbeat);
|
|
56
|
+
if (this.ws === ws) this.ws = null;
|
|
57
|
+
for (const [, p] of this.pending) p.reject(new Error('WTT websocket closed'));
|
|
58
|
+
this.pending.clear();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async close() {
|
|
63
|
+
this.closed = true;
|
|
64
|
+
if (this.ws) this.ws.close();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async handleMessage(raw) {
|
|
68
|
+
if (raw === 'ping') {
|
|
69
|
+
await this.sendHeartbeat();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (raw === 'pong') return;
|
|
73
|
+
let msg;
|
|
74
|
+
try { msg = JSON.parse(raw); } catch { log('debug', 'non-json WTT message', { raw }); return; }
|
|
75
|
+
if (msg.type === 'action_result') {
|
|
76
|
+
const pending = this.pending.get(msg.request_id);
|
|
77
|
+
if (pending) {
|
|
78
|
+
this.pending.delete(msg.request_id);
|
|
79
|
+
msg.ok ? pending.resolve(msg.data) : pending.reject(new Error(String(msg.error || 'action failed')));
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
await this.onEvent(msg);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async sendJson(payload) {
|
|
87
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) throw new Error('WTT websocket is not connected');
|
|
88
|
+
this.ws.send(JSON.stringify(payload));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async sendHeartbeat() {
|
|
92
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
93
|
+
const runtime = this.runtimeInfoProvider ? this.runtimeInfoProvider() : null;
|
|
94
|
+
if (runtime) {
|
|
95
|
+
await this.sendJson({ action: 'heartbeat', runtime });
|
|
96
|
+
} else {
|
|
97
|
+
this.ws.send('ping');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async action(action, payload = {}, timeoutMs = 20000) {
|
|
102
|
+
const requestId = rid(action);
|
|
103
|
+
const promise = new Promise((resolve, reject) => {
|
|
104
|
+
const timer = setTimeout(() => {
|
|
105
|
+
this.pending.delete(requestId);
|
|
106
|
+
reject(new Error(`WTT action timeout: ${action}`));
|
|
107
|
+
}, timeoutMs);
|
|
108
|
+
this.pending.set(requestId, {
|
|
109
|
+
resolve: (v) => { clearTimeout(timer); resolve(v); },
|
|
110
|
+
reject: (e) => { clearTimeout(timer); reject(e); },
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
await this.sendJson({ action, request_id: requestId, ...payload });
|
|
114
|
+
return promise;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async publish(topicId, content, semanticType = 'CHAT_REPLY') {
|
|
118
|
+
if (this.config.dryRun) {
|
|
119
|
+
log('info', 'dry-run publish', { topicId, semanticType, chars: content.length });
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
return this.action('publish', { topic_id: topicId, content, content_type: 'text', semantic_type: semanticType });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async typing(topicId, state) {
|
|
126
|
+
try { await this.action('typing', { topic_id: topicId, state, ttl_ms: 6000 }, 5000); } catch {}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function rid(prefix) {
|
|
131
|
+
return `${prefix}-${crypto.randomUUID().slice(0, 10)}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function sleep(ms) {
|
|
135
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function onceOpen(ws, timeoutMs) {
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
const timer = setTimeout(() => reject(new Error('websocket open timeout')), timeoutMs);
|
|
141
|
+
ws.addEventListener('open', () => { clearTimeout(timer); resolve(); }, { once: true });
|
|
142
|
+
ws.addEventListener('error', () => { clearTimeout(timer); reject(new Error('websocket open failed')); }, { once: true });
|
|
143
|
+
});
|
|
144
|
+
}
|