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.
- package/LICENSE +21 -21
- package/README.md +126 -30
- package/package.json +7 -2
- package/src/app/FirstRun.tsx +190 -146
- package/src/app/FirstRunTimeline.tsx +47 -0
- package/src/app/input/AppInputProvider.tsx +1 -1
- package/src/app/keybindings/KeybindingProvider.tsx +1 -1
- package/src/chat/ChatBottomPane.tsx +0 -1
- package/src/chat/ChatInput.tsx +6 -6
- package/src/chat/ChatScreen.tsx +35 -15
- package/src/chat/ContextLimitView.tsx +4 -4
- package/src/chat/ContinuityEditReviewView.tsx +10 -22
- package/src/chat/CopyPicker.tsx +0 -1
- package/src/chat/MessageList.tsx +62 -45
- package/src/chat/PermissionPrompt.tsx +13 -9
- package/src/chat/PlanApprovalView.tsx +3 -3
- package/src/chat/ResumeView.tsx +1 -4
- package/src/chat/RewindView.tsx +2 -2
- package/src/chat/chatInputState.ts +1 -1
- package/src/chat/chatScreenUtils.ts +22 -11
- package/src/chat/chatSessionState.ts +2 -2
- package/src/chat/chatTurnOrchestrator.ts +16 -81
- package/src/chat/commands.ts +1 -1
- package/src/chat/textCursor.ts +1 -1
- package/src/chat/transcriptViewport.ts +2 -7
- package/src/cli/ResetConfirmView.tsx +1 -1
- package/src/cli/main.tsx +9 -3
- package/src/cli/preview.tsx +0 -5
- package/src/cli/updateNotice.ts +4 -2
- package/src/identity/continuity/editor.ts +7 -107
- package/src/identity/continuity/envelope.ts +1048 -40
- package/src/identity/continuity/history.ts +4 -4
- package/src/identity/continuity/localBackup.ts +249 -0
- package/src/identity/continuity/privateEdit/apply.ts +170 -0
- package/src/identity/continuity/privateEdit/diff.ts +82 -0
- package/src/identity/continuity/privateEdit/files.ts +23 -0
- package/src/identity/continuity/privateEdit/types.ts +28 -0
- package/src/identity/continuity/privateEdit.ts +10 -298
- package/src/identity/continuity/publicSkills.ts +8 -9
- package/src/identity/continuity/snapshots.ts +17 -6
- package/src/identity/continuity/storage/defaults.ts +111 -0
- package/src/identity/continuity/storage/files.ts +72 -0
- package/src/identity/continuity/storage/markdown.ts +81 -0
- package/src/identity/continuity/storage/paths.ts +24 -0
- package/src/identity/continuity/storage/scaffold.ts +124 -0
- package/src/identity/continuity/storage/status.ts +86 -0
- package/src/identity/continuity/storage/types.ts +27 -0
- package/src/identity/continuity/storage.ts +32 -507
- package/src/identity/continuity/zipWriter.ts +95 -0
- package/src/identity/crypto/backupEnvelope.ts +14 -247
- package/src/identity/crypto/eth.ts +7 -7
- package/src/identity/ens/agentRecords.ts +96 -0
- package/src/identity/ens/ensAutomation/contracts.ts +38 -0
- package/src/identity/ens/ensAutomation/delete.ts +80 -0
- package/src/identity/ens/ensAutomation/names.ts +14 -0
- package/src/identity/ens/ensAutomation/operators.ts +29 -0
- package/src/identity/ens/ensAutomation/read.ts +114 -0
- package/src/identity/ens/ensAutomation/root.ts +63 -0
- package/src/identity/ens/ensAutomation/setup.ts +284 -0
- package/src/identity/ens/ensAutomation/transactions.ts +107 -0
- package/src/identity/ens/ensAutomation/types.ts +126 -0
- package/src/identity/ens/ensAutomation.ts +29 -0
- package/src/identity/ens/ensLookup/client.ts +43 -0
- package/src/identity/ens/ensLookup/constants.ts +26 -0
- package/src/identity/ens/ensLookup/discovery.ts +70 -0
- package/src/identity/ens/ensLookup/names.ts +34 -0
- package/src/identity/ens/ensLookup/records.ts +45 -0
- package/src/identity/ens/ensLookup/resolve.ts +75 -0
- package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
- package/src/identity/ens/ensLookup/types.ts +38 -0
- package/src/identity/ens/ensLookup/validation.ts +72 -0
- package/src/identity/ens/ensLookup.ts +19 -0
- package/src/identity/ens/ensRegistration.ts +199 -0
- package/src/identity/ens/resolverDelegation.ts +48 -0
- package/src/identity/hub/IdentityHub.tsx +13 -817
- package/src/identity/hub/OperationalRoutes.tsx +370 -0
- package/src/identity/hub/Routes.tsx +361 -0
- package/src/identity/hub/advancedEnsValidation.ts +45 -0
- package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
- package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
- package/src/identity/hub/components/FlowTimeline.tsx +27 -0
- package/src/identity/hub/components/IdentitySummary.tsx +190 -0
- package/src/identity/hub/components/MenuScreen.tsx +237 -0
- package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
- package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
- package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
- package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
- package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
- package/src/identity/hub/effects/create.ts +310 -0
- package/src/identity/hub/effects/ens/flows.ts +218 -0
- package/src/identity/hub/effects/ens/index.ts +11 -0
- package/src/identity/hub/effects/ens/transactions.ts +239 -0
- package/src/identity/hub/effects/index.ts +74 -0
- package/src/identity/hub/effects/profile/profileState.ts +173 -0
- package/src/identity/hub/effects/publicProfile/index.ts +5 -0
- package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
- package/src/identity/hub/effects/rebackup/index.ts +7 -0
- package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
- package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
- package/src/identity/hub/effects/receipts.ts +46 -0
- package/src/identity/hub/effects/restore/apply.ts +112 -0
- package/src/identity/hub/effects/restore/auth.ts +159 -0
- package/src/identity/hub/effects/restore/discover.ts +86 -0
- package/src/identity/hub/effects/restore/envelopes.ts +21 -0
- package/src/identity/hub/effects/restore/fetch.ts +25 -0
- package/src/identity/hub/effects/restore/index.ts +22 -0
- package/src/identity/hub/effects/restore/recovery.ts +135 -0
- package/src/identity/hub/effects/restore/resolve.ts +102 -0
- package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
- package/src/identity/hub/effects/restore/shared.ts +91 -0
- package/src/identity/hub/effects/restoreAdmin.ts +93 -0
- package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
- package/src/identity/hub/effects/shared/snapshot.ts +336 -0
- package/src/identity/hub/effects/shared/sync.ts +190 -0
- package/src/identity/hub/effects/token-transfer/index.ts +6 -0
- package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
- package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
- package/src/identity/hub/effects/types.ts +53 -0
- package/src/identity/hub/effects/vault/preflight.ts +50 -0
- package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
- package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
- package/src/identity/hub/{screens → flows/continuity}/RecoveryConfirmScreen.tsx +28 -19
- package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
- package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
- package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
- package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
- package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
- package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
- package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
- package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
- package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
- package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
- package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
- package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
- package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
- package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
- package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
- package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
- package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
- package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
- package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
- package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
- package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
- package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
- package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
- package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
- package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +23 -44
- package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
- package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
- package/src/identity/hub/identityHubReducer.ts +164 -99
- package/src/identity/hub/model/continuity.ts +94 -0
- package/src/identity/hub/model/copy.ts +35 -0
- package/src/identity/hub/model/custody.ts +54 -0
- package/src/identity/hub/model/ens.ts +49 -0
- package/src/identity/hub/model/errors.ts +140 -0
- package/src/identity/hub/model/format.ts +15 -0
- package/src/identity/hub/model/identity.ts +94 -0
- package/src/identity/hub/model/network.ts +32 -0
- package/src/identity/hub/model/transfer.ts +57 -0
- package/src/identity/hub/operatorWallets.ts +131 -0
- package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
- package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
- package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
- package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
- package/src/identity/hub/reconciliation/index.ts +21 -0
- package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
- package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
- package/src/identity/hub/txGuard.ts +51 -0
- package/src/identity/hub/types.ts +17 -0
- package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
- package/src/identity/hub/useIdentityHubController.ts +396 -0
- package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
- package/src/identity/hub/utils.ts +79 -0
- package/src/identity/identityCompat.ts +34 -0
- package/src/identity/profile/agentIcon.ts +61 -0
- package/src/identity/profile/imagePicker.ts +12 -12
- package/src/identity/registry/erc8004/abi.ts +14 -0
- package/src/identity/registry/erc8004/chains.ts +150 -0
- package/src/identity/registry/erc8004/client.ts +11 -0
- package/src/identity/registry/erc8004/discovery.ts +511 -0
- package/src/identity/registry/erc8004/metadata.ts +335 -0
- package/src/identity/registry/erc8004/ownership.ts +121 -0
- package/src/identity/registry/erc8004/preflight.ts +123 -0
- package/src/identity/registry/erc8004/transactions.ts +77 -0
- package/src/identity/registry/erc8004/types.ts +88 -0
- package/src/identity/registry/erc8004/uri.ts +59 -0
- package/src/identity/registry/erc8004/utils.ts +58 -0
- package/src/identity/registry/erc8004.ts +53 -1106
- package/src/identity/registry/fieldParsers.ts +28 -0
- package/src/identity/registry/operatorVault/bytecode.ts +98 -0
- package/src/identity/registry/operatorVault/constants.ts +38 -0
- package/src/identity/registry/operatorVault/read.ts +246 -0
- package/src/identity/registry/operatorVault/transactions.ts +81 -0
- package/src/identity/registry/operatorVault.ts +44 -0
- package/src/identity/storage/ipfs.ts +26 -24
- package/src/identity/wallet/browserWallet/gas.ts +41 -0
- package/src/identity/wallet/browserWallet/html.ts +106 -0
- package/src/identity/wallet/browserWallet/http.ts +28 -0
- package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
- package/src/identity/wallet/browserWallet/requests.ts +191 -0
- package/src/identity/wallet/browserWallet/session.ts +325 -0
- package/src/identity/wallet/browserWallet/types.ts +192 -0
- package/src/identity/wallet/browserWallet/validation.ts +74 -0
- package/src/identity/wallet/browserWallet.ts +30 -393
- package/src/identity/wallet/page/constants.ts +5 -0
- package/src/identity/wallet/page/controller.ts +251 -0
- package/src/identity/wallet/page/copy.ts +340 -0
- package/src/identity/wallet/page/grainient.ts +278 -0
- package/src/identity/wallet/page/html.ts +28 -0
- package/src/identity/wallet/page/markup.ts +50 -0
- package/src/identity/wallet/page/state.ts +9 -0
- package/src/identity/wallet/page/styles/base.ts +259 -0
- package/src/identity/wallet/page/styles/components.ts +262 -0
- package/src/identity/wallet/page/styles/index.ts +5 -0
- package/src/identity/wallet/page/styles/responsive.ts +247 -0
- package/src/identity/wallet/page/types.ts +47 -0
- package/src/identity/wallet/page/view.ts +535 -0
- package/src/identity/wallet/page/walletProvider.ts +70 -0
- package/src/identity/wallet/page.tsx +38 -0
- package/src/identity/wallet/walletPurposeCompat.ts +27 -0
- package/src/mcp/manager.ts +0 -1
- package/src/models/ModelPicker.tsx +36 -30
- package/src/models/catalog.ts +5 -2
- package/src/models/huggingface.ts +9 -9
- package/src/models/llamacpp.ts +13 -13
- package/src/models/modelDisplay.ts +75 -0
- package/src/models/modelPickerOptions.ts +16 -3
- package/src/models/modelRecommendation.ts +0 -1
- package/src/providers/errors.ts +16 -0
- package/src/providers/gemini.ts +252 -39
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +1 -1
- package/src/runtime/sessionMode.ts +1 -1
- package/src/runtime/systemPrompt.ts +2 -0
- package/src/runtime/toolExecution.ts +18 -22
- package/src/runtime/toolIntent.ts +0 -20
- package/src/runtime/turn.ts +0 -92
- package/src/storage/atomicWrite.ts +4 -1
- package/src/storage/config.ts +181 -5
- package/src/storage/identity.ts +9 -3
- package/src/storage/secrets.ts +2 -2
- package/src/tools/bashSafety.ts +8 -0
- package/src/tools/changeDirectoryTool.ts +1 -1
- package/src/tools/deleteFileTool.ts +4 -4
- package/src/tools/editTool.ts +4 -4
- package/src/tools/editUtils.ts +5 -5
- package/src/tools/privateContinuityEditTool.ts +4 -5
- package/src/tools/privateContinuityReadTool.ts +1 -2
- package/src/tools/registry.ts +30 -0
- package/src/tools/writeFileTool.ts +5 -5
- package/src/ui/BrandSplash.tsx +20 -85
- package/src/ui/ProgressBar.tsx +3 -5
- package/src/ui/Select.tsx +20 -8
- package/src/ui/Spinner.tsx +38 -3
- package/src/ui/Surface.tsx +2 -2
- package/src/ui/TextInput.tsx +63 -20
- package/src/ui/theme.ts +7 -34
- package/src/utils/openExternal.ts +21 -0
- package/src/utils/withRetry.ts +47 -3
- package/src/identity/hub/identityHubEffects.ts +0 -937
- package/src/identity/hub/identityHubModel.ts +0 -371
- package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -156
- package/src/identity/hub/screens/EditProfileFlow.tsx +0 -146
- package/src/identity/hub/screens/IdentitySummary.tsx +0 -106
- package/src/identity/hub/screens/MenuScreen.tsx +0 -117
- package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
- package/src/identity/wallet/wallet-page/wallet.html +0 -1202
- /package/src/identity/hub/{screens → components}/BusyScreen.tsx +0 -0
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type PrivateContinuityFile,
|
|
12
12
|
} from './storage.js'
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
type PrivateContinuityHistorySnapshot = {
|
|
15
15
|
version: 1
|
|
16
16
|
id: string
|
|
17
17
|
createdAt: string
|
|
@@ -35,7 +35,7 @@ export type PrivateContinuityHistorySnapshot = {
|
|
|
35
35
|
checkpointLabel?: string
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
type RecordPrivateContinuityHistoryInput = {
|
|
39
39
|
identity: EthagentIdentity
|
|
40
40
|
file: PrivateContinuityFile
|
|
41
41
|
filePath: string
|
|
@@ -51,7 +51,7 @@ export type RecordPrivateContinuityHistoryInput = {
|
|
|
51
51
|
checkpointLabel?: string
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
function privateContinuityHistoryPath(identity: EthagentIdentity): string {
|
|
55
55
|
return path.join(continuityVaultRef(identity).dir, '.history.jsonl')
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -121,7 +121,7 @@ export async function restorePrivateContinuityHistorySnapshot(
|
|
|
121
121
|
): Promise<PrivateContinuityHistorySnapshot> {
|
|
122
122
|
const snapshot = (await listPrivateContinuityHistory(identity, 500))
|
|
123
123
|
.find(item => item.id === snapshotId)
|
|
124
|
-
if (!snapshot) throw new Error('
|
|
124
|
+
if (!snapshot) throw new Error('Private continuity checkpoint was not found')
|
|
125
125
|
|
|
126
126
|
if (snapshot.previousFiles) {
|
|
127
127
|
await writeContinuityFiles(identity, snapshot.previousFiles)
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import fsp from 'node:fs/promises'
|
|
4
|
+
import os from 'node:os'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import type { EthagentIdentity } from '../../storage/config.js'
|
|
7
|
+
import { continuityVaultRef } from './storage.js'
|
|
8
|
+
import { buildZip, type ZipEntry } from './zipWriter.js'
|
|
9
|
+
|
|
10
|
+
type LocalBackupResult =
|
|
11
|
+
| { ok: true; path: string; method: string }
|
|
12
|
+
| { ok: false; cancelled: boolean; error: string }
|
|
13
|
+
|
|
14
|
+
type SaveDialogCommand = {
|
|
15
|
+
cmd: string
|
|
16
|
+
args: string[]
|
|
17
|
+
method: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function exportLocalBackup(
|
|
21
|
+
identity: EthagentIdentity,
|
|
22
|
+
options: {
|
|
23
|
+
platform?: NodeJS.Platform
|
|
24
|
+
env?: NodeJS.ProcessEnv
|
|
25
|
+
timeoutMs?: number
|
|
26
|
+
spawnImpl?: typeof spawn
|
|
27
|
+
homeDir?: string
|
|
28
|
+
} = {},
|
|
29
|
+
): Promise<LocalBackupResult> {
|
|
30
|
+
const platform = options.platform ?? process.platform
|
|
31
|
+
const env = options.env ?? process.env
|
|
32
|
+
const homeDir = options.homeDir ?? os.homedir()
|
|
33
|
+
const ref = continuityVaultRef(identity)
|
|
34
|
+
|
|
35
|
+
const entries: ZipEntry[] = []
|
|
36
|
+
for (const [name, file] of [
|
|
37
|
+
['SOUL.md', ref.soulPath],
|
|
38
|
+
['MEMORY.md', ref.memoryPath],
|
|
39
|
+
['skills.json', ref.publicSkillsPath],
|
|
40
|
+
] as const) {
|
|
41
|
+
try {
|
|
42
|
+
const data = await fsp.readFile(file)
|
|
43
|
+
entries.push({ name, data })
|
|
44
|
+
} catch (err: unknown) {
|
|
45
|
+
const code = (err as NodeJS.ErrnoException).code
|
|
46
|
+
if (code !== 'ENOENT') {
|
|
47
|
+
return { ok: false, cancelled: false, error: `read ${name} failed: ${(err as Error).message}` }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (entries.length === 0) {
|
|
52
|
+
return { ok: false, cancelled: false, error: 'no local continuity files to back up' }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const archive = buildZip(entries)
|
|
56
|
+
const defaultName = defaultBackupFilename(identity)
|
|
57
|
+
const dialog = resolveSaveDialogCommand(platform, env, defaultName)
|
|
58
|
+
|
|
59
|
+
if (dialog) {
|
|
60
|
+
const chosen = await runSaveDialog(dialog, options.spawnImpl ?? spawn, options.timeoutMs ?? 120_000)
|
|
61
|
+
if (chosen.ok) {
|
|
62
|
+
const target = ensureZipExtension(chosen.file)
|
|
63
|
+
try {
|
|
64
|
+
await fsp.writeFile(target, archive)
|
|
65
|
+
return { ok: true, path: target, method: dialog.method }
|
|
66
|
+
} catch (err: unknown) {
|
|
67
|
+
return { ok: false, cancelled: false, error: `write failed: ${(err as Error).message}` }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (!chosen.cancelled) {
|
|
71
|
+
const fallback = path.join(homeDir, defaultName)
|
|
72
|
+
try {
|
|
73
|
+
await fsp.writeFile(fallback, archive)
|
|
74
|
+
return { ok: true, path: fallback, method: 'home dir fallback' }
|
|
75
|
+
} catch (err: unknown) {
|
|
76
|
+
return { ok: false, cancelled: false, error: `write failed: ${(err as Error).message}` }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return chosen
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const fallback = path.join(homeDir, defaultName)
|
|
83
|
+
try {
|
|
84
|
+
await fsp.writeFile(fallback, archive)
|
|
85
|
+
return { ok: true, path: fallback, method: 'home dir' }
|
|
86
|
+
} catch (err: unknown) {
|
|
87
|
+
return { ok: false, cancelled: false, error: `write failed: ${(err as Error).message}` }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function defaultBackupFilename(identity: EthagentIdentity): string {
|
|
92
|
+
const idPart = identity.agentId ? `agent-${identity.agentId}` : 'agent'
|
|
93
|
+
const stamp = timestampSlug(new Date())
|
|
94
|
+
return `ethagent-backup-${idPart}-${stamp}.zip`
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function timestampSlug(date: Date): string {
|
|
98
|
+
const pad = (n: number): string => String(n).padStart(2, '0')
|
|
99
|
+
return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function ensureZipExtension(file: string): string {
|
|
103
|
+
return /\.zip$/i.test(file) ? file : `${file}.zip`
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function resolveSaveDialogCommand(platform: NodeJS.Platform, env: NodeJS.ProcessEnv, defaultName: string): SaveDialogCommand | null {
|
|
107
|
+
if (platform === 'win32') {
|
|
108
|
+
const powershell = findExecutable('powershell.exe', env, platform) ?? findExecutable('pwsh.exe', env, platform)
|
|
109
|
+
if (!powershell) return null
|
|
110
|
+
return {
|
|
111
|
+
cmd: powershell,
|
|
112
|
+
args: [
|
|
113
|
+
'-NoProfile',
|
|
114
|
+
'-STA',
|
|
115
|
+
'-ExecutionPolicy',
|
|
116
|
+
'Bypass',
|
|
117
|
+
'-Command',
|
|
118
|
+
[
|
|
119
|
+
'[Console]::OutputEncoding = [System.Text.Encoding]::UTF8',
|
|
120
|
+
'Add-Type -AssemblyName System.Windows.Forms',
|
|
121
|
+
'$dialog = New-Object System.Windows.Forms.SaveFileDialog',
|
|
122
|
+
'$dialog.Title = "Save Local Backup"',
|
|
123
|
+
'$dialog.Filter = "Zip archive (*.zip)|*.zip|All files (*.*)|*.*"',
|
|
124
|
+
'$dialog.DefaultExt = "zip"',
|
|
125
|
+
`$dialog.FileName = "${defaultName.replace(/"/g, '`"')}"`,
|
|
126
|
+
'$dialog.OverwritePrompt = $true',
|
|
127
|
+
'if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { Write-Output $dialog.FileName }',
|
|
128
|
+
].join('; '),
|
|
129
|
+
],
|
|
130
|
+
method: 'windows save dialog',
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (platform === 'darwin') {
|
|
134
|
+
return {
|
|
135
|
+
cmd: 'osascript',
|
|
136
|
+
args: [
|
|
137
|
+
'-e',
|
|
138
|
+
`set chosen to choose file name with prompt "Save Local Backup" default name "${defaultName.replace(/"/g, '\\"')}"`,
|
|
139
|
+
'-e',
|
|
140
|
+
'POSIX path of chosen',
|
|
141
|
+
],
|
|
142
|
+
method: 'macOS save dialog',
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const zenity = findExecutable('zenity', env, platform)
|
|
146
|
+
if (zenity) {
|
|
147
|
+
return {
|
|
148
|
+
cmd: zenity,
|
|
149
|
+
args: [
|
|
150
|
+
'--file-selection',
|
|
151
|
+
'--save',
|
|
152
|
+
'--confirm-overwrite',
|
|
153
|
+
'--title=Save Local Backup',
|
|
154
|
+
`--filename=${path.join(env.HOME ?? '.', defaultName)}`,
|
|
155
|
+
'--file-filter=Zip archive | *.zip',
|
|
156
|
+
],
|
|
157
|
+
method: 'zenity',
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const kdialog = findExecutable('kdialog', env, platform)
|
|
161
|
+
if (kdialog) {
|
|
162
|
+
return {
|
|
163
|
+
cmd: kdialog,
|
|
164
|
+
args: ['--getsavefilename', path.join(env.HOME ?? '.', defaultName), 'Zip archive (*.zip)'],
|
|
165
|
+
method: 'kdialog',
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return null
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function runSaveDialog(
|
|
172
|
+
command: SaveDialogCommand,
|
|
173
|
+
spawnImpl: typeof spawn,
|
|
174
|
+
timeoutMs: number,
|
|
175
|
+
): Promise<{ ok: true; file: string } | { ok: false; cancelled: boolean; error: string }> {
|
|
176
|
+
return new Promise(resolve => {
|
|
177
|
+
let stdout = ''
|
|
178
|
+
let stderr = ''
|
|
179
|
+
let settled = false
|
|
180
|
+
let child: ReturnType<typeof spawn>
|
|
181
|
+
try {
|
|
182
|
+
child = spawnImpl(command.cmd, command.args, {
|
|
183
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
184
|
+
windowsHide: true,
|
|
185
|
+
})
|
|
186
|
+
} catch (err: unknown) {
|
|
187
|
+
resolve({ ok: false, cancelled: false, error: (err as Error).message })
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
const timer = setTimeout(() => {
|
|
191
|
+
if (settled) return
|
|
192
|
+
settled = true
|
|
193
|
+
child.kill()
|
|
194
|
+
resolve({ ok: false, cancelled: false, error: 'save dialog timed out' })
|
|
195
|
+
}, timeoutMs)
|
|
196
|
+
child.stdout?.setEncoding('utf8')
|
|
197
|
+
child.stderr?.setEncoding('utf8')
|
|
198
|
+
child.stdout?.on('data', chunk => { stdout += String(chunk) })
|
|
199
|
+
child.stderr?.on('data', chunk => { stderr += String(chunk) })
|
|
200
|
+
child.on('error', err => {
|
|
201
|
+
if (settled) return
|
|
202
|
+
settled = true
|
|
203
|
+
clearTimeout(timer)
|
|
204
|
+
resolve({ ok: false, cancelled: false, error: err.message })
|
|
205
|
+
})
|
|
206
|
+
child.on('close', code => {
|
|
207
|
+
if (settled) return
|
|
208
|
+
settled = true
|
|
209
|
+
clearTimeout(timer)
|
|
210
|
+
const file = stdout.trim()
|
|
211
|
+
if (code === 0 && file) {
|
|
212
|
+
resolve({ ok: true, file })
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
const detail = stderr.trim()
|
|
216
|
+
const cancelled = code === 0 || /cancel/i.test(detail)
|
|
217
|
+
resolve({ ok: false, cancelled, error: cancelled ? 'save cancelled' : detail || `${command.method} exited ${code}` })
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function findExecutable(command: string, env: NodeJS.ProcessEnv, platform: NodeJS.Platform): string | null {
|
|
223
|
+
const hasPathSeparator = command.includes('/') || command.includes('\\')
|
|
224
|
+
if (hasPathSeparator || path.isAbsolute(command)) return canAccessExecutable(command) ? command : null
|
|
225
|
+
const pathParts = (env.PATH ?? '').split(path.delimiter).filter(Boolean)
|
|
226
|
+
const extensions = platform === 'win32'
|
|
227
|
+
? (env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM').split(';').filter(Boolean)
|
|
228
|
+
: ['']
|
|
229
|
+
for (const dir of pathParts) {
|
|
230
|
+
for (const ext of extensions) {
|
|
231
|
+
const candidate = path.join(dir, platform === 'win32' && path.extname(command) === '' ? `${command}${ext}` : command)
|
|
232
|
+
if (canAccessExecutable(candidate)) return candidate
|
|
233
|
+
if (platform === 'win32') {
|
|
234
|
+
const lower = path.join(dir, path.extname(command) === '' ? `${command}${ext.toLowerCase()}` : command)
|
|
235
|
+
if (canAccessExecutable(lower)) return lower
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return null
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function canAccessExecutable(file: string): boolean {
|
|
243
|
+
try {
|
|
244
|
+
fs.accessSync(file, fs.constants.X_OK)
|
|
245
|
+
return true
|
|
246
|
+
} catch {
|
|
247
|
+
return false
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { EthagentIdentity } from '../../../storage/config.js'
|
|
2
|
+
import { applyRequestedEdit } from '../../../tools/editUtils.js'
|
|
3
|
+
import { defaultContinuityFiles, type PrivateContinuityFile } from '../storage.js'
|
|
4
|
+
import type { PrivateContinuityEditInput } from './types.js'
|
|
5
|
+
|
|
6
|
+
export function applyPrivateContinuityEdit(input: PrivateContinuityEditInput, before: string, identity: EthagentIdentity) {
|
|
7
|
+
if (input.replaceWholeFile) {
|
|
8
|
+
throw new Error('Private continuity files must be edited in place; whole-file replacement is disabled')
|
|
9
|
+
}
|
|
10
|
+
if (input.appendToSection || input.appendText) {
|
|
11
|
+
if (!input.appendToSection?.trim()) throw new Error('Field appendToSection is required for append edits')
|
|
12
|
+
if (!input.appendText?.trim()) throw new Error('Field appendText is required for append edits')
|
|
13
|
+
if (input.oldText || input.newText !== undefined) {
|
|
14
|
+
throw new Error('Use either appendToSection+appendText or oldText+newText, not both')
|
|
15
|
+
}
|
|
16
|
+
return appendToMarkdownSection(identity, input.file, before, input.appendToSection, input.appendText)
|
|
17
|
+
}
|
|
18
|
+
if (!input.oldText?.trim()) {
|
|
19
|
+
throw new Error('Field oldText is required; private continuity edits must patch existing scaffold text')
|
|
20
|
+
}
|
|
21
|
+
if (input.newText === undefined) {
|
|
22
|
+
throw new Error('Field newText is required for targeted private continuity edits')
|
|
23
|
+
}
|
|
24
|
+
return applyRequestedEdit(
|
|
25
|
+
input.file,
|
|
26
|
+
before,
|
|
27
|
+
input.oldText,
|
|
28
|
+
input.newText,
|
|
29
|
+
input.replaceAll ?? false,
|
|
30
|
+
false,
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function appendToMarkdownSection(
|
|
35
|
+
identity: EthagentIdentity,
|
|
36
|
+
file: PrivateContinuityFile,
|
|
37
|
+
before: string,
|
|
38
|
+
section: string,
|
|
39
|
+
appendText: string,
|
|
40
|
+
) {
|
|
41
|
+
const heading = normalizeSectionHeading(section)
|
|
42
|
+
let working = before
|
|
43
|
+
let repairedMissingSection = false
|
|
44
|
+
let lines = working.split(/\r?\n/)
|
|
45
|
+
let bounds = findMarkdownSectionBounds(lines, heading)
|
|
46
|
+
if (!bounds) {
|
|
47
|
+
const repaired = insertDefaultScaffoldSection(identity, file, before, heading)
|
|
48
|
+
if (!repaired) {
|
|
49
|
+
throw new Error(`section "${section}" was not found in ${file}; target an existing scaffold section`)
|
|
50
|
+
}
|
|
51
|
+
working = repaired
|
|
52
|
+
repairedMissingSection = true
|
|
53
|
+
lines = working.split(/\r?\n/)
|
|
54
|
+
bounds = findMarkdownSectionBounds(lines, heading)
|
|
55
|
+
}
|
|
56
|
+
if (!bounds) {
|
|
57
|
+
throw new Error(`section "${section}" was not found in ${file}; target an existing scaffold section`)
|
|
58
|
+
}
|
|
59
|
+
const { start, end: insertAt } = bounds
|
|
60
|
+
const prefix = lines.slice(0, insertAt).join('\n').replace(/\s+$/g, '')
|
|
61
|
+
const suffix = insertAt >= lines.length ? '' : lines.slice(insertAt).join('\n').replace(/^\s+/g, '')
|
|
62
|
+
const normalizedAppend = ensureTrailingNewline(appendText.trim())
|
|
63
|
+
const after = ensureTrailingNewline(suffix
|
|
64
|
+
? `${prefix}\n${normalizedAppend}\n${suffix}`
|
|
65
|
+
: `${prefix}\n${normalizedAppend}`)
|
|
66
|
+
const afterLines = after.split(/\r?\n/)
|
|
67
|
+
const afterBounds = findMarkdownSectionBounds(afterLines, heading)
|
|
68
|
+
return {
|
|
69
|
+
before,
|
|
70
|
+
after,
|
|
71
|
+
summary: repairedMissingSection
|
|
72
|
+
? `repair ${heading} section and append to ${heading} in ${file}`
|
|
73
|
+
: `append to ${heading} in ${file}`,
|
|
74
|
+
previewBefore: repairedMissingSection
|
|
75
|
+
? `section "${heading}" was missing in ${file}; approval will add the scaffold section before appending.`
|
|
76
|
+
: previewText(sectionPreview(lines, start, insertAt)),
|
|
77
|
+
previewAfter: previewText(afterBounds
|
|
78
|
+
? sectionPreview(afterLines, afterBounds.start, afterBounds.end)
|
|
79
|
+
: normalizedAppend),
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function insertDefaultScaffoldSection(
|
|
84
|
+
identity: EthagentIdentity,
|
|
85
|
+
file: PrivateContinuityFile,
|
|
86
|
+
before: string,
|
|
87
|
+
heading: string,
|
|
88
|
+
): string | null {
|
|
89
|
+
const defaults = defaultContinuityFiles(identity)[file]
|
|
90
|
+
const defaultSection = extractMarkdownSection(defaults, heading)
|
|
91
|
+
if (!defaultSection) return null
|
|
92
|
+
|
|
93
|
+
const defaultHeadings = markdownSectionHeadings(defaults)
|
|
94
|
+
const targetIndex = defaultHeadings.indexOf(heading)
|
|
95
|
+
if (targetIndex === -1) return null
|
|
96
|
+
|
|
97
|
+
const lines = before.split(/\r?\n/)
|
|
98
|
+
const followingHeadings = new Set(defaultHeadings.slice(targetIndex + 1))
|
|
99
|
+
const followingIndex = lines.findIndex(line => followingHeadings.has(normalizeSectionHeading(line)))
|
|
100
|
+
if (followingIndex !== -1) {
|
|
101
|
+
return insertSectionAtLine(before, followingIndex, defaultSection)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const previousHeadings = new Set(defaultHeadings.slice(0, targetIndex))
|
|
105
|
+
let insertAfterPrevious: number | null = null
|
|
106
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
107
|
+
if (!previousHeadings.has(normalizeSectionHeading(lines[index] ?? ''))) continue
|
|
108
|
+
const bounds = findMarkdownSectionBounds(lines, normalizeSectionHeading(lines[index] ?? ''))
|
|
109
|
+
if (bounds && bounds.end > (insertAfterPrevious ?? -1)) insertAfterPrevious = bounds.end
|
|
110
|
+
}
|
|
111
|
+
if (insertAfterPrevious !== null) {
|
|
112
|
+
return insertSectionAtLine(before, insertAfterPrevious, defaultSection)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const firstHeading = lines.findIndex(line => /^#\s+/.test(line.trim()))
|
|
116
|
+
return insertSectionAtLine(before, firstHeading === -1 ? 0 : firstHeading + 1, defaultSection)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function insertSectionAtLine(markdown: string, lineIndex: number, section: string): string {
|
|
120
|
+
const lines = markdown.split(/\r?\n/)
|
|
121
|
+
const before = lines.slice(0, lineIndex).join('\n').replace(/\s+$/g, '')
|
|
122
|
+
const after = lines.slice(lineIndex).join('\n').replace(/^\s+/g, '')
|
|
123
|
+
const block = section.trim()
|
|
124
|
+
if (!before) return ensureTrailingNewline(after ? `${block}\n\n${after}` : block)
|
|
125
|
+
return ensureTrailingNewline(after ? `${before}\n\n${block}\n\n${after}` : `${before}\n\n${block}`)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function extractMarkdownSection(markdown: string, heading: string): string | null {
|
|
129
|
+
const lines = markdown.split(/\r?\n/)
|
|
130
|
+
const bounds = findMarkdownSectionBounds(lines, heading)
|
|
131
|
+
if (!bounds) return null
|
|
132
|
+
return lines.slice(bounds.start, bounds.end).join('\n').trim()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function markdownSectionHeadings(markdown: string): string[] {
|
|
136
|
+
return markdown
|
|
137
|
+
.split(/\r?\n/)
|
|
138
|
+
.filter(line => /^##\s+/.test(line.trim()))
|
|
139
|
+
.map(normalizeSectionHeading)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function findMarkdownSectionBounds(lines: string[], heading: string): { start: number; end: number } | null {
|
|
143
|
+
const start = lines.findIndex(line => normalizeSectionHeading(line) === heading && /^#{1,6}\s+/.test(line.trim()))
|
|
144
|
+
if (start === -1) return null
|
|
145
|
+
const nextSection = lines.findIndex((line, index) => index > start && /^##\s+/.test(line.trim()))
|
|
146
|
+
return { start, end: nextSection === -1 ? lines.length : nextSection }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function normalizeSectionHeading(value: string): string {
|
|
150
|
+
return value.trim().replace(/^#+\s*/, '').trim()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function sectionPreview(lines: string[], start: number, end: number): string {
|
|
154
|
+
return lines.slice(start, Math.min(end, start + 8)).join('\n')
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function markdownLines(value: string): string[] {
|
|
158
|
+
const lines = value.split(/\r?\n/)
|
|
159
|
+
if (lines[lines.length - 1] === '') lines.pop()
|
|
160
|
+
return lines
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function ensureTrailingNewline(value: string): string {
|
|
164
|
+
return value.endsWith('\n') ? value : `${value}\n`
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function previewText(text: string, max = 700): string {
|
|
168
|
+
if (text.length <= max) return text
|
|
169
|
+
return `${text.slice(0, max - 3)}...`
|
|
170
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { PrivateContinuityFile } from '../storage.js'
|
|
2
|
+
|
|
3
|
+
export function renderPrivateContinuityDiff(file: PrivateContinuityFile, before: string, after: string): string {
|
|
4
|
+
if (before === after) return '(no changes)'
|
|
5
|
+
const changedLines = changedMarkdownLines(before, after)
|
|
6
|
+
const lines = [
|
|
7
|
+
`--- ${file}`,
|
|
8
|
+
`+++ ${file}`,
|
|
9
|
+
...(changedLines.length > 0 ? changedLines : ['(only whitespace or line ending changes)']),
|
|
10
|
+
]
|
|
11
|
+
const diff = lines.join('\n')
|
|
12
|
+
return diff.length <= 2400 ? diff : `${diff.slice(0, 2397)}...`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function changedMarkdownLines(before: string, after: string): string[] {
|
|
16
|
+
const beforeLines = markdownLines(before)
|
|
17
|
+
const afterLines = markdownLines(after)
|
|
18
|
+
const lengths = lcsLengths(beforeLines, afterLines)
|
|
19
|
+
const changed: string[] = []
|
|
20
|
+
let beforeIndex = 0
|
|
21
|
+
let afterIndex = 0
|
|
22
|
+
|
|
23
|
+
while (beforeIndex < beforeLines.length && afterIndex < afterLines.length) {
|
|
24
|
+
if (beforeLines[beforeIndex] === afterLines[afterIndex]) {
|
|
25
|
+
beforeIndex += 1
|
|
26
|
+
afterIndex += 1
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const deleteScore = lengths[beforeIndex + 1]![afterIndex]!
|
|
31
|
+
const insertScore = lengths[beforeIndex]![afterIndex + 1]!
|
|
32
|
+
const deleteRevealsMatch = beforeLines[beforeIndex + 1] === afterLines[afterIndex]
|
|
33
|
+
const insertRevealsMatch = beforeLines[beforeIndex] === afterLines[afterIndex + 1]
|
|
34
|
+
|
|
35
|
+
if (insertRevealsMatch && insertScore >= deleteScore) {
|
|
36
|
+
changed.push(`+${afterLines[afterIndex]}`)
|
|
37
|
+
afterIndex += 1
|
|
38
|
+
} else if (deleteRevealsMatch && deleteScore >= insertScore) {
|
|
39
|
+
changed.push(`-${beforeLines[beforeIndex]}`)
|
|
40
|
+
beforeIndex += 1
|
|
41
|
+
} else if (deleteScore >= insertScore) {
|
|
42
|
+
changed.push(`-${beforeLines[beforeIndex]}`)
|
|
43
|
+
beforeIndex += 1
|
|
44
|
+
} else {
|
|
45
|
+
changed.push(`+${afterLines[afterIndex]}`)
|
|
46
|
+
afterIndex += 1
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
while (beforeIndex < beforeLines.length) {
|
|
51
|
+
changed.push(`-${beforeLines[beforeIndex]}`)
|
|
52
|
+
beforeIndex += 1
|
|
53
|
+
}
|
|
54
|
+
while (afterIndex < afterLines.length) {
|
|
55
|
+
changed.push(`+${afterLines[afterIndex]}`)
|
|
56
|
+
afterIndex += 1
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return changed
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function lcsLengths(beforeLines: string[], afterLines: string[]): number[][] {
|
|
63
|
+
const lengths = Array.from(
|
|
64
|
+
{ length: beforeLines.length + 1 },
|
|
65
|
+
() => Array<number>(afterLines.length + 1).fill(0),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
for (let beforeIndex = beforeLines.length - 1; beforeIndex >= 0; beforeIndex -= 1) {
|
|
69
|
+
for (let afterIndex = afterLines.length - 1; afterIndex >= 0; afterIndex -= 1) {
|
|
70
|
+
lengths[beforeIndex]![afterIndex] = beforeLines[beforeIndex] === afterLines[afterIndex]
|
|
71
|
+
? lengths[beforeIndex + 1]![afterIndex + 1]! + 1
|
|
72
|
+
: Math.max(lengths[beforeIndex + 1]![afterIndex]!, lengths[beforeIndex]![afterIndex + 1]!)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return lengths
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function markdownLines(value: string): string[] {
|
|
80
|
+
const normalized = value.replace(/\r\n?/g, '\n')
|
|
81
|
+
return normalized.split('\n')
|
|
82
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import { type EthagentIdentity } from '../../../storage/config.js'
|
|
3
|
+
import { continuityVaultRef, defaultContinuityFiles, type PrivateContinuityFile } from '../storage.js'
|
|
4
|
+
|
|
5
|
+
export function privateContinuityPath(identity: EthagentIdentity, file: PrivateContinuityFile): string {
|
|
6
|
+
const ref = continuityVaultRef(identity)
|
|
7
|
+
return file === 'SOUL.md' ? ref.soulPath : ref.memoryPath
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function readPrivateContinuityFile(
|
|
11
|
+
identity: EthagentIdentity,
|
|
12
|
+
file: PrivateContinuityFile,
|
|
13
|
+
fullPath: string,
|
|
14
|
+
): Promise<{ content: string; existedBefore: boolean }> {
|
|
15
|
+
try {
|
|
16
|
+
return { content: await fs.readFile(fullPath, 'utf8'), existedBefore: true }
|
|
17
|
+
} catch (err: unknown) {
|
|
18
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
19
|
+
return { content: defaultContinuityFiles(identity)[file], existedBefore: false }
|
|
20
|
+
}
|
|
21
|
+
throw err
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { EthagentIdentity } from '../../../storage/config.js'
|
|
2
|
+
import type { PrivateContinuityFile } from '../storage.js'
|
|
3
|
+
|
|
4
|
+
export type PrivateContinuityEditInput = {
|
|
5
|
+
file: PrivateContinuityFile
|
|
6
|
+
oldText?: string
|
|
7
|
+
newText?: string
|
|
8
|
+
appendToSection?: string
|
|
9
|
+
appendText?: string
|
|
10
|
+
replaceAll?: boolean
|
|
11
|
+
replaceWholeFile?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type PreparedPrivateContinuityEdit = {
|
|
15
|
+
identity: EthagentIdentity
|
|
16
|
+
file: PrivateContinuityFile
|
|
17
|
+
fullPath: string
|
|
18
|
+
relativePath: string
|
|
19
|
+
directoryPath: string
|
|
20
|
+
existedBefore: boolean
|
|
21
|
+
previousContent: string
|
|
22
|
+
before: string
|
|
23
|
+
after: string
|
|
24
|
+
previewBefore: string
|
|
25
|
+
previewAfter: string
|
|
26
|
+
changeSummary: string
|
|
27
|
+
diff: string
|
|
28
|
+
}
|