ethagent 2.1.1 → 2.3.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/package.json +2 -1
- package/src/app/FirstRun.tsx +1 -7
- package/src/app/FirstRunTimeline.tsx +1 -1
- package/src/auth/openaiOAuth/credentials.ts +47 -0
- package/src/auth/openaiOAuth/crypto.ts +23 -0
- package/src/auth/openaiOAuth/index.ts +238 -0
- package/src/auth/openaiOAuth/landingPage.ts +125 -0
- package/src/auth/openaiOAuth/listener.ts +151 -0
- package/src/auth/openaiOAuth/refresh.ts +70 -0
- package/src/auth/openaiOAuth/shared.ts +115 -0
- package/src/chat/ChatBottomPane.tsx +20 -11
- package/src/chat/ChatScreen.tsx +160 -35
- package/src/chat/ConversationStack.tsx +1 -1
- package/src/chat/MessageList.tsx +185 -72
- package/src/chat/SessionStatus.tsx +3 -1
- package/src/chat/chatScreenUtils.ts +11 -15
- package/src/chat/chatSessionState.ts +3 -2
- package/src/chat/chatTurnOrchestrator.ts +1 -7
- package/src/chat/commands.ts +28 -27
- package/src/chat/display/DiffView.tsx +193 -0
- package/src/chat/display/SyntaxText.tsx +192 -0
- package/src/chat/display/toolCallDisplay.ts +103 -0
- package/src/chat/display/toolResultDisplay.ts +19 -0
- package/src/chat/{ChatInput.tsx → input/ChatInput.tsx} +36 -23
- package/src/chat/{TranscriptView.tsx → transcript/TranscriptView.tsx} +24 -50
- package/src/chat/{transcriptViewport.ts → transcript/transcriptViewport.ts} +12 -30
- package/src/chat/{ContextLimitView.tsx → views/ContextLimitView.tsx} +3 -3
- package/src/chat/{ContinuityEditReviewView.tsx → views/ContinuityEditReviewView.tsx} +11 -3
- package/src/chat/{CopyPicker.tsx → views/CopyPicker.tsx} +4 -5
- package/src/chat/{PermissionPrompt.tsx → views/PermissionPrompt.tsx} +16 -17
- package/src/chat/{PermissionsView.tsx → views/PermissionsView.tsx} +6 -6
- package/src/chat/{PlanApprovalView.tsx → views/PlanApprovalView.tsx} +4 -4
- package/src/chat/{ResumeView.tsx → views/ResumeView.tsx} +35 -35
- package/src/chat/views/RewindView.tsx +410 -0
- package/src/identity/continuity/privateEdit/diff.ts +2 -78
- package/src/identity/ens/agentRecords.ts +5 -19
- package/src/identity/ens/ensAutomation/setup.ts +0 -1
- package/src/identity/ens/ensAutomation/types.ts +0 -1
- package/src/identity/hub/OperationalRoutes.tsx +23 -32
- package/src/identity/hub/Routes.tsx +13 -13
- package/src/identity/hub/{flows/continuity → continuity}/ContinuityDashboardScreen.tsx +9 -9
- package/src/identity/hub/{flows/continuity → continuity}/RebackupStorageScreen.tsx +2 -2
- package/src/identity/hub/{flows/continuity → continuity}/RecoveryConfirmScreen.tsx +5 -5
- package/src/identity/hub/{flows/continuity → continuity}/SavePromptScreen.tsx +5 -5
- package/src/identity/hub/{effects/rebackup/runRebackup.ts → continuity/effects.ts} +19 -19
- package/src/identity/hub/{effects/rebackup → continuity}/index.ts +1 -1
- package/src/identity/hub/{effects/shared → continuity}/snapshot.ts +8 -8
- package/src/identity/hub/{effects/rebackup → continuity}/vault.ts +15 -15
- package/src/identity/hub/{flows/create → create}/CreateFlow.tsx +13 -13
- package/src/identity/hub/{effects/create.ts → create/effects.ts} +4 -4
- package/src/identity/hub/{flows/custody → custody}/CustodyEditFlow.tsx +10 -48
- package/src/identity/hub/{flows/custody/custodyFlowActions.ts → custody/actions.ts} +11 -9
- package/src/identity/hub/{flows/custody/custodyFlowHelpers.ts → custody/helpers.ts} +4 -4
- package/src/identity/hub/{effects/vault → custody}/preflight.ts +5 -5
- package/src/identity/hub/{flows/custody/custodyFlowRoutes.tsx → custody/routes.tsx} +8 -8
- package/src/identity/hub/{flows/custody/custodyEffects.ts → custody/transactions.ts} +9 -9
- package/src/identity/hub/{flows/custody/custodyFlowTypes.ts → custody/types.ts} +6 -6
- package/src/identity/hub/{flows/custody/custodyFlowEffects.ts → custody/useCustodyEffects.ts} +7 -7
- package/src/identity/hub/{flows/custody → custody}/useCustodyFlow.tsx +5 -5
- package/src/identity/hub/ens/EnsEditAdvancedScreens.tsx +241 -0
- package/src/identity/hub/{flows/ens → ens}/EnsEditFlow.tsx +27 -82
- package/src/identity/hub/{flows/ens → ens}/EnsEditMaintenanceScreens.tsx +25 -65
- package/src/identity/hub/{flows/ens → ens}/EnsEditReviewScreens.tsx +12 -30
- package/src/identity/hub/ens/EnsEditRunners.tsx +62 -0
- package/src/identity/hub/{flows/ens → ens}/EnsEditShared.tsx +15 -14
- package/src/identity/hub/{flows/ens → ens}/EnsEditSimpleScreens.tsx +68 -217
- package/src/identity/hub/{flows/ens/IdentityHubEnsFlow.tsx → ens/EnsFlow.tsx} +18 -11
- package/src/identity/hub/{flows/ens/OperatorWalletsScreen.tsx → ens/EnsOperatorWalletsScreen.tsx} +17 -48
- package/src/identity/hub/{advancedEnsValidation.ts → ens/advancedEnsValidation.ts} +2 -2
- package/src/identity/hub/{flows/ens/ensEditCopy.ts → ens/editCopy.ts} +4 -4
- package/src/identity/hub/{effects/ens/flows.ts → ens/effects.ts} +7 -7
- package/src/identity/hub/{effects/ens → ens}/index.ts +1 -1
- package/src/identity/hub/{model/ens.ts → ens/state.ts} +1 -1
- package/src/identity/hub/{effects/ens → ens}/transactions.ts +232 -232
- package/src/identity/hub/{flows/ens/ensEditTypes.ts → ens/types.ts} +12 -26
- package/src/identity/hub/identityHubReducer.ts +3 -3
- package/src/identity/hub/{flows/profile → profile}/EditProfileFlow.tsx +17 -10
- package/src/identity/hub/{effects/publicProfile/runPublicProfileSave.ts → profile/effects.ts} +55 -177
- package/src/identity/hub/{model → profile}/identity.ts +3 -3
- package/src/identity/hub/{effects/profile/profileState.ts → profile/state.ts} +181 -173
- package/src/identity/hub/{flows/restore → restore}/RestoreFlow.tsx +21 -21
- package/src/identity/hub/{effects/restore → restore}/apply.ts +10 -10
- package/src/identity/hub/{effects/restore → restore}/auth.ts +7 -7
- package/src/identity/hub/{effects/restore → restore}/discover.ts +6 -6
- package/src/identity/hub/{effects/restore → restore}/envelopes.ts +2 -2
- package/src/identity/hub/{effects/restore → restore}/fetch.ts +3 -3
- package/src/identity/hub/{effects/restore/shared.ts → restore/helpers.ts} +6 -6
- package/src/identity/hub/{effects/restore → restore}/recovery.ts +10 -10
- package/src/identity/hub/{effects/restore → restore}/resolve.ts +4 -4
- package/src/identity/hub/restore/restoreAdmin.ts +34 -0
- package/src/identity/hub/{flows/restore/useRestoreFlowEffects.ts → restore/useRestoreEffects.ts} +5 -5
- package/src/identity/hub/{flows/settings → settings}/StorageCredentialScreen.tsx +5 -5
- package/src/identity/hub/{components → shared/components}/BusyScreen.tsx +4 -4
- package/src/identity/hub/{components → shared/components}/DetailsScreen.tsx +4 -4
- package/src/identity/hub/{components → shared/components}/ErrorScreen.tsx +4 -4
- package/src/identity/hub/{components → shared/components}/FlowTimeline.tsx +1 -1
- package/src/identity/hub/{components → shared/components}/IdentitySummary.tsx +16 -11
- package/src/identity/hub/{components → shared/components}/MenuScreen.tsx +8 -9
- package/src/identity/hub/{components → shared/components}/NetworkScreen.tsx +4 -4
- package/src/identity/hub/{components → shared/components}/PinataJwtInput.tsx +4 -4
- package/src/identity/hub/{components → shared/components}/UnlinkedIdentityScreen.tsx +5 -5
- package/src/identity/hub/{components → shared/components}/WalletApprovalScreen.tsx +6 -6
- package/src/identity/hub/{components → shared/components}/menuFlagsFromReconciliation.ts +2 -4
- package/src/identity/hub/{effects/shared → shared/effects}/profilePrep.ts +1 -1
- package/src/identity/hub/{effects → shared/effects}/receipts.ts +2 -2
- package/src/identity/hub/{effects/shared → shared/effects}/sync.ts +6 -47
- package/src/identity/hub/{effects → shared/effects}/types.ts +3 -3
- package/src/identity/hub/{model → shared/model}/copy.ts +2 -2
- package/src/identity/hub/{model → shared/model}/errors.ts +5 -5
- package/src/identity/hub/{model → shared/model}/network.ts +3 -3
- package/src/identity/hub/{operatorWallets.ts → shared/operatorWallets.ts} +1 -1
- package/src/identity/hub/{reconciliation → shared/reconciliation}/agentReconciliation/hook.ts +1 -2
- package/src/identity/hub/{reconciliation → shared/reconciliation}/agentReconciliation/ownership.ts +2 -2
- package/src/identity/hub/{reconciliation → shared/reconciliation}/agentReconciliation/run.ts +7 -40
- package/src/identity/hub/{reconciliation → shared/reconciliation}/agentReconciliation/types.ts +0 -4
- package/src/identity/hub/{reconciliation → shared/reconciliation}/index.ts +0 -7
- package/src/identity/hub/shared/reconciliation/walletSetup.ts +27 -0
- package/src/identity/hub/{utils.ts → shared/utils.ts} +5 -5
- package/src/identity/hub/{flows/token-transfer/IdentityHubTokenTransferFlow.tsx → transfer/TokenTransferFlow.tsx} +8 -8
- package/src/identity/hub/{flows/token-transfer → transfer}/TokenTransferScreens.tsx +14 -14
- package/src/identity/hub/{effects/token-transfer/runTokenTransfer.ts → transfer/effects.ts} +16 -16
- package/src/identity/hub/{effects/token-transfer → transfer}/progress.ts +1 -1
- package/src/identity/hub/useIdentityHubController.ts +11 -11
- package/src/identity/hub/useIdentityHubSideEffects.ts +11 -11
- package/src/identity/wallet/browserWallet/types.ts +0 -5
- package/src/identity/wallet/page/copy.ts +1 -31
- package/src/identity/wallet/walletPurposeCompat.ts +0 -2
- package/src/models/ModelPicker.tsx +248 -8
- package/src/models/catalog.ts +29 -1
- package/src/models/modelPickerOptions.ts +12 -10
- package/src/models/providerDisplay.ts +16 -0
- package/src/providers/errors.ts +6 -4
- package/src/providers/openai-chat.ts +2 -1
- package/src/providers/openai-responses-format.ts +156 -0
- package/src/providers/openai-responses.ts +276 -0
- package/src/providers/registry.ts +85 -8
- package/src/runtime/sessionMode.ts +1 -1
- package/src/runtime/systemPrompt.ts +4 -2
- package/src/runtime/toolExecution.ts +9 -6
- package/src/runtime/turn.ts +29 -1
- package/src/storage/rewind.ts +20 -0
- package/src/storage/secrets.ts +4 -1
- package/src/storage/sessions.ts +2 -1
- package/src/tools/bashSafety.ts +7 -3
- package/src/tools/bashTool.ts +1 -1
- package/src/tools/contracts.ts +3 -0
- package/src/tools/deleteFileTool.ts +8 -3
- package/src/tools/editTool.ts +10 -5
- package/src/tools/fileDiff.ts +261 -0
- package/src/tools/privateContinuityEditTool.ts +11 -1
- package/src/tools/writeFileTool.ts +8 -3
- package/src/ui/Spinner.tsx +25 -3
- package/src/ui/TextInput.tsx +2 -2
- package/src/ui/theme.ts +17 -0
- package/src/utils/clipboard.ts +10 -7
- package/src/utils/openExternal.ts +20 -10
- package/src/chat/RewindView.tsx +0 -386
- package/src/chat/toolResultDisplay.ts +0 -8
- package/src/identity/ens/ensRegistration.ts +0 -199
- package/src/identity/hub/effects/index.ts +0 -74
- package/src/identity/hub/effects/publicProfile/index.ts +0 -5
- package/src/identity/hub/effects/restore/restoreEffects.ts +0 -22
- package/src/identity/hub/effects/restoreAdmin.ts +0 -93
- package/src/identity/hub/effects/token-transfer/index.ts +0 -6
- package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +0 -336
- package/src/identity/hub/flows/ens/EnsEditRunners.tsx +0 -198
- package/src/identity/hub/reconciliation/walletSetup.ts +0 -220
- /package/src/chat/{chatInputState.ts → input/chatInputState.ts} +0 -0
- /package/src/chat/{chatPaste.ts → input/chatPaste.ts} +0 -0
- /package/src/chat/{textCursor.ts → input/textCursor.ts} +0 -0
- /package/src/identity/hub/{model/continuity.ts → continuity/state.ts} +0 -0
- /package/src/identity/hub/{model/custody.ts → custody/state.ts} +0 -0
- /package/src/identity/hub/{effects/restore → restore}/index.ts +0 -0
- /package/src/identity/hub/{model → shared/model}/format.ts +0 -0
- /package/src/identity/hub/{reconciliation → shared/reconciliation}/useAgentReconciliation.ts +0 -0
- /package/src/identity/hub/{txGuard.ts → shared/txGuard.ts} +0 -0
- /package/src/identity/hub/{model/transfer.ts → transfer/state.ts} +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { splitFileChangeResult } from '../../tools/fileDiff.js'
|
|
2
|
+
|
|
3
|
+
const COMPACT_SUCCESS_TOOL_RESULTS = new Set([
|
|
4
|
+
'read_file',
|
|
5
|
+
'read_private_continuity_file',
|
|
6
|
+
])
|
|
7
|
+
|
|
8
|
+
export function hidesSuccessfulToolResultContent(name: string, isError?: boolean): boolean {
|
|
9
|
+
return !isError && COMPACT_SUCCESS_TOOL_RESULTS.has(name)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function toolResultDiffContent(content: string, isError?: boolean): string | undefined {
|
|
13
|
+
if (isError) return undefined
|
|
14
|
+
return splitFileChangeResult(content).diff
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function toolResultTextContent(content: string): string {
|
|
18
|
+
return splitFileChangeResult(content).content
|
|
19
|
+
}
|
|
@@ -2,10 +2,10 @@ import fs from 'node:fs/promises'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
4
4
|
import { Box, Text, useStdout } from 'ink'
|
|
5
|
-
import { theme } from '
|
|
6
|
-
import { readClipboardImage } from '
|
|
7
|
-
import { useAppInput } from '
|
|
8
|
-
import type { SlashSuggestion } from '
|
|
5
|
+
import { theme } from '../../ui/theme.js'
|
|
6
|
+
import { readClipboardImage } from '../../utils/clipboard.js'
|
|
7
|
+
import { useAppInput } from '../../app/input/AppInputProvider.js'
|
|
8
|
+
import type { SlashSuggestion } from '../commands.js'
|
|
9
9
|
import {
|
|
10
10
|
beginHistoryPreview,
|
|
11
11
|
canNavigateHistory as canNavigateHistoryState,
|
|
@@ -46,6 +46,8 @@ type PromptInputProps = {
|
|
|
46
46
|
onModeChange?: (mode: 'prompt' | 'bash') => void
|
|
47
47
|
footerRight?: React.ReactNode
|
|
48
48
|
cwd?: string
|
|
49
|
+
seedText?: string | null
|
|
50
|
+
onSeedConsumed?: () => void
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
const MAX_LENGTH = 32_768
|
|
@@ -66,12 +68,14 @@ export const ChatInput: React.FC<PromptInputProps> = ({
|
|
|
66
68
|
disabled,
|
|
67
69
|
placeholderHints,
|
|
68
70
|
queuedMessages = [],
|
|
69
|
-
prefix = '
|
|
71
|
+
prefix = '›',
|
|
70
72
|
slashSuggestions = [],
|
|
71
73
|
mode = 'prompt',
|
|
72
74
|
onModeChange,
|
|
73
75
|
footerRight,
|
|
74
76
|
cwd,
|
|
77
|
+
seedText,
|
|
78
|
+
onSeedConsumed,
|
|
75
79
|
}) => {
|
|
76
80
|
const { stdout } = useStdout()
|
|
77
81
|
const [buffer, setBuffer] = useState<ChatBuffer>(emptyBuffer)
|
|
@@ -141,6 +145,15 @@ export const ChatInput: React.FC<PromptInputProps> = ({
|
|
|
141
145
|
applyHistoryState(exitHistoryPreview(next))
|
|
142
146
|
}, [applyHistoryState])
|
|
143
147
|
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (seedText == null) return
|
|
150
|
+
const next: ChatBuffer = { value: seedText, cursor: seedText.length }
|
|
151
|
+
bufferRef.current = next
|
|
152
|
+
setBuffer(next)
|
|
153
|
+
applyHistoryState(exitHistoryPreview(next))
|
|
154
|
+
onSeedConsumed?.()
|
|
155
|
+
}, [seedText, applyHistoryState, onSeedConsumed])
|
|
156
|
+
|
|
144
157
|
useEffect(() => {
|
|
145
158
|
if (!placeholderHints || placeholderHints.length < 2) return
|
|
146
159
|
const timer = setInterval(() => {
|
|
@@ -506,6 +519,22 @@ export const ChatInput: React.FC<PromptInputProps> = ({
|
|
|
506
519
|
|
|
507
520
|
return (
|
|
508
521
|
<Box flexDirection="column" width="100%">
|
|
522
|
+
{queuedMessages.length > 0 ? (
|
|
523
|
+
<Box marginLeft={2} marginBottom={1} flexDirection="column">
|
|
524
|
+
<Text color={theme.dim}>
|
|
525
|
+
{queuedMessages.length === 1 ? '1 message queued for next turn' : `${queuedMessages.length} messages queued for next turns`}
|
|
526
|
+
</Text>
|
|
527
|
+
{queuedMessages.slice(0, 3).map((message, i) => (
|
|
528
|
+
<Text key={`${i}-${message.slice(0, 24)}`}>
|
|
529
|
+
<Text color={theme.accentPeriwinkle}>{i === 0 ? '» ' : ' '}</Text>
|
|
530
|
+
<Text color={theme.textSubtle}>{summarizeQueuedMessage(message)}</Text>
|
|
531
|
+
</Text>
|
|
532
|
+
))}
|
|
533
|
+
{queuedMessages.length > 3 ? (
|
|
534
|
+
<Text color={theme.dim}>+{queuedMessages.length - 3} more</Text>
|
|
535
|
+
) : null}
|
|
536
|
+
</Box>
|
|
537
|
+
) : null}
|
|
509
538
|
<Box
|
|
510
539
|
borderStyle="round"
|
|
511
540
|
borderColor={borderColor}
|
|
@@ -545,7 +574,7 @@ export const ChatInput: React.FC<PromptInputProps> = ({
|
|
|
545
574
|
<Box marginLeft={2} flexDirection="column">
|
|
546
575
|
{filteredSuggestions.map((s, i) => (
|
|
547
576
|
<Text key={s.name} color={i === suggestionIdx ? theme.accentPeriwinkle : theme.dim}>
|
|
548
|
-
{i === suggestionIdx ? '
|
|
577
|
+
{i === suggestionIdx ? '› ' : ' '}/{s.name}
|
|
549
578
|
<Text color={theme.dim}> {s.summary}{i === suggestionIdx ? (s.executeOnEnter ? ' · enter runs' : ' · enter fills') : ''}</Text>
|
|
550
579
|
</Text>
|
|
551
580
|
))}
|
|
@@ -555,7 +584,7 @@ export const ChatInput: React.FC<PromptInputProps> = ({
|
|
|
555
584
|
<Box marginLeft={2} flexDirection="column">
|
|
556
585
|
{fileSuggestions.slice(0, 8).map((s, i) => (
|
|
557
586
|
<Text key={s.path} color={i === fileSuggestionIdx ? theme.accentPeriwinkle : theme.dim}>
|
|
558
|
-
{i === fileSuggestionIdx ? '
|
|
587
|
+
{i === fileSuggestionIdx ? '› ' : ' '}@{s.path}
|
|
559
588
|
<Text color={theme.dim}> {i === fileSuggestionIdx ? 'tab/enter completes' : s.hint}</Text>
|
|
560
589
|
</Text>
|
|
561
590
|
))}
|
|
@@ -564,22 +593,6 @@ export const ChatInput: React.FC<PromptInputProps> = ({
|
|
|
564
593
|
) : null}
|
|
565
594
|
</Box>
|
|
566
595
|
) : null}
|
|
567
|
-
{queuedMessages.length > 0 ? (
|
|
568
|
-
<Box marginLeft={2} flexDirection="column">
|
|
569
|
-
<Text color={theme.dim}>
|
|
570
|
-
{queuedMessages.length === 1 ? '1 message queued for next turn' : `${queuedMessages.length} messages queued for next turns`}
|
|
571
|
-
</Text>
|
|
572
|
-
{queuedMessages.slice(0, 3).map((message, i) => (
|
|
573
|
-
<Text key={`${i}-${message.slice(0, 24)}`}>
|
|
574
|
-
<Text color={theme.accentPeriwinkle}>{i === 0 ? '» ' : ' '}</Text>
|
|
575
|
-
<Text color={theme.textSubtle}>{summarizeQueuedMessage(message)}</Text>
|
|
576
|
-
</Text>
|
|
577
|
-
))}
|
|
578
|
-
{queuedMessages.length > 3 ? (
|
|
579
|
-
<Text color={theme.dim}>+{queuedMessages.length - 3} more</Text>
|
|
580
|
-
) : null}
|
|
581
|
-
</Box>
|
|
582
|
-
) : null}
|
|
583
596
|
{footerRight ? (
|
|
584
597
|
<Box marginLeft={2}>
|
|
585
598
|
{footerRight}
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useState } from 'react'
|
|
2
2
|
import { Box, Text, useStdout } from 'ink'
|
|
3
|
-
import { useAppInput } from '
|
|
4
|
-
import { MessageList, type MessageRow } from '
|
|
5
|
-
import { theme } from '
|
|
3
|
+
import { useAppInput } from '../../app/input/AppInputProvider.js'
|
|
4
|
+
import { MessageList, type MessageRow } from '../MessageList.js'
|
|
5
|
+
import { theme } from '../../ui/theme.js'
|
|
6
6
|
import {
|
|
7
7
|
anchorForScrollTop,
|
|
8
8
|
buildLineOffsets,
|
|
9
9
|
clampLine,
|
|
10
10
|
estimateMessageRowHeight,
|
|
11
|
-
promptScrollTopForPageDown,
|
|
12
|
-
promptScrollTopForPageUp,
|
|
13
11
|
resolveScrollTopFromAnchor,
|
|
12
|
+
scrollTopForPageDown,
|
|
13
|
+
scrollTopForPageUp,
|
|
14
14
|
selectRowsForScrollTop,
|
|
15
|
-
type TranscriptWindowSelection,
|
|
16
15
|
type TranscriptViewportState,
|
|
17
16
|
} from './transcriptViewport.js'
|
|
18
17
|
|
|
@@ -24,10 +23,10 @@ type TranscriptViewProps = {
|
|
|
24
23
|
onScrollabilityChange?: (canScroll: boolean) => void
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
const PROMPT_RESERVED_LINES =
|
|
28
|
-
const OVERLAY_RESERVED_LINES =
|
|
26
|
+
const PROMPT_RESERVED_LINES = 7
|
|
27
|
+
const OVERLAY_RESERVED_LINES = 12
|
|
29
28
|
const MIN_TRANSCRIPT_LINES = 6
|
|
30
|
-
const MAX_TRANSCRIPT_LINES =
|
|
29
|
+
const MAX_TRANSCRIPT_LINES = 240
|
|
31
30
|
|
|
32
31
|
export const TranscriptView: React.FC<TranscriptViewProps> = ({
|
|
33
32
|
rows,
|
|
@@ -64,15 +63,11 @@ export const TranscriptView: React.FC<TranscriptViewProps> = ({
|
|
|
64
63
|
[metrics, viewportState],
|
|
65
64
|
)
|
|
66
65
|
const selection = useMemo(
|
|
67
|
-
() =>
|
|
68
|
-
selectRowsForScrollTop(
|
|
69
|
-
rows,
|
|
70
|
-
maxLines,
|
|
71
|
-
resolvedViewportState.scrollTopLine,
|
|
72
|
-
row => estimateMessageRowHeight(row, columns),
|
|
73
|
-
),
|
|
66
|
+
() => selectRowsForScrollTop(
|
|
74
67
|
rows,
|
|
75
|
-
|
|
68
|
+
maxLines,
|
|
69
|
+
resolvedViewportState.scrollTopLine,
|
|
70
|
+
row => estimateMessageRowHeight(row, columns),
|
|
76
71
|
),
|
|
77
72
|
[columns, maxLines, resolvedViewportState, rows],
|
|
78
73
|
)
|
|
@@ -97,12 +92,10 @@ export const TranscriptView: React.FC<TranscriptViewProps> = ({
|
|
|
97
92
|
|
|
98
93
|
useAppInput((_input, key) => {
|
|
99
94
|
if (key.pageUp) {
|
|
100
|
-
const target =
|
|
101
|
-
rows,
|
|
102
|
-
metrics.offsets,
|
|
95
|
+
const target = scrollTopForPageUp(
|
|
103
96
|
resolvedViewportState.scrollTopLine,
|
|
104
97
|
metrics.maxScrollTop,
|
|
105
|
-
|
|
98
|
+
maxLines,
|
|
106
99
|
)
|
|
107
100
|
setViewportState(viewportForScrollTop(
|
|
108
101
|
target,
|
|
@@ -111,11 +104,10 @@ export const TranscriptView: React.FC<TranscriptViewProps> = ({
|
|
|
111
104
|
metrics.maxScrollTop,
|
|
112
105
|
))
|
|
113
106
|
} else if (key.pageDown) {
|
|
114
|
-
const target =
|
|
115
|
-
rows,
|
|
116
|
-
metrics.offsets,
|
|
107
|
+
const target = scrollTopForPageDown(
|
|
117
108
|
resolvedViewportState.scrollTopLine,
|
|
118
109
|
metrics.maxScrollTop,
|
|
110
|
+
maxLines,
|
|
119
111
|
)
|
|
120
112
|
setViewportState(viewportForScrollTop(
|
|
121
113
|
target,
|
|
@@ -128,18 +120,21 @@ export const TranscriptView: React.FC<TranscriptViewProps> = ({
|
|
|
128
120
|
|
|
129
121
|
return (
|
|
130
122
|
<Box flexDirection="column">
|
|
131
|
-
<Box marginBottom={1}>
|
|
132
|
-
<Text> </Text>
|
|
133
|
-
</Box>
|
|
134
123
|
{selection.hiddenBefore > 0 ? (
|
|
135
124
|
<Text color={theme.dim}>
|
|
136
|
-
{` ${selection.hiddenBefore} earlier message${selection.hiddenBefore === 1 ? '' : 's'} above
|
|
125
|
+
{` ${selection.hiddenBefore} earlier message${selection.hiddenBefore === 1 ? '' : 's'} above · `}
|
|
126
|
+
<Text color={theme.accentPeriwinkle}>pgup</Text>
|
|
127
|
+
{` to scroll · `}
|
|
128
|
+
<Text color={theme.accentPeriwinkle}>/export</Text>
|
|
129
|
+
{` saves the full transcript`}
|
|
137
130
|
</Text>
|
|
138
131
|
) : null}
|
|
139
132
|
<MessageList rows={selection.rows} />
|
|
140
133
|
{selection.hiddenAfter > 0 ? (
|
|
141
134
|
<Text color={theme.dim}>
|
|
142
|
-
{` ${selection.hiddenAfter} later message${selection.hiddenAfter === 1 ? '' : 's'} below ·
|
|
135
|
+
{` ${selection.hiddenAfter} later message${selection.hiddenAfter === 1 ? '' : 's'} below · `}
|
|
136
|
+
<Text color={theme.accentPeriwinkle}>pgdn</Text>
|
|
137
|
+
{` to return`}
|
|
143
138
|
</Text>
|
|
144
139
|
) : null}
|
|
145
140
|
</Box>
|
|
@@ -179,27 +174,6 @@ function viewportForScrollTop(
|
|
|
179
174
|
}
|
|
180
175
|
}
|
|
181
176
|
|
|
182
|
-
function trimSelectionToFocusedTurn(
|
|
183
|
-
selection: TranscriptWindowSelection<MessageRow>,
|
|
184
|
-
rows: MessageRow[],
|
|
185
|
-
state: TranscriptViewportState,
|
|
186
|
-
): TranscriptWindowSelection<MessageRow> {
|
|
187
|
-
if (state.followTail || state.anchor?.offset !== 0) return selection
|
|
188
|
-
const focusedIndex = rows.findIndex(row => row.id === state.anchor?.rowId)
|
|
189
|
-
if (focusedIndex === -1 || rows[focusedIndex]?.role !== 'user') return selection
|
|
190
|
-
const firstSelected = selection.rows[0]
|
|
191
|
-
if (!firstSelected || firstSelected.id !== state.anchor.rowId) return selection
|
|
192
|
-
|
|
193
|
-
const nextPromptIndex = selection.rows.findIndex((row, index) => index > 0 && row.role === 'user')
|
|
194
|
-
if (nextPromptIndex === -1) return selection
|
|
195
|
-
|
|
196
|
-
return {
|
|
197
|
-
...selection,
|
|
198
|
-
rows: selection.rows.slice(0, nextPromptIndex),
|
|
199
|
-
hiddenAfter: selection.hiddenAfter + selection.rows.length - nextPromptIndex,
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
177
|
function sameViewportState(left: TranscriptViewportState, right: TranscriptViewportState): boolean {
|
|
204
178
|
return left.scrollTopLine === right.scrollTopLine
|
|
205
179
|
&& left.followTail === right.followTail
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MessageRow } from '
|
|
1
|
+
import type { MessageRow } from '../MessageList.js'
|
|
2
2
|
|
|
3
3
|
export type TranscriptAnchor = {
|
|
4
4
|
rowId: string
|
|
@@ -141,45 +141,25 @@ export function selectRowsForScrollTop<T>(
|
|
|
141
141
|
return selectRowsForLineWindow(rows, offsets, budget, startLine, totalLines, maxScrollOffset)
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
export function
|
|
145
|
-
rows: MessageRow[],
|
|
146
|
-
offsets: number[],
|
|
144
|
+
export function scrollTopForPageUp(
|
|
147
145
|
scrollTopLine: number,
|
|
148
146
|
maxScrollTop: number,
|
|
149
|
-
|
|
147
|
+
viewportLines: number,
|
|
150
148
|
): number {
|
|
151
|
-
|
|
152
|
-
if (promptStarts.length === 0) return clampLine(scrollTopLine, maxScrollTop)
|
|
153
|
-
if (followTail) return clampLine(promptStarts[promptStarts.length - 1]!, maxScrollTop)
|
|
154
|
-
|
|
155
|
-
const currentLine = clampLine(scrollTopLine, maxScrollTop)
|
|
156
|
-
for (let index = promptStarts.length - 1; index >= 0; index -= 1) {
|
|
157
|
-
const line = promptStarts[index]!
|
|
158
|
-
if (line < currentLine) return clampLine(line, maxScrollTop)
|
|
159
|
-
}
|
|
160
|
-
return 0
|
|
149
|
+
return clampLine(scrollTopLine - pageScrollDistance(viewportLines), maxScrollTop)
|
|
161
150
|
}
|
|
162
151
|
|
|
163
|
-
export function
|
|
164
|
-
rows: MessageRow[],
|
|
165
|
-
offsets: number[],
|
|
152
|
+
export function scrollTopForPageDown(
|
|
166
153
|
scrollTopLine: number,
|
|
167
154
|
maxScrollTop: number,
|
|
155
|
+
viewportLines: number,
|
|
168
156
|
): number {
|
|
169
|
-
|
|
170
|
-
if (promptStarts.length === 0) return clampLine(scrollTopLine, maxScrollTop)
|
|
171
|
-
|
|
172
|
-
const currentLine = clampLine(scrollTopLine, maxScrollTop)
|
|
173
|
-
const next = promptStarts.find(line => line > currentLine)
|
|
174
|
-
return next === undefined ? maxScrollTop : clampLine(next, maxScrollTop)
|
|
157
|
+
return clampLine(scrollTopLine + pageScrollDistance(viewportLines), maxScrollTop)
|
|
175
158
|
}
|
|
176
159
|
|
|
177
|
-
function
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
if (rows[index]?.role === 'user') starts.push(offsets[index] ?? 0)
|
|
181
|
-
}
|
|
182
|
-
return starts
|
|
160
|
+
function pageScrollDistance(viewportLines: number): number {
|
|
161
|
+
const viewport = Math.max(1, Math.floor(viewportLines))
|
|
162
|
+
return Math.max(1, Math.floor(viewport / 2))
|
|
183
163
|
}
|
|
184
164
|
|
|
185
165
|
function selectRowsForLineWindow<T>(
|
|
@@ -224,6 +204,8 @@ export function estimateMessageRowHeight(row: MessageRow, columns = 80): number
|
|
|
224
204
|
return 1 + wrappedLineCount(row.content, contentWidth)
|
|
225
205
|
case 'progress':
|
|
226
206
|
return 4
|
|
207
|
+
case 'splash':
|
|
208
|
+
return 28
|
|
227
209
|
}
|
|
228
210
|
}
|
|
229
211
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { Box, Text } from 'ink'
|
|
3
|
-
import type { ContextUsage } from '
|
|
4
|
-
import { useAppInput } from '
|
|
5
|
-
import { theme } from '
|
|
3
|
+
import type { ContextUsage } from '../../runtime/compaction.js'
|
|
4
|
+
import { useAppInput } from '../../app/input/AppInputProvider.js'
|
|
5
|
+
import { theme } from '../../ui/theme.js'
|
|
6
6
|
|
|
7
7
|
export type ContextLimitAction = 'compact' | 'switchModel' | 'send' | 'cancel'
|
|
8
8
|
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { Box, Text } from 'ink'
|
|
3
|
-
import { Surface } from '
|
|
4
|
-
import { Select } from '
|
|
5
|
-
import { theme } from '
|
|
3
|
+
import { Surface } from '../../ui/Surface.js'
|
|
4
|
+
import { Select } from '../../ui/Select.js'
|
|
5
|
+
import { theme } from '../../ui/theme.js'
|
|
6
|
+
import { DiffView } from '../display/DiffView.js'
|
|
6
7
|
|
|
7
8
|
export type ContinuityEditReviewState = {
|
|
8
9
|
file: 'SOUL.md' | 'MEMORY.md'
|
|
9
10
|
filePath: string
|
|
10
11
|
summary: string
|
|
12
|
+
diff?: string
|
|
11
13
|
editorOpened?: boolean
|
|
12
14
|
}
|
|
13
15
|
|
|
@@ -23,6 +25,12 @@ export const ContinuityEditReviewView: React.FC<{
|
|
|
23
25
|
footer="enter select · esc dismiss"
|
|
24
26
|
>
|
|
25
27
|
<Text color={theme.accentPeriwinkle}>{displayContinuityReviewText(review.summary)}</Text>
|
|
28
|
+
{review.diff ? (
|
|
29
|
+
<Box flexDirection="column" marginTop={1}>
|
|
30
|
+
<Text color={theme.accentPeriwinkle}>diff</Text>
|
|
31
|
+
<DiffView diff={review.diff} />
|
|
32
|
+
</Box>
|
|
33
|
+
) : null}
|
|
26
34
|
<Box marginTop={1}>
|
|
27
35
|
<Select<ContinuityEditReviewAction>
|
|
28
36
|
options={[
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { Surface } from '
|
|
3
|
-
import { Select } from '
|
|
4
|
-
import { parseSegments, type Segment } from '
|
|
5
|
-
import { copyToClipboard, type CopyResult } from '
|
|
2
|
+
import { Surface } from '../../ui/Surface.js'
|
|
3
|
+
import { Select } from '../../ui/Select.js'
|
|
4
|
+
import { parseSegments, type Segment } from '../../utils/markdownSegments.js'
|
|
5
|
+
import { copyToClipboard, type CopyResult } from '../../utils/clipboard.js'
|
|
6
6
|
|
|
7
7
|
type CopyPickerProps = {
|
|
8
8
|
turnText: string
|
|
@@ -48,4 +48,3 @@ export const CopyPicker: React.FC<CopyPickerProps> = ({ turnText, turnLabel, onD
|
|
|
48
48
|
</Surface>
|
|
49
49
|
)
|
|
50
50
|
}
|
|
51
|
-
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import React, { useMemo } from 'react'
|
|
2
2
|
import { Box, Text } from 'ink'
|
|
3
|
-
import { Surface } from '
|
|
4
|
-
import { Select } from '
|
|
5
|
-
import { theme } from '
|
|
6
|
-
import
|
|
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 }
|
|
7
10
|
|
|
8
11
|
type PermissionPromptProps = {
|
|
9
12
|
request: PermissionRequest
|
|
@@ -34,7 +37,7 @@ export const PermissionPrompt: React.FC<PermissionPromptProps> = ({ request, onD
|
|
|
34
37
|
<Box marginTop={1}>
|
|
35
38
|
<Text color={theme.accentPeriwinkle}>diff</Text>
|
|
36
39
|
</Box>
|
|
37
|
-
<
|
|
40
|
+
<DiffView diff={request.diff} />
|
|
38
41
|
</Box>
|
|
39
42
|
) : null}
|
|
40
43
|
{request.kind === 'private-continuity-read' ? (
|
|
@@ -51,13 +54,9 @@ export const PermissionPrompt: React.FC<PermissionPromptProps> = ({ request, onD
|
|
|
51
54
|
<Box flexDirection="column" marginBottom={1}>
|
|
52
55
|
<Text color={theme.accentPeriwinkle}>{displayPermissionText(request.changeSummary)}</Text>
|
|
53
56
|
<Box marginTop={1}>
|
|
54
|
-
<Text color={theme.
|
|
55
|
-
</Box>
|
|
56
|
-
<Text color={theme.textSubtle}>{request.before || '(empty)'}</Text>
|
|
57
|
-
<Box marginTop={1}>
|
|
58
|
-
<Text color={theme.accentPeriwinkle}>after</Text>
|
|
57
|
+
<Text color={theme.accentPeriwinkle}>diff</Text>
|
|
59
58
|
</Box>
|
|
60
|
-
<
|
|
59
|
+
<DiffView diff={request.diff} />
|
|
61
60
|
</Box>
|
|
62
61
|
) : null}
|
|
63
62
|
{request.kind === 'bash' && request.warning ? (
|
|
@@ -73,20 +72,20 @@ export const PermissionPrompt: React.FC<PermissionPromptProps> = ({ request, onD
|
|
|
73
72
|
export function permissionOptionsForRequest(request: PermissionRequest): Array<{ value: PermissionDecision; label: string; hint?: string; disabled?: boolean }> {
|
|
74
73
|
if (request.kind === 'bash') {
|
|
75
74
|
return [
|
|
76
|
-
{ value: 'allow-once', label: '
|
|
75
|
+
{ value: 'allow-once', label: 'Allow Once', hint: 'Approve only this command execution' },
|
|
77
76
|
{
|
|
78
77
|
value: 'allow-command-project',
|
|
79
|
-
label: '
|
|
80
|
-
hint: '
|
|
78
|
+
label: 'Allow Exact Command',
|
|
79
|
+
hint: 'Remember this exact command for this project',
|
|
81
80
|
disabled: !request.canPersistExact,
|
|
82
81
|
},
|
|
83
82
|
{
|
|
84
83
|
value: 'allow-command-prefix-project',
|
|
85
|
-
label: request.commandPrefix ? `
|
|
86
|
-
hint: '
|
|
84
|
+
label: request.commandPrefix ? `Allow ${request.commandPrefix} Commands` : 'Allow Command Family',
|
|
85
|
+
hint: 'Remember this base command in this working directory for this project',
|
|
87
86
|
disabled: !request.canPersistPrefix,
|
|
88
87
|
},
|
|
89
|
-
{ value: 'deny', label: '
|
|
88
|
+
{ value: 'deny', label: 'Deny', hint: 'Return a denial to the model' },
|
|
90
89
|
]
|
|
91
90
|
}
|
|
92
91
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useState } from 'react'
|
|
2
2
|
import { Box, Text } from 'ink'
|
|
3
|
-
import { clearPermissionRules, deletePermissionRule, loadPermissionRules } from '
|
|
4
|
-
import type { SessionPermissionRule } from '
|
|
5
|
-
import { Select, type SelectOption } from '
|
|
6
|
-
import { Spinner } from '
|
|
7
|
-
import { Surface } from '
|
|
8
|
-
import { theme } from '
|
|
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
9
|
|
|
10
10
|
type PermissionsViewProps = {
|
|
11
11
|
cwd: string
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { Box, Text } from 'ink'
|
|
3
|
-
import { Surface } from '
|
|
4
|
-
import { theme } from '
|
|
5
|
-
import { useAppInput } from '
|
|
3
|
+
import { Surface } from '../../ui/Surface.js'
|
|
4
|
+
import { theme } from '../../ui/theme.js'
|
|
5
|
+
import { useAppInput } from '../../app/input/AppInputProvider.js'
|
|
6
6
|
|
|
7
7
|
export type PlanApprovalAction = 'apply' | 'apply-summary' | 'continue'
|
|
8
8
|
|
|
@@ -32,7 +32,7 @@ export const PLAN_APPROVAL_OPTIONS: Array<{
|
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
value: 'continue',
|
|
35
|
-
label: 'No, stay in
|
|
35
|
+
label: 'No, stay in plan mode',
|
|
36
36
|
title: 'Continue planning with the model.',
|
|
37
37
|
detail: () => 'No files will be changed.',
|
|
38
38
|
},
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react'
|
|
2
2
|
import { Box, Text } from 'ink'
|
|
3
|
-
import { theme } from '
|
|
4
|
-
import { Select, type SelectOption } from '
|
|
5
|
-
import { Spinner } from '
|
|
6
|
-
import { Surface } from '
|
|
7
|
-
import { listSessions, type SessionSummary } from '
|
|
8
|
-
import { useAppInput } from '
|
|
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
9
|
|
|
10
10
|
type ResumeViewProps = {
|
|
11
11
|
currentSessionId: string
|
|
@@ -114,11 +114,11 @@ export const ResumeView: React.FC<ResumeViewProps> = ({ currentSessionId, onResu
|
|
|
114
114
|
return (
|
|
115
115
|
<Surface
|
|
116
116
|
title="Resume Session"
|
|
117
|
-
subtitle="Grouped by
|
|
117
|
+
subtitle="Grouped by working directory."
|
|
118
118
|
footer="enter resumes · esc closes"
|
|
119
119
|
>
|
|
120
120
|
<Box flexDirection="column" marginBottom={1}>
|
|
121
|
-
<Text color={theme.dim}>Recent
|
|
121
|
+
<Text color={theme.dim}>Recent directories</Text>
|
|
122
122
|
</Box>
|
|
123
123
|
<Select
|
|
124
124
|
options={options}
|
|
@@ -143,7 +143,7 @@ export function buildResumeOptions(
|
|
|
143
143
|
): Array<SelectOption<string>> {
|
|
144
144
|
const groups = new Map<string, SessionSummary[]>()
|
|
145
145
|
for (const session of sessions) {
|
|
146
|
-
const key = session.projectRoot
|
|
146
|
+
const key = session.lastCwd || session.workspaceRoot || session.projectRoot
|
|
147
147
|
const existing = groups.get(key) ?? []
|
|
148
148
|
existing.push(session)
|
|
149
149
|
groups.set(key, existing)
|
|
@@ -158,36 +158,26 @@ export function buildResumeOptions(
|
|
|
158
158
|
|
|
159
159
|
const clearOption: SelectOption<string> = {
|
|
160
160
|
value: CLEAR_ALL_SESSIONS_VALUE,
|
|
161
|
-
label: '
|
|
161
|
+
label: 'Clear All Chat Logs',
|
|
162
162
|
hint: 'removes saved chats and resume context',
|
|
163
163
|
role: 'utility',
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
const orderedGroups = [...groups.
|
|
166
|
+
const orderedGroups = [...groups.entries()].sort(([, left], [, right]) => right[0]!.mtimeMs - left[0]!.mtimeMs)
|
|
167
167
|
|
|
168
|
-
for (const group of orderedGroups) {
|
|
169
|
-
const
|
|
168
|
+
for (const [directoryKey, group] of orderedGroups) {
|
|
169
|
+
const sorted = [...group].sort((left, right) => right.mtimeMs - left.mtimeMs)
|
|
170
|
+
const sessionCount = sorted.length
|
|
171
|
+
const lastActivity = formatRelative(sorted[0]!.mtimeMs)
|
|
170
172
|
options.push({
|
|
171
|
-
value: `
|
|
172
|
-
label:
|
|
173
|
-
hint: compressProjectPath(
|
|
174
|
-
|
|
173
|
+
value: `directory:${directoryKey}`,
|
|
174
|
+
label: lastPathSegment(directoryKey) || compressProjectPath(directoryKey),
|
|
175
|
+
hint: `${compressProjectPath(directoryKey)} · ${sessionCount} session${sessionCount === 1 ? '' : 's'} · last ${lastActivity}`,
|
|
176
|
+
role: 'section',
|
|
177
|
+
bold: true,
|
|
175
178
|
})
|
|
176
179
|
|
|
177
|
-
const
|
|
178
|
-
let lastDirectoryLabel: string | null = null
|
|
179
|
-
|
|
180
|
-
for (const session of byDirectory) {
|
|
181
|
-
if (session.directoryLabel !== lastDirectoryLabel) {
|
|
182
|
-
lastDirectoryLabel = session.directoryLabel
|
|
183
|
-
options.push({
|
|
184
|
-
value: `directory:${head.projectRoot}:${session.directoryLabel}`,
|
|
185
|
-
label: `in ${formatDirectoryDisplay(session.directoryLabel)}`,
|
|
186
|
-
hint: undefined,
|
|
187
|
-
disabled: true,
|
|
188
|
-
})
|
|
189
|
-
}
|
|
190
|
-
|
|
180
|
+
for (const session of sorted) {
|
|
191
181
|
const baseLabel = formatFirstLine(session.firstUserMessage) || '(empty session)'
|
|
192
182
|
const markers = [
|
|
193
183
|
session.id === currentSessionId ? 'current' : '',
|
|
@@ -206,6 +196,7 @@ export function buildResumeOptions(
|
|
|
206
196
|
value: session.id,
|
|
207
197
|
label,
|
|
208
198
|
hint: hintParts.join(' · '),
|
|
199
|
+
indent: 2,
|
|
209
200
|
})
|
|
210
201
|
}
|
|
211
202
|
}
|
|
@@ -219,7 +210,15 @@ export function buildResumeOptions(
|
|
|
219
210
|
function findInitialIndex(options: Array<SelectOption<string>>, currentSessionId: string): number {
|
|
220
211
|
const currentIndex = options.findIndex(option => option.value === currentSessionId)
|
|
221
212
|
if (currentIndex >= 0) return currentIndex
|
|
222
|
-
return Math.max(
|
|
213
|
+
return Math.max(
|
|
214
|
+
0,
|
|
215
|
+
options.findIndex(option =>
|
|
216
|
+
!option.disabled
|
|
217
|
+
&& option.role !== 'section'
|
|
218
|
+
&& option.role !== 'group'
|
|
219
|
+
&& option.value !== CLEAR_ALL_SESSIONS_VALUE,
|
|
220
|
+
),
|
|
221
|
+
)
|
|
223
222
|
}
|
|
224
223
|
|
|
225
224
|
async function clearAll(
|
|
@@ -240,9 +239,10 @@ function compressProjectPath(input: string): string {
|
|
|
240
239
|
return home && input.startsWith(home) ? `~${input.slice(home.length)}` : input
|
|
241
240
|
}
|
|
242
241
|
|
|
243
|
-
function
|
|
244
|
-
|
|
245
|
-
|
|
242
|
+
function lastPathSegment(input: string): string {
|
|
243
|
+
const trimmed = input.replace(/[\\/]+$/, '')
|
|
244
|
+
const slash = Math.max(trimmed.lastIndexOf('/'), trimmed.lastIndexOf('\\'))
|
|
245
|
+
return slash >= 0 ? trimmed.slice(slash + 1) : trimmed
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
function formatFirstLine(text: string): string {
|