ethagent 2.3.0 → 3.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/README.md +18 -4
- package/package.json +2 -1
- package/src/app/FirstRun.tsx +157 -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 +12 -1
- package/src/chat/ChatScreen.tsx +17 -5
- package/src/chat/ConversationStack.tsx +25 -19
- package/src/chat/MessageList.tsx +194 -53
- package/src/chat/chatSessionState.ts +4 -1
- package/src/chat/chatTurnOrchestrator.ts +65 -2
- package/src/chat/input/ChatInput.tsx +28 -2
- package/src/chat/input/imageRefs.ts +30 -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/ResumeView.tsx +16 -7
- 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 +125 -0
- package/src/identity/continuity/publicSkills.ts +37 -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 +105 -3
- package/src/identity/hub/Routes.tsx +5 -3
- package/src/identity/hub/continuity/ContinuityDashboardScreen.tsx +5 -51
- package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +1 -1
- package/src/identity/hub/continuity/SavePromptScreen.tsx +1 -0
- 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/DeleteSkillScreen.tsx +123 -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/SkillVisibilityScreen.tsx +171 -0
- package/src/identity/hub/continuity/skills/SkillsTreeScreen.tsx +213 -0
- package/src/identity/hub/continuity/snapshot.ts +3 -0
- package/src/identity/hub/continuity/state.ts +3 -2
- package/src/identity/hub/continuity/vault.ts +42 -10
- package/src/identity/hub/custody/CustodyEditFlow.tsx +3 -3
- package/src/identity/hub/identityHubReducer.ts +21 -0
- 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 +11 -1
- 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 +97 -53
- package/src/identity/hub/shared/components/MenuScreen.tsx +18 -15
- package/src/identity/hub/shared/components/UnlinkedIdentityScreen.tsx +1 -1
- 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/mcp/manager.ts +1 -1
- package/src/models/ModelPicker.tsx +211 -74
- package/src/models/huggingface.ts +180 -2
- package/src/models/llamacpp.ts +261 -17
- package/src/models/llamacppPreflight.ts +16 -12
- package/src/models/modelPickerOptions.ts +57 -38
- package/src/providers/anthropic.ts +36 -5
- package/src/providers/contracts.ts +10 -1
- package/src/providers/gemini.ts +29 -3
- package/src/providers/openai-chat.ts +131 -11
- package/src/providers/openai-responses-format.ts +29 -8
- package/src/providers/openai-responses.ts +41 -11
- package/src/providers/registry.ts +1 -0
- package/src/runtime/toolExecution.ts +4 -3
- package/src/runtime/turn.ts +61 -30
- package/src/storage/config.ts +1 -0
- package/src/storage/sessions.ts +14 -2
- 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 +15 -3
- package/src/ui/theme.ts +2 -0
- package/src/utils/images.ts +140 -0
- package/src/utils/messages.ts +2 -0
- 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
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type SkillVisibility = 'private' | 'public' | 'discoverable'
|
|
2
|
+
|
|
3
|
+
export type SkillFrontmatter = {
|
|
4
|
+
name?: string
|
|
5
|
+
description?: string
|
|
6
|
+
whenToUse?: string
|
|
7
|
+
version?: string
|
|
8
|
+
argumentHint?: string
|
|
9
|
+
tags?: string[]
|
|
10
|
+
visibility?: SkillVisibility
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type SkillIndexEntry = {
|
|
14
|
+
name: string
|
|
15
|
+
displayName?: string
|
|
16
|
+
description: string
|
|
17
|
+
whenToUse?: string
|
|
18
|
+
version?: string
|
|
19
|
+
argumentHint?: string
|
|
20
|
+
tags?: string[]
|
|
21
|
+
visibility: SkillVisibility
|
|
22
|
+
relativePath: string
|
|
23
|
+
absolutePath: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type Skill = SkillIndexEntry & {
|
|
27
|
+
body: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type ContinuitySkillsTree = Record<string, string>
|
|
@@ -16,9 +16,8 @@ export function continuityAgentSnapshot(identity: EthagentIdentity): ContinuityA
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export function defaultContinuityFiles(identity: EthagentIdentity,
|
|
19
|
+
export function defaultContinuityFiles(identity: EthagentIdentity, _now = new Date()): ContinuityFiles {
|
|
20
20
|
const owner = identity.ownerAddress ?? identity.address
|
|
21
|
-
const created = now.toISOString().slice(0, 10)
|
|
22
21
|
const identityBlock = renderPrivateIdentityBlock({
|
|
23
22
|
owner,
|
|
24
23
|
token: identity.agentId ? `#${identity.agentId}` : 'pending registration',
|
|
@@ -33,40 +32,29 @@ export function defaultContinuityFiles(identity: EthagentIdentity, now = new Dat
|
|
|
33
32
|
'',
|
|
34
33
|
'## Persona',
|
|
35
34
|
'',
|
|
36
|
-
'
|
|
37
|
-
'- Keep standing behavior that should survive model switches and device restores.',
|
|
38
|
-
'- Prefer stable guidance over session-specific preferences.',
|
|
35
|
+
'The standing voice, collaboration style, and behavior this agent keeps across sessions and models.',
|
|
39
36
|
'',
|
|
40
|
-
'
|
|
37
|
+
'- Voice: <e.g., direct, concise, no filler>',
|
|
38
|
+
'- Tone: <e.g., warm but professional>',
|
|
39
|
+
'- Communication style: <e.g., bullet lists for technical work, prose for narrative>',
|
|
41
40
|
'',
|
|
42
|
-
'
|
|
43
|
-
'- Keep implementation-specific facts in MEMORY.md unless they define behavior.',
|
|
41
|
+
'## Principles',
|
|
44
42
|
'',
|
|
45
|
-
'
|
|
43
|
+
'Owner-approved working principles. Decisions that should be made the same way every time.',
|
|
46
44
|
'',
|
|
47
|
-
'-
|
|
48
|
-
'-
|
|
49
|
-
'-
|
|
45
|
+
'- <principle: e.g., always confirm before destructive operations>',
|
|
46
|
+
'- <principle: e.g., bias toward editing existing code over creating new files>',
|
|
47
|
+
'- <principle: e.g., never hide errors, always surface them clearly>',
|
|
50
48
|
'',
|
|
51
49
|
'## Boundaries',
|
|
52
50
|
'',
|
|
53
|
-
'
|
|
54
|
-
'- Do not store seed phrases, private keys, raw wallet signatures, or API keys.',
|
|
55
|
-
'- Do not place public delegation claims here; keep them in skills.json.',
|
|
51
|
+
'What this agent must never do, regardless of how it is asked.',
|
|
56
52
|
'',
|
|
57
|
-
'
|
|
53
|
+
'- <boundary: e.g., never bypass authentication or authorization checks>',
|
|
54
|
+
'- <boundary: e.g., never commit secrets to source control>',
|
|
55
|
+
'- Never store seed phrases, private keys, raw wallet signatures, or API keys.',
|
|
58
56
|
'',
|
|
59
|
-
|
|
60
|
-
'- Do not duplicate the mutable public agent name here; it lives in the token URI and Agent Card.',
|
|
61
|
-
'- Move factual project memory to MEMORY.md when it is not persona or instruction material.',
|
|
62
|
-
'- Revise or remove stale guidance instead of accumulating contradictions.',
|
|
63
|
-
'',
|
|
64
|
-
'## Change Notes',
|
|
65
|
-
'',
|
|
66
|
-
'- Add dated notes when the persona or long-lived private guidance changes.',
|
|
67
|
-
'',
|
|
68
|
-
`Created: ${created}`,
|
|
69
|
-
].join('\n') + '\n',
|
|
57
|
+
].join('\n'),
|
|
70
58
|
'MEMORY.md': [
|
|
71
59
|
'# MEMORY.md',
|
|
72
60
|
'',
|
|
@@ -74,35 +62,28 @@ export function defaultContinuityFiles(identity: EthagentIdentity, now = new Dat
|
|
|
74
62
|
'',
|
|
75
63
|
'## Durable User Preferences',
|
|
76
64
|
'',
|
|
77
|
-
'-
|
|
78
|
-
'',
|
|
79
|
-
'## Project Context',
|
|
80
|
-
'',
|
|
81
|
-
'- Add stable project facts, repo conventions, and active workstreams.',
|
|
65
|
+
'Long-lived owner preferences that survive sessions and model switches. Capture how the user works.',
|
|
82
66
|
'',
|
|
83
|
-
'
|
|
67
|
+
'- Communication: <e.g., prefers concise summaries over walls of text>',
|
|
68
|
+
'- Tooling: <e.g., VS Code, zsh, TypeScript over JavaScript>',
|
|
69
|
+
'- Workflow: <e.g., reviews PRs in the morning, deploys on Fridays>',
|
|
84
70
|
'',
|
|
85
|
-
'
|
|
86
|
-
'',
|
|
87
|
-
'## Facts to Revalidate',
|
|
71
|
+
'## Project Context',
|
|
88
72
|
'',
|
|
89
|
-
'
|
|
73
|
+
'Stable facts about active projects, repos, and conventions. Add dates for time-sensitive notes.',
|
|
90
74
|
'',
|
|
91
|
-
'
|
|
75
|
+
'### <project-name>',
|
|
92
76
|
'',
|
|
93
|
-
'-
|
|
94
|
-
'-
|
|
95
|
-
'-
|
|
96
|
-
'-
|
|
77
|
+
'- Repository: <url or local path>',
|
|
78
|
+
'- Stack: <languages, frameworks, key libraries>',
|
|
79
|
+
'- Conventions: <e.g., conventional commits, semver, branch naming>',
|
|
80
|
+
'- Active workstream: <YYYY-MM-DD what you are focused on>',
|
|
97
81
|
'',
|
|
98
82
|
'## Boundaries',
|
|
99
83
|
'',
|
|
100
|
-
'
|
|
101
|
-
'- Do not store secrets unless the user explicitly asks and the risk is clear.',
|
|
102
|
-
'- Keep public capabilities in skills.json.',
|
|
84
|
+
'Never store seed phrases, private keys, raw wallet signatures, or API keys.',
|
|
103
85
|
'',
|
|
104
|
-
|
|
105
|
-
].join('\n') + '\n',
|
|
86
|
+
].join('\n'),
|
|
106
87
|
}
|
|
107
88
|
}
|
|
108
89
|
|
|
@@ -9,6 +9,7 @@ import type { ContinuityVaultRef } from './types.js'
|
|
|
9
9
|
export async function ensureContinuityVault(identity: EthagentIdentity): Promise<ContinuityVaultRef> {
|
|
10
10
|
const ref = continuityVaultRef(identity)
|
|
11
11
|
await fs.mkdir(ref.dir, { recursive: true, mode: 0o700 })
|
|
12
|
+
await fs.mkdir(ref.skillsDir, { recursive: true, mode: 0o700 })
|
|
12
13
|
return ref
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -9,6 +9,7 @@ export function continuityVaultRef(identity: Pick<EthagentIdentity, 'chainId' |
|
|
|
9
9
|
soulPath: path.join(dir, 'SOUL.md'),
|
|
10
10
|
memoryPath: path.join(dir, 'MEMORY.md'),
|
|
11
11
|
publicSkillsPath: path.join(dir, 'skills.json'),
|
|
12
|
+
skillsDir: path.join(dir, 'skills'),
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { atomicWriteText } from '../../../storage/atomicWrite.js'
|
|
2
2
|
import type { EthagentIdentity } from '../../../storage/config.js'
|
|
3
|
-
import type { ContinuityFiles } from '../envelope.js'
|
|
3
|
+
import type { ContinuityFiles, ContinuitySkillsTree } from '../envelope.js'
|
|
4
|
+
import {
|
|
5
|
+
loadSkillsTree,
|
|
6
|
+
materializeSkillsTree,
|
|
7
|
+
} from '../skills/loadSkills.js'
|
|
8
|
+
import {
|
|
9
|
+
renderPublicSkillsJsonForIdentity,
|
|
10
|
+
syncPublicSkillsManifest,
|
|
11
|
+
} from '../skills/publicSkillsSync.js'
|
|
4
12
|
import { defaultContinuityFiles, defaultPublicSkillsJson } from './defaults.js'
|
|
5
13
|
import {
|
|
6
14
|
ensureContinuityFiles,
|
|
@@ -20,9 +28,10 @@ export async function ensureIdentityMarkdownScaffold(
|
|
|
20
28
|
): Promise<IdentityMarkdownScaffold> {
|
|
21
29
|
const privateFiles = await ensureContinuityFiles(identity)
|
|
22
30
|
const publicSkills = await ensurePublicSkillsFile(identity, { fallback: options.publicSkillsFallback })
|
|
31
|
+
const syncedPublic = await syncPublicSkillsManifest(identity).catch(() => publicSkills)
|
|
23
32
|
return {
|
|
24
33
|
...privateFiles,
|
|
25
|
-
'skills.json':
|
|
34
|
+
'skills.json': syncedPublic,
|
|
26
35
|
}
|
|
27
36
|
}
|
|
28
37
|
|
|
@@ -47,9 +56,8 @@ export async function syncIdentityMarkdownScaffold(identity: EthagentIdentity):
|
|
|
47
56
|
export async function prepareSyncedIdentityMarkdownScaffold(identity: EthagentIdentity): Promise<IdentityMarkdownScaffold> {
|
|
48
57
|
await ensureIdentityMarkdownScaffold(identity)
|
|
49
58
|
const privateFiles = await readContinuityFiles(identity)
|
|
50
|
-
const publicSkills = await readPublicSkillsFile(identity)
|
|
51
59
|
const privateDefaults = defaultContinuityFiles(identity)
|
|
52
|
-
const publicDefault =
|
|
60
|
+
const publicDefault = await renderPublicSkillsJsonForIdentity(identity)
|
|
53
61
|
return {
|
|
54
62
|
'SOUL.md': syncGeneratedMarkdown(privateFiles['SOUL.md'], privateDefaults['SOUL.md'], [
|
|
55
63
|
{ marker: 'identity' },
|
|
@@ -57,34 +65,28 @@ export async function prepareSyncedIdentityMarkdownScaffold(identity: EthagentId
|
|
|
57
65
|
'MEMORY.md': syncGeneratedMarkdown(privateFiles['MEMORY.md'], privateDefaults['MEMORY.md'], [
|
|
58
66
|
{ marker: 'identity' },
|
|
59
67
|
]),
|
|
60
|
-
'skills.json':
|
|
68
|
+
'skills.json': publicDefault,
|
|
61
69
|
}
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
export async function prepareSyncedPublicSkillsJson(identity: EthagentIdentity): Promise<string> {
|
|
65
73
|
await ensurePublicSkillsFile(identity)
|
|
66
|
-
|
|
67
|
-
return syncSkillsJson(publicSkills, defaultPublicSkillsJson(identity))
|
|
74
|
+
return renderPublicSkillsJsonForIdentity(identity)
|
|
68
75
|
}
|
|
69
76
|
|
|
70
|
-
function
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const freshParsed = JSON.parse(fresh)
|
|
74
|
-
const merged = {
|
|
75
|
-
...existingParsed,
|
|
76
|
-
...freshParsed,
|
|
77
|
-
skills: existingParsed.skills || freshParsed.skills,
|
|
78
|
-
inputModes: existingParsed.inputModes || freshParsed.inputModes,
|
|
79
|
-
outputModes: existingParsed.outputModes || freshParsed.outputModes,
|
|
80
|
-
}
|
|
81
|
-
if (!Object.hasOwn(freshParsed, 'imageUrl')) delete merged.imageUrl
|
|
82
|
-
return `${JSON.stringify(merged, null, 2)}\n`
|
|
83
|
-
} catch {
|
|
84
|
-
return fresh
|
|
85
|
-
}
|
|
77
|
+
export async function prepareSyncedSkillsTree(identity: EthagentIdentity): Promise<ContinuitySkillsTree> {
|
|
78
|
+
await ensureContinuityVault(identity)
|
|
79
|
+
return loadSkillsTree(identity)
|
|
86
80
|
}
|
|
87
81
|
|
|
82
|
+
export async function restoreSkillsTree(
|
|
83
|
+
identity: EthagentIdentity,
|
|
84
|
+
tree: ContinuitySkillsTree | undefined,
|
|
85
|
+
): Promise<void> {
|
|
86
|
+
await materializeSkillsTree(identity, tree)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
88
90
|
export async function ensurePublicSkillsFile(
|
|
89
91
|
identity: EthagentIdentity,
|
|
90
92
|
options: { fallback?: string | (() => Promise<string>) } = {},
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto'
|
|
2
2
|
import type { EthagentIdentity } from '../../../storage/config.js'
|
|
3
|
-
import type { ContinuityFiles } from '../envelope.js'
|
|
3
|
+
import type { ContinuityFiles, ContinuitySkillsTree } from '../envelope.js'
|
|
4
|
+
import { loadSkillsTree } from '../skills/loadSkills.js'
|
|
5
|
+
import { syncPublicSkillsManifest } from '../skills/publicSkillsSync.js'
|
|
4
6
|
import { continuityVaultRef } from './paths.js'
|
|
5
7
|
import { exists, readContinuityFiles, statIfExists } from './files.js'
|
|
6
8
|
import { readPublicSkillsFile } from './scaffold.js'
|
|
@@ -52,28 +54,55 @@ export async function localContinuitySnapshotContentHashes(
|
|
|
52
54
|
identity: EthagentIdentity,
|
|
53
55
|
): Promise<ContinuitySnapshotContentHashes> {
|
|
54
56
|
const privateFiles = await readContinuityFiles(identity)
|
|
57
|
+
await syncPublicSkillsManifest(identity).catch(() => undefined)
|
|
55
58
|
const publicSkills = await readPublicSkillsFile(identity)
|
|
56
|
-
|
|
59
|
+
const skills = await loadSkillsTree(identity).catch(() => ({} as ContinuitySkillsTree))
|
|
60
|
+
return continuitySnapshotContentHashes(privateFiles, publicSkills, skills)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function continuitySnapshotContentHashesFromSources(args: {
|
|
64
|
+
privateFiles: ContinuityFiles
|
|
65
|
+
publicSkills: string
|
|
66
|
+
skills: ContinuitySkillsTree
|
|
67
|
+
}): ContinuitySnapshotContentHashes {
|
|
68
|
+
return continuitySnapshotContentHashes(args.privateFiles, args.publicSkills, args.skills)
|
|
57
69
|
}
|
|
58
70
|
|
|
59
71
|
function continuitySnapshotContentHashes(
|
|
60
72
|
privateFiles: ContinuityFiles,
|
|
61
73
|
publicSkills: string,
|
|
74
|
+
skills: ContinuitySkillsTree,
|
|
62
75
|
): ContinuitySnapshotContentHashes {
|
|
76
|
+
const skillsHash = hashSkillsTree(skills)
|
|
63
77
|
return {
|
|
64
78
|
'SOUL.md': hashContinuitySnapshotContent(privateFiles['SOUL.md']),
|
|
65
79
|
'MEMORY.md': hashContinuitySnapshotContent(privateFiles['MEMORY.md']),
|
|
66
80
|
'skills.json': hashContinuitySnapshotContent(publicSkills),
|
|
81
|
+
...(skillsHash ? { 'private-skills': skillsHash } : {}),
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function hashSkillsTree(tree: ContinuitySkillsTree): string {
|
|
86
|
+
const keys = Object.keys(tree).sort()
|
|
87
|
+
if (keys.length === 0) return ''
|
|
88
|
+
const h = createHash('sha256')
|
|
89
|
+
for (const key of keys) {
|
|
90
|
+
h.update(key, 'utf8')
|
|
91
|
+
h.update('\0')
|
|
92
|
+
h.update(normalizeSnapshotContent(tree[key] ?? ''), 'utf8')
|
|
93
|
+
h.update('\0')
|
|
67
94
|
}
|
|
95
|
+
return h.digest('hex')
|
|
68
96
|
}
|
|
69
97
|
|
|
70
98
|
function equalContinuitySnapshotHashes(
|
|
71
99
|
a: ContinuitySnapshotContentHashes,
|
|
72
100
|
b: ContinuitySnapshotContentHashes,
|
|
73
101
|
): boolean {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
102
|
+
if (a['SOUL.md'] !== b['SOUL.md']) return false
|
|
103
|
+
if (a['MEMORY.md'] !== b['MEMORY.md']) return false
|
|
104
|
+
if (a['skills.json'] !== b['skills.json']) return false
|
|
105
|
+
return a['private-skills'] === b['private-skills']
|
|
77
106
|
}
|
|
78
107
|
|
|
79
108
|
function hashContinuitySnapshotContent(value: string): string {
|
|
@@ -8,14 +8,15 @@ export type ContinuityVaultRef = {
|
|
|
8
8
|
soulPath: string
|
|
9
9
|
memoryPath: string
|
|
10
10
|
publicSkillsPath: string
|
|
11
|
+
skillsDir: string
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export type IdentityMarkdownScaffold = ContinuityFiles & {
|
|
14
15
|
'skills.json': string
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
export type ContinuitySnapshotFile = PrivateContinuityFile | 'skills.json'
|
|
18
|
-
export type ContinuitySnapshotContentHashes = Record<ContinuitySnapshotFile, string>
|
|
18
|
+
export type ContinuitySnapshotFile = PrivateContinuityFile | 'skills.json' | 'private-skills'
|
|
19
|
+
export type ContinuitySnapshotContentHashes = Partial<Record<ContinuitySnapshotFile, string>> & Record<PrivateContinuityFile | 'skills.json', string>
|
|
19
20
|
export type ContinuityPublishState = 'not-restored' | 'not-published' | 'verify-needed' | 'local-changes' | 'published'
|
|
20
21
|
export type ContinuityWorkingTreeStatus = {
|
|
21
22
|
ready: boolean
|
|
@@ -19,8 +19,10 @@ export {
|
|
|
19
19
|
ensureIdentityMarkdownScaffold,
|
|
20
20
|
ensurePublicSkillsFile,
|
|
21
21
|
prepareSyncedIdentityMarkdownScaffold,
|
|
22
|
+
prepareSyncedSkillsTree,
|
|
22
23
|
prepareSyncedPublicSkillsJson,
|
|
23
24
|
readPublicSkillsFile,
|
|
25
|
+
restoreSkillsTree,
|
|
24
26
|
syncIdentityMarkdownScaffold,
|
|
25
27
|
writeIdentityMarkdownScaffold,
|
|
26
28
|
writePublicSkillsFile,
|
|
@@ -28,5 +30,6 @@ export {
|
|
|
28
30
|
export {
|
|
29
31
|
continuityVaultStatus,
|
|
30
32
|
continuityWorkingTreeStatus,
|
|
33
|
+
continuitySnapshotContentHashesFromSources,
|
|
31
34
|
localContinuitySnapshotContentHashes,
|
|
32
35
|
} from './storage/status.js'
|
|
@@ -17,6 +17,15 @@ import {
|
|
|
17
17
|
PrivateContinuityScreen,
|
|
18
18
|
PublicProfileScreen,
|
|
19
19
|
} from './continuity/ContinuityDashboardScreen.js'
|
|
20
|
+
import { SkillsTreeScreen } from './continuity/skills/SkillsTreeScreen.js'
|
|
21
|
+
import { NewSkillScreen } from './continuity/skills/NewSkillScreen.js'
|
|
22
|
+
import { NewSkillVisibilityScreen } from './continuity/skills/NewSkillVisibilityScreen.js'
|
|
23
|
+
import { DeleteSkillScreen } from './continuity/skills/DeleteSkillScreen.js'
|
|
24
|
+
import { DeleteSkillConfirmScreen } from './continuity/skills/DeleteSkillConfirmScreen.js'
|
|
25
|
+
import {
|
|
26
|
+
SkillVisibilityListScreen,
|
|
27
|
+
SkillVisibilityPickScreen,
|
|
28
|
+
} from './continuity/skills/SkillVisibilityScreen.js'
|
|
20
29
|
import { RecoveryConfirmScreen } from './continuity/RecoveryConfirmScreen.js'
|
|
21
30
|
import { SavePromptScreen } from './continuity/SavePromptScreen.js'
|
|
22
31
|
import { ErrorScreen } from './shared/components/ErrorScreen.js'
|
|
@@ -66,7 +75,11 @@ export const IdentityHubOperationalRoutes: React.FC<IdentityHubOperationalRoutes
|
|
|
66
75
|
openTokenTransferFlow,
|
|
67
76
|
openPublicProfileEdit,
|
|
68
77
|
openContinuityFile,
|
|
69
|
-
|
|
78
|
+
openSkillFile,
|
|
79
|
+
openSkillsFolder,
|
|
80
|
+
createSkill,
|
|
81
|
+
deleteSkill,
|
|
82
|
+
setSkillVisibility,
|
|
70
83
|
} = controller
|
|
71
84
|
|
|
72
85
|
if (step.kind === 'rebackup-confirm') {
|
|
@@ -143,13 +156,102 @@ export const IdentityHubOperationalRoutes: React.FC<IdentityHubOperationalRoutes
|
|
|
143
156
|
editorOpened={step.editorOpened}
|
|
144
157
|
onOpenSoul={() => { void openContinuityFile('soul') }}
|
|
145
158
|
onOpenMemory={() => { void openContinuityFile('memory') }}
|
|
146
|
-
onOpenSkills={() => { void openContinuityFile('skills') }}
|
|
147
|
-
onExportBackup={() => { void exportLocalBackupZip() }}
|
|
148
159
|
onBack={back}
|
|
149
160
|
/>
|
|
150
161
|
)
|
|
151
162
|
}
|
|
152
163
|
|
|
164
|
+
if (step.kind === 'continuity-skills-tree') {
|
|
165
|
+
return (
|
|
166
|
+
<SkillsTreeScreen
|
|
167
|
+
identity={identity}
|
|
168
|
+
config={config}
|
|
169
|
+
workingStatus={workingStatus}
|
|
170
|
+
notice={step.notice}
|
|
171
|
+
editorOpened={step.editorOpened}
|
|
172
|
+
footer={footer}
|
|
173
|
+
onOpenSkill={relativePath => { void openSkillFile(relativePath) }}
|
|
174
|
+
onNewSkill={() => setStep({ kind: 'continuity-skill-new' })}
|
|
175
|
+
onDelete={() => setStep({ kind: 'continuity-skill-delete' })}
|
|
176
|
+
onVisibility={() => setStep({ kind: 'continuity-skill-visibility' })}
|
|
177
|
+
onViewPublicManifest={() => { void openContinuityFile('skills') }}
|
|
178
|
+
onOpenFolder={() => { void openSkillsFolder() }}
|
|
179
|
+
onBack={back}
|
|
180
|
+
/>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (step.kind === 'continuity-skill-visibility') {
|
|
185
|
+
return (
|
|
186
|
+
<SkillVisibilityListScreen
|
|
187
|
+
identity={identity}
|
|
188
|
+
notice={step.notice}
|
|
189
|
+
footer={footer}
|
|
190
|
+
onPick={relativePath => setStep({ kind: 'continuity-skill-visibility-pick', relativePath })}
|
|
191
|
+
onCancel={back}
|
|
192
|
+
/>
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (step.kind === 'continuity-skill-visibility-pick') {
|
|
197
|
+
return (
|
|
198
|
+
<SkillVisibilityPickScreen
|
|
199
|
+
identity={identity}
|
|
200
|
+
relativePath={step.relativePath}
|
|
201
|
+
footer={footer}
|
|
202
|
+
onSelect={visibility => { void setSkillVisibility(step.relativePath, visibility) }}
|
|
203
|
+
onCancel={back}
|
|
204
|
+
/>
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (step.kind === 'continuity-skill-new') {
|
|
209
|
+
return (
|
|
210
|
+
<NewSkillScreen
|
|
211
|
+
error={step.error}
|
|
212
|
+
footer={footer}
|
|
213
|
+
onSubmit={name => setStep({ kind: 'continuity-skill-new-visibility', name })}
|
|
214
|
+
onCancel={back}
|
|
215
|
+
/>
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (step.kind === 'continuity-skill-new-visibility') {
|
|
220
|
+
return (
|
|
221
|
+
<NewSkillVisibilityScreen
|
|
222
|
+
name={step.name}
|
|
223
|
+
{...(step.error ? { error: step.error } : {})}
|
|
224
|
+
footer={footer}
|
|
225
|
+
onSelect={visibility => { void createSkill(step.name, visibility) }}
|
|
226
|
+
onCancel={back}
|
|
227
|
+
/>
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (step.kind === 'continuity-skill-delete') {
|
|
232
|
+
return (
|
|
233
|
+
<DeleteSkillScreen
|
|
234
|
+
identity={identity}
|
|
235
|
+
notice={step.notice}
|
|
236
|
+
footer={footer}
|
|
237
|
+
onPick={target => setStep({ kind: 'continuity-skill-delete-confirm', target })}
|
|
238
|
+
onCancel={back}
|
|
239
|
+
/>
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (step.kind === 'continuity-skill-delete-confirm') {
|
|
244
|
+
return (
|
|
245
|
+
<DeleteSkillConfirmScreen
|
|
246
|
+
identity={identity}
|
|
247
|
+
target={step.target}
|
|
248
|
+
footer={footer}
|
|
249
|
+
onConfirm={() => { void deleteSkill(step.target.relativePath) }}
|
|
250
|
+
onCancel={back}
|
|
251
|
+
/>
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
153
255
|
if (step.kind === 'continuity-public') {
|
|
154
256
|
return (
|
|
155
257
|
<PublicProfileScreen
|
|
@@ -9,11 +9,11 @@ import { copyToClipboard } from '../../utils/clipboard.js'
|
|
|
9
9
|
import { DEFAULT_IPFS_API_URL } from '../storage/ipfs.js'
|
|
10
10
|
import { chainIdForNetwork, erc8004ConfigForSupportedChain } from '../registry/erc8004.js'
|
|
11
11
|
import { shortAddress } from './shared/model/format.js'
|
|
12
|
+
import { canRestoreCandidate } from './restore/discover.js'
|
|
12
13
|
import {
|
|
13
|
-
canRestoreCandidate,
|
|
14
14
|
resolveAgentEnsToCandidate,
|
|
15
15
|
resolveAgentTokenIdToCandidate,
|
|
16
|
-
} from './restore/
|
|
16
|
+
} from './restore/resolve.js'
|
|
17
17
|
import {
|
|
18
18
|
runRegistrySubmit,
|
|
19
19
|
runStorageSubmit,
|
|
@@ -85,7 +85,7 @@ export const IdentityHubRoutes: React.FC<{ controller: IdentityHubController }>
|
|
|
85
85
|
{ value: 'ens', role: 'section', label: 'Set Up Now' },
|
|
86
86
|
{ value: 'ens', label: 'Set Up ENS Name', hint: 'Walks you through Root, Name, Review, and Apply' },
|
|
87
87
|
{ value: 'skip', role: 'section', label: 'Skip' },
|
|
88
|
-
{ value: 'skip', label: 'Skip For Now', hint: 'Continue to model setup
|
|
88
|
+
{ value: 'skip', label: 'Skip For Now', hint: 'Continue to model setup; add ENS later', role: 'utility' },
|
|
89
89
|
]}
|
|
90
90
|
hintLayout="inline"
|
|
91
91
|
onSubmit={choice => {
|
|
@@ -142,6 +142,7 @@ export const IdentityHubRoutes: React.FC<{ controller: IdentityHubController }>
|
|
|
142
142
|
setStep({ kind: 'custody-model', identity, registry: reg, returnTo: { kind: 'menu' } })
|
|
143
143
|
}}
|
|
144
144
|
onContinuity={() => setStep({ kind: 'continuity-private' })}
|
|
145
|
+
onSkillsTree={() => setStep({ kind: 'continuity-skills-tree' })}
|
|
145
146
|
onIdentityValues={() => setStep({ kind: 'details' })}
|
|
146
147
|
onPrepareTransfer={openTokenTransferFlow}
|
|
147
148
|
onStorage={() => setStep({ kind: 'storage-credential' })}
|
|
@@ -329,6 +330,7 @@ export const IdentityHubRoutes: React.FC<{ controller: IdentityHubController }>
|
|
|
329
330
|
<DetailsScreen
|
|
330
331
|
identity={identity}
|
|
331
332
|
config={config}
|
|
333
|
+
workingStatus={workingStatus}
|
|
332
334
|
copyNotice={copyNotice}
|
|
333
335
|
unlinked={reconciliation?.token === 'unlinked'}
|
|
334
336
|
{...(reconciliation?.onChainOwner ? { onchainOwner: reconciliation.onChainOwner } : {})}
|
|
@@ -10,7 +10,7 @@ import { changedContinuitySnapshotFiles } from './state.js'
|
|
|
10
10
|
import { readIdentityStateString } from '../custody/state.js'
|
|
11
11
|
import { shortCid } from '../shared/model/format.js'
|
|
12
12
|
|
|
13
|
-
type PrivateAction = 'soul' | 'memory' | '
|
|
13
|
+
type PrivateAction = 'soul' | 'memory' | 'back'
|
|
14
14
|
type PublicAction = 'edit' | 'back'
|
|
15
15
|
|
|
16
16
|
interface CommonProps {
|
|
@@ -36,7 +36,7 @@ const SaveFromHubHint: React.FC<{ workingStatus?: ContinuityWorkingTreeStatus |
|
|
|
36
36
|
Unsaved changes
|
|
37
37
|
{files.length > 0 ? `: ${files.join(', ')}` : ''}
|
|
38
38
|
</Text>
|
|
39
|
-
<Text color={theme.dim}>
|
|
39
|
+
<Text color={theme.dim}>Save Snapshot Now to publish.</Text>
|
|
40
40
|
</Box>
|
|
41
41
|
)
|
|
42
42
|
}
|
|
@@ -44,8 +44,6 @@ const SaveFromHubHint: React.FC<{ workingStatus?: ContinuityWorkingTreeStatus |
|
|
|
44
44
|
export const PrivateContinuityScreen: React.FC<CommonProps & {
|
|
45
45
|
onOpenSoul: () => void
|
|
46
46
|
onOpenMemory: () => void
|
|
47
|
-
onOpenSkills: () => void
|
|
48
|
-
onExportBackup: () => void
|
|
49
47
|
}> = ({
|
|
50
48
|
identity,
|
|
51
49
|
config,
|
|
@@ -56,13 +54,10 @@ export const PrivateContinuityScreen: React.FC<CommonProps & {
|
|
|
56
54
|
footer,
|
|
57
55
|
onOpenSoul,
|
|
58
56
|
onOpenMemory,
|
|
59
|
-
onOpenSkills,
|
|
60
|
-
onExportBackup,
|
|
61
57
|
onBack,
|
|
62
58
|
}) => (
|
|
63
|
-
<Surface title="Soul
|
|
59
|
+
<Surface title="Soul & Memory" subtitle={notice ?? privateSubtitle(ready)} footer={footer}>
|
|
64
60
|
<IdentitySummary identity={identity} config={config} workingStatus={workingStatus} hideLocalChanges />
|
|
65
|
-
<PrivateRows identity={identity} ready={ready} />
|
|
66
61
|
<SaveFromHubHint workingStatus={workingStatus} />
|
|
67
62
|
{editorOpened && (
|
|
68
63
|
<Box marginTop={1}>
|
|
@@ -72,13 +67,9 @@ export const PrivateContinuityScreen: React.FC<CommonProps & {
|
|
|
72
67
|
<Box marginTop={1}>
|
|
73
68
|
<Select<PrivateAction>
|
|
74
69
|
options={[
|
|
75
|
-
{ value: 'soul', role: 'section', label: '
|
|
70
|
+
{ value: 'soul', role: 'section', label: 'Files' },
|
|
76
71
|
{ value: 'soul', label: 'Edit Soul', hint: 'Voice, behavior, and operating preferences', disabled: !ready },
|
|
77
72
|
{ value: 'memory', label: 'Edit Memory', hint: 'Durable owner memory for this agent', disabled: !ready },
|
|
78
|
-
{ value: 'skills', role: 'section', label: 'Public Capabilities' },
|
|
79
|
-
{ value: 'skills', label: 'Edit skills.json', hint: 'Machine-readable capabilities for discovery', disabled: !ready },
|
|
80
|
-
{ value: 'export-backup', role: 'section', label: 'Local Backup' },
|
|
81
|
-
{ value: 'export-backup', label: 'Save Local Backup', hint: 'Bundle SOUL.md, MEMORY.md, skills.json into a zip you can keep anywhere', disabled: !ready },
|
|
82
73
|
{ value: 'back', role: 'section', label: 'Navigation' },
|
|
83
74
|
{ value: 'back', label: 'Back', hint: 'Return to Identity Hub menu', role: 'utility' },
|
|
84
75
|
]}
|
|
@@ -86,8 +77,6 @@ export const PrivateContinuityScreen: React.FC<CommonProps & {
|
|
|
86
77
|
onSubmit={choice => {
|
|
87
78
|
if (choice === 'soul') return onOpenSoul()
|
|
88
79
|
if (choice === 'memory') return onOpenMemory()
|
|
89
|
-
if (choice === 'skills') return onOpenSkills()
|
|
90
|
-
if (choice === 'export-backup') return onExportBackup()
|
|
91
80
|
return onBack()
|
|
92
81
|
}}
|
|
93
82
|
onCancel={onBack}
|
|
@@ -102,7 +91,6 @@ export const PublicProfileScreen: React.FC<CommonProps & {
|
|
|
102
91
|
return (
|
|
103
92
|
<Surface title="Public Profile" subtitle={notice ?? 'Manage the public name, description, icon, and Agent Card.'} footer={footer}>
|
|
104
93
|
<IdentitySummary identity={identity} config={config} workingStatus={workingStatus} hideLocalChanges />
|
|
105
|
-
<PublicProfileRows identity={identity} />
|
|
106
94
|
<SaveFromHubHint workingStatus={workingStatus} />
|
|
107
95
|
{editorOpened && (
|
|
108
96
|
<Box marginTop={1}>
|
|
@@ -129,42 +117,8 @@ export const PublicProfileScreen: React.FC<CommonProps & {
|
|
|
129
117
|
)
|
|
130
118
|
}
|
|
131
119
|
|
|
132
|
-
const PrivateRows: React.FC<{ identity?: EthagentIdentity; ready: boolean }> = ({ identity, ready }) => (
|
|
133
|
-
<Box flexDirection="column" marginTop={1}>
|
|
134
|
-
<Text>
|
|
135
|
-
<Text color={theme.dim}>{'Private'.padEnd(13)}</Text>
|
|
136
|
-
<Text color={ready ? theme.text : theme.dim}>{ready ? 'Local Files Ready' : 'Missing Local Working Files'}</Text>
|
|
137
|
-
</Text>
|
|
138
|
-
<Text>
|
|
139
|
-
<Text color={theme.dim}>{'Snapshot'.padEnd(13)}</Text>
|
|
140
|
-
<Text color={identity?.backup?.cid ? theme.text : theme.dim}>{identity?.backup?.cid ? shortCid(identity.backup.cid) : 'Not Saved Yet'}</Text>
|
|
141
|
-
</Text>
|
|
142
|
-
<Text>
|
|
143
|
-
<Text color={theme.dim}>{'Skills'.padEnd(13)}</Text>
|
|
144
|
-
<Text color={identity?.publicSkills?.cid ? theme.text : theme.dim}>{identity?.publicSkills?.cid ? shortCid(identity.publicSkills.cid) : 'Not Saved'}</Text>
|
|
145
|
-
</Text>
|
|
146
|
-
</Box>
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
const PublicProfileRows: React.FC<{ identity?: EthagentIdentity }> = ({ identity }) => (
|
|
150
|
-
<Box flexDirection="column" marginTop={1}>
|
|
151
|
-
<Text>
|
|
152
|
-
<Text color={theme.dim}>{publicProfileLabel('Agent Card')}</Text>
|
|
153
|
-
<Text color={identity?.publicSkills?.agentCardCid ? theme.text : theme.dim}>{identity?.publicSkills?.agentCardCid ? shortCid(identity.publicSkills.agentCardCid) : 'Not Saved'}</Text>
|
|
154
|
-
</Text>
|
|
155
|
-
<Text>
|
|
156
|
-
<Text color={theme.dim}>{publicProfileLabel('Agent Icon')}</Text>
|
|
157
|
-
<Text color={readIdentityStateString(identity?.state, 'imageUrl') ? theme.text : theme.dim}>{readIdentityStateString(identity?.state, 'imageUrl') ? 'Attached' : 'Not Attached'}</Text>
|
|
158
|
-
</Text>
|
|
159
|
-
</Box>
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
function publicProfileLabel(label: string): string {
|
|
163
|
-
return label.padEnd(16)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
120
|
function privateSubtitle(ready: boolean): string {
|
|
167
121
|
return ready
|
|
168
|
-
? 'Edit
|
|
122
|
+
? 'Edit the durable persona and memory files for this identity.'
|
|
169
123
|
: 'Use "Refetch Latest Snapshot" from the Identity Hub menu to recover files.'
|
|
170
124
|
}
|
|
@@ -66,7 +66,7 @@ export const RecoveryConfirmScreen: React.FC<RecoveryConfirmScreenProps> = ({ mo
|
|
|
66
66
|
label: isPublish ? 'Yes, Save Snapshot Now' : 'Yes, Refetch From Chain',
|
|
67
67
|
hint: isPublish ? 'Sign and save the encrypted snapshot' : 'Wallet decrypts and overwrites local files',
|
|
68
68
|
},
|
|
69
|
-
{ value: 'back', role: 'section', label: '
|
|
69
|
+
{ value: 'back', role: 'section', label: 'Navigation' },
|
|
70
70
|
{
|
|
71
71
|
value: 'back',
|
|
72
72
|
label: 'No, Go Back',
|