@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.
- package/README.md +5 -9
- package/dist/client/assets/{BoardsPage-BJalB-bc.js → BoardsPage-AckCuswi.js} +1 -1
- package/dist/client/assets/BoardsRoutePage-DzKwKy3f.js +1 -0
- package/dist/client/assets/ChatPage-DsaH8CTz.js +93 -0
- package/dist/client/assets/{CronJobRow-CPbO_jwU.js → CronJobRow-Bx9gRqKW.js} +1 -1
- package/dist/client/assets/{CronJobsPage-Ck3SNgGi.js → CronJobsPage-v0-2X1oX.js} +1 -1
- package/dist/client/assets/{FilePreviewSheet-XEuUtDPn.js → FilePreviewSheet-B6SDxSD0.js} +2 -2
- package/dist/client/assets/{FlowSurface-BCbeMTJe.js → FlowSurface-CLC-ZFar.js} +1 -1
- package/dist/client/assets/{GenUIBlock-BJy_dfs-.js → GenUIBlock-CC79Ujro.js} +2 -2
- package/dist/client/assets/{KannaSocketProvider-D7Lzlt35.js → KannaSocketProvider-B2sHsWP5.js} +1 -1
- package/dist/client/assets/{LocalProjectsPage-9LJkQjhT.js → LocalProjectsPage-DYBkcEAm.js} +1 -1
- package/dist/client/assets/SettingsPage-CZqMkMRW.js +9 -0
- package/dist/client/assets/{StackBoardsRoutePage-t_EaFNG4.js → StackBoardsRoutePage-Clviz0hX.js} +1 -1
- package/dist/client/assets/{ToolCallMessage-RbF9XT2B.js → ToolCallMessage-BfNbWGmc.js} +1 -1
- package/dist/client/assets/{VChartSurface-CKuPelsn.js → VChartSurface-CUbxyBo3.js} +1 -1
- package/dist/client/assets/{WorkflowsPage-B6qUmQA3.js → WorkflowsPage-rengzb97.js} +1 -1
- package/dist/client/assets/{WorkflowsSection-DxO0S4An.js → WorkflowsSection-DuXdLsMO.js} +1 -1
- package/dist/client/assets/{chart-renderer-DjYZvNxh.js → chart-renderer-D7ukB2wg.js} +1 -1
- package/dist/client/assets/{chatSounds-FD2DQzF6.js → chatSounds-BLg2xPDZ.js} +1 -1
- package/dist/client/assets/chevron-left-RbFvmeyK.js +1 -0
- package/dist/client/assets/{clipboard.adapter-GN9nnYak.js → clipboard.adapter-CgdNwn-1.js} +1 -1
- package/dist/client/assets/{columnStyle-DjA7hA9k.js → columnStyle-CMCF0ds6.js} +1 -1
- package/dist/client/assets/{createScopedStore-BoLB9Rhc.js → createScopedStore-BrD5NHWx.js} +1 -1
- package/dist/client/assets/{dist-CimtOoGK.js → dist-DNSTHlSo.js} +1 -1
- package/dist/client/assets/editor-icons-DY4uKeEY.js +1 -0
- package/dist/client/assets/{ellipsis-Cd7sO0LO.js → ellipsis-OHg0x_x1.js} +1 -1
- package/dist/client/assets/{flow-kinds-CW2TU_83.js → flow-kinds-Ki2DP9nP.js} +1 -1
- package/dist/client/assets/{folder-C7sXCaHj.js → folder-CnWzWIG5.js} +1 -1
- package/dist/client/assets/{formatters-wSVyvDFZ.js → formatters-BGORekd-.js} +1 -1
- package/dist/client/assets/{genui-EzBIAYFE.js → genui-C9i6j0XZ.js} +1 -1
- package/dist/client/assets/{globe-xmRpmpma.js → globe-BjR1tyE4.js} +1 -1
- package/dist/client/assets/{host-DCaA9W-Z.js → host-D9tJwZXl.js} +1 -1
- package/dist/client/assets/{index-CDqjh7nt.js → index-CesRxZ0X.js} +7 -7
- package/dist/client/assets/index-CxDE9VOy.css +1 -0
- package/dist/client/assets/{info-D4MMvZFe.js → info-DNYekoE-.js} +1 -1
- package/dist/client/assets/{input-C4fvDiQ4.js → input-DgW9uRZN.js} +1 -1
- package/dist/client/assets/{lexicalToReact-sPUyOWk6.js → lexicalToReact-CZvp9Pgz.js} +3 -3
- package/dist/client/assets/{log-aTaDVF_e.js → log-DnEvbp3E.js} +1 -1
- package/dist/client/assets/{monitor-_CRKcV1h.js → monitor-qbWfUdBG.js} +1 -1
- package/dist/client/assets/{pencil-fNBeUUCf.js → pencil-C965rrpd.js} +1 -1
- package/dist/client/assets/{pendingActionsStore-COTh4Ztq.js → pendingActionsStore-CxhI19V9.js} +1 -1
- package/dist/client/assets/{play-BAmkJfn6.js → play-uofLR7yT.js} +1 -1
- package/dist/client/assets/{plus-c75CkNEO.js → plus-D4CI6rj1.js} +1 -1
- package/dist/client/assets/{popover-CHM4skzh.js → popover-Bc646hKn.js} +1 -1
- package/dist/client/assets/{pushClient-BJH_ITRB.js → pushClient-CxnHu9h4.js} +1 -1
- package/dist/client/assets/runDetached-DfQXHAaY.js +1 -0
- package/dist/client/assets/{segmented-control-DNOwRupT.js → segmented-control-CKGX29Kl.js} +1 -1
- package/dist/client/assets/{select-dLHiAls6.js → select-BjeajpVt.js} +1 -1
- package/dist/client/assets/{settings-header-button-CGKCzFyk.js → settings-header-button-BYvHR6Xl.js} +1 -1
- package/dist/client/assets/{sidebarPendingActions-Dyp80Lv_.js → sidebarPendingActions-xgMTnfRY.js} +1 -1
- package/dist/client/assets/{square-pen-B1IxmScM.js → square-pen-DmppQdTd.js} +1 -1
- package/dist/client/assets/{state-mark-BhTZfcYe.js → state-mark-C3kEL1hX.js} +1 -1
- package/dist/client/assets/{status-pill-CwhJhaMy.js → status-pill-Bo1Nb6HX.js} +1 -1
- package/dist/client/assets/{terminal-Bnj7qFGw.js → terminal-DfCF-J4p.js} +1 -1
- package/dist/client/assets/{timer.adapter-DHt2zwsL.js → timer.adapter-C2RhP4d6.js} +1 -1
- package/dist/client/assets/{tools-3gJcj3hK.js → tools-t5T_3UBg.js} +1 -1
- package/dist/client/assets/{triangle-alert-CWrc0CIO.js → triangle-alert-CYQhobyY.js} +1 -1
- package/dist/client/assets/useAppGlobalState-DUa3pkCF.js +1 -0
- package/dist/client/assets/useKannaState-FcieR5qb.js +5 -0
- package/dist/client/assets/useTheme-D9sKDPEm.js +1 -0
- package/dist/client/index.html +35 -35
- package/package.json +2 -2
- package/src/server/agent-coordinator-types.ts +0 -6
- package/src/server/agent-coordinator.ts +6 -45
- package/src/server/agent.compaction.test.ts +1 -13
- package/src/server/agent.oauth-account-info.test.ts +0 -3
- package/src/server/agent.oauth-release.test.ts +0 -2
- package/src/server/agent.oauth-rotation.test.ts +0 -9
- package/src/server/agent.oauth-token-share-cap.test.ts +0 -1
- package/src/server/agent.openrouter-model.test.ts +0 -2
- package/src/server/agent.openrouter-watchdog.test.ts +0 -3
- package/src/server/agent.sdk-workflow-register.test.ts +2 -64
- package/src/server/agent.test.ts +1 -319
- package/src/server/app-settings.test.ts +14 -17
- package/src/server/app-settings.ts +22 -37
- package/src/server/auto-continue/e2e.test.ts +0 -1
- package/src/server/background-task-output-registry.test.ts +1 -1
- package/src/server/background-task-output-registry.ts +1 -1
- package/src/server/claude-cancel-handler.test.ts +0 -58
- package/src/server/claude-cancel-handler.ts +1 -17
- package/src/server/claude-harness-stream.test.ts +134 -0
- package/src/server/{claude-pty/jsonl-path.test.ts → claude-projects-path.adapter.test.ts} +1 -19
- package/src/server/{claude-pty/jsonl-path.adapter.ts → claude-projects-path.adapter.ts} +0 -11
- package/src/server/claude-send-command.test.ts +0 -1
- package/src/server/claude-session-config-helpers.test.ts +1 -90
- package/src/server/claude-session-config-helpers.ts +1 -38
- package/src/server/claude-session-error-handler.test.ts +0 -1
- package/src/server/claude-session-lifecycle.test.ts +2 -36
- package/src/server/claude-session-lifecycle.ts +2 -8
- package/src/server/claude-session-runner.test.ts +8 -36
- package/src/server/claude-session-runner.ts +1 -26
- package/src/server/claude-session-spawner.test.ts +5 -64
- package/src/server/claude-session-spawner.ts +49 -115
- package/src/server/claude-session-start.test.ts +1 -14
- package/src/server/claude-session-start.ts +0 -8
- package/src/server/claude-session-state-queries.test.ts +1 -2
- package/src/server/claude-session-state.test.ts +0 -1
- package/src/server/claude-session-state.ts +0 -4
- package/src/server/claude-subagent-wiring.test.ts +2 -68
- package/src/server/claude-subagent-wiring.ts +3 -50
- package/src/server/claude-turn-starter-types.ts +0 -3
- package/src/server/claude-turn-starter.test.ts +0 -28
- package/src/server/claude-turn-starter.ts +0 -5
- package/src/server/event-store.test.ts +0 -2
- package/src/server/events.ts +1 -2
- package/src/server/harness-types.ts +1 -2
- package/src/server/import-subagent-drill.e2e.test.ts +1 -1
- package/src/server/imported-subagents-dir.test.ts +1 -1
- package/src/server/imported-subagents-dir.ts +1 -1
- package/src/server/kanna-mcp.test.ts +0 -75
- package/src/server/kanna-mcp.ts +1 -47
- package/src/server/server.test.ts +5 -7
- package/src/server/server.ts +2 -18
- package/src/server/subagent-orchestrator.test.ts +0 -1
- package/src/server/subagent-provider-run.test.ts +48 -10
- package/src/server/subagent-provider-run.ts +12 -3
- package/src/server/tool-callback.test.ts +1 -1
- package/src/server/ws-router-agent-ctrl.test.ts +0 -45
- package/src/server/ws-router-agent-ctrl.ts +1 -31
- package/src/server/ws-router-broadcast.ts +0 -27
- package/src/server/ws-router-defaults.test.ts +2 -3
- package/src/server/ws-router-defaults.ts +2 -3
- package/src/server/ws-router-envelope.ts +0 -15
- package/src/server/ws-router-observability.test.ts +1 -1
- package/src/server/ws-router-settings.test.ts +2 -2
- package/src/server/ws-router.test.ts +2 -2
- package/src/server/ws-router.ts +0 -8
- package/src/shared/app-settings-types.ts +9 -20
- package/src/shared/claude-api-error-classification.ts +1 -1
- package/src/shared/protocol.ts +1 -11
- package/dist/client/assets/BoardsRoutePage-BTTjJX-Y.js +0 -1
- package/dist/client/assets/ChatPage-DH8wtQyD.js +0 -93
- package/dist/client/assets/SettingsPage-CKrZ6dGQ.js +0 -9
- package/dist/client/assets/chevron-left-CJ0qv9kF.js +0 -1
- package/dist/client/assets/editor-icons-CONDQZof.js +0 -1
- package/dist/client/assets/index-C-UYaDk6.css +0 -1
- package/dist/client/assets/runDetached-DqQ3k2ji.js +0 -1
- package/dist/client/assets/useAppGlobalState-DKOGrO5l.js +0 -1
- package/dist/client/assets/useKannaState-D7R_-dM6.js +0 -5
- package/dist/client/assets/useTheme-B57LIKyx.js +0 -1
- package/src/server/agent.pty-rotation.test.ts +0 -322
- package/src/server/claude-pty/auth.test.ts +0 -38
- package/src/server/claude-pty/auth.ts +0 -17
- package/src/server/claude-pty/claude-session-registry.adapter.ts +0 -68
- package/src/server/claude-pty/claude-session-registry.test.ts +0 -149
- package/src/server/claude-pty/driver.test.ts +0 -1153
- package/src/server/claude-pty/driver.ts +0 -829
- package/src/server/claude-pty/env.test.ts +0 -79
- package/src/server/claude-pty/env.ts +0 -20
- package/src/server/claude-pty/jsonl-to-event.test.ts +0 -882
- package/src/server/claude-pty/jsonl-to-event.ts +0 -357
- package/src/server/claude-pty/parity-matrix.test.ts +0 -437
- package/src/server/claude-pty/pid-registry.adapter.ts +0 -164
- package/src/server/claude-pty/pid-registry.test.ts +0 -178
- package/src/server/claude-pty/preflight/binary-fingerprint.adapter.ts +0 -20
- package/src/server/claude-pty/preflight/binary-fingerprint.test.ts +0 -32
- package/src/server/claude-pty/pty-instance-registry.test.ts +0 -208
- package/src/server/claude-pty/pty-instance-registry.ts +0 -172
- package/src/server/claude-pty/pty-memory-sampler.adapter.test.ts +0 -103
- package/src/server/claude-pty/pty-memory-sampler.adapter.ts +0 -86
- package/src/server/claude-pty/pty-process.adapter.ts +0 -60
- package/src/server/claude-pty/pty-process.test.ts +0 -49
- package/src/server/claude-pty/resolve-binary.adapter.ts +0 -106
- package/src/server/claude-pty/resolve-binary.test.ts +0 -118
- package/src/server/claude-pty/runtime-dir.adapter.ts +0 -19
- package/src/server/claude-pty/sandbox/detect.adapter.ts +0 -17
- package/src/server/claude-pty/sandbox/detect.test.ts +0 -17
- package/src/server/claude-pty/sandbox/platform.test.ts +0 -35
- package/src/server/claude-pty/sandbox/platform.ts +0 -27
- package/src/server/claude-pty/sandbox/preflight-io.adapter.ts +0 -14
- package/src/server/claude-pty/sandbox/preflight.test.ts +0 -69
- package/src/server/claude-pty/sandbox/preflight.ts +0 -44
- package/src/server/claude-pty/sandbox/profile-linux.adapter.ts +0 -63
- package/src/server/claude-pty/sandbox/profile-linux.test.ts +0 -90
- package/src/server/claude-pty/sandbox/profile-macos.adapter.ts +0 -72
- package/src/server/claude-pty/sandbox/profile-macos.test.ts +0 -68
- package/src/server/claude-pty/sandbox/wrap.adapter.ts +0 -58
- package/src/server/claude-pty/sandbox/wrap.test.ts +0 -82
- package/src/server/claude-pty/settings-writer.adapter.ts +0 -23
- package/src/server/claude-pty/settings-writer.test.ts +0 -22
- package/src/server/claude-pty/smoke-test-io.adapter.ts +0 -28
- package/src/server/claude-pty/smoke-test.test.ts +0 -199
- package/src/server/claude-pty/smoke-test.ts +0 -182
- package/src/server/claude-pty/tui-control.test.ts +0 -300
- package/src/server/claude-pty/tui-control.ts +0 -174
- package/src/server/claude-pty/tui-source.adapter.test.ts +0 -374
- package/src/server/claude-pty/tui-source.adapter.ts +0 -294
- package/src/server/http-server.adapter.test.ts +0 -18
- package/src/server/http-server.adapter.ts +0 -39
- package/src/server/kanna-mcp-http.test.ts +0 -274
- package/src/server/kanna-mcp-http.ts +0 -194
- package/src/server/kanna-mcp-tools/ask-user-question.test.ts +0 -108
- package/src/server/kanna-mcp-tools/ask-user-question.ts +0 -55
- package/src/server/kanna-mcp-tools/exit-plan-mode.test.ts +0 -61
- package/src/server/kanna-mcp-tools/exit-plan-mode.ts +0 -49
- package/src/server/mcp-zod-compat.adapter.ts +0 -20
- package/src/shared/pty-instance.ts +0 -43
- /package/src/server/{claude-pty/output-ring.test.ts → output-ring.test.ts} +0 -0
- /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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 <
|
|
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 ${
|
|
742
|
+
`claudeDriver.lifecycle.idleTimeoutMs must be between ${CLAUDE_IDLE_TIMEOUT_MS_MIN} and ${CLAUDE_IDLE_TIMEOUT_MS_MAX}`,
|
|
745
743
|
)
|
|
746
|
-
idleTimeoutMs = clampNumber(idleRaw,
|
|
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 =
|
|
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 <
|
|
754
|
+
} else if (maxRaw < CLAUDE_MAX_CONCURRENT_MIN || maxRaw > CLAUDE_MAX_CONCURRENT_MAX) {
|
|
757
755
|
warnings.push(
|
|
758
|
-
`claudeDriver.lifecycle.maxConcurrent must be between ${
|
|
756
|
+
`claudeDriver.lifecycle.maxConcurrent must be between ${CLAUDE_MAX_CONCURRENT_MIN} and ${CLAUDE_MAX_CONCURRENT_MAX}`,
|
|
759
757
|
)
|
|
760
|
-
maxConcurrent = clampNumber(maxRaw,
|
|
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: { ...
|
|
772
|
+
lifecycle: { ...CLAUDE_LIFECYCLE_DEFAULTS },
|
|
775
773
|
}
|
|
776
774
|
}
|
|
777
|
-
|
|
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
|
-
|
|
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 <
|
|
1726
|
-
throw new Error(`claudeDriver.lifecycle.idleTimeoutMs must be between ${
|
|
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 <
|
|
1733
|
-
throw new Error(`claudeDriver.lifecycle.maxConcurrent must be between ${
|
|
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 "./
|
|
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)
|
|
@@ -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 {
|
|
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 {
|
|
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
|
|
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
|
-
})
|