@code-yeongyu/senpi 2026.7.11 → 2026.7.13

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 (267) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/core/agent-session.d.ts +9 -0
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +56 -15
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.d.ts.map +1 -1
  7. package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js +76 -25
  8. package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js.map +1 -1
  9. package/dist/core/resource-loader.d.ts +1 -0
  10. package/dist/core/resource-loader.d.ts.map +1 -1
  11. package/dist/core/resource-loader.js +20 -2
  12. package/dist/core/resource-loader.js.map +1 -1
  13. package/dist/modes/interactive/compaction-queue-transfer.d.ts +22 -0
  14. package/dist/modes/interactive/compaction-queue-transfer.d.ts.map +1 -0
  15. package/dist/modes/interactive/compaction-queue-transfer.js +82 -0
  16. package/dist/modes/interactive/compaction-queue-transfer.js.map +1 -0
  17. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  18. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  19. package/dist/modes/interactive/interactive-mode.js +83 -69
  20. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  21. package/node_modules/@babel/helper-string-parser/LICENSE +22 -0
  22. package/node_modules/@babel/helper-string-parser/README.md +19 -0
  23. package/node_modules/@babel/helper-string-parser/lib/index.js +295 -0
  24. package/node_modules/@babel/helper-string-parser/lib/index.js.map +1 -0
  25. package/node_modules/@babel/helper-string-parser/package.json +31 -0
  26. package/node_modules/@babel/helper-validator-identifier/LICENSE +22 -0
  27. package/node_modules/@babel/helper-validator-identifier/README.md +19 -0
  28. package/node_modules/@babel/helper-validator-identifier/lib/identifier.js +70 -0
  29. package/node_modules/@babel/helper-validator-identifier/lib/identifier.js.map +1 -0
  30. package/node_modules/@babel/helper-validator-identifier/lib/index.js +57 -0
  31. package/node_modules/@babel/helper-validator-identifier/lib/index.js.map +1 -0
  32. package/node_modules/@babel/helper-validator-identifier/lib/keyword.js +35 -0
  33. package/node_modules/@babel/helper-validator-identifier/lib/keyword.js.map +1 -0
  34. package/node_modules/@babel/helper-validator-identifier/package.json +31 -0
  35. package/node_modules/@babel/parser/LICENSE +19 -0
  36. package/node_modules/@babel/parser/README.md +19 -0
  37. package/node_modules/@babel/parser/bin/babel-parser.js +15 -0
  38. package/node_modules/@babel/parser/lib/index.js +14599 -0
  39. package/node_modules/@babel/parser/lib/index.js.map +1 -0
  40. package/node_modules/@babel/parser/package.json +50 -0
  41. package/node_modules/@babel/parser/typings/babel-parser.d.ts +262 -0
  42. package/node_modules/@babel/types/LICENSE +22 -0
  43. package/node_modules/@babel/types/README.md +19 -0
  44. package/node_modules/@babel/types/lib/asserts/assertNode.js +16 -0
  45. package/node_modules/@babel/types/lib/asserts/assertNode.js.map +1 -0
  46. package/node_modules/@babel/types/lib/asserts/generated/index.js +1251 -0
  47. package/node_modules/@babel/types/lib/asserts/generated/index.js.map +1 -0
  48. package/node_modules/@babel/types/lib/ast-types/generated/index.js +3 -0
  49. package/node_modules/@babel/types/lib/ast-types/generated/index.js.map +1 -0
  50. package/node_modules/@babel/types/lib/builders/flow/createFlowUnionType.js +18 -0
  51. package/node_modules/@babel/types/lib/builders/flow/createFlowUnionType.js.map +1 -0
  52. package/node_modules/@babel/types/lib/builders/flow/createTypeAnnotationBasedOnTypeof.js +31 -0
  53. package/node_modules/@babel/types/lib/builders/flow/createTypeAnnotationBasedOnTypeof.js.map +1 -0
  54. package/node_modules/@babel/types/lib/builders/generated/index.js +29 -0
  55. package/node_modules/@babel/types/lib/builders/generated/index.js.map +1 -0
  56. package/node_modules/@babel/types/lib/builders/generated/lowercase.js +2902 -0
  57. package/node_modules/@babel/types/lib/builders/generated/lowercase.js.map +1 -0
  58. package/node_modules/@babel/types/lib/builders/generated/uppercase.js +272 -0
  59. package/node_modules/@babel/types/lib/builders/generated/uppercase.js.map +1 -0
  60. package/node_modules/@babel/types/lib/builders/productions.js +12 -0
  61. package/node_modules/@babel/types/lib/builders/productions.js.map +1 -0
  62. package/node_modules/@babel/types/lib/builders/react/buildChildren.js +24 -0
  63. package/node_modules/@babel/types/lib/builders/react/buildChildren.js.map +1 -0
  64. package/node_modules/@babel/types/lib/builders/typescript/createTSUnionType.js +22 -0
  65. package/node_modules/@babel/types/lib/builders/typescript/createTSUnionType.js.map +1 -0
  66. package/node_modules/@babel/types/lib/builders/validateNode.js +21 -0
  67. package/node_modules/@babel/types/lib/builders/validateNode.js.map +1 -0
  68. package/node_modules/@babel/types/lib/clone/clone.js +12 -0
  69. package/node_modules/@babel/types/lib/clone/clone.js.map +1 -0
  70. package/node_modules/@babel/types/lib/clone/cloneDeep.js +12 -0
  71. package/node_modules/@babel/types/lib/clone/cloneDeep.js.map +1 -0
  72. package/node_modules/@babel/types/lib/clone/cloneDeepWithoutLoc.js +12 -0
  73. package/node_modules/@babel/types/lib/clone/cloneDeepWithoutLoc.js.map +1 -0
  74. package/node_modules/@babel/types/lib/clone/cloneNode.js +107 -0
  75. package/node_modules/@babel/types/lib/clone/cloneNode.js.map +1 -0
  76. package/node_modules/@babel/types/lib/clone/cloneWithoutLoc.js +12 -0
  77. package/node_modules/@babel/types/lib/clone/cloneWithoutLoc.js.map +1 -0
  78. package/node_modules/@babel/types/lib/comments/addComment.js +15 -0
  79. package/node_modules/@babel/types/lib/comments/addComment.js.map +1 -0
  80. package/node_modules/@babel/types/lib/comments/addComments.js +22 -0
  81. package/node_modules/@babel/types/lib/comments/addComments.js.map +1 -0
  82. package/node_modules/@babel/types/lib/comments/inheritInnerComments.js +12 -0
  83. package/node_modules/@babel/types/lib/comments/inheritInnerComments.js.map +1 -0
  84. package/node_modules/@babel/types/lib/comments/inheritLeadingComments.js +12 -0
  85. package/node_modules/@babel/types/lib/comments/inheritLeadingComments.js.map +1 -0
  86. package/node_modules/@babel/types/lib/comments/inheritTrailingComments.js +12 -0
  87. package/node_modules/@babel/types/lib/comments/inheritTrailingComments.js.map +1 -0
  88. package/node_modules/@babel/types/lib/comments/inheritsComments.js +17 -0
  89. package/node_modules/@babel/types/lib/comments/inheritsComments.js.map +1 -0
  90. package/node_modules/@babel/types/lib/comments/removeComments.js +15 -0
  91. package/node_modules/@babel/types/lib/comments/removeComments.js.map +1 -0
  92. package/node_modules/@babel/types/lib/constants/generated/index.js +60 -0
  93. package/node_modules/@babel/types/lib/constants/generated/index.js.map +1 -0
  94. package/node_modules/@babel/types/lib/constants/index.js +31 -0
  95. package/node_modules/@babel/types/lib/constants/index.js.map +1 -0
  96. package/node_modules/@babel/types/lib/converters/ensureBlock.js +14 -0
  97. package/node_modules/@babel/types/lib/converters/ensureBlock.js.map +1 -0
  98. package/node_modules/@babel/types/lib/converters/gatherSequenceExpressions.js +65 -0
  99. package/node_modules/@babel/types/lib/converters/gatherSequenceExpressions.js.map +1 -0
  100. package/node_modules/@babel/types/lib/converters/toBindingIdentifierName.js +14 -0
  101. package/node_modules/@babel/types/lib/converters/toBindingIdentifierName.js.map +1 -0
  102. package/node_modules/@babel/types/lib/converters/toBlock.js +29 -0
  103. package/node_modules/@babel/types/lib/converters/toBlock.js.map +1 -0
  104. package/node_modules/@babel/types/lib/converters/toComputedKey.js +14 -0
  105. package/node_modules/@babel/types/lib/converters/toComputedKey.js.map +1 -0
  106. package/node_modules/@babel/types/lib/converters/toExpression.js +28 -0
  107. package/node_modules/@babel/types/lib/converters/toExpression.js.map +1 -0
  108. package/node_modules/@babel/types/lib/converters/toIdentifier.js +25 -0
  109. package/node_modules/@babel/types/lib/converters/toIdentifier.js.map +1 -0
  110. package/node_modules/@babel/types/lib/converters/toKeyAlias.js +38 -0
  111. package/node_modules/@babel/types/lib/converters/toKeyAlias.js.map +1 -0
  112. package/node_modules/@babel/types/lib/converters/toSequenceExpression.js +19 -0
  113. package/node_modules/@babel/types/lib/converters/toSequenceExpression.js.map +1 -0
  114. package/node_modules/@babel/types/lib/converters/toStatement.js +39 -0
  115. package/node_modules/@babel/types/lib/converters/toStatement.js.map +1 -0
  116. package/node_modules/@babel/types/lib/converters/valueToNode.js +89 -0
  117. package/node_modules/@babel/types/lib/converters/valueToNode.js.map +1 -0
  118. package/node_modules/@babel/types/lib/definitions/core.js +1725 -0
  119. package/node_modules/@babel/types/lib/definitions/core.js.map +1 -0
  120. package/node_modules/@babel/types/lib/definitions/deprecated-aliases.js +11 -0
  121. package/node_modules/@babel/types/lib/definitions/deprecated-aliases.js.map +1 -0
  122. package/node_modules/@babel/types/lib/definitions/experimental.js +124 -0
  123. package/node_modules/@babel/types/lib/definitions/experimental.js.map +1 -0
  124. package/node_modules/@babel/types/lib/definitions/flow.js +495 -0
  125. package/node_modules/@babel/types/lib/definitions/flow.js.map +1 -0
  126. package/node_modules/@babel/types/lib/definitions/index.js +106 -0
  127. package/node_modules/@babel/types/lib/definitions/index.js.map +1 -0
  128. package/node_modules/@babel/types/lib/definitions/jsx.js +157 -0
  129. package/node_modules/@babel/types/lib/definitions/jsx.js.map +1 -0
  130. package/node_modules/@babel/types/lib/definitions/misc.js +31 -0
  131. package/node_modules/@babel/types/lib/definitions/misc.js.map +1 -0
  132. package/node_modules/@babel/types/lib/definitions/placeholders.js +27 -0
  133. package/node_modules/@babel/types/lib/definitions/placeholders.js.map +1 -0
  134. package/node_modules/@babel/types/lib/definitions/typescript.js +528 -0
  135. package/node_modules/@babel/types/lib/definitions/typescript.js.map +1 -0
  136. package/node_modules/@babel/types/lib/definitions/utils.js +296 -0
  137. package/node_modules/@babel/types/lib/definitions/utils.js.map +1 -0
  138. package/node_modules/@babel/types/lib/index-legacy.d.ts +2797 -0
  139. package/node_modules/@babel/types/lib/index.d.ts +3617 -0
  140. package/node_modules/@babel/types/lib/index.js +582 -0
  141. package/node_modules/@babel/types/lib/index.js.flow +2650 -0
  142. package/node_modules/@babel/types/lib/index.js.map +1 -0
  143. package/node_modules/@babel/types/lib/modifications/appendToMemberExpression.js +15 -0
  144. package/node_modules/@babel/types/lib/modifications/appendToMemberExpression.js.map +1 -0
  145. package/node_modules/@babel/types/lib/modifications/flow/removeTypeDuplicates.js +65 -0
  146. package/node_modules/@babel/types/lib/modifications/flow/removeTypeDuplicates.js.map +1 -0
  147. package/node_modules/@babel/types/lib/modifications/inherits.js +28 -0
  148. package/node_modules/@babel/types/lib/modifications/inherits.js.map +1 -0
  149. package/node_modules/@babel/types/lib/modifications/prependToMemberExpression.js +17 -0
  150. package/node_modules/@babel/types/lib/modifications/prependToMemberExpression.js.map +1 -0
  151. package/node_modules/@babel/types/lib/modifications/removeProperties.js +24 -0
  152. package/node_modules/@babel/types/lib/modifications/removeProperties.js.map +1 -0
  153. package/node_modules/@babel/types/lib/modifications/removePropertiesDeep.js +14 -0
  154. package/node_modules/@babel/types/lib/modifications/removePropertiesDeep.js.map +1 -0
  155. package/node_modules/@babel/types/lib/modifications/typescript/removeTypeDuplicates.js +66 -0
  156. package/node_modules/@babel/types/lib/modifications/typescript/removeTypeDuplicates.js.map +1 -0
  157. package/node_modules/@babel/types/lib/retrievers/getAssignmentIdentifiers.js +48 -0
  158. package/node_modules/@babel/types/lib/retrievers/getAssignmentIdentifiers.js.map +1 -0
  159. package/node_modules/@babel/types/lib/retrievers/getBindingIdentifiers.js +102 -0
  160. package/node_modules/@babel/types/lib/retrievers/getBindingIdentifiers.js.map +1 -0
  161. package/node_modules/@babel/types/lib/retrievers/getFunctionName.js +63 -0
  162. package/node_modules/@babel/types/lib/retrievers/getFunctionName.js.map +1 -0
  163. package/node_modules/@babel/types/lib/retrievers/getOuterBindingIdentifiers.js +13 -0
  164. package/node_modules/@babel/types/lib/retrievers/getOuterBindingIdentifiers.js.map +1 -0
  165. package/node_modules/@babel/types/lib/traverse/traverse.js +50 -0
  166. package/node_modules/@babel/types/lib/traverse/traverse.js.map +1 -0
  167. package/node_modules/@babel/types/lib/traverse/traverseFast.js +40 -0
  168. package/node_modules/@babel/types/lib/traverse/traverseFast.js.map +1 -0
  169. package/node_modules/@babel/types/lib/utils/deprecationWarning.js +44 -0
  170. package/node_modules/@babel/types/lib/utils/deprecationWarning.js.map +1 -0
  171. package/node_modules/@babel/types/lib/utils/inherit.js +13 -0
  172. package/node_modules/@babel/types/lib/utils/inherit.js.map +1 -0
  173. package/node_modules/@babel/types/lib/utils/react/cleanJSXElementLiteralChild.js +40 -0
  174. package/node_modules/@babel/types/lib/utils/react/cleanJSXElementLiteralChild.js.map +1 -0
  175. package/node_modules/@babel/types/lib/utils/shallowEqual.js +17 -0
  176. package/node_modules/@babel/types/lib/utils/shallowEqual.js.map +1 -0
  177. package/node_modules/@babel/types/lib/validators/buildMatchMemberExpression.js +13 -0
  178. package/node_modules/@babel/types/lib/validators/buildMatchMemberExpression.js.map +1 -0
  179. package/node_modules/@babel/types/lib/validators/generated/index.js +2797 -0
  180. package/node_modules/@babel/types/lib/validators/generated/index.js.map +1 -0
  181. package/node_modules/@babel/types/lib/validators/is.js +27 -0
  182. package/node_modules/@babel/types/lib/validators/is.js.map +1 -0
  183. package/node_modules/@babel/types/lib/validators/isBinding.js +27 -0
  184. package/node_modules/@babel/types/lib/validators/isBinding.js.map +1 -0
  185. package/node_modules/@babel/types/lib/validators/isBlockScoped.js +13 -0
  186. package/node_modules/@babel/types/lib/validators/isBlockScoped.js.map +1 -0
  187. package/node_modules/@babel/types/lib/validators/isImmutable.js +21 -0
  188. package/node_modules/@babel/types/lib/validators/isImmutable.js.map +1 -0
  189. package/node_modules/@babel/types/lib/validators/isLet.js +13 -0
  190. package/node_modules/@babel/types/lib/validators/isLet.js.map +1 -0
  191. package/node_modules/@babel/types/lib/validators/isNode.js +12 -0
  192. package/node_modules/@babel/types/lib/validators/isNode.js.map +1 -0
  193. package/node_modules/@babel/types/lib/validators/isNodesEquivalent.js +57 -0
  194. package/node_modules/@babel/types/lib/validators/isNodesEquivalent.js.map +1 -0
  195. package/node_modules/@babel/types/lib/validators/isPlaceholderType.js +15 -0
  196. package/node_modules/@babel/types/lib/validators/isPlaceholderType.js.map +1 -0
  197. package/node_modules/@babel/types/lib/validators/isReferenced.js +96 -0
  198. package/node_modules/@babel/types/lib/validators/isReferenced.js.map +1 -0
  199. package/node_modules/@babel/types/lib/validators/isScope.js +18 -0
  200. package/node_modules/@babel/types/lib/validators/isScope.js.map +1 -0
  201. package/node_modules/@babel/types/lib/validators/isSpecifierDefault.js +14 -0
  202. package/node_modules/@babel/types/lib/validators/isSpecifierDefault.js.map +1 -0
  203. package/node_modules/@babel/types/lib/validators/isType.js +17 -0
  204. package/node_modules/@babel/types/lib/validators/isType.js.map +1 -0
  205. package/node_modules/@babel/types/lib/validators/isValidES3Identifier.js +13 -0
  206. package/node_modules/@babel/types/lib/validators/isValidES3Identifier.js.map +1 -0
  207. package/node_modules/@babel/types/lib/validators/isValidIdentifier.js +18 -0
  208. package/node_modules/@babel/types/lib/validators/isValidIdentifier.js.map +1 -0
  209. package/node_modules/@babel/types/lib/validators/isVar.js +15 -0
  210. package/node_modules/@babel/types/lib/validators/isVar.js.map +1 -0
  211. package/node_modules/@babel/types/lib/validators/matchesPattern.js +44 -0
  212. package/node_modules/@babel/types/lib/validators/matchesPattern.js.map +1 -0
  213. package/node_modules/@babel/types/lib/validators/react/isCompatTag.js +11 -0
  214. package/node_modules/@babel/types/lib/validators/react/isCompatTag.js.map +1 -0
  215. package/node_modules/@babel/types/lib/validators/react/isReactComponent.js +11 -0
  216. package/node_modules/@babel/types/lib/validators/react/isReactComponent.js.map +1 -0
  217. package/node_modules/@babel/types/lib/validators/validate.js +42 -0
  218. package/node_modules/@babel/types/lib/validators/validate.js.map +1 -0
  219. package/node_modules/@babel/types/package.json +39 -0
  220. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +10 -2
  221. package/node_modules/@code-yeongyu/senpi-codemode/README.md +128 -78
  222. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -3
  223. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/protocol.ts +15 -2
  224. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/reserved.ts +9 -0
  225. package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/agent-bridge.ts +197 -0
  226. package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/output-bridge.ts +96 -0
  227. package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/schema-injection.ts +3 -0
  228. package/node_modules/@code-yeongyu/senpi-codemode/src/completion/handler.ts +95 -26
  229. package/node_modules/@code-yeongyu/senpi-codemode/src/completion/tool-bridge.ts +11 -2
  230. package/node_modules/@code-yeongyu/senpi-codemode/src/config/settings.ts +96 -8
  231. package/node_modules/@code-yeongyu/senpi-codemode/src/extension/session-manager.ts +24 -3
  232. package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +155 -21
  233. package/node_modules/@code-yeongyu/senpi-codemode/src/interpreters/detect.ts +1 -1
  234. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/prelude.jl +252 -48
  235. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/runner.jl +263 -83
  236. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +11 -5
  237. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/local-module-loader.ts +108 -0
  238. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/prelude.ts +8 -6
  239. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/rewrite-imports.ts +164 -0
  240. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-core.js +6 -1
  241. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-runtime.js +307 -41
  242. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel.ts +3 -2
  243. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/prelude.py +792 -79
  244. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/prelude.rb +234 -21
  245. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/runner.rb +154 -51
  246. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-queue.ts +1 -0
  247. package/node_modules/@code-yeongyu/senpi-codemode/src/output/output-meta.ts +89 -0
  248. package/node_modules/@code-yeongyu/senpi-codemode/src/output/streaming-output.ts +296 -0
  249. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +107 -30
  250. package/node_modules/@code-yeongyu/senpi-codemode/src/timeouts/bridge-timeout.ts +2 -0
  251. package/node_modules/@code-yeongyu/senpi-codemode/src/timeouts/idle-timeout.ts +8 -19
  252. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-handler.ts +175 -113
  253. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool.ts +130 -120
  254. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/image.ts +274 -0
  255. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/json-tree.ts +247 -0
  256. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/render.ts +641 -38
  257. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/status-events.ts +12 -0
  258. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +29 -1
  259. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  260. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +16 -16
  261. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
  262. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +6 -6
  263. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
  264. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  265. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  266. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  267. package/package.json +6 -6
@@ -1,26 +1,59 @@
1
1
  import type { ExtensionContext } from "@code-yeongyu/senpi";
2
2
  import type { KernelToHostMessage } from "./bridge/protocol.ts";
3
+ import type { AgentExecuteTool } from "./bridges/agent-bridge.ts";
3
4
  import { type CompletionRequest, type CompletionResult, createCompletionHandler } from "./completion/handler.ts";
4
- import { type CodemodeSettings, loadCodemodeSettings } from "./config/settings.ts";
5
5
  import {
6
+ type CodemodeSettings,
7
+ defaultCodemodeSettings,
8
+ loadCodemodeSettings,
9
+ type ResolvedCodemodeSettings,
10
+ resolveEnabledLanguages,
11
+ } from "./config/settings.ts";
12
+ import {
13
+ CodemodeSessionDisposedError,
6
14
  type CodemodeSessionManager,
7
15
  type CreateCodemodeSessionManagerOptions,
8
16
  createCodemodeSessionManager,
17
+ type EvalExecutionTracker,
9
18
  } from "./extension/session-manager.ts";
10
19
  import {
11
20
  createInterpreterDetector,
12
21
  getInterpreterAvailability,
13
22
  type InterpreterAvailability,
14
23
  } from "./interpreters/detect.ts";
24
+ import { resolveSessionArtifactsDir } from "./output/streaming-output.ts";
15
25
  import { createEvalTool } from "./tool/eval-tool.ts";
16
- import type { EvalKernel, EvalLanguage, ExecuteTool } from "./tool/types.ts";
26
+ import { renderEvalCall, renderEvalResult } from "./tool/render.ts";
27
+ import type { EnabledEvalLanguages, EvalKernel, EvalLanguage } from "./tool/types.ts";
28
+
29
+ const SESSION_LIFECYCLE_EVENTS = [
30
+ "session_start",
31
+ "session_shutdown",
32
+ "session_before_switch",
33
+ "session_before_fork",
34
+ ] as const;
35
+
36
+ type SessionLifecycleEvent = (typeof SESSION_LIFECYCLE_EVENTS)[number];
37
+
38
+ type TrackedExecution = {
39
+ readonly promise: Promise<unknown>;
40
+ readonly controller: AbortController;
41
+ };
17
42
 
18
- type SessionLifecycleEvent = "session_start" | "session_shutdown" | "session_before_switch" | "session_before_fork";
43
+ type SessionRuntime = {
44
+ readonly manager: CodemodeSessionManager;
45
+ readonly enabledLanguages: EnabledEvalLanguages;
46
+ readonly settings: ResolvedCodemodeSettings;
47
+ readonly artifactsDir: string;
48
+ readonly executeTool: AgentExecuteTool;
49
+ readonly spawns: boolean;
50
+ };
19
51
 
20
52
  export interface CodemodeExtensionAPI {
21
53
  registerTool(tool: ReturnType<typeof createEvalTool>): void;
22
54
  on(event: SessionLifecycleEvent, handler: (event: unknown, ctx: ExtensionContext) => Promise<void> | void): void;
23
- executeTool: ExecuteTool;
55
+ executeTool: AgentExecuteTool;
56
+ getActiveTools(): string[];
24
57
  }
25
58
 
26
59
  export interface SenpiCodemodeOptions {
@@ -30,60 +63,122 @@ export interface SenpiCodemodeOptions {
30
63
  readonly complete?: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>;
31
64
  }
32
65
 
66
+ class CodemodeSessionNotStartedError extends Error {
67
+ readonly name = "CodemodeSessionNotStartedError";
68
+
69
+ constructor() {
70
+ super("codemode session has not started");
71
+ }
72
+ }
73
+
33
74
  export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCodemodeOptions = {}): void {
34
75
  const manager = new SessionManagerProxy();
35
76
  const complete = options.complete ?? ((request, ctx) => createCompletionHandler()(ctx)(request));
77
+ const renderers = { renderCall: renderEvalCall, renderResult: renderEvalResult };
36
78
  pi.registerTool(
37
79
  createEvalTool({
38
80
  enabledLanguages: { py: true, js: true, rb: true, jl: true },
39
81
  kernelManager: manager,
40
- cellTimeoutSeconds: 30,
41
- executeTool: (toolName, params, executeOptions) => pi.executeTool(toolName, params, executeOptions),
82
+ cellTimeoutSeconds: defaultCodemodeSettings.cellTimeoutSeconds,
83
+ executeTool: createExecuteTool(pi),
42
84
  complete,
85
+ settings: defaultCodemodeSettings,
86
+ executionTracker: manager,
87
+ renderers,
43
88
  }),
44
89
  );
45
90
 
46
91
  pi.on("session_start", async (event, ctx) => {
47
92
  const generation = manager.beginReplacement();
48
- await manager.replace(generation, await createManager(pi, ctx, event, complete, options));
93
+ const runtime = await createRuntime(pi, ctx, event, complete, options);
94
+ if (!(await manager.replace(generation, runtime.manager))) return;
95
+ pi.registerTool(
96
+ createEvalTool({
97
+ enabledLanguages: runtime.enabledLanguages,
98
+ kernelManager: manager,
99
+ cellTimeoutSeconds: runtime.settings.cellTimeoutSeconds,
100
+ executeTool: runtime.executeTool,
101
+ complete,
102
+ settings: runtime.settings,
103
+ artifactsDir: runtime.artifactsDir,
104
+ executionTracker: manager,
105
+ renderers,
106
+ spawns: runtime.spawns,
107
+ spawnDefaultAgent: runtime.settings.taskTools.task,
108
+ }),
109
+ );
49
110
  });
50
111
  pi.on("session_shutdown", async () => manager.dispose());
51
112
  pi.on("session_before_switch", async () => manager.dispose());
52
113
  pi.on("session_before_fork", async () => manager.dispose());
53
114
  }
54
115
 
55
- class SessionManagerProxy implements CodemodeSessionManager {
116
+ class SessionManagerProxy implements CodemodeSessionManager, EvalExecutionTracker {
56
117
  #current: CodemodeSessionManager | undefined;
57
118
  #generation = 0;
119
+ #started = false;
120
+ #acceptingExecutions = false;
121
+ readonly #executions = new Set<TrackedExecution>();
58
122
 
59
123
  beginReplacement(): number {
60
124
  this.#generation++;
125
+ this.#acceptingExecutions = false;
126
+ this.#abortExecutions();
61
127
  return this.#generation;
62
128
  }
63
129
 
64
- async replace(generation: number, next: CodemodeSessionManager): Promise<void> {
130
+ async replace(generation: number, next: CodemodeSessionManager): Promise<boolean> {
131
+ if (generation !== this.#generation) {
132
+ await next.dispose();
133
+ return false;
134
+ }
135
+ await this.#settleExecutions();
65
136
  if (generation !== this.#generation) {
66
137
  await next.dispose();
67
- return;
138
+ return false;
68
139
  }
69
140
  const current = this.#current;
70
141
  this.#current = undefined;
71
142
  await current?.dispose();
72
143
  if (generation !== this.#generation) {
73
144
  await next.dispose();
74
- return;
145
+ return false;
75
146
  }
76
147
  this.#current = next;
148
+ this.#started = true;
149
+ this.#acceptingExecutions = true;
150
+ return true;
151
+ }
152
+
153
+ assertEvalExecutionAllowed(): void {
154
+ if (this.#acceptingExecutions && this.#current !== undefined) return;
155
+ if (this.#started) throw new CodemodeSessionDisposedError();
156
+ throw new CodemodeSessionNotStartedError();
157
+ }
158
+
159
+ async trackEvalExecution<Result>(execution: Promise<Result>, controller: AbortController): Promise<Result> {
160
+ this.assertEvalExecutionAllowed();
161
+ const tracked: TrackedExecution = { promise: execution, controller };
162
+ this.#executions.add(tracked);
163
+ try {
164
+ return await execution;
165
+ } finally {
166
+ this.#executions.delete(tracked);
167
+ }
77
168
  }
78
169
 
79
170
  async getKernel(language: EvalLanguage, onMessage: (message: KernelToHostMessage) => void): Promise<EvalKernel> {
80
- if (!this.#current) throw new Error("codemode session has not started");
81
- return await this.#current.getKernel(language, onMessage);
171
+ this.assertEvalExecutionAllowed();
172
+ const current = this.#current;
173
+ if (current === undefined) throw new CodemodeSessionNotStartedError();
174
+ return await current.getKernel(language, onMessage);
82
175
  }
83
176
 
84
177
  async complete(request: CompletionRequest, ctx: ExtensionContext): Promise<CompletionResult> {
85
- if (!this.#current) throw new Error("codemode session has not started");
86
- return await this.#current.complete(request, ctx);
178
+ this.assertEvalExecutionAllowed();
179
+ const current = this.#current;
180
+ if (current === undefined) throw new CodemodeSessionNotStartedError();
181
+ return await current.complete(request, ctx);
87
182
  }
88
183
 
89
184
  setContext(ctx: ExtensionContext): void {
@@ -92,30 +187,69 @@ class SessionManagerProxy implements CodemodeSessionManager {
92
187
 
93
188
  async dispose(): Promise<void> {
94
189
  this.#generation++;
190
+ this.#acceptingExecutions = false;
191
+ this.#abortExecutions();
192
+ await this.#settleExecutions();
95
193
  const current = this.#current;
96
194
  this.#current = undefined;
97
195
  await current?.dispose();
98
196
  }
197
+
198
+ #abortExecutions(): void {
199
+ if (this.#executions.size === 0) return;
200
+ const error = new CodemodeSessionDisposedError();
201
+ for (const execution of this.#executions) execution.controller.abort(error);
202
+ }
203
+
204
+ async #settleExecutions(): Promise<void> {
205
+ if (this.#executions.size === 0) return;
206
+ await Promise.allSettled([...this.#executions].map((execution) => execution.promise));
207
+ }
99
208
  }
100
209
 
101
- async function createManager(
210
+ async function createRuntime(
102
211
  pi: CodemodeExtensionAPI,
103
212
  ctx: ExtensionContext,
104
213
  event: unknown,
105
214
  complete: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>,
106
215
  options: SenpiCodemodeOptions,
107
- ): Promise<CodemodeSessionManager> {
216
+ ): Promise<SessionRuntime> {
108
217
  const loaded = await loadCodemodeSettings({ cwd: ctx.cwd });
109
- const availability = await getInterpreterAvailability(loaded.settings, createInterpreterDetector());
218
+ const settings: ResolvedCodemodeSettings = {
219
+ ...loaded.settings,
220
+ languages: resolveEnabledLanguages(loaded.settings),
221
+ };
222
+ const availability = await getInterpreterAvailability(settings, createInterpreterDetector());
223
+ const enabledLanguages = enabledLanguagesFrom(settings, availability);
224
+ const artifacts = resolveSessionArtifactsDir(ctx.sessionManager.getSessionFile());
225
+ const activeTools = new Set(pi.getActiveTools());
226
+ const executeTool = createExecuteTool(pi, activeTools);
110
227
  const create = options.createSessionManager ?? createCodemodeSessionManager;
111
- return await create({
228
+ const manager = await create({
112
229
  sessionId: sessionIdFrom(event),
113
230
  cwd: ctx.cwd,
114
- settings: loaded.settings,
231
+ settings,
115
232
  availability,
116
- executeTool: (toolName, params, executeOptions) => pi.executeTool(toolName, params, executeOptions),
233
+ artifactsDir: artifacts.dir,
234
+ executeTool,
117
235
  complete,
118
236
  });
237
+ return {
238
+ manager,
239
+ enabledLanguages,
240
+ settings,
241
+ artifactsDir: artifacts.dir,
242
+ executeTool,
243
+ spawns: activeTools.has(settings.taskTools.task),
244
+ };
245
+ }
246
+
247
+ function createExecuteTool(pi: CodemodeExtensionAPI, activeTools?: ReadonlySet<string>): AgentExecuteTool {
248
+ const executeTool: AgentExecuteTool = (toolName, params, executeOptions) =>
249
+ pi.executeTool(toolName, params, executeOptions);
250
+ return Object.assign(executeTool, {
251
+ isToolAvailable: (name: string): boolean => activeTools?.has(name) ?? pi.getActiveTools().includes(name),
252
+ });
119
253
  }
120
254
 
121
255
  function sessionIdFrom(event: unknown): string {
@@ -144,7 +144,7 @@ function candidateInvocation(candidate: string): { readonly command: string; rea
144
144
  }
145
145
 
146
146
  function parseVersion(output: string): string | null {
147
- const match = /(?:Python|ruby|julia)\s+v?(\d+(?:\.\d+){1,3})/i.exec(output.trim());
147
+ const match = /(?:Python|ruby|julia)\s+(?:version\s+)?v?(\d+(?:\.\d+){1,3})/i.exec(output.trim());
148
148
  return match?.[1] ?? null;
149
149
  }
150
150
 
@@ -1,79 +1,283 @@
1
- # NOTE: deliberately NO `using Base64`. On a cold Julia depot (a fresh CI runner
2
- # home) the first `using Base64` triggers a serial package precompile (~1s+ of CPU
3
- # and disk IO); under an oversubscribed CI runner that spike balloons past the
4
- # per-cell timeout, so the kernel's first cell would flakily time out, restart, and
5
- # lose session state. Every other kernel dependency (Sockets in runner.jl, Base) is
6
- # already in the shipped sysimage and needs no precompile, so base64 is the only
7
- # precompile trigger — we hand-roll it against Base to keep cold start deterministic.
8
- # Verified byte-exact against Base64.base64encode (empty / 1-2-3 byte remainders /
9
- # multibyte UTF-8 / binary / long input).
1
+ # allow: SIZE_OK this dependency-free kernel prelude is loaded as one runtime asset.
10
2
  const SENPI_B64_ALPHABET = collect("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
11
3
 
12
4
  function senpi_base64(text::AbstractString)
13
- # prelude.jl shadows Base `write` within Main, so infrastructure must qualify
14
- # Base.write explicitly (see the same policy note in runner.jl).
15
5
  bytes = codeunits(text)
16
6
  out = IOBuffer()
17
- n = length(bytes)
18
- i = 1
19
- while i <= n
20
- b0 = bytes[i]
21
- b1 = i + 1 <= n ? bytes[i + 1] : UInt8(0)
22
- b2 = i + 2 <= n ? bytes[i + 2] : UInt8(0)
23
- Base.write(out, SENPI_B64_ALPHABET[(b0 >> 2) + 1])
24
- Base.write(out, SENPI_B64_ALPHABET[(((b0 & 0x03) << 4) | (b1 >> 4)) + 1])
25
- Base.write(out, i + 1 <= n ? SENPI_B64_ALPHABET[(((b1 & 0x0f) << 2) | (b2 >> 6)) + 1] : '=')
26
- Base.write(out, i + 2 <= n ? SENPI_B64_ALPHABET[(b2 & 0x3f) + 1] : '=')
27
- i += 3
7
+ index = 1
8
+ while index <= length(bytes)
9
+ first_byte = bytes[index]
10
+ second_byte = index + 1 <= length(bytes) ? bytes[index + 1] : UInt8(0)
11
+ third_byte = index + 2 <= length(bytes) ? bytes[index + 2] : UInt8(0)
12
+ Base.write(out, SENPI_B64_ALPHABET[(first_byte >> 2) + 1])
13
+ Base.write(out, SENPI_B64_ALPHABET[(((first_byte & 0x03) << 4) | (second_byte >> 4)) + 1])
14
+ Base.write(out, index + 1 <= length(bytes) ? SENPI_B64_ALPHABET[(((second_byte & 0x0f) << 2) | (third_byte >> 6)) + 1] : '=')
15
+ Base.write(out, index + 2 <= length(bytes) ? SENPI_B64_ALPHABET[(third_byte & 0x3f) + 1] : '=')
16
+ index += 3
28
17
  end
29
- return String(take!(out))
18
+ String(take!(out))
19
+ end
20
+
21
+ function senpi_status_events_enabled()
22
+ get(senpi_connection, "statusEvents", true) !== false
23
+ end
24
+
25
+ function senpi_emit_status(op::AbstractString, fields::AbstractDict=Dict{String, Any}(); force::Bool=false)
26
+ (force || senpi_status_events_enabled()) || return nothing
27
+ event = Dict{String, Any}("op" => string(op))
28
+ for (key, value) in fields
29
+ event[string(key)] = value
30
+ end
31
+ senpi_emit(Dict("type" => "status", "event" => event))
32
+ nothing
33
+ end
34
+
35
+ function senpi_with_bridge_timeout_pause(operation::Function)
36
+ senpi_emit_status("timeout-pause"; force=true)
37
+ try
38
+ return operation()
39
+ finally
40
+ senpi_emit_status("timeout-resume"; force=true)
41
+ end
42
+ end
43
+
44
+ function senpi_url_decode(value::AbstractString)
45
+ out = IOBuffer()
46
+ index = 1
47
+ while index <= ncodeunits(value)
48
+ character = Char(codeunit(value, index))
49
+ if character == '%' && index + 2 <= ncodeunits(value)
50
+ hex = value[index + 1:index + 2]
51
+ parsed = tryparse(UInt8, hex; base=16)
52
+ if parsed !== nothing
53
+ Base.write(out, parsed)
54
+ index += 3
55
+ continue
56
+ end
57
+ end
58
+ Base.write(out, character)
59
+ index += 1
60
+ end
61
+ String(take!(out))
62
+ end
63
+
64
+ function senpi_resolve_path(value::AbstractString)
65
+ raw = string(value)
66
+ matched = match(r"^([a-z][a-z0-9+.\-]*)://(.*)$"i, raw)
67
+ matched === nothing && return abspath(raw)
68
+ scheme = lowercase(string(matched.captures[1]))
69
+ roots = get(senpi_connection, "localRoots", nothing)
70
+ root = roots isa AbstractDict ? get(roots, scheme, nothing) : nothing
71
+ root isa AbstractString && !isempty(root) || error("Protocol paths are not supported by this helper: $raw")
72
+ relative = senpi_url_decode(replace(string(matched.captures[2]), '\\' => '/'))
73
+ root_path = abspath(string(root))
74
+ isempty(relative) && return root_path
75
+ (startswith(relative, '/') || ".." in split(relative, '/')) && error("Unsafe $scheme:// path (absolute or traversal): $raw")
76
+ resolved = abspath(joinpath(root_path, relative))
77
+ (resolved == root_path || startswith(resolved, root_path * string(Base.Filesystem.path_separator))) || error("$scheme:// path escapes its root: $raw")
78
+ resolved
79
+ end
80
+
81
+ function senpi_display_payload(value)
82
+ if value isa AbstractDict
83
+ kind = get(value, "type", get(value, :type, nothing))
84
+ text_value = get(value, "text", get(value, :text, nothing))
85
+ if kind == "markdown" && text_value !== nothing
86
+ return "text/markdown", string(text_value), false
87
+ end
88
+ mime_type = get(value, "mimeType", get(value, :mimeType, nothing))
89
+ data = get(value, "data", get(value, :data, nothing))
90
+ if kind == "image" && mime_type isa AbstractString && data !== nothing
91
+ return string(mime_type), string(data), true
92
+ end
93
+ return "application/json", senpi_json(value), false
94
+ end
95
+ value isa AbstractVector && return "application/json", senpi_json(value), false
96
+ "text/plain", string(value), false
30
97
  end
31
98
 
32
99
  function display(value)
33
- text = senpi_json(value)
34
- senpi_emit(Dict("type" => "display", "mimeType" => "application/json", "dataBase64" => senpi_base64(text)))
35
- return nothing
100
+ mime_type, payload, encoded = senpi_display_payload(value)
101
+ senpi_emit(Dict("type" => "display", "mimeType" => mime_type, "dataBase64" => encoded ? payload : senpi_base64(payload)))
102
+ nothing
103
+ end
104
+
105
+ function display_image(base64_value::AbstractString, mime_type::AbstractString="image/png")
106
+ senpi_emit(Dict("type" => "display", "mimeType" => string(mime_type), "dataBase64" => string(base64_value)))
107
+ nothing
108
+ end
109
+
110
+ function text(value)
111
+ senpi_emit(Dict("type" => "text", "stream" => "stdout", "data" => string(value)))
112
+ nothing
36
113
  end
37
114
 
38
115
  function print(values...)
39
- senpi_emit(Dict("type" => "text", "stream" => "stdout", "data" => join(string.(values), "")))
40
- return nothing
116
+ text(join(string.(values), ""))
41
117
  end
42
118
 
43
- function read(path::AbstractString)
44
- return Base.read(path, String)
119
+ function read(path::AbstractString, offset::Integer=1, limit::Union{Integer, Nothing}=nothing)
120
+ resolved = senpi_resolve_path(path)
121
+ content = Base.read(resolved, String)
122
+ if offset > 1 || limit !== nothing
123
+ lines = split(content, '\n'; keepempty=true)
124
+ start = max(1, Int(offset))
125
+ finish = limit === nothing ? length(lines) : min(length(lines), start + Int(limit) - 1)
126
+ content = start <= length(lines) ? join(lines[start:finish], '\n') : ""
127
+ end
128
+ senpi_emit_status("read", Dict("path" => resolved, "chars" => length(content), "preview" => first(content, min(length(content), 500))))
129
+ content
45
130
  end
46
131
 
47
132
  function write(path::AbstractString, content)
48
- Base.write(path, string(content))
49
- return path
133
+ resolved = senpi_resolve_path(path)
134
+ mkpath(dirname(resolved))
135
+ data = string(content)
136
+ open(resolved, "w") do io
137
+ Base.write(io, data)
138
+ end
139
+ senpi_emit_status("write", Dict("path" => resolved, "chars" => length(data)))
140
+ resolved
50
141
  end
51
142
 
52
143
  function env(key=nothing, value=nothing)
53
144
  if key === nothing
54
- return Dict(k => v for (k, v) in ENV)
55
- elseif value === nothing
56
- return get(ENV, string(key), nothing)
57
- else
58
- ENV[string(key)] = string(value)
59
- return value
145
+ values = Dict{String, String}(string(name) => string(item) for (name, item) in ENV)
146
+ senpi_emit_status("env", Dict("count" => length(values), "keys" => sort(collect(keys(values)))[1:min(20, length(values))]))
147
+ return values
60
148
  end
149
+ name = string(key)
150
+ if value === nothing
151
+ resolved = get(ENV, name, nothing)
152
+ senpi_emit_status("env", Dict("key" => name, "value" => resolved, "action" => "get"))
153
+ return resolved
154
+ end
155
+ resolved = string(value)
156
+ ENV[name] = resolved
157
+ senpi_emit_status("env", Dict("key" => name, "value" => resolved, "action" => "set"))
158
+ resolved
61
159
  end
62
160
 
63
- function log(message)
64
- senpi_emit(Dict("type" => "log", "message" => string(message)))
65
- return nothing
161
+ struct SenpiToolProxy end
162
+
163
+ struct SenpiToolCallable
164
+ name::String
66
165
  end
67
166
 
68
- function phase(title)
69
- senpi_emit(Dict("type" => "phase", "title" => string(title)))
70
- return nothing
167
+ function (callable::SenpiToolCallable)(args=nothing; kwargs...)
168
+ values = Dict{String, Any}()
169
+ if args !== nothing
170
+ args isa AbstractDict || error("tool.$(callable.name)(...) expects a dictionary of arguments")
171
+ for (key, value) in args
172
+ values[string(key)] = value
173
+ end
174
+ end
175
+ for (key, value) in kwargs
176
+ values[string(key)] = value
177
+ end
178
+ senpi_with_bridge_timeout_pause(() -> senpi_call_tool(callable.name, values))
71
179
  end
72
180
 
73
- struct SenpiToolProxy end
181
+ Base.getproperty(::SenpiToolProxy, name::Symbol) = SenpiToolCallable(string(name))
182
+ const tool = SenpiToolProxy()
74
183
 
75
- function Base.getproperty(::SenpiToolProxy, name::Symbol)
76
- return args -> senpi_call_tool(string(name), args)
184
+ function completion(prompt::AbstractString; model="default", system=nothing, schema=nothing, kwargs...)
185
+ options = Dict{String, Any}("model" => model)
186
+ system !== nothing && (options["system"] = system)
187
+ schema !== nothing && (options["schema"] = schema)
188
+ for (key, value) in kwargs
189
+ options[string(key)] = value
190
+ end
191
+ response = senpi_with_bridge_timeout_pause(() -> senpi_completion(string(prompt), options))
192
+ response isa AbstractDict || return response
193
+ haskey(response, "value") && return response["value"]
194
+ get(response, "text", response)
77
195
  end
78
196
 
79
- const tool = SenpiToolProxy()
197
+ function output(ids...; format="raw", offset=nothing, limit=nothing)
198
+ isempty(ids) && error("At least one output ID is required")
199
+ format in ("raw", "tail") || error("output() format must be 'raw' or 'tail'")
200
+ arguments = Dict{String, Any}("ids" => string.(ids), "format" => format)
201
+ offset !== nothing && (arguments["offset"] = offset)
202
+ limit !== nothing && (arguments["limit"] = limit)
203
+ senpi_with_bridge_timeout_pause(() -> senpi_call_tool("__output__", arguments))
204
+ end
205
+
206
+ function agent(prompt::AbstractString; agent="task", model=nothing, label=nothing, schema=nothing, isolated=nothing, apply=nothing, merge=nothing, handle=false, kwargs...)
207
+ arguments = Dict{String, Any}("prompt" => string(prompt), "agent" => agent)
208
+ for (key, value) in (("model", model), ("label", label), ("schema", schema), ("isolated", isolated), ("apply", apply), ("merge", merge))
209
+ value !== nothing && (arguments[key] = value)
210
+ end
211
+ for (key, value) in kwargs
212
+ arguments[string(key)] = value
213
+ end
214
+ handle && (arguments["handle"] = true)
215
+ response = senpi_with_bridge_timeout_pause(() -> senpi_call_tool("__agent__", arguments))
216
+ record = response isa AbstractDict ? response : Dict{String, Any}()
217
+ text_value = get(record, "text", response)
218
+ parsed = schema === nothing ? text_value : haskey(record, "data") ? record["data"] : senpi_json_parse(string(text_value))
219
+ handle || return parsed
220
+ result = Dict{String, Any}("text" => text_value, "output" => text_value, "id" => get(record, "id", nothing), "agent" => get(record, "agent", agent))
221
+ result["handle"] = get(record, "handle", result["id"] === nothing ? nothing : "agent://" * string(result["id"]))
222
+ schema !== nothing && (result["data"] = parsed)
223
+ result
224
+ end
225
+
226
+ function senpi_pool_map(items, callback)
227
+ values = collect(items)
228
+ isempty(values) && return Any[]
229
+ configured_width = get(senpi_connection, "parallelPoolWidth", 4)
230
+ width = configured_width isa Real && isfinite(configured_width) ? Int(floor(configured_width)) : 4
231
+ workers = min(max(width, 1), length(values))
232
+ tokens = Channel{Nothing}(workers)
233
+ for _ in 1:workers
234
+ put!(tokens, nothing)
235
+ end
236
+ results = Vector{Any}(undef, length(values))
237
+ errors = Dict{Int, Any}()
238
+ lock = ReentrantLock()
239
+ @sync for index in eachindex(values)
240
+ @async begin
241
+ take!(tokens)
242
+ try
243
+ results[index] = callback(values[index])
244
+ catch error
245
+ Base.lock(lock) do
246
+ errors[index] = error
247
+ end
248
+ finally
249
+ put!(tokens, nothing)
250
+ end
251
+ end
252
+ end
253
+ isempty(errors) || throw(errors[minimum(keys(errors))])
254
+ results
255
+ end
256
+
257
+ function parallel(thunks)
258
+ values = collect(thunks)
259
+ for thunk in values
260
+ applicable(thunk) || error("parallel() expects zero-argument callables")
261
+ end
262
+ senpi_pool_map(values, thunk -> thunk())
263
+ end
264
+
265
+ function pipeline(items, stages...)
266
+ values = collect(items)
267
+ for stage in stages
268
+ isempty(values) && return values
269
+ applicable(stage, first(values)) || error("pipeline() stages must be callables")
270
+ values = senpi_pool_map(values, stage)
271
+ end
272
+ values
273
+ end
274
+
275
+ function log(message)
276
+ senpi_emit(Dict("type" => "log", "message" => string(message)))
277
+ nothing
278
+ end
279
+
280
+ function phase(title)
281
+ senpi_emit(Dict("type" => "phase", "title" => string(title)))
282
+ nothing
283
+ end