@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.
Files changed (120) hide show
  1. package/.env.example +914 -0
  2. package/LICENSE +190 -0
  3. package/README.md +500 -0
  4. package/agent-endpoint.mjs +918 -0
  5. package/agent-hook-bridge.mjs +230 -0
  6. package/agent-hooks.mjs +1188 -0
  7. package/agent-pool.mjs +2403 -0
  8. package/agent-prompts.mjs +689 -0
  9. package/agent-sdk.mjs +141 -0
  10. package/anomaly-detector.mjs +1195 -0
  11. package/autofix.mjs +1294 -0
  12. package/claude-shell.mjs +708 -0
  13. package/cli.mjs +906 -0
  14. package/codex-config.mjs +1274 -0
  15. package/codex-model-profiles.mjs +135 -0
  16. package/codex-shell.mjs +762 -0
  17. package/config-doctor.mjs +613 -0
  18. package/config.mjs +1720 -0
  19. package/conflict-resolver.mjs +248 -0
  20. package/container-runner.mjs +450 -0
  21. package/copilot-shell.mjs +827 -0
  22. package/daemon-restart-policy.mjs +56 -0
  23. package/diff-stats.mjs +282 -0
  24. package/error-detector.mjs +829 -0
  25. package/fetch-runtime.mjs +34 -0
  26. package/fleet-coordinator.mjs +838 -0
  27. package/get-telegram-chat-id.mjs +71 -0
  28. package/git-safety.mjs +170 -0
  29. package/github-reconciler.mjs +403 -0
  30. package/hook-profiles.mjs +651 -0
  31. package/kanban-adapter.mjs +4491 -0
  32. package/lib/logger.mjs +645 -0
  33. package/maintenance.mjs +828 -0
  34. package/merge-strategy.mjs +1171 -0
  35. package/monitor.mjs +12207 -0
  36. package/openfleet.config.example.json +115 -0
  37. package/openfleet.schema.json +465 -0
  38. package/package.json +203 -0
  39. package/postinstall.mjs +187 -0
  40. package/pr-cleanup-daemon.mjs +978 -0
  41. package/preflight.mjs +408 -0
  42. package/prepublish-check.mjs +90 -0
  43. package/presence.mjs +328 -0
  44. package/primary-agent.mjs +282 -0
  45. package/publish.mjs +151 -0
  46. package/repo-root.mjs +29 -0
  47. package/restart-controller.mjs +100 -0
  48. package/review-agent.mjs +557 -0
  49. package/rotate-agent-logs.sh +133 -0
  50. package/sdk-conflict-resolver.mjs +973 -0
  51. package/session-tracker.mjs +880 -0
  52. package/setup.mjs +3937 -0
  53. package/shared-knowledge.mjs +410 -0
  54. package/shared-state-manager.mjs +841 -0
  55. package/shared-workspace-cli.mjs +199 -0
  56. package/shared-workspace-registry.mjs +537 -0
  57. package/shared-workspaces.json +18 -0
  58. package/startup-service.mjs +1070 -0
  59. package/sync-engine.mjs +1063 -0
  60. package/task-archiver.mjs +801 -0
  61. package/task-assessment.mjs +550 -0
  62. package/task-claims.mjs +924 -0
  63. package/task-complexity.mjs +581 -0
  64. package/task-executor.mjs +5111 -0
  65. package/task-store.mjs +753 -0
  66. package/telegram-bot.mjs +9281 -0
  67. package/telegram-sentinel.mjs +2010 -0
  68. package/ui/app.js +867 -0
  69. package/ui/app.legacy.js +1464 -0
  70. package/ui/app.monolith.js +2488 -0
  71. package/ui/components/charts.js +226 -0
  72. package/ui/components/chat-view.js +567 -0
  73. package/ui/components/command-palette.js +587 -0
  74. package/ui/components/diff-viewer.js +190 -0
  75. package/ui/components/forms.js +327 -0
  76. package/ui/components/kanban-board.js +451 -0
  77. package/ui/components/session-list.js +305 -0
  78. package/ui/components/shared.js +473 -0
  79. package/ui/index.html +70 -0
  80. package/ui/modules/api.js +297 -0
  81. package/ui/modules/icons.js +461 -0
  82. package/ui/modules/router.js +81 -0
  83. package/ui/modules/settings-schema.js +261 -0
  84. package/ui/modules/state.js +679 -0
  85. package/ui/modules/telegram.js +331 -0
  86. package/ui/modules/utils.js +270 -0
  87. package/ui/styles/animations.css +140 -0
  88. package/ui/styles/base.css +98 -0
  89. package/ui/styles/components.css +1915 -0
  90. package/ui/styles/kanban.css +286 -0
  91. package/ui/styles/layout.css +809 -0
  92. package/ui/styles/sessions.css +827 -0
  93. package/ui/styles/variables.css +188 -0
  94. package/ui/styles.css +141 -0
  95. package/ui/styles.monolith.css +1046 -0
  96. package/ui/tabs/agents.js +1417 -0
  97. package/ui/tabs/chat.js +74 -0
  98. package/ui/tabs/control.js +887 -0
  99. package/ui/tabs/dashboard.js +515 -0
  100. package/ui/tabs/infra.js +537 -0
  101. package/ui/tabs/logs.js +783 -0
  102. package/ui/tabs/settings.js +1487 -0
  103. package/ui/tabs/tasks.js +1385 -0
  104. package/ui-server.mjs +4073 -0
  105. package/update-check.mjs +465 -0
  106. package/utils.mjs +172 -0
  107. package/ve-kanban.mjs +654 -0
  108. package/ve-kanban.ps1 +1365 -0
  109. package/ve-kanban.sh +18 -0
  110. package/ve-orchestrator.mjs +340 -0
  111. package/ve-orchestrator.ps1 +6546 -0
  112. package/ve-orchestrator.sh +18 -0
  113. package/vibe-kanban-wrapper.mjs +41 -0
  114. package/vk-error-resolver.mjs +470 -0
  115. package/vk-log-stream.mjs +914 -0
  116. package/whatsapp-channel.mjs +520 -0
  117. package/workspace-monitor.mjs +581 -0
  118. package/workspace-reaper.mjs +405 -0
  119. package/workspace-registry.mjs +238 -0
  120. 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
+ }