ethagent 1.1.1 → 2.0.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.
Files changed (271) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +127 -29
  3. package/package.json +16 -9
  4. package/src/app/FirstRun.tsx +192 -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 +43 -18
  11. package/src/chat/ContextLimitView.tsx +4 -4
  12. package/src/chat/ContinuityEditReviewView.tsx +11 -17
  13. package/src/chat/ConversationStack.tsx +3 -0
  14. package/src/chat/CopyPicker.tsx +0 -1
  15. package/src/chat/MessageList.tsx +62 -45
  16. package/src/chat/PermissionPrompt.tsx +13 -9
  17. package/src/chat/PlanApprovalView.tsx +3 -3
  18. package/src/chat/ResumeView.tsx +1 -4
  19. package/src/chat/RewindView.tsx +2 -2
  20. package/src/chat/TranscriptView.tsx +6 -0
  21. package/src/chat/chatInputState.ts +1 -1
  22. package/src/chat/chatScreenUtils.ts +22 -11
  23. package/src/chat/chatSessionState.ts +2 -2
  24. package/src/chat/chatTurnOrchestrator.ts +16 -81
  25. package/src/chat/commands.ts +1 -1
  26. package/src/chat/textCursor.ts +1 -1
  27. package/src/chat/transcriptViewport.ts +2 -7
  28. package/src/cli/ResetConfirmView.tsx +1 -1
  29. package/src/cli/main.tsx +9 -3
  30. package/src/cli/preview.tsx +0 -5
  31. package/src/cli/updateNotice.ts +5 -3
  32. package/src/identity/continuity/editor.ts +7 -107
  33. package/src/identity/continuity/envelope.ts +1048 -40
  34. package/src/identity/continuity/history.ts +4 -4
  35. package/src/identity/continuity/localBackup.ts +249 -0
  36. package/src/identity/continuity/privateEdit/apply.ts +170 -0
  37. package/src/identity/continuity/privateEdit/diff.ts +82 -0
  38. package/src/identity/continuity/privateEdit/files.ts +23 -0
  39. package/src/identity/continuity/privateEdit/types.ts +28 -0
  40. package/src/identity/continuity/privateEdit.ts +10 -298
  41. package/src/identity/continuity/publicSkills.ts +8 -9
  42. package/src/identity/continuity/snapshots.ts +17 -6
  43. package/src/identity/continuity/storage/defaults.ts +111 -0
  44. package/src/identity/continuity/storage/files.ts +72 -0
  45. package/src/identity/continuity/storage/markdown.ts +81 -0
  46. package/src/identity/continuity/storage/paths.ts +24 -0
  47. package/src/identity/continuity/storage/scaffold.ts +124 -0
  48. package/src/identity/continuity/storage/status.ts +86 -0
  49. package/src/identity/continuity/storage/types.ts +27 -0
  50. package/src/identity/continuity/storage.ts +32 -507
  51. package/src/identity/continuity/zipWriter.ts +95 -0
  52. package/src/identity/crypto/backupEnvelope.ts +14 -247
  53. package/src/identity/crypto/eth.ts +7 -7
  54. package/src/identity/ens/agentRecords.ts +96 -0
  55. package/src/identity/ens/ensAutomation/contracts.ts +38 -0
  56. package/src/identity/ens/ensAutomation/delete.ts +80 -0
  57. package/src/identity/ens/ensAutomation/names.ts +14 -0
  58. package/src/identity/ens/ensAutomation/operators.ts +29 -0
  59. package/src/identity/ens/ensAutomation/read.ts +114 -0
  60. package/src/identity/ens/ensAutomation/root.ts +63 -0
  61. package/src/identity/ens/ensAutomation/setup.ts +284 -0
  62. package/src/identity/ens/ensAutomation/transactions.ts +107 -0
  63. package/src/identity/ens/ensAutomation/types.ts +126 -0
  64. package/src/identity/ens/ensAutomation.ts +29 -0
  65. package/src/identity/ens/ensLookup/client.ts +43 -0
  66. package/src/identity/ens/ensLookup/constants.ts +26 -0
  67. package/src/identity/ens/ensLookup/discovery.ts +70 -0
  68. package/src/identity/ens/ensLookup/names.ts +34 -0
  69. package/src/identity/ens/ensLookup/records.ts +45 -0
  70. package/src/identity/ens/ensLookup/resolve.ts +75 -0
  71. package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
  72. package/src/identity/ens/ensLookup/types.ts +38 -0
  73. package/src/identity/ens/ensLookup/validation.ts +72 -0
  74. package/src/identity/ens/ensLookup.ts +19 -0
  75. package/src/identity/ens/ensRegistration.ts +199 -0
  76. package/src/identity/ens/resolverDelegation.ts +48 -0
  77. package/src/identity/hub/IdentityHub.tsx +13 -815
  78. package/src/identity/hub/OperationalRoutes.tsx +370 -0
  79. package/src/identity/hub/Routes.tsx +361 -0
  80. package/src/identity/hub/advancedEnsValidation.ts +45 -0
  81. package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
  82. package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
  83. package/src/identity/hub/components/FlowTimeline.tsx +27 -0
  84. package/src/identity/hub/components/IdentitySummary.tsx +190 -0
  85. package/src/identity/hub/components/MenuScreen.tsx +237 -0
  86. package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
  87. package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
  88. package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
  89. package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
  90. package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
  91. package/src/identity/hub/effects/create.ts +310 -0
  92. package/src/identity/hub/effects/ens/flows.ts +218 -0
  93. package/src/identity/hub/effects/ens/index.ts +11 -0
  94. package/src/identity/hub/effects/ens/transactions.ts +239 -0
  95. package/src/identity/hub/effects/index.ts +74 -0
  96. package/src/identity/hub/effects/profile/profileState.ts +173 -0
  97. package/src/identity/hub/effects/publicProfile/index.ts +5 -0
  98. package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
  99. package/src/identity/hub/effects/rebackup/index.ts +7 -0
  100. package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
  101. package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
  102. package/src/identity/hub/effects/receipts.ts +46 -0
  103. package/src/identity/hub/effects/restore/apply.ts +112 -0
  104. package/src/identity/hub/effects/restore/auth.ts +159 -0
  105. package/src/identity/hub/effects/restore/discover.ts +86 -0
  106. package/src/identity/hub/effects/restore/envelopes.ts +21 -0
  107. package/src/identity/hub/effects/restore/fetch.ts +25 -0
  108. package/src/identity/hub/effects/restore/index.ts +22 -0
  109. package/src/identity/hub/effects/restore/recovery.ts +135 -0
  110. package/src/identity/hub/effects/restore/resolve.ts +102 -0
  111. package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
  112. package/src/identity/hub/effects/restore/shared.ts +91 -0
  113. package/src/identity/hub/effects/restoreAdmin.ts +93 -0
  114. package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
  115. package/src/identity/hub/effects/shared/snapshot.ts +336 -0
  116. package/src/identity/hub/effects/shared/sync.ts +190 -0
  117. package/src/identity/hub/effects/token-transfer/index.ts +6 -0
  118. package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
  119. package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
  120. package/src/identity/hub/effects/types.ts +53 -0
  121. package/src/identity/hub/effects/vault/preflight.ts +50 -0
  122. package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
  123. package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
  124. package/src/identity/hub/flows/continuity/RecoveryConfirmScreen.tsx +104 -0
  125. package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
  126. package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
  127. package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
  128. package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
  129. package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
  130. package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
  131. package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
  132. package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
  133. package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
  134. package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
  135. package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
  136. package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
  137. package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
  138. package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
  139. package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
  140. package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
  141. package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
  142. package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
  143. package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
  144. package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
  145. package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
  146. package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
  147. package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
  148. package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
  149. package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +25 -43
  150. package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
  151. package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
  152. package/src/identity/hub/identityHubReducer.ts +166 -101
  153. package/src/identity/hub/model/continuity.ts +94 -0
  154. package/src/identity/hub/model/copy.ts +35 -0
  155. package/src/identity/hub/model/custody.ts +54 -0
  156. package/src/identity/hub/model/ens.ts +49 -0
  157. package/src/identity/hub/model/errors.ts +140 -0
  158. package/src/identity/hub/model/format.ts +15 -0
  159. package/src/identity/hub/model/identity.ts +94 -0
  160. package/src/identity/hub/model/network.ts +32 -0
  161. package/src/identity/hub/model/transfer.ts +57 -0
  162. package/src/identity/hub/operatorWallets.ts +131 -0
  163. package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
  164. package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
  165. package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
  166. package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
  167. package/src/identity/hub/reconciliation/index.ts +21 -0
  168. package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
  169. package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
  170. package/src/identity/hub/txGuard.ts +51 -0
  171. package/src/identity/hub/types.ts +17 -0
  172. package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
  173. package/src/identity/hub/useIdentityHubController.ts +396 -0
  174. package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
  175. package/src/identity/hub/utils.ts +79 -0
  176. package/src/identity/identityCompat.ts +34 -0
  177. package/src/identity/profile/agentIcon.ts +61 -0
  178. package/src/identity/profile/imagePicker.ts +12 -12
  179. package/src/identity/registry/erc8004/abi.ts +14 -0
  180. package/src/identity/registry/erc8004/chains.ts +150 -0
  181. package/src/identity/registry/erc8004/client.ts +11 -0
  182. package/src/identity/registry/erc8004/discovery.ts +511 -0
  183. package/src/identity/registry/erc8004/metadata.ts +335 -0
  184. package/src/identity/registry/erc8004/ownership.ts +121 -0
  185. package/src/identity/registry/erc8004/preflight.ts +123 -0
  186. package/src/identity/registry/erc8004/transactions.ts +77 -0
  187. package/src/identity/registry/erc8004/types.ts +88 -0
  188. package/src/identity/registry/erc8004/uri.ts +59 -0
  189. package/src/identity/registry/erc8004/utils.ts +58 -0
  190. package/src/identity/registry/erc8004.ts +53 -1106
  191. package/src/identity/registry/fieldParsers.ts +28 -0
  192. package/src/identity/registry/operatorVault/bytecode.ts +98 -0
  193. package/src/identity/registry/operatorVault/constants.ts +38 -0
  194. package/src/identity/registry/operatorVault/read.ts +246 -0
  195. package/src/identity/registry/operatorVault/transactions.ts +81 -0
  196. package/src/identity/registry/operatorVault.ts +44 -0
  197. package/src/identity/storage/ipfs.ts +26 -24
  198. package/src/identity/wallet/browserWallet/gas.ts +41 -0
  199. package/src/identity/wallet/browserWallet/html.ts +106 -0
  200. package/src/identity/wallet/browserWallet/http.ts +28 -0
  201. package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
  202. package/src/identity/wallet/browserWallet/requests.ts +191 -0
  203. package/src/identity/wallet/browserWallet/session.ts +325 -0
  204. package/src/identity/wallet/browserWallet/types.ts +192 -0
  205. package/src/identity/wallet/browserWallet/validation.ts +74 -0
  206. package/src/identity/wallet/browserWallet.ts +30 -393
  207. package/src/identity/wallet/page/constants.ts +5 -0
  208. package/src/identity/wallet/page/controller.ts +251 -0
  209. package/src/identity/wallet/page/copy.ts +340 -0
  210. package/src/identity/wallet/page/grainient.ts +278 -0
  211. package/src/identity/wallet/page/html.ts +28 -0
  212. package/src/identity/wallet/page/markup.ts +50 -0
  213. package/src/identity/wallet/page/state.ts +9 -0
  214. package/src/identity/wallet/page/styles/base.ts +259 -0
  215. package/src/identity/wallet/page/styles/components.ts +262 -0
  216. package/src/identity/wallet/page/styles/index.ts +5 -0
  217. package/src/identity/wallet/page/styles/responsive.ts +247 -0
  218. package/src/identity/wallet/page/types.ts +47 -0
  219. package/src/identity/wallet/page/view.ts +535 -0
  220. package/src/identity/wallet/page/walletProvider.ts +70 -0
  221. package/src/identity/wallet/page.tsx +38 -0
  222. package/src/identity/wallet/walletPurposeCompat.ts +27 -0
  223. package/src/mcp/manager.ts +0 -1
  224. package/src/models/ModelPicker.tsx +36 -30
  225. package/src/models/catalog.ts +5 -2
  226. package/src/models/huggingface.ts +9 -9
  227. package/src/models/llamacpp.ts +13 -13
  228. package/src/models/modelDisplay.ts +75 -0
  229. package/src/models/modelPickerOptions.ts +16 -3
  230. package/src/models/modelRecommendation.ts +0 -1
  231. package/src/providers/errors.ts +16 -0
  232. package/src/providers/gemini.ts +252 -39
  233. package/src/providers/registry.ts +2 -2
  234. package/src/providers/retry.ts +1 -1
  235. package/src/runtime/sessionMode.ts +1 -1
  236. package/src/runtime/systemPrompt.ts +2 -0
  237. package/src/runtime/toolExecution.ts +18 -22
  238. package/src/runtime/toolIntent.ts +0 -20
  239. package/src/runtime/turn.ts +0 -92
  240. package/src/storage/atomicWrite.ts +4 -1
  241. package/src/storage/config.ts +181 -5
  242. package/src/storage/identity.ts +9 -3
  243. package/src/storage/secrets.ts +2 -2
  244. package/src/tools/bashSafety.ts +8 -0
  245. package/src/tools/changeDirectoryTool.ts +1 -1
  246. package/src/tools/deleteFileTool.ts +4 -4
  247. package/src/tools/editTool.ts +4 -4
  248. package/src/tools/editUtils.ts +5 -5
  249. package/src/tools/privateContinuityEditTool.ts +4 -5
  250. package/src/tools/privateContinuityReadTool.ts +1 -2
  251. package/src/tools/registry.ts +30 -0
  252. package/src/tools/writeFileTool.ts +5 -5
  253. package/src/ui/BrandSplash.tsx +20 -85
  254. package/src/ui/ProgressBar.tsx +3 -5
  255. package/src/ui/Select.tsx +21 -9
  256. package/src/ui/Spinner.tsx +38 -3
  257. package/src/ui/Surface.tsx +3 -3
  258. package/src/ui/TextInput.tsx +191 -29
  259. package/src/ui/theme.ts +7 -34
  260. package/src/utils/openExternal.ts +21 -0
  261. package/src/utils/withRetry.ts +47 -3
  262. package/src/identity/hub/identityHubEffects.ts +0 -937
  263. package/src/identity/hub/identityHubModel.ts +0 -291
  264. package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -144
  265. package/src/identity/hub/screens/EditProfileFlow.tsx +0 -145
  266. package/src/identity/hub/screens/IdentitySummary.tsx +0 -90
  267. package/src/identity/hub/screens/MenuScreen.tsx +0 -117
  268. package/src/identity/hub/screens/RecoveryConfirmScreen.tsx +0 -87
  269. package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
  270. package/src/identity/wallet/wallet-page/wallet.html +0 -1202
  271. /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,
@@ -143,6 +145,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
143
145
  const [mode, setMode] = useState<SessionMode>('chat')
144
146
  const [pendingPlan, setPendingPlan] = useState<PendingPlan | null>(null)
145
147
  const [compactionUi, setCompactionUi] = useState<CompactionUiState | null>(null)
148
+ const [canScrollTranscript, setCanScrollTranscript] = useState(false)
146
149
  const [sessionId, setSessionId] = useState<string>(() => newSessionId())
147
150
  const [sessionKey, setSessionKey] = useState<number>(0)
148
151
  const [cwd, setCwd] = useState<string>(() => syncCwdFromProcess())
@@ -180,6 +183,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
180
183
  const pendingContinuityEditReviewRef = useRef<ContinuityEditReviewState | null>(null)
181
184
  const contextModelSwitchPromptRef = useRef<string | null>(null)
182
185
  const mcpManagerRef = useRef<McpManager | null>(null)
186
+ const savePromptShownRef = useRef<boolean>(false)
183
187
 
184
188
  useEffect(() => { rowsRef.current = rows }, [rows])
185
189
  useEffect(() => { overlayRef.current = overlay }, [overlay])
@@ -205,6 +209,29 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
205
209
  })()
206
210
  }, [])
207
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
+
208
235
  useEffect(() => {
209
236
  void (async () => {
210
237
  try {
@@ -1094,6 +1121,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
1094
1121
  : `open failed: ${result.error}`,
1095
1122
  result.ok ? 'dim' : 'error',
1096
1123
  )
1124
+ if (result.ok) setContinuityEditReview(prev => prev ? { ...prev, editorOpened: true } : null)
1097
1125
  return
1098
1126
  }
1099
1127
  setContinuityEditReview(null)
@@ -1105,7 +1133,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
1105
1133
  })
1106
1134
  overlayRef.current = 'identity'
1107
1135
  setOverlay('identity')
1108
- pushNote('opening snapshot approval.', 'dim')
1136
+ pushNote('opening snapshot signature.', 'dim')
1109
1137
  return
1110
1138
  }
1111
1139
  overlayRef.current = 'none'
@@ -1361,8 +1389,8 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
1361
1389
 
1362
1390
  const contextLine = `${config.provider} · ${formatModelDisplayName(config.provider, config.model, { maxLength: 24 })} · ${compressHome(cwd)}`
1363
1391
  const tipLine = streaming
1364
- ? 'tip: you can keep typing and press enter to queue the next message · shift+enter for newline'
1365
- : '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'
1366
1394
 
1367
1395
  const placeholderHints = useMemo(() => {
1368
1396
  if (compactionUi) return ['compaction in progress · esc to cancel']
@@ -1371,36 +1399,28 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
1371
1399
 
1372
1400
  const exitHint = exitState.pending ? 'ctrl+c again to quit' : null
1373
1401
  const runtimeModeLabel = sessionModeLabel(mode)
1374
- const modeColor =
1375
- mode === 'plan'
1376
- ? theme.accentLavender
1377
- : mode === 'accept-edits'
1378
- ? theme.accentPeach
1379
- : theme.accentMint
1380
1402
  const footerRight = (
1381
1403
  <Box flexDirection="row">
1382
1404
  {exitHint ? (
1383
1405
  <>
1384
- <Text color={theme.accentPrimary}>{exitHint}</Text>
1406
+ <Text color={theme.text}>{exitHint}</Text>
1385
1407
  <Text color={theme.dim}> · </Text>
1386
1408
  </>
1387
1409
  ) : null}
1388
1410
  {runtimeModeLabel ? (
1389
1411
  <>
1390
- <Text color={modeColor}>{runtimeModeLabel}</Text>
1412
+ <Text bold>{runtimeModeLabel}</Text>
1391
1413
  <Text color={theme.dim}> (</Text>
1392
- <Text color={theme.accentMint}>shift+tab to cycle</Text>
1414
+ <Text color={theme.accentPeriwinkle}>shift+tab to cycle</Text>
1393
1415
  <Text color={theme.dim}>) · </Text>
1394
1416
  </>
1395
1417
  ) : (
1396
1418
  <>
1397
- <Text color={theme.accentMint}>shift+tab to cycle</Text>
1419
+ <Text color={theme.accentPeriwinkle}>shift+tab to cycle</Text>
1398
1420
  <Text color={theme.dim}> · </Text>
1399
1421
  </>
1400
1422
  )}
1401
- <Text color={theme.dim}>
1402
- {'pgup/pgdn scroll · alt+p model · alt+i identity'}
1403
- </Text>
1423
+ <Text color={theme.dim}>{chatFooterShortcutText(canScrollTranscript)}</Text>
1404
1424
  </Box>
1405
1425
  )
1406
1426
  const header = <BrandSplash contextLine={contextLine} tipLine={tipLine} updateNotice={updateNotice ?? null} />
@@ -1466,10 +1486,15 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
1466
1486
  )}
1467
1487
  sessionKey={sessionKey}
1468
1488
  onVisibleReasoningIdsChange={updateVisibleReasoningIds}
1489
+ onTranscriptScrollabilityChange={setCanScrollTranscript}
1469
1490
  />
1470
1491
  )
1471
1492
  }
1472
1493
 
1494
+ export function chatFooterShortcutText(canScrollTranscript: boolean): string {
1495
+ return `${canScrollTranscript ? 'pgup/pgdn scroll · ' : ''}alt+p model · alt+i identity`
1496
+ }
1497
+
1473
1498
  function formatContextLabel(usage: ContextUsage): string {
1474
1499
  if (!Number.isFinite(usage.usedTokens) || usage.usedTokens <= 0) return 'Estimated context: empty'
1475
1500
  return `Estimated context: ${usage.percent}% used`
@@ -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>
@@ -8,6 +8,7 @@ export type ContinuityEditReviewState = {
8
8
  file: 'SOUL.md' | 'MEMORY.md'
9
9
  filePath: string
10
10
  summary: string
11
+ editorOpened?: boolean
11
12
  }
12
13
 
13
14
  export type ContinuityEditReviewAction = 'open' | 'save-publish' | 'later'
@@ -18,27 +19,16 @@ export const ContinuityEditReviewView: React.FC<{
18
19
  onCancel: () => void
19
20
  }> = ({ review, onSelect, onCancel }) => (
20
21
  <Surface
21
- title="Private Continuity Updated"
22
- subtitle="Review the file, then save an encrypted snapshot."
23
- footer="enter select · esc later"
22
+ title={`${review.file} Updated`}
23
+ footer="enter select · esc dismiss"
24
24
  >
25
- <Box flexDirection="column">
26
- <Text color={theme.accentMint}>{review.summary}</Text>
27
- <Box marginTop={1} flexDirection="column">
28
- <Text color={theme.textSubtle}>review file</Text>
29
- <Text color={theme.text}>{review.filePath}</Text>
30
- </Box>
31
- <Box marginTop={1} flexDirection="column">
32
- <Text color={theme.textSubtle}>saved locally</Text>
33
- <Text color={theme.dim}>Previous version saved in identity history. /rewind does not restore identity continuity.</Text>
34
- </Box>
35
- </Box>
25
+ <Text color={theme.accentPeriwinkle}>{displayContinuityReviewText(review.summary)}</Text>
36
26
  <Box marginTop={1}>
37
27
  <Select<ContinuityEditReviewAction>
38
28
  options={[
39
- { value: 'open', label: `open ${review.file}`, hint: 'review the edited private file now' },
40
- { value: 'save-publish', label: 'save snapshot now', hint: 'go directly to wallet approval' },
41
- { 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' },
42
32
  ]}
43
33
  onSubmit={onSelect}
44
34
  onCancel={onCancel}
@@ -46,3 +36,7 @@ export const ContinuityEditReviewView: React.FC<{
46
36
  </Box>
47
37
  </Surface>
48
38
  )
39
+
40
+ function displayContinuityReviewText(value: string): string {
41
+ return value ? value[0]!.toUpperCase() + value.slice(1) : value
42
+ }
@@ -12,6 +12,7 @@ type ConversationStackProps = {
12
12
  status?: React.ReactNode
13
13
  sessionKey: number
14
14
  onVisibleReasoningIdsChange?: (ids: string[]) => void
15
+ onTranscriptScrollabilityChange?: (canScroll: boolean) => void
15
16
  }
16
17
 
17
18
  export const ConversationStack: React.FC<ConversationStackProps> = ({
@@ -23,6 +24,7 @@ export const ConversationStack: React.FC<ConversationStackProps> = ({
23
24
  status,
24
25
  sessionKey,
25
26
  onVisibleReasoningIdsChange,
27
+ onTranscriptScrollabilityChange,
26
28
  }) => {
27
29
  return (
28
30
  <Box flexDirection="column" padding={1}>
@@ -33,6 +35,7 @@ export const ConversationStack: React.FC<ConversationStackProps> = ({
33
35
  active={transcriptActive}
34
36
  bottomVariant={bottomVariant}
35
37
  onVisibleReasoningIdsChange={onVisibleReasoningIdsChange}
38
+ onScrollabilityChange={onTranscriptScrollabilityChange}
36
39
  />
37
40
  <Box marginTop={1} width="100%">
38
41
  {bottom}
@@ -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'
@@ -21,6 +21,7 @@ type TranscriptViewProps = {
21
21
  active?: boolean
22
22
  bottomVariant?: 'prompt' | 'overlay'
23
23
  onVisibleReasoningIdsChange?: (ids: string[]) => void
24
+ onScrollabilityChange?: (canScroll: boolean) => void
24
25
  }
25
26
 
26
27
  const PROMPT_RESERVED_LINES = 11
@@ -33,6 +34,7 @@ export const TranscriptView: React.FC<TranscriptViewProps> = ({
33
34
  active = true,
34
35
  bottomVariant = 'prompt',
35
36
  onVisibleReasoningIdsChange,
37
+ onScrollabilityChange,
36
38
  }) => {
37
39
  const { stdout } = useStdout()
38
40
  const columns = stdout.columns ?? process.stdout.columns ?? 80
@@ -89,6 +91,10 @@ export const TranscriptView: React.FC<TranscriptViewProps> = ({
89
91
  onVisibleReasoningIdsChange?.(visibleReasoningIds)
90
92
  }, [onVisibleReasoningIdsChange, visibleReasoningIds])
91
93
 
94
+ useEffect(() => {
95
+ onScrollabilityChange?.(metrics.maxScrollTop > 0)
96
+ }, [metrics.maxScrollTop, onScrollabilityChange])
97
+
92
98
  useAppInput((_input, key) => {
93
99
  if (key.pageUp) {
94
100
  const target = promptScrollTopForPageUp(
@@ -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) {