@virtengine/openfleet 0.25.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/.env.example +914 -0
- package/LICENSE +190 -0
- package/README.md +500 -0
- package/agent-endpoint.mjs +918 -0
- package/agent-hook-bridge.mjs +230 -0
- package/agent-hooks.mjs +1188 -0
- package/agent-pool.mjs +2403 -0
- package/agent-prompts.mjs +689 -0
- package/agent-sdk.mjs +141 -0
- package/anomaly-detector.mjs +1195 -0
- package/autofix.mjs +1294 -0
- package/claude-shell.mjs +708 -0
- package/cli.mjs +906 -0
- package/codex-config.mjs +1274 -0
- package/codex-model-profiles.mjs +135 -0
- package/codex-shell.mjs +762 -0
- package/config-doctor.mjs +613 -0
- package/config.mjs +1720 -0
- package/conflict-resolver.mjs +248 -0
- package/container-runner.mjs +450 -0
- package/copilot-shell.mjs +827 -0
- package/daemon-restart-policy.mjs +56 -0
- package/diff-stats.mjs +282 -0
- package/error-detector.mjs +829 -0
- package/fetch-runtime.mjs +34 -0
- package/fleet-coordinator.mjs +838 -0
- package/get-telegram-chat-id.mjs +71 -0
- package/git-safety.mjs +170 -0
- package/github-reconciler.mjs +403 -0
- package/hook-profiles.mjs +651 -0
- package/kanban-adapter.mjs +4491 -0
- package/lib/logger.mjs +645 -0
- package/maintenance.mjs +828 -0
- package/merge-strategy.mjs +1171 -0
- package/monitor.mjs +12207 -0
- package/openfleet.config.example.json +115 -0
- package/openfleet.schema.json +465 -0
- package/package.json +203 -0
- package/postinstall.mjs +187 -0
- package/pr-cleanup-daemon.mjs +978 -0
- package/preflight.mjs +408 -0
- package/prepublish-check.mjs +90 -0
- package/presence.mjs +328 -0
- package/primary-agent.mjs +282 -0
- package/publish.mjs +151 -0
- package/repo-root.mjs +29 -0
- package/restart-controller.mjs +100 -0
- package/review-agent.mjs +557 -0
- package/rotate-agent-logs.sh +133 -0
- package/sdk-conflict-resolver.mjs +973 -0
- package/session-tracker.mjs +880 -0
- package/setup.mjs +3937 -0
- package/shared-knowledge.mjs +410 -0
- package/shared-state-manager.mjs +841 -0
- package/shared-workspace-cli.mjs +199 -0
- package/shared-workspace-registry.mjs +537 -0
- package/shared-workspaces.json +18 -0
- package/startup-service.mjs +1070 -0
- package/sync-engine.mjs +1063 -0
- package/task-archiver.mjs +801 -0
- package/task-assessment.mjs +550 -0
- package/task-claims.mjs +924 -0
- package/task-complexity.mjs +581 -0
- package/task-executor.mjs +5111 -0
- package/task-store.mjs +753 -0
- package/telegram-bot.mjs +9281 -0
- package/telegram-sentinel.mjs +2010 -0
- package/ui/app.js +867 -0
- package/ui/app.legacy.js +1464 -0
- package/ui/app.monolith.js +2488 -0
- package/ui/components/charts.js +226 -0
- package/ui/components/chat-view.js +567 -0
- package/ui/components/command-palette.js +587 -0
- package/ui/components/diff-viewer.js +190 -0
- package/ui/components/forms.js +327 -0
- package/ui/components/kanban-board.js +451 -0
- package/ui/components/session-list.js +305 -0
- package/ui/components/shared.js +473 -0
- package/ui/index.html +70 -0
- package/ui/modules/api.js +297 -0
- package/ui/modules/icons.js +461 -0
- package/ui/modules/router.js +81 -0
- package/ui/modules/settings-schema.js +261 -0
- package/ui/modules/state.js +679 -0
- package/ui/modules/telegram.js +331 -0
- package/ui/modules/utils.js +270 -0
- package/ui/styles/animations.css +140 -0
- package/ui/styles/base.css +98 -0
- package/ui/styles/components.css +1915 -0
- package/ui/styles/kanban.css +286 -0
- package/ui/styles/layout.css +809 -0
- package/ui/styles/sessions.css +827 -0
- package/ui/styles/variables.css +188 -0
- package/ui/styles.css +141 -0
- package/ui/styles.monolith.css +1046 -0
- package/ui/tabs/agents.js +1417 -0
- package/ui/tabs/chat.js +74 -0
- package/ui/tabs/control.js +887 -0
- package/ui/tabs/dashboard.js +515 -0
- package/ui/tabs/infra.js +537 -0
- package/ui/tabs/logs.js +783 -0
- package/ui/tabs/settings.js +1487 -0
- package/ui/tabs/tasks.js +1385 -0
- package/ui-server.mjs +4073 -0
- package/update-check.mjs +465 -0
- package/utils.mjs +172 -0
- package/ve-kanban.mjs +654 -0
- package/ve-kanban.ps1 +1365 -0
- package/ve-kanban.sh +18 -0
- package/ve-orchestrator.mjs +340 -0
- package/ve-orchestrator.ps1 +6546 -0
- package/ve-orchestrator.sh +18 -0
- package/vibe-kanban-wrapper.mjs +41 -0
- package/vk-error-resolver.mjs +470 -0
- package/vk-log-stream.mjs +914 -0
- package/whatsapp-channel.mjs +520 -0
- package/workspace-monitor.mjs +581 -0
- package/workspace-reaper.mjs +405 -0
- package/workspace-registry.mjs +238 -0
- package/worktree-manager.mjs +1266 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
* VirtEngine Control Center – API Client & WebSocket
|
|
3
|
+
* Handles REST calls, WS connection, and command sending
|
|
4
|
+
* ────────────────────────────────────────────────────────────── */
|
|
5
|
+
|
|
6
|
+
import { signal } from "@preact/signals";
|
|
7
|
+
import { getInitData } from "./telegram.js";
|
|
8
|
+
|
|
9
|
+
/** Reactive signal: whether the WebSocket is currently connected */
|
|
10
|
+
export const wsConnected = signal(false);
|
|
11
|
+
/** Reactive signal: WebSocket round-trip latency in ms (null if unknown) */
|
|
12
|
+
export const wsLatency = signal(null);
|
|
13
|
+
/** Reactive signal: countdown seconds until next reconnect attempt (null when connected) */
|
|
14
|
+
export const wsReconnectIn = signal(null);
|
|
15
|
+
/** Reactive signal: number of reconnections since last user-initiated action */
|
|
16
|
+
export const wsReconnectCount = signal(0);
|
|
17
|
+
|
|
18
|
+
/* ─── REST API Client ─── */
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Fetch from the API (same-origin). Automatically injects the
|
|
22
|
+
* X-Telegram-InitData header and handles JSON parsing / errors.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} path - API path, e.g. "/api/status"
|
|
25
|
+
* @param {RequestInit & {_silent?: boolean}} options
|
|
26
|
+
* @returns {Promise<any>} parsed JSON body
|
|
27
|
+
*/
|
|
28
|
+
export async function apiFetch(path, options = {}) {
|
|
29
|
+
const headers = { ...options.headers };
|
|
30
|
+
headers["Content-Type"] = headers["Content-Type"] || "application/json";
|
|
31
|
+
|
|
32
|
+
const initData = getInitData();
|
|
33
|
+
if (initData) {
|
|
34
|
+
headers["X-Telegram-InitData"] = initData;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const silent = options._silent;
|
|
38
|
+
delete options._silent;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const res = await fetch(path, { ...options, headers });
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
const text = await res.text().catch(() => "");
|
|
44
|
+
throw new Error(text || `Request failed (${res.status})`);
|
|
45
|
+
}
|
|
46
|
+
return await res.json();
|
|
47
|
+
} catch (err) {
|
|
48
|
+
// Re-throw so callers can catch, but don't toast on silent requests
|
|
49
|
+
if (!silent) {
|
|
50
|
+
// Dispatch a custom event so the state layer can show a toast
|
|
51
|
+
try {
|
|
52
|
+
globalThis.dispatchEvent(
|
|
53
|
+
new CustomEvent("ve:api-error", { detail: { message: err.message } }),
|
|
54
|
+
);
|
|
55
|
+
} catch {
|
|
56
|
+
/* noop */
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* ─── Command Sending ─── */
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Send a slash-command to the backend via POST /api/command.
|
|
67
|
+
* @param {string} cmd - e.g. "/status" or "/starttask abc123"
|
|
68
|
+
* @returns {Promise<any>}
|
|
69
|
+
*/
|
|
70
|
+
export async function sendCommandToChat(cmd) {
|
|
71
|
+
return apiFetch("/api/command", {
|
|
72
|
+
method: "POST",
|
|
73
|
+
body: JSON.stringify({ command: cmd }),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* ─── WebSocket ─── */
|
|
78
|
+
|
|
79
|
+
/** @type {WebSocket|null} */
|
|
80
|
+
let ws = null;
|
|
81
|
+
/** @type {ReturnType<typeof setTimeout>|null} */
|
|
82
|
+
let reconnectTimer = null;
|
|
83
|
+
/** @type {ReturnType<typeof setInterval>|null} */
|
|
84
|
+
let countdownTimer = null;
|
|
85
|
+
/** @type {ReturnType<typeof setInterval>|null} */
|
|
86
|
+
let pingTimer = null;
|
|
87
|
+
let retryMs = 1000;
|
|
88
|
+
|
|
89
|
+
/** Registered message handlers */
|
|
90
|
+
const wsHandlers = new Set();
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Register a handler for incoming WS messages.
|
|
94
|
+
* Returns an unsubscribe function.
|
|
95
|
+
* @param {(data: any) => void} handler
|
|
96
|
+
* @returns {() => void}
|
|
97
|
+
*/
|
|
98
|
+
export function onWsMessage(handler) {
|
|
99
|
+
wsHandlers.add(handler);
|
|
100
|
+
return () => wsHandlers.delete(handler);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Clear the reconnect countdown timer and reset signal */
|
|
104
|
+
function clearCountdown() {
|
|
105
|
+
if (countdownTimer) {
|
|
106
|
+
clearInterval(countdownTimer);
|
|
107
|
+
countdownTimer = null;
|
|
108
|
+
}
|
|
109
|
+
wsReconnectIn.value = null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Start a countdown timer that ticks wsReconnectIn every second */
|
|
113
|
+
function startCountdown(ms) {
|
|
114
|
+
clearCountdown();
|
|
115
|
+
let remaining = Math.ceil(ms / 1000);
|
|
116
|
+
wsReconnectIn.value = remaining;
|
|
117
|
+
countdownTimer = setInterval(() => {
|
|
118
|
+
remaining -= 1;
|
|
119
|
+
wsReconnectIn.value = Math.max(0, remaining);
|
|
120
|
+
if (remaining <= 0) {
|
|
121
|
+
clearInterval(countdownTimer);
|
|
122
|
+
countdownTimer = null;
|
|
123
|
+
}
|
|
124
|
+
}, 1000);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Start the client-side ping interval (every 30s) */
|
|
128
|
+
function startPing() {
|
|
129
|
+
stopPing();
|
|
130
|
+
pingTimer = setInterval(() => {
|
|
131
|
+
wsSend({ type: "ping", ts: Date.now() });
|
|
132
|
+
}, 30_000);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Stop the client-side ping interval */
|
|
136
|
+
function stopPing() {
|
|
137
|
+
if (pingTimer) {
|
|
138
|
+
clearInterval(pingTimer);
|
|
139
|
+
pingTimer = null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Open (or re-open) a WebSocket connection to /ws.
|
|
145
|
+
* Automatically reconnects on close with exponential backoff.
|
|
146
|
+
*/
|
|
147
|
+
export function connectWebSocket() {
|
|
148
|
+
// Prevent double connections
|
|
149
|
+
if (
|
|
150
|
+
ws &&
|
|
151
|
+
(ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)
|
|
152
|
+
) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const proto = globalThis.location.protocol === "https:" ? "wss" : "ws";
|
|
157
|
+
const wsUrl = new URL(`${proto}://${globalThis.location.host}/ws`);
|
|
158
|
+
|
|
159
|
+
const initData = getInitData();
|
|
160
|
+
if (initData) {
|
|
161
|
+
wsUrl.searchParams.set("initData", initData);
|
|
162
|
+
} else {
|
|
163
|
+
// Pass session token from cookie for browser-based WS auth
|
|
164
|
+
const m = (document.cookie || "").match(/(?:^|;\s*)ve_session=([^;]+)/);
|
|
165
|
+
if (m) wsUrl.searchParams.set("token", m[1]);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const socket = new WebSocket(wsUrl.toString());
|
|
169
|
+
ws = socket;
|
|
170
|
+
|
|
171
|
+
socket.addEventListener("open", () => {
|
|
172
|
+
wsConnected.value = true;
|
|
173
|
+
wsLatency.value = null;
|
|
174
|
+
retryMs = 1000; // reset backoff on successful connect
|
|
175
|
+
clearCountdown();
|
|
176
|
+
startPing();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
socket.addEventListener("message", (event) => {
|
|
180
|
+
let msg;
|
|
181
|
+
try {
|
|
182
|
+
msg = JSON.parse(event.data || "{}");
|
|
183
|
+
} catch {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Handle pong → calculate RTT
|
|
188
|
+
if (msg.type === "pong" && typeof msg.ts === "number") {
|
|
189
|
+
wsLatency.value = Date.now() - msg.ts;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Handle server-initiated ping → reply with pong
|
|
194
|
+
if (msg.type === "ping" && typeof msg.ts === "number") {
|
|
195
|
+
wsSend({ type: "pong", ts: msg.ts });
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Handle log-lines streaming
|
|
200
|
+
if (msg.type === "log-lines" && Array.isArray(msg.lines)) {
|
|
201
|
+
try {
|
|
202
|
+
globalThis.dispatchEvent(
|
|
203
|
+
new CustomEvent("ve:log-lines", { detail: { lines: msg.lines } }),
|
|
204
|
+
);
|
|
205
|
+
} catch {
|
|
206
|
+
/* noop */
|
|
207
|
+
}
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Dispatch to all registered handlers
|
|
212
|
+
for (const handler of wsHandlers) {
|
|
213
|
+
try {
|
|
214
|
+
handler(msg);
|
|
215
|
+
} catch {
|
|
216
|
+
/* handler errors shouldn't crash the WS loop */
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
socket.addEventListener("close", () => {
|
|
222
|
+
wsConnected.value = false;
|
|
223
|
+
wsLatency.value = null;
|
|
224
|
+
ws = null;
|
|
225
|
+
stopPing();
|
|
226
|
+
wsReconnectCount.value += 1;
|
|
227
|
+
// Auto-reconnect with exponential backoff (max 15 s)
|
|
228
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
229
|
+
startCountdown(retryMs);
|
|
230
|
+
reconnectTimer = setTimeout(() => {
|
|
231
|
+
reconnectTimer = null;
|
|
232
|
+
connectWebSocket();
|
|
233
|
+
}, retryMs);
|
|
234
|
+
retryMs = Math.min(15000, retryMs * 2);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
socket.addEventListener("error", () => {
|
|
238
|
+
wsConnected.value = false;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Disconnect the WebSocket and cancel any pending reconnect.
|
|
244
|
+
*/
|
|
245
|
+
export function disconnectWebSocket() {
|
|
246
|
+
if (reconnectTimer) {
|
|
247
|
+
clearTimeout(reconnectTimer);
|
|
248
|
+
reconnectTimer = null;
|
|
249
|
+
}
|
|
250
|
+
clearCountdown();
|
|
251
|
+
stopPing();
|
|
252
|
+
if (ws) {
|
|
253
|
+
try {
|
|
254
|
+
ws.close();
|
|
255
|
+
} catch {
|
|
256
|
+
/* noop */
|
|
257
|
+
}
|
|
258
|
+
ws = null;
|
|
259
|
+
}
|
|
260
|
+
wsConnected.value = false;
|
|
261
|
+
wsLatency.value = null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Send a raw JSON message over the open WebSocket.
|
|
266
|
+
* @param {any} data
|
|
267
|
+
*/
|
|
268
|
+
export function wsSend(data) {
|
|
269
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
270
|
+
ws.send(typeof data === "string" ? data : JSON.stringify(data));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/* ─── Log Streaming ─── */
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Subscribe to real-time log streaming over the WebSocket.
|
|
278
|
+
* @param {"system"|"agent"} logType
|
|
279
|
+
* @param {string} [query] - optional filter query (e.g. agent name)
|
|
280
|
+
*/
|
|
281
|
+
export function subscribeToLogs(logType, query) {
|
|
282
|
+
wsSend({ type: "subscribe-logs", logType, ...(query ? { query } : {}) });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Unsubscribe from log streaming.
|
|
287
|
+
*/
|
|
288
|
+
export function unsubscribeFromLogs() {
|
|
289
|
+
wsSend({ type: "unsubscribe-logs" });
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Reset the reconnect counter (call on user-initiated actions).
|
|
294
|
+
*/
|
|
295
|
+
export function resetReconnectCount() {
|
|
296
|
+
wsReconnectCount.value = 0;
|
|
297
|
+
}
|