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.
- package/.claude-plugin/marketplace.json +11 -0
- package/.claude-plugin/plugin.json +35 -0
- package/LICENSE +1 -1
- package/README.md +64 -104
- package/commands/ethagent.md +40 -0
- package/package.json +16 -16
- package/src/app/keybindings/KeybindingProvider.tsx +1 -6
- package/src/app/keybindings/types.ts +1 -6
- package/src/cli/ResetConfirmView.tsx +54 -53
- package/src/cli/demo.ts +86 -0
- package/src/cli/hookIo.ts +45 -0
- package/src/cli/main.tsx +94 -123
- package/src/cli/memoryGuard.ts +49 -0
- package/src/cli/reset.ts +28 -70
- package/src/cli/sessionStart.ts +33 -0
- package/src/cli/status.ts +46 -0
- package/src/cli/sync.ts +167 -0
- package/src/cli/syncAdapters/claude-code.ts +86 -0
- package/src/cli/syncAdapters/codex.ts +66 -0
- package/src/cli/syncAdapters/index.ts +45 -0
- package/src/cli/syncAdapters/managedBlock.ts +175 -0
- package/src/cli/syncAdapters/shared.ts +63 -0
- package/src/identity/continuity/envelopeParse.ts +20 -1
- package/src/identity/continuity/publicSkills.ts +3 -1
- package/src/identity/continuity/skills/publicSkillsSync.ts +2 -1
- package/src/identity/continuity/skills/scaffold.ts +5 -2
- package/src/identity/continuity/snapshots.ts +12 -5
- package/src/identity/continuity/storage/defaults.ts +20 -19
- package/src/identity/continuity/storage/status.ts +1 -1
- package/src/identity/ens/ensLookup/constants.ts +1 -1
- package/src/identity/manager/IdentityManager.tsx +33 -0
- package/src/identity/{hub → manager}/OperationalRoutes.tsx +37 -18
- package/src/identity/{hub → manager}/Routes.tsx +48 -34
- package/src/identity/{hub → manager}/continuity/ContinuityDashboardScreen.tsx +9 -19
- package/src/identity/{hub → manager}/continuity/RebackupStorageScreen.tsx +3 -3
- package/src/identity/manager/continuity/RecoveryConfirmScreen.tsx +102 -0
- package/src/identity/{hub → manager}/continuity/SavePromptScreen.tsx +2 -3
- package/src/identity/{hub → manager}/continuity/completion.ts +1 -1
- package/src/identity/{hub → manager}/continuity/effects.ts +1 -1
- package/src/identity/{hub → manager}/continuity/skills/DeleteSkillConfirmScreen.tsx +2 -2
- package/src/identity/{hub → manager}/continuity/skills/NewSkillScreen.tsx +0 -5
- package/src/identity/{hub → manager}/continuity/skills/NewSkillVisibilityScreen.tsx +4 -4
- package/src/identity/{hub → manager}/continuity/skills/SkillActionsScreen.tsx +6 -22
- package/src/identity/{hub → manager}/continuity/skills/SkillsTreeScreen.tsx +5 -17
- package/src/identity/{hub → manager}/continuity/snapshot.ts +1 -1
- package/src/identity/{hub → manager}/continuity/vault.ts +1 -1
- package/src/identity/{hub → manager}/create/CreateFlow.tsx +59 -32
- package/src/identity/{hub → manager}/create/effects.ts +19 -10
- package/src/identity/manager/create/importScan.ts +122 -0
- package/src/identity/{hub → manager}/custody/CustodyEditFlow.tsx +17 -61
- package/src/identity/{hub → manager}/custody/actions.ts +1 -15
- package/src/identity/{hub → manager}/custody/routes.tsx +20 -40
- package/src/identity/{hub → manager}/custody/transactions.ts +1 -0
- package/src/identity/{hub → manager}/custody/types.ts +1 -2
- package/src/identity/{hub → manager}/custody/useCustodyEffects.ts +1 -1
- package/src/identity/{hub → manager}/ens/EnsEditAdvancedScreens.tsx +2 -2
- package/src/identity/{hub → manager}/ens/EnsEditMaintenanceScreens.tsx +12 -23
- package/src/identity/{hub → manager}/ens/EnsEditReviewScreens.tsx +18 -42
- package/src/identity/{hub → manager}/ens/EnsEditRunners.tsx +1 -1
- package/src/identity/{hub → manager}/ens/EnsEditShared.tsx +0 -2
- package/src/identity/{hub → manager}/ens/EnsEditSimpleScreens.tsx +10 -19
- package/src/identity/{hub → manager}/ens/EnsFlow.tsx +133 -41
- package/src/identity/{hub → manager}/ens/EnsOperatorWalletsScreen.tsx +14 -19
- package/src/identity/{hub → manager}/ens/editCopy.ts +1 -14
- package/src/identity/{hub → manager}/profile/EditProfileFlow.tsx +99 -66
- package/src/identity/{hub → manager}/profile/effects.ts +1 -3
- package/src/identity/{hub → manager}/profile/operatorSave.ts +1 -1
- package/src/identity/{hub → manager}/profile/state.ts +1 -1
- package/src/identity/{hub/identityHubReducer.ts → manager/reducer.ts} +25 -26
- package/src/identity/{hub → manager}/restore/RestoreFlow.tsx +16 -24
- package/src/identity/{hub → manager}/restore/apply.ts +1 -1
- package/src/identity/{hub → manager}/restore/auth.ts +1 -1
- package/src/identity/{hub → manager}/restore/discover.ts +1 -1
- package/src/identity/{hub → manager}/restore/fetch.ts +1 -1
- package/src/identity/{hub → manager}/restore/restoreAdmin.ts +1 -1
- package/src/identity/{hub → manager}/restore/useRestoreEffects.ts +2 -9
- package/src/identity/{hub → manager}/settings/StorageCredentialScreen.tsx +10 -25
- package/src/identity/{hub → manager}/shared/components/DetailsScreen.tsx +5 -7
- package/src/identity/{hub → manager}/shared/components/ErrorScreen.tsx +6 -10
- package/src/identity/{hub → manager}/shared/components/FlowTimeline.tsx +4 -3
- package/src/identity/{hub → manager}/shared/components/IdentitySummary.tsx +19 -59
- package/src/identity/manager/shared/components/LazyMenu.tsx +147 -0
- package/src/identity/manager/shared/components/MenuScreen.tsx +220 -0
- package/src/identity/manager/shared/components/OperationCompleteScreen.tsx +28 -0
- package/src/identity/{hub → manager}/shared/components/UnlinkedIdentityScreen.tsx +9 -10
- package/src/identity/{hub → manager}/shared/components/WalletApprovalScreen.tsx +1 -2
- package/src/identity/manager/shared/components/Wordmark.tsx +54 -0
- package/src/identity/{hub → manager}/shared/components/menuFlagsFromReconciliation.ts +39 -15
- package/src/identity/{hub → manager}/shared/effects/profilePrep.ts +1 -1
- package/src/identity/manager/shared/effects/types.ts +30 -0
- package/src/identity/{hub → manager}/shared/model/copy.ts +0 -4
- package/src/identity/{hub → manager}/shared/model/errors.ts +32 -3
- package/src/identity/{hub → manager}/shared/model/network.ts +2 -2
- package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/hook.ts +5 -0
- package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/run.ts +1 -1
- package/src/identity/{hub/shared/reconciliation/useAgentReconciliation.ts → manager/shared/reconciliation/index.ts} +6 -0
- package/src/identity/{hub → manager}/shared/utils.ts +6 -10
- package/src/identity/{hub → manager}/transfer/TokenTransferFlow.tsx +3 -3
- package/src/identity/{hub → manager}/transfer/TokenTransferScreens.tsx +4 -10
- package/src/identity/{hub → manager}/transfer/effects.ts +1 -1
- package/src/identity/{hub → manager}/types.ts +5 -6
- package/src/identity/{hub/useIdentityHubContinuity.ts → manager/useContinuity.ts} +59 -27
- package/src/identity/{hub/useIdentityHubController.ts → manager/useController.ts} +38 -35
- package/src/identity/{hub/useIdentityHubSideEffects.ts → manager/useSideEffects.ts} +40 -4
- package/src/identity/registry/erc8004/discovery.ts +3 -17
- package/src/identity/registry/erc8004/utils.ts +1 -1
- package/src/identity/storage/ipfs.ts +21 -1
- package/src/identity/wallet/browserWallet/html.ts +10 -2
- package/src/identity/wallet/browserWallet/http.ts +18 -0
- package/src/identity/wallet/browserWallet/requestServer.ts +5 -1
- package/src/identity/wallet/browserWallet/requests.ts +10 -28
- package/src/identity/wallet/browserWallet/session.ts +26 -33
- package/src/identity/wallet/browserWallet/validation.ts +14 -0
- package/src/identity/wallet/browserWallet/walletPageSource.ts +22 -40
- package/src/identity/wallet/page/boot.ts +43 -0
- package/src/identity/wallet/page/config.ts +59 -0
- package/src/identity/wallet/page/constants.ts +12 -0
- package/src/identity/wallet/page/copy.ts +47 -68
- package/src/identity/wallet/page/css.ts +638 -0
- package/src/identity/wallet/page/{errorView.ts → errors.ts} +5 -14
- package/src/identity/wallet/page/{controller.ts → flow.ts} +4 -71
- package/src/identity/wallet/page/markup.ts +44 -34
- package/src/identity/wallet/page/{walletProvider.ts → provider.ts} +0 -3
- package/src/identity/wallet/page/resize.ts +95 -0
- package/src/identity/wallet/page/state.ts +135 -8
- package/src/identity/wallet/page/timeline.ts +161 -0
- package/src/identity/wallet/page/view.ts +22 -302
- package/src/storage/config.ts +30 -80
- package/src/storage/reset.ts +31 -0
- package/src/storage/secrets.ts +1 -16
- package/src/ui/Select.tsx +27 -5
- package/src/ui/Spinner.tsx +16 -15
- package/src/ui/Surface.tsx +21 -17
- package/src/ui/TextArea.tsx +173 -0
- package/src/ui/TextInput.tsx +31 -133
- package/src/ui/theme.ts +22 -13
- package/src/utils/clipboard.ts +0 -140
- package/src/app/FirstRun.tsx +0 -577
- package/src/app/FirstRunTimeline.tsx +0 -51
- package/src/app/firstRunConfig.ts +0 -26
- package/src/app/hooks/useCancelRequest.ts +0 -22
- package/src/app/hooks/useDoublePress.ts +0 -46
- package/src/app/hooks/useExitOnCtrlC.ts +0 -36
- package/src/auth/openaiOAuth/credentials.ts +0 -47
- package/src/auth/openaiOAuth/crypto.ts +0 -23
- package/src/auth/openaiOAuth/index.ts +0 -238
- package/src/auth/openaiOAuth/landingPage.ts +0 -116
- package/src/auth/openaiOAuth/listener.ts +0 -151
- package/src/auth/openaiOAuth/refresh.ts +0 -70
- package/src/auth/openaiOAuth/shared.ts +0 -115
- package/src/chat/ChatBottomPane.tsx +0 -296
- package/src/chat/ChatScreen.tsx +0 -1685
- package/src/chat/ConversationStack.tsx +0 -56
- package/src/chat/MessageList.tsx +0 -638
- package/src/chat/SessionStatus.tsx +0 -53
- package/src/chat/chatEnvironment.ts +0 -16
- package/src/chat/chatScreenUtils.ts +0 -194
- package/src/chat/chatSessionState.ts +0 -146
- package/src/chat/chatTurnContext.ts +0 -50
- package/src/chat/chatTurnOrchestrator.ts +0 -603
- package/src/chat/chatTurnRows.ts +0 -64
- package/src/chat/commands.ts +0 -494
- package/src/chat/continuityEditReview.ts +0 -42
- package/src/chat/display/DiffView.tsx +0 -193
- package/src/chat/display/SyntaxText.tsx +0 -192
- package/src/chat/display/toolCallDisplay.ts +0 -103
- package/src/chat/display/toolResultDisplay.ts +0 -19
- package/src/chat/input/ChatInput.tsx +0 -625
- package/src/chat/input/chatInputHelpers.ts +0 -62
- package/src/chat/input/chatInputState.ts +0 -247
- package/src/chat/input/chatPaste.ts +0 -49
- package/src/chat/input/imageRefs.ts +0 -30
- package/src/chat/input/inputRendering.tsx +0 -93
- package/src/chat/input/textCursor.ts +0 -212
- package/src/chat/messageMarkdown.ts +0 -220
- package/src/chat/messageRows.ts +0 -43
- package/src/chat/planImplementation.ts +0 -62
- package/src/chat/slashCommandHandlers.ts +0 -122
- package/src/chat/slashCommandViews.ts +0 -120
- package/src/chat/transcript/TranscriptView.tsx +0 -184
- package/src/chat/transcript/transcriptViewport.ts +0 -295
- package/src/chat/views/ContextLimitView.tsx +0 -95
- package/src/chat/views/ContinuityEditReviewView.tsx +0 -50
- package/src/chat/views/CopyPicker.tsx +0 -50
- package/src/chat/views/PermissionPrompt.tsx +0 -156
- package/src/chat/views/PermissionsView.tsx +0 -165
- package/src/chat/views/PlanApprovalView.tsx +0 -91
- package/src/chat/views/ResumeView.tsx +0 -273
- package/src/chat/views/RewindView.tsx +0 -412
- package/src/cli/preview.tsx +0 -14
- package/src/cli/updateNotice.ts +0 -54
- package/src/identity/continuity/privateEdit/apply.ts +0 -170
- package/src/identity/continuity/privateEdit/diff.ts +0 -6
- package/src/identity/continuity/privateEdit/files.ts +0 -23
- package/src/identity/continuity/privateEdit/types.ts +0 -28
- package/src/identity/continuity/privateEdit.ts +0 -46
- package/src/identity/hub/IdentityHub.tsx +0 -14
- package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +0 -104
- package/src/identity/hub/ens/effects.ts +0 -218
- package/src/identity/hub/shared/components/MenuScreen.tsx +0 -241
- package/src/identity/hub/shared/effects/types.ts +0 -53
- package/src/identity/hub/shared/reconciliation/index.ts +0 -14
- package/src/identity/wallet/page/grainient.ts +0 -278
- package/src/identity/wallet/page/html.ts +0 -28
- package/src/identity/wallet/page/styles/base.ts +0 -259
- package/src/identity/wallet/page/styles/components.ts +0 -262
- package/src/identity/wallet/page/styles/index.ts +0 -5
- package/src/identity/wallet/page/styles/responsive.ts +0 -247
- package/src/identity/wallet/page.tsx +0 -38
- package/src/mcp/approvals.ts +0 -113
- package/src/mcp/config.ts +0 -235
- package/src/mcp/manager.ts +0 -482
- package/src/mcp/managerHelpers.ts +0 -70
- package/src/mcp/names.ts +0 -19
- package/src/mcp/output.ts +0 -96
- package/src/models/ModelPicker.tsx +0 -1009
- package/src/models/catalog.ts +0 -327
- package/src/models/huggingface.ts +0 -712
- package/src/models/huggingfaceStorage.ts +0 -136
- package/src/models/llamacpp.ts +0 -848
- package/src/models/llamacppCommands.ts +0 -44
- package/src/models/llamacppConfig.ts +0 -34
- package/src/models/llamacppDiscovery.ts +0 -176
- package/src/models/llamacppOutput.ts +0 -65
- package/src/models/llamacppPreflight.ts +0 -158
- package/src/models/modelDisplay.ts +0 -180
- package/src/models/modelPickerCatalogFlow.ts +0 -56
- package/src/models/modelPickerCredentials.ts +0 -166
- package/src/models/modelPickerData.ts +0 -41
- package/src/models/modelPickerDisplay.tsx +0 -132
- package/src/models/modelPickerHfFlow.ts +0 -192
- package/src/models/modelPickerLocalRunnerFlow.ts +0 -115
- package/src/models/modelPickerOptions.ts +0 -457
- package/src/models/modelPickerTypes.ts +0 -69
- package/src/models/modelPickerUninstallFlow.ts +0 -48
- package/src/models/modelPickerViewHelpers.ts +0 -174
- package/src/models/modelRecommendation.ts +0 -139
- package/src/models/providerDisplay.ts +0 -16
- package/src/models/runtimeDetection.ts +0 -81
- package/src/models/uncensoredCatalog.ts +0 -86
- package/src/providers/anthropic.ts +0 -290
- package/src/providers/contracts.ts +0 -71
- package/src/providers/errors.ts +0 -80
- package/src/providers/gemini.ts +0 -391
- package/src/providers/openai-chat.ts +0 -474
- package/src/providers/openai-responses-format.ts +0 -177
- package/src/providers/openai-responses.ts +0 -306
- package/src/providers/openaiChatWire.ts +0 -124
- package/src/providers/registry.ts +0 -120
- package/src/providers/retry.ts +0 -58
- package/src/providers/sse.ts +0 -93
- package/src/runtime/compaction.ts +0 -395
- package/src/runtime/cwd.ts +0 -43
- package/src/runtime/providerTurn.ts +0 -38
- package/src/runtime/sessionMode.ts +0 -55
- package/src/runtime/systemPrompt.ts +0 -213
- package/src/runtime/textToolParser.ts +0 -161
- package/src/runtime/toolClaimGuards.ts +0 -143
- package/src/runtime/toolExecution.ts +0 -304
- package/src/runtime/toolIntent.ts +0 -143
- package/src/runtime/turn.ts +0 -369
- package/src/runtime/turnNudges.ts +0 -223
- package/src/runtime/turnTypes.ts +0 -86
- package/src/storage/factoryReset.ts +0 -127
- package/src/storage/history.ts +0 -58
- package/src/storage/permissions.ts +0 -76
- package/src/storage/rewind.ts +0 -266
- package/src/storage/sessionExport.ts +0 -49
- package/src/storage/sessions.ts +0 -495
- package/src/tools/bashSafety.ts +0 -186
- package/src/tools/bashTool.ts +0 -140
- package/src/tools/changeDirectoryTool.ts +0 -213
- package/src/tools/contracts.ts +0 -192
- package/src/tools/deleteFileTool.ts +0 -116
- package/src/tools/editTool.ts +0 -165
- package/src/tools/editUtils.ts +0 -170
- package/src/tools/fileDiff.ts +0 -261
- package/src/tools/listDirectoryTool.ts +0 -55
- package/src/tools/listSkillFilesTool.ts +0 -77
- package/src/tools/listSkillsTool.ts +0 -68
- package/src/tools/mcpResourceTools.ts +0 -95
- package/src/tools/permissionRules.ts +0 -85
- package/src/tools/privateContinuityEditTool.ts +0 -187
- package/src/tools/privateContinuityReadTool.ts +0 -106
- package/src/tools/readSkillTool.ts +0 -107
- package/src/tools/readTool.ts +0 -85
- package/src/tools/registry.ts +0 -103
- package/src/tools/writeFileTool.ts +0 -167
- package/src/ui/BrandSplash.tsx +0 -133
- package/src/ui/terminalTitle.ts +0 -30
- package/src/utils/images.ts +0 -140
- package/src/utils/markdownSegments.ts +0 -51
- package/src/utils/messages.ts +0 -37
- package/src/utils/withRetry.ts +0 -324
- /package/src/identity/{hub → manager}/continuity/state.ts +0 -0
- /package/src/identity/{hub → manager}/custody/helpers.ts +0 -0
- /package/src/identity/{hub → manager}/custody/preflight.ts +0 -0
- /package/src/identity/{hub → manager}/custody/state.ts +0 -0
- /package/src/identity/{hub → manager}/custody/useCustodyFlow.tsx +0 -0
- /package/src/identity/{hub → manager}/ens/EnsEditFlow.tsx +0 -0
- /package/src/identity/{hub → manager}/ens/advancedEnsValidation.ts +0 -0
- /package/src/identity/{hub → manager}/ens/state.ts +0 -0
- /package/src/identity/{hub → manager}/ens/transactions.ts +0 -0
- /package/src/identity/{hub → manager}/ens/types.ts +0 -0
- /package/src/identity/{hub → manager}/profile/identity.ts +0 -0
- /package/src/identity/{hub → manager}/restore/envelopes.ts +0 -0
- /package/src/identity/{hub → manager}/restore/helpers.ts +0 -0
- /package/src/identity/{hub → manager}/restore/recovery.ts +0 -0
- /package/src/identity/{hub → manager}/restore/resolve.ts +0 -0
- /package/src/identity/{hub → manager}/shared/components/BusyScreen.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/components/NetworkScreen.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/components/PinataJwtInput.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/effects/receipts.ts +0 -0
- /package/src/identity/{hub → manager}/shared/effects/sync.ts +0 -0
- /package/src/identity/{hub → manager}/shared/model/format.ts +0 -0
- /package/src/identity/{hub → manager}/shared/operatorWallets.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/ownership.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/types.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/walletSetup.ts +0 -0
- /package/src/identity/{hub → manager}/shared/txGuard.ts +0 -0
- /package/src/identity/{hub → manager}/transfer/progress.ts +0 -0
- /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
|
-
}
|
package/src/cli/preview.tsx
DELETED
|
@@ -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
|
-
}
|
package/src/cli/updateNotice.ts
DELETED
|
@@ -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
|
-
}
|