@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,689 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { resolve, isAbsolute } from "node:path";
3
+ import { homedir } from "node:os";
4
+
5
+ function toEnvSuffix(key) {
6
+ return String(key)
7
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
8
+ .replace(/[^A-Za-z0-9]+/g, "_")
9
+ .toUpperCase();
10
+ }
11
+
12
+ export const PROMPT_WORKSPACE_DIR = ".openfleet/agents";
13
+
14
+ const PROMPT_DEFS = [
15
+ {
16
+ key: "orchestrator",
17
+ filename: "orchestrator.md",
18
+ description: "Primary task execution prompt for autonomous task agents.",
19
+ },
20
+ {
21
+ key: "planner",
22
+ filename: "task-planner.md",
23
+ description: "Backlog planning prompt used by task planner runs.",
24
+ },
25
+ {
26
+ key: "monitorMonitor",
27
+ filename: "monitor-monitor.md",
28
+ description: "Long-running reliability monitor prompt used in devmode.",
29
+ },
30
+ {
31
+ key: "taskExecutor",
32
+ filename: "task-executor.md",
33
+ description: "Task execution prompt used for actual implementation runs.",
34
+ },
35
+ {
36
+ key: "taskExecutorRetry",
37
+ filename: "task-executor-retry.md",
38
+ description: "Recovery prompt after a failed task execution attempt.",
39
+ },
40
+ {
41
+ key: "taskExecutorContinueHasCommits",
42
+ filename: "task-executor-continue-has-commits.md",
43
+ description:
44
+ "Continue prompt when edits were committed but not fully finalized.",
45
+ },
46
+ {
47
+ key: "taskExecutorContinueHasEdits",
48
+ filename: "task-executor-continue-has-edits.md",
49
+ description: "Continue prompt when uncommitted edits exist.",
50
+ },
51
+ {
52
+ key: "taskExecutorContinueNoProgress",
53
+ filename: "task-executor-continue-no-progress.md",
54
+ description:
55
+ "Continue prompt when the task stalled without meaningful progress.",
56
+ },
57
+ {
58
+ key: "reviewer",
59
+ filename: "reviewer.md",
60
+ description: "Prompt used by automated review agent.",
61
+ },
62
+ {
63
+ key: "conflictResolver",
64
+ filename: "conflict-resolver.md",
65
+ description: "Prompt used for rebase conflict follow-up guidance.",
66
+ },
67
+ {
68
+ key: "sdkConflictResolver",
69
+ filename: "sdk-conflict-resolver.md",
70
+ description: "Prompt for SDK-driven merge conflict resolution sessions.",
71
+ },
72
+ {
73
+ key: "mergeStrategy",
74
+ filename: "merge-strategy.md",
75
+ description: "Prompt for merge strategy analysis and decisioning.",
76
+ },
77
+ {
78
+ key: "mergeStrategyFix",
79
+ filename: "merge-strategy-fix.md",
80
+ description:
81
+ "Prompt used when merge strategy decides to send a fix message.",
82
+ },
83
+ {
84
+ key: "mergeStrategyReAttempt",
85
+ filename: "merge-strategy-reattempt.md",
86
+ description:
87
+ "Prompt used when merge strategy decides to re-attempt the task.",
88
+ },
89
+ {
90
+ key: "autofixFix",
91
+ filename: "autofix-fix.md",
92
+ description:
93
+ "Prompt used by crash autofix when structured error data is available.",
94
+ },
95
+ {
96
+ key: "autofixFallback",
97
+ filename: "autofix-fallback.md",
98
+ description:
99
+ "Prompt used by crash autofix when only log-tail context is available.",
100
+ },
101
+ {
102
+ key: "autofixLoop",
103
+ filename: "autofix-loop.md",
104
+ description: "Prompt used by repeating-error loop fixer.",
105
+ },
106
+ {
107
+ key: "monitorCrashFix",
108
+ filename: "monitor-crash-fix.md",
109
+ description: "Prompt used when monitor process crashes unexpectedly.",
110
+ },
111
+ {
112
+ key: "monitorRestartLoopFix",
113
+ filename: "monitor-restart-loop-fix.md",
114
+ description: "Prompt used when monitor/orchestrator enters restart loops.",
115
+ },
116
+ ];
117
+
118
+ export const AGENT_PROMPT_DEFINITIONS = Object.freeze(
119
+ PROMPT_DEFS.map((item) =>
120
+ Object.freeze({
121
+ ...item,
122
+ envVar: `CODEX_MONITOR_PROMPT_${toEnvSuffix(item.key)}`,
123
+ defaultRelativePath: `${PROMPT_WORKSPACE_DIR}/${item.filename}`,
124
+ }),
125
+ ),
126
+ );
127
+
128
+ const DEFAULT_PROMPTS = {
129
+ orchestrator: `# Task Orchestrator Agent
130
+
131
+ You are an autonomous task orchestrator agent. You receive implementation tasks and execute them end-to-end.
132
+
133
+ ## Prime Directives
134
+
135
+ 1. Never ask for human input for normal engineering decisions.
136
+ 2. Complete the assigned scope fully before stopping.
137
+ 3. Keep changes minimal, correct, and production-safe.
138
+ 4. Run relevant verification (tests/lint/build) before finalizing.
139
+ 5. Use conventional commit messages.
140
+
141
+ ## Completion Criteria
142
+
143
+ - Implementation matches requested behavior.
144
+ - Existing functionality is preserved.
145
+ - Relevant checks pass.
146
+ - Branch is pushed and ready for PR/review flow.
147
+ `,
148
+ planner: `# Codex-Task-Planner Agent
149
+
150
+ You generate production-grade backlog tasks for autonomous executors.
151
+
152
+ ## Mission
153
+
154
+ 1. Analyze current repo and delivery state.
155
+ 2. Identify highest-value next work.
156
+ 3. Create concrete, execution-ready tasks.
157
+
158
+ ## Requirements
159
+
160
+ - Avoid vague tasks and duplicate work.
161
+ - Balance reliability fixes, feature delivery, and debt reduction.
162
+ - Every task includes implementation steps, acceptance criteria, and verification plan.
163
+ - Every task title starts with one size label: [xs], [s], [m], [l], [xl], [xxl].
164
+ - Prefer task sets that can run in parallel with low file overlap.
165
+ - Do not call any kanban API, CLI, or external service to create tasks.
166
+ - Output must be machine-parseable JSON in a fenced json block.
167
+
168
+ ## Output Contract (Mandatory)
169
+
170
+ Return exactly one fenced json block with this shape:
171
+
172
+ \`\`\`json
173
+ {
174
+ "tasks": [
175
+ {
176
+ "title": "[m] Example task title",
177
+ "description": "Problem statement and scope",
178
+ "implementation_steps": ["step 1", "step 2"],
179
+ "acceptance_criteria": ["criterion 1", "criterion 2"],
180
+ "verification": ["test/check 1", "test/check 2"]
181
+ }
182
+ ]
183
+ }
184
+ \`\`\`
185
+
186
+ Rules:
187
+ - Provide at least the requested task count unless blocked by duplicate safeguards.
188
+ - Keep titles unique and specific.
189
+ - Keep file overlap low across tasks to maximize parallel execution.
190
+ `,
191
+ monitorMonitor: `# OpenFleet-Monitor Agent
192
+
193
+ You are the always-on reliability guardian for openfleet in devmode.
194
+
195
+ ## Core Role
196
+
197
+ - Monitor logs, failures, and agent/orchestrator behavior continuously.
198
+ - Immediately fix reliability regressions and execution blockers.
199
+ - Improve prompt/tool/executor reliability to reduce failure loops.
200
+ - Only when runtime is healthy, perform code-analysis improvements.
201
+
202
+ ## Constraints
203
+
204
+ - Operate only in devmode.
205
+ - Do not commit/push/open PRs in this context.
206
+ - Apply focused fixes, run focused validation, and keep monitoring.
207
+ `,
208
+ taskExecutor: `# {{TASK_ID}} — {{TASK_TITLE}}
209
+
210
+ ## Description
211
+ {{TASK_DESCRIPTION}}
212
+
213
+ ## Environment
214
+ - Working Directory: {{WORKTREE_PATH}}
215
+ - Branch: {{BRANCH}}
216
+ - Repository: {{REPO_SLUG}}
217
+
218
+ ## Instructions
219
+ 1. Read task requirements carefully.
220
+ 2. Implement required code changes.
221
+ 3. Run relevant tests/lint/build checks.
222
+ 4. Commit with conventional commit format.
223
+ 5. Push branch updates.
224
+
225
+ ## Critical Rules
226
+ - Do not ask for manual confirmation.
227
+ - No placeholders/stubs/TODO-only output.
228
+ - Keep behavior stable and production-safe.
229
+
230
+ ## Agent Status Endpoint
231
+ - URL: http://127.0.0.1:{{ENDPOINT_PORT}}/api/tasks/{{TASK_ID}}
232
+ - POST /status {"status":"inreview"} after PR-ready push
233
+ - POST /heartbeat {} while running
234
+ - POST /error {"error":"..."} on fatal failure
235
+ - POST /complete {"hasCommits":true} when done
236
+
237
+ ## Task Reference
238
+ {{TASK_URL_LINE}}
239
+
240
+ ## Repository Context
241
+ {{REPO_CONTEXT}}
242
+ `,
243
+ taskExecutorRetry: `# {{TASK_ID}} — ERROR RECOVERY (Attempt {{ATTEMPT_NUMBER}})
244
+
245
+ Your previous attempt on task "{{TASK_TITLE}}" encountered an issue:
246
+
247
+ \`\`\`
248
+ {{LAST_ERROR}}
249
+ \`\`\`
250
+
251
+ Error classification: {{CLASSIFICATION_PATTERN}} (confidence: {{CLASSIFICATION_CONFIDENCE}})
252
+
253
+ Please:
254
+ 1. Diagnose the failure root cause.
255
+ 2. Fix the issue with minimal safe changes.
256
+ 3. Re-run verification checks.
257
+ 4. Commit and push the fix.
258
+
259
+ Original task description:
260
+ {{TASK_DESCRIPTION}}
261
+ `,
262
+ taskExecutorContinueHasCommits: `# {{TASK_ID}} — CONTINUE (Verify and Push)
263
+
264
+ You were working on "{{TASK_TITLE}}" and appear to have stopped.
265
+ You already made commits.
266
+
267
+ 1. Run tests to verify changes.
268
+ 2. If passing, push: git push origin HEAD
269
+ 3. If failing, fix issues, commit, and push.
270
+ 4. Task is not complete until push succeeds.
271
+ `,
272
+ taskExecutorContinueHasEdits: `# {{TASK_ID}} — CONTINUE (Commit and Push)
273
+
274
+ You were working on "{{TASK_TITLE}}" and appear to have stopped.
275
+ You made file edits but no commit yet.
276
+
277
+ 1. Review edits for correctness.
278
+ 2. Run relevant tests.
279
+ 3. Commit with conventional format.
280
+ 4. Push: git push origin HEAD
281
+ `,
282
+ taskExecutorContinueNoProgress: `# CONTINUE - Resume Implementation
283
+
284
+ You were working on "{{TASK_TITLE}}" but stopped without meaningful progress.
285
+
286
+ Execute now:
287
+ 1. Read relevant source files.
288
+ 2. Implement required changes.
289
+ 3. Run verification checks.
290
+ 4. Commit with conventional format.
291
+ 5. Push to current branch.
292
+
293
+ Task: {{TASK_TITLE}}
294
+ Description: {{TASK_DESCRIPTION}}
295
+ `,
296
+ reviewer: `You are a senior code reviewer for a production software project.
297
+
298
+ Review the following PR diff for CRITICAL issues ONLY.
299
+
300
+ ## What to flag
301
+ 1. Security vulnerabilities
302
+ 2. Bugs / correctness regressions
303
+ 3. Missing implementations
304
+ 4. Broken functionality
305
+
306
+ ## What to ignore
307
+ - Style-only concerns
308
+ - Naming-only concerns
309
+ - Minor refactor ideas
310
+ - Non-critical perf suggestions
311
+ - Documentation-only gaps
312
+
313
+ ## PR Diff
314
+ \`\`\`diff
315
+ {{DIFF}}
316
+ \`\`\`
317
+
318
+ ## Task Description
319
+ {{TASK_DESCRIPTION}}
320
+
321
+ ## Response Format
322
+ Respond with JSON only:
323
+ {
324
+ "verdict": "approved" | "changes_requested",
325
+ "issues": [
326
+ {
327
+ "severity": "critical" | "major",
328
+ "category": "security" | "bug" | "missing_impl" | "broken",
329
+ "file": "path/to/file",
330
+ "line": 123,
331
+ "description": "..."
332
+ }
333
+ ],
334
+ "summary": "One sentence overall assessment"
335
+ }
336
+ `,
337
+ conflictResolver: `Conflicts detected while rebasing onto {{UPSTREAM_BRANCH}}.
338
+ Auto-resolve summary: {{AUTO_RESOLVE_SUMMARY}}.
339
+
340
+ {{MANUAL_CONFLICTS_SECTION}}
341
+
342
+ Use 'git checkout --theirs <file>' for lockfiles and 'git checkout --ours <file>' for CHANGELOG.md/coverage.txt/results.txt.
343
+ `,
344
+ sdkConflictResolver: `# Merge Conflict Resolution
345
+
346
+ You are resolving merge conflicts in a git worktree.
347
+
348
+ ## Context
349
+ - Working directory: {{WORKTREE_PATH}}
350
+ - PR branch (HEAD): {{BRANCH}}
351
+ - Base branch (incoming): origin/{{BASE_BRANCH}}
352
+ {{PR_LINE}}
353
+ {{TASK_TITLE_LINE}}
354
+ {{TASK_DESCRIPTION_LINE}}
355
+
356
+ ## Merge State
357
+ A merge is already in progress. Do not start a new merge or rebase.
358
+
359
+ {{AUTO_FILES_SECTION}}
360
+
361
+ {{MANUAL_FILES_SECTION}}
362
+
363
+ ## After Resolving All Files
364
+ 1. Ensure no conflict markers remain.
365
+ 2. Commit merge result.
366
+ 3. Push: git push origin HEAD:{{BRANCH}}
367
+
368
+ ## Critical Rules
369
+ - Do not abort merge.
370
+ - Do not run merge again.
371
+ - Do not use rebase for this recovery.
372
+ - Preserve behavior from both sides where possible.
373
+ `,
374
+ mergeStrategy: `# Merge Strategy Decision
375
+
376
+ You are a senior engineering reviewer. An AI agent has completed (or attempted) a task.
377
+ Review the context and decide the next action.
378
+
379
+ {{TASK_CONTEXT_BLOCK}}
380
+ {{AGENT_LAST_MESSAGE_BLOCK}}
381
+ {{PULL_REQUEST_BLOCK}}
382
+ {{CHANGES_BLOCK}}
383
+ {{CHANGED_FILES_BLOCK}}
384
+ {{DIFF_STATS_BLOCK}}
385
+ {{WORKTREE_BLOCK}}
386
+
387
+ ## Decision Rules
388
+ Return exactly one action:
389
+ - merge_after_ci_pass
390
+ - prompt
391
+ - close_pr
392
+ - re_attempt
393
+ - manual_review
394
+ - wait
395
+ - noop
396
+
397
+ Respond with JSON only.
398
+ `,
399
+ mergeStrategyFix: `# Fix Required
400
+
401
+ {{TASK_CONTEXT_BLOCK}}
402
+
403
+ ## Fix Instruction
404
+ {{FIX_MESSAGE}}
405
+
406
+ {{CI_STATUS_LINE}}
407
+
408
+ After fixing:
409
+ 1. Run relevant checks.
410
+ 2. Commit with clear message.
411
+ 3. Push updates.
412
+ `,
413
+ mergeStrategyReAttempt: `# Task Re-Attempt
414
+
415
+ A previous attempt failed.
416
+
417
+ {{TASK_CONTEXT_BLOCK}}
418
+
419
+ Failure reason: {{FAILURE_REASON}}
420
+
421
+ Start fresh, complete task, verify, commit, and push.
422
+ `,
423
+ autofixFix: `You are a PowerShell expert fixing a crash in a running orchestrator script.
424
+
425
+ ## Error
426
+ Type: {{ERROR_TYPE}}
427
+ File: {{ERROR_FILE}}
428
+ Line: {{ERROR_LINE}}
429
+ {{ERROR_COLUMN_LINE}}
430
+ Message: {{ERROR_MESSAGE}}
431
+ {{ERROR_CODE_LINE}}
432
+ Crash reason: {{CRASH_REASON}}
433
+
434
+ ## Source context around line {{ERROR_LINE}}
435
+ \`\`\`powershell
436
+ {{SOURCE_CONTEXT}}
437
+ \`\`\`
438
+ {{RECENT_MESSAGES_CONTEXT}}
439
+ ## Instructions
440
+ 1. Read file {{ERROR_FILE}}.
441
+ 2. Identify root cause.
442
+ 3. Apply minimal safe fix only.
443
+ 4. Preserve existing behavior.
444
+ 5. Write fix directly in file.
445
+ `,
446
+ autofixFallback: `You are a PowerShell expert analyzing an orchestrator crash.
447
+ No structured error was extracted. Termination reason: {{FALLBACK_REASON}}
448
+
449
+ ## Error indicators from log tail
450
+ {{FALLBACK_ERROR_LINES}}
451
+
452
+ ## Last {{FALLBACK_LINE_COUNT}} lines of crash log
453
+ \`\`\`
454
+ {{FALLBACK_TAIL}}
455
+ \`\`\`
456
+ {{RECENT_MESSAGES_CONTEXT}}
457
+ ## Instructions
458
+ 1. Analyze likely root cause.
459
+ 2. Main script: scripts/openfleet/ve-orchestrator.ps1
460
+ 3. If fixable bug exists, apply minimal safe fix.
461
+ 4. If crash is external only (OOM/SIGKILL), do not modify code.
462
+ `,
463
+ autofixLoop: `You are a PowerShell expert fixing a loop bug in a running orchestrator script.
464
+
465
+ ## Problem
466
+ This error repeats {{REPEAT_COUNT}} times:
467
+ "{{ERROR_LINE}}"
468
+
469
+ {{RECENT_MESSAGES_CONTEXT}}
470
+
471
+ ## Instructions
472
+ 1. Main script: scripts/openfleet/ve-orchestrator.ps1
473
+ 2. Find where this error is emitted.
474
+ 3. Fix loop root cause (missing state change, missing stop condition, etc).
475
+ 4. Apply minimal safe fix only.
476
+ 5. Write fix directly in file.
477
+ `,
478
+ monitorCrashFix: `You are debugging {{PROJECT_NAME}} openfleet.
479
+
480
+ The monitor process hit an unexpected exception and needs a fix.
481
+ Inspect and fix code in openfleet modules.
482
+
483
+ Crash info:
484
+ {{CRASH_INFO}}
485
+
486
+ Recent log context:
487
+ {{LOG_TAIL}}
488
+
489
+ Instructions:
490
+ 1. Identify root cause.
491
+ 2. Apply minimal production-safe fix.
492
+ 3. Do not refactor unrelated code.
493
+ `,
494
+ monitorRestartLoopFix: `You are a reliability engineer debugging a crash loop in {{PROJECT_NAME}} automation.
495
+
496
+ The orchestrator is restarting repeatedly within minutes.
497
+ Diagnose likely root cause and apply a minimal fix.
498
+
499
+ Targets (edit only if needed):
500
+ - {{SCRIPT_PATH}}
501
+ - openfleet/monitor.mjs
502
+ - openfleet/autofix.mjs
503
+ - openfleet/maintenance.mjs
504
+
505
+ Recent log excerpt:
506
+ {{LOG_TAIL}}
507
+
508
+ Constraints:
509
+ 1. Prevent rapid restart loops.
510
+ 2. Keep behavior stable and production-safe.
511
+ 3. Avoid unrelated refactors.
512
+ 4. Prefer small guardrails.
513
+ `,
514
+ };
515
+
516
+ function normalizeTemplateValue(value) {
517
+ if (value == null) return "";
518
+ if (typeof value === "string") return value;
519
+ if (typeof value === "number" || typeof value === "boolean") {
520
+ return String(value);
521
+ }
522
+ try {
523
+ return JSON.stringify(value, null, 2);
524
+ } catch {
525
+ return String(value);
526
+ }
527
+ }
528
+
529
+ function asPathCandidates(pathValue, configDir, repoRoot) {
530
+ if (!pathValue || typeof pathValue !== "string") return [];
531
+ const raw = pathValue.trim();
532
+ if (!raw) return [];
533
+ if (raw.startsWith("~")) {
534
+ const home = process.env.HOME || process.env.USERPROFILE || "";
535
+ return [resolve(home, raw.slice(1))];
536
+ }
537
+ if (isAbsolute(raw)) return [resolve(raw)];
538
+
539
+ const candidates = [];
540
+ if (repoRoot) candidates.push(resolve(repoRoot, raw));
541
+ if (configDir) candidates.push(resolve(configDir, raw));
542
+ candidates.push(resolve(process.cwd(), raw));
543
+
544
+ return candidates.filter((p, idx, arr) => p && arr.indexOf(p) === idx);
545
+ }
546
+
547
+ function readTemplateFile(candidates) {
548
+ for (const filePath of candidates) {
549
+ if (!existsSync(filePath)) continue;
550
+ try {
551
+ return { content: readFileSync(filePath, "utf8"), path: filePath };
552
+ } catch {
553
+ // Continue to next candidate.
554
+ }
555
+ }
556
+ return null;
557
+ }
558
+
559
+ export function getAgentPromptDefinitions() {
560
+ return AGENT_PROMPT_DEFINITIONS;
561
+ }
562
+
563
+ export function getDefaultPromptWorkspace(repoRoot) {
564
+ const override = String(
565
+ process.env.CODEX_MONITOR_PROMPT_WORKSPACE || "",
566
+ ).trim();
567
+ if (override) {
568
+ return isAbsolute(override)
569
+ ? override
570
+ : resolve(repoRoot || process.cwd(), override);
571
+ }
572
+ return resolve(repoRoot || process.cwd(), PROMPT_WORKSPACE_DIR);
573
+ }
574
+
575
+ export function getDefaultPromptTemplate(key) {
576
+ return DEFAULT_PROMPTS[key] || "";
577
+ }
578
+
579
+ export function renderPromptTemplate(template, values = {}) {
580
+ if (typeof template !== "string") return "";
581
+ const normalized = {};
582
+ for (const [k, v] of Object.entries(values || {})) {
583
+ normalized[String(k).trim().toUpperCase()] = normalizeTemplateValue(v);
584
+ }
585
+
586
+ return template.replace(/\{\{\s*([A-Za-z0-9_]+)\s*\}\}/g, (full, key) => {
587
+ const hit = normalized[String(key).toUpperCase()];
588
+ return hit == null ? "" : hit;
589
+ });
590
+ }
591
+
592
+ export function resolvePromptTemplate(template, values, fallback) {
593
+ const base = typeof fallback === "string" ? fallback : "";
594
+ if (typeof template !== "string" || !template.trim()) return base;
595
+ const rendered = renderPromptTemplate(template, {
596
+ ...(values || {}),
597
+ DEFAULT_PROMPT: base,
598
+ });
599
+ return rendered && rendered.trim() ? rendered : base;
600
+ }
601
+
602
+ export function ensureAgentPromptWorkspace(repoRoot) {
603
+ const root = resolve(repoRoot || process.cwd());
604
+ let workspaceDir = getDefaultPromptWorkspace(root);
605
+
606
+ try {
607
+ mkdirSync(workspaceDir, { recursive: true });
608
+ } catch (err) {
609
+ const fallbackRoot = resolve(
610
+ process.env.CODEX_MONITOR_HOME ||
611
+ process.env.HOME ||
612
+ process.env.USERPROFILE ||
613
+ homedir(),
614
+ );
615
+ const fallbackDir = resolve(fallbackRoot, PROMPT_WORKSPACE_DIR);
616
+ process.env.CODEX_MONITOR_PROMPT_WORKSPACE = fallbackDir;
617
+ workspaceDir = fallbackDir;
618
+ mkdirSync(workspaceDir, { recursive: true });
619
+ console.warn(
620
+ `[agent-prompts] prompt workspace fallback enabled: ${workspaceDir} (primary path failed: ${err?.code || err?.message || err})`,
621
+ );
622
+ }
623
+
624
+ const written = [];
625
+ for (const def of AGENT_PROMPT_DEFINITIONS) {
626
+ const filePath = resolve(workspaceDir, def.filename);
627
+ if (existsSync(filePath)) continue;
628
+
629
+ const body = [
630
+ `<!-- openfleet prompt: ${def.key} -->`,
631
+ `<!-- ${def.description} -->`,
632
+ "",
633
+ DEFAULT_PROMPTS[def.key] || "",
634
+ "",
635
+ ].join("\n");
636
+
637
+ writeFileSync(filePath, body, "utf8");
638
+ written.push(filePath);
639
+ }
640
+
641
+ return {
642
+ workspaceDir,
643
+ written,
644
+ };
645
+ }
646
+
647
+ export function resolveAgentPrompts(configDir, repoRoot, configData = {}) {
648
+ const workspaceDir = getDefaultPromptWorkspace(repoRoot);
649
+ const configured =
650
+ configData && typeof configData.agentPrompts === "object"
651
+ ? configData.agentPrompts
652
+ : {};
653
+
654
+ const prompts = {};
655
+ const sources = {};
656
+
657
+ for (const def of AGENT_PROMPT_DEFINITIONS) {
658
+ const fallback = DEFAULT_PROMPTS[def.key] || "";
659
+ const envPath = process.env[def.envVar];
660
+ const configuredPath = configured?.[def.key];
661
+
662
+ const candidates = [
663
+ ...asPathCandidates(envPath, configDir, repoRoot),
664
+ ...asPathCandidates(configuredPath, configDir, repoRoot),
665
+ resolve(workspaceDir, def.filename),
666
+ ];
667
+
668
+ const loaded = readTemplateFile(candidates);
669
+ prompts[def.key] = loaded?.content || fallback;
670
+ sources[def.key] = {
671
+ source: loaded
672
+ ? envPath
673
+ ? "env"
674
+ : configuredPath
675
+ ? "config"
676
+ : "workspace"
677
+ : "builtin",
678
+ path: loaded?.path || null,
679
+ envVar: def.envVar,
680
+ filename: def.filename,
681
+ };
682
+ }
683
+
684
+ return {
685
+ prompts,
686
+ sources,
687
+ workspaceDir,
688
+ };
689
+ }