@superblocksteam/vite-plugin-file-sync 2.0.123 → 2.0.124-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts +16 -1
  2. package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts.map +1 -1
  3. package/dist/ai-service/agent/prompts/build-base-system-prompt.js +32 -1
  4. package/dist/ai-service/agent/prompts/build-base-system-prompt.js.map +1 -1
  5. package/dist/ai-service/agent/prompts/build-security-scan-prompt.d.ts.map +1 -1
  6. package/dist/ai-service/agent/prompts/build-security-scan-prompt.js +3 -1
  7. package/dist/ai-service/agent/prompts/build-security-scan-prompt.js.map +1 -1
  8. package/dist/ai-service/agent/tools/apis/api-comparator.d.ts.map +1 -1
  9. package/dist/ai-service/agent/tools/apis/api-comparator.js +1 -4
  10. package/dist/ai-service/agent/tools/apis/api-comparator.js.map +1 -1
  11. package/dist/ai-service/agent/tools/apis/api-testing-state.js +7 -0
  12. package/dist/ai-service/agent/tools/apis/api-testing-state.js.map +1 -1
  13. package/dist/ai-service/agent/tools/apis/get-api-docs.d.ts +1 -1
  14. package/dist/ai-service/agent/tools/apis/write-api.d.ts +2 -2
  15. package/dist/ai-service/agent/tools/build-capture-screenshot.d.ts.map +1 -1
  16. package/dist/ai-service/agent/tools/build-capture-screenshot.js +11 -33
  17. package/dist/ai-service/agent/tools/build-capture-screenshot.js.map +1 -1
  18. package/dist/ai-service/agent/tools/build-copy-directory.d.ts +1 -1
  19. package/dist/ai-service/agent/tools/build-finalize.d.ts.map +1 -1
  20. package/dist/ai-service/agent/tools/build-finalize.js +11 -2
  21. package/dist/ai-service/agent/tools/build-finalize.js.map +1 -1
  22. package/dist/ai-service/agent/tools/build-install-packages.d.ts +41 -1
  23. package/dist/ai-service/agent/tools/build-install-packages.d.ts.map +1 -1
  24. package/dist/ai-service/agent/tools/build-install-packages.js +217 -0
  25. package/dist/ai-service/agent/tools/build-install-packages.js.map +1 -1
  26. package/dist/ai-service/agent/tools/build-lookup-npm-package.d.ts +28 -0
  27. package/dist/ai-service/agent/tools/build-lookup-npm-package.d.ts.map +1 -0
  28. package/dist/ai-service/agent/tools/build-lookup-npm-package.js +78 -0
  29. package/dist/ai-service/agent/tools/build-lookup-npm-package.js.map +1 -0
  30. package/dist/ai-service/agent/tools/build-manage-checklist.d.ts +4 -4
  31. package/dist/ai-service/agent/tools/build-navigate-preview.d.ts +16 -0
  32. package/dist/ai-service/agent/tools/build-navigate-preview.d.ts.map +1 -0
  33. package/dist/ai-service/agent/tools/build-navigate-preview.js +68 -0
  34. package/dist/ai-service/agent/tools/build-navigate-preview.js.map +1 -0
  35. package/dist/ai-service/agent/tools/build-write-file.d.ts +1 -1
  36. package/dist/ai-service/agent/tools/databases/dev-database.d.ts +9 -9
  37. package/dist/ai-service/agent/tools/index.d.ts +2 -0
  38. package/dist/ai-service/agent/tools/index.d.ts.map +1 -1
  39. package/dist/ai-service/agent/tools/index.js +2 -0
  40. package/dist/ai-service/agent/tools/index.js.map +1 -1
  41. package/dist/ai-service/agent/tools/integrations/execute-request.d.ts +3 -3
  42. package/dist/ai-service/agent/tools/report-security-findings.d.ts +11 -5
  43. package/dist/ai-service/agent/tools/report-security-findings.d.ts.map +1 -1
  44. package/dist/ai-service/agent/tools/report-security-findings.js +1 -0
  45. package/dist/ai-service/agent/tools/report-security-findings.js.map +1 -1
  46. package/dist/ai-service/agent/tools.d.ts.map +1 -1
  47. package/dist/ai-service/agent/tools.js +3 -1
  48. package/dist/ai-service/agent/tools.js.map +1 -1
  49. package/dist/ai-service/agent/tools2/tools/git.d.ts +2 -2
  50. package/dist/ai-service/agent/tools2/tools/grep.d.ts +2 -2
  51. package/dist/ai-service/agent/tools2/tools/update-test-case-status.d.ts +2 -2
  52. package/dist/ai-service/app-interface/npm-error-parser.d.ts +161 -0
  53. package/dist/ai-service/app-interface/npm-error-parser.d.ts.map +1 -0
  54. package/dist/ai-service/app-interface/npm-error-parser.js +510 -0
  55. package/dist/ai-service/app-interface/npm-error-parser.js.map +1 -0
  56. package/dist/ai-service/app-interface/npm-package-lookup.d.ts +286 -0
  57. package/dist/ai-service/app-interface/npm-package-lookup.d.ts.map +1 -0
  58. package/dist/ai-service/app-interface/npm-package-lookup.js +793 -0
  59. package/dist/ai-service/app-interface/npm-package-lookup.js.map +1 -0
  60. package/dist/ai-service/app-interface/npm-registry.d.ts +735 -47
  61. package/dist/ai-service/app-interface/npm-registry.d.ts.map +1 -1
  62. package/dist/ai-service/app-interface/npm-registry.js +1882 -153
  63. package/dist/ai-service/app-interface/npm-registry.js.map +1 -1
  64. package/dist/ai-service/app-interface/shell.d.ts +118 -1
  65. package/dist/ai-service/app-interface/shell.d.ts.map +1 -1
  66. package/dist/ai-service/app-interface/shell.js +590 -157
  67. package/dist/ai-service/app-interface/shell.js.map +1 -1
  68. package/dist/ai-service/app-skills/manager.d.ts +1 -1
  69. package/dist/ai-service/app-skills/manager.d.ts.map +1 -1
  70. package/dist/ai-service/app-skills/manager.js +39 -7
  71. package/dist/ai-service/app-skills/manager.js.map +1 -1
  72. package/dist/ai-service/checklist/api-migration-checklist-gate.d.ts +9 -0
  73. package/dist/ai-service/checklist/api-migration-checklist-gate.d.ts.map +1 -0
  74. package/dist/ai-service/checklist/api-migration-checklist-gate.js +30 -0
  75. package/dist/ai-service/checklist/api-migration-checklist-gate.js.map +1 -0
  76. package/dist/ai-service/checklist/api-migration-origins.d.ts +4 -0
  77. package/dist/ai-service/checklist/api-migration-origins.d.ts.map +1 -0
  78. package/dist/ai-service/checklist/api-migration-origins.js +8 -0
  79. package/dist/ai-service/checklist/api-migration-origins.js.map +1 -0
  80. package/dist/ai-service/checklist/persisted-checklist-store.d.ts +2 -3
  81. package/dist/ai-service/checklist/persisted-checklist-store.d.ts.map +1 -1
  82. package/dist/ai-service/checklist/persisted-checklist-store.js +6 -7
  83. package/dist/ai-service/checklist/persisted-checklist-store.js.map +1 -1
  84. package/dist/ai-service/context-archive-paths.d.ts +4 -0
  85. package/dist/ai-service/context-archive-paths.d.ts.map +1 -0
  86. package/dist/ai-service/context-archive-paths.js +28 -0
  87. package/dist/ai-service/context-archive-paths.js.map +1 -0
  88. package/dist/ai-service/context-download.d.ts +3 -1
  89. package/dist/ai-service/context-download.d.ts.map +1 -1
  90. package/dist/ai-service/context-download.js +48 -9
  91. package/dist/ai-service/context-download.js.map +1 -1
  92. package/dist/ai-service/context-upload.d.ts +3 -2
  93. package/dist/ai-service/context-upload.d.ts.map +1 -1
  94. package/dist/ai-service/context-upload.js +44 -26
  95. package/dist/ai-service/context-upload.js.map +1 -1
  96. package/dist/ai-service/dev-database-client.d.ts +3 -11
  97. package/dist/ai-service/dev-database-client.d.ts.map +1 -1
  98. package/dist/ai-service/dev-database-client.js.map +1 -1
  99. package/dist/ai-service/features.d.ts +4 -0
  100. package/dist/ai-service/features.d.ts.map +1 -1
  101. package/dist/ai-service/features.js +8 -0
  102. package/dist/ai-service/features.js.map +1 -1
  103. package/dist/ai-service/filter-disabled-tools-for-migration.d.ts.map +1 -1
  104. package/dist/ai-service/filter-disabled-tools-for-migration.js +4 -0
  105. package/dist/ai-service/filter-disabled-tools-for-migration.js.map +1 -1
  106. package/dist/ai-service/index.d.ts +103 -0
  107. package/dist/ai-service/index.d.ts.map +1 -1
  108. package/dist/ai-service/index.js +610 -25
  109. package/dist/ai-service/index.js.map +1 -1
  110. package/dist/ai-service/llm/client.d.ts +38 -3
  111. package/dist/ai-service/llm/client.d.ts.map +1 -1
  112. package/dist/ai-service/llm/client.js +73 -22
  113. package/dist/ai-service/llm/client.js.map +1 -1
  114. package/dist/ai-service/llm/context-v2/adapter.d.ts +3 -1
  115. package/dist/ai-service/llm/context-v2/adapter.d.ts.map +1 -1
  116. package/dist/ai-service/llm/context-v2/adapter.js +11 -11
  117. package/dist/ai-service/llm/context-v2/adapter.js.map +1 -1
  118. package/dist/ai-service/llm/context-v2/compaction/client-side.d.ts +44 -0
  119. package/dist/ai-service/llm/context-v2/compaction/client-side.d.ts.map +1 -0
  120. package/dist/ai-service/llm/context-v2/compaction/client-side.js +170 -0
  121. package/dist/ai-service/llm/context-v2/compaction/client-side.js.map +1 -0
  122. package/dist/ai-service/llm/context-v2/compaction/compaction-strategy.d.ts +56 -0
  123. package/dist/ai-service/llm/context-v2/compaction/compaction-strategy.d.ts.map +1 -0
  124. package/dist/ai-service/llm/context-v2/compaction/compaction-strategy.js +9 -0
  125. package/dist/ai-service/llm/context-v2/compaction/compaction-strategy.js.map +1 -0
  126. package/dist/ai-service/llm/context-v2/compaction/index.d.ts +5 -0
  127. package/dist/ai-service/llm/context-v2/compaction/index.d.ts.map +1 -0
  128. package/dist/ai-service/llm/context-v2/compaction/index.js +3 -0
  129. package/dist/ai-service/llm/context-v2/compaction/index.js.map +1 -0
  130. package/dist/ai-service/llm/context-v2/compaction/server-side.d.ts +30 -0
  131. package/dist/ai-service/llm/context-v2/compaction/server-side.d.ts.map +1 -0
  132. package/dist/ai-service/llm/context-v2/compaction/server-side.js +130 -0
  133. package/dist/ai-service/llm/context-v2/compaction/server-side.js.map +1 -0
  134. package/dist/ai-service/llm/context-v2/context-management.d.ts +203 -0
  135. package/dist/ai-service/llm/context-v2/context-management.d.ts.map +1 -0
  136. package/dist/ai-service/llm/context-v2/context-management.js +183 -0
  137. package/dist/ai-service/llm/context-v2/context-management.js.map +1 -0
  138. package/dist/ai-service/llm/context-v2/context.d.ts +64 -22
  139. package/dist/ai-service/llm/context-v2/context.d.ts.map +1 -1
  140. package/dist/ai-service/llm/context-v2/context.js +233 -157
  141. package/dist/ai-service/llm/context-v2/context.js.map +1 -1
  142. package/dist/ai-service/llm/context-v2/conversation-context.d.ts +24 -7
  143. package/dist/ai-service/llm/context-v2/conversation-context.d.ts.map +1 -1
  144. package/dist/ai-service/llm/context-v2/conversation-context.js +1 -1
  145. package/dist/ai-service/llm/context-v2/index.d.ts +0 -4
  146. package/dist/ai-service/llm/context-v2/index.d.ts.map +1 -1
  147. package/dist/ai-service/llm/context-v2/index.js +0 -4
  148. package/dist/ai-service/llm/context-v2/index.js.map +1 -1
  149. package/dist/ai-service/llm/context-v2/manager.d.ts +24 -3
  150. package/dist/ai-service/llm/context-v2/manager.d.ts.map +1 -1
  151. package/dist/ai-service/llm/context-v2/manager.js +146 -4
  152. package/dist/ai-service/llm/context-v2/manager.js.map +1 -1
  153. package/dist/ai-service/llm/context-v2/migrations/v1-to-v2.d.ts +2 -7
  154. package/dist/ai-service/llm/context-v2/migrations/v1-to-v2.d.ts.map +1 -1
  155. package/dist/ai-service/llm/context-v2/migrations/v1-to-v2.js +5 -73
  156. package/dist/ai-service/llm/context-v2/migrations/v1-to-v2.js.map +1 -1
  157. package/dist/ai-service/llm/context-v2/storage/event-store.d.ts +57 -0
  158. package/dist/ai-service/llm/context-v2/storage/event-store.d.ts.map +1 -0
  159. package/dist/ai-service/llm/context-v2/storage/event-store.js +10 -0
  160. package/dist/ai-service/llm/context-v2/storage/event-store.js.map +1 -0
  161. package/dist/ai-service/llm/context-v2/storage/event-types.d.ts +50 -0
  162. package/dist/ai-service/llm/context-v2/storage/event-types.d.ts.map +1 -0
  163. package/dist/ai-service/llm/context-v2/storage/event-types.js +10 -0
  164. package/dist/ai-service/llm/context-v2/storage/event-types.js.map +1 -0
  165. package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.d.ts +87 -0
  166. package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.d.ts.map +1 -0
  167. package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.js +184 -0
  168. package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.js.map +1 -0
  169. package/dist/ai-service/llm/context-v2/storage/migration.d.ts +49 -0
  170. package/dist/ai-service/llm/context-v2/storage/migration.d.ts.map +1 -0
  171. package/dist/ai-service/llm/context-v2/storage/migration.js +126 -0
  172. package/dist/ai-service/llm/context-v2/storage/migration.js.map +1 -0
  173. package/dist/ai-service/llm/context-v2/types.d.ts +16 -11
  174. package/dist/ai-service/llm/context-v2/types.d.ts.map +1 -1
  175. package/dist/ai-service/llm/context-v2/types.js +2 -2
  176. package/dist/ai-service/llm/context-v2/types.js.map +1 -1
  177. package/dist/ai-service/llm/impl/clark.d.ts +8 -0
  178. package/dist/ai-service/llm/impl/clark.d.ts.map +1 -1
  179. package/dist/ai-service/llm/impl/clark.js +8 -0
  180. package/dist/ai-service/llm/impl/clark.js.map +1 -1
  181. package/dist/ai-service/llm/interaction/adapters/vercel.d.ts.map +1 -1
  182. package/dist/ai-service/llm/interaction/adapters/vercel.js +17 -1
  183. package/dist/ai-service/llm/interaction/adapters/vercel.js.map +1 -1
  184. package/dist/ai-service/llm/provider.d.ts.map +1 -1
  185. package/dist/ai-service/llm/provider.js +1 -3
  186. package/dist/ai-service/llm/provider.js.map +1 -1
  187. package/dist/ai-service/llm/stream/observers/context.d.ts +23 -0
  188. package/dist/ai-service/llm/stream/observers/context.d.ts.map +1 -1
  189. package/dist/ai-service/llm/stream/observers/context.js +62 -0
  190. package/dist/ai-service/llm/stream/observers/context.js.map +1 -1
  191. package/dist/ai-service/llm/tool-context-integrity.d.ts +25 -0
  192. package/dist/ai-service/llm/tool-context-integrity.d.ts.map +1 -0
  193. package/dist/ai-service/llm/tool-context-integrity.js +141 -0
  194. package/dist/ai-service/llm/tool-context-integrity.js.map +1 -0
  195. package/dist/ai-service/skills/system/superblocks-migration/skill.generated.d.ts +1 -1
  196. package/dist/ai-service/skills/system/superblocks-migration/skill.generated.d.ts.map +1 -1
  197. package/dist/ai-service/skills/system/superblocks-migration/skill.generated.js +6 -1
  198. package/dist/ai-service/skills/system/superblocks-migration/skill.generated.js.map +1 -1
  199. package/dist/ai-service/skills/system/third-party-migration/skill.generated.d.ts +1 -1
  200. package/dist/ai-service/skills/system/third-party-migration/skill.generated.d.ts.map +1 -1
  201. package/dist/ai-service/skills/system/third-party-migration/skill.generated.js +3 -2
  202. package/dist/ai-service/skills/system/third-party-migration/skill.generated.js.map +1 -1
  203. package/dist/ai-service/state-machine/clark-fsm.d.ts +23 -2
  204. package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
  205. package/dist/ai-service/state-machine/clark-fsm.js +37 -2
  206. package/dist/ai-service/state-machine/clark-fsm.js.map +1 -1
  207. package/dist/ai-service/state-machine/handlers/agent-planning.d.ts.map +1 -1
  208. package/dist/ai-service/state-machine/handlers/agent-planning.js +29 -2
  209. package/dist/ai-service/state-machine/handlers/agent-planning.js.map +1 -1
  210. package/dist/ai-service/state-machine/handlers/awaiting-user.d.ts.map +1 -1
  211. package/dist/ai-service/state-machine/handlers/awaiting-user.js +9 -0
  212. package/dist/ai-service/state-machine/handlers/awaiting-user.js.map +1 -1
  213. package/dist/ai-service/state-machine/handlers/llm-generating.d.ts.map +1 -1
  214. package/dist/ai-service/state-machine/handlers/llm-generating.js +25 -2
  215. package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
  216. package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.d.ts +6 -0
  217. package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.d.ts.map +1 -0
  218. package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.js +36 -0
  219. package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.js.map +1 -0
  220. package/dist/ai-service/state-machine/helpers/stable-peer.d.ts +30 -1
  221. package/dist/ai-service/state-machine/helpers/stable-peer.d.ts.map +1 -1
  222. package/dist/ai-service/state-machine/helpers/stable-peer.js +85 -2
  223. package/dist/ai-service/state-machine/helpers/stable-peer.js.map +1 -1
  224. package/dist/ai-service/state-machine/helpers/user-preferences.d.ts +8 -0
  225. package/dist/ai-service/state-machine/helpers/user-preferences.d.ts.map +1 -0
  226. package/dist/ai-service/state-machine/helpers/user-preferences.js +11 -0
  227. package/dist/ai-service/state-machine/helpers/user-preferences.js.map +1 -0
  228. package/dist/ai-service/template-renderer.d.ts +18 -1
  229. package/dist/ai-service/template-renderer.d.ts.map +1 -1
  230. package/dist/ai-service/template-renderer.js +85 -20
  231. package/dist/ai-service/template-renderer.js.map +1 -1
  232. package/dist/ai-service/types.d.ts +157 -0
  233. package/dist/ai-service/types.d.ts.map +1 -1
  234. package/dist/ai-service/types.js +137 -0
  235. package/dist/ai-service/types.js.map +1 -1
  236. package/dist/ai-service/util/llm-config-utils.d.ts +21 -22
  237. package/dist/ai-service/util/llm-config-utils.d.ts.map +1 -1
  238. package/dist/ai-service/util/llm-config-utils.js +40 -67
  239. package/dist/ai-service/util/llm-config-utils.js.map +1 -1
  240. package/dist/file-sync-vite-plugin.d.ts.map +1 -1
  241. package/dist/file-sync-vite-plugin.js +5 -0
  242. package/dist/file-sync-vite-plugin.js.map +1 -1
  243. package/dist/file-system-helpers.d.ts +17 -0
  244. package/dist/file-system-helpers.d.ts.map +1 -1
  245. package/dist/file-system-helpers.js +22 -0
  246. package/dist/file-system-helpers.js.map +1 -1
  247. package/dist/git-service/index.d.ts.map +1 -1
  248. package/dist/git-service/index.js +15 -1
  249. package/dist/git-service/index.js.map +1 -1
  250. package/dist/migration/migration-routes.d.ts.map +1 -1
  251. package/dist/migration/migration-routes.js +26 -3
  252. package/dist/migration/migration-routes.js.map +1 -1
  253. package/dist/migration/translation-prompt.d.ts.map +1 -1
  254. package/dist/migration/translation-prompt.js +5 -0
  255. package/dist/migration/translation-prompt.js.map +1 -1
  256. package/dist/migration-templates/app-fullstack/package.json +2 -2
  257. package/dist/policy-gate-callback-mapper.d.ts +24 -0
  258. package/dist/policy-gate-callback-mapper.d.ts.map +1 -0
  259. package/dist/policy-gate-callback-mapper.js +61 -0
  260. package/dist/policy-gate-callback-mapper.js.map +1 -0
  261. package/dist/policy-gate-runner.d.ts +32 -0
  262. package/dist/policy-gate-runner.d.ts.map +1 -0
  263. package/dist/policy-gate-runner.js +191 -0
  264. package/dist/policy-gate-runner.js.map +1 -0
  265. package/dist/router-parser.d.ts.map +1 -1
  266. package/dist/router-parser.js +107 -11
  267. package/dist/router-parser.js.map +1 -1
  268. package/dist/sync-service/list-dir.d.ts.map +1 -1
  269. package/dist/sync-service/list-dir.js +13 -3
  270. package/dist/sync-service/list-dir.js.map +1 -1
  271. package/package.json +28 -10
@@ -1,30 +1,35 @@
1
- import { randomUUID } from "node:crypto";
1
+ import { createHash, randomUUID } from "node:crypto";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
- import { generateObject, hasToolCall, } from "ai";
4
+ import { generateObject, hasToolCall, stepCountIs, streamText as aiStreamText, } from "ai";
5
5
  import { z } from "zod";
6
6
  import { AiMode, resolveAttachmentFileName, } from "@superblocksteam/library-shared/types";
7
7
  import { addTracingToMethods, classifyAttachmentDelivery, FactAccessType, FactCreatedSource, FactEntityScope, TracedEventEmitter, } from "@superblocksteam/shared";
8
8
  import { buildTranslationPrompt } from "../migration/translation-prompt.js";
9
9
  import { getErrorMeta, getLogger, setDefaultLogger } from "../util/logger.js";
10
+ import { composeSecurityAgentPrompt, } from "./agent/prompts/build-security-scan-prompt.js";
10
11
  import { getToolCallArguments, getToolErrorCode, getToolOutput, } from "./agent/tool-message-utils.js";
12
+ import { buildSecurityScanTools, } from "./agent/tools.js";
11
13
  import { hasPendingEscalations, flushPendingEscalations, } from "./agent/tools/apis/api-validation-orchestrator.js";
12
14
  import { safeSampleJson } from "./agent/tools/apis/sample-json.js";
13
15
  import { resolveSdkApiEntryPoint } from "./agent/tools/apis/sdk-registry.js";
14
16
  import { readFile } from "./agent/tools/build-read-file.js";
15
17
  import { debugCache } from "./agent/tools/debug-cache.js";
16
18
  import { buildReadFileToolFactory } from "./agent/tools/index.js";
19
+ import { securityScanResultSchema, } from "./agent/tools/report-security-findings.js";
17
20
  import { SessionEntityPermissionStore, } from "./agent/tools2/entity-permissions.js";
18
21
  import { ToolRegistry } from "./agent/tools2/registry.js";
19
22
  import { explainCodeFinalizeToolFactory } from "./agent/tools2/tools/explain-code-finalize.js";
20
23
  import { FileSystemInterface } from "./app-interface/file-system-interface.js";
21
24
  import { createDefaultVirtualFileSystem, createPathValidator, } from "./app-interface/filesystem/index.js";
25
+ import { NpmPackageLookup } from "./app-interface/npm-package-lookup.js";
26
+ import { NpmRegistryClient, createDiskRegistryPolicyStore, } from "./app-interface/npm-registry.js";
22
27
  import { AppShell } from "./app-interface/shell.js";
23
28
  import { AppSkillsManager } from "./app-skills/manager.js";
24
29
  import { isUploadedAttachment, toUploadedAttachmentContentPart, } from "./attachments/uploaded-content-part.js";
25
30
  import { ChatSessionStore } from "./chat/chat-session-store.js";
26
31
  import { normalizeTextAttachmentForModel } from "./chat/utils.js";
27
- import { API_MIGRATION_ORIGINS, readPersistedChecklist, } from "./checklist/persisted-checklist-store.js";
32
+ import { API_MIGRATION_ORIGINS, readPersistedChecklist, updateChecklistItem, } from "./checklist/persisted-checklist-store.js";
28
33
  import { downloadContextFromBucketeer, restoreContextFromCheckpoint, } from "./context-download.js";
29
34
  import { uploadContextToBucketeer } from "./context-upload.js";
30
35
  import { AppContextStore } from "./context/app-context.js";
@@ -35,7 +40,9 @@ import { CsvJudgeStorage } from "./judge/storage/csv-storage.js";
35
40
  import { KnowledgeManager } from "./knowledge/knowledge-manager.js";
36
41
  import { createChaosFetch } from "./llm/chaos-fetch.js";
37
42
  import { LLMClient } from "./llm/client.js";
43
+ import { selectContextManagementStrategy, } from "./llm/context-v2/context-management.js";
38
44
  import { ContextManagerV2 } from "./llm/context-v2/manager.js";
45
+ import { JsonlEventStore } from "./llm/context-v2/storage/jsonl-event-store.js";
39
46
  import { LocalContextStorageV2 } from "./llm/context-v2/storage/local.js";
40
47
  import { createLLMProvider } from "./llm/provider.js";
41
48
  import { traceLLM } from "./llmobs/helpers.js";
@@ -62,12 +69,145 @@ import { transitionFrom } from "./state-machine/helpers/transition.js";
62
69
  import { TemplateRenderer } from "./template-renderer.js";
63
70
  import { createJsonStreamParser } from "./util/json-stream-parser.js";
64
71
  import { processLLMConfig } from "./util/llm-config-utils.js";
72
+ import { buildModeMessage } from "./util/mode-message.js";
65
73
  import { parseJwt } from "./util/parse-jwt.js";
66
74
  import { getToolCallSignature } from "./util/tool-signature.js";
67
75
  export { AiServiceFeatureFlags } from "./features.js";
68
76
  export { stripResolvedFromLockfile } from "./app-interface/npm-registry.js";
69
77
  export { SnapshotManager } from "./recording/index.js";
70
78
  export { isSdkApiTemplate } from "./sdk-api-templates.js";
79
+ /**
80
+ * APPS-4370. Build the disk-backed `LastKnownRegistryPolicyStore` the
81
+ * production `AiService` wires into its default `NpmRegistryClient`.
82
+ *
83
+ * Exported so unit tests can exercise the exact wiring path the
84
+ * constructor uses without standing up a full AiService — the
85
+ * constructor calls this function inline. Tests pass a tmp directory as
86
+ * `rootDir`; production callers leave the field undefined on
87
+ * `AiServiceConfig` and we fall back to `os.homedir()` at the call
88
+ * site.
89
+ *
90
+ * Kept as a thin pass-through (rather than inlining
91
+ * `createDiskRegistryPolicyStore`) so the production wiring shape is
92
+ * named and reviewable: a single function, a single call site, one
93
+ * place to assert about in coverage.
94
+ */
95
+ export function buildLastKnownPolicyStore(opts) {
96
+ return createDiskRegistryPolicyStore(opts);
97
+ }
98
+ const MAX_POLICY_GATE_STREAM_ERRORS = 5;
99
+ const MAX_POLICY_GATE_STREAM_ERROR_ARRAY_LENGTH = 10;
100
+ const MAX_POLICY_GATE_STREAM_ERROR_FIELD_LENGTH = 500;
101
+ const MAX_POLICY_GATE_STREAM_ERROR_OBJECT_DEPTH = 3;
102
+ const MAX_POLICY_GATE_STREAM_ERROR_OBJECT_KEYS = 20;
103
+ const POLICY_GATE_STREAM_ERROR_REDACTED_VALUE = "[redacted]";
104
+ const POLICY_GATE_STREAM_ERROR_SECRET_KEY_PATTERN = /(access[-_]?key|api[-_]?key|authorization|connection[-_]?string|credential|dsn|password|private[-_]?key|secret|token)/i;
105
+ const POLICY_GATE_STREAM_ERROR_SECRET_VALUE_PATTERN_SOURCES = [
106
+ String.raw `\bbearer\s+[a-z0-9._~+/=-]+`,
107
+ String.raw `\bhttps?:\/\/[^@\s/:]+:[^@\s]+@\S+`,
108
+ String.raw `\b(?:mongodb(?:\+srv)?|mysql|postgres(?:ql)?|snowflake):\/\/\S+`,
109
+ String.raw `\bA[SK]IA[0-9A-Z]{16}\b`,
110
+ String.raw `\bgh[pousr]_[a-z0-9_]{20,}\b`,
111
+ String.raw `\bsk-[a-z0-9_-]{8,}\b`,
112
+ String.raw `\bxox[baprs]-[a-z0-9-]{10,}\b`,
113
+ ];
114
+ function truncatePolicyGateStreamErrorField(value) {
115
+ if (value.length <= MAX_POLICY_GATE_STREAM_ERROR_FIELD_LENGTH) {
116
+ return value;
117
+ }
118
+ return `${value.slice(0, MAX_POLICY_GATE_STREAM_ERROR_FIELD_LENGTH)}...`;
119
+ }
120
+ function sanitizePolicyGateStreamErrorString(value) {
121
+ const redacted = POLICY_GATE_STREAM_ERROR_SECRET_VALUE_PATTERN_SOURCES.reduce((current, patternSource) => current.replace(new RegExp(patternSource, "gi"), POLICY_GATE_STREAM_ERROR_REDACTED_VALUE), value);
122
+ return truncatePolicyGateStreamErrorField(redacted);
123
+ }
124
+ function isUnhelpfulPolicyGateStreamError(serialized) {
125
+ return (Object.keys(serialized).length === 1 &&
126
+ serialized.message === "[object Object]");
127
+ }
128
+ function serializePolicyGateStreamError(error, streamPart) {
129
+ const attachStreamPartIfUseful = (serialized) => {
130
+ if (isUnhelpfulPolicyGateStreamError(serialized) &&
131
+ streamPart &&
132
+ typeof streamPart === "object") {
133
+ return {
134
+ ...serialized,
135
+ streamPart: serializePolicyGateStreamErrorRecord(streamPart, 0),
136
+ };
137
+ }
138
+ return serialized;
139
+ };
140
+ if (error instanceof Error) {
141
+ const serialized = {
142
+ message: sanitizePolicyGateStreamErrorString(error.message),
143
+ name: error.name,
144
+ };
145
+ const extraFields = serializePolicyGateStreamErrorRecord(error, 0);
146
+ return attachStreamPartIfUseful({
147
+ ...extraFields,
148
+ ...serialized,
149
+ });
150
+ }
151
+ if (error && typeof error === "object") {
152
+ const serialized = serializePolicyGateStreamErrorRecord(error, 0);
153
+ if (Object.keys(serialized).length > 0) {
154
+ return attachStreamPartIfUseful(serialized);
155
+ }
156
+ }
157
+ return attachStreamPartIfUseful({
158
+ message: truncatePolicyGateStreamErrorField(String(error)),
159
+ });
160
+ }
161
+ function serializePolicyGateStreamErrorValue(value, depth) {
162
+ if (typeof value === "string") {
163
+ return sanitizePolicyGateStreamErrorString(value);
164
+ }
165
+ if (typeof value === "number" ||
166
+ typeof value === "boolean" ||
167
+ value === null) {
168
+ return value;
169
+ }
170
+ if (value instanceof Error) {
171
+ return {
172
+ ...serializePolicyGateStreamErrorRecord(value, depth + 1),
173
+ message: sanitizePolicyGateStreamErrorString(value.message),
174
+ name: value.name,
175
+ };
176
+ }
177
+ if (Array.isArray(value)) {
178
+ if (depth >= MAX_POLICY_GATE_STREAM_ERROR_OBJECT_DEPTH) {
179
+ return `[array:${value.length}]`;
180
+ }
181
+ return value
182
+ .slice(0, MAX_POLICY_GATE_STREAM_ERROR_ARRAY_LENGTH)
183
+ .map((item) => serializePolicyGateStreamErrorValue(item, depth + 1));
184
+ }
185
+ if (value && typeof value === "object") {
186
+ if (depth >= MAX_POLICY_GATE_STREAM_ERROR_OBJECT_DEPTH) {
187
+ return "[object]";
188
+ }
189
+ return serializePolicyGateStreamErrorRecord(value, depth + 1);
190
+ }
191
+ if (value === undefined) {
192
+ return undefined;
193
+ }
194
+ return sanitizePolicyGateStreamErrorString(String(value));
195
+ }
196
+ function serializePolicyGateStreamErrorRecord(value, depth) {
197
+ const output = {};
198
+ for (const key of Object.keys(value).slice(0, MAX_POLICY_GATE_STREAM_ERROR_OBJECT_KEYS)) {
199
+ if (POLICY_GATE_STREAM_ERROR_SECRET_KEY_PATTERN.test(key)) {
200
+ output[key] = POLICY_GATE_STREAM_ERROR_REDACTED_VALUE;
201
+ continue;
202
+ }
203
+ const fieldValue = value[key];
204
+ const serializedValue = serializePolicyGateStreamErrorValue(fieldValue, depth);
205
+ if (serializedValue !== undefined) {
206
+ output[key] = serializedValue;
207
+ }
208
+ }
209
+ return output;
210
+ }
71
211
  // Helper functions for console log and runtime error buffer management
72
212
  export function pruneByTTL(items, ttlMs) {
73
213
  const now = Date.now();
@@ -89,6 +229,29 @@ export function estimateSize(obj) {
89
229
  return 0;
90
230
  }
91
231
  }
232
+ /**
233
+ * How long the npm-registry client's `getJwt` will block waiting for the
234
+ * first `tokenUpdated` event when no JWT is yet primed. Kept conservative —
235
+ * a cold prefetch that misses the window degrades to last-known-good or
236
+ * not-configured rather than crashing the install path.
237
+ */
238
+ const NPM_REGISTRY_JWT_WAIT_MS = 5_000;
239
+ /**
240
+ * How long the npm-registry client will wait for a fresh `tokenUpdated`
241
+ * event between an HTTP 401 and its retry.
242
+ *
243
+ * Two failure modes for the refresh:
244
+ * - `waitForTokenUpdate` times out (push-refresh channel hung): the
245
+ * adapter throws, the client treats it as a refresh-channel failure
246
+ * and routes through `serveStaleOrUnconfigured` rather than retrying
247
+ * with the stale JWT — the install path gets last-known-good (or
248
+ * `not-configured`).
249
+ * - No `tokenManager` is wired (SaaS without push refresh, some test
250
+ * fixtures): the adapter resolves immediately on the next microtask;
251
+ * the retry runs with the existing JWT, and a second 401 hard-throws
252
+ * via the second-401 branch in `NpmRegistryClient.fetchWithRefresh`.
253
+ */
254
+ const NPM_REGISTRY_REFRESH_WAIT_MS = 5_000;
92
255
  export function truncateString(str, maxBytes) {
93
256
  // Rough approximation: assume 2 bytes per character
94
257
  const maxChars = Math.floor(maxBytes / 2);
@@ -148,6 +311,39 @@ export class AiService extends TracedEventEmitter {
148
311
  entityPermissionStore = new SessionEntityPermissionStore();
149
312
  _onGenerationCompleteCallback;
150
313
  tokenManagerJwt;
314
+ /**
315
+ * Listener body for `tokenManager.on("tokenUpdated", ...)`. Defined as a
316
+ * prototype method (not a class field arrow) so tests can exercise the
317
+ * real implementation via `AiService.prototype.handleTokenUpdated`; the
318
+ * constructor binds `this` explicitly when registering with the emitter.
319
+ * Wrapped in try/catch because EventEmitter re-throws synchronous
320
+ * listener exceptions — a logger transport disconnect during JWT
321
+ * rotation would otherwise crash the process. `console.error` is the
322
+ * fallback because it cannot itself trigger the emitter chain.
323
+ */
324
+ handleTokenUpdated(event) {
325
+ try {
326
+ this.tokenManagerJwt = event.token;
327
+ this.getLogger().info(`[ai-service] token updated via tokenManager`);
328
+ }
329
+ catch (err) {
330
+ console.error("[ai-service] tokenUpdated listener threw", err);
331
+ }
332
+ }
333
+ npmRegistryClient;
334
+ /**
335
+ * Long-lived `NpmPackageLookup` reused across every `buildTools()` call.
336
+ * Constructed once in the AiService constructor and passed into the
337
+ * per-turn tool factory via `ClarkStateHandlerParams`. The factory used
338
+ * to `new NpmPackageLookup(...)` on every LLM generation turn, which
339
+ * leaked the `superblocks.npm.lookup.cache_size` `ObservableGauge`
340
+ * callback (the OTel API keeps a strong reference to every registered
341
+ * callback) and produced multiple same-instrument/same-attribute
342
+ * observations per collection cycle. Sharing one instance lifecycle-
343
+ * bound to the AiService ties the gauge to the process lifetime — same
344
+ * pattern as `npmRegistryClient` / `templateRenderer` / `appShell`.
345
+ */
346
+ npmPackageLookup;
151
347
  viteServer;
152
348
  mcpServerManager;
153
349
  recordingManager;
@@ -163,6 +359,21 @@ export class AiService extends TracedEventEmitter {
163
359
  get clarkState() {
164
360
  return this.clark.state;
165
361
  }
362
+ /**
363
+ * Exposes the shared `NpmRegistryClient` so external SDK dev-server startup
364
+ * paths in `cli-replacement/dev.mts` — the home `~/.npmrc` writer
365
+ * (APPS-4231) and the startup `installPackages` call that honors the
366
+ * per-org `allow_install_scripts` policy (APPS-4251) — can share the
367
+ * in-memory cache, 401-refresh, and last-known-policy state already used
368
+ * by AppShell and TemplateRenderer. A separate client would fragment that
369
+ * state and double the network roundtrips against the registry-config
370
+ * endpoint. The client is always constructed (LD flag and empty-list
371
+ * cases short-circuit internally) so callers can treat this as
372
+ * non-optional once the AiService has been initialized.
373
+ */
374
+ getNpmRegistryClient() {
375
+ return this.npmRegistryClient;
376
+ }
166
377
  /**
167
378
  * Injects git and sync services for the git tool (called by the plugin when native git is available).
168
379
  */
@@ -292,6 +503,71 @@ export class AiService extends TracedEventEmitter {
292
503
  // Keep this opt-out alongside the injected model factory.
293
504
  useSuperblocksAuthorizationHeader: config.providerSettings?.useSuperblocksAuthorizationHeader,
294
505
  };
506
+ // Seed `tokenManagerJwt` synchronously from the manager's current
507
+ // value before any consumer that calls `getJwt()` is constructed. The
508
+ // `tokenUpdated` listener below covers FUTURE refreshes; this read
509
+ // covers the cold-start case where the CLI (or any earlier code) has
510
+ // already called `tokenManager.updateToken(...)` before this AiService
511
+ // was constructed. Without it, the prior emission is lost — Node
512
+ // `EventEmitter` has no replay — and `NpmRegistryClient.getJwt()`
513
+ // would time out via `waitForJwt(...)`, downgrading `syncHomeNpmrc`
514
+ // to `unreachable` and leaving `~/.npmrc` unwritten on cold boot.
515
+ this.tokenManagerJwt = this.config.tokenManager?.getCurrentToken();
516
+ // Resolve the npm registry client once and reuse across TemplateRenderer
517
+ // and AppShell so both install paths share the same in-memory cache and
518
+ // 401-refresh state. Explicit injection wins (tests); otherwise we
519
+ // construct a default that reads the LD flag from `config.features` and
520
+ // routes JWT through the AiService's own `getJwt()` (which prefers the
521
+ // tokenManager token and falls back to clark.context.jwt). Empty
522
+ // tokens are refused upstream of `attempt()` so the server doesn't
523
+ // see `Bearer ` and reply 400 (which the client would now treat as a
524
+ // hard fail rather than a transient outage).
525
+ //
526
+ // APPS-4370. Wire the disk-backed `LastKnownRegistryPolicyStore` here
527
+ // so the production client carries `everConfigured` end-to-end. Without
528
+ // this the unreachable short-circuit in AppShell is inert: the marker
529
+ // writer is fire-and-forget and `hasEverBeenConfigured()` always
530
+ // resolves false when no store is wired. `rootDir` is `os.homedir()`
531
+ // for the same reason the home-npmrc writer roots at
532
+ // `~/.superblocks/...`: the marker lives next to the existing
533
+ // CLI-managed state and survives application-directory recycles.
534
+ const npmRegistryClient = config.npmRegistryClient ??
535
+ new NpmRegistryClient({
536
+ isFlagEnabled: () => config.features.npmRegistryEnabled === true,
537
+ // The prefetch can fire before tokenManager has primed
538
+ // `tokenManagerJwt`. Block (with a short timeout) on the next
539
+ // `tokenUpdated` event rather than throwing immediately, so the
540
+ // first install in the shared template dir actually gets a
541
+ // `.npmrc` instead of silently downgrading to "not configured".
542
+ getJwt: () => this.waitForJwt(NPM_REGISTRY_JWT_WAIT_MS),
543
+ // Push-based refresh hook: on 401, wait briefly for the next
544
+ // `tokenUpdated` event before re-reading the JWT. Without this,
545
+ // a 401 retry fires immediately with the same expired token and
546
+ // throws `re-authenticated and still received HTTP 401` for any
547
+ // user whose JWT expired mid-install.
548
+ refreshJwt: () => this.waitForTokenUpdate(NPM_REGISTRY_REFRESH_WAIT_MS),
549
+ organizationId: config.organizationId,
550
+ baseUrl: config.superblocksBaseUrl,
551
+ lastKnownPolicyStore: buildLastKnownPolicyStore({
552
+ organizationId: config.organizationId,
553
+ rootDir: config.policyStoreRootDir ?? os.homedir(),
554
+ }),
555
+ });
556
+ this.npmRegistryClient = npmRegistryClient;
557
+ // One process-lifetime `NpmPackageLookup` shared across every
558
+ // `buildTools()` call. The `build_lookupNpmPackage` tool factory used
559
+ // to construct a fresh instance per LLM turn; each construction
560
+ // registered an `ObservableGauge` callback for
561
+ // `superblocks.npm.lookup.cache_size` that the OTel API keeps strongly
562
+ // referenced for the lifetime of the meter. `ToolRegistry.clear()`
563
+ // does not dispose tool resources, so every cleared instance's callback
564
+ // (and the cache it closed over) stuck around for the process lifetime
565
+ // and emitted multiple same-instrument/same-attribute observations per
566
+ // collection cycle. Owning the lookup here — same pattern as
567
+ // `npmRegistryClient` / `templateRenderer` / `appShell` — means the
568
+ // gauge wires up exactly once and the 60s TTL cache stays warm across
569
+ // turns instead of being thrown away.
570
+ this.npmPackageLookup = new NpmPackageLookup({ client: npmRegistryClient });
295
571
  this.templateRenderer =
296
572
  config.templateRenderer ??
297
573
  new TemplateRenderer({
@@ -301,6 +577,7 @@ export class AiService extends TracedEventEmitter {
301
577
  // process.cwd() — process.cwd() is fragile in CI matrix shards and
302
578
  // monorepo sub-invocations. See PR #19282 (F3).
303
579
  detectionCwd: config.appRootDirPath,
580
+ npmRegistryClient,
304
581
  });
305
582
  // Create KnowledgeManager before AppShell so it can be passed to VirtualFileSystem
306
583
  this.knowledgeManager = new KnowledgeManager();
@@ -309,6 +586,7 @@ export class AiService extends TracedEventEmitter {
309
586
  fsOperationQueue: config.fsOperationQueue,
310
587
  draftInterface: config.draftInterface,
311
588
  templateRenderer: this.templateRenderer,
589
+ npmRegistryClient,
312
590
  virtualFileSystem: createDefaultVirtualFileSystem({
313
591
  useSystemSkills: config.features.useSystemSkills,
314
592
  knowledgeManager: this.knowledgeManager,
@@ -344,15 +622,13 @@ export class AiService extends TracedEventEmitter {
344
622
  logger: config.logger,
345
623
  });
346
624
  if (this.config.tokenManager) {
347
- this.config.tokenManager.on("tokenUpdated", (event) => {
348
- this.tokenManagerJwt = event.token;
349
- this.getLogger().info(`[ai-service] token updated via tokenManager`);
350
- });
625
+ this.config.tokenManager.on("tokenUpdated", this.handleTokenUpdated.bind(this));
351
626
  }
352
627
  this.clarkProfiler = new ClarkProfiler(this.sessionId);
353
628
  this.clarkProfiler.initialize(this.appShell);
354
629
  this.contextManagerV2 = new ContextManagerV2({
355
630
  storage: new LocalContextStorageV2(config.appRootDirPath),
631
+ eventStore: new JsonlEventStore(config.appRootDirPath),
356
632
  rootDir: config.appRootDirPath,
357
633
  getModeState: () => {
358
634
  const mode = this.clark?.context?.currentMode;
@@ -363,7 +639,7 @@ export class AiService extends TracedEventEmitter {
363
639
  planApproved: this.clark?.context?.planContext?.approved,
364
640
  };
365
641
  },
366
- remoteRestoreFallback: async () => {
642
+ remoteRestoreFallback: async (id) => {
367
643
  const jwt = this.getJwt();
368
644
  // Prefer the live sync service commitId (set after uploads), but on
369
645
  // fresh pods downloadDirectory doesn't set it. Fall back to the
@@ -382,6 +658,8 @@ export class AiService extends TracedEventEmitter {
382
658
  superblocksBaseUrl: config.superblocksBaseUrl,
383
659
  logger: this.getLogger(),
384
660
  jwt,
661
+ contextId: id,
662
+ appRootDirPath: config.appRootDirPath,
385
663
  });
386
664
  },
387
665
  });
@@ -489,6 +767,18 @@ export class AiService extends TracedEventEmitter {
489
767
  if (!checklist) {
490
768
  return;
491
769
  }
770
+ // Reset stale in_progress items to pending. After a dev-server restart,
771
+ // items left in_progress are not truly running — resetting them allows
772
+ // the UI polling trigger and backend handler to re-dispatch Clark.
773
+ const staleInProgressItems = checklist.items.filter((item) => API_MIGRATION_ORIGINS.has(item.origin) && item.status === "in_progress");
774
+ for (const item of staleInProgressItems) {
775
+ await updateChecklistItem({
776
+ appRootDirPath: this.appRootDirPath,
777
+ itemId: item.id,
778
+ status: "pending",
779
+ });
780
+ this.getLogger().info(`[ai-service] Reset stale in_progress item "${item.id}" to pending after restart`);
781
+ }
492
782
  const hasPendingSeedApi = checklist.items.some((item) => API_MIGRATION_ORIGINS.has(item.origin) &&
493
783
  (item.status === "pending" || item.status === "in_progress"));
494
784
  if (hasPendingSeedApi) {
@@ -553,6 +843,20 @@ export class AiService extends TracedEventEmitter {
553
843
  logger: this.getLogger(),
554
844
  templateRenderer: this.templateRenderer,
555
845
  appShell: this.appShell,
846
+ // AiServiceConfig.npmRegistryClient is optional (injectable test
847
+ // seam); the SDK's `dev()` never passes it in production, so
848
+ // spreading `...this.config` above leaves it `undefined` and any
849
+ // state-handler / tool that reads `services.npmRegistryClient`
850
+ // sees nothing. The `build_lookupNpmPackage` tool's `!this.client`
851
+ // branch silently downgraded to public npm because of this gap.
852
+ // Carry the AiService's resolved instance through explicitly,
853
+ // same pattern as `appShell` / `templateRenderer` above.
854
+ npmRegistryClient: this.npmRegistryClient,
855
+ // Pass the AiService's long-lived lookup so `build_lookupNpmPackage`
856
+ // reuses one instance across LLM turns (gauge registered once,
857
+ // cache warm). See the `npmPackageLookup` field comment for why
858
+ // per-turn instantiation in the tool factory leaked OTel callbacks.
859
+ npmPackageLookup: this.npmPackageLookup,
556
860
  signals: this,
557
861
  fileSystemInterface: this.fileSystemInterface,
558
862
  integrationStore: this.integrationStore,
@@ -627,6 +931,79 @@ export class AiService extends TracedEventEmitter {
627
931
  // fallback to clark context if no token manager is provided
628
932
  return this.clark?.context?.jwt;
629
933
  }
934
+ buildContextManagementForCurrentClark(llmConfig) {
935
+ const modeInstructions = this.clark.context.currentMode
936
+ ? `Preserve the following mode context in your summary: ${buildModeMessage(this.clark.context.currentMode, this.clark.context.planContext?.approved)}`
937
+ : undefined;
938
+ return selectContextManagementStrategy({
939
+ serverContextManagementEnabled: llmConfig?.serverContextManagement?.enabled,
940
+ modeInstructions,
941
+ thresholds: llmConfig?.serverContextManagement,
942
+ summarizationModel: () => createLLMProvider(this.getLLMProviderSettings(llmConfig), () => this.getJwt(), llmConfig, () => ({
943
+ prompt_id: this.clark.context.promptId,
944
+ chatmessage_id: this.clark.context.chatmessageId,
945
+ application_id: this.config.applicationId,
946
+ session_id: this.sessionId,
947
+ mode: this.clark.context.currentMode === AiMode.PLAN ? "plan" : "build",
948
+ plan_size: this.clark.context.planSize,
949
+ })).modelForTask("summarizeMessages"),
950
+ });
951
+ }
952
+ /**
953
+ * Resolve a JWT for the npm-registry fetch, blocking briefly on the
954
+ * first `tokenUpdated` event when no JWT is yet primed. The
955
+ * `TemplateRenderer` prefetch fires during AiService construction —
956
+ * before the `tokenManager` subscription has emitted — so a synchronous
957
+ * `getJwt()` would observe `undefined` and silently downgrade the
958
+ * private-registry install to public npm. Blocking here is the
959
+ * narrowest fix: callers that already have a primed JWT take the fast
960
+ * path and never await.
961
+ */
962
+ async waitForJwt(timeoutMs) {
963
+ const immediate = this.getJwt();
964
+ if (immediate) {
965
+ return immediate;
966
+ }
967
+ if (!this.config.tokenManager) {
968
+ throw new Error("[npm-registry] no JWT available and no tokenManager wired to wait for one");
969
+ }
970
+ await this.waitForTokenUpdate(timeoutMs);
971
+ const after = this.getJwt();
972
+ if (!after) {
973
+ throw new Error("[npm-registry] no JWT available after waiting for tokenManager update");
974
+ }
975
+ return after;
976
+ }
977
+ /**
978
+ * Resolve when the next `tokenUpdated` event lands on `tokenManager`,
979
+ * or reject when `timeoutMs` elapses without one. When no
980
+ * `tokenManager` is wired (test fixtures, SaaS paths without push
981
+ * refresh), resolves immediately on the next microtask.
982
+ *
983
+ * Resolution only signals "a wait completed" — never that a JWT actually
984
+ * changed. The constructor's `tokenUpdated` listener (above) is the only
985
+ * code that mutates `tokenManagerJwt`. Callers (`waitForJwt`, the
986
+ * npm-registry `refreshJwt` adapter) must re-read the JWT after this
987
+ * resolves and treat an unchanged value the same as a refresh that
988
+ * never happened.
989
+ */
990
+ waitForTokenUpdate(timeoutMs) {
991
+ const tokenManager = this.config.tokenManager;
992
+ if (!tokenManager) {
993
+ return Promise.resolve();
994
+ }
995
+ return new Promise((resolve, reject) => {
996
+ const onUpdate = () => {
997
+ clearTimeout(timer);
998
+ resolve();
999
+ };
1000
+ const timer = setTimeout(() => {
1001
+ tokenManager.off("tokenUpdated", onUpdate);
1002
+ reject(new Error(`[npm-registry] timed out after ${timeoutMs}ms waiting for tokenManager update`));
1003
+ }, timeoutMs);
1004
+ tokenManager.once("tokenUpdated", onUpdate);
1005
+ });
1006
+ }
630
1007
  createClarkTagProviderFactory() {
631
1008
  return (fsm) => {
632
1009
  const clark = fsm;
@@ -652,11 +1029,19 @@ export class AiService extends TracedEventEmitter {
652
1029
  }
653
1030
  return (await response.json());
654
1031
  }
655
- async ensureAiQuotaAvailable(jwt) {
1032
+ /**
1033
+ * Non-throwing AI quota check. Returns `{ allowed: true }` when the org is
1034
+ * within its limit, and also when the check itself can't run (no JWT, or the
1035
+ * quota endpoint errors) — callers fail open so a flaky quota service never
1036
+ * blocks legitimate work. When the org is over limit, returns the reason and
1037
+ * a user-facing message instead of throwing, so callers that don't surface
1038
+ * exceptions (e.g. the background migration turn) can react.
1039
+ */
1040
+ async checkAiQuota(jwt) {
656
1041
  const token = jwt ?? this.getJwt();
657
1042
  if (!token) {
658
1043
  this.getLogger().warn("[ai-service] No JWT available for quota check, skipping");
659
- return;
1044
+ return { allowed: true };
660
1045
  }
661
1046
  try {
662
1047
  const quota = await this.callServer({
@@ -670,14 +1055,19 @@ export class AiService extends TracedEventEmitter {
670
1055
  : reason === "no_seat_assigned"
671
1056
  ? "You don't have an AI Builder license assigned. Ask your admin to assign a license."
672
1057
  : "Your organization has reached its AI usage limit.";
673
- throw new Error(`${message} [reason:${reason}]`);
1058
+ return { allowed: false, reason, message };
674
1059
  }
1060
+ return { allowed: true };
675
1061
  }
676
1062
  catch (error) {
677
- if (error instanceof Error && error.message.includes("[reason:")) {
678
- throw error;
679
- }
680
1063
  this.getLogger().warn("[ai-service] Quota check failed, allowing request", getErrorMeta(error));
1064
+ return { allowed: true };
1065
+ }
1066
+ }
1067
+ async ensureAiQuotaAvailable(jwt) {
1068
+ const quota = await this.checkAiQuota(jwt);
1069
+ if (!quota.allowed) {
1070
+ throw new Error(`${quota.message} [reason:${quota.reason}]`);
681
1071
  }
682
1072
  }
683
1073
  /**
@@ -712,16 +1102,35 @@ export class AiService extends TracedEventEmitter {
712
1102
  if (this.clark.context.pendingToolPermissionRequest) {
713
1103
  return { dispatched: false, reason: "pending-tool-permission" };
714
1104
  }
1105
+ if (this.clark.state === ClarkStateNames.AwaitingUser) {
1106
+ if (this.clark.context.activeInteractiveMessage ||
1107
+ this.clark.context.deferredInteractiveMessage) {
1108
+ return { dispatched: false, reason: "awaiting-interactive-response" };
1109
+ }
1110
+ }
715
1111
  const checklist = await readPersistedChecklist(this.appRootDirPath);
716
1112
  if (!checklist) {
717
1113
  this.getLogger().warn("[ai-service] Migration checklist not found or unreadable; skipping translation turn");
718
1114
  return { dispatched: false, reason: "checklist-unavailable" };
719
1115
  }
720
- const pendingSeedApiItems = checklist.items.filter((item) => API_MIGRATION_ORIGINS.has(item.origin) && item.status === "pending");
1116
+ const pendingSeedApiItems = checklist.items.filter((item) => API_MIGRATION_ORIGINS.has(item.origin) &&
1117
+ (item.status === "pending" || item.status === "in_progress"));
721
1118
  const pendingSeedApiCount = pendingSeedApiItems.length;
722
1119
  if (pendingSeedApiCount === 0) {
723
1120
  return { dispatched: false, reason: "no-pending-items" };
724
1121
  }
1122
+ // Pre-flight quota gate. The migration turn drives LLM inference (which
1123
+ // doesn't enforce quota) followed by tool calls that hit quota-protected
1124
+ // endpoints (e.g. attachment uploads via /api/v1/files). Without this gate
1125
+ // an over-limit org burns more credits on the migration turn, then fails
1126
+ // mid-turn with a generic "inference error" when a downstream tool call
1127
+ // 429s. Fail fast with a clear reason before consuming any credits, the
1128
+ // same pre-flight check handleAiGenerate already runs.
1129
+ const quota = await this.checkAiQuota(request.jwt);
1130
+ if (!quota.allowed) {
1131
+ this.getLogger().warn("[ai-service] Skipping v2→v3 migration turn — AI quota exceeded", { reason: quota.reason });
1132
+ return { dispatched: false, reason: "quota-exceeded" };
1133
+ }
725
1134
  this.migrationTurnTriggered = true;
726
1135
  this.appShell.setApiHandlerEnabled(false);
727
1136
  this.getLogger().info(`[ai-service] Dispatching internal v2→v3 migration turn for ${pendingSeedApiCount} API(s) (API VFS projection disabled, retryMode=${request.retryMode ?? "none"})`);
@@ -764,7 +1173,7 @@ export class AiService extends TracedEventEmitter {
764
1173
  getMigrationSafeLlmConfig(llmConfig) {
765
1174
  const result = filterDisabledToolsForMigration(llmConfig);
766
1175
  if (result.removedTools.length > 0) {
767
- this.getLogger().warn("[ai-service] Migration turn overrides operator-disabled tools required for migration: install/checklist/askMultiChoice", { removedTools: result.removedTools });
1176
+ this.getLogger().warn("[ai-service] Migration turn overrides operator-disabled tools required for migration: install/checklist/askMultiChoice/build_navigatePreview/build_captureScreenshot", { removedTools: result.removedTools });
768
1177
  }
769
1178
  return result.config;
770
1179
  }
@@ -814,6 +1223,7 @@ export class AiService extends TracedEventEmitter {
814
1223
  },
815
1224
  llmConfig: request.llmConfig,
816
1225
  partialResponse: undefined,
1226
+ userPreferences: request.userPreferences,
817
1227
  };
818
1228
  });
819
1229
  const transitionTo = transitionFrom(this.clark);
@@ -1688,12 +2098,13 @@ export class AiService extends TracedEventEmitter {
1688
2098
  }
1689
2099
  catch (error) {
1690
2100
  const errorMessage = error instanceof Error ? error.message : String(error);
1691
- // create a tool-error message with the error
2101
+ // Provider-facing context expects tool failures to be encoded as
2102
+ // errored tool results so they still satisfy the preceding tool call.
1692
2103
  toolResultMessage = {
1693
2104
  role: "tool",
1694
2105
  content: [
1695
2106
  {
1696
- type: "tool-error",
2107
+ type: "tool-result",
1697
2108
  toolCallId,
1698
2109
  toolName,
1699
2110
  output: {
@@ -1712,9 +2123,7 @@ export class AiService extends TracedEventEmitter {
1712
2123
  type: "tool-error",
1713
2124
  toolCallId,
1714
2125
  toolName,
1715
- error: typeof errorMessage === "string"
1716
- ? errorMessage
1717
- : JSON.stringify(errorMessage),
2126
+ error: errorMessage,
1718
2127
  errorCode,
1719
2128
  });
1720
2129
  }
@@ -1749,10 +2158,8 @@ export class AiService extends TracedEventEmitter {
1749
2158
  };
1750
2159
  logger.info(`[ai-service] Tool ${toolName} permission denied, error injected into context`);
1751
2160
  }
1752
- const llmProviderSettings = this.getLLMProviderSettings(llmConfig);
1753
- const llmProvider = createLLMProvider(llmProviderSettings, () => this.clark.context.jwt || "", llmConfig, undefined);
1754
- const summarizationModel = llmProvider.modelForTask("summarizeMessages");
1755
- const context = await this.contextManagerV2.getOrCreateContext(contextId, summarizationModel, llmConfig?.contextOptionsV2);
2161
+ const contextManagement = this.buildContextManagementForCurrentClark(llmConfig);
2162
+ const context = await this.contextManagerV2.getOrCreateContext(contextId, llmConfig?.contextOptionsV2, undefined, contextManagement);
1756
2163
  const placeholderUsage = {
1757
2164
  inputTokens: 0,
1758
2165
  outputTokens: 0,
@@ -2185,6 +2592,184 @@ export class AiService extends TracedEventEmitter {
2185
2592
  this.tokenManagerJwt = previousJwt;
2186
2593
  }
2187
2594
  }
2595
+ async handlePolicyGateExecution(request) {
2596
+ const logger = this.getLogger();
2597
+ logger.info(`[ai-service] Policy gate execution requested for app=${request.applicationId}`);
2598
+ const requestLlmConfig = request.llmConfig;
2599
+ const contextLlmConfig = this.clark.context.llmConfig;
2600
+ const llmConfig = requestLlmConfig ?? contextLlmConfig;
2601
+ const llmProvider = createLLMProvider(this.getLLMProviderSettings(llmConfig), () => this.getJwt(), llmConfig, () => ({
2602
+ ai_agent_run_id: request.aiAgentRunId,
2603
+ application_id: request.applicationId,
2604
+ session_id: this.sessionId,
2605
+ trigger_type: "policy_gate",
2606
+ }));
2607
+ const model = llmProvider.modelForTask("generateBroadEdit");
2608
+ const availableIntegrations = await this.integrationStore.availableIntegrations();
2609
+ const integrations = availableIntegrations.map((i) => ({
2610
+ name: i.name,
2611
+ pluginType: i.plugin?.id ?? "unknown",
2612
+ }));
2613
+ const appContext = { integrations };
2614
+ const systemPrompt = composeSecurityAgentPrompt(appContext, request.adminPrompt);
2615
+ const startedAt = Date.now();
2616
+ const toolCallCounts = {};
2617
+ const filePathsRead = new Set();
2618
+ const filePathsListed = new Set();
2619
+ const finishReasons = [];
2620
+ const grepPatterns = [];
2621
+ const grepMatchCounts = [];
2622
+ const grepPatternByToolCallId = new Map();
2623
+ const streamErrors = [];
2624
+ const streamPartTypeCounts = {};
2625
+ let textDeltaChars = 0;
2626
+ const promptHash = createHash("sha256").update(systemPrompt).digest("hex");
2627
+ const modelName = typeof model === "string" ? model : model.modelId;
2628
+ const scanRoot = typeof this.appShell.appRootDirPath === "string"
2629
+ ? this.appShell.appRootDirPath
2630
+ : undefined;
2631
+ let scanResult;
2632
+ const buildRuntimeMetadata = (finalFailureReason) => ({
2633
+ diagnosticsVersion: 2,
2634
+ durationMs: Date.now() - startedAt,
2635
+ filePathsListed: Array.from(filePathsListed).slice(0, 100),
2636
+ filePathsRead: Array.from(filePathsRead).slice(0, 100),
2637
+ finalFailureReason,
2638
+ finishReasons,
2639
+ grepMatchCounts,
2640
+ grepPatterns,
2641
+ model: modelName,
2642
+ promptHash,
2643
+ reportToolCalled: Boolean(scanResult),
2644
+ scanComplete: scanResult?.scanComplete === true,
2645
+ scanRoot,
2646
+ streamErrors,
2647
+ streamPartTypeCounts,
2648
+ textDeltaChars,
2649
+ toolCallCounts,
2650
+ });
2651
+ const failPolicyGateExecution = (message) => {
2652
+ const error = new Error(message);
2653
+ error.runtimeMetadataJson = buildRuntimeMetadata(message);
2654
+ throw error;
2655
+ };
2656
+ const scanToolsDeps = {
2657
+ appShell: this.appShell,
2658
+ features: this.config.features ?? {},
2659
+ integrationStore: this.integrationStore,
2660
+ llmProvider: llmProvider,
2661
+ };
2662
+ const tools = await buildSecurityScanTools(this.clark, scanToolsDeps);
2663
+ const result = aiStreamText({
2664
+ messages: [
2665
+ { content: systemPrompt, role: "system" },
2666
+ {
2667
+ content: "Begin your security review of this application now.",
2668
+ role: "user",
2669
+ },
2670
+ ],
2671
+ model,
2672
+ tools,
2673
+ stopWhen: stepCountIs(30),
2674
+ });
2675
+ for await (const part of result.fullStream) {
2676
+ const partType = typeof part.type === "string"
2677
+ ? part.type
2678
+ : "unknown";
2679
+ streamPartTypeCounts[partType] =
2680
+ (streamPartTypeCounts[partType] ?? 0) + 1;
2681
+ if (partType === "error") {
2682
+ const streamError = serializePolicyGateStreamError(part.error, part);
2683
+ if (streamErrors.length < MAX_POLICY_GATE_STREAM_ERRORS) {
2684
+ streamErrors.push(streamError);
2685
+ }
2686
+ logger.warn("[ai-service] Policy gate stream error", { streamError });
2687
+ }
2688
+ const textDelta = part.textDelta ??
2689
+ part.text ??
2690
+ part.delta;
2691
+ if (typeof textDelta === "string") {
2692
+ textDeltaChars += textDelta.length;
2693
+ }
2694
+ const finishReason = part.finishReason;
2695
+ if (typeof finishReason === "string") {
2696
+ finishReasons.push(finishReason);
2697
+ }
2698
+ if (part.type === "tool-call") {
2699
+ toolCallCounts[part.toolName] =
2700
+ (toolCallCounts[part.toolName] ?? 0) + 1;
2701
+ const input = part.input ??
2702
+ part.args;
2703
+ if (input && typeof input === "object") {
2704
+ const record = input;
2705
+ if (part.toolName === "build_readFile" &&
2706
+ typeof record.filePath === "string") {
2707
+ filePathsRead.add(record.filePath);
2708
+ }
2709
+ if (part.toolName === "grep" && typeof record.pattern === "string") {
2710
+ grepPatterns.push(record.pattern);
2711
+ const toolCallId = typeof part.toolCallId === "string"
2712
+ ? part.toolCallId
2713
+ : `grep-${grepPatterns.length}`;
2714
+ grepPatternByToolCallId.set(toolCallId, record.pattern);
2715
+ }
2716
+ }
2717
+ }
2718
+ if (part.type === "tool-result") {
2719
+ const output = part.output ??
2720
+ part.result;
2721
+ if (output && typeof output === "object") {
2722
+ const record = output;
2723
+ if (part.toolName === "build_listFiles" &&
2724
+ Array.isArray(record.files)) {
2725
+ for (const file of record.files) {
2726
+ if (typeof file === "string") {
2727
+ filePathsListed.add(file);
2728
+ }
2729
+ }
2730
+ }
2731
+ if (part.toolName === "grep" && typeof record.matches === "number") {
2732
+ const toolCallId = typeof part.toolCallId === "string"
2733
+ ? part.toolCallId
2734
+ : `grep-${grepMatchCounts.length + 1}`;
2735
+ grepMatchCounts.push({
2736
+ matches: record.matches,
2737
+ pattern: grepPatternByToolCallId.get(toolCallId) ??
2738
+ grepPatterns[grepPatterns.length - 1] ??
2739
+ "",
2740
+ });
2741
+ }
2742
+ }
2743
+ }
2744
+ if (part.type === "tool-call" && part.toolName === "reportReviewRun") {
2745
+ const input = part.input ??
2746
+ part.args;
2747
+ const parsedInput = securityScanResultSchema.safeParse(input);
2748
+ if (!parsedInput.success) {
2749
+ logger.error("[ai-service] Invalid policy gate reportReviewRun input", getErrorMeta(parsedInput.error));
2750
+ failPolicyGateExecution("Invalid policy gate reportReviewRun input");
2751
+ }
2752
+ scanResult = parsedInput.data;
2753
+ }
2754
+ }
2755
+ if (!scanResult) {
2756
+ logger.warn("[ai-service] Policy gate agent did not call reportReviewRun");
2757
+ failPolicyGateExecution("Policy gate agent did not complete a report");
2758
+ }
2759
+ const completedScanResult = scanResult;
2760
+ if (!completedScanResult.scanComplete) {
2761
+ logger.warn("[ai-service] Policy gate agent reported an incomplete scan", {
2762
+ findingCount: completedScanResult.findings.length,
2763
+ scannedFiles: completedScanResult.scannedFiles,
2764
+ });
2765
+ failPolicyGateExecution("Policy gate agent did not complete a report");
2766
+ }
2767
+ logger.info(`[ai-service] Policy gate execution complete: ${completedScanResult.findings.length} findings`);
2768
+ return {
2769
+ ...completedScanResult,
2770
+ runtimeMetadataJson: buildRuntimeMetadata(),
2771
+ };
2772
+ }
2188
2773
  handleUserChange(change) {
2189
2774
  this.getLogger().info(`[ai-service] User changed: ${change.type}`, change);
2190
2775
  this.clark.updateContext((context) => ({