@cuongtran001/kanna 1.63.2 → 1.64.0
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--7VsnbXM.js} +1 -1
- package/dist/client/assets/BoardsRoutePage-Hkl_oQv0.js +1 -0
- package/dist/client/assets/ChatPage-h-VTkkkt.js +93 -0
- package/dist/client/assets/{CronJobRow-CPbO_jwU.js → CronJobRow-eb86NFF7.js} +1 -1
- package/dist/client/assets/{CronJobsPage-Ck3SNgGi.js → CronJobsPage-B0NHa3ss.js} +1 -1
- package/dist/client/assets/{FilePreviewSheet-XEuUtDPn.js → FilePreviewSheet-DOEK5SRs.js} +2 -2
- package/dist/client/assets/{FlowSurface-BCbeMTJe.js → FlowSurface-DPotbbWr.js} +1 -1
- package/dist/client/assets/{GenUIBlock-BJy_dfs-.js → GenUIBlock-BNZfT-Vk.js} +2 -2
- package/dist/client/assets/{KannaSocketProvider-D7Lzlt35.js → KannaSocketProvider-DtKR165f.js} +1 -1
- package/dist/client/assets/{LocalProjectsPage-9LJkQjhT.js → LocalProjectsPage-DNLMYHFL.js} +1 -1
- package/dist/client/assets/SettingsPage-B3bF5Xse.js +9 -0
- package/dist/client/assets/{StackBoardsRoutePage-t_EaFNG4.js → StackBoardsRoutePage-DplbkiEr.js} +1 -1
- package/dist/client/assets/{ToolCallMessage-RbF9XT2B.js → ToolCallMessage-BJQRDH0E.js} +1 -1
- package/dist/client/assets/{VChartSurface-CKuPelsn.js → VChartSurface-gDrFdsRD.js} +1 -1
- package/dist/client/assets/{WorkflowsPage-B6qUmQA3.js → WorkflowsPage-RDNDgswE.js} +1 -1
- package/dist/client/assets/{WorkflowsSection-DxO0S4An.js → WorkflowsSection-8tzLFcmi.js} +1 -1
- package/dist/client/assets/browser-uLao0xGx.js +3 -0
- package/dist/client/assets/{chart-renderer-DjYZvNxh.js → chart-renderer-Bp9ekEsh.js} +1 -1
- package/dist/client/assets/{chatSounds-FD2DQzF6.js → chatSounds-4LPsoFE0.js} +1 -1
- package/dist/client/assets/chevron-left-BXDenZrj.js +1 -0
- package/dist/client/assets/{clipboard.adapter-GN9nnYak.js → clipboard.adapter-V4RFuBQg.js} +1 -1
- package/dist/client/assets/{columnStyle-DjA7hA9k.js → columnStyle-CPsUAD1y.js} +1 -1
- package/dist/client/assets/{createScopedStore-BoLB9Rhc.js → createScopedStore-vi_qBrnv.js} +1 -1
- package/dist/client/assets/{dist-CimtOoGK.js → dist-CQv2I5w0.js} +1 -1
- package/dist/client/assets/editor-icons-DSEWhYdt.js +1 -0
- package/dist/client/assets/{ellipsis-Cd7sO0LO.js → ellipsis-Dw447mDf.js} +1 -1
- package/dist/client/assets/{flow-kinds-CW2TU_83.js → flow-kinds-Dctbq8tY.js} +1 -1
- package/dist/client/assets/{folder-C7sXCaHj.js → folder-K_gAJ1DI.js} +1 -1
- package/dist/client/assets/{formatters-wSVyvDFZ.js → formatters-BGORekd-.js} +1 -1
- package/dist/client/assets/{genui-EzBIAYFE.js → genui-buyzerHE.js} +1 -1
- package/dist/client/assets/{globe-xmRpmpma.js → globe-D0uBqjk3.js} +1 -1
- package/dist/client/assets/{host-DCaA9W-Z.js → host-Bmj1x1Ur.js} +1 -1
- package/dist/client/assets/{index-CDqjh7nt.js → index-BioMVpYd.js} +7 -7
- package/dist/client/assets/index-CxDE9VOy.css +1 -0
- package/dist/client/assets/{info-D4MMvZFe.js → info-XTOdbyHd.js} +1 -1
- package/dist/client/assets/{input-C4fvDiQ4.js → input-BdIYegqO.js} +1 -1
- package/dist/client/assets/{lexicalToReact-sPUyOWk6.js → lexicalToReact-DqgmPzHx.js} +3 -3
- package/dist/client/assets/{log-aTaDVF_e.js → log-CKbWpKMT.js} +1 -1
- package/dist/client/assets/{monitor-_CRKcV1h.js → monitor-CGybOpir.js} +1 -1
- package/dist/client/assets/{pencil-fNBeUUCf.js → pencil-B8yLmanR.js} +1 -1
- package/dist/client/assets/{pendingActionsStore-COTh4Ztq.js → pendingActionsStore-CdwEvP-X.js} +1 -1
- package/dist/client/assets/{play-BAmkJfn6.js → play-BmOQO8Wo.js} +1 -1
- package/dist/client/assets/{plus-c75CkNEO.js → plus-wpoGLqmM.js} +1 -1
- package/dist/client/assets/{popover-CHM4skzh.js → popover-B9W07k-v.js} +1 -1
- package/dist/client/assets/{pushClient-BJH_ITRB.js → pushClient-C3BFEIhM.js} +1 -1
- package/dist/client/assets/runDetached-B3NSxNTV.js +1 -0
- package/dist/client/assets/{segmented-control-DNOwRupT.js → segmented-control-D1eTj6vZ.js} +1 -1
- package/dist/client/assets/{select-dLHiAls6.js → select-rChS1ZvA.js} +1 -1
- package/dist/client/assets/{settings-header-button-CGKCzFyk.js → settings-header-button-D0i3JySg.js} +1 -1
- package/dist/client/assets/{sidebarPendingActions-Dyp80Lv_.js → sidebarPendingActions-BKojWYqD.js} +1 -1
- package/dist/client/assets/{square-pen-B1IxmScM.js → square-pen-C_M6NWE6.js} +1 -1
- package/dist/client/assets/{state-mark-BhTZfcYe.js → state-mark-D71tSKL1.js} +1 -1
- package/dist/client/assets/{status-pill-CwhJhaMy.js → status-pill-IuanKijZ.js} +1 -1
- package/dist/client/assets/{terminal-Bnj7qFGw.js → terminal-ChU65qN4.js} +1 -1
- package/dist/client/assets/{timer.adapter-DHt2zwsL.js → timer.adapter-DxStl6YQ.js} +1 -1
- package/dist/client/assets/{tools-3gJcj3hK.js → tools-DcwSZn_Y.js} +1 -1
- package/dist/client/assets/{triangle-alert-CWrc0CIO.js → triangle-alert-CVNaunqf.js} +1 -1
- package/dist/client/assets/useAppGlobalState-MUKjQL55.js +1 -0
- package/dist/client/assets/useKannaState-DiOIS_Z6.js +5 -0
- package/dist/client/assets/useTheme-DpVQlqks.js +1 -0
- package/dist/client/index.html +35 -35
- package/package.json +5 -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 +17 -20
- package/src/server/app-settings.ts +22 -37
- package/src/server/auth.test.ts +5 -1
- 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/http-api-routes.ts +27 -103
- package/src/server/http-dispatcher.test.ts +7 -9
- package/src/server/http-dispatcher.ts +16 -3
- package/src/server/http-file-response.ts +92 -0
- 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 +3 -22
- 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/tus-uploads.adapter.ts +142 -0
- package/src/server/uploads.adapter.ts +133 -9
- package/src/server/uploads.test.ts +295 -120
- package/src/server/uploads.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/src/shared/settings/uploads.test.ts +8 -0
- package/src/shared/settings/uploads.ts +2 -2
- 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
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import type { PtyProcess } from "./pty-process.adapter"
|
|
2
|
-
import type { OutputRing } from "./output-ring"
|
|
3
|
-
|
|
4
|
-
export const TRUST_DIALOG_MARKER = "trust this folder"
|
|
5
|
-
export const DEV_CHANNELS_DIALOG_MARKER = "local channel development"
|
|
6
|
-
export const TUI_READY_MARKER = "❯ "
|
|
7
|
-
export const TUI_READY_HARD_CAP_DEFAULT_MS = 3000
|
|
8
|
-
export const TUI_READY_QUIET_DEFAULT_MS = 300
|
|
9
|
-
|
|
10
|
-
function stripAnsi(s: string): string {
|
|
11
|
-
return s
|
|
12
|
-
.replace(/\x1b\[[0-9;]*[A-Za-z]/g, " ")
|
|
13
|
-
.replace(/\x1b./g, "")
|
|
14
|
-
.replace(/\u00a0/g, " ")
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface WaitForTuiReadyOpts {
|
|
18
|
-
hardCapMs?: number
|
|
19
|
-
pollMs?: number
|
|
20
|
-
quietPeriodMs?: number
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function waitForTuiReady(
|
|
24
|
-
ring: OutputRing,
|
|
25
|
-
opts: WaitForTuiReadyOpts = {},
|
|
26
|
-
): Promise<"marker" | "timeout"> {
|
|
27
|
-
const hardCapMs = opts.hardCapMs ?? TUI_READY_HARD_CAP_DEFAULT_MS
|
|
28
|
-
const pollMs = opts.pollMs ?? 50
|
|
29
|
-
const quietPeriodMs = opts.quietPeriodMs ?? TUI_READY_QUIET_DEFAULT_MS
|
|
30
|
-
const start = Date.now()
|
|
31
|
-
while (true) {
|
|
32
|
-
if (stripAnsi(ring.tail()).includes(TUI_READY_MARKER)) {
|
|
33
|
-
await waitForRingQuiet(ring, { quietMs: quietPeriodMs, pollMs, deadline: start + hardCapMs })
|
|
34
|
-
return "marker"
|
|
35
|
-
}
|
|
36
|
-
if (Date.now() - start >= hardCapMs) return "timeout"
|
|
37
|
-
await new Promise((r) => setTimeout(r, pollMs))
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function waitForRingQuiet(
|
|
42
|
-
ring: OutputRing,
|
|
43
|
-
opts: { quietMs: number; pollMs: number; deadline: number },
|
|
44
|
-
): Promise<void> {
|
|
45
|
-
if (opts.quietMs <= 0) return
|
|
46
|
-
let lastLength = ring.tail().length
|
|
47
|
-
let quietStart = Date.now()
|
|
48
|
-
while (Date.now() - quietStart < opts.quietMs) {
|
|
49
|
-
if (Date.now() >= opts.deadline) return
|
|
50
|
-
await new Promise((r) => setTimeout(r, opts.pollMs))
|
|
51
|
-
const currentLength = ring.tail().length
|
|
52
|
-
if (currentLength !== lastLength) {
|
|
53
|
-
lastLength = currentLength
|
|
54
|
-
quietStart = Date.now()
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function dismissTrustDialogIfPresent(
|
|
60
|
-
pty: PtyProcess,
|
|
61
|
-
ring: OutputRing,
|
|
62
|
-
): Promise<boolean> {
|
|
63
|
-
if (!stripAnsi(ring.tail()).includes(TRUST_DIALOG_MARKER)) return false
|
|
64
|
-
await pty.sendInput("\r")
|
|
65
|
-
return true
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export async function dismissDevChannelsDialogIfPresent(
|
|
69
|
-
pty: PtyProcess,
|
|
70
|
-
ring: OutputRing,
|
|
71
|
-
): Promise<boolean> {
|
|
72
|
-
if (!stripAnsi(ring.tail()).includes(DEV_CHANNELS_DIALOG_MARKER)) return false
|
|
73
|
-
await pty.sendInput("\r")
|
|
74
|
-
return true
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface WaitForTuiReadyWithTrustDismissOpts {
|
|
78
|
-
hardCapMs?: number
|
|
79
|
-
pollMs?: number
|
|
80
|
-
quietPeriodMs?: number
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export async function waitForTuiReadyWithTrustDismiss(
|
|
84
|
-
pty: PtyProcess,
|
|
85
|
-
ring: OutputRing,
|
|
86
|
-
opts: WaitForTuiReadyWithTrustDismissOpts = {},
|
|
87
|
-
): Promise<"ready" | "timeout"> {
|
|
88
|
-
const hardCapMs = opts.hardCapMs ?? 15_000
|
|
89
|
-
const pollMs = opts.pollMs ?? 50
|
|
90
|
-
const quietPeriodMs = opts.quietPeriodMs ?? TUI_READY_QUIET_DEFAULT_MS
|
|
91
|
-
const start = Date.now()
|
|
92
|
-
let trustDismissed = false
|
|
93
|
-
let postDismissOffset = 0
|
|
94
|
-
|
|
95
|
-
while (Date.now() - start < hardCapMs) {
|
|
96
|
-
const raw = ring.tail()
|
|
97
|
-
if (!trustDismissed && stripAnsi(raw).includes(TRUST_DIALOG_MARKER)) {
|
|
98
|
-
postDismissOffset = raw.length
|
|
99
|
-
await pty.sendInput("\r")
|
|
100
|
-
trustDismissed = true
|
|
101
|
-
} else {
|
|
102
|
-
const checkWindow = trustDismissed ? raw.slice(postDismissOffset) : raw
|
|
103
|
-
if (stripAnsi(checkWindow).includes(TUI_READY_MARKER)) {
|
|
104
|
-
await waitForRingQuiet(ring, { quietMs: quietPeriodMs, pollMs, deadline: start + hardCapMs })
|
|
105
|
-
return "ready"
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
await new Promise((r) => setTimeout(r, pollMs))
|
|
109
|
-
}
|
|
110
|
-
return "timeout"
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export async function waitForTuiReadyDismissingDialogs(
|
|
114
|
-
pty: PtyProcess,
|
|
115
|
-
ring: OutputRing,
|
|
116
|
-
opts: { hardCapMs?: number; pollMs?: number } = {},
|
|
117
|
-
): Promise<"marker" | "timeout"> {
|
|
118
|
-
const hardCapMs = opts.hardCapMs ?? TUI_READY_HARD_CAP_DEFAULT_MS + 5_000
|
|
119
|
-
const pollMs = opts.pollMs ?? 50
|
|
120
|
-
const start = Date.now()
|
|
121
|
-
let trustDone = false
|
|
122
|
-
let devDone = false
|
|
123
|
-
let postDismissOffset = 0
|
|
124
|
-
while (Date.now() - start < hardCapMs) {
|
|
125
|
-
const raw = ring.tail()
|
|
126
|
-
const view = stripAnsi(raw)
|
|
127
|
-
if (!trustDone && view.includes(TRUST_DIALOG_MARKER)) {
|
|
128
|
-
postDismissOffset = raw.length
|
|
129
|
-
await pty.sendInput("\r")
|
|
130
|
-
trustDone = true
|
|
131
|
-
await new Promise((r) => setTimeout(r, pollMs))
|
|
132
|
-
continue
|
|
133
|
-
}
|
|
134
|
-
if (!devDone && view.includes(DEV_CHANNELS_DIALOG_MARKER)) {
|
|
135
|
-
postDismissOffset = raw.length
|
|
136
|
-
await pty.sendInput("\r")
|
|
137
|
-
devDone = true
|
|
138
|
-
await new Promise((r) => setTimeout(r, pollMs))
|
|
139
|
-
continue
|
|
140
|
-
}
|
|
141
|
-
if (devDone && stripAnsi(raw.slice(postDismissOffset)).includes(TUI_READY_MARKER)) {
|
|
142
|
-
return "marker"
|
|
143
|
-
}
|
|
144
|
-
await new Promise((r) => setTimeout(r, pollMs))
|
|
145
|
-
}
|
|
146
|
-
return "timeout"
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export interface SendUserPromptOpts {
|
|
150
|
-
commitTimeoutMs?: number
|
|
151
|
-
pollMs?: number
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export async function sendUserPrompt(
|
|
155
|
-
pty: PtyProcess,
|
|
156
|
-
ring: OutputRing,
|
|
157
|
-
text: string,
|
|
158
|
-
opts: SendUserPromptOpts = {},
|
|
159
|
-
): Promise<void> {
|
|
160
|
-
const commitTimeoutMs = opts.commitTimeoutMs ?? 2_000
|
|
161
|
-
const pollMs = opts.pollMs ?? 10
|
|
162
|
-
const baseline = ring.tail().length
|
|
163
|
-
await pty.sendInput(`\x1b[200~${text}\x1b[201~`)
|
|
164
|
-
const deadline = Date.now() + commitTimeoutMs
|
|
165
|
-
while (Date.now() < deadline) {
|
|
166
|
-
if (ring.tail().length > baseline) break
|
|
167
|
-
await new Promise((r) => setTimeout(r, pollMs))
|
|
168
|
-
}
|
|
169
|
-
await pty.sendInput("\r")
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export async function sendExitCommand(pty: PtyProcess): Promise<void> {
|
|
173
|
-
await pty.sendInput("/exit\r")
|
|
174
|
-
}
|
|
@@ -1,374 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test, beforeEach, afterEach } from "bun:test"
|
|
2
|
-
import { mkdtemp, rm, writeFile, mkdir, appendFile } from "node:fs/promises"
|
|
3
|
-
import { tmpdir } from "node:os"
|
|
4
|
-
import path from "node:path"
|
|
5
|
-
import {
|
|
6
|
-
findLatestTranscript,
|
|
7
|
-
startTranscriptStream,
|
|
8
|
-
waitForResultEntry,
|
|
9
|
-
} from "./tui-source.adapter"
|
|
10
|
-
import { encodeCwd } from "./jsonl-path.adapter"
|
|
11
|
-
|
|
12
|
-
let workHome: string
|
|
13
|
-
let projectDir: string
|
|
14
|
-
|
|
15
|
-
beforeEach(async () => {
|
|
16
|
-
workHome = await mkdtemp(path.join(tmpdir(), "kanna-tui-source-"))
|
|
17
|
-
projectDir = path.join(workHome, ".claude", "projects", "fake-cwd")
|
|
18
|
-
await mkdir(projectDir, { recursive: true })
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
afterEach(async () => {
|
|
22
|
-
await rm(workHome, { recursive: true, force: true })
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
describe("findLatestTranscript", () => {
|
|
26
|
-
test("returns null when project dir empty", async () => {
|
|
27
|
-
const result = await findLatestTranscript(projectDir)
|
|
28
|
-
expect(result).toBeNull()
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
test("returns path of newest .jsonl file", async () => {
|
|
32
|
-
const fileA = path.join(projectDir, "aaa.jsonl")
|
|
33
|
-
const fileB = path.join(projectDir, "bbb.jsonl")
|
|
34
|
-
await writeFile(fileA, "{}\n")
|
|
35
|
-
await new Promise((r) => setTimeout(r, 20))
|
|
36
|
-
await writeFile(fileB, "{}\n")
|
|
37
|
-
const result = await findLatestTranscript(projectDir)
|
|
38
|
-
expect(result).toBe(fileB)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
test("ignores non-.jsonl files", async () => {
|
|
42
|
-
await writeFile(path.join(projectDir, "notes.txt"), "hello")
|
|
43
|
-
const result = await findLatestTranscript(projectDir)
|
|
44
|
-
expect(result).toBeNull()
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test("returns null when project dir does not exist", async () => {
|
|
48
|
-
const result = await findLatestTranscript(path.join(workHome, "no-such-dir"))
|
|
49
|
-
expect(result).toBeNull()
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
test("minMtimeMs filter skips JSONLs older than the floor", async () => {
|
|
53
|
-
const stale = path.join(projectDir, "stale.jsonl")
|
|
54
|
-
const fresh = path.join(projectDir, "fresh.jsonl")
|
|
55
|
-
await writeFile(stale, "{}\n")
|
|
56
|
-
await new Promise((r) => setTimeout(r, 20))
|
|
57
|
-
const floor = Date.now()
|
|
58
|
-
await new Promise((r) => setTimeout(r, 20))
|
|
59
|
-
await writeFile(fresh, "{}\n")
|
|
60
|
-
const result = await findLatestTranscript(projectDir, { minMtimeMs: floor })
|
|
61
|
-
expect(result).toBe(fresh)
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
test("minMtimeMs returns null when every JSONL is older than the floor", async () => {
|
|
65
|
-
const a = path.join(projectDir, "a.jsonl")
|
|
66
|
-
await writeFile(a, "{}\n")
|
|
67
|
-
await new Promise((r) => setTimeout(r, 20))
|
|
68
|
-
const result = await findLatestTranscript(projectDir, { minMtimeMs: Date.now() })
|
|
69
|
-
expect(result).toBeNull()
|
|
70
|
-
})
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
describe("startTranscriptStream (dir-watch)", () => {
|
|
74
|
-
test("picks up file written after stream start", async () => {
|
|
75
|
-
const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
|
|
76
|
-
const filePath = path.join(projectDir, "new.jsonl")
|
|
77
|
-
setTimeout(() => writeFile(filePath, '{"type":"hello"}\n'), 100)
|
|
78
|
-
const resolved = await stream.filePath
|
|
79
|
-
expect(resolved).toBe(filePath)
|
|
80
|
-
stream.close()
|
|
81
|
-
}, 5000)
|
|
82
|
-
|
|
83
|
-
test("opens existing file when present at start", async () => {
|
|
84
|
-
const filePath = path.join(projectDir, "existing.jsonl")
|
|
85
|
-
await writeFile(filePath, '{"type":"hello"}\n')
|
|
86
|
-
const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
|
|
87
|
-
const resolved = await stream.filePath
|
|
88
|
-
expect(resolved).toBe(filePath)
|
|
89
|
-
stream.close()
|
|
90
|
-
}, 5000)
|
|
91
|
-
|
|
92
|
-
test("emits complete lines as they are appended", async () => {
|
|
93
|
-
const filePath = path.join(projectDir, "stream.jsonl")
|
|
94
|
-
await writeFile(filePath, '{"type":"one"}\n')
|
|
95
|
-
const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
|
|
96
|
-
const iter = stream.lines[Symbol.asyncIterator]()
|
|
97
|
-
const first = await iter.next()
|
|
98
|
-
expect(first.value).toBe('{"type":"one"}')
|
|
99
|
-
setTimeout(() => writeFile(filePath, '{"type":"one"}\n{"type":"two"}\n'), 100)
|
|
100
|
-
const second = await iter.next()
|
|
101
|
-
expect(second.value).toBe('{"type":"two"}')
|
|
102
|
-
stream.close()
|
|
103
|
-
}, 5000)
|
|
104
|
-
|
|
105
|
-
test("holds partial line across writes", async () => {
|
|
106
|
-
const filePath = path.join(projectDir, "partial.jsonl")
|
|
107
|
-
await writeFile(filePath, '{"type":')
|
|
108
|
-
const stream = await startTranscriptStream({
|
|
109
|
-
projectDir,
|
|
110
|
-
firstFileTimeoutMs: 2000,
|
|
111
|
-
pollIntervalMs: 30,
|
|
112
|
-
})
|
|
113
|
-
const iter = stream.lines[Symbol.asyncIterator]()
|
|
114
|
-
let resolved = false
|
|
115
|
-
const firstPromise = iter.next().then((r: IteratorResult<string>) => { resolved = true; return r })
|
|
116
|
-
await new Promise((r) => setTimeout(r, 200))
|
|
117
|
-
expect(resolved).toBe(false)
|
|
118
|
-
await writeFile(filePath, '{"type":"one"}\n')
|
|
119
|
-
const first = await firstPromise
|
|
120
|
-
expect(first.value).toBe('{"type":"one"}')
|
|
121
|
-
stream.close()
|
|
122
|
-
}, 5000)
|
|
123
|
-
|
|
124
|
-
test("times out when no file appears within firstFileTimeoutMs", async () => {
|
|
125
|
-
const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 200 })
|
|
126
|
-
await expect(stream.filePath).rejects.toThrow(/transcript file did not appear/)
|
|
127
|
-
stream.close()
|
|
128
|
-
}, 5000)
|
|
129
|
-
|
|
130
|
-
test("knownFilePath skips dir-watch", async () => {
|
|
131
|
-
const filePath = path.join(projectDir, "known.jsonl")
|
|
132
|
-
await writeFile(filePath, '{"type":"hello"}\n')
|
|
133
|
-
const stream = await startTranscriptStream({
|
|
134
|
-
projectDir,
|
|
135
|
-
knownFilePath: filePath,
|
|
136
|
-
firstFileTimeoutMs: 500,
|
|
137
|
-
})
|
|
138
|
-
const resolved = await stream.filePath
|
|
139
|
-
expect(resolved).toBe(filePath)
|
|
140
|
-
stream.close()
|
|
141
|
-
}, 5000)
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
describe("startTranscriptStream (poll follower)", () => {
|
|
145
|
-
test("emits lines via the tail-poll", async () => {
|
|
146
|
-
const stream = await startTranscriptStream({
|
|
147
|
-
projectDir,
|
|
148
|
-
pollIntervalMs: 30,
|
|
149
|
-
firstFileTimeoutMs: 2000,
|
|
150
|
-
})
|
|
151
|
-
const filePath = path.join(projectDir, "poll.jsonl")
|
|
152
|
-
setTimeout(() => writeFile(filePath, '{"type":"polled"}\n'), 100)
|
|
153
|
-
const iter = stream.lines[Symbol.asyncIterator]()
|
|
154
|
-
const first = await iter.next()
|
|
155
|
-
expect(first.value).toBe('{"type":"polled"}')
|
|
156
|
-
stream.close()
|
|
157
|
-
}, 5000)
|
|
158
|
-
|
|
159
|
-
test("close() stops the poll timer (no leak)", async () => {
|
|
160
|
-
const filePath = path.join(projectDir, "leak.jsonl")
|
|
161
|
-
await writeFile(filePath, '{"type":"one"}\n')
|
|
162
|
-
const stream = await startTranscriptStream({
|
|
163
|
-
projectDir,
|
|
164
|
-
knownFilePath: filePath,
|
|
165
|
-
pollIntervalMs: 20,
|
|
166
|
-
firstFileTimeoutMs: 500,
|
|
167
|
-
})
|
|
168
|
-
const iter = stream.lines[Symbol.asyncIterator]()
|
|
169
|
-
expect((await iter.next()).value).toBe('{"type":"one"}')
|
|
170
|
-
stream.close()
|
|
171
|
-
await appendFile(filePath, '{"type":"two"}\n')
|
|
172
|
-
await new Promise((r) => setTimeout(r, 120))
|
|
173
|
-
const next = await Promise.race([
|
|
174
|
-
iter.next(),
|
|
175
|
-
new Promise<{ value: undefined; done: true }>((r) => setTimeout(() => r({ value: undefined, done: true }), 150)),
|
|
176
|
-
])
|
|
177
|
-
expect(next.done).toBe(true)
|
|
178
|
-
}, 5000)
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
describe("startTranscriptStream (registry resolution)", () => {
|
|
182
|
-
test("registry resolves but JSONL missing — never falls back to other JSONL in same dir", async () => {
|
|
183
|
-
const pid = 99001
|
|
184
|
-
const realCwd = await mkdtemp(path.join(workHome, "real-cwd-"))
|
|
185
|
-
const encoded = encodeCwd(realCwd)
|
|
186
|
-
const ownProjectDir = path.join(workHome, ".claude", "projects", encoded)
|
|
187
|
-
await mkdir(ownProjectDir, { recursive: true })
|
|
188
|
-
const sessionsDir = path.join(workHome, ".claude", "sessions")
|
|
189
|
-
await mkdir(sessionsDir, { recursive: true })
|
|
190
|
-
const ownSessionId = "our-session-aaaaaaaaaa"
|
|
191
|
-
await writeFile(
|
|
192
|
-
path.join(sessionsDir, `${pid}.json`),
|
|
193
|
-
JSON.stringify({ pid, sessionId: ownSessionId, cwd: realCwd, kind: "interactive", startedAt: Date.now() }),
|
|
194
|
-
)
|
|
195
|
-
const strangerFile = path.join(ownProjectDir, "stranger-session.jsonl")
|
|
196
|
-
await writeFile(strangerFile, '{"type":"assistant","message":{"content":[{"type":"text","text":"NOT OURS"}]}}\n')
|
|
197
|
-
|
|
198
|
-
const stream = await startTranscriptStream({
|
|
199
|
-
projectDir: ownProjectDir,
|
|
200
|
-
homeDir: workHome,
|
|
201
|
-
claudeChildPid: pid,
|
|
202
|
-
sessionRegistryTimeoutMs: 300,
|
|
203
|
-
firstFileTimeoutMs: 5_000,
|
|
204
|
-
pollIntervalMs: 20,
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
const beforeWrite = await Promise.race([
|
|
208
|
-
stream.filePath
|
|
209
|
-
.then((fp) => ({ kind: "resolved" as const, fp }))
|
|
210
|
-
.catch((err: Error) => ({ kind: "rejected" as const, err })),
|
|
211
|
-
new Promise<{ kind: "pending" }>((r) => setTimeout(() => r({ kind: "pending" }), 600)),
|
|
212
|
-
])
|
|
213
|
-
expect(beforeWrite.kind).toBe("pending")
|
|
214
|
-
|
|
215
|
-
const ownFile = path.join(ownProjectDir, `${ownSessionId}.jsonl`)
|
|
216
|
-
await writeFile(ownFile, '{"type":"assistant","message":{"content":[{"type":"text","text":"ours"}]}}\n')
|
|
217
|
-
const resolved = await stream.filePath
|
|
218
|
-
expect(resolved).toBe(ownFile)
|
|
219
|
-
expect(resolved).not.toBe(strangerFile)
|
|
220
|
-
stream.close()
|
|
221
|
-
}, 5000)
|
|
222
|
-
|
|
223
|
-
test("registry resolved but JSONL never appears — filePath rejects after firstFileTimeoutMs", async () => {
|
|
224
|
-
const pid = 99003
|
|
225
|
-
const realCwd = await mkdtemp(path.join(workHome, "real-cwd-"))
|
|
226
|
-
const encoded = encodeCwd(realCwd)
|
|
227
|
-
const ownProjectDir = path.join(workHome, ".claude", "projects", encoded)
|
|
228
|
-
await mkdir(ownProjectDir, { recursive: true })
|
|
229
|
-
const sessionsDir = path.join(workHome, ".claude", "sessions")
|
|
230
|
-
await mkdir(sessionsDir, { recursive: true })
|
|
231
|
-
const ownSessionId = "our-session-cccccccccc"
|
|
232
|
-
await writeFile(
|
|
233
|
-
path.join(sessionsDir, `${pid}.json`),
|
|
234
|
-
JSON.stringify({ pid, sessionId: ownSessionId, cwd: realCwd, kind: "interactive", startedAt: Date.now() }),
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
const stream = await startTranscriptStream({
|
|
238
|
-
projectDir: ownProjectDir,
|
|
239
|
-
homeDir: workHome,
|
|
240
|
-
claudeChildPid: pid,
|
|
241
|
-
sessionRegistryTimeoutMs: 300,
|
|
242
|
-
firstFileTimeoutMs: 150,
|
|
243
|
-
pollIntervalMs: 20,
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
const start = Date.now()
|
|
247
|
-
await expect(stream.filePath).rejects.toThrow(/did not appear in 150ms/)
|
|
248
|
-
const elapsed = Date.now() - start
|
|
249
|
-
expect(elapsed).toBeLessThan(1_500)
|
|
250
|
-
stream.close()
|
|
251
|
-
}, 5000)
|
|
252
|
-
|
|
253
|
-
test("registry resolves with existing JSONL — returns registry path immediately", async () => {
|
|
254
|
-
const pid = 99002
|
|
255
|
-
const realCwd = await mkdtemp(path.join(workHome, "real-cwd-"))
|
|
256
|
-
const encoded = encodeCwd(realCwd)
|
|
257
|
-
const ownProjectDir = path.join(workHome, ".claude", "projects", encoded)
|
|
258
|
-
await mkdir(ownProjectDir, { recursive: true })
|
|
259
|
-
const sessionsDir = path.join(workHome, ".claude", "sessions")
|
|
260
|
-
await mkdir(sessionsDir, { recursive: true })
|
|
261
|
-
const ownSessionId = "our-session-bbbbbbbbbb"
|
|
262
|
-
const ownFile = path.join(ownProjectDir, `${ownSessionId}.jsonl`)
|
|
263
|
-
await writeFile(ownFile, "{}\n")
|
|
264
|
-
await writeFile(
|
|
265
|
-
path.join(sessionsDir, `${pid}.json`),
|
|
266
|
-
JSON.stringify({ pid, sessionId: ownSessionId, cwd: realCwd, kind: "interactive", startedAt: Date.now() }),
|
|
267
|
-
)
|
|
268
|
-
await new Promise((r) => setTimeout(r, 20))
|
|
269
|
-
await writeFile(path.join(ownProjectDir, "stranger.jsonl"), "{}\n")
|
|
270
|
-
|
|
271
|
-
const stream = await startTranscriptStream({
|
|
272
|
-
projectDir: ownProjectDir,
|
|
273
|
-
homeDir: workHome,
|
|
274
|
-
claudeChildPid: pid,
|
|
275
|
-
})
|
|
276
|
-
const resolved = await stream.filePath
|
|
277
|
-
expect(resolved).toBe(ownFile)
|
|
278
|
-
stream.close()
|
|
279
|
-
}, 5000)
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
describe("startTranscriptStream (rapid turn-end appends)", () => {
|
|
283
|
-
test("delivers all rows appended in rapid succession after stream setup", async () => {
|
|
284
|
-
const filePath = path.join(projectDir, "watched.jsonl")
|
|
285
|
-
await writeFile(filePath, '{"type":"system","subtype":"init"}\n')
|
|
286
|
-
const stream = await startTranscriptStream({
|
|
287
|
-
projectDir,
|
|
288
|
-
knownFilePath: filePath,
|
|
289
|
-
firstFileTimeoutMs: 500,
|
|
290
|
-
})
|
|
291
|
-
const iter = stream.lines[Symbol.asyncIterator]()
|
|
292
|
-
const first = await iter.next()
|
|
293
|
-
expect(first.value).toContain('"system"')
|
|
294
|
-
|
|
295
|
-
await appendFile(filePath, '{"type":"assistant","message":{"content":[{"type":"text","text":"a"}]}}\n')
|
|
296
|
-
await appendFile(filePath, '{"type":"assistant","message":{"content":[{"type":"text","text":"b"}]}}\n')
|
|
297
|
-
await appendFile(filePath, '{"type":"system","subtype":"turn_duration","durationMs":42}\n')
|
|
298
|
-
|
|
299
|
-
const collected: string[] = []
|
|
300
|
-
const deadline = Date.now() + 2_000
|
|
301
|
-
while (collected.length < 3 && Date.now() < deadline) {
|
|
302
|
-
const nxt = await Promise.race([
|
|
303
|
-
iter.next(),
|
|
304
|
-
new Promise<{ value: undefined; done: true }>((r) => setTimeout(() => r({ value: undefined, done: true }), 1_500)),
|
|
305
|
-
])
|
|
306
|
-
if (nxt.done) break
|
|
307
|
-
collected.push(nxt.value)
|
|
308
|
-
}
|
|
309
|
-
expect(collected.length).toBe(3)
|
|
310
|
-
expect(collected[0]).toContain('"a"')
|
|
311
|
-
expect(collected[1]).toContain('"b"')
|
|
312
|
-
expect(collected[2]).toContain("turn_duration")
|
|
313
|
-
stream.close()
|
|
314
|
-
}, 8000)
|
|
315
|
-
})
|
|
316
|
-
|
|
317
|
-
describe("waitForResultEntry", () => {
|
|
318
|
-
test("resolves on first result line", async () => {
|
|
319
|
-
const filePath = path.join(projectDir, "result.jsonl")
|
|
320
|
-
await writeFile(filePath, '{"type":"system"}\n{"type":"assistant"}\n')
|
|
321
|
-
const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
|
|
322
|
-
setTimeout(() => writeFile(filePath, '{"type":"system"}\n{"type":"assistant"}\n{"type":"result","subtype":"success"}\n'), 100)
|
|
323
|
-
const entry = await waitForResultEntry(stream, { timeoutMs: 2000 })
|
|
324
|
-
expect(entry.parsed.type).toBe("result")
|
|
325
|
-
stream.close()
|
|
326
|
-
}, 5000)
|
|
327
|
-
|
|
328
|
-
test("resolves on assistant row with terminal stop_reason (new CLI format)", async () => {
|
|
329
|
-
const filePath = path.join(projectDir, "stop-reason.jsonl")
|
|
330
|
-
await writeFile(filePath, '{"type":"user","message":{"role":"user","content":"hi"}}\n')
|
|
331
|
-
const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
|
|
332
|
-
const endTurnRow = JSON.stringify({
|
|
333
|
-
type: "assistant",
|
|
334
|
-
message: { id: "msg_end", role: "assistant", stop_reason: "end_turn", content: [{ type: "text", text: "done" }] },
|
|
335
|
-
})
|
|
336
|
-
setTimeout(() => writeFile(
|
|
337
|
-
filePath,
|
|
338
|
-
`{"type":"user","message":{"role":"user","content":"hi"}}\n${endTurnRow}\n`,
|
|
339
|
-
), 100)
|
|
340
|
-
const entry = await waitForResultEntry(stream, { timeoutMs: 2000 })
|
|
341
|
-
expect(entry.parsed.type).toBe("assistant")
|
|
342
|
-
stream.close()
|
|
343
|
-
}, 5000)
|
|
344
|
-
|
|
345
|
-
test("does NOT resolve on assistant row with tool_use stop_reason", async () => {
|
|
346
|
-
const filePath = path.join(projectDir, "stop-reason-tooluse.jsonl")
|
|
347
|
-
const toolUseRow = JSON.stringify({
|
|
348
|
-
type: "assistant",
|
|
349
|
-
message: { id: "msg_t", role: "assistant", stop_reason: "tool_use", content: [{ type: "tool_use", id: "t1", name: "Read", input: {} }] },
|
|
350
|
-
})
|
|
351
|
-
await writeFile(filePath, `${toolUseRow}\n`)
|
|
352
|
-
const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
|
|
353
|
-
await expect(waitForResultEntry(stream, { timeoutMs: 300 })).rejects.toThrow(/timed out/i)
|
|
354
|
-
stream.close()
|
|
355
|
-
}, 5000)
|
|
356
|
-
|
|
357
|
-
test("rejects on abort signal", async () => {
|
|
358
|
-
const filePath = path.join(projectDir, "abort.jsonl")
|
|
359
|
-
await writeFile(filePath, '{"type":"system"}\n')
|
|
360
|
-
const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
|
|
361
|
-
const ctrl = new AbortController()
|
|
362
|
-
setTimeout(() => ctrl.abort(), 50)
|
|
363
|
-
await expect(waitForResultEntry(stream, { signal: ctrl.signal })).rejects.toThrow(/aborted/i)
|
|
364
|
-
stream.close()
|
|
365
|
-
}, 5000)
|
|
366
|
-
|
|
367
|
-
test("rejects on timeout", async () => {
|
|
368
|
-
const filePath = path.join(projectDir, "timeout.jsonl")
|
|
369
|
-
await writeFile(filePath, '{"type":"system"}\n')
|
|
370
|
-
const stream = await startTranscriptStream({ projectDir, firstFileTimeoutMs: 2000 })
|
|
371
|
-
await expect(waitForResultEntry(stream, { timeoutMs: 100 })).rejects.toThrow(/timed out/i)
|
|
372
|
-
stream.close()
|
|
373
|
-
}, 5000)
|
|
374
|
-
})
|