ethagent 3.3.3 → 4.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 (322) hide show
  1. package/.claude-plugin/marketplace.json +11 -0
  2. package/.claude-plugin/plugin.json +35 -0
  3. package/LICENSE +1 -1
  4. package/README.md +64 -104
  5. package/commands/ethagent.md +40 -0
  6. package/package.json +16 -16
  7. package/src/app/keybindings/KeybindingProvider.tsx +1 -6
  8. package/src/app/keybindings/types.ts +1 -6
  9. package/src/cli/ResetConfirmView.tsx +54 -53
  10. package/src/cli/demo.ts +86 -0
  11. package/src/cli/hookIo.ts +45 -0
  12. package/src/cli/main.tsx +94 -123
  13. package/src/cli/memoryGuard.ts +49 -0
  14. package/src/cli/reset.ts +28 -70
  15. package/src/cli/sessionStart.ts +33 -0
  16. package/src/cli/status.ts +46 -0
  17. package/src/cli/sync.ts +167 -0
  18. package/src/cli/syncAdapters/claude-code.ts +86 -0
  19. package/src/cli/syncAdapters/codex.ts +66 -0
  20. package/src/cli/syncAdapters/index.ts +45 -0
  21. package/src/cli/syncAdapters/managedBlock.ts +175 -0
  22. package/src/cli/syncAdapters/shared.ts +63 -0
  23. package/src/identity/continuity/envelopeParse.ts +20 -1
  24. package/src/identity/continuity/publicSkills.ts +3 -1
  25. package/src/identity/continuity/skills/publicSkillsSync.ts +2 -1
  26. package/src/identity/continuity/skills/scaffold.ts +5 -2
  27. package/src/identity/continuity/snapshots.ts +12 -5
  28. package/src/identity/continuity/storage/defaults.ts +20 -19
  29. package/src/identity/continuity/storage/status.ts +1 -1
  30. package/src/identity/ens/ensLookup/constants.ts +1 -1
  31. package/src/identity/manager/IdentityManager.tsx +33 -0
  32. package/src/identity/{hub → manager}/OperationalRoutes.tsx +37 -18
  33. package/src/identity/{hub → manager}/Routes.tsx +48 -34
  34. package/src/identity/{hub → manager}/continuity/ContinuityDashboardScreen.tsx +9 -19
  35. package/src/identity/{hub → manager}/continuity/RebackupStorageScreen.tsx +3 -3
  36. package/src/identity/manager/continuity/RecoveryConfirmScreen.tsx +102 -0
  37. package/src/identity/{hub → manager}/continuity/SavePromptScreen.tsx +2 -3
  38. package/src/identity/{hub → manager}/continuity/completion.ts +1 -1
  39. package/src/identity/{hub → manager}/continuity/effects.ts +1 -1
  40. package/src/identity/{hub → manager}/continuity/skills/DeleteSkillConfirmScreen.tsx +2 -2
  41. package/src/identity/{hub → manager}/continuity/skills/NewSkillScreen.tsx +0 -5
  42. package/src/identity/{hub → manager}/continuity/skills/NewSkillVisibilityScreen.tsx +4 -4
  43. package/src/identity/{hub → manager}/continuity/skills/SkillActionsScreen.tsx +6 -22
  44. package/src/identity/{hub → manager}/continuity/skills/SkillsTreeScreen.tsx +5 -17
  45. package/src/identity/{hub → manager}/continuity/snapshot.ts +1 -1
  46. package/src/identity/{hub → manager}/continuity/vault.ts +1 -1
  47. package/src/identity/{hub → manager}/create/CreateFlow.tsx +59 -32
  48. package/src/identity/{hub → manager}/create/effects.ts +19 -10
  49. package/src/identity/manager/create/importScan.ts +122 -0
  50. package/src/identity/{hub → manager}/custody/CustodyEditFlow.tsx +17 -61
  51. package/src/identity/{hub → manager}/custody/actions.ts +1 -15
  52. package/src/identity/{hub → manager}/custody/routes.tsx +20 -40
  53. package/src/identity/{hub → manager}/custody/transactions.ts +1 -0
  54. package/src/identity/{hub → manager}/custody/types.ts +1 -2
  55. package/src/identity/{hub → manager}/custody/useCustodyEffects.ts +1 -1
  56. package/src/identity/{hub → manager}/ens/EnsEditAdvancedScreens.tsx +2 -2
  57. package/src/identity/{hub → manager}/ens/EnsEditMaintenanceScreens.tsx +12 -23
  58. package/src/identity/{hub → manager}/ens/EnsEditReviewScreens.tsx +18 -42
  59. package/src/identity/{hub → manager}/ens/EnsEditRunners.tsx +1 -1
  60. package/src/identity/{hub → manager}/ens/EnsEditShared.tsx +0 -2
  61. package/src/identity/{hub → manager}/ens/EnsEditSimpleScreens.tsx +10 -19
  62. package/src/identity/{hub → manager}/ens/EnsFlow.tsx +133 -41
  63. package/src/identity/{hub → manager}/ens/EnsOperatorWalletsScreen.tsx +14 -19
  64. package/src/identity/{hub → manager}/ens/editCopy.ts +1 -14
  65. package/src/identity/{hub → manager}/profile/EditProfileFlow.tsx +99 -66
  66. package/src/identity/{hub → manager}/profile/effects.ts +1 -3
  67. package/src/identity/{hub → manager}/profile/operatorSave.ts +1 -1
  68. package/src/identity/{hub → manager}/profile/state.ts +1 -1
  69. package/src/identity/{hub/identityHubReducer.ts → manager/reducer.ts} +25 -26
  70. package/src/identity/{hub → manager}/restore/RestoreFlow.tsx +16 -24
  71. package/src/identity/{hub → manager}/restore/apply.ts +1 -1
  72. package/src/identity/{hub → manager}/restore/auth.ts +1 -1
  73. package/src/identity/{hub → manager}/restore/discover.ts +1 -1
  74. package/src/identity/{hub → manager}/restore/fetch.ts +1 -1
  75. package/src/identity/{hub → manager}/restore/restoreAdmin.ts +1 -1
  76. package/src/identity/{hub → manager}/restore/useRestoreEffects.ts +2 -9
  77. package/src/identity/{hub → manager}/settings/StorageCredentialScreen.tsx +10 -25
  78. package/src/identity/{hub → manager}/shared/components/DetailsScreen.tsx +5 -7
  79. package/src/identity/{hub → manager}/shared/components/ErrorScreen.tsx +6 -10
  80. package/src/identity/{hub → manager}/shared/components/FlowTimeline.tsx +4 -3
  81. package/src/identity/{hub → manager}/shared/components/IdentitySummary.tsx +19 -59
  82. package/src/identity/manager/shared/components/LazyMenu.tsx +147 -0
  83. package/src/identity/manager/shared/components/MenuScreen.tsx +220 -0
  84. package/src/identity/manager/shared/components/OperationCompleteScreen.tsx +28 -0
  85. package/src/identity/{hub → manager}/shared/components/UnlinkedIdentityScreen.tsx +9 -10
  86. package/src/identity/{hub → manager}/shared/components/WalletApprovalScreen.tsx +1 -2
  87. package/src/identity/manager/shared/components/Wordmark.tsx +54 -0
  88. package/src/identity/{hub → manager}/shared/components/menuFlagsFromReconciliation.ts +39 -15
  89. package/src/identity/{hub → manager}/shared/effects/profilePrep.ts +1 -1
  90. package/src/identity/manager/shared/effects/types.ts +30 -0
  91. package/src/identity/{hub → manager}/shared/model/copy.ts +0 -4
  92. package/src/identity/{hub → manager}/shared/model/errors.ts +32 -3
  93. package/src/identity/{hub → manager}/shared/model/network.ts +2 -2
  94. package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/hook.ts +5 -0
  95. package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/run.ts +1 -1
  96. package/src/identity/{hub/shared/reconciliation/useAgentReconciliation.ts → manager/shared/reconciliation/index.ts} +6 -0
  97. package/src/identity/{hub → manager}/shared/utils.ts +6 -10
  98. package/src/identity/{hub → manager}/transfer/TokenTransferFlow.tsx +3 -3
  99. package/src/identity/{hub → manager}/transfer/TokenTransferScreens.tsx +4 -10
  100. package/src/identity/{hub → manager}/transfer/effects.ts +1 -1
  101. package/src/identity/{hub → manager}/types.ts +5 -6
  102. package/src/identity/{hub/useIdentityHubContinuity.ts → manager/useContinuity.ts} +59 -27
  103. package/src/identity/{hub/useIdentityHubController.ts → manager/useController.ts} +38 -35
  104. package/src/identity/{hub/useIdentityHubSideEffects.ts → manager/useSideEffects.ts} +40 -4
  105. package/src/identity/registry/erc8004/discovery.ts +3 -17
  106. package/src/identity/registry/erc8004/utils.ts +1 -1
  107. package/src/identity/storage/ipfs.ts +21 -1
  108. package/src/identity/wallet/browserWallet/html.ts +10 -2
  109. package/src/identity/wallet/browserWallet/http.ts +18 -0
  110. package/src/identity/wallet/browserWallet/requestServer.ts +5 -1
  111. package/src/identity/wallet/browserWallet/requests.ts +10 -28
  112. package/src/identity/wallet/browserWallet/session.ts +26 -33
  113. package/src/identity/wallet/browserWallet/validation.ts +14 -0
  114. package/src/identity/wallet/browserWallet/walletPageSource.ts +22 -40
  115. package/src/identity/wallet/page/boot.ts +43 -0
  116. package/src/identity/wallet/page/config.ts +59 -0
  117. package/src/identity/wallet/page/constants.ts +12 -0
  118. package/src/identity/wallet/page/copy.ts +47 -68
  119. package/src/identity/wallet/page/css.ts +638 -0
  120. package/src/identity/wallet/page/{errorView.ts → errors.ts} +5 -14
  121. package/src/identity/wallet/page/{controller.ts → flow.ts} +4 -71
  122. package/src/identity/wallet/page/markup.ts +44 -34
  123. package/src/identity/wallet/page/{walletProvider.ts → provider.ts} +0 -3
  124. package/src/identity/wallet/page/resize.ts +95 -0
  125. package/src/identity/wallet/page/state.ts +135 -8
  126. package/src/identity/wallet/page/timeline.ts +161 -0
  127. package/src/identity/wallet/page/view.ts +22 -302
  128. package/src/storage/config.ts +30 -80
  129. package/src/storage/reset.ts +31 -0
  130. package/src/storage/secrets.ts +1 -16
  131. package/src/ui/Select.tsx +27 -5
  132. package/src/ui/Spinner.tsx +16 -15
  133. package/src/ui/Surface.tsx +21 -17
  134. package/src/ui/TextArea.tsx +173 -0
  135. package/src/ui/TextInput.tsx +31 -133
  136. package/src/ui/theme.ts +22 -13
  137. package/src/utils/clipboard.ts +0 -140
  138. package/src/app/FirstRun.tsx +0 -577
  139. package/src/app/FirstRunTimeline.tsx +0 -51
  140. package/src/app/firstRunConfig.ts +0 -26
  141. package/src/app/hooks/useCancelRequest.ts +0 -22
  142. package/src/app/hooks/useDoublePress.ts +0 -46
  143. package/src/app/hooks/useExitOnCtrlC.ts +0 -36
  144. package/src/auth/openaiOAuth/credentials.ts +0 -47
  145. package/src/auth/openaiOAuth/crypto.ts +0 -23
  146. package/src/auth/openaiOAuth/index.ts +0 -238
  147. package/src/auth/openaiOAuth/landingPage.ts +0 -116
  148. package/src/auth/openaiOAuth/listener.ts +0 -151
  149. package/src/auth/openaiOAuth/refresh.ts +0 -70
  150. package/src/auth/openaiOAuth/shared.ts +0 -115
  151. package/src/chat/ChatBottomPane.tsx +0 -296
  152. package/src/chat/ChatScreen.tsx +0 -1685
  153. package/src/chat/ConversationStack.tsx +0 -56
  154. package/src/chat/MessageList.tsx +0 -638
  155. package/src/chat/SessionStatus.tsx +0 -53
  156. package/src/chat/chatEnvironment.ts +0 -16
  157. package/src/chat/chatScreenUtils.ts +0 -194
  158. package/src/chat/chatSessionState.ts +0 -146
  159. package/src/chat/chatTurnContext.ts +0 -50
  160. package/src/chat/chatTurnOrchestrator.ts +0 -603
  161. package/src/chat/chatTurnRows.ts +0 -64
  162. package/src/chat/commands.ts +0 -494
  163. package/src/chat/continuityEditReview.ts +0 -42
  164. package/src/chat/display/DiffView.tsx +0 -193
  165. package/src/chat/display/SyntaxText.tsx +0 -192
  166. package/src/chat/display/toolCallDisplay.ts +0 -103
  167. package/src/chat/display/toolResultDisplay.ts +0 -19
  168. package/src/chat/input/ChatInput.tsx +0 -625
  169. package/src/chat/input/chatInputHelpers.ts +0 -62
  170. package/src/chat/input/chatInputState.ts +0 -247
  171. package/src/chat/input/chatPaste.ts +0 -49
  172. package/src/chat/input/imageRefs.ts +0 -30
  173. package/src/chat/input/inputRendering.tsx +0 -93
  174. package/src/chat/input/textCursor.ts +0 -212
  175. package/src/chat/messageMarkdown.ts +0 -220
  176. package/src/chat/messageRows.ts +0 -43
  177. package/src/chat/planImplementation.ts +0 -62
  178. package/src/chat/slashCommandHandlers.ts +0 -122
  179. package/src/chat/slashCommandViews.ts +0 -120
  180. package/src/chat/transcript/TranscriptView.tsx +0 -184
  181. package/src/chat/transcript/transcriptViewport.ts +0 -295
  182. package/src/chat/views/ContextLimitView.tsx +0 -95
  183. package/src/chat/views/ContinuityEditReviewView.tsx +0 -50
  184. package/src/chat/views/CopyPicker.tsx +0 -50
  185. package/src/chat/views/PermissionPrompt.tsx +0 -156
  186. package/src/chat/views/PermissionsView.tsx +0 -165
  187. package/src/chat/views/PlanApprovalView.tsx +0 -91
  188. package/src/chat/views/ResumeView.tsx +0 -273
  189. package/src/chat/views/RewindView.tsx +0 -412
  190. package/src/cli/preview.tsx +0 -14
  191. package/src/cli/updateNotice.ts +0 -54
  192. package/src/identity/continuity/privateEdit/apply.ts +0 -170
  193. package/src/identity/continuity/privateEdit/diff.ts +0 -6
  194. package/src/identity/continuity/privateEdit/files.ts +0 -23
  195. package/src/identity/continuity/privateEdit/types.ts +0 -28
  196. package/src/identity/continuity/privateEdit.ts +0 -46
  197. package/src/identity/hub/IdentityHub.tsx +0 -14
  198. package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +0 -104
  199. package/src/identity/hub/ens/effects.ts +0 -218
  200. package/src/identity/hub/shared/components/MenuScreen.tsx +0 -241
  201. package/src/identity/hub/shared/effects/types.ts +0 -53
  202. package/src/identity/hub/shared/reconciliation/index.ts +0 -14
  203. package/src/identity/wallet/page/grainient.ts +0 -278
  204. package/src/identity/wallet/page/html.ts +0 -28
  205. package/src/identity/wallet/page/styles/base.ts +0 -259
  206. package/src/identity/wallet/page/styles/components.ts +0 -262
  207. package/src/identity/wallet/page/styles/index.ts +0 -5
  208. package/src/identity/wallet/page/styles/responsive.ts +0 -247
  209. package/src/identity/wallet/page.tsx +0 -38
  210. package/src/mcp/approvals.ts +0 -113
  211. package/src/mcp/config.ts +0 -235
  212. package/src/mcp/manager.ts +0 -482
  213. package/src/mcp/managerHelpers.ts +0 -70
  214. package/src/mcp/names.ts +0 -19
  215. package/src/mcp/output.ts +0 -96
  216. package/src/models/ModelPicker.tsx +0 -1009
  217. package/src/models/catalog.ts +0 -327
  218. package/src/models/huggingface.ts +0 -712
  219. package/src/models/huggingfaceStorage.ts +0 -136
  220. package/src/models/llamacpp.ts +0 -848
  221. package/src/models/llamacppCommands.ts +0 -44
  222. package/src/models/llamacppConfig.ts +0 -34
  223. package/src/models/llamacppDiscovery.ts +0 -176
  224. package/src/models/llamacppOutput.ts +0 -65
  225. package/src/models/llamacppPreflight.ts +0 -158
  226. package/src/models/modelDisplay.ts +0 -180
  227. package/src/models/modelPickerCatalogFlow.ts +0 -56
  228. package/src/models/modelPickerCredentials.ts +0 -166
  229. package/src/models/modelPickerData.ts +0 -41
  230. package/src/models/modelPickerDisplay.tsx +0 -132
  231. package/src/models/modelPickerHfFlow.ts +0 -192
  232. package/src/models/modelPickerLocalRunnerFlow.ts +0 -115
  233. package/src/models/modelPickerOptions.ts +0 -457
  234. package/src/models/modelPickerTypes.ts +0 -69
  235. package/src/models/modelPickerUninstallFlow.ts +0 -48
  236. package/src/models/modelPickerViewHelpers.ts +0 -174
  237. package/src/models/modelRecommendation.ts +0 -139
  238. package/src/models/providerDisplay.ts +0 -16
  239. package/src/models/runtimeDetection.ts +0 -81
  240. package/src/models/uncensoredCatalog.ts +0 -86
  241. package/src/providers/anthropic.ts +0 -290
  242. package/src/providers/contracts.ts +0 -71
  243. package/src/providers/errors.ts +0 -80
  244. package/src/providers/gemini.ts +0 -391
  245. package/src/providers/openai-chat.ts +0 -474
  246. package/src/providers/openai-responses-format.ts +0 -177
  247. package/src/providers/openai-responses.ts +0 -306
  248. package/src/providers/openaiChatWire.ts +0 -124
  249. package/src/providers/registry.ts +0 -120
  250. package/src/providers/retry.ts +0 -58
  251. package/src/providers/sse.ts +0 -93
  252. package/src/runtime/compaction.ts +0 -395
  253. package/src/runtime/cwd.ts +0 -43
  254. package/src/runtime/providerTurn.ts +0 -38
  255. package/src/runtime/sessionMode.ts +0 -55
  256. package/src/runtime/systemPrompt.ts +0 -213
  257. package/src/runtime/textToolParser.ts +0 -161
  258. package/src/runtime/toolClaimGuards.ts +0 -143
  259. package/src/runtime/toolExecution.ts +0 -304
  260. package/src/runtime/toolIntent.ts +0 -143
  261. package/src/runtime/turn.ts +0 -369
  262. package/src/runtime/turnNudges.ts +0 -223
  263. package/src/runtime/turnTypes.ts +0 -86
  264. package/src/storage/factoryReset.ts +0 -127
  265. package/src/storage/history.ts +0 -58
  266. package/src/storage/permissions.ts +0 -76
  267. package/src/storage/rewind.ts +0 -266
  268. package/src/storage/sessionExport.ts +0 -49
  269. package/src/storage/sessions.ts +0 -495
  270. package/src/tools/bashSafety.ts +0 -186
  271. package/src/tools/bashTool.ts +0 -140
  272. package/src/tools/changeDirectoryTool.ts +0 -213
  273. package/src/tools/contracts.ts +0 -192
  274. package/src/tools/deleteFileTool.ts +0 -116
  275. package/src/tools/editTool.ts +0 -165
  276. package/src/tools/editUtils.ts +0 -170
  277. package/src/tools/fileDiff.ts +0 -261
  278. package/src/tools/listDirectoryTool.ts +0 -55
  279. package/src/tools/listSkillFilesTool.ts +0 -77
  280. package/src/tools/listSkillsTool.ts +0 -68
  281. package/src/tools/mcpResourceTools.ts +0 -95
  282. package/src/tools/permissionRules.ts +0 -85
  283. package/src/tools/privateContinuityEditTool.ts +0 -187
  284. package/src/tools/privateContinuityReadTool.ts +0 -106
  285. package/src/tools/readSkillTool.ts +0 -107
  286. package/src/tools/readTool.ts +0 -85
  287. package/src/tools/registry.ts +0 -103
  288. package/src/tools/writeFileTool.ts +0 -167
  289. package/src/ui/BrandSplash.tsx +0 -133
  290. package/src/ui/terminalTitle.ts +0 -30
  291. package/src/utils/images.ts +0 -140
  292. package/src/utils/markdownSegments.ts +0 -51
  293. package/src/utils/messages.ts +0 -37
  294. package/src/utils/withRetry.ts +0 -324
  295. /package/src/identity/{hub → manager}/continuity/state.ts +0 -0
  296. /package/src/identity/{hub → manager}/custody/helpers.ts +0 -0
  297. /package/src/identity/{hub → manager}/custody/preflight.ts +0 -0
  298. /package/src/identity/{hub → manager}/custody/state.ts +0 -0
  299. /package/src/identity/{hub → manager}/custody/useCustodyFlow.tsx +0 -0
  300. /package/src/identity/{hub → manager}/ens/EnsEditFlow.tsx +0 -0
  301. /package/src/identity/{hub → manager}/ens/advancedEnsValidation.ts +0 -0
  302. /package/src/identity/{hub → manager}/ens/state.ts +0 -0
  303. /package/src/identity/{hub → manager}/ens/transactions.ts +0 -0
  304. /package/src/identity/{hub → manager}/ens/types.ts +0 -0
  305. /package/src/identity/{hub → manager}/profile/identity.ts +0 -0
  306. /package/src/identity/{hub → manager}/restore/envelopes.ts +0 -0
  307. /package/src/identity/{hub → manager}/restore/helpers.ts +0 -0
  308. /package/src/identity/{hub → manager}/restore/recovery.ts +0 -0
  309. /package/src/identity/{hub → manager}/restore/resolve.ts +0 -0
  310. /package/src/identity/{hub → manager}/shared/components/BusyScreen.tsx +0 -0
  311. /package/src/identity/{hub → manager}/shared/components/NetworkScreen.tsx +0 -0
  312. /package/src/identity/{hub → manager}/shared/components/PinataJwtInput.tsx +0 -0
  313. /package/src/identity/{hub → manager}/shared/effects/receipts.ts +0 -0
  314. /package/src/identity/{hub → manager}/shared/effects/sync.ts +0 -0
  315. /package/src/identity/{hub → manager}/shared/model/format.ts +0 -0
  316. /package/src/identity/{hub → manager}/shared/operatorWallets.ts +0 -0
  317. /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/ownership.ts +0 -0
  318. /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/types.ts +0 -0
  319. /package/src/identity/{hub → manager}/shared/reconciliation/walletSetup.ts +0 -0
  320. /package/src/identity/{hub → manager}/shared/txGuard.ts +0 -0
  321. /package/src/identity/{hub → manager}/transfer/progress.ts +0 -0
  322. /package/src/identity/{hub → manager}/transfer/state.ts +0 -0
@@ -1,412 +0,0 @@
1
- import React, { useEffect, useState } from 'react'
2
- import fs from 'node:fs/promises'
3
- import path from 'node:path'
4
- import { Box, Text } from 'ink'
5
- import { Surface } from '../../ui/Surface.js'
6
- import { Select, type SelectOption } from '../../ui/Select.js'
7
- import { Spinner } from '../../ui/Spinner.js'
8
- import { theme } from '../../ui/theme.js'
9
- import { useAppInput } from '../../app/input/AppInputProvider.js'
10
- import {
11
- groupRewindEntriesByTurn,
12
- rewindWorkspaceEditsByEntryIds,
13
- type RewindEntry,
14
- } from '../../storage/rewind.js'
15
- import { loadSession } from '../../storage/sessions.js'
16
- import { computeLineDiffStats } from '../../tools/fileDiff.js'
17
-
18
- type RestoreAction = 'both' | 'conversation' | 'code' | 'summarize'
19
- type ConfirmOption = RestoreAction | 'nevermind'
20
-
21
- type RewindViewProps = {
22
- cwd: string
23
- currentSessionId: string
24
- onRestoreConversation: (turnId: string, promptText?: string) => void
25
- onSummarizeFromTurn: (turnId: string) => void | Promise<unknown>
26
- onDone: (message: string, variant?: 'info' | 'error' | 'dim') => void
27
- onCancel: () => void
28
- }
29
-
30
- type DiffStats = {
31
- files: string[]
32
- inserts: number
33
- deletes: number
34
- }
35
-
36
- type PromptRow = {
37
- turnId: string
38
- text: string
39
- createdAt: string
40
- entries: RewindEntry[]
41
- stats: DiffStats
42
- }
43
-
44
- type State =
45
- | { kind: 'loading' }
46
- | { kind: 'error'; message: string }
47
- | { kind: 'picker' }
48
- | { kind: 'confirm'; turnId: string; selectedAction: ConfirmOption }
49
- | { kind: 'restoring' }
50
-
51
- export const RewindView: React.FC<RewindViewProps> = ({
52
- cwd,
53
- currentSessionId,
54
- onRestoreConversation,
55
- onSummarizeFromTurn,
56
- onDone,
57
- onCancel,
58
- }) => {
59
- const [state, setState] = useState<State>({ kind: 'loading' })
60
- const [rows, setRows] = useState<PromptRow[]>([])
61
-
62
- useEffect(() => {
63
- let cancelled = false
64
- void (async () => {
65
- try {
66
- const [sessionMessages, grouped] = await Promise.all([
67
- loadSession(currentSessionId),
68
- groupRewindEntriesByTurn(cwd, currentSessionId),
69
- ])
70
- const prompts: PromptRow[] = []
71
- for (const msg of sessionMessages) {
72
- if (msg.role !== 'user') continue
73
- if (msg.synthetic) continue
74
- if (!msg.turnId) continue
75
- const entries = grouped.get(msg.turnId) ?? []
76
- const stats = await computeDiffStatsForEntries(entries)
77
- prompts.push({
78
- turnId: msg.turnId,
79
- text: msg.content,
80
- createdAt: msg.createdAt,
81
- entries,
82
- stats,
83
- })
84
- }
85
- if (cancelled) return
86
- setRows(prompts)
87
- setState({ kind: 'picker' })
88
- } catch (err: unknown) {
89
- if (cancelled) return
90
- setState({ kind: 'error', message: (err as Error).message })
91
- }
92
- })()
93
- return () => { cancelled = true }
94
- }, [cwd, currentSessionId])
95
-
96
- const escActive =
97
- state.kind === 'loading' ||
98
- state.kind === 'error' ||
99
- (state.kind === 'picker' && rows.length === 0)
100
- useAppInput((_input, key) => {
101
- if (key.escape) onCancel()
102
- }, { isActive: escActive })
103
-
104
- if (state.kind === 'loading') {
105
- return (
106
- <Surface title="Rewind" subtitle="Loading session history...">
107
- <Spinner label="Loading rewind history..." />
108
- </Surface>
109
- )
110
- }
111
-
112
- if (state.kind === 'error') {
113
- return (
114
- <Surface title="Rewind" tone="muted" footer="Esc closes.">
115
- <Text color={theme.dim}>{state.message}</Text>
116
- </Surface>
117
- )
118
- }
119
-
120
- if (state.kind === 'restoring') {
121
- return (
122
- <Surface title="Rewind" subtitle="Restoring..." footer="Restoring...">
123
- <Spinner label="Restoring..." />
124
- </Surface>
125
- )
126
- }
127
-
128
- if (rows.length === 0) {
129
- return (
130
- <Surface title="Rewind" tone="muted" footer="Esc closes.">
131
- <Text color={theme.dim}>Nothing to rewind to yet.</Text>
132
- </Surface>
133
- )
134
- }
135
-
136
- if (state.kind === 'confirm') {
137
- const selectedRow = rows.find(row => row.turnId === state.turnId)
138
- if (!selectedRow) {
139
- setState({ kind: 'picker' })
140
- return null
141
- }
142
- const canRestoreCode = selectedRow.entries.length > 0
143
-
144
- const actionOptions: Array<SelectOption<ConfirmOption>> = [
145
- { value: 'both', role: 'section', label: 'Restore' },
146
- { value: 'both', prefix: '1.', label: 'Restore code and conversation', disabled: !canRestoreCode },
147
- { value: 'conversation', prefix: '2.', label: 'Restore conversation' },
148
- { value: 'code', prefix: '3.', label: 'Restore code', disabled: !canRestoreCode },
149
- { value: 'summarize', prefix: '4.', label: 'Summarize from here' },
150
- { value: 'nevermind', role: 'section', label: 'Navigation' },
151
- { value: 'nevermind', prefix: '5.', label: 'Never mind', role: 'utility' },
152
- ]
153
- const defaultValue: ConfirmOption = canRestoreCode ? 'both' : 'conversation'
154
- const initialIndex = actionOptions.findIndex(option => option.value === defaultValue && !option.disabled)
155
-
156
- const runRestore = async (action: RestoreAction) => {
157
- if (action === 'summarize') {
158
- try {
159
- await onSummarizeFromTurn(state.turnId)
160
- onDone('Summarizing the conversation from this point...', 'dim')
161
- } catch (err: unknown) {
162
- onDone(`Summarize failed: ${(err as Error).message}`, 'error')
163
- }
164
- return
165
- }
166
- setState({ kind: 'restoring' })
167
- const codeIds = selectedRow.entries.map(entry => entry.id)
168
- let codeError: Error | null = null
169
- let codeFiles: string[] = []
170
- let conversationError: Error | null = null
171
-
172
- if (action === 'code' || action === 'both') {
173
- try {
174
- const result = await rewindWorkspaceEditsByEntryIds(cwd, codeIds)
175
- codeFiles = result.files
176
- if (result.reverted === 0) {
177
- codeError = new Error('No matching rewind entries were found for this prompt.')
178
- }
179
- } catch (err: unknown) {
180
- codeError = err as Error
181
- }
182
- }
183
- if (action === 'conversation' || action === 'both') {
184
- try {
185
- onRestoreConversation(state.turnId, selectedRow.text)
186
- } catch (err: unknown) {
187
- conversationError = err as Error
188
- }
189
- }
190
-
191
- if (codeError && conversationError) {
192
- onDone(`Rewind failed: ${codeError.message}; ${conversationError.message}`, 'error')
193
- return
194
- }
195
- if (codeError) {
196
- onDone(`Code restore failed: ${codeError.message}`, 'error')
197
- return
198
- }
199
- if (conversationError) {
200
- onDone(`Conversation restore failed: ${conversationError.message}`, 'error')
201
- return
202
- }
203
-
204
- if (action === 'both') {
205
- const files = describeFileList(codeFiles, cwd)
206
- onDone(`Restored code and conversation to before this prompt.${files ? ` Files: ${files}.` : ''}`, 'dim')
207
- } else if (action === 'code') {
208
- const files = describeFileList(codeFiles, cwd)
209
- onDone(`Restored code to before this prompt.${files ? ` Files: ${files}.` : ''}`, 'dim')
210
- } else {
211
- onDone('Restored conversation to before this prompt.', 'dim')
212
- }
213
- }
214
-
215
- return (
216
- <Surface
217
- title="Rewind"
218
- subtitle="Confirm you want to restore to the point before you sent this message."
219
- footer="Enter to restore. Esc to go back."
220
- >
221
- <Box flexDirection="column">
222
- <Text color={theme.accentPeriwinkle}>{truncate(selectedRow.text, 280)}</Text>
223
- <Text color={theme.dim}>
224
- {formatTimestamp(selectedRow.createdAt)}
225
- {describeStats(selectedRow.stats, cwd) ? ` · ${describeStats(selectedRow.stats, cwd)}` : ''}
226
- </Text>
227
- </Box>
228
- <Box marginTop={1}>
229
- <Select
230
- options={actionOptions}
231
- initialIndex={initialIndex < 0 ? 1 : initialIndex}
232
- onSubmit={value => {
233
- if (value === 'nevermind') {
234
- setState({ kind: 'picker' })
235
- return
236
- }
237
- void runRestore(value)
238
- }}
239
- onCancel={() => setState({ kind: 'picker' })}
240
- onHighlight={value => {
241
- setState(prev => prev.kind === 'confirm'
242
- ? { ...prev, selectedAction: value }
243
- : prev)
244
- }}
245
- />
246
- </Box>
247
- <Box marginTop={1} flexDirection="column">
248
- <ConfirmDescription
249
- action={state.selectedAction}
250
- stats={selectedRow.stats}
251
- cwd={cwd}
252
- canRestoreCode={canRestoreCode}
253
- />
254
- </Box>
255
- </Surface>
256
- )
257
- }
258
-
259
- const pickerOptions = buildPickerOptions(rows, cwd)
260
- const lastIndex = Math.max(0, rows.length - 1)
261
-
262
- return (
263
- <Surface
264
- title="Rewind"
265
- subtitle="Restore the code and/or conversation to the point before a prior prompt."
266
- footer="Enter to continue. Esc to exit."
267
- >
268
- <Select
269
- options={pickerOptions}
270
- initialIndex={lastIndex}
271
- maxVisible={7}
272
- onSubmit={turnId => {
273
- const row = rows.find(r => r.turnId === turnId)
274
- const canRestoreCode = (row?.entries.length ?? 0) > 0
275
- setState({
276
- kind: 'confirm',
277
- turnId,
278
- selectedAction: canRestoreCode ? 'both' : 'conversation',
279
- })
280
- }}
281
- onCancel={onCancel}
282
- />
283
- </Surface>
284
- )
285
- }
286
-
287
- const ConfirmDescription: React.FC<{
288
- action: ConfirmOption
289
- stats: DiffStats
290
- cwd: string
291
- canRestoreCode: boolean
292
- }> = ({ action, stats, cwd, canRestoreCode }) => {
293
- if (action === 'nevermind') {
294
- return <Text color={theme.textSubtle}>The conversation and code will be unchanged.</Text>
295
- }
296
- if (action === 'summarize') {
297
- return (
298
- <Text color={theme.textSubtle}>
299
- Messages from this point forward will be summarized into a handoff. The earlier conversation will remain unchanged.
300
- </Text>
301
- )
302
- }
303
- const lines: React.ReactNode[] = []
304
- if (action === 'both') {
305
- lines.push(<Text key="0" color={theme.textSubtle}>The conversation will be forked.</Text>)
306
- lines.push(<CodeRestoreLine key="1" stats={stats} cwd={cwd} canRestoreCode={canRestoreCode} />)
307
- } else if (action === 'conversation') {
308
- lines.push(<Text key="0" color={theme.textSubtle}>The conversation will be forked.</Text>)
309
- lines.push(<Text key="1" color={theme.textSubtle}>The code will be unchanged.</Text>)
310
- } else {
311
- lines.push(<Text key="0" color={theme.textSubtle}>The conversation will be unchanged.</Text>)
312
- lines.push(<CodeRestoreLine key="1" stats={stats} cwd={cwd} canRestoreCode={canRestoreCode} />)
313
- }
314
- if ((action === 'code' || action === 'both') && canRestoreCode) {
315
- lines.push(
316
- <Text key="footnote" color={theme.dim}>
317
- Rewinding does not affect files edited manually or via bash.
318
- </Text>,
319
- )
320
- }
321
- return <>{lines}</>
322
- }
323
-
324
- const CodeRestoreLine: React.FC<{ stats: DiffStats; cwd: string; canRestoreCode: boolean }> = ({ stats, cwd, canRestoreCode }) => {
325
- if (!canRestoreCode) {
326
- return <Text color={theme.textSubtle}>The code has not changed (nothing will be restored).</Text>
327
- }
328
- if (stats.inserts === 0 && stats.deletes === 0) {
329
- return <Text color={theme.textSubtle}>The code will be restored in {describeFiles(stats.files, cwd)}.</Text>
330
- }
331
- return (
332
- <Text color={theme.textSubtle}>
333
- The code will be restored{' '}
334
- <Text color={theme.diffAdded}>+{stats.inserts} </Text>
335
- <Text color={theme.diffRemoved}>-{stats.deletes}</Text>
336
- {' '}in {describeFiles(stats.files, cwd)}.
337
- </Text>
338
- )
339
- }
340
-
341
- function buildPickerOptions(rows: PromptRow[], cwd: string): Array<SelectOption<string>> {
342
- return rows.map(row => ({
343
- value: row.turnId,
344
- label: truncate(row.text, 80),
345
- subtext: describeStats(row.stats, cwd) || 'No code changes',
346
- hint: formatTimestamp(row.createdAt),
347
- }))
348
- }
349
-
350
- async function computeDiffStatsForEntries(entries: RewindEntry[]): Promise<DiffStats> {
351
- const files: string[] = []
352
- const seen = new Set<string>()
353
- let inserts = 0
354
- let deletes = 0
355
- for (const entry of entries) {
356
- if (!seen.has(entry.filePath)) {
357
- seen.add(entry.filePath)
358
- files.push(entry.filePath)
359
- }
360
- let currentContent = ''
361
- try {
362
- currentContent = await fs.readFile(entry.filePath, 'utf8')
363
- } catch (err: unknown) {
364
- if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err
365
- }
366
- const stats = computeLineDiffStats(entry.previousContent, currentContent)
367
- inserts += stats.inserts
368
- deletes += stats.deletes
369
- }
370
- return { files, inserts, deletes }
371
- }
372
-
373
- function describeStats(stats: DiffStats, cwd: string): string {
374
- if (stats.files.length === 0) return ''
375
- const fileLabel = describeFiles(stats.files, cwd)
376
- if (stats.inserts === 0 && stats.deletes === 0) return fileLabel
377
- return `${fileLabel} +${stats.inserts} -${stats.deletes}`
378
- }
379
-
380
- function describeFiles(files: string[], cwd: string): string {
381
- if (files.length === 0) return ''
382
- const formatted = files.map(file => formatRelative(file, cwd))
383
- if (formatted.length === 1) return formatted[0]!
384
- if (formatted.length === 2) return `${formatted[0]} and ${formatted[1]}`
385
- return `${formatted[0]} and ${formatted.length - 1} other files`
386
- }
387
-
388
- function describeFileList(files: string[], cwd: string): string {
389
- if (files.length === 0) return ''
390
- return files.map(file => formatRelative(file, cwd)).join(', ')
391
- }
392
-
393
- function formatRelative(filePath: string, cwd: string): string {
394
- try {
395
- const rel = path.relative(cwd, filePath)
396
- if (rel && !rel.startsWith('..') && !path.isAbsolute(rel)) return rel
397
- } catch {
398
- return path.basename(filePath)
399
- }
400
- return path.basename(filePath)
401
- }
402
-
403
- function formatTimestamp(iso: string): string {
404
- const date = new Date(iso)
405
- return Number.isNaN(date.getTime()) ? iso : date.toLocaleString()
406
- }
407
-
408
- function truncate(text: string, limit: number): string {
409
- const normalized = text.replace(/\s+/g, ' ').trim()
410
- if (normalized.length <= limit) return normalized
411
- return `${normalized.slice(0, Math.max(0, limit - 3))}...`
412
- }
@@ -1,14 +0,0 @@
1
- import React from 'react'
2
- import { Box, render } from 'ink'
3
- import { BrandSplash } from '../ui/BrandSplash.js'
4
-
5
- export async function runPreviewCommand(): Promise<number> {
6
- const instance = render(
7
- <Box flexDirection="column" marginY={1}>
8
- <BrandSplash />
9
- </Box>,
10
- )
11
- await new Promise<void>(resolve => setTimeout(resolve, 50))
12
- instance.unmount()
13
- return 0
14
- }
@@ -1,54 +0,0 @@
1
- type RegistryResponse = {
2
- ok?: boolean
3
- json: () => Promise<unknown>
4
- }
5
-
6
- type RegistryFetch = (url: string, init: { signal: AbortSignal }) => Promise<RegistryResponse>
7
-
8
- const REGISTRY_LATEST_URL = 'https://registry.npmjs.org/ethagent/latest'
9
-
10
- export function compareVersions(left: string, right: string): number {
11
- const a = parseVersion(left)
12
- const b = parseVersion(right)
13
- if (!a || !b) return 0
14
- for (let i = 0; i < 3; i++) {
15
- const av = a[i as 0 | 1 | 2]
16
- const bv = b[i as 0 | 1 | 2]
17
- if (av > bv) return 1
18
- if (av < bv) return -1
19
- }
20
- return 0
21
- }
22
-
23
- export function isNewerVersion(candidate: string, current: string): boolean {
24
- return compareVersions(candidate, current) > 0
25
- }
26
-
27
- export function formatUpdateNotice(currentVersion: string, latestVersion: string): string | null {
28
- if (!isNewerVersion(latestVersion, currentVersion)) return null
29
- return `✨ update available: ethagent ${currentVersion} -> ${latestVersion} · run npm i -g ethagent@latest`
30
- }
31
-
32
- export async function checkForUpdates(
33
- currentVersion: string,
34
- options: { fetchImpl?: RegistryFetch; timeoutMs?: number } = {},
35
- ): Promise<string | null> {
36
- const fetchImpl = options.fetchImpl ?? fetch
37
- const timeoutMs = options.timeoutMs ?? 1200
38
- try {
39
- const res = await fetchImpl(REGISTRY_LATEST_URL, { signal: AbortSignal.timeout(timeoutMs) })
40
- if (res.ok === false) return null
41
- const body = await res.json()
42
- if (!body || typeof body !== 'object') return null
43
- const latest = (body as { version?: unknown }).version
44
- return typeof latest === 'string' ? formatUpdateNotice(currentVersion, latest) : null
45
- } catch {
46
- return null
47
- }
48
- }
49
-
50
- function parseVersion(value: string): [number, number, number] | null {
51
- const match = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(value.trim())
52
- if (!match) return null
53
- return [Number(match[1]), Number(match[2]), Number(match[3])]
54
- }
@@ -1,170 +0,0 @@
1
- import type { EthagentIdentity } from '../../../storage/config.js'
2
- import { applyRequestedEdit } from '../../../tools/editUtils.js'
3
- import { defaultContinuityFiles, type PrivateContinuityFile } from '../storage.js'
4
- import type { PrivateContinuityEditInput } from './types.js'
5
-
6
- export function applyPrivateContinuityEdit(input: PrivateContinuityEditInput, before: string, identity: EthagentIdentity) {
7
- if (input.replaceWholeFile) {
8
- throw new Error('Private continuity files must be edited in place; whole-file replacement is disabled')
9
- }
10
- if (input.appendToSection || input.appendText) {
11
- if (!input.appendToSection?.trim()) throw new Error('Field appendToSection is required for append edits')
12
- if (!input.appendText?.trim()) throw new Error('Field appendText is required for append edits')
13
- if (input.oldText || input.newText !== undefined) {
14
- throw new Error('Use either appendToSection+appendText or oldText+newText, not both')
15
- }
16
- return appendToMarkdownSection(identity, input.file, before, input.appendToSection, input.appendText)
17
- }
18
- if (!input.oldText?.trim()) {
19
- throw new Error('Field oldText is required; private continuity edits must patch existing scaffold text')
20
- }
21
- if (input.newText === undefined) {
22
- throw new Error('Field newText is required for targeted private continuity edits')
23
- }
24
- return applyRequestedEdit(
25
- input.file,
26
- before,
27
- input.oldText,
28
- input.newText,
29
- input.replaceAll ?? false,
30
- false,
31
- )
32
- }
33
-
34
- function appendToMarkdownSection(
35
- identity: EthagentIdentity,
36
- file: PrivateContinuityFile,
37
- before: string,
38
- section: string,
39
- appendText: string,
40
- ) {
41
- const heading = normalizeSectionHeading(section)
42
- let working = before
43
- let repairedMissingSection = false
44
- let lines = working.split(/\r?\n/)
45
- let bounds = findMarkdownSectionBounds(lines, heading)
46
- if (!bounds) {
47
- const repaired = insertDefaultScaffoldSection(identity, file, before, heading)
48
- if (!repaired) {
49
- throw new Error(`section "${section}" was not found in ${file}; target an existing scaffold section`)
50
- }
51
- working = repaired
52
- repairedMissingSection = true
53
- lines = working.split(/\r?\n/)
54
- bounds = findMarkdownSectionBounds(lines, heading)
55
- }
56
- if (!bounds) {
57
- throw new Error(`section "${section}" was not found in ${file}; target an existing scaffold section`)
58
- }
59
- const { start, end: insertAt } = bounds
60
- const prefix = lines.slice(0, insertAt).join('\n').replace(/\s+$/g, '')
61
- const suffix = insertAt >= lines.length ? '' : lines.slice(insertAt).join('\n').replace(/^\s+/g, '')
62
- const normalizedAppend = ensureTrailingNewline(appendText.trim())
63
- const after = ensureTrailingNewline(suffix
64
- ? `${prefix}\n${normalizedAppend}\n${suffix}`
65
- : `${prefix}\n${normalizedAppend}`)
66
- const afterLines = after.split(/\r?\n/)
67
- const afterBounds = findMarkdownSectionBounds(afterLines, heading)
68
- return {
69
- before,
70
- after,
71
- summary: repairedMissingSection
72
- ? `repair ${heading} section and append to ${heading} in ${file}`
73
- : `append to ${heading} in ${file}`,
74
- previewBefore: repairedMissingSection
75
- ? `section "${heading}" was missing in ${file}; approval will add the scaffold section before appending.`
76
- : previewText(sectionPreview(lines, start, insertAt)),
77
- previewAfter: previewText(afterBounds
78
- ? sectionPreview(afterLines, afterBounds.start, afterBounds.end)
79
- : normalizedAppend),
80
- }
81
- }
82
-
83
- function insertDefaultScaffoldSection(
84
- identity: EthagentIdentity,
85
- file: PrivateContinuityFile,
86
- before: string,
87
- heading: string,
88
- ): string | null {
89
- const defaults = defaultContinuityFiles(identity)[file]
90
- const defaultSection = extractMarkdownSection(defaults, heading)
91
- if (!defaultSection) return null
92
-
93
- const defaultHeadings = markdownSectionHeadings(defaults)
94
- const targetIndex = defaultHeadings.indexOf(heading)
95
- if (targetIndex === -1) return null
96
-
97
- const lines = before.split(/\r?\n/)
98
- const followingHeadings = new Set(defaultHeadings.slice(targetIndex + 1))
99
- const followingIndex = lines.findIndex(line => followingHeadings.has(normalizeSectionHeading(line)))
100
- if (followingIndex !== -1) {
101
- return insertSectionAtLine(before, followingIndex, defaultSection)
102
- }
103
-
104
- const previousHeadings = new Set(defaultHeadings.slice(0, targetIndex))
105
- let insertAfterPrevious: number | null = null
106
- for (let index = 0; index < lines.length; index += 1) {
107
- if (!previousHeadings.has(normalizeSectionHeading(lines[index] ?? ''))) continue
108
- const bounds = findMarkdownSectionBounds(lines, normalizeSectionHeading(lines[index] ?? ''))
109
- if (bounds && bounds.end > (insertAfterPrevious ?? -1)) insertAfterPrevious = bounds.end
110
- }
111
- if (insertAfterPrevious !== null) {
112
- return insertSectionAtLine(before, insertAfterPrevious, defaultSection)
113
- }
114
-
115
- const firstHeading = lines.findIndex(line => /^#\s+/.test(line.trim()))
116
- return insertSectionAtLine(before, firstHeading === -1 ? 0 : firstHeading + 1, defaultSection)
117
- }
118
-
119
- function insertSectionAtLine(markdown: string, lineIndex: number, section: string): string {
120
- const lines = markdown.split(/\r?\n/)
121
- const before = lines.slice(0, lineIndex).join('\n').replace(/\s+$/g, '')
122
- const after = lines.slice(lineIndex).join('\n').replace(/^\s+/g, '')
123
- const block = section.trim()
124
- if (!before) return ensureTrailingNewline(after ? `${block}\n\n${after}` : block)
125
- return ensureTrailingNewline(after ? `${before}\n\n${block}\n\n${after}` : `${before}\n\n${block}`)
126
- }
127
-
128
- function extractMarkdownSection(markdown: string, heading: string): string | null {
129
- const lines = markdown.split(/\r?\n/)
130
- const bounds = findMarkdownSectionBounds(lines, heading)
131
- if (!bounds) return null
132
- return lines.slice(bounds.start, bounds.end).join('\n').trim()
133
- }
134
-
135
- function markdownSectionHeadings(markdown: string): string[] {
136
- return markdown
137
- .split(/\r?\n/)
138
- .filter(line => /^##\s+/.test(line.trim()))
139
- .map(normalizeSectionHeading)
140
- }
141
-
142
- function findMarkdownSectionBounds(lines: string[], heading: string): { start: number; end: number } | null {
143
- const start = lines.findIndex(line => normalizeSectionHeading(line) === heading && /^#{1,6}\s+/.test(line.trim()))
144
- if (start === -1) return null
145
- const nextSection = lines.findIndex((line, index) => index > start && /^##\s+/.test(line.trim()))
146
- return { start, end: nextSection === -1 ? lines.length : nextSection }
147
- }
148
-
149
- function normalizeSectionHeading(value: string): string {
150
- return value.trim().replace(/^#+\s*/, '').trim()
151
- }
152
-
153
- function sectionPreview(lines: string[], start: number, end: number): string {
154
- return lines.slice(start, Math.min(end, start + 8)).join('\n')
155
- }
156
-
157
- function markdownLines(value: string): string[] {
158
- const lines = value.split(/\r?\n/)
159
- if (lines[lines.length - 1] === '') lines.pop()
160
- return lines
161
- }
162
-
163
- function ensureTrailingNewline(value: string): string {
164
- return value.endsWith('\n') ? value : `${value}\n`
165
- }
166
-
167
- function previewText(text: string, max = 700): string {
168
- if (text.length <= max) return text
169
- return `${text.slice(0, max - 3)}...`
170
- }
@@ -1,6 +0,0 @@
1
- import type { PrivateContinuityFile } from '../storage.js'
2
- import { renderUnifiedFileDiff } from '../../../tools/fileDiff.js'
3
-
4
- export function renderPrivateContinuityDiff(file: PrivateContinuityFile, before: string, after: string): string {
5
- return renderUnifiedFileDiff({ filePath: file, before, after })
6
- }
@@ -1,23 +0,0 @@
1
- import fs from 'node:fs/promises'
2
- import { type EthagentIdentity } from '../../../storage/config.js'
3
- import { continuityVaultRef, defaultContinuityFiles, type PrivateContinuityFile } from '../storage.js'
4
-
5
- export function privateContinuityPath(identity: EthagentIdentity, file: PrivateContinuityFile): string {
6
- const ref = continuityVaultRef(identity)
7
- return file === 'SOUL.md' ? ref.soulPath : ref.memoryPath
8
- }
9
-
10
- export async function readPrivateContinuityFile(
11
- identity: EthagentIdentity,
12
- file: PrivateContinuityFile,
13
- fullPath: string,
14
- ): Promise<{ content: string; existedBefore: boolean }> {
15
- try {
16
- return { content: await fs.readFile(fullPath, 'utf8'), existedBefore: true }
17
- } catch (err: unknown) {
18
- if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
19
- return { content: defaultContinuityFiles(identity)[file], existedBefore: false }
20
- }
21
- throw err
22
- }
23
- }