ethagent 2.4.0 → 3.0.1
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/README.md +7 -4
- package/package.json +2 -1
- package/src/app/FirstRun.tsx +155 -15
- package/src/app/FirstRunTimeline.tsx +4 -0
- package/src/app/input/AppInputProvider.tsx +19 -0
- package/src/app/input/appInputParser.ts +19 -4
- package/src/chat/ChatBottomPane.tsx +3 -1
- package/src/chat/ChatScreen.tsx +7 -1
- package/src/chat/ConversationStack.tsx +25 -19
- package/src/chat/MessageList.tsx +194 -53
- package/src/chat/chatSessionState.ts +1 -1
- package/src/chat/chatTurnOrchestrator.ts +59 -0
- package/src/chat/input/ChatInput.tsx +3 -0
- package/src/chat/input/textCursor.ts +13 -3
- package/src/chat/transcript/TranscriptView.tsx +7 -5
- package/src/chat/transcript/transcriptViewport.ts +88 -17
- package/src/chat/views/PermissionPrompt.tsx +26 -26
- package/src/chat/views/PermissionsView.tsx +18 -12
- package/src/chat/views/RewindView.tsx +3 -1
- package/src/cli/ResetConfirmView.tsx +24 -9
- package/src/identity/continuity/editor.ts +27 -2
- package/src/identity/continuity/envelope.ts +134 -9
- package/src/identity/continuity/publicSkills.ts +54 -1
- package/src/identity/continuity/skills/frontmatter.ts +183 -0
- package/src/identity/continuity/skills/loadSkills.ts +609 -0
- package/src/identity/continuity/skills/publicSkillsSync.ts +32 -0
- package/src/identity/continuity/skills/scaffold.ts +52 -0
- package/src/identity/continuity/skills/types.ts +30 -0
- package/src/identity/continuity/storage/defaults.ts +28 -47
- package/src/identity/continuity/storage/files.ts +1 -0
- package/src/identity/continuity/storage/paths.ts +1 -0
- package/src/identity/continuity/storage/scaffold.ts +25 -23
- package/src/identity/continuity/storage/status.ts +34 -5
- package/src/identity/continuity/storage/types.ts +3 -2
- package/src/identity/continuity/storage.ts +3 -0
- package/src/identity/hub/OperationalRoutes.tsx +79 -5
- package/src/identity/hub/Routes.tsx +5 -3
- package/src/identity/hub/continuity/ContinuityDashboardScreen.tsx +7 -73
- package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +6 -6
- package/src/identity/hub/continuity/SavePromptScreen.tsx +1 -2
- package/src/identity/hub/continuity/effects.ts +36 -5
- package/src/identity/hub/continuity/skills/DeleteSkillConfirmScreen.tsx +112 -0
- package/src/identity/hub/continuity/skills/NewSkillScreen.tsx +57 -0
- package/src/identity/hub/continuity/skills/NewSkillVisibilityScreen.tsx +52 -0
- package/src/identity/hub/continuity/skills/SkillActionsScreen.tsx +151 -0
- package/src/identity/hub/continuity/skills/SkillsTreeScreen.tsx +181 -0
- package/src/identity/hub/continuity/snapshot.ts +3 -0
- package/src/identity/hub/continuity/state.ts +9 -8
- package/src/identity/hub/continuity/vault.ts +42 -10
- package/src/identity/hub/create/CreateFlow.tsx +1 -1
- package/src/identity/hub/custody/CustodyEditFlow.tsx +3 -3
- package/src/identity/hub/custody/routes.tsx +1 -1
- package/src/identity/hub/ens/EnsEditAdvancedScreens.tsx +0 -1
- package/src/identity/hub/ens/EnsEditMaintenanceScreens.tsx +0 -1
- package/src/identity/hub/identityHubReducer.ts +15 -0
- package/src/identity/hub/profile/EditProfileFlow.tsx +5 -5
- package/src/identity/hub/profile/effects.ts +16 -3
- package/src/identity/hub/restore/RestoreFlow.tsx +43 -6
- package/src/identity/hub/restore/apply.ts +12 -1
- package/src/identity/hub/restore/recovery.ts +14 -4
- package/src/identity/hub/restore/resolve.ts +1 -1
- package/src/identity/hub/restore/useRestoreEffects.ts +4 -6
- package/src/identity/hub/shared/components/DetailsScreen.tsx +4 -1
- package/src/identity/hub/shared/components/IdentitySummary.tsx +118 -54
- package/src/identity/hub/shared/components/MenuScreen.tsx +21 -18
- package/src/identity/hub/shared/components/UnlinkedIdentityScreen.tsx +4 -4
- package/src/identity/hub/shared/components/menuFlagsFromReconciliation.ts +8 -12
- package/src/identity/hub/shared/effects/sync.ts +16 -3
- package/src/identity/hub/shared/model/copy.ts +2 -4
- package/src/identity/hub/transfer/effects.ts +15 -2
- package/src/identity/hub/useIdentityHubContinuity.ts +145 -23
- package/src/identity/hub/useIdentityHubController.ts +5 -1
- package/src/identity/hub/useIdentityHubSideEffects.ts +2 -4
- package/src/identity/wallet/page/copy.ts +43 -43
- package/src/mcp/manager.ts +1 -1
- package/src/models/ModelPicker.tsx +89 -84
- package/src/models/llamacpp.ts +160 -11
- package/src/models/llamacppPreflight.ts +1 -16
- package/src/models/modelPickerOptions.ts +45 -37
- package/src/providers/contracts.ts +1 -0
- package/src/providers/openai-chat.ts +50 -9
- package/src/providers/openai-responses.ts +19 -4
- package/src/runtime/toolExecution.ts +4 -3
- package/src/runtime/turn.ts +61 -30
- package/src/tools/changeDirectoryTool.ts +1 -1
- package/src/tools/contracts.ts +10 -0
- package/src/tools/deleteFileTool.ts +1 -1
- package/src/tools/editTool.ts +1 -1
- package/src/tools/listDirectoryTool.ts +1 -1
- package/src/tools/listSkillFilesTool.ts +77 -0
- package/src/tools/listSkillsTool.ts +68 -0
- package/src/tools/mcpResourceTools.ts +2 -2
- package/src/tools/privateContinuityReadTool.ts +1 -1
- package/src/tools/readSkillTool.ts +107 -0
- package/src/tools/readTool.ts +1 -1
- package/src/tools/registry.ts +6 -0
- package/src/tools/writeFileTool.ts +22 -2
- package/src/ui/Spinner.tsx +1 -1
- package/src/identity/continuity/localBackup.ts +0 -249
- package/src/identity/continuity/zipWriter.ts +0 -95
- package/src/identity/hub/continuity/index.ts +0 -7
- package/src/identity/hub/ens/index.ts +0 -11
- package/src/identity/hub/restore/index.ts +0 -22
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { Text } from 'ink'
|
|
2
|
+
import { Box, Text } from 'ink'
|
|
3
3
|
import { Surface } from '../../../ui/Surface.js'
|
|
4
4
|
import { Select } from '../../../ui/Select.js'
|
|
5
5
|
import { TextInput } from '../../../ui/TextInput.js'
|
|
6
|
+
import { Spinner } from '../../../ui/Spinner.js'
|
|
6
7
|
import { theme } from '../../../ui/theme.js'
|
|
7
8
|
import { normalizeErc8004RegistryConfig } from '../../registry/erc8004.js'
|
|
8
9
|
import {
|
|
@@ -18,7 +19,7 @@ import { WalletApprovalScreen } from '../shared/components/WalletApprovalScreen.
|
|
|
18
19
|
import { BusyScreen } from '../shared/components/BusyScreen.js'
|
|
19
20
|
import type { BrowserWalletReady } from '../../wallet/browserWallet.js'
|
|
20
21
|
import type { EthagentConfig } from '../../../storage/config.js'
|
|
21
|
-
import { restoreSignatureRequestForStep } from './
|
|
22
|
+
import { restoreSignatureRequestForStep } from './auth.js'
|
|
22
23
|
import type { RestoreProgress } from '../shared/effects/types.js'
|
|
23
24
|
|
|
24
25
|
type RestoreStep = Exclude<Extract<Step, { kind: `restore-${string}` }>, { kind: 'restore-wallet' | 'restore-network' }>
|
|
@@ -122,11 +123,24 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
|
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
if (step.kind === 'restore-ens-input') {
|
|
126
|
+
if (step.busy) {
|
|
127
|
+
return (
|
|
128
|
+
<Surface
|
|
129
|
+
title={isSwitch ? 'Load Agent' : 'Restore Agent'}
|
|
130
|
+
subtitle="Looking up the agent onchain."
|
|
131
|
+
footer={footerHint('esc cancels')}
|
|
132
|
+
>
|
|
133
|
+
<Box marginTop={1}>
|
|
134
|
+
<Spinner label="looking up ENS name onchain..." />
|
|
135
|
+
</Box>
|
|
136
|
+
</Surface>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
125
139
|
return (
|
|
126
140
|
<Surface
|
|
127
141
|
title={isSwitch ? 'Load Agent' : 'Restore Agent'}
|
|
128
142
|
subtitle="Enter the agent's ENS name to decrypt with an authorized operator wallet."
|
|
129
|
-
footer={footerHint(
|
|
143
|
+
footer={footerHint('enter continue · esc back')}
|
|
130
144
|
>
|
|
131
145
|
<Text color={theme.dim}>The full agent subdomain, e.g. agent.example.eth.</Text>
|
|
132
146
|
<TextInput
|
|
@@ -134,17 +148,35 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
|
|
|
134
148
|
onSubmit={value => onEnsSubmit(value.trim())}
|
|
135
149
|
onCancel={onBack}
|
|
136
150
|
/>
|
|
137
|
-
{step.error ?
|
|
151
|
+
{step.error ? (
|
|
152
|
+
<Box marginTop={1} flexDirection="column">
|
|
153
|
+
<Text color={theme.accentError}>Could not resolve ENS name</Text>
|
|
154
|
+
<Text color={theme.textSubtle}>{step.error}</Text>
|
|
155
|
+
</Box>
|
|
156
|
+
) : null}
|
|
138
157
|
</Surface>
|
|
139
158
|
)
|
|
140
159
|
}
|
|
141
160
|
|
|
142
161
|
if (step.kind === 'restore-token-id-input') {
|
|
162
|
+
if (step.busy) {
|
|
163
|
+
return (
|
|
164
|
+
<Surface
|
|
165
|
+
title={isSwitch ? 'Load Agent' : 'Restore Agent'}
|
|
166
|
+
subtitle="Looking up the agent onchain."
|
|
167
|
+
footer={footerHint('esc cancels')}
|
|
168
|
+
>
|
|
169
|
+
<Box marginTop={1}>
|
|
170
|
+
<Spinner label="looking up token onchain..." />
|
|
171
|
+
</Box>
|
|
172
|
+
</Surface>
|
|
173
|
+
)
|
|
174
|
+
}
|
|
143
175
|
return (
|
|
144
176
|
<Surface
|
|
145
177
|
title={isSwitch ? 'Load Agent' : 'Restore Agent'}
|
|
146
178
|
subtitle={`Enter the ERC-8004 token ID on ${networkLabelForRegistry(step.registry)}.`}
|
|
147
|
-
footer={footerHint(
|
|
179
|
+
footer={footerHint('enter continue · esc back')}
|
|
148
180
|
>
|
|
149
181
|
<Text color={theme.dim}>The integer token ID assigned at mint.</Text>
|
|
150
182
|
<TextInput
|
|
@@ -152,7 +184,12 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
|
|
|
152
184
|
onSubmit={value => onTokenIdSubmit(value.trim())}
|
|
153
185
|
onCancel={onBack}
|
|
154
186
|
/>
|
|
155
|
-
{step.error ?
|
|
187
|
+
{step.error ? (
|
|
188
|
+
<Box marginTop={1} flexDirection="column">
|
|
189
|
+
<Text color={theme.accentError}>Could not resolve token</Text>
|
|
190
|
+
<Text color={theme.textSubtle}>{step.error}</Text>
|
|
191
|
+
</Box>
|
|
192
|
+
) : null}
|
|
156
193
|
</Surface>
|
|
157
194
|
)
|
|
158
195
|
}
|
|
@@ -5,7 +5,12 @@ import {
|
|
|
5
5
|
restoreContinuitySnapshotEnvelope,
|
|
6
6
|
transferSnapshotMetadataFromEnvelope,
|
|
7
7
|
} from '../../continuity/envelope.js'
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
ensureIdentityMarkdownScaffold,
|
|
10
|
+
restoreSkillsTree,
|
|
11
|
+
writeContinuityFiles,
|
|
12
|
+
} from '../../continuity/storage.js'
|
|
13
|
+
import { syncPublicSkillsManifest } from '../../continuity/skills/publicSkillsSync.js'
|
|
9
14
|
import { recordPublishedContinuitySnapshot } from '../../continuity/snapshots.js'
|
|
10
15
|
import { requestBrowserWalletSignature } from '../../wallet/browserWallet.js'
|
|
11
16
|
import { setVaultAddressField } from '../../identityCompat.js'
|
|
@@ -31,6 +36,7 @@ export async function runRestoreAuthorize(
|
|
|
31
36
|
callbacks.onRestoreProgress?.({ phase: 'decrypting', label: 'signature received · decrypting encrypted snapshot...' })
|
|
32
37
|
let restored: ReturnType<typeof restoreAgentStateBackupEnvelope> | ReturnType<typeof restoreContinuitySnapshotEnvelope>
|
|
33
38
|
let continuityFiles: ReturnType<typeof restoreContinuitySnapshotEnvelope>['files'] | undefined
|
|
39
|
+
let continuitySkills: ReturnType<typeof restoreContinuitySnapshotEnvelope>['skills']
|
|
34
40
|
if (isContinuitySnapshotEnvelope(step.envelope)) {
|
|
35
41
|
const payload = restoreContinuitySnapshotEnvelope({
|
|
36
42
|
envelope: step.envelope,
|
|
@@ -39,6 +45,7 @@ export async function runRestoreAuthorize(
|
|
|
39
45
|
})
|
|
40
46
|
restored = payload
|
|
41
47
|
continuityFiles = payload.files
|
|
48
|
+
continuitySkills = payload.skills
|
|
42
49
|
} else {
|
|
43
50
|
restored = restoreAgentStateBackupEnvelope({
|
|
44
51
|
envelope: step.envelope,
|
|
@@ -104,9 +111,13 @@ export async function runRestoreAuthorize(
|
|
|
104
111
|
if (continuityFiles) {
|
|
105
112
|
await writeContinuityFiles(nextIdentity, continuityFiles)
|
|
106
113
|
}
|
|
114
|
+
if (continuitySkills) {
|
|
115
|
+
await restoreSkillsTree(nextIdentity, continuitySkills)
|
|
116
|
+
}
|
|
107
117
|
callbacks.onRestoreProgress?.({ phase: 'finishing', label: 'finalizing restored identity...' })
|
|
108
118
|
await restorePublishedPublicSkills(nextIdentity, step.apiUrl, step.candidate.publicDiscovery?.skillsCid)
|
|
109
119
|
await ensureIdentityMarkdownScaffold(nextIdentity)
|
|
120
|
+
await syncPublicSkillsManifest(nextIdentity).catch(() => null)
|
|
110
121
|
await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'restored from agent backup' }).catch(() => null)
|
|
111
122
|
await callbacks.onIdentityComplete(nextIdentity, `ERC-8004 agent restored · #${step.candidate.agentId.toString()}`, 'restore')
|
|
112
123
|
}
|
|
@@ -6,7 +6,13 @@ import {
|
|
|
6
6
|
restoreContinuitySnapshotEnvelope,
|
|
7
7
|
transferSnapshotMetadataFromEnvelope,
|
|
8
8
|
} from '../../continuity/envelope.js'
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
ensureIdentityMarkdownScaffold,
|
|
11
|
+
localContinuitySnapshotContentHashes,
|
|
12
|
+
restoreSkillsTree,
|
|
13
|
+
writeContinuityFiles,
|
|
14
|
+
} from '../../continuity/storage.js'
|
|
15
|
+
import { syncPublicSkillsManifest } from '../../continuity/skills/publicSkillsSync.js'
|
|
10
16
|
import { recordPublishedContinuitySnapshot, updatePublishedContinuitySnapshotContentHashes } from '../../continuity/snapshots.js'
|
|
11
17
|
import { catFromIpfs, DEFAULT_IPFS_API_URL } from '../../storage/ipfs.js'
|
|
12
18
|
import {
|
|
@@ -72,7 +78,7 @@ export async function runRecoveryRefetch(
|
|
|
72
78
|
walletSignature: wallet.signature,
|
|
73
79
|
currentOwnerAddress: getAddress(wallet.account),
|
|
74
80
|
})
|
|
75
|
-
callbacks.onRestoreProgress?.({ phase: 'writing', label: 'restoring SOUL.md, MEMORY.md, and skills
|
|
81
|
+
callbacks.onRestoreProgress?.({ phase: 'writing', label: 'restoring SOUL.md, MEMORY.md, and skills...' })
|
|
76
82
|
const transferSnapshot = transferSnapshotMetadataFromEnvelope(envelope)
|
|
77
83
|
const refreshedBackup: BackupMetadata = {
|
|
78
84
|
cid: candidate.backup.cid,
|
|
@@ -123,13 +129,17 @@ export async function runRecoveryRefetch(
|
|
|
123
129
|
} : {}),
|
|
124
130
|
}
|
|
125
131
|
await writeContinuityFiles(nextIdentity, payload.files)
|
|
132
|
+
if (payload.skills) {
|
|
133
|
+
await restoreSkillsTree(nextIdentity, payload.skills)
|
|
134
|
+
}
|
|
126
135
|
callbacks.onRestoreProgress?.({ phase: 'finishing', label: 'finalizing refreshed identity...' })
|
|
127
136
|
const publicSkillsRestored = await restorePublishedPublicSkills(nextIdentity, apiUrl, candidate.publicDiscovery?.skillsCid)
|
|
128
137
|
await ensureIdentityMarkdownScaffold(nextIdentity)
|
|
129
|
-
await
|
|
138
|
+
await syncPublicSkillsManifest(nextIdentity).catch(() => null)
|
|
139
|
+
await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'Refetched Latest Snapshot From Onchain' }).catch(() => null)
|
|
130
140
|
if (publicSkillsRestored) {
|
|
131
141
|
const contentHashes = await localContinuitySnapshotContentHashes(nextIdentity)
|
|
132
142
|
await updatePublishedContinuitySnapshotContentHashes(nextIdentity, candidate.backup.cid, contentHashes).catch(() => null)
|
|
133
143
|
}
|
|
134
|
-
await callbacks.onIdentityComplete(nextIdentity, 'Latest Published Snapshot Restored From
|
|
144
|
+
await callbacks.onIdentityComplete(nextIdentity, 'Latest Published Snapshot Restored From Onchain', 'update')
|
|
135
145
|
}
|
|
@@ -29,7 +29,7 @@ export async function resolveAgentEnsToCandidate(
|
|
|
29
29
|
return { ok: false, message: `Could not reach Ethereum mainnet to resolve ${trimmed}: ${err instanceof Error ? err.message : String(err)}` }
|
|
30
30
|
}
|
|
31
31
|
const tokenValue = records[AGENT_RECORD_KEYS.token]
|
|
32
|
-
if (!tokenValue) return { ok: false, message: `${trimmed} has no org.ethagent.token record
|
|
32
|
+
if (!tokenValue) return { ok: false, message: `${trimmed} has no org.ethagent.token record.` }
|
|
33
33
|
const tokenRef = parseAgentTokenReference(tokenValue)
|
|
34
34
|
if (!tokenRef) return { ok: false, message: `${trimmed}'s org.ethagent.token record is not a valid eip155 reference.` }
|
|
35
35
|
if (tokenRef.chainId !== registry.chainId) {
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { useEffect } from 'react'
|
|
2
2
|
import type { EthagentConfig } from '../../../storage/config.js'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
runRestoreFetch,
|
|
8
|
-
} from './index.js'
|
|
3
|
+
import { runRestoreAuthorize } from './apply.js'
|
|
4
|
+
import { runRestoreConnectWallet } from './auth.js'
|
|
5
|
+
import { runRestoreDiscover } from './discover.js'
|
|
6
|
+
import { runRestoreFetch } from './fetch.js'
|
|
9
7
|
import type { EffectCallbacks } from '../shared/effects/types.js'
|
|
10
8
|
import type { Step } from '../identityHubReducer.js'
|
|
11
9
|
|
|
@@ -6,12 +6,14 @@ import { Select, type SelectOption } from '../../../../ui/Select.js'
|
|
|
6
6
|
import type { EthagentConfig, EthagentIdentity } from '../../../../storage/config.js'
|
|
7
7
|
import { copyableIdentityFields, identityValuesCopyHint } from '../model/copy.js'
|
|
8
8
|
import { IdentitySummary } from './IdentitySummary.js'
|
|
9
|
+
import type { ContinuityWorkingTreeStatus } from '../../../continuity/storage.js'
|
|
9
10
|
|
|
10
11
|
type CopyAction = `copy:${string}` | 'back'
|
|
11
12
|
|
|
12
13
|
type DetailsScreenProps = {
|
|
13
14
|
identity?: EthagentIdentity
|
|
14
15
|
config?: EthagentConfig
|
|
16
|
+
workingStatus?: ContinuityWorkingTreeStatus | null
|
|
15
17
|
copyNotice?: string | null
|
|
16
18
|
unlinked?: boolean
|
|
17
19
|
onchainOwner?: string
|
|
@@ -23,6 +25,7 @@ type DetailsScreenProps = {
|
|
|
23
25
|
export const DetailsScreen: React.FC<DetailsScreenProps> = ({
|
|
24
26
|
identity,
|
|
25
27
|
config,
|
|
28
|
+
workingStatus,
|
|
26
29
|
copyNotice,
|
|
27
30
|
unlinked,
|
|
28
31
|
onchainOwner,
|
|
@@ -45,7 +48,7 @@ export const DetailsScreen: React.FC<DetailsScreenProps> = ({
|
|
|
45
48
|
|
|
46
49
|
return (
|
|
47
50
|
<Surface title="Token Values" subtitle={unlinked ? 'Token no longer linked to this wallet, values retained for reference.' : `${identityValuesCopyHint(identity)}.`} footer={footer}>
|
|
48
|
-
<IdentitySummary identity={identity} config={config} onchainOwner={onchainOwner} />
|
|
51
|
+
<IdentitySummary identity={identity} config={config} workingStatus={workingStatus} onchainOwner={onchainOwner} />
|
|
49
52
|
{copyNotice ? <Text color={theme.accentPeriwinkle} bold>{copyNotice}</Text> : null}
|
|
50
53
|
<Box marginTop={1}>
|
|
51
54
|
<Select<CopyAction>
|
|
@@ -28,9 +28,10 @@ interface IdentitySummaryProps {
|
|
|
28
28
|
hideHeader?: boolean
|
|
29
29
|
tokenLinked?: boolean
|
|
30
30
|
onchainOwner?: string
|
|
31
|
+
compact?: boolean
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
export const IdentitySummary: React.FC<IdentitySummaryProps> = ({ identity, config, workingStatus, hideLocalChanges = false, hideHeader = false, tokenLinked = true, onchainOwner }) => {
|
|
34
|
+
export const IdentitySummary: React.FC<IdentitySummaryProps> = ({ identity, config, workingStatus, hideLocalChanges = false, hideHeader = false, tokenLinked = true, onchainOwner, compact = false }) => {
|
|
34
35
|
if (!identity) {
|
|
35
36
|
return (
|
|
36
37
|
<Text color={theme.dim}>No agent yet. Create or load one.</Text>
|
|
@@ -59,6 +60,25 @@ export const IdentitySummary: React.FC<IdentitySummaryProps> = ({ identity, conf
|
|
|
59
60
|
? `${tokenValue} · ${displayValue(networkValue)}`
|
|
60
61
|
: displayValue(tokenValue)
|
|
61
62
|
|
|
63
|
+
if (compact) {
|
|
64
|
+
const name = stateName || 'Active Agent'
|
|
65
|
+
const tokenSegment = identity.agentId ? `#${identity.agentId}` : null
|
|
66
|
+
const networkSegment = identity.agentId ? displayValue(networkValue) : null
|
|
67
|
+
const ensSegment = ensStatus.kind === 'linked'
|
|
68
|
+
? ensStatus.name
|
|
69
|
+
: ensStatus.kind === 'issue'
|
|
70
|
+
? ensStatus.name
|
|
71
|
+
: null
|
|
72
|
+
return (
|
|
73
|
+
<Text>
|
|
74
|
+
<Text color={theme.accentPeriwinkle} bold>{name}</Text>
|
|
75
|
+
{tokenSegment ? <><Text color={theme.dim}> · </Text><Text color={theme.text}>{tokenSegment}</Text></> : null}
|
|
76
|
+
{networkSegment ? <><Text color={theme.dim}> · </Text><Text color={theme.text}>{networkSegment}</Text></> : null}
|
|
77
|
+
{ensSegment ? <><Text color={theme.dim}> · </Text><Text color={ensStatus.kind === 'issue' ? theme.accentError : theme.accentPeriwinkle}>{ensSegment}</Text></> : null}
|
|
78
|
+
</Text>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
62
82
|
return (
|
|
63
83
|
<Box flexDirection="column">
|
|
64
84
|
{hideHeader ? null : (
|
|
@@ -67,59 +87,67 @@ export const IdentitySummary: React.FC<IdentitySummaryProps> = ({ identity, conf
|
|
|
67
87
|
<Text color={identity.agentId ? theme.text : theme.dim} bold={Boolean(identity.agentId)}>{tokenLine}</Text>
|
|
68
88
|
</>
|
|
69
89
|
)}
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
<Text color={theme.dim}>{'Owner'.padEnd(12)}</Text>
|
|
87
|
-
<Text color={theme.text}>{shortAddress(ownerAddress)}</Text>
|
|
88
|
-
</Text>
|
|
89
|
-
) : null}
|
|
90
|
+
<SummaryRow
|
|
91
|
+
left={{
|
|
92
|
+
label: 'ENS',
|
|
93
|
+
value: ensStatus.kind === 'linked'
|
|
94
|
+
? <Text color={theme.accentPeriwinkle}>{ensStatus.name}</Text>
|
|
95
|
+
: ensStatus.kind === 'issue'
|
|
96
|
+
? <Text color={theme.accentError}>{ensStatus.name} ({ensValidationReasonText(ensStatus.reason)})</Text>
|
|
97
|
+
: <Text color={theme.dim}>Not Linked</Text>,
|
|
98
|
+
}}
|
|
99
|
+
right={tokenLinked
|
|
100
|
+
? {
|
|
101
|
+
label: 'Custody',
|
|
102
|
+
value: <Text color={custodyMode ? theme.text : theme.dim}>{displayCustodyMode(custodyMode)}</Text>,
|
|
103
|
+
}
|
|
104
|
+
: undefined}
|
|
105
|
+
/>
|
|
90
106
|
{(() => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
107
|
+
const vaultAddress = custodyMode === 'advanced'
|
|
108
|
+
? readIdentityStateString(identity.state, 'operatorVaultAddress')
|
|
109
|
+
: undefined
|
|
110
|
+
const pairedOperatorsValue = custodyMode === 'advanced' && tokenLinked
|
|
111
|
+
? approvedOperatorCount > 1
|
|
112
|
+
? <Text color={theme.text}>{`${approvedOperatorCount} authorized`}</Text>
|
|
113
|
+
: activeOperator
|
|
114
|
+
? <Text color={theme.text}>{shortAddress(activeOperator)}</Text>
|
|
115
|
+
: <Text color={theme.dim}>None Authorized</Text>
|
|
116
|
+
: null
|
|
117
|
+
const lastSavedCell = {
|
|
118
|
+
label: 'Last Saved',
|
|
119
|
+
value: <Text color={lastBackup === 'never' ? theme.dim : theme.text}>{displayValue(lastBackup)}</Text>,
|
|
120
|
+
}
|
|
121
|
+
const pendingCell = {
|
|
122
|
+
label: 'Pending',
|
|
123
|
+
value: <Text color={theme.dim}>local ahead of onchain, owner rotates pointer</Text>,
|
|
124
|
+
}
|
|
94
125
|
return (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
126
|
+
<>
|
|
127
|
+
{ownerAddress ? (
|
|
128
|
+
<SummaryRow
|
|
129
|
+
left={{
|
|
130
|
+
label: 'Owner',
|
|
131
|
+
value: <Text color={theme.text}>{shortAddress(ownerAddress)}</Text>,
|
|
132
|
+
}}
|
|
133
|
+
{...(pairedOperatorsValue
|
|
134
|
+
? { right: { label: 'Operators', value: pairedOperatorsValue } }
|
|
135
|
+
: {})}
|
|
136
|
+
/>
|
|
137
|
+
) : null}
|
|
138
|
+
{vaultAddress ? (
|
|
139
|
+
<SummaryRow
|
|
140
|
+
left={{ label: 'Vault', value: <Text color={theme.text}>{shortAddress(vaultAddress)}</Text> }}
|
|
141
|
+
right={hasPendingPublish(identity) ? pendingCell : lastSavedCell}
|
|
142
|
+
/>
|
|
143
|
+
) : (
|
|
144
|
+
hasPendingPublish(identity)
|
|
145
|
+
? <SummaryRow left={lastSavedCell} right={pendingCell} />
|
|
146
|
+
: <SummaryRow left={lastSavedCell} />
|
|
147
|
+
)}
|
|
148
|
+
</>
|
|
99
149
|
)
|
|
100
150
|
})()}
|
|
101
|
-
{tokenLinked && custodyMode === 'advanced' ? (
|
|
102
|
-
<Text>
|
|
103
|
-
<Text color={theme.dim}>{'Operators'.padEnd(12)}</Text>
|
|
104
|
-
{approvedOperatorCount > 1 ? (
|
|
105
|
-
<Text color={theme.text}>{`${approvedOperatorCount} authorized${activeOperator ? ` (active ${shortAddress(activeOperator)})` : ''}`}</Text>
|
|
106
|
-
) : activeOperator ? (
|
|
107
|
-
<Text color={theme.text}>{shortAddress(activeOperator)}</Text>
|
|
108
|
-
) : (
|
|
109
|
-
<Text color={theme.dim}>None Authorized</Text>
|
|
110
|
-
)}
|
|
111
|
-
</Text>
|
|
112
|
-
) : null}
|
|
113
|
-
<Text>
|
|
114
|
-
<Text color={theme.dim}>{'Last Saved'.padEnd(12)}</Text>
|
|
115
|
-
<Text color={lastBackup === 'never' ? theme.dim : theme.text}>{displayValue(lastBackup)}</Text>
|
|
116
|
-
</Text>
|
|
117
|
-
{hasPendingPublish(identity) ? (
|
|
118
|
-
<Text>
|
|
119
|
-
<Text color={theme.dim}>{'Pending'.padEnd(12)}</Text>
|
|
120
|
-
<Text color={theme.dim}>local snapshot ahead of chain, owner wallet rotates the pointer</Text>
|
|
121
|
-
</Text>
|
|
122
|
-
) : null}
|
|
123
151
|
{transferSnapshot ? (
|
|
124
152
|
<Box marginTop={1}>
|
|
125
153
|
<TransferSnapshotStatus status={transferSnapshot} />
|
|
@@ -134,6 +162,39 @@ export const IdentitySummary: React.FC<IdentitySummaryProps> = ({ identity, conf
|
|
|
134
162
|
)
|
|
135
163
|
}
|
|
136
164
|
|
|
165
|
+
type SummaryCell = { label: string; value: React.ReactNode }
|
|
166
|
+
|
|
167
|
+
const LEFT_LABEL_WIDTH = 12
|
|
168
|
+
const LEFT_VALUE_WIDTH = 30
|
|
169
|
+
const RIGHT_CELL_WIDTH = 36
|
|
170
|
+
|
|
171
|
+
const SummaryRow: React.FC<{ left: SummaryCell; right?: SummaryCell }> = ({ left, right }) => {
|
|
172
|
+
if (!right) {
|
|
173
|
+
return (
|
|
174
|
+
<Text>
|
|
175
|
+
<Text color={theme.dim}>{left.label.padEnd(LEFT_LABEL_WIDTH)}</Text>
|
|
176
|
+
{left.value}
|
|
177
|
+
</Text>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
return (
|
|
181
|
+
<Box flexDirection="row">
|
|
182
|
+
<Box width={LEFT_LABEL_WIDTH + LEFT_VALUE_WIDTH}>
|
|
183
|
+
<Text>
|
|
184
|
+
<Text color={theme.dim}>{left.label.padEnd(LEFT_LABEL_WIDTH)}</Text>
|
|
185
|
+
{left.value}
|
|
186
|
+
</Text>
|
|
187
|
+
</Box>
|
|
188
|
+
<Box width={RIGHT_CELL_WIDTH}>
|
|
189
|
+
<Text>
|
|
190
|
+
<Text color={theme.dim}>{right.label.padEnd(LEFT_LABEL_WIDTH)}</Text>
|
|
191
|
+
{right.value}
|
|
192
|
+
</Text>
|
|
193
|
+
</Box>
|
|
194
|
+
</Box>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
137
198
|
const TransferSnapshotStatus: React.FC<{ status: NonNullable<TransferSnapshotView> }> = ({ status }) => {
|
|
138
199
|
const receiverLabel = status.receiverHandle && status.receiverHandle !== status.receiver
|
|
139
200
|
? `${shortAddress(status.receiver)} (${status.receiverHandle})`
|
|
@@ -163,10 +224,13 @@ const TransferSnapshotStatus: React.FC<{ status: NonNullable<TransferSnapshotVie
|
|
|
163
224
|
const LocalChangeStatusLine: React.FC<{ status: LocalChangeStatusView }> = ({ status }) => {
|
|
164
225
|
if (status.hasLocalChanges) {
|
|
165
226
|
return (
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
227
|
+
<Box flexDirection="column">
|
|
228
|
+
<Text color={theme.accentError} bold>
|
|
229
|
+
Local changes detected
|
|
230
|
+
{status.files.length > 0 ? `: ${status.files.join(', ')}` : ''}
|
|
231
|
+
</Text>
|
|
232
|
+
<Text color={theme.dim}>Save Snapshot Now to publish.</Text>
|
|
233
|
+
</Box>
|
|
170
234
|
)
|
|
171
235
|
}
|
|
172
236
|
|
|
@@ -28,6 +28,7 @@ type MenuScreenProps = {
|
|
|
28
28
|
onEnsName: () => void
|
|
29
29
|
onWalletSetup: () => void
|
|
30
30
|
onContinuity: () => void
|
|
31
|
+
onSkillsTree: () => void
|
|
31
32
|
onIdentityValues: () => void
|
|
32
33
|
onPrepareTransfer: () => void
|
|
33
34
|
onStorage: () => void
|
|
@@ -40,6 +41,7 @@ type Action =
|
|
|
40
41
|
| 'ens-name'
|
|
41
42
|
| 'wallet-setup'
|
|
42
43
|
| 'continuity'
|
|
44
|
+
| 'skills-tree'
|
|
43
45
|
| 'backup'
|
|
44
46
|
| 'refetch'
|
|
45
47
|
| 'identity-values'
|
|
@@ -66,6 +68,7 @@ export const MenuScreen: React.FC<MenuScreenProps> = ({
|
|
|
66
68
|
onEnsName,
|
|
67
69
|
onWalletSetup,
|
|
68
70
|
onContinuity,
|
|
71
|
+
onSkillsTree,
|
|
69
72
|
onIdentityValues,
|
|
70
73
|
onPrepareTransfer,
|
|
71
74
|
onStorage,
|
|
@@ -91,47 +94,46 @@ export const MenuScreen: React.FC<MenuScreenProps> = ({
|
|
|
91
94
|
: null)
|
|
92
95
|
|
|
93
96
|
const walletSetupBaseHint = custodyMode === 'advanced'
|
|
94
|
-
? '
|
|
95
|
-
: 'Simple
|
|
97
|
+
? 'Owner wallet, vault, operators'
|
|
98
|
+
: 'Simple mode, switch for vault delegation'
|
|
96
99
|
|
|
97
100
|
const walletSetupLabel = flags?.custodyAsterisk ? 'Custody Mode *' : 'Custody Mode'
|
|
98
101
|
const walletSetupHint = flags?.custodyModeReason ?? flags?.custodyHint ?? walletSetupBaseHint
|
|
99
102
|
|
|
100
|
-
const saveSnapshotLabel = flags?.saveSnapshotAsterisk ? 'Save Snapshot
|
|
101
|
-
const saveSnapshotHint = flags?.saveSnapshotHint ?? '
|
|
103
|
+
const saveSnapshotLabel = flags?.saveSnapshotAsterisk ? 'Save Snapshot *' : 'Save Snapshot'
|
|
104
|
+
const saveSnapshotHint = flags?.saveSnapshotHint ?? 'Publish encrypted snapshot'
|
|
102
105
|
|
|
103
|
-
const ensNameHint = flags?.ensNameReason ?? 'Public name or subdomain
|
|
106
|
+
const ensNameHint = flags?.ensNameReason ?? 'Public name or subdomain'
|
|
104
107
|
|
|
105
|
-
const prepareTransferHint = flags?.prepareTransferReason ?? '
|
|
108
|
+
const prepareTransferHint = flags?.prepareTransferReason ?? 'Hand off this agent'
|
|
106
109
|
|
|
107
110
|
const tokenValuesHint = flags?.tokenValuesUnlinkedNote ?? identityValuesCopyHint(identity)
|
|
108
111
|
|
|
109
112
|
const options: Array<SelectOption<Action>> = identity
|
|
110
113
|
? [
|
|
111
114
|
{ value: 'public-profile', role: 'section', label: 'Public Identity' },
|
|
112
|
-
{ value: 'public-profile', label: 'Public Profile', hint: '
|
|
115
|
+
{ value: 'public-profile', label: 'Public Profile', hint: 'Agent card and profile fields' },
|
|
113
116
|
{ value: 'ens-name', label: 'ENS Name', hint: ensNameHint, disabled: flags?.ensNameDisabled ?? false },
|
|
114
117
|
{ value: 'continuity', role: 'section', label: 'Continuity' },
|
|
115
|
-
{ value: 'continuity', label: 'Soul
|
|
118
|
+
{ value: 'continuity', label: 'Soul & Memory', hint: 'Edit SOUL.md and MEMORY.md' },
|
|
119
|
+
{ value: 'skills-tree', label: 'Skills', hint: 'Browse and edit SKILL.md files' },
|
|
116
120
|
{ value: 'backup', label: saveSnapshotLabel, hint: saveSnapshotHint, disabled: !canRebackup || (flags?.saveSnapshotDisabled ?? false) },
|
|
117
|
-
{ value: 'refetch', label: 'Refetch
|
|
121
|
+
{ value: 'refetch', label: 'Refetch Snapshot', hint: 'Restore latest snapshot', disabled: !canRefetch || (flags?.refetchLatestDisabled ?? false) },
|
|
118
122
|
{ value: 'wallet-setup', role: 'section', label: 'Custody' },
|
|
119
123
|
{ value: 'wallet-setup', label: walletSetupLabel, hint: walletSetupHint, disabled: !identity.agentId || (flags?.custodyModeDisabled ?? false) },
|
|
120
124
|
{ value: 'prepare-transfer', label: 'Prepare Transfer', hint: prepareTransferHint, disabled: flags?.prepareTransferDisabled ?? false },
|
|
121
125
|
{ value: 'identity-values', role: 'section', label: 'Token' },
|
|
122
126
|
{ value: 'identity-values', label: 'Token Values', hint: tokenValuesHint },
|
|
123
|
-
{ value: 'load', label: 'Load Agent', hint: 'Refresh
|
|
124
|
-
{ value: 'create', label: 'New Agent', hint: 'Mint another
|
|
125
|
-
{ value: 'storage',
|
|
126
|
-
{ value: 'storage', label: 'IPFS Storage', hint: 'Publishing credentials for encrypted snapshots' },
|
|
127
|
+
{ value: 'load', label: 'Load Agent', hint: 'Refresh or load another agent' },
|
|
128
|
+
{ value: 'create', label: 'New Agent', hint: 'Mint another agent' },
|
|
129
|
+
{ value: 'storage', label: 'IPFS Storage', hint: 'Publishing credentials' },
|
|
127
130
|
{ value: 'cancel', role: 'section', label: 'Exit' },
|
|
128
|
-
{ value: 'cancel', label: 'Close Identity Hub', hint: 'Return to chat
|
|
131
|
+
{ value: 'cancel', label: 'Close Identity Hub', hint: 'Return to chat', role: 'utility' },
|
|
129
132
|
]
|
|
130
133
|
: [
|
|
131
134
|
{ value: 'create', role: 'section', label: 'Setup' },
|
|
132
135
|
{ value: 'create', label: 'Create New Agent', hint: 'Mint a wallet-owned token for this machine' },
|
|
133
136
|
{ value: 'load', label: 'Load Existing Agent', hint: 'Find a token owned by this wallet or linked to it' },
|
|
134
|
-
{ value: mode === 'first-run' ? 'skip' : 'cancel', role: 'section', label: 'Exit' },
|
|
135
137
|
...(mode === 'first-run'
|
|
136
138
|
? [
|
|
137
139
|
{ value: 'skip' as Action, label: 'Skip For Now', hint: 'Continue now, use /identity later', role: 'utility' as const },
|
|
@@ -170,6 +172,7 @@ export const MenuScreen: React.FC<MenuScreenProps> = ({
|
|
|
170
172
|
if (choice === 'ens-name') return onEnsName()
|
|
171
173
|
if (choice === 'wallet-setup') return onWalletSetup()
|
|
172
174
|
if (choice === 'continuity') return onContinuity()
|
|
175
|
+
if (choice === 'skills-tree') return onSkillsTree()
|
|
173
176
|
if (choice === 'backup') return onBackupNow()
|
|
174
177
|
if (choice === 'refetch') return onRefetchLatest()
|
|
175
178
|
if (choice === 'identity-values') return onIdentityValues()
|
|
@@ -194,7 +197,7 @@ function renderReconciliationBanner(r: AgentReconciliation, identity: EthagentId
|
|
|
194
197
|
return (
|
|
195
198
|
<>
|
|
196
199
|
<Text color={theme.accentError} bold>Agent Unlinked</Text>
|
|
197
|
-
<Text color={theme.textSubtle}>{tokenLabel} was transferred. Local SOUL.md, MEMORY.md, skills
|
|
200
|
+
<Text color={theme.textSubtle}>{tokenLabel} was transferred. Local SOUL.md, MEMORY.md, and skills remain. Back them up before this directory is reused.</Text>
|
|
198
201
|
<Text color={theme.textSubtle}>Use Load Agent or New Agent to re-enable disabled actions.</Text>
|
|
199
202
|
</>
|
|
200
203
|
)
|
|
@@ -202,7 +205,7 @@ function renderReconciliationBanner(r: AgentReconciliation, identity: EthagentId
|
|
|
202
205
|
return (
|
|
203
206
|
<>
|
|
204
207
|
<Text color={theme.accentError} bold>Agent Unlinked</Text>
|
|
205
|
-
<Text color={theme.textSubtle}>{tokenLabel} left without Prepare Transfer. Back up local SOUL.md, MEMORY.md, skills
|
|
208
|
+
<Text color={theme.textSubtle}>{tokenLabel} left without Prepare Transfer. Back up local SOUL.md, MEMORY.md, and skills before loading another agent.</Text>
|
|
206
209
|
<Text color={theme.textSubtle}>For continuity handoff: ask the new holder to return the token, then run Prepare Transfer before re-sending.</Text>
|
|
207
210
|
<Text color={theme.textSubtle}>Use Load Agent or New Agent to re-enable disabled actions.</Text>
|
|
208
211
|
</>
|
|
@@ -221,7 +224,7 @@ function renderReconciliationBanner(r: AgentReconciliation, identity: EthagentId
|
|
|
221
224
|
}
|
|
222
225
|
const lines: string[] = []
|
|
223
226
|
if (r.custody === 'mid-flow-uri-pending') lines.push('Advanced setup pending. Open Custody Mode to finish.')
|
|
224
|
-
if (r.agentUri === 'local-newer') lines.push('Local state newer than
|
|
227
|
+
if (r.agentUri === 'local-newer') lines.push('Local state newer than onchain. Save Snapshot Now to publish.')
|
|
225
228
|
if (r.agentUri === 'chain-newer') lines.push('Onchain agentURI is newer than local. Refetch Latest.')
|
|
226
229
|
if (r.vault === 'missing') lines.push('Recorded vault address has no contract at it. Open Custody Mode to redeploy.')
|
|
227
230
|
if (r.workingTree === 'dirty') lines.push('Local edits pending. Save Snapshot Now to publish.')
|
|
@@ -33,7 +33,7 @@ export const UnlinkedIdentityScreen: React.FC<UnlinkedIdentityScreenProps> = ({
|
|
|
33
33
|
]
|
|
34
34
|
if (onRetry) {
|
|
35
35
|
options.push({ value: 'retry', role: 'section', label: 'Recheck' })
|
|
36
|
-
options.push({ value: 'retry', label: 'Retry Ownership Check', hint: 'Re-query
|
|
36
|
+
options.push({ value: 'retry', label: 'Retry Ownership Check', hint: 'Re-query onchain to confirm the current owner', role: 'utility' })
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const tokenLabel = agentId ? `Token #${agentId}` : 'Token'
|
|
@@ -43,18 +43,18 @@ export const UnlinkedIdentityScreen: React.FC<UnlinkedIdentityScreenProps> = ({
|
|
|
43
43
|
<Surface
|
|
44
44
|
title="No Linked Agent"
|
|
45
45
|
subtitle="The agent token recorded locally is not currently owned by your wallet."
|
|
46
|
-
footer={<Text color={theme.dim}>enter selects
|
|
46
|
+
footer={<Text color={theme.dim}>enter selects · esc back</Text>}
|
|
47
47
|
>
|
|
48
48
|
<Box flexDirection="column">
|
|
49
49
|
{transferSnapshot ? (
|
|
50
50
|
<Text color={theme.textSubtle}>
|
|
51
|
-
{tokenLabel} was transferred. Local SOUL.md, MEMORY.md, skills
|
|
51
|
+
{tokenLabel} was transferred. Local SOUL.md, MEMORY.md, and skills remain. Back them up before this directory is reused.
|
|
52
52
|
</Text>
|
|
53
53
|
) : (
|
|
54
54
|
<>
|
|
55
55
|
<Text color={theme.accentPeriwinkle}>{tokenLabel} left this wallet without Prepare Transfer, so the new holder has no continuity handoff.</Text>
|
|
56
56
|
<Text color={theme.textSubtle}>
|
|
57
|
-
Local SOUL.md, MEMORY.md, skills
|
|
57
|
+
Local SOUL.md, MEMORY.md, and skills remain. Back them up before this directory is reused.
|
|
58
58
|
</Text>
|
|
59
59
|
</>
|
|
60
60
|
)}
|