ethagent 1.1.2 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +124 -32
  3. package/package.json +8 -3
  4. package/src/app/FirstRun.tsx +190 -146
  5. package/src/app/FirstRunTimeline.tsx +47 -0
  6. package/src/app/input/AppInputProvider.tsx +1 -1
  7. package/src/app/keybindings/KeybindingProvider.tsx +1 -1
  8. package/src/chat/ChatBottomPane.tsx +0 -1
  9. package/src/chat/ChatInput.tsx +6 -6
  10. package/src/chat/ChatScreen.tsx +35 -15
  11. package/src/chat/ContextLimitView.tsx +4 -4
  12. package/src/chat/ContinuityEditReviewView.tsx +10 -22
  13. package/src/chat/CopyPicker.tsx +0 -1
  14. package/src/chat/MessageList.tsx +62 -45
  15. package/src/chat/PermissionPrompt.tsx +13 -9
  16. package/src/chat/PlanApprovalView.tsx +3 -3
  17. package/src/chat/ResumeView.tsx +1 -4
  18. package/src/chat/RewindView.tsx +2 -2
  19. package/src/chat/chatInputState.ts +1 -1
  20. package/src/chat/chatScreenUtils.ts +22 -11
  21. package/src/chat/chatSessionState.ts +2 -2
  22. package/src/chat/chatTurnOrchestrator.ts +16 -81
  23. package/src/chat/commands.ts +1 -1
  24. package/src/chat/textCursor.ts +1 -1
  25. package/src/chat/transcriptViewport.ts +2 -7
  26. package/src/cli/ResetConfirmView.tsx +1 -1
  27. package/src/cli/main.tsx +9 -3
  28. package/src/cli/preview.tsx +0 -5
  29. package/src/cli/updateNotice.ts +4 -2
  30. package/src/identity/continuity/editor.ts +7 -107
  31. package/src/identity/continuity/envelope.ts +1048 -40
  32. package/src/identity/continuity/history.ts +4 -4
  33. package/src/identity/continuity/localBackup.ts +249 -0
  34. package/src/identity/continuity/privateEdit/apply.ts +170 -0
  35. package/src/identity/continuity/privateEdit/diff.ts +82 -0
  36. package/src/identity/continuity/privateEdit/files.ts +23 -0
  37. package/src/identity/continuity/privateEdit/types.ts +28 -0
  38. package/src/identity/continuity/privateEdit.ts +10 -298
  39. package/src/identity/continuity/publicSkills.ts +8 -9
  40. package/src/identity/continuity/snapshots.ts +17 -6
  41. package/src/identity/continuity/storage/defaults.ts +111 -0
  42. package/src/identity/continuity/storage/files.ts +72 -0
  43. package/src/identity/continuity/storage/markdown.ts +81 -0
  44. package/src/identity/continuity/storage/paths.ts +24 -0
  45. package/src/identity/continuity/storage/scaffold.ts +124 -0
  46. package/src/identity/continuity/storage/status.ts +86 -0
  47. package/src/identity/continuity/storage/types.ts +27 -0
  48. package/src/identity/continuity/storage.ts +32 -507
  49. package/src/identity/continuity/zipWriter.ts +95 -0
  50. package/src/identity/crypto/backupEnvelope.ts +14 -247
  51. package/src/identity/crypto/eth.ts +7 -7
  52. package/src/identity/ens/agentRecords.ts +96 -0
  53. package/src/identity/ens/ensAutomation/contracts.ts +38 -0
  54. package/src/identity/ens/ensAutomation/delete.ts +80 -0
  55. package/src/identity/ens/ensAutomation/names.ts +14 -0
  56. package/src/identity/ens/ensAutomation/operators.ts +29 -0
  57. package/src/identity/ens/ensAutomation/read.ts +114 -0
  58. package/src/identity/ens/ensAutomation/root.ts +63 -0
  59. package/src/identity/ens/ensAutomation/setup.ts +284 -0
  60. package/src/identity/ens/ensAutomation/transactions.ts +107 -0
  61. package/src/identity/ens/ensAutomation/types.ts +126 -0
  62. package/src/identity/ens/ensAutomation.ts +29 -0
  63. package/src/identity/ens/ensLookup/client.ts +43 -0
  64. package/src/identity/ens/ensLookup/constants.ts +26 -0
  65. package/src/identity/ens/ensLookup/discovery.ts +70 -0
  66. package/src/identity/ens/ensLookup/names.ts +34 -0
  67. package/src/identity/ens/ensLookup/records.ts +45 -0
  68. package/src/identity/ens/ensLookup/resolve.ts +75 -0
  69. package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
  70. package/src/identity/ens/ensLookup/types.ts +38 -0
  71. package/src/identity/ens/ensLookup/validation.ts +72 -0
  72. package/src/identity/ens/ensLookup.ts +19 -0
  73. package/src/identity/ens/ensRegistration.ts +199 -0
  74. package/src/identity/ens/resolverDelegation.ts +48 -0
  75. package/src/identity/hub/IdentityHub.tsx +13 -817
  76. package/src/identity/hub/OperationalRoutes.tsx +370 -0
  77. package/src/identity/hub/Routes.tsx +361 -0
  78. package/src/identity/hub/advancedEnsValidation.ts +45 -0
  79. package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
  80. package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
  81. package/src/identity/hub/components/FlowTimeline.tsx +27 -0
  82. package/src/identity/hub/components/IdentitySummary.tsx +190 -0
  83. package/src/identity/hub/components/MenuScreen.tsx +237 -0
  84. package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
  85. package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
  86. package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
  87. package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
  88. package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
  89. package/src/identity/hub/effects/create.ts +310 -0
  90. package/src/identity/hub/effects/ens/flows.ts +218 -0
  91. package/src/identity/hub/effects/ens/index.ts +11 -0
  92. package/src/identity/hub/effects/ens/transactions.ts +239 -0
  93. package/src/identity/hub/effects/index.ts +74 -0
  94. package/src/identity/hub/effects/profile/profileState.ts +173 -0
  95. package/src/identity/hub/effects/publicProfile/index.ts +5 -0
  96. package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
  97. package/src/identity/hub/effects/rebackup/index.ts +7 -0
  98. package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
  99. package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
  100. package/src/identity/hub/effects/receipts.ts +46 -0
  101. package/src/identity/hub/effects/restore/apply.ts +112 -0
  102. package/src/identity/hub/effects/restore/auth.ts +159 -0
  103. package/src/identity/hub/effects/restore/discover.ts +86 -0
  104. package/src/identity/hub/effects/restore/envelopes.ts +21 -0
  105. package/src/identity/hub/effects/restore/fetch.ts +25 -0
  106. package/src/identity/hub/effects/restore/index.ts +22 -0
  107. package/src/identity/hub/effects/restore/recovery.ts +135 -0
  108. package/src/identity/hub/effects/restore/resolve.ts +102 -0
  109. package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
  110. package/src/identity/hub/effects/restore/shared.ts +91 -0
  111. package/src/identity/hub/effects/restoreAdmin.ts +93 -0
  112. package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
  113. package/src/identity/hub/effects/shared/snapshot.ts +336 -0
  114. package/src/identity/hub/effects/shared/sync.ts +190 -0
  115. package/src/identity/hub/effects/token-transfer/index.ts +6 -0
  116. package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
  117. package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
  118. package/src/identity/hub/effects/types.ts +53 -0
  119. package/src/identity/hub/effects/vault/preflight.ts +50 -0
  120. package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
  121. package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
  122. package/src/identity/hub/{screens → flows/continuity}/RecoveryConfirmScreen.tsx +28 -19
  123. package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
  124. package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
  125. package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
  126. package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
  127. package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
  128. package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
  129. package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
  130. package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
  131. package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
  132. package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
  133. package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
  134. package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
  135. package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
  136. package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
  137. package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
  138. package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
  139. package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
  140. package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
  141. package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
  142. package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
  143. package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
  144. package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
  145. package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
  146. package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
  147. package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +23 -44
  148. package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
  149. package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
  150. package/src/identity/hub/identityHubReducer.ts +164 -99
  151. package/src/identity/hub/model/continuity.ts +94 -0
  152. package/src/identity/hub/model/copy.ts +35 -0
  153. package/src/identity/hub/model/custody.ts +54 -0
  154. package/src/identity/hub/model/ens.ts +49 -0
  155. package/src/identity/hub/model/errors.ts +140 -0
  156. package/src/identity/hub/model/format.ts +15 -0
  157. package/src/identity/hub/model/identity.ts +94 -0
  158. package/src/identity/hub/model/network.ts +32 -0
  159. package/src/identity/hub/model/transfer.ts +57 -0
  160. package/src/identity/hub/operatorWallets.ts +131 -0
  161. package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
  162. package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
  163. package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
  164. package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
  165. package/src/identity/hub/reconciliation/index.ts +21 -0
  166. package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
  167. package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
  168. package/src/identity/hub/txGuard.ts +51 -0
  169. package/src/identity/hub/types.ts +17 -0
  170. package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
  171. package/src/identity/hub/useIdentityHubController.ts +396 -0
  172. package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
  173. package/src/identity/hub/utils.ts +79 -0
  174. package/src/identity/identityCompat.ts +34 -0
  175. package/src/identity/profile/agentIcon.ts +61 -0
  176. package/src/identity/profile/imagePicker.ts +12 -12
  177. package/src/identity/registry/erc8004/abi.ts +14 -0
  178. package/src/identity/registry/erc8004/chains.ts +150 -0
  179. package/src/identity/registry/erc8004/client.ts +11 -0
  180. package/src/identity/registry/erc8004/discovery.ts +511 -0
  181. package/src/identity/registry/erc8004/metadata.ts +335 -0
  182. package/src/identity/registry/erc8004/ownership.ts +121 -0
  183. package/src/identity/registry/erc8004/preflight.ts +123 -0
  184. package/src/identity/registry/erc8004/transactions.ts +77 -0
  185. package/src/identity/registry/erc8004/types.ts +88 -0
  186. package/src/identity/registry/erc8004/uri.ts +59 -0
  187. package/src/identity/registry/erc8004/utils.ts +58 -0
  188. package/src/identity/registry/erc8004.ts +53 -1106
  189. package/src/identity/registry/fieldParsers.ts +28 -0
  190. package/src/identity/registry/operatorVault/bytecode.ts +98 -0
  191. package/src/identity/registry/operatorVault/constants.ts +38 -0
  192. package/src/identity/registry/operatorVault/read.ts +246 -0
  193. package/src/identity/registry/operatorVault/transactions.ts +81 -0
  194. package/src/identity/registry/operatorVault.ts +44 -0
  195. package/src/identity/storage/ipfs.ts +26 -24
  196. package/src/identity/wallet/browserWallet/gas.ts +41 -0
  197. package/src/identity/wallet/browserWallet/html.ts +106 -0
  198. package/src/identity/wallet/browserWallet/http.ts +28 -0
  199. package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
  200. package/src/identity/wallet/browserWallet/requests.ts +191 -0
  201. package/src/identity/wallet/browserWallet/session.ts +325 -0
  202. package/src/identity/wallet/browserWallet/types.ts +192 -0
  203. package/src/identity/wallet/browserWallet/validation.ts +74 -0
  204. package/src/identity/wallet/browserWallet.ts +30 -393
  205. package/src/identity/wallet/page/constants.ts +5 -0
  206. package/src/identity/wallet/page/controller.ts +251 -0
  207. package/src/identity/wallet/page/copy.ts +340 -0
  208. package/src/identity/wallet/page/grainient.ts +278 -0
  209. package/src/identity/wallet/page/html.ts +28 -0
  210. package/src/identity/wallet/page/markup.ts +50 -0
  211. package/src/identity/wallet/page/state.ts +9 -0
  212. package/src/identity/wallet/page/styles/base.ts +259 -0
  213. package/src/identity/wallet/page/styles/components.ts +262 -0
  214. package/src/identity/wallet/page/styles/index.ts +5 -0
  215. package/src/identity/wallet/page/styles/responsive.ts +247 -0
  216. package/src/identity/wallet/page/types.ts +47 -0
  217. package/src/identity/wallet/page/view.ts +535 -0
  218. package/src/identity/wallet/page/walletProvider.ts +70 -0
  219. package/src/identity/wallet/page.tsx +38 -0
  220. package/src/identity/wallet/walletPurposeCompat.ts +27 -0
  221. package/src/mcp/manager.ts +0 -1
  222. package/src/models/ModelPicker.tsx +36 -30
  223. package/src/models/catalog.ts +5 -2
  224. package/src/models/huggingface.ts +9 -9
  225. package/src/models/llamacpp.ts +13 -13
  226. package/src/models/modelDisplay.ts +75 -0
  227. package/src/models/modelPickerOptions.ts +16 -3
  228. package/src/models/modelRecommendation.ts +0 -1
  229. package/src/providers/errors.ts +16 -0
  230. package/src/providers/gemini.ts +252 -39
  231. package/src/providers/registry.ts +2 -2
  232. package/src/providers/retry.ts +1 -1
  233. package/src/runtime/sessionMode.ts +1 -1
  234. package/src/runtime/systemPrompt.ts +2 -0
  235. package/src/runtime/toolExecution.ts +18 -22
  236. package/src/runtime/toolIntent.ts +0 -20
  237. package/src/runtime/turn.ts +0 -92
  238. package/src/storage/atomicWrite.ts +4 -1
  239. package/src/storage/config.ts +181 -5
  240. package/src/storage/identity.ts +9 -3
  241. package/src/storage/secrets.ts +2 -2
  242. package/src/tools/bashSafety.ts +8 -0
  243. package/src/tools/changeDirectoryTool.ts +1 -1
  244. package/src/tools/deleteFileTool.ts +4 -4
  245. package/src/tools/editTool.ts +4 -4
  246. package/src/tools/editUtils.ts +5 -5
  247. package/src/tools/privateContinuityEditTool.ts +4 -5
  248. package/src/tools/privateContinuityReadTool.ts +1 -2
  249. package/src/tools/registry.ts +30 -0
  250. package/src/tools/writeFileTool.ts +5 -5
  251. package/src/ui/BrandSplash.tsx +20 -85
  252. package/src/ui/ProgressBar.tsx +3 -5
  253. package/src/ui/Select.tsx +20 -8
  254. package/src/ui/Spinner.tsx +38 -3
  255. package/src/ui/Surface.tsx +2 -2
  256. package/src/ui/TextInput.tsx +63 -20
  257. package/src/ui/theme.ts +7 -34
  258. package/src/utils/openExternal.ts +21 -0
  259. package/src/utils/withRetry.ts +47 -3
  260. package/src/identity/hub/identityHubEffects.ts +0 -937
  261. package/src/identity/hub/identityHubModel.ts +0 -371
  262. package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -156
  263. package/src/identity/hub/screens/EditProfileFlow.tsx +0 -146
  264. package/src/identity/hub/screens/IdentitySummary.tsx +0 -106
  265. package/src/identity/hub/screens/MenuScreen.tsx +0 -117
  266. package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
  267. package/src/identity/wallet/wallet-page/wallet.html +0 -1202
  268. /package/src/identity/hub/{screens → components}/BusyScreen.tsx +0 -0
@@ -18,7 +18,7 @@ import { SessionStatus, formatTokens } from './SessionStatus.js'
18
18
  import { formatModelDisplayName } from '../models/modelDisplay.js'
19
19
  import { toggleReasoningRow, type MessageRow } from './MessageList.js'
20
20
  import { ConversationStack } from './ConversationStack.js'
21
- import { ModelPicker, type ModelPickerSelection } from '../models/ModelPicker.js'
21
+ import type { ModelPickerSelection } from '../models/ModelPicker.js'
22
22
  import type { ModelPickerContextFit } from '../models/modelPickerOptions.js'
23
23
  import type { CopyResult } from '../utils/clipboard.js'
24
24
  import { useKeybinding, useRegisterKeybindingContext } from '../app/keybindings/KeybindingProvider.js'
@@ -55,13 +55,15 @@ import type {
55
55
  } from '../tools/contracts.js'
56
56
  import {
57
57
  buildBaseMessages,
58
- formatBytes,
59
58
  sessionMessagesToRows,
60
59
  type TurnCheckpoint,
61
60
  } from './chatScreenUtils.js'
62
61
  import { ChatBottomPane, type ContextLimitState, type CopyPickerState, type IdentityOverlayState, type Overlay } from './ChatBottomPane.js'
63
62
  import { setTokenIdentity, getIdentityStatus } from '../storage/identity.js'
64
63
  import type { IdentityHubResult } from '../identity/hub/IdentityHub.js'
64
+ import { continuityWorkingTreeStatus } from '../identity/continuity/storage.js'
65
+ import { listPublishedContinuitySnapshots } from '../identity/continuity/snapshots.js'
66
+ import { localChangeStatusView } from '../identity/hub/model/continuity.js'
65
67
  import {
66
68
  buildResumedSessionState,
67
69
  promptHistoryFromSessionMessages,
@@ -181,6 +183,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
181
183
  const pendingContinuityEditReviewRef = useRef<ContinuityEditReviewState | null>(null)
182
184
  const contextModelSwitchPromptRef = useRef<string | null>(null)
183
185
  const mcpManagerRef = useRef<McpManager | null>(null)
186
+ const savePromptShownRef = useRef<boolean>(false)
184
187
 
185
188
  useEffect(() => { rowsRef.current = rows }, [rows])
186
189
  useEffect(() => { overlayRef.current = overlay }, [overlay])
@@ -206,6 +209,29 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
206
209
  })()
207
210
  }, [])
208
211
 
212
+ useEffect(() => {
213
+ if (savePromptShownRef.current) return
214
+ savePromptShownRef.current = true
215
+ void (async () => {
216
+ try {
217
+ const identity = configRef.current.identity
218
+ if (!identity) return
219
+ const [latest] = await listPublishedContinuitySnapshots(identity, 1)
220
+ const status = await continuityWorkingTreeStatus(identity, latest)
221
+ if (!localChangeStatusView(status).hasLocalChanges) return
222
+ if (overlayRef.current !== 'none') return
223
+ setIdentityOverlay({
224
+ initialAction: 'save-prompt',
225
+ existing: { address: identity.address },
226
+ })
227
+ overlayRef.current = 'identity'
228
+ setOverlay('identity')
229
+ } catch {
230
+ // best-effort; skip prompt on any error
231
+ }
232
+ })()
233
+ }, [])
234
+
209
235
  useEffect(() => {
210
236
  void (async () => {
211
237
  try {
@@ -1107,7 +1133,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
1107
1133
  })
1108
1134
  overlayRef.current = 'identity'
1109
1135
  setOverlay('identity')
1110
- pushNote('opening snapshot approval.', 'dim')
1136
+ pushNote('opening snapshot signature.', 'dim')
1111
1137
  return
1112
1138
  }
1113
1139
  overlayRef.current = 'none'
@@ -1363,8 +1389,8 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
1363
1389
 
1364
1390
  const contextLine = `${config.provider} · ${formatModelDisplayName(config.provider, config.model, { maxLength: 24 })} · ${compressHome(cwd)}`
1365
1391
  const tipLine = streaming
1366
- ? 'tip: you can keep typing and press enter to queue the next message · shift+enter for newline'
1367
- : 'tip: type /help to get started · shift+enter for newline'
1392
+ ? 'Tip: You can keep typing and press enter to queue the next message · shift+enter for newline'
1393
+ : 'Tip: type /help to get started · shift+enter for newline'
1368
1394
 
1369
1395
  const placeholderHints = useMemo(() => {
1370
1396
  if (compactionUi) return ['compaction in progress · esc to cancel']
@@ -1373,30 +1399,24 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
1373
1399
 
1374
1400
  const exitHint = exitState.pending ? 'ctrl+c again to quit' : null
1375
1401
  const runtimeModeLabel = sessionModeLabel(mode)
1376
- const modeColor =
1377
- mode === 'plan'
1378
- ? theme.accentLavender
1379
- : mode === 'accept-edits'
1380
- ? theme.accentPeach
1381
- : theme.accentMint
1382
1402
  const footerRight = (
1383
1403
  <Box flexDirection="row">
1384
1404
  {exitHint ? (
1385
1405
  <>
1386
- <Text color={theme.accentPrimary}>{exitHint}</Text>
1406
+ <Text color={theme.text}>{exitHint}</Text>
1387
1407
  <Text color={theme.dim}> · </Text>
1388
1408
  </>
1389
1409
  ) : null}
1390
1410
  {runtimeModeLabel ? (
1391
1411
  <>
1392
- <Text color={modeColor}>{runtimeModeLabel}</Text>
1412
+ <Text bold>{runtimeModeLabel}</Text>
1393
1413
  <Text color={theme.dim}> (</Text>
1394
- <Text color={theme.accentMint}>shift+tab to cycle</Text>
1414
+ <Text color={theme.accentPeriwinkle}>shift+tab to cycle</Text>
1395
1415
  <Text color={theme.dim}>) · </Text>
1396
1416
  </>
1397
1417
  ) : (
1398
1418
  <>
1399
- <Text color={theme.accentMint}>shift+tab to cycle</Text>
1419
+ <Text color={theme.accentPeriwinkle}>shift+tab to cycle</Text>
1400
1420
  <Text color={theme.dim}> · </Text>
1401
1421
  </>
1402
1422
  )}
@@ -64,20 +64,20 @@ export const ContextLimitView: React.FC<ContextLimitViewProps> = ({
64
64
  })
65
65
 
66
66
  return (
67
- <Box flexDirection="column" borderStyle="round" borderColor={theme.accentPeach} paddingX={1}>
68
- <Text color={theme.accentPeach} bold>context limit</Text>
67
+ <Box flexDirection="column" borderStyle="round" borderColor={theme.accentPeriwinkle} paddingX={1}>
68
+ <Text color={theme.accentPeriwinkle} bold>context limit</Text>
69
69
  <Text color={theme.dim}>
70
70
  {`Context ${usage.percent}% · ~${formatTokens(usage.usedTokens)} / ${formatTokens(usage.windowTokens)} tokens (${usage.source}).`}
71
71
  </Text>
72
72
  {usage.percent >= 100 ? (
73
- <Text color={theme.accentPeach}>
73
+ <Text color={theme.accentPeriwinkle}>
74
74
  This transcript is over the selected model's estimated window. You can still send, but summarizing first is safer.
75
75
  </Text>
76
76
  ) : null}
77
77
  <Text color={theme.textSubtle}>{`Pending: ${promptPreview || '(empty)'}`}</Text>
78
78
  <Box flexDirection="column" marginTop={1}>
79
79
  {CONTEXT_LIMIT_OPTIONS.map((option, index) => (
80
- <Text key={option.action} color={index === selected ? theme.accentPrimary : theme.text}>
80
+ <Text key={option.action} color={index === selected ? theme.accentPeriwinkle : theme.text}>
81
81
  {index === selected ? '> ' : ' '}
82
82
  {option.label}
83
83
  <Text color={theme.dim}>{` · ${option.detail}`}</Text>
@@ -19,32 +19,16 @@ export const ContinuityEditReviewView: React.FC<{
19
19
  onCancel: () => void
20
20
  }> = ({ review, onSelect, onCancel }) => (
21
21
  <Surface
22
- title="Private Continuity Updated"
23
- subtitle="Review the file, then save an encrypted snapshot."
24
- footer="enter select · esc later"
22
+ title={`${review.file} Updated`}
23
+ footer="enter select · esc dismiss"
25
24
  >
26
- <Box flexDirection="column">
27
- <Text color={theme.accentMint}>{review.summary}</Text>
28
- <Box marginTop={1} flexDirection="column">
29
- <Text color={theme.textSubtle}>review file</Text>
30
- <Text color={theme.text}>{review.filePath}</Text>
31
- </Box>
32
- {review.editorOpened && (
33
- <Box marginTop={1}>
34
- <Text color={theme.accentPeach}>Save with ctrl+s in your editor</Text>
35
- </Box>
36
- )}
37
- <Box marginTop={1} flexDirection="column">
38
- <Text color={theme.textSubtle}>saved locally</Text>
39
- <Text color={theme.dim}>Previous version saved in identity history. /rewind does not restore identity continuity.</Text>
40
- </Box>
41
- </Box>
25
+ <Text color={theme.accentPeriwinkle}>{displayContinuityReviewText(review.summary)}</Text>
42
26
  <Box marginTop={1}>
43
27
  <Select<ContinuityEditReviewAction>
44
28
  options={[
45
- { value: 'open', label: `open ${review.file}`, hint: 'review the edited private file now' },
46
- { value: 'save-publish', label: 'save snapshot now', hint: 'go directly to wallet approval' },
47
- { value: 'later', label: 'later', hint: 'keep the local draft unsaved' },
29
+ { value: 'open', label: `Open ${review.file}`, hint: 'Review in editor' },
30
+ { value: 'save-publish', label: 'Save Snapshot', hint: 'Wallet signature' },
31
+ { value: 'later', label: 'Dismiss', hint: 'Save later from Identity Hub' },
48
32
  ]}
49
33
  onSubmit={onSelect}
50
34
  onCancel={onCancel}
@@ -52,3 +36,7 @@ export const ContinuityEditReviewView: React.FC<{
52
36
  </Box>
53
37
  </Surface>
54
38
  )
39
+
40
+ function displayContinuityReviewText(value: string): string {
41
+ return value ? value[0]!.toUpperCase() + value.slice(1) : value
42
+ }
@@ -49,4 +49,3 @@ export const CopyPicker: React.FC<CopyPickerProps> = ({ turnText, turnLabel, onD
49
49
  )
50
50
  }
51
51
 
52
-
@@ -3,14 +3,25 @@ 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 { hidesSuccessfulToolResultContent } from './toolResultDisplay.js'
6
+
7
+ export type ToolCallResult = {
8
+ content: string
9
+ summary: string
10
+ isError: boolean
11
+ }
7
12
 
8
13
  export type MessageRow =
9
14
  | { role: 'user'; id: string; content: string }
10
15
  | { role: 'assistant'; id: string; content: string; liveTail?: string; streaming?: boolean }
11
16
  | { role: 'thinking'; id: string; content: string; liveTail?: string; streaming?: boolean; expanded?: boolean; showCursor?: boolean }
12
- | { role: 'tool_use'; id: string; name: string; summary: string; input?: string }
13
- | { role: 'tool_result'; id: string; name: string; summary: string; content: string; isError?: boolean }
17
+ | {
18
+ role: 'tool_call'
19
+ id: string
20
+ name: string
21
+ summary: string
22
+ input?: string
23
+ result?: ToolCallResult
24
+ }
14
25
  | { role: 'note'; id: string; kind: 'info' | 'error' | 'dim'; content: string }
15
26
  | {
16
27
  role: 'progress'
@@ -43,7 +54,7 @@ type InlineToken =
43
54
 
44
55
  const MAX_RENDERED_MESSAGE_CHARS = 12_000
45
56
  const MAX_RENDERED_REASONING_CHARS = 10_000
46
- const ASSISTANT_ACCENT = theme.accentMint
57
+ const ASSISTANT_ACCENT = theme.accentPeriwinkle
47
58
  const UNREADABLE_REASONING_TEXT = 'reasoning output was not readable text'
48
59
 
49
60
  const MessageListInner: React.FC<MessageListProps> = ({ rows }) => (
@@ -54,18 +65,27 @@ const MessageListInner: React.FC<MessageListProps> = ({ rows }) => (
54
65
 
55
66
  export const MessageList = React.memo(MessageListInner)
56
67
 
68
+ function isInspectableRole(role: MessageRow['role']): boolean {
69
+ return role === 'thinking'
70
+ }
71
+
57
72
  export function toggleLatestReasoningRow(rows: MessageRow[]): MessageRow[] {
58
- return toggleReasoningRow(rows)
73
+ return toggleInspectableRow(rows)
59
74
  }
60
75
 
61
76
  export function toggleReasoningRow(rows: MessageRow[], rowId?: string): MessageRow[] {
77
+ return toggleInspectableRow(rows, rowId)
78
+ }
79
+
80
+ export function toggleInspectableRow(rows: MessageRow[], rowId?: string): MessageRow[] {
62
81
  let index = -1
63
82
  if (rowId) {
64
- index = rows.findIndex(row => row.id === rowId && row.role === 'thinking')
83
+ index = rows.findIndex(row => row.id === rowId && isInspectableRole(row.role))
65
84
  }
66
85
  if (index === -1) {
67
86
  for (let cursor = rows.length - 1; cursor >= 0; cursor -= 1) {
68
- if (rows[cursor]?.role === 'thinking') {
87
+ const role = rows[cursor]?.role
88
+ if (role && isInspectableRole(role)) {
69
89
  index = cursor
70
90
  break
71
91
  }
@@ -73,10 +93,13 @@ export function toggleReasoningRow(rows: MessageRow[], rowId?: string): MessageR
73
93
  }
74
94
  if (index === -1) return rows
75
95
  const row = rows[index]
76
- if (!row || row.role !== 'thinking') return rows
77
- const next = rows.slice()
78
- next[index] = { ...row, expanded: !row.expanded }
79
- return next
96
+ if (!row) return rows
97
+ if (row.role === 'thinking') {
98
+ const next = rows.slice()
99
+ next[index] = { ...row, expanded: !row.expanded }
100
+ return next
101
+ }
102
+ return rows
80
103
  }
81
104
 
82
105
  const RowViewInner: React.FC<{ row: MessageRow }> = ({ row }) => {
@@ -90,7 +113,7 @@ const RowViewInner: React.FC<{ row: MessageRow }> = ({ row }) => {
90
113
  ) : null}
91
114
  {lines.map((line, i) => (
92
115
  <Text key={i}>
93
- <Text color={i === 0 ? theme.accentMint : theme.dim}>{i === 0 ? '> ' : ' '}</Text>
116
+ <Text color={i === 0 ? theme.accentPeriwinkle : theme.dim}>{i === 0 ? '> ' : ' '}</Text>
94
117
  <Text color={theme.textSubtle}>{line}</Text>
95
118
  </Text>
96
119
  ))}
@@ -115,7 +138,7 @@ const RowViewInner: React.FC<{ row: MessageRow }> = ({ row }) => {
115
138
  return (
116
139
  <Box flexDirection="column" marginTop={1} borderStyle="round" borderColor={borderColor} paddingX={1}>
117
140
  <Text>
118
- <Text color={theme.accentPeach} bold>reasoning</Text>
141
+ <Text color={theme.accentPeriwinkle} bold>reasoning</Text>
119
142
  <Text color={theme.dim}> · expanded · alt+t collapse</Text>
120
143
  </Text>
121
144
  <ReasoningBody content={text} showCursor={showCursor} />
@@ -125,7 +148,7 @@ const RowViewInner: React.FC<{ row: MessageRow }> = ({ row }) => {
125
148
  return (
126
149
  <Box flexDirection="column" marginTop={1} borderStyle="round" borderColor={borderColor} paddingX={1}>
127
150
  <Text>
128
- <Text color={theme.accentPeach} bold>reasoning</Text>
151
+ <Text color={theme.accentPeriwinkle} bold>reasoning</Text>
129
152
  <Text color={theme.dim}> · collapsed · alt+t inspect</Text>
130
153
  </Text>
131
154
  <Text color={theme.textSubtle}>
@@ -136,39 +159,27 @@ const RowViewInner: React.FC<{ row: MessageRow }> = ({ row }) => {
136
159
  )
137
160
  }
138
161
 
139
- if (row.role === 'tool_use') {
162
+ if (row.role === 'tool_call') {
163
+ const result = row.result
164
+ const inputPreview = row.input ? truncateToolInputForLine(row.input, 60) : ''
140
165
  return (
141
- <Box flexDirection="column" marginTop={1} borderStyle="round" borderColor={theme.border} paddingX={1}>
142
- <Text color={theme.accentNeutral} bold>{`tool · ${row.name}`}</Text>
143
- <Text color={theme.dim}>{row.summary}</Text>
144
- {row.input ? <Text color={theme.textSubtle}>{row.input}</Text> : null}
145
- </Box>
146
- )
147
- }
148
-
149
- if (row.role === 'tool_result') {
150
- const hideContent = hidesSuccessfulToolResultContent(row.name, row.isError)
151
- return (
152
- <Box
153
- flexDirection="column"
154
- marginTop={1}
155
- borderStyle="round"
156
- borderColor={row.isError ? '#a84c4c' : theme.border}
157
- paddingX={1}
158
- >
159
- <Text color={row.isError ? '#e87070' : theme.accentSecondary} bold>{`result · ${row.name}`}</Text>
160
- <Text color={theme.dim}>{row.summary}</Text>
161
- {row.isError ? (
162
- <Text color="#f1b0b0">{row.content}</Text>
163
- ) : hideContent || !row.content ? null : (
164
- <AssistantBody content={row.content} />
165
- )}
166
+ <Box marginTop={1}>
167
+ <Text>
168
+ <Text color={theme.dim}>{'· '}</Text>
169
+ <Text color={theme.accentPeriwinkle} bold>{row.name}</Text>
170
+ {inputPreview ? <Text color={theme.textSubtle}>{` ${inputPreview}`}</Text> : null}
171
+ {result ? (
172
+ <Text color={result.isError ? theme.accentError : theme.dim}>{` ${result.summary}`}</Text>
173
+ ) : (
174
+ <Text color={theme.dim}>{' running…'}</Text>
175
+ )}
176
+ </Text>
166
177
  </Box>
167
178
  )
168
179
  }
169
180
 
170
181
  if (row.role === 'note') {
171
- const color = row.kind === 'error' ? '#e87070' : row.kind === 'dim' ? theme.dim : theme.accentInfo
182
+ const color = row.kind === 'error' ? theme.accentError : row.kind === 'dim' ? theme.dim : theme.accentPeriwinkle
172
183
  return (
173
184
  <Box marginTop={1}>
174
185
  <Text color={color}>{row.content}</Text>
@@ -178,7 +189,7 @@ const RowViewInner: React.FC<{ row: MessageRow }> = ({ row }) => {
178
189
 
179
190
  return (
180
191
  <Box flexDirection="column" marginTop={1}>
181
- <Text color={theme.accentMint} bold>{row.title}</Text>
192
+ <Text color={theme.accentPeriwinkle} bold>{row.title}</Text>
182
193
  {row.indeterminate ? (
183
194
  <ProgressSpinner row={row} />
184
195
  ) : (
@@ -198,7 +209,13 @@ const ProgressSpinner: React.FC<{ row: Extract<MessageRow, { role: 'progress' }>
198
209
  }
199
210
 
200
211
  export function reasoningBorderColor(row: Extract<MessageRow, { role: 'thinking' }>): string {
201
- return row.streaming ? theme.accentPeach : theme.border
212
+ return row.streaming ? theme.accentPeriwinkle : theme.border
213
+ }
214
+
215
+ function truncateToolInputForLine(input: string, max: number): string {
216
+ const flat = input.replace(/\s+/g, ' ').trim()
217
+ if (flat.length <= max) return flat
218
+ return `${flat.slice(0, Math.max(1, max - 1))}…`
202
219
  }
203
220
 
204
221
  export function reasoningCursorVisible(row: Extract<MessageRow, { role: 'thinking' }>): boolean {
@@ -370,7 +387,7 @@ const InlineText: React.FC<{ text: string; color: string; bold?: boolean }> = ({
370
387
  const ThinkingCursor: React.FC<{ active: boolean; hasPreview: boolean }> = ({ active, hasPreview }) => {
371
388
  if (!active) return null
372
389
  return (
373
- <Text color={theme.accentPeach}>
390
+ <Text color={theme.accentPeriwinkle}>
374
391
  {hasPreview ? ' ' : ''}
375
392
  <StreamCursor active />
376
393
  </Text>
@@ -488,7 +505,7 @@ function parseMarkdownBlocks(markdown: string): MarkdownBlock[] {
488
505
  return blocks
489
506
  }
490
507
 
491
- function codeAccent(lang: string | null): string {
508
+ function codeAccent(_lang: string | null): string {
492
509
  return ASSISTANT_ACCENT
493
510
  }
494
511
 
@@ -23,23 +23,23 @@ export const PermissionPrompt: React.FC<PermissionPromptProps> = ({ request, onD
23
23
  >
24
24
  {request.kind === 'private-continuity-edit' ? (
25
25
  <Box flexDirection="column" marginBottom={1}>
26
- <Text color={theme.accentPeach}>{request.changeSummary}</Text>
26
+ <Text color={theme.accentPeriwinkle}>{displayPermissionText(request.changeSummary)}</Text>
27
27
  <Text color={theme.textSubtle}>
28
- Not reversible by /rewind. A private identity-history snapshot is saved before the edit is applied.
28
+ Not reversible by /rewind. A private identity-history snapshot is saved before writing.
29
29
  </Text>
30
30
  <Box marginTop={1}>
31
31
  <Text color={theme.textSubtle}>target</Text>
32
32
  </Box>
33
33
  <Text color={theme.text}>{request.file}</Text>
34
34
  <Box marginTop={1}>
35
- <Text color={theme.accentPrimary}>diff</Text>
35
+ <Text color={theme.accentPeriwinkle}>diff</Text>
36
36
  </Box>
37
37
  <Text color={theme.text}>{request.diff}</Text>
38
38
  </Box>
39
39
  ) : null}
40
40
  {request.kind === 'private-continuity-read' ? (
41
41
  <Box flexDirection="column" marginBottom={1}>
42
- <Text color={theme.accentPeach}>read private {request.file}</Text>
42
+ <Text color={theme.accentPeriwinkle}>read private {request.file}</Text>
43
43
  <Text color={theme.textSubtle}>This reveals private identity continuity to the model for this turn.</Text>
44
44
  <Box marginTop={1}>
45
45
  <Text color={theme.textSubtle}>range</Text>
@@ -49,20 +49,20 @@ export const PermissionPrompt: React.FC<PermissionPromptProps> = ({ request, onD
49
49
  ) : null}
50
50
  {request.kind === 'edit' || request.kind === 'write' || request.kind === 'delete' ? (
51
51
  <Box flexDirection="column" marginBottom={1}>
52
- <Text color={theme.accentPeach}>{request.changeSummary}</Text>
52
+ <Text color={theme.accentPeriwinkle}>{displayPermissionText(request.changeSummary)}</Text>
53
53
  <Box marginTop={1}>
54
54
  <Text color={theme.textSubtle}>before</Text>
55
55
  </Box>
56
56
  <Text color={theme.textSubtle}>{request.before || '(empty)'}</Text>
57
57
  <Box marginTop={1}>
58
- <Text color={theme.accentPrimary}>after</Text>
58
+ <Text color={theme.accentPeriwinkle}>after</Text>
59
59
  </Box>
60
60
  <Text color={theme.text}>{request.after || '(empty)'}</Text>
61
61
  </Box>
62
62
  ) : null}
63
63
  {request.kind === 'bash' && request.warning ? (
64
64
  <Box marginBottom={1}>
65
- <Text color="#e87070">{request.warning}</Text>
65
+ <Text color={theme.accentError}>{request.warning}</Text>
66
66
  </Box>
67
67
  ) : null}
68
68
  <Select options={options} onSubmit={onDecision} onCancel={onCancel} />
@@ -127,8 +127,8 @@ export function permissionOptionsForRequest(request: PermissionRequest): Array<{
127
127
 
128
128
  if (request.kind === 'private-continuity-edit') {
129
129
  return [
130
- { value: 'allow-once', label: 'approve once', hint: `apply this edit to ${request.file}` },
131
- { value: 'deny', label: 'deny', hint: 'keep private continuity unchanged' },
130
+ { value: 'allow-once', label: 'Approve Once', hint: `Apply this edit to ${request.file}` },
131
+ { value: 'deny', label: 'Deny', hint: 'Keep private continuity unchanged' },
132
132
  ]
133
133
  }
134
134
 
@@ -151,3 +151,7 @@ export function permissionOptionsForRequest(request: PermissionRequest): Array<{
151
151
  { value: 'deny', label: 'deny', hint: 'return a denial back to the model' },
152
152
  ]
153
153
  }
154
+
155
+ function displayPermissionText(value: string): string {
156
+ return value ? value[0]!.toUpperCase() + value.slice(1) : value
157
+ }
@@ -70,11 +70,11 @@ export const PlanApprovalView: React.FC<PlanApprovalViewProps> = ({
70
70
  const active = optionIndex === index
71
71
  return (
72
72
  <Box key={option.value} flexDirection="row">
73
- <Text color={active ? theme.accentMint : theme.dim}>
73
+ <Text color={active ? theme.accentPeriwinkle : theme.dim}>
74
74
  {active ? '> ' : ' '}
75
75
  {optionIndex + 1}.{' '}
76
76
  </Text>
77
- <Text color={active ? theme.accentMint : theme.text} bold={active}>
77
+ <Text color={active ? theme.accentPeriwinkle : theme.text} bold={active}>
78
78
  {option.label}
79
79
  </Text>
80
80
  </Box>
@@ -82,7 +82,7 @@ export const PlanApprovalView: React.FC<PlanApprovalViewProps> = ({
82
82
  })}
83
83
  </Box>
84
84
  <Box flexDirection="column" marginLeft={4} flexShrink={1}>
85
- <Text color={theme.accentMint} bold>{selected.title}</Text>
85
+ <Text color={theme.accentPeriwinkle} bold>{selected.title}</Text>
86
86
  <Text color={theme.dim}>{selected.detail(contextLabel)}</Text>
87
87
  </Box>
88
88
  </Box>
@@ -26,8 +26,6 @@ export const CLEAR_ALL_SESSIONS_VALUE = '__clear_all_sessions__'
26
26
  export const ResumeView: React.FC<ResumeViewProps> = ({ currentSessionId, onResume, onClearAll, onCancel }) => {
27
27
  const [state, setState] = useState<State>({ kind: 'loading' })
28
28
 
29
- // Allow ESC to close the view during loading / error states
30
- // (Select handles ESC only when it's rendered in the 'ready' state)
31
29
  const escActive = state.kind === 'loading' || state.kind === 'error' || (state.kind === 'ready' && state.sessions.length === 0)
32
30
  useAppInput((_input, key) => {
33
31
  if (key.escape) onCancel()
@@ -75,7 +73,7 @@ export const ResumeView: React.FC<ResumeViewProps> = ({ currentSessionId, onResu
75
73
  <Box flexDirection="column" marginBottom={1}>
76
74
  <Text color={theme.dim}>Removes saved chats and resume context from this machine.</Text>
77
75
  <Text color={theme.dim}>Config, identities, keys, and local models stay.</Text>
78
- {state.error ? <Text color="#e87070">{state.error}</Text> : null}
76
+ {state.error ? <Text color={theme.accentError}>{state.error}</Text> : null}
79
77
  </Box>
80
78
  <Select<'back' | 'clear'>
81
79
  options={[
@@ -212,7 +210,6 @@ export function buildResumeOptions(
212
210
  }
213
211
  }
214
212
 
215
- // Utility section sits below all session groups with a visual gap
216
213
  options.push(manageSpacer)
217
214
  options.push(clearOption)
218
215
 
@@ -312,7 +312,7 @@ function buildActionOptions(canRestoreConversation: boolean): Array<SelectOption
312
312
 
313
313
  const CompactPreview: React.FC<{ entry: RewindEntry }> = ({ entry }) => (
314
314
  <Box flexDirection="column" marginTop={1}>
315
- <Text color={theme.accentPrimary}>{entry.relativePath}</Text>
315
+ <Text color={theme.accentPeriwinkle}>{entry.relativePath}</Text>
316
316
  <Text color={theme.dim}>{entry.promptSnippet || '(prompt snippet unavailable for older checkpoints)'}</Text>
317
317
  </Box>
318
318
  )
@@ -323,7 +323,7 @@ const ActionPreview: React.FC<{
323
323
  canRestoreConversation: boolean
324
324
  }> = ({ entry, selectedAction, canRestoreConversation }) => (
325
325
  <Box flexDirection="column" marginTop={1}>
326
- <Text color={theme.accentPrimary}>{entry.relativePath}</Text>
326
+ <Text color={theme.accentPeriwinkle}>{entry.relativePath}</Text>
327
327
  <Text color={theme.dim}>{formatTimestamp(entry.createdAt)} · {entry.changeSummary}</Text>
328
328
  <Text color={theme.textSubtle}>
329
329
  {selectedAction === 'both'
@@ -73,7 +73,7 @@ export function moveThroughHistory(
73
73
  historyIndex: number,
74
74
  direction: 1 | -1,
75
75
  draftBuffer: ChatBuffer,
76
- preferredColumn: number | null,
76
+ _preferredColumn: number | null,
77
77
  ): { preview: HistoryPreviewState; buffer: ChatBuffer } {
78
78
  const next = historyIndex + direction
79
79
  if (next < 0) {
@@ -52,26 +52,37 @@ export function buildBaseMessages(
52
52
 
53
53
  export function sessionMessagesToRows(messages: SessionMessage[], nextRowId: () => string): MessageRow[] {
54
54
  const restored: MessageRow[] = []
55
+ const toolCallByUseId = new Map<string, Extract<MessageRow, { role: 'tool_call' }>>()
55
56
  for (const msg of messages) {
56
57
  if (msg.role === 'user') restored.push({ role: 'user', id: nextRowId(), content: msg.content })
57
58
  else if (msg.role === 'assistant') restored.push({ role: 'assistant', id: nextRowId(), content: msg.content })
58
59
  else if (msg.role === 'tool_use') {
59
- restored.push({
60
- role: 'tool_use',
60
+ const row: Extract<MessageRow, { role: 'tool_call' }> = {
61
+ role: 'tool_call',
61
62
  id: nextRowId(),
62
63
  name: msg.name,
63
64
  summary: msg.name,
64
65
  input: summarizeToolInput(msg.input),
65
- })
66
+ }
67
+ restored.push(row)
68
+ toolCallByUseId.set(msg.toolUseId, row)
66
69
  } else if (msg.role === 'tool_result') {
67
- restored.push({
68
- role: 'tool_result',
69
- id: nextRowId(),
70
- name: msg.name,
71
- summary: msg.isError ? `${msg.name} failed` : `${msg.name} completed`,
72
- content: toolResultContentForRow(msg.name, msg.content, msg.isError),
73
- isError: msg.isError,
74
- })
70
+ const isError = Boolean(msg.isError)
71
+ const summary = isError ? `${msg.name} failed` : `${msg.name} completed`
72
+ const content = toolResultContentForRow(msg.name, msg.content, msg.isError)
73
+ const existing = toolCallByUseId.get(msg.toolUseId)
74
+ if (existing) {
75
+ existing.result = { content, summary, isError }
76
+ existing.name = msg.name
77
+ } else {
78
+ restored.push({
79
+ role: 'tool_call',
80
+ id: nextRowId(),
81
+ name: msg.name,
82
+ summary: msg.name,
83
+ result: { content, summary, isError },
84
+ })
85
+ }
75
86
  }
76
87
  }
77
88
  return restored
@@ -49,7 +49,7 @@ export function resolveModelSelection(
49
49
  model: selection.model,
50
50
  baseUrl,
51
51
  },
52
- notice: `local Hugging Face model ready. now using ${formatModelDisplayName('llamacpp', selection.model, { maxLength: 64 })}.`,
52
+ notice: `Local Hugging Face model ready. Now using ${formatModelDisplayName('llamacpp', selection.model, { maxLength: 64 })}.`,
53
53
  tone: 'info',
54
54
  }
55
55
  }
@@ -69,7 +69,7 @@ export function resolveModelSelection(
69
69
  return {
70
70
  kind: 'switch',
71
71
  config: nextConfig,
72
- notice: `${selection.keyJustSet ? `${selection.provider} key saved.` : `${selection.provider} ready.`} now using ${nextConfig.provider} · ${formatModelDisplayName(nextConfig.provider, nextConfig.model, { maxLength: 64 })}.`,
72
+ notice: `${selection.keyJustSet ? `${selection.provider} key saved.` : `${selection.provider} ready.`} Now using ${nextConfig.provider} · ${formatModelDisplayName(nextConfig.provider, nextConfig.model, { maxLength: 64 })}.`,
73
73
  tone: 'dim',
74
74
  }
75
75
  }