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,115 @@
|
|
|
1
|
+
export const OPENAI_OAUTH_ISSUER = 'https://auth.openai.com'
|
|
2
|
+
export const OPENAI_OAUTH_TOKEN_URL = `${OPENAI_OAUTH_ISSUER}/oauth/token`
|
|
3
|
+
export const OPENAI_OAUTH_CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann'
|
|
4
|
+
export const OPENAI_OAUTH_CALLBACK_PORT = 1455
|
|
5
|
+
export const OPENAI_OAUTH_SCOPE =
|
|
6
|
+
'openid profile email offline_access api.connectors.read api.connectors.invoke'
|
|
7
|
+
export const OPENAI_OAUTH_ORIGINATOR = 'codex_cli_rs'
|
|
8
|
+
export const OPENAI_API_KEY_TOKEN_NAME = 'openai-api-key'
|
|
9
|
+
export const OPENAI_ID_TOKEN_SUBJECT_TYPE =
|
|
10
|
+
'urn:ietf:params:oauth:token-type:id_token'
|
|
11
|
+
export const OPENAI_TOKEN_EXCHANGE_GRANT =
|
|
12
|
+
'urn:ietf:params:oauth:grant-type:token-exchange'
|
|
13
|
+
|
|
14
|
+
export function asTrimmedString(value: unknown): string | undefined {
|
|
15
|
+
if (typeof value !== 'string') return undefined
|
|
16
|
+
const trimmed = value.trim()
|
|
17
|
+
return trimmed ? trimmed : undefined
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function decodeJwtPayload(
|
|
21
|
+
token: string,
|
|
22
|
+
): Record<string, unknown> | undefined {
|
|
23
|
+
const parts = token.split('.')
|
|
24
|
+
if (parts.length < 2) return undefined
|
|
25
|
+
const segment = parts[1]
|
|
26
|
+
if (!segment) return undefined
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const normalized = segment.replace(/-/g, '+').replace(/_/g, '/')
|
|
30
|
+
const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4)
|
|
31
|
+
const json = Buffer.from(padded, 'base64').toString('utf8')
|
|
32
|
+
const parsed = JSON.parse(json) as unknown
|
|
33
|
+
return parsed && typeof parsed === 'object'
|
|
34
|
+
? (parsed as Record<string, unknown>)
|
|
35
|
+
: undefined
|
|
36
|
+
} catch {
|
|
37
|
+
return undefined
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function parseChatgptAccountId(
|
|
42
|
+
token: string | undefined,
|
|
43
|
+
): string | undefined {
|
|
44
|
+
if (!token) return undefined
|
|
45
|
+
|
|
46
|
+
const payload = decodeJwtPayload(token)
|
|
47
|
+
const nestedAuthRaw = payload?.['https://api.openai.com/auth']
|
|
48
|
+
const nestedAuth =
|
|
49
|
+
nestedAuthRaw && typeof nestedAuthRaw === 'object'
|
|
50
|
+
? (nestedAuthRaw as Record<string, unknown>)
|
|
51
|
+
: undefined
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
asTrimmedString(
|
|
55
|
+
nestedAuth?.chatgpt_account_id ??
|
|
56
|
+
payload?.['https://api.openai.com/auth.chatgpt_account_id'] ??
|
|
57
|
+
payload?.chatgpt_account_id,
|
|
58
|
+
) ?? undefined
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function escapeHtml(value: string): string {
|
|
63
|
+
return value.replace(/[&<>"']/g, char => {
|
|
64
|
+
switch (char) {
|
|
65
|
+
case '&':
|
|
66
|
+
return '&'
|
|
67
|
+
case '<':
|
|
68
|
+
return '<'
|
|
69
|
+
case '>':
|
|
70
|
+
return '>'
|
|
71
|
+
case '"':
|
|
72
|
+
return '"'
|
|
73
|
+
case '\'':
|
|
74
|
+
return '''
|
|
75
|
+
default:
|
|
76
|
+
return char
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function exchangeIdTokenForApiKey(idToken: string): Promise<string> {
|
|
82
|
+
const body = new URLSearchParams({
|
|
83
|
+
grant_type: OPENAI_TOKEN_EXCHANGE_GRANT,
|
|
84
|
+
client_id: OPENAI_OAUTH_CLIENT_ID,
|
|
85
|
+
requested_token: OPENAI_API_KEY_TOKEN_NAME,
|
|
86
|
+
subject_token: idToken,
|
|
87
|
+
subject_token_type: OPENAI_ID_TOKEN_SUBJECT_TYPE,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const response = await fetch(OPENAI_OAUTH_TOKEN_URL, {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
93
|
+
body,
|
|
94
|
+
signal: AbortSignal.timeout(15_000),
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
const bodyText = await response.text().catch(() => '')
|
|
99
|
+
throw new Error(
|
|
100
|
+
bodyText.trim()
|
|
101
|
+
? `OpenAI API key exchange failed (${response.status}): ${bodyText.trim()}`
|
|
102
|
+
: `OpenAI API key exchange failed with status ${response.status}.`,
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const payload = (await response.json()) as { access_token?: string }
|
|
107
|
+
const apiKey = asTrimmedString(payload.access_token)
|
|
108
|
+
if (!apiKey) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
'OpenAI API key exchange completed, but no key was returned.',
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return apiKey
|
|
115
|
+
}
|
|
@@ -3,26 +3,26 @@ import type { EthagentConfig } from '../storage/config.js'
|
|
|
3
3
|
import type { PermissionDecision, PermissionRequest, SessionPermissionRule } from '../tools/contracts.js'
|
|
4
4
|
import { type ModelPickerSelection, ModelPicker } from '../models/ModelPicker.js'
|
|
5
5
|
import type { ModelPickerContextFit } from '../models/modelPickerOptions.js'
|
|
6
|
-
import { ResumeView } from './ResumeView.js'
|
|
7
|
-
import { RewindView } from './RewindView.js'
|
|
8
|
-
import { PermissionsView } from './PermissionsView.js'
|
|
9
|
-
import { CopyPicker } from './CopyPicker.js'
|
|
10
|
-
import { PermissionPrompt } from './PermissionPrompt.js'
|
|
11
|
-
import { PlanApprovalView, type PlanApprovalAction } from './PlanApprovalView.js'
|
|
12
|
-
import { ChatInput } from './ChatInput.js'
|
|
6
|
+
import { ResumeView } from './views/ResumeView.js'
|
|
7
|
+
import { RewindView } from './views/RewindView.js'
|
|
8
|
+
import { PermissionsView } from './views/PermissionsView.js'
|
|
9
|
+
import { CopyPicker } from './views/CopyPicker.js'
|
|
10
|
+
import { PermissionPrompt } from './views/PermissionPrompt.js'
|
|
11
|
+
import { PlanApprovalView, type PlanApprovalAction } from './views/PlanApprovalView.js'
|
|
12
|
+
import { ChatInput } from './input/ChatInput.js'
|
|
13
13
|
import { IdentityHub, type IdentityHubInitialAction, type IdentityHubResult } from '../identity/hub/IdentityHub.js'
|
|
14
14
|
import type { CopyResult } from '../utils/clipboard.js'
|
|
15
15
|
import { getSlashSuggestions } from './commands.js'
|
|
16
16
|
import { Box, Text } from 'ink'
|
|
17
17
|
import { theme } from '../ui/theme.js'
|
|
18
18
|
import { Spinner } from '../ui/Spinner.js'
|
|
19
|
-
import { ContextLimitView, type ContextLimitAction } from './ContextLimitView.js'
|
|
19
|
+
import { ContextLimitView, type ContextLimitAction } from './views/ContextLimitView.js'
|
|
20
20
|
import type { ContextUsage } from '../runtime/compaction.js'
|
|
21
21
|
import {
|
|
22
22
|
ContinuityEditReviewView,
|
|
23
23
|
type ContinuityEditReviewAction,
|
|
24
24
|
type ContinuityEditReviewState,
|
|
25
|
-
} from './ContinuityEditReviewView.js'
|
|
25
|
+
} from './views/ContinuityEditReviewView.js'
|
|
26
26
|
|
|
27
27
|
export type Overlay = 'none' | 'modelPicker' | 'resume' | 'rewind' | 'copyPicker' | 'permission' | 'permissions' | 'planApproval' | 'identity' | 'contextLimit' | 'continuityEditReview'
|
|
28
28
|
export type CopyPickerState = { turnText: string; turnLabel: string } | null
|
|
@@ -66,7 +66,10 @@ type ChatBottomPaneProps = {
|
|
|
66
66
|
handleResumeClearAll: () => void | Promise<void>
|
|
67
67
|
identityOverlay: IdentityOverlayState | null
|
|
68
68
|
handleIdentityResult: (result: IdentityHubResult) => void
|
|
69
|
-
handleRestoreConversation: (turnId: string) => void
|
|
69
|
+
handleRestoreConversation: (turnId: string, promptText?: string) => void
|
|
70
|
+
pendingInputDraft: string | null
|
|
71
|
+
onInputDraftConsumed: () => void
|
|
72
|
+
handleSummarizeFromTurn: (turnId: string) => void | Promise<unknown>
|
|
70
73
|
handleCopyDone: (result: CopyResult, label: string) => void
|
|
71
74
|
handleCopyCancel: () => void
|
|
72
75
|
resolvePermission: (decision: PermissionDecision) => void
|
|
@@ -110,6 +113,9 @@ export function ChatBottomPane({
|
|
|
110
113
|
identityOverlay,
|
|
111
114
|
handleIdentityResult,
|
|
112
115
|
handleRestoreConversation,
|
|
116
|
+
handleSummarizeFromTurn,
|
|
117
|
+
pendingInputDraft,
|
|
118
|
+
onInputDraftConsumed,
|
|
113
119
|
handleCopyDone,
|
|
114
120
|
handleCopyCancel,
|
|
115
121
|
resolvePermission,
|
|
@@ -155,6 +161,7 @@ export function ChatBottomPane({
|
|
|
155
161
|
cwd={cwd}
|
|
156
162
|
currentSessionId={currentSessionId}
|
|
157
163
|
onRestoreConversation={handleRestoreConversation}
|
|
164
|
+
onSummarizeFromTurn={handleSummarizeFromTurn}
|
|
158
165
|
onDone={(message, variant = 'info') => {
|
|
159
166
|
setOverlay('none')
|
|
160
167
|
pushNote(message, variant)
|
|
@@ -261,10 +268,12 @@ export function ChatBottomPane({
|
|
|
261
268
|
slashSuggestions={slashSuggestions}
|
|
262
269
|
footerRight={footerRight}
|
|
263
270
|
cwd={cwd}
|
|
271
|
+
seedText={pendingInputDraft}
|
|
272
|
+
onSeedConsumed={onInputDraftConsumed}
|
|
264
273
|
/>
|
|
265
274
|
<Box marginLeft={2} marginTop={0} flexDirection="column">
|
|
266
275
|
<Text>
|
|
267
|
-
<Text color={theme.dim}>
|
|
276
|
+
<Text color={theme.dim}>Workspace · </Text>
|
|
268
277
|
<Text color={theme.textSubtle}>{cwd}</Text>
|
|
269
278
|
</Text>
|
|
270
279
|
</Box>
|
package/src/chat/ChatScreen.tsx
CHANGED
|
@@ -16,6 +16,7 @@ import { theme } from '../ui/theme.js'
|
|
|
16
16
|
import { BrandSplash } from '../ui/BrandSplash.js'
|
|
17
17
|
import { SessionStatus, formatTokens } from './SessionStatus.js'
|
|
18
18
|
import { formatModelDisplayName } from '../models/modelDisplay.js'
|
|
19
|
+
import { providerDisplayName } from '../models/providerDisplay.js'
|
|
19
20
|
import { toggleReasoningRow, type MessageRow } from './MessageList.js'
|
|
20
21
|
import { ConversationStack } from './ConversationStack.js'
|
|
21
22
|
import type { ModelPickerSelection } from '../models/ModelPicker.js'
|
|
@@ -53,6 +54,7 @@ import type {
|
|
|
53
54
|
PermissionRequest,
|
|
54
55
|
SessionPermissionRule,
|
|
55
56
|
} from '../tools/contracts.js'
|
|
57
|
+
import { splitFileChangeResult } from '../tools/fileDiff.js'
|
|
56
58
|
import {
|
|
57
59
|
buildBaseMessages,
|
|
58
60
|
sessionMessagesToRows,
|
|
@@ -63,7 +65,7 @@ import { setTokenIdentity, getIdentityStatus } from '../storage/identity.js'
|
|
|
63
65
|
import type { IdentityHubResult } from '../identity/hub/IdentityHub.js'
|
|
64
66
|
import { continuityWorkingTreeStatus } from '../identity/continuity/storage.js'
|
|
65
67
|
import { listPublishedContinuitySnapshots } from '../identity/continuity/snapshots.js'
|
|
66
|
-
import { localChangeStatusView } from '../identity/hub/
|
|
68
|
+
import { localChangeStatusView } from '../identity/hub/continuity/state.js'
|
|
67
69
|
import {
|
|
68
70
|
buildResumedSessionState,
|
|
69
71
|
promptHistoryFromSessionMessages,
|
|
@@ -72,9 +74,9 @@ import {
|
|
|
72
74
|
} from './chatSessionState.js'
|
|
73
75
|
import { runStreamingTurn } from './chatTurnOrchestrator.js'
|
|
74
76
|
import { ensureLlamaCppRunnerReady } from '../models/llamacppPreflight.js'
|
|
75
|
-
import type { PlanApprovalAction } from './PlanApprovalView.js'
|
|
76
|
-
import type { ContextLimitAction } from './ContextLimitView.js'
|
|
77
|
-
import type { ContinuityEditReviewAction, ContinuityEditReviewState } from './ContinuityEditReviewView.js'
|
|
77
|
+
import type { PlanApprovalAction } from './views/PlanApprovalView.js'
|
|
78
|
+
import type { ContextLimitAction } from './views/ContextLimitView.js'
|
|
79
|
+
import type { ContinuityEditReviewAction, ContinuityEditReviewState } from './views/ContinuityEditReviewView.js'
|
|
78
80
|
import { openFileInEditor } from '../identity/continuity/editor.js'
|
|
79
81
|
import { EMPTY_MCP_SNAPSHOT, McpManager, type McpSnapshot } from '../mcp/manager.js'
|
|
80
82
|
|
|
@@ -154,6 +156,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
154
156
|
contextUsageFromTokens(0, initialConfig.provider, initialConfig.model),
|
|
155
157
|
)
|
|
156
158
|
const [mcpSnapshot, setMcpSnapshot] = useState<McpSnapshot>(EMPTY_MCP_SNAPSHOT)
|
|
159
|
+
const [pendingInputDraft, setPendingInputDraft] = useState<string | null>(null)
|
|
157
160
|
|
|
158
161
|
const rowsRef = useRef<MessageRow[]>([])
|
|
159
162
|
const visibleReasoningIdsRef = useRef<string[]>([])
|
|
@@ -380,7 +383,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
380
383
|
setModelPickerContextFit(null)
|
|
381
384
|
overlayRef.current = 'none'
|
|
382
385
|
setOverlay('none')
|
|
383
|
-
if (hadPendingPrompt) pushNote('
|
|
386
|
+
if (hadPendingPrompt) pushNote('Pending message cancelled.', 'dim')
|
|
384
387
|
}, [pushNote])
|
|
385
388
|
|
|
386
389
|
const changeCwd = useCallback((next: string) => {
|
|
@@ -489,7 +492,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
489
492
|
modeRef.current,
|
|
490
493
|
)
|
|
491
494
|
if (priorMessages.length <= 5) {
|
|
492
|
-
pushNote('
|
|
495
|
+
pushNote('Not enough turns to compact yet.', 'dim')
|
|
493
496
|
return false
|
|
494
497
|
}
|
|
495
498
|
compactingRef.current = true
|
|
@@ -501,14 +504,14 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
501
504
|
})
|
|
502
505
|
if (!result.ok && result.cancelled) {
|
|
503
506
|
removeCompactionProgress(compaction)
|
|
504
|
-
pushNote('
|
|
507
|
+
pushNote('Compaction cancelled.', 'dim')
|
|
505
508
|
return false
|
|
506
509
|
}
|
|
507
510
|
const summary = result.ok
|
|
508
511
|
? normalizeHandoffSummary(result.summary)
|
|
509
512
|
: normalizeHandoffSummary(summarizeTranscriptLocally(priorMessages, result.reason))
|
|
510
513
|
if (!result.ok) {
|
|
511
|
-
pushNote(`
|
|
514
|
+
pushNote(`Provider summary failed; created a local summary instead: ${result.reason}`, 'dim')
|
|
512
515
|
}
|
|
513
516
|
|
|
514
517
|
updateCompactionStage(compaction, 'saving summarized conversation')
|
|
@@ -573,9 +576,123 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
573
576
|
} catch (err: unknown) {
|
|
574
577
|
removeCompactionProgress(compaction)
|
|
575
578
|
if (compaction.controller.signal.aborted) {
|
|
576
|
-
pushNote('
|
|
579
|
+
pushNote('Compaction cancelled.', 'dim')
|
|
577
580
|
} else {
|
|
578
|
-
pushNote(`
|
|
581
|
+
pushNote(`Compact error: ${(err as Error).message}`, 'error')
|
|
582
|
+
}
|
|
583
|
+
return false
|
|
584
|
+
} finally {
|
|
585
|
+
compactingRef.current = false
|
|
586
|
+
compactionUiRef.current = null
|
|
587
|
+
setCompactionUi(null)
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
[beginCompactionUi, pushNote, refreshVisibleStats, removeCompactionProgress, updateCompactionStage],
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
const runCompactionFromTurn = useCallback(
|
|
594
|
+
async (turnId: string): Promise<boolean> => {
|
|
595
|
+
if (compactingRef.current) return false
|
|
596
|
+
const sourceSessionId = sessionIdRef.current
|
|
597
|
+
const all = sessionMessagesRef.current
|
|
598
|
+
const splitIndex = all.findIndex(m => m.turnId === turnId)
|
|
599
|
+
if (splitIndex < 0) {
|
|
600
|
+
pushNote('Could not find that prompt to summarize from.', 'error')
|
|
601
|
+
return false
|
|
602
|
+
}
|
|
603
|
+
const before = all.slice(0, splitIndex)
|
|
604
|
+
const from = all.slice(splitIndex)
|
|
605
|
+
const fromBase = buildBaseMessages(
|
|
606
|
+
from,
|
|
607
|
+
configRef.current,
|
|
608
|
+
providerRef.current.supportsTools,
|
|
609
|
+
cwdRef.current,
|
|
610
|
+
modeRef.current,
|
|
611
|
+
)
|
|
612
|
+
if (fromBase.length <= 2) {
|
|
613
|
+
pushNote('Not enough messages from that point to summarize.', 'dim')
|
|
614
|
+
return false
|
|
615
|
+
}
|
|
616
|
+
compactingRef.current = true
|
|
617
|
+
const compaction = beginCompactionUi('conversation', sourceSessionId)
|
|
618
|
+
try {
|
|
619
|
+
const result = await compactTranscript(providerRef.current, fromBase, {
|
|
620
|
+
signal: compaction.controller.signal,
|
|
621
|
+
onStage: stage => updateCompactionStage(compaction, stage),
|
|
622
|
+
})
|
|
623
|
+
if (!result.ok && result.cancelled) {
|
|
624
|
+
removeCompactionProgress(compaction)
|
|
625
|
+
pushNote('Compaction cancelled.', 'dim')
|
|
626
|
+
return false
|
|
627
|
+
}
|
|
628
|
+
const summary = result.ok
|
|
629
|
+
? normalizeHandoffSummary(result.summary)
|
|
630
|
+
: normalizeHandoffSummary(summarizeTranscriptLocally(fromBase, result.reason))
|
|
631
|
+
if (!result.ok) {
|
|
632
|
+
pushNote(`Provider summary failed; created a local summary instead: ${result.reason}`, 'dim')
|
|
633
|
+
}
|
|
634
|
+
updateCompactionStage(compaction, 'saving summarized conversation')
|
|
635
|
+
const nextSessionId = newSessionId()
|
|
636
|
+
const summaryMessage: SessionMessage = {
|
|
637
|
+
role: 'user',
|
|
638
|
+
synthetic: true,
|
|
639
|
+
content: [
|
|
640
|
+
`Conversation handoff from ${sourceSessionId.slice(0, 8)} (summarized from a chosen prompt forward):`,
|
|
641
|
+
'',
|
|
642
|
+
summary,
|
|
643
|
+
].join('\n'),
|
|
644
|
+
createdAt: nowIso(),
|
|
645
|
+
}
|
|
646
|
+
const acknowledgement: SessionMessage = {
|
|
647
|
+
role: 'assistant',
|
|
648
|
+
content: 'Ready to continue from this summary.',
|
|
649
|
+
createdAt: nowIso(),
|
|
650
|
+
model: configRef.current.model,
|
|
651
|
+
}
|
|
652
|
+
const context = {
|
|
653
|
+
cwd: cwdRef.current,
|
|
654
|
+
provider: configRef.current.provider,
|
|
655
|
+
model: configRef.current.model,
|
|
656
|
+
mode: modeRef.current,
|
|
657
|
+
}
|
|
658
|
+
await ensureSessionMetadata(nextSessionId, context)
|
|
659
|
+
await updateSessionActivity(nextSessionId, context, { compactedFromSessionId: sourceSessionId })
|
|
660
|
+
for (const msg of before) {
|
|
661
|
+
await appendSessionMessage(nextSessionId, msg, context)
|
|
662
|
+
}
|
|
663
|
+
await appendSessionMessage(nextSessionId, summaryMessage, context)
|
|
664
|
+
await appendSessionMessage(nextSessionId, acknowledgement, context)
|
|
665
|
+
|
|
666
|
+
updateCompactionStage(compaction, 'opening summarized conversation')
|
|
667
|
+
const nextMessages: SessionMessage[] = [...before, summaryMessage, acknowledgement]
|
|
668
|
+
compactionUiRef.current = null
|
|
669
|
+
setCompactionUi(null)
|
|
670
|
+
sessionIdRef.current = nextSessionId
|
|
671
|
+
setSessionId(nextSessionId)
|
|
672
|
+
sessionMessagesRef.current = nextMessages
|
|
673
|
+
historyScopeRef.current = 'session'
|
|
674
|
+
setHistory(promptHistoryFromSessionMessages(nextMessages))
|
|
675
|
+
statsSegmentStartRef.current = 0
|
|
676
|
+
setRows([
|
|
677
|
+
{
|
|
678
|
+
role: 'note',
|
|
679
|
+
id: nextRowId(),
|
|
680
|
+
kind: 'dim',
|
|
681
|
+
content: `kept ${sourceSessionId.slice(0, 8)} saved; summarized from a chosen prompt into ${nextSessionId.slice(0, 8)}.`,
|
|
682
|
+
},
|
|
683
|
+
...sessionMessagesToRows(nextMessages, nextRowId),
|
|
684
|
+
])
|
|
685
|
+
setQueuedInputs([])
|
|
686
|
+
setStatusStartedAt(Date.now())
|
|
687
|
+
refreshVisibleStats(nextMessages, providerRef.current.supportsTools, cwdRef.current, configRef.current, modeRef.current)
|
|
688
|
+
setSessionKey(key => key + 1)
|
|
689
|
+
return true
|
|
690
|
+
} catch (err: unknown) {
|
|
691
|
+
removeCompactionProgress(compaction)
|
|
692
|
+
if (compaction.controller.signal.aborted) {
|
|
693
|
+
pushNote('Compaction cancelled.', 'dim')
|
|
694
|
+
} else {
|
|
695
|
+
pushNote(`Compact error: ${(err as Error).message}`, 'error')
|
|
579
696
|
}
|
|
580
697
|
return false
|
|
581
698
|
} finally {
|
|
@@ -707,7 +824,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
707
824
|
try {
|
|
708
825
|
await savePermissionRule(cwdRef.current, sessionRule)
|
|
709
826
|
} catch (error: unknown) {
|
|
710
|
-
pushNote(`
|
|
827
|
+
pushNote(`Failed to save permission rule: ${(error as Error).message}`, 'error')
|
|
711
828
|
}
|
|
712
829
|
},
|
|
713
830
|
[pushNote],
|
|
@@ -855,7 +972,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
855
972
|
|
|
856
973
|
if (streaming || pullInFlight || compactionUiRef.current) {
|
|
857
974
|
if (parseSlash(value)) {
|
|
858
|
-
pushNote('
|
|
975
|
+
pushNote('Slash commands cannot be queued. Wait for the current task to finish.', 'dim')
|
|
859
976
|
return
|
|
860
977
|
}
|
|
861
978
|
setQueuedInputs(prev => [...prev, value])
|
|
@@ -892,7 +1009,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
892
1009
|
|
|
893
1010
|
const handleContextLimitCancel = useCallback(() => {
|
|
894
1011
|
clearContextLimit()
|
|
895
|
-
pushNote('
|
|
1012
|
+
pushNote('Pending message cancelled.', 'dim')
|
|
896
1013
|
}, [clearContextLimit, pushNote])
|
|
897
1014
|
|
|
898
1015
|
const handleContextLimitAction = useCallback(
|
|
@@ -905,7 +1022,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
905
1022
|
const prompt = state.prompt
|
|
906
1023
|
clearContextLimit()
|
|
907
1024
|
if (action === 'cancel') {
|
|
908
|
-
pushNote('
|
|
1025
|
+
pushNote('Pending message cancelled.', 'dim')
|
|
909
1026
|
return
|
|
910
1027
|
}
|
|
911
1028
|
if (action === 'switchModel') {
|
|
@@ -1020,7 +1137,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
1020
1137
|
pushNote(resolution.notice, resolution.tone)
|
|
1021
1138
|
await continuePendingPromptAfterModelSwitch(pendingPrompt)
|
|
1022
1139
|
} catch (err: unknown) {
|
|
1023
|
-
pushNote(`
|
|
1140
|
+
pushNote(`Provider switch failed: ${(err as Error).message}`, 'error')
|
|
1024
1141
|
if (pendingPrompt) showContextLimitForPrompt(pendingPrompt)
|
|
1025
1142
|
}
|
|
1026
1143
|
},
|
|
@@ -1033,7 +1150,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
1033
1150
|
try {
|
|
1034
1151
|
const [loaded, metadata] = await Promise.all([loadSession(id), loadSessionMetadata(id)])
|
|
1035
1152
|
if (loaded.length === 0) {
|
|
1036
|
-
pushNote('
|
|
1153
|
+
pushNote('Session was empty.', 'error')
|
|
1037
1154
|
return
|
|
1038
1155
|
}
|
|
1039
1156
|
const resumed = buildResumedSessionState({
|
|
@@ -1068,7 +1185,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
1068
1185
|
refreshVisibleStats(loaded, providerRef.current.supportsTools, resumedCwd, configRef.current, resumed.mode)
|
|
1069
1186
|
setSessionKey(k => k + 1)
|
|
1070
1187
|
} catch (err: unknown) {
|
|
1071
|
-
pushNote(`
|
|
1188
|
+
pushNote(`Resume failed: ${(err as Error).message}`, 'error')
|
|
1072
1189
|
}
|
|
1073
1190
|
},
|
|
1074
1191
|
[clearContextLimit, clearPendingPlan, cwd, pushNote, refreshVisibleStats],
|
|
@@ -1080,7 +1197,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
1080
1197
|
clearTranscript()
|
|
1081
1198
|
overlayRef.current = 'none'
|
|
1082
1199
|
setOverlay('none')
|
|
1083
|
-
pushNote('
|
|
1200
|
+
pushNote('Cleared saved chat logs and resume context from this machine.', 'dim')
|
|
1084
1201
|
},
|
|
1085
1202
|
[clearTranscript, pushNote],
|
|
1086
1203
|
)
|
|
@@ -1099,9 +1216,9 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
1099
1216
|
try {
|
|
1100
1217
|
const nextConfig = await setTokenIdentity(configRef.current, result.identity)
|
|
1101
1218
|
applyConfigChange(nextConfig)
|
|
1102
|
-
pushNote(`
|
|
1219
|
+
pushNote(`Identity saved · ERC-8004 #${result.identity.agentId}`, 'info')
|
|
1103
1220
|
} catch (err: unknown) {
|
|
1104
|
-
pushNote(`
|
|
1221
|
+
pushNote(`Identity save failed: ${(err as Error).message}`, 'error')
|
|
1105
1222
|
}
|
|
1106
1223
|
})()
|
|
1107
1224
|
}
|
|
@@ -1133,12 +1250,12 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
1133
1250
|
})
|
|
1134
1251
|
overlayRef.current = 'identity'
|
|
1135
1252
|
setOverlay('identity')
|
|
1136
|
-
pushNote('
|
|
1253
|
+
pushNote('Opening snapshot signature.', 'dim')
|
|
1137
1254
|
return
|
|
1138
1255
|
}
|
|
1139
1256
|
overlayRef.current = 'none'
|
|
1140
1257
|
setOverlay('none')
|
|
1141
|
-
pushNote('
|
|
1258
|
+
pushNote('Snapshot not saved yet.', 'dim')
|
|
1142
1259
|
},
|
|
1143
1260
|
[continuityEditReview, pushNote],
|
|
1144
1261
|
)
|
|
@@ -1147,7 +1264,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
1147
1264
|
setContinuityEditReview(null)
|
|
1148
1265
|
overlayRef.current = 'none'
|
|
1149
1266
|
setOverlay('none')
|
|
1150
|
-
pushNote('
|
|
1267
|
+
pushNote('Snapshot not saved yet.', 'dim')
|
|
1151
1268
|
}, [pushNote])
|
|
1152
1269
|
|
|
1153
1270
|
const handleCopyDone = useCallback(
|
|
@@ -1155,9 +1272,9 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
1155
1272
|
setOverlay('none')
|
|
1156
1273
|
setCopyPickerState(null)
|
|
1157
1274
|
if (result.ok) {
|
|
1158
|
-
pushNote(
|
|
1275
|
+
pushNote(`${label} copied to clipboard · ${result.chars} chars`, 'dim')
|
|
1159
1276
|
} else {
|
|
1160
|
-
pushNote(`
|
|
1277
|
+
pushNote(`Copy failed: ${result.error}`, 'error')
|
|
1161
1278
|
}
|
|
1162
1279
|
},
|
|
1163
1280
|
[pushNote],
|
|
@@ -1166,15 +1283,18 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
1166
1283
|
const handleCopyCancel = useCallback(() => {
|
|
1167
1284
|
setOverlay('none')
|
|
1168
1285
|
setCopyPickerState(null)
|
|
1169
|
-
pushNote('
|
|
1286
|
+
pushNote('Copy cancelled.', 'dim')
|
|
1170
1287
|
}, [pushNote])
|
|
1171
1288
|
|
|
1172
|
-
const handleRestoreConversation = useCallback((turnId: string) => {
|
|
1289
|
+
const handleRestoreConversation = useCallback((turnId: string, promptText?: string) => {
|
|
1173
1290
|
const restored = restoreConversationState(sessionMessagesRef.current, turnId, nextRowId)
|
|
1174
1291
|
sessionMessagesRef.current = restored.messages
|
|
1175
1292
|
setRows(restored.rows)
|
|
1176
1293
|
historyScopeRef.current = 'session'
|
|
1177
1294
|
setHistory(restored.promptHistory)
|
|
1295
|
+
if (promptText != null && promptText.length > 0) {
|
|
1296
|
+
setPendingInputDraft(promptText)
|
|
1297
|
+
}
|
|
1178
1298
|
if (restored.truncated) {
|
|
1179
1299
|
setQueuedInputs([])
|
|
1180
1300
|
statsSegmentStartRef.current = Math.min(statsSegmentStartRef.current, restored.messages.length)
|
|
@@ -1216,7 +1336,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
1216
1336
|
|
|
1217
1337
|
if (priorMessages.length <= 5) {
|
|
1218
1338
|
startFreshImplementationContext()
|
|
1219
|
-
pushNote('
|
|
1339
|
+
pushNote('Not enough planning context to summarize; starting a plan-only implementation conversation.', 'dim')
|
|
1220
1340
|
return true
|
|
1221
1341
|
}
|
|
1222
1342
|
|
|
@@ -1229,14 +1349,14 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
1229
1349
|
})
|
|
1230
1350
|
if (!result.ok && result.cancelled) {
|
|
1231
1351
|
removeCompactionProgress(compaction)
|
|
1232
|
-
pushNote('
|
|
1352
|
+
pushNote('Plan context summary cancelled.', 'dim')
|
|
1233
1353
|
return false
|
|
1234
1354
|
}
|
|
1235
1355
|
const summary = result.ok
|
|
1236
1356
|
? normalizeHandoffSummary(result.summary)
|
|
1237
1357
|
: normalizeHandoffSummary(summarizeTranscriptLocally(priorMessages, result.reason))
|
|
1238
1358
|
if (!result.ok) {
|
|
1239
|
-
pushNote(`
|
|
1359
|
+
pushNote(`Provider summary failed; created a local summary instead: ${result.reason}`, 'dim')
|
|
1240
1360
|
}
|
|
1241
1361
|
|
|
1242
1362
|
updateCompactionStage(compaction, 'saving summarized conversation')
|
|
@@ -1287,9 +1407,9 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
1287
1407
|
} catch (err: unknown) {
|
|
1288
1408
|
removeCompactionProgress(compaction)
|
|
1289
1409
|
if (compaction.controller.signal.aborted) {
|
|
1290
|
-
pushNote('
|
|
1410
|
+
pushNote('Plan context summary cancelled.', 'dim')
|
|
1291
1411
|
} else {
|
|
1292
|
-
pushNote(`
|
|
1412
|
+
pushNote(`Context summary error: ${(err as Error).message}`, 'error')
|
|
1293
1413
|
}
|
|
1294
1414
|
return false
|
|
1295
1415
|
} finally {
|
|
@@ -1323,7 +1443,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
1323
1443
|
}
|
|
1324
1444
|
if (plan.cwd !== cwdRef.current || plan.sessionId !== sessionIdRef.current) {
|
|
1325
1445
|
clearPendingPlan()
|
|
1326
|
-
pushNote('
|
|
1446
|
+
pushNote('Dismissed stale plan approval because the workspace changed.', 'dim')
|
|
1327
1447
|
return
|
|
1328
1448
|
}
|
|
1329
1449
|
if (action === 'continue') {
|
|
@@ -1387,7 +1507,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
1387
1507
|
})
|
|
1388
1508
|
}, [compactionUi, overlay, projectedUsageForInput, pullInFlight, pushNote, queuedInputs, runStream, showContextLimitForPrompt, streaming])
|
|
1389
1509
|
|
|
1390
|
-
const contextLine = `${config.provider} · ${formatModelDisplayName(config.provider, config.model, { maxLength: 24 })} · ${compressHome(cwd)}`
|
|
1510
|
+
const contextLine = `${providerDisplayName(config.provider)} · ${formatModelDisplayName(config.provider, config.model, { maxLength: 24 })} · ${compressHome(cwd)}`
|
|
1391
1511
|
const tipLine = 'Tip: type /help to get started · shift+enter for newline'
|
|
1392
1512
|
|
|
1393
1513
|
const placeholderHints = useMemo(() => {
|
|
@@ -1456,6 +1576,9 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
|
|
|
1456
1576
|
identityOverlay={identityOverlay}
|
|
1457
1577
|
handleIdentityResult={handleIdentityResult}
|
|
1458
1578
|
handleRestoreConversation={handleRestoreConversation}
|
|
1579
|
+
pendingInputDraft={pendingInputDraft}
|
|
1580
|
+
onInputDraftConsumed={() => setPendingInputDraft(null)}
|
|
1581
|
+
handleSummarizeFromTurn={runCompactionFromTurn}
|
|
1459
1582
|
handleCopyDone={handleCopyDone}
|
|
1460
1583
|
handleCopyCancel={handleCopyCancel}
|
|
1461
1584
|
resolvePermission={resolvePermission}
|
|
@@ -1568,12 +1691,14 @@ export function privateContinuityEditReviewFromToolResult(
|
|
|
1568
1691
|
if (name !== 'propose_private_continuity_edit' || !result.ok) return null
|
|
1569
1692
|
const file = normalizePrivateContinuityFile(input.file)
|
|
1570
1693
|
if (!file) return null
|
|
1571
|
-
const
|
|
1694
|
+
const parsed = splitFileChangeResult(result.content)
|
|
1695
|
+
const filePath = extractReviewFilePath(parsed.content)
|
|
1572
1696
|
if (!filePath) return null
|
|
1573
1697
|
return {
|
|
1574
1698
|
file,
|
|
1575
1699
|
filePath,
|
|
1576
1700
|
summary: result.summary,
|
|
1701
|
+
...(parsed.diff ? { diff: parsed.diff } : {}),
|
|
1577
1702
|
}
|
|
1578
1703
|
}
|
|
1579
1704
|
|