@penclipai/server 2026.426.0 → 2026.505.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 (253) hide show
  1. package/dist/adapters/builtin-adapter-types.d.ts.map +1 -1
  2. package/dist/adapters/builtin-adapter-types.js +3 -0
  3. package/dist/adapters/builtin-adapter-types.js.map +1 -1
  4. package/dist/adapters/index.d.ts +2 -2
  5. package/dist/adapters/index.d.ts.map +1 -1
  6. package/dist/adapters/index.js +1 -1
  7. package/dist/adapters/index.js.map +1 -1
  8. package/dist/adapters/registry.d.ts +2 -1
  9. package/dist/adapters/registry.d.ts.map +1 -1
  10. package/dist/adapters/registry.js +76 -6
  11. package/dist/adapters/registry.js.map +1 -1
  12. package/dist/adapters/types.d.ts +1 -1
  13. package/dist/adapters/types.d.ts.map +1 -1
  14. package/dist/adapters/utils.d.ts.map +1 -1
  15. package/dist/adapters/utils.js +2 -1
  16. package/dist/adapters/utils.js.map +1 -1
  17. package/dist/attachment-types.d.ts +1 -16
  18. package/dist/attachment-types.d.ts.map +1 -1
  19. package/dist/attachment-types.js +7 -0
  20. package/dist/attachment-types.js.map +1 -1
  21. package/dist/auth/better-auth.d.ts +3 -1
  22. package/dist/auth/better-auth.d.ts.map +1 -1
  23. package/dist/auth/better-auth.js +8 -2
  24. package/dist/auth/better-auth.js.map +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +27 -13
  27. package/dist/index.js.map +1 -1
  28. package/dist/middleware/auth.d.ts.map +1 -1
  29. package/dist/middleware/auth.js +143 -2
  30. package/dist/middleware/auth.js.map +1 -1
  31. package/dist/onboarding-assets/ceo/AGENTS.md +1 -1
  32. package/dist/onboarding-assets/ceo/HEARTBEAT.md +5 -5
  33. package/dist/redaction.d.ts.map +1 -1
  34. package/dist/redaction.js +30 -12
  35. package/dist/redaction.js.map +1 -1
  36. package/dist/routes/access.d.ts.map +1 -1
  37. package/dist/routes/access.js +10 -0
  38. package/dist/routes/access.js.map +1 -1
  39. package/dist/routes/activity.d.ts.map +1 -1
  40. package/dist/routes/activity.js +4 -2
  41. package/dist/routes/activity.js.map +1 -1
  42. package/dist/routes/adapters.d.ts.map +1 -1
  43. package/dist/routes/adapters.js +1 -0
  44. package/dist/routes/adapters.js.map +1 -1
  45. package/dist/routes/agents.d.ts.map +1 -1
  46. package/dist/routes/agents.js +317 -56
  47. package/dist/routes/agents.js.map +1 -1
  48. package/dist/routes/costs.d.ts.map +1 -1
  49. package/dist/routes/costs.js +21 -2
  50. package/dist/routes/costs.js.map +1 -1
  51. package/dist/routes/instance-settings.d.ts.map +1 -1
  52. package/dist/routes/instance-settings.js +37 -2
  53. package/dist/routes/instance-settings.js.map +1 -1
  54. package/dist/routes/issue-tree-control.d.ts.map +1 -1
  55. package/dist/routes/issue-tree-control.js +3 -1
  56. package/dist/routes/issue-tree-control.js.map +1 -1
  57. package/dist/routes/issues.d.ts.map +1 -1
  58. package/dist/routes/issues.js +257 -32
  59. package/dist/routes/issues.js.map +1 -1
  60. package/dist/routes/projects.d.ts.map +1 -1
  61. package/dist/routes/projects.js +10 -3
  62. package/dist/routes/projects.js.map +1 -1
  63. package/dist/routes/routines.d.ts.map +1 -1
  64. package/dist/routes/routines.js +6 -1
  65. package/dist/routes/routines.js.map +1 -1
  66. package/dist/routes/workspace-command-authz.d.ts +1 -1
  67. package/dist/routes/workspace-command-authz.d.ts.map +1 -1
  68. package/dist/routes/workspace-command-authz.js +2 -2
  69. package/dist/routes/workspace-command-authz.js.map +1 -1
  70. package/dist/runtime-api.d.ts +4 -0
  71. package/dist/runtime-api.d.ts.map +1 -1
  72. package/dist/runtime-api.js +38 -10
  73. package/dist/runtime-api.js.map +1 -1
  74. package/dist/services/companies.d.ts +6 -0
  75. package/dist/services/companies.d.ts.map +1 -1
  76. package/dist/services/companies.js +1 -0
  77. package/dist/services/companies.js.map +1 -1
  78. package/dist/services/company-portability.d.ts.map +1 -1
  79. package/dist/services/company-portability.js +16 -15
  80. package/dist/services/company-portability.js.map +1 -1
  81. package/dist/services/costs.d.ts +9 -0
  82. package/dist/services/costs.d.ts.map +1 -1
  83. package/dist/services/costs.js +45 -1
  84. package/dist/services/costs.js.map +1 -1
  85. package/dist/services/environment-execution-target.d.ts.map +1 -1
  86. package/dist/services/environment-execution-target.js +7 -13
  87. package/dist/services/environment-execution-target.js.map +1 -1
  88. package/dist/services/environment-run-orchestrator.d.ts.map +1 -1
  89. package/dist/services/environment-run-orchestrator.js +56 -0
  90. package/dist/services/environment-run-orchestrator.js.map +1 -1
  91. package/dist/services/environment-runtime.d.ts +2 -0
  92. package/dist/services/environment-runtime.d.ts.map +1 -1
  93. package/dist/services/environment-runtime.js +80 -39
  94. package/dist/services/environment-runtime.js.map +1 -1
  95. package/dist/services/heartbeat-stop-metadata.d.ts +2 -1
  96. package/dist/services/heartbeat-stop-metadata.d.ts.map +1 -1
  97. package/dist/services/heartbeat-stop-metadata.js +10 -1
  98. package/dist/services/heartbeat-stop-metadata.js.map +1 -1
  99. package/dist/services/heartbeat-stop-metadata.test.js +24 -0
  100. package/dist/services/heartbeat-stop-metadata.test.js.map +1 -1
  101. package/dist/services/heartbeat.d.ts +156 -5
  102. package/dist/services/heartbeat.d.ts.map +1 -1
  103. package/dist/services/heartbeat.js +1384 -112
  104. package/dist/services/heartbeat.js.map +1 -1
  105. package/dist/services/index.d.ts +1 -0
  106. package/dist/services/index.d.ts.map +1 -1
  107. package/dist/services/index.js +1 -0
  108. package/dist/services/index.js.map +1 -1
  109. package/dist/services/instance-settings.d.ts.map +1 -1
  110. package/dist/services/instance-settings.js +4 -1
  111. package/dist/services/instance-settings.js.map +1 -1
  112. package/dist/services/issue-execution-policy.d.ts +56 -1
  113. package/dist/services/issue-execution-policy.d.ts.map +1 -1
  114. package/dist/services/issue-execution-policy.js +400 -2
  115. package/dist/services/issue-execution-policy.js.map +1 -1
  116. package/dist/services/issue-thread-interactions.d.ts +5 -1
  117. package/dist/services/issue-thread-interactions.d.ts.map +1 -1
  118. package/dist/services/issue-thread-interactions.js +44 -1
  119. package/dist/services/issue-thread-interactions.js.map +1 -1
  120. package/dist/services/issue-tree-control.d.ts +1 -0
  121. package/dist/services/issue-tree-control.d.ts.map +1 -1
  122. package/dist/services/issue-tree-control.js +84 -4
  123. package/dist/services/issue-tree-control.js.map +1 -1
  124. package/dist/services/issues.d.ts +10 -1
  125. package/dist/services/issues.d.ts.map +1 -1
  126. package/dist/services/issues.js +452 -48
  127. package/dist/services/issues.js.map +1 -1
  128. package/dist/services/plugin-environment-driver.d.ts +4 -0
  129. package/dist/services/plugin-environment-driver.d.ts.map +1 -1
  130. package/dist/services/plugin-environment-driver.js +18 -1
  131. package/dist/services/plugin-environment-driver.js.map +1 -1
  132. package/dist/services/productivity-review.d.ts +83 -0
  133. package/dist/services/productivity-review.d.ts.map +1 -0
  134. package/dist/services/productivity-review.js +650 -0
  135. package/dist/services/productivity-review.js.map +1 -0
  136. package/dist/services/recovery/index.d.ts +1 -1
  137. package/dist/services/recovery/index.d.ts.map +1 -1
  138. package/dist/services/recovery/index.js +1 -1
  139. package/dist/services/recovery/index.js.map +1 -1
  140. package/dist/services/recovery/issue-graph-liveness.d.ts +13 -1
  141. package/dist/services/recovery/issue-graph-liveness.d.ts.map +1 -1
  142. package/dist/services/recovery/issue-graph-liveness.js +212 -92
  143. package/dist/services/recovery/issue-graph-liveness.js.map +1 -1
  144. package/dist/services/recovery/origins.d.ts +2 -0
  145. package/dist/services/recovery/origins.d.ts.map +1 -1
  146. package/dist/services/recovery/origins.js +4 -0
  147. package/dist/services/recovery/origins.js.map +1 -1
  148. package/dist/services/recovery/run-liveness-continuations.d.ts.map +1 -1
  149. package/dist/services/recovery/run-liveness-continuations.js.map +1 -1
  150. package/dist/services/recovery/service.d.ts +20 -2
  151. package/dist/services/recovery/service.d.ts.map +1 -1
  152. package/dist/services/recovery/service.js +405 -63
  153. package/dist/services/recovery/service.js.map +1 -1
  154. package/dist/services/routines.d.ts +5 -2
  155. package/dist/services/routines.d.ts.map +1 -1
  156. package/dist/services/routines.js +47 -3
  157. package/dist/services/routines.js.map +1 -1
  158. package/dist/worktree-config.d.ts.map +1 -1
  159. package/dist/worktree-config.js +2 -5
  160. package/dist/worktree-config.js.map +1 -1
  161. package/package.json +16 -15
  162. package/skills/diagnose-why-work-stopped/SKILL.md +161 -0
  163. package/skills/paperclip/SKILL.md +37 -26
  164. package/skills/paperclip/references/api-reference.md +6 -2
  165. package/skills/paperclip-converting-plans-to-tasks/SKILL.md +42 -0
  166. package/skills/paperclip-create-agent/SKILL.md +3 -2
  167. package/skills/paperclip-create-agent/references/agent-instruction-templates.md +1 -1
  168. package/skills/paperclip-create-agent/references/api-reference.md +7 -2
  169. package/skills/paperclip-create-agent/references/baseline-role-guide.md +1 -1
  170. package/skills/paperclip-create-agent/references/draft-review-checklist.md +2 -2
  171. package/skills/paperclip-dev/SKILL.md +267 -0
  172. package/skills/terminal-bench-loop/SKILL.md +236 -0
  173. package/ui-dist/assets/{_basePickBy-BRqa7PJ5.js → _basePickBy-BS0Fg_DB.js} +1 -1
  174. package/ui-dist/assets/{_baseUniq-DhE2yrXC.js → _baseUniq-Dtnt_4SE.js} +1 -1
  175. package/ui-dist/assets/{arc-7qnikTQ3.js → arc-BCoOPxh5.js} +1 -1
  176. package/ui-dist/assets/{architectureDiagram-VXUJARFQ-CH0wVUOM.js → architectureDiagram-VXUJARFQ-C6eX2QUo.js} +1 -1
  177. package/ui-dist/assets/{blockDiagram-VD42YOAC-CeeRyJQX.js → blockDiagram-VD42YOAC-aUueUD4B.js} +1 -1
  178. package/ui-dist/assets/browser-ponyfill-BlAfsWm_.js +2 -0
  179. package/ui-dist/assets/{c4Diagram-YG6GDRKO-C_cV0CGo.js → c4Diagram-YG6GDRKO-CfPWRlOF.js} +1 -1
  180. package/ui-dist/assets/channel-ChNSCFJf.js +1 -0
  181. package/ui-dist/assets/{chunk-4BX2VUAB-DQ6pxPVT.js → chunk-4BX2VUAB-BTD1apA4.js} +1 -1
  182. package/ui-dist/assets/{chunk-55IACEB6-L8pS0IoX.js → chunk-55IACEB6-BXXF_ClN.js} +1 -1
  183. package/ui-dist/assets/{chunk-B4BG7PRW-BZKGE88E.js → chunk-B4BG7PRW-hAZeWGP8.js} +1 -1
  184. package/ui-dist/assets/{chunk-DI55MBZ5-CefSoZ_K.js → chunk-DI55MBZ5-cOH3UoEl.js} +1 -1
  185. package/ui-dist/assets/{chunk-FMBD7UC4-Bc3qTTHB.js → chunk-FMBD7UC4-Cu2yZOcl.js} +1 -1
  186. package/ui-dist/assets/{chunk-QN33PNHL-CjWBr5bI.js → chunk-QN33PNHL-0DNN5aRU.js} +1 -1
  187. package/ui-dist/assets/{chunk-QZHKN3VN-C0JUdmmz.js → chunk-QZHKN3VN-B9_bhK2n.js} +1 -1
  188. package/ui-dist/assets/{chunk-TZMSLE5B-D4d4I82z.js → chunk-TZMSLE5B-Cr5xwxio.js} +1 -1
  189. package/ui-dist/assets/classDiagram-2ON5EDUG-4aK1QZU3.js +1 -0
  190. package/ui-dist/assets/classDiagram-v2-WZHVMYZB-4aK1QZU3.js +1 -0
  191. package/ui-dist/assets/clone-C8lk5Qbc.js +1 -0
  192. package/ui-dist/assets/{cose-bilkent-S5V4N54A-B09h9XGZ.js → cose-bilkent-S5V4N54A-6_Dw6gpQ.js} +1 -1
  193. package/ui-dist/assets/{dagre-6UL2VRFP-CA02PXuX.js → dagre-6UL2VRFP-CFBhlh5H.js} +1 -1
  194. package/ui-dist/assets/{diagram-PSM6KHXK-DaT9cnrY.js → diagram-PSM6KHXK-C88ftcah.js} +1 -1
  195. package/ui-dist/assets/{diagram-QEK2KX5R-Drwc3gBw.js → diagram-QEK2KX5R-9EUupcuH.js} +1 -1
  196. package/ui-dist/assets/{diagram-S2PKOQOG-CpsGCaT6.js → diagram-S2PKOQOG-Dsml0wWh.js} +1 -1
  197. package/ui-dist/assets/{erDiagram-Q2GNP2WA-CVkBh9TY.js → erDiagram-Q2GNP2WA-sM-XdfHS.js} +1 -1
  198. package/ui-dist/assets/{flowDiagram-NV44I4VS-De9sXvPR.js → flowDiagram-NV44I4VS-qll7oaoW.js} +1 -1
  199. package/ui-dist/assets/{ganttDiagram-JELNMOA3-CSFa0gXS.js → ganttDiagram-JELNMOA3-VWnJMcjC.js} +1 -1
  200. package/ui-dist/assets/{gitGraphDiagram-V2S2FVAM-DEJaChxa.js → gitGraphDiagram-V2S2FVAM-DFnocrfl.js} +1 -1
  201. package/ui-dist/assets/{graph-D2R4DCtu.js → graph-nq3Qye4Z.js} +1 -1
  202. package/ui-dist/assets/{index-DEG-9CFs.js → index-3Owzaheh.js} +1 -1
  203. package/ui-dist/assets/{index-DHnKx9xX.js → index-B2A-a635.js} +1 -1
  204. package/ui-dist/assets/{index-C1I0SGDm.js → index-BGFrRiqa.js} +1 -1
  205. package/ui-dist/assets/{index-B44EtLRv.js → index-BVC5UhRK.js} +1 -1
  206. package/ui-dist/assets/{index-C_dAXwxT.js → index-BrP1U_Hy.js} +1 -1
  207. package/ui-dist/assets/{index-flZjKn_n.js → index-CXXHGqM8.js} +1 -1
  208. package/ui-dist/assets/{index-ssM_UKPW.js → index-CgyPAauR.js} +1 -1
  209. package/ui-dist/assets/{index-Ct1AraKR.js → index-CksQ4Ytv.js} +1 -1
  210. package/ui-dist/assets/{index-DQ6I_vpd.js → index-CrNzj2vZ.js} +1 -1
  211. package/ui-dist/assets/{index-DzZID5RY.js → index-CxbZBH3M.js} +1 -1
  212. package/ui-dist/assets/{index-Cn6_RRY5.js → index-D-dSSrf-.js} +1 -1
  213. package/ui-dist/assets/{index-CVa2OHgx.js → index-D6uZ_7Vh.js} +1 -1
  214. package/ui-dist/assets/{index-BzjWQd50.js → index-D7JGmxas.js} +1 -1
  215. package/ui-dist/assets/{index-CnT1_9UF.js → index-DDqO9GAq.js} +1 -1
  216. package/ui-dist/assets/index-DEUtmlPm.js +513 -0
  217. package/ui-dist/assets/{index-D2fEhyQg.js → index-DF5RDSoK.js} +1 -1
  218. package/ui-dist/assets/{index-CZGNe8K3.js → index-DfI92epU.js} +1 -1
  219. package/ui-dist/assets/{index-ByamXtyB.js → index-Dukb9MDQ.js} +1 -1
  220. package/ui-dist/assets/index-HP73_6Vr.css +1 -0
  221. package/ui-dist/assets/{index-BJS4rvUh.js → index-NXDTW2n4.js} +1 -1
  222. package/ui-dist/assets/{index-Bad5Hy7e.js → index-SxPPG9ig.js} +1 -1
  223. package/ui-dist/assets/{index-CC51mhhA.js → index-lC4Yz3Gw.js} +1 -1
  224. package/ui-dist/assets/{index-BFzkl36p.js → index-q2RXGI2V.js} +1 -1
  225. package/ui-dist/assets/{index-40icqWwg.js → index-qjfdrS96.js} +1 -1
  226. package/ui-dist/assets/{infoDiagram-HS3SLOUP-CJcjzWkM.js → infoDiagram-HS3SLOUP-CTrK5xoS.js} +1 -1
  227. package/ui-dist/assets/{journeyDiagram-XKPGCS4Q-ByITI00s.js → journeyDiagram-XKPGCS4Q-YFC7FykG.js} +1 -1
  228. package/ui-dist/assets/{kanban-definition-3W4ZIXB7-DvEjKke-.js → kanban-definition-3W4ZIXB7-B3dlyva0.js} +1 -1
  229. package/ui-dist/assets/{layout-CZcd66hi.js → layout-DefunPTK.js} +1 -1
  230. package/ui-dist/assets/{linear-jTUy3iHu.js → linear-CIPvzeMv.js} +1 -1
  231. package/ui-dist/assets/{mermaid.core-DECSZPbJ.js → mermaid.core-zKYhmnnR.js} +4 -4
  232. package/ui-dist/assets/{mindmap-definition-VGOIOE7T-Twtu17_c.js → mindmap-definition-VGOIOE7T-BlU-ebRa.js} +1 -1
  233. package/ui-dist/assets/{pieDiagram-ADFJNKIX-DlbgZ010.js → pieDiagram-ADFJNKIX-Ceto4LXH.js} +1 -1
  234. package/ui-dist/assets/{quadrantDiagram-AYHSOK5B-CMAa3qAT.js → quadrantDiagram-AYHSOK5B-C6M6hkuE.js} +1 -1
  235. package/ui-dist/assets/{requirementDiagram-UZGBJVZJ-CXRTfJOe.js → requirementDiagram-UZGBJVZJ-B-bcG938.js} +1 -1
  236. package/ui-dist/assets/{sankeyDiagram-TZEHDZUN-DeyO4fer.js → sankeyDiagram-TZEHDZUN-CIqty6Qi.js} +1 -1
  237. package/ui-dist/assets/{sequenceDiagram-WL72ISMW-Ch8wlJIL.js → sequenceDiagram-WL72ISMW-CIt2R5tk.js} +1 -1
  238. package/ui-dist/assets/{stateDiagram-FKZM4ZOC-BgL_AAl9.js → stateDiagram-FKZM4ZOC-BC1RFlfg.js} +1 -1
  239. package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-Iy6tYSSw.js +1 -0
  240. package/ui-dist/assets/{timeline-definition-IT6M3QCI-D1QWd7TQ.js → timeline-definition-IT6M3QCI-DZqvoU94.js} +1 -1
  241. package/ui-dist/assets/{treemap-GDKQZRPO-B5RkmUv8.js → treemap-GDKQZRPO-CSeKauwA.js} +1 -1
  242. package/ui-dist/assets/{xychartDiagram-PRI3JC2R-WtDhjZfk.js → xychartDiagram-PRI3JC2R-Ut3mCiEd.js} +1 -1
  243. package/ui-dist/index.html +2 -2
  244. package/ui-dist/locales/en/common.json +137 -1
  245. package/ui-dist/locales/zh-CN/common.json +111 -1
  246. package/ui-dist/assets/browser-ponyfill-Ct3hGqsr.js +0 -2
  247. package/ui-dist/assets/channel-pHFjGZL-.js +0 -1
  248. package/ui-dist/assets/classDiagram-2ON5EDUG-X4ZksqXl.js +0 -1
  249. package/ui-dist/assets/classDiagram-v2-WZHVMYZB-X4ZksqXl.js +0 -1
  250. package/ui-dist/assets/clone-DZzimpfG.js +0 -1
  251. package/ui-dist/assets/index-C1oE3J7o.css +0 -1
  252. package/ui-dist/assets/index-fSIlEIHr.js +0 -510
  253. package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-gnLzrhSv.js +0 -1
@@ -3,7 +3,7 @@ import { generateKeyPairSync, randomUUID } from "node:crypto";
3
3
  import path from "node:path";
4
4
  import { agents as agentsTable, companies, heartbeatRuns, issues as issuesTable } from "@penclipai/db";
5
5
  import { and, desc, eq, inArray, not, sql } from "drizzle-orm";
6
- import { agentSkillSyncSchema, agentMineInboxQuerySchema, AGENT_DEFAULT_MAX_CONCURRENT_RUNS, createAgentKeySchema, createAgentHireSchema, createAgentSchema, deriveAgentUrlKey, isUuidLike, resetAgentSessionSchema, testAdapterEnvironmentSchema, upsertAgentInstructionsFileSchema, updateAgentInstructionsBundleSchema, updateAgentPermissionsSchema, updateAgentInstructionsPathSchema, wakeAgentSchema, updateAgentSchema, supportedEnvironmentDriversForAdapter, } from "@penclipai/shared";
6
+ import { agentSkillSyncSchema, agentMineInboxQuerySchema, AGENT_DEFAULT_MAX_CONCURRENT_RUNS, createAgentKeySchema, createAgentHireSchema, createAgentSchema, deriveAgentUrlKey, isUuidLike, normalizeIssueIdentifier, resetAgentSessionSchema, testAdapterEnvironmentSchema, upsertAgentInstructionsFileSchema, updateAgentInstructionsBundleSchema, updateAgentPermissionsSchema, updateAgentInstructionsPathSchema, wakeAgentSchema, updateAgentSchema, supportedEnvironmentDriversForAdapter, } from "@penclipai/shared";
7
7
  import { readPaperclipSkillSyncPreference, writePaperclipSkillSyncPreference, } from "@penclipai/adapter-utils/server-utils";
8
8
  import { trackAgentCreated } from "@penclipai/shared/telemetry";
9
9
  import { validate } from "../middleware/validate.js";
@@ -12,8 +12,9 @@ import { conflict, forbidden, notFound, unprocessable } from "../errors.js";
12
12
  import { assertBoard, assertCompanyAccess, assertInstanceAdmin, getActorInfo } from "./authz.js";
13
13
  import { assertNoAgentHostWorkspaceCommandMutation, collectAgentAdapterWorkspaceCommandPaths, } from "./workspace-command-authz.js";
14
14
  import { environmentService } from "../services/environments.js";
15
+ import { resolveEnvironmentExecutionTarget } from "../services/environment-execution-target.js";
15
16
  import { secretService } from "../services/secrets.js";
16
- import { detectAdapterModel, findActiveServerAdapter, findServerAdapter, listAdapterModels, refreshAdapterModels, requireServerAdapter, } from "../adapters/index.js";
17
+ import { detectAdapterModel, findActiveServerAdapter, findServerAdapter, listAdapterModels, listAdapterModelProfiles, refreshAdapterModels, requireServerAdapter, } from "../adapters/index.js";
17
18
  import { redactEventPayload } from "../redaction.js";
18
19
  import { redactCurrentUserValue } from "../log-redaction.js";
19
20
  import { renderOrgChartSvg, renderOrgChartPng, ORG_CHART_STYLES } from "./org-chart-svg.js";
@@ -21,12 +22,14 @@ import { instanceSettingsService } from "../services/instance-settings.js";
21
22
  import { resolveExplicitRequestUiLocale } from "../ui-locale.js";
22
23
  import { runClaudeLogin } from "@penclipai/adapter-claude-local/server";
23
24
  import { DEFAULT_CODEBUDDY_LOCAL_MODEL, } from "@penclipai/adapter-codebuddy-local";
25
+ import { DEFAULT_ACPX_LOCAL_AGENT, DEFAULT_ACPX_LOCAL_MODE, DEFAULT_ACPX_LOCAL_NON_INTERACTIVE_PERMISSIONS, DEFAULT_ACPX_LOCAL_PERMISSION_MODE, } from "@penclipai/adapter-acpx-local";
24
26
  import { DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX, DEFAULT_CODEX_LOCAL_MODEL, } from "@penclipai/adapter-codex-local";
25
27
  import { DEFAULT_CURSOR_LOCAL_MODEL } from "@penclipai/adapter-cursor-local";
26
28
  import { DEFAULT_GEMINI_LOCAL_MODEL } from "@penclipai/adapter-gemini-local";
29
+ import { DEFAULT_OPENCODE_LOCAL_MODEL } from "@penclipai/adapter-opencode-local";
27
30
  import { DEFAULT_QWEN_LOCAL_MODEL } from "@penclipai/adapter-qwen-local";
28
31
  import { ensureCodeBuddyModelConfiguredAndAvailable } from "@penclipai/adapter-codebuddy-local/server";
29
- import { ensureOpenCodeModelConfiguredAndAvailable } from "@penclipai/adapter-opencode-local/server";
32
+ import { requireOpenCodeModelId } from "@penclipai/adapter-opencode-local/server";
30
33
  import { loadDefaultAgentInstructionsBundle, resolveDefaultAgentInstructionsBundleRole, } from "../services/default-agent-instructions.js";
31
34
  import { getTelemetryClient } from "../telemetry.js";
32
35
  import { assertEnvironmentSelectionForCompany } from "./environment-selection.js";
@@ -43,12 +46,15 @@ function readLiveRunsQueryInt(value, max, fallback = 0) {
43
46
  const parsed = Number(value);
44
47
  if (!Number.isFinite(parsed))
45
48
  return fallback;
46
- return Math.max(0, Math.min(max, Math.trunc(parsed)));
49
+ if (parsed <= 0)
50
+ return fallback;
51
+ return Math.min(max, Math.trunc(parsed));
47
52
  }
48
53
  export function agentRoutes(db, options = {}) {
49
54
  // Legacy hardcoded maps — used as fallback when adapter module does not
50
55
  // declare capability flags explicitly.
51
56
  const DEFAULT_INSTRUCTIONS_PATH_KEYS = {
57
+ acpx_local: "instructionsFilePath",
52
58
  claude_local: "instructionsFilePath",
53
59
  codex_local: "instructionsFilePath",
54
60
  codebuddy_local: "instructionsFilePath",
@@ -111,6 +117,96 @@ export function agentRoutes(db, options = {}) {
111
117
  allowedDrivers: allowedEnvironmentDriversForAgent(adapterType),
112
118
  });
113
119
  }
120
+ /**
121
+ * Resolve the execution target the adapter should run its test probes against.
122
+ *
123
+ * - No environmentId / local environment → returns a local target so the
124
+ * adapter probes the Paperclip host (legacy behavior).
125
+ * - SSH environment → builds an SSH execution target from the environment
126
+ * config so the adapter probes the remote box. No lease is required:
127
+ * the SSH spec is fully derived from the saved environment config.
128
+ * - Sandbox / plugin environments → currently fall back to local probing
129
+ * with a warning check, since lifting a temporary sandbox lease for an
130
+ * ad-hoc test invocation is out of scope for this iteration.
131
+ */
132
+ async function resolveAdapterTestExecutionContext(input) {
133
+ if (!input.environmentId) {
134
+ return { executionTarget: null, environmentName: null, fallbackChecks: [] };
135
+ }
136
+ const environment = await environmentsSvc.getById(input.environmentId);
137
+ if (!environment || environment.companyId !== input.companyId) {
138
+ return {
139
+ executionTarget: null,
140
+ environmentName: null,
141
+ fallbackChecks: [
142
+ {
143
+ code: "environment_not_found",
144
+ level: "warn",
145
+ message: "Selected environment was not found. Falling back to a local probe.",
146
+ },
147
+ ],
148
+ };
149
+ }
150
+ if (environment.driver === "local") {
151
+ return { executionTarget: null, environmentName: environment.name, fallbackChecks: [] };
152
+ }
153
+ if (environment.driver === "ssh") {
154
+ try {
155
+ const target = await resolveEnvironmentExecutionTarget({
156
+ db,
157
+ companyId: input.companyId,
158
+ adapterType: input.adapterType,
159
+ environment: {
160
+ id: environment.id,
161
+ driver: environment.driver,
162
+ config: environment.config ?? null,
163
+ },
164
+ leaseMetadata: null,
165
+ });
166
+ if (target) {
167
+ return { executionTarget: target, environmentName: environment.name, fallbackChecks: [] };
168
+ }
169
+ return {
170
+ executionTarget: null,
171
+ environmentName: environment.name,
172
+ fallbackChecks: [
173
+ {
174
+ code: "environment_target_unavailable",
175
+ level: "warn",
176
+ message: `Could not resolve an execution target for environment "${environment.name}". Falling back to a local probe.`,
177
+ },
178
+ ],
179
+ };
180
+ }
181
+ catch (err) {
182
+ return {
183
+ executionTarget: null,
184
+ environmentName: environment.name,
185
+ fallbackChecks: [
186
+ {
187
+ code: "environment_target_failed",
188
+ level: "warn",
189
+ message: `Could not connect to environment "${environment.name}" to run the test. Falling back to a local probe.`,
190
+ detail: err instanceof Error ? err.message : String(err),
191
+ },
192
+ ],
193
+ };
194
+ }
195
+ }
196
+ // sandbox / plugin / other drivers: not yet supported for ad-hoc adapter tests.
197
+ return {
198
+ executionTarget: null,
199
+ environmentName: environment.name,
200
+ fallbackChecks: [
201
+ {
202
+ code: "environment_driver_not_supported_for_test",
203
+ level: "warn",
204
+ message: `Adapter testing inside ${environment.driver} environments is not yet supported. Falling back to a local probe; results may not reflect runs in "${environment.name}".`,
205
+ hint: "Run a real heartbeat in the environment to verify end-to-end behavior.",
206
+ },
207
+ ],
208
+ };
209
+ }
114
210
  async function getCurrentUserRedactionOptions() {
115
211
  return {
116
212
  enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs,
@@ -484,6 +580,66 @@ export function agentRoutes(db, options = {}) {
484
580
  normalizedRuntimeConfig.heartbeat = heartbeat;
485
581
  return normalizedRuntimeConfig;
486
582
  }
583
+ function listRuntimeModelProfileAdapterConfigs(runtimeConfig) {
584
+ const runtimeRecord = asRecord(runtimeConfig);
585
+ const modelProfiles = asRecord(runtimeRecord?.modelProfiles);
586
+ if (!modelProfiles)
587
+ return [];
588
+ const entries = [];
589
+ for (const [profileKey, rawProfile] of Object.entries(modelProfiles)) {
590
+ const profile = asRecord(rawProfile);
591
+ const adapterConfig = asRecord(profile?.adapterConfig);
592
+ if (!profile || !adapterConfig)
593
+ continue;
594
+ entries.push({
595
+ profileKey,
596
+ profile,
597
+ adapterConfig,
598
+ path: `runtimeConfig.modelProfiles.${profileKey}.adapterConfig`,
599
+ });
600
+ }
601
+ return entries;
602
+ }
603
+ function assertNoAgentRuntimeConfigAdapterConfigMutation(req, runtimeConfig) {
604
+ for (const entry of listRuntimeModelProfileAdapterConfigs(runtimeConfig)) {
605
+ assertNoAgentAdapterConfigMutation(req, entry.adapterConfig, entry.path);
606
+ }
607
+ }
608
+ async function normalizeMediatedAdapterConfigForPersistence(input) {
609
+ const normalizedAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(input.companyId, input.adapterConfig, { strictMode: strictSecretsMode });
610
+ await assertAdapterConfigConstraints(input.companyId, input.adapterType, input.constraintAdapterConfig
611
+ ? { ...input.constraintAdapterConfig, ...normalizedAdapterConfig }
612
+ : normalizedAdapterConfig);
613
+ return normalizedAdapterConfig;
614
+ }
615
+ async function normalizeRuntimeConfigAdapterConfigsForPersistence(companyId, adapterType, runtimeConfig, baseAdapterConfig) {
616
+ const entries = listRuntimeModelProfileAdapterConfigs(runtimeConfig);
617
+ if (entries.length === 0)
618
+ return runtimeConfig;
619
+ const adapterModelProfiles = await listAdapterModelProfiles(adapterType);
620
+ const normalizedRuntimeConfig = { ...runtimeConfig };
621
+ const modelProfiles = asRecord(runtimeConfig.modelProfiles) ?? {};
622
+ const normalizedModelProfiles = { ...modelProfiles };
623
+ normalizedRuntimeConfig.modelProfiles = normalizedModelProfiles;
624
+ for (const entry of entries) {
625
+ const adapterProfile = adapterModelProfiles.find((profile) => profile.key === entry.profileKey);
626
+ const adapterDefaultConfig = asRecord(adapterProfile?.adapterConfig) ?? {};
627
+ const normalizedAdapterConfig = await normalizeMediatedAdapterConfigForPersistence({
628
+ companyId,
629
+ adapterType,
630
+ adapterConfig: entry.adapterConfig,
631
+ constraintAdapterConfig: {
632
+ ...baseAdapterConfig,
633
+ ...adapterDefaultConfig,
634
+ },
635
+ });
636
+ normalizedModelProfiles[entry.profileKey] = {
637
+ ...entry.profile,
638
+ adapterConfig: normalizedAdapterConfig,
639
+ };
640
+ }
641
+ return normalizedRuntimeConfig;
642
+ }
487
643
  function generateEd25519PrivateKeyPem() {
488
644
  const { privateKey } = generateKeyPairSync("ed25519");
489
645
  return privateKey.export({ type: "pkcs8", format: "pem" }).toString();
@@ -500,6 +656,21 @@ export function agentRoutes(db, options = {}) {
500
656
  }
501
657
  function applyCreateDefaultsByAdapterType(adapterType, adapterConfig) {
502
658
  const next = { ...adapterConfig };
659
+ if (adapterType === "acpx_local") {
660
+ if (!asNonEmptyString(next.agent)) {
661
+ next.agent = DEFAULT_ACPX_LOCAL_AGENT;
662
+ }
663
+ if (!asNonEmptyString(next.mode)) {
664
+ next.mode = DEFAULT_ACPX_LOCAL_MODE;
665
+ }
666
+ if (!asNonEmptyString(next.permissionMode)) {
667
+ next.permissionMode = DEFAULT_ACPX_LOCAL_PERMISSION_MODE;
668
+ }
669
+ if (!asNonEmptyString(next.nonInteractivePermissions)) {
670
+ next.nonInteractivePermissions = DEFAULT_ACPX_LOCAL_NON_INTERACTIVE_PERMISSIONS;
671
+ }
672
+ return ensureGatewayDeviceKey(adapterType, next);
673
+ }
503
674
  if (adapterType === "codex_local") {
504
675
  if (!asNonEmptyString(next.model)) {
505
676
  next.model = DEFAULT_CODEX_LOCAL_MODEL;
@@ -523,7 +694,10 @@ export function agentRoutes(db, options = {}) {
523
694
  next.model = DEFAULT_QWEN_LOCAL_MODEL;
524
695
  return ensureGatewayDeviceKey(adapterType, next);
525
696
  }
526
- // OpenCode requires explicit model selection — no default
697
+ if (adapterType === "opencode_local" && !asNonEmptyString(next.model)) {
698
+ next.model = DEFAULT_OPENCODE_LOCAL_MODEL;
699
+ return ensureGatewayDeviceKey(adapterType, next);
700
+ }
527
701
  if (adapterType === "cursor" && !asNonEmptyString(next.model)) {
528
702
  next.model = DEFAULT_CURSOR_LOCAL_MODEL;
529
703
  }
@@ -549,15 +723,8 @@ export function agentRoutes(db, options = {}) {
549
723
  }
550
724
  if (adapterType !== "opencode_local")
551
725
  return;
552
- const { config: runtimeConfig } = await secretsSvc.resolveAdapterConfigForRuntime(companyId, adapterConfig);
553
- const runtimeEnv = asRecord(runtimeConfig.env) ?? {};
554
726
  try {
555
- await ensureOpenCodeModelConfiguredAndAvailable({
556
- model: runtimeConfig.model,
557
- command: runtimeConfig.command,
558
- cwd: runtimeConfig.cwd,
559
- env: runtimeEnv,
560
- });
727
+ requireOpenCodeModelId(adapterConfig.model);
561
728
  }
562
729
  catch (err) {
563
730
  const reason = err instanceof Error ? err.message : String(err);
@@ -577,7 +744,7 @@ export function agentRoutes(db, options = {}) {
577
744
  }
578
745
  return path.resolve(cwd, trimmed);
579
746
  }
580
- async function materializeDefaultInstructionsBundleForNewAgent(agent) {
747
+ async function materializeDefaultInstructionsBundleForNewAgent(agent, input) {
581
748
  if (!adapterSupportsInstructionsBundle(agent.adapterType)) {
582
749
  return agent;
583
750
  }
@@ -588,20 +755,33 @@ export function agentRoutes(db, options = {}) {
588
755
  || Boolean(asNonEmptyString(adapterConfig.instructionsFilePath))
589
756
  || Boolean(asNonEmptyString(adapterConfig.agentsMdPath));
590
757
  if (hasExplicitInstructionsBundle) {
591
- return agent;
592
- }
593
- const promptTemplate = typeof adapterConfig.promptTemplate === "string"
594
- ? adapterConfig.promptTemplate
595
- : "";
596
- const files = promptTemplate.trim().length === 0
597
- ? await loadDefaultAgentInstructionsBundle(resolveDefaultAgentInstructionsBundleRole(agent.role))
598
- : { "AGENTS.md": promptTemplate };
599
- const materialized = await instructions.materializeManagedBundle(agent, files, { entryFile: "AGENTS.md", replaceExisting: false });
758
+ const nextAdapterConfig = { ...adapterConfig };
759
+ const hadLegacyPrompt = Object.prototype.hasOwnProperty.call(nextAdapterConfig, "promptTemplate")
760
+ || Object.prototype.hasOwnProperty.call(nextAdapterConfig, "bootstrapPromptTemplate");
761
+ delete nextAdapterConfig.promptTemplate;
762
+ delete nextAdapterConfig.bootstrapPromptTemplate;
763
+ if (!hadLegacyPrompt)
764
+ return agent;
765
+ const updated = await svc.update(agent.id, { adapterConfig: nextAdapterConfig });
766
+ return updated ?? { ...agent, adapterConfig: nextAdapterConfig };
767
+ }
768
+ const files = input?.files
769
+ ?? await loadDefaultAgentInstructionsBundle(resolveDefaultAgentInstructionsBundleRole(agent.role));
770
+ const materialized = await instructions.materializeManagedBundle(agent, files, { entryFile: input?.entryFile ?? "AGENTS.md", replaceExisting: false });
600
771
  const nextAdapterConfig = { ...materialized.adapterConfig };
601
772
  delete nextAdapterConfig.promptTemplate;
773
+ delete nextAdapterConfig.bootstrapPromptTemplate;
602
774
  const updated = await svc.update(agent.id, { adapterConfig: nextAdapterConfig });
603
775
  return updated ?? { ...agent, adapterConfig: nextAdapterConfig };
604
776
  }
777
+ function assertNoNewAgentLegacyPromptTemplate(adapterType, adapterConfig) {
778
+ if (!adapterSupportsInstructionsBundle(adapterType))
779
+ return;
780
+ if (Object.prototype.hasOwnProperty.call(adapterConfig, "promptTemplate")
781
+ || Object.prototype.hasOwnProperty.call(adapterConfig, "bootstrapPromptTemplate")) {
782
+ throw unprocessable("New agents must use instructionsBundle/AGENTS.md instead of adapterConfig.promptTemplate or bootstrapPromptTemplate");
783
+ }
784
+ }
605
785
  async function assertCanManageInstructionsPath(req, targetAgent) {
606
786
  assertCompanyAccess(req, targetAgent.companyId);
607
787
  if (req.actor.type !== "board") {
@@ -609,14 +789,23 @@ export function agentRoutes(db, options = {}) {
609
789
  }
610
790
  await assertBoardCanManageAgentsForCompany(req, targetAgent.companyId);
611
791
  }
612
- function assertNoAgentInstructionsConfigMutation(req, adapterConfig) {
792
+ function assertNoAgentInstructionsConfigMutation(req, adapterConfig, path = "adapterConfig") {
613
793
  if (req.actor.type !== "agent" || !adapterConfig)
614
794
  return;
615
- const changedSensitiveKeys = KNOWN_INSTRUCTIONS_BUNDLE_KEYS.filter((key) => adapterConfig[key] !== undefined);
795
+ const changedSensitiveKeys = KNOWN_INSTRUCTIONS_BUNDLE_KEYS
796
+ .filter((key) => adapterConfig[key] !== undefined)
797
+ .map((key) => `${path}.${key}`);
616
798
  if (changedSensitiveKeys.length === 0)
617
799
  return;
618
800
  throw forbidden(`Agent-authenticated callers cannot modify instructions path or bundle configuration (${changedSensitiveKeys.join(", ")})`);
619
801
  }
802
+ function adapterConfigTouchesInstructionsConfig(adapterConfig) {
803
+ return KNOWN_INSTRUCTIONS_BUNDLE_KEYS.some((key) => adapterConfig[key] !== undefined);
804
+ }
805
+ function assertNoAgentAdapterConfigMutation(req, adapterConfig, path = "adapterConfig") {
806
+ assertNoAgentInstructionsConfigMutation(req, adapterConfig, path);
807
+ assertNoAgentHostWorkspaceCommandMutation(req, collectAgentAdapterWorkspaceCommandPaths(adapterConfig, path));
808
+ }
620
809
  function summarizeAgentUpdateDetails(patch) {
621
810
  const changedTopLevelKeys = Object.keys(patch).sort();
622
811
  const details = { changedTopLevelKeys };
@@ -765,11 +954,29 @@ export function agentRoutes(db, options = {}) {
765
954
  const refresh = typeof req.query.refresh === "string"
766
955
  ? ["1", "true", "yes"].includes(req.query.refresh.toLowerCase())
767
956
  : false;
957
+ const environmentId = asNonEmptyString(req.query.environmentId);
958
+ const environment = environmentId ? await environmentsSvc.getById(environmentId) : null;
959
+ if (environmentId && (!environment || environment.companyId !== companyId)) {
960
+ res.status(404).json({ error: "Environment not found" });
961
+ return;
962
+ }
963
+ if (type === "opencode_local" && environment && environment.driver !== "local") {
964
+ const adapter = requireServerAdapter(type);
965
+ res.json(adapter.models ?? []);
966
+ return;
967
+ }
768
968
  const models = refresh
769
969
  ? await refreshAdapterModels(type)
770
970
  : await listAdapterModels(type);
771
971
  res.json(models);
772
972
  });
973
+ router.get("/companies/:companyId/adapters/:type/model-profiles", async (req, res) => {
974
+ const companyId = req.params.companyId;
975
+ assertCompanyAccess(req, companyId);
976
+ const type = assertKnownAdapterType(req.params.type);
977
+ const profiles = await listAdapterModelProfiles(type);
978
+ res.json(profiles);
979
+ });
773
980
  router.get("/companies/:companyId/adapters/:type/detect-model", async (req, res) => {
774
981
  const companyId = req.params.companyId;
775
982
  await assertCanReadConfigurations(req, companyId);
@@ -783,13 +990,33 @@ export function agentRoutes(db, options = {}) {
783
990
  await assertCanReadConfigurations(req, companyId);
784
991
  const adapter = requireServerAdapter(type);
785
992
  const inputAdapterConfig = (req.body?.adapterConfig ?? {});
993
+ const requestedEnvironmentId = typeof req.body?.environmentId === "string" && req.body.environmentId.trim().length > 0
994
+ ? req.body.environmentId
995
+ : null;
786
996
  const normalizedAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(companyId, inputAdapterConfig, { strictMode: strictSecretsMode });
787
997
  const { config: runtimeAdapterConfig } = await secretsSvc.resolveAdapterConfigForRuntime(companyId, normalizedAdapterConfig);
998
+ const { executionTarget, environmentName, fallbackChecks } = await resolveAdapterTestExecutionContext({
999
+ companyId,
1000
+ adapterType: type,
1001
+ environmentId: requestedEnvironmentId,
1002
+ });
788
1003
  const result = await adapter.testEnvironment({
789
1004
  companyId,
790
1005
  adapterType: type,
791
1006
  config: runtimeAdapterConfig,
1007
+ executionTarget,
1008
+ environmentName,
792
1009
  });
1010
+ if (fallbackChecks.length > 0) {
1011
+ const checks = [...fallbackChecks, ...result.checks];
1012
+ const status = checks.some((c) => c.level === "error")
1013
+ ? "fail"
1014
+ : checks.some((c) => c.level === "warn")
1015
+ ? "warn"
1016
+ : result.status;
1017
+ res.json({ ...result, checks, status });
1018
+ return;
1019
+ }
793
1020
  res.json(result);
794
1021
  });
795
1022
  router.get("/agents/:id/skills", async (req, res) => {
@@ -1199,18 +1426,24 @@ export function agentRoutes(db, options = {}) {
1199
1426
  const companyId = req.params.companyId;
1200
1427
  await assertCanCreateAgentsForCompany(req, companyId);
1201
1428
  const sourceIssueIds = parseSourceIssueIds(req.body);
1202
- const { desiredSkills: requestedDesiredSkills, sourceIssueId: _sourceIssueId, sourceIssueIds: _sourceIssueIds, ...hireInput } = req.body;
1429
+ const { desiredSkills: requestedDesiredSkills, instructionsBundle, sourceIssueId: _sourceIssueId, sourceIssueIds: _sourceIssueIds, ...hireInput } = req.body;
1203
1430
  hireInput.adapterType = assertKnownAdapterType(hireInput.adapterType);
1204
- assertNoAgentHostWorkspaceCommandMutation(req, collectAgentAdapterWorkspaceCommandPaths(hireInput.adapterConfig));
1205
- assertNoAgentInstructionsConfigMutation(req, (hireInput.adapterConfig ?? {}));
1206
- const requestedAdapterConfig = applyCreateDefaultsByAdapterType(hireInput.adapterType, (hireInput.adapterConfig ?? {}));
1431
+ const rawHireAdapterConfig = (hireInput.adapterConfig ?? {});
1432
+ assertNoNewAgentLegacyPromptTemplate(hireInput.adapterType, rawHireAdapterConfig);
1433
+ assertNoAgentAdapterConfigMutation(req, rawHireAdapterConfig);
1434
+ assertNoAgentRuntimeConfigAdapterConfigMutation(req, hireInput.runtimeConfig);
1435
+ const requestedAdapterConfig = applyCreateDefaultsByAdapterType(hireInput.adapterType, rawHireAdapterConfig);
1207
1436
  const desiredSkillAssignment = await resolveDesiredSkillAssignment(companyId, hireInput.adapterType, requestedAdapterConfig, Array.isArray(requestedDesiredSkills) ? requestedDesiredSkills : undefined);
1208
- const normalizedAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(companyId, desiredSkillAssignment.adapterConfig, { strictMode: strictSecretsMode });
1209
- await assertAdapterConfigConstraints(companyId, hireInput.adapterType, normalizedAdapterConfig);
1437
+ const normalizedAdapterConfig = await normalizeMediatedAdapterConfigForPersistence({
1438
+ companyId,
1439
+ adapterType: hireInput.adapterType,
1440
+ adapterConfig: desiredSkillAssignment.adapterConfig,
1441
+ });
1442
+ const normalizedRuntimeConfig = await normalizeRuntimeConfigAdapterConfigsForPersistence(companyId, hireInput.adapterType, normalizeNewAgentRuntimeConfig(hireInput.runtimeConfig), normalizedAdapterConfig);
1210
1443
  const normalizedHireInput = {
1211
1444
  ...hireInput,
1212
1445
  adapterConfig: normalizedAdapterConfig,
1213
- runtimeConfig: normalizeNewAgentRuntimeConfig(hireInput.runtimeConfig),
1446
+ runtimeConfig: normalizedRuntimeConfig,
1214
1447
  };
1215
1448
  const company = await db
1216
1449
  .select()
@@ -1229,7 +1462,7 @@ export function agentRoutes(db, options = {}) {
1229
1462
  spentMonthlyCents: 0,
1230
1463
  lastHeartbeatAt: null,
1231
1464
  });
1232
- const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent);
1465
+ const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent, instructionsBundle);
1233
1466
  let approval = null;
1234
1467
  const actor = getActorInfo(req);
1235
1468
  if (requiresApproval) {
@@ -1331,14 +1564,20 @@ export function agentRoutes(db, options = {}) {
1331
1564
  if (company.requireBoardApprovalForNewAgents) {
1332
1565
  throw conflict("Direct agent creation requires board approval. Use POST /api/companies/:companyId/agent-hires to create a pending hire approval.");
1333
1566
  }
1334
- const { desiredSkills: requestedDesiredSkills, ...createInput } = req.body;
1567
+ const { desiredSkills: requestedDesiredSkills, instructionsBundle, ...createInput } = req.body;
1335
1568
  createInput.adapterType = assertKnownAdapterType(createInput.adapterType);
1336
- assertNoAgentHostWorkspaceCommandMutation(req, collectAgentAdapterWorkspaceCommandPaths(createInput.adapterConfig));
1337
- assertNoAgentInstructionsConfigMutation(req, (createInput.adapterConfig ?? {}));
1338
- const requestedAdapterConfig = applyCreateDefaultsByAdapterType(createInput.adapterType, (createInput.adapterConfig ?? {}));
1569
+ const rawCreateAdapterConfig = (createInput.adapterConfig ?? {});
1570
+ assertNoNewAgentLegacyPromptTemplate(createInput.adapterType, rawCreateAdapterConfig);
1571
+ assertNoAgentAdapterConfigMutation(req, rawCreateAdapterConfig);
1572
+ assertNoAgentRuntimeConfigAdapterConfigMutation(req, createInput.runtimeConfig);
1573
+ const requestedAdapterConfig = applyCreateDefaultsByAdapterType(createInput.adapterType, rawCreateAdapterConfig);
1339
1574
  const desiredSkillAssignment = await resolveDesiredSkillAssignment(companyId, createInput.adapterType, requestedAdapterConfig, Array.isArray(requestedDesiredSkills) ? requestedDesiredSkills : undefined);
1340
- const normalizedAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(companyId, desiredSkillAssignment.adapterConfig, { strictMode: strictSecretsMode });
1341
- await assertAdapterConfigConstraints(companyId, createInput.adapterType, normalizedAdapterConfig);
1575
+ const normalizedAdapterConfig = await normalizeMediatedAdapterConfigForPersistence({
1576
+ companyId,
1577
+ adapterType: createInput.adapterType,
1578
+ adapterConfig: desiredSkillAssignment.adapterConfig,
1579
+ });
1580
+ const normalizedRuntimeConfig = await normalizeRuntimeConfigAdapterConfigsForPersistence(companyId, createInput.adapterType, normalizeNewAgentRuntimeConfig(createInput.runtimeConfig), normalizedAdapterConfig);
1342
1581
  await assertAgentEnvironmentSelection(companyId, createInput.adapterType, createInput.defaultEnvironmentId);
1343
1582
  await assertAgentDefaultEnvironmentSelection(companyId, createInput.defaultEnvironmentId, {
1344
1583
  allowedDrivers: allowedEnvironmentDriversForAgent(createInput.adapterType),
@@ -1347,12 +1586,12 @@ export function agentRoutes(db, options = {}) {
1347
1586
  const createdAgent = await svc.create(companyId, {
1348
1587
  ...createInput,
1349
1588
  adapterConfig: normalizedAdapterConfig,
1350
- runtimeConfig: normalizeNewAgentRuntimeConfig(createInput.runtimeConfig),
1589
+ runtimeConfig: normalizedRuntimeConfig,
1351
1590
  status: "idle",
1352
1591
  spentMonthlyCents: 0,
1353
1592
  lastHeartbeatAt: null,
1354
1593
  });
1355
- const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent);
1594
+ const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent, instructionsBundle);
1356
1595
  const actor = getActorInfo(req);
1357
1596
  await logActivity(db, {
1358
1597
  companyId,
@@ -1646,9 +1885,8 @@ export function agentRoutes(db, options = {}) {
1646
1885
  res.status(422).json({ error: "adapterConfig must be an object" });
1647
1886
  return;
1648
1887
  }
1649
- assertNoAgentInstructionsConfigMutation(req, adapterConfig);
1650
- assertNoAgentHostWorkspaceCommandMutation(req, collectAgentAdapterWorkspaceCommandPaths(adapterConfig));
1651
- const changingInstructionsConfig = Object.keys(adapterConfig).some((key) => KNOWN_INSTRUCTIONS_BUNDLE_KEYS.includes(key));
1888
+ assertNoAgentAdapterConfigMutation(req, adapterConfig);
1889
+ const changingInstructionsConfig = adapterConfigTouchesInstructionsConfig(adapterConfig);
1652
1890
  if (changingInstructionsConfig) {
1653
1891
  await assertCanManageInstructionsPath(req, existing);
1654
1892
  }
@@ -1657,6 +1895,16 @@ export function agentRoutes(db, options = {}) {
1657
1895
  const requestedAdapterType = hasOwn(patchData, "adapterType")
1658
1896
  ? assertKnownAdapterType(patchData.adapterType)
1659
1897
  : existing.adapterType;
1898
+ let requestedRuntimeConfig = null;
1899
+ if (hasOwn(patchData, "runtimeConfig")) {
1900
+ const runtimeConfig = asRecord(patchData.runtimeConfig);
1901
+ if (!runtimeConfig) {
1902
+ res.status(422).json({ error: "runtimeConfig must be an object" });
1903
+ return;
1904
+ }
1905
+ assertNoAgentRuntimeConfigAdapterConfigMutation(req, runtimeConfig);
1906
+ requestedRuntimeConfig = runtimeConfig;
1907
+ }
1660
1908
  const touchesAdapterConfiguration = hasOwn(patchData, "adapterType") ||
1661
1909
  hasOwn(patchData, "adapterConfig");
1662
1910
  if (touchesAdapterConfiguration) {
@@ -1690,12 +1938,16 @@ export function agentRoutes(db, options = {}) {
1690
1938
  rawEffectiveAdapterConfig = preserveInstructionsBundleConfig(existingAdapterConfig, rawEffectiveAdapterConfig);
1691
1939
  }
1692
1940
  const effectiveAdapterConfig = applyCreateDefaultsByAdapterType(requestedAdapterType, rawEffectiveAdapterConfig);
1693
- const normalizedEffectiveAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(existing.companyId, effectiveAdapterConfig, { strictMode: strictSecretsMode });
1941
+ const normalizedEffectiveAdapterConfig = await normalizeMediatedAdapterConfigForPersistence({
1942
+ companyId: existing.companyId,
1943
+ adapterType: requestedAdapterType,
1944
+ adapterConfig: effectiveAdapterConfig,
1945
+ });
1694
1946
  patchData.adapterConfig = syncInstructionsBundleConfigFromFilePath(existing, normalizedEffectiveAdapterConfig);
1695
1947
  }
1696
- if (touchesAdapterConfiguration && requestedAdapterType === "opencode_local") {
1697
- const effectiveAdapterConfig = asRecord(patchData.adapterConfig) ?? {};
1698
- await assertAdapterConfigConstraints(existing.companyId, requestedAdapterType, effectiveAdapterConfig);
1948
+ if (requestedRuntimeConfig) {
1949
+ const baseAdapterConfig = asRecord(patchData.adapterConfig) ?? asRecord(existing.adapterConfig) ?? {};
1950
+ patchData.runtimeConfig = await normalizeRuntimeConfigAdapterConfigsForPersistence(existing.companyId, requestedAdapterType, requestedRuntimeConfig, baseAdapterConfig);
1699
1951
  }
1700
1952
  if (touchesAdapterConfiguration || Object.prototype.hasOwnProperty.call(patchData, "defaultEnvironmentId")) {
1701
1953
  await assertAgentDefaultEnvironmentSelection(existing.companyId, Object.prototype.hasOwnProperty.call(patchData, "defaultEnvironmentId")
@@ -2042,14 +2294,21 @@ export function agentRoutes(db, options = {}) {
2042
2294
  router.get("/companies/:companyId/live-runs", async (req, res) => {
2043
2295
  const companyId = req.params.companyId;
2044
2296
  assertCompanyAccess(req, companyId);
2045
- const minCount = readLiveRunsQueryInt(req.query.minCount, 50);
2046
- const limit = readLiveRunsQueryInt(req.query.limit, 50);
2297
+ // `minCount` is a padding floor for callers that want a minimum number of
2298
+ // recent runs to render (e.g. dashboard cards). It must default to 0 so
2299
+ // callers asking for "live runs" get only actually-live runs — otherwise
2300
+ // every caller with no minCount param gets up to 50 historical runs
2301
+ // padded in and renders bogus "live" counts.
2302
+ const minCount = readLiveRunsQueryInt(req.query.minCount, 50, 0);
2303
+ const limit = readLiveRunsQueryInt(req.query.limit, 50, 50);
2047
2304
  const columns = {
2048
2305
  id: heartbeatRuns.id,
2049
2306
  companyId: heartbeatRuns.companyId,
2050
2307
  status: heartbeatRuns.status,
2051
2308
  invocationSource: heartbeatRuns.invocationSource,
2052
2309
  triggerDetail: heartbeatRuns.triggerDetail,
2310
+ contextCommentId: sql `${heartbeatRuns.contextSnapshot} ->> 'commentId'`.as("contextCommentId"),
2311
+ contextWakeCommentId: sql `${heartbeatRuns.contextSnapshot} ->> 'wakeCommentId'`.as("contextWakeCommentId"),
2053
2312
  startedAt: heartbeatRuns.startedAt,
2054
2313
  finishedAt: heartbeatRuns.finishedAt,
2055
2314
  createdAt: heartbeatRuns.createdAt,
@@ -2075,8 +2334,8 @@ export function agentRoutes(db, options = {}) {
2075
2334
  .innerJoin(agentsTable, eq(heartbeatRuns.agentId, agentsTable.id))
2076
2335
  .where(and(eq(heartbeatRuns.companyId, companyId), inArray(heartbeatRuns.status, ["queued", "running"])))
2077
2336
  .orderBy(desc(heartbeatRuns.createdAt));
2078
- const liveRuns = limit > 0 ? await liveRunsQuery.limit(limit) : await liveRunsQuery;
2079
- const targetRunCount = limit > 0 ? Math.min(minCount, limit) : minCount;
2337
+ const liveRuns = await liveRunsQuery.limit(limit);
2338
+ const targetRunCount = Math.min(minCount, limit);
2080
2339
  if (targetRunCount > 0 && liveRuns.length < targetRunCount) {
2081
2340
  const activeIds = liveRuns.map((r) => r.id);
2082
2341
  const recentRuns = await db
@@ -2231,8 +2490,8 @@ export function agentRoutes(db, options = {}) {
2231
2490
  router.get("/issues/:issueId/live-runs", async (req, res) => {
2232
2491
  const rawId = req.params.issueId;
2233
2492
  const issueSvc = issueService(db);
2234
- const isIdentifier = /^[A-Z]+-\d+$/i.test(rawId);
2235
- const issue = isIdentifier ? await issueSvc.getByIdentifier(rawId) : await issueSvc.getById(rawId);
2493
+ const identifier = normalizeIssueIdentifier(rawId);
2494
+ const issue = identifier ? await issueSvc.getByIdentifier(identifier) : await issueSvc.getById(rawId);
2236
2495
  if (!issue) {
2237
2496
  res.status(404).json({ error: "Issue not found" });
2238
2497
  return;
@@ -2244,6 +2503,8 @@ export function agentRoutes(db, options = {}) {
2244
2503
  status: heartbeatRuns.status,
2245
2504
  invocationSource: heartbeatRuns.invocationSource,
2246
2505
  triggerDetail: heartbeatRuns.triggerDetail,
2506
+ contextCommentId: sql `${heartbeatRuns.contextSnapshot} ->> 'commentId'`.as("contextCommentId"),
2507
+ contextWakeCommentId: sql `${heartbeatRuns.contextSnapshot} ->> 'wakeCommentId'`.as("contextWakeCommentId"),
2247
2508
  startedAt: heartbeatRuns.startedAt,
2248
2509
  finishedAt: heartbeatRuns.finishedAt,
2249
2510
  createdAt: heartbeatRuns.createdAt,
@@ -2274,8 +2535,8 @@ export function agentRoutes(db, options = {}) {
2274
2535
  router.get("/issues/:issueId/active-run", async (req, res) => {
2275
2536
  const rawId = req.params.issueId;
2276
2537
  const issueSvc = issueService(db);
2277
- const isIdentifier = /^[A-Z]+-\d+$/i.test(rawId);
2278
- const issue = isIdentifier ? await issueSvc.getByIdentifier(rawId) : await issueSvc.getById(rawId);
2538
+ const identifier = normalizeIssueIdentifier(rawId);
2539
+ const issue = identifier ? await issueSvc.getByIdentifier(identifier) : await issueSvc.getById(rawId);
2279
2540
  if (!issue) {
2280
2541
  res.status(404).json({ error: "Issue not found" });
2281
2542
  return;