ethagent 2.2.0 → 2.4.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 +11 -0
- package/package.json +2 -1
- package/src/app/FirstRun.tsx +3 -7
- package/src/app/FirstRunTimeline.tsx +1 -1
- package/src/chat/ChatBottomPane.tsx +29 -11
- package/src/chat/ChatScreen.tsx +169 -38
- package/src/chat/ConversationStack.tsx +1 -1
- package/src/chat/MessageList.tsx +185 -72
- package/src/chat/SessionStatus.tsx +3 -1
- package/src/chat/chatScreenUtils.ts +11 -15
- package/src/chat/chatSessionState.ts +5 -2
- package/src/chat/chatTurnOrchestrator.ts +7 -9
- package/src/chat/commands.ts +26 -26
- package/src/chat/display/DiffView.tsx +193 -0
- package/src/chat/display/SyntaxText.tsx +192 -0
- package/src/chat/display/toolCallDisplay.ts +103 -0
- package/src/chat/display/toolResultDisplay.ts +19 -0
- package/src/chat/{ChatInput.tsx → input/ChatInput.tsx} +61 -25
- package/src/chat/input/imageRefs.ts +30 -0
- package/src/chat/{TranscriptView.tsx → transcript/TranscriptView.tsx} +24 -50
- package/src/chat/{transcriptViewport.ts → transcript/transcriptViewport.ts} +12 -30
- package/src/chat/{ContextLimitView.tsx → views/ContextLimitView.tsx} +3 -3
- package/src/chat/{ContinuityEditReviewView.tsx → views/ContinuityEditReviewView.tsx} +11 -3
- package/src/chat/{CopyPicker.tsx → views/CopyPicker.tsx} +4 -5
- package/src/chat/{PermissionPrompt.tsx → views/PermissionPrompt.tsx} +16 -17
- package/src/chat/{PermissionsView.tsx → views/PermissionsView.tsx} +6 -6
- package/src/chat/{PlanApprovalView.tsx → views/PlanApprovalView.tsx} +4 -4
- package/src/chat/{ResumeView.tsx → views/ResumeView.tsx} +50 -41
- package/src/chat/views/RewindView.tsx +410 -0
- package/src/identity/continuity/privateEdit/diff.ts +2 -78
- package/src/identity/hub/OperationalRoutes.tsx +21 -21
- package/src/identity/hub/Routes.tsx +13 -13
- package/src/identity/hub/{flows/continuity → continuity}/ContinuityDashboardScreen.tsx +9 -9
- package/src/identity/hub/{flows/continuity → continuity}/RebackupStorageScreen.tsx +2 -2
- package/src/identity/hub/{flows/continuity → continuity}/RecoveryConfirmScreen.tsx +5 -5
- package/src/identity/hub/{flows/continuity → continuity}/SavePromptScreen.tsx +5 -5
- package/src/identity/hub/{effects/rebackup/runRebackup.ts → continuity/effects.ts} +17 -17
- package/src/identity/hub/{effects/rebackup → continuity}/index.ts +1 -1
- package/src/identity/hub/{effects/shared → continuity}/snapshot.ts +8 -8
- package/src/identity/hub/{effects/rebackup → continuity}/vault.ts +15 -15
- package/src/identity/hub/{flows/create → create}/CreateFlow.tsx +13 -13
- package/src/identity/hub/{effects/create.ts → create/effects.ts} +4 -4
- package/src/identity/hub/{flows/custody → custody}/CustodyEditFlow.tsx +9 -9
- package/src/identity/hub/{flows/custody/custodyFlowActions.ts → custody/actions.ts} +6 -6
- package/src/identity/hub/{flows/custody/custodyFlowHelpers.ts → custody/helpers.ts} +4 -4
- package/src/identity/hub/{effects/vault → custody}/preflight.ts +5 -5
- package/src/identity/hub/{flows/custody/custodyFlowRoutes.tsx → custody/routes.tsx} +8 -8
- package/src/identity/hub/{flows/custody/custodyEffects.ts → custody/transactions.ts} +9 -9
- package/src/identity/hub/{flows/custody/custodyFlowTypes.ts → custody/types.ts} +5 -5
- package/src/identity/hub/{flows/custody/custodyFlowEffects.ts → custody/useCustodyEffects.ts} +7 -7
- package/src/identity/hub/{flows/custody → custody}/useCustodyFlow.tsx +5 -5
- package/src/identity/hub/{flows/ens → ens}/EnsEditAdvancedScreens.tsx +13 -13
- package/src/identity/hub/{flows/ens → ens}/EnsEditFlow.tsx +7 -7
- package/src/identity/hub/{flows/ens → ens}/EnsEditMaintenanceScreens.tsx +10 -10
- package/src/identity/hub/{flows/ens → ens}/EnsEditReviewScreens.tsx +12 -12
- package/src/identity/hub/{flows/ens → ens}/EnsEditRunners.tsx +5 -5
- package/src/identity/hub/{flows/ens → ens}/EnsEditShared.tsx +10 -10
- package/src/identity/hub/{flows/ens → ens}/EnsEditSimpleScreens.tsx +14 -14
- package/src/identity/hub/{flows/ens/IdentityHubEnsFlow.tsx → ens/EnsFlow.tsx} +12 -12
- package/src/identity/hub/{flows/ens/OperatorWalletsScreen.tsx → ens/EnsOperatorWalletsScreen.tsx} +17 -17
- package/src/identity/hub/{advancedEnsValidation.ts → ens/advancedEnsValidation.ts} +2 -2
- package/src/identity/hub/{flows/ens/ensEditCopy.ts → ens/editCopy.ts} +3 -3
- package/src/identity/hub/{effects/ens/flows.ts → ens/effects.ts} +7 -7
- package/src/identity/hub/{effects/ens → ens}/index.ts +1 -1
- package/src/identity/hub/{model/ens.ts → ens/state.ts} +1 -1
- package/src/identity/hub/{effects/ens → ens}/transactions.ts +239 -239
- package/src/identity/hub/{flows/ens/ensEditTypes.ts → ens/types.ts} +7 -7
- package/src/identity/hub/identityHubReducer.ts +3 -3
- package/src/identity/hub/{flows/profile → profile}/EditProfileFlow.tsx +11 -11
- package/src/identity/hub/{effects/publicProfile/runPublicProfileSave.ts → profile/effects.ts} +18 -18
- package/src/identity/hub/{model → profile}/identity.ts +3 -3
- package/src/identity/hub/{effects/profile/profileState.ts → profile/state.ts} +181 -181
- package/src/identity/hub/{flows/restore → restore}/RestoreFlow.tsx +16 -16
- package/src/identity/hub/{effects/restore → restore}/apply.ts +10 -10
- package/src/identity/hub/{effects/restore → restore}/auth.ts +7 -7
- package/src/identity/hub/{effects/restore → restore}/discover.ts +6 -6
- package/src/identity/hub/{effects/restore → restore}/envelopes.ts +2 -2
- package/src/identity/hub/{effects/restore → restore}/fetch.ts +3 -3
- package/src/identity/hub/{effects/restore/shared.ts → restore/helpers.ts} +6 -6
- package/src/identity/hub/{effects/restore → restore}/recovery.ts +10 -10
- package/src/identity/hub/{effects/restore → restore}/resolve.ts +4 -4
- package/src/identity/hub/{effects → restore}/restoreAdmin.ts +1 -1
- package/src/identity/hub/{flows/restore/useRestoreFlowEffects.ts → restore/useRestoreEffects.ts} +5 -5
- package/src/identity/hub/{flows/settings → settings}/StorageCredentialScreen.tsx +5 -5
- package/src/identity/hub/{components → shared/components}/BusyScreen.tsx +4 -4
- package/src/identity/hub/{components → shared/components}/DetailsScreen.tsx +4 -4
- package/src/identity/hub/{components → shared/components}/ErrorScreen.tsx +4 -4
- package/src/identity/hub/{components → shared/components}/FlowTimeline.tsx +1 -1
- package/src/identity/hub/{components → shared/components}/IdentitySummary.tsx +8 -8
- package/src/identity/hub/{components → shared/components}/MenuScreen.tsx +7 -7
- package/src/identity/hub/{components → shared/components}/NetworkScreen.tsx +4 -4
- package/src/identity/hub/{components → shared/components}/PinataJwtInput.tsx +4 -4
- package/src/identity/hub/{components → shared/components}/UnlinkedIdentityScreen.tsx +5 -5
- package/src/identity/hub/{components → shared/components}/WalletApprovalScreen.tsx +6 -6
- package/src/identity/hub/{components → shared/components}/menuFlagsFromReconciliation.ts +1 -1
- package/src/identity/hub/{effects/shared → shared/effects}/profilePrep.ts +1 -1
- package/src/identity/hub/{effects → shared/effects}/receipts.ts +2 -2
- package/src/identity/hub/{effects/shared → shared/effects}/sync.ts +4 -4
- package/src/identity/hub/{effects → shared/effects}/types.ts +3 -3
- package/src/identity/hub/{model → shared/model}/copy.ts +2 -2
- package/src/identity/hub/{model → shared/model}/errors.ts +5 -5
- package/src/identity/hub/{model → shared/model}/network.ts +3 -3
- package/src/identity/hub/{operatorWallets.ts → shared/operatorWallets.ts} +1 -1
- package/src/identity/hub/{reconciliation → shared/reconciliation}/agentReconciliation/hook.ts +1 -1
- package/src/identity/hub/{reconciliation → shared/reconciliation}/agentReconciliation/ownership.ts +2 -2
- package/src/identity/hub/{reconciliation → shared/reconciliation}/agentReconciliation/run.ts +6 -6
- package/src/identity/hub/{utils.ts → shared/utils.ts} +5 -5
- package/src/identity/hub/{flows/token-transfer/IdentityHubTokenTransferFlow.tsx → transfer/TokenTransferFlow.tsx} +8 -8
- package/src/identity/hub/{flows/token-transfer → transfer}/TokenTransferScreens.tsx +14 -14
- package/src/identity/hub/{effects/token-transfer/runTokenTransfer.ts → transfer/effects.ts} +16 -16
- package/src/identity/hub/{effects/token-transfer → transfer}/progress.ts +1 -1
- package/src/identity/hub/useIdentityHubController.ts +11 -11
- package/src/identity/hub/useIdentityHubSideEffects.ts +11 -11
- package/src/models/ModelPicker.tsx +143 -9
- package/src/models/catalog.ts +2 -1
- package/src/models/huggingface.ts +180 -2
- package/src/models/llamacpp.ts +110 -15
- package/src/models/llamacppPreflight.ts +30 -11
- package/src/models/modelPickerOptions.ts +16 -15
- package/src/models/providerDisplay.ts +16 -0
- package/src/providers/anthropic.ts +36 -5
- package/src/providers/contracts.ts +9 -1
- package/src/providers/errors.ts +6 -4
- package/src/providers/gemini.ts +29 -3
- package/src/providers/openai-chat.ts +83 -3
- package/src/providers/openai-responses-format.ts +29 -8
- package/src/providers/openai-responses.ts +22 -7
- package/src/providers/registry.ts +1 -0
- package/src/runtime/sessionMode.ts +1 -1
- package/src/runtime/systemPrompt.ts +3 -1
- package/src/runtime/toolExecution.ts +9 -6
- package/src/runtime/turn.ts +29 -0
- package/src/storage/config.ts +1 -0
- package/src/storage/rewind.ts +20 -0
- package/src/storage/sessions.ts +16 -3
- package/src/tools/bashSafety.ts +7 -3
- package/src/tools/bashTool.ts +1 -1
- package/src/tools/contracts.ts +3 -0
- package/src/tools/deleteFileTool.ts +8 -3
- package/src/tools/editTool.ts +10 -5
- package/src/tools/fileDiff.ts +261 -0
- package/src/tools/privateContinuityEditTool.ts +5 -1
- package/src/tools/writeFileTool.ts +8 -3
- package/src/ui/Spinner.tsx +39 -5
- package/src/ui/TextInput.tsx +2 -2
- package/src/ui/theme.ts +19 -0
- package/src/utils/clipboard.ts +10 -7
- package/src/utils/images.ts +140 -0
- package/src/utils/messages.ts +2 -0
- package/src/chat/RewindView.tsx +0 -386
- package/src/chat/toolResultDisplay.ts +0 -8
- package/src/identity/hub/effects/index.ts +0 -73
- package/src/identity/hub/effects/publicProfile/index.ts +0 -5
- package/src/identity/hub/effects/restore/restoreEffects.ts +0 -22
- package/src/identity/hub/effects/token-transfer/index.ts +0 -6
- /package/src/chat/{chatInputState.ts → input/chatInputState.ts} +0 -0
- /package/src/chat/{chatPaste.ts → input/chatPaste.ts} +0 -0
- /package/src/chat/{textCursor.ts → input/textCursor.ts} +0 -0
- /package/src/identity/hub/{model/continuity.ts → continuity/state.ts} +0 -0
- /package/src/identity/hub/{model/custody.ts → custody/state.ts} +0 -0
- /package/src/identity/hub/{effects/restore → restore}/index.ts +0 -0
- /package/src/identity/hub/{model → shared/model}/format.ts +0 -0
- /package/src/identity/hub/{reconciliation → shared/reconciliation}/agentReconciliation/types.ts +0 -0
- /package/src/identity/hub/{reconciliation → shared/reconciliation}/index.ts +0 -0
- /package/src/identity/hub/{reconciliation → shared/reconciliation}/useAgentReconciliation.ts +0 -0
- /package/src/identity/hub/{reconciliation → shared/reconciliation}/walletSetup.ts +0 -0
- /package/src/identity/hub/{txGuard.ts → shared/txGuard.ts} +0 -0
- /package/src/identity/hub/{model/transfer.ts → transfer/state.ts} +0 -0
package/src/chat/MessageList.tsx
CHANGED
|
@@ -3,11 +3,16 @@ import { Box, Text } from 'ink'
|
|
|
3
3
|
import { theme } from '../ui/theme.js'
|
|
4
4
|
import { ProgressBar } from '../ui/ProgressBar.js'
|
|
5
5
|
import { Spinner } from '../ui/Spinner.js'
|
|
6
|
+
import { DiffView } from './display/DiffView.js'
|
|
7
|
+
import { SyntaxLine } from './display/SyntaxText.js'
|
|
8
|
+
import { formatToolCall } from './display/toolCallDisplay.js'
|
|
9
|
+
import { BrandSplash } from '../ui/BrandSplash.js'
|
|
6
10
|
|
|
7
11
|
export type ToolCallResult = {
|
|
8
12
|
content: string
|
|
9
13
|
summary: string
|
|
10
14
|
isError: boolean
|
|
15
|
+
diff?: string
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
export type MessageRow =
|
|
@@ -19,7 +24,7 @@ export type MessageRow =
|
|
|
19
24
|
id: string
|
|
20
25
|
name: string
|
|
21
26
|
summary: string
|
|
22
|
-
input?: string
|
|
27
|
+
input?: Record<string, unknown>
|
|
23
28
|
result?: ToolCallResult
|
|
24
29
|
}
|
|
25
30
|
| { role: 'note'; id: string; kind: 'info' | 'error' | 'dim'; content: string }
|
|
@@ -34,6 +39,13 @@ export type MessageRow =
|
|
|
34
39
|
indeterminate?: boolean
|
|
35
40
|
startedAt?: number
|
|
36
41
|
}
|
|
42
|
+
| {
|
|
43
|
+
role: 'splash'
|
|
44
|
+
id: string
|
|
45
|
+
contextLine?: string
|
|
46
|
+
tipLine?: string
|
|
47
|
+
updateNotice?: string | null
|
|
48
|
+
}
|
|
37
49
|
|
|
38
50
|
type MessageListProps = {
|
|
39
51
|
rows: MessageRow[]
|
|
@@ -55,11 +67,18 @@ type InlineToken =
|
|
|
55
67
|
const MAX_RENDERED_MESSAGE_CHARS = 12_000
|
|
56
68
|
const MAX_RENDERED_REASONING_CHARS = 10_000
|
|
57
69
|
const ASSISTANT_ACCENT = theme.accentPeriwinkle
|
|
70
|
+
const ASSISTANT_MARKER = '• '
|
|
58
71
|
const UNREADABLE_REASONING_TEXT = 'reasoning output was not readable text'
|
|
59
72
|
|
|
60
73
|
const MessageListInner: React.FC<MessageListProps> = ({ rows }) => (
|
|
61
74
|
<Box flexDirection="column">
|
|
62
|
-
{rows.map(row =>
|
|
75
|
+
{rows.map((row, index) => (
|
|
76
|
+
<RowView
|
|
77
|
+
key={row.id}
|
|
78
|
+
row={row}
|
|
79
|
+
tightTop={row.role === 'tool_call' && rows[index - 1]?.role === 'tool_call'}
|
|
80
|
+
/>
|
|
81
|
+
))}
|
|
63
82
|
</Box>
|
|
64
83
|
)
|
|
65
84
|
|
|
@@ -102,7 +121,7 @@ export function toggleInspectableRow(rows: MessageRow[], rowId?: string): Messag
|
|
|
102
121
|
return rows
|
|
103
122
|
}
|
|
104
123
|
|
|
105
|
-
const RowViewInner: React.FC<{ row: MessageRow }> = ({ row }) => {
|
|
124
|
+
const RowViewInner: React.FC<{ row: MessageRow; tightTop?: boolean }> = ({ row, tightTop }) => {
|
|
106
125
|
if (row.role === 'user') {
|
|
107
126
|
const display = clipTextForDisplay(row.content, MAX_RENDERED_MESSAGE_CHARS)
|
|
108
127
|
const lines = display.text.length === 0 ? [''] : display.text.split('\n')
|
|
@@ -132,48 +151,65 @@ const RowViewInner: React.FC<{ row: MessageRow }> = ({ row }) => {
|
|
|
132
151
|
if (row.role === 'thinking') {
|
|
133
152
|
const text = sanitizeReasoningForDisplay(reasoningText(row))
|
|
134
153
|
const preview = summarizeThinking(text)
|
|
135
|
-
const
|
|
154
|
+
const active = Boolean(row.streaming)
|
|
136
155
|
const showCursor = reasoningCursorVisible(row)
|
|
137
156
|
if (row.expanded) {
|
|
138
157
|
return (
|
|
139
|
-
<
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
158
|
+
<ReasoningBlock
|
|
159
|
+
content={text}
|
|
160
|
+
detail="alt+t collapse"
|
|
161
|
+
expanded
|
|
162
|
+
active={active}
|
|
163
|
+
showCursor={showCursor}
|
|
164
|
+
/>
|
|
146
165
|
)
|
|
147
166
|
}
|
|
148
167
|
return (
|
|
149
|
-
<
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
{preview || 'thinking...'}
|
|
156
|
-
{showCursor ? <ThinkingCursor active hasPreview={Boolean(preview)} /> : null}
|
|
157
|
-
</Text>
|
|
158
|
-
</Box>
|
|
168
|
+
<ReasoningBlock
|
|
169
|
+
content={preview || 'thinking...'}
|
|
170
|
+
detail="alt+t inspect"
|
|
171
|
+
active={active}
|
|
172
|
+
showCursor={showCursor}
|
|
173
|
+
/>
|
|
159
174
|
)
|
|
160
175
|
}
|
|
161
176
|
|
|
162
177
|
if (row.role === 'tool_call') {
|
|
178
|
+
const { displayName, argSummary } = formatToolCall(row.name, row.input)
|
|
163
179
|
const result = row.result
|
|
164
|
-
const
|
|
180
|
+
const showResultLine = !result || result.isError
|
|
165
181
|
return (
|
|
166
|
-
<Box marginTop={1}>
|
|
182
|
+
<Box flexDirection="column" marginTop={tightTop ? 0 : 1}>
|
|
167
183
|
<Text>
|
|
168
|
-
<Text color={theme.dim}>{'
|
|
169
|
-
<Text color={theme.accentPeriwinkle} bold>{
|
|
170
|
-
{
|
|
171
|
-
{result ?
|
|
172
|
-
<Text color={result.isError ? theme.accentError : theme.dim}>{` ${result.summary}`}</Text>
|
|
173
|
-
) : (
|
|
174
|
-
<Text color={theme.dim}>{' running…'}</Text>
|
|
175
|
-
)}
|
|
184
|
+
<Text color={theme.dim}>{'● '}</Text>
|
|
185
|
+
<Text color={theme.accentPeriwinkle} bold>{displayName}</Text>
|
|
186
|
+
{row.name !== 'run_bash' && argSummary ? <Text color={theme.textSubtle}>{` ${argSummary}`}</Text> : null}
|
|
187
|
+
{result && !result.isError ? <Text color={theme.dim}>{' done'}</Text> : null}
|
|
176
188
|
</Text>
|
|
189
|
+
{row.name === 'run_bash' && argSummary ? (
|
|
190
|
+
<Box marginLeft={2}>
|
|
191
|
+
<Text>
|
|
192
|
+
<Text color={theme.dim}>{'$ '}</Text>
|
|
193
|
+
<Text color={theme.text}>{argSummary}</Text>
|
|
194
|
+
</Text>
|
|
195
|
+
</Box>
|
|
196
|
+
) : null}
|
|
197
|
+
{showResultLine ? (
|
|
198
|
+
result ? (
|
|
199
|
+
<Box marginLeft={2}>
|
|
200
|
+
<Text color={result.isError ? theme.accentError : theme.dim}>{result.summary}</Text>
|
|
201
|
+
</Box>
|
|
202
|
+
) : (
|
|
203
|
+
<Box marginLeft={2}>
|
|
204
|
+
<Text color={theme.dim}>running…</Text>
|
|
205
|
+
</Box>
|
|
206
|
+
)
|
|
207
|
+
) : null}
|
|
208
|
+
{result?.diff && !result.isError ? (
|
|
209
|
+
<Box flexDirection="column" marginLeft={2}>
|
|
210
|
+
<DiffView diff={result.diff} />
|
|
211
|
+
</Box>
|
|
212
|
+
) : null}
|
|
177
213
|
</Box>
|
|
178
214
|
)
|
|
179
215
|
}
|
|
@@ -187,6 +223,16 @@ const RowViewInner: React.FC<{ row: MessageRow }> = ({ row }) => {
|
|
|
187
223
|
)
|
|
188
224
|
}
|
|
189
225
|
|
|
226
|
+
if (row.role === 'splash') {
|
|
227
|
+
return (
|
|
228
|
+
<BrandSplash
|
|
229
|
+
contextLine={row.contextLine}
|
|
230
|
+
tipLine={row.tipLine}
|
|
231
|
+
updateNotice={row.updateNotice ?? null}
|
|
232
|
+
/>
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
190
236
|
return (
|
|
191
237
|
<Box flexDirection="column" marginTop={1}>
|
|
192
238
|
<Text color={theme.accentPeriwinkle} bold>{row.title}</Text>
|
|
@@ -208,21 +254,60 @@ const ProgressSpinner: React.FC<{ row: Extract<MessageRow, { role: 'progress' }>
|
|
|
208
254
|
return <Spinner active label={row.status} hint={row.suffix} startedAt={row.startedAt} />
|
|
209
255
|
}
|
|
210
256
|
|
|
211
|
-
|
|
212
|
-
|
|
257
|
+
const ShimmerText: React.FC<{
|
|
258
|
+
text: string
|
|
259
|
+
color: string
|
|
260
|
+
active?: boolean
|
|
261
|
+
bold?: boolean
|
|
262
|
+
italic?: boolean
|
|
263
|
+
}> = ({ text, color, active = false, bold, italic }) => {
|
|
264
|
+
const [position, setPosition] = useState(0)
|
|
265
|
+
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
if (!active) return
|
|
268
|
+
const period = text.length + 8
|
|
269
|
+
const timer = setInterval(() => {
|
|
270
|
+
setPosition(prev => (prev + 1) % period)
|
|
271
|
+
}, 90)
|
|
272
|
+
return () => clearInterval(timer)
|
|
273
|
+
}, [active, text.length])
|
|
274
|
+
|
|
275
|
+
if (!active) {
|
|
276
|
+
return <Text color={color} bold={bold} italic={italic}>{text}</Text>
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const shimmerStart = position - 1
|
|
280
|
+
const shimmerEnd = position + 1
|
|
281
|
+
const visibleStart = Math.max(0, shimmerStart)
|
|
282
|
+
const visibleEnd = Math.min(text.length, shimmerEnd + 1)
|
|
283
|
+
const before = text.slice(0, visibleStart)
|
|
284
|
+
const shimmer = shimmerStart < text.length && shimmerEnd >= 0 ? text.slice(visibleStart, visibleEnd) : ''
|
|
285
|
+
const after = text.slice(visibleEnd)
|
|
286
|
+
|
|
287
|
+
return (
|
|
288
|
+
<>
|
|
289
|
+
{before ? <Text color={color} bold={bold} italic={italic}>{before}</Text> : null}
|
|
290
|
+
{shimmer ? <Text color={theme.accentWhite} bold italic={italic}>{shimmer}</Text> : null}
|
|
291
|
+
{after ? <Text color={color} bold={bold} italic={italic}>{after}</Text> : null}
|
|
292
|
+
</>
|
|
293
|
+
)
|
|
213
294
|
}
|
|
214
295
|
|
|
215
|
-
function
|
|
216
|
-
|
|
217
|
-
if (flat.length <= max) return flat
|
|
218
|
-
return `${flat.slice(0, Math.max(1, max - 1))}…`
|
|
296
|
+
export function reasoningBorderColor(row: Extract<MessageRow, { role: 'thinking' }>): string {
|
|
297
|
+
return row.streaming ? theme.accentPeriwinkle : theme.border
|
|
219
298
|
}
|
|
220
299
|
|
|
221
300
|
export function reasoningCursorVisible(row: Extract<MessageRow, { role: 'thinking' }>): boolean {
|
|
222
301
|
return Boolean(row.streaming && row.showCursor)
|
|
223
302
|
}
|
|
224
303
|
|
|
225
|
-
const
|
|
304
|
+
const ReasoningBlock: React.FC<{
|
|
305
|
+
content: string
|
|
306
|
+
detail: string
|
|
307
|
+
active?: boolean
|
|
308
|
+
expanded?: boolean
|
|
309
|
+
showCursor?: boolean
|
|
310
|
+
}> = ({ content, detail, active = false, expanded = false, showCursor }) => {
|
|
226
311
|
const display = useMemo(
|
|
227
312
|
() => clipTextForDisplay(content, MAX_RENDERED_REASONING_CHARS),
|
|
228
313
|
[content],
|
|
@@ -233,16 +318,35 @@ const ReasoningBody: React.FC<{ content: string; showCursor?: boolean }> = ({ co
|
|
|
233
318
|
}, [display.text])
|
|
234
319
|
|
|
235
320
|
return (
|
|
236
|
-
<Box flexDirection="column">
|
|
321
|
+
<Box flexDirection="column" marginTop={1}>
|
|
237
322
|
{display.omittedChars > 0 ? (
|
|
238
323
|
<Text color={theme.dim}>{`${display.omittedChars} earlier reasoning characters omitted`}</Text>
|
|
239
324
|
) : null}
|
|
240
|
-
|
|
241
|
-
<
|
|
242
|
-
|
|
243
|
-
{
|
|
244
|
-
|
|
245
|
-
|
|
325
|
+
<Text>
|
|
326
|
+
<AssistantMarker />
|
|
327
|
+
<ShimmerText
|
|
328
|
+
text={expanded ? 'Thinking…' : 'Thinking'}
|
|
329
|
+
color={theme.accentPeriwinkle}
|
|
330
|
+
active={active}
|
|
331
|
+
bold
|
|
332
|
+
italic
|
|
333
|
+
/>
|
|
334
|
+
<Text color={theme.dim}>{` · ${detail}`}</Text>
|
|
335
|
+
{!expanded && showCursor ? <ThinkingCursor active hasPreview /> : null}
|
|
336
|
+
</Text>
|
|
337
|
+
{expanded ? (
|
|
338
|
+
<Box flexDirection="column" marginLeft={2}>
|
|
339
|
+
{lines.map((line, index) => (
|
|
340
|
+
<Box key={index}>
|
|
341
|
+
<Text color={theme.textSubtle}>
|
|
342
|
+
<Text color={theme.dim}>{' '}</Text>
|
|
343
|
+
{line || ' '}
|
|
344
|
+
{showCursor && index === lines.length - 1 ? <ThinkingCursor active hasPreview={line.length > 0} /> : null}
|
|
345
|
+
</Text>
|
|
346
|
+
</Box>
|
|
347
|
+
))}
|
|
348
|
+
</Box>
|
|
349
|
+
) : null}
|
|
246
350
|
</Box>
|
|
247
351
|
)
|
|
248
352
|
}
|
|
@@ -269,22 +373,33 @@ const AssistantBody: React.FC<{ content: string; liveTail?: string; streaming?:
|
|
|
269
373
|
key={index}
|
|
270
374
|
block={block}
|
|
271
375
|
streaming={streaming && index === blocks.length - 1}
|
|
376
|
+
prefix={index === 0 || block.kind === 'code' ? <AssistantMarker /> : null}
|
|
272
377
|
/>
|
|
273
378
|
))}
|
|
274
379
|
{streaming && blocks.length === 0 ? (
|
|
275
|
-
<Text
|
|
276
|
-
<
|
|
380
|
+
<Text>
|
|
381
|
+
<AssistantMarker />
|
|
382
|
+
<Text color={ASSISTANT_ACCENT}><StreamCursor active /></Text>
|
|
277
383
|
</Text>
|
|
278
384
|
) : null}
|
|
279
385
|
</Box>
|
|
280
386
|
)
|
|
281
387
|
}
|
|
282
388
|
|
|
283
|
-
const
|
|
389
|
+
const AssistantMarker: React.FC = () => (
|
|
390
|
+
<Text color={theme.dim}>{ASSISTANT_MARKER}</Text>
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
const MarkdownBlockView: React.FC<{ block: MarkdownBlock; streaming?: boolean; prefix?: React.ReactNode }> = ({
|
|
394
|
+
block,
|
|
395
|
+
streaming = false,
|
|
396
|
+
prefix = null,
|
|
397
|
+
}) => {
|
|
284
398
|
if (block.kind === 'heading') {
|
|
285
399
|
return (
|
|
286
400
|
<Box flexDirection="column" marginTop={1}>
|
|
287
401
|
<Text>
|
|
402
|
+
{prefix}
|
|
288
403
|
<InlineText text={block.text} color={ASSISTANT_ACCENT} bold />
|
|
289
404
|
</Text>
|
|
290
405
|
</Box>
|
|
@@ -296,6 +411,7 @@ const MarkdownBlockView: React.FC<{ block: MarkdownBlock; streaming?: boolean }>
|
|
|
296
411
|
<Box flexDirection="column" marginTop={1}>
|
|
297
412
|
{block.lines.map((line, index) => (
|
|
298
413
|
<Text key={index}>
|
|
414
|
+
{index === 0 ? prefix : null}
|
|
299
415
|
<Text color={ASSISTANT_ACCENT}>| </Text>
|
|
300
416
|
<InlineText text={line} color={theme.dim} />
|
|
301
417
|
</Text>
|
|
@@ -309,6 +425,7 @@ const MarkdownBlockView: React.FC<{ block: MarkdownBlock; streaming?: boolean }>
|
|
|
309
425
|
<Box flexDirection="column" marginTop={1}>
|
|
310
426
|
{block.items.map((item, index) => (
|
|
311
427
|
<Text key={index}>
|
|
428
|
+
{index === 0 ? prefix : null}
|
|
312
429
|
<Text color={ASSISTANT_ACCENT}>{block.ordered ? `${index + 1}. ` : '- '}</Text>
|
|
313
430
|
<InlineText text={item} color={theme.text} />
|
|
314
431
|
</Text>
|
|
@@ -319,18 +436,20 @@ const MarkdownBlockView: React.FC<{ block: MarkdownBlock; streaming?: boolean }>
|
|
|
319
436
|
|
|
320
437
|
if (block.kind === 'code') {
|
|
321
438
|
const lines = block.code.length === 0 ? [''] : block.code.split('\n')
|
|
322
|
-
const
|
|
439
|
+
const isShell = block.lang === 'bash' || block.lang === 'sh'
|
|
323
440
|
return (
|
|
324
|
-
<Box flexDirection="column" marginTop={1}
|
|
325
|
-
<
|
|
326
|
-
|
|
327
|
-
<Text color={theme.
|
|
328
|
-
|
|
329
|
-
|
|
441
|
+
<Box flexDirection="column" marginTop={1}>
|
|
442
|
+
<Text>
|
|
443
|
+
{prefix}
|
|
444
|
+
<Text color={theme.accentPeriwinkle} bold>{block.lang ?? 'code'}</Text>
|
|
445
|
+
{block.open ? <Text color={theme.dim}> streaming</Text> : null}
|
|
446
|
+
</Text>
|
|
447
|
+
<Box flexDirection="column" marginLeft={2}>
|
|
330
448
|
{lines.map((line, index) => (
|
|
331
449
|
<Text key={index}>
|
|
332
|
-
<Text color={theme.dim}>{
|
|
333
|
-
<
|
|
450
|
+
<Text color={theme.dim}>{isShell ? '$ ' : ' '}</Text>
|
|
451
|
+
<SyntaxLine line={line} lang={block.lang} fallbackColor={theme.textSubtle} />
|
|
452
|
+
{block.open && index === lines.length - 1 ? <Text color={ASSISTANT_ACCENT}> <StreamCursor active /></Text> : null}
|
|
334
453
|
</Text>
|
|
335
454
|
))}
|
|
336
455
|
</Box>
|
|
@@ -341,6 +460,7 @@ const MarkdownBlockView: React.FC<{ block: MarkdownBlock; streaming?: boolean }>
|
|
|
341
460
|
return (
|
|
342
461
|
<Box flexDirection="column" marginTop={1}>
|
|
343
462
|
<Text>
|
|
463
|
+
{prefix}
|
|
344
464
|
<InlineText text={block.text} color={theme.text} />
|
|
345
465
|
{streaming ? <Text color={ASSISTANT_ACCENT}> <StreamCursor active /></Text> : null}
|
|
346
466
|
</Text>
|
|
@@ -408,6 +528,14 @@ const StreamCursor: React.FC<{ active: boolean }> = ({ active }) => {
|
|
|
408
528
|
return <>{visible ? '|' : ' '}</>
|
|
409
529
|
}
|
|
410
530
|
|
|
531
|
+
function blockContentWidth(lines: string[]): number {
|
|
532
|
+
return Math.max(1, ...lines.map(displayWidth))
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function displayWidth(line: string): number {
|
|
536
|
+
return (line || ' ').replace(/\t/g, ' ').length
|
|
537
|
+
}
|
|
538
|
+
|
|
411
539
|
function parseMarkdownBlocks(markdown: string): MarkdownBlock[] {
|
|
412
540
|
const text = markdown.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
|
413
541
|
if (!text.trim()) return []
|
|
@@ -505,21 +633,6 @@ function parseMarkdownBlocks(markdown: string): MarkdownBlock[] {
|
|
|
505
633
|
return blocks
|
|
506
634
|
}
|
|
507
635
|
|
|
508
|
-
function codeAccent(_lang: string | null): string {
|
|
509
|
-
return ASSISTANT_ACCENT
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
function codeLineColor(lang: string | null, line: string): string {
|
|
513
|
-
const trimmed = line.trim()
|
|
514
|
-
if (!trimmed) return theme.textSubtle
|
|
515
|
-
if ((lang === 'json' || lang === 'jsonc') && /^["[{]/.test(trimmed)) return ASSISTANT_ACCENT
|
|
516
|
-
if (/^(\/\/|#|\/\*|\*)/.test(trimmed)) return theme.dim
|
|
517
|
-
if (/\b(function|const|let|return|if|else|class|export|import)\b/.test(trimmed)) return theme.text
|
|
518
|
-
if (/<\/?[A-Za-z]/.test(trimmed)) return ASSISTANT_ACCENT
|
|
519
|
-
if (/^[.#@]/.test(trimmed)) return ASSISTANT_ACCENT
|
|
520
|
-
return theme.textSubtle
|
|
521
|
-
}
|
|
522
|
-
|
|
523
636
|
function parseInlineTokens(text: string): InlineToken[] {
|
|
524
637
|
const tokens: InlineToken[] = []
|
|
525
638
|
const source = normalizeInlineDisplayText(text)
|
|
@@ -3,6 +3,8 @@ import { Box, Text } from 'ink'
|
|
|
3
3
|
import type { ContextUsage } from '../runtime/compaction.js'
|
|
4
4
|
import { theme } from '../ui/theme.js'
|
|
5
5
|
import { formatModelDisplayName } from '../models/modelDisplay.js'
|
|
6
|
+
import { providerDisplayName } from '../models/providerDisplay.js'
|
|
7
|
+
import type { ProviderId } from '../storage/config.js'
|
|
6
8
|
|
|
7
9
|
type StatusBarProps = {
|
|
8
10
|
provider: string
|
|
@@ -25,7 +27,7 @@ const SessionStatusInner: React.FC<StatusBarProps> = ({
|
|
|
25
27
|
return (
|
|
26
28
|
<Box flexDirection="row">
|
|
27
29
|
<Text color={theme.dim}>
|
|
28
|
-
{provider} · {displayModel} · {turns} {turns === 1 ? 'turn' : 'turns'} · ~{formatTokens(approxTokens)} tokens · Context {contextUsage.percent}% (~{formatTokens(contextUsage.usedTokens)} / {formatTokens(contextUsage.windowTokens)}) · {formatElapsed(Date.now() - startedAt)}
|
|
30
|
+
{providerDisplayName(provider as ProviderId)} · {displayModel} · {turns} {turns === 1 ? 'turn' : 'turns'} · ~{formatTokens(approxTokens)} tokens · Context {contextUsage.percent}% (~{formatTokens(contextUsage.usedTokens)} / {formatTokens(contextUsage.windowTokens)}) · {formatElapsed(Date.now() - startedAt)}
|
|
29
31
|
</Text>
|
|
30
32
|
</Box>
|
|
31
33
|
)
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type SessionMessage,
|
|
12
12
|
} from '../storage/sessions.js'
|
|
13
13
|
import type { MessageRow } from './MessageList.js'
|
|
14
|
-
import { hidesSuccessfulToolResultContent } from './toolResultDisplay.js'
|
|
14
|
+
import { hidesSuccessfulToolResultContent, toolResultDiffContent, toolResultTextContent } from './display/toolResultDisplay.js'
|
|
15
15
|
|
|
16
16
|
export type TurnCheckpoint = {
|
|
17
17
|
sessionId: string
|
|
@@ -62,7 +62,7 @@ export function sessionMessagesToRows(messages: SessionMessage[], nextRowId: ()
|
|
|
62
62
|
id: nextRowId(),
|
|
63
63
|
name: msg.name,
|
|
64
64
|
summary: msg.name,
|
|
65
|
-
input:
|
|
65
|
+
input: msg.input,
|
|
66
66
|
}
|
|
67
67
|
restored.push(row)
|
|
68
68
|
toolCallByUseId.set(msg.toolUseId, row)
|
|
@@ -70,9 +70,10 @@ export function sessionMessagesToRows(messages: SessionMessage[], nextRowId: ()
|
|
|
70
70
|
const isError = Boolean(msg.isError)
|
|
71
71
|
const summary = isError ? `${msg.name} failed` : `${msg.name} completed`
|
|
72
72
|
const content = toolResultContentForRow(msg.name, msg.content, msg.isError)
|
|
73
|
+
const diff = toolResultDiffForRow(msg.content, msg.isError)
|
|
73
74
|
const existing = toolCallByUseId.get(msg.toolUseId)
|
|
74
75
|
if (existing) {
|
|
75
|
-
existing.result = { content, summary, isError }
|
|
76
|
+
existing.result = diff ? { content, summary, isError, diff } : { content, summary, isError }
|
|
76
77
|
existing.name = msg.name
|
|
77
78
|
} else {
|
|
78
79
|
restored.push({
|
|
@@ -80,7 +81,7 @@ export function sessionMessagesToRows(messages: SessionMessage[], nextRowId: ()
|
|
|
80
81
|
id: nextRowId(),
|
|
81
82
|
name: msg.name,
|
|
82
83
|
summary: msg.name,
|
|
83
|
-
result: { content, summary, isError },
|
|
84
|
+
result: diff ? { content, summary, isError, diff } : { content, summary, isError },
|
|
84
85
|
})
|
|
85
86
|
}
|
|
86
87
|
}
|
|
@@ -88,23 +89,18 @@ export function sessionMessagesToRows(messages: SessionMessage[], nextRowId: ()
|
|
|
88
89
|
return restored
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
export function summarizeToolInput(input: Record<string, unknown>): string {
|
|
92
|
-
try {
|
|
93
|
-
const text = JSON.stringify(input)
|
|
94
|
-
if (text.length <= 160) return text
|
|
95
|
-
return `${text.slice(0, 157)}...`
|
|
96
|
-
} catch {
|
|
97
|
-
return '[unserializable input]'
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
92
|
export function truncateForRow(text: string, max = 1200): string {
|
|
102
93
|
if (text.length <= max) return text
|
|
103
94
|
return `${text.slice(0, max - 3)}...`
|
|
104
95
|
}
|
|
105
96
|
|
|
106
97
|
export function toolResultContentForRow(name: string, content: string, isError?: boolean): string {
|
|
107
|
-
|
|
98
|
+
const textContent = toolResultTextContent(content)
|
|
99
|
+
return hidesSuccessfulToolResultContent(name, isError) ? '' : truncateForRow(textContent)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function toolResultDiffForRow(content: string, isError?: boolean): string | undefined {
|
|
103
|
+
return toolResultDiffContent(content, isError)
|
|
108
104
|
}
|
|
109
105
|
|
|
110
106
|
export function splitStreamingContent(text: string): { committed: string; liveTail: string } {
|
|
@@ -39,6 +39,7 @@ export function resolveModelSelection(
|
|
|
39
39
|
selection.model === currentConfig.model
|
|
40
40
|
&& currentConfig.provider === 'llamacpp'
|
|
41
41
|
&& currentConfig.baseUrl === baseUrl
|
|
42
|
+
&& currentConfig.localMmprojPath === selection.mmprojPath
|
|
42
43
|
) {
|
|
43
44
|
return { kind: 'noop' }
|
|
44
45
|
}
|
|
@@ -49,8 +50,9 @@ export function resolveModelSelection(
|
|
|
49
50
|
provider: 'llamacpp',
|
|
50
51
|
model: selection.model,
|
|
51
52
|
baseUrl,
|
|
53
|
+
localMmprojPath: selection.mmprojPath,
|
|
52
54
|
},
|
|
53
|
-
notice: `Local Hugging Face model ready. Now using ${formatModelDisplayName('llamacpp', selection.model, { maxLength: 64 })}.`,
|
|
55
|
+
notice: `Local Hugging Face model ready. Now using ${formatModelDisplayName('llamacpp', selection.model, { maxLength: 64 })}${selection.mmprojPath ? ' with vision encoder' : ''}.`,
|
|
54
56
|
tone: 'info',
|
|
55
57
|
}
|
|
56
58
|
}
|
|
@@ -65,6 +67,7 @@ export function resolveModelSelection(
|
|
|
65
67
|
provider: nextProvider,
|
|
66
68
|
model: selection.model,
|
|
67
69
|
baseUrl: nextBaseUrl,
|
|
70
|
+
localMmprojPath: undefined,
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
return {
|
|
@@ -102,7 +105,7 @@ export function buildResumedSessionState(args: {
|
|
|
102
105
|
function formatResumeNote(metadata: SessionMetadata | null): string {
|
|
103
106
|
const id = metadata?.id?.slice(0, 8) ?? ''
|
|
104
107
|
const source = metadata?.compactedFromSessionId ? ` summarized from ${metadata.compactedFromSessionId.slice(0, 8)}` : ''
|
|
105
|
-
return `
|
|
108
|
+
return `Resumed from session ${id}.${source}`.trim()
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
export function restoreConversationState(
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
createTurnCheckpoint,
|
|
16
16
|
type TurnCheckpoint,
|
|
17
17
|
} from './chatScreenUtils.js'
|
|
18
|
+
import { collapseImagePathsToRefs, userTextToContentBlocks } from '../utils/images.js'
|
|
18
19
|
|
|
19
20
|
type MutableRef<T> = { current: T }
|
|
20
21
|
|
|
@@ -101,10 +102,13 @@ export async function runStreamingTurn(
|
|
|
101
102
|
const activeCheckpoint = createTurnCheckpoint(sessionId, userText)
|
|
102
103
|
setActiveCheckpoint(activeCheckpoint)
|
|
103
104
|
|
|
104
|
-
|
|
105
|
+
const userContent = userTextToContentBlocks(userText)
|
|
106
|
+
const displayText = collapseImagePathsToRefs(userText)
|
|
107
|
+
updateRows(prev => [...prev, { role: 'user', id: nextRowId(), content: displayText }])
|
|
105
108
|
await persistTurnMessage({
|
|
106
109
|
role: 'user',
|
|
107
|
-
content:
|
|
110
|
+
content: displayText,
|
|
111
|
+
providerContent: typeof userContent === 'string' ? undefined : userContent,
|
|
108
112
|
createdAt: nowIso(),
|
|
109
113
|
turnId: activeCheckpoint.turnId,
|
|
110
114
|
})
|
|
@@ -127,7 +131,6 @@ export async function runStreamingTurn(
|
|
|
127
131
|
let thinkingRowId: string | null = null
|
|
128
132
|
let thinkingCursorActive = false
|
|
129
133
|
let assistantId: string | null = null
|
|
130
|
-
let hasPendingToolUse = false
|
|
131
134
|
|
|
132
135
|
const resetIteration = () => {
|
|
133
136
|
accumulated = ''
|
|
@@ -135,7 +138,6 @@ export async function runStreamingTurn(
|
|
|
135
138
|
thinkingRowId = null
|
|
136
139
|
thinkingCursorActive = false
|
|
137
140
|
assistantId = null
|
|
138
|
-
hasPendingToolUse = false
|
|
139
141
|
}
|
|
140
142
|
|
|
141
143
|
const stopThinkingCursor = () => {
|
|
@@ -192,7 +194,7 @@ export async function runStreamingTurn(
|
|
|
192
194
|
flushStreamRows(true)
|
|
193
195
|
updateRows(prev => {
|
|
194
196
|
let next = finalizeStreamingRowsById(prev, assistantId, thinkingRowId, accumulated, thinkingContent)
|
|
195
|
-
if (assistantId &&
|
|
197
|
+
if (assistantId && accumulated.length === 0) {
|
|
196
198
|
next = next.filter(r => r.id !== assistantId)
|
|
197
199
|
}
|
|
198
200
|
return next
|
|
@@ -294,7 +296,6 @@ export async function runStreamingTurn(
|
|
|
294
296
|
setThinkingRowId: id => { thinkingRowId = id },
|
|
295
297
|
markThinkingCursorActive: () => { thinkingCursorActive = true },
|
|
296
298
|
getThinkingRowId: () => thinkingRowId,
|
|
297
|
-
markPendingToolUse: () => { hasPendingToolUse = true },
|
|
298
299
|
updateRows,
|
|
299
300
|
pushNote,
|
|
300
301
|
nextRowId,
|
|
@@ -340,7 +341,6 @@ type EventHandlerContext = {
|
|
|
340
341
|
setThinkingRowId: (id: string | null) => void
|
|
341
342
|
getThinkingRowId: () => string | null
|
|
342
343
|
markThinkingCursorActive: () => void
|
|
343
|
-
markPendingToolUse: () => void
|
|
344
344
|
updateRows: (updater: (prev: MessageRow[]) => MessageRow[]) => void
|
|
345
345
|
pushNote: (text: string, kind?: 'info' | 'error' | 'dim') => void
|
|
346
346
|
nextRowId: () => string
|
|
@@ -403,7 +403,6 @@ async function handleEvent(ev: TurnEvent, ctx: EventHandlerContext): Promise<voi
|
|
|
403
403
|
return
|
|
404
404
|
}
|
|
405
405
|
case 'tool_use_stop': {
|
|
406
|
-
ctx.markPendingToolUse()
|
|
407
406
|
ctx.finalizeStreamingRows()
|
|
408
407
|
return
|
|
409
408
|
}
|
|
@@ -426,7 +425,6 @@ async function handleEvent(ev: TurnEvent, ctx: EventHandlerContext): Promise<voi
|
|
|
426
425
|
}
|
|
427
426
|
case 'local_tool_recovery': {
|
|
428
427
|
ctx.discardStreamingRows()
|
|
429
|
-
ctx.markPendingToolUse()
|
|
430
428
|
return
|
|
431
429
|
}
|
|
432
430
|
case 'continuation_nudge': {
|