vibeman 0.0.0 → 0.0.1

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 (220) hide show
  1. package/README.md +12 -0
  2. package/dist/index.js +116 -0
  3. package/dist/runtime/api/.tsbuildinfo +1 -0
  4. package/dist/runtime/api/agent/agent-service.d.ts +226 -0
  5. package/dist/runtime/api/agent/agent-service.js +901 -0
  6. package/dist/runtime/api/agent/ai-providers/claude-code-adapter.d.ts +61 -0
  7. package/dist/runtime/api/agent/ai-providers/claude-code-adapter.js +373 -0
  8. package/dist/runtime/api/agent/ai-providers/codex-cli-provider.d.ts +34 -0
  9. package/dist/runtime/api/agent/ai-providers/codex-cli-provider.js +281 -0
  10. package/dist/runtime/api/agent/ai-providers/index.d.ts +9 -0
  11. package/dist/runtime/api/agent/ai-providers/index.js +7 -0
  12. package/dist/runtime/api/agent/ai-providers/types.d.ts +180 -0
  13. package/dist/runtime/api/agent/ai-providers/types.js +5 -0
  14. package/dist/runtime/api/agent/codex-cli-provider.test.d.ts +1 -0
  15. package/dist/runtime/api/agent/codex-cli-provider.test.js +88 -0
  16. package/dist/runtime/api/agent/core-agent-service.d.ts +119 -0
  17. package/dist/runtime/api/agent/core-agent-service.js +267 -0
  18. package/dist/runtime/api/agent/parsers.d.ts +15 -0
  19. package/dist/runtime/api/agent/parsers.js +241 -0
  20. package/dist/runtime/api/agent/prompt-service.d.ts +17 -0
  21. package/dist/runtime/api/agent/prompt-service.js +340 -0
  22. package/dist/runtime/api/agent/routing-policy.d.ts +188 -0
  23. package/dist/runtime/api/agent/routing-policy.js +246 -0
  24. package/dist/runtime/api/api/router-helpers.d.ts +32 -0
  25. package/dist/runtime/api/api/router-helpers.js +31 -0
  26. package/dist/runtime/api/api/routers/ai.d.ts +188 -0
  27. package/dist/runtime/api/api/routers/ai.js +410 -0
  28. package/dist/runtime/api/api/routers/executions.d.ts +98 -0
  29. package/dist/runtime/api/api/routers/executions.js +103 -0
  30. package/dist/runtime/api/api/routers/git.d.ts +45 -0
  31. package/dist/runtime/api/api/routers/git.js +35 -0
  32. package/dist/runtime/api/api/routers/settings.d.ts +139 -0
  33. package/dist/runtime/api/api/routers/settings.js +113 -0
  34. package/dist/runtime/api/api/routers/tasks.d.ts +141 -0
  35. package/dist/runtime/api/api/routers/tasks.js +238 -0
  36. package/dist/runtime/api/api/routers/workflows.d.ts +268 -0
  37. package/dist/runtime/api/api/routers/workflows.js +308 -0
  38. package/dist/runtime/api/api/routers/worktrees.d.ts +102 -0
  39. package/dist/runtime/api/api/routers/worktrees.js +80 -0
  40. package/dist/runtime/api/api/trpc.d.ts +118 -0
  41. package/dist/runtime/api/api/trpc.js +34 -0
  42. package/dist/runtime/api/index.d.ts +9 -0
  43. package/dist/runtime/api/index.js +125 -0
  44. package/dist/runtime/api/lib/id-generator.d.ts +70 -0
  45. package/dist/runtime/api/lib/id-generator.js +123 -0
  46. package/dist/runtime/api/lib/image-paste-drop-extension.d.ts +26 -0
  47. package/dist/runtime/api/lib/image-paste-drop-extension.js +125 -0
  48. package/dist/runtime/api/lib/logger.d.ts +11 -0
  49. package/dist/runtime/api/lib/logger.js +188 -0
  50. package/dist/runtime/api/lib/markdown-utils.d.ts +8 -0
  51. package/dist/runtime/api/lib/markdown-utils.js +282 -0
  52. package/dist/runtime/api/lib/markdown-utils.test.d.ts +1 -0
  53. package/dist/runtime/api/lib/markdown-utils.test.js +348 -0
  54. package/dist/runtime/api/lib/server/agent-service-singleton.d.ts +6 -0
  55. package/dist/runtime/api/lib/server/agent-service-singleton.js +27 -0
  56. package/dist/runtime/api/lib/server/git-service-singleton.d.ts +6 -0
  57. package/dist/runtime/api/lib/server/git-service-singleton.js +47 -0
  58. package/dist/runtime/api/lib/server/project-root.d.ts +2 -0
  59. package/dist/runtime/api/lib/server/project-root.js +38 -0
  60. package/dist/runtime/api/lib/server/task-service-singleton.d.ts +7 -0
  61. package/dist/runtime/api/lib/server/task-service-singleton.js +58 -0
  62. package/dist/runtime/api/lib/server/vibing-orchestrator-singleton.d.ts +7 -0
  63. package/dist/runtime/api/lib/server/vibing-orchestrator-singleton.js +57 -0
  64. package/dist/runtime/api/lib/tiptap-utils.clamp-selection.test.d.ts +1 -0
  65. package/dist/runtime/api/lib/tiptap-utils.clamp-selection.test.js +27 -0
  66. package/dist/runtime/api/lib/tiptap-utils.d.ts +130 -0
  67. package/dist/runtime/api/lib/tiptap-utils.js +327 -0
  68. package/dist/runtime/api/lib/trpc/client.d.ts +1 -0
  69. package/dist/runtime/api/lib/trpc/client.js +5 -0
  70. package/dist/runtime/api/lib/trpc/server.d.ts +822 -0
  71. package/dist/runtime/api/lib/trpc/server.js +11 -0
  72. package/dist/runtime/api/lib/trpc/ws-server.d.ts +8 -0
  73. package/dist/runtime/api/lib/trpc/ws-server.js +33 -0
  74. package/dist/runtime/api/persistence/database-service.d.ts +14 -0
  75. package/dist/runtime/api/persistence/database-service.js +74 -0
  76. package/dist/runtime/api/persistence/execution-log-persistence.d.ts +90 -0
  77. package/dist/runtime/api/persistence/execution-log-persistence.js +410 -0
  78. package/dist/runtime/api/persistence/execution-log-persistence.test.d.ts +1 -0
  79. package/dist/runtime/api/persistence/execution-log-persistence.test.js +170 -0
  80. package/dist/runtime/api/router.d.ts +825 -0
  81. package/dist/runtime/api/router.js +56 -0
  82. package/dist/runtime/api/settings-service.d.ts +110 -0
  83. package/dist/runtime/api/settings-service.js +611 -0
  84. package/dist/runtime/api/tasks/file-watcher.d.ts +23 -0
  85. package/dist/runtime/api/tasks/file-watcher.js +88 -0
  86. package/dist/runtime/api/tasks/task-file-parser.d.ts +13 -0
  87. package/dist/runtime/api/tasks/task-file-parser.js +161 -0
  88. package/dist/runtime/api/tasks/task-service.d.ts +36 -0
  89. package/dist/runtime/api/tasks/task-service.js +173 -0
  90. package/dist/runtime/api/types/index.d.ts +179 -0
  91. package/dist/runtime/api/types/index.js +1 -0
  92. package/dist/runtime/api/types/settings.d.ts +81 -0
  93. package/dist/runtime/api/types/settings.js +2 -0
  94. package/dist/runtime/api/types.d.ts +2 -0
  95. package/dist/runtime/api/types.js +1 -0
  96. package/dist/runtime/api/utils/env.d.ts +6 -0
  97. package/dist/runtime/api/utils/env.js +12 -0
  98. package/dist/runtime/api/utils/stripNextEnv.d.ts +7 -0
  99. package/dist/runtime/api/utils/stripNextEnv.js +22 -0
  100. package/dist/runtime/api/utils/title-slug.d.ts +6 -0
  101. package/dist/runtime/api/utils/title-slug.js +77 -0
  102. package/dist/runtime/api/utils/url.d.ts +2 -0
  103. package/dist/runtime/api/utils/url.js +19 -0
  104. package/dist/runtime/api/vcs/git-history-service.d.ts +57 -0
  105. package/dist/runtime/api/vcs/git-history-service.js +228 -0
  106. package/dist/runtime/api/vcs/git-service.d.ts +127 -0
  107. package/dist/runtime/api/vcs/git-service.js +284 -0
  108. package/dist/runtime/api/vcs/worktree-service.d.ts +93 -0
  109. package/dist/runtime/api/vcs/worktree-service.js +506 -0
  110. package/dist/runtime/api/vcs/worktree-service.test.d.ts +1 -0
  111. package/dist/runtime/api/vcs/worktree-service.test.js +20 -0
  112. package/dist/runtime/api/workflows/quality-pipeline.d.ts +58 -0
  113. package/dist/runtime/api/workflows/quality-pipeline.js +400 -0
  114. package/dist/runtime/api/workflows/vibing-orchestrator.d.ts +313 -0
  115. package/dist/runtime/api/workflows/vibing-orchestrator.js +1861 -0
  116. package/dist/runtime/web/.next/BUILD_ID +1 -0
  117. package/dist/runtime/web/.next/app-build-manifest.json +59 -0
  118. package/dist/runtime/web/.next/app-path-routes-manifest.json +7 -0
  119. package/dist/runtime/web/.next/build-manifest.json +33 -0
  120. package/dist/runtime/web/.next/package.json +1 -0
  121. package/dist/runtime/web/.next/prerender-manifest.json +61 -0
  122. package/dist/runtime/web/.next/react-loadable-manifest.json +39 -0
  123. package/dist/runtime/web/.next/required-server-files.json +334 -0
  124. package/dist/runtime/web/.next/routes-manifest.json +62 -0
  125. package/dist/runtime/web/.next/server/app/_not-found/page.js +2 -0
  126. package/dist/runtime/web/.next/server/app/_not-found/page.js.nft.json +1 -0
  127. package/dist/runtime/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  128. package/dist/runtime/web/.next/server/app/_not-found.html +7 -0
  129. package/dist/runtime/web/.next/server/app/_not-found.meta +8 -0
  130. package/dist/runtime/web/.next/server/app/_not-found.rsc +22 -0
  131. package/dist/runtime/web/.next/server/app/api/health/route.js +1 -0
  132. package/dist/runtime/web/.next/server/app/api/health/route.js.nft.json +1 -0
  133. package/dist/runtime/web/.next/server/app/api/health/route_client-reference-manifest.js +1 -0
  134. package/dist/runtime/web/.next/server/app/api/images/[...path]/route.js +1 -0
  135. package/dist/runtime/web/.next/server/app/api/images/[...path]/route.js.nft.json +1 -0
  136. package/dist/runtime/web/.next/server/app/api/images/[...path]/route_client-reference-manifest.js +1 -0
  137. package/dist/runtime/web/.next/server/app/api/upload/route.js +1 -0
  138. package/dist/runtime/web/.next/server/app/api/upload/route.js.nft.json +1 -0
  139. package/dist/runtime/web/.next/server/app/api/upload/route_client-reference-manifest.js +1 -0
  140. package/dist/runtime/web/.next/server/app/index.html +7 -0
  141. package/dist/runtime/web/.next/server/app/index.meta +7 -0
  142. package/dist/runtime/web/.next/server/app/index.rsc +27 -0
  143. package/dist/runtime/web/.next/server/app/page.js +147 -0
  144. package/dist/runtime/web/.next/server/app/page.js.nft.json +1 -0
  145. package/dist/runtime/web/.next/server/app/page_client-reference-manifest.js +1 -0
  146. package/dist/runtime/web/.next/server/app-paths-manifest.json +7 -0
  147. package/dist/runtime/web/.next/server/chunks/217.js +1 -0
  148. package/dist/runtime/web/.next/server/chunks/383.js +6 -0
  149. package/dist/runtime/web/.next/server/chunks/458.js +1 -0
  150. package/dist/runtime/web/.next/server/chunks/576.js +18 -0
  151. package/dist/runtime/web/.next/server/chunks/635.js +22 -0
  152. package/dist/runtime/web/.next/server/chunks/761.js +1 -0
  153. package/dist/runtime/web/.next/server/chunks/777.js +3 -0
  154. package/dist/runtime/web/.next/server/chunks/825.js +1 -0
  155. package/dist/runtime/web/.next/server/chunks/838.js +1 -0
  156. package/dist/runtime/web/.next/server/chunks/973.js +15 -0
  157. package/dist/runtime/web/.next/server/functions-config-manifest.json +4 -0
  158. package/dist/runtime/web/.next/server/middleware-build-manifest.js +1 -0
  159. package/dist/runtime/web/.next/server/middleware-manifest.json +6 -0
  160. package/dist/runtime/web/.next/server/middleware-react-loadable-manifest.js +1 -0
  161. package/dist/runtime/web/.next/server/next-font-manifest.js +1 -0
  162. package/dist/runtime/web/.next/server/next-font-manifest.json +1 -0
  163. package/dist/runtime/web/.next/server/pages/404.html +7 -0
  164. package/dist/runtime/web/.next/server/pages/500.html +1 -0
  165. package/dist/runtime/web/.next/server/pages/_app.js +1 -0
  166. package/dist/runtime/web/.next/server/pages/_app.js.nft.json +1 -0
  167. package/dist/runtime/web/.next/server/pages/_document.js +1 -0
  168. package/dist/runtime/web/.next/server/pages/_document.js.nft.json +1 -0
  169. package/dist/runtime/web/.next/server/pages/_error.js +19 -0
  170. package/dist/runtime/web/.next/server/pages/_error.js.nft.json +1 -0
  171. package/dist/runtime/web/.next/server/pages-manifest.json +6 -0
  172. package/dist/runtime/web/.next/server/server-reference-manifest.js +1 -0
  173. package/dist/runtime/web/.next/server/server-reference-manifest.json +1 -0
  174. package/dist/runtime/web/.next/server/webpack-runtime.js +1 -0
  175. package/dist/runtime/web/.next/static/1HR8N0rJkCvFRtbTPJMyH/_buildManifest.js +1 -0
  176. package/dist/runtime/web/.next/static/1HR8N0rJkCvFRtbTPJMyH/_ssgManifest.js +1 -0
  177. package/dist/runtime/web/.next/static/chunks/18-15c10d3288afef2e.js +1 -0
  178. package/dist/runtime/web/.next/static/chunks/1c0ca389.537bbe362e3ffbd9.js +3 -0
  179. package/dist/runtime/web/.next/static/chunks/22747d63-ad5da0c19f4cfe41.js +71 -0
  180. package/dist/runtime/web/.next/static/chunks/277-0142a939f08738c3.js +63 -0
  181. package/dist/runtime/web/.next/static/chunks/355.056c2645878a799a.js +1 -0
  182. package/dist/runtime/web/.next/static/chunks/420.a5ccf151c9e2b2f1.js +1 -0
  183. package/dist/runtime/web/.next/static/chunks/439.1be0c6242fd248d5.js +15 -0
  184. package/dist/runtime/web/.next/static/chunks/440.c52e7c0f797e22b2.js +1 -0
  185. package/dist/runtime/web/.next/static/chunks/575-e2478287c27da87b.js +1 -0
  186. package/dist/runtime/web/.next/static/chunks/691.920d88c115087314.js +1 -0
  187. package/dist/runtime/web/.next/static/chunks/765-e838910065b50c3d.js +1 -0
  188. package/dist/runtime/web/.next/static/chunks/87c73c54-09e1ba5c70e60a51.js +1 -0
  189. package/dist/runtime/web/.next/static/chunks/891cff7f.0f71fc028f87e683.js +1 -0
  190. package/dist/runtime/web/.next/static/chunks/8bb4d8db-3e2aa02b0a2384b9.js +1 -0
  191. package/dist/runtime/web/.next/static/chunks/9af238c7-271a911d4e99ab18.js +1 -0
  192. package/dist/runtime/web/.next/static/chunks/app/_not-found/page-1cb74d1cba27d0ab.js +1 -0
  193. package/dist/runtime/web/.next/static/chunks/app/api/health/route-105a61ae865ba536.js +1 -0
  194. package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-105a61ae865ba536.js +1 -0
  195. package/dist/runtime/web/.next/static/chunks/app/api/upload/route-105a61ae865ba536.js +1 -0
  196. package/dist/runtime/web/.next/static/chunks/app/layout-dc0cfd29075b2160.js +1 -0
  197. package/dist/runtime/web/.next/static/chunks/app/page-f34a8b196b18850b.js +1 -0
  198. package/dist/runtime/web/.next/static/chunks/cac567b0-5b77dd12911823cd.js +1 -0
  199. package/dist/runtime/web/.next/static/chunks/framework-2518f1345b5b2806.js +1 -0
  200. package/dist/runtime/web/.next/static/chunks/main-17665e5e39de9a8a.js +1 -0
  201. package/dist/runtime/web/.next/static/chunks/main-app-c0b0f5ba4f7f9d75.js +1 -0
  202. package/dist/runtime/web/.next/static/chunks/pages/_app-d6f6b3bbc3d81ee1.js +1 -0
  203. package/dist/runtime/web/.next/static/chunks/pages/_error-75a96cf1997cc3b9.js +1 -0
  204. package/dist/runtime/web/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  205. package/dist/runtime/web/.next/static/chunks/webpack-c8de37305b4635cf.js +1 -0
  206. package/dist/runtime/web/.next/static/css/08c950681f1a9a92.css +1 -0
  207. package/dist/runtime/web/.next/static/css/2728291c68f99cb1.css +3 -0
  208. package/dist/runtime/web/.next/static/css/521bd69cc298cd1a.css +1 -0
  209. package/dist/runtime/web/.next/static/css/537e22821e101b87.css +1 -0
  210. package/dist/runtime/web/.next/static/media/19cfc7226ec3afaa-s.woff2 +0 -0
  211. package/dist/runtime/web/.next/static/media/21350d82a1f187e9-s.woff2 +0 -0
  212. package/dist/runtime/web/.next/static/media/8e9860b6e62d6359-s.woff2 +0 -0
  213. package/dist/runtime/web/.next/static/media/ba9851c3c22cd980-s.woff2 +0 -0
  214. package/dist/runtime/web/.next/static/media/c5fe6dc8356a8c31-s.woff2 +0 -0
  215. package/dist/runtime/web/.next/static/media/df0a9ae256c0569c-s.woff2 +0 -0
  216. package/dist/runtime/web/.next/static/media/e4af272ccee01ff0-s.p.woff2 +0 -0
  217. package/dist/runtime/web/package.json +65 -0
  218. package/dist/runtime/web/server.js +44 -0
  219. package/dist/tsconfig.tsbuildinfo +1 -0
  220. package/package.json +80 -7
@@ -0,0 +1,901 @@
1
+ import { EventEmitter } from 'events';
2
+ import { WorktreeService } from '../vcs/worktree-service.js';
3
+ import { ExecutionLogPersistence } from '../persistence/execution-log-persistence.js';
4
+ import { log } from '../lib/logger.js';
5
+ import { generateId } from '../lib/id-generator.js';
6
+ import { getProjectRoot } from '../lib/server/project-root.js';
7
+ import { CoreAgentService } from './core-agent-service.js';
8
+ import { ClaudeCodeAdapter, CodexCliProvider } from './ai-providers/index.js';
9
+ import { PromptService } from './prompt-service.js';
10
+ import { RoutingPolicyManager, } from './routing-policy.js';
11
+ import { isTestEnv } from '../utils/env.js';
12
+ import { parseImprovementResult, parseReviewResult } from './parsers.js';
13
+ import { getSettingsService } from '../settings-service.js';
14
+ const fullTools = [
15
+ 'Bash',
16
+ 'Edit',
17
+ 'Glob',
18
+ 'Grep',
19
+ 'MultiEdit',
20
+ 'NotebookEdit',
21
+ 'NotebookRead',
22
+ 'Read',
23
+ 'Task',
24
+ 'TodoWrite',
25
+ 'WebSearch',
26
+ 'WebFetch',
27
+ 'Write',
28
+ ];
29
+ export class AgentService extends EventEmitter {
30
+ constructor(taskService, projectRoot = getProjectRoot(), worktreeService) {
31
+ super();
32
+ this.taskService = taskService;
33
+ this.projectRoot = projectRoot;
34
+ // Keep only minimal execution context locally; defer status/messages to CoreAgentService
35
+ this.executionRegistry = new Map();
36
+ this.initialized = false;
37
+ // Track lightweight status for generic (non-core) executions
38
+ this.genericStates = new Map();
39
+ // Centralized default tool sets per operation
40
+ // TODO: need to be more granular, based on the task type and the toolset available for the AI provider
41
+ this.defaultTools = {
42
+ execute_task: fullTools,
43
+ improve_task: fullTools,
44
+ code_review: fullTools,
45
+ ai_merge: fullTools,
46
+ };
47
+ // Centralized system prompts for all AI operations
48
+ this.systemPrompts = {
49
+ execute_task: {
50
+ base: 'You are an expert software engineer. Follow best practices, write clean maintainable code, and ensure your implementation is robust and well-tested.',
51
+ rerun: 'You are continuing a rerun of this task. Use prior context and focus on fixes.',
52
+ },
53
+ improve_task: 'You are an expert at improving task specifications. Respond with ONLY a valid JSON object containing type, priority, and content fields. No additional text or formatting.',
54
+ ai_codereview: 'You are a senior developer performing thorough code review. Focus on quality, security, and best practices. Respond with ONLY a valid JSON object. Ultrathink.',
55
+ ai_merge: 'You are an expert at resolving merge conflicts and integrating code changes. Ensure clean merges that maintain code consistency and functionality.',
56
+ };
57
+ this.worktreeService = worktreeService || new WorktreeService(projectRoot);
58
+ this.logPersistence = new ExecutionLogPersistence();
59
+ this.initialize();
60
+ // Log streaming updates for verification in dev environments
61
+ if (!isTestEnv()) {
62
+ this.on('executionUpdated', (update) => {
63
+ const exec = this.getExecutionStatus(update.executionId);
64
+ const last = exec?.logs?.[exec.logs.length - 1];
65
+ if (last) {
66
+ log.info('Execution update', { executionId: update.executionId, message: last }, 'agent-service');
67
+ }
68
+ });
69
+ }
70
+ // Initialize worktree manager
71
+ this.initializeWorktrees();
72
+ }
73
+ // ============ Generic (non-AI) execution helpers for local tasks (e.g., validation) ============
74
+ /**
75
+ * Start a generic execution that is not backed by an AI provider.
76
+ * This enables persistent logs and status queries via existing APIs.
77
+ */
78
+ async startGenericExecution(taskId, options) {
79
+ const executionId = generateId('exec');
80
+ const startTime = new Date().toISOString();
81
+ const workingDirectory = options?.workingDirectory || this.projectRoot;
82
+ // Register minimal context so status queries work
83
+ this.executionRegistry.set(executionId, { taskId, workingDirectory, startTime });
84
+ const initial = {
85
+ id: executionId,
86
+ taskId,
87
+ status: 'pending',
88
+ startTime,
89
+ logs: [],
90
+ workingDirectory,
91
+ };
92
+ await this.logPersistence.startExecution(initial, options?.workflowId);
93
+ this.genericStates.set(executionId, { status: 'running' });
94
+ // Emit an update to notify subscribers
95
+ this.emit('executionUpdated', { executionId, status: 'running', logs: [] });
96
+ return executionId;
97
+ }
98
+ /** Append a log line to a generic execution and notify listeners (best-effort). */
99
+ async logGenericExecution(executionId, message, level = 'info', data) {
100
+ await this.logPersistence.logMessage(executionId, message, level, data);
101
+ try {
102
+ const logs = (await this.logPersistence.readExecutionLogs(executionId)).logs.map((e) => e.message);
103
+ this.emit('executionUpdated', { executionId, status: 'running', logs });
104
+ }
105
+ catch {
106
+ // ignore
107
+ }
108
+ }
109
+ /** Mark a generic execution as completed/failed/cancelled and notify listeners. */
110
+ async completeGenericExecution(executionId, status, error) {
111
+ await this.logPersistence.completeExecution(executionId, status, error);
112
+ this.genericStates.set(executionId, { status, endTime: new Date().toISOString(), error });
113
+ try {
114
+ const logs = (await this.logPersistence.readExecutionLogs(executionId)).logs.map((e) => e.message);
115
+ this.emit('executionUpdated', { executionId, status, logs, error });
116
+ }
117
+ catch {
118
+ this.emit('executionUpdated', { executionId, status, logs: [], error });
119
+ }
120
+ }
121
+ getAgentConfig() {
122
+ try {
123
+ const settings = getSettingsService().getSettings();
124
+ return {
125
+ executionTimeout: 30 * 60 * 1000, // 30 minutes fixed
126
+ defaultProvider: settings.agents.defaultProvider || 'claude-code',
127
+ };
128
+ }
129
+ catch {
130
+ return { executionTimeout: 30 * 60 * 1000, defaultProvider: 'claude-code' };
131
+ }
132
+ }
133
+ initialize() {
134
+ if (this.initialized)
135
+ return;
136
+ // Initialize core agent service with settings
137
+ const agentConfig = this.getAgentConfig();
138
+ this.coreAgentService = new CoreAgentService({
139
+ defaultProvider: agentConfig.defaultProvider,
140
+ executionTimeout: agentConfig.executionTimeout,
141
+ });
142
+ // Register Claude Code adapter
143
+ const claudeAdapter = new ClaudeCodeAdapter({
144
+ defaultWorkingDirectory: this.projectRoot,
145
+ defaultModel: 'claude-sonnet-4-20250514',
146
+ });
147
+ this.coreAgentService.registerProvider('claude-code', claudeAdapter);
148
+ // Register Codex CLI provider (not default)
149
+ const codexProvider = new CodexCliProvider({
150
+ defaultWorkingDirectory: this.projectRoot,
151
+ });
152
+ this.coreAgentService.registerProvider('codex', codexProvider);
153
+ // Initialize prompt service
154
+ this.promptService = new PromptService(this.projectRoot, this.taskService);
155
+ // Initialize routing policy manager
156
+ this.routingPolicyManager = new RoutingPolicyManager();
157
+ // Create example policy file if needed (guarded for mocked environments)
158
+ try {
159
+ const maybePromise = this.routingPolicyManager.createExamplePolicy?.();
160
+ if (maybePromise && typeof maybePromise.catch === 'function') {
161
+ maybePromise.catch((err) => log.warn('Failed to create example policy', err, 'agent-service'));
162
+ }
163
+ }
164
+ catch (err) {
165
+ log.warn('Failed to trigger example policy creation', err, 'agent-service');
166
+ }
167
+ // Set up event forwarding
168
+ this.setupEventForwarding();
169
+ this.initialized = true;
170
+ }
171
+ setupEventForwarding() {
172
+ // Persist every message and enrich realtime UI for non-assistant types
173
+ this.coreAgentService.on('executionMessage', (data) => {
174
+ const state = this.coreAgentService.getExecutionStatus(data.executionId);
175
+ const status = state?.status || 'running';
176
+ const summary = this.summarizeMessage(data.message);
177
+ void this.logPersistence.logMessage(data.executionId, summary, data.message.type === 'error' ? 'error' : 'info', { raw: data.message });
178
+ const logs = this.coreAgentService
179
+ .getExecutionMessages(data.executionId)
180
+ .map((m) => this.summarizeMessage(m));
181
+ this.emit('executionUpdated', {
182
+ executionId: data.executionId,
183
+ status,
184
+ logs,
185
+ });
186
+ });
187
+ this.coreAgentService.on('executionCompleted', (data) => {
188
+ void this.logPersistence.completeExecution(data.executionId, 'completed');
189
+ const logs = this.coreAgentService
190
+ .getExecutionMessages(data.executionId)
191
+ .map((m) => this.summarizeMessage(m));
192
+ this.emit('executionUpdated', {
193
+ executionId: data.executionId,
194
+ status: 'completed',
195
+ logs,
196
+ });
197
+ });
198
+ this.coreAgentService.on('executionFailed', (data) => {
199
+ void this.logPersistence.completeExecution(data.executionId, 'failed', data.error);
200
+ const logs = this.coreAgentService
201
+ .getExecutionMessages(data.executionId)
202
+ .map((m) => this.summarizeMessage(m));
203
+ this.emit('executionUpdated', {
204
+ executionId: data.executionId,
205
+ status: 'failed',
206
+ logs,
207
+ error: data.error,
208
+ });
209
+ });
210
+ this.coreAgentService.on('executionCancelled', (data) => {
211
+ void this.logPersistence.completeExecution(data.executionId, 'cancelled');
212
+ const logs = this.coreAgentService
213
+ .getExecutionMessages(data.executionId)
214
+ .map((m) => this.summarizeMessage(m));
215
+ this.emit('executionUpdated', {
216
+ executionId: data.executionId,
217
+ status: 'cancelled',
218
+ logs,
219
+ });
220
+ });
221
+ }
222
+ /**
223
+ * Map core agent update to legacy format
224
+ * TODO: remove this later in a separate task, need to migrate the UI to handle raw message streaming
225
+ */
226
+ mapToLegacyUpdate(data) {
227
+ const messages = this.coreAgentService
228
+ .getExecutionMessages(data.executionId)
229
+ .map((m) => this.summarizeMessage(m));
230
+ return {
231
+ executionId: data.executionId,
232
+ status: data.status ||
233
+ this.coreAgentService.getExecutionStatus(data.executionId)?.status ||
234
+ 'running',
235
+ logs: messages,
236
+ error: data.error,
237
+ };
238
+ }
239
+ /**
240
+ * Create a concise, human-readable line from a structured execution message
241
+ */
242
+ summarizeMessage(msg) {
243
+ const ts = new Date(msg.timestamp || Date.now()).toLocaleTimeString();
244
+ switch (msg.type) {
245
+ case 'init':
246
+ return `[${ts}] Init: session started`;
247
+ case 'assistant':
248
+ return typeof msg.content === 'string' ? msg.content : '[assistant message]';
249
+ case 'tool_use': {
250
+ const tool = msg.tool || 'tool';
251
+ const input = msg.input ? JSON.stringify(msg.input) : '';
252
+ return `[${ts}] TOOL ${tool} ${input}`;
253
+ }
254
+ case 'result': {
255
+ const success = msg.success;
256
+ return `[${ts}] Result: ${success ? 'success' : 'error'}`;
257
+ }
258
+ case 'system':
259
+ return `[${ts}] SYSTEM: ${typeof msg.content === 'string' ? msg.content : ''}`;
260
+ case 'error':
261
+ return `[${ts}] ERROR: ${String(msg.content || '')}`;
262
+ default:
263
+ // Ensure template-literal receives a string; avoid `never` type in lint
264
+ return `[${ts}] ${String(msg.type)}`;
265
+ }
266
+ }
267
+ /**
268
+ * Initialize worktree manager
269
+ */
270
+ async initializeWorktrees() {
271
+ try {
272
+ await this.worktreeService.initialize();
273
+ }
274
+ catch (error) {
275
+ log.warn('Failed to initialize WorktreeService', error, 'agent-service');
276
+ }
277
+ }
278
+ /**
279
+ * Execute a task using AI in an isolated worktree
280
+ */
281
+ async executeTask(taskId, workflowId, options) {
282
+ const task = this.taskService.getTask(taskId);
283
+ if (!task) {
284
+ throw new Error(`Task ${taskId} not found`);
285
+ }
286
+ const executionId = generateId('exec');
287
+ // Pre-register minimal context for this execution
288
+ const initialStart = new Date().toISOString();
289
+ this.executionRegistry.set(executionId, {
290
+ taskId,
291
+ workingDirectory: this.projectRoot,
292
+ startTime: initialStart,
293
+ });
294
+ // Start persistent logging with workflow context
295
+ const initialExecution = {
296
+ id: executionId,
297
+ taskId,
298
+ status: 'pending',
299
+ startTime: initialStart,
300
+ logs: [],
301
+ workingDirectory: this.projectRoot,
302
+ };
303
+ await this.logPersistence.startExecution(initialExecution, workflowId);
304
+ // Update task status to in-progress
305
+ await this.taskService.updateTask(taskId, { status: 'in-progress' });
306
+ // Fire-and-forget creation event if needed by listeners
307
+ // Include workflowId so orchestrators can associate execution to workflows
308
+ this.emit('executionCreated', { executionId, taskId, workflowId });
309
+ // Create worktree for isolated execution
310
+ let worktree;
311
+ let workingDirectory = this.projectRoot;
312
+ try {
313
+ worktree = await this.worktreeService.createWorktree(task);
314
+ if (worktree?.path) {
315
+ workingDirectory = worktree.path;
316
+ log.info('Task executing in worktree', { taskId, workingDirectory }, 'agent-service');
317
+ // Update registry with actual working directory
318
+ this.executionRegistry.set(executionId, {
319
+ taskId,
320
+ workingDirectory,
321
+ startTime: initialStart,
322
+ });
323
+ }
324
+ }
325
+ catch (error) {
326
+ log.error('Failed to create worktree, using main directory', error, 'agent-service');
327
+ }
328
+ // Generate task prompt
329
+ const prompt = await this.promptService.generateTaskPrompt(task);
330
+ // Build system prompt - use rerun context if available, otherwise base prompt
331
+ let appendSystemPrompt;
332
+ if (options?.rerunContext) {
333
+ appendSystemPrompt = await this.buildRerunSystemPrompt(task, options.rerunContext, workingDirectory);
334
+ }
335
+ else {
336
+ appendSystemPrompt = this.systemPrompts.execute_task.base;
337
+ }
338
+ // Persist prompt for auditing
339
+ void this.logPersistence.logMessage(executionId, 'Prompt prepared', 'info', {
340
+ prompt,
341
+ appendSystemPrompt, // include system/rerun append
342
+ options: { workingDirectory, tools: this.defaultTools.execute_task },
343
+ taskId: task.id,
344
+ workflowId,
345
+ rerunContext: options?.rerunContext,
346
+ });
347
+ await this.executeWithRouting('execute_task', prompt, {
348
+ workingDirectory,
349
+ tools: this.defaultTools.execute_task,
350
+ timeout: 30 * 60 * 1000,
351
+ appendSystemPrompt,
352
+ executionId,
353
+ metadata: {
354
+ operation: 'execute_task',
355
+ taskId,
356
+ workflowId,
357
+ worktree,
358
+ rerun: !!options?.rerunContext,
359
+ previousExecutionId: options?.rerunContext?.previousExecutionId,
360
+ },
361
+ }, options?.providerOverride);
362
+ return executionId;
363
+ }
364
+ /**
365
+ * Build an appended system prompt with rerun context (reason, logs, failed checks)
366
+ */
367
+ async buildRerunSystemPrompt(task, rerun, workingDirectory) {
368
+ const parts = [];
369
+ // Start with base system prompt, then add rerun context
370
+ parts.push(this.systemPrompts.execute_task.base);
371
+ parts.push('\n\n' + this.systemPrompts.execute_task.rerun);
372
+ parts.push(`Rerun reason: ${rerun.reason}`);
373
+ if (rerun.attempt)
374
+ parts.push(`Attempt: ${String(rerun.attempt)}`);
375
+ if (rerun.failurePhase)
376
+ parts.push(`Previous failure phase: ${rerun.failurePhase}`);
377
+ parts.push(`Working directory: ${workingDirectory}`);
378
+ // Try to get persisted logs tail if not provided and we have an execution id
379
+ let logs = rerun.lastLogs || [];
380
+ if ((!logs || logs.length === 0) && rerun.previousExecutionId) {
381
+ try {
382
+ const persisted = await this.getPersistedExecutionLogs(rerun.previousExecutionId);
383
+ const tail = persisted.logs.slice(-30).map((e) => e.message || JSON.stringify(e));
384
+ logs = tail;
385
+ }
386
+ catch {
387
+ const mem = this.getExecutionLogs(rerun.previousExecutionId);
388
+ logs = mem.slice(-30);
389
+ }
390
+ }
391
+ if (logs && logs.length) {
392
+ parts.push('Recent logs (tail):');
393
+ parts.push(logs.join('\n'));
394
+ }
395
+ const q = rerun.qualityResults;
396
+ if (q) {
397
+ const failed = Object.entries(q.details || {}).filter(([, ok]) => !ok);
398
+ if (failed.length) {
399
+ parts.push('Quality checks failed previously:');
400
+ for (const [name] of failed) {
401
+ const output = q.outputs?.[name];
402
+ parts.push(`- ${name}: ${output ? String(output).slice(0, 2000) : 'failed'}`);
403
+ }
404
+ }
405
+ }
406
+ parts.push('Instruction: Address the reasons above. Avoid repeating prior mistakes. Keep changes scoped and test thoroughly. ');
407
+ return parts.join('\n');
408
+ }
409
+ /**
410
+ * Stop a running execution and cleanup worktree
411
+ */
412
+ async stopExecution(executionId) {
413
+ const registry = this.executionRegistry.get(executionId);
414
+ if (!registry) {
415
+ throw new Error(`Execution ${executionId} not found`);
416
+ }
417
+ try {
418
+ await this.coreAgentService.stopExecution(executionId);
419
+ }
420
+ catch (error) {
421
+ log.warn('Failed to stop execution', error, 'agent-service');
422
+ }
423
+ const logs = this.coreAgentService
424
+ .getExecutionMessages(executionId)
425
+ .map((m) => this.summarizeMessage(m));
426
+ this.emit('executionUpdated', {
427
+ executionId,
428
+ status: 'cancelled',
429
+ logs,
430
+ });
431
+ }
432
+ /**
433
+ * Get execution status
434
+ */
435
+ getExecutionStatus(executionId) {
436
+ const registry = this.executionRegistry.get(executionId);
437
+ const state = this.coreAgentService.getExecutionStatus(executionId);
438
+ if (registry && state) {
439
+ const logs = this.coreAgentService
440
+ .getExecutionMessages(executionId)
441
+ .map((m) => this.summarizeMessage(m));
442
+ return {
443
+ id: executionId,
444
+ taskId: registry.taskId,
445
+ status: state.status,
446
+ startTime: registry.startTime || state.startTime,
447
+ endTime: state.endTime,
448
+ logs,
449
+ error: state.error,
450
+ provider: state.provider,
451
+ usage: state.usage,
452
+ workingDirectory: registry.workingDirectory,
453
+ };
454
+ }
455
+ // Fallback for generic/local executions (non-core) using in-memory state
456
+ if (registry && this.genericStates.has(executionId)) {
457
+ const gs = this.genericStates.get(executionId);
458
+ return {
459
+ id: executionId,
460
+ taskId: registry.taskId,
461
+ status: gs.status,
462
+ startTime: registry.startTime,
463
+ endTime: gs.endTime,
464
+ logs: [],
465
+ error: gs.error,
466
+ provider: 'local',
467
+ workingDirectory: registry.workingDirectory,
468
+ };
469
+ }
470
+ return null;
471
+ }
472
+ /**
473
+ * Get execution logs
474
+ */
475
+ getExecutionLogs(executionId) {
476
+ return this.coreAgentService
477
+ .getExecutionMessages(executionId)
478
+ .map((m) => this.summarizeMessage(m));
479
+ }
480
+ /**
481
+ * Get all executions for a task
482
+ */
483
+ getTaskExecutions(taskId) {
484
+ const results = [];
485
+ for (const [id, meta] of this.executionRegistry.entries()) {
486
+ if (meta.taskId !== taskId)
487
+ continue;
488
+ const state = this.coreAgentService.getExecutionStatus(id);
489
+ if (!state)
490
+ continue;
491
+ results.push({
492
+ id,
493
+ taskId: meta.taskId,
494
+ status: state.status,
495
+ startTime: meta.startTime || state.startTime,
496
+ endTime: state.endTime,
497
+ logs: [],
498
+ error: state.error,
499
+ workingDirectory: meta.workingDirectory,
500
+ });
501
+ }
502
+ return results.sort((a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime());
503
+ }
504
+ /**
505
+ * Improve task content using AI
506
+ */
507
+ async improveTaskContent(task, taskData, executionId) {
508
+ const finalExecutionId = executionId || generateId('improve');
509
+ try {
510
+ // Pre-register execution to enable streaming logs and persistence
511
+ if (!this.executionRegistry.has(finalExecutionId)) {
512
+ const startTime = new Date().toISOString();
513
+ this.executionRegistry.set(finalExecutionId, {
514
+ taskId: task.id,
515
+ workingDirectory: this.projectRoot,
516
+ startTime,
517
+ });
518
+ const execRec = {
519
+ id: finalExecutionId,
520
+ taskId: task.id,
521
+ status: 'pending',
522
+ startTime,
523
+ logs: [],
524
+ workingDirectory: this.projectRoot,
525
+ };
526
+ await this.logPersistence.startExecution(execRec);
527
+ }
528
+ // Generate improvement prompt
529
+ const prompt = await this.promptService.generateImprovementPrompt(task, taskData);
530
+ void this.logPersistence.logMessage(finalExecutionId, 'Improvement prompt prepared', 'info', {
531
+ prompt,
532
+ options: { workingDirectory: this.projectRoot, tools: this.defaultTools.improve_task },
533
+ taskId: task.id,
534
+ });
535
+ // Execute improvement with AI
536
+ const result = await this.executeWithRouting('improve_task', prompt, {
537
+ workingDirectory: this.projectRoot,
538
+ appendSystemPrompt: this.systemPrompts.improve_task,
539
+ tools: this.defaultTools.improve_task,
540
+ timeout: 5 * 60 * 1000,
541
+ executionId: finalExecutionId,
542
+ metadata: {
543
+ operation: 'improve_task',
544
+ taskId: task.id,
545
+ },
546
+ });
547
+ // Parse the result
548
+ const improvement = this.parseImprovementResult(result, taskData);
549
+ const payload = { ...improvement, executionId: finalExecutionId };
550
+ // Ensure registry exists (already handled above) and mark completion in persistence
551
+ await this.logPersistence.completeExecution(finalExecutionId, 'completed');
552
+ return payload;
553
+ }
554
+ catch (error) {
555
+ await this.logPersistence.completeExecution(finalExecutionId, 'failed', error instanceof Error ? error.message : String(error));
556
+ throw new Error(`Failed to improve task content: ${error instanceof Error ? error.message : String(error)}`);
557
+ }
558
+ }
559
+ /**
560
+ * Perform AI code review of changes in a worktree
561
+ */
562
+ async aiReviewCode(taskId, reviewContext, overrides) {
563
+ const task = this.taskService.getTask(taskId);
564
+ if (!task) {
565
+ throw new Error(`Task ${taskId} not found`);
566
+ }
567
+ const worktree = this.worktreeService.getWorktree(taskId);
568
+ if (!worktree) {
569
+ throw new Error(`No worktree found for task ${taskId}`);
570
+ }
571
+ const executionId = generateId('review');
572
+ try {
573
+ // Pre-register execution for streaming/persistence
574
+ if (!this.executionRegistry.has(executionId)) {
575
+ const startTime = new Date().toISOString();
576
+ this.executionRegistry.set(executionId, {
577
+ taskId,
578
+ workingDirectory: this.projectRoot,
579
+ startTime,
580
+ });
581
+ const execRec = {
582
+ id: executionId,
583
+ taskId,
584
+ status: 'pending',
585
+ startTime,
586
+ logs: [],
587
+ workingDirectory: this.projectRoot,
588
+ };
589
+ await this.logPersistence.startExecution(execRec);
590
+ }
591
+ // Generate review prompt
592
+ const reviewPrompt = await this.promptService.generateReviewPrompt(task, worktree, reviewContext);
593
+ void this.logPersistence.logMessage(executionId, 'Review prompt prepared', 'info', {
594
+ prompt: reviewPrompt,
595
+ options: { workingDirectory: worktree.path, tools: this.defaultTools.code_review },
596
+ taskId,
597
+ });
598
+ // Execute review with AI
599
+ const result = await this.executeWithRouting('ai_codereview', reviewPrompt, {
600
+ workingDirectory: worktree.path,
601
+ appendSystemPrompt: this.systemPrompts.ai_codereview,
602
+ tools: this.defaultTools.code_review,
603
+ timeout: 10 * 60 * 1000,
604
+ executionId,
605
+ metadata: {
606
+ operation: 'ai_codereview',
607
+ taskId,
608
+ },
609
+ }, overrides);
610
+ // Parse review result
611
+ const review = this.parseReviewResult(result);
612
+ const payload = { executionId, ...review };
613
+ // Ensure registry exists for lookups
614
+ if (!this.executionRegistry.has(executionId)) {
615
+ this.executionRegistry.set(executionId, {
616
+ taskId,
617
+ workingDirectory: this.projectRoot,
618
+ startTime: new Date().toISOString(),
619
+ });
620
+ }
621
+ return payload;
622
+ }
623
+ catch (error) {
624
+ throw new Error(`Failed to review code: ${error instanceof Error ? error.message : String(error)}`);
625
+ }
626
+ }
627
+ /**
628
+ * Perform AI-assisted merge of a task's worktree into the base project
629
+ */
630
+ async aiMerge(taskId, options, overrides) {
631
+ const task = this.taskService.getTask(taskId);
632
+ if (!task) {
633
+ throw new Error(`Task ${taskId} not found`);
634
+ }
635
+ const worktree = this.worktreeService.getWorktree(taskId);
636
+ if (!worktree) {
637
+ throw new Error(`No worktree found for task ${taskId}`);
638
+ }
639
+ const executionId = generateId('merge');
640
+ try {
641
+ // Pre-register execution for streaming/persistence
642
+ if (!this.executionRegistry.has(executionId)) {
643
+ const startTime = new Date().toISOString();
644
+ this.executionRegistry.set(executionId, {
645
+ taskId,
646
+ workingDirectory: this.projectRoot,
647
+ startTime,
648
+ });
649
+ const execRec = {
650
+ id: executionId,
651
+ taskId,
652
+ status: 'pending',
653
+ startTime,
654
+ logs: [],
655
+ workingDirectory: this.projectRoot,
656
+ };
657
+ await this.logPersistence.startExecution(execRec);
658
+ }
659
+ // Generate merge prompt
660
+ const mergePrompt = await this.promptService.generateAIMergePrompt(task, worktree, options?.baseBranch);
661
+ void this.logPersistence.logMessage(executionId, 'Merge prompt prepared', 'info', {
662
+ prompt: mergePrompt,
663
+ options: { workingDirectory: this.projectRoot, tools: this.defaultTools.ai_merge },
664
+ taskId,
665
+ baseBranch: options?.baseBranch,
666
+ });
667
+ // Execute merge with AI
668
+ await this.executeWithRouting('ai_merge', mergePrompt, {
669
+ workingDirectory: this.projectRoot,
670
+ appendSystemPrompt: this.systemPrompts.ai_merge,
671
+ tools: this.defaultTools.ai_merge,
672
+ timeout: 15 * 60 * 1000,
673
+ executionId,
674
+ metadata: {
675
+ operation: 'ai_merge',
676
+ taskId,
677
+ baseBranch: options?.baseBranch,
678
+ },
679
+ }, overrides);
680
+ // Ensure registry exists for lookups (no-op otherwise)
681
+ if (!this.executionRegistry.has(executionId)) {
682
+ this.executionRegistry.set(executionId, {
683
+ taskId,
684
+ workingDirectory: this.projectRoot,
685
+ startTime: new Date().toISOString(),
686
+ });
687
+ }
688
+ return { executionId };
689
+ }
690
+ catch (error) {
691
+ throw new Error(`AI merge failed: ${error instanceof Error ? error.message : String(error)}`);
692
+ }
693
+ }
694
+ /**
695
+ * Compatibility alias for aiReviewCode
696
+ * @deprecated Use aiReviewCode instead
697
+ * TODO: migrate UI to use aiReviewCode directly and remove this
698
+ */
699
+ async reviewCode(taskId, reviewContext, overrides) {
700
+ return this.aiReviewCode(taskId, reviewContext, overrides);
701
+ }
702
+ /**
703
+ * Get persistent execution logs
704
+ */
705
+ async getPersistedExecutionLogs(executionId) {
706
+ return await this.logPersistence.readExecutionLogs(executionId);
707
+ }
708
+ /**
709
+ * List all execution metadata from persistence
710
+ */
711
+ async listAllExecutions() {
712
+ return await this.logPersistence.listExecutions();
713
+ }
714
+ /**
715
+ * Clean up old execution logs
716
+ */
717
+ async cleanupOldExecutionLogs(maxAgeDays = 30) {
718
+ return await this.logPersistence.cleanupOldLogs(maxAgeDays);
719
+ }
720
+ /**
721
+ * Remove execution logs for a specific task
722
+ */
723
+ async removeExecutionLogsByTask(taskId) {
724
+ return await this.logPersistence.removeExecutionsByTask(taskId);
725
+ }
726
+ /**
727
+ * Get execution log statistics
728
+ */
729
+ async getExecutionLogStats() {
730
+ return await this.logPersistence.getStats();
731
+ }
732
+ /**
733
+ * Get statistics about executions
734
+ */
735
+ getExecutionStats() {
736
+ const states = this.coreAgentService.getAllExecutions();
737
+ const worktreeCounts = this.worktreeService.getWorktreeCounts();
738
+ return {
739
+ total: states.length,
740
+ pending: states.filter((e) => e.status === 'pending').length,
741
+ running: states.filter((e) => e.status === 'running').length,
742
+ completed: states.filter((e) => e.status === 'completed').length,
743
+ failed: states.filter((e) => e.status === 'failed').length,
744
+ cancelled: states.filter((e) => e.status === 'cancelled').length,
745
+ worktrees: {
746
+ active: worktreeCounts.active,
747
+ total: worktreeCounts.total,
748
+ enabled: true,
749
+ },
750
+ };
751
+ }
752
+ /**
753
+ * Get worktree service instance
754
+ */
755
+ getWorktreeService() {
756
+ return this.worktreeService;
757
+ }
758
+ /**
759
+ * Get worktree information for a task
760
+ */
761
+ getWorktreeInfo(taskId) {
762
+ const wt = this.worktreeService.getWorktree(taskId);
763
+ if (!wt)
764
+ return null;
765
+ const { git, ...rest } = wt;
766
+ return rest;
767
+ }
768
+ /**
769
+ * Get core agent service metrics
770
+ */
771
+ getCoreMetrics() {
772
+ const states = this.coreAgentService.getAllExecutions();
773
+ return {
774
+ totalExecutions: states.length,
775
+ activeExecutions: states.filter((e) => e.status === 'running').length,
776
+ completedExecutions: states.filter((e) => e.status === 'completed').length,
777
+ failedExecutions: states.filter((e) => e.status === 'failed').length,
778
+ cancelledExecutions: states.filter((e) => e.status === 'cancelled').length,
779
+ };
780
+ }
781
+ /**
782
+ * Get available AI providers
783
+ */
784
+ getAvailableProviders() {
785
+ return this.coreAgentService.getProviders();
786
+ }
787
+ /**
788
+ * Validate provider setup
789
+ */
790
+ async validateProviders() {
791
+ return await this.coreAgentService.validateProviders();
792
+ }
793
+ /**
794
+ * Get available models from all providers
795
+ */
796
+ async getAvailableModels() {
797
+ return await this.coreAgentService.getAvailableModels();
798
+ }
799
+ /**
800
+ * Parse improvement result from AI response
801
+ */
802
+ parseImprovementResult(result, originalData) {
803
+ return parseImprovementResult(result, originalData);
804
+ }
805
+ /**
806
+ * Parse review result from AI response
807
+ */
808
+ parseReviewResult(result) {
809
+ return parseReviewResult(result);
810
+ }
811
+ /**
812
+ * Resolve provider for specific operation with failover support
813
+ */
814
+ async resolveProviderForOperation(operation, overrides) {
815
+ return await this.routingPolicyManager.resolveProviderForOperation(operation, overrides);
816
+ }
817
+ /**
818
+ * Execute with provider resolution and failover
819
+ */
820
+ async executeWithRouting(operation, prompt, options = {}, overrides) {
821
+ const resolved = await this.resolveProviderForOperation(operation, overrides);
822
+ // Merge resolved options with base options
823
+ const executionOptions = {
824
+ ...options,
825
+ ...resolved.options,
826
+ model: resolved.model || options.model,
827
+ // Don't override workingDirectory or timeout from base options
828
+ workingDirectory: options.workingDirectory,
829
+ timeout: options.timeout,
830
+ };
831
+ // Default maxTokens from settings when not provided (unlimited if omitted)
832
+ try {
833
+ if (executionOptions.maxTokens === undefined) {
834
+ const s = getSettingsService().getSettings();
835
+ const def = operation === 'ai_codereview'
836
+ ? s.agents?.judgeAgent?.maxTokens
837
+ : s.agents?.codingAgent?.maxTokens;
838
+ if (typeof def === 'number') {
839
+ executionOptions.maxTokens = def;
840
+ }
841
+ }
842
+ }
843
+ catch {
844
+ // best effort; ignore if settings unavailable
845
+ }
846
+ let lastError = null;
847
+ const providersToTry = [resolved.provider, ...(resolved.fallbacks || [])];
848
+ for (let i = 0; i < providersToTry.length; i++) {
849
+ const provider = providersToTry[i];
850
+ const isLastProvider = i === providersToTry.length - 1;
851
+ try {
852
+ log.debug(`Attempting execution with provider: ${provider} (attempt ${i + 1}/${providersToTry.length})`, { operation, provider, isLastProvider }, 'agent-service');
853
+ const result = await this.coreAgentService.execute({
854
+ prompt,
855
+ provider,
856
+ options: executionOptions,
857
+ metadata: {
858
+ ...options.metadata,
859
+ operation,
860
+ provider,
861
+ model: resolved.model,
862
+ providerAttempt: i + 1,
863
+ totalProviderAttempts: providersToTry.length,
864
+ },
865
+ executionId: options.executionId,
866
+ });
867
+ // Success - log provider usage
868
+ log.info(`Successful execution with provider: ${provider}`, {
869
+ operation,
870
+ provider,
871
+ model: resolved.model,
872
+ attempt: i + 1,
873
+ fallbackUsed: i > 0,
874
+ }, 'agent-service');
875
+ return result;
876
+ }
877
+ catch (error) {
878
+ lastError = error instanceof Error ? error : new Error(String(error));
879
+ log.warn(`Provider ${provider} failed for operation ${operation}`, {
880
+ operation,
881
+ provider,
882
+ attempt: i + 1,
883
+ error: lastError.message,
884
+ willRetry: !isLastProvider,
885
+ }, 'agent-service');
886
+ // If this is not the last provider, continue to next
887
+ if (!isLastProvider) {
888
+ continue;
889
+ }
890
+ }
891
+ }
892
+ // All providers failed
893
+ throw new Error(`All providers failed for operation ${operation}. Last error: ${lastError?.message}`);
894
+ }
895
+ /**
896
+ * Get routing policy manager (for tRPC router access)
897
+ */
898
+ getRoutingPolicyManager() {
899
+ return this.routingPolicyManager;
900
+ }
901
+ }