@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,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
|
+
}
|