@virtengine/openfleet 0.26.2 → 0.26.3
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 +2 -2
- package/cli.mjs +48 -6
- package/compat.mjs +286 -0
- package/config.mjs +4 -0
- package/kanban-adapter.mjs +5 -5
- package/monitor.mjs +34 -4
- package/package.json +4 -2
- package/presence.mjs +1 -1
- package/publish.mjs +2 -2
- package/setup.mjs +9 -0
- package/shared-state-manager.mjs +1 -1
- package/startup-service.mjs +2 -2
- package/telegram-bot.mjs +136 -2
- package/ui/demo.html +640 -0
- package/ui/modules/settings-schema.js +1 -1
- package/ui/styles/variables.css +1 -1
- package/ui-server.mjs +3 -2
- package/update-check.mjs +9 -3
- package/ve-orchestrator.ps1 +6 -6
- package/whatsapp-channel.mjs +3 -3
package/ui/demo.html
ADDED
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
|
6
|
+
<meta http-equiv="Content-Security-Policy" content="default-src * 'unsafe-inline' 'unsafe-eval' data: blob:; img-src * data: blob:; connect-src *;" />
|
|
7
|
+
<title>OpenFleet MiniApp — Demo Mode</title>
|
|
8
|
+
|
|
9
|
+
<!-- Telegram WebApp SDK is fully mocked below — no real SDK needed -->
|
|
10
|
+
|
|
11
|
+
<!-- Import map — same as the real app -->
|
|
12
|
+
<script type="importmap">
|
|
13
|
+
{
|
|
14
|
+
"imports": {
|
|
15
|
+
"preact": "https://esm.sh/preact@10.25.4",
|
|
16
|
+
"preact/hooks": "https://esm.sh/preact@10.25.4/hooks",
|
|
17
|
+
"preact/compat": "https://esm.sh/preact@10.25.4/compat",
|
|
18
|
+
"htm": "https://esm.sh/htm@3.1.1",
|
|
19
|
+
"@preact/signals": "https://esm.sh/@preact/signals@1.3.1?deps=preact@10.25.4"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<!-- Load the real MiniApp styles -->
|
|
25
|
+
<link rel="stylesheet" href="styles.css" />
|
|
26
|
+
<link rel="stylesheet" href="styles/kanban.css" />
|
|
27
|
+
<link rel="stylesheet" href="styles/sessions.css" />
|
|
28
|
+
|
|
29
|
+
<!-- Fonts (match site) -->
|
|
30
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
31
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
32
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet" />
|
|
33
|
+
|
|
34
|
+
<style>
|
|
35
|
+
/* Demo mode indicator */
|
|
36
|
+
.demo-banner {
|
|
37
|
+
position: fixed;
|
|
38
|
+
top: 0;
|
|
39
|
+
left: 0;
|
|
40
|
+
right: 0;
|
|
41
|
+
z-index: 99999;
|
|
42
|
+
background: linear-gradient(135deg, #60cc5d, #4caf50);
|
|
43
|
+
color: #000;
|
|
44
|
+
text-align: center;
|
|
45
|
+
font-size: 11px;
|
|
46
|
+
font-weight: 700;
|
|
47
|
+
padding: 3px 8px;
|
|
48
|
+
font-family: 'Inter', -apple-system, sans-serif;
|
|
49
|
+
letter-spacing: 0.05em;
|
|
50
|
+
text-transform: uppercase;
|
|
51
|
+
}
|
|
52
|
+
/* Push content down */
|
|
53
|
+
body { padding-top: 22px !important; }
|
|
54
|
+
/* Ensure no scrollbar leaks */
|
|
55
|
+
html, body { overflow: hidden; height: 100%; margin: 0; }
|
|
56
|
+
/* Force dark theme */
|
|
57
|
+
:root {
|
|
58
|
+
color-scheme: dark;
|
|
59
|
+
}
|
|
60
|
+
</style>
|
|
61
|
+
|
|
62
|
+
<!-- ═══ DEMO MOCK LAYER ═══════════════════════════════════════════════
|
|
63
|
+
This script runs BEFORE the real app.js to mock:
|
|
64
|
+
1. Telegram.WebApp — provides fake theme, user, initData
|
|
65
|
+
2. fetch() — intercepts /api/* to return seed data
|
|
66
|
+
3. WebSocket — provides fake WS connection for live updates
|
|
67
|
+
═══════════════════════════════════════════════════════════════════ -->
|
|
68
|
+
<script>
|
|
69
|
+
(function() {
|
|
70
|
+
'use strict';
|
|
71
|
+
|
|
72
|
+
/* ── Mock Telegram.WebApp ──────────────────────────────────────── */
|
|
73
|
+
const mockUser = {
|
|
74
|
+
id: 1337420,
|
|
75
|
+
first_name: 'Demo',
|
|
76
|
+
last_name: 'User',
|
|
77
|
+
username: 'demo_user',
|
|
78
|
+
language_code: 'en',
|
|
79
|
+
is_premium: true,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const theme = {
|
|
83
|
+
bg_color: '#0b0f14',
|
|
84
|
+
text_color: '#f1f5f9',
|
|
85
|
+
hint_color: '#64748b',
|
|
86
|
+
link_color: '#4cc9f0',
|
|
87
|
+
button_color: '#4cc9f0',
|
|
88
|
+
button_text_color: '#000000',
|
|
89
|
+
secondary_bg_color: '#131a24',
|
|
90
|
+
header_bg_color: '#0b0f14',
|
|
91
|
+
accent_text_color: '#4cc9f0',
|
|
92
|
+
section_bg_color: '#131a24',
|
|
93
|
+
section_header_text_color: '#94a3b8',
|
|
94
|
+
subtitle_text_color: '#94a3b8',
|
|
95
|
+
destructive_text_color: '#ef4444',
|
|
96
|
+
section_separator_color: '#1e293b',
|
|
97
|
+
bottom_bar_bg_color: '#0b0f14',
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
window.Telegram = {
|
|
101
|
+
WebApp: {
|
|
102
|
+
initData: 'demo_mode=true&user=' + encodeURIComponent(JSON.stringify(mockUser)),
|
|
103
|
+
initDataUnsafe: { user: mockUser, auth_date: Date.now() },
|
|
104
|
+
version: '8.0',
|
|
105
|
+
platform: 'web',
|
|
106
|
+
colorScheme: 'dark',
|
|
107
|
+
themeParams: theme,
|
|
108
|
+
isExpanded: true,
|
|
109
|
+
viewportHeight: window.innerHeight,
|
|
110
|
+
viewportStableHeight: window.innerHeight,
|
|
111
|
+
headerColor: theme.header_bg_color,
|
|
112
|
+
backgroundColor: theme.bg_color,
|
|
113
|
+
bottomBarColor: theme.bottom_bar_bg_color,
|
|
114
|
+
isClosingConfirmationEnabled: false,
|
|
115
|
+
isVerticalSwipesEnabled: false,
|
|
116
|
+
isFullscreen: true,
|
|
117
|
+
safeAreaInset: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
118
|
+
contentSafeAreaInset: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
119
|
+
BackButton: {
|
|
120
|
+
isVisible: false,
|
|
121
|
+
show() { this.isVisible = true; },
|
|
122
|
+
hide() { this.isVisible = false; },
|
|
123
|
+
onClick(cb) { this._cb = cb; },
|
|
124
|
+
offClick() { this._cb = null; },
|
|
125
|
+
},
|
|
126
|
+
MainButton: {
|
|
127
|
+
isVisible: false,
|
|
128
|
+
text: '',
|
|
129
|
+
color: theme.button_color,
|
|
130
|
+
textColor: theme.button_text_color,
|
|
131
|
+
isProgressVisible: false,
|
|
132
|
+
show() { this.isVisible = true; },
|
|
133
|
+
hide() { this.isVisible = false; },
|
|
134
|
+
setText(t) { this.text = t; },
|
|
135
|
+
onClick(cb) { this._cb = cb; },
|
|
136
|
+
offClick() { this._cb = null; },
|
|
137
|
+
showProgress() { this.isProgressVisible = true; },
|
|
138
|
+
hideProgress() { this.isProgressVisible = false; },
|
|
139
|
+
enable() {},
|
|
140
|
+
disable() {},
|
|
141
|
+
},
|
|
142
|
+
HapticFeedback: {
|
|
143
|
+
impactOccurred() {},
|
|
144
|
+
notificationOccurred() {},
|
|
145
|
+
selectionChanged() {},
|
|
146
|
+
},
|
|
147
|
+
ready() {},
|
|
148
|
+
expand() {},
|
|
149
|
+
close() {},
|
|
150
|
+
requestFullscreen() {},
|
|
151
|
+
disableVerticalSwipes() {},
|
|
152
|
+
enableClosingConfirmation() {},
|
|
153
|
+
setHeaderColor(c) { this.headerColor = c; },
|
|
154
|
+
setBackgroundColor(c) { this.backgroundColor = c; },
|
|
155
|
+
setBottomBarColor(c) { this.bottomBarColor = c; },
|
|
156
|
+
onEvent(name, cb) {},
|
|
157
|
+
offEvent(name, cb) {},
|
|
158
|
+
showConfirm(msg, cb) { if (cb) cb(true); },
|
|
159
|
+
showAlert(msg, cb) { if (cb) cb(); },
|
|
160
|
+
showPopup(params, cb) { if (cb) cb('ok'); },
|
|
161
|
+
openLink(url) { window.open(url, '_blank'); },
|
|
162
|
+
openTelegramLink(url) {},
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/* ── Seed Data ─────────────────────────────────────────────────── */
|
|
167
|
+
const now = Date.now();
|
|
168
|
+
const min = 60000;
|
|
169
|
+
const hr = 3600000;
|
|
170
|
+
|
|
171
|
+
const SEED_TASKS = [
|
|
172
|
+
{ id: 've-abc123', title: 'feat(market): add order expiry validation', status: 'done', priority: 'high', assignee: 'copilot-claude', branch: 've/abc123-market-order-expiry', pr: 187, created: now - 4*hr, updated: now - 1*hr },
|
|
173
|
+
{ id: 've-def456', title: 'fix(veid): token validation edge case', status: 'done', priority: 'medium', assignee: 'codex-default', branch: 've/def456-veid-token-fix', pr: 188, created: now - 3*hr, updated: now - 45*min },
|
|
174
|
+
{ id: 've-ghi789', title: 'refactor(escrow): batch settlement pipeline', status: 'inprogress', priority: 'high', assignee: 'copilot-claude', branch: 've/ghi789-escrow-batch', pr: 189, created: now - 2*hr, updated: now - 15*min },
|
|
175
|
+
{ id: 've-jkl012', title: 'feat(hpc): GPU resource metering', status: 'inprogress', priority: 'critical', assignee: 'codex-default', branch: 've/jkl012-hpc-gpu-metering', pr: null, created: now - 1*hr, updated: now - 5*min },
|
|
176
|
+
{ id: 've-mno345', title: 'docs: update provider integration guide', status: 'todo', priority: 'low', assignee: null, branch: null, pr: null, created: now - 30*min, updated: now - 30*min },
|
|
177
|
+
{ id: 've-pqr678', title: 'feat(provider): health check endpoint', status: 'todo', priority: 'medium', assignee: null, branch: null, pr: null, created: now - 20*min, updated: now - 20*min },
|
|
178
|
+
{ id: 've-stu901', title: 'fix(roles): permission cascade bug', status: 'draft', priority: 'high', assignee: null, branch: null, pr: null, created: now - 10*min, updated: now - 10*min },
|
|
179
|
+
{ id: 've-vwx234', title: 'test(mfa): add integration test suite', status: 'inreview', priority: 'medium', assignee: 'copilot-claude', branch: 've/vwx234-mfa-tests', pr: 190, created: now - 5*hr, updated: now - 30*min },
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
const SEED_STATUS = {
|
|
183
|
+
status: 'running',
|
|
184
|
+
uptime: '2h 34m 12s',
|
|
185
|
+
executorMode: 'internal',
|
|
186
|
+
board: 'github',
|
|
187
|
+
maxParallel: 6,
|
|
188
|
+
version: '0.26.2',
|
|
189
|
+
tasks: { completed: 13, failed: 1, retried: 2, active: 2, queued: 3 },
|
|
190
|
+
prs: { created: 15, merged: 11, pending: 2, ciFailures: 1 },
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const SEED_EXECUTORS = [
|
|
194
|
+
{ name: 'copilot-claude', sdk: 'copilot', model: 'claude-opus-4-6', weight: 50, status: 'active', load: 67, tasksCompleted: 8, avgTime: '12m', uptime: '2h 34m', session: 'sk-...7f3a' },
|
|
195
|
+
{ name: 'codex-default', sdk: 'codex', model: 'o4-mini', weight: 50, status: 'active', load: 42, tasksCompleted: 5, avgTime: '18m', uptime: '2h 34m', session: 'cx-...a91b' },
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
const SEED_AGENTS = [
|
|
199
|
+
{ id: 'agent-001', name: 'copilot-claude', type: 'copilot', model: 'claude-opus-4-6', status: 'busy', currentTask: 've-ghi789', lastHeartbeat: now - 30000 },
|
|
200
|
+
{ id: 'agent-002', name: 'codex-default', type: 'codex', model: 'o4-mini', status: 'busy', currentTask: 've-jkl012', lastHeartbeat: now - 15000 },
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
const SEED_LOGS = [
|
|
204
|
+
{ ts: now - 20*min, level: 'info', source: 'monitor', msg: 'Polling github for new tasks...' },
|
|
205
|
+
{ ts: now - 19*min, level: 'info', source: 'kanban', msg: 'Found 2 new tasks in backlog' },
|
|
206
|
+
{ ts: now - 18*min, level: 'info', source: 'task-executor', msg: 'Routing #42 feat(market): add order expiry → copilot-claude' },
|
|
207
|
+
{ ts: now - 17*min, level: 'info', source: 'task-executor', msg: 'Routing #43 fix(veid): token validation → codex-default' },
|
|
208
|
+
{ ts: now - 14*min, level: 'info', source: 'worktree', msg: 'Created ve/abc123-market-order-expiry' },
|
|
209
|
+
{ ts: now - 12*min, level: 'info', source: 'copilot', msg: 'Session started for #42' },
|
|
210
|
+
{ ts: now - 10*min, level: 'success', source: 'pr', msg: 'PR #187 created — CI running...' },
|
|
211
|
+
{ ts: now - 8*min, level: 'success', source: 'ci', msg: 'PR #187 — all checks passed' },
|
|
212
|
+
{ ts: now - 7*min, level: 'success', source: 'merge', msg: 'PR #187 merged to main ✓' },
|
|
213
|
+
{ ts: now - 5*min, level: 'info', source: 'health', msg: 'Health check: all systems nominal' },
|
|
214
|
+
{ ts: now - 2*min, level: 'info', source: 'review-agent', msg: 'Reviewing PR #189...' },
|
|
215
|
+
{ ts: now - 1*min, level: 'info', source: 'monitor', msg: 'Next poll in 60s...' },
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
const SEED_HEALTH = {
|
|
219
|
+
overall: 'healthy',
|
|
220
|
+
components: [
|
|
221
|
+
{ name: 'GitHub API', status: 'ok', latency: '142ms' },
|
|
222
|
+
{ name: 'Telegram Bot', status: 'ok', latency: '89ms' },
|
|
223
|
+
{ name: 'Codex SDK', status: 'ok', latency: '234ms' },
|
|
224
|
+
{ name: 'Copilot SDK', status: 'ok', latency: '178ms' },
|
|
225
|
+
{ name: 'Shared State', status: 'ok', latency: '12ms' },
|
|
226
|
+
{ name: 'Worktree Manager', status: 'ok', latency: '45ms' },
|
|
227
|
+
],
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const SEED_INFRA = {
|
|
231
|
+
worktrees: [
|
|
232
|
+
{ branch: 've/abc123-market-order-expiry', path: '/worktrees/abc123', active: false },
|
|
233
|
+
{ branch: 've/ghi789-escrow-batch', path: '/worktrees/ghi789', active: true },
|
|
234
|
+
{ branch: 've/jkl012-hpc-gpu-metering', path: '/worktrees/jkl012', active: true },
|
|
235
|
+
{ branch: 've/vwx234-mfa-tests', path: '/worktrees/vwx234', active: true },
|
|
236
|
+
],
|
|
237
|
+
containers: [],
|
|
238
|
+
tunnelStatus: 'active',
|
|
239
|
+
tunnelUrl: 'https://openfleet-demo.trycloudflare.com',
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const SEED_PROJECT = {
|
|
243
|
+
name: 'virtengine',
|
|
244
|
+
repo: 'virtengine/virtengine',
|
|
245
|
+
defaultBranch: 'main',
|
|
246
|
+
totalTasks: SEED_TASKS.length,
|
|
247
|
+
completedTasks: 2,
|
|
248
|
+
activePRs: 3,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/* ── Internal State (mutable — actions modify this in-place) ──── */
|
|
252
|
+
const STATE = {
|
|
253
|
+
tasks: SEED_TASKS,
|
|
254
|
+
status: SEED_STATUS,
|
|
255
|
+
executors: SEED_EXECUTORS,
|
|
256
|
+
agents: SEED_AGENTS,
|
|
257
|
+
logs: SEED_LOGS,
|
|
258
|
+
health: SEED_HEALTH,
|
|
259
|
+
infra: SEED_INFRA,
|
|
260
|
+
project: SEED_PROJECT,
|
|
261
|
+
paused: false,
|
|
262
|
+
maxParallel: 6,
|
|
263
|
+
settings: {
|
|
264
|
+
maxParallel: 6,
|
|
265
|
+
executorMode: 'internal',
|
|
266
|
+
autoMerge: true,
|
|
267
|
+
autoRebase: true,
|
|
268
|
+
reviewAgent: true,
|
|
269
|
+
sentinelEnabled: false,
|
|
270
|
+
sdk: 'auto',
|
|
271
|
+
region: 'auto',
|
|
272
|
+
},
|
|
273
|
+
sessions: [
|
|
274
|
+
{ id: 'ses-001', name: 'copilot-claude #42', agentType: 'copilot', status: 'active', taskId: 've-ghi789', created: now - 2*hr, messages: [
|
|
275
|
+
{ role: 'system', text: 'Session started for ve-ghi789', ts: now - 2*hr },
|
|
276
|
+
{ role: 'assistant', text: 'Analyzing escrow batch settlement pipeline...', ts: now - 110*min },
|
|
277
|
+
{ role: 'assistant', text: 'Implementing batch processor in x/escrow/keeper/batch.go', ts: now - 90*min },
|
|
278
|
+
]},
|
|
279
|
+
{ id: 'ses-002', name: 'codex-default #43', agentType: 'codex', status: 'active', taskId: 've-jkl012', created: now - 1*hr, messages: [
|
|
280
|
+
{ role: 'system', text: 'Session started for ve-jkl012', ts: now - 1*hr },
|
|
281
|
+
{ role: 'assistant', text: 'Reading GPU metering requirements...', ts: now - 50*min },
|
|
282
|
+
]},
|
|
283
|
+
],
|
|
284
|
+
worktrees: [
|
|
285
|
+
{ branch: 've/abc123-market-order-expiry', path: '/worktrees/abc123', active: false, taskId: 've-abc123' },
|
|
286
|
+
{ branch: 've/ghi789-escrow-batch', path: '/worktrees/ghi789', active: true, taskId: 've-ghi789' },
|
|
287
|
+
{ branch: 've/jkl012-hpc-gpu-metering', path: '/worktrees/jkl012', active: true, taskId: 've-jkl012' },
|
|
288
|
+
{ branch: 've/vwx234-mfa-tests', path: '/worktrees/vwx234', active: true, taskId: 've-vwx234' },
|
|
289
|
+
],
|
|
290
|
+
sharedWorkspaces: [
|
|
291
|
+
{ id: 'sw-1', name: 'shared-escrow-dev', claimedBy: 'copilot-claude', claimedAt: now - 1*hr, expiresAt: now + 1*hr },
|
|
292
|
+
],
|
|
293
|
+
gitBranches: ['main', 've/abc123-market-order-expiry', 've/ghi789-escrow-batch', 've/jkl012-hpc-gpu-metering', 've/vwx234-mfa-tests'],
|
|
294
|
+
gitDiff: '--- a/x/escrow/keeper/batch.go\n+++ b/x/escrow/keeper/batch.go\n@@ -0,0 +1,42 @@\n+package keeper\n+\n+// BatchSettle processes pending settlements in batch.\n+func (k Keeper) BatchSettle(ctx sdk.Context) error {\n+ // ... implementation\n+ return nil\n+}',
|
|
295
|
+
agentLogFiles: ['copilot-claude-42.log', 'codex-default-43.log', 'monitor.log'],
|
|
296
|
+
presence: {
|
|
297
|
+
instances: [{ id: 'inst-1', host: 'dev-machine', pid: 12345, started: now - 3*hr, role: 'primary' }],
|
|
298
|
+
coordinator: { id: 'inst-1', host: 'dev-machine', elected: now - 3*hr },
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
/* ── Helper: find task by ID ───────────────────────────────────── */
|
|
303
|
+
function findTask(id) { return STATE.tasks.find(t => t.id === id); }
|
|
304
|
+
function addLog(level, source, msg) {
|
|
305
|
+
STATE.logs.unshift({ ts: Date.now(), level, source, msg });
|
|
306
|
+
if (STATE.logs.length > 100) STATE.logs.length = 100;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* ── API Route Handler (comprehensive — catches ALL /api/* calls) */
|
|
310
|
+
function handleApi(path, method, body) {
|
|
311
|
+
// Normalize: strip query string for route matching
|
|
312
|
+
const [route, qs] = path.split('?');
|
|
313
|
+
const params = new URLSearchParams(qs || '');
|
|
314
|
+
|
|
315
|
+
// ── Status & Executor ──
|
|
316
|
+
if (route === '/api/status')
|
|
317
|
+
return { data: STATE.status };
|
|
318
|
+
if (route === '/api/executor')
|
|
319
|
+
return { data: { ...STATE.status, maxParallel: STATE.maxParallel, paused: STATE.paused, executors: STATE.executors } };
|
|
320
|
+
if (route === '/api/executor/pause') {
|
|
321
|
+
STATE.paused = true; addLog('info', 'executor', 'Executor paused');
|
|
322
|
+
return { ok: true, paused: true };
|
|
323
|
+
}
|
|
324
|
+
if (route === '/api/executor/resume') {
|
|
325
|
+
STATE.paused = false; addLog('info', 'executor', 'Executor resumed');
|
|
326
|
+
return { ok: true, paused: false };
|
|
327
|
+
}
|
|
328
|
+
if (route === '/api/executor/maxparallel') {
|
|
329
|
+
const mp = body?.maxParallel ?? STATE.maxParallel;
|
|
330
|
+
STATE.maxParallel = mp; STATE.status.maxParallel = mp;
|
|
331
|
+
addLog('info', 'executor', `Max parallel set to ${mp}`);
|
|
332
|
+
return { ok: true, maxParallel: mp };
|
|
333
|
+
}
|
|
334
|
+
if (route === '/api/executor/stop-slot') {
|
|
335
|
+
addLog('info', 'executor', `Slot stopped: ${body?.slotId || 'unknown'}`);
|
|
336
|
+
return { ok: true };
|
|
337
|
+
}
|
|
338
|
+
if (route === '/api/executor/dispatch') {
|
|
339
|
+
addLog('info', 'executor', `Manual dispatch: ${body?.taskId || 'unknown'}`);
|
|
340
|
+
return { ok: true, dispatched: body?.taskId };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ── Tasks ──
|
|
344
|
+
if (route === '/api/tasks') {
|
|
345
|
+
let tasks = [...STATE.tasks];
|
|
346
|
+
const status = params.get('status');
|
|
347
|
+
const priority = params.get('priority');
|
|
348
|
+
const search = params.get('search');
|
|
349
|
+
const sort = params.get('sort');
|
|
350
|
+
if (status && status !== 'all') tasks = tasks.filter(t => t.status === status);
|
|
351
|
+
if (priority && priority !== 'all') tasks = tasks.filter(t => t.priority === priority);
|
|
352
|
+
if (search) tasks = tasks.filter(t => t.title.toLowerCase().includes(search.toLowerCase()));
|
|
353
|
+
if (sort === 'priority') tasks.sort((a, b) => { const o = {critical:0,high:1,medium:2,low:3}; return (o[a.priority]??9)-(o[b.priority]??9); });
|
|
354
|
+
if (sort === '-updated') tasks.sort((a, b) => b.updated - a.updated);
|
|
355
|
+
const page = parseInt(params.get('page')) || 1;
|
|
356
|
+
const pageSize = parseInt(params.get('pageSize') || params.get('limit')) || 50;
|
|
357
|
+
const total = tasks.length;
|
|
358
|
+
const paged = tasks.slice((page-1)*pageSize, page*pageSize);
|
|
359
|
+
return { data: paged, tasks: paged, total, totalPages: Math.max(1, Math.ceil(total/pageSize)), page, pageSize };
|
|
360
|
+
}
|
|
361
|
+
if (route === '/api/tasks/detail') {
|
|
362
|
+
const t = findTask(params.get('taskId'));
|
|
363
|
+
return { data: t || null };
|
|
364
|
+
}
|
|
365
|
+
if (route === '/api/tasks/create') {
|
|
366
|
+
const id = 've-' + Math.random().toString(36).slice(2, 8);
|
|
367
|
+
const t = { id, title: body?.title || 'New task', status: 'todo', priority: body?.priority || 'medium', assignee: null, branch: null, pr: null, created: Date.now(), updated: Date.now() };
|
|
368
|
+
STATE.tasks.unshift(t); addLog('success', 'kanban', `Task created: ${t.title}`);
|
|
369
|
+
return { ok: true, data: t };
|
|
370
|
+
}
|
|
371
|
+
if (route === '/api/tasks/update') {
|
|
372
|
+
const t = findTask(body?.taskId || body?.id);
|
|
373
|
+
if (t) { Object.assign(t, body, { updated: Date.now() }); addLog('info', 'kanban', `Task updated: ${t.title}`); }
|
|
374
|
+
return { ok: true, data: t || null };
|
|
375
|
+
}
|
|
376
|
+
if (route === '/api/tasks/edit') {
|
|
377
|
+
const t = findTask(body?.taskId || body?.id);
|
|
378
|
+
if (t) { if (body?.title) t.title = body.title; if (body?.priority) t.priority = body.priority; t.updated = Date.now(); }
|
|
379
|
+
return { ok: true, data: t || null };
|
|
380
|
+
}
|
|
381
|
+
if (route === '/api/tasks/start') {
|
|
382
|
+
const t = findTask(body?.taskId);
|
|
383
|
+
if (t) { t.status = 'inprogress'; t.updated = Date.now(); addLog('info', 'task-executor', `Task started: ${t.title}`); }
|
|
384
|
+
return { ok: true, data: t || null };
|
|
385
|
+
}
|
|
386
|
+
if (route === '/api/tasks/retry') {
|
|
387
|
+
const t = findTask(body?.taskId);
|
|
388
|
+
if (t) { t.status = 'todo'; t.updated = Date.now(); addLog('info', 'task-executor', `Task retried: ${t.title}`); }
|
|
389
|
+
return { ok: true, data: t || null };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ── Agents ──
|
|
393
|
+
if (route === '/api/agents')
|
|
394
|
+
return { data: STATE.agents };
|
|
395
|
+
if (route === '/api/executors')
|
|
396
|
+
return { executors: STATE.executors };
|
|
397
|
+
|
|
398
|
+
// ── Logs ──
|
|
399
|
+
if (route === '/api/logs') {
|
|
400
|
+
const lines = parseInt(params.get('lines')) || 50;
|
|
401
|
+
return { data: STATE.logs.slice(0, lines), logs: STATE.logs.slice(0, lines) };
|
|
402
|
+
}
|
|
403
|
+
if (route === '/api/agent-logs')
|
|
404
|
+
return { data: STATE.agentLogFiles };
|
|
405
|
+
if (route === '/api/agent-logs/tail') {
|
|
406
|
+
const file = params.get('file') || '';
|
|
407
|
+
const n = parseInt(params.get('lines')) || 100;
|
|
408
|
+
const lines = Array.from({ length: Math.min(n, 20) }, (_, i) => {
|
|
409
|
+
const ts = new Date(now - (20 - i) * 30000).toISOString();
|
|
410
|
+
return `[${ts}] [info] Demo log line ${i + 1} from ${file || 'agent'}`;
|
|
411
|
+
});
|
|
412
|
+
return { data: lines.join('\n') };
|
|
413
|
+
}
|
|
414
|
+
if (route === '/api/agent-context') {
|
|
415
|
+
const q = params.get('query') || '';
|
|
416
|
+
return { data: { query: q, branch: q, files: ['keeper.go', 'types.go', 'msg_server.go'], recentCommits: ['feat: initial implementation', 'fix: lint errors'], worktree: '/worktrees/' + q.slice(0, 6) } };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ── Health ──
|
|
420
|
+
if (route === '/api/health')
|
|
421
|
+
return STATE.health;
|
|
422
|
+
|
|
423
|
+
// ── Infrastructure ──
|
|
424
|
+
if (route === '/api/infra')
|
|
425
|
+
return { data: STATE.infra };
|
|
426
|
+
if (route === '/api/worktrees') {
|
|
427
|
+
if (route === '/api/worktrees') return { data: STATE.worktrees, stats: { total: STATE.worktrees.length, active: STATE.worktrees.filter(w => w.active).length } };
|
|
428
|
+
}
|
|
429
|
+
if (route === '/api/worktrees/prune') {
|
|
430
|
+
STATE.worktrees = STATE.worktrees.filter(w => w.active); addLog('info', 'worktree', 'Pruned inactive worktrees');
|
|
431
|
+
return { ok: true, pruned: 1 };
|
|
432
|
+
}
|
|
433
|
+
if (route === '/api/worktrees/release') {
|
|
434
|
+
const branch = body?.branch;
|
|
435
|
+
const wt = STATE.worktrees.find(w => w.branch === branch);
|
|
436
|
+
if (wt) wt.active = false;
|
|
437
|
+
return { ok: true };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ── Shared Workspaces ──
|
|
441
|
+
if (route === '/api/shared-workspaces')
|
|
442
|
+
return { data: STATE.sharedWorkspaces };
|
|
443
|
+
if (route === '/api/shared-workspaces/claim') {
|
|
444
|
+
const ws = { id: 'sw-' + Math.random().toString(36).slice(2, 5), name: body?.name || 'workspace', claimedBy: body?.agent || 'demo', claimedAt: Date.now(), expiresAt: Date.now() + 2*hr };
|
|
445
|
+
STATE.sharedWorkspaces.push(ws);
|
|
446
|
+
return { ok: true, data: ws };
|
|
447
|
+
}
|
|
448
|
+
if (route === '/api/shared-workspaces/renew') {
|
|
449
|
+
const ws = STATE.sharedWorkspaces.find(w => w.id === body?.id);
|
|
450
|
+
if (ws) ws.expiresAt = Date.now() + 2*hr;
|
|
451
|
+
return { ok: true };
|
|
452
|
+
}
|
|
453
|
+
if (route === '/api/shared-workspaces/release') {
|
|
454
|
+
STATE.sharedWorkspaces = STATE.sharedWorkspaces.filter(w => w.id !== body?.id);
|
|
455
|
+
return { ok: true };
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// ── Git ──
|
|
459
|
+
if (route === '/api/git/branches')
|
|
460
|
+
return { data: STATE.gitBranches };
|
|
461
|
+
if (route === '/api/git/diff')
|
|
462
|
+
return { data: STATE.gitDiff };
|
|
463
|
+
|
|
464
|
+
// ── Sessions ──
|
|
465
|
+
if (route === '/api/sessions' && method === 'GET') {
|
|
466
|
+
return { data: STATE.sessions.map(s => ({ id: s.id, name: s.name, agentType: s.agentType, status: s.status, taskId: s.taskId, created: s.created })) };
|
|
467
|
+
}
|
|
468
|
+
if (route.match(/^\/api\/sessions\/[^/]+\/diff$/)) {
|
|
469
|
+
return { data: STATE.gitDiff };
|
|
470
|
+
}
|
|
471
|
+
if (route.match(/^\/api\/sessions\/[^/]+\/message$/)) {
|
|
472
|
+
const sid = route.split('/')[3];
|
|
473
|
+
const ses = STATE.sessions.find(s => s.id === sid);
|
|
474
|
+
if (ses) ses.messages.push({ role: 'user', text: body?.text || body?.message || '', ts: Date.now() });
|
|
475
|
+
return { ok: true };
|
|
476
|
+
}
|
|
477
|
+
if (route.match(/^\/api\/sessions\/[^/]+\/resume$/)) {
|
|
478
|
+
return { ok: true };
|
|
479
|
+
}
|
|
480
|
+
if (route.match(/^\/api\/sessions\/[^/]+\/archive$/)) {
|
|
481
|
+
const sid = route.split('/')[3];
|
|
482
|
+
const ses = STATE.sessions.find(s => s.id === sid);
|
|
483
|
+
if (ses) ses.status = 'archived';
|
|
484
|
+
return { ok: true };
|
|
485
|
+
}
|
|
486
|
+
if (route === '/api/sessions/create') {
|
|
487
|
+
const s = { id: 'ses-' + Math.random().toString(36).slice(2, 5), name: body?.name || 'New session', agentType: body?.agentType || 'copilot', status: 'active', taskId: body?.taskId || null, created: Date.now(), messages: [] };
|
|
488
|
+
STATE.sessions.push(s);
|
|
489
|
+
return { ok: true, data: s };
|
|
490
|
+
}
|
|
491
|
+
if (route.match(/^\/api\/sessions\/[^/]+$/) && method === 'GET') {
|
|
492
|
+
const sid = route.split('/')[3];
|
|
493
|
+
return { data: STATE.sessions.find(s => s.id === sid) || null };
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// ── Settings & Config ──
|
|
497
|
+
if (route === '/api/settings')
|
|
498
|
+
return STATE.settings;
|
|
499
|
+
if (route === '/api/config/update') {
|
|
500
|
+
Object.assign(STATE.settings, body || {});
|
|
501
|
+
return { ok: true };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// ── Command ──
|
|
505
|
+
if (route === '/api/command') {
|
|
506
|
+
const cmd = body?.command || '';
|
|
507
|
+
addLog('info', 'command', `Executed: ${cmd}`);
|
|
508
|
+
return { ok: true, command: cmd, response: 'Command executed in demo mode: ' + cmd };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ── Presence ──
|
|
512
|
+
if (route === '/api/presence')
|
|
513
|
+
return { data: STATE.presence };
|
|
514
|
+
|
|
515
|
+
// ── Project ──
|
|
516
|
+
if (route === '/api/project' || route === '/api/project-summary')
|
|
517
|
+
return { data: STATE.project };
|
|
518
|
+
|
|
519
|
+
// ── Catch-all: return empty success for any unhandled /api/ route ──
|
|
520
|
+
console.debug('[demo] Unhandled API route:', method, path);
|
|
521
|
+
return { ok: true, data: null };
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/* ── Intercept fetch() — ALL /api/ calls handled internally ───── */
|
|
525
|
+
const _realFetch = window.fetch;
|
|
526
|
+
window.fetch = function(url, options) {
|
|
527
|
+
const path = typeof url === 'string' ? url : url?.url || '';
|
|
528
|
+
|
|
529
|
+
// Intercept ALL /api/ calls — nothing reaches the network
|
|
530
|
+
if (path.startsWith('/api/') || path.startsWith('/api?')) {
|
|
531
|
+
let body = null;
|
|
532
|
+
if (options?.body) {
|
|
533
|
+
try { body = JSON.parse(options.body); } catch {}
|
|
534
|
+
}
|
|
535
|
+
const method = (options?.method || 'GET').toUpperCase();
|
|
536
|
+
const data = handleApi(path, method, body);
|
|
537
|
+
return Promise.resolve(new Response(JSON.stringify(data), {
|
|
538
|
+
status: 200,
|
|
539
|
+
headers: { 'Content-Type': 'application/json' },
|
|
540
|
+
}));
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Pass through non-API requests (fonts, CDN, ES modules, etc.)
|
|
544
|
+
return _realFetch.apply(this, arguments);
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
/* ── Intercept WebSocket ───────────────────────────────────────── */
|
|
548
|
+
const _RealWS = window.WebSocket;
|
|
549
|
+
window.WebSocket = function(url, protocols) {
|
|
550
|
+
const fake = {
|
|
551
|
+
url: url,
|
|
552
|
+
readyState: 1, // OPEN
|
|
553
|
+
bufferedAmount: 0,
|
|
554
|
+
extensions: '',
|
|
555
|
+
protocol: '',
|
|
556
|
+
onopen: null,
|
|
557
|
+
onmessage: null,
|
|
558
|
+
onclose: null,
|
|
559
|
+
onerror: null,
|
|
560
|
+
send(data) { /* swallow */ },
|
|
561
|
+
close() {
|
|
562
|
+
this.readyState = 3;
|
|
563
|
+
if (this.onclose) this.onclose({ code: 1000, reason: 'Demo mode' });
|
|
564
|
+
},
|
|
565
|
+
addEventListener(type, handler) {
|
|
566
|
+
this['on' + type] = handler;
|
|
567
|
+
},
|
|
568
|
+
removeEventListener() {},
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
// Simulate open after microtask
|
|
572
|
+
setTimeout(() => {
|
|
573
|
+
fake.readyState = 1;
|
|
574
|
+
if (fake.onopen) fake.onopen({});
|
|
575
|
+
|
|
576
|
+
// Send pong responses
|
|
577
|
+
setInterval(() => {
|
|
578
|
+
if (fake.onmessage) {
|
|
579
|
+
fake.onmessage({ data: JSON.stringify({ type: 'pong', ts: Date.now() }) });
|
|
580
|
+
}
|
|
581
|
+
}, 5000);
|
|
582
|
+
|
|
583
|
+
// Simulate periodic task updates
|
|
584
|
+
let updateIdx = 0;
|
|
585
|
+
setInterval(() => {
|
|
586
|
+
if (!fake.onmessage) return;
|
|
587
|
+
const updates = [
|
|
588
|
+
{ type: 'task-update', task: { id: 've-ghi789', status: 'inprogress' } },
|
|
589
|
+
{ type: 'log', entry: { ts: Date.now(), level: 'info', source: 'monitor', msg: 'Health check passed — all systems nominal' } },
|
|
590
|
+
{ type: 'executor-update', executor: { name: 'copilot-claude', load: 55 + Math.floor(Math.random() * 25) } },
|
|
591
|
+
];
|
|
592
|
+
fake.onmessage({ data: JSON.stringify(updates[updateIdx % updates.length]) });
|
|
593
|
+
updateIdx++;
|
|
594
|
+
}, 8000);
|
|
595
|
+
}, 100);
|
|
596
|
+
|
|
597
|
+
return fake;
|
|
598
|
+
};
|
|
599
|
+
window.WebSocket.CONNECTING = 0;
|
|
600
|
+
window.WebSocket.OPEN = 1;
|
|
601
|
+
window.WebSocket.CLOSING = 2;
|
|
602
|
+
window.WebSocket.CLOSED = 3;
|
|
603
|
+
|
|
604
|
+
})();
|
|
605
|
+
</script>
|
|
606
|
+
</head>
|
|
607
|
+
<body>
|
|
608
|
+
<!-- Demo mode banner -->
|
|
609
|
+
<div class="demo-banner">⚡ OpenFleet Demo Mode — Seed Data</div>
|
|
610
|
+
|
|
611
|
+
<!-- App root — boot loader lives INSIDE #app so Preact's render() replaces it -->
|
|
612
|
+
<div id="app">
|
|
613
|
+
<div id="boot-loader" style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;font-family:'Inter',sans-serif;color:#9aa3b8;background:#0b0f14;overflow:hidden">
|
|
614
|
+
<style>
|
|
615
|
+
.boot-ring{position:absolute;inset:0;border-radius:50%;border:2px solid rgba(76,201,240,.15);animation:boot-spin 3s linear infinite}
|
|
616
|
+
.boot-ring-inner{position:absolute;inset:8px;border-radius:50%;border:2px solid transparent;border-top-color:#4cc9f0;border-right-color:rgba(76,201,240,.4);animation:boot-spin 1.5s linear infinite reverse}
|
|
617
|
+
.boot-dot{position:absolute;top:50%;left:50%;width:16px;height:16px;margin:-8px 0 0 -8px;border-radius:50%;background:linear-gradient(135deg,#4cc9f0,#60cc5d);box-shadow:0 0 20px rgba(76,201,240,.5);animation:boot-pulse 2s ease-in-out infinite}
|
|
618
|
+
@keyframes boot-spin{to{transform:rotate(360deg)}}
|
|
619
|
+
@keyframes boot-pulse{0%,100%{transform:scale(1);opacity:.8}50%{transform:scale(1.2);opacity:1}}
|
|
620
|
+
</style>
|
|
621
|
+
<div style="position:relative;width:60px;height:60px;margin-bottom:16px">
|
|
622
|
+
<div class="boot-ring"></div>
|
|
623
|
+
<div class="boot-ring-inner"></div>
|
|
624
|
+
<div class="boot-dot"></div>
|
|
625
|
+
</div>
|
|
626
|
+
<div style="font-size:13px;color:#64748b">Loading demo...</div>
|
|
627
|
+
</div>
|
|
628
|
+
</div>
|
|
629
|
+
|
|
630
|
+
<!-- Load the REAL app.js (it uses ES modules + importmap) -->
|
|
631
|
+
<script type="module" src="app.js"></script>
|
|
632
|
+
<script>
|
|
633
|
+
// Fallback: if ES modules fail to load within 12s, show an error
|
|
634
|
+
setTimeout(function() {
|
|
635
|
+
var el = document.getElementById('boot-loader');
|
|
636
|
+
if (el) el.innerHTML = '<div style="text-align:center;padding:40px;font-family:Inter,sans-serif;color:#94a3b8"><div style="font-size:18px;margin-bottom:8px">⚠️ Failed to load</div><div style="font-size:12px;color:#64748b">Check connection (CDN: esm.sh) or refresh</div></div>';
|
|
637
|
+
}, 12000);
|
|
638
|
+
</script>
|
|
639
|
+
</body>
|
|
640
|
+
</html>
|
|
@@ -104,7 +104,7 @@ export const SETTINGS_SCHEMA = [
|
|
|
104
104
|
// ── Kanban / Tasks ─────────────────────────────────────────
|
|
105
105
|
{ key: "KANBAN_BACKEND", label: "Kanban Backend", category: "kanban", type: "select", defaultVal: "internal", options: ["internal", "vk", "github", "jira"], description: "Task management backend. 'internal' uses built-in store, 'github' syncs with GitHub Issues/Projects." },
|
|
106
106
|
{ key: "KANBAN_SYNC_POLICY", label: "Sync Policy", category: "kanban", type: "select", defaultVal: "internal-primary", options: ["internal-primary", "bidirectional"], description: "How tasks sync between internal store and external backend." },
|
|
107
|
-
{ key: "OPENFLEET_TASK_LABEL", label: "Task Label", category: "kanban", type: "string", defaultVal: "openfleet", description: "GitHub label used to scope which issues are managed by
|
|
107
|
+
{ key: "OPENFLEET_TASK_LABEL", label: "Task Label", category: "kanban", type: "string", defaultVal: "openfleet", description: "GitHub label used to scope which issues are managed by OpenFleet." },
|
|
108
108
|
{ key: "OPENFLEET_ENFORCE_TASK_LABEL", label: "Enforce Task Label", category: "kanban", type: "boolean", defaultVal: true, description: "Only pick up issues that have the task label. Prevents processing unrelated issues." },
|
|
109
109
|
{ key: "STALE_TASK_AGE_HOURS", label: "Stale Task Age", category: "kanban", type: "number", defaultVal: 3, min: 1, max: 168, unit: "hours", description: "Hours before an in-progress task with no activity is considered stale and eligible for recovery." },
|
|
110
110
|
{ key: "TASK_PLANNER_MODE", label: "Task Planner Mode", category: "kanban", type: "select", defaultVal: "kanban", options: ["kanban", "codex-sdk", "disabled"], description: "How the autonomous task planner operates. 'disabled' turns off automatic task generation." },
|
package/ui/styles/variables.css
CHANGED