ethagent 1.0.9 → 1.1.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 +1 -2
- package/src/chat/ChatScreen.tsx +5 -4
- package/src/chat/ContinuityEditReviewView.tsx +3 -3
- package/src/chat/commands.ts +5 -5
- package/src/cli/ResetConfirmView.tsx +12 -13
- package/src/cli/main.tsx +27 -30
- package/src/cli/reset.ts +7 -8
- package/src/cli/updateNotice.ts +52 -0
- package/src/identity/continuity/envelope.ts +11 -5
- package/src/identity/continuity/storage.ts +1 -1
- package/src/identity/hub/IdentityHub.tsx +6 -7
- package/src/identity/hub/identityHubModel.ts +12 -12
- package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +39 -34
- package/src/identity/hub/screens/CreateFlow.tsx +4 -4
- package/src/identity/hub/screens/DetailsScreen.tsx +2 -2
- package/src/identity/hub/screens/EditProfileFlow.tsx +5 -5
- package/src/identity/hub/screens/ErrorScreen.tsx +2 -2
- package/src/identity/hub/screens/IdentitySummary.tsx +32 -12
- package/src/identity/hub/screens/MenuScreen.tsx +17 -17
- package/src/identity/hub/screens/NetworkScreen.tsx +7 -3
- package/src/identity/hub/screens/RecoveryConfirmScreen.tsx +9 -7
- package/src/identity/hub/screens/RestoreFlow.tsx +2 -2
- package/src/identity/hub/screens/StorageCredentialScreen.tsx +5 -5
- package/src/identity/wallet/wallet-page/wallet.html +1095 -966
- package/src/models/ModelPicker.tsx +71 -71
- package/src/models/llamacppPreflight.ts +1 -1
- package/src/models/modelPickerOptions.ts +22 -22
- package/src/storage/factoryReset.ts +17 -20
- package/src/tools/privateContinuityEditTool.ts +1 -1
- package/src/ui/BrandSplash.tsx +7 -4
|
@@ -167,8 +167,8 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
167
167
|
|
|
168
168
|
if (state.kind === 'loading') {
|
|
169
169
|
return (
|
|
170
|
-
<Surface title={contextFit ? 'Switch to Larger-Context Model' : 'Switch Provider
|
|
171
|
-
<Spinner label="
|
|
170
|
+
<Surface title={contextFit ? 'Switch to Larger-Context Model' : 'Switch Provider · Model'} subtitle="Loading providers and models.">
|
|
171
|
+
<Spinner label="Loading providers..." />
|
|
172
172
|
</Surface>
|
|
173
173
|
)
|
|
174
174
|
}
|
|
@@ -178,10 +178,10 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
178
178
|
<Surface
|
|
179
179
|
title="Add Local Model"
|
|
180
180
|
subtitle={LOCAL_MODEL_LINK_EXAMPLE}
|
|
181
|
-
footer="enter
|
|
181
|
+
footer="enter check link · esc back"
|
|
182
182
|
>
|
|
183
183
|
<TextInput
|
|
184
|
-
label="
|
|
184
|
+
label="Model Link"
|
|
185
185
|
placeholder={LOCAL_MODEL_LINK_HINT}
|
|
186
186
|
onSubmit={value => void inspectHfInput(state, value, setState)}
|
|
187
187
|
onCancel={() => setState({ kind: 'list', data: state.data })}
|
|
@@ -194,19 +194,19 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
194
194
|
if (state.kind === 'hfLoading') {
|
|
195
195
|
return (
|
|
196
196
|
<Surface title="Checking Model Link" subtitle={state.input}>
|
|
197
|
-
<Spinner label="
|
|
197
|
+
<Spinner label="Reading model page..." />
|
|
198
198
|
</Surface>
|
|
199
199
|
)
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
if (state.kind === 'hfFilePick') {
|
|
203
203
|
const options = buildHfFileOptions(state.repo, state.files, state.data.machineSpec, state.data.hfModels.map(model => model.id))
|
|
204
|
-
const recommendedIndex = Math.max(0, options.findIndex(option => option.subtext?.includes('
|
|
204
|
+
const recommendedIndex = Math.max(0, options.findIndex(option => option.subtext?.includes('Recommended')))
|
|
205
205
|
return (
|
|
206
206
|
<Surface
|
|
207
207
|
title="Choose a Compatible File"
|
|
208
208
|
subtitle={`${state.repo.repoId} has ${state.files.length} compatible local model file${state.files.length === 1 ? '' : 's'}.`}
|
|
209
|
-
footer="enter
|
|
209
|
+
footer="enter select · esc back"
|
|
210
210
|
>
|
|
211
211
|
<Select
|
|
212
212
|
options={options}
|
|
@@ -228,7 +228,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
228
228
|
<Surface
|
|
229
229
|
title="Review Model Link"
|
|
230
230
|
subtitle="Only download models from creators you trust. Check the license and source before continuing."
|
|
231
|
-
footer="enter
|
|
231
|
+
footer="enter select · esc back"
|
|
232
232
|
tone={plan.review.risk === 'high' ? 'error' : plan.review.risk === 'medium' ? 'muted' : 'primary'}
|
|
233
233
|
>
|
|
234
234
|
<Box flexDirection="column" marginBottom={1}>
|
|
@@ -243,9 +243,9 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
243
243
|
</Box>
|
|
244
244
|
<Select<'download' | 'pick' | 'cancel'>
|
|
245
245
|
options={[
|
|
246
|
-
{ value: 'download', label: '
|
|
247
|
-
{ value: 'pick', label: '
|
|
248
|
-
{ value: 'cancel', label: '
|
|
246
|
+
{ value: 'download', label: 'Download This Model', disabled: !canDownload },
|
|
247
|
+
{ value: 'pick', label: 'Pick Another File' },
|
|
248
|
+
{ value: 'cancel', label: 'Cancel' },
|
|
249
249
|
]}
|
|
250
250
|
onSubmit={choice => {
|
|
251
251
|
if (choice === 'download') void startHfDownload(state, setState, hfAbortRef, onPick)
|
|
@@ -276,12 +276,12 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
276
276
|
<Surface
|
|
277
277
|
title={state.alreadyInstalled ? 'Model Already Downloaded' : 'Model Ready'}
|
|
278
278
|
subtitle={state.model.displayName}
|
|
279
|
-
footer="enter
|
|
279
|
+
footer="enter select · esc back"
|
|
280
280
|
>
|
|
281
281
|
<Select<'use' | 'back'>
|
|
282
282
|
options={[
|
|
283
|
-
{ value: 'use', label: '
|
|
284
|
-
{ value: 'back', label: '
|
|
283
|
+
{ value: 'use', label: 'Use This Model Now' },
|
|
284
|
+
{ value: 'back', label: 'Back To Picker' },
|
|
285
285
|
]}
|
|
286
286
|
onSubmit={choice => {
|
|
287
287
|
if (choice === 'use') void startAndPickHfModel(state.model, state, setState, onPick)
|
|
@@ -295,11 +295,11 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
295
295
|
|
|
296
296
|
if (state.kind === 'hfError') {
|
|
297
297
|
return (
|
|
298
|
-
<Surface title="Model Link Failed" subtitle={state.message} tone="error" footer="enter
|
|
298
|
+
<Surface title="Model Link Failed" subtitle={state.message} tone="error" footer="enter select · esc back">
|
|
299
299
|
<Select<'retry' | 'back'>
|
|
300
300
|
options={[
|
|
301
|
-
{ value: 'retry', label: state.input ? '
|
|
302
|
-
{ value: 'back', label: '
|
|
301
|
+
{ value: 'retry', label: state.input ? 'Retry Link' : 'Download Another Model' },
|
|
302
|
+
{ value: 'back', label: 'Back To Picker' },
|
|
303
303
|
]}
|
|
304
304
|
onSubmit={choice => {
|
|
305
305
|
if (choice === 'retry') setState({ kind: 'hfInput', data: state.data, error: state.input ? undefined : state.message })
|
|
@@ -324,7 +324,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
324
324
|
role: 'option' as const,
|
|
325
325
|
}))
|
|
326
326
|
return (
|
|
327
|
-
<Surface title="Uninstall Downloaded GGUF" subtitle="Choose a downloaded model file to remove." footer="enter
|
|
327
|
+
<Surface title="Uninstall Downloaded GGUF" subtitle="Choose a downloaded model file to remove." footer="enter select · esc back">
|
|
328
328
|
{options.length === 0 ? (
|
|
329
329
|
<Text color={theme.dim}>No local models found.</Text>
|
|
330
330
|
) : (
|
|
@@ -345,15 +345,15 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
345
345
|
if (state.kind === 'localUninstallConfirm') {
|
|
346
346
|
const modelName = state.target.displayName
|
|
347
347
|
return (
|
|
348
|
-
<Surface title="Confirm Uninstall" subtitle={modelName} footer="enter
|
|
348
|
+
<Surface title="Confirm Uninstall" subtitle={modelName} footer="enter select · esc back">
|
|
349
349
|
<Box flexDirection="column" marginBottom={1}>
|
|
350
350
|
<Text color={theme.dim}>{localUninstallBoundaryCopy(state.target)}</Text>
|
|
351
351
|
<Text color={theme.dim}>Runner binaries are left unchanged.</Text>
|
|
352
352
|
</Box>
|
|
353
353
|
<Select<'confirm' | 'back'>
|
|
354
354
|
options={[
|
|
355
|
-
{ value: 'confirm', label: '
|
|
356
|
-
{ value: 'back', label: '
|
|
355
|
+
{ value: 'confirm', label: 'Uninstall Local Model' },
|
|
356
|
+
{ value: 'back', label: 'Back' },
|
|
357
357
|
]}
|
|
358
358
|
onSubmit={choice => {
|
|
359
359
|
if (choice === 'confirm') void uninstallLocalModel(state, setState)
|
|
@@ -378,9 +378,9 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
378
378
|
|
|
379
379
|
if (state.kind === 'localUninstallDone') {
|
|
380
380
|
return (
|
|
381
|
-
<Surface title="Local Model Uninstalled" subtitle={state.modelName} footer="enter
|
|
381
|
+
<Surface title="Local Model Uninstalled" subtitle={state.modelName} footer="enter back to picker · esc close">
|
|
382
382
|
<Select<'back'>
|
|
383
|
-
options={[{ value: 'back', label: '
|
|
383
|
+
options={[{ value: 'back', label: 'Back To Picker' }]}
|
|
384
384
|
onSubmit={() => setState({ kind: 'list', data: state.data })}
|
|
385
385
|
onCancel={() => setState({ kind: 'list', data: state.data })}
|
|
386
386
|
/>
|
|
@@ -390,11 +390,11 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
390
390
|
|
|
391
391
|
if (state.kind === 'localUninstallError') {
|
|
392
392
|
return (
|
|
393
|
-
<Surface title="Could Not Uninstall Local Model" subtitle={state.message} tone="error" footer="enter
|
|
393
|
+
<Surface title="Could Not Uninstall Local Model" subtitle={state.message} tone="error" footer="enter select · esc back">
|
|
394
394
|
<Select<'retry' | 'back'>
|
|
395
395
|
options={[
|
|
396
|
-
{ value: 'retry', label: '
|
|
397
|
-
{ value: 'back', label: '
|
|
396
|
+
{ value: 'retry', label: 'Try Again' },
|
|
397
|
+
{ value: 'back', label: 'Back To Picker' },
|
|
398
398
|
]}
|
|
399
399
|
onSubmit={choice => {
|
|
400
400
|
if (choice === 'retry') void uninstallLocalModel({ kind: 'localUninstallConfirm', data: state.data, target: state.target }, setState)
|
|
@@ -411,7 +411,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
411
411
|
<Surface
|
|
412
412
|
title="Install Local Runner"
|
|
413
413
|
subtitle="This model is downloaded. Install the local runner once to start it here."
|
|
414
|
-
footer="enter
|
|
414
|
+
footer="enter select · esc back"
|
|
415
415
|
>
|
|
416
416
|
<Box flexDirection="column" marginBottom={1}>
|
|
417
417
|
<Text color={theme.dim}>Ethagent tried to start {friendlyFileName(state.model.filename)} automatically.</Text>
|
|
@@ -420,10 +420,10 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
420
420
|
</Box>
|
|
421
421
|
<Select<'install' | 'path' | 'back' | 'download'>
|
|
422
422
|
options={[
|
|
423
|
-
{ value: 'install', label: '
|
|
424
|
-
{ value: 'path', label: '
|
|
425
|
-
{ value: 'back', label: '
|
|
426
|
-
{ value: 'download', label: '
|
|
423
|
+
{ value: 'install', label: 'Install Local Runner' },
|
|
424
|
+
{ value: 'path', label: 'Use Existing Runner Path' },
|
|
425
|
+
{ value: 'back', label: 'Back To Picker' },
|
|
426
|
+
{ value: 'download', label: 'Add Another Local Model' },
|
|
427
427
|
]}
|
|
428
428
|
onSubmit={choice => {
|
|
429
429
|
if (choice === 'download') setState({ kind: 'hfInput', data: state.data })
|
|
@@ -449,7 +449,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
449
449
|
if (state.kind === 'localRunnerInstallFail') {
|
|
450
450
|
const options = buildRunnerRecoveryOptions(state.result)
|
|
451
451
|
return (
|
|
452
|
-
<Surface title="Runner Setup Needs Attention" subtitle={state.result.message} tone="error" footer="enter
|
|
452
|
+
<Surface title="Runner Setup Needs Attention" subtitle={state.result.message} tone="error" footer="enter select · esc back">
|
|
453
453
|
<Select<'retry' | 'build' | 'path' | 'back'>
|
|
454
454
|
options={options}
|
|
455
455
|
onSubmit={choice => {
|
|
@@ -469,7 +469,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
469
469
|
<Surface
|
|
470
470
|
title="Runner Path"
|
|
471
471
|
subtitle="Paste the full path to llama-server."
|
|
472
|
-
footer="enter
|
|
472
|
+
footer="enter save · esc back"
|
|
473
473
|
>
|
|
474
474
|
{state.submitting ? (
|
|
475
475
|
<Spinner label="checking runner path..." />
|
|
@@ -489,20 +489,20 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
489
489
|
if (state.kind === 'localRunnerStarting') {
|
|
490
490
|
return (
|
|
491
491
|
<Surface title="Starting Local Model" subtitle={state.model.displayName}>
|
|
492
|
-
<ElapsedSpinner startedAt={state.startedAt} label="
|
|
492
|
+
<ElapsedSpinner startedAt={state.startedAt} label="Starting local runner" />
|
|
493
493
|
</Surface>
|
|
494
494
|
)
|
|
495
495
|
}
|
|
496
496
|
|
|
497
497
|
if (state.kind === 'localRunnerStartFail') {
|
|
498
498
|
return (
|
|
499
|
-
<Surface title="Local Model Failed to Start" subtitle={localRunnerStartFailureSubtitle(state.result)} tone="error" footer="enter
|
|
499
|
+
<Surface title="Local Model Failed to Start" subtitle={localRunnerStartFailureSubtitle(state.result)} tone="error" footer="enter select · esc back">
|
|
500
500
|
<Select<'retry' | 'path' | 'install' | 'back'>
|
|
501
501
|
options={[
|
|
502
|
-
{ value: 'retry', label: '
|
|
503
|
-
{ value: 'path', label: '
|
|
504
|
-
{ value: 'install', label: '
|
|
505
|
-
{ value: 'back', label: '
|
|
502
|
+
{ value: 'retry', label: 'Try Again' },
|
|
503
|
+
{ value: 'path', label: 'Use Existing Runner Path' },
|
|
504
|
+
{ value: 'install', label: 'Install Local Runner' },
|
|
505
|
+
{ value: 'back', label: 'Back To Picker' },
|
|
506
506
|
]}
|
|
507
507
|
onSubmit={choice => {
|
|
508
508
|
if (choice === 'retry') void startAndPickHfModel(state.model, { kind: 'hfDone', data: state.data, model: state.model }, setState, onPick)
|
|
@@ -522,7 +522,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
522
522
|
<Surface
|
|
523
523
|
title={`${capitalize(action)} ${provider} API Key`}
|
|
524
524
|
subtitle="Stored in your OS keyring when available; never written to config in plaintext."
|
|
525
|
-
footer="enter
|
|
525
|
+
footer="enter save · esc back"
|
|
526
526
|
>
|
|
527
527
|
{submitting ? (
|
|
528
528
|
<Spinner label={`saving ${provider} key...`} />
|
|
@@ -546,16 +546,16 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
546
546
|
<Surface
|
|
547
547
|
title={`${capitalize(provider)} API Key`}
|
|
548
548
|
subtitle="Manage the stored key for this provider."
|
|
549
|
-
footer="enter
|
|
549
|
+
footer="enter select · esc back"
|
|
550
550
|
>
|
|
551
551
|
{submitting ? (
|
|
552
552
|
<Spinner label={`removing ${provider} key...`} />
|
|
553
553
|
) : (
|
|
554
554
|
<Select
|
|
555
555
|
options={[
|
|
556
|
-
{ value: 'edit', label: '
|
|
557
|
-
{ value: 'delete', label: '
|
|
558
|
-
{ value: 'cancel', label: '
|
|
556
|
+
{ value: 'edit', label: 'Replace Stored API Key' },
|
|
557
|
+
{ value: 'delete', label: 'Remove Stored API Key' },
|
|
558
|
+
{ value: 'cancel', label: 'Back' },
|
|
559
559
|
]}
|
|
560
560
|
onSubmit={(value) => {
|
|
561
561
|
if (value === 'edit') {
|
|
@@ -590,7 +590,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
590
590
|
<Surface
|
|
591
591
|
title={`${capitalize(state.provider)} Full Catalog`}
|
|
592
592
|
subtitle={contextFit ? contextFitSubtitle(contextFit) : 'All discovered models for this provider'}
|
|
593
|
-
footer="enter
|
|
593
|
+
footer="enter select · esc back"
|
|
594
594
|
>
|
|
595
595
|
<Select
|
|
596
596
|
options={options}
|
|
@@ -608,20 +608,20 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
608
608
|
|
|
609
609
|
if (state.kind === 'localCatalogLoading') {
|
|
610
610
|
return (
|
|
611
|
-
<Surface title="
|
|
612
|
-
<Spinner label="
|
|
611
|
+
<Surface title="View Full Catalog" subtitle="Loading curated local GGUF files.">
|
|
612
|
+
<Spinner label="Reading Hugging Face files..." />
|
|
613
613
|
</Surface>
|
|
614
614
|
)
|
|
615
615
|
}
|
|
616
616
|
|
|
617
617
|
if (state.kind === 'localCatalogError') {
|
|
618
618
|
return (
|
|
619
|
-
<Surface title="
|
|
619
|
+
<Surface title="Catalog Failed" subtitle={state.message} tone="error" footer="enter select · esc back">
|
|
620
620
|
<Select<'retry' | 'paste' | 'back'>
|
|
621
621
|
options={[
|
|
622
|
-
{ value: 'retry', label: '
|
|
623
|
-
{ value: 'paste', label: '
|
|
624
|
-
{ value: 'back', label: '
|
|
622
|
+
{ value: 'retry', label: 'Retry Catalog' },
|
|
623
|
+
{ value: 'paste', label: 'Paste GGUF Link' },
|
|
624
|
+
{ value: 'back', label: 'Back To Picker' },
|
|
625
625
|
]}
|
|
626
626
|
onSubmit={choice => {
|
|
627
627
|
if (choice === 'retry') void openLocalCatalog(state.data, setState)
|
|
@@ -639,9 +639,9 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
639
639
|
const initialIndex = localModelOptionIndex(options, currentProvider, currentModel)
|
|
640
640
|
return (
|
|
641
641
|
<Surface
|
|
642
|
-
title="
|
|
643
|
-
subtitle="
|
|
644
|
-
footer="enter
|
|
642
|
+
title="View Full Catalog"
|
|
643
|
+
subtitle="Curated local GGUF files with recommendation and install status."
|
|
644
|
+
footer="enter select · esc back"
|
|
645
645
|
>
|
|
646
646
|
<Select
|
|
647
647
|
options={options}
|
|
@@ -660,9 +660,9 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
660
660
|
|
|
661
661
|
return (
|
|
662
662
|
<Surface
|
|
663
|
-
title={contextFit ? 'Switch to Larger-Context Model' : 'Switch Provider
|
|
664
|
-
subtitle={contextFit ? contextFitSubtitle(contextFit) : 'Downloaded GGUF files
|
|
665
|
-
footer="enter
|
|
663
|
+
title={contextFit ? 'Switch to Larger-Context Model' : 'Switch Provider · Model'}
|
|
664
|
+
subtitle={contextFit ? contextFitSubtitle(contextFit) : 'Downloaded GGUF files · cloud providers'}
|
|
665
|
+
footer="enter select · esc close · /models lists installed models"
|
|
666
666
|
>
|
|
667
667
|
<Select
|
|
668
668
|
options={options}
|
|
@@ -798,7 +798,7 @@ function buildCatalogOptions(
|
|
|
798
798
|
if (!catalog || catalog.entries.length === 0) {
|
|
799
799
|
return [{
|
|
800
800
|
value: `hdr:catalog-empty:${provider}`,
|
|
801
|
-
label: '
|
|
801
|
+
label: 'No Models Found',
|
|
802
802
|
disabled: true,
|
|
803
803
|
role: 'notice',
|
|
804
804
|
prefix: 'note',
|
|
@@ -953,8 +953,8 @@ export function buildHfFileOptions(
|
|
|
953
953
|
return ordered.map(item => {
|
|
954
954
|
const size = item.file.sizeBytes ? formatBytes(item.file.sizeBytes) : ''
|
|
955
955
|
const indicators = [
|
|
956
|
-
item.file.filename === recommended ? '
|
|
957
|
-
installed.has(localModelId(repo.repoId, item.file.filename)) ? '
|
|
956
|
+
item.file.filename === recommended ? 'Recommended' : '',
|
|
957
|
+
installed.has(localModelId(repo.repoId, item.file.filename)) ? 'Installed' : '',
|
|
958
958
|
]
|
|
959
959
|
return {
|
|
960
960
|
value: item.file.filename,
|
|
@@ -972,17 +972,17 @@ function buildRunnerRecoveryOptions(
|
|
|
972
972
|
if (result.recovery.includes('source-build')) {
|
|
973
973
|
options.push({
|
|
974
974
|
value: 'build',
|
|
975
|
-
label: '
|
|
976
|
-
hint: '
|
|
975
|
+
label: 'Build Local Runner',
|
|
976
|
+
hint: 'Uses git and CMake if installed',
|
|
977
977
|
})
|
|
978
978
|
}
|
|
979
979
|
if (result.recovery.includes('runner-path')) {
|
|
980
|
-
options.push({ value: 'path', label: '
|
|
980
|
+
options.push({ value: 'path', label: 'Use Existing Runner Path' })
|
|
981
981
|
}
|
|
982
982
|
if (result.recovery.includes('retry-install')) {
|
|
983
|
-
options.push({ value: 'retry', label: '
|
|
983
|
+
options.push({ value: 'retry', label: 'Retry Automatic Install' })
|
|
984
984
|
}
|
|
985
|
-
options.push({ value: 'back', label: '
|
|
985
|
+
options.push({ value: 'back', label: 'Back To Picker' })
|
|
986
986
|
return options
|
|
987
987
|
}
|
|
988
988
|
|
|
@@ -1256,7 +1256,7 @@ async function runRunnerSetup(
|
|
|
1256
1256
|
const startedAt = Date.now()
|
|
1257
1257
|
const initialProgress: LlamaCppInstallProgress = {
|
|
1258
1258
|
phase: 'checking',
|
|
1259
|
-
label: '
|
|
1259
|
+
label: 'Preparing local runner',
|
|
1260
1260
|
progress: 0.04,
|
|
1261
1261
|
}
|
|
1262
1262
|
const updateProgress = (progress: LlamaCppInstallProgress): void => {
|
|
@@ -1373,16 +1373,16 @@ function fitColor(fit: GgufMachineFit): string {
|
|
|
1373
1373
|
}
|
|
1374
1374
|
|
|
1375
1375
|
function fitLabel(fit: GgufMachineFit, recommended: boolean): string {
|
|
1376
|
-
if (recommended && fit !== 'too-large') return '
|
|
1377
|
-
if (recommended) return '
|
|
1376
|
+
if (recommended && fit !== 'too-large') return 'Recommended for this machine'
|
|
1377
|
+
if (recommended) return 'Best match found; may be too large'
|
|
1378
1378
|
return fileFitHint(fit)
|
|
1379
1379
|
}
|
|
1380
1380
|
|
|
1381
1381
|
function fileFitHint(fit: GgufMachineFit): string {
|
|
1382
1382
|
switch (fit) {
|
|
1383
|
-
case 'fits': return '
|
|
1384
|
-
case 'tight': return '
|
|
1385
|
-
case 'too-large': return '
|
|
1383
|
+
case 'fits': return 'Fits this machine'
|
|
1384
|
+
case 'tight': return 'May be slow or tight on memory'
|
|
1385
|
+
case 'too-large': return 'Likely too large for this machine'
|
|
1386
1386
|
case 'unknown': return 'machine fit unknown'
|
|
1387
1387
|
}
|
|
1388
1388
|
}
|
|
@@ -43,7 +43,7 @@ export async function ensureLlamaCppRunnerReady(
|
|
|
43
43
|
message: formatPreflightFailure(
|
|
44
44
|
'local model is not imported',
|
|
45
45
|
config.model,
|
|
46
|
-
'choose an imported
|
|
46
|
+
'choose an imported GGUF model from View Full Catalog or Add Local Model File',
|
|
47
47
|
),
|
|
48
48
|
}
|
|
49
49
|
}
|
|
@@ -11,7 +11,7 @@ import type { UncensoredCatalogEntry } from './uncensoredCatalog.js'
|
|
|
11
11
|
export type CloudProviderId = Exclude<ProviderId, 'llamacpp'>
|
|
12
12
|
|
|
13
13
|
export const MODEL_PICKER_CLOUD_PROVIDERS: CloudProviderId[] = ['openai', 'anthropic', 'gemini']
|
|
14
|
-
export const LOCAL_MODEL_LINK_HINT = '
|
|
14
|
+
export const LOCAL_MODEL_LINK_HINT = 'Paste a GGUF link'
|
|
15
15
|
export const LOCAL_MODEL_LINK_EXAMPLE = 'e.g. https://huggingface.co/Qwen/Qwen3-8B-GGUF'
|
|
16
16
|
|
|
17
17
|
export type LocalHfPickerModel = {
|
|
@@ -57,20 +57,20 @@ export function buildModelPickerOptions(
|
|
|
57
57
|
): SelectOption<string>[] {
|
|
58
58
|
const options: SelectOption<string>[] = []
|
|
59
59
|
|
|
60
|
-
options.push(sectionOption('hdr:local', '
|
|
61
|
-
appendHfModelOptions(options, data, context, '
|
|
62
|
-
options.push(utilityOption('hf:download', '
|
|
63
|
-
options.push(utilityOption('local:catalog', '
|
|
60
|
+
options.push(sectionOption('hdr:local', 'Local Models'))
|
|
61
|
+
appendHfModelOptions(options, data, context, 'Added From Links', 46)
|
|
62
|
+
options.push(utilityOption('hf:download', 'Add Local Model File', LOCAL_MODEL_LINK_HINT))
|
|
63
|
+
options.push(utilityOption('local:catalog', 'View Full Catalog', 'Curated local GGUF files'))
|
|
64
64
|
if (data.hfModels.length > 0) {
|
|
65
|
-
options.push(utilityOption('local:uninstall', '
|
|
65
|
+
options.push(utilityOption('local:uninstall', 'Uninstall Downloaded GGUF'))
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
options.push(sectionOption('hdr:cloud', '
|
|
68
|
+
options.push(sectionOption('hdr:cloud', 'Cloud'))
|
|
69
69
|
for (const provider of MODEL_PICKER_CLOUD_PROVIDERS) {
|
|
70
70
|
options.push(groupOption(`hdr:cloud:${provider}`, provider))
|
|
71
71
|
const keySet = data.cloudKeys[provider] === true
|
|
72
72
|
if (!keySet) {
|
|
73
|
-
options.push(utilityOption(`key:set:${provider}`, '
|
|
73
|
+
options.push(utilityOption(`key:set:${provider}`, 'API Key · Add'))
|
|
74
74
|
continue
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -79,14 +79,14 @@ export function buildModelPickerOptions(
|
|
|
79
79
|
const reason = catalog.error ? ` · ${catalog.error}` : ''
|
|
80
80
|
options.push(noticeOption(
|
|
81
81
|
`hdr:cloud-fallback:${provider}`,
|
|
82
|
-
`
|
|
82
|
+
`Catalog unavailable${reason} · showing configured model`,
|
|
83
83
|
CHILD_INDENT,
|
|
84
84
|
))
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
const models = orderModelsForContextFit(provider, cloudPickerModels(provider, catalog, context), context.contextFit)
|
|
88
88
|
if (models.length === 0) {
|
|
89
|
-
options.push(noticeOption(`hdr:cloud-empty:${provider}`, '
|
|
89
|
+
options.push(noticeOption(`hdr:cloud-empty:${provider}`, 'No selectable models', CHILD_INDENT))
|
|
90
90
|
}
|
|
91
91
|
for (const model of models) {
|
|
92
92
|
const active = context.currentProvider === provider && context.currentModel === model
|
|
@@ -96,8 +96,8 @@ export function buildModelPickerOptions(
|
|
|
96
96
|
contextFitLabel(provider, model, `${displayName}${active ? ' *' : ''}`, context.contextFit),
|
|
97
97
|
))
|
|
98
98
|
}
|
|
99
|
-
options.push(utilityOption(`catalog:${provider}`, '
|
|
100
|
-
options.push(utilityOption(`key:manage:${provider}`, '
|
|
99
|
+
options.push(utilityOption(`catalog:${provider}`, 'Full Catalog'))
|
|
100
|
+
options.push(utilityOption(`key:manage:${provider}`, 'API Key · Manage'))
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
return options
|
|
@@ -109,10 +109,10 @@ export function buildLocalModelCatalogOptions(
|
|
|
109
109
|
catalog: UncensoredCatalogEntry[] = [],
|
|
110
110
|
): SelectOption<string>[] {
|
|
111
111
|
const options: SelectOption<string>[] = []
|
|
112
|
-
options.push(sectionOption('hdr:local-catalog', '
|
|
113
|
-
options.push(groupOption('hdr:uncensored:catalog', '
|
|
112
|
+
options.push(sectionOption('hdr:local-catalog', 'View Full Catalog'))
|
|
113
|
+
options.push(groupOption('hdr:uncensored:catalog', 'Curated Local GGUF Files'))
|
|
114
114
|
if (catalog.length === 0) {
|
|
115
|
-
options.push(noticeOption('hdr:uncensored-empty', '
|
|
115
|
+
options.push(noticeOption('hdr:uncensored-empty', 'Setup files unavailable; paste a GGUF link instead', CHILD_INDENT))
|
|
116
116
|
} else {
|
|
117
117
|
for (const entry of catalog) {
|
|
118
118
|
const id = localModelId(entry.repo.repoId, entry.file.filename)
|
|
@@ -126,18 +126,18 @@ export function buildLocalModelCatalogOptions(
|
|
|
126
126
|
displayName,
|
|
127
127
|
undefined,
|
|
128
128
|
modelMetadataSubtext(`${quant} · ${formatSize(entry.file.sizeBytes ?? 0)}`, [
|
|
129
|
-
entry.recommended ? '
|
|
130
|
-
entry.installed ? '
|
|
129
|
+
entry.recommended ? 'Recommended for this machine' : '',
|
|
130
|
+
entry.installed ? 'Installed' : '',
|
|
131
131
|
]),
|
|
132
132
|
))
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
appendHfModelOptions(options, data, context, '
|
|
137
|
-
options.push(utilityOption('hf:download', '
|
|
136
|
+
appendHfModelOptions(options, data, context, 'Downloaded GGUF', 50)
|
|
137
|
+
options.push(utilityOption('hf:download', 'Add Local Model File', LOCAL_MODEL_LINK_HINT))
|
|
138
138
|
|
|
139
139
|
if (data.hfModels.length > 0) {
|
|
140
|
-
options.push(utilityOption('local:uninstall', '
|
|
140
|
+
options.push(utilityOption('local:uninstall', 'Uninstall Downloaded GGUF'))
|
|
141
141
|
}
|
|
142
142
|
return options
|
|
143
143
|
}
|
|
@@ -151,7 +151,7 @@ function appendHfModelOptions(
|
|
|
151
151
|
): void {
|
|
152
152
|
options.push(groupOption('hdr:local:hf', groupLabel))
|
|
153
153
|
if (data.hfModels.length === 0) {
|
|
154
|
-
options.push(noticeOption('hdr:hf-empty', '
|
|
154
|
+
options.push(noticeOption('hdr:hf-empty', 'No downloaded files', CHILD_INDENT))
|
|
155
155
|
return
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -174,7 +174,7 @@ function appendHfModelOptions(
|
|
|
174
174
|
`hf:${id}`,
|
|
175
175
|
contextFitLabel('llamacpp', id, `${active ? '* ' : ' '}${displayName}`, context.contextFit),
|
|
176
176
|
undefined,
|
|
177
|
-
modelMetadataSubtext(size, ['
|
|
177
|
+
modelMetadataSubtext(size, ['Installed']),
|
|
178
178
|
))
|
|
179
179
|
}
|
|
180
180
|
}
|
|
@@ -48,15 +48,13 @@ export async function createFactoryResetPlan(): Promise<FactoryResetPlan> {
|
|
|
48
48
|
deletePaths,
|
|
49
49
|
preservedPaths,
|
|
50
50
|
preservedDescriptions: [
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'local runner path config local-runner.json',
|
|
55
|
-
'external package-installed runtimes outside ~/.ethagent',
|
|
51
|
+
'Local GGUF models and metadata',
|
|
52
|
+
'llama.cpp runner assets',
|
|
53
|
+
'External package-installed runtimes',
|
|
56
54
|
],
|
|
57
55
|
remoteDescriptions: [
|
|
58
56
|
'ERC-8004 tokens and onchain records',
|
|
59
|
-
'IPFS
|
|
57
|
+
'IPFS snapshots and public metadata',
|
|
60
58
|
],
|
|
61
59
|
}
|
|
62
60
|
}
|
|
@@ -91,18 +89,16 @@ export async function runFactoryReset(options: { clearSecrets?: boolean } = {}):
|
|
|
91
89
|
|
|
92
90
|
export function formatFactoryResetPlan(plan: FactoryResetPlan): string {
|
|
93
91
|
return [
|
|
94
|
-
'
|
|
92
|
+
'Ethagent Reset',
|
|
95
93
|
'',
|
|
96
|
-
'
|
|
97
|
-
...
|
|
94
|
+
'Deletes:',
|
|
95
|
+
...formatDeleteSummary(plan.deletePaths.length),
|
|
98
96
|
'',
|
|
99
|
-
'
|
|
100
|
-
...plan.preservedDescriptions.map(item => `
|
|
97
|
+
'Keeps:',
|
|
98
|
+
...plan.preservedDescriptions.map(item => ` - ${item}`),
|
|
101
99
|
'',
|
|
102
|
-
'
|
|
103
|
-
...plan.remoteDescriptions.map(item => `
|
|
104
|
-
'',
|
|
105
|
-
'type confirm to reset this machine.',
|
|
100
|
+
'Not Touched:',
|
|
101
|
+
...plan.remoteDescriptions.map(item => ` - ${item}`),
|
|
106
102
|
].join('\n')
|
|
107
103
|
}
|
|
108
104
|
|
|
@@ -115,11 +111,12 @@ async function readConfigEntries(configDir: string): Promise<string[]> {
|
|
|
115
111
|
}
|
|
116
112
|
}
|
|
117
113
|
|
|
118
|
-
function
|
|
119
|
-
if (
|
|
120
|
-
return
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
function formatDeleteSummary(count: number): string[] {
|
|
115
|
+
if (count === 0) return [' - No local ethagent data found']
|
|
116
|
+
return [
|
|
117
|
+
' - Identity files, sessions, history, credentials',
|
|
118
|
+
` - ${count} local path${count === 1 ? '' : 's'} under ~/.ethagent`,
|
|
119
|
+
]
|
|
123
120
|
}
|
|
124
121
|
|
|
125
122
|
function assertInsideConfigDir(configDir: string, target: string): void {
|
|
@@ -154,7 +154,7 @@ function formatPrivateContinuityEditResult(file: 'SOUL.md' | 'MEMORY.md', fullPa
|
|
|
154
154
|
`- File: \`identity-vault/${file}\``,
|
|
155
155
|
`- Review file: \`${fullPath}\``,
|
|
156
156
|
'- Open: Identity Hub > Memory and Persona',
|
|
157
|
-
'-
|
|
157
|
+
'- Save: Identity Hub > Recovery > Save Snapshot Now',
|
|
158
158
|
'- History: previous version saved to private identity history; `/rewind` does not restore identity continuity',
|
|
159
159
|
].join('\n')
|
|
160
160
|
}
|
package/src/ui/BrandSplash.tsx
CHANGED
|
@@ -113,10 +113,11 @@ const Eyes = () => {
|
|
|
113
113
|
type SplashProps = {
|
|
114
114
|
contextLine?: string
|
|
115
115
|
tipLine?: string
|
|
116
|
+
updateNotice?: string | null
|
|
116
117
|
compact?: boolean
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
export const BrandSplash: React.FC<SplashProps> = ({ contextLine, tipLine, compact }) => {
|
|
120
|
+
export const BrandSplash: React.FC<SplashProps> = ({ contextLine, tipLine, updateNotice, compact }) => {
|
|
120
121
|
const [width, setWidth] = useState<number>(() => process.stdout.columns ?? 80)
|
|
121
122
|
|
|
122
123
|
useEffect(() => {
|
|
@@ -138,6 +139,7 @@ export const BrandSplash: React.FC<SplashProps> = ({ contextLine, tipLine, compa
|
|
|
138
139
|
<Text color={theme.dim}>{glyphs.tagline.trim()}</Text>
|
|
139
140
|
{contextLine ? <Text color={theme.dim}>{contextLine}</Text> : null}
|
|
140
141
|
{tipLine ? <Text color={theme.dim}>{tipLine}</Text> : null}
|
|
142
|
+
{updateNotice ? <Text color={theme.accentPeach}>{updateNotice}</Text> : null}
|
|
141
143
|
</Box>
|
|
142
144
|
)
|
|
143
145
|
}
|
|
@@ -177,9 +179,10 @@ export const BrandSplash: React.FC<SplashProps> = ({ contextLine, tipLine, compa
|
|
|
177
179
|
) : (
|
|
178
180
|
<Text color={theme.border}>{glyphs.frame.bottomLeft.slice(0, 1) + glyphs.frame.horizontal.repeat(w) + glyphs.frame.bottomRight}</Text>
|
|
179
181
|
)}
|
|
180
|
-
{tipLine ? (
|
|
181
|
-
<Box marginTop={1}>
|
|
182
|
-
<Text color={theme.dim}>{tipLine}</Text>
|
|
182
|
+
{tipLine || updateNotice ? (
|
|
183
|
+
<Box marginTop={1} flexDirection="column">
|
|
184
|
+
{tipLine ? <Text color={theme.dim}>{tipLine}</Text> : null}
|
|
185
|
+
{updateNotice ? <Text color={theme.accentPeach}>{updateNotice}</Text> : null}
|
|
183
186
|
</Box>
|
|
184
187
|
) : null}
|
|
185
188
|
</Box>
|