ethagent 2.1.1 → 2.3.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/package.json +2 -1
- package/src/app/FirstRun.tsx +1 -7
- package/src/app/FirstRunTimeline.tsx +1 -1
- package/src/auth/openaiOAuth/credentials.ts +47 -0
- package/src/auth/openaiOAuth/crypto.ts +23 -0
- package/src/auth/openaiOAuth/index.ts +238 -0
- package/src/auth/openaiOAuth/landingPage.ts +125 -0
- package/src/auth/openaiOAuth/listener.ts +151 -0
- package/src/auth/openaiOAuth/refresh.ts +70 -0
- package/src/auth/openaiOAuth/shared.ts +115 -0
- package/src/chat/ChatBottomPane.tsx +20 -11
- package/src/chat/ChatScreen.tsx +160 -35
- 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 +3 -2
- package/src/chat/chatTurnOrchestrator.ts +1 -7
- package/src/chat/commands.ts +28 -27
- 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} +36 -23
- 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} +35 -35
- package/src/chat/views/RewindView.tsx +410 -0
- package/src/identity/continuity/privateEdit/diff.ts +2 -78
- package/src/identity/ens/agentRecords.ts +5 -19
- package/src/identity/ens/ensAutomation/setup.ts +0 -1
- package/src/identity/ens/ensAutomation/types.ts +0 -1
- package/src/identity/hub/OperationalRoutes.tsx +23 -32
- 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} +19 -19
- 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 +10 -48
- package/src/identity/hub/{flows/custody/custodyFlowActions.ts → custody/actions.ts} +11 -9
- 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} +6 -6
- 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/ens/EnsEditAdvancedScreens.tsx +241 -0
- package/src/identity/hub/{flows/ens → ens}/EnsEditFlow.tsx +27 -82
- package/src/identity/hub/{flows/ens → ens}/EnsEditMaintenanceScreens.tsx +25 -65
- package/src/identity/hub/{flows/ens → ens}/EnsEditReviewScreens.tsx +12 -30
- package/src/identity/hub/ens/EnsEditRunners.tsx +62 -0
- package/src/identity/hub/{flows/ens → ens}/EnsEditShared.tsx +15 -14
- package/src/identity/hub/{flows/ens → ens}/EnsEditSimpleScreens.tsx +68 -217
- package/src/identity/hub/{flows/ens/IdentityHubEnsFlow.tsx → ens/EnsFlow.tsx} +18 -11
- package/src/identity/hub/{flows/ens/OperatorWalletsScreen.tsx → ens/EnsOperatorWalletsScreen.tsx} +17 -48
- package/src/identity/hub/{advancedEnsValidation.ts → ens/advancedEnsValidation.ts} +2 -2
- package/src/identity/hub/{flows/ens/ensEditCopy.ts → ens/editCopy.ts} +4 -4
- 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 +232 -232
- package/src/identity/hub/{flows/ens/ensEditTypes.ts → ens/types.ts} +12 -26
- package/src/identity/hub/identityHubReducer.ts +3 -3
- package/src/identity/hub/{flows/profile → profile}/EditProfileFlow.tsx +17 -10
- package/src/identity/hub/{effects/publicProfile/runPublicProfileSave.ts → profile/effects.ts} +55 -177
- package/src/identity/hub/{model → profile}/identity.ts +3 -3
- package/src/identity/hub/{effects/profile/profileState.ts → profile/state.ts} +181 -173
- package/src/identity/hub/{flows/restore → restore}/RestoreFlow.tsx +21 -21
- 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/restore/restoreAdmin.ts +34 -0
- 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 +16 -11
- package/src/identity/hub/{components → shared/components}/MenuScreen.tsx +8 -9
- 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 +2 -4
- 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 +6 -47
- 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 -2
- package/src/identity/hub/{reconciliation → shared/reconciliation}/agentReconciliation/ownership.ts +2 -2
- package/src/identity/hub/{reconciliation → shared/reconciliation}/agentReconciliation/run.ts +7 -40
- package/src/identity/hub/{reconciliation → shared/reconciliation}/agentReconciliation/types.ts +0 -4
- package/src/identity/hub/{reconciliation → shared/reconciliation}/index.ts +0 -7
- package/src/identity/hub/shared/reconciliation/walletSetup.ts +27 -0
- 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/identity/wallet/browserWallet/types.ts +0 -5
- package/src/identity/wallet/page/copy.ts +1 -31
- package/src/identity/wallet/walletPurposeCompat.ts +0 -2
- package/src/models/ModelPicker.tsx +248 -8
- package/src/models/catalog.ts +29 -1
- package/src/models/modelPickerOptions.ts +12 -10
- package/src/models/providerDisplay.ts +16 -0
- package/src/providers/errors.ts +6 -4
- package/src/providers/openai-chat.ts +2 -1
- package/src/providers/openai-responses-format.ts +156 -0
- package/src/providers/openai-responses.ts +276 -0
- package/src/providers/registry.ts +85 -8
- package/src/runtime/sessionMode.ts +1 -1
- package/src/runtime/systemPrompt.ts +4 -2
- package/src/runtime/toolExecution.ts +9 -6
- package/src/runtime/turn.ts +29 -1
- package/src/storage/rewind.ts +20 -0
- package/src/storage/secrets.ts +4 -1
- package/src/storage/sessions.ts +2 -1
- 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 +11 -1
- package/src/tools/writeFileTool.ts +8 -3
- package/src/ui/Spinner.tsx +25 -3
- package/src/ui/TextInput.tsx +2 -2
- package/src/ui/theme.ts +17 -0
- package/src/utils/clipboard.ts +10 -7
- package/src/utils/openExternal.ts +20 -10
- package/src/chat/RewindView.tsx +0 -386
- package/src/chat/toolResultDisplay.ts +0 -8
- package/src/identity/ens/ensRegistration.ts +0 -199
- package/src/identity/hub/effects/index.ts +0 -74
- 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/restoreAdmin.ts +0 -93
- package/src/identity/hub/effects/token-transfer/index.ts +0 -6
- package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +0 -336
- package/src/identity/hub/flows/ens/EnsEditRunners.tsx +0 -198
- package/src/identity/hub/reconciliation/walletSetup.ts +0 -220
- /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}/useAgentReconciliation.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 } {
|
|
@@ -5,6 +5,7 @@ import type { MessageRow } from './MessageList.js'
|
|
|
5
5
|
import type { ModelPickerSelection } from '../models/ModelPicker.js'
|
|
6
6
|
import { sessionMessagesToRows } from './chatScreenUtils.js'
|
|
7
7
|
import { formatModelDisplayName } from '../models/modelDisplay.js'
|
|
8
|
+
import { providerDisplayName } from '../models/modelPickerOptions.js'
|
|
8
9
|
|
|
9
10
|
export type ModelSelectionResolution =
|
|
10
11
|
| { kind: 'noop' }
|
|
@@ -69,7 +70,7 @@ export function resolveModelSelection(
|
|
|
69
70
|
return {
|
|
70
71
|
kind: 'switch',
|
|
71
72
|
config: nextConfig,
|
|
72
|
-
notice: `${selection.keyJustSet ? `${selection.provider} key saved.` : `${selection.provider} ready.`} Now using ${nextConfig.provider} · ${formatModelDisplayName(nextConfig.provider, nextConfig.model, { maxLength: 64 })}.`,
|
|
73
|
+
notice: `${selection.keyJustSet ? `${providerDisplayName(selection.provider)} key saved.` : `${providerDisplayName(selection.provider)} ready.`} Now using ${providerDisplayName(nextConfig.provider)} · ${formatModelDisplayName(nextConfig.provider, nextConfig.model, { maxLength: 64 })}.`,
|
|
73
74
|
tone: 'dim',
|
|
74
75
|
}
|
|
75
76
|
}
|
|
@@ -101,7 +102,7 @@ export function buildResumedSessionState(args: {
|
|
|
101
102
|
function formatResumeNote(metadata: SessionMetadata | null): string {
|
|
102
103
|
const id = metadata?.id?.slice(0, 8) ?? ''
|
|
103
104
|
const source = metadata?.compactedFromSessionId ? ` summarized from ${metadata.compactedFromSessionId.slice(0, 8)}` : ''
|
|
104
|
-
return `
|
|
105
|
+
return `Resumed from session ${id}.${source}`.trim()
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
export function restoreConversationState(
|
|
@@ -127,7 +127,6 @@ export async function runStreamingTurn(
|
|
|
127
127
|
let thinkingRowId: string | null = null
|
|
128
128
|
let thinkingCursorActive = false
|
|
129
129
|
let assistantId: string | null = null
|
|
130
|
-
let hasPendingToolUse = false
|
|
131
130
|
|
|
132
131
|
const resetIteration = () => {
|
|
133
132
|
accumulated = ''
|
|
@@ -135,7 +134,6 @@ export async function runStreamingTurn(
|
|
|
135
134
|
thinkingRowId = null
|
|
136
135
|
thinkingCursorActive = false
|
|
137
136
|
assistantId = null
|
|
138
|
-
hasPendingToolUse = false
|
|
139
137
|
}
|
|
140
138
|
|
|
141
139
|
const stopThinkingCursor = () => {
|
|
@@ -192,7 +190,7 @@ export async function runStreamingTurn(
|
|
|
192
190
|
flushStreamRows(true)
|
|
193
191
|
updateRows(prev => {
|
|
194
192
|
let next = finalizeStreamingRowsById(prev, assistantId, thinkingRowId, accumulated, thinkingContent)
|
|
195
|
-
if (assistantId &&
|
|
193
|
+
if (assistantId && accumulated.length === 0) {
|
|
196
194
|
next = next.filter(r => r.id !== assistantId)
|
|
197
195
|
}
|
|
198
196
|
return next
|
|
@@ -294,7 +292,6 @@ export async function runStreamingTurn(
|
|
|
294
292
|
setThinkingRowId: id => { thinkingRowId = id },
|
|
295
293
|
markThinkingCursorActive: () => { thinkingCursorActive = true },
|
|
296
294
|
getThinkingRowId: () => thinkingRowId,
|
|
297
|
-
markPendingToolUse: () => { hasPendingToolUse = true },
|
|
298
295
|
updateRows,
|
|
299
296
|
pushNote,
|
|
300
297
|
nextRowId,
|
|
@@ -340,7 +337,6 @@ type EventHandlerContext = {
|
|
|
340
337
|
setThinkingRowId: (id: string | null) => void
|
|
341
338
|
getThinkingRowId: () => string | null
|
|
342
339
|
markThinkingCursorActive: () => void
|
|
343
|
-
markPendingToolUse: () => void
|
|
344
340
|
updateRows: (updater: (prev: MessageRow[]) => MessageRow[]) => void
|
|
345
341
|
pushNote: (text: string, kind?: 'info' | 'error' | 'dim') => void
|
|
346
342
|
nextRowId: () => string
|
|
@@ -403,7 +399,6 @@ async function handleEvent(ev: TurnEvent, ctx: EventHandlerContext): Promise<voi
|
|
|
403
399
|
return
|
|
404
400
|
}
|
|
405
401
|
case 'tool_use_stop': {
|
|
406
|
-
ctx.markPendingToolUse()
|
|
407
402
|
ctx.finalizeStreamingRows()
|
|
408
403
|
return
|
|
409
404
|
}
|
|
@@ -426,7 +421,6 @@ async function handleEvent(ev: TurnEvent, ctx: EventHandlerContext): Promise<voi
|
|
|
426
421
|
}
|
|
427
422
|
case 'local_tool_recovery': {
|
|
428
423
|
ctx.discardStreamingRows()
|
|
429
|
-
ctx.markPendingToolUse()
|
|
430
424
|
return
|
|
431
425
|
}
|
|
432
426
|
case 'continuation_nudge': {
|