@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,5 +1,7 @@
1
+ // allow: SIZE_OK — one eval render pipeline avoids the runtime/render TDZ that motivated this module boundary.
1
2
  import {
2
3
  type AgentToolResult,
4
+ highlightCode,
3
5
  sanitizeTerminalLabel,
4
6
  type Theme,
5
7
  type ThemeColor,
@@ -7,12 +9,34 @@ import {
7
9
  type ToolRenderResultOptions,
8
10
  truncateToVisualLines,
9
11
  } from "@code-yeongyu/senpi";
10
- import type { createEvalInputSchema, EvalToolDetails, EvalToolInput } from "./types.ts";
12
+ import { formatTruncationWarning, stripOutputNotice, type TruncationMeta } from "../output/output-meta.ts";
13
+ import {
14
+ JSON_TREE_MAX_DEPTH_COLLAPSED,
15
+ JSON_TREE_MAX_DEPTH_EXPANDED,
16
+ JSON_TREE_MAX_LINES_COLLAPSED,
17
+ JSON_TREE_MAX_LINES_EXPANDED,
18
+ JSON_TREE_SCALAR_LEN_COLLAPSED,
19
+ JSON_TREE_SCALAR_LEN_EXPANDED,
20
+ renderJsonTreeLines,
21
+ } from "./json-tree.ts";
22
+ import type {
23
+ EvalCellResult,
24
+ EvalInputSchema,
25
+ EvalLanguage,
26
+ EvalStatusEvent,
27
+ EvalToolDetails,
28
+ EvalToolInput,
29
+ } from "./types.ts";
11
30
 
12
- type EvalToolDefinition = ToolDefinition<ReturnType<typeof createEvalInputSchema>, EvalToolDetails>;
31
+ type EvalToolDefinition = ToolDefinition<EvalInputSchema, EvalToolDetails>;
13
32
  type RenderContext = Parameters<NonNullable<EvalToolDefinition["renderCall"]>>[2];
14
- type Component = ReturnType<NonNullable<EvalToolDefinition["renderCall"]>>;
33
+ type ResultRenderContext = Parameters<NonNullable<EvalToolDefinition["renderResult"]>>[3];
15
34
  type CollapsibleKind = "code" | "output";
35
+
36
+ export interface EvalRenderComponent {
37
+ render(width: number): string[];
38
+ invalidate(): void;
39
+ }
16
40
  type ToolCallRow = {
17
41
  readonly summary: string;
18
42
  readonly error?: string;
@@ -32,16 +56,19 @@ type RenderBlock =
32
56
  readonly calls: readonly ToolCallRow[];
33
57
  readonly expanded: boolean;
34
58
  readonly theme?: Theme;
35
- };
59
+ }
60
+ | { readonly kind: "dynamic"; readonly render: (width: number) => readonly string[] };
36
61
 
37
62
  const CODE_PREVIEW_LINES = 4;
38
63
  const OUTPUT_PREVIEW_LINES = 8;
64
+ const STATUS_PREVIEW_COUNT = 3;
65
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const;
39
66
  const TOOL_CALL_PREVIEW_COUNT = 5;
40
67
  const TOOL_CALL_COLLAPSED_VISUAL_LINES = 4;
41
68
  const TOOL_CALL_COLLAPSED_ERROR_CODE_POINTS = 512;
42
69
  const TOOL_ERROR_OMISSION_MARKER = "[tool error omitted]";
43
70
 
44
- class PlainTextComponent {
71
+ class PlainTextComponent implements EvalRenderComponent {
45
72
  #blocks: readonly RenderBlock[] = [];
46
73
 
47
74
  setBlocks(blocks: readonly RenderBlock[]): void {
@@ -51,12 +78,21 @@ class PlainTextComponent {
51
78
  render(width: number): string[] {
52
79
  const lines: string[] = [];
53
80
  for (const block of this.#blocks) {
54
- if (block.kind === "blank") {
55
- lines.push("");
56
- } else if (block.kind === "toolCalls") {
57
- appendLines(lines, renderToolCallBlock(block, width));
58
- } else {
59
- appendLines(lines, renderTextBlock(block, width));
81
+ switch (block.kind) {
82
+ case "blank":
83
+ lines.push("");
84
+ break;
85
+ case "toolCalls":
86
+ appendLines(lines, renderToolCallBlock(block, width));
87
+ break;
88
+ case "text":
89
+ appendLines(lines, renderTextBlock(block, width));
90
+ break;
91
+ case "dynamic":
92
+ appendLines(lines, block.render(width));
93
+ break;
94
+ default:
95
+ assertNever(block);
60
96
  }
61
97
  }
62
98
  return lines;
@@ -65,7 +101,7 @@ class PlainTextComponent {
65
101
  invalidate(): void {}
66
102
  }
67
103
 
68
- function componentFor(context: RenderContext): PlainTextComponent {
104
+ function componentFor(context: RenderContext | ResultRenderContext): PlainTextComponent {
69
105
  const existing = context.lastComponent;
70
106
  if (existing instanceof PlainTextComponent) return existing;
71
107
  return new PlainTextComponent();
@@ -152,8 +188,473 @@ function renderToolCallBlock(block: Extract<RenderBlock, { kind: "toolCalls" }>,
152
188
  return lines;
153
189
  }
154
190
 
155
- function callCode(code: string): string {
156
- return code.trim().length > 0 ? code : "...";
191
+ type CellStatus = EvalCellResult["status"];
192
+ type AgentStatus = "pending" | "running" | "completed" | "failed" | "aborted";
193
+ type StatusPresentation = { readonly label: string; readonly icon: string; readonly color: ThemeColor };
194
+ type RenderEnvironment = {
195
+ readonly expanded: boolean;
196
+ readonly theme: Theme | undefined;
197
+ readonly spinnerFrame: number | undefined;
198
+ readonly width: number;
199
+ readonly meta: TruncationMeta | undefined;
200
+ };
201
+ type CellBadges = { readonly reset: boolean; readonly timeout: number | undefined };
202
+ type PrefixStyle = { readonly prefix: string; readonly continuation: string; readonly color: ThemeColor };
203
+ type DetailedRenderContext = {
204
+ readonly environment: RenderEnvironment;
205
+ readonly args: EvalToolInput;
206
+ readonly showImageFallback: boolean;
207
+ };
208
+
209
+ function assertNever(value: never): never {
210
+ throw new TypeError(`Unhandled eval render variant: ${String(value)}`);
211
+ }
212
+
213
+ function languageForHighlighter(language: EvalLanguage): "python" | "javascript" | "ruby" | "julia" {
214
+ switch (language) {
215
+ case "py":
216
+ return "python";
217
+ case "js":
218
+ return "javascript";
219
+ case "rb":
220
+ return "ruby";
221
+ case "jl":
222
+ return "julia";
223
+ default:
224
+ return assertNever(language);
225
+ }
226
+ }
227
+
228
+ function highlightedCode(code: string, language: EvalLanguage, theme: Theme | undefined): string {
229
+ const normalizedCode = code.trim().length > 0 ? code : "...";
230
+ const lines = highlightCode(normalizedCode, languageForHighlighter(language));
231
+ return (theme === undefined ? lines.map((line) => line.replace(/\u001b\[[0-9;]*m/gu, "")) : lines).join("\n");
232
+ }
233
+
234
+ function formatDuration(milliseconds: number): string {
235
+ const totalSeconds = Math.floor(Math.max(0, milliseconds) / 1_000);
236
+ if (totalSeconds < 1) return "<1s";
237
+ const seconds = totalSeconds % 60;
238
+ const totalMinutes = Math.floor(totalSeconds / 60);
239
+ if (totalMinutes < 1) return `${seconds}s`;
240
+ const minutes = totalMinutes % 60;
241
+ const hours = Math.floor(totalMinutes / 60);
242
+ if (hours < 1) return seconds === 0 ? `${minutes}m` : `${minutes}m ${seconds}s`;
243
+ return minutes === 0 ? `${hours}h` : `${hours}h ${minutes}m`;
244
+ }
245
+
246
+ function spinner(frame: number | undefined): string {
247
+ return SPINNER_FRAMES.at((frame ?? 0) % SPINNER_FRAMES.length) ?? SPINNER_FRAMES[0];
248
+ }
249
+
250
+ function cellPresentation(status: CellStatus, spinnerFrame: number | undefined): StatusPresentation {
251
+ switch (status) {
252
+ case "pending":
253
+ return { label: "pending", icon: "○", color: "muted" };
254
+ case "running":
255
+ return { label: "running", icon: spinner(spinnerFrame), color: "warning" };
256
+ case "complete":
257
+ return { label: "done", icon: "✓", color: "success" };
258
+ case "error":
259
+ return { label: "error", icon: "✗", color: "error" };
260
+ default:
261
+ return assertNever(status);
262
+ }
263
+ }
264
+
265
+ function renderPrefixed(text: string, environment: RenderEnvironment, prefixStyle: PrefixStyle): string[] {
266
+ const bodyLines = renderAllVisualLines(text, Math.max(1, environment.width - prefixStyle.prefix.length));
267
+ if (bodyLines.length === 0) return [style(environment.theme, prefixStyle.color, prefixStyle.prefix.trimEnd())];
268
+ return bodyLines.map(
269
+ (line, index) =>
270
+ `${style(environment.theme, prefixStyle.color, index === 0 ? prefixStyle.prefix : prefixStyle.continuation)}${line}`,
271
+ );
272
+ }
273
+
274
+ function cellHeader(cell: EvalCellResult, environment: RenderEnvironment, badges: CellBadges): string {
275
+ const presentation = cellPresentation(cell.status, environment.spinnerFrame);
276
+ const title = cell.title === undefined ? "" : ` ${cell.title}`;
277
+ let header = `eval ${cell.language}${title} ${presentation.label} ${presentation.icon}`;
278
+ if (cell.durationMs !== undefined) header += ` · ${formatDuration(cell.durationMs)}`;
279
+ if (badges.reset) header += " · reset";
280
+ if (badges.timeout !== undefined) header += ` · timeout ${badges.timeout}s`;
281
+ return style(environment.theme, presentation.color, header);
282
+ }
283
+
284
+ function previewText(
285
+ text: string,
286
+ maxLines: number,
287
+ width: number,
288
+ ): { readonly lines: string[]; readonly skipped: number } {
289
+ const preview = truncateToVisualLines(text, maxLines, Math.max(1, width));
290
+ return { lines: preview.visualLines.map((line) => line.trimEnd()), skipped: preview.skippedCount };
291
+ }
292
+
293
+ function eventString(value: unknown): string | undefined {
294
+ return typeof value === "string" && value.length > 0 ? value : undefined;
295
+ }
296
+
297
+ function eventNumber(value: unknown): number {
298
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
299
+ }
300
+
301
+ function plural(count: number, singular: string, pluralNoun: string): string {
302
+ return `${count} ${count === 1 ? singular : pluralNoun}`;
303
+ }
304
+
305
+ function statusIcon(op: string): string {
306
+ if (op.startsWith("git_")) return "⌁";
307
+ switch (op) {
308
+ case "read":
309
+ case "write":
310
+ case "cat":
311
+ case "touch":
312
+ return "▣";
313
+ case "ls":
314
+ case "cd":
315
+ case "pwd":
316
+ case "mkdir":
317
+ return "▤";
318
+ case "run":
319
+ case "sh":
320
+ return "▶";
321
+ case "completion":
322
+ return "◇";
323
+ case "phase":
324
+ return "◆";
325
+ default:
326
+ return "•";
327
+ }
328
+ }
329
+
330
+ function formatStatusEvent(event: EvalStatusEvent, theme: Theme | undefined): string {
331
+ const op = event.op;
332
+ const icon = style(theme, "muted", statusIcon(op));
333
+ const error = eventString(event["error"]);
334
+ if (error !== undefined) return `${icon} ${style(theme, "warning", op)}: ${style(theme, "dim", error)}`;
335
+ const parts: string[] = [];
336
+ switch (op) {
337
+ case "read": {
338
+ parts.push(`${eventNumber(event["chars"] ?? event["bytes"])} chars`);
339
+ const path = eventString(event["path"]);
340
+ if (path !== undefined) parts.push(`from ${path}`);
341
+ break;
342
+ }
343
+ case "write": {
344
+ parts.push(`${eventNumber(event["chars"] ?? event["bytes"])} chars`);
345
+ const path = eventString(event["path"]);
346
+ if (path !== undefined) parts.push(`to ${path}`);
347
+ break;
348
+ }
349
+ case "cat":
350
+ parts.push(plural(eventNumber(event["files"]), "file", "files"));
351
+ parts.push(`${eventNumber(event["chars"])} chars`);
352
+ break;
353
+ case "ls":
354
+ parts.push(plural(eventNumber(event["count"]), "entry", "entries"));
355
+ break;
356
+ case "env": {
357
+ const action = eventString(event["action"]);
358
+ const key = eventString(event["key"]);
359
+ const value = eventString(event["value"]) ?? "";
360
+ if (action === "set" && key !== undefined) parts.push(`set ${key}=${value.slice(0, 30)}`);
361
+ else if (action === "get" && key !== undefined) parts.push(`${key}=${value.slice(0, 30)}`);
362
+ else parts.push(plural(eventNumber(event["count"]), "variable", "variables"));
363
+ break;
364
+ }
365
+ case "git_status": {
366
+ if (event["clean"] === true) parts.push("clean");
367
+ else {
368
+ const changes: string[] = [];
369
+ for (const key of ["staged", "modified", "untracked"] as const) {
370
+ const count = eventNumber(event[key]);
371
+ if (count > 0) changes.push(`${count} ${key}`);
372
+ }
373
+ parts.push(changes.join(", ") || "unknown");
374
+ }
375
+ const branch = eventString(event["branch"]);
376
+ if (branch !== undefined) parts.push(`on ${branch}`);
377
+ break;
378
+ }
379
+ case "git_diff":
380
+ parts.push(plural(eventNumber(event["lines"]), "line", "lines"));
381
+ if (event["staged"] === true) parts.push("staged");
382
+ break;
383
+ case "git_log":
384
+ parts.push(plural(eventNumber(event["commits"]), "commit", "commits"));
385
+ break;
386
+ case "run":
387
+ case "sh": {
388
+ const command = eventString(event["command"] ?? event["cmd"]);
389
+ if (command !== undefined) parts.push(command);
390
+ if (typeof event["exitCode"] === "number") parts.push(`exit ${event["exitCode"]}`);
391
+ break;
392
+ }
393
+ case "completion": {
394
+ const model = eventString(event["model"]);
395
+ const tier = eventString(event["tier"]);
396
+ if (model !== undefined) parts.push(model);
397
+ if (tier !== undefined && tier !== model) parts.push(tier);
398
+ parts.push(`${eventNumber(event["chars"])} chars`);
399
+ break;
400
+ }
401
+ case "log":
402
+ parts.push(eventString(event["message"]) ?? "");
403
+ break;
404
+ case "phase":
405
+ parts.push(eventString(event["title"]) ?? "");
406
+ break;
407
+ default: {
408
+ if (event["count"] !== undefined) parts.push(String(event["count"]));
409
+ const path = eventString(event["path"]);
410
+ if (path !== undefined) parts.push(path);
411
+ }
412
+ }
413
+ const description = parts.filter((part) => part.length > 0).join(" · ");
414
+ return `${icon} ${style(theme, "muted", op)}${description.length > 0 ? ` ${style(theme, "dim", description)}` : ""}`;
415
+ }
416
+
417
+ function renderStatusEvents(events: readonly EvalStatusEvent[], environment: RenderEnvironment): string[] {
418
+ const retained = environment.expanded ? events : events.slice(-STATUS_PREVIEW_COUNT);
419
+ const skipped = events.length - retained.length;
420
+ const lines: string[] = [];
421
+ if (skipped > 0) lines.push(style(environment.theme, "dim", `├ … ${skipped} earlier status events`));
422
+ for (const [index, event] of retained.entries()) {
423
+ const branch = index === retained.length - 1 ? "└" : "├";
424
+ lines.push(`${style(environment.theme, "dim", branch)} ${formatStatusEvent(event, environment.theme)}`);
425
+ }
426
+ return lines;
427
+ }
428
+
429
+ function agentStatus(value: unknown): AgentStatus {
430
+ switch (value) {
431
+ case "pending":
432
+ case "running":
433
+ case "completed":
434
+ case "failed":
435
+ case "aborted":
436
+ return value;
437
+ default:
438
+ return "running";
439
+ }
440
+ }
441
+
442
+ function coalesceAgentEvents(events: readonly EvalStatusEvent[]): EvalStatusEvent[] {
443
+ const rows: EvalStatusEvent[] = [];
444
+ const indexes = new Map<string, number>();
445
+ for (const event of events) {
446
+ const id = eventString(event["id"]);
447
+ if (id === undefined) {
448
+ rows.push(event);
449
+ continue;
450
+ }
451
+ const index = indexes.get(id);
452
+ if (index === undefined) {
453
+ indexes.set(id, rows.length);
454
+ rows.push(event);
455
+ } else rows[index] = event;
456
+ }
457
+ return rows;
458
+ }
459
+
460
+ function agentPresentation(status: AgentStatus, spinnerFrame: number | undefined): StatusPresentation {
461
+ switch (status) {
462
+ case "pending":
463
+ return { label: "pending", icon: "○", color: "muted" };
464
+ case "running":
465
+ return { label: "running", icon: spinner(spinnerFrame), color: "warning" };
466
+ case "completed":
467
+ return { label: "done", icon: "✓", color: "success" };
468
+ case "failed":
469
+ return { label: "failed", icon: "✗", color: "error" };
470
+ case "aborted":
471
+ return { label: "aborted", icon: "×", color: "error" };
472
+ default:
473
+ return assertNever(status);
474
+ }
475
+ }
476
+
477
+ function renderAgentProgressEvents(events: readonly EvalStatusEvent[], environment: RenderEnvironment): string[] {
478
+ const rows = coalesceAgentEvents(events);
479
+ const lines: string[] = [];
480
+ // Senpi Theme has no tree-token API; fixed ├/└/│ glyphs intentionally mirror omp.
481
+ for (const [index, event] of rows.entries()) {
482
+ const isLast = index === rows.length - 1;
483
+ const status = agentStatus(event["status"]);
484
+ const presentation = agentPresentation(status, environment.spinnerFrame);
485
+ const id = eventString(event["id"]) ?? "agent";
486
+ const styledId = environment.theme === undefined ? id : environment.theme.bold(id);
487
+ let body = `${style(environment.theme, presentation.color, presentation.icon)} ${styledId} ${presentation.label}`;
488
+ if (status === "completed" || status === "failed" || status === "aborted") {
489
+ const duration = eventNumber(event["durationMs"]);
490
+ if (duration > 0) body += ` · ${style(environment.theme, "dim", formatDuration(duration))}`;
491
+ }
492
+ const branch = isLast ? "└ " : "├ ";
493
+ const continuation = isLast ? " " : "│ ";
494
+ appendLines(
495
+ lines,
496
+ renderPrefixed(body, environment, { prefix: branch, continuation: continuation, color: "dim" }),
497
+ );
498
+ if (status !== "running") continue;
499
+ const currentTool = eventString(event["currentTool"]);
500
+ const lastIntent = eventString(event["lastIntent"]);
501
+ if (currentTool === undefined && lastIntent === undefined) continue;
502
+ const detail =
503
+ currentTool === undefined
504
+ ? (lastIntent ?? "")
505
+ : `${currentTool}${lastIntent === undefined ? "" : `: ${lastIntent}`}`;
506
+ appendLines(
507
+ lines,
508
+ renderPrefixed(detail, environment, {
509
+ prefix: `${continuation}└ `,
510
+ continuation: `${continuation} `,
511
+ color: "dim",
512
+ }),
513
+ );
514
+ }
515
+ return lines;
516
+ }
517
+
518
+ function renderCell(cell: EvalCellResult, environment: RenderEnvironment, badges: CellBadges): string[] {
519
+ const lines = renderPrefixed(cellHeader(cell, environment, badges), environment, {
520
+ prefix: "╭─ ",
521
+ continuation: "│ ",
522
+ color: "borderAccent",
523
+ });
524
+ const innerWidth = Math.max(1, environment.width - 2);
525
+ const codePreview = previewText(
526
+ highlightedCode(cell.code, cell.language, environment.theme),
527
+ environment.expanded ? Number.POSITIVE_INFINITY : CODE_PREVIEW_LINES,
528
+ innerWidth,
529
+ );
530
+ if (codePreview.skipped > 0) {
531
+ appendLines(
532
+ lines,
533
+ renderPrefixed(`${codePreview.skipped} earlier code lines`, environment, {
534
+ prefix: "│ ",
535
+ continuation: "│ ",
536
+ color: "muted",
537
+ }),
538
+ );
539
+ }
540
+ for (const line of codePreview.lines) {
541
+ appendLines(lines, renderPrefixed(line, environment, { prefix: "│ ", continuation: "│ ", color: "borderMuted" }));
542
+ }
543
+ const output = stripOutputNotice(cell.output, environment.meta).trimEnd();
544
+ if (output.length > 0) {
545
+ appendLines(lines, renderPrefixed("output", environment, { prefix: "├─ ", continuation: "│ ", color: "dim" }));
546
+ const outputColor: ThemeColor = cell.status === "error" ? "error" : "toolOutput";
547
+ const styledOutput = output
548
+ .split("\n")
549
+ .map((line) => style(environment.theme, outputColor, line))
550
+ .join("\n");
551
+ const outputPreview = previewText(
552
+ styledOutput,
553
+ environment.expanded ? Number.POSITIVE_INFINITY : OUTPUT_PREVIEW_LINES,
554
+ innerWidth,
555
+ );
556
+ if (outputPreview.skipped > 0) {
557
+ appendLines(
558
+ lines,
559
+ renderPrefixed(`${outputPreview.skipped} earlier output lines`, environment, {
560
+ prefix: "│ ",
561
+ continuation: "│ ",
562
+ color: "muted",
563
+ }),
564
+ );
565
+ }
566
+ for (const line of outputPreview.lines) {
567
+ appendLines(
568
+ lines,
569
+ renderPrefixed(line, environment, { prefix: "│ ", continuation: "│ ", color: "borderMuted" }),
570
+ );
571
+ }
572
+ }
573
+ const allEvents = cell.statusEvents ?? [];
574
+ const statusEvents = allEvents.filter((event) => event.op !== "agent");
575
+ if (statusEvents.length > 0) {
576
+ appendLines(lines, renderPrefixed("status", environment, { prefix: "├─ ", continuation: "│ ", color: "dim" }));
577
+ for (const line of renderStatusEvents(statusEvents, environment)) {
578
+ appendLines(
579
+ lines,
580
+ renderPrefixed(line, environment, { prefix: "│ ", continuation: "│ ", color: "borderMuted" }),
581
+ );
582
+ }
583
+ }
584
+ lines.push(style(environment.theme, "borderMuted", "╰─"));
585
+ const agentEvents = allEvents.filter((event) => event.op === "agent");
586
+ if (agentEvents.length > 0) appendLines(lines, renderAgentProgressEvents(agentEvents, environment));
587
+ return lines;
588
+ }
589
+
590
+ function renderJsonOutputs(values: readonly unknown[], environment: RenderEnvironment): string[] {
591
+ const lines: string[] = [];
592
+ const depth = environment.expanded ? JSON_TREE_MAX_DEPTH_EXPANDED : JSON_TREE_MAX_DEPTH_COLLAPSED;
593
+ const lineCap = environment.expanded ? JSON_TREE_MAX_LINES_EXPANDED : JSON_TREE_MAX_LINES_COLLAPSED;
594
+ const scalarLen = environment.expanded ? JSON_TREE_SCALAR_LEN_EXPANDED : JSON_TREE_SCALAR_LEN_COLLAPSED;
595
+ for (const [index, value] of values.entries()) {
596
+ appendLines(
597
+ lines,
598
+ renderAllVisualLines(style(environment.theme, "dim", `display[${index + 1}]`), environment.width),
599
+ );
600
+ const tree = renderJsonTreeLines(value, environment.theme, depth, lineCap, scalarLen);
601
+ for (const line of tree.lines) appendLines(lines, renderAllVisualLines(line, environment.width));
602
+ if (tree.truncated)
603
+ appendLines(lines, renderAllVisualLines(style(environment.theme, "dim", "…"), environment.width));
604
+ }
605
+ return lines;
606
+ }
607
+
608
+ function renderDetailedLines(
609
+ details: EvalToolDetails,
610
+ result: AgentToolResult<EvalToolDetails>,
611
+ context: DetailedRenderContext,
612
+ ): string[] {
613
+ const lines: string[] = [];
614
+ const cells = details.cells ?? [];
615
+ for (const [index, cell] of cells.entries()) {
616
+ const badges = {
617
+ reset: index === 0 && context.args.reset === true,
618
+ timeout: index === 0 ? context.args.timeout : undefined,
619
+ };
620
+ appendLines(lines, renderCell(cell, context.environment, badges));
621
+ if (index < cells.length - 1) lines.push("");
622
+ }
623
+ const jsonOutputs = details.jsonOutputs ?? [];
624
+ if (jsonOutputs.length > 0) {
625
+ if (lines.length > 0) lines.push("");
626
+ appendLines(lines, renderJsonOutputs(jsonOutputs, context.environment));
627
+ }
628
+ if (context.showImageFallback) {
629
+ for (const part of result.content) {
630
+ if (part.type !== "image") continue;
631
+ if (lines.length > 0) lines.push("");
632
+ appendLines(
633
+ lines,
634
+ renderAllVisualLines(`[image: ${sanitizeTerminalLabel(part.mimeType)}]`, context.environment.width),
635
+ );
636
+ }
637
+ }
638
+ if (details.phase !== undefined)
639
+ appendLines(
640
+ lines,
641
+ renderAllVisualLines(
642
+ style(context.environment.theme, "muted", `phase ${details.phase}`),
643
+ context.environment.width,
644
+ ),
645
+ );
646
+ if (details.notice !== undefined)
647
+ appendLines(
648
+ lines,
649
+ renderAllVisualLines(style(context.environment.theme, "dim", details.notice), context.environment.width),
650
+ );
651
+ const warning = formatTruncationWarning(details.meta) ?? (details.truncated ? "[eval output truncated]" : null);
652
+ if (warning !== null)
653
+ appendLines(
654
+ lines,
655
+ renderAllVisualLines(style(context.environment.theme, "warning", warning), context.environment.width),
656
+ );
657
+ return lines;
157
658
  }
158
659
 
159
660
  function textOutput(result: AgentToolResult<EvalToolDetails>, showImageFallback: boolean): string {
@@ -190,12 +691,20 @@ function resultHeader(
190
691
  status: "running" | "done" | "error",
191
692
  theme: Theme | undefined,
192
693
  ): string {
193
- const title = details?.title ? ` ${details.title}` : "";
194
- return style(
195
- theme,
196
- status === "running" ? "warning" : status === "done" ? "success" : "error",
197
- `eval ${details?.language ?? "?"}${title} ${status}`,
198
- );
694
+ const title = details?.title === undefined ? "" : ` ${details.title}`;
695
+ let color: ThemeColor;
696
+ switch (status) {
697
+ case "running":
698
+ color = "warning";
699
+ break;
700
+ case "done":
701
+ color = "success";
702
+ break;
703
+ case "error":
704
+ color = "error";
705
+ break;
706
+ }
707
+ return style(theme, color, `eval ${details?.language ?? "?"}${title} ${status}`);
199
708
  }
200
709
 
201
710
  function resultMetadata(
@@ -210,19 +719,49 @@ function resultMetadata(
210
719
  return [{ kind: "text", text: style(theme, "muted", metadata.join(" | ")) }];
211
720
  }
212
721
 
213
- export function renderEvalCall(args: EvalToolInput, theme: Theme | undefined, context: RenderContext): Component {
722
+ export function renderEvalCall(
723
+ args: EvalToolInput,
724
+ theme: Theme | undefined,
725
+ context: RenderContext,
726
+ ): EvalRenderComponent {
214
727
  const component = componentFor(context);
215
- const title = args.title ? ` ${args.title}` : "";
216
- const reset = args.reset ? " reset" : "";
217
- const timeout = args.timeout ? ` timeout ${args.timeout}s` : "";
728
+ if (theme === undefined && context.spinnerFrame === undefined) {
729
+ const title = args.title === undefined ? "" : ` ${args.title}`;
730
+ const reset = args.reset === true ? " reset" : "";
731
+ const timeout = args.timeout === undefined ? "" : ` timeout ${args.timeout}s`;
732
+ component.setBlocks([
733
+ { kind: "text", text: style(theme, "toolTitle", `eval ${args.language}${title}${reset}${timeout}`) },
734
+ {
735
+ kind: "text",
736
+ text: style(theme, "mdCodeBlock", args.code.trim().length > 0 ? args.code : "..."),
737
+ maxVisualLines: context.expanded ? undefined : CODE_PREVIEW_LINES,
738
+ collapseKind: "code",
739
+ theme,
740
+ },
741
+ ]);
742
+ return component;
743
+ }
218
744
  component.setBlocks([
219
- { kind: "text", text: style(theme, "toolTitle", `eval ${args.language}${title}${reset}${timeout}`) },
220
745
  {
221
- kind: "text",
222
- text: style(theme, "mdCodeBlock", callCode(args.code)),
223
- maxVisualLines: context.expanded ? undefined : CODE_PREVIEW_LINES,
224
- collapseKind: "code",
225
- theme,
746
+ kind: "dynamic",
747
+ render: (width) => {
748
+ const environment: RenderEnvironment = {
749
+ expanded: context.expanded,
750
+ theme,
751
+ spinnerFrame: context.spinnerFrame,
752
+ width,
753
+ meta: undefined,
754
+ };
755
+ const cell: EvalCellResult = {
756
+ index: 0,
757
+ ...(args.title === undefined ? {} : { title: args.title }),
758
+ code: args.code,
759
+ language: args.language,
760
+ output: "",
761
+ status: context.spinnerFrame === undefined ? "pending" : "running",
762
+ };
763
+ return renderCell(cell, environment, { reset: args.reset === true, timeout: args.timeout });
764
+ },
226
765
  },
227
766
  ]);
228
767
  return component;
@@ -232,22 +771,46 @@ export function renderEvalResult(
232
771
  result: AgentToolResult<EvalToolDetails>,
233
772
  options: ToolRenderResultOptions,
234
773
  theme: Theme | undefined,
235
- context: Parameters<NonNullable<EvalToolDefinition["renderResult"]>>[3],
236
- ): Component {
774
+ context: ResultRenderContext,
775
+ ): EvalRenderComponent {
237
776
  const component = componentFor(context);
238
777
  const details = result.details;
239
778
  const expanded = options.expanded || context.expanded;
240
779
  const imageProtocol = context.imageProtocol ?? null;
780
+ if (details?.cells !== undefined && details.cells.length > 0) {
781
+ const blocks: RenderBlock[] = [
782
+ {
783
+ kind: "dynamic",
784
+ render: (width) =>
785
+ renderDetailedLines(details, result, {
786
+ environment: {
787
+ expanded,
788
+ theme,
789
+ spinnerFrame: context.spinnerFrame,
790
+ width,
791
+ meta: details.meta,
792
+ },
793
+ args: context.args,
794
+ showImageFallback: context.showImages && imageProtocol === null,
795
+ }),
796
+ },
797
+ ];
798
+ const calls = toolCallRows(details);
799
+ if (calls.length > 0) blocks.push({ kind: "blank" }, { kind: "toolCalls", calls, expanded, theme });
800
+ component.setBlocks(blocks);
801
+ return component;
802
+ }
241
803
  const status = resultStatus(details, options, context.isError);
242
804
  const blocks: RenderBlock[] = [
243
805
  { kind: "text", text: resultHeader(details, status, theme) },
244
806
  ...resultMetadata(details, options, theme),
245
807
  { kind: "blank" },
246
808
  ];
247
- const output = textOutput(result, context.showImages && imageProtocol === null).trimEnd();
809
+ const rawOutput = textOutput(result, context.showImages && imageProtocol === null);
810
+ const output = stripOutputNotice(rawOutput, details?.meta).trimEnd();
248
811
  const hasRenderedImage =
249
812
  context.showImages && imageProtocol !== null && result.content.some((part) => part.type === "image");
250
- if (output) {
813
+ if (output.length > 0) {
251
814
  blocks.push({
252
815
  kind: "text",
253
816
  text: style(theme, "toolOutput", output),
@@ -255,13 +818,53 @@ export function renderEvalResult(
255
818
  collapseKind: "output",
256
819
  theme,
257
820
  });
258
- } else if (!hasRenderedImage) {
259
- blocks.push({ kind: "text", text: style(theme, "muted", "(no output)") });
821
+ } else if (!hasRenderedImage) blocks.push({ kind: "text", text: style(theme, "muted", "(no output)") });
822
+ const statusEvents = details?.statusEvents ?? [];
823
+ const nonAgentEvents = statusEvents.filter((event) => event.op !== "agent");
824
+ const agentEvents = statusEvents.filter((event) => event.op === "agent");
825
+ if (nonAgentEvents.length > 0 || agentEvents.length > 0) {
826
+ blocks.push(
827
+ { kind: "blank" },
828
+ {
829
+ kind: "dynamic",
830
+ render: (width) => {
831
+ const environment: RenderEnvironment = {
832
+ expanded,
833
+ theme,
834
+ spinnerFrame: context.spinnerFrame,
835
+ width,
836
+ meta: details?.meta,
837
+ };
838
+ return [
839
+ ...renderStatusEvents(nonAgentEvents, environment),
840
+ ...renderAgentProgressEvents(agentEvents, environment),
841
+ ];
842
+ },
843
+ },
844
+ );
845
+ }
846
+ if ((details?.jsonOutputs?.length ?? 0) > 0) {
847
+ blocks.push(
848
+ { kind: "blank" },
849
+ {
850
+ kind: "dynamic",
851
+ render: (width) =>
852
+ renderJsonOutputs(details?.jsonOutputs ?? [], {
853
+ expanded,
854
+ theme,
855
+ spinnerFrame: context.spinnerFrame,
856
+ width,
857
+ meta: details?.meta,
858
+ }),
859
+ },
860
+ );
260
861
  }
261
862
  const calls = toolCallRows(details);
262
863
  if (calls.length > 0) blocks.push({ kind: "blank" }, { kind: "toolCalls", calls, expanded, theme });
263
- if (details?.truncated)
264
- blocks.push({ kind: "blank" }, { kind: "text", text: style(theme, "warning", "[eval output truncated]") });
864
+ if (details?.notice !== undefined)
865
+ blocks.push({ kind: "blank" }, { kind: "text", text: style(theme, "dim", details.notice) });
866
+ const warning = formatTruncationWarning(details?.meta) ?? (details?.truncated ? "[eval output truncated]" : null);
867
+ if (warning !== null) blocks.push({ kind: "blank" }, { kind: "text", text: style(theme, "warning", warning) });
265
868
  component.setBlocks(blocks);
266
869
  return component;
267
870
  }