carthagent 0.0.0-stage → 1.0.7

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 (379) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -2
  3. package/bin/ctg.mjs +736 -0
  4. package/docs/budgets.md +72 -0
  5. package/docs/compaction.md +34 -0
  6. package/docs/context-efficiency-plan.md +24 -0
  7. package/docs/context-efficiency-review.md +27 -0
  8. package/docs/context-efficiency.md +49 -0
  9. package/docs/deepseek-jev-benchmark-plan.md +70 -0
  10. package/docs/deepseek-jev-benchmark-repair-plan.md +20 -0
  11. package/docs/deepseek-jev-benchmark-restart.md +9 -0
  12. package/docs/deepseek-jev-benchmark-results.md +77 -0
  13. package/docs/deepseek-jev-benchmark-running.md +60 -0
  14. package/docs/deepseek-jev-benchmark-v3-results.md +41 -0
  15. package/docs/deepseek-jev-benchmark-v4-protocol.md +15 -0
  16. package/docs/deepseek-jev-benchmark-v4-results.md +119 -0
  17. package/docs/deepseek-jev-http400-diagnosis.md +45 -0
  18. package/docs/delivery-review-evidence.md +53 -0
  19. package/docs/delivery-timeout-repair-plan.md +51 -0
  20. package/docs/evaluation-readiness.md +31 -0
  21. package/docs/evaluation.md +70 -0
  22. package/docs/extending.md +354 -0
  23. package/docs/glm-pilot-execution.md +9 -0
  24. package/docs/glm-pilot-plan.md +15 -0
  25. package/docs/glm-pilot-results.md +71 -0
  26. package/docs/graph-context-optimization.md +15 -0
  27. package/docs/jev-repository-pilot-plan.md +25 -0
  28. package/docs/jev-repository-pilot-protocol.md +39 -0
  29. package/docs/jev-repository-pilot-restart.md +7 -0
  30. package/docs/jev-repository-pilot-results.md +79 -0
  31. package/docs/jev.md +32 -0
  32. package/docs/model-registry.md +25 -0
  33. package/docs/native-compatibility.md +117 -0
  34. package/docs/opencode-archive.md +9 -0
  35. package/docs/opencode-migration.md +115 -0
  36. package/docs/opencode-runtime.md +94 -0
  37. package/docs/opencode-workspace.md +31 -0
  38. package/docs/owned-runtime-workflow-registration.md +17 -0
  39. package/docs/plan-advisor-evaluation.md +38 -0
  40. package/docs/planning-startup-fix-plan.md +23 -0
  41. package/docs/planning-startup-fix-review.md +24 -0
  42. package/docs/planning.md +118 -0
  43. package/docs/production-merge-review.md +23 -0
  44. package/docs/quality.md +35 -0
  45. package/docs/repository-benchmark-design.md +111 -0
  46. package/docs/review-assurance.md +9 -0
  47. package/docs/review-experiment-results.md +59 -0
  48. package/docs/tested-planning-roadmap.md +58 -0
  49. package/docs/tested-planning.md +58 -0
  50. package/docs/tui-pickers.md +9 -0
  51. package/docs/webdriver.md +83 -0
  52. package/docs/workflow-modes.md +26 -0
  53. package/docs/workflow.d2 +17 -0
  54. package/docs/workflow.svg +149 -0
  55. package/docs/workspace-coordination.md +9 -0
  56. package/evaluate.mjs +249 -0
  57. package/extensions/codegraph.ts +36 -0
  58. package/extensions/delivery.ts +819 -0
  59. package/extensions/experiential.ts +21 -0
  60. package/extensions/subscriptions.ts +21 -0
  61. package/guidance/profiles/authenticated-web.json +24 -0
  62. package/guidance/profiles/data-visualization.json +25 -0
  63. package/guidance/profiles/database-change.json +24 -0
  64. package/guidance/profiles/external-api.json +24 -0
  65. package/guidance/profiles/transactional-data.json +25 -0
  66. package/guidance/profiles/web-application.json +26 -0
  67. package/lib/acceptance-review.mjs +4 -0
  68. package/lib/browser-bin.mjs +160 -0
  69. package/lib/browser-errors.mjs +54 -0
  70. package/lib/browser.mjs +219 -0
  71. package/lib/budget.mjs +37 -0
  72. package/lib/cdp.mjs +221 -0
  73. package/lib/cloud/client.mjs +275 -0
  74. package/lib/cloud/control-plane.mjs +350 -0
  75. package/lib/cloud/gateway.mjs +380 -0
  76. package/lib/cloud/http.mjs +135 -0
  77. package/lib/cloud/ledger.mjs +215 -0
  78. package/lib/codegraph-query.mjs +60 -0
  79. package/lib/codegraph.mjs +378 -0
  80. package/lib/context-archive.mjs +18 -0
  81. package/lib/delivery-context.mjs +132 -0
  82. package/lib/delivery-guidance.mjs +9 -0
  83. package/lib/delivery-review.mjs +94 -0
  84. package/lib/delivery.mjs +610 -0
  85. package/lib/engine.mjs +137 -0
  86. package/lib/guidance.mjs +71 -0
  87. package/lib/jev-advisor.mjs +47 -0
  88. package/lib/opencode/archive.mjs +122 -0
  89. package/lib/opencode/compatibility.mjs +19 -0
  90. package/lib/opencode/credentials.mjs +205 -0
  91. package/lib/opencode/evaluation-budget-plugin/index.ts +147 -0
  92. package/lib/opencode/evaluation.mjs +242 -0
  93. package/lib/opencode/file-lock.mjs +86 -0
  94. package/lib/opencode/headless.mjs +200 -0
  95. package/lib/opencode/review.mjs +213 -0
  96. package/lib/opencode/runtime.mjs +246 -0
  97. package/lib/opencode/service.mjs +249 -0
  98. package/lib/opencode/session-options.mjs +60 -0
  99. package/lib/opencode/workspace.mjs +133 -0
  100. package/lib/openrouter-jev.mjs +37 -0
  101. package/lib/outcome-contract.mjs +21 -0
  102. package/lib/plan-advisor.mjs +38 -0
  103. package/lib/planning-access.mjs +7 -0
  104. package/lib/planning-advice.mjs +28 -0
  105. package/lib/planning-discovery.mjs +14 -0
  106. package/lib/planning-guide.mjs +34 -0
  107. package/lib/planning-view.mjs +57 -0
  108. package/lib/planning.mjs +155 -0
  109. package/lib/probe-diagnostics.mjs +13 -0
  110. package/lib/providers/experiential.mjs +292 -0
  111. package/lib/providers/model-catalog.json +700 -0
  112. package/lib/providers/model-catalog.mjs +7 -0
  113. package/lib/providers/names.mjs +13 -0
  114. package/lib/providers/subscription-models.mjs +483 -0
  115. package/lib/reports.mjs +80 -0
  116. package/lib/review-state.mjs +49 -0
  117. package/lib/review.mjs +114 -0
  118. package/lib/scenarios.mjs +99 -0
  119. package/lib/ship-auth.mjs +44 -0
  120. package/lib/ship-jev.mjs +59 -0
  121. package/lib/ship-telemetry.mjs +183 -0
  122. package/lib/tui/ansi.mjs +255 -0
  123. package/lib/tui/app.mjs +2445 -0
  124. package/lib/tui/auth.mjs +249 -0
  125. package/lib/tui/carthage.mjs +109 -0
  126. package/lib/tui/clipboard.mjs +122 -0
  127. package/lib/tui/d2.mjs +431 -0
  128. package/lib/tui/feed.mjs +467 -0
  129. package/lib/tui/framing.mjs +80 -0
  130. package/lib/tui/glyphs.mjs +172 -0
  131. package/lib/tui/hash-worker.mjs +11 -0
  132. package/lib/tui/mock-engine.mjs +222 -0
  133. package/lib/tui/models.mjs +73 -0
  134. package/lib/tui/picker.mjs +18 -0
  135. package/lib/tui/theme.mjs +663 -0
  136. package/lib/tui/title.mjs +7 -0
  137. package/lib/update-channel.mjs +62 -0
  138. package/lib/update.mjs +230 -0
  139. package/lib/webdriver.mjs +82 -0
  140. package/lib/workflow.mjs +88 -0
  141. package/lib/workspace.mjs +39 -0
  142. package/metadata.json +8 -0
  143. package/package.json +94 -4
  144. package/plugins/carthagent/index.ts +3 -0
  145. package/plugins/carthagent/package.json +28 -0
  146. package/plugins/carthagent/src/archive-rpc.ts +46 -0
  147. package/plugins/carthagent/src/bootstrap.ts +38 -0
  148. package/plugins/carthagent/src/index.ts +62 -0
  149. package/plugins/carthagent/src/pull-request-rpc.ts +54 -0
  150. package/plugins/carthagent/src/rpc.ts +76 -0
  151. package/plugins/carthagent/src/server/archive-rpc.ts +327 -0
  152. package/plugins/carthagent/src/server/budgets.ts +21 -0
  153. package/plugins/carthagent/src/server/codegraph.ts +46 -0
  154. package/plugins/carthagent/src/server/delivery.ts +488 -0
  155. package/plugins/carthagent/src/server/device-auth.ts +91 -0
  156. package/plugins/carthagent/src/server/native-delivery.ts +26 -0
  157. package/plugins/carthagent/src/server/orchestration.ts +56 -0
  158. package/plugins/carthagent/src/server/policy.ts +24 -0
  159. package/plugins/carthagent/src/server/providers.ts +424 -0
  160. package/plugins/carthagent/src/server/pull-request-rpc.ts +387 -0
  161. package/plugins/carthagent/src/server/session-controls.ts +82 -0
  162. package/plugins/carthagent/src/server/telemetry.ts +92 -0
  163. package/plugins/carthagent/src/server/workspace-rpc.ts +349 -0
  164. package/plugins/carthagent/src/session-rpc.ts +31 -0
  165. package/plugins/carthagent/src/testing.ts +85 -0
  166. package/plugins/carthagent/src/workspace-rpc.ts +133 -0
  167. package/plugins/carthagent/test-plugin/index.ts +4 -0
  168. package/plugins/carthagent/test-plugin/package.json +1 -0
  169. package/plugins/carthagent/tsconfig.json +19 -0
  170. package/plugins/carthagent/tui.ts +1 -0
  171. package/prompts/delivery.md +7 -0
  172. package/public/index.html +983 -0
  173. package/scenarios/cli/grade_cli.py +69 -0
  174. package/scenarios/cli/scenario.json +21 -0
  175. package/scenarios/game/baseline/minecraft.py +193 -0
  176. package/scenarios/game/context.md +1 -0
  177. package/scenarios/game/grade_minecraft_clone.py +603 -0
  178. package/scenarios/game/requirements.txt +2 -0
  179. package/scenarios/game/scenario.json +33 -0
  180. package/server.mjs +341 -0
  181. package/skills/game-development/SKILL.md +36 -0
  182. package/skills/game-development/assets/ursina_starter.py +98 -0
  183. package/skills/game-development/scripts/verify_ursina.py +80 -0
  184. package/skills/software-delivery/SKILL.md +50 -0
  185. package/skills/typescript-delivery/SKILL.md +69 -0
  186. package/tests/acceptance-review.test.mjs +13 -0
  187. package/tests/auth.test.mjs +234 -0
  188. package/tests/bounded-summary.test.mjs +68 -0
  189. package/tests/browser-bin.test.mjs +91 -0
  190. package/tests/browser.test.mjs +96 -0
  191. package/tests/budget-engine.test.mjs +110 -0
  192. package/tests/budget.test.mjs +23 -0
  193. package/tests/cli.test.mjs +245 -0
  194. package/tests/clipboard.test.mjs +79 -0
  195. package/tests/cloud-client.test.mjs +129 -0
  196. package/tests/cloud-control-plane.test.mjs +144 -0
  197. package/tests/cloud-gateway.test.mjs +147 -0
  198. package/tests/cloud-http.test.mjs +102 -0
  199. package/tests/cloud-ledger.test.mjs +75 -0
  200. package/tests/codegraph-query.test.mjs +66 -0
  201. package/tests/codegraph.test.mjs +142 -0
  202. package/tests/delivery-context.test.mjs +66 -0
  203. package/tests/delivery-review.test.mjs +139 -0
  204. package/tests/delivery.test.mjs +343 -0
  205. package/tests/engine-api.mjs +24 -0
  206. package/tests/experiential-extension.test.mjs +37 -0
  207. package/tests/experiential.test.mjs +300 -0
  208. package/tests/extension.test.mjs +921 -0
  209. package/tests/fixtures/archive-plugin/index.ts +10 -0
  210. package/tests/guidance.test.mjs +78 -0
  211. package/tests/model-registry.test.mjs +68 -0
  212. package/tests/models.test.mjs +70 -0
  213. package/tests/openai-idempotency.test.mjs +99 -0
  214. package/tests/opencode-archive-rpc.test.mjs +82 -0
  215. package/tests/opencode-delivery.test.mjs +231 -0
  216. package/tests/opencode-providers.test.mjs +414 -0
  217. package/tests/opencode-runtime.test.mjs +455 -0
  218. package/tests/opencode-session-controls.test.mjs +115 -0
  219. package/tests/opencode-session-options.test.mjs +26 -0
  220. package/tests/opencode-workspace.test.mjs +339 -0
  221. package/tests/outcome-contract.test.mjs +17 -0
  222. package/tests/picker.test.mjs +34 -0
  223. package/tests/plan-advisor.test.mjs +88 -0
  224. package/tests/plan-review-loop.test.mjs +53 -0
  225. package/tests/planning-advice.test.mjs +27 -0
  226. package/tests/planning-contract.test.mjs +65 -0
  227. package/tests/planning-startup.test.mjs +38 -0
  228. package/tests/planning-view.test.mjs +33 -0
  229. package/tests/planning.test.mjs +223 -0
  230. package/tests/probe-diagnostics.test.mjs +21 -0
  231. package/tests/pull-request-rpc.test.mjs +366 -0
  232. package/tests/review-state.test.mjs +32 -0
  233. package/tests/review.test.mjs +127 -0
  234. package/tests/server.test.mjs +252 -0
  235. package/tests/ship-jev.test.mjs +77 -0
  236. package/tests/ship-telemetry.test.mjs +333 -0
  237. package/tests/standard-workflow.test.mjs +28 -0
  238. package/tests/subscription-models.test.mjs +416 -0
  239. package/tests/subscriptions-extension.test.mjs +27 -0
  240. package/tests/terminal-title.test.mjs +13 -0
  241. package/tests/test_grade_minecraft_clone.py +199 -0
  242. package/tests/test_starter.py +78 -0
  243. package/tests/theme.test.mjs +175 -0
  244. package/tests/tui-copy-paste.test.mjs +54 -0
  245. package/tests/tui.test.mjs +808 -0
  246. package/tests/update-channels.test.mjs +129 -0
  247. package/tests/update.test.mjs +175 -0
  248. package/tests/webdriver.test.mjs +31 -0
  249. package/tests/workflow.test.mjs +47 -0
  250. package/tests/workspace-rpc.test.mjs +236 -0
  251. package/tests/workspace.test.mjs +24 -0
  252. package/tools/benchmark/advisor.test.mjs +7 -0
  253. package/tools/benchmark/advisor.ts +27 -0
  254. package/tools/benchmark/campaign.mjs +45 -0
  255. package/tools/benchmark/diagnose-request.mjs +40 -0
  256. package/tools/benchmark/eval-common.mjs +32 -0
  257. package/tools/benchmark/eval-ledger.mjs +22 -0
  258. package/tools/benchmark/eval-shop.mjs +26 -0
  259. package/tools/benchmark/eval-voxel.mjs +20 -0
  260. package/tools/benchmark/evaluate.mjs +24 -0
  261. package/tools/benchmark/evaluators.test.mjs +13 -0
  262. package/tools/benchmark/fixtures/shop/server.mjs +18 -0
  263. package/tools/benchmark/fixtures/shop/ui.html +8 -0
  264. package/tools/benchmark/freeze.mjs +30 -0
  265. package/tools/benchmark/gateway.mjs +91 -0
  266. package/tools/benchmark/gateway.test.mjs +38 -0
  267. package/tools/benchmark/generator.mjs +11 -0
  268. package/tools/benchmark/generator.test.mjs +26 -0
  269. package/tools/benchmark/guard.test.mjs +133 -0
  270. package/tools/benchmark/guard.ts +136 -0
  271. package/tools/benchmark/native-plugin/index.ts +24 -0
  272. package/tools/benchmark/planning-startup.test.mjs +47 -0
  273. package/tools/benchmark/preflight-live.mjs +31 -0
  274. package/tools/benchmark/preflight.mjs +22 -0
  275. package/tools/benchmark/replay-diagnostic.mjs +23 -0
  276. package/tools/benchmark/runner.mjs +141 -0
  277. package/tools/benchmark/runner.test.mjs +93 -0
  278. package/tools/benchmark/runtime.mjs +23 -0
  279. package/tools/benchmark/selftest.mjs +47 -0
  280. package/tools/benchmark/smoke-jev.mjs +5 -0
  281. package/tools/benchmark/tasks.mjs +20 -0
  282. package/tools/benchmark/transport.mjs +65 -0
  283. package/tools/benchmark/transport.test.mjs +44 -0
  284. package/tools/benchmark/upstream-error.mjs +18 -0
  285. package/tools/benchmark/upstream-error.test.mjs +10 -0
  286. package/tools/browser-check.mjs +109 -0
  287. package/tools/check-syntax.mjs +27 -0
  288. package/tools/evaluate-plan-advisor.mjs +20 -0
  289. package/tools/native-fixture.mjs +194 -0
  290. package/tools/native-pty-smoke.py +108 -0
  291. package/tools/native-report-plugin/index.ts +50 -0
  292. package/tools/native-report-plugin/package.json +7 -0
  293. package/tools/patch-compaction.mjs +24 -0
  294. package/tools/patch-model-catalog.mjs +12 -0
  295. package/tools/patch-print-shutdown.mjs +39 -0
  296. package/tools/repo-pilot/evaluator.cjs +32 -0
  297. package/tools/repo-pilot/reference.mjs +43 -0
  298. package/tools/repo-pilot/runner.mjs +85 -0
  299. package/tools/repo-pilot/sandbox-entry.mjs +12 -0
  300. package/tools/repo-pilot/tasks.mjs +20 -0
  301. package/tools/repo-pilot/type-evaluator.cjs +6 -0
  302. package/tools/test-native-controls.mjs +84 -0
  303. package/tools/test-native-delivery-host.mjs +307 -0
  304. package/tools/test-native-evaluation.mjs +46 -0
  305. package/tools/test-native-host.mjs +385 -0
  306. package/tools/test-native-outcomes.mjs +73 -0
  307. package/tools/test-native-package.mjs +70 -0
  308. package/tools/test-native-pty.mjs +20 -0
  309. package/tools/test-native-report.mjs +88 -0
  310. package/tools/test-native-swe-host.mjs +132 -0
  311. package/tools/test-native-swe.mjs +122 -0
  312. package/tools/test-picker-pty.py +45 -0
  313. package/tools/test-python.mjs +29 -0
  314. package/tools/test-webdriver.mjs +54 -0
  315. package/tools/verify-release-version.mjs +37 -0
  316. package/tools/webdriver-check.mjs +52 -0
  317. package/vendor/agent/bounded-summary.mjs +88 -0
  318. package/vendor/agent/chunks/anthropic-messages-VWZZOSJQ.js +6 -0
  319. package/vendor/agent/chunks/anthropic.js +75 -0
  320. package/vendor/agent/chunks/azure-openai-responses-BJLAM6CD.js +2 -0
  321. package/vendor/agent/chunks/bedrock-converse-stream.js +74 -0
  322. package/vendor/agent/chunks/chord-context.js +96 -0
  323. package/vendor/agent/chunks/chunk-4L3WN2XY.js +2 -0
  324. package/vendor/agent/chunks/chunk-6CYCI3EJ.js +12 -0
  325. package/vendor/agent/chunks/chunk-6R7UI4IY.js +2 -0
  326. package/vendor/agent/chunks/chunk-7ZC4UXSL.js +2 -0
  327. package/vendor/agent/chunks/chunk-AXIIZGTV.js +2 -0
  328. package/vendor/agent/chunks/chunk-CLNPYIDP.js +42 -0
  329. package/vendor/agent/chunks/chunk-DRQDKNXA.js +2 -0
  330. package/vendor/agent/chunks/chunk-GMWCTUPB.js +2 -0
  331. package/vendor/agent/chunks/chunk-IDDQWTHI.js +2 -0
  332. package/vendor/agent/chunks/chunk-JVUZSMYM.js +1548 -0
  333. package/vendor/agent/chunks/chunk-L2ZPNLYD.js +2 -0
  334. package/vendor/agent/chunks/chunk-NUHFSC37.js +25 -0
  335. package/vendor/agent/chunks/chunk-OUXLLA64.js +11 -0
  336. package/vendor/agent/chunks/chunk-PGXDEOVV.js +2 -0
  337. package/vendor/agent/chunks/chunk-PJACZCIR.js +2 -0
  338. package/vendor/agent/chunks/chunk-PRRNXB7S.js +2 -0
  339. package/vendor/agent/chunks/chunk-TEPMHNKQ.js +2 -0
  340. package/vendor/agent/chunks/chunk-TRDNDS6A.js +4 -0
  341. package/vendor/agent/chunks/chunk-U6ZMQKGD.js +50 -0
  342. package/vendor/agent/chunks/chunk-UAQELI3K.js +2 -0
  343. package/vendor/agent/chunks/chunk-USJZMAIR.js +2 -0
  344. package/vendor/agent/chunks/chunk-XDUWOHPL.js +6 -0
  345. package/vendor/agent/chunks/chunk-XNGRGP62.js +2 -0
  346. package/vendor/agent/chunks/github-copilot.js +2 -0
  347. package/vendor/agent/chunks/google-generative-ai-Q2K7FBDI.js +2 -0
  348. package/vendor/agent/chunks/google-vertex-7UJBF4S7.js +2 -0
  349. package/vendor/agent/chunks/https-proxy-agent-2VXB7436.js +13 -0
  350. package/vendor/agent/chunks/image-resize-worker.js +2 -0
  351. package/vendor/agent/chunks/kimi-coding.js +2 -0
  352. package/vendor/agent/chunks/lib-HGQMNYVT.js +9 -0
  353. package/vendor/agent/chunks/mistral-conversations-4M6BZBX6.js +5 -0
  354. package/vendor/agent/chunks/multipart-parser-SQ6GDJL7.js +3 -0
  355. package/vendor/agent/chunks/node-KG362ECQ.js +16 -0
  356. package/vendor/agent/chunks/node-TWVVAGPO.js +2 -0
  357. package/vendor/agent/chunks/openai-codex-responses-R6VYZTVY.js +10 -0
  358. package/vendor/agent/chunks/openai-codex.js +75 -0
  359. package/vendor/agent/chunks/openai-completions-EKZT2IH2.js +7 -0
  360. package/vendor/agent/chunks/openai-responses-TFDINO6W.js +2 -0
  361. package/vendor/agent/chunks/openrouter-images-N4XVVFSD.js +2 -0
  362. package/vendor/agent/chunks/openrouter.js +75 -0
  363. package/vendor/agent/chunks/pi-messages-PML4SPVJ.js +8 -0
  364. package/vendor/agent/chunks/radius.js +75 -0
  365. package/vendor/agent/chunks/src-UK77SYZE.js +4 -0
  366. package/vendor/agent/chunks/xai.js +2 -0
  367. package/vendor/agent/cli.js +3 -0
  368. package/vendor/agent/dist/modes/interactive/theme/dark.json +91 -0
  369. package/vendor/agent/dist/modes/interactive/theme/light.json +90 -0
  370. package/vendor/agent/dist/modes/interactive/theme/theme-schema.json +356 -0
  371. package/vendor/agent/index.js +2 -0
  372. package/vendor/agent/modes/interactive/theme/dark.json +91 -0
  373. package/vendor/agent/modes/interactive/theme/light.json +90 -0
  374. package/vendor/agent/modes/interactive/theme/theme-schema.json +356 -0
  375. package/vendor/agent/package.json +14 -0
  376. package/vendor/agent/rpc-entry.js +3 -0
  377. package/vendor/agent/theme/dark.json +91 -0
  378. package/vendor/agent/theme/light.json +90 -0
  379. package/vendor/agent/theme/theme-schema.json +356 -0
@@ -0,0 +1,133 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import guard from './guard.ts';
7
+
8
+ const key = sessionID => `benchmark/${sessionID}/tools`;
9
+ function harness(storage = new Map()) {
10
+ const hooks = new Map();
11
+ const eventQueues=[];
12
+ const ctx = {
13
+ session:{hook:async(name,callback)=>{hooks.set(`session:${name}`,callback);return{dispose:async()=>{}};}},
14
+ tool:{hook:async(name,callback)=>{hooks.set(`tool:${name}`,callback);return{dispose:async()=>{}};}},
15
+ storage:{
16
+ get:async name=>storage.get(name),
17
+ set:async(name,value)=>{storage.set(name,structuredClone(value));},
18
+ },
19
+ event:{subscribe:({signal})=>({[Symbol.asyncIterator]:async function*(){
20
+ const queue=[];const waiters=[];eventQueues.push({queue,waiters});
21
+ const onAbort=()=>{for(const waiter of waiters.splice(0))waiter();};
22
+ signal.addEventListener('abort',onAbort,{once:true});
23
+ try {
24
+ while(!signal.aborted) {
25
+ if(queue.length){yield queue.shift();continue;}
26
+ await new Promise(resolve=>waiters.push(resolve));
27
+ }
28
+ } finally {signal.removeEventListener('abort',onAbort);}
29
+ }})},
30
+ };
31
+ return { hooks, storage, ctx, emit:event=>{for(const {queue,waiters} of eventQueues){queue.push(event);for(const waiter of waiters.splice(0))waiter();}} };
32
+ }
33
+
34
+ test('parallel per-session admissions reserve the final slot once, replay IDs idempotently and persist across guard restart',async()=>{
35
+ const temp=mkdtempSync(join(tmpdir(),'benchmark-guard-'));
36
+ const old={limit:process.env.BENCH_TOOL_LIMIT,log:process.env.BENCH_GUARD_LOG,home:process.env.CTG_OPENCODE_HOME};
37
+ process.env.BENCH_TOOL_LIMIT='2';
38
+ process.env.BENCH_GUARD_LOG=join(temp,'guard.jsonl');
39
+ process.env.CTG_OPENCODE_HOME=temp;
40
+ const state=harness();
41
+ let dispose=await guard(state.ctx);
42
+ try {
43
+ state.storage.set(key('session-a'),[{id:'already-used',tool:'read',admitted:true,at:1}]);
44
+ const before=state.hooks.get('tool:execute.before');
45
+ const results=await Promise.allSettled(Array.from({length:12},(_,index)=>before({id:`call-${index}`,tool:'read',sessionID:'session-a'})));
46
+ assert.equal(results.filter(result=>result.status==='fulfilled').length,1);
47
+ assert.equal(results.filter(result=>result.status==='rejected').length,11);
48
+ assert.equal(state.storage.get(key('session-a')).filter(row=>row.admitted).length,2);
49
+
50
+ // A duplicate delivery of the admitted call ID is idempotent, including
51
+ // after a plugin restart which discards in-memory state.
52
+ const admittedID=state.storage.get(key('session-a')).find(row=>row.id.startsWith('call-')&&row.admitted).id;
53
+ await assert.rejects(before({id:admittedID,tool:'read',sessionID:'session-a'}),/budget exhausted/);
54
+ assert.equal(state.storage.get(key('session-a')).filter(row=>row.admitted).length,2);
55
+ await dispose();
56
+ const other=harness(state.storage);
57
+ dispose=await guard(other.ctx);
58
+ await assert.rejects(other.hooks.get('tool:execute.before')({id:admittedID,tool:'read',sessionID:'session-a'}),/budget exhausted/);
59
+ await other.hooks.get('tool:execute.before')({id:'neighbor',tool:'read',sessionID:'session-b'});
60
+ assert.equal(other.storage.get(key('session-b')).filter(row=>row.admitted).length,1);
61
+ await assert.rejects(other.hooks.get('tool:execute.before')({id:'new-after-restart',tool:'read',sessionID:'session-a'}),/budget exhausted/);
62
+ assert.equal(other.storage.get(key('session-a')).filter(row=>row.admitted).length,2);
63
+ await other.hooks.get('tool:execute.before')({id:'neighbor-after-rejection',tool:'read',sessionID:'session-b'});
64
+ await assert.rejects(other.hooks.get('tool:execute.before')({id:'neighbor-at-cap',tool:'read',sessionID:'session-b'}),/budget exhausted/);
65
+ assert.equal(other.storage.get(key('session-b')).filter(row=>row.admitted).length,2);
66
+ assert.equal(JSON.parse(readFileSync(process.env.BENCH_GUARD_LOG,'utf8').trim().split('\n').at(-1)).sessionID,'session-b');
67
+ } finally {
68
+ await dispose();
69
+ for(const [name,value] of Object.entries({BENCH_TOOL_LIMIT:old.limit,BENCH_GUARD_LOG:old.log,CTG_OPENCODE_HOME:old.home})) {
70
+ if(value===undefined) delete process.env[name]; else process.env[name]=value;
71
+ }
72
+ rmSync(temp,{recursive:true,force:true});
73
+ }
74
+ });
75
+
76
+ test('failed durable reservation is fail-closed and a cancelled queued call consumes no slot',async()=>{
77
+ const temp=mkdtempSync(join(tmpdir(),'benchmark-guard-fail-'));
78
+ const old={limit:process.env.BENCH_TOOL_LIMIT,log:process.env.BENCH_GUARD_LOG,home:process.env.CTG_OPENCODE_HOME};
79
+ process.env.BENCH_TOOL_LIMIT='1';
80
+ process.env.BENCH_GUARD_LOG=join(temp,'guard.jsonl');
81
+ process.env.CTG_OPENCODE_HOME=temp;
82
+ const storage=new Map();
83
+ const state=harness(storage);
84
+ let failWrite=true, releaseRead, enteredRead;
85
+ const entered=new Promise(resolve=>{enteredRead=resolve;});
86
+ let holdNextRead=false;
87
+ state.ctx.storage.get=async name=>{
88
+ if(name===key('queued-session')&&holdNextRead){
89
+ holdNextRead=false;
90
+ await new Promise(resolve=>{releaseRead=resolve;enteredRead();});
91
+ }
92
+ return storage.get(name);
93
+ };
94
+ state.ctx.storage.set=async(name,value)=>{
95
+ if(failWrite){failWrite=false;throw Error('durable storage unavailable');}
96
+ storage.set(name,structuredClone(value));
97
+ };
98
+ let dispose=await guard(state.ctx);
99
+ try {
100
+ const before=state.hooks.get('tool:execute.before');
101
+ await assert.rejects(before({id:'write-fails',tool:'read',sessionID:'session-write-fails'}),/durable storage unavailable/);
102
+ assert.equal(storage.has(key('session-write-fails')),false);
103
+ holdNextRead=true;
104
+ const owner=before({id:'slow-owner',tool:'read',sessionID:'queued-session'});
105
+ await entered;
106
+ state.emit({type:'session.execution.interrupted',data:{sessionID:'queued-session'}});
107
+ await new Promise(resolve=>setImmediate(resolve));
108
+ const cancelled=before({id:'cancelled',tool:'read',sessionID:'queued-session'});
109
+ await before({id:'independent-session',tool:'read',sessionID:'independent'});
110
+ assert.equal(storage.get(key('independent')).filter(row=>row.admitted).length,1,'different session admissions do not wait on the held session queue');
111
+ releaseRead();
112
+ await assert.rejects(owner,/cancelled before reservation/);
113
+ await assert.rejects(cancelled,/cancelled before reservation/);
114
+ assert.equal(storage.has(key('queued-session')),false,'interrupted in-flight admission must not write a reservation');
115
+ await before({id:'neighbor',tool:'read',sessionID:'neighbor'});
116
+ assert.equal(storage.get(key('neighbor')).filter(row=>row.admitted).length,1);
117
+ holdNextRead=true;
118
+ const draining=before({id:'drain-owner',tool:'read',sessionID:'draining'});
119
+ await entered;
120
+ const unloading=dispose();
121
+ releaseRead();
122
+ await assert.rejects(draining,/cancelled before reservation/);
123
+ await unloading;
124
+ assert.equal(storage.has(key('draining')),false,'unload waits for queued callbacks without admitting late work');
125
+ } finally {
126
+ releaseRead?.();
127
+ await dispose();
128
+ for(const [name,value] of Object.entries({BENCH_TOOL_LIMIT:old.limit,BENCH_GUARD_LOG:old.log,CTG_OPENCODE_HOME:old.home})) {
129
+ if(value===undefined) delete process.env[name]; else process.env[name]=value;
130
+ }
131
+ rmSync(temp,{recursive:true,force:true});
132
+ }
133
+ });
@@ -0,0 +1,136 @@
1
+ import { appendFileSync } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { withFileLock } from '../../lib/opencode/runtime.mjs';
6
+
7
+ type Registration = { dispose(): Promise<void> };
8
+ const guardLedger = (sessionID: string) => `benchmark/${sessionID}/tools`;
9
+
10
+ /** Identical native preventive tool gate in every condition. */
11
+ export default async function guard(ctx: any): Promise<() => Promise<void>> {
12
+ const limit = Number(process.env.BENCH_TOOL_LIMIT || 120);
13
+ const retryLimit = Number(process.env.BENCH_RETRY_LIMIT || 3);
14
+ if (!Number.isInteger(limit) || limit < 1 || limit > 120) throw Error('Invalid tool limit');
15
+ if (!Number.isInteger(retryLimit) || retryLimit < 0 || retryLimit > 20) throw Error('Invalid benchmark retry limit');
16
+
17
+ const registrations: Registration[] = [];
18
+ let disposed = false;
19
+ let disposing: Promise<void> | undefined;
20
+ const tails = new Map<string, Promise<void>>();
21
+ const admitted = new Map<string, boolean>();
22
+ const cancelledSessions = new Set<string>();
23
+ let cancellationEventsAvailable = true;
24
+ const eventAbort = new AbortController();
25
+ const eventTask = (async () => {
26
+ try {
27
+ for await (const event of ctx.event.subscribe({ signal:eventAbort.signal })) {
28
+ if (!event.type.startsWith('session.') || !('sessionID' in event.data)) continue;
29
+ if (event.type === 'session.execution.interrupted' || event.type === 'session.deleted') {
30
+ cancelledSessions.add(event.data.sessionID);
31
+ } else if (event.type === 'session.execution.started') {
32
+ cancelledSessions.delete(event.data.sessionID);
33
+ }
34
+ }
35
+ if (!eventAbort.signal.aborted) cancellationEventsAvailable = false;
36
+ } catch {
37
+ if (!eventAbort.signal.aborted) cancellationEventsAvailable = false;
38
+ }
39
+ })();
40
+ void eventTask.catch(() => undefined);
41
+ const callKey = (sessionID: string, callID: string) => `${sessionID}\0${callID}`;
42
+ const lockPath = (sessionID: string) => {
43
+ const safeID = createHash('sha256').update(sessionID).digest('hex');
44
+ const root = process.env.CTG_OPENCODE_HOME;
45
+ return root ? join(root, 'state', 'benchmark-admission', `${safeID}.lock`)
46
+ : join(tmpdir(), 'ctg-opencode-benchmark-admission', `${safeID}.lock`);
47
+ };
48
+ const serializeSession = async (sessionID: string, action: () => Promise<void>) => {
49
+ const previous = tails.get(sessionID) ?? Promise.resolve();
50
+ let release!: () => void;
51
+ const current = new Promise<void>(resolve => { release = resolve; });
52
+ tails.set(sessionID, current);
53
+ try {
54
+ await previous;
55
+ await withFileLock(lockPath(sessionID), action);
56
+ } finally {
57
+ release();
58
+ if (tails.get(sessionID) === current) tails.delete(sessionID);
59
+ }
60
+ };
61
+ const register = async (pending: Promise<Registration>) => {
62
+ const registration = await pending;
63
+ registrations.push(registration);
64
+ return registration;
65
+ };
66
+ const dispose = () => {
67
+ if (disposing) return disposing;
68
+ disposed = true;
69
+ disposing = (async () => {
70
+ // Stop new hook dispatch first. Callbacks already queued re-check
71
+ // disposed after acquiring their session lock; drain until every such
72
+ // callback has released its queue before plugin unload completes.
73
+ await Promise.allSettled(registrations.splice(0).reverse().map(registration => registration.dispose()));
74
+ while (tails.size) await Promise.allSettled([...tails.values()]);
75
+ eventAbort.abort();
76
+ await eventTask.catch(() => undefined);
77
+ })();
78
+ return disposing;
79
+ };
80
+ try {
81
+ await register(ctx.session.hook('context', async (event: any) => {
82
+ if (event.kind !== 'primary') return;
83
+ const rows = await ctx.storage.get(guardLedger(event.sessionID));
84
+ const attempts = Array.isArray(rows) ? rows.filter(row => row && typeof row === 'object' && row.admitted === true).length : 0;
85
+ if (attempts >= limit) {
86
+ event.tools = {};
87
+ event.system.push({ type:'text', text:'Native benchmark tool budget is exhausted. Stop issuing tool calls, summarize verified status and remaining work.' });
88
+ }
89
+ }));
90
+ await register(ctx.session.hook('retry', async (event: any) => {
91
+ if (event.attempt >= retryLimit) event.decision = { retry:false };
92
+ }));
93
+ await register(ctx.tool.hook('execute.before', async (event: any) => {
94
+ let blocked = false;
95
+ let attempts = 0;
96
+ await serializeSession(event.sessionID, async () => {
97
+ if (disposed || !cancellationEventsAvailable || cancelledSessions.has(event.sessionID)) throw Error('Benchmark tool admission cancelled before reservation.');
98
+ const currentCallKey = callKey(event.sessionID, event.id);
99
+ const previous = await ctx.storage.get(guardLedger(event.sessionID));
100
+ const rows = Array.isArray(previous) ? previous : [];
101
+ const existing = rows.find(row => row && typeof row === 'object' && row.id === event.id);
102
+ if (existing) {
103
+ if (existing.tool !== event.tool || typeof existing.admitted !== 'boolean') throw Error('Conflicting benchmark tool reservation ID.');
104
+ attempts = rows.filter(row => row && typeof row === 'object' && row.admitted === true).length;
105
+ blocked = true; // Duplicate invocation never executes twice.
106
+ } else {
107
+ attempts = rows.filter(row => row && typeof row === 'object' && row.admitted === true).length;
108
+ blocked = attempts >= limit;
109
+ if (disposed || !cancellationEventsAvailable || cancelledSessions.has(event.sessionID)) throw Error('Benchmark tool admission cancelled before reservation.');
110
+ // Persist a reservation (or rejection) while holding both the
111
+ // per-session queue and inter-process file lock. No tool is admitted
112
+ // until this durable write has succeeded.
113
+ await ctx.storage.set(guardLedger(event.sessionID), [...rows, { id:event.id, tool:event.tool, admitted:!blocked, at:Date.now() }]);
114
+ attempts += Number(!blocked);
115
+ appendFileSync(process.env.BENCH_GUARD_LOG!, JSON.stringify({ type:'tool-attempt', sessionID:event.sessionID, callID:event.id, tool:event.tool, attempts, blocked })+'\n');
116
+ }
117
+ if (!existing) admitted.set(currentCallKey, !blocked);
118
+ });
119
+ if (blocked) throw Error('Benchmark tool budget exhausted before execution.');
120
+ }));
121
+ await register(ctx.tool.hook('execute.after', async (event: any) => {
122
+ const currentCallKey=callKey(event.sessionID,event.id);
123
+ if (admitted.get(currentCallKey) === false) return;
124
+ if (event.status !== 'completed' || event.tool !== 'delivery_finish') return;
125
+ const raw = event.result.content.filter((item: any) => item.type === 'text').map((item: any) => item.text).join('\n');
126
+ try {
127
+ const result = JSON.parse(raw);
128
+ if (result.status !== 'verified') event.result.content.push({ type:'text', text:'Benchmark verification policy: only verified status is accepted after the delivery extension checks.' });
129
+ } catch { event.result.content.push({ type:'text', text:'Benchmark verification policy: result shape was not recognized.' }); }
130
+ }));
131
+ } catch (error) {
132
+ await dispose();
133
+ throw error;
134
+ }
135
+ return dispose;
136
+ }
@@ -0,0 +1,24 @@
1
+ import { Plugin } from '@opencode/plugin';
2
+ import guard from '../guard.js';
3
+ import advisor from '../advisor.js';
4
+
5
+ export default Plugin.define({
6
+ id:'ctg.benchmark',
7
+ async setup(ctx) {
8
+ const cleanups: Array<() => Promise<void>> = [];
9
+ let disposed = false;
10
+ const dispose = async () => {
11
+ if (disposed) return;
12
+ disposed = true;
13
+ await Promise.allSettled(cleanups.reverse().map(cleanup => cleanup()));
14
+ };
15
+ try {
16
+ cleanups.push(await guard(ctx));
17
+ cleanups.push(await advisor(ctx));
18
+ } catch (error) {
19
+ await dispose();
20
+ throw error;
21
+ }
22
+ return dispose;
23
+ },
24
+ });
@@ -0,0 +1,47 @@
1
+ // Offline scripted provider: exercises actual installed pi plumbing, not model efficacy.
2
+ import test from 'node:test';
3
+ import assert from 'node:assert/strict';
4
+ import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import { runCandidate } from './runner.mjs';
8
+ import { initializeCampaign } from './campaign.mjs';
9
+ import { MODEL } from './gateway.mjs';
10
+ import { fixtureDesign, fixtureReview } from '../../tests/helpers/tested-design.mjs';
11
+
12
+ test('installed engine traverses discovery guide and full delivery with offline scripted responses', async t => {
13
+ const root=mkdtempSync(join(tmpdir(),'ctg-startup-engine-'));t.after(()=>rmSync(root,{recursive:true,force:true}));
14
+ const campaignPath=join(root,'campaign.json');initializeCampaign(campaignPath,{historicalCandidate:0,historicalSmoke:0});
15
+ const requirement='Writes the exact expected file';
16
+ const plan={goal:'Implement fixture output',assumptions:[],artifacts:['.'],outputs:['output.txt'],steps:[{id:'build',title:'Write and verify',checks:['output']}],acceptance:[{requirement,checks:['output']}],checks:[{id:'output',kind:'test',argv:[process.execPath,'-e',"require('node:assert/strict').equal(require('node:fs').readFileSync('output.txt','utf8'),'ok')"],timeoutSeconds:5}]};plan.design=fixtureDesign(plan);
17
+ const lastTool=payload=>{
18
+ const raw=payload.messages.filter(m=>m.role==='tool').at(-1)?.content;
19
+ return JSON.parse(typeof raw==='string'?raw:raw.map(b=>b.text||'').join('\n'));
20
+ };
21
+ const sequence=[...Array.from({length:8},()=>()=>({name:'read',args:{path:'TASK.md'}})),
22
+ ()=>({name:'delivery_design',args:{action:'guide'}}),
23
+ ()=>({name:'delivery_plan',args:plan}),
24
+ ()=>({name:'delivery_design',args:{action:'validate'}}),
25
+ ()=>({name:'delivery_design',args:{action:'inspect'}}),
26
+ p=>({name:'delivery_design',args:{action:'review',review:{...fixtureReview(plan),captureId:lastTool(p).result.id}}}),
27
+ ()=>({name:'delivery_design',args:{action:'approve'}}),
28
+ ()=>({name:'write',args:{path:'output.txt',content:'ok'}}),
29
+ ()=>({name:'delivery_check',args:{id:'all'}}),
30
+ ()=>({name:'delivery_progress',args:{step:'build',status:'done'}}),
31
+ ()=>({name:'delivery_review',args:{action:'inspect'}}),
32
+ p=>({name:'delivery_review',args:{action:'record',captureId:lastTool(p).id,coverage:[{requirement,assertions:'Exact file content asserted by Node strict equality'}],probes:['Declared output check exited zero'],findings:[],limitations:['Scripted fixture; no semantic review claim']}}),
33
+ ()=>({name:'delivery_finish',args:{status:'verified',review:'Reviewed fixture evidence',launch:'n/a',limitations:['Offline scripted provider']}}),
34
+ ];
35
+ let i=0,sawCheckpoint=false;
36
+ const r=await runCandidate('smoke','harness',{smoke:true,key:'dummy',root,campaignPath,promptOverride:'Implement a fixture output.txt containing exactly ok. Use the delivery contract and finish verified.',fetchImpl:async(_url,options)=>{
37
+ const payload=JSON.parse(options.body);sawCheckpoint ||= JSON.stringify(payload.messages).includes('Pre-plan checkpoint: 8 discovery');
38
+ const call=i<sequence.length?sequence[i++](payload):null;
39
+ return new Response('data: '+JSON.stringify({id:'offline',model:MODEL,choices:[{index:0,delta:{role:'assistant',content:call?'':'SMOKE_OK',...(call?{tool_calls:[{index:0,id:'call'+i,type:'function',function:{name:call.name,arguments:JSON.stringify(call.args)}}]}:{})},finish_reason:call?'tool_calls':'stop'}],usage:{cost:0,prompt_tokens:1,completion_tokens:1}})+'\n\ndata: [DONE]\n\n');
40
+ }});
41
+ assert.equal(r.smokePassed,true);assert.equal(i,sequence.length);assert.ok(sawCheckpoint);
42
+ const events=readFileSync(join(root,'smoke-harness/events.jsonl'),'utf8').trim().split('\n').map(JSON.parse);
43
+ assert.deepEqual(events.filter(e=>e.type==='tool_execution_end'&&e.isError),[]);
44
+ assert.equal(readFileSync(join(root,'smoke-harness/workspace/output.txt'),'utf8'),'ok');
45
+ const finish=events.findLast(e=>e.type==='tool_execution_end'&&e.toolName==='delivery_finish');
46
+ assert.match(JSON.stringify(finish.result),/verified/);
47
+ });
@@ -0,0 +1,31 @@
1
+ // Explicit paid opt-in only; all calls debit the original shared smoke allowance.
2
+ import { readFileSync,writeFileSync,mkdirSync,existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { startGateway,MODEL } from './gateway.mjs';
6
+ import { campaignAccount,initializeCampaign } from './campaign.mjs';
7
+ import { CAMPAIGN,ROOT,runCandidate } from './runner.mjs';
8
+ export async function livePreflight(){
9
+ if(!existsSync(CAMPAIGN))initializeCampaign(CAMPAIGN);
10
+ const dir=join(ROOT,'live-preflight');mkdirSync(ROOT,{recursive:true});mkdirSync(dir); // exclusive: preserve every attempt
11
+ const key=JSON.parse(readFileSync('/home/baissi/.pi/agent/auth.json','utf8')).openrouter.key;
12
+ const gateway=await startGateway({key,limit:.09,allowJev:true,outputTokens:32,account:campaignAccount(CAMPAIGN,'smoke',dir),receiptPath:join(dir,'gateway.jsonl')});
13
+ const checks=[];let failure;
14
+ try{
15
+ // Multiple calls exceed the earlier failing request counts. Two larger payloads
16
+ // exercise TLS record boundaries; output length is intentionally bounded.
17
+ for(let i=0;i<24;i++){
18
+ const content='Reply OK. The following repeated filler is inert data: '+ 'a'.repeat([8,19].includes(i)?60000:1000);
19
+ const r=await fetch(gateway.url+'/v1/chat/completions',{method:'POST',body:JSON.stringify({model:MODEL,messages:[{role:'user',content}]})});await r.text();
20
+ if(r.status!==200)throw Error(`Transport probe ${i} failed; no automatic retry`);checks.push({kind:'generation',index:i,passed:true});
21
+ }
22
+ const r=await fetch(gateway.url+'/jev',{method:'POST',body:JSON.stringify({state:{goal:'Transport readiness',acceptance:[],design:{components:[],ports:[],scenarios:[]}}})});await r.text();if(r.status!==200)throw Error('Jev connectivity failed');checks.push({kind:'jev',passed:true});
23
+ }catch(e){failure=e.message;}finally{await gateway.close();}
24
+ const result={passed:!failure,checks,failure,budget:gateway.budget,at:new Date().toISOString()};writeFileSync(join(dir,'result.json'),JSON.stringify(result,null,2));
25
+ if(failure)throw Error(failure);
26
+ const smoke=await runCandidate('smoke','plain',{smoke:true,key});
27
+ if(!smoke.smokePassed)throw Error('Live installed-engine smoke failed');
28
+ writeFileSync(join(dir,'readiness.json'),JSON.stringify({passed:true,transport:result,engineSmoke:smoke,limitations:['Finite live soak does not establish TLS root cause or guarantee future availability.']},null,2));
29
+ return result;
30
+ }
31
+ if(process.argv[1]===fileURLToPath(import.meta.url)){if(process.argv[2]!=='--paid')throw Error('Explicit --paid required');console.log(JSON.stringify(await livePreflight()));}
@@ -0,0 +1,22 @@
1
+ import { readdirSync,mkdirSync,writeFileSync,readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { command } from './eval-common.mjs';
5
+ import { ROOT } from './runner.mjs';
6
+ import { REPO,createFreeze,frozenFiles } from './freeze.mjs';
7
+ import { sha } from './runtime.mjs';
8
+ // No paid calls. Rebuild readiness after any code change. Output directory is exclusive.
9
+ export async function preflight(out){
10
+ mkdirSync(out,{recursive:false});
11
+ const files=Object.fromEntries(frozenFiles().map(f=>[f,sha(readFileSync(f))]));
12
+ const tests=readdirSync(join(REPO,'tools/benchmark')).filter(f=>f.endsWith('.test.mjs')).map(f=>'tools/benchmark/'+f);
13
+ const focused=await command(process.execPath,['--test',...tests,'tools/benchmark/selftest.mjs'],REPO,{},240000);writeFileSync(join(out,'focused.log'),focused.stdout+focused.stderr);
14
+ const quality=await command('npm',['run','quality'],REPO,{},240000);writeFileSync(join(out,'quality.log'),quality.stdout+quality.stderr);
15
+ const livePath=join(ROOT,'live-preflight/readiness.json');const live=JSON.parse(readFileSync(livePath,'utf8'));
16
+ const result={passed:focused.code===0&&!focused.error&&quality.code===0&&!quality.error&&live.passed===true,focused:{code:focused.code,error:focused.error},quality:{code:quality.code,error:quality.error},live:{path:livePath,sha256:sha(readFileSync(livePath))},files,at:new Date().toISOString()};
17
+ const path=join(out,'readiness.json');writeFileSync(path,JSON.stringify(result,null,2));if(!result.passed)throw Error('Readiness failed; inspect logs');return path;
18
+ }
19
+ if(process.argv[1]===fileURLToPath(import.meta.url)){
20
+ if(!process.argv[2])throw Error('Usage: node tools/benchmark/preflight.mjs NEW_OUTPUT_DIRECTORY [--freeze]');
21
+ const path=await preflight(process.argv[2]);if(process.argv[3]==='--freeze')createFreeze(ROOT,path);console.log(path);
22
+ }
@@ -0,0 +1,23 @@
1
+ // Explicit one-shot paid diagnostic. Never executes returned tools or resumes a candidate.
2
+ import{readFileSync,writeFileSync}from'node:fs';import{join}from'node:path';import{fileURLToPath}from'node:url';
3
+ import{curlFetch}from'./transport.mjs';import{upstreamError}from'./upstream-error.mjs';import{campaignAccount}from'./campaign.mjs';import{sha}from'./runtime.mjs';
4
+ export async function replay(dir){
5
+ const original=readFileSync(join(dir,'payload.json'),'utf8'),inspection=JSON.parse(readFileSync(join(dir,'inspection.json'),'utf8'));
6
+ if(!inspection.hashMatches||sha(original)!==inspection.originalHash)throw Error('Exact reconstruction required');
7
+ const path=join(dir,'replay.json');writeFileSync(path,JSON.stringify({status:'reserved-pending',at:new Date().toISOString()}),{flag:'wx',mode:0o600});
8
+ const payload=JSON.parse(original);payload.max_tokens=32;const wire=JSON.stringify(payload);
9
+ if(Buffer.byteLength(wire)>100000)throw Error('Diagnostic input exceeds reservation envelope');
10
+ const account=campaignAccount('/home/baissi/benchmarks/deepseek-jev-campaign.json','smoke',dir+'/one-shot-replay');const hold=account.reserve(.04);
11
+ const key=JSON.parse(readFileSync('/home/baissi/.pi/agent/auth.json','utf8')).openrouter.key;
12
+ const result={originalHash:sha(original),replayHash:sha(wire),changedFields:{max_tokens:{before:16384,after:32}},reserved:.04,hold,at:new Date().toISOString()};
13
+ try{
14
+ const response=await curlFetch('https://openrouter.ai/api/v1/chat/completions',{method:'POST',headers:{Authorization:`Bearer ${key}`,'Content-Type':'application/json'},body:wire,signal:AbortSignal.timeout(180000)});
15
+ result.httpStatus=response.status;
16
+ if(!response.ok){result.error=await upstreamError(response,{secrets:[key]});result.billing='unknown-hold-retained';}
17
+ else{const text=await response.text();let usage;for(const line of text.split('\n'))if(line.startsWith('data: {')){const c=JSON.parse(line.slice(6));if(c.usage)usage=c.usage;if(c.id)result.id=c.id;if(c.provider)result.provider=c.provider;if(c.model)result.model=c.model;}
18
+ if(!Number.isFinite(usage?.cost)||usage.cost<0||usage.cost>.04)throw Error('Unknown diagnostic billing');account.reconcile(hold,usage.cost);result.usage=usage;result.billing='reconciled';result.outputExecuted=false;
19
+ }
20
+ }catch(e){result.failure={name:e.name,code:e.code||null};result.billing='unknown-hold-retained';}
21
+ writeFileSync(path,JSON.stringify(result,null,2),{mode:0o600});return result;
22
+ }
23
+ if(process.argv[1]===fileURLToPath(import.meta.url)){if(process.argv[2]!=='--paid'||!process.argv[3])throw Error('Usage: replay-diagnostic.mjs --paid DIAGNOSTIC_DIR');console.log(JSON.stringify(await replay(process.argv[3])));}
@@ -0,0 +1,141 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdirSync, writeFileSync, existsSync, appendFileSync } from 'node:fs';
3
+ import { resolve, join, dirname } from 'node:path';
4
+ import { startGateway, MODEL } from './gateway.mjs';
5
+ import { TASKS, MATRIX } from './tasks.mjs';
6
+ import { safeEnv, provisionTools, ENGINE_RUNTIME, isolatedHome } from './runtime.mjs';
7
+ import { campaignAccount } from './campaign.mjs';
8
+ import { verifyFreeze } from './freeze.mjs';
9
+ import { runNativeEvaluation } from '../../lib/opencode/evaluation.mjs';
10
+ import { createServer } from 'node:http';
11
+ import { readFileSync } from 'node:fs';
12
+ import { fileURLToPath } from 'node:url';
13
+ import { ensureService, createSession, listPlugins, stopService } from '../../lib/opencode/service.mjs';
14
+ import { provisionRuntime } from '../../lib/opencode/runtime.mjs';
15
+
16
+ const REPO = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
17
+ export const ROOT = process.env.CTG_BENCHMARK_ROOT || join(REPO, '.harness/deepseek-jev-v4');
18
+ export const CAMPAIGN = process.env.CTG_BENCHMARK_CAMPAIGN || join(REPO, '.harness/deepseek-jev-campaign.json');
19
+ export function terminalStatus(events, exit, stopReason, budget, outcome = null) {
20
+ const last = events.filter(e=>e.type==='session.step.ended').at(-1)?.data;
21
+ const status = budget.stopped ? 'infrastructure-error' : stopReason ? stopReason : exit.code!==0 ? 'process-error' : outcome !== 'succeeded' ? 'non-successful-session-outcome' : !last ? 'missing-terminal-message' : last.finish==='error' ? 'model-error' : last.finish!=='stop' ? 'incomplete' : 'completed';
22
+ return { status, terminalModelReason:last?.finish || null, terminalError:last?.error?.message || null, outcome, finalText:'' };
23
+ }
24
+ export function childEnv(config, gateway, log) {
25
+ const home = isolatedHome(config);
26
+ return safeEnv({ CTG_OPENCODE_HOME: home, CTG_OPENCODE_URL: process.env.CTG_OPENCODE_URL || '', BENCH_GATEWAY: gateway, BENCH_ADVISOR_LOG: log, BENCH_GUARD_LOG: join(dirname(config),'guard.jsonl') }, home);
27
+ }
28
+ async function waitForService(url, { timeoutMs = 30000, signal } = {}) {
29
+ const deadline = Date.now() + timeoutMs;
30
+ while (Date.now() < deadline && !signal?.aborted) {
31
+ try { const response = await fetch(`${url}/api/info`, { signal: AbortSignal.timeout(1000) }); if (response.ok) return; } catch {}
32
+ await new Promise(resolve => setTimeout(resolve, 150));
33
+ }
34
+ throw Error('Isolated OpenCode 2 service readiness timeout');
35
+ }
36
+ async function startFixtureService(directory) {
37
+ const server = createServer((req,res) => {
38
+ if (req.url === '/api/info') { res.writeHead(200,{'content-type':'application/json'}); res.end(JSON.stringify({version:'2.0.32'})); return; }
39
+ res.writeHead(404); res.end();
40
+ });
41
+ await new Promise(resolve => server.listen(0,'127.0.0.1',resolve));
42
+ const url = `http://127.0.0.1:${server.address().port}`;
43
+ return { url, close: () => new Promise(resolve => server.close(resolve)), directory };
44
+ }
45
+ async function startNativeService({ workspace, config, mode, arm, smoke, toolLimit, gateway, apiKey, serviceManager }) {
46
+ const home = isolatedHome(config), configDir = join(home,'.config','opencode');
47
+ mkdirSync(configDir,{recursive:true});
48
+ const provider = arm === 'plain' ? 'benchmark-fixture' : 'benchmark';
49
+ const model = arm === 'plain' ? 'fixture' : MODEL;
50
+ const plugins = [join(REPO,'lib/opencode/evaluation-budget-plugin'), ...(arm === 'plain' ? [] : [join(REPO,'tools/benchmark/native-plugin')])];
51
+ const configJson = {
52
+ '$schema':'https://opencode.ai/config.json', update:'disable', model:`${provider}/${model}`,
53
+ plugins:plugins.filter((_,index)=>mode!=='smoke' || index===0),
54
+ providers:{[provider]:{
55
+ package:'@opencode/ai/providers/openai-compatible',
56
+ settings:{baseURL:`${gateway}/v1`,apiKey:apiKey || 'fixture-no-key'},
57
+ models:{[model]:{limit:{context:100000,output:16384},cost:{input:.3,output:1.2,cacheRead:.006,cacheWrite:0}}},
58
+ }},
59
+ };
60
+ await provisionRuntime({ root:home, profile:configJson });
61
+ const service=await serviceManager.ensureService({root:home,env:safeEnv({
62
+ BENCH_TOOL_LIMIT:String(toolLimit),BENCH_GUARD_LOG:join(dirname(config),'guard.jsonl'),
63
+ BENCH_GATEWAY:gateway,BENCH_ADVISOR_LOG:join(dirname(config),'advisor.jsonl'),
64
+ CTG_EVALUATION_MAX_TURNS:String(toolLimit),CTG_EVALUATION_MAX_COST:String(smoke ? .08 : 2.79),
65
+ OPENROUTER_API_KEY:apiKey || '',
66
+ },home)});
67
+ const endpoint=service.url,auth=service.headers;
68
+ const request={location:{directory:workspace},signal:AbortSignal.timeout(15000)};
69
+ try {
70
+ if (configJson.plugins.length) {
71
+ await createSession(service,workspace,{model:{providerID:provider,id:model},title:'native plugin activation probe',signal:request.signal});
72
+ const deadline=Date.now()+15000;
73
+ while(Date.now()<deadline){
74
+ const loaded=await listPlugins(service,request).catch(()=>null);
75
+ const expected = ['ctg.evaluation-budget', ...(arm === 'plain' ? [] : ['ctg.benchmark'])];
76
+ const active = expected.every(id=>loaded?.some(plugin=>plugin.id===id&&plugin.state?.status==='active'));
77
+ const failure=loaded?.find(plugin=>expected.includes(plugin.id)&&plugin.state?.status==='failed');
78
+ if(active) break;
79
+ if(failure) throw Error(`Native OpenCode plugin failed to activate: ${JSON.stringify(failure)}`);
80
+ if(Date.now()+100>=deadline) throw Error(`Native benchmark plugin did not become active: ${JSON.stringify(loaded)}`);
81
+ await new Promise(resolve=>setTimeout(resolve,100));
82
+ }
83
+ }
84
+ return {endpoint,auth,version:'2.0.32',close:()=>serviceManager.stopService({root:home})};
85
+ } catch(error) {
86
+ await serviceManager.stopService({root:home}).catch(()=>{});
87
+ throw error;
88
+ }
89
+ }
90
+ export async function runCandidate(task, arm, { smoke = false, key, root = ROOT, campaignPath = CAMPAIGN, fetchImpl, promptOverride, toolLimit = 120, nativeRunner = runNativeEvaluation, serviceManager = { ensureService, stopService } } = {}) {
91
+ if ((!TASKS[task] && !smoke) || !['plain','harness','jev'].includes(arm)) throw Error('Invalid run');
92
+ if (process.env.CTG_OPENCODE_URL) throw Error('CTG_OPENCODE_URL is not accepted by the benchmark: service ownership and authentication must be established by this runner.');
93
+ if (!smoke) { if (promptOverride || toolLimit !== 120 || fetchImpl) throw Error('Candidate overrides forbidden'); verifyFreeze(root); }
94
+ const apiKey = key || process.env.OPENROUTER_API_KEY;
95
+ if (!apiKey && !nativeRunner.fixture) throw Error('Set OPENROUTER_API_KEY explicitly; credential discovery is disabled.');
96
+ const run = join(root, smoke ? `smoke-${arm}` : `${task}-${arm}`), workspace = join(run, 'workspace'), config = join(run, 'config');
97
+ if (existsSync(run)) throw Error(`Run exists; refusing overwrite: ${run}`);
98
+ mkdirSync(root, { recursive: true }); mkdirSync(run); mkdirSync(workspace); mkdirSync(config);
99
+ if (!Number.isInteger(toolLimit) || toolLimit < 1 || toolLimit > 120) throw Error('Invalid tool limit');
100
+ const toolsReceipt = provisionTools(config);
101
+ const gateway = await startGateway({ key: apiKey || 'fixture-only-no-upstream', limit: smoke ? 0.08 : 2.79, allowJev: arm === 'jev', receiptPath: join(run, 'gateway.jsonl'), fetchImpl, account: campaignAccount(campaignPath, smoke ? 'smoke' : 'candidate', run) });
102
+ let fixtureService, nativeService;
103
+ try {
104
+ const env = childEnv(config, gateway.url, join(run,'advisor.jsonl'));
105
+ const prompt = promptOverride || (smoke ? 'Infrastructure smoke only. Read TASK.md and report SMOKE_OK. Do not create a plan or change files.' : TASKS[task]);
106
+ writeFileSync(join(workspace, 'TASK.md'), prompt);
107
+ writeFileSync(join(workspace, '.gitignore'), 'node_modules/\ntarget/\nartifacts/\n.harness/\ndata/\n');
108
+ for (const args of [['init','-q'],['add','.'],['-c','user.name=Benchmark','-c','user.email=benchmark@localhost','commit','-qm','Frozen task']]) {
109
+ const result = spawnSync('git', args, { cwd: workspace, env }); if (result.status) throw Error('Git initialization failed');
110
+ }
111
+ if (nativeRunner.fixture) fixtureService = await startFixtureService(workspace);
112
+ else if (!process.env.CTG_OPENCODE_URL) nativeService = await startNativeService({workspace,config,mode:arm,arm,smoke,toolLimit,gateway:gateway.url,apiKey,serviceManager});
113
+ env.CTG_OPENCODE_URL = process.env.CTG_OPENCODE_URL || nativeService?.endpoint || fixtureService?.url;
114
+ env.CTG_OPENCODE_AUTHORIZATION = nativeService ? nativeService.auth.authorization : '';
115
+ const args = { task, arm, smoke, runtime: ENGINE_RUNTIME, openCodeService: nativeService ? 'dedicated-isolated-service' : process.env.CTG_OPENCODE_URL ? 'explicit-isolated-endpoint' : 'injected-fixture', toolsReceipt, harnessCommit: spawnSync('git',['rev-parse','HEAD'],{cwd:REPO,encoding:'utf8'}).stdout.trim(), ceilings: { seconds: smoke ? 180 : 900, tools:toolLimit, usd:smoke ? .08 : 2.79 }, startedAt:new Date().toISOString() };
116
+ writeFileSync(join(run,'invocation.json'), JSON.stringify(args,null,2));
117
+ let stopReason = null; const events = [], started = Date.now();
118
+ const controller = new AbortController();
119
+ const timer = setTimeout(() => { stopReason = 'wall-time'; controller.abort(); }, (smoke ? 180 : 900) * 1000);
120
+ let result;
121
+ try { await waitForService(env.CTG_OPENCODE_URL,{signal:controller.signal}); result = await nativeRunner({ directory: workspace, prompt, systemPrompt: arm === 'plain' ? '' : `Use the Carthagent delivery workflow. Required checks: ${JSON.stringify({ toolLimit })}.`, provider:arm === 'plain' ? 'benchmark-fixture' : 'benchmark', model:arm === 'plain' ? 'fixture' : MODEL, timeoutSeconds: smoke ? 180 : 900, maxTurns: toolLimit, maxCost: smoke ? .08 : 2.79, signal: controller.signal, baseUrl: env.CTG_OPENCODE_URL, headers:nativeService?.auth || {}, onEvent: event => { events.push(event); appendFileSync(join(run,'events.jsonl'),JSON.stringify(event)+'\n'); } }); }
122
+ catch (error) { result = { exitCode:1, errors:[error.message], turns:0, productiveTurns:0, tools:{} }; }
123
+ finally { clearTimeout(timer); }
124
+ if (result.outcome !== 'succeeded') {
125
+ result.exitCode = 1;
126
+ result.budgetReason ||= 'missing terminal session outcome';
127
+ result.errors = [...(result.errors || []), 'Native run cannot be successful without outcome=succeeded from session.get().'];
128
+ }
129
+ const classification = terminalStatus(events,{code:result.exitCode},stopReason,gateway.budget,result.outcome);
130
+ if (result.errors?.some(error => /usage|cost/i.test(error)) || result.budgetReason === 'unknown usage; fail-closed') gateway.budget.stopped = 'unknown-or-excess-billing';
131
+ const guardRows=existsSync(join(run,'guard.jsonl'))?readFileSync(join(run,'guard.jsonl'),'utf8').trim().split('\n').filter(Boolean).map(JSON.parse):[];
132
+ const final = { task,arm,smoke,...classification,status:classification.status,smokePassed:smoke ? result.exitCode===0 && result.outcome==='succeeded' && !result.budgetReason : undefined, stopReason:result.budgetReason || stopReason, outcome:result.outcome||null, interruptionStatus:result.interruptionStatus||'unknown', terminalAuthority:'session.outcome', costScope:'session', usageSource:'session.get', toolStarts:Object.values(result.tools||{}).reduce((a,b)=>a+b,0), toolAdmittedByBudget:guardRows.filter(row=>!row.blocked).length, toolBlockedByBudget:guardRows.filter(row=>row.blocked).length, wallMs:Date.now()-started,budget:gateway.budget,runtime:ENGINE_RUNTIME,openCodeVersion:result.opencodeVersion||nativeService?.version||'2.0.32',productiveTurns:result.productiveTurns||0, productiveTurnEvidence:'paginated-session-transcript',reportedCost:result.cost,finishedAt:new Date().toISOString() };
133
+ writeFileSync(join(run,'result.json'),JSON.stringify(final,null,2)); console.log(JSON.stringify(final)); return final;
134
+ } finally { await nativeService?.close(); await fixtureService?.close(); await gateway.close(); }
135
+ }
136
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
137
+ const key = process.env.OPENROUTER_API_KEY;
138
+ const [task,arm]=process.argv.slice(2);
139
+ if(task==='matrix') { verifyFreeze(ROOT); for(const [t,a] of MATRIX) { const result = await runCandidate(t,a,{key}); if(['infrastructure-error','process-error','missing-terminal-message','model-error'].includes(result.status)) throw Error('Matrix stopped on infrastructure/billing failure; retain results and diagnose before any further runs.'); } }
140
+ else await runCandidate(task,arm,{key,smoke:task==='smoke'});
141
+ }