@cuongtran001/kanna 1.63.2 → 1.63.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 (199) hide show
  1. package/README.md +5 -9
  2. package/dist/client/assets/{BoardsPage-BJalB-bc.js → BoardsPage-AckCuswi.js} +1 -1
  3. package/dist/client/assets/BoardsRoutePage-DzKwKy3f.js +1 -0
  4. package/dist/client/assets/ChatPage-DsaH8CTz.js +93 -0
  5. package/dist/client/assets/{CronJobRow-CPbO_jwU.js → CronJobRow-Bx9gRqKW.js} +1 -1
  6. package/dist/client/assets/{CronJobsPage-Ck3SNgGi.js → CronJobsPage-v0-2X1oX.js} +1 -1
  7. package/dist/client/assets/{FilePreviewSheet-XEuUtDPn.js → FilePreviewSheet-B6SDxSD0.js} +2 -2
  8. package/dist/client/assets/{FlowSurface-BCbeMTJe.js → FlowSurface-CLC-ZFar.js} +1 -1
  9. package/dist/client/assets/{GenUIBlock-BJy_dfs-.js → GenUIBlock-CC79Ujro.js} +2 -2
  10. package/dist/client/assets/{KannaSocketProvider-D7Lzlt35.js → KannaSocketProvider-B2sHsWP5.js} +1 -1
  11. package/dist/client/assets/{LocalProjectsPage-9LJkQjhT.js → LocalProjectsPage-DYBkcEAm.js} +1 -1
  12. package/dist/client/assets/SettingsPage-CZqMkMRW.js +9 -0
  13. package/dist/client/assets/{StackBoardsRoutePage-t_EaFNG4.js → StackBoardsRoutePage-Clviz0hX.js} +1 -1
  14. package/dist/client/assets/{ToolCallMessage-RbF9XT2B.js → ToolCallMessage-BfNbWGmc.js} +1 -1
  15. package/dist/client/assets/{VChartSurface-CKuPelsn.js → VChartSurface-CUbxyBo3.js} +1 -1
  16. package/dist/client/assets/{WorkflowsPage-B6qUmQA3.js → WorkflowsPage-rengzb97.js} +1 -1
  17. package/dist/client/assets/{WorkflowsSection-DxO0S4An.js → WorkflowsSection-DuXdLsMO.js} +1 -1
  18. package/dist/client/assets/{chart-renderer-DjYZvNxh.js → chart-renderer-D7ukB2wg.js} +1 -1
  19. package/dist/client/assets/{chatSounds-FD2DQzF6.js → chatSounds-BLg2xPDZ.js} +1 -1
  20. package/dist/client/assets/chevron-left-RbFvmeyK.js +1 -0
  21. package/dist/client/assets/{clipboard.adapter-GN9nnYak.js → clipboard.adapter-CgdNwn-1.js} +1 -1
  22. package/dist/client/assets/{columnStyle-DjA7hA9k.js → columnStyle-CMCF0ds6.js} +1 -1
  23. package/dist/client/assets/{createScopedStore-BoLB9Rhc.js → createScopedStore-BrD5NHWx.js} +1 -1
  24. package/dist/client/assets/{dist-CimtOoGK.js → dist-DNSTHlSo.js} +1 -1
  25. package/dist/client/assets/editor-icons-DY4uKeEY.js +1 -0
  26. package/dist/client/assets/{ellipsis-Cd7sO0LO.js → ellipsis-OHg0x_x1.js} +1 -1
  27. package/dist/client/assets/{flow-kinds-CW2TU_83.js → flow-kinds-Ki2DP9nP.js} +1 -1
  28. package/dist/client/assets/{folder-C7sXCaHj.js → folder-CnWzWIG5.js} +1 -1
  29. package/dist/client/assets/{formatters-wSVyvDFZ.js → formatters-BGORekd-.js} +1 -1
  30. package/dist/client/assets/{genui-EzBIAYFE.js → genui-C9i6j0XZ.js} +1 -1
  31. package/dist/client/assets/{globe-xmRpmpma.js → globe-BjR1tyE4.js} +1 -1
  32. package/dist/client/assets/{host-DCaA9W-Z.js → host-D9tJwZXl.js} +1 -1
  33. package/dist/client/assets/{index-CDqjh7nt.js → index-CesRxZ0X.js} +7 -7
  34. package/dist/client/assets/index-CxDE9VOy.css +1 -0
  35. package/dist/client/assets/{info-D4MMvZFe.js → info-DNYekoE-.js} +1 -1
  36. package/dist/client/assets/{input-C4fvDiQ4.js → input-DgW9uRZN.js} +1 -1
  37. package/dist/client/assets/{lexicalToReact-sPUyOWk6.js → lexicalToReact-CZvp9Pgz.js} +3 -3
  38. package/dist/client/assets/{log-aTaDVF_e.js → log-DnEvbp3E.js} +1 -1
  39. package/dist/client/assets/{monitor-_CRKcV1h.js → monitor-qbWfUdBG.js} +1 -1
  40. package/dist/client/assets/{pencil-fNBeUUCf.js → pencil-C965rrpd.js} +1 -1
  41. package/dist/client/assets/{pendingActionsStore-COTh4Ztq.js → pendingActionsStore-CxhI19V9.js} +1 -1
  42. package/dist/client/assets/{play-BAmkJfn6.js → play-uofLR7yT.js} +1 -1
  43. package/dist/client/assets/{plus-c75CkNEO.js → plus-D4CI6rj1.js} +1 -1
  44. package/dist/client/assets/{popover-CHM4skzh.js → popover-Bc646hKn.js} +1 -1
  45. package/dist/client/assets/{pushClient-BJH_ITRB.js → pushClient-CxnHu9h4.js} +1 -1
  46. package/dist/client/assets/runDetached-DfQXHAaY.js +1 -0
  47. package/dist/client/assets/{segmented-control-DNOwRupT.js → segmented-control-CKGX29Kl.js} +1 -1
  48. package/dist/client/assets/{select-dLHiAls6.js → select-BjeajpVt.js} +1 -1
  49. package/dist/client/assets/{settings-header-button-CGKCzFyk.js → settings-header-button-BYvHR6Xl.js} +1 -1
  50. package/dist/client/assets/{sidebarPendingActions-Dyp80Lv_.js → sidebarPendingActions-xgMTnfRY.js} +1 -1
  51. package/dist/client/assets/{square-pen-B1IxmScM.js → square-pen-DmppQdTd.js} +1 -1
  52. package/dist/client/assets/{state-mark-BhTZfcYe.js → state-mark-C3kEL1hX.js} +1 -1
  53. package/dist/client/assets/{status-pill-CwhJhaMy.js → status-pill-Bo1Nb6HX.js} +1 -1
  54. package/dist/client/assets/{terminal-Bnj7qFGw.js → terminal-DfCF-J4p.js} +1 -1
  55. package/dist/client/assets/{timer.adapter-DHt2zwsL.js → timer.adapter-C2RhP4d6.js} +1 -1
  56. package/dist/client/assets/{tools-3gJcj3hK.js → tools-t5T_3UBg.js} +1 -1
  57. package/dist/client/assets/{triangle-alert-CWrc0CIO.js → triangle-alert-CYQhobyY.js} +1 -1
  58. package/dist/client/assets/useAppGlobalState-DUa3pkCF.js +1 -0
  59. package/dist/client/assets/useKannaState-FcieR5qb.js +5 -0
  60. package/dist/client/assets/useTheme-D9sKDPEm.js +1 -0
  61. package/dist/client/index.html +35 -35
  62. package/package.json +2 -2
  63. package/src/server/agent-coordinator-types.ts +0 -6
  64. package/src/server/agent-coordinator.ts +6 -45
  65. package/src/server/agent.compaction.test.ts +1 -13
  66. package/src/server/agent.oauth-account-info.test.ts +0 -3
  67. package/src/server/agent.oauth-release.test.ts +0 -2
  68. package/src/server/agent.oauth-rotation.test.ts +0 -9
  69. package/src/server/agent.oauth-token-share-cap.test.ts +0 -1
  70. package/src/server/agent.openrouter-model.test.ts +0 -2
  71. package/src/server/agent.openrouter-watchdog.test.ts +0 -3
  72. package/src/server/agent.sdk-workflow-register.test.ts +2 -64
  73. package/src/server/agent.test.ts +1 -319
  74. package/src/server/app-settings.test.ts +14 -17
  75. package/src/server/app-settings.ts +22 -37
  76. package/src/server/auto-continue/e2e.test.ts +0 -1
  77. package/src/server/background-task-output-registry.test.ts +1 -1
  78. package/src/server/background-task-output-registry.ts +1 -1
  79. package/src/server/claude-cancel-handler.test.ts +0 -58
  80. package/src/server/claude-cancel-handler.ts +1 -17
  81. package/src/server/claude-harness-stream.test.ts +134 -0
  82. package/src/server/{claude-pty/jsonl-path.test.ts → claude-projects-path.adapter.test.ts} +1 -19
  83. package/src/server/{claude-pty/jsonl-path.adapter.ts → claude-projects-path.adapter.ts} +0 -11
  84. package/src/server/claude-send-command.test.ts +0 -1
  85. package/src/server/claude-session-config-helpers.test.ts +1 -90
  86. package/src/server/claude-session-config-helpers.ts +1 -38
  87. package/src/server/claude-session-error-handler.test.ts +0 -1
  88. package/src/server/claude-session-lifecycle.test.ts +2 -36
  89. package/src/server/claude-session-lifecycle.ts +2 -8
  90. package/src/server/claude-session-runner.test.ts +8 -36
  91. package/src/server/claude-session-runner.ts +1 -26
  92. package/src/server/claude-session-spawner.test.ts +5 -64
  93. package/src/server/claude-session-spawner.ts +49 -115
  94. package/src/server/claude-session-start.test.ts +1 -14
  95. package/src/server/claude-session-start.ts +0 -8
  96. package/src/server/claude-session-state-queries.test.ts +1 -2
  97. package/src/server/claude-session-state.test.ts +0 -1
  98. package/src/server/claude-session-state.ts +0 -4
  99. package/src/server/claude-subagent-wiring.test.ts +2 -68
  100. package/src/server/claude-subagent-wiring.ts +3 -50
  101. package/src/server/claude-turn-starter-types.ts +0 -3
  102. package/src/server/claude-turn-starter.test.ts +0 -28
  103. package/src/server/claude-turn-starter.ts +0 -5
  104. package/src/server/event-store.test.ts +0 -2
  105. package/src/server/events.ts +1 -2
  106. package/src/server/harness-types.ts +1 -2
  107. package/src/server/import-subagent-drill.e2e.test.ts +1 -1
  108. package/src/server/imported-subagents-dir.test.ts +1 -1
  109. package/src/server/imported-subagents-dir.ts +1 -1
  110. package/src/server/kanna-mcp.test.ts +0 -75
  111. package/src/server/kanna-mcp.ts +1 -47
  112. package/src/server/server.test.ts +5 -7
  113. package/src/server/server.ts +2 -18
  114. package/src/server/subagent-orchestrator.test.ts +0 -1
  115. package/src/server/subagent-provider-run.test.ts +48 -10
  116. package/src/server/subagent-provider-run.ts +12 -3
  117. package/src/server/tool-callback.test.ts +1 -1
  118. package/src/server/ws-router-agent-ctrl.test.ts +0 -45
  119. package/src/server/ws-router-agent-ctrl.ts +1 -31
  120. package/src/server/ws-router-broadcast.ts +0 -27
  121. package/src/server/ws-router-defaults.test.ts +2 -3
  122. package/src/server/ws-router-defaults.ts +2 -3
  123. package/src/server/ws-router-envelope.ts +0 -15
  124. package/src/server/ws-router-observability.test.ts +1 -1
  125. package/src/server/ws-router-settings.test.ts +2 -2
  126. package/src/server/ws-router.test.ts +2 -2
  127. package/src/server/ws-router.ts +0 -8
  128. package/src/shared/app-settings-types.ts +9 -20
  129. package/src/shared/claude-api-error-classification.ts +1 -1
  130. package/src/shared/protocol.ts +1 -11
  131. package/dist/client/assets/BoardsRoutePage-BTTjJX-Y.js +0 -1
  132. package/dist/client/assets/ChatPage-DH8wtQyD.js +0 -93
  133. package/dist/client/assets/SettingsPage-CKrZ6dGQ.js +0 -9
  134. package/dist/client/assets/chevron-left-CJ0qv9kF.js +0 -1
  135. package/dist/client/assets/editor-icons-CONDQZof.js +0 -1
  136. package/dist/client/assets/index-C-UYaDk6.css +0 -1
  137. package/dist/client/assets/runDetached-DqQ3k2ji.js +0 -1
  138. package/dist/client/assets/useAppGlobalState-DKOGrO5l.js +0 -1
  139. package/dist/client/assets/useKannaState-D7R_-dM6.js +0 -5
  140. package/dist/client/assets/useTheme-B57LIKyx.js +0 -1
  141. package/src/server/agent.pty-rotation.test.ts +0 -322
  142. package/src/server/claude-pty/auth.test.ts +0 -38
  143. package/src/server/claude-pty/auth.ts +0 -17
  144. package/src/server/claude-pty/claude-session-registry.adapter.ts +0 -68
  145. package/src/server/claude-pty/claude-session-registry.test.ts +0 -149
  146. package/src/server/claude-pty/driver.test.ts +0 -1153
  147. package/src/server/claude-pty/driver.ts +0 -829
  148. package/src/server/claude-pty/env.test.ts +0 -79
  149. package/src/server/claude-pty/env.ts +0 -20
  150. package/src/server/claude-pty/jsonl-to-event.test.ts +0 -882
  151. package/src/server/claude-pty/jsonl-to-event.ts +0 -357
  152. package/src/server/claude-pty/parity-matrix.test.ts +0 -437
  153. package/src/server/claude-pty/pid-registry.adapter.ts +0 -164
  154. package/src/server/claude-pty/pid-registry.test.ts +0 -178
  155. package/src/server/claude-pty/preflight/binary-fingerprint.adapter.ts +0 -20
  156. package/src/server/claude-pty/preflight/binary-fingerprint.test.ts +0 -32
  157. package/src/server/claude-pty/pty-instance-registry.test.ts +0 -208
  158. package/src/server/claude-pty/pty-instance-registry.ts +0 -172
  159. package/src/server/claude-pty/pty-memory-sampler.adapter.test.ts +0 -103
  160. package/src/server/claude-pty/pty-memory-sampler.adapter.ts +0 -86
  161. package/src/server/claude-pty/pty-process.adapter.ts +0 -60
  162. package/src/server/claude-pty/pty-process.test.ts +0 -49
  163. package/src/server/claude-pty/resolve-binary.adapter.ts +0 -106
  164. package/src/server/claude-pty/resolve-binary.test.ts +0 -118
  165. package/src/server/claude-pty/runtime-dir.adapter.ts +0 -19
  166. package/src/server/claude-pty/sandbox/detect.adapter.ts +0 -17
  167. package/src/server/claude-pty/sandbox/detect.test.ts +0 -17
  168. package/src/server/claude-pty/sandbox/platform.test.ts +0 -35
  169. package/src/server/claude-pty/sandbox/platform.ts +0 -27
  170. package/src/server/claude-pty/sandbox/preflight-io.adapter.ts +0 -14
  171. package/src/server/claude-pty/sandbox/preflight.test.ts +0 -69
  172. package/src/server/claude-pty/sandbox/preflight.ts +0 -44
  173. package/src/server/claude-pty/sandbox/profile-linux.adapter.ts +0 -63
  174. package/src/server/claude-pty/sandbox/profile-linux.test.ts +0 -90
  175. package/src/server/claude-pty/sandbox/profile-macos.adapter.ts +0 -72
  176. package/src/server/claude-pty/sandbox/profile-macos.test.ts +0 -68
  177. package/src/server/claude-pty/sandbox/wrap.adapter.ts +0 -58
  178. package/src/server/claude-pty/sandbox/wrap.test.ts +0 -82
  179. package/src/server/claude-pty/settings-writer.adapter.ts +0 -23
  180. package/src/server/claude-pty/settings-writer.test.ts +0 -22
  181. package/src/server/claude-pty/smoke-test-io.adapter.ts +0 -28
  182. package/src/server/claude-pty/smoke-test.test.ts +0 -199
  183. package/src/server/claude-pty/smoke-test.ts +0 -182
  184. package/src/server/claude-pty/tui-control.test.ts +0 -300
  185. package/src/server/claude-pty/tui-control.ts +0 -174
  186. package/src/server/claude-pty/tui-source.adapter.test.ts +0 -374
  187. package/src/server/claude-pty/tui-source.adapter.ts +0 -294
  188. package/src/server/http-server.adapter.test.ts +0 -18
  189. package/src/server/http-server.adapter.ts +0 -39
  190. package/src/server/kanna-mcp-http.test.ts +0 -274
  191. package/src/server/kanna-mcp-http.ts +0 -194
  192. package/src/server/kanna-mcp-tools/ask-user-question.test.ts +0 -108
  193. package/src/server/kanna-mcp-tools/ask-user-question.ts +0 -55
  194. package/src/server/kanna-mcp-tools/exit-plan-mode.test.ts +0 -61
  195. package/src/server/kanna-mcp-tools/exit-plan-mode.ts +0 -49
  196. package/src/server/mcp-zod-compat.adapter.ts +0 -20
  197. package/src/shared/pty-instance.ts +0 -43
  198. /package/src/server/{claude-pty/output-ring.test.ts → output-ring.test.ts} +0 -0
  199. /package/src/server/{claude-pty/output-ring.ts → output-ring.ts} +0 -0
@@ -25,16 +25,15 @@ import {
25
25
  AUTH_SESSION_MAX_AGE_DAYS_MIN,
26
26
  CLAUDE_AUTH_DEFAULTS,
27
27
  CLAUDE_DRIVER_DEFAULTS,
28
- CLAUDE_PTY_IDLE_TIMEOUT_MS_MAX,
29
- CLAUDE_PTY_IDLE_TIMEOUT_MS_MIN,
30
- CLAUDE_PTY_LIFECYCLE_DEFAULTS,
31
- CLAUDE_PTY_MAX_CONCURRENT_MAX,
32
- CLAUDE_PTY_MAX_CONCURRENT_MIN,
28
+ CLAUDE_IDLE_TIMEOUT_MS_MAX,
29
+ CLAUDE_IDLE_TIMEOUT_MS_MIN,
30
+ CLAUDE_LIFECYCLE_DEFAULTS,
31
+ CLAUDE_MAX_CONCURRENT_MAX,
32
+ CLAUDE_MAX_CONCURRENT_MIN,
33
33
  DEFAULT_CLAUDE_MODEL_OPTIONS,
34
34
  DEFAULT_CODEX_MODEL_OPTIONS,
35
35
  DEFAULT_OPENROUTER_SDK_MODEL,
36
36
  GLOBAL_PROMPT_APPEND_MAX_CHARS,
37
- isClaudeDriverPreference,
38
37
  isClaudeReasoningEffort,
39
38
  isCodexReasoningEffort,
40
39
  normalizeClaudeContextWindow,
@@ -60,10 +59,9 @@ import {
60
59
  type ChatSoundId,
61
60
  type ChatSoundPreference,
62
61
  type ClaudeAuthSettings,
63
- type ClaudeDriverPreference,
64
62
  type ClaudeDriverSettings,
65
63
  type ClaudeModelOptions,
66
- type ClaudePtyLifecycleSettings,
64
+ type ClaudeLifecycleSettings,
67
65
  type CloudflareTunnelSettings,
68
66
  type CodexModelOptions,
69
67
  type OpenRouterModelOptions,
@@ -729,35 +727,35 @@ function normalizeTokenEntry<T>(value: T, warnings: string[]): OAuthTokenEntry |
729
727
  }
730
728
  }
731
729
 
732
- function normalizeClaudePtyLifecycle<T>(value: T, warnings: string[]): ClaudePtyLifecycleSettings {
730
+ function normalizeClaudeLifecycle<T>(value: T, warnings: string[]): ClaudeLifecycleSettings {
733
731
  const source = isPlainObject(value) ? value : null
734
732
  if (value !== undefined && !source) {
735
733
  warnings.push("claudeDriver.lifecycle must be an object")
736
734
  }
737
735
  const idleRaw = source?.idleTimeoutMs
738
- let idleTimeoutMs = CLAUDE_PTY_LIFECYCLE_DEFAULTS.idleTimeoutMs
736
+ let idleTimeoutMs = CLAUDE_LIFECYCLE_DEFAULTS.idleTimeoutMs
739
737
  if (idleRaw !== undefined) {
740
738
  if (typeof idleRaw !== "number" || !Number.isFinite(idleRaw)) {
741
739
  warnings.push("claudeDriver.lifecycle.idleTimeoutMs must be a number")
742
- } else if (idleRaw < CLAUDE_PTY_IDLE_TIMEOUT_MS_MIN || idleRaw > CLAUDE_PTY_IDLE_TIMEOUT_MS_MAX) {
740
+ } else if (idleRaw < CLAUDE_IDLE_TIMEOUT_MS_MIN || idleRaw > CLAUDE_IDLE_TIMEOUT_MS_MAX) {
743
741
  warnings.push(
744
- `claudeDriver.lifecycle.idleTimeoutMs must be between ${CLAUDE_PTY_IDLE_TIMEOUT_MS_MIN} and ${CLAUDE_PTY_IDLE_TIMEOUT_MS_MAX}`,
742
+ `claudeDriver.lifecycle.idleTimeoutMs must be between ${CLAUDE_IDLE_TIMEOUT_MS_MIN} and ${CLAUDE_IDLE_TIMEOUT_MS_MAX}`,
745
743
  )
746
- idleTimeoutMs = clampNumber(idleRaw, CLAUDE_PTY_LIFECYCLE_DEFAULTS.idleTimeoutMs, CLAUDE_PTY_IDLE_TIMEOUT_MS_MIN, CLAUDE_PTY_IDLE_TIMEOUT_MS_MAX)
744
+ idleTimeoutMs = clampNumber(idleRaw, CLAUDE_LIFECYCLE_DEFAULTS.idleTimeoutMs, CLAUDE_IDLE_TIMEOUT_MS_MIN, CLAUDE_IDLE_TIMEOUT_MS_MAX)
747
745
  } else {
748
746
  idleTimeoutMs = Math.round(idleRaw)
749
747
  }
750
748
  }
751
749
  const maxRaw = source?.maxConcurrent
752
- let maxConcurrent = CLAUDE_PTY_LIFECYCLE_DEFAULTS.maxConcurrent
750
+ let maxConcurrent = CLAUDE_LIFECYCLE_DEFAULTS.maxConcurrent
753
751
  if (maxRaw !== undefined) {
754
752
  if (typeof maxRaw !== "number" || !Number.isFinite(maxRaw)) {
755
753
  warnings.push("claudeDriver.lifecycle.maxConcurrent must be a number")
756
- } else if (maxRaw < CLAUDE_PTY_MAX_CONCURRENT_MIN || maxRaw > CLAUDE_PTY_MAX_CONCURRENT_MAX) {
754
+ } else if (maxRaw < CLAUDE_MAX_CONCURRENT_MIN || maxRaw > CLAUDE_MAX_CONCURRENT_MAX) {
757
755
  warnings.push(
758
- `claudeDriver.lifecycle.maxConcurrent must be between ${CLAUDE_PTY_MAX_CONCURRENT_MIN} and ${CLAUDE_PTY_MAX_CONCURRENT_MAX}`,
756
+ `claudeDriver.lifecycle.maxConcurrent must be between ${CLAUDE_MAX_CONCURRENT_MIN} and ${CLAUDE_MAX_CONCURRENT_MAX}`,
759
757
  )
760
- maxConcurrent = clampNumber(maxRaw, CLAUDE_PTY_LIFECYCLE_DEFAULTS.maxConcurrent, CLAUDE_PTY_MAX_CONCURRENT_MIN, CLAUDE_PTY_MAX_CONCURRENT_MAX)
758
+ maxConcurrent = clampNumber(maxRaw, CLAUDE_LIFECYCLE_DEFAULTS.maxConcurrent, CLAUDE_MAX_CONCURRENT_MIN, CLAUDE_MAX_CONCURRENT_MAX)
761
759
  } else {
762
760
  maxConcurrent = Math.round(maxRaw)
763
761
  }
@@ -771,18 +769,10 @@ function normalizeClaudeDriverSettings<T>(value: T, warnings: string[]): ClaudeD
771
769
  warnings.push("claudeDriver must be an object")
772
770
  return {
773
771
  ...CLAUDE_DRIVER_DEFAULTS,
774
- lifecycle: { ...CLAUDE_PTY_LIFECYCLE_DEFAULTS },
772
+ lifecycle: { ...CLAUDE_LIFECYCLE_DEFAULTS },
775
773
  }
776
774
  }
777
- const rawPref = typeof source?.preference === "string" ? source.preference : undefined
778
- const preference: ClaudeDriverPreference = isClaudeDriverPreference(rawPref)
779
- ? rawPref
780
- : CLAUDE_DRIVER_DEFAULTS.preference
781
- if (source?.preference !== undefined && !isClaudeDriverPreference(rawPref)) {
782
- warnings.push(`claudeDriver.preference must be "sdk" or "pty"`)
783
- }
784
- const lifecycle = normalizeClaudePtyLifecycle(source?.lifecycle, warnings)
785
- return { preference, lifecycle }
775
+ return { lifecycle: normalizeClaudeLifecycle(source?.lifecycle, warnings) }
786
776
  }
787
777
 
788
778
  function normalizeGlobalPromptAppend<T>(value: T, warnings: string[]): string {
@@ -1593,7 +1583,6 @@ function applyPatch(state: AppSettingsState, patch: AppSettingsPatch): AppSettin
1593
1583
  textSnippets: nextTextSnippets,
1594
1584
  ...mergePluginPatch(state, patch),
1595
1585
  claudeDriver: {
1596
- preference: patch.claudeDriver?.preference ?? state.claudeDriver.preference,
1597
1586
  lifecycle: {
1598
1587
  ...state.claudeDriver.lifecycle,
1599
1588
  ...patch.claudeDriver?.lifecycle,
@@ -1713,24 +1702,20 @@ export class AppSettingsManager {
1713
1702
  }
1714
1703
 
1715
1704
  async setClaudeDriver(patch: {
1716
- preference?: ClaudeDriverPreference
1717
- lifecycle?: Partial<ClaudePtyLifecycleSettings>
1705
+ lifecycle?: Partial<ClaudeLifecycleSettings>
1718
1706
  }) {
1719
- if (patch.preference !== undefined && !isClaudeDriverPreference(patch.preference)) {
1720
- throw new Error(`claudeDriver.preference must be "sdk" or "pty"`)
1721
- }
1722
1707
  if (patch.lifecycle?.idleTimeoutMs !== undefined) {
1723
1708
  const value = patch.lifecycle.idleTimeoutMs
1724
1709
  if (typeof value !== "number" || !Number.isFinite(value)
1725
- || value < CLAUDE_PTY_IDLE_TIMEOUT_MS_MIN || value > CLAUDE_PTY_IDLE_TIMEOUT_MS_MAX) {
1726
- throw new Error(`claudeDriver.lifecycle.idleTimeoutMs must be between ${CLAUDE_PTY_IDLE_TIMEOUT_MS_MIN} and ${CLAUDE_PTY_IDLE_TIMEOUT_MS_MAX}`)
1710
+ || value < CLAUDE_IDLE_TIMEOUT_MS_MIN || value > CLAUDE_IDLE_TIMEOUT_MS_MAX) {
1711
+ throw new Error(`claudeDriver.lifecycle.idleTimeoutMs must be between ${CLAUDE_IDLE_TIMEOUT_MS_MIN} and ${CLAUDE_IDLE_TIMEOUT_MS_MAX}`)
1727
1712
  }
1728
1713
  }
1729
1714
  if (patch.lifecycle?.maxConcurrent !== undefined) {
1730
1715
  const value = patch.lifecycle.maxConcurrent
1731
1716
  if (typeof value !== "number" || !Number.isFinite(value)
1732
- || value < CLAUDE_PTY_MAX_CONCURRENT_MIN || value > CLAUDE_PTY_MAX_CONCURRENT_MAX) {
1733
- throw new Error(`claudeDriver.lifecycle.maxConcurrent must be between ${CLAUDE_PTY_MAX_CONCURRENT_MIN} and ${CLAUDE_PTY_MAX_CONCURRENT_MAX}`)
1717
+ || value < CLAUDE_MAX_CONCURRENT_MIN || value > CLAUDE_MAX_CONCURRENT_MAX) {
1718
+ throw new Error(`claudeDriver.lifecycle.maxConcurrent must be between ${CLAUDE_MAX_CONCURRENT_MIN} and ${CLAUDE_MAX_CONCURRENT_MAX}`)
1734
1719
  }
1735
1720
  }
1736
1721
  return this.writePatch({ claudeDriver: patch })
@@ -100,7 +100,6 @@ describe("auto-continue end-to-end", () => {
100
100
  closed: Promise.resolve(),
101
101
  setModel: async () => {},
102
102
  setPermissionMode: async () => {},
103
- getSupportedCommands: async () => [],
104
103
  sendPrompt: async () => {
105
104
  events.throw(makeRateLimitError(resetAtMs))
106
105
  },
@@ -1,7 +1,7 @@
1
1
  import { describe, test, expect } from "bun:test"
2
2
  import { createBackgroundTaskOutputRegistry } from "./background-task-output-registry"
3
3
  import type { BackgroundTaskOutputDeps } from "./background-task-output-registry"
4
- import { OUTPUT_RING_DEFAULT_BYTES } from "./claude-pty/output-ring"
4
+ import { OUTPUT_RING_DEFAULT_BYTES } from "./output-ring"
5
5
 
6
6
  function makeDeps(fileContents: Map<string, string> = new Map()): BackgroundTaskOutputDeps & { advance(path: string, append: string): void } {
7
7
  const contents = new Map(fileContents)
@@ -1,4 +1,4 @@
1
- import { OutputRing, OUTPUT_RING_DEFAULT_BYTES } from "./claude-pty/output-ring"
1
+ import { OutputRing, OUTPUT_RING_DEFAULT_BYTES } from "./output-ring"
2
2
 
3
3
  export interface BackgroundTaskOutputDeps {
4
4
  statSize(path: string): number | null
@@ -28,7 +28,6 @@ function makeFakeHandle(): ClaudeSessionHandle {
28
28
  sendPrompt: async () => {},
29
29
  setModel: async () => {},
30
30
  setPermissionMode: async () => {},
31
- getSupportedCommands: async () => [],
32
31
  }
33
32
  }
34
33
 
@@ -107,8 +106,6 @@ type DepOverrides = {
107
106
  stateChanges?: string[]
108
107
  rejectCalled?: string[]
109
108
  orchestratorCancelled?: string[]
110
- closedSessions?: string[]
111
- driver?: "sdk" | "pty"
112
109
  }
113
110
 
114
111
  function makeDeps(overrides: DepOverrides = {}): CancelHandlerDeps {
@@ -122,8 +119,6 @@ function makeDeps(overrides: DepOverrides = {}): CancelHandlerDeps {
122
119
  const stateChanges = overrides.stateChanges ?? []
123
120
  const rejectCalled = overrides.rejectCalled ?? []
124
121
  const orchestratorCancelled = overrides.orchestratorCancelled ?? []
125
- const closedSessions = overrides.closedSessions ?? []
126
- const driver = overrides.driver ?? "sdk"
127
122
 
128
123
  return {
129
124
  drainingStreams,
@@ -138,8 +133,6 @@ function makeDeps(overrides: DepOverrides = {}): CancelHandlerDeps {
138
133
  },
139
134
  claudeSessions,
140
135
  emitStateChange: (chatId) => { stateChanges.push(chatId) },
141
- resolveClaudeDriverPreference: () => driver,
142
- closeClaudeSession: (chatId) => { closedSessions.push(chatId) },
143
136
  }
144
137
  }
145
138
 
@@ -276,24 +269,6 @@ describe("self-wake turn cancel", () => {
276
269
  expect(stateChanges).toContain("chat-1")
277
270
  })
278
271
 
279
- test("PTY driver drops the dead session after a self-wake interrupt", async () => {
280
- const session = makeSession({ selfWakeActive: true })
281
- session.session = {
282
- ...session.session,
283
- interrupt: async () => {},
284
- }
285
- const closedSessions: string[] = []
286
- const deps = makeDeps({
287
- claudeSessions: new Map([["chat-1", session]]),
288
- closedSessions,
289
- driver: "pty",
290
- })
291
-
292
- await cancelChat(deps, "chat-1")
293
-
294
- expect(closedSessions).toContain("chat-1")
295
- })
296
-
297
272
  test("no self-wake, no session interrupt — stays a no-op", async () => {
298
273
  let interrupted = false
299
274
  const session = makeSession({ selfWakeActive: false })
@@ -557,39 +532,6 @@ describe("interrupt and close", () => {
557
532
  const deps = makeDeps({ activeTurns })
558
533
  await expect(cancelChat(deps, "chat-1")).resolves.toBeUndefined()
559
534
  })
560
-
561
- test("closes Claude session on PTY driver for claude provider", async () => {
562
- const closedSessions: string[] = []
563
- const session = makeSession({ chatId: "chat-1" })
564
- const claudeSessions = new Map([["chat-1", session]])
565
- const active = makeActiveTurn({ provider: "claude" })
566
- const activeTurns = new Map([["chat-1", active]])
567
- const deps = makeDeps({ activeTurns, claudeSessions, closedSessions, driver: "pty" })
568
- await cancelChat(deps, "chat-1")
569
- expect(closedSessions).toContain("chat-1")
570
- })
571
-
572
- test("does NOT close Claude session on SDK driver", async () => {
573
- const closedSessions: string[] = []
574
- const session = makeSession({ chatId: "chat-1" })
575
- const claudeSessions = new Map([["chat-1", session]])
576
- const active = makeActiveTurn({ provider: "claude" })
577
- const activeTurns = new Map([["chat-1", active]])
578
- const deps = makeDeps({ activeTurns, claudeSessions, closedSessions, driver: "sdk" })
579
- await cancelChat(deps, "chat-1")
580
- expect(closedSessions.length).toBe(0)
581
- })
582
-
583
- test("does NOT close Claude session for codex provider even on PTY", async () => {
584
- const closedSessions: string[] = []
585
- const session = makeSession({ chatId: "chat-1" })
586
- const claudeSessions = new Map([["chat-1", session]])
587
- const active = makeActiveTurn({ provider: "codex" })
588
- const activeTurns = new Map([["chat-1", active]])
589
- const deps = makeDeps({ activeTurns, claudeSessions, closedSessions, driver: "pty" })
590
- await cancelChat(deps, "chat-1")
591
- expect(closedSessions.length).toBe(0)
592
- })
593
535
  })
594
536
 
595
537
 
@@ -1,5 +1,5 @@
1
1
 
2
- import type { ClaudeDriverPreference, TranscriptEntry } from "../shared/types"
2
+ import type { TranscriptEntry } from "../shared/types"
3
3
  import type { ActiveTurn, ClaudeSessionState, StartingTurn } from "./claude-session-state"
4
4
  import type { PendingToolSlots } from "./pending-tool-slot"
5
5
  import type { HarnessTurn } from "./harness-types"
@@ -51,10 +51,6 @@ export interface CancelHandlerDeps {
51
51
  claudeSessions: ClaudeSessionsMap
52
52
 
53
53
  emitStateChange(chatId: string): void
54
-
55
- resolveClaudeDriverPreference(): ClaudeDriverPreference
56
-
57
- closeClaudeSession(chatId: string, session: ClaudeSessionState): void
58
54
  }
59
55
 
60
56
 
@@ -120,10 +116,6 @@ export async function cancelChat(
120
116
  ])
121
117
  } catch {
122
118
  }
123
- if (deps.resolveClaudeDriverPreference() === "pty") {
124
- deps.closeClaudeSession(chatId, session)
125
- deps.emitStateChange(chatId)
126
- }
127
119
  }
128
120
  return
129
121
  }
@@ -178,12 +170,4 @@ export async function cancelChat(
178
170
  } catch {
179
171
  }
180
172
  active.turn.close()
181
-
182
- if (active.provider === "claude" && deps.resolveClaudeDriverPreference() === "pty") {
183
- const session = deps.claudeSessions.get(chatId)
184
- if (session) {
185
- deps.closeClaudeSession(chatId, session)
186
- }
187
- }
188
-
189
173
  }
@@ -326,3 +326,137 @@ describe("createClaudeHarnessStream", () => {
326
326
  expect(lastSnapshot.usage.maxTokens).toBeGreaterThanOrEqual(1_000_000)
327
327
  })
328
328
  })
329
+
330
+ describe("createClaudeHarnessStream cost attachment", () => {
331
+ test("claude result cost is attached to the final-turn snapshot", async () => {
332
+ const events = await collect([
333
+ {
334
+ type: "assistant",
335
+ session_id: "sess-cost",
336
+ message: { id: "m1", role: "assistant", content: [] },
337
+ usage: { input_tokens: 100, output_tokens: 50 },
338
+ },
339
+ {
340
+ type: "result",
341
+ subtype: "success",
342
+ session_id: "sess-cost",
343
+ is_error: false,
344
+ total_cost_usd: 0.0123,
345
+ usage: { input_tokens: 100, output_tokens: 50 },
346
+ duration_ms: 10,
347
+ num_turns: 1,
348
+ result: "ok",
349
+ },
350
+ ])
351
+
352
+ const cwuEntries = events.flatMap((ev) =>
353
+ ev.type === "transcript" && ev.entry.kind === "context_window_updated" ? [ev.entry] : [],
354
+ )
355
+ const lastCwu = cwuEntries.at(-1)
356
+ expect(lastCwu).toBeDefined()
357
+ expect(lastCwu!.usage.costUsd).toBeCloseTo(0.0123, 6)
358
+ })
359
+
360
+ test("openrouter turn gets computed cost from a price resolver", async () => {
361
+ const price: ModelPrice = { inputPerMTok: 3, outputPerMTok: 15 }
362
+ const events = await collect(
363
+ [
364
+ {
365
+ type: "assistant",
366
+ session_id: "sess-or",
367
+ message: { id: "m1", role: "assistant", content: [] },
368
+ usage: { input_tokens: 1_000_000, output_tokens: 0 },
369
+ },
370
+ {
371
+ type: "result",
372
+ subtype: "success",
373
+ session_id: "sess-or",
374
+ is_error: false,
375
+ usage: { input_tokens: 1_000_000, output_tokens: 0 },
376
+ duration_ms: 10,
377
+ num_turns: 1,
378
+ result: "ok",
379
+ },
380
+ ],
381
+ undefined,
382
+ () => price,
383
+ )
384
+
385
+ const cwuEntries = events.flatMap((ev) =>
386
+ ev.type === "transcript" && ev.entry.kind === "context_window_updated" ? [ev.entry] : [],
387
+ )
388
+ const lastCwu = cwuEntries.at(-1)
389
+ expect(lastCwu).toBeDefined()
390
+ expect(lastCwu!.usage.costUsd).toBeCloseTo(3, 6)
391
+ })
392
+ })
393
+
394
+ describe("result entry usage + cost enrichment", () => {
395
+ test("claude: result entry carries usage tokens and provider costUsd", async () => {
396
+ const events = await collect([
397
+ {
398
+ type: "assistant",
399
+ session_id: "sess-ru",
400
+ message: { id: "m1", role: "assistant", content: [] },
401
+ usage: { input_tokens: 200, output_tokens: 80 },
402
+ },
403
+ {
404
+ type: "result",
405
+ subtype: "success",
406
+ session_id: "sess-ru",
407
+ is_error: false,
408
+ total_cost_usd: 0.02,
409
+ usage: { input_tokens: 200, output_tokens: 80 },
410
+ duration_ms: 100,
411
+ num_turns: 1,
412
+ result: "done",
413
+ },
414
+ ])
415
+
416
+ const resultEntries = events.flatMap((ev) =>
417
+ ev.type === "transcript" && ev.entry.kind === "result" ? [ev.entry] : [],
418
+ )
419
+ expect(resultEntries).toHaveLength(1)
420
+ const resultEntry = resultEntries[0]
421
+ expect(resultEntry.costUsd).toBeCloseTo(0.02, 6)
422
+ expect(resultEntry.usage).toBeDefined()
423
+ expect(resultEntry.usage?.outputTokens).toBe(80)
424
+ expect(resultEntry.usage?.inputTokens).toBe(200)
425
+ })
426
+
427
+ test("openrouter: result entry carries computed cost and usage tokens from resolver", async () => {
428
+ const price: ModelPrice = { inputPerMTok: 3, outputPerMTok: 15 }
429
+ const events = await collect(
430
+ [
431
+ {
432
+ type: "assistant",
433
+ session_id: "sess-oru",
434
+ message: { id: "m1", role: "assistant", content: [] },
435
+ usage: { input_tokens: 1_000_000, output_tokens: 0 },
436
+ },
437
+ {
438
+ type: "result",
439
+ subtype: "success",
440
+ session_id: "sess-oru",
441
+ is_error: false,
442
+ usage: { input_tokens: 1_000_000, output_tokens: 0 },
443
+ duration_ms: 10,
444
+ num_turns: 1,
445
+ result: "ok",
446
+ },
447
+ ],
448
+ undefined,
449
+ () => price,
450
+ )
451
+
452
+ const resultEntries = events.flatMap((ev) =>
453
+ ev.type === "transcript" && ev.entry.kind === "result" ? [ev.entry] : [],
454
+ )
455
+ expect(resultEntries).toHaveLength(1)
456
+ const resultEntry = resultEntries[0]
457
+ expect(resultEntry.costUsd).toBeCloseTo(3, 6)
458
+ expect(resultEntry.usage).toBeDefined()
459
+ expect(resultEntry.usage?.inputTokens).toBe(1_000_000)
460
+ expect(resultEntry.usage?.outputTokens).toBeUndefined()
461
+ })
462
+ })
@@ -3,7 +3,7 @@ import { mkdtemp, rm } from "node:fs/promises"
3
3
  import { homedir, tmpdir } from "node:os"
4
4
  import path from "node:path"
5
5
  import { describe, expect, test } from "bun:test"
6
- import { computeJsonlPath, computeProjectDir, computeWorkflowsDir, encodeCwd } from "./jsonl-path.adapter"
6
+ import { computeProjectDir, computeWorkflowsDir, encodeCwd } from "./claude-projects-path.adapter"
7
7
 
8
8
  describe("encodeCwd", () => {
9
9
  test("absolute path: replaces / with -", () => {
@@ -23,24 +23,6 @@ describe("encodeCwd", () => {
23
23
  })
24
24
  })
25
25
 
26
- describe("computeJsonlPath", () => {
27
- test("combines homeDir + encoded cwd + session uuid", async () => {
28
- const tmp = await mkdtemp(path.join(tmpdir(), "kanna-jsonlpath-"))
29
- try {
30
- const realPath = realpathSync(tmp)
31
- const encodedCwd = realPath.replace(/[^a-zA-Z0-9]/g, "-")
32
- const result = computeJsonlPath({
33
- homeDir: "/home/u",
34
- cwd: tmp,
35
- sessionId: "abc-123",
36
- })
37
- expect(result).toBe(`/home/u/.claude/projects/${encodedCwd}/abc-123.jsonl`)
38
- } finally {
39
- await rm(tmp, { recursive: true, force: true })
40
- }
41
- })
42
- })
43
-
44
26
  describe("encodeCwd realpath + dot replacement", () => {
45
27
  test("resolves macOS /var -> /private/var symlink", async () => {
46
28
  const tmp = await mkdtemp(path.join(tmpdir(), "kanna-encodecwd-"))
@@ -39,17 +39,6 @@ export function computeProjectDir(args: {
39
39
  return path.join(args.homeDir, ".claude", "projects", encodeCwd(args.cwd))
40
40
  }
41
41
 
42
- export function computeJsonlPath(args: {
43
- homeDir: string
44
- cwd: string
45
- sessionId: string
46
- }): string {
47
- return path.join(
48
- computeProjectDir({ homeDir: args.homeDir, cwd: args.cwd }),
49
- `${args.sessionId}.jsonl`,
50
- )
51
- }
52
-
53
42
  export function computeWorkflowsDir(args: {
54
43
  homeDir: string
55
44
  cwd: string
@@ -42,7 +42,6 @@ const STUB_HANDLE = {
42
42
  closed: Promise.resolve(),
43
43
  setModel: async () => {},
44
44
  setPermissionMode: async () => {},
45
- getSupportedCommands: async () => [],
46
45
  }
47
46
 
48
47
  function makeTestSession(overrides: Partial<ConstructorParameters<typeof ClaudeSessionState>[0]> = {}): ClaudeSessionState {
@@ -1,10 +1,8 @@
1
- import { describe, test, expect, mock, beforeEach } from "bun:test"
1
+ import { describe, test, expect, mock } from "bun:test"
2
2
  import {
3
- resolveClaudeDriverPreference,
4
3
  getEnabledCustomMcpServers,
5
4
  buildOAuthBearers,
6
5
  resolveChatPolicy,
7
- killPtyInstance,
8
6
  type ClaudeSessionConfigHelpersDeps,
9
7
  } from "./claude-session-config-helpers"
10
8
  import { POLICY_DEFAULT } from "../shared/permission-policy"
@@ -31,51 +29,13 @@ function makeDeps(overrides: Partial<ClaudeSessionConfigHelpersDeps> = {}): Clau
31
29
  getAppSettingsSnapshot: () => ({}),
32
30
  chatPolicy: POLICY_DEFAULT,
33
31
  store: { state: null },
34
- ptyInstanceRegistry: null,
35
32
  ensureFreshToken: async () => "test-token",
36
33
  persistOAuthState: null,
37
- killProcessTree: async (_pid) => {},
38
34
  ...overrides,
39
35
  }
40
36
  }
41
37
 
42
38
 
43
- describe("resolveClaudeDriverPreference", () => {
44
- const originalEnv = process.env.KANNA_CLAUDE_DRIVER
45
-
46
- beforeEach(() => {
47
- delete process.env.KANNA_CLAUDE_DRIVER
48
- })
49
-
50
- test("returns 'pty' when settings preference is 'pty'", () => {
51
- const deps = makeDeps({
52
- getAppSettingsSnapshot: () => ({ claudeDriver: { preference: "pty" } }),
53
- })
54
- expect(resolveClaudeDriverPreference(deps)).toBe("pty")
55
- })
56
-
57
- test("returns 'sdk' when settings preference is 'sdk'", () => {
58
- const deps = makeDeps({
59
- getAppSettingsSnapshot: () => ({ claudeDriver: { preference: "sdk" } }),
60
- })
61
- expect(resolveClaudeDriverPreference(deps)).toBe("sdk")
62
- })
63
-
64
- test("falls through to env var when settings have no preference", () => {
65
- process.env.KANNA_CLAUDE_DRIVER = "pty"
66
- const deps = makeDeps({ getAppSettingsSnapshot: () => ({}) })
67
- expect(resolveClaudeDriverPreference(deps)).toBe("pty")
68
- process.env.KANNA_CLAUDE_DRIVER = originalEnv ?? ""
69
- if (!originalEnv) delete process.env.KANNA_CLAUDE_DRIVER
70
- })
71
-
72
- test("defaults to 'sdk' when no settings and no env var", () => {
73
- const deps = makeDeps({ getAppSettingsSnapshot: () => ({}) })
74
- expect(resolveClaudeDriverPreference(deps)).toBe("sdk")
75
- })
76
- })
77
-
78
-
79
39
  describe("getEnabledCustomMcpServers", () => {
80
40
  test("returns only enabled servers", () => {
81
41
  const enabled = makeServer({ id: "s1", enabled: true })
@@ -245,52 +205,3 @@ describe("resolveChatPolicy", () => {
245
205
  expect(result).toEqual(POLICY_DEFAULT)
246
206
  })
247
207
  })
248
-
249
-
250
- describe("killPtyInstance", () => {
251
- test("throws when no PTY instance found for chat", async () => {
252
- const deps = makeDeps({
253
- ptyInstanceRegistry: {
254
- snapshot: () => [],
255
- markExitedIfCurrent: () => {},
256
- },
257
- })
258
- await expect(killPtyInstance(deps, "chat-1")).rejects.toThrow("No live PTY instance for chat")
259
- })
260
-
261
- test("throws when PTY instance has no pid", async () => {
262
- const deps = makeDeps({
263
- ptyInstanceRegistry: {
264
- snapshot: () => [{ chatId: "chat-1", pid: null }],
265
- markExitedIfCurrent: () => {},
266
- },
267
- })
268
- await expect(killPtyInstance(deps, "chat-1")).rejects.toThrow("No live PTY instance for chat")
269
- })
270
-
271
- test("calls killProcessTree with the instance pid", async () => {
272
- const killed: number[] = []
273
- const deps = makeDeps({
274
- ptyInstanceRegistry: {
275
- snapshot: () => [{ chatId: "chat-1", pid: 42 }],
276
- markExitedIfCurrent: () => {},
277
- },
278
- killProcessTree: async (pid) => { killed.push(pid) },
279
- })
280
- await killPtyInstance(deps, "chat-1")
281
- expect(killed).toEqual([42])
282
- })
283
-
284
- test("calls markExitedIfCurrent after killProcessTree", async () => {
285
- const marked: Array<{ chatId: string; pid: number }> = []
286
- const deps = makeDeps({
287
- ptyInstanceRegistry: {
288
- snapshot: () => [{ chatId: "chat-1", pid: 99 }],
289
- markExitedIfCurrent: (chatId, pid) => { marked.push({ chatId, pid }) },
290
- },
291
- })
292
- await killPtyInstance(deps, "chat-1")
293
- expect(marked).toHaveLength(1)
294
- expect(marked[0]).toMatchObject({ chatId: "chat-1", pid: 99 })
295
- })
296
- })