@otto-assistant/otto 0.1.2 → 0.7.15

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 (638) hide show
  1. package/bin.js +2 -0
  2. package/dist/agent-model.e2e.test.js +755 -0
  3. package/dist/ai-tool-to-genai.js +233 -0
  4. package/dist/ai-tool-to-genai.test.js +267 -0
  5. package/dist/ai-tool.js +6 -0
  6. package/dist/anthropic-account-identity.js +62 -0
  7. package/dist/anthropic-account-identity.test.js +38 -0
  8. package/dist/anthropic-auth-plugin.js +917 -0
  9. package/dist/anthropic-auth-state.js +303 -0
  10. package/dist/anthropic-auth-state.test.js +150 -0
  11. package/dist/bin.js +152 -0
  12. package/dist/btw-prefix-detection.js +17 -0
  13. package/dist/btw-prefix-detection.test.js +63 -0
  14. package/dist/channel-management.js +259 -0
  15. package/dist/cli-parsing.test.js +142 -0
  16. package/dist/cli-send-thread.e2e.test.js +353 -0
  17. package/dist/cli-telegram-options.test.js +99 -0
  18. package/dist/cli.js +4210 -568
  19. package/dist/commands/abort.js +65 -0
  20. package/dist/commands/action-buttons.js +245 -0
  21. package/dist/commands/add-dir.js +124 -0
  22. package/dist/commands/add-dir.test.js +126 -0
  23. package/dist/commands/add-project.js +113 -0
  24. package/dist/commands/agent.js +355 -0
  25. package/dist/commands/ask-question.js +320 -0
  26. package/dist/commands/ask-question.test.js +92 -0
  27. package/dist/commands/btw.js +121 -0
  28. package/dist/commands/cli-commands-group-a.test.js +728 -0
  29. package/dist/commands/cli-commands-group-b.test.js +695 -0
  30. package/dist/commands/compact.js +120 -0
  31. package/dist/commands/context-usage.js +140 -0
  32. package/dist/commands/create-new-project.js +130 -0
  33. package/dist/commands/diff.js +63 -0
  34. package/dist/commands/discord-commands-group-a.test.js +621 -0
  35. package/dist/commands/discord-commands-group-b.test.js +595 -0
  36. package/dist/commands/discord-commands-group-c.test.js +739 -0
  37. package/dist/commands/file-upload.js +275 -0
  38. package/dist/commands/fork-subagent.js +177 -0
  39. package/dist/commands/fork.js +262 -0
  40. package/dist/commands/gemini-apikey.js +70 -0
  41. package/dist/commands/login.js +887 -0
  42. package/dist/commands/mcp.js +239 -0
  43. package/dist/commands/memory-snapshot.js +24 -0
  44. package/dist/commands/mention-mode.js +44 -0
  45. package/dist/commands/merge-worktree.js +162 -0
  46. package/dist/commands/model-variant.js +366 -0
  47. package/dist/commands/model.js +794 -0
  48. package/dist/commands/new-worktree.js +465 -0
  49. package/dist/commands/paginated-select.js +57 -0
  50. package/dist/commands/permissions.js +274 -0
  51. package/dist/commands/queue.js +223 -0
  52. package/dist/commands/remove-project.js +115 -0
  53. package/dist/commands/restart-opencode-server.js +127 -0
  54. package/dist/commands/resume.js +149 -0
  55. package/dist/commands/run-command.js +79 -0
  56. package/dist/commands/screenshare.js +303 -0
  57. package/dist/commands/screenshare.test.js +20 -0
  58. package/dist/commands/session-id.js +78 -0
  59. package/dist/commands/session.js +176 -0
  60. package/dist/commands/share.js +80 -0
  61. package/dist/commands/tasks.js +205 -0
  62. package/dist/commands/thread-deletion-sync.js +50 -0
  63. package/dist/commands/types.js +2 -0
  64. package/dist/commands/undo-redo.js +305 -0
  65. package/dist/commands/unset-model.js +139 -0
  66. package/dist/commands/upgrade.js +48 -0
  67. package/dist/commands/user-command.js +155 -0
  68. package/dist/commands/verbosity.js +125 -0
  69. package/dist/commands/vscode.js +269 -0
  70. package/dist/commands/worktree-settings.js +43 -0
  71. package/dist/commands/worktrees.js +468 -0
  72. package/dist/condense-memory.js +33 -0
  73. package/dist/config.js +100 -255
  74. package/dist/context-awareness-plugin.js +340 -0
  75. package/dist/context-awareness-plugin.test.js +126 -0
  76. package/dist/critique-utils.js +95 -0
  77. package/dist/database.js +1355 -0
  78. package/dist/db.js +260 -0
  79. package/dist/db.test.js +138 -0
  80. package/dist/debounce-timeout.js +28 -0
  81. package/dist/debounced-process-flush.js +77 -0
  82. package/dist/discord-bot.js +1124 -0
  83. package/dist/discord-command-registration.js +567 -0
  84. package/dist/discord-urls.js +82 -0
  85. package/dist/discord-utils.js +616 -0
  86. package/dist/discord-utils.test.js +134 -0
  87. package/dist/errors.js +157 -0
  88. package/dist/escape-backticks.test.js +429 -0
  89. package/dist/event-stream-real-capture.e2e.test.js +533 -0
  90. package/dist/eventsource-parser.test.js +327 -0
  91. package/dist/exec-async.js +26 -0
  92. package/dist/external-opencode-sync.js +480 -0
  93. package/dist/format-tables.js +491 -0
  94. package/dist/format-tables.test.js +478 -0
  95. package/dist/forum-sync/config.js +79 -0
  96. package/dist/forum-sync/discord-operations.js +154 -0
  97. package/dist/forum-sync/index.js +5 -0
  98. package/dist/forum-sync/markdown.js +113 -0
  99. package/dist/forum-sync/sync-to-discord.js +417 -0
  100. package/dist/forum-sync/sync-to-files.js +190 -0
  101. package/dist/forum-sync/types.js +53 -0
  102. package/dist/forum-sync/watchers.js +307 -0
  103. package/dist/gateway-proxy-reconnect.e2e.test.js +394 -0
  104. package/dist/gateway-proxy.e2e.test.js +485 -0
  105. package/dist/genai-worker-wrapper.js +111 -0
  106. package/dist/genai-worker.js +311 -0
  107. package/dist/genai.js +232 -0
  108. package/dist/generated/browser.js +17 -0
  109. package/dist/generated/client.js +37 -0
  110. package/dist/generated/commonInputTypes.js +10 -0
  111. package/dist/generated/enums.js +58 -0
  112. package/dist/generated/internal/class.js +49 -0
  113. package/dist/generated/internal/prismaNamespace.js +254 -0
  114. package/dist/generated/internal/prismaNamespaceBrowser.js +224 -0
  115. package/dist/generated/models/bot_api_keys.js +1 -0
  116. package/dist/generated/models/bot_tokens.js +1 -0
  117. package/dist/generated/models/channel_agents.js +1 -0
  118. package/dist/generated/models/channel_directories.js +1 -0
  119. package/dist/generated/models/channel_mention_mode.js +1 -0
  120. package/dist/generated/models/channel_models.js +1 -0
  121. package/dist/generated/models/channel_verbosity.js +1 -0
  122. package/dist/generated/models/channel_worktrees.js +1 -0
  123. package/dist/generated/models/forum_sync_configs.js +1 -0
  124. package/dist/generated/models/global_models.js +1 -0
  125. package/dist/generated/models/ipc_requests.js +1 -0
  126. package/dist/generated/models/part_messages.js +1 -0
  127. package/dist/generated/models/scheduled_tasks.js +1 -0
  128. package/dist/generated/models/session_agents.js +1 -0
  129. package/dist/generated/models/session_events.js +1 -0
  130. package/dist/generated/models/session_models.js +1 -0
  131. package/dist/generated/models/session_start_sources.js +1 -0
  132. package/dist/generated/models/thread_sessions.js +1 -0
  133. package/dist/generated/models/thread_worktrees.js +1 -0
  134. package/dist/generated/models.js +1 -0
  135. package/dist/heap-monitor.js +122 -0
  136. package/dist/hrana-server.js +251 -0
  137. package/dist/hrana-server.test.js +370 -0
  138. package/dist/html-actions.js +123 -0
  139. package/dist/html-actions.test.js +70 -0
  140. package/dist/html-components.js +117 -0
  141. package/dist/html-components.test.js +34 -0
  142. package/dist/image-optimizer-plugin.js +153 -0
  143. package/dist/image-utils.js +112 -0
  144. package/dist/interaction-handler.js +420 -0
  145. package/dist/ipc-polling.js +327 -0
  146. package/dist/ipc-tools-plugin.js +193 -0
  147. package/dist/ipc-utils.js +18 -0
  148. package/dist/limit-heading-depth.js +25 -0
  149. package/dist/limit-heading-depth.test.js +105 -0
  150. package/dist/logger.js +171 -0
  151. package/dist/markdown.js +342 -0
  152. package/dist/markdown.test.js +264 -0
  153. package/dist/memory-overview-plugin.js +128 -0
  154. package/dist/message-finish-field.e2e.test.js +168 -0
  155. package/dist/message-formatting.js +415 -0
  156. package/dist/message-formatting.test.js +115 -0
  157. package/dist/message-preprocessing.js +359 -0
  158. package/dist/onboarding-tutorial.js +163 -0
  159. package/dist/onboarding-welcome.js +37 -0
  160. package/dist/openai-realtime.js +224 -0
  161. package/dist/opencode-command-detection.js +65 -0
  162. package/dist/opencode-command-detection.test.js +240 -0
  163. package/dist/opencode-command.js +131 -0
  164. package/dist/opencode-command.test.js +48 -0
  165. package/dist/opencode-interrupt-plugin.js +388 -0
  166. package/dist/opencode-interrupt-plugin.test.js +463 -0
  167. package/dist/opencode.js +1117 -0
  168. package/dist/otto/branding.js +22 -0
  169. package/dist/otto/index.js +21 -0
  170. package/dist/otto-digital-twin.e2e.test.js +161 -0
  171. package/dist/otto-opencode-plugin-loading.e2e.test.js +94 -0
  172. package/dist/otto-opencode-plugin.js +21 -0
  173. package/dist/otto-opencode-plugin.test.js +98 -0
  174. package/dist/parse-permission-rules.test.js +117 -0
  175. package/dist/patch-text-parser.js +97 -0
  176. package/dist/plugin-logger.js +68 -0
  177. package/dist/privacy-sanitizer.js +105 -0
  178. package/dist/queue-advanced-abort.e2e.test.js +293 -0
  179. package/dist/queue-advanced-action-buttons.e2e.test.js +206 -0
  180. package/dist/queue-advanced-e2e-setup.js +790 -0
  181. package/dist/queue-advanced-footer.e2e.test.js +481 -0
  182. package/dist/queue-advanced-model-switch.e2e.test.js +299 -0
  183. package/dist/queue-advanced-permissions-typing.e2e.test.js +179 -0
  184. package/dist/queue-advanced-question.e2e.test.js +261 -0
  185. package/dist/queue-advanced-typing-interrupt.e2e.test.js +114 -0
  186. package/dist/queue-advanced-typing.e2e.test.js +153 -0
  187. package/dist/queue-drain-after-interactive-ui.e2e.test.js +119 -0
  188. package/dist/queue-interrupt-drain.e2e.test.js +135 -0
  189. package/dist/queue-question-select-drain.e2e.test.js +256 -0
  190. package/dist/runtime-idle-sweeper.js +52 -0
  191. package/dist/runtime-lifecycle.e2e.test.js +514 -0
  192. package/dist/sentry.js +23 -0
  193. package/dist/session-handler/agent-utils.js +67 -0
  194. package/dist/session-handler/event-stream-state.js +475 -0
  195. package/dist/session-handler/event-stream-state.test.js +632 -0
  196. package/dist/session-handler/model-utils.js +147 -0
  197. package/dist/session-handler/opencode-session-event-log.js +94 -0
  198. package/dist/session-handler/thread-runtime-state.js +131 -0
  199. package/dist/session-handler/thread-session-runtime.js +3390 -0
  200. package/dist/session-handler.js +9 -0
  201. package/dist/session-search.js +100 -0
  202. package/dist/session-search.test.js +40 -0
  203. package/dist/session-title-rename.test.js +92 -0
  204. package/dist/skill-filter.js +31 -0
  205. package/dist/skill-filter.test.js +65 -0
  206. package/dist/startup-service.js +153 -0
  207. package/dist/startup-time.e2e.test.js +296 -0
  208. package/dist/store.js +19 -0
  209. package/dist/subagent-rate-limit-plugin.js +175 -0
  210. package/dist/system-message.js +702 -0
  211. package/dist/system-message.test.js +697 -0
  212. package/dist/task-runner.js +530 -0
  213. package/dist/task-schedule.js +213 -0
  214. package/dist/task-schedule.test.js +71 -0
  215. package/dist/test-utils.js +313 -0
  216. package/dist/thinking-utils.js +35 -0
  217. package/dist/thread-message-queue.e2e.test.js +1111 -0
  218. package/dist/tools.js +357 -0
  219. package/dist/undo-redo.e2e.test.js +161 -0
  220. package/dist/unnest-code-blocks.js +146 -0
  221. package/dist/unnest-code-blocks.test.js +673 -0
  222. package/dist/upgrade.js +156 -0
  223. package/dist/utils.js +172 -0
  224. package/dist/utils.test.js +130 -0
  225. package/dist/voice-attachment.js +34 -0
  226. package/dist/voice-handler.js +646 -0
  227. package/dist/voice-message.e2e.test.js +1021 -0
  228. package/dist/voice.js +456 -0
  229. package/dist/voice.test.js +235 -0
  230. package/dist/wait-session.js +171 -0
  231. package/dist/websockify.js +69 -0
  232. package/dist/worker-types.js +4 -0
  233. package/dist/worktree-lifecycle.e2e.test.js +311 -0
  234. package/dist/worktree-utils.js +3 -0
  235. package/dist/worktrees.js +991 -0
  236. package/dist/worktrees.test.js +415 -0
  237. package/dist/xml.js +92 -0
  238. package/dist/xml.test.js +32 -0
  239. package/package.json +90 -38
  240. package/schema.prisma +303 -0
  241. package/skills/batch/SKILL.md +87 -0
  242. package/skills/critique/SKILL.md +112 -0
  243. package/skills/egaki/SKILL.md +100 -0
  244. package/skills/errore/SKILL.md +647 -0
  245. package/skills/event-sourcing-state/SKILL.md +252 -0
  246. package/skills/goke/SKILL.md +38 -0
  247. package/skills/jitter/EDITOR.md +219 -0
  248. package/skills/jitter/EXPORT-INTERNALS.md +309 -0
  249. package/skills/jitter/SKILL.md +158 -0
  250. package/skills/jitter/jitter-clipboard.json +1042 -0
  251. package/skills/jitter/package.json +14 -0
  252. package/skills/jitter/tsconfig.json +15 -0
  253. package/skills/jitter/utils/actions.ts +212 -0
  254. package/skills/jitter/utils/export.ts +114 -0
  255. package/skills/jitter/utils/index.ts +141 -0
  256. package/skills/jitter/utils/snapshot.ts +154 -0
  257. package/skills/jitter/utils/traverse.ts +246 -0
  258. package/skills/jitter/utils/types.ts +279 -0
  259. package/skills/jitter/utils/wait.ts +133 -0
  260. package/skills/lintcn/SKILL.md +873 -0
  261. package/skills/manual-kimaki-upstream-adapt/SKILL.md +114 -0
  262. package/skills/new-skill/SKILL.md +237 -0
  263. package/skills/npm-package/SKILL.md +617 -0
  264. package/skills/opensrc/SKILL.md +78 -0
  265. package/skills/otto-publish/SKILL.md +61 -0
  266. package/skills/playwriter/SKILL.md +35 -0
  267. package/skills/profano/SKILL.md +16 -0
  268. package/skills/proxyman/SKILL.md +215 -0
  269. package/skills/security-review/SKILL.md +208 -0
  270. package/skills/sigillo/SKILL.md +101 -0
  271. package/skills/simplify/SKILL.md +58 -0
  272. package/skills/spiceflow/SKILL.md +28 -0
  273. package/skills/termcast/SKILL.md +945 -0
  274. package/skills/tuistory/SKILL.md +98 -0
  275. package/skills/usecomputer/SKILL.md +264 -0
  276. package/skills/x-articles/SKILL.md +554 -0
  277. package/skills/zele/SKILL.md +49 -0
  278. package/skills/zustand-centralized-state/SKILL.md +1004 -0
  279. package/src/agent-model.e2e.test.ts +979 -0
  280. package/src/ai-tool-to-genai.test.ts +296 -0
  281. package/src/ai-tool-to-genai.ts +283 -0
  282. package/src/ai-tool.ts +39 -0
  283. package/src/anthropic-account-identity.test.ts +52 -0
  284. package/src/anthropic-account-identity.ts +77 -0
  285. package/src/anthropic-auth-plugin.ts +1139 -0
  286. package/src/anthropic-auth-state.test.ts +187 -0
  287. package/src/anthropic-auth-state.ts +386 -0
  288. package/src/bin.ts +182 -0
  289. package/src/btw-prefix-detection.test.ts +73 -0
  290. package/src/btw-prefix-detection.ts +23 -0
  291. package/src/channel-management.ts +376 -0
  292. package/src/cli-parsing.test.ts +197 -0
  293. package/src/cli-send-thread.e2e.test.ts +463 -0
  294. package/src/cli-telegram-options.test.ts +114 -0
  295. package/src/cli.ts +5718 -580
  296. package/src/commands/abort.ts +89 -0
  297. package/src/commands/action-buttons.ts +364 -0
  298. package/src/commands/add-dir.test.ts +154 -0
  299. package/src/commands/add-dir.ts +175 -0
  300. package/src/commands/add-project.ts +149 -0
  301. package/src/commands/agent.ts +496 -0
  302. package/src/commands/ask-question.test.ts +111 -0
  303. package/src/commands/ask-question.ts +455 -0
  304. package/src/commands/btw.ts +184 -0
  305. package/src/commands/cli-commands-group-a.test.ts +837 -0
  306. package/src/commands/cli-commands-group-b.test.ts +800 -0
  307. package/src/commands/compact.ts +157 -0
  308. package/src/commands/context-usage.ts +199 -0
  309. package/src/commands/create-new-project.ts +190 -0
  310. package/src/commands/diff.ts +91 -0
  311. package/src/commands/discord-commands-group-a.test.ts +751 -0
  312. package/src/commands/discord-commands-group-b.test.ts +648 -0
  313. package/src/commands/discord-commands-group-c.test.ts +882 -0
  314. package/src/commands/file-upload.ts +389 -0
  315. package/src/commands/fork-subagent.ts +263 -0
  316. package/src/commands/fork.ts +386 -0
  317. package/src/commands/gemini-apikey.ts +104 -0
  318. package/src/commands/login.ts +1175 -0
  319. package/src/commands/mcp.ts +307 -0
  320. package/src/commands/memory-snapshot.ts +30 -0
  321. package/src/commands/mention-mode.ts +68 -0
  322. package/src/commands/merge-worktree.ts +226 -0
  323. package/src/commands/model-variant.ts +485 -0
  324. package/src/commands/model.ts +1078 -0
  325. package/src/commands/new-worktree.ts +645 -0
  326. package/src/commands/paginated-select.ts +81 -0
  327. package/src/commands/permissions.ts +397 -0
  328. package/src/commands/queue.ts +293 -0
  329. package/src/commands/remove-project.ts +155 -0
  330. package/src/commands/restart-opencode-server.ts +162 -0
  331. package/src/commands/resume.ts +230 -0
  332. package/src/commands/run-command.ts +123 -0
  333. package/src/commands/screenshare.test.ts +30 -0
  334. package/src/commands/screenshare.ts +366 -0
  335. package/src/commands/session-id.ts +109 -0
  336. package/src/commands/session.ts +227 -0
  337. package/src/commands/share.ts +106 -0
  338. package/src/commands/tasks.ts +293 -0
  339. package/src/commands/thread-deletion-sync.ts +80 -0
  340. package/src/commands/types.ts +25 -0
  341. package/src/commands/undo-redo.ts +386 -0
  342. package/src/commands/unset-model.ts +174 -0
  343. package/src/commands/upgrade.ts +59 -0
  344. package/src/commands/user-command.ts +198 -0
  345. package/src/commands/verbosity.ts +173 -0
  346. package/src/commands/vscode.ts +342 -0
  347. package/src/commands/worktree-settings.ts +70 -0
  348. package/src/commands/worktrees.ts +645 -0
  349. package/src/condense-memory.ts +36 -0
  350. package/src/config.ts +103 -339
  351. package/src/context-awareness-plugin.test.ts +144 -0
  352. package/src/context-awareness-plugin.ts +469 -0
  353. package/src/critique-utils.ts +139 -0
  354. package/src/database.ts +1949 -0
  355. package/src/db.test.ts +162 -0
  356. package/src/db.ts +295 -0
  357. package/src/debounce-timeout.ts +43 -0
  358. package/src/debounced-process-flush.ts +104 -0
  359. package/src/discord-bot.ts +1505 -0
  360. package/src/discord-command-registration.ts +752 -0
  361. package/src/discord-urls.ts +89 -0
  362. package/src/discord-utils.test.ts +153 -0
  363. package/src/discord-utils.ts +846 -0
  364. package/src/errors.ts +201 -0
  365. package/src/escape-backticks.test.ts +469 -0
  366. package/src/event-stream-real-capture.e2e.test.ts +692 -0
  367. package/src/eventsource-parser.test.ts +351 -0
  368. package/src/exec-async.ts +35 -0
  369. package/src/external-opencode-sync.ts +685 -0
  370. package/src/format-tables.test.ts +515 -0
  371. package/src/format-tables.ts +718 -0
  372. package/src/forum-sync/config.ts +92 -0
  373. package/src/forum-sync/discord-operations.ts +241 -0
  374. package/src/forum-sync/index.ts +9 -0
  375. package/src/forum-sync/markdown.ts +172 -0
  376. package/src/forum-sync/sync-to-discord.ts +595 -0
  377. package/src/forum-sync/sync-to-files.ts +294 -0
  378. package/src/forum-sync/types.ts +175 -0
  379. package/src/forum-sync/watchers.ts +454 -0
  380. package/src/gateway-proxy-reconnect.e2e.test.ts +523 -0
  381. package/src/gateway-proxy.e2e.test.ts +644 -0
  382. package/src/genai-worker-wrapper.ts +164 -0
  383. package/src/genai-worker.ts +386 -0
  384. package/src/genai.ts +321 -0
  385. package/src/generated/browser.ts +114 -0
  386. package/src/generated/client.ts +138 -0
  387. package/src/generated/commonInputTypes.ts +770 -0
  388. package/src/generated/enums.ts +98 -0
  389. package/src/generated/internal/class.ts +384 -0
  390. package/src/generated/internal/prismaNamespace.ts +2394 -0
  391. package/src/generated/internal/prismaNamespaceBrowser.ts +327 -0
  392. package/src/generated/models/bot_api_keys.ts +1288 -0
  393. package/src/generated/models/bot_tokens.ts +1700 -0
  394. package/src/generated/models/channel_agents.ts +1256 -0
  395. package/src/generated/models/channel_directories.ts +1859 -0
  396. package/src/generated/models/channel_mention_mode.ts +1300 -0
  397. package/src/generated/models/channel_models.ts +1288 -0
  398. package/src/generated/models/channel_verbosity.ts +1228 -0
  399. package/src/generated/models/channel_worktrees.ts +1300 -0
  400. package/src/generated/models/forum_sync_configs.ts +1452 -0
  401. package/src/generated/models/global_models.ts +1288 -0
  402. package/src/generated/models/ipc_requests.ts +1485 -0
  403. package/src/generated/models/part_messages.ts +1302 -0
  404. package/src/generated/models/scheduled_tasks.ts +2320 -0
  405. package/src/generated/models/session_agents.ts +1086 -0
  406. package/src/generated/models/session_events.ts +1439 -0
  407. package/src/generated/models/session_models.ts +1114 -0
  408. package/src/generated/models/session_start_sources.ts +1408 -0
  409. package/src/generated/models/thread_sessions.ts +1781 -0
  410. package/src/generated/models/thread_worktrees.ts +1356 -0
  411. package/src/generated/models.ts +30 -0
  412. package/src/heap-monitor.ts +152 -0
  413. package/src/hrana-server.test.ts +434 -0
  414. package/src/hrana-server.ts +299 -0
  415. package/src/html-actions.test.ts +87 -0
  416. package/src/html-actions.ts +174 -0
  417. package/src/html-components.test.ts +38 -0
  418. package/src/html-components.ts +181 -0
  419. package/src/image-optimizer-plugin.ts +194 -0
  420. package/src/image-utils.ts +149 -0
  421. package/src/interaction-handler.ts +610 -0
  422. package/src/ipc-polling.ts +427 -0
  423. package/src/ipc-tools-plugin.ts +236 -0
  424. package/src/ipc-utils.ts +29 -0
  425. package/src/limit-heading-depth.test.ts +116 -0
  426. package/src/limit-heading-depth.ts +26 -0
  427. package/src/logger.ts +215 -0
  428. package/src/markdown.test.ts +315 -0
  429. package/src/markdown.ts +410 -0
  430. package/src/memory-overview-plugin.ts +163 -0
  431. package/src/message-finish-field.e2e.test.ts +195 -0
  432. package/src/message-formatting.test.ts +126 -0
  433. package/src/message-formatting.ts +535 -0
  434. package/src/message-preprocessing.ts +488 -0
  435. package/src/onboarding-tutorial.ts +167 -0
  436. package/src/onboarding-welcome.ts +49 -0
  437. package/src/openai-realtime.ts +358 -0
  438. package/src/opencode-command-detection.test.ts +307 -0
  439. package/src/opencode-command-detection.ts +76 -0
  440. package/src/opencode-command.test.ts +70 -0
  441. package/src/opencode-command.ts +191 -0
  442. package/src/opencode-interrupt-plugin.test.ts +682 -0
  443. package/src/opencode-interrupt-plugin.ts +507 -0
  444. package/src/opencode.ts +1453 -0
  445. package/src/otto/branding.ts +23 -0
  446. package/src/otto/index.ts +22 -0
  447. package/src/otto-digital-twin.e2e.test.ts +199 -0
  448. package/src/otto-opencode-plugin-loading.e2e.test.ts +117 -0
  449. package/src/otto-opencode-plugin.test.ts +108 -0
  450. package/src/otto-opencode-plugin.ts +22 -0
  451. package/src/parse-permission-rules.test.ts +127 -0
  452. package/src/patch-text-parser.ts +107 -0
  453. package/src/plugin-logger.ts +84 -0
  454. package/src/privacy-sanitizer.ts +142 -0
  455. package/src/queue-advanced-abort.e2e.test.ts +382 -0
  456. package/src/queue-advanced-action-buttons.e2e.test.ts +268 -0
  457. package/src/queue-advanced-e2e-setup.ts +877 -0
  458. package/src/queue-advanced-footer.e2e.test.ts +591 -0
  459. package/src/queue-advanced-model-switch.e2e.test.ts +383 -0
  460. package/src/queue-advanced-permissions-typing.e2e.test.ts +246 -0
  461. package/src/queue-advanced-question.e2e.test.ts +316 -0
  462. package/src/queue-advanced-typing-interrupt.e2e.test.ts +146 -0
  463. package/src/queue-advanced-typing.e2e.test.ts +199 -0
  464. package/src/queue-drain-after-interactive-ui.e2e.test.ts +151 -0
  465. package/src/queue-interrupt-drain.e2e.test.ts +166 -0
  466. package/src/queue-question-select-drain.e2e.test.ts +327 -0
  467. package/src/runtime-idle-sweeper.ts +76 -0
  468. package/src/runtime-lifecycle.e2e.test.ts +651 -0
  469. package/src/schema.sql +174 -0
  470. package/src/sentry.ts +26 -0
  471. package/src/session-handler/agent-utils.ts +99 -0
  472. package/src/session-handler/event-stream-fixtures/real-session-action-buttons.jsonl +45 -0
  473. package/src/session-handler/event-stream-fixtures/real-session-footer-suppressed-on-pre-idle-interrupt.jsonl +40 -0
  474. package/src/session-handler/event-stream-fixtures/real-session-permission-external-file.jsonl +23 -0
  475. package/src/session-handler/event-stream-fixtures/real-session-task-normal.jsonl +22 -0
  476. package/src/session-handler/event-stream-fixtures/real-session-task-three-parallel-sleeps.jsonl +277 -0
  477. package/src/session-handler/event-stream-fixtures/real-session-task-user-interruption.jsonl +46 -0
  478. package/src/session-handler/event-stream-fixtures/session-abort-after-idle-race.jsonl +21 -0
  479. package/src/session-handler/event-stream-fixtures/session-concurrent-messages-serialized.jsonl +56 -0
  480. package/src/session-handler/event-stream-fixtures/session-explicit-abort.jsonl +44 -0
  481. package/src/session-handler/event-stream-fixtures/session-normal-completion.jsonl +29 -0
  482. package/src/session-handler/event-stream-fixtures/session-tool-call-noisy-stream.jsonl +29 -0
  483. package/src/session-handler/event-stream-fixtures/session-two-completions-same-session.jsonl +50 -0
  484. package/src/session-handler/event-stream-fixtures/session-user-interruption.jsonl +59 -0
  485. package/src/session-handler/event-stream-fixtures/session-voice-queued-followup.jsonl +52 -0
  486. package/src/session-handler/event-stream-state.test.ts +717 -0
  487. package/src/session-handler/event-stream-state.ts +706 -0
  488. package/src/session-handler/model-utils.ts +217 -0
  489. package/src/session-handler/opencode-session-event-log.ts +130 -0
  490. package/src/session-handler/thread-runtime-state.ts +247 -0
  491. package/src/session-handler/thread-session-runtime.ts +4440 -0
  492. package/src/session-handler.ts +15 -0
  493. package/src/session-search.test.ts +50 -0
  494. package/src/session-search.ts +148 -0
  495. package/src/session-title-rename.test.ts +130 -0
  496. package/src/skill-filter.test.ts +83 -0
  497. package/src/skill-filter.ts +42 -0
  498. package/src/startup-service.ts +200 -0
  499. package/src/startup-time.e2e.test.ts +373 -0
  500. package/src/store.ts +139 -0
  501. package/src/subagent-rate-limit-plugin.ts +218 -0
  502. package/src/system-message.test.ts +710 -0
  503. package/src/system-message.ts +814 -0
  504. package/src/task-runner.ts +725 -0
  505. package/src/task-schedule.test.ts +84 -0
  506. package/src/task-schedule.ts +317 -0
  507. package/src/test-utils.ts +451 -0
  508. package/src/thinking-utils.ts +61 -0
  509. package/src/thread-message-queue.e2e.test.ts +1350 -0
  510. package/src/tools.ts +430 -0
  511. package/src/undici.d.ts +12 -0
  512. package/src/undo-redo.e2e.test.ts +209 -0
  513. package/src/unnest-code-blocks.test.ts +713 -0
  514. package/src/unnest-code-blocks.ts +185 -0
  515. package/src/upgrade.ts +185 -0
  516. package/src/utils.test.ts +155 -0
  517. package/src/utils.ts +265 -0
  518. package/src/voice-attachment.ts +51 -0
  519. package/src/voice-handler.ts +908 -0
  520. package/src/voice-message.e2e.test.ts +1255 -0
  521. package/src/voice.test.ts +281 -0
  522. package/src/voice.ts +638 -0
  523. package/src/wait-session.ts +273 -0
  524. package/src/websockify.ts +101 -0
  525. package/src/worker-types.ts +64 -0
  526. package/src/worktree-lifecycle.e2e.test.ts +396 -0
  527. package/src/worktree-utils.ts +4 -0
  528. package/src/worktrees.test.ts +489 -0
  529. package/src/worktrees.ts +1370 -0
  530. package/src/xml.test.ts +38 -0
  531. package/src/xml.ts +121 -0
  532. package/README.md +0 -142
  533. package/dist/cli.d.ts +0 -3
  534. package/dist/cli.d.ts.map +0 -1
  535. package/dist/cli.js.map +0 -1
  536. package/dist/config.d.ts +0 -39
  537. package/dist/config.d.ts.map +0 -1
  538. package/dist/config.js.map +0 -1
  539. package/dist/config.test.d.ts +0 -2
  540. package/dist/config.test.d.ts.map +0 -1
  541. package/dist/config.test.js +0 -202
  542. package/dist/config.test.js.map +0 -1
  543. package/dist/detect.d.ts +0 -9
  544. package/dist/detect.d.ts.map +0 -1
  545. package/dist/detect.js +0 -40
  546. package/dist/detect.js.map +0 -1
  547. package/dist/detect.test.d.ts +0 -2
  548. package/dist/detect.test.d.ts.map +0 -1
  549. package/dist/detect.test.js +0 -26
  550. package/dist/detect.test.js.map +0 -1
  551. package/dist/docker.d.ts +0 -7
  552. package/dist/docker.d.ts.map +0 -1
  553. package/dist/docker.js +0 -17
  554. package/dist/docker.js.map +0 -1
  555. package/dist/docker.test.d.ts +0 -2
  556. package/dist/docker.test.d.ts.map +0 -1
  557. package/dist/docker.test.js +0 -12
  558. package/dist/docker.test.js.map +0 -1
  559. package/dist/health.d.ts +0 -31
  560. package/dist/health.d.ts.map +0 -1
  561. package/dist/health.js +0 -117
  562. package/dist/health.js.map +0 -1
  563. package/dist/health.test.d.ts +0 -2
  564. package/dist/health.test.d.ts.map +0 -1
  565. package/dist/health.test.js +0 -52
  566. package/dist/health.test.js.map +0 -1
  567. package/dist/index.d.ts +0 -20
  568. package/dist/index.d.ts.map +0 -1
  569. package/dist/index.js +0 -15
  570. package/dist/index.js.map +0 -1
  571. package/dist/index.test.d.ts +0 -2
  572. package/dist/index.test.d.ts.map +0 -1
  573. package/dist/index.test.js +0 -8
  574. package/dist/index.test.js.map +0 -1
  575. package/dist/installer.d.ts +0 -10
  576. package/dist/installer.d.ts.map +0 -1
  577. package/dist/installer.js +0 -50
  578. package/dist/installer.js.map +0 -1
  579. package/dist/installer.test.d.ts +0 -2
  580. package/dist/installer.test.d.ts.map +0 -1
  581. package/dist/installer.test.js +0 -43
  582. package/dist/installer.test.js.map +0 -1
  583. package/dist/lifecycle.d.ts +0 -10
  584. package/dist/lifecycle.d.ts.map +0 -1
  585. package/dist/lifecycle.js +0 -45
  586. package/dist/lifecycle.js.map +0 -1
  587. package/dist/lifecycle.test.d.ts +0 -2
  588. package/dist/lifecycle.test.d.ts.map +0 -1
  589. package/dist/lifecycle.test.js +0 -20
  590. package/dist/lifecycle.test.js.map +0 -1
  591. package/dist/manifest.d.ts +0 -18
  592. package/dist/manifest.d.ts.map +0 -1
  593. package/dist/manifest.js +0 -30
  594. package/dist/manifest.js.map +0 -1
  595. package/dist/skills-baseline.d.ts +0 -7
  596. package/dist/skills-baseline.d.ts.map +0 -1
  597. package/dist/skills-baseline.js +0 -9
  598. package/dist/skills-baseline.js.map +0 -1
  599. package/dist/skills.d.ts +0 -110
  600. package/dist/skills.d.ts.map +0 -1
  601. package/dist/skills.js +0 -429
  602. package/dist/skills.js.map +0 -1
  603. package/dist/skills.test.d.ts +0 -2
  604. package/dist/skills.test.d.ts.map +0 -1
  605. package/dist/skills.test.js +0 -416
  606. package/dist/skills.test.js.map +0 -1
  607. package/dist/sync.d.ts +0 -10
  608. package/dist/sync.d.ts.map +0 -1
  609. package/dist/sync.js +0 -39
  610. package/dist/sync.js.map +0 -1
  611. package/dist/tenant.d.ts +0 -13
  612. package/dist/tenant.d.ts.map +0 -1
  613. package/dist/tenant.js +0 -105
  614. package/dist/tenant.js.map +0 -1
  615. package/dist/tenant.test.d.ts +0 -2
  616. package/dist/tenant.test.d.ts.map +0 -1
  617. package/dist/tenant.test.js +0 -37
  618. package/dist/tenant.test.js.map +0 -1
  619. package/src/config.test.ts +0 -237
  620. package/src/detect.test.ts +0 -29
  621. package/src/detect.ts +0 -52
  622. package/src/docker.test.ts +0 -12
  623. package/src/docker.ts +0 -23
  624. package/src/health.test.ts +0 -61
  625. package/src/health.ts +0 -158
  626. package/src/index.test.ts +0 -8
  627. package/src/index.ts +0 -62
  628. package/src/installer.test.ts +0 -52
  629. package/src/installer.ts +0 -62
  630. package/src/lifecycle.test.ts +0 -23
  631. package/src/lifecycle.ts +0 -49
  632. package/src/manifest.ts +0 -42
  633. package/src/skills-baseline.ts +0 -14
  634. package/src/skills.test.ts +0 -503
  635. package/src/skills.ts +0 -512
  636. package/src/sync.ts +0 -53
  637. package/src/tenant.test.ts +0 -49
  638. package/src/tenant.ts +0 -120
@@ -0,0 +1,706 @@
1
+ // Pure event-stream derivation functions for session lifecycle state.
2
+ // These functions derive lifecycle decisions from an event buffer array.
3
+ // Zero imports from thread-session-runtime.ts, store.ts, or state.ts.
4
+ // Only types from @opencode-ai/sdk/v2 and the getOpencodeEventSessionId helper.
5
+
6
+ import type {
7
+ Event as OpenCodeEvent,
8
+ Message as OpenCodeMessage,
9
+ Part,
10
+ } from '@opencode-ai/sdk/v2'
11
+ import { getOpencodeEventSessionId } from './opencode-session-event-log.js'
12
+
13
+ type QueueQuestionHandoffStartedEvent = {
14
+ type: 'queue.question-handoff-started'
15
+ properties: {
16
+ sessionID: string
17
+ }
18
+ }
19
+
20
+ export type EventBufferEvent = OpenCodeEvent | QueueQuestionHandoffStartedEvent
21
+
22
+ export type EventBufferEntry = {
23
+ event: EventBufferEvent
24
+ timestamp: number
25
+ eventIndex?: number
26
+ }
27
+
28
+ export function getEventBufferSessionId(event: EventBufferEvent): string | undefined {
29
+ if (event.type === 'queue.question-handoff-started') {
30
+ return event.properties.sessionID
31
+ }
32
+ return getOpencodeEventSessionId(event)
33
+ }
34
+
35
+ type AssistantMessage = Extract<OpenCodeMessage, { role: 'assistant' }>
36
+ type UserMessage = Extract<OpenCodeMessage, { role: 'user' }>
37
+
38
+ function getTaskChildSessionId({
39
+ part,
40
+ }: {
41
+ part: Extract<Part, { type: 'tool' }>
42
+ }): string | undefined {
43
+ // Event-shape reference:
44
+ // - cli/src/session-handler/event-stream-fixtures/real-session-task-three-parallel-sleeps.jsonl
45
+ // - In real task events, state.metadata.sessionId appears on running/completed
46
+ // tool updates and is the canonical child-session identifier.
47
+ // We intentionally do not parse state.output because it is user-facing text
48
+ // and can change format across providers/versions.
49
+ const metadataValue = (part.state as { metadata?: unknown }).metadata
50
+ const metadataSessionId =
51
+ metadataValue && typeof metadataValue === 'object'
52
+ ? (metadataValue as { sessionId?: unknown }).sessionId
53
+ : undefined
54
+ if (typeof metadataSessionId === 'string' && metadataSessionId.length > 0) {
55
+ return metadataSessionId
56
+ }
57
+ return undefined
58
+ }
59
+
60
+ function getTaskCandidateFromEvent({
61
+ event,
62
+ mainSessionId,
63
+ }: {
64
+ event: EventBufferEvent
65
+ mainSessionId: string
66
+ }): {
67
+ assistantMessageId: string
68
+ childSessionId: string
69
+ subagentType?: string
70
+ description?: string
71
+ } | undefined {
72
+ if (event.type !== 'message.part.updated') {
73
+ return undefined
74
+ }
75
+
76
+ const part = event.properties.part
77
+ if (part.sessionID !== mainSessionId) {
78
+ return undefined
79
+ }
80
+ if (part.type !== 'tool' || part.tool !== 'task' || part.state.status === 'pending') {
81
+ return undefined
82
+ }
83
+
84
+ const childSessionId = getTaskChildSessionId({ part })
85
+ if (!childSessionId) {
86
+ return undefined
87
+ }
88
+
89
+ const subagentType = part.state.input?.subagent_type
90
+ const description = part.state.input?.description
91
+ return {
92
+ assistantMessageId: part.messageID,
93
+ childSessionId,
94
+ subagentType: typeof subagentType === 'string' ? subagentType : undefined,
95
+ description: typeof description === 'string' ? description : undefined,
96
+ }
97
+ }
98
+
99
+ export type DerivedSubagentSession = {
100
+ childSessionId: string
101
+ subagentType?: string
102
+ description?: string
103
+ timestamp: number
104
+ }
105
+
106
+ // Scans backward for most recent session-scoped lifecycle event.
107
+ // Returns true if the latest lifecycle event for sessionId is session.status busy.
108
+ export function isSessionBusy({
109
+ events,
110
+ sessionId,
111
+ upToIndex,
112
+ }: {
113
+ events: EventBufferEntry[]
114
+ sessionId: string
115
+ upToIndex?: number
116
+ }): boolean {
117
+ const end = upToIndex ?? events.length - 1
118
+ for (let i = end; i >= 0; i--) {
119
+ const entry = events[i]
120
+ if (!entry) {
121
+ continue
122
+ }
123
+ const e = entry.event
124
+ const eid = getEventBufferSessionId(e)
125
+ if (eid !== sessionId) {
126
+ continue
127
+ }
128
+ if (e.type === 'session.idle') {
129
+ return false
130
+ }
131
+ if (e.type === 'session.status') {
132
+ return e.properties.status.type === 'busy'
133
+ }
134
+ }
135
+ return false
136
+ }
137
+
138
+ export function didQuestionQueueHandoffSinceLatestQuestionAsked({
139
+ events,
140
+ sessionId,
141
+ upToIndex,
142
+ }: {
143
+ events: EventBufferEntry[]
144
+ sessionId: string
145
+ upToIndex?: number
146
+ }): boolean {
147
+ const end = upToIndex ?? events.length - 1
148
+ for (let i = end; i >= 0; i--) {
149
+ const entry = events[i]
150
+ if (!entry) {
151
+ continue
152
+ }
153
+ const event = entry.event
154
+ const eventSessionId = getEventBufferSessionId(event)
155
+ if (eventSessionId !== sessionId) {
156
+ continue
157
+ }
158
+ if (event.type === 'queue.question-handoff-started') {
159
+ return true
160
+ }
161
+ if (event.type === 'question.asked') {
162
+ return false
163
+ }
164
+ }
165
+ return false
166
+ }
167
+
168
+ export function isAssistantMessageNaturalCompletion({
169
+ message,
170
+ }: {
171
+ message: AssistantMessage
172
+ }): boolean {
173
+ if (typeof message.time.completed !== 'number') {
174
+ return false
175
+ }
176
+ if (message.error) {
177
+ return false
178
+ }
179
+ // finish="tool-calls" means the model's last step was tool execution.
180
+ // Mid-turn tool-call steps don't get footers — the footer comes from the
181
+ // final text response (finish="stop") that follows. If the turn ends with
182
+ // only tool-calls and no text follow-up, no footer is emitted. This is
183
+ // acceptable since models almost always follow up with text after tools.
184
+ return message.finish !== 'tool-calls'
185
+ }
186
+
187
+ export function hasAssistantMessageCompletedBefore({
188
+ events,
189
+ sessionId,
190
+ messageId,
191
+ upToIndex,
192
+ }: {
193
+ events: EventBufferEntry[]
194
+ sessionId: string
195
+ messageId: string
196
+ upToIndex?: number
197
+ }): boolean {
198
+ const end = upToIndex ?? events.length - 1
199
+ for (let i = end; i >= 0; i--) {
200
+ const entry = events[i]
201
+ if (!entry) {
202
+ continue
203
+ }
204
+ const event = entry.event
205
+ if (event.type !== 'message.updated') {
206
+ continue
207
+ }
208
+ const info = event.properties.info
209
+ if (info.sessionID !== sessionId || info.role !== 'assistant' || info.id !== messageId) {
210
+ continue
211
+ }
212
+ return typeof info.time.completed === 'number'
213
+ }
214
+ return false
215
+ }
216
+
217
+ export function getLatestUserMessage({
218
+ events,
219
+ sessionId,
220
+ upToIndex,
221
+ }: {
222
+ events: EventBufferEntry[]
223
+ sessionId: string
224
+ upToIndex?: number
225
+ }): UserMessage | undefined {
226
+ const end = upToIndex ?? events.length - 1
227
+ let latestUserMessage: UserMessage | undefined
228
+ for (let i = end; i >= 0; i--) {
229
+ const entry = events[i]
230
+ if (!entry) {
231
+ continue
232
+ }
233
+ const event = entry.event
234
+ if (event.type !== 'message.updated') {
235
+ continue
236
+ }
237
+ const info = event.properties.info
238
+ if (info.sessionID !== sessionId || info.role !== 'user') {
239
+ continue
240
+ }
241
+ if (!latestUserMessage) {
242
+ latestUserMessage = info
243
+ continue
244
+ }
245
+ if (info.time.created > latestUserMessage.time.created) {
246
+ latestUserMessage = info
247
+ }
248
+ }
249
+ return latestUserMessage
250
+ }
251
+
252
+ export function getCurrentTurnStartTime({
253
+ events,
254
+ sessionId,
255
+ upToIndex,
256
+ }: {
257
+ events: EventBufferEntry[]
258
+ sessionId: string
259
+ upToIndex?: number
260
+ }): number | undefined {
261
+ const latestUserMessage = getLatestUserMessage({
262
+ events,
263
+ sessionId,
264
+ upToIndex,
265
+ })
266
+ return latestUserMessage?.time.created
267
+ }
268
+
269
+ // Token total helper — sum of input + output + reasoning + cache.read + cache.write
270
+ function getTokenTotal(tokens: {
271
+ input: number
272
+ output: number
273
+ reasoning: number
274
+ cache: { read: number; write: number }
275
+ }): number {
276
+ return tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write
277
+ }
278
+
279
+ // Scans backward for most recent message.updated with role=assistant for sessionId.
280
+ // Extracts model, providerID, agent, tokensUsed.
281
+ export function getLatestRunInfo({
282
+ events,
283
+ sessionId,
284
+ upToIndex,
285
+ }: {
286
+ events: EventBufferEntry[]
287
+ sessionId: string
288
+ upToIndex?: number
289
+ }): {
290
+ model: string | undefined
291
+ providerID: string | undefined
292
+ agent: string | undefined
293
+ tokensUsed: number
294
+ } {
295
+ const result = {
296
+ model: undefined as string | undefined,
297
+ providerID: undefined as string | undefined,
298
+ agent: undefined as string | undefined,
299
+ tokensUsed: 0,
300
+ }
301
+ const end = upToIndex ?? events.length - 1
302
+ for (let i = end; i >= 0; i--) {
303
+ const entry = events[i]
304
+ if (!entry) {
305
+ continue
306
+ }
307
+ const e = entry.event
308
+ if (e.type !== 'message.updated') {
309
+ continue
310
+ }
311
+ const msg = e.properties.info
312
+ if (msg.sessionID !== sessionId || msg.role !== 'assistant') {
313
+ continue
314
+ }
315
+ return {
316
+ model: 'modelID' in msg ? (msg.modelID as string) : undefined,
317
+ providerID: 'providerID' in msg ? (msg.providerID as string) : undefined,
318
+ agent: 'mode' in msg ? (msg.mode as string) : undefined,
319
+ tokensUsed: 'tokens' in msg && msg.tokens
320
+ ? getTokenTotal(msg.tokens as { input: number; output: number; reasoning: number; cache: { read: number; write: number } })
321
+ : 0,
322
+ }
323
+ }
324
+ return result
325
+ }
326
+
327
+ export function getAssistantMessageIdsForLatestUserTurn({
328
+ events,
329
+ sessionId,
330
+ upToIndex,
331
+ }: {
332
+ events: EventBufferEntry[]
333
+ sessionId: string
334
+ upToIndex?: number
335
+ }): Set<string> {
336
+ const latestUserMessage = getLatestUserMessage({
337
+ events,
338
+ sessionId,
339
+ upToIndex,
340
+ })
341
+ if (!latestUserMessage) {
342
+ return new Set<string>()
343
+ }
344
+ const end = upToIndex === undefined ? events.length : upToIndex + 1
345
+ const assistantMessageIds = new Set<string>()
346
+ for (let i = 0; i < end; i++) {
347
+ const entry = events[i]
348
+ if (!entry) {
349
+ continue
350
+ }
351
+ const e = entry.event
352
+ if (e.type !== 'message.updated') {
353
+ continue
354
+ }
355
+ const msg = e.properties.info
356
+ if (msg.sessionID !== sessionId || msg.role !== 'assistant') {
357
+ continue
358
+ }
359
+ if (msg.parentID === latestUserMessage.id) {
360
+ assistantMessageIds.add(msg.id)
361
+ }
362
+ }
363
+ return assistantMessageIds
364
+ }
365
+
366
+ export function getLatestAssistantMessageIdForLatestUserTurn({
367
+ events,
368
+ sessionId,
369
+ upToIndex,
370
+ }: {
371
+ events: EventBufferEntry[]
372
+ sessionId: string
373
+ upToIndex?: number
374
+ }): string | undefined {
375
+ const latestUserMessage = getLatestUserMessage({
376
+ events,
377
+ sessionId,
378
+ upToIndex,
379
+ })
380
+ if (!latestUserMessage) {
381
+ return undefined
382
+ }
383
+ const end = upToIndex ?? events.length - 1
384
+ let latestAssistantMessage:
385
+ | Extract<OpenCodeMessage, { role: 'assistant' }>
386
+ | undefined
387
+ for (let i = end; i >= 0; i--) {
388
+ const entry = events[i]
389
+ if (!entry) {
390
+ continue
391
+ }
392
+ const event = entry.event
393
+ if (event.type !== 'message.updated') {
394
+ continue
395
+ }
396
+ const info = event.properties.info
397
+ if (info.sessionID !== sessionId || info.role !== 'assistant') {
398
+ continue
399
+ }
400
+ if (info.parentID !== latestUserMessage.id) {
401
+ continue
402
+ }
403
+ if (!latestAssistantMessage) {
404
+ latestAssistantMessage = info
405
+ continue
406
+ }
407
+ if (info.time.created > latestAssistantMessage.time.created) {
408
+ latestAssistantMessage = info
409
+ }
410
+ }
411
+ return latestAssistantMessage?.id
412
+ }
413
+
414
+ type EventBufferedAssistantMessage = AssistantMessage & {
415
+ partsSummary?: Array<{ id: string; type: string }>
416
+ }
417
+
418
+ function hasRenderablePartSummary(message: EventBufferedAssistantMessage): boolean {
419
+ if (!('partsSummary' in message) || !Array.isArray(message.partsSummary)) {
420
+ return false
421
+ }
422
+ return message.partsSummary.some((part) => {
423
+ return part.type === 'text' || part.type === 'tool'
424
+ })
425
+ }
426
+
427
+ function hasAssistantPartEvidence({
428
+ events,
429
+ sessionId,
430
+ messageId,
431
+ upToIndex,
432
+ }: {
433
+ events: EventBufferEntry[]
434
+ sessionId: string
435
+ messageId: string
436
+ upToIndex?: number
437
+ }): boolean {
438
+ const end = upToIndex ?? events.length - 1
439
+ for (let i = end; i >= 0; i--) {
440
+ const entry = events[i]
441
+ if (!entry) {
442
+ continue
443
+ }
444
+ const event = entry.event
445
+ if (event.type === 'message.updated') {
446
+ const info = event.properties.info as EventBufferedAssistantMessage
447
+ if (info.sessionID !== sessionId || info.role !== 'assistant' || info.id !== messageId) {
448
+ continue
449
+ }
450
+ if (hasRenderablePartSummary(info)) {
451
+ return true
452
+ }
453
+ continue
454
+ }
455
+ if (event.type !== 'message.part.updated') {
456
+ continue
457
+ }
458
+ const { part } = event.properties
459
+ if (part.messageID !== messageId) {
460
+ continue
461
+ }
462
+ if (part.type === 'text' || part.type === 'tool') {
463
+ return true
464
+ }
465
+ }
466
+ return false
467
+ }
468
+
469
+ function hasAssistantStepFinished({
470
+ events,
471
+ messageId,
472
+ upToIndex,
473
+ }: {
474
+ events: EventBufferEntry[]
475
+ messageId: string
476
+ upToIndex?: number
477
+ }): boolean {
478
+ const end = upToIndex ?? events.length - 1
479
+ for (let i = end; i >= 0; i--) {
480
+ const entry = events[i]
481
+ if (!entry || entry.event.type !== 'message.part.updated') {
482
+ continue
483
+ }
484
+ const { part } = entry.event.properties
485
+ if (part.messageID !== messageId) {
486
+ continue
487
+ }
488
+ if (part.type === 'step-finish') {
489
+ return true
490
+ }
491
+ }
492
+ return false
493
+ }
494
+
495
+ export function doesLatestUserTurnHaveNaturalCompletion({
496
+ events,
497
+ sessionId,
498
+ upToIndex,
499
+ }: {
500
+ events: EventBufferEntry[]
501
+ sessionId: string
502
+ upToIndex?: number
503
+ }): boolean {
504
+ const latestAssistantMessageId = getLatestAssistantMessageIdForLatestUserTurn({
505
+ events,
506
+ sessionId,
507
+ upToIndex,
508
+ })
509
+ if (!latestAssistantMessageId) {
510
+ return false
511
+ }
512
+
513
+ const end = upToIndex ?? events.length - 1
514
+ let latestAssistantMessage: EventBufferedAssistantMessage | undefined
515
+ for (let i = end; i >= 0; i--) {
516
+ const entry = events[i]
517
+ if (!entry) {
518
+ continue
519
+ }
520
+ const event = entry.event
521
+ if (event.type !== 'message.updated') {
522
+ continue
523
+ }
524
+ const info = event.properties.info
525
+ if (info.sessionID !== sessionId || info.role !== 'assistant') {
526
+ continue
527
+ }
528
+ if (info.id !== latestAssistantMessageId) {
529
+ continue
530
+ }
531
+ latestAssistantMessage = info as EventBufferedAssistantMessage
532
+ if (isAssistantMessageNaturalCompletion({ message: info })) {
533
+ return true
534
+ }
535
+ break
536
+ }
537
+
538
+ if (!latestAssistantMessage) {
539
+ return false
540
+ }
541
+ if (latestAssistantMessage.error) {
542
+ return false
543
+ }
544
+ if (latestAssistantMessage.finish === 'tool-calls') {
545
+ return false
546
+ }
547
+ return hasAssistantStepFinished({
548
+ events,
549
+ messageId: latestAssistantMessageId,
550
+ upToIndex,
551
+ }) && hasAssistantPartEvidence({
552
+ events,
553
+ sessionId,
554
+ messageId: latestAssistantMessageId,
555
+ upToIndex,
556
+ })
557
+ }
558
+
559
+ export function isAssistantMessageInLatestUserTurn({
560
+ events,
561
+ sessionId,
562
+ messageId,
563
+ upToIndex,
564
+ }: {
565
+ events: EventBufferEntry[]
566
+ sessionId: string
567
+ messageId: string
568
+ upToIndex?: number
569
+ }): boolean {
570
+ const assistantMessageIds = getAssistantMessageIdsForLatestUserTurn({
571
+ events,
572
+ sessionId,
573
+ upToIndex,
574
+ })
575
+ return assistantMessageIds.has(messageId)
576
+ }
577
+
578
+ // Returns a stable 1-based subtask index for candidateSessionId.
579
+ // Indexing scope is the parent assistant message that spawned the task tool calls,
580
+ // so numbering restarts at 1 for each assistant message.
581
+ export function getDerivedSubtaskIndex({
582
+ events,
583
+ mainSessionId,
584
+ candidateSessionId,
585
+ upToIndex,
586
+ }: {
587
+ events: EventBufferEntry[]
588
+ mainSessionId: string
589
+ candidateSessionId: string
590
+ upToIndex?: number
591
+ }): number | undefined {
592
+ const end = upToIndex ?? events.length - 1
593
+ let parentAssistantMessageId: string | undefined
594
+
595
+ for (let i = end; i >= 0; i--) {
596
+ const entry = events[i]
597
+ if (!entry) {
598
+ continue
599
+ }
600
+ const candidate = getTaskCandidateFromEvent({
601
+ event: entry.event,
602
+ mainSessionId,
603
+ })
604
+ if (!candidate) {
605
+ continue
606
+ }
607
+ if (candidate.childSessionId !== candidateSessionId) {
608
+ continue
609
+ }
610
+ parentAssistantMessageId = candidate.assistantMessageId
611
+ break
612
+ }
613
+
614
+ if (!parentAssistantMessageId) {
615
+ return undefined
616
+ }
617
+
618
+ const indexByChildSessionId = new Map<string, number>()
619
+ for (let i = 0; i <= end; i++) {
620
+ const entry = events[i]
621
+ if (!entry) {
622
+ continue
623
+ }
624
+ const candidate = getTaskCandidateFromEvent({
625
+ event: entry.event,
626
+ mainSessionId,
627
+ })
628
+ if (!candidate || candidate.assistantMessageId !== parentAssistantMessageId) {
629
+ continue
630
+ }
631
+ if (!indexByChildSessionId.has(candidate.childSessionId)) {
632
+ indexByChildSessionId.set(
633
+ candidate.childSessionId,
634
+ indexByChildSessionId.size + 1,
635
+ )
636
+ }
637
+ }
638
+
639
+ return indexByChildSessionId.get(candidateSessionId)
640
+ }
641
+
642
+ // Returns the subagent_type (e.g. "explore", "general") for a given child session.
643
+ // Used to build labels like "explore-1" instead of generic "task-1".
644
+ export function getDerivedSubtaskAgentType({
645
+ events,
646
+ mainSessionId,
647
+ candidateSessionId,
648
+ }: {
649
+ events: EventBufferEntry[]
650
+ mainSessionId: string
651
+ candidateSessionId: string
652
+ }): string | undefined {
653
+ for (let i = events.length - 1; i >= 0; i--) {
654
+ const entry = events[i]
655
+ if (!entry) {
656
+ continue
657
+ }
658
+ const candidate = getTaskCandidateFromEvent({
659
+ event: entry.event,
660
+ mainSessionId,
661
+ })
662
+ if (!candidate || candidate.childSessionId !== candidateSessionId) {
663
+ continue
664
+ }
665
+ return candidate.subagentType
666
+ }
667
+ return undefined
668
+ }
669
+
670
+ export function getDerivedSubagentSessions({
671
+ events,
672
+ mainSessionId,
673
+ upToIndex,
674
+ }: {
675
+ events: EventBufferEntry[]
676
+ mainSessionId: string
677
+ upToIndex?: number
678
+ }): DerivedSubagentSession[] {
679
+ const end = upToIndex ?? events.length - 1
680
+ const seenChildSessionIds = new Set<string>()
681
+ const sessions: DerivedSubagentSession[] = []
682
+
683
+ for (let i = end; i >= 0; i--) {
684
+ const entry = events[i]
685
+ if (!entry) {
686
+ continue
687
+ }
688
+ const candidate = getTaskCandidateFromEvent({
689
+ event: entry.event,
690
+ mainSessionId,
691
+ })
692
+ if (!candidate || seenChildSessionIds.has(candidate.childSessionId)) {
693
+ continue
694
+ }
695
+
696
+ seenChildSessionIds.add(candidate.childSessionId)
697
+ sessions.push({
698
+ childSessionId: candidate.childSessionId,
699
+ subagentType: candidate.subagentType,
700
+ description: candidate.description,
701
+ timestamp: entry.timestamp,
702
+ })
703
+ }
704
+
705
+ return sessions
706
+ }