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
@@ -0,0 +1,81 @@
1
+ type SyncBlock = {
2
+ marker: string
3
+ }
4
+
5
+ export function renderPrivateIdentityBlock(args: {
6
+ owner: string
7
+ token: string
8
+ chainId: string
9
+ registry: string
10
+ }): string {
11
+ return [
12
+ '<!-- ethagent:identity:start -->',
13
+ '## Agent Identity',
14
+ `- Owner wallet: ${args.owner}`,
15
+ `- ERC-8004 token: ${args.token}`,
16
+ `- Chain ID: ${args.chainId}`,
17
+ `- Registry: ${args.registry}`,
18
+ '- Visibility: private local working file; encrypted before IPFS backup.',
19
+ '<!-- ethagent:identity:end -->',
20
+ ].join('\n')
21
+ }
22
+
23
+ export function syncGeneratedMarkdown(existing: string, fresh: string, blocks: SyncBlock[]): string {
24
+ let next = replaceFirstHeading(existing, firstHeading(fresh))
25
+ for (const block of blocks) {
26
+ next = replaceOrInsertMarkedBlock(next, fresh, block)
27
+ }
28
+ return ensureTrailingNewline(next)
29
+ }
30
+
31
+ function firstHeading(markdown: string): string {
32
+ return markdown.split(/\r?\n/).find(line => line.startsWith('# ')) ?? ''
33
+ }
34
+
35
+ function replaceFirstHeading(markdown: string, heading: string): string {
36
+ if (!heading) return markdown
37
+ const lines = markdown.split(/\r?\n/)
38
+ const index = lines.findIndex(line => line.startsWith('# '))
39
+ if (index === -1) return `${heading}\n\n${markdown.trimStart()}`
40
+ lines[index] = heading
41
+ return lines.join('\n')
42
+ }
43
+
44
+ function replaceOrInsertMarkedBlock(markdown: string, fresh: string, block: SyncBlock): string {
45
+ const freshBlock = extractMarkedBlock(fresh, block.marker)
46
+ if (!freshBlock) return markdown
47
+ const replaced = replaceMarkedBlock(markdown, block.marker, freshBlock)
48
+ if (replaced) return replaced
49
+ return insertAfterFirstHeading(markdown, freshBlock)
50
+ }
51
+
52
+ function extractMarkedBlock(markdown: string, marker: string): string | null {
53
+ const start = `<!-- ethagent:${marker}:start -->`
54
+ const end = `<!-- ethagent:${marker}:end -->`
55
+ const startIndex = markdown.indexOf(start)
56
+ const endIndex = markdown.indexOf(end, startIndex + start.length)
57
+ if (startIndex === -1 || endIndex === -1) return null
58
+ return markdown.slice(startIndex, endIndex + end.length).trim()
59
+ }
60
+
61
+ function replaceMarkedBlock(markdown: string, marker: string, replacement: string): string | null {
62
+ const start = `<!-- ethagent:${marker}:start -->`
63
+ const end = `<!-- ethagent:${marker}:end -->`
64
+ const startIndex = markdown.indexOf(start)
65
+ const endIndex = markdown.indexOf(end, startIndex + start.length)
66
+ if (startIndex === -1 || endIndex === -1) return null
67
+ return `${markdown.slice(0, startIndex)}${replacement}${markdown.slice(endIndex + end.length)}`
68
+ }
69
+
70
+ function insertAfterFirstHeading(markdown: string, block: string): string {
71
+ const lines = markdown.split(/\r?\n/)
72
+ const headingIndex = lines.findIndex(line => line.startsWith('# '))
73
+ if (headingIndex === -1) return `${block}\n\n${markdown.trimStart()}`
74
+ const before = lines.slice(0, headingIndex + 1)
75
+ const after = lines.slice(headingIndex + 1)
76
+ return [...before, '', block, '', ...after].join('\n')
77
+ }
78
+
79
+ function ensureTrailingNewline(value: string): string {
80
+ return value.endsWith('\n') ? value : `${value}\n`
81
+ }
@@ -0,0 +1,24 @@
1
+ import path from 'node:path'
2
+ import { getConfigDir, type EthagentIdentity } from '../../../storage/config.js'
3
+ import type { ContinuityVaultRef } from './types.js'
4
+
5
+ export function continuityVaultRef(identity: Pick<EthagentIdentity, 'chainId' | 'identityRegistryAddress' | 'agentId' | 'address'>): ContinuityVaultRef {
6
+ const dir = path.join(getConfigDir(), 'continuity', continuityVaultId(identity))
7
+ return {
8
+ dir,
9
+ soulPath: path.join(dir, 'SOUL.md'),
10
+ memoryPath: path.join(dir, 'MEMORY.md'),
11
+ publicSkillsPath: path.join(dir, 'skills.json'),
12
+ }
13
+ }
14
+
15
+ function continuityVaultId(identity: Pick<EthagentIdentity, 'chainId' | 'identityRegistryAddress' | 'agentId' | 'address'>): string {
16
+ const chain = identity.chainId?.toString() ?? 'unknown-chain'
17
+ const registry = sanitizePathPart(identity.identityRegistryAddress ?? 'unknown-registry')
18
+ const token = sanitizePathPart(identity.agentId ?? identity.address)
19
+ return `${chain}-${registry}-${token}`
20
+ }
21
+
22
+ function sanitizePathPart(value: string): string {
23
+ return value.trim().toLowerCase().replace(/^0x/, '').replace(/[^a-z0-9._-]+/g, '-').slice(0, 120) || 'unknown'
24
+ }
@@ -0,0 +1,124 @@
1
+ import { atomicWriteText } from '../../../storage/atomicWrite.js'
2
+ import type { EthagentIdentity } from '../../../storage/config.js'
3
+ import type { ContinuityFiles } from '../envelope.js'
4
+ import { defaultContinuityFiles, defaultPublicSkillsJson } from './defaults.js'
5
+ import {
6
+ ensureContinuityFiles,
7
+ ensureContinuityVault,
8
+ ensureTrailingNewline,
9
+ exists,
10
+ readContinuityFiles,
11
+ readOrDefault,
12
+ writeContinuityFiles,
13
+ } from './files.js'
14
+ import { syncGeneratedMarkdown } from './markdown.js'
15
+ import type { ContinuityVaultRef, IdentityMarkdownScaffold } from './types.js'
16
+
17
+ export async function ensureIdentityMarkdownScaffold(
18
+ identity: EthagentIdentity,
19
+ options: { publicSkillsFallback?: string | (() => Promise<string>) } = {},
20
+ ): Promise<IdentityMarkdownScaffold> {
21
+ const privateFiles = await ensureContinuityFiles(identity)
22
+ const publicSkills = await ensurePublicSkillsFile(identity, { fallback: options.publicSkillsFallback })
23
+ return {
24
+ ...privateFiles,
25
+ 'skills.json': publicSkills,
26
+ }
27
+ }
28
+
29
+ export async function writeIdentityMarkdownScaffold(
30
+ identity: EthagentIdentity,
31
+ files: IdentityMarkdownScaffold,
32
+ ): Promise<ContinuityVaultRef> {
33
+ const ref = await writeContinuityFiles(identity, {
34
+ 'SOUL.md': files['SOUL.md'],
35
+ 'MEMORY.md': files['MEMORY.md'],
36
+ })
37
+ await writePublicSkillsFile(identity, files['skills.json'])
38
+ return ref
39
+ }
40
+
41
+ export async function syncIdentityMarkdownScaffold(identity: EthagentIdentity): Promise<IdentityMarkdownScaffold> {
42
+ const next = await prepareSyncedIdentityMarkdownScaffold(identity)
43
+ await writeIdentityMarkdownScaffold(identity, next)
44
+ return next
45
+ }
46
+
47
+ export async function prepareSyncedIdentityMarkdownScaffold(identity: EthagentIdentity): Promise<IdentityMarkdownScaffold> {
48
+ await ensureIdentityMarkdownScaffold(identity)
49
+ const privateFiles = await readContinuityFiles(identity)
50
+ const publicSkills = await readPublicSkillsFile(identity)
51
+ const privateDefaults = defaultContinuityFiles(identity)
52
+ const publicDefault = defaultPublicSkillsJson(identity)
53
+ return {
54
+ 'SOUL.md': syncGeneratedMarkdown(privateFiles['SOUL.md'], privateDefaults['SOUL.md'], [
55
+ { marker: 'identity' },
56
+ ]),
57
+ 'MEMORY.md': syncGeneratedMarkdown(privateFiles['MEMORY.md'], privateDefaults['MEMORY.md'], [
58
+ { marker: 'identity' },
59
+ ]),
60
+ 'skills.json': syncSkillsJson(publicSkills, publicDefault),
61
+ }
62
+ }
63
+
64
+ export async function prepareSyncedPublicSkillsJson(identity: EthagentIdentity): Promise<string> {
65
+ await ensurePublicSkillsFile(identity)
66
+ const publicSkills = await readPublicSkillsFile(identity)
67
+ return syncSkillsJson(publicSkills, defaultPublicSkillsJson(identity))
68
+ }
69
+
70
+ function syncSkillsJson(existing: string, fresh: string): string {
71
+ try {
72
+ const existingParsed = JSON.parse(existing)
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
+ }
86
+ }
87
+
88
+ export async function ensurePublicSkillsFile(
89
+ identity: EthagentIdentity,
90
+ options: { fallback?: string | (() => Promise<string>) } = {},
91
+ ): Promise<string> {
92
+ const ref = await ensureContinuityVault(identity)
93
+ if (await exists(ref.publicSkillsPath)) return readPublicSkillsFile(identity)
94
+
95
+ const fallback = await resolvePublicSkillsFallback(identity, options.fallback)
96
+ await atomicWriteText(ref.publicSkillsPath, ensureTrailingNewline(fallback), { mode: 0o644 })
97
+ return readPublicSkillsFile(identity)
98
+ }
99
+
100
+ export async function readPublicSkillsFile(identity: EthagentIdentity): Promise<string> {
101
+ const ref = await ensureContinuityVault(identity)
102
+ return readOrDefault(ref.publicSkillsPath, defaultPublicSkillsJson(identity))
103
+ }
104
+
105
+ export async function writePublicSkillsFile(identity: EthagentIdentity, content: string): Promise<ContinuityVaultRef> {
106
+ const ref = await ensureContinuityVault(identity)
107
+ await atomicWriteText(ref.publicSkillsPath, ensureTrailingNewline(content), { mode: 0o644 })
108
+ return ref
109
+ }
110
+
111
+ async function resolvePublicSkillsFallback(
112
+ identity: EthagentIdentity,
113
+ fallback: string | (() => Promise<string>) | undefined,
114
+ ): Promise<string> {
115
+ if (typeof fallback === 'string') return fallback
116
+ if (fallback) {
117
+ try {
118
+ return await fallback()
119
+ } catch {
120
+ return defaultPublicSkillsJson(identity)
121
+ }
122
+ }
123
+ return defaultPublicSkillsJson(identity)
124
+ }
@@ -0,0 +1,86 @@
1
+ import { createHash } from 'node:crypto'
2
+ import type { EthagentIdentity } from '../../../storage/config.js'
3
+ import type { ContinuityFiles } from '../envelope.js'
4
+ import { continuityVaultRef } from './paths.js'
5
+ import { exists, readContinuityFiles, statIfExists } from './files.js'
6
+ import { readPublicSkillsFile } from './scaffold.js'
7
+ import type { ContinuityPublishState, ContinuitySnapshotContentHashes, ContinuityVaultRef, ContinuityWorkingTreeStatus } from './types.js'
8
+
9
+ export async function continuityVaultStatus(identity: EthagentIdentity): Promise<{ ready: boolean; files: ContinuityVaultRef }> {
10
+ const ref = continuityVaultRef(identity)
11
+ const [soul, memory] = await Promise.all([exists(ref.soulPath), exists(ref.memoryPath)])
12
+ return { ready: soul && memory, files: ref }
13
+ }
14
+
15
+ export async function continuityWorkingTreeStatus(
16
+ identity: EthagentIdentity,
17
+ publishedSnapshot?: { contentHashes?: ContinuitySnapshotContentHashes },
18
+ ): Promise<ContinuityWorkingTreeStatus> {
19
+ const ref = continuityVaultRef(identity)
20
+ const stats = await Promise.all([
21
+ statIfExists(ref.soulPath),
22
+ statIfExists(ref.memoryPath),
23
+ statIfExists(ref.publicSkillsPath),
24
+ ])
25
+ const newestMs = Math.max(0, ...stats.flatMap(stat => stat ? [stat.mtimeMs] : []))
26
+ const ready = Boolean(stats[0] && stats[1])
27
+ const localContentHashes = ready
28
+ ? await localContinuitySnapshotContentHashes(identity).catch(() => undefined)
29
+ : undefined
30
+ const publishedContentHashes = publishedSnapshot?.contentHashes
31
+ const publishState: ContinuityPublishState = !ready
32
+ ? 'not-restored'
33
+ : !identity.backup?.cid
34
+ ? 'not-published'
35
+ : !localContentHashes || !publishedContentHashes
36
+ ? 'verify-needed'
37
+ : equalContinuitySnapshotHashes(localContentHashes, publishedContentHashes)
38
+ ? 'published'
39
+ : 'local-changes'
40
+
41
+ return {
42
+ ready,
43
+ ...(newestMs > 0 ? { newestLocalChangeAt: new Date(newestMs).toISOString() } : {}),
44
+ localChangedAfterBackup: publishState === 'local-changes',
45
+ publishState,
46
+ ...(localContentHashes ? { localContentHashes } : {}),
47
+ ...(publishedContentHashes ? { publishedContentHashes } : {}),
48
+ }
49
+ }
50
+
51
+ export async function localContinuitySnapshotContentHashes(
52
+ identity: EthagentIdentity,
53
+ ): Promise<ContinuitySnapshotContentHashes> {
54
+ const privateFiles = await readContinuityFiles(identity)
55
+ const publicSkills = await readPublicSkillsFile(identity)
56
+ return continuitySnapshotContentHashes(privateFiles, publicSkills)
57
+ }
58
+
59
+ function continuitySnapshotContentHashes(
60
+ privateFiles: ContinuityFiles,
61
+ publicSkills: string,
62
+ ): ContinuitySnapshotContentHashes {
63
+ return {
64
+ 'SOUL.md': hashContinuitySnapshotContent(privateFiles['SOUL.md']),
65
+ 'MEMORY.md': hashContinuitySnapshotContent(privateFiles['MEMORY.md']),
66
+ 'skills.json': hashContinuitySnapshotContent(publicSkills),
67
+ }
68
+ }
69
+
70
+ function equalContinuitySnapshotHashes(
71
+ a: ContinuitySnapshotContentHashes,
72
+ b: ContinuitySnapshotContentHashes,
73
+ ): boolean {
74
+ return a['SOUL.md'] === b['SOUL.md']
75
+ && a['MEMORY.md'] === b['MEMORY.md']
76
+ && a['skills.json'] === b['skills.json']
77
+ }
78
+
79
+ function hashContinuitySnapshotContent(value: string): string {
80
+ return createHash('sha256').update(normalizeSnapshotContent(value), 'utf8').digest('hex')
81
+ }
82
+
83
+ function normalizeSnapshotContent(value: string): string {
84
+ const normalized = value.replace(/\r\n?/g, '\n')
85
+ return normalized.endsWith('\n') ? normalized : `${normalized}\n`
86
+ }
@@ -0,0 +1,27 @@
1
+ import type { ContinuityFiles } from '../envelope.js'
2
+
3
+ export const PRIVATE_CONTINUITY_FILES = ['SOUL.md', 'MEMORY.md'] as const
4
+ export type PrivateContinuityFile = (typeof PRIVATE_CONTINUITY_FILES)[number]
5
+
6
+ export type ContinuityVaultRef = {
7
+ dir: string
8
+ soulPath: string
9
+ memoryPath: string
10
+ publicSkillsPath: string
11
+ }
12
+
13
+ export type IdentityMarkdownScaffold = ContinuityFiles & {
14
+ 'skills.json': string
15
+ }
16
+
17
+ export type ContinuitySnapshotFile = PrivateContinuityFile | 'skills.json'
18
+ export type ContinuitySnapshotContentHashes = Record<ContinuitySnapshotFile, string>
19
+ export type ContinuityPublishState = 'not-restored' | 'not-published' | 'verify-needed' | 'local-changes' | 'published'
20
+ export type ContinuityWorkingTreeStatus = {
21
+ ready: boolean
22
+ newestLocalChangeAt?: string
23
+ localChangedAfterBackup: boolean
24
+ publishState: ContinuityPublishState
25
+ localContentHashes?: ContinuitySnapshotContentHashes
26
+ publishedContentHashes?: ContinuitySnapshotContentHashes
27
+ }