@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,305 @@
1
+ /* ─────────────────────────────────────────────────────────────
2
+ * Component: Session List — ChatGPT-style sidebar for agent sessions
3
+ * ────────────────────────────────────────────────────────────── */
4
+ import { h } from "preact";
5
+ import { useState, useEffect, useCallback } from "preact/hooks";
6
+ import htm from "htm";
7
+ import { signal, computed } from "@preact/signals";
8
+ import { apiFetch } from "../modules/api.js";
9
+ import { formatRelative, truncate } from "../modules/utils.js";
10
+
11
+ const html = htm.bind(h);
12
+
13
+ /* ─── Signals ─── */
14
+ export const sessionsData = signal([]);
15
+ export const selectedSessionId = signal(null);
16
+ export const sessionMessages = signal([]);
17
+ export const sessionsError = signal(null);
18
+
19
+ /* ─── Data loaders ─── */
20
+ export async function loadSessions(filter = {}) {
21
+ try {
22
+ const params = new URLSearchParams(filter);
23
+ const res = await apiFetch(`/api/sessions?${params}`, { _silent: true });
24
+ if (res?.sessions) sessionsData.value = res.sessions;
25
+ sessionsError.value = null;
26
+ } catch {
27
+ sessionsError.value = "unavailable";
28
+ }
29
+ }
30
+
31
+ export async function loadSessionMessages(id) {
32
+ try {
33
+ const res = await apiFetch(`/api/sessions/${id}`, { _silent: true });
34
+ if (res?.session) sessionMessages.value = res.session.messages || [];
35
+ } catch {
36
+ sessionMessages.value = [];
37
+ }
38
+ }
39
+
40
+ export async function createSession(options = {}) {
41
+ try {
42
+ const body = options && Object.keys(options).length > 0 ? options : null;
43
+ const res = await apiFetch("/api/sessions/create", {
44
+ method: "POST",
45
+ ...(body ? { body: JSON.stringify(body) } : {}),
46
+ });
47
+ if (res?.session?.id) {
48
+ await loadSessions();
49
+ selectedSessionId.value = res.session.id;
50
+ }
51
+ return res;
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ /* ─── Helpers ─── */
58
+ const TYPE_ICONS = { primary: "🤖", task: "🔨", review: "👀", manual: "💬" };
59
+ const STATUS_ICONS = { active: "🟢", paused: "⏸️", completed: "✅", error: "🔴" };
60
+
61
+ function sessionIcon(type) {
62
+ return TYPE_ICONS[(type || "").toLowerCase()] || "💬";
63
+ }
64
+
65
+ function statusIcon(status) {
66
+ return STATUS_ICONS[(status || "").toLowerCase()] || "";
67
+ }
68
+
69
+ /* ─── SessionList component ─── */
70
+ export function SessionList({
71
+ onSelect,
72
+ showArchived = true,
73
+ onToggleArchived,
74
+ defaultType = null,
75
+ }) {
76
+ const [search, setSearch] = useState("");
77
+ const allSessions = sessionsData.value || [];
78
+ const error = sessionsError.value;
79
+ const hasSearch = search.trim().length > 0;
80
+
81
+ const base = showArchived
82
+ ? allSessions
83
+ : allSessions.filter((s) => s.status !== "archived");
84
+
85
+ const filtered = search
86
+ ? base.filter(
87
+ (s) =>
88
+ (s.title || "").toLowerCase().includes(search.toLowerCase()) ||
89
+ (s.taskId || "").toLowerCase().includes(search.toLowerCase()),
90
+ )
91
+ : base;
92
+
93
+ const active = filtered.filter(
94
+ (s) => s.status === "active" || s.status === "running",
95
+ );
96
+ const archived = filtered.filter((s) => s.status === "archived");
97
+ const recent = filtered.filter(
98
+ (s) =>
99
+ s.status !== "active" &&
100
+ s.status !== "running" &&
101
+ s.status !== "archived",
102
+ );
103
+
104
+ const handleSelect = useCallback(
105
+ (id) => {
106
+ selectedSessionId.value = id;
107
+ if (onSelect) onSelect(id);
108
+ },
109
+ [onSelect],
110
+ );
111
+
112
+ const handleRetry = useCallback(() => {
113
+ sessionsError.value = null;
114
+ loadSessions();
115
+ }, []);
116
+
117
+ if (error) {
118
+ return html`
119
+ <div class="session-list">
120
+ <div class="session-list-header">
121
+ <span class="session-list-title">Sessions</span>
122
+ </div>
123
+ <div class="session-empty">
124
+ <div class="session-empty-icon">📡</div>
125
+ <div class="session-empty-text">Sessions not available</div>
126
+ <button class="btn btn-primary btn-sm" onClick=${handleRetry}>
127
+ Retry
128
+ </button>
129
+ </div>
130
+ </div>
131
+ `;
132
+ }
133
+
134
+ return html`
135
+ <div class="session-list">
136
+ <div class="session-list-header">
137
+ <span class="session-list-title">Sessions</span>
138
+ <div style="display:flex;gap:6px;align-items:center">
139
+ ${typeof onToggleArchived === "function" &&
140
+ html`
141
+ <button
142
+ class="btn btn-ghost btn-sm"
143
+ onClick=${() => onToggleArchived(!showArchived)}
144
+ >
145
+ ${showArchived ? "Hide Archived" : "Show Archived"}
146
+ </button>
147
+ `}
148
+ <button
149
+ class="btn btn-primary btn-sm"
150
+ onClick=${() =>
151
+ createSession(defaultType ? { type: defaultType } : {})}
152
+ >
153
+ New Session
154
+ </button>
155
+ </div>
156
+ </div>
157
+
158
+ <div class="session-search">
159
+ <input
160
+ class="input session-search-input"
161
+ placeholder="Search by title or task ID…"
162
+ value=${search}
163
+ onInput=${(e) => setSearch(e.target.value)}
164
+ />
165
+ </div>
166
+
167
+ <div class="session-list-scroll">
168
+ ${active.length > 0 &&
169
+ html`
170
+ <div class="session-group-label">Active Sessions</div>
171
+ ${active.map(
172
+ (s) => html`
173
+ <div
174
+ key=${s.id}
175
+ class="session-item ${selectedSessionId.value === s.id
176
+ ? "active"
177
+ : ""}"
178
+ onClick=${() => handleSelect(s.id)}
179
+ >
180
+ <div class="session-item-row">
181
+ <span class="session-item-icon"
182
+ >${sessionIcon(s.type)}</span
183
+ >
184
+ <span class="session-item-title"
185
+ >${truncate(s.title || s.taskId || "Untitled", 28)}</span
186
+ >
187
+ <span class="session-item-status"
188
+ >${statusIcon(s.status)}</span
189
+ >
190
+ </div>
191
+ ${s.lastMessage &&
192
+ html`
193
+ <div class="session-item-preview">
194
+ ${truncate(s.lastMessage, 50)}
195
+ </div>
196
+ `}
197
+ <div class="session-item-time">${formatRelative(s.updatedAt || s.createdAt)}</div>
198
+ </div>
199
+ `,
200
+ )}
201
+ `}
202
+ ${recent.length > 0 &&
203
+ html`
204
+ <div class="session-group-label">Recent Sessions</div>
205
+ ${recent.map(
206
+ (s) => html`
207
+ <div
208
+ key=${s.id}
209
+ class="session-item ${selectedSessionId.value === s.id
210
+ ? "active"
211
+ : ""}"
212
+ onClick=${() => handleSelect(s.id)}
213
+ >
214
+ <div class="session-item-row">
215
+ <span class="session-item-icon"
216
+ >${sessionIcon(s.type)}</span
217
+ >
218
+ <span class="session-item-title"
219
+ >${truncate(s.title || s.taskId || "Untitled", 28)}</span
220
+ >
221
+ <span class="session-item-status"
222
+ >${statusIcon(s.status)}</span
223
+ >
224
+ </div>
225
+ ${s.lastMessage &&
226
+ html`
227
+ <div class="session-item-preview">
228
+ ${truncate(s.lastMessage, 50)}
229
+ </div>
230
+ `}
231
+ <div class="session-item-time">${formatRelative(s.updatedAt || s.createdAt)}</div>
232
+ </div>
233
+ `,
234
+ )}
235
+ `}
236
+ ${archived.length > 0 &&
237
+ html`
238
+ <div class="session-group-label">Archived</div>
239
+ ${archived.map(
240
+ (s) => html`
241
+ <div
242
+ key=${s.id}
243
+ class="session-item ${selectedSessionId.value === s.id
244
+ ? "active"
245
+ : ""}"
246
+ onClick=${() => handleSelect(s.id)}
247
+ >
248
+ <div class="session-item-row">
249
+ <span class="session-item-icon"
250
+ >${sessionIcon(s.type)}</span
251
+ >
252
+ <span class="session-item-title"
253
+ >${truncate(s.title || s.taskId || "Untitled", 28)}</span
254
+ >
255
+ <span class="session-item-status"
256
+ >${statusIcon(s.status)}</span
257
+ >
258
+ </div>
259
+ ${s.lastMessage &&
260
+ html`
261
+ <div class="session-item-preview">
262
+ ${truncate(s.lastMessage, 50)}
263
+ </div>
264
+ `}
265
+ <div class="session-item-time">${formatRelative(s.updatedAt || s.createdAt)}</div>
266
+ </div>
267
+ `,
268
+ )}
269
+ `}
270
+ ${filtered.length === 0 &&
271
+ html`
272
+ <div class="session-empty">
273
+ <div class="session-empty-icon">💬</div>
274
+ <div class="session-empty-text">
275
+ ${hasSearch ? "No matching sessions" : "No sessions yet"}
276
+ <div class="session-empty-subtext">
277
+ ${hasSearch
278
+ ? "Try a different keyword or clear the search."
279
+ : "Create a session to start streaming agent output."}
280
+ </div>
281
+ </div>
282
+ <div class="session-empty-actions">
283
+ <button
284
+ class="btn btn-primary btn-sm"
285
+ onClick=${() =>
286
+ createSession(defaultType ? { type: defaultType } : {})}
287
+ >
288
+ + New Session
289
+ </button>
290
+ ${hasSearch &&
291
+ html`
292
+ <button
293
+ class="btn btn-ghost btn-sm"
294
+ onClick=${() => setSearch("")}
295
+ >
296
+ Clear search
297
+ </button>
298
+ `}
299
+ </div>
300
+ </div>
301
+ `}
302
+ </div>
303
+ </div>
304
+ `;
305
+ }