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,40 +1,14 @@
1
- import fs from 'node:fs/promises'
2
1
  import path from 'node:path'
3
2
  import { atomicWriteText } from '../../storage/atomicWrite.js'
4
- import type { EthagentConfig, EthagentIdentity } from '../../storage/config.js'
5
- import { applyRequestedEdit } from '../../tools/editUtils.js'
6
- import {
7
- continuityVaultRef,
8
- defaultContinuityFiles,
9
- ensureContinuityVault,
10
- type PrivateContinuityFile,
11
- } from './storage.js'
3
+ import type { EthagentConfig } from '../../storage/config.js'
4
+ import { ensureContinuityVault } from './storage.js'
5
+ import { ensureTrailingNewline } from './storage/files.js'
6
+ import { applyPrivateContinuityEdit } from './privateEdit/apply.js'
7
+ import { renderPrivateContinuityDiff } from './privateEdit/diff.js'
8
+ import { privateContinuityPath, readPrivateContinuityFile } from './privateEdit/files.js'
9
+ import type { PreparedPrivateContinuityEdit, PrivateContinuityEditInput } from './privateEdit/types.js'
12
10
 
13
- export type PrivateContinuityEditInput = {
14
- file: PrivateContinuityFile
15
- oldText?: string
16
- newText?: string
17
- appendToSection?: string
18
- appendText?: string
19
- replaceAll?: boolean
20
- replaceWholeFile?: boolean
21
- }
22
-
23
- export type PreparedPrivateContinuityEdit = {
24
- identity: EthagentIdentity
25
- file: PrivateContinuityFile
26
- fullPath: string
27
- relativePath: string
28
- directoryPath: string
29
- existedBefore: boolean
30
- previousContent: string
31
- before: string
32
- after: string
33
- previewBefore: string
34
- previewAfter: string
35
- changeSummary: string
36
- diff: string
37
- }
11
+ export type { PreparedPrivateContinuityEdit, PrivateContinuityEditInput } from './privateEdit/types.js'
38
12
 
39
13
  export async function preparePrivateContinuityEdit(
40
14
  input: PrivateContinuityEditInput,
@@ -42,7 +16,7 @@ export async function preparePrivateContinuityEdit(
42
16
  ): Promise<PreparedPrivateContinuityEdit> {
43
17
  const identity = config?.identity
44
18
  if (!identity) {
45
- throw new Error('no active identity; create or load an identity before proposing private continuity edits')
19
+ throw new Error('No active identity; create or load an identity before proposing private continuity edits')
46
20
  }
47
21
 
48
22
  const fullPath = privateContinuityPath(identity, input.file)
@@ -53,7 +27,7 @@ export async function preparePrivateContinuityEdit(
53
27
  identity,
54
28
  file: input.file,
55
29
  fullPath,
56
- relativePath: `identity-vault/${input.file}`,
30
+ relativePath: 'identity-vault/' + input.file,
57
31
  directoryPath: path.dirname(fullPath),
58
32
  existedBefore: existing.existedBefore,
59
33
  previousContent: existing.existedBefore ? existing.content : '',
@@ -66,269 +40,7 @@ export async function preparePrivateContinuityEdit(
66
40
  }
67
41
  }
68
42
 
69
- function applyPrivateContinuityEdit(input: PrivateContinuityEditInput, before: string, identity: EthagentIdentity) {
70
- if (input.replaceWholeFile) {
71
- throw new Error('private continuity files must be edited in place; whole-file replacement is disabled')
72
- }
73
- if (input.appendToSection || input.appendText) {
74
- if (!input.appendToSection?.trim()) throw new Error('appendToSection is required for append edits')
75
- if (!input.appendText?.trim()) throw new Error('appendText is required for append edits')
76
- if (input.oldText || input.newText !== undefined) {
77
- throw new Error('use either appendToSection+appendText or oldText+newText, not both')
78
- }
79
- return appendToMarkdownSection(identity, input.file, before, input.appendToSection, input.appendText)
80
- }
81
- if (!input.oldText?.trim()) {
82
- throw new Error('oldText is required; private continuity edits must patch existing scaffold text')
83
- }
84
- if (input.newText === undefined) {
85
- throw new Error('newText is required for targeted private continuity edits')
86
- }
87
- return applyRequestedEdit(
88
- input.file,
89
- before,
90
- input.oldText,
91
- input.newText,
92
- input.replaceAll ?? false,
93
- false,
94
- )
95
- }
96
-
97
43
  export async function writePreparedPrivateContinuityEdit(edit: PreparedPrivateContinuityEdit): Promise<void> {
98
44
  await ensureContinuityVault(edit.identity)
99
45
  await atomicWriteText(edit.fullPath, ensureTrailingNewline(edit.after), { mode: 0o600 })
100
46
  }
101
-
102
- function privateContinuityPath(identity: EthagentIdentity, file: PrivateContinuityFile): string {
103
- const ref = continuityVaultRef(identity)
104
- return file === 'SOUL.md' ? ref.soulPath : ref.memoryPath
105
- }
106
-
107
- async function readPrivateContinuityFile(
108
- identity: EthagentIdentity,
109
- file: PrivateContinuityFile,
110
- fullPath: string,
111
- ): Promise<{ content: string; existedBefore: boolean }> {
112
- try {
113
- return { content: await fs.readFile(fullPath, 'utf8'), existedBefore: true }
114
- } catch (err: unknown) {
115
- if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
116
- return { content: defaultContinuityFiles(identity)[file], existedBefore: false }
117
- }
118
- throw err
119
- }
120
- }
121
-
122
- function renderPrivateContinuityDiff(file: PrivateContinuityFile, before: string, after: string): string {
123
- if (before === after) return '(no changes)'
124
- const changedLines = changedMarkdownLines(before, after)
125
- const lines = [
126
- `--- ${file}`,
127
- `+++ ${file}`,
128
- ...(changedLines.length > 0 ? changedLines : ['(only whitespace or line ending changes)']),
129
- ]
130
- const diff = lines.join('\n')
131
- return diff.length <= 2400 ? diff : `${diff.slice(0, 2397)}...`
132
- }
133
-
134
- function changedMarkdownLines(before: string, after: string): string[] {
135
- const beforeLines = markdownLines(before)
136
- const afterLines = markdownLines(after)
137
- const lengths = lcsLengths(beforeLines, afterLines)
138
- const changed: string[] = []
139
- let beforeIndex = 0
140
- let afterIndex = 0
141
-
142
- while (beforeIndex < beforeLines.length && afterIndex < afterLines.length) {
143
- if (beforeLines[beforeIndex] === afterLines[afterIndex]) {
144
- beforeIndex += 1
145
- afterIndex += 1
146
- continue
147
- }
148
-
149
- const deleteScore = lengths[beforeIndex + 1]![afterIndex]!
150
- const insertScore = lengths[beforeIndex]![afterIndex + 1]!
151
- const deleteRevealsMatch = beforeLines[beforeIndex + 1] === afterLines[afterIndex]
152
- const insertRevealsMatch = beforeLines[beforeIndex] === afterLines[afterIndex + 1]
153
-
154
- if (insertRevealsMatch && insertScore >= deleteScore) {
155
- changed.push(`+${afterLines[afterIndex]}`)
156
- afterIndex += 1
157
- } else if (deleteRevealsMatch && deleteScore >= insertScore) {
158
- changed.push(`-${beforeLines[beforeIndex]}`)
159
- beforeIndex += 1
160
- } else if (deleteScore >= insertScore) {
161
- changed.push(`-${beforeLines[beforeIndex]}`)
162
- beforeIndex += 1
163
- } else {
164
- changed.push(`+${afterLines[afterIndex]}`)
165
- afterIndex += 1
166
- }
167
- }
168
-
169
- while (beforeIndex < beforeLines.length) {
170
- changed.push(`-${beforeLines[beforeIndex]}`)
171
- beforeIndex += 1
172
- }
173
- while (afterIndex < afterLines.length) {
174
- changed.push(`+${afterLines[afterIndex]}`)
175
- afterIndex += 1
176
- }
177
-
178
- return changed
179
- }
180
-
181
- function lcsLengths(beforeLines: string[], afterLines: string[]): number[][] {
182
- const lengths = Array.from(
183
- { length: beforeLines.length + 1 },
184
- () => Array<number>(afterLines.length + 1).fill(0),
185
- )
186
-
187
- for (let beforeIndex = beforeLines.length - 1; beforeIndex >= 0; beforeIndex -= 1) {
188
- for (let afterIndex = afterLines.length - 1; afterIndex >= 0; afterIndex -= 1) {
189
- lengths[beforeIndex]![afterIndex] = beforeLines[beforeIndex] === afterLines[afterIndex]
190
- ? lengths[beforeIndex + 1]![afterIndex + 1]! + 1
191
- : Math.max(lengths[beforeIndex + 1]![afterIndex]!, lengths[beforeIndex]![afterIndex + 1]!)
192
- }
193
- }
194
-
195
- return lengths
196
- }
197
-
198
- function appendToMarkdownSection(
199
- identity: EthagentIdentity,
200
- file: PrivateContinuityFile,
201
- before: string,
202
- section: string,
203
- appendText: string,
204
- ) {
205
- const heading = normalizeSectionHeading(section)
206
- let working = before
207
- let repairedMissingSection = false
208
- let lines = working.split(/\r?\n/)
209
- let bounds = findMarkdownSectionBounds(lines, heading)
210
- if (!bounds) {
211
- const repaired = insertDefaultScaffoldSection(identity, file, before, heading)
212
- if (!repaired) {
213
- throw new Error(`section "${section}" was not found in ${file}; target an existing scaffold section`)
214
- }
215
- working = repaired
216
- repairedMissingSection = true
217
- lines = working.split(/\r?\n/)
218
- bounds = findMarkdownSectionBounds(lines, heading)
219
- }
220
- if (!bounds) {
221
- throw new Error(`section "${section}" was not found in ${file}; target an existing scaffold section`)
222
- }
223
- const { start, end: insertAt } = bounds
224
- const prefix = lines.slice(0, insertAt).join('\n').replace(/\s+$/g, '')
225
- const suffix = insertAt >= lines.length ? '' : lines.slice(insertAt).join('\n').replace(/^\s+/g, '')
226
- const normalizedAppend = ensureTrailingNewline(appendText.trim())
227
- const after = ensureTrailingNewline(suffix
228
- ? `${prefix}\n${normalizedAppend}\n${suffix}`
229
- : `${prefix}\n${normalizedAppend}`)
230
- const afterLines = after.split(/\r?\n/)
231
- const afterBounds = findMarkdownSectionBounds(afterLines, heading)
232
- return {
233
- before,
234
- after,
235
- summary: repairedMissingSection
236
- ? `repair ${heading} section and append to ${heading} in ${file}`
237
- : `append to ${heading} in ${file}`,
238
- previewBefore: repairedMissingSection
239
- ? `section "${heading}" was missing in ${file}; approval will add the scaffold section before appending.`
240
- : previewText(sectionPreview(lines, start, insertAt)),
241
- previewAfter: previewText(afterBounds
242
- ? sectionPreview(afterLines, afterBounds.start, afterBounds.end)
243
- : normalizedAppend),
244
- }
245
- }
246
-
247
- function insertDefaultScaffoldSection(
248
- identity: EthagentIdentity,
249
- file: PrivateContinuityFile,
250
- before: string,
251
- heading: string,
252
- ): string | null {
253
- const defaults = defaultContinuityFiles(identity)[file]
254
- const defaultSection = extractMarkdownSection(defaults, heading)
255
- if (!defaultSection) return null
256
-
257
- const defaultHeadings = markdownSectionHeadings(defaults)
258
- const targetIndex = defaultHeadings.indexOf(heading)
259
- if (targetIndex === -1) return null
260
-
261
- const lines = before.split(/\r?\n/)
262
- const followingHeadings = new Set(defaultHeadings.slice(targetIndex + 1))
263
- const followingIndex = lines.findIndex(line => followingHeadings.has(normalizeSectionHeading(line)))
264
- if (followingIndex !== -1) {
265
- return insertSectionAtLine(before, followingIndex, defaultSection)
266
- }
267
-
268
- const previousHeadings = new Set(defaultHeadings.slice(0, targetIndex))
269
- let insertAfterPrevious: number | null = null
270
- for (let index = 0; index < lines.length; index += 1) {
271
- if (!previousHeadings.has(normalizeSectionHeading(lines[index] ?? ''))) continue
272
- const bounds = findMarkdownSectionBounds(lines, normalizeSectionHeading(lines[index] ?? ''))
273
- if (bounds && bounds.end > (insertAfterPrevious ?? -1)) insertAfterPrevious = bounds.end
274
- }
275
- if (insertAfterPrevious !== null) {
276
- return insertSectionAtLine(before, insertAfterPrevious, defaultSection)
277
- }
278
-
279
- const firstHeading = lines.findIndex(line => /^#\s+/.test(line.trim()))
280
- return insertSectionAtLine(before, firstHeading === -1 ? 0 : firstHeading + 1, defaultSection)
281
- }
282
-
283
- function insertSectionAtLine(markdown: string, lineIndex: number, section: string): string {
284
- const lines = markdown.split(/\r?\n/)
285
- const before = lines.slice(0, lineIndex).join('\n').replace(/\s+$/g, '')
286
- const after = lines.slice(lineIndex).join('\n').replace(/^\s+/g, '')
287
- const block = section.trim()
288
- if (!before) return ensureTrailingNewline(after ? `${block}\n\n${after}` : block)
289
- return ensureTrailingNewline(after ? `${before}\n\n${block}\n\n${after}` : `${before}\n\n${block}`)
290
- }
291
-
292
- function extractMarkdownSection(markdown: string, heading: string): string | null {
293
- const lines = markdown.split(/\r?\n/)
294
- const bounds = findMarkdownSectionBounds(lines, heading)
295
- if (!bounds) return null
296
- return lines.slice(bounds.start, bounds.end).join('\n').trim()
297
- }
298
-
299
- function markdownSectionHeadings(markdown: string): string[] {
300
- return markdown
301
- .split(/\r?\n/)
302
- .filter(line => /^##\s+/.test(line.trim()))
303
- .map(normalizeSectionHeading)
304
- }
305
-
306
- function findMarkdownSectionBounds(lines: string[], heading: string): { start: number; end: number } | null {
307
- const start = lines.findIndex(line => normalizeSectionHeading(line) === heading && /^#{1,6}\s+/.test(line.trim()))
308
- if (start === -1) return null
309
- const nextSection = lines.findIndex((line, index) => index > start && /^##\s+/.test(line.trim()))
310
- return { start, end: nextSection === -1 ? lines.length : nextSection }
311
- }
312
-
313
- function normalizeSectionHeading(value: string): string {
314
- return value.trim().replace(/^#+\s*/, '').trim()
315
- }
316
-
317
- function sectionPreview(lines: string[], start: number, end: number): string {
318
- return lines.slice(start, Math.min(end, start + 8)).join('\n')
319
- }
320
-
321
- function markdownLines(value: string): string[] {
322
- const lines = value.split(/\r?\n/)
323
- if (lines[lines.length - 1] === '') lines.pop()
324
- return lines
325
- }
326
-
327
- function ensureTrailingNewline(value: string): string {
328
- return value.endsWith('\n') ? value : `${value}\n`
329
- }
330
-
331
- function previewText(text: string, max = 700): string {
332
- if (text.length <= max) return text
333
- return `${text.slice(0, max - 3)}...`
334
- }
@@ -1,6 +1,6 @@
1
1
  import type { EthagentIdentity } from '../../storage/config.js'
2
2
 
3
- export type PublicSkill = {
3
+ type PublicSkill = {
4
4
  id: string
5
5
  name: string
6
6
  description: string
@@ -8,7 +8,7 @@ export type PublicSkill = {
8
8
  outputModes: string[]
9
9
  }
10
10
 
11
- export type PublicSkillsProfile = {
11
+ type PublicSkillsProfile = {
12
12
  name: string
13
13
  description: string
14
14
  version: string
@@ -16,14 +16,13 @@ export type PublicSkillsProfile = {
16
16
  skills: PublicSkill[]
17
17
  }
18
18
 
19
- export type AgentCard = {
19
+ type AgentCard = {
20
20
  name: string
21
21
  description: string
22
22
  version: string
23
23
  protocolVersion: string
24
- url: string
24
+ url?: string
25
25
  image?: string
26
- iconUrl?: string
27
26
  defaultInputModes: string[]
28
27
  defaultOutputModes: string[]
29
28
  capabilities: {
@@ -93,7 +92,7 @@ export function renderPublicSkillsJson(profile: PublicSkillsProfile): string {
93
92
  ...(profile.imageUrl ? { imageUrl: profile.imageUrl } : {}),
94
93
  inputModes,
95
94
  outputModes,
96
- boundary: 'Public discovery metadata only. This is not executable code, private memory, or a skill installation manifest.',
95
+ boundary: 'Public discovery only. This is not executable code, private memory, or a skill installation manifest.',
97
96
  capabilities: {
98
97
  softwareEngineering: true,
99
98
  workspaceTools: 'permissioned',
@@ -134,7 +133,7 @@ export function renderPublicSkillsJson(profile: PublicSkillsProfile): string {
134
133
  return `${JSON.stringify(summary, null, 2)}\n`
135
134
  }
136
135
 
137
- export function createAgentCard(profile: PublicSkillsProfile, url = 'ipfs://pending-agent-endpoint'): AgentCard {
136
+ export function createAgentCard(profile: PublicSkillsProfile, url?: string): AgentCard {
138
137
  const inputModes = unique(profile.skills.flatMap(skill => skill.inputModes))
139
138
  const outputModes = unique(profile.skills.flatMap(skill => skill.outputModes))
140
139
  return {
@@ -142,8 +141,8 @@ export function createAgentCard(profile: PublicSkillsProfile, url = 'ipfs://pend
142
141
  description: profile.description,
143
142
  version: profile.version,
144
143
  protocolVersion: '0.2.6',
145
- url,
146
- ...(profile.imageUrl ? { image: profile.imageUrl, iconUrl: profile.imageUrl } : {}),
144
+ ...(url ? { url } : {}),
145
+ ...(profile.imageUrl ? { image: profile.imageUrl } : {}),
147
146
  defaultInputModes: inputModes.length ? inputModes : ['text/markdown'],
148
147
  defaultOutputModes: outputModes.length ? outputModes : ['text/markdown'],
149
148
  capabilities: {
@@ -9,7 +9,7 @@ import {
9
9
  type ContinuitySnapshotContentHashes,
10
10
  } from './storage.js'
11
11
 
12
- export type PublishedContinuitySnapshot = {
12
+ type PublishedContinuitySnapshot = {
13
13
  version: 1
14
14
  id: string
15
15
  createdAt: string
@@ -30,12 +30,12 @@ export type PublishedContinuitySnapshot = {
30
30
  }
31
31
  }
32
32
 
33
- export type RecordPublishedContinuitySnapshotInput = {
33
+ type RecordPublishedContinuitySnapshotInput = {
34
34
  identity: EthagentIdentity
35
35
  label?: string
36
36
  }
37
37
 
38
- export function publishedContinuitySnapshotsPath(identity: EthagentIdentity): string {
38
+ function publishedContinuitySnapshotsPath(identity: EthagentIdentity): string {
39
39
  return path.join(continuityVaultRef(identity).dir, '.published-snapshots.jsonl')
40
40
  }
41
41
 
@@ -88,10 +88,10 @@ export async function updatePublishedContinuitySnapshotContentHashes(
88
88
  const index = snapshots.findIndex(item => item.cid === cid)
89
89
  if (index === -1) {
90
90
  const base = current.find(item => item.cid === cid)
91
- if (!base) throw new Error('published snapshot was not found')
92
- snapshots.push({ ...base, contentHashes })
91
+ if (!base) throw new Error('Published snapshot was not found')
92
+ snapshots.push(refreshPublishedSnapshotSidecars({ ...base, contentHashes }, identity))
93
93
  } else {
94
- snapshots[index] = { ...snapshots[index]!, contentHashes }
94
+ snapshots[index] = refreshPublishedSnapshotSidecars({ ...snapshots[index]!, contentHashes }, identity)
95
95
  }
96
96
  await atomicWriteText(
97
97
  publishedContinuitySnapshotsPath(identity),
@@ -135,6 +135,17 @@ function enrichPublishedSnapshot(
135
135
  }
136
136
  }
137
137
 
138
+ function refreshPublishedSnapshotSidecars(
139
+ snapshot: PublishedContinuitySnapshot,
140
+ identity: EthagentIdentity,
141
+ ): PublishedContinuitySnapshot {
142
+ return {
143
+ ...snapshot,
144
+ ...(identity.publicSkills?.cid ? { publicSkillsCid: identity.publicSkills.cid } : {}),
145
+ ...(identity.publicSkills?.agentCardCid ? { agentCardCid: identity.publicSkills.agentCardCid } : {}),
146
+ }
147
+ }
148
+
138
149
  async function readPublishedContinuitySnapshotFile(identity: EthagentIdentity): Promise<PublishedContinuitySnapshot[]> {
139
150
  let raw: string
140
151
  try {
@@ -0,0 +1,111 @@
1
+ import type { EthagentIdentity } from '../../../storage/config.js'
2
+ import type { ContinuityAgentSnapshot, ContinuityFiles } from '../envelope.js'
3
+ import { defaultPublicSkillsProfile, renderPublicSkillsJson } from '../publicSkills.js'
4
+ import { renderPrivateIdentityBlock } from './markdown.js'
5
+
6
+ export function continuityAgentSnapshot(identity: EthagentIdentity): ContinuityAgentSnapshot {
7
+ const state = identity.state ?? {}
8
+ return {
9
+ ...(identity.chainId ? { chainId: identity.chainId } : {}),
10
+ ...(identity.identityRegistryAddress ? { identityRegistryAddress: identity.identityRegistryAddress } : {}),
11
+ ...(identity.agentId ? { agentId: identity.agentId } : {}),
12
+ ...(identity.agentUri ? { agentUri: identity.agentUri } : {}),
13
+ ...(identity.metadataCid ? { metadataCid: identity.metadataCid } : {}),
14
+ ...(typeof state.name === 'string' ? { name: state.name } : {}),
15
+ ...(typeof state.description === 'string' ? { description: state.description } : {}),
16
+ }
17
+ }
18
+
19
+ export function defaultContinuityFiles(identity: EthagentIdentity, now = new Date()): ContinuityFiles {
20
+ const owner = identity.ownerAddress ?? identity.address
21
+ const created = now.toISOString().slice(0, 10)
22
+ const identityBlock = renderPrivateIdentityBlock({
23
+ owner,
24
+ token: identity.agentId ? `#${identity.agentId}` : 'pending registration',
25
+ chainId: identity.chainId ? identity.chainId.toString() : 'unknown',
26
+ registry: identity.identityRegistryAddress ?? 'unknown',
27
+ })
28
+ return {
29
+ 'SOUL.md': [
30
+ '# SOUL.md',
31
+ '',
32
+ identityBlock,
33
+ '',
34
+ '## Persona',
35
+ '',
36
+ '- Describe the private agent persona, voice, and collaboration style.',
37
+ '- Keep standing behavior that should survive model switches and device restores.',
38
+ '- Prefer stable guidance over session-specific preferences.',
39
+ '',
40
+ '## Operating Principles',
41
+ '',
42
+ '- Record durable values, decision preferences, and owner-approved working principles.',
43
+ '- Keep implementation-specific facts in MEMORY.md unless they define behavior.',
44
+ '',
45
+ '## Private Instructions',
46
+ '',
47
+ '- Keep owner-specific standing instructions in this file.',
48
+ '- Do not share this file directly; save it via the Identity Hub encrypted snapshot.',
49
+ '- Public capabilities belong in skills.json.',
50
+ '',
51
+ '## Boundaries',
52
+ '',
53
+ '- Record private behavioral limits and owner-approved constraints here.',
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.',
56
+ '',
57
+ '## Maintenance Rules',
58
+ '',
59
+ '- Keep the generated Agent Identity block intact; edit owner-authored sections below it.',
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',
70
+ 'MEMORY.md': [
71
+ '# MEMORY.md',
72
+ '',
73
+ identityBlock,
74
+ '',
75
+ '## Durable User Preferences',
76
+ '',
77
+ '- Add long-lived owner preferences that should survive across sessions and model switches.',
78
+ '',
79
+ '## Project Context',
80
+ '',
81
+ '- Add stable project facts, repo conventions, and active workstreams.',
82
+ '',
83
+ '## Decisions and Rationale',
84
+ '',
85
+ '- Record important decisions and why they were made.',
86
+ '',
87
+ '## Facts to Revalidate',
88
+ '',
89
+ '- Add time-sensitive facts that should be checked before reuse, with dates or source context when available.',
90
+ '',
91
+ '## Maintenance Rules',
92
+ '',
93
+ '- Prefer stable facts, preferences, and decisions over chat transcripts.',
94
+ '- Do not duplicate the mutable public agent name here; it lives in the token URI and Agent Card.',
95
+ '- Add dates or source context when a note may become stale or environment-specific.',
96
+ '- Remove or rewrite stale memory instead of accumulating contradictions.',
97
+ '',
98
+ '## Boundaries',
99
+ '',
100
+ '- Do not store seed phrases, private keys, raw wallet signatures, or API keys.',
101
+ '- Do not store secrets unless the user explicitly asks and the risk is clear.',
102
+ '- Keep public capabilities in skills.json.',
103
+ '',
104
+ `Created: ${created}`,
105
+ ].join('\n') + '\n',
106
+ }
107
+ }
108
+
109
+ export function defaultPublicSkillsJson(identity: EthagentIdentity): string {
110
+ return renderPublicSkillsJson(defaultPublicSkillsProfile(identity))
111
+ }
@@ -0,0 +1,72 @@
1
+ import fs from 'node:fs/promises'
2
+ import { atomicWriteText } from '../../../storage/atomicWrite.js'
3
+ import type { EthagentIdentity } from '../../../storage/config.js'
4
+ import type { ContinuityFiles } from '../envelope.js'
5
+ import { defaultContinuityFiles } from './defaults.js'
6
+ import { continuityVaultRef } from './paths.js'
7
+ import type { ContinuityVaultRef } from './types.js'
8
+
9
+ export async function ensureContinuityVault(identity: EthagentIdentity): Promise<ContinuityVaultRef> {
10
+ const ref = continuityVaultRef(identity)
11
+ await fs.mkdir(ref.dir, { recursive: true, mode: 0o700 })
12
+ return ref
13
+ }
14
+
15
+ export async function ensureContinuityFiles(identity: EthagentIdentity): Promise<ContinuityFiles> {
16
+ const ref = await ensureContinuityVault(identity)
17
+ const defaults = defaultContinuityFiles(identity)
18
+ await writeMissingPrivateFile(ref.soulPath, defaults['SOUL.md'])
19
+ await writeMissingPrivateFile(ref.memoryPath, defaults['MEMORY.md'])
20
+ return readContinuityFiles(identity)
21
+ }
22
+
23
+ export async function readContinuityFiles(identity: EthagentIdentity): Promise<ContinuityFiles> {
24
+ const ref = await ensureContinuityVault(identity)
25
+ const defaults = defaultContinuityFiles(identity)
26
+ return {
27
+ 'SOUL.md': await readOrDefault(ref.soulPath, defaults['SOUL.md']),
28
+ 'MEMORY.md': await readOrDefault(ref.memoryPath, defaults['MEMORY.md']),
29
+ }
30
+ }
31
+
32
+ export async function writeContinuityFiles(identity: EthagentIdentity, files: ContinuityFiles): Promise<ContinuityVaultRef> {
33
+ const ref = await ensureContinuityVault(identity)
34
+ await atomicWriteText(ref.soulPath, ensureTrailingNewline(files['SOUL.md']), { mode: 0o600 })
35
+ await atomicWriteText(ref.memoryPath, ensureTrailingNewline(files['MEMORY.md']), { mode: 0o600 })
36
+ return ref
37
+ }
38
+
39
+ async function writeMissingPrivateFile(file: string, content: string): Promise<void> {
40
+ if (await exists(file)) return
41
+ await atomicWriteText(file, ensureTrailingNewline(content), { mode: 0o600 })
42
+ }
43
+
44
+ export async function readOrDefault(file: string, fallback: string): Promise<string> {
45
+ try {
46
+ return await fs.readFile(file, 'utf8')
47
+ } catch (err: unknown) {
48
+ if ((err as NodeJS.ErrnoException).code === 'ENOENT') return fallback
49
+ throw err
50
+ }
51
+ }
52
+
53
+ export async function exists(file: string): Promise<boolean> {
54
+ try {
55
+ await fs.access(file)
56
+ return true
57
+ } catch {
58
+ return false
59
+ }
60
+ }
61
+
62
+ export async function statIfExists(file: string): Promise<import('node:fs').Stats | null> {
63
+ try {
64
+ return await fs.stat(file)
65
+ } catch {
66
+ return null
67
+ }
68
+ }
69
+
70
+ export function ensureTrailingNewline(value: string): string {
71
+ return value.endsWith('\n') ? value : `${value}\n`
72
+ }