@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,56 @@
1
+ export function createDaemonCrashTracker(options = {}) {
2
+ const instantCrashWindowMs = Math.max(
3
+ 1000,
4
+ Number(options.instantCrashWindowMs || 15_000) || 15_000,
5
+ );
6
+ const maxInstantCrashes = Math.max(
7
+ 1,
8
+ Number(options.maxInstantCrashes || 3) || 3,
9
+ );
10
+
11
+ let lastStartAtMs = 0;
12
+ let instantCrashCount = 0;
13
+
14
+ return {
15
+ markStart(nowMs = Date.now()) {
16
+ lastStartAtMs = Number(nowMs) || Date.now();
17
+ },
18
+
19
+ recordExit(nowMs = Date.now()) {
20
+ const now = Number(nowMs) || Date.now();
21
+ const runDurationMs =
22
+ lastStartAtMs > 0 ? Math.max(0, now - lastStartAtMs) : 0;
23
+ const instantCrash =
24
+ lastStartAtMs > 0 && runDurationMs <= instantCrashWindowMs;
25
+
26
+ if (instantCrash) {
27
+ instantCrashCount += 1;
28
+ } else {
29
+ instantCrashCount = 0;
30
+ }
31
+
32
+ return {
33
+ runDurationMs,
34
+ instantCrash,
35
+ instantCrashCount,
36
+ exceeded: instantCrashCount >= maxInstantCrashes,
37
+ instantCrashWindowMs,
38
+ maxInstantCrashes,
39
+ };
40
+ },
41
+
42
+ reset() {
43
+ lastStartAtMs = 0;
44
+ instantCrashCount = 0;
45
+ },
46
+
47
+ getState() {
48
+ return {
49
+ lastStartAtMs,
50
+ instantCrashCount,
51
+ instantCrashWindowMs,
52
+ maxInstantCrashes,
53
+ };
54
+ },
55
+ };
56
+ }
package/diff-stats.mjs ADDED
@@ -0,0 +1,282 @@
1
+ /**
2
+ * diff-stats.mjs — Collects git diff statistics for review handoff.
3
+ *
4
+ * Produces a tree of file changes with +/- line counts, like:
5
+ * monitor.mjs +890 -750
6
+ * task-executor.mjs +320 -180
7
+ * review-agent.mjs +680 -528
8
+ *
9
+ * @module diff-stats
10
+ */
11
+
12
+ import { spawnSync } from "node:child_process";
13
+
14
+ const TAG = "[diff-stats]";
15
+
16
+ /**
17
+ * @typedef {Object} FileChangeStats
18
+ * @property {string} file - Relative file path
19
+ * @property {number} additions - Lines added
20
+ * @property {number} deletions - Lines deleted
21
+ * @property {boolean} binary - True if binary file
22
+ */
23
+
24
+ /**
25
+ * @typedef {Object} DiffStats
26
+ * @property {FileChangeStats[]} files
27
+ * @property {number} totalFiles
28
+ * @property {number} totalAdditions
29
+ * @property {number} totalDeletions
30
+ * @property {string} formatted - Human-readable summary string
31
+ */
32
+
33
+ // ── Main Collectors ─────────────────────────────────────────────────────────
34
+
35
+ /**
36
+ * Collect diff stats for a worktree (vs origin/main or the upstream branch).
37
+ *
38
+ * Tries three strategies in order:
39
+ * 1. `git diff --numstat origin/main...HEAD`
40
+ * 2. `git diff --numstat HEAD~10...HEAD` (last 10 commits)
41
+ * 3. `git diff --stat origin/main...HEAD` (parsed stat output)
42
+ *
43
+ * @param {string} worktreePath - Path to the git worktree
44
+ * @param {Object} [options]
45
+ * @param {string} [options.baseBranch="origin/main"] - Branch to diff against
46
+ * @param {number} [options.timeoutMs=30000]
47
+ * @returns {DiffStats}
48
+ */
49
+ export function collectDiffStats(worktreePath, options = {}) {
50
+ const {
51
+ baseBranch = "origin/main",
52
+ timeoutMs = 30_000,
53
+ } = options;
54
+
55
+ // Strategy 1: --numstat (most reliable)
56
+ const numstat = tryNumstat(worktreePath, `${baseBranch}...HEAD`, timeoutMs);
57
+ if (numstat) return buildResult(numstat);
58
+
59
+ // Strategy 2: last N commits
60
+ const recent = tryNumstat(worktreePath, "HEAD~10...HEAD", timeoutMs);
61
+ if (recent) return buildResult(recent);
62
+
63
+ // Strategy 3: --stat fallback
64
+ const stat = tryStat(worktreePath, `${baseBranch}...HEAD`, timeoutMs);
65
+ if (stat) return buildResult(stat);
66
+
67
+ // Strategy 4: staged + unstaged changes
68
+ const working = tryNumstat(worktreePath, "HEAD", timeoutMs);
69
+ if (working) return buildResult(working);
70
+
71
+ // Nothing worked
72
+ return {
73
+ files: [],
74
+ totalFiles: 0,
75
+ totalAdditions: 0,
76
+ totalDeletions: 0,
77
+ formatted: "(no diff stats available)",
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Get a compact string summary of diff stats.
83
+ *
84
+ * Example output:
85
+ * ```
86
+ * 5 files changed, +1250 -890
87
+ * review-agent.mjs +680 -528
88
+ * task-executor.mjs +320 -180
89
+ * monitor.mjs +150 -82
90
+ * session-tracker.mjs +370 -0
91
+ * diff-stats.mjs +380 -0
92
+ * ```
93
+ *
94
+ * @param {string} worktreePath
95
+ * @param {Object} [options]
96
+ * @returns {string}
97
+ */
98
+ export function getCompactDiffSummary(worktreePath, options = {}) {
99
+ const stats = collectDiffStats(worktreePath, options);
100
+ return stats.formatted;
101
+ }
102
+
103
+ /**
104
+ * Get the recent commits on the current branch (vs origin/main).
105
+ *
106
+ * @param {string} worktreePath
107
+ * @param {number} [maxCommits=10]
108
+ * @returns {string[]} - Array of one-line commit messages
109
+ */
110
+ export function getRecentCommits(worktreePath, maxCommits = 10) {
111
+ try {
112
+ // Try vs origin/main first
113
+ const result = spawnSync(
114
+ "git",
115
+ ["log", "--oneline", `--max-count=${maxCommits}`, "origin/main..HEAD"],
116
+ { cwd: worktreePath, encoding: "utf8", timeout: 10_000 },
117
+ );
118
+
119
+ if (result.status === 0 && (result.stdout || "").trim()) {
120
+ return result.stdout.trim().split("\n").filter(Boolean);
121
+ }
122
+
123
+ // Fallback: last N commits on current branch
124
+ const fallback = spawnSync(
125
+ "git",
126
+ ["log", "--oneline", `--max-count=${maxCommits}`],
127
+ { cwd: worktreePath, encoding: "utf8", timeout: 10_000 },
128
+ );
129
+
130
+ if (fallback.status === 0 && (fallback.stdout || "").trim()) {
131
+ return fallback.stdout.trim().split("\n").filter(Boolean);
132
+ }
133
+ } catch (err) {
134
+ console.warn(`${TAG} getRecentCommits error: ${err.message}`);
135
+ }
136
+
137
+ return [];
138
+ }
139
+
140
+ // ── Internal Strategies ─────────────────────────────────────────────────────
141
+
142
+ /**
143
+ * Try `git diff --numstat` and parse the output.
144
+ * @param {string} cwd
145
+ * @param {string} range - e.g., "origin/main...HEAD"
146
+ * @param {number} timeoutMs
147
+ * @returns {FileChangeStats[]|null}
148
+ */
149
+ function tryNumstat(cwd, range, timeoutMs) {
150
+ try {
151
+ const result = spawnSync(
152
+ "git",
153
+ ["diff", "--numstat", range],
154
+ { cwd, encoding: "utf8", timeout: timeoutMs, stdio: ["pipe", "pipe", "pipe"] },
155
+ );
156
+
157
+ if (result.status !== 0 || !(result.stdout || "").trim()) return null;
158
+
159
+ const files = [];
160
+ for (const line of result.stdout.trim().split("\n")) {
161
+ if (!line.trim()) continue;
162
+
163
+ const parts = line.split("\t");
164
+ if (parts.length < 3) continue;
165
+
166
+ const [addStr, delStr, ...fileParts] = parts;
167
+ const file = fileParts.join("\t"); // Handle filenames with tabs
168
+
169
+ if (addStr === "-" && delStr === "-") {
170
+ // Binary file
171
+ files.push({ file, additions: 0, deletions: 0, binary: true });
172
+ } else {
173
+ files.push({
174
+ file,
175
+ additions: parseInt(addStr, 10) || 0,
176
+ deletions: parseInt(delStr, 10) || 0,
177
+ binary: false,
178
+ });
179
+ }
180
+ }
181
+
182
+ return files.length > 0 ? files : null;
183
+ } catch {
184
+ return null;
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Try `git diff --stat` and parse the output.
190
+ * Fallback when --numstat fails.
191
+ *
192
+ * @param {string} cwd
193
+ * @param {string} range
194
+ * @param {number} timeoutMs
195
+ * @returns {FileChangeStats[]|null}
196
+ */
197
+ function tryStat(cwd, range, timeoutMs) {
198
+ try {
199
+ const result = spawnSync(
200
+ "git",
201
+ ["diff", "--stat", range],
202
+ { cwd, encoding: "utf8", timeout: timeoutMs, stdio: ["pipe", "pipe", "pipe"] },
203
+ );
204
+
205
+ if (result.status !== 0 || !(result.stdout || "").trim()) return null;
206
+
207
+ const files = [];
208
+ const lines = result.stdout.trim().split("\n");
209
+
210
+ // Last line is the summary — skip it
211
+ for (let i = 0; i < lines.length - 1; i++) {
212
+ const line = lines[i].trim();
213
+ if (!line) continue;
214
+
215
+ // Format: " filename | 123 +++---" or " filename | Bin 0 -> 1234 bytes"
216
+ const pipeIdx = line.lastIndexOf("|");
217
+ if (pipeIdx === -1) continue;
218
+
219
+ const file = line.slice(0, pipeIdx).trim();
220
+ const statsStr = line.slice(pipeIdx + 1).trim();
221
+
222
+ if (statsStr.startsWith("Bin")) {
223
+ files.push({ file, additions: 0, deletions: 0, binary: true });
224
+ } else {
225
+ // Count '+' and '-' chars
226
+ const additions = (statsStr.match(/\+/g) || []).length;
227
+ const deletions = (statsStr.match(/-/g) || []).length;
228
+ files.push({ file, additions, deletions, binary: false });
229
+ }
230
+ }
231
+
232
+ return files.length > 0 ? files : null;
233
+ } catch {
234
+ return null;
235
+ }
236
+ }
237
+
238
+ // ── Result Builder ──────────────────────────────────────────────────────────
239
+
240
+ /**
241
+ * Build a DiffStats result from parsed file changes.
242
+ * @param {FileChangeStats[]} files
243
+ * @returns {DiffStats}
244
+ */
245
+ function buildResult(files) {
246
+ let totalAdditions = 0;
247
+ let totalDeletions = 0;
248
+
249
+ for (const f of files) {
250
+ totalAdditions += f.additions;
251
+ totalDeletions += f.deletions;
252
+ }
253
+
254
+ // Sort by total changes (largest first)
255
+ const sorted = [...files].sort(
256
+ (a, b) => (b.additions + b.deletions) - (a.additions + a.deletions),
257
+ );
258
+
259
+ // Find max filename length for alignment
260
+ const maxNameLen = Math.max(...sorted.map((f) => f.file.length), 10);
261
+
262
+ const lines = sorted.map((f) => {
263
+ const name = f.file.padEnd(maxNameLen);
264
+ if (f.binary) {
265
+ return ` ${name} (binary)`;
266
+ }
267
+ const add = `+${f.additions}`.padStart(6);
268
+ const del = `-${f.deletions}`.padStart(6);
269
+ return ` ${name} ${add} ${del}`;
270
+ });
271
+
272
+ const header = `${files.length} file(s) changed, +${totalAdditions} -${totalDeletions}`;
273
+ const formatted = `${header}\n${lines.join("\n")}`;
274
+
275
+ return {
276
+ files: sorted,
277
+ totalFiles: files.length,
278
+ totalAdditions,
279
+ totalDeletions,
280
+ formatted,
281
+ };
282
+ }