@diff-review-system/drs 1.0.0 → 2.0.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 (287) hide show
  1. package/.opencode/agent/describe/pr-describer.md +221 -0
  2. package/.opencode/agent/review/documentation.md +56 -0
  3. package/.opencode/agent/review/performance.md +32 -130
  4. package/.opencode/agent/review/quality.md +36 -104
  5. package/.opencode/agent/review/security.md +32 -94
  6. package/.opencode/agent/review/style.md +26 -10
  7. package/.opencode/agent/review/unified-reviewer.md +74 -0
  8. package/.opencode/opencode.jsonc +4 -41
  9. package/.opencode/tool/write_json_output.ts +24 -0
  10. package/README.md +215 -82
  11. package/dist/ci/runner.d.ts.map +1 -1
  12. package/dist/ci/runner.js +4 -4
  13. package/dist/ci/runner.js.map +1 -1
  14. package/dist/cli/describe-mr.d.ts +11 -0
  15. package/dist/cli/describe-mr.d.ts.map +1 -0
  16. package/dist/cli/describe-mr.js +104 -0
  17. package/dist/cli/describe-mr.js.map +1 -0
  18. package/dist/cli/describe-pr.d.ts +12 -0
  19. package/dist/cli/describe-pr.d.ts.map +1 -0
  20. package/dist/cli/describe-pr.js +105 -0
  21. package/dist/cli/describe-pr.js.map +1 -0
  22. package/dist/cli/index.js +234 -20
  23. package/dist/cli/index.js.map +1 -1
  24. package/dist/cli/init.d.ts +1 -1
  25. package/dist/cli/init.d.ts.map +1 -1
  26. package/dist/cli/init.js +337 -120
  27. package/dist/cli/init.js.map +1 -1
  28. package/dist/cli/post-comments.d.ts +15 -0
  29. package/dist/cli/post-comments.d.ts.map +1 -0
  30. package/dist/cli/post-comments.js +216 -0
  31. package/dist/cli/post-comments.js.map +1 -0
  32. package/dist/cli/review-local.d.ts +3 -0
  33. package/dist/cli/review-local.d.ts.map +1 -1
  34. package/dist/cli/review-local.js +46 -63
  35. package/dist/cli/review-local.js.map +1 -1
  36. package/dist/cli/review-mr.d.ts +7 -0
  37. package/dist/cli/review-mr.d.ts.map +1 -1
  38. package/dist/cli/review-mr.js +88 -117
  39. package/dist/cli/review-mr.js.map +1 -1
  40. package/dist/cli/review-pr.d.ts +6 -0
  41. package/dist/cli/review-pr.d.ts.map +1 -1
  42. package/dist/cli/review-pr.js +81 -114
  43. package/dist/cli/review-pr.js.map +1 -1
  44. package/dist/cli/show-changes.d.ts +15 -0
  45. package/dist/cli/show-changes.d.ts.map +1 -0
  46. package/dist/cli/show-changes.js +184 -0
  47. package/dist/cli/show-changes.js.map +1 -0
  48. package/dist/github/client.d.ts +199 -4
  49. package/dist/github/client.d.ts.map +1 -1
  50. package/dist/github/client.js +37 -2
  51. package/dist/github/client.js.map +1 -1
  52. package/dist/github/client.test.d.ts +2 -0
  53. package/dist/github/client.test.d.ts.map +1 -0
  54. package/dist/github/client.test.js +206 -0
  55. package/dist/github/client.test.js.map +1 -0
  56. package/dist/github/platform-adapter.d.ts +31 -0
  57. package/dist/github/platform-adapter.d.ts.map +1 -0
  58. package/dist/github/platform-adapter.js +129 -0
  59. package/dist/github/platform-adapter.js.map +1 -0
  60. package/dist/github/platform-adapter.test.d.ts +2 -0
  61. package/dist/github/platform-adapter.test.d.ts.map +1 -0
  62. package/dist/github/platform-adapter.test.js +40 -0
  63. package/dist/github/platform-adapter.test.js.map +1 -0
  64. package/dist/gitlab/client.d.ts +12 -0
  65. package/dist/gitlab/client.d.ts.map +1 -1
  66. package/dist/gitlab/client.js +19 -1
  67. package/dist/gitlab/client.js.map +1 -1
  68. package/dist/gitlab/diff-parser.test.d.ts +2 -0
  69. package/dist/gitlab/diff-parser.test.d.ts.map +1 -0
  70. package/dist/gitlab/diff-parser.test.js +315 -0
  71. package/dist/gitlab/diff-parser.test.js.map +1 -0
  72. package/dist/gitlab/platform-adapter.d.ts +27 -0
  73. package/dist/gitlab/platform-adapter.d.ts.map +1 -0
  74. package/dist/gitlab/platform-adapter.js +121 -0
  75. package/dist/gitlab/platform-adapter.js.map +1 -0
  76. package/dist/gitlab/platform-adapter.test.d.ts +2 -0
  77. package/dist/gitlab/platform-adapter.test.d.ts.map +1 -0
  78. package/dist/gitlab/platform-adapter.test.js +21 -0
  79. package/dist/gitlab/platform-adapter.test.js.map +1 -0
  80. package/dist/index.test.d.ts +2 -0
  81. package/dist/index.test.d.ts.map +1 -0
  82. package/dist/index.test.js +7 -0
  83. package/dist/index.test.js.map +1 -0
  84. package/dist/lib/change-summary.d.ts +8 -0
  85. package/dist/lib/change-summary.d.ts.map +1 -0
  86. package/dist/lib/change-summary.js +2 -0
  87. package/dist/lib/change-summary.js.map +1 -0
  88. package/dist/lib/code-quality-report.d.ts +44 -0
  89. package/dist/lib/code-quality-report.d.ts.map +1 -0
  90. package/dist/lib/code-quality-report.js +62 -0
  91. package/dist/lib/code-quality-report.js.map +1 -0
  92. package/dist/lib/code-quality-report.test.d.ts +2 -0
  93. package/dist/lib/code-quality-report.test.d.ts.map +1 -0
  94. package/dist/lib/code-quality-report.test.js +327 -0
  95. package/dist/lib/code-quality-report.test.js.map +1 -0
  96. package/dist/{gitlab → lib}/comment-formatter.d.ts +6 -3
  97. package/dist/lib/comment-formatter.d.ts.map +1 -0
  98. package/dist/{gitlab → lib}/comment-formatter.js +63 -16
  99. package/dist/lib/comment-formatter.js.map +1 -0
  100. package/dist/lib/comment-formatter.test.d.ts +2 -0
  101. package/dist/lib/comment-formatter.test.d.ts.map +1 -0
  102. package/dist/lib/comment-formatter.test.js +607 -0
  103. package/dist/lib/comment-formatter.test.js.map +1 -0
  104. package/dist/lib/comment-manager.d.ts +61 -0
  105. package/dist/lib/comment-manager.d.ts.map +1 -0
  106. package/dist/lib/comment-manager.js +91 -0
  107. package/dist/lib/comment-manager.js.map +1 -0
  108. package/dist/lib/comment-manager.test.d.ts +2 -0
  109. package/dist/lib/comment-manager.test.d.ts.map +1 -0
  110. package/dist/lib/comment-manager.test.js +618 -0
  111. package/dist/lib/comment-manager.test.js.map +1 -0
  112. package/dist/lib/comment-poster.d.ts +21 -0
  113. package/dist/lib/comment-poster.d.ts.map +1 -0
  114. package/dist/lib/comment-poster.js +96 -0
  115. package/dist/lib/comment-poster.js.map +1 -0
  116. package/dist/lib/comment-poster.test.d.ts +5 -0
  117. package/dist/lib/comment-poster.test.d.ts.map +1 -0
  118. package/dist/lib/comment-poster.test.js +215 -0
  119. package/dist/lib/comment-poster.test.js.map +1 -0
  120. package/dist/lib/config-model-overrides.test.d.ts +12 -0
  121. package/dist/lib/config-model-overrides.test.d.ts.map +1 -0
  122. package/dist/lib/config-model-overrides.test.js +254 -0
  123. package/dist/lib/config-model-overrides.test.js.map +1 -0
  124. package/dist/lib/config.d.ts +93 -8
  125. package/dist/lib/config.d.ts.map +1 -1
  126. package/dist/lib/config.js +178 -25
  127. package/dist/lib/config.js.map +1 -1
  128. package/dist/lib/config.test.d.ts +2 -0
  129. package/dist/lib/config.test.d.ts.map +1 -0
  130. package/dist/lib/config.test.js +36 -0
  131. package/dist/lib/config.test.js.map +1 -0
  132. package/dist/lib/context-compression.d.ts +19 -0
  133. package/dist/lib/context-compression.d.ts.map +1 -0
  134. package/dist/lib/context-compression.js +170 -0
  135. package/dist/lib/context-compression.js.map +1 -0
  136. package/dist/lib/context-compression.test.d.ts +2 -0
  137. package/dist/lib/context-compression.test.d.ts.map +1 -0
  138. package/dist/lib/context-compression.test.js +33 -0
  139. package/dist/lib/context-compression.test.js.map +1 -0
  140. package/dist/lib/context-loader.d.ts +29 -0
  141. package/dist/lib/context-loader.d.ts.map +1 -0
  142. package/dist/lib/context-loader.js +75 -0
  143. package/dist/lib/context-loader.js.map +1 -0
  144. package/dist/lib/context-loader.test.d.ts +2 -0
  145. package/dist/lib/context-loader.test.d.ts.map +1 -0
  146. package/dist/lib/context-loader.test.js +207 -0
  147. package/dist/lib/context-loader.test.js.map +1 -0
  148. package/dist/lib/describe-core.d.ts +9 -0
  149. package/dist/lib/describe-core.d.ts.map +1 -0
  150. package/dist/lib/describe-core.js +71 -0
  151. package/dist/lib/describe-core.js.map +1 -0
  152. package/dist/lib/describe-core.test.d.ts +2 -0
  153. package/dist/lib/describe-core.test.d.ts.map +1 -0
  154. package/dist/lib/describe-core.test.js +208 -0
  155. package/dist/lib/describe-core.test.js.map +1 -0
  156. package/dist/lib/describe-output-path.test.d.ts +2 -0
  157. package/dist/lib/describe-output-path.test.d.ts.map +1 -0
  158. package/dist/lib/describe-output-path.test.js +51 -0
  159. package/dist/lib/describe-output-path.test.js.map +1 -0
  160. package/dist/lib/describe-parser.d.ts +3 -0
  161. package/dist/lib/describe-parser.d.ts.map +1 -0
  162. package/dist/lib/describe-parser.js +163 -0
  163. package/dist/lib/describe-parser.js.map +1 -0
  164. package/dist/lib/describe-parser.test.d.ts +2 -0
  165. package/dist/lib/describe-parser.test.d.ts.map +1 -0
  166. package/dist/lib/describe-parser.test.js +282 -0
  167. package/dist/lib/describe-parser.test.js.map +1 -0
  168. package/dist/lib/description-executor.d.ts +22 -0
  169. package/dist/lib/description-executor.d.ts.map +1 -0
  170. package/dist/lib/description-executor.js +72 -0
  171. package/dist/lib/description-executor.js.map +1 -0
  172. package/dist/lib/description-formatter.d.ts +37 -0
  173. package/dist/lib/description-formatter.d.ts.map +1 -0
  174. package/dist/lib/description-formatter.js +219 -0
  175. package/dist/lib/description-formatter.js.map +1 -0
  176. package/dist/{gitlab → lib}/diff-parser.d.ts +11 -0
  177. package/dist/lib/diff-parser.d.ts.map +1 -0
  178. package/dist/{gitlab → lib}/diff-parser.js +40 -3
  179. package/dist/lib/diff-parser.js.map +1 -0
  180. package/dist/lib/issue-parser.d.ts +29 -0
  181. package/dist/lib/issue-parser.d.ts.map +1 -0
  182. package/dist/lib/issue-parser.js +153 -0
  183. package/dist/lib/issue-parser.js.map +1 -0
  184. package/dist/lib/issue-parser.test.d.ts +2 -0
  185. package/dist/lib/issue-parser.test.d.ts.map +1 -0
  186. package/dist/lib/issue-parser.test.js +281 -0
  187. package/dist/lib/issue-parser.test.js.map +1 -0
  188. package/dist/lib/json-output-schema.d.ts +207 -0
  189. package/dist/lib/json-output-schema.d.ts.map +1 -0
  190. package/dist/lib/json-output-schema.js +124 -0
  191. package/dist/lib/json-output-schema.js.map +1 -0
  192. package/dist/lib/json-output-schema.test.d.ts +2 -0
  193. package/dist/lib/json-output-schema.test.d.ts.map +1 -0
  194. package/dist/lib/json-output-schema.test.js +92 -0
  195. package/dist/lib/json-output-schema.test.js.map +1 -0
  196. package/dist/lib/json-output.d.ts +43 -0
  197. package/dist/lib/json-output.d.ts.map +1 -0
  198. package/dist/lib/json-output.js +34 -0
  199. package/dist/lib/json-output.js.map +1 -0
  200. package/dist/lib/output-paths.d.ts +6 -0
  201. package/dist/lib/output-paths.d.ts.map +1 -0
  202. package/dist/lib/output-paths.js +5 -0
  203. package/dist/lib/output-paths.js.map +1 -0
  204. package/dist/lib/platform-client.d.ts +130 -0
  205. package/dist/lib/platform-client.d.ts.map +1 -0
  206. package/dist/lib/platform-client.js +8 -0
  207. package/dist/lib/platform-client.js.map +1 -0
  208. package/dist/lib/position-validator.d.ts +36 -0
  209. package/dist/lib/position-validator.d.ts.map +1 -0
  210. package/dist/lib/position-validator.js +43 -0
  211. package/dist/lib/position-validator.js.map +1 -0
  212. package/dist/lib/repository-validator.d.ts +52 -0
  213. package/dist/lib/repository-validator.d.ts.map +1 -0
  214. package/dist/lib/repository-validator.js +219 -0
  215. package/dist/lib/repository-validator.js.map +1 -0
  216. package/dist/lib/repository-validator.test.d.ts +5 -0
  217. package/dist/lib/repository-validator.test.d.ts.map +1 -0
  218. package/dist/lib/repository-validator.test.js +341 -0
  219. package/dist/lib/repository-validator.test.js.map +1 -0
  220. package/dist/lib/review-core.d.ts +66 -0
  221. package/dist/lib/review-core.d.ts.map +1 -0
  222. package/dist/lib/review-core.js +449 -0
  223. package/dist/lib/review-core.js.map +1 -0
  224. package/dist/lib/review-core.test.d.ts +2 -0
  225. package/dist/lib/review-core.test.d.ts.map +1 -0
  226. package/dist/lib/review-core.test.js +552 -0
  227. package/dist/lib/review-core.test.js.map +1 -0
  228. package/dist/lib/review-orchestrator.d.ts +77 -0
  229. package/dist/lib/review-orchestrator.d.ts.map +1 -0
  230. package/dist/lib/review-orchestrator.js +124 -0
  231. package/dist/lib/review-orchestrator.js.map +1 -0
  232. package/dist/lib/review-orchestrator.test.d.ts +2 -0
  233. package/dist/lib/review-orchestrator.test.d.ts.map +1 -0
  234. package/dist/lib/review-orchestrator.test.js +413 -0
  235. package/dist/lib/review-orchestrator.test.js.map +1 -0
  236. package/dist/lib/review-output-path.test.d.ts +2 -0
  237. package/dist/lib/review-output-path.test.d.ts.map +1 -0
  238. package/dist/lib/review-output-path.test.js +83 -0
  239. package/dist/lib/review-output-path.test.js.map +1 -0
  240. package/dist/lib/review-parser.d.ts +2 -0
  241. package/dist/lib/review-parser.d.ts.map +1 -0
  242. package/dist/lib/review-parser.js +100 -0
  243. package/dist/lib/review-parser.js.map +1 -0
  244. package/dist/lib/unified-review-executor.d.ts +49 -0
  245. package/dist/lib/unified-review-executor.d.ts.map +1 -0
  246. package/dist/lib/unified-review-executor.js +158 -0
  247. package/dist/lib/unified-review-executor.js.map +1 -0
  248. package/dist/lib/unified-review-executor.test.d.ts +5 -0
  249. package/dist/lib/unified-review-executor.test.d.ts.map +1 -0
  250. package/dist/lib/unified-review-executor.test.js +344 -0
  251. package/dist/lib/unified-review-executor.test.js.map +1 -0
  252. package/dist/lib/write-json-output.d.ts +13 -0
  253. package/dist/lib/write-json-output.d.ts.map +1 -0
  254. package/dist/lib/write-json-output.js +37 -0
  255. package/dist/lib/write-json-output.js.map +1 -0
  256. package/dist/opencode/agent-loader.d.ts +3 -4
  257. package/dist/opencode/agent-loader.d.ts.map +1 -1
  258. package/dist/opencode/agent-loader.js +51 -42
  259. package/dist/opencode/agent-loader.js.map +1 -1
  260. package/dist/opencode/agent-skill-overlay.d.ts +11 -0
  261. package/dist/opencode/agent-skill-overlay.d.ts.map +1 -0
  262. package/dist/opencode/agent-skill-overlay.js +164 -0
  263. package/dist/opencode/agent-skill-overlay.js.map +1 -0
  264. package/dist/opencode/client.d.ts +14 -5
  265. package/dist/opencode/client.d.ts.map +1 -1
  266. package/dist/opencode/client.js +311 -32
  267. package/dist/opencode/client.js.map +1 -1
  268. package/dist/opencode/client.test.d.ts +2 -0
  269. package/dist/opencode/client.test.d.ts.map +1 -0
  270. package/dist/opencode/client.test.js +317 -0
  271. package/dist/opencode/client.test.js.map +1 -0
  272. package/dist/opencode/opencode-paths.d.ts +2 -0
  273. package/dist/opencode/opencode-paths.d.ts.map +1 -0
  274. package/dist/opencode/opencode-paths.js +7 -0
  275. package/dist/opencode/opencode-paths.js.map +1 -0
  276. package/dist/opencode/skill-loader.d.ts +6 -0
  277. package/dist/opencode/skill-loader.d.ts.map +1 -0
  278. package/dist/opencode/skill-loader.js +36 -0
  279. package/dist/opencode/skill-loader.js.map +1 -0
  280. package/package.json +29 -20
  281. package/.opencode/agent/github-reviewer.md +0 -62
  282. package/.opencode/agent/gitlab-reviewer.md +0 -62
  283. package/.opencode/agent/local-reviewer.md +0 -71
  284. package/dist/gitlab/comment-formatter.d.ts.map +0 -1
  285. package/dist/gitlab/comment-formatter.js.map +0 -1
  286. package/dist/gitlab/diff-parser.d.ts.map +0 -1
  287. package/dist/gitlab/diff-parser.js.map +0 -1
@@ -0,0 +1,164 @@
1
+ import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'fs/promises';
2
+ import { existsSync } from 'fs';
3
+ import { dirname, join, relative } from 'path';
4
+ import { tmpdir } from 'os';
5
+ import * as yaml from 'yaml';
6
+ import { getDefaultSkills, normalizeAgentConfig } from '../lib/config.js';
7
+ import { builtInAgentPath } from './opencode-paths.js';
8
+ import { loadProjectSkills } from './skill-loader.js';
9
+ const SKILL_FILE_NAME = 'SKILL.md';
10
+ function normalizeSkillList(skills) {
11
+ if (!Array.isArray(skills)) {
12
+ return [];
13
+ }
14
+ return skills.map(String).filter((skill) => skill.length > 0);
15
+ }
16
+ function resolveConfiguredSkills(skillConfig, agentName) {
17
+ if (!skillConfig || skillConfig.agents.length === 0) {
18
+ return [];
19
+ }
20
+ const normalizedName = agentName.startsWith('review/') ? agentName.slice(7) : agentName;
21
+ const entry = skillConfig.agents.find((agent) => agent.name === normalizedName) ??
22
+ skillConfig.agents.find((agent) => agent.name === agentName);
23
+ const defaultSkills = normalizeSkillList(skillConfig.defaultSkills);
24
+ const agentSkills = entry ? normalizeSkillList(entry.skills) : [];
25
+ const mergedSkills = new Set([...defaultSkills, ...agentSkills]);
26
+ if (mergedSkills.size === 0) {
27
+ return [];
28
+ }
29
+ return Array.from(mergedSkills);
30
+ }
31
+ function upsertAgentSkills(content, skills) {
32
+ if (skills.length === 0) {
33
+ return content;
34
+ }
35
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n?/);
36
+ const parsed = frontmatterMatch ? yaml.parse(frontmatterMatch[1]) : {};
37
+ const frontmatter = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
38
+ // Enable the skill tool and set permissions for configured skills
39
+ // OpenCode uses on-demand skill loading via the 'skill' tool, not preloaded skills
40
+ const updatedFrontmatter = {
41
+ ...frontmatter,
42
+ tools: {
43
+ ...(frontmatter.tools || {}),
44
+ skill: true, // Enable the skill tool
45
+ },
46
+ permission: {
47
+ ...(frontmatter.permission || {}),
48
+ skill: {
49
+ // Allow all skills by default - could be made more granular if needed
50
+ '*': 'allow',
51
+ },
52
+ },
53
+ };
54
+ const frontmatterText = yaml.stringify(updatedFrontmatter).trimEnd();
55
+ const newFrontmatter = `---\n${frontmatterText}\n---\n`;
56
+ if (frontmatterMatch) {
57
+ return `${newFrontmatter}${content.slice(frontmatterMatch[0].length)}`;
58
+ }
59
+ return `${newFrontmatter}\n${content}`;
60
+ }
61
+ async function writeFileWithSkills(sourcePath, targetPath, agentName, skillConfig) {
62
+ const content = await readFile(sourcePath, 'utf-8');
63
+ const skills = resolveConfiguredSkills(skillConfig, agentName);
64
+ const updatedContent = upsertAgentSkills(content, skills);
65
+ await mkdir(dirname(targetPath), { recursive: true });
66
+ await writeFile(targetPath, updatedContent);
67
+ }
68
+ async function traverseDirectory(currentPath, fileFilter) {
69
+ const files = [];
70
+ if (!existsSync(currentPath)) {
71
+ return files;
72
+ }
73
+ const entries = await readdir(currentPath, { withFileTypes: true });
74
+ for (const entry of entries) {
75
+ const fullPath = join(currentPath, entry.name);
76
+ if (entry.isDirectory()) {
77
+ files.push(...(await traverseDirectory(fullPath, fileFilter)));
78
+ }
79
+ else if (entry.isFile() && fileFilter(entry.name, fullPath)) {
80
+ files.push(fullPath);
81
+ }
82
+ }
83
+ return files;
84
+ }
85
+ function resolveOverrideAgentName(overrideRoot, filePath) {
86
+ const relativePath = relative(overrideRoot, filePath).replace(/\\/g, '/');
87
+ const stripped = relativePath.replace(/\/agent\.md$/, '');
88
+ return stripped.startsWith('review/') ? stripped : `review/${stripped}`;
89
+ }
90
+ async function copyBuiltInAgents(destinationRoot, skillConfig) {
91
+ const files = await traverseDirectory(builtInAgentPath, (entry) => entry.endsWith('.md'));
92
+ const results = await Promise.allSettled(files.map(async (filePath) => {
93
+ const relativePath = relative(builtInAgentPath, filePath).replace(/\\/g, '/');
94
+ const agentName = relativePath.replace(/\.md$/, '').replace(/\\/g, '/');
95
+ const targetPath = join(destinationRoot, relativePath);
96
+ await writeFileWithSkills(filePath, targetPath, agentName, skillConfig);
97
+ }));
98
+ const failures = results.filter((result) => result.status === 'rejected');
99
+ if (failures.length > 0) {
100
+ throw new Error(`Failed to prepare ${failures.length} built-in agent(s)`);
101
+ }
102
+ }
103
+ async function copyOverrideAgents(projectPath, destinationRoot, skillConfig) {
104
+ const overrideRoot = join(projectPath, '.drs', 'agents');
105
+ const files = await traverseDirectory(overrideRoot, (entry) => entry === 'agent.md');
106
+ const results = await Promise.allSettled(files.map(async (filePath) => {
107
+ const agentName = resolveOverrideAgentName(overrideRoot, filePath);
108
+ const targetPath = join(destinationRoot, `${agentName}.md`);
109
+ await writeFileWithSkills(filePath, targetPath, agentName, skillConfig);
110
+ }));
111
+ const failures = results.filter((result) => result.status === 'rejected');
112
+ if (failures.length > 0) {
113
+ throw new Error(`Failed to prepare ${failures.length} override agent(s)`);
114
+ }
115
+ }
116
+ async function copyProjectSkills(projectPath, destinationRoot) {
117
+ const skills = loadProjectSkills(projectPath);
118
+ const results = await Promise.allSettled(skills.map(async (skill) => {
119
+ const targetPath = join(destinationRoot, skill.name, SKILL_FILE_NAME);
120
+ const content = await readFile(skill.path, 'utf-8');
121
+ await mkdir(dirname(targetPath), { recursive: true });
122
+ await writeFile(targetPath, content);
123
+ }));
124
+ const failures = results.filter((result) => result.status === 'rejected');
125
+ if (failures.length > 0) {
126
+ throw new Error(`Failed to prepare ${failures.length} skill file(s)`);
127
+ }
128
+ return skills;
129
+ }
130
+ export async function createAgentSkillOverlay(projectPath, config) {
131
+ const normalizedAgents = normalizeAgentConfig(config.review.agents);
132
+ const defaultSkills = getDefaultSkills(config);
133
+ const hasSkillConfig = defaultSkills.length > 0 || normalizedAgents.some((agent) => (agent.skills ?? []).length > 0);
134
+ const skills = loadProjectSkills(projectPath);
135
+ if (!hasSkillConfig && skills.length === 0) {
136
+ return null;
137
+ }
138
+ const overlayRoot = await mkdtemp(join(tmpdir(), 'drs-opencode-'));
139
+ const opencodeRoot = join(overlayRoot, '.opencode');
140
+ const agentRoot = join(opencodeRoot, 'agent');
141
+ const skillRoot = join(opencodeRoot, 'skills');
142
+ try {
143
+ await mkdir(agentRoot, { recursive: true });
144
+ await mkdir(skillRoot, { recursive: true });
145
+ const skillConfig = {
146
+ defaultSkills,
147
+ agents: normalizedAgents,
148
+ };
149
+ await copyBuiltInAgents(agentRoot, skillConfig);
150
+ await copyOverrideAgents(projectPath, agentRoot, skillConfig);
151
+ await copyProjectSkills(projectPath, skillRoot);
152
+ }
153
+ catch (error) {
154
+ await rm(overlayRoot, { recursive: true, force: true });
155
+ throw error;
156
+ }
157
+ return {
158
+ root: overlayRoot,
159
+ cleanup: async () => {
160
+ await rm(overlayRoot, { recursive: true, force: true });
161
+ },
162
+ };
163
+ }
164
+ //# sourceMappingURL=agent-skill-overlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-skill-overlay.js","sourceRoot":"","sources":["../../src/opencode/agent-skill-overlay.ts"],"names":[],"mappings":"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"}
@@ -5,16 +5,19 @@
5
5
  * 1. Connect to an existing remote OpenCode server (when baseUrl is provided)
6
6
  * 2. Start an OpenCode server in-process (when baseUrl is not provided)
7
7
  */
8
+ import type { CustomProvider, DRSConfig } from '../lib/config.js';
8
9
  export interface OpencodeConfig {
9
10
  baseUrl?: string;
10
11
  directory?: string;
11
- serverPort?: number;
12
- serverHostname?: string;
12
+ modelOverrides?: Record<string, string>;
13
+ provider?: Record<string, CustomProvider>;
14
+ debug?: boolean;
15
+ config?: DRSConfig;
13
16
  }
14
17
  export interface SessionCreateOptions {
15
18
  agent: string;
16
19
  message: string;
17
- context?: Record<string, any>;
20
+ context?: Record<string, unknown>;
18
21
  }
19
22
  export interface SessionMessage {
20
23
  id: string;
@@ -36,17 +39,19 @@ export declare class OpencodeClient {
36
39
  private inProcessServer?;
37
40
  private client?;
38
41
  private config;
42
+ private overlay?;
43
+ private projectRootEnv?;
39
44
  constructor(config: OpencodeConfig);
40
45
  /**
41
46
  * Initialize - either connect to remote server or start in-process
42
47
  */
43
48
  initialize(): Promise<void>;
44
49
  /**
45
- * Create a new session with an agent
50
+ * Create a new session with an agent and send initial message
46
51
  */
47
52
  createSession(options: SessionCreateOptions): Promise<Session>;
48
53
  /**
49
- * Stream messages from a session
54
+ * Stream messages from a session (polls until agent completes)
50
55
  */
51
56
  streamMessages(sessionId: string): AsyncGenerator<SessionMessage>;
52
57
  /**
@@ -69,6 +74,10 @@ export declare class OpencodeClient {
69
74
  * Get server URL
70
75
  */
71
76
  getServerUrl(): string;
77
+ /**
78
+ * Resolve {env:VAR_NAME} references to actual environment variable values
79
+ */
80
+ private resolveEnvReferences;
72
81
  }
73
82
  /**
74
83
  * Create an OpenCode client with the given configuration
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/opencode/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,eAAe,CAAC,CAA6C;IACrE,OAAO,CAAC,MAAM,CAAC,CAAqC;IACpD,OAAO,CAAC,MAAM,CAAiB;gBAEnB,MAAM,EAAE,cAAc;IAMlC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAgCjC;;OAEG;IACG,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC;IA2BpE;;OAEG;IACI,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,CAAC,cAAc,CAAC;IA0BxE;;OAEG;IACG,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAQrE;;OAEG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBpE;;OAEG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBpD;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAO/B;;OAEG;IACH,YAAY,IAAI,MAAM;CAMvB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAE3E;AAED;;GAEG;AACH,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,cAAc,CAAC,CAIzB"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/opencode/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAIlE,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC1C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAID,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,eAAe,CAAC,CAA6C;IACrE,OAAO,CAAC,MAAM,CAAC,CAAqC;IACpD,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,OAAO,CAAC,CAAsD;IACtE,OAAO,CAAC,cAAc,CAAC,CAAS;gBAEpB,MAAM,EAAE,cAAc;IAMlC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA8LjC;;OAEG;IACG,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC;IAmDpE;;OAEG;IACI,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,CAAC,cAAc,CAAC;IAyExE;;OAEG;IACG,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAQrE;;OAEG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBpE;;OAEG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBpD;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB/B;;OAEG;IACH,YAAY,IAAI,MAAM;IAOtB;;OAEG;IACH,OAAO,CAAC,oBAAoB;CA8B7B;AAgDD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAE3E;AAED;;GAEG;AACH,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,cAAc,CAAC,CAIzB"}
@@ -6,6 +6,10 @@
6
6
  * 2. Start an OpenCode server in-process (when baseUrl is not provided)
7
7
  */
8
8
  import { createOpencode, createOpencodeClient as createSDKClient } from '@opencode-ai/sdk';
9
+ import net from 'net';
10
+ import { getDefaultSkills, normalizeAgentConfig } from '../lib/config.js';
11
+ import { createAgentSkillOverlay } from './agent-skill-overlay.js';
12
+ const SERVER_START_TIMEOUT_MS = 10000;
9
13
  /**
10
14
  * OpenCode client that can start a server in-process or connect to remote
11
15
  */
@@ -15,6 +19,8 @@ export class OpencodeClient {
15
19
  inProcessServer;
16
20
  client;
17
21
  config;
22
+ overlay;
23
+ projectRootEnv;
18
24
  constructor(config) {
19
25
  this.baseUrl = config.baseUrl;
20
26
  this.directory = config.directory;
@@ -33,70 +39,261 @@ export class OpencodeClient {
33
39
  }
34
40
  else {
35
41
  // Start server in-process
36
- console.log('Starting OpenCode server in-process...');
37
- const apiKey = process.env.ANTHROPIC_API_KEY;
38
- if (!apiKey) {
39
- throw new Error('ANTHROPIC_API_KEY environment variable is required for in-process OpenCode server');
42
+ // Build OpenCode config programmatically from DRS config
43
+ const opencodeConfig = {
44
+ // Tools available to DRS review agents
45
+ tools: {
46
+ Read: true,
47
+ Glob: true,
48
+ Grep: true,
49
+ Bash: true,
50
+ write_json_output: true,
51
+ Write: false,
52
+ Edit: false,
53
+ },
54
+ };
55
+ // Set log level to DEBUG when --debug flag is used
56
+ // This shows full system prompts, tools, API calls, etc. from OpenCode
57
+ if (this.config.debug) {
58
+ opencodeConfig.logLevel = 'DEBUG';
59
+ console.log('🔍 OpenCode debug logging enabled');
60
+ }
61
+ // Add custom provider if configured in DRS config
62
+ if (this.config.provider && Object.keys(this.config.provider).length > 0) {
63
+ // Deep clone and resolve environment variable references
64
+ opencodeConfig.provider = this.resolveEnvReferences(this.config.provider);
65
+ const providerNames = Object.keys(this.config.provider);
66
+ console.log(`📦 Custom provider configured: ${providerNames.join(', ')}`);
67
+ }
68
+ // Apply model overrides from DRS config
69
+ if (this.config.modelOverrides && Object.keys(this.config.modelOverrides).length > 0) {
70
+ const agentConfig = {};
71
+ console.log('📋 Agent model configuration:');
72
+ // Merge model overrides into agent configuration
73
+ for (const [agentName, model] of Object.entries(this.config.modelOverrides)) {
74
+ agentConfig[agentName] = { model };
75
+ console.log(` • ${agentName}: ${model}`);
76
+ }
77
+ opencodeConfig.agent = agentConfig;
78
+ console.log('');
79
+ }
80
+ if (this.config.config) {
81
+ const normalizedAgents = normalizeAgentConfig(this.config.config.review.agents);
82
+ const defaultSkills = getDefaultSkills(this.config.config);
83
+ const agentSkills = normalizedAgents
84
+ .map((agent) => {
85
+ const combined = new Set([
86
+ ...defaultSkills,
87
+ ...(agent.skills ? agent.skills.map(String) : []),
88
+ ]);
89
+ return {
90
+ name: agent.name,
91
+ skills: Array.from(combined).filter((skill) => skill.length > 0),
92
+ };
93
+ })
94
+ .filter((agent) => agent.skills.length > 0);
95
+ if (agentSkills.length > 0) {
96
+ console.log('🧩 Agent skill configuration:');
97
+ for (const agent of agentSkills) {
98
+ console.log(` • review/${agent.name}: ${agent.skills.join(', ')}`);
99
+ }
100
+ console.log('');
101
+ }
40
102
  }
41
- // OpenCode SDK reads ANTHROPIC_API_KEY from environment automatically
103
+ // Debug: Print final OpenCode config
104
+ if (this.config.debug) {
105
+ console.log('🔧 DEBUG: Final OpenCode configuration (after env resolution):');
106
+ console.log('─'.repeat(50));
107
+ // Show environment variable status for custom providers
108
+ if (this.config.provider) {
109
+ console.log('\n📍 Environment variable status:');
110
+ for (const [providerName, provider] of Object.entries(this.config.provider)) {
111
+ const apiKeyConfig = provider.options?.apiKey;
112
+ if (apiKeyConfig && typeof apiKeyConfig === 'string') {
113
+ const envMatch = apiKeyConfig.match(/^\{env:([^}]+)\}$/);
114
+ if (envMatch) {
115
+ const envVarName = envMatch[1];
116
+ const envValue = process.env[envVarName];
117
+ if (envValue) {
118
+ console.log(` ✓ ${envVarName}: SET (${envValue.substring(0, 8)}...)`);
119
+ }
120
+ else {
121
+ console.log(` ✗ ${envVarName}: NOT SET`);
122
+ }
123
+ }
124
+ else {
125
+ console.log(` • ${providerName}: API key is hardcoded (not env var)`);
126
+ }
127
+ }
128
+ }
129
+ console.log('');
130
+ }
131
+ // Sanitize config to hide API keys
132
+ const sanitizedConfig = JSON.parse(JSON.stringify(opencodeConfig));
133
+ if (sanitizedConfig.provider) {
134
+ for (const providerName of Object.keys(sanitizedConfig.provider)) {
135
+ if (sanitizedConfig.provider[providerName]?.options?.apiKey) {
136
+ const apiKey = sanitizedConfig.provider[providerName].options.apiKey;
137
+ // Always redact since we've resolved env vars
138
+ if (apiKey && apiKey.length > 0) {
139
+ sanitizedConfig.provider[providerName].options.apiKey =
140
+ `***REDACTED (${apiKey.length} chars)***`;
141
+ }
142
+ else {
143
+ sanitizedConfig.provider[providerName].options.apiKey = '***EMPTY***';
144
+ }
145
+ }
146
+ }
147
+ }
148
+ console.log('Config being passed to OpenCode:');
149
+ console.log(JSON.stringify(sanitizedConfig, null, 2));
150
+ if (this.config.config) {
151
+ const normalizedAgents = normalizeAgentConfig(this.config.config.review.agents);
152
+ const defaultSkills = getDefaultSkills(this.config.config);
153
+ const agentSkills = normalizedAgents
154
+ .map((agent) => {
155
+ const combined = new Set([
156
+ ...defaultSkills,
157
+ ...(agent.skills ? agent.skills.map(String) : []),
158
+ ]);
159
+ return {
160
+ name: `review/${agent.name}`,
161
+ skills: Array.from(combined).filter((skill) => skill.length > 0),
162
+ };
163
+ })
164
+ .filter((agent) => agent.skills.length > 0);
165
+ if (agentSkills.length > 0) {
166
+ console.log('Agent skills (applied via overlay frontmatter):');
167
+ console.log(JSON.stringify(agentSkills, null, 2));
168
+ }
169
+ }
170
+ console.log('─'.repeat(50));
171
+ console.log('');
172
+ }
173
+ // Change to project directory so OpenCode can discover agents
174
+ const originalCwd = process.cwd();
175
+ const projectDir = this.directory || originalCwd;
176
+ this.projectRootEnv = process.env.DRS_PROJECT_ROOT;
177
+ process.env.DRS_PROJECT_ROOT = projectDir;
178
+ if (this.config.config) {
179
+ this.overlay = await createAgentSkillOverlay(projectDir, this.config.config);
180
+ }
181
+ const discoveryRoot = this.overlay?.root ?? projectDir;
182
+ if (discoveryRoot !== originalCwd) {
183
+ process.chdir(discoveryRoot);
184
+ }
185
+ // OpenCode SDK reads provider-specific API keys from environment automatically
186
+ // (ANTHROPIC_API_KEY, ZHIPU_API_KEY, OPENAI_API_KEY, etc.)
42
187
  this.inProcessServer = await createOpencode({
43
- hostname: this.config.serverHostname || '127.0.0.1',
44
- port: this.config.serverPort || 4096,
45
- timeout: 10000,
46
- config: {
47
- model: 'anthropic/claude-opus-4-20250514',
48
- },
188
+ timeout: SERVER_START_TIMEOUT_MS,
189
+ config: opencodeConfig,
49
190
  });
191
+ // Restore original working directory
192
+ if (discoveryRoot !== originalCwd) {
193
+ process.chdir(originalCwd);
194
+ }
50
195
  this.client = this.inProcessServer.client;
51
196
  this.baseUrl = this.inProcessServer.server.url;
52
- console.log(`OpenCode server started at ${this.baseUrl}`);
197
+ const ready = await waitForServerReady(this.baseUrl);
198
+ if (!ready) {
199
+ console.warn(`⚠️ OpenCode server did not become ready at ${this.baseUrl}. Review requests may fail.`);
200
+ }
53
201
  }
54
202
  }
55
203
  /**
56
- * Create a new session with an agent
204
+ * Create a new session with an agent and send initial message
57
205
  */
58
206
  async createSession(options) {
59
207
  if (!this.client) {
60
208
  throw new Error('OpenCode client not initialized. Call initialize() first.');
61
209
  }
62
210
  try {
63
- // Create session using OpenCode SDK
64
- // Note: This is a simplified implementation that needs to be adapted to the actual SDK API
65
- const response = await this.client.session.create({
211
+ // Step 1: Create empty session
212
+ const createResponse = (await this.client.session.create({
213
+ query: {
214
+ directory: this.directory,
215
+ },
216
+ }));
217
+ const sessionId = createResponse.data?.id;
218
+ if (!sessionId) {
219
+ throw new Error('Failed to get session ID from create response');
220
+ }
221
+ // Step 2: Send initial message to start the agent
222
+ await this.client.session.prompt({
223
+ path: { id: sessionId },
224
+ query: {
225
+ directory: this.directory,
226
+ },
66
227
  body: {
67
228
  agent: options.agent,
68
- userMessage: options.message,
229
+ parts: [
230
+ {
231
+ type: 'text',
232
+ text: options.message,
233
+ },
234
+ ],
69
235
  },
70
236
  });
71
237
  return {
72
- id: response.data?.id || 'session-' + Date.now(),
238
+ id: sessionId,
73
239
  agent: options.agent,
74
240
  createdAt: new Date(),
75
241
  };
76
242
  }
77
243
  catch (error) {
78
- throw new Error(`Failed to create session: ${error instanceof Error ? error.message : String(error)}`);
244
+ const message = error instanceof Error ? error.message : String(error);
245
+ const connectionHint = message.includes('fetch failed') || message.includes('ECONNREFUSED')
246
+ ? ` Check the OpenCode server URL (${this.baseUrl ?? 'in-process'}) and ensure it is reachable.`
247
+ : '';
248
+ throw new Error(`Failed to create session: ${message}${connectionHint}`);
79
249
  }
80
250
  }
81
251
  /**
82
- * Stream messages from a session
252
+ * Stream messages from a session (polls until agent completes)
83
253
  */
84
254
  async *streamMessages(sessionId) {
85
255
  if (!this.client) {
86
256
  throw new Error('OpenCode client not initialized. Call initialize() first.');
87
257
  }
88
258
  try {
89
- const response = await this.client.session.messages({
90
- path: { id: sessionId },
91
- });
92
- const messages = response.data || [];
93
- for (const msg of messages) {
94
- yield {
95
- id: msg.info?.id || 'msg-' + Date.now(),
96
- role: (msg.info?.role || 'assistant'),
97
- content: msg.parts?.map((p) => p.text || '').join('') || '',
98
- timestamp: new Date(),
99
- };
259
+ // Poll messages until agent completes
260
+ let lastMessageCount = 0;
261
+ let attempts = 0;
262
+ const maxAttempts = 60; // 60 attempts * 2s = 2 minutes max
263
+ while (attempts < maxAttempts) {
264
+ attempts++;
265
+ const messagesResponse = (await this.client.session.messages({
266
+ path: { id: sessionId },
267
+ }));
268
+ const messages = messagesResponse.data ?? [];
269
+ // Yield any new messages
270
+ for (let i = lastMessageCount; i < messages.length; i++) {
271
+ const msg = messages[i];
272
+ yield {
273
+ id: msg.info?.id ?? 'msg-' + Date.now(),
274
+ role: (msg.info?.role ?? 'assistant'),
275
+ content: msg.parts?.map((p) => p.text ?? '').join('') ?? '',
276
+ timestamp: new Date(),
277
+ };
278
+ }
279
+ lastMessageCount = messages.length;
280
+ // Check if the last assistant message has completed
281
+ const lastAssistantMsg = [...messages].reverse().find((m) => m.info?.role === 'assistant');
282
+ if (lastAssistantMsg) {
283
+ const isComplete = lastAssistantMsg.info?.time?.completed !== undefined;
284
+ const hasError = lastAssistantMsg.info?.error !== undefined;
285
+ if (hasError) {
286
+ throw new Error(`Agent error: ${JSON.stringify(lastAssistantMsg.info?.error)}`);
287
+ }
288
+ if (isComplete) {
289
+ break;
290
+ }
291
+ }
292
+ // Wait before polling again
293
+ await new Promise((resolve) => setTimeout(resolve, 2000));
294
+ }
295
+ if (attempts >= maxAttempts) {
296
+ throw new Error(`Session ${sessionId} timed out after ${maxAttempts * 2} seconds`);
100
297
  }
101
298
  }
102
299
  catch (error) {
@@ -152,8 +349,20 @@ export class OpencodeClient {
152
349
  */
153
350
  async shutdown() {
154
351
  if (this.inProcessServer) {
352
+ // Close the OpenCode server
155
353
  this.inProcessServer.server.close();
156
- console.log('OpenCode server stopped');
354
+ // Give server time to clean up connections
355
+ await new Promise((resolve) => setTimeout(resolve, 100));
356
+ }
357
+ if (this.overlay) {
358
+ await this.overlay.cleanup();
359
+ this.overlay = undefined;
360
+ }
361
+ if (this.projectRootEnv === undefined) {
362
+ delete process.env.DRS_PROJECT_ROOT;
363
+ }
364
+ else {
365
+ process.env.DRS_PROJECT_ROOT = this.projectRootEnv;
157
366
  }
158
367
  }
159
368
  /**
@@ -165,6 +374,76 @@ export class OpencodeClient {
165
374
  }
166
375
  return this.baseUrl;
167
376
  }
377
+ /**
378
+ * Resolve {env:VAR_NAME} references to actual environment variable values
379
+ */
380
+ resolveEnvReferences(obj) {
381
+ if (typeof obj === 'string') {
382
+ // Check for {env:VAR_NAME} pattern
383
+ const envMatch = obj.match(/^\{env:([^}]+)\}$/);
384
+ if (envMatch) {
385
+ const envVarName = envMatch[1];
386
+ const envValue = process.env[envVarName];
387
+ if (!envValue) {
388
+ console.warn(`⚠️ Environment variable ${envVarName} is not set`);
389
+ return '';
390
+ }
391
+ return envValue;
392
+ }
393
+ return obj;
394
+ }
395
+ if (Array.isArray(obj)) {
396
+ return obj.map((item) => this.resolveEnvReferences(item));
397
+ }
398
+ if (obj !== null && typeof obj === 'object') {
399
+ const resolved = {};
400
+ for (const [key, value] of Object.entries(obj)) {
401
+ resolved[key] = this.resolveEnvReferences(value);
402
+ }
403
+ return resolved;
404
+ }
405
+ return obj;
406
+ }
407
+ }
408
+ function parseServerEndpoint(baseUrl) {
409
+ try {
410
+ const url = new URL(baseUrl);
411
+ const port = url.port !== '' ? Number(url.port) : url.protocol === 'https:' ? 443 : 80;
412
+ if (!url.hostname || Number.isNaN(port)) {
413
+ return null;
414
+ }
415
+ return { host: url.hostname, port };
416
+ }
417
+ catch {
418
+ return null;
419
+ }
420
+ }
421
+ async function isServerReachable(baseUrl) {
422
+ const endpoint = parseServerEndpoint(baseUrl);
423
+ if (!endpoint) {
424
+ return false;
425
+ }
426
+ return new Promise((resolve) => {
427
+ const socket = net.createConnection(endpoint, () => {
428
+ socket.end();
429
+ resolve(true);
430
+ });
431
+ socket.setTimeout(1000);
432
+ socket.on('timeout', () => {
433
+ socket.destroy();
434
+ resolve(false);
435
+ });
436
+ socket.on('error', () => resolve(false));
437
+ });
438
+ }
439
+ async function waitForServerReady(baseUrl, attempts = 10, delayMs = 200) {
440
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
441
+ if (await isServerReachable(baseUrl)) {
442
+ return true;
443
+ }
444
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
445
+ }
446
+ return false;
168
447
  }
169
448
  /**
170
449
  * Create an OpenCode client with the given configuration