context-mode 1.0.168 → 2.0.0-next.5

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 (395) hide show
  1. package/.claude-plugin/marketplace.json +4 -22
  2. package/.claude-plugin/plugin.json +9 -24
  3. package/SHA256SUMS +31 -0
  4. package/claude-code/skills/ctx-doctor/SKILL.md +6 -0
  5. package/claude-code/skills/ctx-stats/SKILL.md +6 -0
  6. package/claude-code/skills/ctx-upgrade/SKILL.md +6 -0
  7. package/cli.mjs +6744 -0
  8. package/copilot/plugin.json +14 -0
  9. package/fixtures/hook-roundtrip.json +22 -0
  10. package/gemini-extension.json +14 -0
  11. package/hook.mjs +202 -0
  12. package/hooks/claude-code.json +50 -0
  13. package/hooks/codex.json +50 -0
  14. package/hooks/copilot-cli.json +33 -0
  15. package/hooks/hooks.json +16 -111
  16. package/hooks/kiro.json +69 -0
  17. package/hooks/qwen.json +50 -0
  18. package/hooks/vscode.json +40 -0
  19. package/hooks/zcode.json +53 -0
  20. package/opencode/plugin.js +22456 -0
  21. package/package.json +29 -114
  22. package/pi/index.js +50841 -0
  23. package/pi/skills/ctx-doctor/SKILL.md +6 -0
  24. package/pi/skills/ctx-stats/SKILL.md +6 -0
  25. package/pi/skills/ctx-upgrade/SKILL.md +6 -0
  26. package/qwen-extension.json +15 -0
  27. package/run.mjs +5633 -0
  28. package/search.mjs +5633 -0
  29. package/server.mjs +48198 -0
  30. package/skills/ctx-doctor/SKILL.md +3 -19
  31. package/skills/ctx-stats/SKILL.md +3 -23
  32. package/skills/ctx-upgrade/SKILL.md +3 -28
  33. package/.codex-plugin/hooks.json +0 -65
  34. package/.codex-plugin/mcp.json +0 -13
  35. package/.codex-plugin/plugin.json +0 -31
  36. package/.openclaw-plugin/index.ts +0 -12
  37. package/.openclaw-plugin/openclaw.plugin.json +0 -38
  38. package/.openclaw-plugin/package.json +0 -28
  39. package/LICENSE +0 -94
  40. package/README.md +0 -1619
  41. package/bin/statusline.mjs +0 -378
  42. package/build/adapters/antigravity/index.d.ts +0 -51
  43. package/build/adapters/antigravity/index.js +0 -169
  44. package/build/adapters/antigravity-cli/index.d.ts +0 -51
  45. package/build/adapters/antigravity-cli/index.js +0 -342
  46. package/build/adapters/base.d.ts +0 -92
  47. package/build/adapters/base.js +0 -135
  48. package/build/adapters/claude-code/config.d.ts +0 -8
  49. package/build/adapters/claude-code/config.js +0 -8
  50. package/build/adapters/claude-code/hooks.d.ts +0 -106
  51. package/build/adapters/claude-code/hooks.js +0 -183
  52. package/build/adapters/claude-code/index.d.ts +0 -80
  53. package/build/adapters/claude-code/index.js +0 -569
  54. package/build/adapters/claude-code-base.d.ts +0 -49
  55. package/build/adapters/claude-code-base.js +0 -113
  56. package/build/adapters/client-map.d.ts +0 -10
  57. package/build/adapters/client-map.js +0 -42
  58. package/build/adapters/codex/config.d.ts +0 -8
  59. package/build/adapters/codex/config.js +0 -8
  60. package/build/adapters/codex/hooks.d.ts +0 -55
  61. package/build/adapters/codex/hooks.js +0 -64
  62. package/build/adapters/codex/index.d.ts +0 -87
  63. package/build/adapters/codex/index.js +0 -992
  64. package/build/adapters/codex/paths.d.ts +0 -1
  65. package/build/adapters/codex/paths.js +0 -12
  66. package/build/adapters/codex/usage.d.ts +0 -107
  67. package/build/adapters/codex/usage.js +0 -227
  68. package/build/adapters/copilot-base.d.ts +0 -94
  69. package/build/adapters/copilot-base.js +0 -297
  70. package/build/adapters/copilot-cli/hooks.d.ts +0 -33
  71. package/build/adapters/copilot-cli/hooks.js +0 -64
  72. package/build/adapters/copilot-cli/index.d.ts +0 -48
  73. package/build/adapters/copilot-cli/index.js +0 -341
  74. package/build/adapters/cursor/config.d.ts +0 -4
  75. package/build/adapters/cursor/config.js +0 -4
  76. package/build/adapters/cursor/hooks.d.ts +0 -55
  77. package/build/adapters/cursor/hooks.js +0 -82
  78. package/build/adapters/cursor/index.d.ts +0 -64
  79. package/build/adapters/cursor/index.js +0 -517
  80. package/build/adapters/detect.d.ts +0 -136
  81. package/build/adapters/detect.js +0 -623
  82. package/build/adapters/gemini-cli/config.d.ts +0 -8
  83. package/build/adapters/gemini-cli/config.js +0 -8
  84. package/build/adapters/gemini-cli/hooks.d.ts +0 -83
  85. package/build/adapters/gemini-cli/hooks.js +0 -115
  86. package/build/adapters/gemini-cli/index.d.ts +0 -80
  87. package/build/adapters/gemini-cli/index.js +0 -483
  88. package/build/adapters/jetbrains-copilot/config.d.ts +0 -8
  89. package/build/adapters/jetbrains-copilot/config.js +0 -8
  90. package/build/adapters/jetbrains-copilot/hooks.d.ts +0 -59
  91. package/build/adapters/jetbrains-copilot/hooks.js +0 -87
  92. package/build/adapters/jetbrains-copilot/index.d.ts +0 -31
  93. package/build/adapters/jetbrains-copilot/index.js +0 -131
  94. package/build/adapters/kimi/config.d.ts +0 -8
  95. package/build/adapters/kimi/config.js +0 -8
  96. package/build/adapters/kimi/hooks.d.ts +0 -28
  97. package/build/adapters/kimi/hooks.js +0 -34
  98. package/build/adapters/kimi/index.d.ts +0 -66
  99. package/build/adapters/kimi/index.js +0 -537
  100. package/build/adapters/kimi/paths.d.ts +0 -21
  101. package/build/adapters/kimi/paths.js +0 -52
  102. package/build/adapters/kimi/usage.d.ts +0 -82
  103. package/build/adapters/kimi/usage.js +0 -217
  104. package/build/adapters/kiro/hooks.d.ts +0 -61
  105. package/build/adapters/kiro/hooks.js +0 -94
  106. package/build/adapters/kiro/index.d.ts +0 -61
  107. package/build/adapters/kiro/index.js +0 -301
  108. package/build/adapters/omp/index.d.ts +0 -65
  109. package/build/adapters/omp/index.js +0 -182
  110. package/build/adapters/omp/plugin.d.ts +0 -81
  111. package/build/adapters/omp/plugin.js +0 -331
  112. package/build/adapters/omp/usage.d.ts +0 -49
  113. package/build/adapters/omp/usage.js +0 -110
  114. package/build/adapters/openclaw/config.d.ts +0 -8
  115. package/build/adapters/openclaw/config.js +0 -8
  116. package/build/adapters/openclaw/hooks.d.ts +0 -50
  117. package/build/adapters/openclaw/hooks.js +0 -61
  118. package/build/adapters/openclaw/index.d.ts +0 -74
  119. package/build/adapters/openclaw/index.js +0 -439
  120. package/build/adapters/openclaw/mcp-tools.d.ts +0 -54
  121. package/build/adapters/openclaw/mcp-tools.js +0 -207
  122. package/build/adapters/openclaw/plugin.d.ts +0 -141
  123. package/build/adapters/openclaw/plugin.js +0 -818
  124. package/build/adapters/openclaw/session-db.d.ts +0 -55
  125. package/build/adapters/openclaw/session-db.js +0 -88
  126. package/build/adapters/openclaw/usage.d.ts +0 -34
  127. package/build/adapters/openclaw/usage.js +0 -52
  128. package/build/adapters/openclaw/workspace-router.d.ts +0 -29
  129. package/build/adapters/openclaw/workspace-router.js +0 -64
  130. package/build/adapters/opencode/config.d.ts +0 -8
  131. package/build/adapters/opencode/config.js +0 -8
  132. package/build/adapters/opencode/hooks.d.ts +0 -38
  133. package/build/adapters/opencode/hooks.js +0 -50
  134. package/build/adapters/opencode/index.d.ts +0 -67
  135. package/build/adapters/opencode/index.js +0 -463
  136. package/build/adapters/opencode/plugin.d.ts +0 -184
  137. package/build/adapters/opencode/plugin.js +0 -558
  138. package/build/adapters/opencode/zod3tov4.d.ts +0 -1
  139. package/build/adapters/opencode/zod3tov4.js +0 -132
  140. package/build/adapters/pi/extension.d.ts +0 -81
  141. package/build/adapters/pi/extension.js +0 -861
  142. package/build/adapters/pi/index.d.ts +0 -57
  143. package/build/adapters/pi/index.js +0 -173
  144. package/build/adapters/pi/mcp-bridge.d.ts +0 -268
  145. package/build/adapters/pi/mcp-bridge.js +0 -894
  146. package/build/adapters/qwen-code/hooks.d.ts +0 -26
  147. package/build/adapters/qwen-code/hooks.js +0 -29
  148. package/build/adapters/qwen-code/index.d.ts +0 -40
  149. package/build/adapters/qwen-code/index.js +0 -361
  150. package/build/adapters/qwen-code/usage.d.ts +0 -90
  151. package/build/adapters/qwen-code/usage.js +0 -222
  152. package/build/adapters/types.d.ts +0 -360
  153. package/build/adapters/types.js +0 -118
  154. package/build/adapters/vscode-copilot/config.d.ts +0 -8
  155. package/build/adapters/vscode-copilot/config.js +0 -8
  156. package/build/adapters/vscode-copilot/hooks.d.ts +0 -71
  157. package/build/adapters/vscode-copilot/hooks.js +0 -98
  158. package/build/adapters/vscode-copilot/index.d.ts +0 -32
  159. package/build/adapters/vscode-copilot/index.js +0 -222
  160. package/build/adapters/zed/index.d.ts +0 -40
  161. package/build/adapters/zed/index.js +0 -163
  162. package/build/cache-heal.d.ts +0 -48
  163. package/build/cache-heal.js +0 -150
  164. package/build/cli.d.ts +0 -34
  165. package/build/cli.js +0 -1827
  166. package/build/concurrency/runPool.d.ts +0 -36
  167. package/build/concurrency/runPool.js +0 -51
  168. package/build/db-base.d.ts +0 -179
  169. package/build/db-base.js +0 -569
  170. package/build/executor.d.ts +0 -72
  171. package/build/executor.js +0 -678
  172. package/build/exit-classify.d.ts +0 -19
  173. package/build/exit-classify.js +0 -12
  174. package/build/fetch-cache.d.ts +0 -13
  175. package/build/fetch-cache.js +0 -15
  176. package/build/lifecycle.d.ts +0 -124
  177. package/build/lifecycle.js +0 -278
  178. package/build/openclaw/mcp-tools.d.ts +0 -54
  179. package/build/openclaw/mcp-tools.js +0 -198
  180. package/build/openclaw/workspace-router.d.ts +0 -29
  181. package/build/openclaw/workspace-router.js +0 -64
  182. package/build/openclaw-plugin.d.ts +0 -130
  183. package/build/openclaw-plugin.js +0 -626
  184. package/build/opencode-plugin.d.ts +0 -122
  185. package/build/opencode-plugin.js +0 -375
  186. package/build/pi-extension.d.ts +0 -14
  187. package/build/pi-extension.js +0 -451
  188. package/build/routing-block.d.ts +0 -8
  189. package/build/routing-block.js +0 -86
  190. package/build/runPool.d.ts +0 -36
  191. package/build/runPool.js +0 -51
  192. package/build/runtime.d.ts +0 -71
  193. package/build/runtime.js +0 -659
  194. package/build/search/auto-memory.d.ts +0 -47
  195. package/build/search/auto-memory.js +0 -170
  196. package/build/search/ctx-search-schema.d.ts +0 -90
  197. package/build/search/ctx-search-schema.js +0 -135
  198. package/build/search/flood-guard.d.ts +0 -57
  199. package/build/search/flood-guard.js +0 -80
  200. package/build/search/unified.d.ts +0 -56
  201. package/build/search/unified.js +0 -104
  202. package/build/security.d.ts +0 -206
  203. package/build/security.js +0 -780
  204. package/build/server.d.ts +0 -184
  205. package/build/server.js +0 -4542
  206. package/build/session/analytics.d.ts +0 -745
  207. package/build/session/analytics.js +0 -2369
  208. package/build/session/db.d.ts +0 -468
  209. package/build/session/db.js +0 -1308
  210. package/build/session/error-classifier.d.ts +0 -87
  211. package/build/session/error-classifier.js +0 -303
  212. package/build/session/event-emit.d.ts +0 -48
  213. package/build/session/event-emit.js +0 -101
  214. package/build/session/extract.d.ts +0 -333
  215. package/build/session/extract.js +0 -2688
  216. package/build/session/model-prices.json +0 -429
  217. package/build/session/persist-tool-calls.d.ts +0 -54
  218. package/build/session/persist-tool-calls.js +0 -105
  219. package/build/session/pricing.d.ts +0 -64
  220. package/build/session/pricing.js +0 -151
  221. package/build/session/project-attribution.d.ts +0 -73
  222. package/build/session/project-attribution.js +0 -245
  223. package/build/session/purge.d.ts +0 -138
  224. package/build/session/purge.js +0 -240
  225. package/build/session/snapshot.d.ts +0 -46
  226. package/build/session/snapshot.js +0 -532
  227. package/build/store-directory.d.ts +0 -56
  228. package/build/store-directory.js +0 -254
  229. package/build/store.d.ts +0 -147
  230. package/build/store.js +0 -1714
  231. package/build/tool-naming.d.ts +0 -4
  232. package/build/tool-naming.js +0 -24
  233. package/build/truncate.d.ts +0 -57
  234. package/build/truncate.js +0 -142
  235. package/build/types.d.ts +0 -117
  236. package/build/types.js +0 -20
  237. package/build/util/claude-config.d.ts +0 -32
  238. package/build/util/claude-config.js +0 -84
  239. package/build/util/hook-config.d.ts +0 -27
  240. package/build/util/hook-config.js +0 -76
  241. package/build/util/jsonc.d.ts +0 -14
  242. package/build/util/jsonc.js +0 -104
  243. package/build/util/plugin-cache-integrity.d.ts +0 -51
  244. package/build/util/plugin-cache-integrity.js +0 -146
  245. package/build/util/project-dir.d.ts +0 -149
  246. package/build/util/project-dir.js +0 -340
  247. package/build/util/sibling-mcp.d.ts +0 -79
  248. package/build/util/sibling-mcp.js +0 -181
  249. package/cli.bundle.mjs +0 -1235
  250. package/configs/antigravity/GEMINI.md +0 -68
  251. package/configs/antigravity/mcp_config.json +0 -7
  252. package/configs/antigravity-cli/hooks/hooks.json +0 -37
  253. package/configs/antigravity-cli/hooks.json +0 -37
  254. package/configs/antigravity-cli/mcp_config.json +0 -10
  255. package/configs/antigravity-cli/plugin.json +0 -14
  256. package/configs/antigravity-cli/rules/context-mode.md +0 -77
  257. package/configs/antigravity-cli/skills/context-mode/SKILL.md +0 -77
  258. package/configs/claude-code/CLAUDE.md +0 -91
  259. package/configs/codex/AGENTS.md +0 -99
  260. package/configs/codex/config.toml +0 -8
  261. package/configs/codex/hooks.json +0 -47
  262. package/configs/copilot-cli/.github/plugin/plugin.json +0 -23
  263. package/configs/copilot-cli/.mcp.json +0 -12
  264. package/configs/copilot-cli/README.md +0 -47
  265. package/configs/copilot-cli/hooks.json +0 -41
  266. package/configs/copilot-cli/skills/context-mode/SKILL.md +0 -38
  267. package/configs/cursor/context-mode.mdc +0 -76
  268. package/configs/cursor/hooks.json +0 -21
  269. package/configs/cursor/mcp.json +0 -7
  270. package/configs/gemini-cli/GEMINI.md +0 -88
  271. package/configs/gemini-cli/mcp.json +0 -7
  272. package/configs/gemini-cli/settings.json +0 -60
  273. package/configs/jetbrains-copilot/copilot-instructions.md +0 -80
  274. package/configs/jetbrains-copilot/hooks.json +0 -16
  275. package/configs/jetbrains-copilot/mcp.json +0 -7
  276. package/configs/kilo/AGENTS.md +0 -84
  277. package/configs/kilo/kilo.json +0 -6
  278. package/configs/kimi/hooks.json +0 -54
  279. package/configs/kiro/KIRO.md +0 -84
  280. package/configs/kiro/agent.json +0 -18
  281. package/configs/kiro/mcp.json +0 -7
  282. package/configs/omp/SYSTEM.md +0 -85
  283. package/configs/omp/mcp.json +0 -7
  284. package/configs/openclaw/AGENTS.md +0 -87
  285. package/configs/openclaw/openclaw.json +0 -13
  286. package/configs/opencode/AGENTS.md +0 -84
  287. package/configs/opencode/opencode.json +0 -6
  288. package/configs/pi/AGENTS.md +0 -3
  289. package/configs/qwen-code/QWEN.md +0 -91
  290. package/configs/vscode-copilot/copilot-instructions.md +0 -80
  291. package/configs/vscode-copilot/hooks.json +0 -16
  292. package/configs/vscode-copilot/mcp.json +0 -7
  293. package/configs/zed/AGENTS.md +0 -68
  294. package/hooks/antigravity-cli/payload.mjs +0 -98
  295. package/hooks/antigravity-cli/posttooluse.mjs +0 -138
  296. package/hooks/antigravity-cli/pretooluse.mjs +0 -78
  297. package/hooks/antigravity-cli/stop.mjs +0 -58
  298. package/hooks/auto-injection.mjs +0 -102
  299. package/hooks/cache-heal-utils.mjs +0 -379
  300. package/hooks/codex/platform.mjs +0 -1
  301. package/hooks/codex/posttooluse.mjs +0 -66
  302. package/hooks/codex/precompact.mjs +0 -70
  303. package/hooks/codex/pretooluse.mjs +0 -42
  304. package/hooks/codex/sessionstart.mjs +0 -121
  305. package/hooks/codex/stop.mjs +0 -138
  306. package/hooks/codex/userpromptsubmit.mjs +0 -75
  307. package/hooks/copilot-cli/posttooluse.mjs +0 -79
  308. package/hooks/copilot-cli/precompact.mjs +0 -66
  309. package/hooks/copilot-cli/pretooluse.mjs +0 -41
  310. package/hooks/copilot-cli/sessionstart.mjs +0 -121
  311. package/hooks/copilot-cli/stop.mjs +0 -59
  312. package/hooks/copilot-cli/userpromptsubmit.mjs +0 -77
  313. package/hooks/core/codex-caps.mjs +0 -112
  314. package/hooks/core/formatters.mjs +0 -356
  315. package/hooks/core/mcp-ready.mjs +0 -109
  316. package/hooks/core/platform-detect.mjs +0 -49
  317. package/hooks/core/routing.mjs +0 -1017
  318. package/hooks/core/stdin.mjs +0 -90
  319. package/hooks/core/tool-naming.mjs +0 -57
  320. package/hooks/cursor/afteragentresponse.mjs +0 -74
  321. package/hooks/cursor/hooks.json +0 -31
  322. package/hooks/cursor/posttooluse.mjs +0 -72
  323. package/hooks/cursor/pretooluse.mjs +0 -26
  324. package/hooks/cursor/sessionstart.mjs +0 -97
  325. package/hooks/cursor/stop.mjs +0 -48
  326. package/hooks/ensure-deps.mjs +0 -254
  327. package/hooks/formatters/claude-code.mjs +0 -113
  328. package/hooks/formatters/cursor.mjs +0 -37
  329. package/hooks/formatters/gemini-cli.mjs +0 -55
  330. package/hooks/formatters/vscode-copilot.mjs +0 -55
  331. package/hooks/gemini-cli/aftermodel.mjs +0 -70
  332. package/hooks/gemini-cli/aftertool.mjs +0 -61
  333. package/hooks/gemini-cli/beforeagent.mjs +0 -99
  334. package/hooks/gemini-cli/beforetool.mjs +0 -27
  335. package/hooks/gemini-cli/precompress.mjs +0 -55
  336. package/hooks/gemini-cli/sessionstart.mjs +0 -137
  337. package/hooks/heal-partial-install.mjs +0 -712
  338. package/hooks/jetbrains-copilot/posttooluse.mjs +0 -61
  339. package/hooks/jetbrains-copilot/precompact.mjs +0 -55
  340. package/hooks/jetbrains-copilot/pretooluse.mjs +0 -27
  341. package/hooks/jetbrains-copilot/sessionstart.mjs +0 -124
  342. package/hooks/kimi/platform.mjs +0 -1
  343. package/hooks/kimi/posttooluse.mjs +0 -72
  344. package/hooks/kimi/precompact.mjs +0 -80
  345. package/hooks/kimi/pretooluse.mjs +0 -42
  346. package/hooks/kimi/sessionend.mjs +0 -61
  347. package/hooks/kimi/sessionstart.mjs +0 -113
  348. package/hooks/kimi/stop.mjs +0 -132
  349. package/hooks/kimi/userpromptsubmit.mjs +0 -90
  350. package/hooks/kiro/agentspawn.mjs +0 -97
  351. package/hooks/kiro/posttooluse.mjs +0 -51
  352. package/hooks/kiro/pretooluse.mjs +0 -64
  353. package/hooks/kiro/userpromptsubmit.mjs +0 -88
  354. package/hooks/normalize-hooks.mjs +0 -323
  355. package/hooks/platform-bridge.mjs +0 -340
  356. package/hooks/posttooluse.mjs +0 -187
  357. package/hooks/precompact.mjs +0 -100
  358. package/hooks/pretooluse.mjs +0 -227
  359. package/hooks/qwen-code/platform.mjs +0 -1
  360. package/hooks/qwen-code/stop.mjs +0 -168
  361. package/hooks/routing-block.mjs +0 -105
  362. package/hooks/run-hook.mjs +0 -95
  363. package/hooks/security.bundle.mjs +0 -2
  364. package/hooks/session-attribution.bundle.mjs +0 -1
  365. package/hooks/session-db.bundle.mjs +0 -144
  366. package/hooks/session-directive.mjs +0 -520
  367. package/hooks/session-extract.bundle.mjs +0 -3
  368. package/hooks/session-helpers.mjs +0 -428
  369. package/hooks/session-loaders.mjs +0 -448
  370. package/hooks/session-snapshot.bundle.mjs +0 -32
  371. package/hooks/sessionstart.mjs +0 -464
  372. package/hooks/stop.mjs +0 -82
  373. package/hooks/suppress-stderr.mjs +0 -25
  374. package/hooks/userpromptsubmit.mjs +0 -108
  375. package/hooks/vscode-copilot/posttooluse.mjs +0 -61
  376. package/hooks/vscode-copilot/precompact.mjs +0 -55
  377. package/hooks/vscode-copilot/pretooluse.mjs +0 -27
  378. package/hooks/vscode-copilot/sessionstart.mjs +0 -129
  379. package/openclaw.plugin.json +0 -38
  380. package/scripts/heal-better-sqlite3.mjs +0 -430
  381. package/scripts/heal-installed-plugins.mjs +0 -598
  382. package/scripts/plugin-cache-integrity.mjs +0 -248
  383. package/scripts/postinstall.mjs +0 -384
  384. package/server.bundle.mjs +0 -1161
  385. package/skills/.ignore +0 -7
  386. package/skills/context-mode/SKILL.md +0 -300
  387. package/skills/context-mode/references/anti-patterns.md +0 -283
  388. package/skills/context-mode/references/patterns-javascript.md +0 -298
  389. package/skills/context-mode/references/patterns-python.md +0 -304
  390. package/skills/context-mode/references/patterns-shell.md +0 -277
  391. package/skills/ctx-index/SKILL.md +0 -46
  392. package/skills/ctx-insight/SKILL.md +0 -24
  393. package/skills/ctx-purge/SKILL.md +0 -49
  394. package/skills/ctx-search/SKILL.md +0 -35
  395. package/start.mjs +0 -616
@@ -1,451 +0,0 @@
1
- /**
2
- * Pi coding agent extension for context-mode.
3
- *
4
- * Follows the OpenClaw adapter pattern: imports shared session modules,
5
- * registers Pi-specific hooks. NO copy-paste of session logic.
6
- * NO external npm dependencies beyond what Pi runtime provides.
7
- *
8
- * Entry point: `export default function(pi: ExtensionAPI) { ... }`
9
- *
10
- * Lifecycle: session_start, tool_call, tool_result, before_agent_start,
11
- * session_before_compact, session_compact, session_shutdown.
12
- */
13
- import { createHash } from "node:crypto";
14
- import { existsSync, mkdirSync } from "node:fs";
15
- import { homedir } from "node:os";
16
- import { join, resolve, dirname } from "node:path";
17
- import { fileURLToPath, pathToFileURL } from "node:url";
18
- import { SessionDB } from "./session/db.js";
19
- import { extractEvents, extractUserEvents } from "./session/extract.js";
20
- import { buildResumeSnapshot } from "./session/snapshot.js";
21
- // ── Pi Tool Name Mapping ─────────────────────────────────
22
- // Pi uses lowercase; shared extractors expect PascalCase (Claude Code convention).
23
- const PI_TOOL_MAP = {
24
- bash: "Bash",
25
- read: "Read",
26
- write: "Write",
27
- edit: "Edit",
28
- grep: "Grep",
29
- find: "Glob",
30
- ls: "Glob",
31
- };
32
- // ── Routing patterns ─────────────────────────────────────
33
- // Inline HTTP client patterns to block in bash — self-contained, no routing module needed.
34
- const BLOCKED_BASH_PATTERNS = [
35
- /\bcurl\s/,
36
- /\bwget\s/,
37
- /\bfetch\s*\(/,
38
- /\brequests\.get\s*\(/,
39
- /\brequests\.post\s*\(/,
40
- /\bhttp\.get\s*\(/,
41
- /\bhttp\.request\s*\(/,
42
- /\burllib\.request/,
43
- /\bInvoke-WebRequest\b/,
44
- ];
45
- // ── Module-level DB singleton ────────────────────────────
46
- let _db = null;
47
- let _sessionId = "";
48
- // Per-session gate: routing block injected at most once per session_id.
49
- const _routingInjected = new Set();
50
- // Cached routing-block string (built once per process from hooks/routing-block.mjs).
51
- let _routingBlock = null;
52
- async function getRoutingBlock(pluginRoot) {
53
- if (_routingBlock !== null)
54
- return _routingBlock;
55
- try {
56
- const routingMod = await import(pathToFileURL(join(pluginRoot, "hooks", "routing-block.mjs")).href);
57
- const namingMod = await import(pathToFileURL(join(pluginRoot, "hooks", "core", "tool-naming.mjs")).href);
58
- const t = namingMod.createToolNamer("pi");
59
- _routingBlock = String(routingMod.createRoutingBlock(t));
60
- }
61
- catch {
62
- _routingBlock = "";
63
- }
64
- return _routingBlock;
65
- }
66
- // Cached buildAutoInjection (500-token cap, prioritized).
67
- let _buildAutoInjection = undefined;
68
- async function getAutoInjection(pluginRoot) {
69
- if (_buildAutoInjection !== undefined)
70
- return _buildAutoInjection;
71
- try {
72
- const mod = await import(pathToFileURL(join(pluginRoot, "hooks", "auto-injection.mjs")).href);
73
- _buildAutoInjection = mod.buildAutoInjection;
74
- }
75
- catch {
76
- _buildAutoInjection = null;
77
- }
78
- return _buildAutoInjection ?? null;
79
- }
80
- // ── Helpers ──────────────────────────────────────────────
81
- function getSessionDir() {
82
- const dir = join(homedir(), ".pi", "context-mode", "sessions");
83
- mkdirSync(dir, { recursive: true });
84
- return dir;
85
- }
86
- function getDBPath() {
87
- return join(getSessionDir(), "context-mode.db");
88
- }
89
- function getOrCreateDB() {
90
- if (!_db) {
91
- _db = new SessionDB({ dbPath: getDBPath() });
92
- }
93
- return _db;
94
- }
95
- /** Derive a stable session ID from Pi's session file path (SHA256, 16 hex chars). */
96
- function deriveSessionId(ctx) {
97
- try {
98
- const sessionManager = ctx.sessionManager;
99
- const sessionFile = sessionManager?.getSessionFile?.();
100
- if (sessionFile && typeof sessionFile === "string") {
101
- return createHash("sha256").update(sessionFile).digest("hex").slice(0, 16);
102
- }
103
- }
104
- catch {
105
- // best effort
106
- }
107
- return `pi-${Date.now()}`;
108
- }
109
- /** Build stats text for the /ctx-stats command. */
110
- function buildStatsText(db, sessionId) {
111
- try {
112
- const events = db.getEvents(sessionId);
113
- const stats = db.getSessionStats(sessionId);
114
- const lines = [
115
- "## context-mode stats (Pi)",
116
- "",
117
- `- Session: \`${sessionId.slice(0, 8)}...\``,
118
- `- Events captured: ${events.length}`,
119
- `- Compactions: ${stats?.compact_count ?? 0}`,
120
- ];
121
- // Event breakdown by category
122
- const byCategory = {};
123
- for (const ev of events) {
124
- const key = ev.category ?? "unknown";
125
- byCategory[key] = (byCategory[key] ?? 0) + 1;
126
- }
127
- if (Object.keys(byCategory).length > 0) {
128
- lines.push("- Event breakdown:");
129
- for (const [category, count] of Object.entries(byCategory)) {
130
- lines.push(` - ${category}: ${count}`);
131
- }
132
- }
133
- // Session age
134
- if (stats?.started_at) {
135
- const startedMs = new Date(stats.started_at).getTime();
136
- const ageMinutes = Math.round((Date.now() - startedMs) / 60_000);
137
- lines.push(`- Session age: ${ageMinutes}m`);
138
- }
139
- return lines.join("\n");
140
- }
141
- catch {
142
- return "context-mode stats unavailable (session DB error)";
143
- }
144
- }
145
- function resolveCommandContext(argsOrCtx, ctx) {
146
- if (ctx !== undefined)
147
- return ctx;
148
- if (argsOrCtx && typeof argsOrCtx === "object")
149
- return argsOrCtx;
150
- return undefined;
151
- }
152
- function handleCommandText(text, ctx) {
153
- if (ctx?.hasUI) {
154
- ctx.ui.notify(text, "info");
155
- return;
156
- }
157
- return { text };
158
- }
159
- // ── Extension entry point ────────────────────────────────
160
- /** Pi extension default export. Called once by Pi runtime with the extension API. */
161
- export default function piExtension(pi) {
162
- const buildDir = dirname(fileURLToPath(import.meta.url));
163
- const pluginRoot = resolve(buildDir, "..");
164
- const projectDir = process.env.PI_PROJECT_DIR || process.cwd();
165
- const db = getOrCreateDB();
166
- // ── 1. session_start — Initialize session ──────────────
167
- pi.on("session_start", (ctx) => {
168
- try {
169
- _sessionId = deriveSessionId(ctx ?? {});
170
- db.ensureSession(_sessionId, projectDir);
171
- db.cleanupOldSessions(7);
172
- }
173
- catch {
174
- // best effort — never break session start
175
- if (!_sessionId) {
176
- _sessionId = `pi-${Date.now()}`;
177
- }
178
- }
179
- });
180
- // ── 2. tool_call — PreToolUse routing enforcement ──────
181
- // Block bash commands that contain curl/wget/fetch/requests patterns.
182
- pi.on("tool_call", (event) => {
183
- try {
184
- const toolName = String(event?.toolName ?? "").toLowerCase();
185
- if (toolName !== "bash")
186
- return;
187
- const command = String(event?.input?.command ?? "");
188
- if (!command)
189
- return;
190
- const isBlocked = BLOCKED_BASH_PATTERNS.some((p) => p.test(command));
191
- if (isBlocked) {
192
- return {
193
- block: true,
194
- reason: "Use context-mode MCP tools (execute, fetch_and_index) instead of inline HTTP clients. " +
195
- "Raw curl/wget/fetch output floods the context window.",
196
- };
197
- }
198
- }
199
- catch {
200
- // Routing failure — allow passthrough
201
- }
202
- });
203
- // ── 3. tool_result — PostToolUse event capture ─────────
204
- pi.on("tool_result", (event) => {
205
- try {
206
- if (!_sessionId)
207
- return;
208
- const rawToolName = String(event?.toolName ?? event?.tool_name ?? "");
209
- const mappedToolName = PI_TOOL_MAP[rawToolName.toLowerCase()] ?? rawToolName;
210
- // Normalize result to string
211
- const rawResult = event?.result ?? event?.output;
212
- const resultStr = typeof rawResult === "string"
213
- ? rawResult
214
- : rawResult != null
215
- ? JSON.stringify(rawResult)
216
- : undefined;
217
- // Detect errors
218
- const hasError = Boolean(event?.error || event?.isError);
219
- const hookInput = {
220
- tool_name: mappedToolName,
221
- tool_input: event?.params ?? event?.input ?? {},
222
- tool_response: resultStr,
223
- tool_output: hasError ? { isError: true } : undefined,
224
- };
225
- const events = extractEvents(hookInput);
226
- if (events.length > 0) {
227
- for (const ev of events) {
228
- db.insertEvent(_sessionId, ev, "PostToolUse");
229
- }
230
- }
231
- else if (rawToolName) {
232
- // Fallback: record unrecognized tool call as generic event
233
- const data = JSON.stringify({
234
- tool: rawToolName,
235
- params: event?.params ?? event?.input,
236
- });
237
- db.insertEvent(_sessionId, {
238
- type: "tool_call",
239
- category: "pi",
240
- data,
241
- priority: 1,
242
- data_hash: createHash("sha256")
243
- .update(data)
244
- .digest("hex")
245
- .slice(0, 16),
246
- }, "PostToolUse");
247
- }
248
- }
249
- catch {
250
- // Silent — session capture must never break the tool call
251
- }
252
- });
253
- // ── 4. before_agent_start — Routing + active_memory + resume injection ─
254
- pi.on("before_agent_start", async (event) => {
255
- try {
256
- if (!_sessionId)
257
- return;
258
- const prompt = String(event?.prompt ?? "");
259
- // Extract user events from the prompt text
260
- if (prompt) {
261
- const userEvents = extractUserEvents(prompt);
262
- for (const ev of userEvents) {
263
- db.insertEvent(_sessionId, ev, "UserPromptSubmit");
264
- }
265
- }
266
- const existingPrompt = String(event?.systemPrompt ?? "");
267
- const parts = [];
268
- if (existingPrompt)
269
- parts.push(existingPrompt);
270
- // Pi-1: Inject routing block once per session (gated by _routingInjected).
271
- // v1.0.107 — visible marker so Pi users can verify the routing block
272
- // reached the model (Mickey-class verification path; mirrors OpenCode).
273
- if (!_routingInjected.has(_sessionId)) {
274
- const routingBlock = await getRoutingBlock(pluginRoot);
275
- if (routingBlock) {
276
- const marker = `<!-- context-mode: routing block injected (sessionID=${String(_sessionId).slice(0, 8)}) -->`;
277
- parts.push(marker + "\n" + routingBlock);
278
- _routingInjected.add(_sessionId);
279
- }
280
- }
281
- // Pi-3 + Pi-4: Always build active_memory (not just post-compact),
282
- // capped at 500 tokens via buildAutoInjection. Falls back to inline
283
- // budget loop if the helper is unavailable.
284
- const activeEvents = db.getEvents(_sessionId, {
285
- minPriority: 3,
286
- limit: 50,
287
- });
288
- if (activeEvents.length > 0) {
289
- const buildAuto = await getAutoInjection(pluginRoot);
290
- let memoryContext = "";
291
- if (buildAuto) {
292
- memoryContext = buildAuto(activeEvents.map((e) => ({
293
- category: String(e.category ?? ""),
294
- data: String(e.data ?? ""),
295
- })));
296
- }
297
- // Fallback (or if helper produced empty output): inline 500-token cap.
298
- if (!memoryContext) {
299
- const memoryLines = ["<active_memory>"];
300
- let budget = 2000; // ~500 tokens at 4 chars/token
301
- for (const ev of activeEvents) {
302
- const line = ` <event type="${ev.type}" category="${ev.category}">${ev.data}</event>`;
303
- if (line.length > budget)
304
- break;
305
- memoryLines.push(line);
306
- budget -= line.length;
307
- }
308
- memoryLines.push("</active_memory>");
309
- if (memoryLines.length > 2)
310
- memoryContext = memoryLines.join("\n");
311
- }
312
- if (memoryContext)
313
- parts.push(memoryContext);
314
- }
315
- // Resume snapshot (only when present and unconsumed).
316
- const resume = db.getResume(_sessionId);
317
- if (resume && !resume.consumed && resume.snapshot) {
318
- parts.push(resume.snapshot);
319
- db.markResumeConsumed(_sessionId);
320
- }
321
- // Return modified systemPrompt only if we added something beyond existing.
322
- const baseLen = existingPrompt ? 1 : 0;
323
- if (parts.length > baseLen) {
324
- return { systemPrompt: parts.join("\n\n") };
325
- }
326
- }
327
- catch {
328
- // best effort — never break agent start
329
- }
330
- });
331
- // ── 4b. before_provider_response — capture response metadata ───
332
- // Pi-2: Register the missing event so providers can record latency,
333
- // model, and token usage when Pi exposes them. Best-effort only;
334
- // the handler must never throw or modify the response.
335
- pi.on("before_provider_response", (event) => {
336
- try {
337
- if (!_sessionId)
338
- return;
339
- const meta = {
340
- model: event?.model ?? event?.providerModel,
341
- provider: event?.provider,
342
- latencyMs: event?.latencyMs ?? event?.latency,
343
- tokens: event?.usage ?? event?.tokens,
344
- };
345
- // Skip when Pi gives us nothing useful — avoids noise in the DB.
346
- if (meta.model == null &&
347
- meta.provider == null &&
348
- meta.latencyMs == null &&
349
- meta.tokens == null) {
350
- return;
351
- }
352
- const data = JSON.stringify(meta);
353
- db.insertEvent(_sessionId, {
354
- type: "provider_response",
355
- category: "pi",
356
- data,
357
- priority: 1,
358
- data_hash: createHash("sha256").update(data).digest("hex").slice(0, 16),
359
- }, "PostToolUse");
360
- }
361
- catch {
362
- // best effort — never break provider response
363
- }
364
- });
365
- // ── 5. session_before_compact — Build resume snapshot ──
366
- pi.on("session_before_compact", () => {
367
- try {
368
- if (!_sessionId)
369
- return;
370
- const allEvents = db.getEvents(_sessionId);
371
- if (allEvents.length === 0)
372
- return;
373
- const stats = db.getSessionStats(_sessionId);
374
- const snapshot = buildResumeSnapshot(allEvents, {
375
- compactCount: (stats?.compact_count ?? 0) + 1,
376
- });
377
- db.upsertResume(_sessionId, snapshot, allEvents.length);
378
- }
379
- catch {
380
- // best effort — never break compaction
381
- }
382
- });
383
- // ── 6. session_compact — Increment compact counter ─────
384
- pi.on("session_compact", () => {
385
- try {
386
- if (!_sessionId)
387
- return;
388
- db.incrementCompactCount(_sessionId);
389
- }
390
- catch {
391
- // best effort
392
- }
393
- });
394
- // ── 7. session_shutdown — Cleanup old sessions ─────────
395
- pi.on("session_shutdown", () => {
396
- try {
397
- if (_db) {
398
- _db.cleanupOldSessions(7);
399
- }
400
- _db = null;
401
- _routingInjected.clear();
402
- _sessionId = "";
403
- }
404
- catch {
405
- // best effort — never throw during shutdown
406
- }
407
- });
408
- // ── 8. Slash commands ──────────────────────────────────
409
- pi.registerCommand("ctx-stats", {
410
- description: "Show context-mode session statistics",
411
- handler: async (argsOrCtx, maybeCtx) => {
412
- const ctx = resolveCommandContext(argsOrCtx, maybeCtx);
413
- const text = !_db || !_sessionId
414
- ? "context-mode: no active session"
415
- : buildStatsText(_db, _sessionId);
416
- return handleCommandText(text, ctx);
417
- },
418
- });
419
- pi.registerCommand("ctx-doctor", {
420
- description: "Run context-mode diagnostics",
421
- handler: async (argsOrCtx, maybeCtx) => {
422
- const ctx = resolveCommandContext(argsOrCtx, maybeCtx);
423
- const dbPath = getDBPath();
424
- const dbExists = existsSync(dbPath);
425
- const lines = [
426
- "## ctx-doctor (Pi)",
427
- "",
428
- `- DB path: \`${dbPath}\``,
429
- `- DB exists: ${dbExists}`,
430
- `- Session ID: \`${_sessionId ? _sessionId.slice(0, 8) + "..." : "none"}\``,
431
- `- Plugin root: \`${pluginRoot}\``,
432
- `- Project dir: \`${projectDir}\``,
433
- ];
434
- if (_db && _sessionId) {
435
- try {
436
- const stats = _db.getSessionStats(_sessionId);
437
- const eventCount = _db.getEventCount(_sessionId);
438
- lines.push(`- Events: ${eventCount}`);
439
- lines.push(`- Compactions: ${stats?.compact_count ?? 0}`);
440
- const resume = _db.getResume(_sessionId);
441
- lines.push(`- Resume snapshot: ${resume ? (resume.consumed ? "consumed" : "available") : "none"}`);
442
- }
443
- catch {
444
- lines.push("- DB query error");
445
- }
446
- }
447
- const text = lines.join("\n");
448
- return handleCommandText(text, ctx);
449
- },
450
- });
451
- }
@@ -1,8 +0,0 @@
1
- import type { ToolNamer } from "./tool-naming.js";
2
- export interface RoutingBlockOptions {
3
- includeCommands?: boolean;
4
- }
5
- export declare function createRoutingBlock(t: ToolNamer, options?: RoutingBlockOptions): string;
6
- export declare function createReadGuidance(t: ToolNamer): string;
7
- export declare function createGrepGuidance(t: ToolNamer): string;
8
- export declare function createBashGuidance(t: ToolNamer): string;
@@ -1,86 +0,0 @@
1
- export function createRoutingBlock(t, options = {}) {
2
- const { includeCommands = true } = options;
3
- return `
4
- <context_window_protection>
5
- <priority_instructions>
6
- Raw tool output floods context window. MUST use context-mode MCP tools. Keep raw data in sandbox.
7
- </priority_instructions>
8
-
9
- <tool_selection_hierarchy>
10
- 0. MEMORY: ${t("ctx_search")}(sort: "timeline")
11
- - After resume, check prior context before asking user.
12
- 1. GATHER: ${t("ctx_batch_execute")}(commands, queries)
13
- - Primary research tool. Runs commands, auto-indexes, searches. ONE call replaces many steps.
14
- - Each command: {label: "section header", command: "shell command"}
15
- - label becomes FTS5 chunk title — descriptive labels improve search.
16
- 2. FOLLOW-UP: ${t("ctx_search")}(queries: ["q1", "q2", ...])
17
- - All follow-up questions. ONE call, many queries (default relevance mode).
18
- 3. PROCESSING: ${t("ctx_execute")}(language, code) | ${t("ctx_execute_file")}(path, language, code)
19
- - API calls, log analysis, data processing.
20
- </tool_selection_hierarchy>
21
-
22
- <forbidden_actions>
23
- - NO Bash for commands producing >20 lines output.
24
- - NO Read for analysis — use execute_file. Read IS correct for files you intend to Edit.
25
- - NO WebFetch — use ${t("ctx_fetch_and_index")}.
26
- - Bash ONLY for git/mkdir/rm/mv/navigation.
27
- - NO ${t("ctx_execute")} or ${t("ctx_execute_file")} for file creation/modification.
28
- ctx_execute is for analysis, processing, computation only.
29
- </forbidden_actions>
30
-
31
- <file_writing_policy>
32
- ALWAYS use native Write/Edit tools for file creation/modification.
33
- NEVER use ${t("ctx_execute")}, ${t("ctx_execute_file")}, or Bash to write files.
34
- Applies to all file types: code, configs, plans, specs, YAML, JSON, markdown.
35
- </file_writing_policy>
36
-
37
- <output_constraints>
38
- <communication_style>
39
- Terse like caveman. Technical substance exact. Only fluff die.
40
- Use fragments when clear. Short synonyms (fix not "implement a solution for").
41
- Technical terms exact. Code blocks unchanged.
42
- Auto-expand for: security warnings, irreversible actions, user confusion.
43
- </communication_style>
44
- <artifact_policy>
45
- Write artifacts (code, configs, PRDs) to FILES. NEVER inline.
46
- Return only: file path + 1-line description.
47
- </artifact_policy>
48
- <response_format>
49
- Concise summary:
50
- - Actions taken (2-3 bullets)
51
- - File paths created/modified
52
- - Key findings
53
- </response_format>
54
- </output_constraints>
55
- <session_continuity>
56
- Skills, roles, and decisions set during this session remain active until the user revokes them.
57
- Do not drop behavioral directives as context grows.
58
- </session_continuity>
59
- ${includeCommands ? `
60
- <ctx_commands>
61
- "ctx stats" | "ctx-stats" | "/ctx-stats" | context savings question
62
- → Call stats MCP tool, display full output verbatim.
63
-
64
- "ctx doctor" | "ctx-doctor" | "/ctx-doctor" | diagnose context-mode
65
- → Call doctor MCP tool, run returned shell command, display as checklist.
66
-
67
- "ctx upgrade" | "ctx-upgrade" | "/ctx-upgrade" | update context-mode
68
- → Call upgrade MCP tool, run returned shell command, display as checklist.
69
-
70
- "ctx purge" | "ctx-purge" | "/ctx-purge" | wipe/reset knowledge base
71
- → Call purge MCP tool with confirm: true. Warn: irreversible.
72
-
73
- After /clear or /compact: knowledge base preserved. Tell user: "context-mode knowledge base preserved. Use \`ctx purge\` to start fresh."
74
- </ctx_commands>
75
- ` : ''}
76
- </context_window_protection>`;
77
- }
78
- export function createReadGuidance(t) {
79
- return '<context_guidance>\n <tip>\n Reading to Edit? Read is correct — Edit needs content in context.\n Reading to analyze/explore? Use ' + t("ctx_execute_file") + '(path, language, code) — only printed summary enters context.\n </tip>\n</context_guidance>';
80
- }
81
- export function createGrepGuidance(t) {
82
- return '<context_guidance>\n <tip>\n May flood context. Use ' + t("ctx_execute") + '(language: "shell", code: "...") to run searches in sandbox. Only printed summary enters context.\n </tip>\n</context_guidance>';
83
- }
84
- export function createBashGuidance(t) {
85
- return '<context_guidance>\n <tip>\n May produce large output. Use ' + t("ctx_batch_execute") + '(commands, queries) for multiple commands, ' + t("ctx_execute") + '(language: "shell", code: "...") for single. Only printed summary enters context. Bash only for: git, mkdir, rm, mv, navigation.\n </tip>\n</context_guidance>';
86
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * Generic in-flight-capped worker pool.
3
- *
4
- * Used by:
5
- * - runBatchCommands (ctx_batch_execute parallel branch)
6
- * - runBatchFetch (ctx_fetch_and_index batch path)
7
- *
8
- * Returns Promise.allSettled-style results so one job's throw cannot
9
- * strand siblings. Caller maps fulfilled/rejected per index. Output
10
- * order is preserved by input index (not completion order).
11
- *
12
- * Designed to be the SINGLE concurrency primitive for the project —
13
- * all "run N independent operations with at most M in flight" needs
14
- * route here. Avoids the worker-pool copy-paste flagged in the
15
- * concurrency PRD architectural review (finding G).
16
- */
17
- export interface PoolJob<T> {
18
- run(): Promise<T>;
19
- }
20
- export interface RunPoolOptions {
21
- /** Hard concurrency cap (1-N). Auto-clamped to job count. */
22
- concurrency: number;
23
- /** Optional: also clamp by `os.cpus().length` (memory-pressure safety). Default false. */
24
- capByCpuCount?: boolean;
25
- /** Optional: per-settled callback (e.g. for progress reporting / metrics). */
26
- onSettled?: (idx: number, result: PromiseSettledResult<unknown>) => void;
27
- }
28
- export interface RunPoolResult<T> {
29
- /** Per-index settled result, ordered by input index. */
30
- settled: PromiseSettledResult<T>[];
31
- /** Concurrency actually used after all caps applied. */
32
- effectiveConcurrency: number;
33
- /** True when effectiveConcurrency < requested concurrency. */
34
- capped: boolean;
35
- }
36
- export declare function runPool<T>(jobs: PoolJob<T>[], opts: RunPoolOptions): Promise<RunPoolResult<T>>;
package/build/runPool.js DELETED
@@ -1,51 +0,0 @@
1
- /**
2
- * Generic in-flight-capped worker pool.
3
- *
4
- * Used by:
5
- * - runBatchCommands (ctx_batch_execute parallel branch)
6
- * - runBatchFetch (ctx_fetch_and_index batch path)
7
- *
8
- * Returns Promise.allSettled-style results so one job's throw cannot
9
- * strand siblings. Caller maps fulfilled/rejected per index. Output
10
- * order is preserved by input index (not completion order).
11
- *
12
- * Designed to be the SINGLE concurrency primitive for the project —
13
- * all "run N independent operations with at most M in flight" needs
14
- * route here. Avoids the worker-pool copy-paste flagged in the
15
- * concurrency PRD architectural review (finding G).
16
- */
17
- import { cpus } from "node:os";
18
- export async function runPool(jobs, opts) {
19
- const { concurrency, capByCpuCount = false, onSettled } = opts;
20
- if (jobs.length === 0) {
21
- return { settled: [], effectiveConcurrency: 0, capped: false };
22
- }
23
- const requested = Math.max(1, concurrency);
24
- const cpuCap = capByCpuCount ? Math.max(1, cpus().length) : requested;
25
- const effectiveConcurrency = Math.min(requested, cpuCap, jobs.length);
26
- const capped = effectiveConcurrency < requested;
27
- const settled = new Array(jobs.length);
28
- let nextIdx = 0;
29
- async function worker() {
30
- while (true) {
31
- const idx = nextIdx++;
32
- if (idx >= jobs.length)
33
- return;
34
- try {
35
- const value = await jobs[idx].run();
36
- settled[idx] = { status: "fulfilled", value };
37
- }
38
- catch (err) {
39
- settled[idx] = { status: "rejected", reason: err };
40
- }
41
- onSettled?.(idx, settled[idx]);
42
- }
43
- }
44
- const workers = [];
45
- for (let w = 0; w < effectiveConcurrency; w++)
46
- workers.push(worker());
47
- // allSettled defends against any promise rejection escaping a worker
48
- // (the worker already swallows its own errors, but this is belt-and-braces).
49
- await Promise.allSettled(workers);
50
- return { settled, effectiveConcurrency, capped };
51
- }