@code-yeongyu/senpi 2026.5.29 → 2026.6.3

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 (310) hide show
  1. package/CHANGELOG.md +131 -1
  2. package/README.md +12 -2
  3. package/dist/cli/args.d.ts +3 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +28 -0
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +9 -1
  9. package/dist/config.js.map +1 -1
  10. package/dist/core/agent-session-services.d.ts +1 -0
  11. package/dist/core/agent-session-services.d.ts.map +1 -1
  12. package/dist/core/agent-session-services.js +1 -0
  13. package/dist/core/agent-session-services.js.map +1 -1
  14. package/dist/core/agent-session.d.ts +9 -2
  15. package/dist/core/agent-session.d.ts.map +1 -1
  16. package/dist/core/agent-session.js +36 -13
  17. package/dist/core/agent-session.js.map +1 -1
  18. package/dist/core/compaction/branch-summarization.d.ts +3 -1
  19. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  20. package/dist/core/compaction/branch-summarization.js +9 -3
  21. package/dist/core/compaction/branch-summarization.js.map +1 -1
  22. package/dist/core/extensions/index.d.ts +1 -1
  23. package/dist/core/extensions/index.d.ts.map +1 -1
  24. package/dist/core/extensions/index.js.map +1 -1
  25. package/dist/core/extensions/runner.d.ts +5 -3
  26. package/dist/core/extensions/runner.d.ts.map +1 -1
  27. package/dist/core/extensions/runner.js +21 -3
  28. package/dist/core/extensions/runner.js.map +1 -1
  29. package/dist/core/extensions/types.d.ts +14 -6
  30. package/dist/core/extensions/types.d.ts.map +1 -1
  31. package/dist/core/extensions/types.js.map +1 -1
  32. package/dist/core/footer-data-provider.d.ts +2 -0
  33. package/dist/core/footer-data-provider.d.ts.map +1 -1
  34. package/dist/core/footer-data-provider.js +29 -1
  35. package/dist/core/footer-data-provider.js.map +1 -1
  36. package/dist/core/model-registry.d.ts.map +1 -1
  37. package/dist/core/model-registry.js +82 -21
  38. package/dist/core/model-registry.js.map +1 -1
  39. package/dist/core/model-resolver.d.ts.map +1 -1
  40. package/dist/core/model-resolver.js +2 -1
  41. package/dist/core/model-resolver.js.map +1 -1
  42. package/dist/core/provider-attribution.d.ts +4 -0
  43. package/dist/core/provider-attribution.d.ts.map +1 -0
  44. package/dist/core/provider-attribution.js +73 -0
  45. package/dist/core/provider-attribution.js.map +1 -0
  46. package/dist/core/provider-display-names.d.ts.map +1 -1
  47. package/dist/core/provider-display-names.js +1 -0
  48. package/dist/core/provider-display-names.js.map +1 -1
  49. package/dist/core/resolve-config-value.d.ts +9 -1
  50. package/dist/core/resolve-config-value.d.ts.map +1 -1
  51. package/dist/core/resolve-config-value.js +134 -11
  52. package/dist/core/resolve-config-value.js.map +1 -1
  53. package/dist/core/sdk.d.ts +2 -0
  54. package/dist/core/sdk.d.ts.map +1 -1
  55. package/dist/core/sdk.js +18 -40
  56. package/dist/core/sdk.js.map +1 -1
  57. package/dist/core/session-manager.d.ts +6 -7
  58. package/dist/core/session-manager.d.ts.map +1 -1
  59. package/dist/core/session-manager.js +167 -96
  60. package/dist/core/session-manager.js.map +1 -1
  61. package/dist/core/settings-manager.d.ts +3 -1
  62. package/dist/core/settings-manager.d.ts.map +1 -1
  63. package/dist/core/settings-manager.js +15 -11
  64. package/dist/core/settings-manager.js.map +1 -1
  65. package/dist/core/system-prompt.d.ts.map +1 -1
  66. package/dist/core/system-prompt.js +0 -3
  67. package/dist/core/system-prompt.js.map +1 -1
  68. package/dist/core/thinking-levels.d.ts.map +1 -1
  69. package/dist/core/thinking-levels.js +6 -2
  70. package/dist/core/thinking-levels.js.map +1 -1
  71. package/dist/core/tools/edit.d.ts.map +1 -1
  72. package/dist/core/tools/edit.js +7 -10
  73. package/dist/core/tools/edit.js.map +1 -1
  74. package/dist/core/tools/find.d.ts.map +1 -1
  75. package/dist/core/tools/find.js.map +1 -1
  76. package/dist/core/tools/grep.d.ts.map +1 -1
  77. package/dist/core/tools/grep.js.map +1 -1
  78. package/dist/core/tools/ls.d.ts.map +1 -1
  79. package/dist/core/tools/ls.js +5 -7
  80. package/dist/core/tools/ls.js.map +1 -1
  81. package/dist/core/tools/read.d.ts.map +1 -1
  82. package/dist/core/tools/read.js +6 -7
  83. package/dist/core/tools/read.js.map +1 -1
  84. package/dist/core/tools/render-utils.d.ts +5 -2
  85. package/dist/core/tools/render-utils.d.ts.map +1 -1
  86. package/dist/core/tools/render-utils.js +17 -1
  87. package/dist/core/tools/render-utils.js.map +1 -1
  88. package/dist/core/tools/write.d.ts.map +1 -1
  89. package/dist/core/tools/write.js +5 -6
  90. package/dist/core/tools/write.js.map +1 -1
  91. package/dist/index.d.ts +2 -0
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +2 -0
  94. package/dist/index.js.map +1 -1
  95. package/dist/main.d.ts.map +1 -1
  96. package/dist/main.js +76 -16
  97. package/dist/main.js.map +1 -1
  98. package/dist/migrations.d.ts.map +1 -1
  99. package/dist/migrations.js +118 -1
  100. package/dist/migrations.js.map +1 -1
  101. package/dist/modes/interactive/components/login-dialog.d.ts +1 -3
  102. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  103. package/dist/modes/interactive/components/login-dialog.js +2 -4
  104. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  105. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  106. package/dist/modes/interactive/components/tool-execution.js +25 -1
  107. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  108. package/dist/modes/interactive/interactive-mode.d.ts +3 -0
  109. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  110. package/dist/modes/interactive/interactive-mode.js +64 -6
  111. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  112. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  113. package/dist/modes/interactive/theme/theme.js +10 -0
  114. package/dist/modes/interactive/theme/theme.js.map +1 -1
  115. package/dist/modes/print-mode.d.ts.map +1 -1
  116. package/dist/modes/print-mode.js +1 -0
  117. package/dist/modes/print-mode.js.map +1 -1
  118. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  119. package/dist/modes/rpc/rpc-mode.js +4 -1
  120. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  121. package/dist/modes/rpc/rpc-types.d.ts +1 -0
  122. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  123. package/dist/modes/rpc/rpc-types.js.map +1 -1
  124. package/dist/utils/deprecation.d.ts +4 -0
  125. package/dist/utils/deprecation.d.ts.map +1 -0
  126. package/dist/utils/deprecation.js +13 -0
  127. package/dist/utils/deprecation.js.map +1 -0
  128. package/dist/utils/json.d.ts +3 -0
  129. package/dist/utils/json.d.ts.map +1 -0
  130. package/dist/utils/json.js +7 -0
  131. package/dist/utils/json.js.map +1 -0
  132. package/docs/custom-provider.md +13 -10
  133. package/docs/extensions.md +47 -17
  134. package/docs/models.md +25 -12
  135. package/docs/providers.md +15 -5
  136. package/docs/quickstart.md +1 -0
  137. package/docs/rpc.md +3 -2
  138. package/docs/sdk.md +6 -0
  139. package/docs/session-format.md +1 -1
  140. package/docs/sessions.md +8 -0
  141. package/docs/settings.md +4 -2
  142. package/docs/terminal-setup.md +2 -0
  143. package/docs/tui.md +12 -3
  144. package/docs/usage.md +10 -1
  145. package/examples/extensions/README.md +1 -0
  146. package/examples/extensions/custom-header.ts +1 -1
  147. package/examples/extensions/custom-provider-anthropic/index.ts +1 -1
  148. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  149. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  150. package/examples/extensions/custom-provider-gitlab-duo/index.ts +54 -3
  151. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  152. package/examples/extensions/doom-overlay/index.ts +1 -1
  153. package/examples/extensions/git-merge-and-resolve.ts +115 -0
  154. package/examples/extensions/handoff.ts +1 -1
  155. package/examples/extensions/input-transform-streaming.ts +39 -0
  156. package/examples/extensions/interactive-shell.ts +1 -1
  157. package/examples/extensions/overlay-qa-tests.ts +152 -81
  158. package/examples/extensions/qna.ts +1 -1
  159. package/examples/extensions/question.ts +1 -1
  160. package/examples/extensions/questionnaire.ts +1 -1
  161. package/examples/extensions/sandbox/package-lock.json +2 -2
  162. package/examples/extensions/sandbox/package.json +1 -1
  163. package/examples/extensions/snake.ts +1 -1
  164. package/examples/extensions/space-invaders.ts +1 -1
  165. package/examples/extensions/summarize.ts +1 -1
  166. package/examples/extensions/tic-tac-toe.ts +1 -1
  167. package/examples/extensions/todo.ts +1 -1
  168. package/examples/extensions/tools.ts +5 -0
  169. package/examples/extensions/with-deps/package-lock.json +2 -2
  170. package/examples/extensions/with-deps/package.json +1 -1
  171. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +1 -0
  172. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  173. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +15 -0
  174. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  175. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.d.ts +5 -2
  176. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
  177. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js +81 -18
  178. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
  179. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.d.ts.map +1 -1
  180. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js +1 -0
  181. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js.map +1 -1
  182. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts.map +1 -1
  183. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +1 -0
  184. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js.map +1 -1
  185. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.d.ts +1 -0
  186. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.d.ts.map +1 -1
  187. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +14 -1
  188. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js.map +1 -1
  189. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +22 -8
  190. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  191. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
  192. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  193. package/node_modules/@earendil-works/pi-ai/README.md +5 -3
  194. package/node_modules/@earendil-works/pi-ai/dist/cli.js +0 -0
  195. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
  196. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +1 -0
  197. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
  198. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +15 -0
  199. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  200. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +15 -0
  201. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
  202. package/node_modules/@earendil-works/pi-ai/dist/models.d.ts +2 -2
  203. package/node_modules/@earendil-works/pi-ai/dist/models.d.ts.map +1 -1
  204. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +1294 -412
  205. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  206. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +1278 -652
  207. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
  208. package/node_modules/@earendil-works/pi-ai/dist/models.js +9 -4
  209. package/node_modules/@earendil-works/pi-ai/dist/models.js.map +1 -1
  210. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.d.ts +1 -1
  211. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.d.ts.map +1 -1
  212. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +89 -21
  213. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  214. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  215. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +27 -14
  216. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js.map +1 -1
  217. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  218. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +5 -9
  219. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  220. package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
  221. package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.js +1 -1
  222. package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.js.map +1 -1
  223. package/node_modules/@earendil-works/pi-ai/dist/providers/google.d.ts.map +1 -1
  224. package/node_modules/@earendil-works/pi-ai/dist/providers/google.js +5 -3
  225. package/node_modules/@earendil-works/pi-ai/dist/providers/google.js.map +1 -1
  226. package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.d.ts.map +1 -1
  227. package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js +2 -3
  228. package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js.map +1 -1
  229. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.d.ts.map +1 -1
  230. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.js +2 -3
  231. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.js.map +1 -1
  232. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  233. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +118 -52
  234. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  235. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  236. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +27 -17
  237. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js.map +1 -1
  238. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.d.ts +1 -0
  239. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.d.ts.map +1 -1
  240. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.js +5 -1
  241. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.js.map +1 -1
  242. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  243. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +5 -9
  244. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js.map +1 -1
  245. package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  246. package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js +1 -0
  247. package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js.map +1 -1
  248. package/node_modules/@earendil-works/pi-ai/dist/providers/transform-messages.d.ts +7 -0
  249. package/node_modules/@earendil-works/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  250. package/node_modules/@earendil-works/pi-ai/dist/providers/transform-messages.js +8 -4
  251. package/node_modules/@earendil-works/pi-ai/dist/providers/transform-messages.js.map +1 -1
  252. package/node_modules/@earendil-works/pi-ai/dist/stream.d.ts.map +1 -1
  253. package/node_modules/@earendil-works/pi-ai/dist/stream.js +18 -4
  254. package/node_modules/@earendil-works/pi-ai/dist/stream.js.map +1 -1
  255. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +21 -5
  256. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
  257. package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
  258. package/node_modules/@earendil-works/pi-ai/dist/utils/abort-signals.d.ts +6 -0
  259. package/node_modules/@earendil-works/pi-ai/dist/utils/abort-signals.d.ts.map +1 -0
  260. package/node_modules/@earendil-works/pi-ai/dist/utils/abort-signals.js +34 -0
  261. package/node_modules/@earendil-works/pi-ai/dist/utils/abort-signals.js.map +1 -0
  262. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.d.ts +9 -7
  263. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.d.ts.map +1 -1
  264. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js +8 -7
  265. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js.map +1 -1
  266. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  267. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js +1 -1
  268. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  269. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.d.ts +1 -1
  270. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.d.ts.map +1 -1
  271. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.js +1 -1
  272. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.js.map +1 -1
  273. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.d.ts +10 -1
  274. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.d.ts.map +1 -1
  275. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js +179 -79
  276. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js.map +1 -1
  277. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  278. package/node_modules/@earendil-works/pi-tui/README.md +15 -3
  279. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  280. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +9 -53
  281. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  282. package/node_modules/@earendil-works/pi-tui/dist/components/input.d.ts.map +1 -1
  283. package/node_modules/@earendil-works/pi-tui/dist/components/input.js +6 -54
  284. package/node_modules/@earendil-works/pi-tui/dist/components/input.js.map +1 -1
  285. package/node_modules/@earendil-works/pi-tui/dist/index.d.ts +1 -1
  286. package/node_modules/@earendil-works/pi-tui/dist/index.d.ts.map +1 -1
  287. package/node_modules/@earendil-works/pi-tui/dist/index.js.map +1 -1
  288. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.d.ts +1 -1
  289. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.d.ts.map +1 -1
  290. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +34 -7
  291. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js.map +1 -1
  292. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +33 -10
  293. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
  294. package/node_modules/@earendil-works/pi-tui/dist/terminal.js +173 -39
  295. package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
  296. package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts +18 -3
  297. package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts.map +1 -1
  298. package/node_modules/@earendil-works/pi-tui/dist/tui.js +166 -22
  299. package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
  300. package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts +1 -0
  301. package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts.map +1 -1
  302. package/node_modules/@earendil-works/pi-tui/dist/utils.js +11 -3
  303. package/node_modules/@earendil-works/pi-tui/dist/utils.js.map +1 -1
  304. package/node_modules/@earendil-works/pi-tui/dist/word-navigation.d.ts +25 -0
  305. package/node_modules/@earendil-works/pi-tui/dist/word-navigation.d.ts.map +1 -0
  306. package/node_modules/@earendil-works/pi-tui/dist/word-navigation.js +96 -0
  307. package/node_modules/@earendil-works/pi-tui/dist/word-navigation.js.map +1 -0
  308. package/node_modules/@earendil-works/pi-tui/package.json +2 -2
  309. package/npm-shrinkwrap.json +56 -56
  310. package/package.json +5 -5
@@ -1,7 +1,9 @@
1
1
  import { randomBytes, randomUUID } from "crypto";
2
- import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, statSync, writeFileSync, } from "fs";
3
- import { readdir, readFile, stat } from "fs/promises";
2
+ import { appendFileSync, closeSync, createReadStream, existsSync, mkdirSync, openSync, readdirSync, readSync, statSync, writeFileSync, } from "fs";
3
+ import { readdir, stat } from "fs/promises";
4
4
  import { join, resolve } from "path";
5
+ import { createInterface } from "readline";
6
+ import { StringDecoder } from "string_decoder";
5
7
  import { getAgentDir as getDefaultAgentDir, getSessionsDir } from "../config.js";
6
8
  import { normalizePath, resolvePath } from "../utils/paths.js";
7
9
  // Fork change: inlined UUIDv7 (upstream uses the `uuid` npm package). Keeps this
@@ -29,6 +31,11 @@ export const CURRENT_SESSION_VERSION = 3;
29
31
  function createSessionId() {
30
32
  return uuidv7();
31
33
  }
34
+ export function assertValidSessionId(id) {
35
+ if (!/^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/.test(id)) {
36
+ throw new Error("Session id must be non-empty, contain only alphanumeric characters, '-', '_', and '.', and start and end with an alphanumeric character");
37
+ }
38
+ }
32
39
  /** Generate a unique short ID (8 hex chars, collision-checked) */
33
40
  function generateId(byId) {
34
41
  for (let i = 0; i < 100; i++) {
@@ -229,34 +236,65 @@ export function buildSessionContext(entries, leafId, byId) {
229
236
  * Compute the default session directory for a cwd.
230
237
  * Encodes cwd into a safe directory name under ~/.senpi/agent/sessions/.
231
238
  */
232
- export function getDefaultSessionDir(cwd, agentDir = getDefaultAgentDir()) {
239
+ function getDefaultSessionDirPath(cwd, agentDir = getDefaultAgentDir()) {
233
240
  const resolvedCwd = resolvePath(cwd);
234
241
  const resolvedAgentDir = resolvePath(agentDir);
235
242
  const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
236
- const sessionDir = join(resolvedAgentDir, "sessions", safePath);
243
+ return join(resolvedAgentDir, "sessions", safePath);
244
+ }
245
+ export function getDefaultSessionDir(cwd, agentDir = getDefaultAgentDir()) {
246
+ const sessionDir = getDefaultSessionDirPath(cwd, agentDir);
237
247
  if (!existsSync(sessionDir)) {
238
248
  mkdirSync(sessionDir, { recursive: true });
239
249
  }
240
250
  return sessionDir;
241
251
  }
252
+ const SESSION_READ_BUFFER_SIZE = 1024 * 1024;
253
+ function parseSessionEntryLine(line) {
254
+ if (!line.trim())
255
+ return null;
256
+ try {
257
+ return JSON.parse(line);
258
+ }
259
+ catch {
260
+ // Skip malformed lines
261
+ return null;
262
+ }
263
+ }
242
264
  /** Exported for testing */
243
265
  export function loadEntriesFromFile(filePath) {
244
266
  const resolvedFilePath = normalizePath(filePath);
245
267
  if (!existsSync(resolvedFilePath))
246
268
  return [];
247
- const content = readFileSync(resolvedFilePath, "utf8");
248
269
  const entries = [];
249
- const lines = content.trim().split("\n");
250
- for (const line of lines) {
251
- if (!line.trim())
252
- continue;
253
- try {
254
- const entry = JSON.parse(line);
255
- entries.push(entry);
256
- }
257
- catch {
258
- // Skip malformed lines
270
+ const fd = openSync(resolvedFilePath, "r");
271
+ try {
272
+ const decoder = new StringDecoder("utf8");
273
+ const buffer = Buffer.allocUnsafe(SESSION_READ_BUFFER_SIZE);
274
+ let pending = "";
275
+ while (true) {
276
+ const bytesRead = readSync(fd, buffer, 0, buffer.length, null);
277
+ if (bytesRead === 0)
278
+ break;
279
+ pending += decoder.write(buffer.subarray(0, bytesRead));
280
+ let lineStart = 0;
281
+ let newlineIndex = pending.indexOf("\n", lineStart);
282
+ while (newlineIndex !== -1) {
283
+ const entry = parseSessionEntryLine(pending.slice(lineStart, newlineIndex));
284
+ if (entry)
285
+ entries.push(entry);
286
+ lineStart = newlineIndex + 1;
287
+ newlineIndex = pending.indexOf("\n", lineStart);
288
+ }
289
+ pending = pending.slice(lineStart);
259
290
  }
291
+ pending += decoder.end();
292
+ const finalEntry = parseSessionEntryLine(pending);
293
+ if (finalEntry)
294
+ entries.push(finalEntry);
295
+ }
296
+ finally {
297
+ closeSync(fd);
260
298
  }
261
299
  // Validate session header
262
300
  if (entries.length === 0)
@@ -267,7 +305,7 @@ export function loadEntriesFromFile(filePath) {
267
305
  }
268
306
  return entries;
269
307
  }
270
- function isValidSessionFile(filePath) {
308
+ function readSessionHeader(filePath) {
271
309
  try {
272
310
  const fd = openSync(filePath, "r");
273
311
  const buffer = Buffer.alloc(512);
@@ -275,23 +313,36 @@ function isValidSessionFile(filePath) {
275
313
  closeSync(fd);
276
314
  const firstLine = buffer.toString("utf8", 0, bytesRead).split("\n")[0];
277
315
  if (!firstLine)
278
- return false;
316
+ return null;
279
317
  const header = JSON.parse(firstLine);
280
- return header.type === "session" && typeof header.id === "string";
318
+ if (header.type !== "session" || typeof header.id !== "string") {
319
+ return null;
320
+ }
321
+ return header;
281
322
  }
282
323
  catch {
283
- return false;
324
+ return null;
284
325
  }
285
326
  }
327
+ function getSessionHeaderCwd(header) {
328
+ const cwd = header.cwd;
329
+ return typeof cwd === "string" ? cwd : undefined;
330
+ }
331
+ function sessionCwdMatches(cwd, resolvedCwd) {
332
+ return cwd !== undefined && cwd !== "" && resolvePath(cwd) === resolvedCwd;
333
+ }
286
334
  /** Exported for testing */
287
- export function findMostRecentSession(sessionDir) {
335
+ export function findMostRecentSession(sessionDir, cwd) {
288
336
  const resolvedSessionDir = normalizePath(sessionDir);
337
+ const resolvedCwd = cwd ? resolvePath(cwd) : undefined;
289
338
  try {
290
339
  const files = readdirSync(resolvedSessionDir)
291
340
  .filter((f) => f.endsWith(".jsonl"))
292
341
  .map((f) => join(resolvedSessionDir, f))
293
- .filter(isValidSessionFile)
294
- .map((path) => ({ path, mtime: statSync(path).mtime }))
342
+ .map((path) => ({ path, header: readSessionHeader(path) }))
343
+ .filter((file) => file.header !== null &&
344
+ (!resolvedCwd || sessionCwdMatches(getSessionHeaderCwd(file.header), resolvedCwd)))
345
+ .map(({ path }) => ({ path, mtime: statSync(path).mtime }))
295
346
  .sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
296
347
  return files[0]?.path || null;
297
348
  }
@@ -312,73 +363,53 @@ function extractTextContent(message) {
312
363
  .map((block) => block.text)
313
364
  .join(" ");
314
365
  }
315
- function getLastActivityTime(entries) {
316
- let lastActivityTime;
317
- for (const entry of entries) {
318
- if (entry.type !== "message")
319
- continue;
320
- const message = entry.message;
321
- if (!isMessageWithContent(message))
322
- continue;
323
- if (message.role !== "user" && message.role !== "assistant")
324
- continue;
325
- const msgTimestamp = message.timestamp;
326
- if (typeof msgTimestamp === "number") {
327
- lastActivityTime = Math.max(lastActivityTime ?? 0, msgTimestamp);
328
- continue;
329
- }
330
- const entryTimestamp = entry.timestamp;
331
- if (typeof entryTimestamp === "string") {
332
- const t = new Date(entryTimestamp).getTime();
333
- if (!Number.isNaN(t)) {
334
- lastActivityTime = Math.max(lastActivityTime ?? 0, t);
335
- }
336
- }
337
- }
338
- return lastActivityTime;
339
- }
340
- function getSessionModifiedDate(entries, header, statsMtime) {
341
- const lastActivityTime = getLastActivityTime(entries);
342
- if (typeof lastActivityTime === "number" && lastActivityTime > 0) {
343
- return new Date(lastActivityTime);
366
+ function getMessageActivityTime(entry) {
367
+ const message = entry.message;
368
+ if (!isMessageWithContent(message))
369
+ return undefined;
370
+ if (message.role !== "user" && message.role !== "assistant")
371
+ return undefined;
372
+ const msgTimestamp = message.timestamp;
373
+ if (typeof msgTimestamp === "number") {
374
+ return msgTimestamp;
344
375
  }
345
- const headerTime = typeof header.timestamp === "string" ? new Date(header.timestamp).getTime() : NaN;
346
- return !Number.isNaN(headerTime) ? new Date(headerTime) : statsMtime;
376
+ const t = new Date(entry.timestamp).getTime();
377
+ return Number.isNaN(t) ? undefined : t;
347
378
  }
348
379
  async function buildSessionInfo(filePath) {
349
380
  try {
350
- const content = await readFile(filePath, "utf8");
351
- const entries = [];
352
- const lines = content.trim().split("\n");
353
- for (const line of lines) {
354
- if (!line.trim())
355
- continue;
356
- try {
357
- entries.push(JSON.parse(line));
358
- }
359
- catch {
360
- // Skip malformed lines
361
- }
362
- }
363
- if (entries.length === 0)
364
- return null;
365
- const header = entries[0];
366
- if (header.type !== "session")
367
- return null;
368
381
  const stats = await stat(filePath);
382
+ let header = null;
369
383
  let messageCount = 0;
370
384
  let firstMessage = "";
371
385
  const allMessages = [];
372
386
  let name;
373
- for (const entry of entries) {
387
+ let lastActivityTime;
388
+ const rl = createInterface({
389
+ input: createReadStream(filePath, { encoding: "utf8" }),
390
+ crlfDelay: Infinity,
391
+ });
392
+ for await (const line of rl) {
393
+ const entry = parseSessionEntryLine(line);
394
+ if (!entry)
395
+ continue;
396
+ if (!header) {
397
+ if (entry.type !== "session")
398
+ return null;
399
+ header = entry;
400
+ continue;
401
+ }
374
402
  // Extract session name (use latest, including explicit clears)
375
403
  if (entry.type === "session_info") {
376
- const infoEntry = entry;
377
- name = infoEntry.name?.trim() || undefined;
404
+ name = entry.name?.trim() || undefined;
378
405
  }
379
406
  if (entry.type !== "message")
380
407
  continue;
381
408
  messageCount++;
409
+ const activityTime = getMessageActivityTime(entry);
410
+ if (typeof activityTime === "number") {
411
+ lastActivityTime = Math.max(lastActivityTime ?? 0, activityTime);
412
+ }
382
413
  const message = entry.message;
383
414
  if (!isMessageWithContent(message))
384
415
  continue;
@@ -392,9 +423,16 @@ async function buildSessionInfo(filePath) {
392
423
  firstMessage = textContent;
393
424
  }
394
425
  }
426
+ if (!header)
427
+ return null;
395
428
  const cwd = typeof header.cwd === "string" ? header.cwd : "";
396
429
  const parentSessionPath = header.parentSession;
397
- const modified = getSessionModifiedDate(entries, header, stats.mtime);
430
+ const headerTime = typeof header.timestamp === "string" ? new Date(header.timestamp).getTime() : NaN;
431
+ const modified = typeof lastActivityTime === "number" && lastActivityTime > 0
432
+ ? new Date(lastActivityTime)
433
+ : !Number.isNaN(headerTime)
434
+ ? new Date(headerTime)
435
+ : stats.mtime;
398
436
  return {
399
437
  path: filePath,
400
438
  id: header.id,
@@ -494,7 +532,7 @@ export class SessionManager {
494
532
  labelsById = new Map();
495
533
  labelTimestampsById = new Map();
496
534
  leafId = null;
497
- constructor(cwd, sessionDir, sessionFile, persist) {
535
+ constructor(cwd, sessionDir, sessionFile, persist, newSessionOptions) {
498
536
  this.cwd = resolvePath(cwd);
499
537
  this.sessionDir = normalizePath(sessionDir);
500
538
  this.persist = persist;
@@ -505,7 +543,7 @@ export class SessionManager {
505
543
  this.setSessionFile(sessionFile);
506
544
  }
507
545
  else {
508
- this.newSession();
546
+ this.newSession(newSessionOptions);
509
547
  }
510
548
  }
511
549
  /** Switch to a different session file (used for resume and branching) */
@@ -538,6 +576,9 @@ export class SessionManager {
538
576
  }
539
577
  }
540
578
  newSession(options) {
579
+ if (options?.id !== undefined) {
580
+ assertValidSessionId(options.id);
581
+ }
541
582
  this.sessionId = options?.id ?? createSessionId();
542
583
  const timestamp = new Date().toISOString();
543
584
  const header = {
@@ -584,8 +625,15 @@ export class SessionManager {
584
625
  _rewriteFile() {
585
626
  if (!this.persist || !this.sessionFile)
586
627
  return;
587
- const content = `${this.fileEntries.map((e) => JSON.stringify(e)).join("\n")}\n`;
588
- writeFileSync(this.sessionFile, content);
628
+ const fd = openSync(this.sessionFile, "w");
629
+ try {
630
+ for (const entry of this.fileEntries) {
631
+ writeFileSync(fd, `${JSON.stringify(entry)}\n`);
632
+ }
633
+ }
634
+ finally {
635
+ closeSync(fd);
636
+ }
589
637
  }
590
638
  isPersisted() {
591
639
  return this.persist;
@@ -596,6 +644,9 @@ export class SessionManager {
596
644
  getSessionDir() {
597
645
  return this.sessionDir;
598
646
  }
647
+ usesDefaultSessionDir() {
648
+ return this.sessionDir === getDefaultSessionDirPath(this.cwd);
649
+ }
599
650
  getSessionId() {
600
651
  return this.sessionId;
601
652
  }
@@ -607,13 +658,24 @@ export class SessionManager {
607
658
  return;
608
659
  const hasAssistant = this.fileEntries.some((e) => e.type === "message" && e.message.role === "assistant");
609
660
  if (!hasAssistant) {
610
- // Mark as not flushed so when assistant arrives, all entries get written
611
- this.flushed = false;
661
+ if (this.flushed) {
662
+ appendFileSync(this.sessionFile, `${JSON.stringify(entry)}\n`);
663
+ }
664
+ else {
665
+ // Mark as not flushed so when assistant arrives, all entries get written
666
+ this.flushed = false;
667
+ }
612
668
  return;
613
669
  }
614
670
  if (!this.flushed) {
615
- for (const e of this.fileEntries) {
616
- appendFileSync(this.sessionFile, `${JSON.stringify(e)}\n`);
671
+ const fd = openSync(this.sessionFile, "wx");
672
+ try {
673
+ for (const e of this.fileEntries) {
674
+ writeFileSync(fd, `${JSON.stringify(e)}\n`);
675
+ }
676
+ }
677
+ finally {
678
+ closeSync(fd);
617
679
  }
618
680
  this.flushed = true;
619
681
  }
@@ -1023,9 +1085,9 @@ export class SessionManager {
1023
1085
  * @param cwd Working directory (stored in session header)
1024
1086
  * @param sessionDir Optional session directory. If omitted, uses default (~/.senpi/agent/sessions/<encoded-cwd>/).
1025
1087
  */
1026
- static create(cwd, sessionDir) {
1088
+ static create(cwd, sessionDir, options) {
1027
1089
  const dir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);
1028
- return new SessionManager(cwd, dir, undefined, true);
1090
+ return new SessionManager(cwd, dir, undefined, true, options);
1029
1091
  }
1030
1092
  /**
1031
1093
  * Open a specific session file.
@@ -1050,7 +1112,8 @@ export class SessionManager {
1050
1112
  */
1051
1113
  static continueRecent(cwd, sessionDir) {
1052
1114
  const dir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);
1053
- const mostRecent = findMostRecentSession(dir);
1115
+ const filterCwd = sessionDir !== undefined && dir !== getDefaultSessionDirPath(cwd);
1116
+ const mostRecent = findMostRecentSession(dir, filterCwd ? cwd : undefined);
1054
1117
  if (mostRecent) {
1055
1118
  return new SessionManager(cwd, dir, mostRecent, true);
1056
1119
  }
@@ -1067,7 +1130,7 @@ export class SessionManager {
1067
1130
  * @param targetCwd Target working directory (where the new session will be stored)
1068
1131
  * @param sessionDir Optional session directory. If omitted, uses default for targetCwd.
1069
1132
  */
1070
- static forkFrom(sourcePath, targetCwd, sessionDir) {
1133
+ static forkFrom(sourcePath, targetCwd, sessionDir, options) {
1071
1134
  const resolvedSourcePath = resolvePath(sourcePath);
1072
1135
  const resolvedTargetCwd = resolvePath(targetCwd);
1073
1136
  const sourceEntries = loadEntriesFromFile(resolvedSourcePath);
@@ -1083,7 +1146,10 @@ export class SessionManager {
1083
1146
  mkdirSync(dir, { recursive: true });
1084
1147
  }
1085
1148
  // Create new session file with new ID but forked content
1086
- const newSessionId = createSessionId();
1149
+ if (options?.id !== undefined) {
1150
+ assertValidSessionId(options.id);
1151
+ }
1152
+ const newSessionId = options?.id ?? createSessionId();
1087
1153
  const timestamp = new Date().toISOString();
1088
1154
  const fileTimestamp = timestamp.replace(/[:.]/g, "-");
1089
1155
  const newSessionFile = join(dir, `${fileTimestamp}_${newSessionId}.jsonl`);
@@ -1096,7 +1162,7 @@ export class SessionManager {
1096
1162
  cwd: resolvedTargetCwd,
1097
1163
  parentSession: resolvedSourcePath,
1098
1164
  };
1099
- appendFileSync(newSessionFile, `${JSON.stringify(newHeader)}\n`);
1165
+ writeFileSync(newSessionFile, `${JSON.stringify(newHeader)}\n`, { flag: "wx" });
1100
1166
  // Copy all non-header entries from source
1101
1167
  for (const entry of sourceEntries) {
1102
1168
  if (entry.type !== "session") {
@@ -1113,15 +1179,20 @@ export class SessionManager {
1113
1179
  */
1114
1180
  static async list(cwd, sessionDir, onProgress) {
1115
1181
  const dir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);
1116
- const sessions = await listSessionsFromDir(dir, onProgress);
1182
+ const filterCwd = sessionDir !== undefined && dir !== getDefaultSessionDirPath(cwd);
1183
+ const resolvedCwd = resolvePath(cwd);
1184
+ const sessions = (await listSessionsFromDir(dir, onProgress)).filter((session) => !filterCwd || sessionCwdMatches(session.cwd, resolvedCwd));
1117
1185
  sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
1118
1186
  return sessions;
1119
1187
  }
1120
- /**
1121
- * List all sessions across all project directories.
1122
- * @param onProgress Optional callback for progress updates (loaded, total)
1123
- */
1124
- static async listAll(onProgress) {
1188
+ static async listAll(sessionDirOrOnProgress, onProgress) {
1189
+ const customSessionDir = typeof sessionDirOrOnProgress === "string" ? normalizePath(sessionDirOrOnProgress) : undefined;
1190
+ const progress = typeof sessionDirOrOnProgress === "function" ? sessionDirOrOnProgress : onProgress;
1191
+ if (customSessionDir) {
1192
+ const sessions = await listSessionsFromDir(customSessionDir, progress);
1193
+ sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
1194
+ return sessions;
1195
+ }
1125
1196
  const sessionsDir = getSessionsDir();
1126
1197
  try {
1127
1198
  if (!existsSync(sessionsDir)) {
@@ -1148,7 +1219,7 @@ export class SessionManager {
1148
1219
  const allFiles = dirFiles.flat();
1149
1220
  const results = await buildSessionInfosWithConcurrency(allFiles, () => {
1150
1221
  loaded++;
1151
- onProgress?.(loaded, totalFiles);
1222
+ progress?.(loaded, totalFiles);
1152
1223
  });
1153
1224
  for (const info of results) {
1154
1225
  if (info) {