ethagent 3.3.3 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +11 -0
- package/.claude-plugin/plugin.json +35 -0
- package/LICENSE +1 -1
- package/README.md +64 -104
- package/commands/ethagent.md +40 -0
- package/package.json +16 -16
- package/src/app/keybindings/KeybindingProvider.tsx +1 -6
- package/src/app/keybindings/types.ts +1 -6
- package/src/cli/ResetConfirmView.tsx +54 -53
- package/src/cli/demo.ts +86 -0
- package/src/cli/hookIo.ts +45 -0
- package/src/cli/main.tsx +94 -123
- package/src/cli/memoryGuard.ts +49 -0
- package/src/cli/reset.ts +28 -70
- package/src/cli/sessionStart.ts +33 -0
- package/src/cli/status.ts +46 -0
- package/src/cli/sync.ts +167 -0
- package/src/cli/syncAdapters/claude-code.ts +86 -0
- package/src/cli/syncAdapters/codex.ts +66 -0
- package/src/cli/syncAdapters/index.ts +45 -0
- package/src/cli/syncAdapters/managedBlock.ts +175 -0
- package/src/cli/syncAdapters/shared.ts +63 -0
- package/src/identity/continuity/envelopeParse.ts +20 -1
- package/src/identity/continuity/publicSkills.ts +3 -1
- package/src/identity/continuity/skills/publicSkillsSync.ts +2 -1
- package/src/identity/continuity/skills/scaffold.ts +5 -2
- package/src/identity/continuity/snapshots.ts +12 -5
- package/src/identity/continuity/storage/defaults.ts +20 -19
- package/src/identity/continuity/storage/status.ts +1 -1
- package/src/identity/ens/ensLookup/constants.ts +1 -1
- package/src/identity/manager/IdentityManager.tsx +33 -0
- package/src/identity/{hub → manager}/OperationalRoutes.tsx +37 -18
- package/src/identity/{hub → manager}/Routes.tsx +48 -34
- package/src/identity/{hub → manager}/continuity/ContinuityDashboardScreen.tsx +9 -19
- package/src/identity/{hub → manager}/continuity/RebackupStorageScreen.tsx +3 -3
- package/src/identity/manager/continuity/RecoveryConfirmScreen.tsx +102 -0
- package/src/identity/{hub → manager}/continuity/SavePromptScreen.tsx +2 -3
- package/src/identity/{hub → manager}/continuity/completion.ts +1 -1
- package/src/identity/{hub → manager}/continuity/effects.ts +1 -1
- package/src/identity/{hub → manager}/continuity/skills/DeleteSkillConfirmScreen.tsx +2 -2
- package/src/identity/{hub → manager}/continuity/skills/NewSkillScreen.tsx +0 -5
- package/src/identity/{hub → manager}/continuity/skills/NewSkillVisibilityScreen.tsx +4 -4
- package/src/identity/{hub → manager}/continuity/skills/SkillActionsScreen.tsx +6 -22
- package/src/identity/{hub → manager}/continuity/skills/SkillsTreeScreen.tsx +5 -17
- package/src/identity/{hub → manager}/continuity/snapshot.ts +1 -1
- package/src/identity/{hub → manager}/continuity/vault.ts +1 -1
- package/src/identity/{hub → manager}/create/CreateFlow.tsx +59 -32
- package/src/identity/{hub → manager}/create/effects.ts +19 -10
- package/src/identity/manager/create/importScan.ts +122 -0
- package/src/identity/{hub → manager}/custody/CustodyEditFlow.tsx +17 -61
- package/src/identity/{hub → manager}/custody/actions.ts +1 -15
- package/src/identity/{hub → manager}/custody/routes.tsx +20 -40
- package/src/identity/{hub → manager}/custody/transactions.ts +1 -0
- package/src/identity/{hub → manager}/custody/types.ts +1 -2
- package/src/identity/{hub → manager}/custody/useCustodyEffects.ts +1 -1
- package/src/identity/{hub → manager}/ens/EnsEditAdvancedScreens.tsx +2 -2
- package/src/identity/{hub → manager}/ens/EnsEditMaintenanceScreens.tsx +12 -23
- package/src/identity/{hub → manager}/ens/EnsEditReviewScreens.tsx +18 -42
- package/src/identity/{hub → manager}/ens/EnsEditRunners.tsx +1 -1
- package/src/identity/{hub → manager}/ens/EnsEditShared.tsx +0 -2
- package/src/identity/{hub → manager}/ens/EnsEditSimpleScreens.tsx +10 -19
- package/src/identity/{hub → manager}/ens/EnsFlow.tsx +133 -41
- package/src/identity/{hub → manager}/ens/EnsOperatorWalletsScreen.tsx +14 -19
- package/src/identity/{hub → manager}/ens/editCopy.ts +1 -14
- package/src/identity/{hub → manager}/profile/EditProfileFlow.tsx +99 -66
- package/src/identity/{hub → manager}/profile/effects.ts +1 -3
- package/src/identity/{hub → manager}/profile/operatorSave.ts +1 -1
- package/src/identity/{hub → manager}/profile/state.ts +1 -1
- package/src/identity/{hub/identityHubReducer.ts → manager/reducer.ts} +25 -26
- package/src/identity/{hub → manager}/restore/RestoreFlow.tsx +16 -24
- package/src/identity/{hub → manager}/restore/apply.ts +1 -1
- package/src/identity/{hub → manager}/restore/auth.ts +1 -1
- package/src/identity/{hub → manager}/restore/discover.ts +1 -1
- package/src/identity/{hub → manager}/restore/fetch.ts +1 -1
- package/src/identity/{hub → manager}/restore/restoreAdmin.ts +1 -1
- package/src/identity/{hub → manager}/restore/useRestoreEffects.ts +2 -9
- package/src/identity/{hub → manager}/settings/StorageCredentialScreen.tsx +10 -25
- package/src/identity/{hub → manager}/shared/components/DetailsScreen.tsx +5 -7
- package/src/identity/{hub → manager}/shared/components/ErrorScreen.tsx +6 -10
- package/src/identity/{hub → manager}/shared/components/FlowTimeline.tsx +4 -3
- package/src/identity/{hub → manager}/shared/components/IdentitySummary.tsx +19 -59
- package/src/identity/manager/shared/components/LazyMenu.tsx +147 -0
- package/src/identity/manager/shared/components/MenuScreen.tsx +220 -0
- package/src/identity/manager/shared/components/OperationCompleteScreen.tsx +28 -0
- package/src/identity/{hub → manager}/shared/components/UnlinkedIdentityScreen.tsx +9 -10
- package/src/identity/{hub → manager}/shared/components/WalletApprovalScreen.tsx +1 -2
- package/src/identity/manager/shared/components/Wordmark.tsx +54 -0
- package/src/identity/{hub → manager}/shared/components/menuFlagsFromReconciliation.ts +39 -15
- package/src/identity/{hub → manager}/shared/effects/profilePrep.ts +1 -1
- package/src/identity/manager/shared/effects/types.ts +30 -0
- package/src/identity/{hub → manager}/shared/model/copy.ts +0 -4
- package/src/identity/{hub → manager}/shared/model/errors.ts +32 -3
- package/src/identity/{hub → manager}/shared/model/network.ts +2 -2
- package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/hook.ts +5 -0
- package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/run.ts +1 -1
- package/src/identity/{hub/shared/reconciliation/useAgentReconciliation.ts → manager/shared/reconciliation/index.ts} +6 -0
- package/src/identity/{hub → manager}/shared/utils.ts +6 -10
- package/src/identity/{hub → manager}/transfer/TokenTransferFlow.tsx +3 -3
- package/src/identity/{hub → manager}/transfer/TokenTransferScreens.tsx +4 -10
- package/src/identity/{hub → manager}/transfer/effects.ts +1 -1
- package/src/identity/{hub → manager}/types.ts +5 -6
- package/src/identity/{hub/useIdentityHubContinuity.ts → manager/useContinuity.ts} +59 -27
- package/src/identity/{hub/useIdentityHubController.ts → manager/useController.ts} +38 -35
- package/src/identity/{hub/useIdentityHubSideEffects.ts → manager/useSideEffects.ts} +40 -4
- package/src/identity/registry/erc8004/discovery.ts +3 -17
- package/src/identity/registry/erc8004/utils.ts +1 -1
- package/src/identity/storage/ipfs.ts +21 -1
- package/src/identity/wallet/browserWallet/html.ts +10 -2
- package/src/identity/wallet/browserWallet/http.ts +18 -0
- package/src/identity/wallet/browserWallet/requestServer.ts +5 -1
- package/src/identity/wallet/browserWallet/requests.ts +10 -28
- package/src/identity/wallet/browserWallet/session.ts +26 -33
- package/src/identity/wallet/browserWallet/validation.ts +14 -0
- package/src/identity/wallet/browserWallet/walletPageSource.ts +22 -40
- package/src/identity/wallet/page/boot.ts +43 -0
- package/src/identity/wallet/page/config.ts +59 -0
- package/src/identity/wallet/page/constants.ts +12 -0
- package/src/identity/wallet/page/copy.ts +47 -68
- package/src/identity/wallet/page/css.ts +638 -0
- package/src/identity/wallet/page/{errorView.ts → errors.ts} +5 -14
- package/src/identity/wallet/page/{controller.ts → flow.ts} +4 -71
- package/src/identity/wallet/page/markup.ts +44 -34
- package/src/identity/wallet/page/{walletProvider.ts → provider.ts} +0 -3
- package/src/identity/wallet/page/resize.ts +95 -0
- package/src/identity/wallet/page/state.ts +135 -8
- package/src/identity/wallet/page/timeline.ts +161 -0
- package/src/identity/wallet/page/view.ts +22 -302
- package/src/storage/config.ts +30 -80
- package/src/storage/reset.ts +31 -0
- package/src/storage/secrets.ts +1 -16
- package/src/ui/Select.tsx +27 -5
- package/src/ui/Spinner.tsx +16 -15
- package/src/ui/Surface.tsx +21 -17
- package/src/ui/TextArea.tsx +173 -0
- package/src/ui/TextInput.tsx +31 -133
- package/src/ui/theme.ts +22 -13
- package/src/utils/clipboard.ts +0 -140
- package/src/app/FirstRun.tsx +0 -577
- package/src/app/FirstRunTimeline.tsx +0 -51
- package/src/app/firstRunConfig.ts +0 -26
- package/src/app/hooks/useCancelRequest.ts +0 -22
- package/src/app/hooks/useDoublePress.ts +0 -46
- package/src/app/hooks/useExitOnCtrlC.ts +0 -36
- package/src/auth/openaiOAuth/credentials.ts +0 -47
- package/src/auth/openaiOAuth/crypto.ts +0 -23
- package/src/auth/openaiOAuth/index.ts +0 -238
- package/src/auth/openaiOAuth/landingPage.ts +0 -116
- package/src/auth/openaiOAuth/listener.ts +0 -151
- package/src/auth/openaiOAuth/refresh.ts +0 -70
- package/src/auth/openaiOAuth/shared.ts +0 -115
- package/src/chat/ChatBottomPane.tsx +0 -296
- package/src/chat/ChatScreen.tsx +0 -1685
- package/src/chat/ConversationStack.tsx +0 -56
- package/src/chat/MessageList.tsx +0 -638
- package/src/chat/SessionStatus.tsx +0 -53
- package/src/chat/chatEnvironment.ts +0 -16
- package/src/chat/chatScreenUtils.ts +0 -194
- package/src/chat/chatSessionState.ts +0 -146
- package/src/chat/chatTurnContext.ts +0 -50
- package/src/chat/chatTurnOrchestrator.ts +0 -603
- package/src/chat/chatTurnRows.ts +0 -64
- package/src/chat/commands.ts +0 -494
- package/src/chat/continuityEditReview.ts +0 -42
- package/src/chat/display/DiffView.tsx +0 -193
- package/src/chat/display/SyntaxText.tsx +0 -192
- package/src/chat/display/toolCallDisplay.ts +0 -103
- package/src/chat/display/toolResultDisplay.ts +0 -19
- package/src/chat/input/ChatInput.tsx +0 -625
- package/src/chat/input/chatInputHelpers.ts +0 -62
- package/src/chat/input/chatInputState.ts +0 -247
- package/src/chat/input/chatPaste.ts +0 -49
- package/src/chat/input/imageRefs.ts +0 -30
- package/src/chat/input/inputRendering.tsx +0 -93
- package/src/chat/input/textCursor.ts +0 -212
- package/src/chat/messageMarkdown.ts +0 -220
- package/src/chat/messageRows.ts +0 -43
- package/src/chat/planImplementation.ts +0 -62
- package/src/chat/slashCommandHandlers.ts +0 -122
- package/src/chat/slashCommandViews.ts +0 -120
- package/src/chat/transcript/TranscriptView.tsx +0 -184
- package/src/chat/transcript/transcriptViewport.ts +0 -295
- package/src/chat/views/ContextLimitView.tsx +0 -95
- package/src/chat/views/ContinuityEditReviewView.tsx +0 -50
- package/src/chat/views/CopyPicker.tsx +0 -50
- package/src/chat/views/PermissionPrompt.tsx +0 -156
- package/src/chat/views/PermissionsView.tsx +0 -165
- package/src/chat/views/PlanApprovalView.tsx +0 -91
- package/src/chat/views/ResumeView.tsx +0 -273
- package/src/chat/views/RewindView.tsx +0 -412
- package/src/cli/preview.tsx +0 -14
- package/src/cli/updateNotice.ts +0 -54
- package/src/identity/continuity/privateEdit/apply.ts +0 -170
- package/src/identity/continuity/privateEdit/diff.ts +0 -6
- package/src/identity/continuity/privateEdit/files.ts +0 -23
- package/src/identity/continuity/privateEdit/types.ts +0 -28
- package/src/identity/continuity/privateEdit.ts +0 -46
- package/src/identity/hub/IdentityHub.tsx +0 -14
- package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +0 -104
- package/src/identity/hub/ens/effects.ts +0 -218
- package/src/identity/hub/shared/components/MenuScreen.tsx +0 -241
- package/src/identity/hub/shared/effects/types.ts +0 -53
- package/src/identity/hub/shared/reconciliation/index.ts +0 -14
- package/src/identity/wallet/page/grainient.ts +0 -278
- package/src/identity/wallet/page/html.ts +0 -28
- package/src/identity/wallet/page/styles/base.ts +0 -259
- package/src/identity/wallet/page/styles/components.ts +0 -262
- package/src/identity/wallet/page/styles/index.ts +0 -5
- package/src/identity/wallet/page/styles/responsive.ts +0 -247
- package/src/identity/wallet/page.tsx +0 -38
- package/src/mcp/approvals.ts +0 -113
- package/src/mcp/config.ts +0 -235
- package/src/mcp/manager.ts +0 -482
- package/src/mcp/managerHelpers.ts +0 -70
- package/src/mcp/names.ts +0 -19
- package/src/mcp/output.ts +0 -96
- package/src/models/ModelPicker.tsx +0 -1009
- package/src/models/catalog.ts +0 -327
- package/src/models/huggingface.ts +0 -712
- package/src/models/huggingfaceStorage.ts +0 -136
- package/src/models/llamacpp.ts +0 -848
- package/src/models/llamacppCommands.ts +0 -44
- package/src/models/llamacppConfig.ts +0 -34
- package/src/models/llamacppDiscovery.ts +0 -176
- package/src/models/llamacppOutput.ts +0 -65
- package/src/models/llamacppPreflight.ts +0 -158
- package/src/models/modelDisplay.ts +0 -180
- package/src/models/modelPickerCatalogFlow.ts +0 -56
- package/src/models/modelPickerCredentials.ts +0 -166
- package/src/models/modelPickerData.ts +0 -41
- package/src/models/modelPickerDisplay.tsx +0 -132
- package/src/models/modelPickerHfFlow.ts +0 -192
- package/src/models/modelPickerLocalRunnerFlow.ts +0 -115
- package/src/models/modelPickerOptions.ts +0 -457
- package/src/models/modelPickerTypes.ts +0 -69
- package/src/models/modelPickerUninstallFlow.ts +0 -48
- package/src/models/modelPickerViewHelpers.ts +0 -174
- package/src/models/modelRecommendation.ts +0 -139
- package/src/models/providerDisplay.ts +0 -16
- package/src/models/runtimeDetection.ts +0 -81
- package/src/models/uncensoredCatalog.ts +0 -86
- package/src/providers/anthropic.ts +0 -290
- package/src/providers/contracts.ts +0 -71
- package/src/providers/errors.ts +0 -80
- package/src/providers/gemini.ts +0 -391
- package/src/providers/openai-chat.ts +0 -474
- package/src/providers/openai-responses-format.ts +0 -177
- package/src/providers/openai-responses.ts +0 -306
- package/src/providers/openaiChatWire.ts +0 -124
- package/src/providers/registry.ts +0 -120
- package/src/providers/retry.ts +0 -58
- package/src/providers/sse.ts +0 -93
- package/src/runtime/compaction.ts +0 -395
- package/src/runtime/cwd.ts +0 -43
- package/src/runtime/providerTurn.ts +0 -38
- package/src/runtime/sessionMode.ts +0 -55
- package/src/runtime/systemPrompt.ts +0 -213
- package/src/runtime/textToolParser.ts +0 -161
- package/src/runtime/toolClaimGuards.ts +0 -143
- package/src/runtime/toolExecution.ts +0 -304
- package/src/runtime/toolIntent.ts +0 -143
- package/src/runtime/turn.ts +0 -369
- package/src/runtime/turnNudges.ts +0 -223
- package/src/runtime/turnTypes.ts +0 -86
- package/src/storage/factoryReset.ts +0 -127
- package/src/storage/history.ts +0 -58
- package/src/storage/permissions.ts +0 -76
- package/src/storage/rewind.ts +0 -266
- package/src/storage/sessionExport.ts +0 -49
- package/src/storage/sessions.ts +0 -495
- package/src/tools/bashSafety.ts +0 -186
- package/src/tools/bashTool.ts +0 -140
- package/src/tools/changeDirectoryTool.ts +0 -213
- package/src/tools/contracts.ts +0 -192
- package/src/tools/deleteFileTool.ts +0 -116
- package/src/tools/editTool.ts +0 -165
- package/src/tools/editUtils.ts +0 -170
- package/src/tools/fileDiff.ts +0 -261
- package/src/tools/listDirectoryTool.ts +0 -55
- package/src/tools/listSkillFilesTool.ts +0 -77
- package/src/tools/listSkillsTool.ts +0 -68
- package/src/tools/mcpResourceTools.ts +0 -95
- package/src/tools/permissionRules.ts +0 -85
- package/src/tools/privateContinuityEditTool.ts +0 -187
- package/src/tools/privateContinuityReadTool.ts +0 -106
- package/src/tools/readSkillTool.ts +0 -107
- package/src/tools/readTool.ts +0 -85
- package/src/tools/registry.ts +0 -103
- package/src/tools/writeFileTool.ts +0 -167
- package/src/ui/BrandSplash.tsx +0 -133
- package/src/ui/terminalTitle.ts +0 -30
- package/src/utils/images.ts +0 -140
- package/src/utils/markdownSegments.ts +0 -51
- package/src/utils/messages.ts +0 -37
- package/src/utils/withRetry.ts +0 -324
- /package/src/identity/{hub → manager}/continuity/state.ts +0 -0
- /package/src/identity/{hub → manager}/custody/helpers.ts +0 -0
- /package/src/identity/{hub → manager}/custody/preflight.ts +0 -0
- /package/src/identity/{hub → manager}/custody/state.ts +0 -0
- /package/src/identity/{hub → manager}/custody/useCustodyFlow.tsx +0 -0
- /package/src/identity/{hub → manager}/ens/EnsEditFlow.tsx +0 -0
- /package/src/identity/{hub → manager}/ens/advancedEnsValidation.ts +0 -0
- /package/src/identity/{hub → manager}/ens/state.ts +0 -0
- /package/src/identity/{hub → manager}/ens/transactions.ts +0 -0
- /package/src/identity/{hub → manager}/ens/types.ts +0 -0
- /package/src/identity/{hub → manager}/profile/identity.ts +0 -0
- /package/src/identity/{hub → manager}/restore/envelopes.ts +0 -0
- /package/src/identity/{hub → manager}/restore/helpers.ts +0 -0
- /package/src/identity/{hub → manager}/restore/recovery.ts +0 -0
- /package/src/identity/{hub → manager}/restore/resolve.ts +0 -0
- /package/src/identity/{hub → manager}/shared/components/BusyScreen.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/components/NetworkScreen.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/components/PinataJwtInput.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/effects/receipts.ts +0 -0
- /package/src/identity/{hub → manager}/shared/effects/sync.ts +0 -0
- /package/src/identity/{hub → manager}/shared/model/format.ts +0 -0
- /package/src/identity/{hub → manager}/shared/operatorWallets.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/ownership.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/types.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/walletSetup.ts +0 -0
- /package/src/identity/{hub → manager}/shared/txGuard.ts +0 -0
- /package/src/identity/{hub → manager}/transfer/progress.ts +0 -0
- /package/src/identity/{hub → manager}/transfer/state.ts +0 -0
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
export type MarkdownBlock =
|
|
2
|
-
| { kind: 'heading'; level: 1 | 2 | 3 | 4 | 5 | 6; text: string }
|
|
3
|
-
| { kind: 'paragraph'; text: string }
|
|
4
|
-
| { kind: 'quote'; lines: string[] }
|
|
5
|
-
| { kind: 'list'; ordered: boolean; items: string[] }
|
|
6
|
-
| { kind: 'code'; lang: string | null; code: string; open?: boolean }
|
|
7
|
-
|
|
8
|
-
export type InlineToken =
|
|
9
|
-
| { kind: 'text'; text: string }
|
|
10
|
-
| { kind: 'bold'; text: string }
|
|
11
|
-
| { kind: 'italic'; text: string }
|
|
12
|
-
| { kind: 'code'; text: string }
|
|
13
|
-
|
|
14
|
-
const UNREADABLE_REASONING_TEXT = 'reasoning output was not readable text'
|
|
15
|
-
|
|
16
|
-
export function blockContentWidth(lines: string[]): number {
|
|
17
|
-
return Math.max(1, ...lines.map(displayWidth))
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function displayWidth(line: string): number {
|
|
21
|
-
return (line || ' ').replace(/\t/g, ' ').length
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function parseMarkdownBlocks(markdown: string): MarkdownBlock[] {
|
|
25
|
-
const text = markdown.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
|
26
|
-
if (!text.trim()) return []
|
|
27
|
-
|
|
28
|
-
const blocks: MarkdownBlock[] = []
|
|
29
|
-
const lines = text.split('\n')
|
|
30
|
-
let index = 0
|
|
31
|
-
|
|
32
|
-
while (index < lines.length) {
|
|
33
|
-
const line = lines[index] ?? ''
|
|
34
|
-
const trimmed = line.trim()
|
|
35
|
-
|
|
36
|
-
if (!trimmed) {
|
|
37
|
-
index += 1
|
|
38
|
-
continue
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const fence = trimmed.match(/^```([\w+-]*)\s*$/)
|
|
42
|
-
if (fence) {
|
|
43
|
-
const lang = fence[1] && fence[1].length > 0 ? fence[1] : null
|
|
44
|
-
index += 1
|
|
45
|
-
const body: string[] = []
|
|
46
|
-
let closed = false
|
|
47
|
-
while (index < lines.length) {
|
|
48
|
-
const nextLine = lines[index] ?? ''
|
|
49
|
-
if (nextLine.trim().match(/^```\s*$/)) {
|
|
50
|
-
closed = true
|
|
51
|
-
index += 1
|
|
52
|
-
break
|
|
53
|
-
}
|
|
54
|
-
body.push(nextLine)
|
|
55
|
-
index += 1
|
|
56
|
-
}
|
|
57
|
-
blocks.push({ kind: 'code', lang, code: body.join('\n'), open: !closed })
|
|
58
|
-
continue
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const heading = line.match(/^(#{1,6})\s+(.*)$/)
|
|
62
|
-
if (heading) {
|
|
63
|
-
const [, hashes = '#', headingText = ''] = heading
|
|
64
|
-
blocks.push({
|
|
65
|
-
kind: 'heading',
|
|
66
|
-
level: hashes.length as 1 | 2 | 3 | 4 | 5 | 6,
|
|
67
|
-
text: headingText.trim(),
|
|
68
|
-
})
|
|
69
|
-
index += 1
|
|
70
|
-
continue
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (/^>\s?/.test(trimmed)) {
|
|
74
|
-
const quoteLines: string[] = []
|
|
75
|
-
while (index < lines.length) {
|
|
76
|
-
const nextLine = lines[index] ?? ''
|
|
77
|
-
if (!/^>\s?/.test(nextLine.trim())) break
|
|
78
|
-
quoteLines.push(nextLine.trim().replace(/^>\s?/, ''))
|
|
79
|
-
index += 1
|
|
80
|
-
}
|
|
81
|
-
blocks.push({ kind: 'quote', lines: quoteLines })
|
|
82
|
-
continue
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const ordered = trimmed.match(/^\d+\.\s+(.*)$/)
|
|
86
|
-
const unordered = trimmed.match(/^[-*+]\s+(.*)$/)
|
|
87
|
-
if (ordered || unordered) {
|
|
88
|
-
const items: string[] = []
|
|
89
|
-
const orderedList = Boolean(ordered)
|
|
90
|
-
while (index < lines.length) {
|
|
91
|
-
const nextLine = lines[index] ?? ''
|
|
92
|
-
const match = orderedList
|
|
93
|
-
? nextLine.trim().match(/^\d+\.\s+(.*)$/)
|
|
94
|
-
: nextLine.trim().match(/^[-*+]\s+(.*)$/)
|
|
95
|
-
if (!match) break
|
|
96
|
-
items.push(match[1] ?? '')
|
|
97
|
-
index += 1
|
|
98
|
-
}
|
|
99
|
-
blocks.push({ kind: 'list', ordered: orderedList, items })
|
|
100
|
-
continue
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const paragraph: string[] = []
|
|
104
|
-
while (index < lines.length) {
|
|
105
|
-
const nextLine = lines[index] ?? ''
|
|
106
|
-
const nextTrimmed = nextLine.trim()
|
|
107
|
-
if (!nextTrimmed) break
|
|
108
|
-
if (nextTrimmed.match(/^```([\w+-]*)\s*$/)) break
|
|
109
|
-
if (nextLine.match(/^(#{1,6})\s+(.*)$/)) break
|
|
110
|
-
if (/^>\s?/.test(nextTrimmed)) break
|
|
111
|
-
if (nextTrimmed.match(/^\d+\.\s+(.*)$/) || nextTrimmed.match(/^[-*+]\s+(.*)$/)) break
|
|
112
|
-
paragraph.push(nextLine)
|
|
113
|
-
index += 1
|
|
114
|
-
}
|
|
115
|
-
blocks.push({ kind: 'paragraph', text: paragraph.join('\n').trim() })
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return blocks
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function parseInlineTokens(text: string): InlineToken[] {
|
|
122
|
-
const tokens: InlineToken[] = []
|
|
123
|
-
const source = normalizeInlineDisplayText(text)
|
|
124
|
-
const pattern = /(`[^`\n]+`|\*\*[^*\n]+?\*\*|__[^_\n]+?__|\*[^*\n]+?\*|_[^_\n]+?_)/g
|
|
125
|
-
let lastIndex = 0
|
|
126
|
-
let match: RegExpExecArray | null
|
|
127
|
-
|
|
128
|
-
while ((match = pattern.exec(source)) !== null) {
|
|
129
|
-
if (match.index > lastIndex) {
|
|
130
|
-
tokens.push({ kind: 'text', text: cleanPlainInlineText(source.slice(lastIndex, match.index)) })
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const token = match[0]
|
|
134
|
-
if ((token.startsWith('**') && token.endsWith('**')) || (token.startsWith('__') && token.endsWith('__'))) {
|
|
135
|
-
tokens.push({ kind: 'bold', text: cleanPlainInlineText(token.slice(2, -2)) })
|
|
136
|
-
} else if ((token.startsWith('*') && token.endsWith('*')) || (token.startsWith('_') && token.endsWith('_'))) {
|
|
137
|
-
tokens.push({ kind: 'italic', text: cleanPlainInlineText(token.slice(1, -1)) })
|
|
138
|
-
} else if (token.startsWith('`') && token.endsWith('`')) {
|
|
139
|
-
tokens.push({ kind: 'code', text: token.slice(1, -1) })
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
lastIndex = match.index + token.length
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (lastIndex < source.length || tokens.length === 0) {
|
|
146
|
-
tokens.push({ kind: 'text', text: cleanPlainInlineText(source.slice(lastIndex)) })
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return tokens.filter(token => token.text.length > 0)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function normalizeInlineDisplayText(text: string): string {
|
|
153
|
-
return text
|
|
154
|
-
.replace(/\\\(/g, '')
|
|
155
|
-
.replace(/\\\)/g, '')
|
|
156
|
-
.replace(/\\\[/g, '')
|
|
157
|
-
.replace(/\\\]/g, '')
|
|
158
|
-
.replace(/\$\$([^$]+)\$\$/g, '$1')
|
|
159
|
-
.replace(/\$([^$\n]+)\$/g, '$1')
|
|
160
|
-
.replace(/\\([{}[\]()])/g, '$1')
|
|
161
|
-
.replace(/\/([{}])/g, '$1')
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function cleanPlainInlineText(text: string): string {
|
|
165
|
-
return text.replace(/\*+/g, '')
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export function summarizeThinking(text: string): string {
|
|
169
|
-
const sample = text.length > 1000 ? text.slice(-1000) : text
|
|
170
|
-
const normalized = sample.replace(/\s+/g, ' ').trim()
|
|
171
|
-
if (!normalized) return ''
|
|
172
|
-
const prefix = text.length > sample.length ? '...' : ''
|
|
173
|
-
if (normalized.length + prefix.length <= 120) return `${prefix}${normalized}`
|
|
174
|
-
return `${prefix}${normalized.slice(Math.max(0, normalized.length - (120 - prefix.length)))}`
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export function clipTextForDisplay(text: string, maxChars: number): { text: string; omittedChars: number } {
|
|
178
|
-
if (text.length <= maxChars) return { text, omittedChars: 0 }
|
|
179
|
-
const rawStart = Math.max(0, text.length - maxChars)
|
|
180
|
-
const newline = text.indexOf('\n', rawStart)
|
|
181
|
-
const start = newline >= 0 && newline - rawStart <= 240 ? newline + 1 : rawStart
|
|
182
|
-
return {
|
|
183
|
-
text: text.slice(start),
|
|
184
|
-
omittedChars: start,
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export function sanitizeReasoningForDisplay(text: string): string {
|
|
189
|
-
const normalized = text
|
|
190
|
-
.replace(/\r\n/g, '\n')
|
|
191
|
-
.replace(/\r/g, '\n')
|
|
192
|
-
.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '')
|
|
193
|
-
const controlCount = countMatches(normalized, /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F\uFFFD]/g)
|
|
194
|
-
const cleaned = normalized
|
|
195
|
-
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F\uFFFD]/g, '')
|
|
196
|
-
.replace(/\t/g, ' ')
|
|
197
|
-
const visibleLength = cleaned.replace(/\s/g, '').length
|
|
198
|
-
if (visibleLength === 0) return ''
|
|
199
|
-
if (controlCount > 0 && controlCount / Math.max(1, text.length) > 0.05) return UNREADABLE_REASONING_TEXT
|
|
200
|
-
if (looksLikeUnreadableReasoning(cleaned)) return UNREADABLE_REASONING_TEXT
|
|
201
|
-
return cleaned
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function looksLikeUnreadableReasoning(text: string): boolean {
|
|
205
|
-
const visible = text.replace(/\s/g, '')
|
|
206
|
-
if (visible.length < 120) return false
|
|
207
|
-
const letters = countMatches(visible, /[A-Za-z]/g)
|
|
208
|
-
const digits = countMatches(visible, /\d/g)
|
|
209
|
-
const words = text.match(/[A-Za-z]{3,}/g) ?? []
|
|
210
|
-
const wordChars = words.reduce((sum, word) => sum + word.length, 0)
|
|
211
|
-
const whitespace = countMatches(text, /\s/g)
|
|
212
|
-
const symbolDensity = (visible.length - letters - digits) / visible.length
|
|
213
|
-
const wordDensity = wordChars / visible.length
|
|
214
|
-
const whitespaceDensity = whitespace / Math.max(1, text.length)
|
|
215
|
-
return symbolDensity > 0.38 && wordDensity < 0.32 && whitespaceDensity < 0.12
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function countMatches(text: string, pattern: RegExp): number {
|
|
219
|
-
return text.match(pattern)?.length ?? 0
|
|
220
|
-
}
|
package/src/chat/messageRows.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { RowSlice } from './transcript/transcriptViewport.js'
|
|
2
|
-
import type { MessageRow } from './MessageList.js'
|
|
3
|
-
|
|
4
|
-
export function rowsToFullSlices(rows: MessageRow[]): Array<RowSlice<MessageRow>> {
|
|
5
|
-
return rows.map(row => ({ row, clipStart: 0, clipEnd: Number.MAX_SAFE_INTEGER, rowHeight: Number.MAX_SAFE_INTEGER }))
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function toggleLatestReasoningRow(rows: MessageRow[]): MessageRow[] {
|
|
9
|
-
return toggleInspectableRow(rows)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function toggleReasoningRow(rows: MessageRow[], rowId?: string): MessageRow[] {
|
|
13
|
-
return toggleInspectableRow(rows, rowId)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function toggleInspectableRow(rows: MessageRow[], rowId?: string): MessageRow[] {
|
|
17
|
-
let index = -1
|
|
18
|
-
if (rowId) {
|
|
19
|
-
index = rows.findIndex(row => row.id === rowId && isInspectableRole(row.role))
|
|
20
|
-
}
|
|
21
|
-
if (index === -1) {
|
|
22
|
-
for (let cursor = rows.length - 1; cursor >= 0; cursor -= 1) {
|
|
23
|
-
const role = rows[cursor]?.role
|
|
24
|
-
if (role && isInspectableRole(role)) {
|
|
25
|
-
index = cursor
|
|
26
|
-
break
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
if (index === -1) return rows
|
|
31
|
-
const row = rows[index]
|
|
32
|
-
if (!row) return rows
|
|
33
|
-
if (row.role === 'thinking') {
|
|
34
|
-
const next = rows.slice()
|
|
35
|
-
next[index] = { ...row, expanded: !row.expanded }
|
|
36
|
-
return next
|
|
37
|
-
}
|
|
38
|
-
return rows
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function isInspectableRole(role: MessageRow['role']): boolean {
|
|
42
|
-
return role === 'thinking'
|
|
43
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import type { SessionMessage } from '../storage/sessions.js'
|
|
2
|
-
|
|
3
|
-
const MAX_HANDOFF_SUMMARY_CHARS = 12_000
|
|
4
|
-
|
|
5
|
-
export function chatFooterShortcutText(_canScrollTranscript: boolean): string {
|
|
6
|
-
return 'alt+p model · alt+i identity'
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function buildPlanImplementationPrompt(plan: string): string {
|
|
10
|
-
return [
|
|
11
|
-
'Implement the approved plan below.',
|
|
12
|
-
'',
|
|
13
|
-
'Use native ethagent tools directly. Do not translate tool names into shell commands.',
|
|
14
|
-
'For workspace inspection, call list_directory and read_file directly.',
|
|
15
|
-
'For file creation or edits, call edit_file directly.',
|
|
16
|
-
'Use run_bash only for an actual shell command that cannot be performed by a narrower native tool, such as starting a local server after files exist.',
|
|
17
|
-
'Ignore any plan wording that says to execute file work as a Bash script or directly in the terminal; the native tools above are authoritative.',
|
|
18
|
-
'Read the relevant files before editing, make the required changes, and verify the result when possible.',
|
|
19
|
-
'',
|
|
20
|
-
plan,
|
|
21
|
-
].join('\n')
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function buildPlanTransferSeedMessages(args: {
|
|
25
|
-
sourceSessionId: string
|
|
26
|
-
summary: string
|
|
27
|
-
plan: string
|
|
28
|
-
createdAt: string
|
|
29
|
-
}): SessionMessage[] {
|
|
30
|
-
return [
|
|
31
|
-
{
|
|
32
|
-
role: 'user',
|
|
33
|
-
synthetic: true,
|
|
34
|
-
content: [
|
|
35
|
-
`Planning handoff from ${args.sourceSessionId.slice(0, 8)}:`,
|
|
36
|
-
'',
|
|
37
|
-
args.summary.trim(),
|
|
38
|
-
].join('\n'),
|
|
39
|
-
createdAt: args.createdAt,
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
role: 'user',
|
|
43
|
-
synthetic: true,
|
|
44
|
-
content: [
|
|
45
|
-
'Approved plan to implement:',
|
|
46
|
-
'',
|
|
47
|
-
args.plan.trim(),
|
|
48
|
-
].join('\n'),
|
|
49
|
-
createdAt: args.createdAt,
|
|
50
|
-
},
|
|
51
|
-
]
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function normalizeHandoffSummary(summary: string): string {
|
|
55
|
-
const trimmed = summary.trim()
|
|
56
|
-
if (trimmed.length <= MAX_HANDOFF_SUMMARY_CHARS) return trimmed
|
|
57
|
-
return [
|
|
58
|
-
trimmed.slice(0, MAX_HANDOFF_SUMMARY_CHARS - 96).trimEnd(),
|
|
59
|
-
'',
|
|
60
|
-
'[handoff truncated to keep the resumed conversation responsive]',
|
|
61
|
-
].join('\n')
|
|
62
|
-
}
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { clearIdentity, getIdentityStatus } from '../storage/identity.js'
|
|
2
|
-
import type { SlashContext, SlashResult } from './commands.js'
|
|
3
|
-
|
|
4
|
-
export async function runMcp(args: string, ctx: SlashContext): Promise<SlashResult> {
|
|
5
|
-
if (!ctx.mcp) {
|
|
6
|
-
return { kind: 'note', variant: 'error', text: 'MCP runtime is not available in this session.' }
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const tokens = args.trim().split(/\s+/).filter(Boolean)
|
|
10
|
-
const sub = tokens[0]?.toLowerCase() ?? ''
|
|
11
|
-
if (!sub || sub === 'status' || sub === 'list') {
|
|
12
|
-
return { kind: 'note', text: ctx.mcp.renderStatus(), variant: 'info' }
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
if (sub === 'approve') {
|
|
17
|
-
const name = tokens.slice(1).join(' ')
|
|
18
|
-
if (!name) return { kind: 'note', variant: 'error', text: 'usage: /mcp approve <server>' }
|
|
19
|
-
return { kind: 'note', text: await ctx.mcp.approveServer(name), variant: 'dim' }
|
|
20
|
-
}
|
|
21
|
-
if (sub === 'reject') {
|
|
22
|
-
const name = tokens.slice(1).join(' ')
|
|
23
|
-
if (!name) return { kind: 'note', variant: 'error', text: 'usage: /mcp reject <server>' }
|
|
24
|
-
return { kind: 'note', text: await ctx.mcp.rejectServer(name), variant: 'dim' }
|
|
25
|
-
}
|
|
26
|
-
if (sub === 'reconnect') {
|
|
27
|
-
const name = tokens.slice(1).join(' ') || undefined
|
|
28
|
-
return { kind: 'note', text: await ctx.mcp.reconnect(name), variant: 'dim' }
|
|
29
|
-
}
|
|
30
|
-
if (sub === 'enable' || sub === 'disable') {
|
|
31
|
-
const name = tokens.slice(1).join(' ')
|
|
32
|
-
if (!name) return { kind: 'note', variant: 'error', text: `usage: /mcp ${sub} <server>` }
|
|
33
|
-
return { kind: 'note', text: await ctx.mcp.setEnabled(name, sub === 'enable'), variant: 'dim' }
|
|
34
|
-
}
|
|
35
|
-
if (sub === 'add-json') {
|
|
36
|
-
const project = tokens[1] === '--project'
|
|
37
|
-
const nameIndex = project ? 2 : 1
|
|
38
|
-
const name = tokens[nameIndex]
|
|
39
|
-
if (!name) return { kind: 'note', variant: 'error', text: 'usage: /mcp add-json [--project] <name> <json>' }
|
|
40
|
-
const jsonStart = nthTokenStart(args, nameIndex + 1)
|
|
41
|
-
const json = jsonStart >= 0 ? args.slice(jsonStart).trim() : ''
|
|
42
|
-
if (!json) return { kind: 'note', variant: 'error', text: 'usage: /mcp add-json [--project] <name> <json>' }
|
|
43
|
-
return { kind: 'note', text: await ctx.mcp.addJson(name, json, project ? 'project' : 'user'), variant: 'dim' }
|
|
44
|
-
}
|
|
45
|
-
} catch (err: unknown) {
|
|
46
|
-
return { kind: 'note', variant: 'error', text: `MCP failed: ${(err as Error).message}` }
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
kind: 'note',
|
|
51
|
-
variant: 'error',
|
|
52
|
-
text: 'usage: /mcp [status|approve <server>|reject <server>|reconnect [server]|enable <server>|disable <server>|add-json [--project] <name> <json>]',
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export async function runIdentity(args: string, ctx: SlashContext): Promise<SlashResult> {
|
|
57
|
-
const tokens = args.trim().split(/\s+/).filter(Boolean)
|
|
58
|
-
const sub = tokens[0]?.toLowerCase() ?? ''
|
|
59
|
-
const rest = tokens.slice(1)
|
|
60
|
-
|
|
61
|
-
if (!sub) {
|
|
62
|
-
ctx.onIdentityRequest('manage')
|
|
63
|
-
return { kind: 'handled' }
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (sub === 'status') {
|
|
67
|
-
const status = await getIdentityStatus(ctx.config)
|
|
68
|
-
if (!status) {
|
|
69
|
-
return {
|
|
70
|
-
kind: 'note',
|
|
71
|
-
variant: 'dim',
|
|
72
|
-
text: 'No Ethereum identity set. Run /identity create to make one.',
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
const lines = [
|
|
76
|
-
`address ${status.address}`,
|
|
77
|
-
`updated ${status.createdAt}`,
|
|
78
|
-
`wallet ${status.backend}`,
|
|
79
|
-
]
|
|
80
|
-
if (status.source) lines.push(`registry ${status.source}`)
|
|
81
|
-
if (status.agentId) lines.push(`agent #${status.agentId}`)
|
|
82
|
-
return { kind: 'note', text: lines.join('\n') }
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (sub === 'create') {
|
|
86
|
-
ctx.onIdentityRequest('create')
|
|
87
|
-
return { kind: 'handled' }
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (sub === 'load') {
|
|
91
|
-
ctx.onIdentityRequest('load')
|
|
92
|
-
return { kind: 'handled' }
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (sub === 'remove') {
|
|
96
|
-
if (rest[0] !== 'confirm') {
|
|
97
|
-
return {
|
|
98
|
-
kind: 'note',
|
|
99
|
-
variant: 'error',
|
|
100
|
-
text: 'Remove deletes local identity metadata and any legacy stored key. Re-run with: /identity remove confirm',
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
const status = await getIdentityStatus(ctx.config)
|
|
104
|
-
if (!status) {
|
|
105
|
-
return { kind: 'note', variant: 'dim', text: 'No Ethereum identity to remove.' }
|
|
106
|
-
}
|
|
107
|
-
const next = await clearIdentity(ctx.config)
|
|
108
|
-
ctx.onReplaceConfig(next)
|
|
109
|
-
return { kind: 'note', text: `Removed identity ${status.address}.`, variant: 'dim' }
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
kind: 'note',
|
|
114
|
-
variant: 'error',
|
|
115
|
-
text: 'usage: /identity [status|create|load|remove confirm]',
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function nthTokenStart(value: string, tokenIndex: number): number {
|
|
120
|
-
const matches = [...value.matchAll(/\S+/g)]
|
|
121
|
-
return matches[tokenIndex]?.index ?? -1
|
|
122
|
-
}
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import type { EthagentConfig, ProviderId } from '../storage/config.js'
|
|
2
|
-
import { getConfigPath, localProviderBaseUrlFor } from '../storage/config.js'
|
|
3
|
-
import { detectLlamaCpp } from '../models/llamacpp.js'
|
|
4
|
-
import { detectSpec } from '../models/runtimeDetection.js'
|
|
5
|
-
import { getIdentityStatus } from '../storage/identity.js'
|
|
6
|
-
import { getLocalHfCacheDir } from '../models/huggingface.js'
|
|
7
|
-
import { formatModelDisplayName } from '../models/modelDisplay.js'
|
|
8
|
-
import { providerDisplayName } from '../models/modelPickerOptions.js'
|
|
9
|
-
import type { ModelCatalogResult } from '../models/catalog.js'
|
|
10
|
-
import type { SlashContext } from './commands.js'
|
|
11
|
-
|
|
12
|
-
export function renderStatus(ctx: SlashContext): string {
|
|
13
|
-
const elapsedMs = Date.now() - ctx.startedAt
|
|
14
|
-
const minutes = Math.floor(elapsedMs / 60000)
|
|
15
|
-
const seconds = Math.floor((elapsedMs % 60000) / 1000)
|
|
16
|
-
const elapsed = minutes > 0 ? `${minutes}m${seconds.toString().padStart(2, '0')}s` : `${seconds}s`
|
|
17
|
-
const displayModel = formatModelDisplayName(ctx.config.provider, ctx.config.model, { maxLength: 72 })
|
|
18
|
-
return [
|
|
19
|
-
`provider ${providerDisplayName(ctx.config.provider)}`,
|
|
20
|
-
`model ${displayModel}`,
|
|
21
|
-
`cwd ${ctx.cwd}`,
|
|
22
|
-
`session ${ctx.sessionId.slice(0, 8)}`,
|
|
23
|
-
'state active',
|
|
24
|
-
`turns ${ctx.turns}`,
|
|
25
|
-
`tokens ~${ctx.approxTokens}`,
|
|
26
|
-
`context ${ctx.contextUsage.percent}% (~${ctx.contextUsage.usedTokens}/${ctx.contextUsage.windowTokens}, ${ctx.contextUsage.source})`,
|
|
27
|
-
`elapsed ${elapsed}`,
|
|
28
|
-
].join('\n')
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function renderContext(ctx: SlashContext): string {
|
|
32
|
-
const usage = ctx.contextUsage
|
|
33
|
-
const free = Math.max(0, usage.windowTokens - usage.usedTokens)
|
|
34
|
-
const action =
|
|
35
|
-
usage.percent >= 90
|
|
36
|
-
? 'Context is near the model limit. New requests will ask you to summarize into a new conversation, switch models, ignore and send, or cancel.'
|
|
37
|
-
: usage.percent >= 75
|
|
38
|
-
? 'Context is getting full. Consider /compact before a new task boundary.'
|
|
39
|
-
: 'Context has comfortable room.'
|
|
40
|
-
return [
|
|
41
|
-
'context usage:',
|
|
42
|
-
` model ${providerDisplayName(ctx.config.provider)} · ${formatModelDisplayName(ctx.config.provider, ctx.config.model, { maxLength: 72 })}`,
|
|
43
|
-
` used ~${usage.usedTokens} / ${usage.windowTokens} tokens (${usage.percent}%)`,
|
|
44
|
-
` free ~${free} tokens`,
|
|
45
|
-
` estimate ${usage.confidence} (${usage.source})`,
|
|
46
|
-
' session active',
|
|
47
|
-
'',
|
|
48
|
-
action,
|
|
49
|
-
].join('\n')
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function renderDoctor(
|
|
53
|
-
spec: Awaited<ReturnType<typeof detectSpec>>,
|
|
54
|
-
keys: ReadonlyArray<readonly [ProviderId, boolean]>,
|
|
55
|
-
identity: Awaited<ReturnType<typeof getIdentityStatus>>,
|
|
56
|
-
ctx: SlashContext,
|
|
57
|
-
llamaCpp: Awaited<ReturnType<typeof detectLlamaCpp>>,
|
|
58
|
-
hfModelCount: number,
|
|
59
|
-
): string {
|
|
60
|
-
const lines: string[] = ['diagnostics:']
|
|
61
|
-
lines.push(` platform ${spec.platform}/${spec.arch}${spec.isAppleSilicon ? ' (apple silicon)' : ''}`)
|
|
62
|
-
lines.push(` ram ${formatGB(spec.effectiveRamBytes)}${spec.gpuVramBytes ? ` · vram ${formatGB(spec.gpuVramBytes)}` : ''}`)
|
|
63
|
-
lines.push(` local run ${llamaCpp.binaryPresent ? 'installed' : 'not installed'} · server ${llamaCpp.serverUp ? 'up' : 'down'}`)
|
|
64
|
-
lines.push(` hf models ${hfModelCount} downloaded`)
|
|
65
|
-
lines.push('')
|
|
66
|
-
lines.push('config:')
|
|
67
|
-
lines.push(` provider ${providerDisplayName(ctx.config.provider)}`)
|
|
68
|
-
lines.push(` model ${formatModelDisplayName(ctx.config.provider, ctx.config.model, { maxLength: 72 })}`)
|
|
69
|
-
if (ctx.config.baseUrl) lines.push(` baseUrl ${ctx.config.baseUrl}`)
|
|
70
|
-
if (ctx.config.provider === 'llamacpp') lines.push(` hf cache ${getLocalHfCacheDir()}`)
|
|
71
|
-
lines.push(` path ${getConfigPath()}`)
|
|
72
|
-
lines.push('')
|
|
73
|
-
lines.push('keys:')
|
|
74
|
-
for (const [provider, present] of keys) {
|
|
75
|
-
lines.push(` ${providerDisplayName(provider).padEnd(9)} ${present ? 'set' : 'not set'}`)
|
|
76
|
-
}
|
|
77
|
-
lines.push('')
|
|
78
|
-
lines.push('identity:')
|
|
79
|
-
if (identity) {
|
|
80
|
-
lines.push(` address ${identity.address}`)
|
|
81
|
-
lines.push(` backend ${identity.backend}`)
|
|
82
|
-
if (identity.source) lines.push(` source ${identity.source}`)
|
|
83
|
-
if (identity.agentId) lines.push(` token #${identity.agentId}`)
|
|
84
|
-
} else {
|
|
85
|
-
lines.push(' address not set')
|
|
86
|
-
}
|
|
87
|
-
return lines.join('\n')
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function renderModelCatalog(catalog: ModelCatalogResult, currentModel: string): string {
|
|
91
|
-
const title = catalog.status === 'fallback'
|
|
92
|
-
? `${providerDisplayName(catalog.provider)} models (fallback${catalog.error ? `: ${catalog.error}` : ''}):`
|
|
93
|
-
: `${providerDisplayName(catalog.provider)} models:`
|
|
94
|
-
const lines = catalog.entries.map(entry => {
|
|
95
|
-
const marker = entry.id === currentModel ? '*' : ' '
|
|
96
|
-
const suffix = entry.source === 'fallback' ? ' fallback' : ''
|
|
97
|
-
return `${marker} ${formatModelDisplayName(catalog.provider, entry.id, { maxLength: 72 })}${suffix}`
|
|
98
|
-
})
|
|
99
|
-
return [title, ...lines].join('\n')
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function baseUrlForModelSwitch(config: EthagentConfig): string | undefined {
|
|
103
|
-
if (config.provider === 'llamacpp') return localProviderBaseUrlFor('llamacpp', config.baseUrl)
|
|
104
|
-
if (config.provider === 'openai') return config.baseUrl
|
|
105
|
-
return undefined
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function formatBytes(bytes: number): string {
|
|
109
|
-
if (bytes <= 0) return '—'
|
|
110
|
-
const gb = bytes / (1024 * 1024 * 1024)
|
|
111
|
-
if (gb >= 1) return `${gb.toFixed(1)}GB`
|
|
112
|
-
const mb = bytes / (1024 * 1024)
|
|
113
|
-
return `${mb.toFixed(0)}MB`
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function formatGB(bytes: number): string {
|
|
117
|
-
const gb = bytes / (1024 * 1024 * 1024)
|
|
118
|
-
if (gb >= 10) return `${Math.round(gb)}GB`
|
|
119
|
-
return `${gb.toFixed(1)}GB`
|
|
120
|
-
}
|