ethagent 3.3.4 → 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 -260
- 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,156 +0,0 @@
|
|
|
1
|
-
import React, { useMemo } from 'react'
|
|
2
|
-
import { Box, Text } from 'ink'
|
|
3
|
-
import { Surface } from '../../ui/Surface.js'
|
|
4
|
-
import { Select } from '../../ui/Select.js'
|
|
5
|
-
import { theme } from '../../ui/theme.js'
|
|
6
|
-
import { DiffView, diffLineColor } from '../display/DiffView.js'
|
|
7
|
-
import type { PermissionDecision, PermissionRequest } from '../../tools/contracts.js'
|
|
8
|
-
|
|
9
|
-
export { diffLineColor as permissionDiffLineColor }
|
|
10
|
-
|
|
11
|
-
type PermissionPromptProps = {
|
|
12
|
-
request: PermissionRequest
|
|
13
|
-
onDecision: (decision: PermissionDecision) => void
|
|
14
|
-
onCancel: () => void
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const PermissionPrompt: React.FC<PermissionPromptProps> = ({ request, onDecision, onCancel }) => {
|
|
18
|
-
const options = useMemo(() => permissionOptionsForRequest(request), [request])
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<Surface
|
|
22
|
-
title={request.title}
|
|
23
|
-
subtitle={request.subtitle}
|
|
24
|
-
tone={request.kind === 'bash' && request.warning ? 'error' : 'primary'}
|
|
25
|
-
footer="enter confirms · esc denies"
|
|
26
|
-
>
|
|
27
|
-
{request.kind === 'private-continuity-edit' ? (
|
|
28
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
29
|
-
<Text color={theme.accentPeriwinkle}>{displayPermissionText(request.changeSummary)}</Text>
|
|
30
|
-
<Text color={theme.textSubtle}>
|
|
31
|
-
Not reversible by /rewind. A private identity-history snapshot is saved before writing.
|
|
32
|
-
</Text>
|
|
33
|
-
<Box marginTop={1}>
|
|
34
|
-
<Text color={theme.textSubtle}>target</Text>
|
|
35
|
-
</Box>
|
|
36
|
-
<Text color={theme.text}>{request.file}</Text>
|
|
37
|
-
<Box marginTop={1}>
|
|
38
|
-
<Text color={theme.accentPeriwinkle}>diff</Text>
|
|
39
|
-
</Box>
|
|
40
|
-
<DiffView diff={request.diff} />
|
|
41
|
-
</Box>
|
|
42
|
-
) : null}
|
|
43
|
-
{request.kind === 'private-continuity-read' ? (
|
|
44
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
45
|
-
<Text color={theme.accentPeriwinkle}>read private {request.file}</Text>
|
|
46
|
-
<Text color={theme.textSubtle}>This reveals private identity continuity to the model for this turn.</Text>
|
|
47
|
-
<Box marginTop={1}>
|
|
48
|
-
<Text color={theme.textSubtle}>range</Text>
|
|
49
|
-
</Box>
|
|
50
|
-
<Text color={theme.text}>{request.range}</Text>
|
|
51
|
-
</Box>
|
|
52
|
-
) : null}
|
|
53
|
-
{request.kind === 'edit' || request.kind === 'write' || request.kind === 'delete' ? (
|
|
54
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
55
|
-
<Text color={theme.accentPeriwinkle}>{displayPermissionText(request.changeSummary)}</Text>
|
|
56
|
-
<Box marginTop={1}>
|
|
57
|
-
<Text color={theme.accentPeriwinkle}>diff</Text>
|
|
58
|
-
</Box>
|
|
59
|
-
<DiffView diff={request.diff} />
|
|
60
|
-
</Box>
|
|
61
|
-
) : null}
|
|
62
|
-
{request.kind === 'bash' && request.warning ? (
|
|
63
|
-
<Box marginBottom={1}>
|
|
64
|
-
<Text color={theme.accentError}>{request.warning}</Text>
|
|
65
|
-
</Box>
|
|
66
|
-
) : null}
|
|
67
|
-
<Select options={options} onSubmit={onDecision} onCancel={onCancel} />
|
|
68
|
-
</Surface>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function permissionOptionsForRequest(request: PermissionRequest): Array<{ value: PermissionDecision; label: string; hint?: string; disabled?: boolean }> {
|
|
73
|
-
if (request.kind === 'bash') {
|
|
74
|
-
return [
|
|
75
|
-
{ value: 'allow-once', label: 'Allow once', hint: 'Approve only this command execution' },
|
|
76
|
-
{
|
|
77
|
-
value: 'allow-command-project',
|
|
78
|
-
label: 'Allow exact command',
|
|
79
|
-
hint: 'Remember this exact command for this project',
|
|
80
|
-
disabled: !request.canPersistExact,
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
value: 'allow-command-prefix-project',
|
|
84
|
-
label: request.commandPrefix ? `Allow ${request.commandPrefix} commands` : 'Allow command family',
|
|
85
|
-
hint: 'Remember this base command in this working directory for this project',
|
|
86
|
-
disabled: !request.canPersistPrefix,
|
|
87
|
-
},
|
|
88
|
-
{ value: 'deny', label: 'Deny', hint: 'Return a denial to the model' },
|
|
89
|
-
]
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (request.kind === 'mcp') {
|
|
93
|
-
const risk = request.destructive
|
|
94
|
-
? 'Server marks this tool as destructive'
|
|
95
|
-
: request.openWorld
|
|
96
|
-
? 'Server marks this tool as open-world'
|
|
97
|
-
: request.readOnly
|
|
98
|
-
? 'Server marks this tool as read-only'
|
|
99
|
-
: 'Server did not mark this tool read-only'
|
|
100
|
-
return [
|
|
101
|
-
{ value: 'allow-once', label: 'Allow once', hint: risk },
|
|
102
|
-
{ value: 'allow-mcp-tool-project', label: 'Always allow this MCP tool', hint: request.toolKey },
|
|
103
|
-
{
|
|
104
|
-
value: 'allow-mcp-server-project',
|
|
105
|
-
label: `Always allow ${request.serverName}`,
|
|
106
|
-
hint: 'Remember all tools from this MCP server for this project',
|
|
107
|
-
disabled: !request.canPersistServer,
|
|
108
|
-
},
|
|
109
|
-
{ value: 'deny', label: 'Deny', hint: 'Return a denial back to the model' },
|
|
110
|
-
]
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (request.kind === 'delete') {
|
|
114
|
-
return [
|
|
115
|
-
{ value: 'allow-once', label: 'Delete this file', hint: 'Approve this deletion only' },
|
|
116
|
-
{ value: 'deny', label: 'Deny', hint: 'Keep the file unchanged' },
|
|
117
|
-
]
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (request.kind === 'private-continuity-read') {
|
|
121
|
-
return [
|
|
122
|
-
{ value: 'allow-once', label: 'Allow once', hint: `Read ${request.file}` },
|
|
123
|
-
{ value: 'deny', label: 'Deny', hint: 'Keep private continuity hidden' },
|
|
124
|
-
]
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (request.kind === 'private-continuity-edit') {
|
|
128
|
-
return [
|
|
129
|
-
{ value: 'allow-once', label: 'Approve once', hint: `Apply this edit to ${request.file}` },
|
|
130
|
-
{ value: 'deny', label: 'Deny', hint: 'Keep private continuity unchanged' },
|
|
131
|
-
]
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return [
|
|
135
|
-
{ value: 'allow-once', label: 'Allow once', hint: 'Approve only this action' },
|
|
136
|
-
{ value: 'allow-path-project', label: 'Always allow this file', hint: request.relativePath },
|
|
137
|
-
{ value: 'allow-directory-project', label: 'Always allow this folder', hint: request.directoryPath },
|
|
138
|
-
{
|
|
139
|
-
value: 'allow-kind-project',
|
|
140
|
-
label:
|
|
141
|
-
request.kind === 'edit'
|
|
142
|
-
? 'Always allow edits'
|
|
143
|
-
: request.kind === 'write'
|
|
144
|
-
? 'Always allow writes'
|
|
145
|
-
: request.kind === 'cd'
|
|
146
|
-
? 'Always allow directory changes'
|
|
147
|
-
: 'Always allow reads',
|
|
148
|
-
hint: 'Remember this tool kind for this project',
|
|
149
|
-
},
|
|
150
|
-
{ value: 'deny', label: 'Deny', hint: 'Return a denial back to the model' },
|
|
151
|
-
]
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function displayPermissionText(value: string): string {
|
|
155
|
-
return value ? value[0]!.toUpperCase() + value.slice(1) : value
|
|
156
|
-
}
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useState } from 'react'
|
|
2
|
-
import { Box, Text } from 'ink'
|
|
3
|
-
import { clearPermissionRules, deletePermissionRule, loadPermissionRules } from '../../storage/permissions.js'
|
|
4
|
-
import type { SessionPermissionRule } from '../../tools/contracts.js'
|
|
5
|
-
import { Select, type SelectOption } from '../../ui/Select.js'
|
|
6
|
-
import { Spinner } from '../../ui/Spinner.js'
|
|
7
|
-
import { Surface } from '../../ui/Surface.js'
|
|
8
|
-
import { theme } from '../../ui/theme.js'
|
|
9
|
-
|
|
10
|
-
type PermissionsViewProps = {
|
|
11
|
-
cwd: string
|
|
12
|
-
onRulesChanged: (rules: SessionPermissionRule[]) => void
|
|
13
|
-
onNotice: (message: string, variant?: 'info' | 'error' | 'dim') => void
|
|
14
|
-
onCancel: () => void
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type State =
|
|
18
|
-
| { kind: 'loading' }
|
|
19
|
-
| { kind: 'error'; message: string }
|
|
20
|
-
| { kind: 'ready'; rules: SessionPermissionRule[] }
|
|
21
|
-
|
|
22
|
-
const CLEAR_ALL_VALUE = '__clear_all__'
|
|
23
|
-
|
|
24
|
-
export const PermissionsView: React.FC<PermissionsViewProps> = ({
|
|
25
|
-
cwd,
|
|
26
|
-
onRulesChanged,
|
|
27
|
-
onNotice,
|
|
28
|
-
onCancel,
|
|
29
|
-
}) => {
|
|
30
|
-
const [state, setState] = useState<State>({ kind: 'loading' })
|
|
31
|
-
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
let cancelled = false
|
|
34
|
-
void (async () => {
|
|
35
|
-
try {
|
|
36
|
-
const rules = await loadPermissionRules(cwd)
|
|
37
|
-
if (!cancelled) setState({ kind: 'ready', rules })
|
|
38
|
-
} catch (err: unknown) {
|
|
39
|
-
if (!cancelled) setState({ kind: 'error', message: (err as Error).message })
|
|
40
|
-
}
|
|
41
|
-
})()
|
|
42
|
-
return () => { cancelled = true }
|
|
43
|
-
}, [cwd])
|
|
44
|
-
|
|
45
|
-
const refreshRules = async () => {
|
|
46
|
-
const rules = await loadPermissionRules(cwd)
|
|
47
|
-
setState({ kind: 'ready', rules })
|
|
48
|
-
onRulesChanged(rules)
|
|
49
|
-
return rules
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const options = useMemo(
|
|
53
|
-
() => state.kind === 'ready' ? buildOptions(state.rules) : [],
|
|
54
|
-
[state],
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
if (state.kind === 'loading') {
|
|
58
|
-
return (
|
|
59
|
-
<Surface title="Permissions" subtitle="Loading saved project rules...">
|
|
60
|
-
<Spinner label="loading permission rules..." />
|
|
61
|
-
</Surface>
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (state.kind === 'error') {
|
|
66
|
-
return (
|
|
67
|
-
<Surface title="Permissions" tone="muted" footer="esc closes">
|
|
68
|
-
<Text color={theme.dim}>{state.message}</Text>
|
|
69
|
-
</Surface>
|
|
70
|
-
)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (state.rules.length === 0) {
|
|
74
|
-
return (
|
|
75
|
-
<Surface title="Permissions" tone="muted" footer="esc closes">
|
|
76
|
-
<Text color={theme.dim}>No saved permission rules for this project.</Text>
|
|
77
|
-
</Surface>
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<Surface
|
|
83
|
-
title="Permissions"
|
|
84
|
-
subtitle="Saved rules for this project. Enter removes the selected rule."
|
|
85
|
-
footer="enter removes · esc closes"
|
|
86
|
-
>
|
|
87
|
-
<Select
|
|
88
|
-
options={options}
|
|
89
|
-
onSubmit={async value => {
|
|
90
|
-
try {
|
|
91
|
-
if (value === CLEAR_ALL_VALUE) {
|
|
92
|
-
await clearPermissionRules(cwd)
|
|
93
|
-
onRulesChanged([])
|
|
94
|
-
onCancel()
|
|
95
|
-
onNotice('cleared saved permission rules for this project.', 'dim')
|
|
96
|
-
return
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
await deletePermissionRule(cwd, value)
|
|
100
|
-
const remaining = await refreshRules()
|
|
101
|
-
if (remaining.length === 0) {
|
|
102
|
-
onCancel()
|
|
103
|
-
onNotice('removed the last saved permission rule for this project.', 'dim')
|
|
104
|
-
return
|
|
105
|
-
}
|
|
106
|
-
onNotice(`removed permission rule: ${describeRule(value)}`, 'dim')
|
|
107
|
-
} catch (err: unknown) {
|
|
108
|
-
onNotice(`failed to update permission rules: ${(err as Error).message}`, 'error')
|
|
109
|
-
}
|
|
110
|
-
}}
|
|
111
|
-
onCancel={onCancel}
|
|
112
|
-
/>
|
|
113
|
-
<Box marginTop={1} flexDirection="column">
|
|
114
|
-
<Text color={theme.dim}>Rules apply only within the current project root.</Text>
|
|
115
|
-
</Box>
|
|
116
|
-
</Surface>
|
|
117
|
-
)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function buildOptions(rules: SessionPermissionRule[]): Array<SelectOption<SessionPermissionRule | typeof CLEAR_ALL_VALUE>> {
|
|
121
|
-
const out: Array<SelectOption<SessionPermissionRule | typeof CLEAR_ALL_VALUE>> = []
|
|
122
|
-
if (rules.length > 0) {
|
|
123
|
-
out.push({ value: CLEAR_ALL_VALUE, role: 'section', label: 'Saved Rules' })
|
|
124
|
-
for (const rule of rules) {
|
|
125
|
-
out.push({
|
|
126
|
-
value: rule,
|
|
127
|
-
label: describeRule(rule),
|
|
128
|
-
hint: describeRuleScope(rule),
|
|
129
|
-
})
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
out.push({ value: CLEAR_ALL_VALUE, role: 'section', label: 'Manage' })
|
|
133
|
-
out.push({
|
|
134
|
-
value: CLEAR_ALL_VALUE,
|
|
135
|
-
label: 'Remove all saved rules',
|
|
136
|
-
hint: 'Clear all remembered permissions for this project',
|
|
137
|
-
})
|
|
138
|
-
return out
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function describeRule(rule: SessionPermissionRule): string {
|
|
142
|
-
if (rule.kind === 'bash') {
|
|
143
|
-
if (rule.scope === 'command') return `bash exact: ${rule.command}`
|
|
144
|
-
return `bash prefix: ${rule.commandPrefix}`
|
|
145
|
-
}
|
|
146
|
-
if (rule.kind === 'mcp') {
|
|
147
|
-
if (rule.scope === 'tool') return `mcp tool: ${rule.toolKey}`
|
|
148
|
-
return `mcp server: ${rule.normalizedServerName}`
|
|
149
|
-
}
|
|
150
|
-
if (rule.scope === 'kind') {
|
|
151
|
-
return rule.kind === 'read'
|
|
152
|
-
? 'allow all reads'
|
|
153
|
-
: rule.kind === 'edit'
|
|
154
|
-
? 'allow all edits'
|
|
155
|
-
: 'allow all directory changes'
|
|
156
|
-
}
|
|
157
|
-
if (rule.scope === 'path') return `${rule.kind} file: ${rule.path}`
|
|
158
|
-
return `${rule.kind} folder: ${rule.path}`
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function describeRuleScope(rule: SessionPermissionRule): string {
|
|
162
|
-
if (rule.kind === 'bash') return `cwd ${rule.cwd}`
|
|
163
|
-
if (rule.kind === 'mcp') return 'MCP project permission'
|
|
164
|
-
return rule.scope === 'kind' ? 'whole project' : rule.path
|
|
165
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
2
|
-
import { Box, Text } from 'ink'
|
|
3
|
-
import { Surface } from '../../ui/Surface.js'
|
|
4
|
-
import { theme } from '../../ui/theme.js'
|
|
5
|
-
import { useAppInput } from '../../app/input/AppInputProvider.js'
|
|
6
|
-
|
|
7
|
-
export type PlanApprovalAction = 'apply' | 'apply-summary' | 'continue'
|
|
8
|
-
|
|
9
|
-
type PlanApprovalViewProps = {
|
|
10
|
-
contextLabel: string
|
|
11
|
-
onSelect: (action: PlanApprovalAction) => void
|
|
12
|
-
onCancel: () => void
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const PLAN_APPROVAL_OPTIONS: Array<{
|
|
16
|
-
value: PlanApprovalAction
|
|
17
|
-
label: string
|
|
18
|
-
title: string
|
|
19
|
-
detail: (contextLabel: string) => string
|
|
20
|
-
}> = [
|
|
21
|
-
{
|
|
22
|
-
value: 'apply',
|
|
23
|
-
label: 'Yes, implement this plan',
|
|
24
|
-
title: 'Switch to Accept Edits and start coding.',
|
|
25
|
-
detail: contextLabel => `Same conversation. ${contextLabel}.`,
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
value: 'apply-summary',
|
|
29
|
-
label: 'Yes, start a new conversation',
|
|
30
|
-
title: 'Summarize context and start coding.',
|
|
31
|
-
detail: () => 'Keeps this conversation active and carries summary plus plan.',
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
value: 'continue',
|
|
35
|
-
label: 'No, stay in plan mode',
|
|
36
|
-
title: 'Continue planning with the model.',
|
|
37
|
-
detail: () => 'No files will be changed.',
|
|
38
|
-
},
|
|
39
|
-
]
|
|
40
|
-
|
|
41
|
-
export const PlanApprovalView: React.FC<PlanApprovalViewProps> = ({
|
|
42
|
-
contextLabel,
|
|
43
|
-
onSelect,
|
|
44
|
-
onCancel,
|
|
45
|
-
}) => {
|
|
46
|
-
const [index, setIndex] = useState(0)
|
|
47
|
-
const selected = PLAN_APPROVAL_OPTIONS[index] ?? PLAN_APPROVAL_OPTIONS[0]!
|
|
48
|
-
|
|
49
|
-
useAppInput((input, key) => {
|
|
50
|
-
if (key.upArrow || input === 'k') {
|
|
51
|
-
setIndex(current => (current - 1 + PLAN_APPROVAL_OPTIONS.length) % PLAN_APPROVAL_OPTIONS.length)
|
|
52
|
-
} else if (key.downArrow || input === 'j') {
|
|
53
|
-
setIndex(current => (current + 1) % PLAN_APPROVAL_OPTIONS.length)
|
|
54
|
-
} else if (key.return) {
|
|
55
|
-
onSelect(selected.value)
|
|
56
|
-
} else if (key.escape) {
|
|
57
|
-
onCancel()
|
|
58
|
-
}
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<Surface
|
|
63
|
-
title="Implement this plan?"
|
|
64
|
-
tone="muted"
|
|
65
|
-
footer="Press enter to confirm or esc to go back"
|
|
66
|
-
>
|
|
67
|
-
<Box flexDirection="row">
|
|
68
|
-
<Box flexDirection="column" minWidth={36}>
|
|
69
|
-
{PLAN_APPROVAL_OPTIONS.map((option, optionIndex) => {
|
|
70
|
-
const active = optionIndex === index
|
|
71
|
-
return (
|
|
72
|
-
<Box key={option.value} flexDirection="row">
|
|
73
|
-
<Text color={active ? theme.accentPeriwinkle : theme.dim}>
|
|
74
|
-
{active ? '> ' : ' '}
|
|
75
|
-
{optionIndex + 1}.{' '}
|
|
76
|
-
</Text>
|
|
77
|
-
<Text color={active ? theme.accentPeriwinkle : theme.text} bold={active}>
|
|
78
|
-
{option.label}
|
|
79
|
-
</Text>
|
|
80
|
-
</Box>
|
|
81
|
-
)
|
|
82
|
-
})}
|
|
83
|
-
</Box>
|
|
84
|
-
<Box flexDirection="column" marginLeft={4} flexShrink={1}>
|
|
85
|
-
<Text color={theme.accentPeriwinkle} bold>{selected.title}</Text>
|
|
86
|
-
<Text color={theme.dim}>{selected.detail(contextLabel)}</Text>
|
|
87
|
-
</Box>
|
|
88
|
-
</Box>
|
|
89
|
-
</Surface>
|
|
90
|
-
)
|
|
91
|
-
}
|
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react'
|
|
2
|
-
import { Box, Text } from 'ink'
|
|
3
|
-
import { theme } from '../../ui/theme.js'
|
|
4
|
-
import { Select, type SelectOption } from '../../ui/Select.js'
|
|
5
|
-
import { Spinner } from '../../ui/Spinner.js'
|
|
6
|
-
import { Surface } from '../../ui/Surface.js'
|
|
7
|
-
import { listSessions, type SessionSummary } from '../../storage/sessions.js'
|
|
8
|
-
import { useAppInput } from '../../app/input/AppInputProvider.js'
|
|
9
|
-
|
|
10
|
-
type ResumeViewProps = {
|
|
11
|
-
currentSessionId: string
|
|
12
|
-
onResume: (sessionId: string) => void
|
|
13
|
-
onClearAll: () => void | Promise<void>
|
|
14
|
-
onCancel: () => void
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type State =
|
|
18
|
-
| { kind: 'loading' }
|
|
19
|
-
| { kind: 'error'; message: string }
|
|
20
|
-
| { kind: 'ready'; sessions: SessionSummary[] }
|
|
21
|
-
| { kind: 'confirmClear'; sessions: SessionSummary[]; error?: string }
|
|
22
|
-
| { kind: 'clearing'; sessions: SessionSummary[] }
|
|
23
|
-
|
|
24
|
-
export const CLEAR_ALL_SESSIONS_VALUE = '__clear_all_sessions__'
|
|
25
|
-
|
|
26
|
-
export const ResumeView: React.FC<ResumeViewProps> = ({ currentSessionId, onResume, onClearAll, onCancel }) => {
|
|
27
|
-
const [state, setState] = useState<State>({ kind: 'loading' })
|
|
28
|
-
|
|
29
|
-
const escActive = state.kind === 'loading' || state.kind === 'error' || (state.kind === 'ready' && state.sessions.length === 0)
|
|
30
|
-
useAppInput((_input, key) => {
|
|
31
|
-
if (key.escape) onCancel()
|
|
32
|
-
}, { isActive: escActive })
|
|
33
|
-
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
let cancelled = false
|
|
36
|
-
void (async () => {
|
|
37
|
-
try {
|
|
38
|
-
const all = await listSessions(50)
|
|
39
|
-
if (cancelled) return
|
|
40
|
-
setState({ kind: 'ready', sessions: all })
|
|
41
|
-
} catch (err: unknown) {
|
|
42
|
-
if (cancelled) return
|
|
43
|
-
setState({ kind: 'error', message: (err as Error).message })
|
|
44
|
-
}
|
|
45
|
-
})()
|
|
46
|
-
return () => { cancelled = true }
|
|
47
|
-
}, [currentSessionId])
|
|
48
|
-
|
|
49
|
-
if (state.kind === 'loading') {
|
|
50
|
-
return (
|
|
51
|
-
<Surface title="Resume Session" subtitle="Recent chats and directories." footer="esc closes">
|
|
52
|
-
<Spinner label="loading..." />
|
|
53
|
-
</Surface>
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (state.kind === 'error') {
|
|
58
|
-
return (
|
|
59
|
-
<Surface title="Resume Session" tone="muted" footer="esc closes">
|
|
60
|
-
<Text color={theme.dim}>{state.message}</Text>
|
|
61
|
-
</Surface>
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (state.kind === 'confirmClear') {
|
|
66
|
-
return (
|
|
67
|
-
<Surface
|
|
68
|
-
title="Clear All Saved Sessions?"
|
|
69
|
-
subtitle={`${state.sessions.length} saved session${state.sessions.length === 1 ? '' : 's'} will be removed.`}
|
|
70
|
-
tone="error"
|
|
71
|
-
footer="enter selects · esc returns to resume"
|
|
72
|
-
>
|
|
73
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
74
|
-
<Text color={theme.dim}>Removes saved chats and resume context from this machine.</Text>
|
|
75
|
-
<Text color={theme.dim}>Config, identities, keys, and local models stay.</Text>
|
|
76
|
-
{state.error ? <Text color={theme.accentError}>{state.error}</Text> : null}
|
|
77
|
-
</Box>
|
|
78
|
-
<Select<'back' | 'clear'>
|
|
79
|
-
hintLayout="inline"
|
|
80
|
-
options={[
|
|
81
|
-
{ value: 'back', label: 'Back to Sessions' },
|
|
82
|
-
{ value: 'clear', label: 'Clear All Saved Sessions', hint: 'Cannot be undone' },
|
|
83
|
-
]}
|
|
84
|
-
onSubmit={choice => {
|
|
85
|
-
if (choice === 'back') {
|
|
86
|
-
setState({ kind: 'ready', sessions: state.sessions })
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
|
-
void clearAll(state.sessions, onClearAll, setState)
|
|
90
|
-
}}
|
|
91
|
-
onCancel={() => setState({ kind: 'ready', sessions: state.sessions })}
|
|
92
|
-
/>
|
|
93
|
-
</Surface>
|
|
94
|
-
)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (state.kind === 'clearing') {
|
|
98
|
-
return (
|
|
99
|
-
<Surface title="Clearing Chat Logs" subtitle="Removing saved chats and resume context.">
|
|
100
|
-
<Spinner label="clearing sessions..." />
|
|
101
|
-
</Surface>
|
|
102
|
-
)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (state.sessions.length === 0) {
|
|
106
|
-
return (
|
|
107
|
-
<Surface title="Resume Session" tone="muted" footer="esc closes">
|
|
108
|
-
<Text color={theme.dim}>No prior sessions to resume.</Text>
|
|
109
|
-
</Surface>
|
|
110
|
-
)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const options = buildResumeOptions(state.sessions, currentSessionId)
|
|
114
|
-
|
|
115
|
-
return (
|
|
116
|
-
<Surface
|
|
117
|
-
title="Resume Session"
|
|
118
|
-
subtitle="Grouped by working directory."
|
|
119
|
-
footer="enter resumes · esc closes"
|
|
120
|
-
>
|
|
121
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
122
|
-
<Text color={theme.dim}>Recent directories</Text>
|
|
123
|
-
</Box>
|
|
124
|
-
<Select
|
|
125
|
-
options={options}
|
|
126
|
-
initialIndex={findInitialIndex(options, currentSessionId)}
|
|
127
|
-
maxVisible={14}
|
|
128
|
-
onSubmit={value => {
|
|
129
|
-
if (value === CLEAR_ALL_SESSIONS_VALUE) {
|
|
130
|
-
setState({ kind: 'confirmClear', sessions: state.sessions })
|
|
131
|
-
return
|
|
132
|
-
}
|
|
133
|
-
onResume(value)
|
|
134
|
-
}}
|
|
135
|
-
onCancel={onCancel}
|
|
136
|
-
/>
|
|
137
|
-
</Surface>
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function buildResumeOptions(
|
|
142
|
-
sessions: SessionSummary[],
|
|
143
|
-
currentSessionId: string,
|
|
144
|
-
): Array<SelectOption<string>> {
|
|
145
|
-
const groups = new Map<string, SessionSummary[]>()
|
|
146
|
-
for (const session of sessions) {
|
|
147
|
-
const key = session.lastCwd || session.workspaceRoot || session.projectRoot
|
|
148
|
-
const existing = groups.get(key) ?? []
|
|
149
|
-
existing.push(session)
|
|
150
|
-
groups.set(key, existing)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const options: Array<SelectOption<string>> = []
|
|
154
|
-
const manageSpacer: SelectOption<string> = {
|
|
155
|
-
value: 'separator:spacer',
|
|
156
|
-
label: '',
|
|
157
|
-
disabled: true,
|
|
158
|
-
}
|
|
159
|
-
const manageHeader: SelectOption<string> = {
|
|
160
|
-
value: 'separator:manage',
|
|
161
|
-
label: 'Manage',
|
|
162
|
-
role: 'section',
|
|
163
|
-
bold: true,
|
|
164
|
-
disabled: true,
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const clearOption: SelectOption<string> = {
|
|
168
|
-
value: CLEAR_ALL_SESSIONS_VALUE,
|
|
169
|
-
label: 'Clear All Saved Sessions',
|
|
170
|
-
hint: 'Removes saved chats and resume context',
|
|
171
|
-
role: 'utility',
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const orderedGroups = [...groups.entries()].sort(([, left], [, right]) => right[0]!.mtimeMs - left[0]!.mtimeMs)
|
|
175
|
-
|
|
176
|
-
for (const [directoryKey, group] of orderedGroups) {
|
|
177
|
-
const sorted = [...group].sort((left, right) => right.mtimeMs - left.mtimeMs)
|
|
178
|
-
const sessionCount = sorted.length
|
|
179
|
-
const lastActivity = formatRelative(sorted[0]!.mtimeMs)
|
|
180
|
-
options.push({
|
|
181
|
-
value: `directory:${directoryKey}`,
|
|
182
|
-
label: lastPathSegment(directoryKey) || compressProjectPath(directoryKey),
|
|
183
|
-
hint: `${compressProjectPath(directoryKey)} · ${sessionCount} session${sessionCount === 1 ? '' : 's'} · last ${lastActivity}`,
|
|
184
|
-
role: 'section',
|
|
185
|
-
bold: true,
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
for (const session of sorted) {
|
|
189
|
-
const baseLabel = formatFirstLine(session.firstUserMessage) || '(empty session)'
|
|
190
|
-
const markers = [
|
|
191
|
-
session.id === currentSessionId ? 'current' : '',
|
|
192
|
-
].filter(Boolean)
|
|
193
|
-
const label = markers.length > 0 ? `${baseLabel} (${markers.join(', ')})` : baseLabel
|
|
194
|
-
const summaryHint = session.compactedFromSessionId
|
|
195
|
-
? `summary from ${session.compactedFromSessionId.slice(0, 8)}`
|
|
196
|
-
: null
|
|
197
|
-
const hintParts = [
|
|
198
|
-
`${session.turnCount} turn${session.turnCount === 1 ? '' : 's'}`,
|
|
199
|
-
formatRelative(session.mtimeMs),
|
|
200
|
-
session.id.slice(0, 8),
|
|
201
|
-
summaryHint,
|
|
202
|
-
].filter(Boolean)
|
|
203
|
-
options.push({
|
|
204
|
-
value: session.id,
|
|
205
|
-
label,
|
|
206
|
-
hint: hintParts.join(' · '),
|
|
207
|
-
indent: 2,
|
|
208
|
-
})
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
options.push(manageSpacer)
|
|
213
|
-
options.push(manageHeader)
|
|
214
|
-
options.push(clearOption)
|
|
215
|
-
|
|
216
|
-
return options
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function findInitialIndex(options: Array<SelectOption<string>>, currentSessionId: string): number {
|
|
220
|
-
const currentIndex = options.findIndex(option => option.value === currentSessionId)
|
|
221
|
-
if (currentIndex >= 0) return currentIndex
|
|
222
|
-
return Math.max(
|
|
223
|
-
0,
|
|
224
|
-
options.findIndex(option =>
|
|
225
|
-
!option.disabled
|
|
226
|
-
&& option.role !== 'section'
|
|
227
|
-
&& option.role !== 'group'
|
|
228
|
-
&& option.value !== CLEAR_ALL_SESSIONS_VALUE,
|
|
229
|
-
),
|
|
230
|
-
)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async function clearAll(
|
|
234
|
-
sessions: SessionSummary[],
|
|
235
|
-
onClearAll: () => void | Promise<void>,
|
|
236
|
-
setState: (state: State) => void,
|
|
237
|
-
): Promise<void> {
|
|
238
|
-
setState({ kind: 'clearing', sessions })
|
|
239
|
-
try {
|
|
240
|
-
await onClearAll()
|
|
241
|
-
} catch (err: unknown) {
|
|
242
|
-
setState({ kind: 'confirmClear', sessions, error: (err as Error).message })
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function compressProjectPath(input: string): string {
|
|
247
|
-
const home = process.env.USERPROFILE || process.env.HOME || ''
|
|
248
|
-
return home && input.startsWith(home) ? `~${input.slice(home.length)}` : input
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function lastPathSegment(input: string): string {
|
|
252
|
-
const trimmed = input.replace(/[\\/]+$/, '')
|
|
253
|
-
const slash = Math.max(trimmed.lastIndexOf('/'), trimmed.lastIndexOf('\\'))
|
|
254
|
-
return slash >= 0 ? trimmed.slice(slash + 1) : trimmed
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function formatFirstLine(text: string): string {
|
|
258
|
-
const firstLine = text.split('\n', 1)[0] ?? ''
|
|
259
|
-
if (firstLine.length <= 56) return firstLine
|
|
260
|
-
return `${firstLine.slice(0, 53)}...`
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function formatRelative(ms: number): string {
|
|
264
|
-
const diffMs = Date.now() - ms
|
|
265
|
-
const minutes = Math.floor(diffMs / 60_000)
|
|
266
|
-
if (minutes < 1) return 'just now'
|
|
267
|
-
if (minutes < 60) return `${minutes}m ago`
|
|
268
|
-
const hours = Math.floor(minutes / 60)
|
|
269
|
-
if (hours < 24) return `${hours}h ago`
|
|
270
|
-
const days = Math.floor(hours / 24)
|
|
271
|
-
if (days < 7) return `${days}d ago`
|
|
272
|
-
return new Date(ms).toISOString().slice(0, 10)
|
|
273
|
-
}
|