ethagent 1.1.2 → 2.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.
Files changed (268) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +126 -30
  3. package/package.json +7 -2
  4. package/src/app/FirstRun.tsx +190 -146
  5. package/src/app/FirstRunTimeline.tsx +47 -0
  6. package/src/app/input/AppInputProvider.tsx +1 -1
  7. package/src/app/keybindings/KeybindingProvider.tsx +1 -1
  8. package/src/chat/ChatBottomPane.tsx +0 -1
  9. package/src/chat/ChatInput.tsx +6 -6
  10. package/src/chat/ChatScreen.tsx +35 -15
  11. package/src/chat/ContextLimitView.tsx +4 -4
  12. package/src/chat/ContinuityEditReviewView.tsx +10 -22
  13. package/src/chat/CopyPicker.tsx +0 -1
  14. package/src/chat/MessageList.tsx +62 -45
  15. package/src/chat/PermissionPrompt.tsx +13 -9
  16. package/src/chat/PlanApprovalView.tsx +3 -3
  17. package/src/chat/ResumeView.tsx +1 -4
  18. package/src/chat/RewindView.tsx +2 -2
  19. package/src/chat/chatInputState.ts +1 -1
  20. package/src/chat/chatScreenUtils.ts +22 -11
  21. package/src/chat/chatSessionState.ts +2 -2
  22. package/src/chat/chatTurnOrchestrator.ts +16 -81
  23. package/src/chat/commands.ts +1 -1
  24. package/src/chat/textCursor.ts +1 -1
  25. package/src/chat/transcriptViewport.ts +2 -7
  26. package/src/cli/ResetConfirmView.tsx +1 -1
  27. package/src/cli/main.tsx +9 -3
  28. package/src/cli/preview.tsx +0 -5
  29. package/src/cli/updateNotice.ts +4 -2
  30. package/src/identity/continuity/editor.ts +7 -107
  31. package/src/identity/continuity/envelope.ts +1048 -40
  32. package/src/identity/continuity/history.ts +4 -4
  33. package/src/identity/continuity/localBackup.ts +249 -0
  34. package/src/identity/continuity/privateEdit/apply.ts +170 -0
  35. package/src/identity/continuity/privateEdit/diff.ts +82 -0
  36. package/src/identity/continuity/privateEdit/files.ts +23 -0
  37. package/src/identity/continuity/privateEdit/types.ts +28 -0
  38. package/src/identity/continuity/privateEdit.ts +10 -298
  39. package/src/identity/continuity/publicSkills.ts +8 -9
  40. package/src/identity/continuity/snapshots.ts +17 -6
  41. package/src/identity/continuity/storage/defaults.ts +111 -0
  42. package/src/identity/continuity/storage/files.ts +72 -0
  43. package/src/identity/continuity/storage/markdown.ts +81 -0
  44. package/src/identity/continuity/storage/paths.ts +24 -0
  45. package/src/identity/continuity/storage/scaffold.ts +124 -0
  46. package/src/identity/continuity/storage/status.ts +86 -0
  47. package/src/identity/continuity/storage/types.ts +27 -0
  48. package/src/identity/continuity/storage.ts +32 -507
  49. package/src/identity/continuity/zipWriter.ts +95 -0
  50. package/src/identity/crypto/backupEnvelope.ts +14 -247
  51. package/src/identity/crypto/eth.ts +7 -7
  52. package/src/identity/ens/agentRecords.ts +96 -0
  53. package/src/identity/ens/ensAutomation/contracts.ts +38 -0
  54. package/src/identity/ens/ensAutomation/delete.ts +80 -0
  55. package/src/identity/ens/ensAutomation/names.ts +14 -0
  56. package/src/identity/ens/ensAutomation/operators.ts +29 -0
  57. package/src/identity/ens/ensAutomation/read.ts +114 -0
  58. package/src/identity/ens/ensAutomation/root.ts +63 -0
  59. package/src/identity/ens/ensAutomation/setup.ts +284 -0
  60. package/src/identity/ens/ensAutomation/transactions.ts +107 -0
  61. package/src/identity/ens/ensAutomation/types.ts +126 -0
  62. package/src/identity/ens/ensAutomation.ts +29 -0
  63. package/src/identity/ens/ensLookup/client.ts +43 -0
  64. package/src/identity/ens/ensLookup/constants.ts +26 -0
  65. package/src/identity/ens/ensLookup/discovery.ts +70 -0
  66. package/src/identity/ens/ensLookup/names.ts +34 -0
  67. package/src/identity/ens/ensLookup/records.ts +45 -0
  68. package/src/identity/ens/ensLookup/resolve.ts +75 -0
  69. package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
  70. package/src/identity/ens/ensLookup/types.ts +38 -0
  71. package/src/identity/ens/ensLookup/validation.ts +72 -0
  72. package/src/identity/ens/ensLookup.ts +19 -0
  73. package/src/identity/ens/ensRegistration.ts +199 -0
  74. package/src/identity/ens/resolverDelegation.ts +48 -0
  75. package/src/identity/hub/IdentityHub.tsx +13 -817
  76. package/src/identity/hub/OperationalRoutes.tsx +370 -0
  77. package/src/identity/hub/Routes.tsx +361 -0
  78. package/src/identity/hub/advancedEnsValidation.ts +45 -0
  79. package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
  80. package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
  81. package/src/identity/hub/components/FlowTimeline.tsx +27 -0
  82. package/src/identity/hub/components/IdentitySummary.tsx +190 -0
  83. package/src/identity/hub/components/MenuScreen.tsx +237 -0
  84. package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
  85. package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
  86. package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
  87. package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
  88. package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
  89. package/src/identity/hub/effects/create.ts +310 -0
  90. package/src/identity/hub/effects/ens/flows.ts +218 -0
  91. package/src/identity/hub/effects/ens/index.ts +11 -0
  92. package/src/identity/hub/effects/ens/transactions.ts +239 -0
  93. package/src/identity/hub/effects/index.ts +74 -0
  94. package/src/identity/hub/effects/profile/profileState.ts +173 -0
  95. package/src/identity/hub/effects/publicProfile/index.ts +5 -0
  96. package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
  97. package/src/identity/hub/effects/rebackup/index.ts +7 -0
  98. package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
  99. package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
  100. package/src/identity/hub/effects/receipts.ts +46 -0
  101. package/src/identity/hub/effects/restore/apply.ts +112 -0
  102. package/src/identity/hub/effects/restore/auth.ts +159 -0
  103. package/src/identity/hub/effects/restore/discover.ts +86 -0
  104. package/src/identity/hub/effects/restore/envelopes.ts +21 -0
  105. package/src/identity/hub/effects/restore/fetch.ts +25 -0
  106. package/src/identity/hub/effects/restore/index.ts +22 -0
  107. package/src/identity/hub/effects/restore/recovery.ts +135 -0
  108. package/src/identity/hub/effects/restore/resolve.ts +102 -0
  109. package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
  110. package/src/identity/hub/effects/restore/shared.ts +91 -0
  111. package/src/identity/hub/effects/restoreAdmin.ts +93 -0
  112. package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
  113. package/src/identity/hub/effects/shared/snapshot.ts +336 -0
  114. package/src/identity/hub/effects/shared/sync.ts +190 -0
  115. package/src/identity/hub/effects/token-transfer/index.ts +6 -0
  116. package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
  117. package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
  118. package/src/identity/hub/effects/types.ts +53 -0
  119. package/src/identity/hub/effects/vault/preflight.ts +50 -0
  120. package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
  121. package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
  122. package/src/identity/hub/{screens → flows/continuity}/RecoveryConfirmScreen.tsx +28 -19
  123. package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
  124. package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
  125. package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
  126. package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
  127. package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
  128. package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
  129. package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
  130. package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
  131. package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
  132. package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
  133. package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
  134. package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
  135. package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
  136. package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
  137. package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
  138. package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
  139. package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
  140. package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
  141. package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
  142. package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
  143. package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
  144. package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
  145. package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
  146. package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
  147. package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +23 -44
  148. package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
  149. package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
  150. package/src/identity/hub/identityHubReducer.ts +164 -99
  151. package/src/identity/hub/model/continuity.ts +94 -0
  152. package/src/identity/hub/model/copy.ts +35 -0
  153. package/src/identity/hub/model/custody.ts +54 -0
  154. package/src/identity/hub/model/ens.ts +49 -0
  155. package/src/identity/hub/model/errors.ts +140 -0
  156. package/src/identity/hub/model/format.ts +15 -0
  157. package/src/identity/hub/model/identity.ts +94 -0
  158. package/src/identity/hub/model/network.ts +32 -0
  159. package/src/identity/hub/model/transfer.ts +57 -0
  160. package/src/identity/hub/operatorWallets.ts +131 -0
  161. package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
  162. package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
  163. package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
  164. package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
  165. package/src/identity/hub/reconciliation/index.ts +21 -0
  166. package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
  167. package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
  168. package/src/identity/hub/txGuard.ts +51 -0
  169. package/src/identity/hub/types.ts +17 -0
  170. package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
  171. package/src/identity/hub/useIdentityHubController.ts +396 -0
  172. package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
  173. package/src/identity/hub/utils.ts +79 -0
  174. package/src/identity/identityCompat.ts +34 -0
  175. package/src/identity/profile/agentIcon.ts +61 -0
  176. package/src/identity/profile/imagePicker.ts +12 -12
  177. package/src/identity/registry/erc8004/abi.ts +14 -0
  178. package/src/identity/registry/erc8004/chains.ts +150 -0
  179. package/src/identity/registry/erc8004/client.ts +11 -0
  180. package/src/identity/registry/erc8004/discovery.ts +511 -0
  181. package/src/identity/registry/erc8004/metadata.ts +335 -0
  182. package/src/identity/registry/erc8004/ownership.ts +121 -0
  183. package/src/identity/registry/erc8004/preflight.ts +123 -0
  184. package/src/identity/registry/erc8004/transactions.ts +77 -0
  185. package/src/identity/registry/erc8004/types.ts +88 -0
  186. package/src/identity/registry/erc8004/uri.ts +59 -0
  187. package/src/identity/registry/erc8004/utils.ts +58 -0
  188. package/src/identity/registry/erc8004.ts +53 -1106
  189. package/src/identity/registry/fieldParsers.ts +28 -0
  190. package/src/identity/registry/operatorVault/bytecode.ts +98 -0
  191. package/src/identity/registry/operatorVault/constants.ts +38 -0
  192. package/src/identity/registry/operatorVault/read.ts +246 -0
  193. package/src/identity/registry/operatorVault/transactions.ts +81 -0
  194. package/src/identity/registry/operatorVault.ts +44 -0
  195. package/src/identity/storage/ipfs.ts +26 -24
  196. package/src/identity/wallet/browserWallet/gas.ts +41 -0
  197. package/src/identity/wallet/browserWallet/html.ts +106 -0
  198. package/src/identity/wallet/browserWallet/http.ts +28 -0
  199. package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
  200. package/src/identity/wallet/browserWallet/requests.ts +191 -0
  201. package/src/identity/wallet/browserWallet/session.ts +325 -0
  202. package/src/identity/wallet/browserWallet/types.ts +192 -0
  203. package/src/identity/wallet/browserWallet/validation.ts +74 -0
  204. package/src/identity/wallet/browserWallet.ts +30 -393
  205. package/src/identity/wallet/page/constants.ts +5 -0
  206. package/src/identity/wallet/page/controller.ts +251 -0
  207. package/src/identity/wallet/page/copy.ts +340 -0
  208. package/src/identity/wallet/page/grainient.ts +278 -0
  209. package/src/identity/wallet/page/html.ts +28 -0
  210. package/src/identity/wallet/page/markup.ts +50 -0
  211. package/src/identity/wallet/page/state.ts +9 -0
  212. package/src/identity/wallet/page/styles/base.ts +259 -0
  213. package/src/identity/wallet/page/styles/components.ts +262 -0
  214. package/src/identity/wallet/page/styles/index.ts +5 -0
  215. package/src/identity/wallet/page/styles/responsive.ts +247 -0
  216. package/src/identity/wallet/page/types.ts +47 -0
  217. package/src/identity/wallet/page/view.ts +535 -0
  218. package/src/identity/wallet/page/walletProvider.ts +70 -0
  219. package/src/identity/wallet/page.tsx +38 -0
  220. package/src/identity/wallet/walletPurposeCompat.ts +27 -0
  221. package/src/mcp/manager.ts +0 -1
  222. package/src/models/ModelPicker.tsx +36 -30
  223. package/src/models/catalog.ts +5 -2
  224. package/src/models/huggingface.ts +9 -9
  225. package/src/models/llamacpp.ts +13 -13
  226. package/src/models/modelDisplay.ts +75 -0
  227. package/src/models/modelPickerOptions.ts +16 -3
  228. package/src/models/modelRecommendation.ts +0 -1
  229. package/src/providers/errors.ts +16 -0
  230. package/src/providers/gemini.ts +252 -39
  231. package/src/providers/registry.ts +2 -2
  232. package/src/providers/retry.ts +1 -1
  233. package/src/runtime/sessionMode.ts +1 -1
  234. package/src/runtime/systemPrompt.ts +2 -0
  235. package/src/runtime/toolExecution.ts +18 -22
  236. package/src/runtime/toolIntent.ts +0 -20
  237. package/src/runtime/turn.ts +0 -92
  238. package/src/storage/atomicWrite.ts +4 -1
  239. package/src/storage/config.ts +181 -5
  240. package/src/storage/identity.ts +9 -3
  241. package/src/storage/secrets.ts +2 -2
  242. package/src/tools/bashSafety.ts +8 -0
  243. package/src/tools/changeDirectoryTool.ts +1 -1
  244. package/src/tools/deleteFileTool.ts +4 -4
  245. package/src/tools/editTool.ts +4 -4
  246. package/src/tools/editUtils.ts +5 -5
  247. package/src/tools/privateContinuityEditTool.ts +4 -5
  248. package/src/tools/privateContinuityReadTool.ts +1 -2
  249. package/src/tools/registry.ts +30 -0
  250. package/src/tools/writeFileTool.ts +5 -5
  251. package/src/ui/BrandSplash.tsx +20 -85
  252. package/src/ui/ProgressBar.tsx +3 -5
  253. package/src/ui/Select.tsx +20 -8
  254. package/src/ui/Spinner.tsx +38 -3
  255. package/src/ui/Surface.tsx +2 -2
  256. package/src/ui/TextInput.tsx +63 -20
  257. package/src/ui/theme.ts +7 -34
  258. package/src/utils/openExternal.ts +21 -0
  259. package/src/utils/withRetry.ts +47 -3
  260. package/src/identity/hub/identityHubEffects.ts +0 -937
  261. package/src/identity/hub/identityHubModel.ts +0 -371
  262. package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -156
  263. package/src/identity/hub/screens/EditProfileFlow.tsx +0 -146
  264. package/src/identity/hub/screens/IdentitySummary.tsx +0 -106
  265. package/src/identity/hub/screens/MenuScreen.tsx +0 -117
  266. package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
  267. package/src/identity/wallet/wallet-page/wallet.html +0 -1202
  268. /package/src/identity/hub/{screens → components}/BusyScreen.tsx +0 -0
@@ -1,507 +1,32 @@
1
- import fs from 'node:fs/promises'
2
- import path from 'node:path'
3
- import { createHash } from 'node:crypto'
4
- import { getConfigDir, type EthagentIdentity } from '../../storage/config.js'
5
- import { atomicWriteText } from '../../storage/atomicWrite.js'
6
- import type { ContinuityAgentSnapshot, ContinuityFiles } from './envelope.js'
7
- import { defaultPublicSkillsProfile, renderPublicSkillsJson } from './publicSkills.js'
8
-
9
- export const PRIVATE_CONTINUITY_FILES = ['SOUL.md', 'MEMORY.md'] as const
10
- export type PrivateContinuityFile = (typeof PRIVATE_CONTINUITY_FILES)[number]
11
-
12
- export type ContinuityVaultRef = {
13
- dir: string
14
- soulPath: string
15
- memoryPath: string
16
- publicSkillsPath: string
17
- }
18
-
19
- export type IdentityMarkdownScaffold = ContinuityFiles & {
20
- 'skills.json': string
21
- }
22
-
23
- export type ContinuitySnapshotFile = PrivateContinuityFile | 'skills.json'
24
- export type ContinuitySnapshotContentHashes = Record<ContinuitySnapshotFile, string>
25
- export type ContinuityPublishState = 'not-restored' | 'not-published' | 'verify-needed' | 'local-changes' | 'published'
26
- export type ContinuityWorkingTreeStatus = {
27
- ready: boolean
28
- newestLocalChangeAt?: string
29
- localChangedAfterBackup: boolean
30
- publishState: ContinuityPublishState
31
- localContentHashes?: ContinuitySnapshotContentHashes
32
- publishedContentHashes?: ContinuitySnapshotContentHashes
33
- }
34
-
35
- export function continuityVaultRef(identity: Pick<EthagentIdentity, 'chainId' | 'identityRegistryAddress' | 'agentId' | 'address'>): ContinuityVaultRef {
36
- const dir = path.join(getConfigDir(), 'continuity', continuityVaultId(identity))
37
- return {
38
- dir,
39
- soulPath: path.join(dir, 'SOUL.md'),
40
- memoryPath: path.join(dir, 'MEMORY.md'),
41
- publicSkillsPath: path.join(dir, 'skills.json'),
42
- }
43
- }
44
-
45
- export async function ensureContinuityVault(identity: EthagentIdentity): Promise<ContinuityVaultRef> {
46
- const ref = continuityVaultRef(identity)
47
- await fs.mkdir(ref.dir, { recursive: true, mode: 0o700 })
48
- return ref
49
- }
50
-
51
- export async function ensureContinuityFiles(identity: EthagentIdentity): Promise<ContinuityFiles> {
52
- const ref = await ensureContinuityVault(identity)
53
- const defaults = defaultContinuityFiles(identity)
54
- await writeMissingPrivateFile(ref.soulPath, defaults['SOUL.md'])
55
- await writeMissingPrivateFile(ref.memoryPath, defaults['MEMORY.md'])
56
- return readContinuityFiles(identity)
57
- }
58
-
59
- export async function readContinuityFiles(identity: EthagentIdentity): Promise<ContinuityFiles> {
60
- const ref = await ensureContinuityVault(identity)
61
- const defaults = defaultContinuityFiles(identity)
62
- return {
63
- 'SOUL.md': await readOrDefault(ref.soulPath, defaults['SOUL.md']),
64
- 'MEMORY.md': await readOrDefault(ref.memoryPath, defaults['MEMORY.md']),
65
- }
66
- }
67
-
68
- export async function writeContinuityFiles(identity: EthagentIdentity, files: ContinuityFiles): Promise<ContinuityVaultRef> {
69
- const ref = await ensureContinuityVault(identity)
70
- await atomicWriteText(ref.soulPath, ensureTrailingNewline(files['SOUL.md']), { mode: 0o600 })
71
- await atomicWriteText(ref.memoryPath, ensureTrailingNewline(files['MEMORY.md']), { mode: 0o600 })
72
- return ref
73
- }
74
-
75
- export async function ensureIdentityMarkdownScaffold(
76
- identity: EthagentIdentity,
77
- options: { publicSkillsFallback?: string | (() => Promise<string>) } = {},
78
- ): Promise<IdentityMarkdownScaffold> {
79
- const privateFiles = await ensureContinuityFiles(identity)
80
- const publicSkills = await ensurePublicSkillsFile(identity, { fallback: options.publicSkillsFallback })
81
- return {
82
- ...privateFiles,
83
- 'skills.json': publicSkills,
84
- }
85
- }
86
-
87
- export async function writeIdentityMarkdownScaffold(
88
- identity: EthagentIdentity,
89
- files: IdentityMarkdownScaffold,
90
- ): Promise<ContinuityVaultRef> {
91
- const ref = await writeContinuityFiles(identity, {
92
- 'SOUL.md': files['SOUL.md'],
93
- 'MEMORY.md': files['MEMORY.md'],
94
- })
95
- await writePublicSkillsFile(identity, files['skills.json'])
96
- return ref
97
- }
98
-
99
- export async function syncIdentityMarkdownScaffold(identity: EthagentIdentity): Promise<IdentityMarkdownScaffold> {
100
- const next = await prepareSyncedIdentityMarkdownScaffold(identity)
101
- await writeIdentityMarkdownScaffold(identity, next)
102
- return next
103
- }
104
-
105
- export async function prepareSyncedIdentityMarkdownScaffold(identity: EthagentIdentity): Promise<IdentityMarkdownScaffold> {
106
- await ensureIdentityMarkdownScaffold(identity)
107
- const privateFiles = await readContinuityFiles(identity)
108
- const publicSkills = await readPublicSkillsFile(identity)
109
- const privateDefaults = defaultContinuityFiles(identity)
110
- const publicDefault = defaultPublicSkillsJson(identity)
111
- return {
112
- 'SOUL.md': syncGeneratedMarkdown(privateFiles['SOUL.md'], privateDefaults['SOUL.md'], [
113
- { marker: 'identity', legacyHeading: 'Identity' },
114
- ]),
115
- 'MEMORY.md': syncGeneratedMarkdown(privateFiles['MEMORY.md'], privateDefaults['MEMORY.md'], [
116
- { marker: 'identity' },
117
- ]),
118
- 'skills.json': syncSkillsJson(publicSkills, publicDefault),
119
- }
120
- }
121
-
122
- export async function prepareSyncedPublicSkillsJson(identity: EthagentIdentity): Promise<string> {
123
- await ensurePublicSkillsFile(identity)
124
- const publicSkills = await readPublicSkillsFile(identity)
125
- return syncSkillsJson(publicSkills, defaultPublicSkillsJson(identity))
126
- }
127
-
128
- function syncSkillsJson(existing: string, fresh: string): string {
129
- try {
130
- const existingParsed = JSON.parse(existing)
131
- const freshParsed = JSON.parse(fresh)
132
- const merged = {
133
- ...existingParsed,
134
- ...freshParsed,
135
- skills: existingParsed.skills || freshParsed.skills,
136
- inputModes: existingParsed.inputModes || freshParsed.inputModes,
137
- outputModes: existingParsed.outputModes || freshParsed.outputModes,
138
- }
139
- return `${JSON.stringify(merged, null, 2)}\n`
140
- } catch {
141
- return fresh
142
- }
143
- }
144
-
145
- export async function ensurePublicSkillsFile(
146
- identity: EthagentIdentity,
147
- options: { fallback?: string | (() => Promise<string>) } = {},
148
- ): Promise<string> {
149
- const ref = await ensureContinuityVault(identity)
150
- if (await exists(ref.publicSkillsPath)) return readPublicSkillsFile(identity)
151
-
152
-
153
- const fallback = await resolvePublicSkillsFallback(identity, options.fallback)
154
- await atomicWriteText(ref.publicSkillsPath, ensureTrailingNewline(fallback), { mode: 0o644 })
155
- return readPublicSkillsFile(identity)
156
- }
157
-
158
- export async function readPublicSkillsFile(identity: EthagentIdentity): Promise<string> {
159
- const ref = await ensureContinuityVault(identity)
160
- return readOrDefault(ref.publicSkillsPath, defaultPublicSkillsJson(identity))
161
- }
162
-
163
- export async function writePublicSkillsFile(identity: EthagentIdentity, content: string): Promise<ContinuityVaultRef> {
164
- const ref = await ensureContinuityVault(identity)
165
- await atomicWriteText(ref.publicSkillsPath, ensureTrailingNewline(content), { mode: 0o644 })
166
- return ref
167
- }
168
-
169
- export async function continuityVaultStatus(identity: EthagentIdentity): Promise<{ ready: boolean; files: ContinuityVaultRef }> {
170
- const ref = continuityVaultRef(identity)
171
- const [soul, memory] = await Promise.all([exists(ref.soulPath), exists(ref.memoryPath)])
172
- return { ready: soul && memory, files: ref }
173
- }
174
-
175
- export async function continuityWorkingTreeStatus(
176
- identity: EthagentIdentity,
177
- publishedSnapshot?: { contentHashes?: ContinuitySnapshotContentHashes },
178
- ): Promise<ContinuityWorkingTreeStatus> {
179
- const ref = continuityVaultRef(identity)
180
- const stats = await Promise.all([
181
- statIfExists(ref.soulPath),
182
- statIfExists(ref.memoryPath),
183
- statIfExists(ref.publicSkillsPath),
184
- ])
185
- const newestMs = Math.max(0, ...stats.flatMap(stat => stat ? [stat.mtimeMs] : []))
186
- const ready = Boolean(stats[0] && stats[1])
187
- const localContentHashes = ready
188
- ? await localContinuitySnapshotContentHashes(identity).catch(() => undefined)
189
- : undefined
190
- const publishedContentHashes = publishedSnapshot?.contentHashes
191
- const publishState: ContinuityPublishState = !ready
192
- ? 'not-restored'
193
- : !identity.backup?.cid
194
- ? 'not-published'
195
- : !localContentHashes || !publishedContentHashes
196
- ? 'verify-needed'
197
- : equalContinuitySnapshotHashes(localContentHashes, publishedContentHashes)
198
- ? 'published'
199
- : 'local-changes'
200
-
201
- return {
202
- ready,
203
- ...(newestMs > 0 ? { newestLocalChangeAt: new Date(newestMs).toISOString() } : {}),
204
- localChangedAfterBackup: publishState === 'local-changes',
205
- publishState,
206
- ...(localContentHashes ? { localContentHashes } : {}),
207
- ...(publishedContentHashes ? { publishedContentHashes } : {}),
208
- }
209
- }
210
-
211
- export async function localContinuitySnapshotContentHashes(
212
- identity: EthagentIdentity,
213
- ): Promise<ContinuitySnapshotContentHashes> {
214
- const privateFiles = await readContinuityFiles(identity)
215
- const publicSkills = await readPublicSkillsFile(identity)
216
- return continuitySnapshotContentHashes(privateFiles, publicSkills)
217
- }
218
-
219
- export function continuitySnapshotContentHashes(
220
- privateFiles: ContinuityFiles,
221
- publicSkills: string,
222
- ): ContinuitySnapshotContentHashes {
223
- return {
224
- 'SOUL.md': hashContinuitySnapshotContent(privateFiles['SOUL.md']),
225
- 'MEMORY.md': hashContinuitySnapshotContent(privateFiles['MEMORY.md']),
226
- 'skills.json': hashContinuitySnapshotContent(publicSkills),
227
- }
228
- }
229
-
230
- export function equalContinuitySnapshotHashes(
231
- a: ContinuitySnapshotContentHashes,
232
- b: ContinuitySnapshotContentHashes,
233
- ): boolean {
234
- return a['SOUL.md'] === b['SOUL.md']
235
- && a['MEMORY.md'] === b['MEMORY.md']
236
- && a['skills.json'] === b['skills.json']
237
- }
238
-
239
- function hashContinuitySnapshotContent(value: string): string {
240
- return createHash('sha256').update(normalizeSnapshotContent(value), 'utf8').digest('hex')
241
- }
242
-
243
- function normalizeSnapshotContent(value: string): string {
244
- const normalized = value.replace(/\r\n?/g, '\n')
245
- return normalized.endsWith('\n') ? normalized : `${normalized}\n`
246
- }
247
-
248
- export function continuityAgentSnapshot(identity: EthagentIdentity): ContinuityAgentSnapshot {
249
- const state = identity.state ?? {}
250
- return {
251
- ...(identity.chainId ? { chainId: identity.chainId } : {}),
252
- ...(identity.identityRegistryAddress ? { identityRegistryAddress: identity.identityRegistryAddress } : {}),
253
- ...(identity.agentId ? { agentId: identity.agentId } : {}),
254
- ...(identity.agentUri ? { agentUri: identity.agentUri } : {}),
255
- ...(identity.metadataCid ? { metadataCid: identity.metadataCid } : {}),
256
- ...(typeof state.name === 'string' ? { name: state.name } : {}),
257
- ...(typeof state.description === 'string' ? { description: state.description } : {}),
258
- }
259
- }
260
-
261
- export function defaultContinuityFiles(identity: EthagentIdentity, now = new Date()): ContinuityFiles {
262
- const owner = identity.ownerAddress ?? identity.address
263
- const created = now.toISOString().slice(0, 10)
264
- const identityBlock = renderPrivateIdentityBlock({
265
- owner,
266
- token: identity.agentId ? `#${identity.agentId}` : 'pending registration',
267
- chainId: identity.chainId ? identity.chainId.toString() : 'unknown',
268
- registry: identity.identityRegistryAddress ?? 'unknown',
269
- })
270
- return {
271
- 'SOUL.md': [
272
- '# SOUL.md',
273
- '',
274
- identityBlock,
275
- '',
276
- '## Persona',
277
- '',
278
- '- Describe the private agent persona, voice, and collaboration style.',
279
- '- Keep standing behavior that should survive model switches and device restores.',
280
- '- Prefer stable guidance over session-specific preferences.',
281
- '',
282
- '## Operating Principles',
283
- '',
284
- '- Record durable values, decision preferences, and owner-approved working principles.',
285
- '- Keep implementation-specific facts in MEMORY.md unless they define behavior.',
286
- '',
287
- '## Private Instructions',
288
- '',
289
- '- Keep owner-specific standing instructions in this file.',
290
- '- Do not share this file directly; save it via the Identity Hub encrypted snapshot.',
291
- '- Public capabilities belong in skills.json.',
292
- '',
293
- '## Boundaries',
294
- '',
295
- '- Record private behavioral limits and owner-approved constraints here.',
296
- '- Do not store seed phrases, private keys, raw wallet signatures, or API keys.',
297
- '- Do not place public delegation claims here; keep them in skills.json.',
298
- '',
299
- '## Maintenance Rules',
300
- '',
301
- '- Keep the generated Agent Identity block intact; edit owner-authored sections below it.',
302
- '- Do not duplicate the mutable public agent name here; it lives in token metadata and the Agent Card.',
303
- '- Move factual project memory to MEMORY.md when it is not persona or instruction material.',
304
- '- Revise or remove stale guidance instead of accumulating contradictions.',
305
- '',
306
- '## Change Notes',
307
- '',
308
- '- Add dated notes when the persona or long-lived private guidance changes.',
309
- '',
310
- `Created: ${created}`,
311
- ].join('\n') + '\n',
312
- 'MEMORY.md': [
313
- '# MEMORY.md',
314
- '',
315
- identityBlock,
316
- '',
317
- '## Durable User Preferences',
318
- '',
319
- '- Add long-lived owner preferences that should survive across sessions and model switches.',
320
- '',
321
- '## Project Context',
322
- '',
323
- '- Add stable project facts, repo conventions, and active workstreams.',
324
- '',
325
- '## Decisions and Rationale',
326
- '',
327
- '- Record important decisions and why they were made.',
328
- '',
329
- '## Facts to Revalidate',
330
- '',
331
- '- Add time-sensitive facts that should be checked before reuse, with dates or source context when available.',
332
- '',
333
- '## Maintenance Rules',
334
- '',
335
- '- Prefer stable facts, preferences, and decisions over chat transcripts.',
336
- '- Do not duplicate the mutable public agent name here; it lives in token metadata and the Agent Card.',
337
- '- Add dates or source context when a note may become stale or environment-specific.',
338
- '- Remove or rewrite stale memory instead of accumulating contradictions.',
339
- '',
340
- '## Boundaries',
341
- '',
342
- '- Do not store seed phrases, private keys, raw wallet signatures, or API keys.',
343
- '- Do not store secrets unless the user explicitly asks and the risk is clear.',
344
- '- Keep public capabilities in skills.json.',
345
- '',
346
- `Created: ${created}`,
347
- ].join('\n') + '\n',
348
- }
349
- }
350
-
351
- export function defaultPublicSkillsJson(identity: EthagentIdentity): string {
352
- return renderPublicSkillsJson(defaultPublicSkillsProfile(identity))
353
- }
354
-
355
- function continuityVaultId(identity: Pick<EthagentIdentity, 'chainId' | 'identityRegistryAddress' | 'agentId' | 'address'>): string {
356
- const chain = identity.chainId?.toString() ?? 'unknown-chain'
357
- const registry = sanitizePathPart(identity.identityRegistryAddress ?? 'unknown-registry')
358
- const token = sanitizePathPart(identity.agentId ?? identity.address)
359
- return `${chain}-${registry}-${token}`
360
- }
361
-
362
- function sanitizePathPart(value: string): string {
363
- return value.trim().toLowerCase().replace(/^0x/, '').replace(/[^a-z0-9._-]+/g, '-').slice(0, 120) || 'unknown'
364
- }
365
-
366
- async function writeMissingPrivateFile(file: string, content: string): Promise<void> {
367
- if (await exists(file)) return
368
- await atomicWriteText(file, ensureTrailingNewline(content), { mode: 0o600 })
369
- }
370
-
371
- async function resolvePublicSkillsFallback(
372
- identity: EthagentIdentity,
373
- fallback: string | (() => Promise<string>) | undefined,
374
- ): Promise<string> {
375
- if (typeof fallback === 'string') return fallback
376
- if (fallback) {
377
- try {
378
- return await fallback()
379
- } catch {
380
- return defaultPublicSkillsJson(identity)
381
- }
382
- }
383
- return defaultPublicSkillsJson(identity)
384
- }
385
-
386
- async function readOrDefault(file: string, fallback: string): Promise<string> {
387
- try {
388
- return await fs.readFile(file, 'utf8')
389
- } catch (err: unknown) {
390
- if ((err as NodeJS.ErrnoException).code === 'ENOENT') return fallback
391
- throw err
392
- }
393
- }
394
-
395
- async function exists(file: string): Promise<boolean> {
396
- try {
397
- await fs.access(file)
398
- return true
399
- } catch {
400
- return false
401
- }
402
- }
403
-
404
- async function statIfExists(file: string): Promise<import('node:fs').Stats | null> {
405
- try {
406
- return await fs.stat(file)
407
- } catch {
408
- return null
409
- }
410
- }
411
-
412
- function ensureTrailingNewline(value: string): string {
413
- return value.endsWith('\n') ? value : `${value}\n`
414
- }
415
-
416
- type SyncBlock = {
417
- marker: string
418
- legacyHeading?: string
419
- }
420
-
421
- function renderPrivateIdentityBlock(args: {
422
- owner: string
423
- token: string
424
- chainId: string
425
- registry: string
426
- }): string {
427
- return [
428
- '<!-- ethagent:identity:start -->',
429
- '## Agent Identity',
430
- `- Owner wallet: ${args.owner}`,
431
- `- ERC-8004 token: ${args.token}`,
432
- `- Chain ID: ${args.chainId}`,
433
- `- Registry: ${args.registry}`,
434
- '- Visibility: private local working file; encrypted before IPFS backup.',
435
- '<!-- ethagent:identity:end -->',
436
- ].join('\n')
437
- }
438
-
439
- function syncGeneratedMarkdown(existing: string, fresh: string, blocks: SyncBlock[]): string {
440
- let next = replaceFirstHeading(existing, firstHeading(fresh))
441
- for (const block of blocks) {
442
- next = replaceOrInsertMarkedBlock(next, fresh, block)
443
- }
444
- return ensureTrailingNewline(next)
445
- }
446
-
447
- function firstHeading(markdown: string): string {
448
- return markdown.split(/\r?\n/).find(line => line.startsWith('# ')) ?? ''
449
- }
450
-
451
- function replaceFirstHeading(markdown: string, heading: string): string {
452
- if (!heading) return markdown
453
- const lines = markdown.split(/\r?\n/)
454
- const index = lines.findIndex(line => line.startsWith('# '))
455
- if (index === -1) return `${heading}\n\n${markdown.trimStart()}`
456
- lines[index] = heading
457
- return lines.join('\n')
458
- }
459
-
460
- function replaceOrInsertMarkedBlock(markdown: string, fresh: string, block: SyncBlock): string {
461
- const freshBlock = extractMarkedBlock(fresh, block.marker)
462
- if (!freshBlock) return markdown
463
- const replaced = replaceMarkedBlock(markdown, block.marker, freshBlock)
464
- if (replaced) return replaced
465
- if (block.legacyHeading) {
466
- const replacedLegacy = replaceMarkdownSection(markdown, block.legacyHeading, freshBlock)
467
- if (replacedLegacy) return replacedLegacy
468
- }
469
- return insertAfterFirstHeading(markdown, freshBlock)
470
- }
471
-
472
- function extractMarkedBlock(markdown: string, marker: string): string | null {
473
- const start = `<!-- ethagent:${marker}:start -->`
474
- const end = `<!-- ethagent:${marker}:end -->`
475
- const startIndex = markdown.indexOf(start)
476
- const endIndex = markdown.indexOf(end, startIndex + start.length)
477
- if (startIndex === -1 || endIndex === -1) return null
478
- return markdown.slice(startIndex, endIndex + end.length).trim()
479
- }
480
-
481
- function replaceMarkedBlock(markdown: string, marker: string, replacement: string): string | null {
482
- const start = `<!-- ethagent:${marker}:start -->`
483
- const end = `<!-- ethagent:${marker}:end -->`
484
- const startIndex = markdown.indexOf(start)
485
- const endIndex = markdown.indexOf(end, startIndex + start.length)
486
- if (startIndex === -1 || endIndex === -1) return null
487
- return `${markdown.slice(0, startIndex)}${replacement}${markdown.slice(endIndex + end.length)}`
488
- }
489
-
490
- function replaceMarkdownSection(markdown: string, heading: string, replacement: string): string | null {
491
- const lines = markdown.split(/\r?\n/)
492
- const start = lines.findIndex(line => line.trim() === `## ${heading}`)
493
- if (start === -1) return null
494
- const end = lines.findIndex((line, index) => index > start && /^##\s+/.test(line))
495
- const before = lines.slice(0, start)
496
- const after = end === -1 ? [] : lines.slice(end)
497
- return [...before, replacement, '', ...after].join('\n')
498
- }
499
-
500
- function insertAfterFirstHeading(markdown: string, block: string): string {
501
- const lines = markdown.split(/\r?\n/)
502
- const headingIndex = lines.findIndex(line => line.startsWith('# '))
503
- if (headingIndex === -1) return `${block}\n\n${markdown.trimStart()}`
504
- const before = lines.slice(0, headingIndex + 1)
505
- const after = lines.slice(headingIndex + 1)
506
- return [...before, '', block, '', ...after].join('\n')
507
- }
1
+ export type {
2
+ ContinuityPublishState,
3
+ ContinuitySnapshotContentHashes,
4
+ ContinuitySnapshotFile,
5
+ ContinuityVaultRef,
6
+ ContinuityWorkingTreeStatus,
7
+ IdentityMarkdownScaffold,
8
+ PrivateContinuityFile,
9
+ } from './storage/types.js'
10
+ export { continuityVaultRef } from './storage/paths.js'
11
+ export {
12
+ ensureContinuityFiles,
13
+ ensureContinuityVault,
14
+ readContinuityFiles,
15
+ writeContinuityFiles,
16
+ } from './storage/files.js'
17
+ export { continuityAgentSnapshot, defaultContinuityFiles } from './storage/defaults.js'
18
+ export {
19
+ ensureIdentityMarkdownScaffold,
20
+ ensurePublicSkillsFile,
21
+ prepareSyncedIdentityMarkdownScaffold,
22
+ prepareSyncedPublicSkillsJson,
23
+ readPublicSkillsFile,
24
+ syncIdentityMarkdownScaffold,
25
+ writeIdentityMarkdownScaffold,
26
+ writePublicSkillsFile,
27
+ } from './storage/scaffold.js'
28
+ export {
29
+ continuityVaultStatus,
30
+ continuityWorkingTreeStatus,
31
+ localContinuitySnapshotContentHashes,
32
+ } from './storage/status.js'
@@ -0,0 +1,95 @@
1
+ export type ZipEntry = {
2
+ name: string
3
+ data: Buffer
4
+ }
5
+
6
+ const crcTable = (() => {
7
+ const table = new Uint32Array(256)
8
+ for (let i = 0; i < 256; i++) {
9
+ let c = i
10
+ for (let k = 0; k < 8; k++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)
11
+ table[i] = c
12
+ }
13
+ return table
14
+ })()
15
+
16
+ export function crc32(buf: Buffer): number {
17
+ let c = 0xFFFFFFFF
18
+ for (let i = 0; i < buf.length; i++) {
19
+ const byte = buf[i] as number
20
+ c = (crcTable[(c ^ byte) & 0xff] as number) ^ (c >>> 8)
21
+ }
22
+ return (c ^ 0xFFFFFFFF) >>> 0
23
+ }
24
+
25
+ export function buildZip(entries: ZipEntry[], modifiedAt: Date = new Date()): Buffer {
26
+ const dosTime = toDosTime(modifiedAt)
27
+ const dosDate = toDosDate(modifiedAt)
28
+ const localParts: Buffer[] = []
29
+ const centralParts: Buffer[] = []
30
+ let offset = 0
31
+ for (const entry of entries) {
32
+ const nameBuf = Buffer.from(entry.name, 'utf8')
33
+ const crc = crc32(entry.data)
34
+ const size = entry.data.length
35
+
36
+ const localHeader = Buffer.alloc(30)
37
+ localHeader.writeUInt32LE(0x04034b50, 0)
38
+ localHeader.writeUInt16LE(20, 4)
39
+ localHeader.writeUInt16LE(0x0800, 6)
40
+ localHeader.writeUInt16LE(0, 8)
41
+ localHeader.writeUInt16LE(dosTime, 10)
42
+ localHeader.writeUInt16LE(dosDate, 12)
43
+ localHeader.writeUInt32LE(crc, 14)
44
+ localHeader.writeUInt32LE(size, 18)
45
+ localHeader.writeUInt32LE(size, 22)
46
+ localHeader.writeUInt16LE(nameBuf.length, 26)
47
+ localHeader.writeUInt16LE(0, 28)
48
+ localParts.push(localHeader, nameBuf, entry.data)
49
+
50
+ const centralHeader = Buffer.alloc(46)
51
+ centralHeader.writeUInt32LE(0x02014b50, 0)
52
+ centralHeader.writeUInt16LE(20, 4)
53
+ centralHeader.writeUInt16LE(20, 6)
54
+ centralHeader.writeUInt16LE(0x0800, 8)
55
+ centralHeader.writeUInt16LE(0, 10)
56
+ centralHeader.writeUInt16LE(dosTime, 12)
57
+ centralHeader.writeUInt16LE(dosDate, 14)
58
+ centralHeader.writeUInt32LE(crc, 16)
59
+ centralHeader.writeUInt32LE(size, 20)
60
+ centralHeader.writeUInt32LE(size, 24)
61
+ centralHeader.writeUInt16LE(nameBuf.length, 28)
62
+ centralHeader.writeUInt16LE(0, 30)
63
+ centralHeader.writeUInt16LE(0, 32)
64
+ centralHeader.writeUInt16LE(0, 34)
65
+ centralHeader.writeUInt16LE(0, 36)
66
+ centralHeader.writeUInt32LE(0, 38)
67
+ centralHeader.writeUInt32LE(offset, 42)
68
+ centralParts.push(centralHeader, nameBuf)
69
+
70
+ offset += localHeader.length + nameBuf.length + entry.data.length
71
+ }
72
+
73
+ const centralBuf = Buffer.concat(centralParts)
74
+ const localBuf = Buffer.concat(localParts)
75
+ const eocd = Buffer.alloc(22)
76
+ eocd.writeUInt32LE(0x06054b50, 0)
77
+ eocd.writeUInt16LE(0, 4)
78
+ eocd.writeUInt16LE(0, 6)
79
+ eocd.writeUInt16LE(entries.length, 8)
80
+ eocd.writeUInt16LE(entries.length, 10)
81
+ eocd.writeUInt32LE(centralBuf.length, 12)
82
+ eocd.writeUInt32LE(localBuf.length, 16)
83
+ eocd.writeUInt16LE(0, 20)
84
+
85
+ return Buffer.concat([localBuf, centralBuf, eocd])
86
+ }
87
+
88
+ function toDosTime(date: Date): number {
89
+ return ((date.getHours() & 0x1f) << 11) | ((date.getMinutes() & 0x3f) << 5) | ((date.getSeconds() / 2) & 0x1f)
90
+ }
91
+
92
+ function toDosDate(date: Date): number {
93
+ const year = Math.max(1980, date.getFullYear()) - 1980
94
+ return ((year & 0x7f) << 9) | (((date.getMonth() + 1) & 0x0f) << 5) | (date.getDate() & 0x1f)
95
+ }