From 5dc2ca10c4feba36c793d161d41b40f78d217c2e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 22:04:15 +0000 Subject: [PATCH 1/2] Remove dead code across src and the tests that only covered it Delete exported functions, types and constants that nothing in the codebase calls, plus the imports and private helpers left orphaned by those removals. Functions that only tests still reached are removed together with their tests; test seams and fixture builders stay. Dependencies and files were also checked: every package and every file under src is still in use. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01GjuD2e7tgdWWdpmUMkcZcS --- src/app/keybindings/KeybindingProvider.tsx | 41 +------ src/identity/continuity/envelopeTypes.ts | 6 -- src/identity/continuity/localKeyDecrypt.ts | 11 -- src/identity/continuity/skills/frontmatter.ts | 16 --- src/identity/continuity/skills/loadSkills.ts | 39 ------- src/identity/continuity/storage/packed.ts | 13 --- src/identity/ens/resolverDelegation.ts | 17 --- src/identity/manager/custody/helpers.ts | 14 +-- src/identity/manager/ens/editCopy.ts | 8 -- src/identity/manager/ens/types.ts | 2 - src/identity/manager/profile/effects.ts | 30 +----- src/identity/manager/profile/identity.ts | 43 +------- .../manager/shared/effects/receipts.ts | 16 --- src/identity/manager/shared/model/network.ts | 21 ---- src/identity/registry/fieldParsers.ts | 5 - src/identity/wallet/browserWallet/html.ts | 4 - src/storage/config.ts | 26 ----- src/storage/identity.ts | 27 ----- src/ui/text.ts | 10 -- .../continuity/skillsFrontmatter.test.ts | 28 +---- test/identity/ens/resolverDelegation.test.ts | 35 ------ .../manager/lazyMenuTruncation.test.tsx | 8 +- test/identity/manager/model/model.test.ts | 100 +----------------- .../identity/manager/networkFailures.test.tsx | 5 +- test/identity/registry/fieldParsers.test.ts | 11 -- test/identity/storage/identityStorage.test.ts | 58 +--------- test/run.ts | 1 - 27 files changed, 16 insertions(+), 579 deletions(-) delete mode 100644 test/identity/ens/resolverDelegation.test.ts diff --git a/src/app/keybindings/KeybindingProvider.tsx b/src/app/keybindings/KeybindingProvider.tsx index b400058..c1b65bb 100644 --- a/src/app/keybindings/KeybindingProvider.tsx +++ b/src/app/keybindings/KeybindingProvider.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react' +import React, { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react' import type { Key } from 'ink' import { useAppInput } from '../input/AppInputProvider.js' import type { Action, Binding, KeybindingContextName } from './types.js' @@ -88,42 +88,3 @@ export function useKeybindingContext(): KeybindingContextValue { if (!ctx) throw new Error('Hook useKeybindingContext requires KeybindingProvider') return ctx } - -export function useOptionalKeybindingContext(): KeybindingContextValue | null { - return useContext(Ctx) -} - -type UseKeybindingOptions = { - context?: KeybindingContextName - isActive?: boolean -} - -export function useKeybinding( - action: Action, - handler: () => void, - options: UseKeybindingOptions = {}, -): void { - const ctx = useOptionalKeybindingContext() - const context = options.context ?? 'Global' - const isActive = options.isActive ?? true - const handlerRef = useRef(handler) - useEffect(() => { handlerRef.current = handler }, [handler]) - - useEffect(() => { - if (!ctx) return - const entry: HandlerEntry = { - handler: () => handlerRef.current(), - context, - isActive, - } - return ctx.register(action, entry) - }, [ctx, action, context, isActive]) -} - -export function useRegisterKeybindingContext(context: KeybindingContextName, isActive = true): void { - const ctx = useOptionalKeybindingContext() - useEffect(() => { - if (!ctx || !isActive) return - return ctx.activateContext(context) - }, [ctx, context, isActive]) -} diff --git a/src/identity/continuity/envelopeTypes.ts b/src/identity/continuity/envelopeTypes.ts index f76e4c0..3dfe399 100644 --- a/src/identity/continuity/envelopeTypes.ts +++ b/src/identity/continuity/envelopeTypes.ts @@ -169,12 +169,6 @@ export type CreateWalletContinuitySnapshotEnvelopeArgs = { } } -export type RestoreContinuitySnapshotEnvelopeArgs = { - envelope: ContinuitySnapshotEnvelope - walletSignature: string - currentOwnerAddress?: string -} - export class ContinuitySnapshotOwnerMismatchError extends Error { constructor( readonly snapshotOwner: string, diff --git a/src/identity/continuity/localKeyDecrypt.ts b/src/identity/continuity/localKeyDecrypt.ts index ee396df..0a388a5 100644 --- a/src/identity/continuity/localKeyDecrypt.ts +++ b/src/identity/continuity/localKeyDecrypt.ts @@ -92,14 +92,3 @@ export async function decryptWithSigner(envelope: ContinuitySnapshotEnvelope, si } return { ok: false, reason: 'signature-mismatch' } } - -export function envelopeChallengeFor(envelope: ContinuitySnapshotEnvelope, address: string): string | null { - const me = address.toLowerCase() - if (isWalletContinuitySnapshotEnvelope(envelope)) { - return envelope.slots.find(slot => slot.address.toLowerCase() === me)?.challenge ?? null - } - if (isTransferContinuitySnapshotEnvelope(envelope)) { - return [envelope.slots.owner, envelope.slots.target].find(slot => slot.address.toLowerCase() === me)?.challenge ?? null - } - return envelope.ownerAddress.toLowerCase() === me ? envelope.challenge : null -} diff --git a/src/identity/continuity/skills/frontmatter.ts b/src/identity/continuity/skills/frontmatter.ts index ffcc64b..f9d3398 100644 --- a/src/identity/continuity/skills/frontmatter.ts +++ b/src/identity/continuity/skills/frontmatter.ts @@ -42,22 +42,6 @@ function yamlQuote(value: string): string { return out + '"' } -export function serializeSkillFile(frontmatter: SkillFrontmatter, body: string): string { - const lines: string[] = ['---'] - if (frontmatter.name) lines.push(`name: ${yamlQuote(frontmatter.name)}`) - if (frontmatter.description) lines.push(`description: ${yamlQuote(frontmatter.description)}`) - if (frontmatter.whenToUse) lines.push(`when_to_use: ${yamlQuote(frontmatter.whenToUse)}`) - if (frontmatter.version) lines.push(`version: ${yamlQuote(frontmatter.version)}`) - if (frontmatter.argumentHint) lines.push(`argument-hint: ${yamlQuote(frontmatter.argumentHint)}`) - if (frontmatter.tags && frontmatter.tags.length > 0) { - lines.push(`tags: [${frontmatter.tags.map(yamlQuote).join(', ')}]`) - } - if (frontmatter.visibility) lines.push(`visibility: ${yamlQuote(frontmatter.visibility)}`) - lines.push('---', '') - const trimmedBody = body.trim() - return lines.join('\n') + (trimmedBody ? trimmedBody + '\n' : '') -} - export function sanitizeSkillFileForStrictYaml(content: string): string { const normalized = content.replace(/^/, '').replace(/\r\n?/g, '\n') if (!normalized.startsWith('---\n')) return content diff --git a/src/identity/continuity/skills/loadSkills.ts b/src/identity/continuity/skills/loadSkills.ts index 2c8115a..3ceef89 100644 --- a/src/identity/continuity/skills/loadSkills.ts +++ b/src/identity/continuity/skills/loadSkills.ts @@ -136,19 +136,6 @@ async function walkSkillFileStats(root: string): Promise { return out } -export async function readSkill(identity: EthagentIdentity, name: string): Promise { - const entries = await listSkills(identity) - const lookup = name.replace(/^.*:/, '').replace(/:SKILL$/i, '') - const match = entries.find(entry => - entry.name === name - || entry.name === lookup - || entry.displayName === name - || entry.displayName === lookup, - ) - if (!match) throw new Error(`unknown private skill: ${name}`) - return loadSkillBody(match) -} - export async function readSkillByRelativePath( identity: EthagentIdentity, relativePath: string, @@ -170,26 +157,6 @@ export async function readSkillByRelativePath( return { ...entry, body: parsed.body } } -export async function readSkillFile( - identity: EthagentIdentity, - skillName: string, - filePath: string, -): Promise<{ relativePath: string; absolutePath: string; content: string }> { - if (!isValidSegment(skillName)) throw new Error('skill name is invalid') - const ref = await ensureContinuityVault(identity) - const skillDir = path.join(ref.skillsDir, skillName) - const normalizedFile = filePath.replace(/\\/g, '/').replace(/^\/+/, '') - const rel = `${skillName}/${normalizedFile}` - if (!isValidSkillFilePath(rel)) throw new Error('skill file path is not allowed') - const absolute = path.resolve(skillDir, normalizedFile) - if (!isWithin(ref.skillsDir, absolute)) throw new Error('skill file path escapes the vault') - const stat = await statOrNull(absolute) - if (!stat || !stat.isFile()) throw new Error(`skill file not found: ${rel}`) - if (stat.size > MAX_SKILL_FILE_BYTES) throw new Error(`skill file too large: ${rel}`) - const content = await fs.readFile(absolute, 'utf8') - return { relativePath: rel, absolutePath: absolute, content } -} - export async function loadSkillsTree(identity: EthagentIdentity): Promise { const ref = await ensureContinuityVault(identity) await migrateLegacySkillFiles(ref.skillsDir) @@ -726,10 +693,4 @@ function pickDescription(fromFrontmatter: string | undefined, body: string): str return '' } -async function loadSkillBody(entry: SkillIndexEntry): Promise { - const raw = await fs.readFile(entry.absolutePath, 'utf8') - const parsed = parseSkillFile(raw) - return { ...entry, body: parsed.body } -} - export { isValidSkillEntryKey, isValidSkillFilePath } from './skillPaths.js' diff --git a/src/identity/continuity/storage/packed.ts b/src/identity/continuity/storage/packed.ts index 0e9d41b..8a4c34e 100644 --- a/src/identity/continuity/storage/packed.ts +++ b/src/identity/continuity/storage/packed.ts @@ -28,7 +28,6 @@ export type PackedWorkingView = { warnings: PackedWarning[] } -export const PRIVATE_SNAPSHOT_PATHS = ['SOUL.md', 'MEMORY.md'] as const export const AGENT_CARD_PATH = 'agent-card.json' export const SKILLS_PREFIX = 'skills/' @@ -45,18 +44,6 @@ export function packedFileMap(sources: PackedSources): Record { return files } -export function sourcesFromFileMap(files: Record): PackedSources { - const skills: ContinuitySkillsTree = {} - for (const [path, value] of Object.entries(files)) { - if (path.startsWith(SKILLS_PREFIX)) skills[path.slice(SKILLS_PREFIX.length)] = value - } - return { - privateFiles: { 'SOUL.md': files['SOUL.md'] ?? '', 'MEMORY.md': files['MEMORY.md'] ?? '' }, - ...(files[AGENT_CARD_PATH] !== undefined ? { agentCard: files[AGENT_CARD_PATH] } : {}), - ...(Object.keys(skills).length > 0 ? { skills } : {}), - } -} - export async function readPackedWorkingView( identity: EthagentIdentity, soulMemory?: { files: ContinuityFiles; pulled: string[] }, diff --git a/src/identity/ens/resolverDelegation.ts b/src/identity/ens/resolverDelegation.ts index 6af84d9..4b499c8 100644 --- a/src/identity/ens/resolverDelegation.ts +++ b/src/identity/ens/resolverDelegation.ts @@ -1,5 +1,4 @@ import { - encodeFunctionData, getAddress, type Address, type Hex, @@ -18,22 +17,6 @@ export type ResolverDelegationReadArgs = { delegateAddress: Address } -export function encodeApprove(node: Hex, delegateAddress: Address): Hex { - return encodeFunctionData({ - abi: ENS_AUTOMATION_RESOLVER_ABI, - functionName: 'approve', - args: [node, getAddress(delegateAddress), true], - }) -} - -export function encodeApprovalRevoke(node: Hex, delegateAddress: Address): Hex { - return encodeFunctionData({ - abi: ENS_AUTOMATION_RESOLVER_ABI, - functionName: 'approve', - args: [node, getAddress(delegateAddress), false], - }) -} - export async function readDelegation(args: ResolverDelegationReadArgs): Promise { try { const approved = await args.client.readContract({ diff --git a/src/identity/manager/custody/helpers.ts b/src/identity/manager/custody/helpers.ts index 7a240b5..d68ee3f 100644 --- a/src/identity/manager/custody/helpers.ts +++ b/src/identity/manager/custody/helpers.ts @@ -1,7 +1,6 @@ -import type { EthagentConfig, EthagentIdentity } from '../../../storage/config.js' -import { buildSeedConfigForIdentity } from '../../../storage/config.js' +import type { EthagentIdentity } from '../../../storage/config.js' import { readOwnerAddressField } from '../../identityCompat.js' -import { supportedErc8004ChainForId, type Erc8004RegistryConfig } from '../../registry/erc8004.js' +import { supportedErc8004ChainForId } from '../../registry/erc8004.js' export function chainLabel(chainId: number): string { return supportedErc8004ChainForId(chainId)?.name ?? `chain ${chainId}` @@ -14,12 +13,3 @@ export function humanOwnerAddress(identity: EthagentIdentity): `0x${string}` { } return (identity.ownerAddress ?? identity.address) as `0x${string}` } - -export function buildSeedConfigFromStep(identity: EthagentIdentity, registry: Erc8004RegistryConfig): EthagentConfig { - return buildSeedConfigForIdentity({ - identity, - chainId: registry.chainId, - rpcUrl: registry.rpcUrl, - identityRegistryAddress: registry.identityRegistryAddress, - }) -} diff --git a/src/identity/manager/ens/editCopy.ts b/src/identity/manager/ens/editCopy.ts index 8f1252b..ec8b235 100644 --- a/src/identity/manager/ens/editCopy.ts +++ b/src/identity/manager/ens/editCopy.ts @@ -116,11 +116,3 @@ export function manualReasonTitle(reason: EnsSetupBlockedPlan['reason']): string return 'The name could not be checked.' } } - -export function readValidationFromState(state: Record | undefined): { ok: boolean; reason?: string } | null { - const raw = state?.ensValidation - if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null - const obj = raw as Record - if (typeof obj.ok !== 'boolean') return null - return { ok: obj.ok, ...(typeof obj.reason === 'string' ? { reason: obj.reason } : {}) } -} diff --git a/src/identity/manager/ens/types.ts b/src/identity/manager/ens/types.ts index a1a9be4..94056ba 100644 --- a/src/identity/manager/ens/types.ts +++ b/src/identity/manager/ens/types.ts @@ -15,8 +15,6 @@ import type { Erc8004RegistryConfig } from '../../registry/erc8004.js' import type { AgentReconciliation } from '../shared/reconciliation/index.js' import type { EnsLinkOptions } from './editCopy.js' -export type EnsIssueValidation = Extract - export type SimpleEnsPhase = | { kind: 'discovering'; mode?: 'simple' | 'advanced' } | { kind: 'pick-parent'; mode?: 'simple' | 'advanced'; error?: string } diff --git a/src/identity/manager/profile/effects.ts b/src/identity/manager/profile/effects.ts index f04271e..f0b0bd9 100644 --- a/src/identity/manager/profile/effects.ts +++ b/src/identity/manager/profile/effects.ts @@ -9,23 +9,22 @@ import { } from '../../continuity/storage.js' import { syncAgentCardManifest } from '../../continuity/skills/publicSkillsSync.js' import { recordPublishedContinuitySnapshot } from '../../continuity/snapshots.js' -import { addToIpfs, DEFAULT_IPFS_API_URL, isPinataUploadUrl } from '../../storage/ipfs.js' +import { addToIpfs, DEFAULT_IPFS_API_URL } from '../../storage/ipfs.js' import { createErc8004PublicClient, encodeSetAgentUri, preflightSetAgentUri, withEthagentPointers, - type Erc8004RegistryConfig, } from '../../registry/erc8004.js' import { encodeRotateAgentURI } from '../../registry/vault.js' -import { resolveValidatedPinataJwt, savePinataJwt } from '../../storage/pinataJwt.js' +import { savePinataJwt } from '../../storage/pinataJwt.js' import { openBrowserWalletSession, prepareTransactionGasFee, requestBrowserWalletSignatureAndTransaction, type WalletPurpose, } from '../../wallet/browserWallet.js' -import type { ProfileUpdates, Step } from '../reducer.js' +import type { Step } from '../reducer.js' import { acquireTxGuard, releaseTxGuard } from '../shared/txGuard.js' import type { EffectCallbacks } from '../shared/effects/types.js' import { awaitConfirmedReceipt } from '../shared/effects/receipts.js' @@ -66,29 +65,6 @@ type PublicProfilePreparedTransaction = { agentCardJson: string } -export async function runPublicProfilePreflight( - identity: EthagentIdentity, - registry: Erc8004RegistryConfig, - callbacks: EffectCallbacks, - profileUpdates?: ProfileUpdates, - returnTo: Step = { kind: 'continuity-public' }, - vaultAddress?: Address, -): Promise { - const apiUrl = DEFAULT_IPFS_API_URL - let jwt: string | undefined - try { - jwt = isPinataUploadUrl(apiUrl) ? await resolveValidatedPinataJwt() : undefined - } catch (err: unknown) { - callbacks.onStep({ kind: 'public-profile-storage', identity, registry, error: (err as Error).message, profileUpdates, returnTo, ...(vaultAddress ? { vaultAddress } : {}) }) - return - } - if (isPinataUploadUrl(apiUrl) && !jwt) { - callbacks.onStep({ kind: 'public-profile-storage', identity, registry, profileUpdates, returnTo, ...(vaultAddress ? { vaultAddress } : {}) }) - return - } - callbacks.onStep({ kind: 'public-profile-signing', identity, registry, pinataJwt: jwt, profileUpdates, returnTo, ...(vaultAddress ? { vaultAddress } : {}) }) -} - export async function runPublicProfileSigning( step: Extract, callbacks: EffectCallbacks, diff --git a/src/identity/manager/profile/identity.ts b/src/identity/manager/profile/identity.ts index e7d61b5..855e956 100644 --- a/src/identity/manager/profile/identity.ts +++ b/src/identity/manager/profile/identity.ts @@ -1,9 +1,8 @@ -import type { EthagentConfig, EthagentIdentity } from '../../../storage/config.js' -import { supportedErc8004ChainForId, type Erc8004AgentCandidate } from '../../registry/erc8004.js' +import type { EthagentIdentity } from '../../../storage/config.js' +import type { Erc8004AgentCandidate } from '../../registry/erc8004.js' import type { CustodyMode } from '../custody/state.js' -import { formatDate, shortAddress, shortCid } from '../shared/model/format.js' +import { formatDate } from '../shared/model/format.js' import { truncateEnd } from '../../../ui/text.js' -import { chainSummaryRow } from '../shared/model/network.js' export const PREFLIGHT_AGENT_URI = 'ipfs://bafybeigdyrztma2dbfczw7q6ooozbxlqzyw5r7w4f3qw2axvvxqg3w6y7q' @@ -33,16 +32,6 @@ export function tokenCandidateSelectLabel( return `${truncateEnd(tokenCandidateLabel(candidate), maxLength - suffix.length)}${suffix}` } -export function tokenCandidateHint(candidate: Erc8004AgentCandidate): string { - const network = supportedErc8004ChainForId(candidate.chainId)?.name ?? `Chain ${candidate.chainId}` - const parts = [ - candidate.name?.trim() ? `#${candidate.agentId.toString()}` : null, - network, - candidate.backup?.createdAt ? `saved ${formatDate(candidate.backup.createdAt)}` : null, - ].filter((part): part is string => Boolean(part)) - return parts.join(' · ') -} - export function isCurrentAgentCandidate( identity: EthagentIdentity | undefined, candidate: Erc8004AgentCandidate, @@ -62,32 +51,6 @@ export function isCurrentAgentCandidate( return true } -export function identitySummaryRows( - identity: EthagentIdentity | undefined, - config?: EthagentConfig, -): Array<{ - label: string - value: string - tone: 'ok' | 'dim' -}> { - const backup = identity?.backup - const owner = identity?.ownerAddress ?? identity?.address - const ownerValue = owner ? shortAddress(owner) : 'not connected' - const tokenValue = identity?.agentId ? `#${identity.agentId}` : 'not created' - const chain = chainSummaryRow(config, identity) - const stateValue = backup?.cid ? shortCid(backup.cid) : 'not saved yet' - const cardValue = identity?.agentCard?.cid ? shortCid(identity.agentCard.cid) : 'not saved' - const iconValue = typeof identity?.state?.imageUrl === 'string' && identity.state.imageUrl.trim() ? 'attached' : 'not attached' - return [ - { label: 'owner wallet', value: ownerValue, tone: identity ? 'ok' : 'dim' }, - { label: 'token', value: tokenValue, tone: identity?.agentId ? 'ok' : 'dim' }, - { label: 'network', value: chain.value, tone: chain.tone }, - { label: 'state', value: stateValue, tone: backup ? 'ok' : 'dim' }, - { label: 'card', value: cardValue, tone: identity?.agentCard?.cid ? 'ok' : 'dim' }, - { label: 'icon', value: iconValue, tone: iconValue === 'attached' ? 'ok' : 'dim' }, - ] -} - export function lastBackupLabel(identity?: EthagentIdentity): string { const created = identity?.backup?.createdAt return created ? formatDate(created) : 'never' diff --git a/src/identity/manager/shared/effects/receipts.ts b/src/identity/manager/shared/effects/receipts.ts index 595cbd8..4013e9f 100644 --- a/src/identity/manager/shared/effects/receipts.ts +++ b/src/identity/manager/shared/effects/receipts.ts @@ -30,19 +30,3 @@ export async function awaitConfirmedReceipt( } } } - -export async function awaitOptionalReceipt( - client: Pick, - hash: Hex, - action: string, -): Promise { - let receipt - try { - receipt = await client.waitForTransactionReceipt({ hash, timeout: 0 }) - } catch { - return - } - if (receipt.status !== 'success') { - throw new Error(`${action} reverted onchain (tx ${hash}).`) - } -} diff --git a/src/identity/manager/shared/model/network.ts b/src/identity/manager/shared/model/network.ts index 12d394c..8c0e804 100644 --- a/src/identity/manager/shared/model/network.ts +++ b/src/identity/manager/shared/model/network.ts @@ -1,15 +1,5 @@ import type { EthagentConfig, EthagentIdentity, SelectableNetwork } from '../../../../storage/config.js' import { chainIdForNetwork, supportedErc8004ChainForId } from '../../../registry/erc8004.js' -import { resolveSelectedNetwork } from '../../../registry/registryConfig.js' - -const NETWORK_LABELS: Record = { - mainnet: 'ethereum mainnet', - base: 'base', -} - -export function networkLabel(network: SelectableNetwork): string { - return NETWORK_LABELS[network] -} const NETWORK_SUBTITLES: Record = { mainnet: 'Most secure', @@ -29,14 +19,3 @@ export function identityNetworkName(identity?: EthagentIdentity, config?: Ethage if (chainId) return networkName(chainId) return config?.selectedNetwork ? networkName(chainIdForNetwork(config.selectedNetwork)) : null } - -export function chainSummaryRow(config?: EthagentConfig, identity?: EthagentIdentity): { - label: string - value: string - tone: 'ok' | 'dim' -} { - const network = resolveSelectedNetwork(config) - const fromIdentity = identity?.chainId ? supportedErc8004ChainForId(identity.chainId)?.name.toLowerCase() : undefined - const value = fromIdentity ?? networkLabel(network) - return { label: 'chain', value, tone: identity?.chainId ? 'ok' : 'dim' } -} diff --git a/src/identity/registry/fieldParsers.ts b/src/identity/registry/fieldParsers.ts index e812f7d..13bd790 100644 --- a/src/identity/registry/fieldParsers.ts +++ b/src/identity/registry/fieldParsers.ts @@ -21,8 +21,3 @@ export function numberField(input: Record | null | undefined, k const value = input?.[key] return typeof value === 'number' && Number.isSafeInteger(value) && value > 0 ? value : undefined } - -export function booleanField(input: Record | null | undefined, key: string): boolean | undefined { - const value = input?.[key] - return typeof value === 'boolean' ? value : undefined -} diff --git a/src/identity/wallet/browserWallet/html.ts b/src/identity/wallet/browserWallet/html.ts index a095e05..fc3b634 100644 --- a/src/identity/wallet/browserWallet/html.ts +++ b/src/identity/wallet/browserWallet/html.ts @@ -16,10 +16,6 @@ export function walletPage(title: string, sessionToken: string, payload: Record< return injectWalletConfig(WALLET_HTML, title, sessionToken, payload) } -export function walletPageFresh(title: string, sessionToken: string, payload: Record): string { - return injectWalletConfig(loadWalletHtml(), title, sessionToken, payload) -} - export function __testWalletPage(title: string, sessionToken: string, payload: Record): string { return walletPage(title, sessionToken, payload) } diff --git a/src/storage/config.ts b/src/storage/config.ts index 5d23c64..d5f51ac 100644 --- a/src/storage/config.ts +++ b/src/storage/config.ts @@ -206,24 +206,6 @@ export function getConfiguredVaultAddress( return config?.erc8004?.operatorVaults?.[String(chainId)] } -export function buildSeedConfigForIdentity(args: { - identity: EthagentIdentity - chainId: number - rpcUrl: string - identityRegistryAddress: string -}): EthagentConfig { - return { - version: 2, - firstSeenAt: new Date().toISOString(), - identity: { ...args.identity, source: 'erc8004' }, - erc8004: { - chainId: args.chainId, - rpcUrl: args.rpcUrl, - identityRegistryAddress: args.identityRegistryAddress, - }, - } -} - export function setConfiguredVaultAddress( config: EthagentConfig, chainId: number, @@ -281,14 +263,6 @@ async function savePendingTxMutation( return null } -export async function deleteConfig(): Promise { - try { - await fs.unlink(getConfigPath()) - } catch (err: unknown) { - if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err - } -} - export function normalizeConfig(config: EthagentConfig): EthagentConfig { let next = config if (!next.erc8004 && next.identity?.chainId && next.identity.identityRegistryAddress && next.identity.rpcUrl) { diff --git a/src/storage/identity.ts b/src/storage/identity.ts index 52f073b..62111c1 100644 --- a/src/storage/identity.ts +++ b/src/storage/identity.ts @@ -5,7 +5,6 @@ import { type EthagentIdentity, } from './config.js' import { - rmSecret, hasSecret, whichBackend, type KeyBackend, @@ -77,29 +76,3 @@ export async function setTokenIdentity( await saveConfig(next) return next } - -export async function updateIdentityBackup( - config: EthagentConfig, - backup: NonNullable, -): Promise { - if (!config.identity) throw new Error('No identity set') - const next: EthagentConfig = { - ...config, - identity: { - ...config.identity, - backup, - }, - } - await saveConfig(next) - return next -} - -export async function clearIdentity(config: EthagentConfig): Promise { - await rmSecret(IDENTITY_ACCOUNT) - if (!config.identity) return config - const { identity: _drop, ...rest } = config - void _drop - const next = { ...rest } as EthagentConfig - await saveConfig(next) - return next -} diff --git a/src/ui/text.ts b/src/ui/text.ts index 38d2227..1a6755d 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -120,16 +120,6 @@ export function firstThatFits(candidates: readonly string[], budget: number): st return null } -export function joinWithMore(items: readonly string[], budget: number, separator = ', '): string | null { - const all = items.join(separator) - if (all.length <= budget) return all - for (let shown = items.length - 1; shown >= 1; shown -= 1) { - const candidate = `${items.slice(0, shown).join(separator)} +${items.length - shown} more` - if (candidate.length <= budget) return candidate - } - return null -} - export function middleEllipsis(value: string, head: number, tail: number): string { if (value.length <= head + tail + 1) return value return `${value.slice(0, head)}…${value.slice(-tail)}` diff --git a/test/identity/continuity/skillsFrontmatter.test.ts b/test/identity/continuity/skillsFrontmatter.test.ts index 5343764..bf1616d 100644 --- a/test/identity/continuity/skillsFrontmatter.test.ts +++ b/test/identity/continuity/skillsFrontmatter.test.ts @@ -1,6 +1,6 @@ import test from 'node:test' import assert from 'node:assert/strict' -import { parseSkillFile, serializeSkillFile, sanitizeSkillFileForStrictYaml } from '../../../src/identity/continuity/skills/frontmatter.js' +import { parseSkillFile, sanitizeSkillFileForStrictYaml } from '../../../src/identity/continuity/skills/frontmatter.js' test('sanitizeSkillFileForStrictYaml quotes colon-y values, preserves unknown keys, leaves the body', () => { const src = [ @@ -59,32 +59,6 @@ test('sanitizeSkillFileForStrictYaml leaves block-scalar content lines verbatim assert.match(out, /visibility: "public"/) }) -test('serializeSkillFile emits strict-valid quoted YAML and round-trips through parseSkillFile', () => { - const frontmatter = { - name: 'vscode-setup', - description: 'Set up VS Code in one shot: apply my "Preferences Dark" theme', - visibility: 'public' as const, - tags: ['vscode', 'setup'], - } - const out = serializeSkillFile(frontmatter, 'body text\n') - assert.match(out, /description: "Set up VS Code in one shot: apply my \\"Preferences Dark\\" theme"/) - const parsed = parseSkillFile(out) - assert.equal(parsed.frontmatter.name, frontmatter.name) - assert.equal(parsed.frontmatter.description, frontmatter.description) - assert.equal(parsed.frontmatter.visibility, 'public') - assert.deepEqual(parsed.frontmatter.tags, frontmatter.tags) - assert.equal(parsed.body, 'body text') -}) - -test('serializeSkillFile escapes control characters so a strict YAML parser stays happy', () => { - const out = serializeSkillFile({ name: 'x', description: 'a\fb\x07c\x1bd\x7fe' }, '') - assert.doesNotMatch(out, /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/) - assert.match(out, /\\x0C/) - assert.match(out, /\\x07/) - assert.match(out, /\\x1B/) - assert.match(out, /\\x7F/) -}) - test('parses supported frontmatter keys with quoted and bare scalars', () => { const content = [ '---', diff --git a/test/identity/ens/resolverDelegation.test.ts b/test/identity/ens/resolverDelegation.test.ts deleted file mode 100644 index 87825ed..0000000 --- a/test/identity/ens/resolverDelegation.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import test from 'node:test' -import assert from 'node:assert/strict' -import { decodeFunctionData, namehash, parseAbi } from 'viem' -import { - encodeApprove, - encodeApprovalRevoke, -} from '../../../src/identity/ens/resolverDelegation.js' - -const RESOLVER_ABI = parseAbi([ - 'function approve(bytes32 node, address delegate, bool approved)', -]) - -const SUBDOMAIN = 'agent1.alice.eth' -const NODE = namehash(SUBDOMAIN) -const OPERATOR_WALLET = '0x000000000000000000000000000000000000bEEF' as `0x${string}` - -test('encodeApprove builds calldata for resolver.approve(node, operatorWallet, true)', () => { - const data = encodeApprove(NODE, OPERATOR_WALLET) - const decoded = decodeFunctionData({ abi: RESOLVER_ABI, data }) - assert.equal(decoded.functionName, 'approve') - const args = decoded.args as readonly [`0x${string}`, `0x${string}`, boolean] - assert.equal(args[0], NODE) - assert.equal(args[1].toLowerCase(), OPERATOR_WALLET.toLowerCase()) - assert.equal(args[2], true) -}) - -test('encodeApprovalRevoke builds calldata for resolver.approve(node, operatorWallet, false)', () => { - const data = encodeApprovalRevoke(NODE, OPERATOR_WALLET) - const decoded = decodeFunctionData({ abi: RESOLVER_ABI, data }) - assert.equal(decoded.functionName, 'approve') - const args = decoded.args as readonly [`0x${string}`, `0x${string}`, boolean] - assert.equal(args[0], NODE) - assert.equal(args[1].toLowerCase(), OPERATOR_WALLET.toLowerCase()) - assert.equal(args[2], false) -}) diff --git a/test/identity/manager/lazyMenuTruncation.test.tsx b/test/identity/manager/lazyMenuTruncation.test.tsx index 0bffbb3..735ba77 100644 --- a/test/identity/manager/lazyMenuTruncation.test.tsx +++ b/test/identity/manager/lazyMenuTruncation.test.tsx @@ -4,7 +4,7 @@ import React from 'react' import { render } from 'ink-testing-library' import { LazyMenu } from '../../../src/identity/manager/shared/components/LazyMenu.js' import { changeSummaryCandidates, type LocalChangeItem } from '../../../src/identity/manager/continuity/state.js' -import { joinWithMore, wrapWords } from '../../../src/ui/text.js' +import { wrapWords } from '../../../src/ui/text.js' const WIDTH = 42 @@ -82,12 +82,6 @@ test('change summaries go from full names to a skill count to a total', () => { assert.deepEqual(changeSummaryCandidates([items[1]!]), ['MEMORY.md']) }) -test('whole-item shortening never splits an item', () => { - assert.equal(joinWithMore(['SOUL.md', 'MEMORY.md', 'canvas'], 40), 'SOUL.md, MEMORY.md, canvas') - assert.equal(joinWithMore(['SOUL.md', 'MEMORY.md', 'canvas'], 20), 'SOUL.md +2 more') - assert.equal(joinWithMore(['SOUL.md', 'MEMORY.md'], 5), null) -}) - test('word wrapping trims at the break instead of starting a line with a space', () => { const lines = wrapWords('Choose a root .eth name to create an agent subdomain under.', 42) assert.deepEqual(lines, ['Choose a root .eth name to create an agent', 'subdomain under.']) diff --git a/test/identity/manager/model/model.test.ts b/test/identity/manager/model/model.test.ts index 0993c2f..40e70b9 100644 --- a/test/identity/manager/model/model.test.ts +++ b/test/identity/manager/model/model.test.ts @@ -16,16 +16,11 @@ import { import { RecoveryConfirmScreen } from '../../../../src/identity/manager/continuity/RecoveryConfirmScreen.js' import { ensValidationReasonText } from '../../../../src/identity/manager/ens/state.js' import { - chainSummaryRow, - networkLabel, networkSubtitle, } from '../../../../src/identity/manager/shared/model/network.js' import { - identitySummaryRows, isCurrentAgentCandidate, lastBackupLabel, - tokenCandidateHint, - tokenCandidateLabel, tokenCandidateSelectLabel, } from '../../../../src/identity/manager/profile/identity.js' import { @@ -91,40 +86,6 @@ test('identity manager explains missing restore access without generic identity assert.equal(view.hint, 'Use the owner wallet once to save a fresh snapshot, then restore with this wallet.') }) -test('identity manager summary always shows the short state CID for the menu card', () => { - const rows = identitySummaryRows({ - source: 'erc8004', - address: '0x000000000000000000000000000000000000dEaD', - ownerAddress: '0x000000000000000000000000000000000000dEaD', - createdAt: new Date(0).toISOString(), - agentId: '42', - backup: { - cid: 'bafybeigdyrztma2dbfczw7q6ooozbxlqzyw5r7w4f3qw2axvvxqg3w6y7q', - createdAt: new Date(0).toISOString(), - envelopeVersion: 'ethagent-state-backup-v1', - ipfsApiUrl: 'https://uploads.pinata.cloud/v3/files', - status: 'pinned', - }, - }) - - assert.deepEqual(rows.map(row => row.label), ['owner wallet', 'token', 'network', 'state', 'card', 'icon']) - assert.equal(rows[3]?.value, 'bafybeigdy…3w6y7q') - assert.equal(rows[0]?.value, '0x0000…dEaD') - assert.equal(rows[4]?.value, 'not saved') - assert.equal(rows[5]?.value, 'not attached') -}) - -test('identity manager summary collapses gracefully when no identity is loaded', () => { - const rows = identitySummaryRows(undefined) - assert.deepEqual(rows.map(row => row.label), ['owner wallet', 'token', 'network', 'state', 'card', 'icon']) - assert.equal(rows[0]?.value, 'not connected') - assert.equal(rows[1]?.value, 'not created') - assert.equal(rows[2]?.value, 'ethereum mainnet') - assert.equal(rows[3]?.value, 'not saved yet') - assert.equal(rows[4]?.value, 'not saved') - assert.equal(rows[5]?.value, 'not attached') -}) - test('ensValidationReasonText covers advanced ENS ownership codes', () => { assert.equal(ensValidationReasonText('token-owner-mismatch'), 'Owner wallet does not hold the agent token') assert.equal(ensValidationReasonText('token-owner-lookup-failed'), 'Could not confirm the agent token owner') @@ -263,34 +224,12 @@ test('storage credential confirmation says pinned snapshots survive and defaults assert.match(output, /❯ Keep JWT/) }) -test('networkLabel and networkSubtitle return human-readable strings for every curated network', () => { - assert.equal(networkLabel('mainnet'), 'ethereum mainnet') - assert.equal(networkLabel('base'), 'base') +test('networkSubtitle returns a human-readable string for every curated network', () => { for (const n of ['mainnet', 'base'] as const) { assert.ok(networkSubtitle(n).length > 0) } }) -test('chainSummaryRow prefers identity.chainId, falls back to selectedNetwork', () => { - const onMainnet = chainSummaryRow( - { version: 2, firstSeenAt: new Date(0).toISOString(), selectedNetwork: 'base' }, - undefined, - ) - assert.equal(onMainnet.value, 'base') - assert.equal(onMainnet.tone, 'dim') - - const fromIdentity = chainSummaryRow( - { version: 2, firstSeenAt: new Date(0).toISOString(), selectedNetwork: 'base' }, - { - address: '0x000000000000000000000000000000000000dEaD', - createdAt: new Date(0).toISOString(), - chainId: 1, - }, - ) - assert.equal(fromIdentity.value, 'ethereum mainnet') - assert.equal(fromIdentity.tone, 'ok') -}) - test('networkSubtitle frames each network as a value-prop, not a jargon dump', () => { for (const n of ['mainnet', 'base'] as const) { assertNoNetworkJargon(networkSubtitle(n)) @@ -364,43 +303,6 @@ test('copyableIdentityFields exposes advanced ENS operator wallet state', () => assert.equal(fields.find(field => field.label === 'Approved Operator Wallets'), undefined) }) -test('token candidate hint stays terse so wallets with many agents stay scannable', () => { - const candidate = { - ownerAddress: '0x8DDe0C47EdC7C0a0745f56d4BB92a959CD0c5394', - chainId: 8453, - rpcUrl: 'https://base.publicnode.com', - identityRegistryAddress: '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432', - agentId: 45744n, - agentUri: 'ipfs://bafy-metadata', - name: 'research agent', - backup: { - cid: 'bafkreib2abcdefghijklmnopqrstuvwxyzjsyzdy', - createdAt: '2026-04-25T00:00:00.000Z', - }, - registration: null, - } as const - - assert.equal(tokenCandidateLabel(candidate), 'research agent') - assert.equal(tokenCandidateHint(candidate), '#45744 · Base · saved 2026-04-25') - assert.doesNotMatch(tokenCandidateHint(candidate), /owner/) - assert.doesNotMatch(tokenCandidateHint(candidate), /state/) -}) - -test('token candidate hint falls back to network only when no backup is pinned yet', () => { - const candidate = { - ownerAddress: '0x8DDe0C47EdC7C0a0745f56d4BB92a959CD0c5394', - chainId: 1, - rpcUrl: 'https://eth.publicnode.com', - identityRegistryAddress: '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432', - agentId: 1n, - agentUri: 'ipfs://bafy', - registration: null, - } as const - - assert.equal(tokenCandidateLabel(candidate), 'Agent Token #1') - assert.equal(tokenCandidateHint(candidate), 'Ethereum Mainnet') -}) - test('current agent candidate marker requires the selected token identity', () => { const identity = { address: '0x8DDe0C47EdC7C0a0745f56d4BB92a959CD0c5394', diff --git a/test/identity/manager/networkFailures.test.tsx b/test/identity/manager/networkFailures.test.tsx index ea941d9..62500e1 100644 --- a/test/identity/manager/networkFailures.test.tsx +++ b/test/identity/manager/networkFailures.test.tsx @@ -9,7 +9,7 @@ import { RestoreFlow } from '../../../src/identity/manager/restore/RestoreFlow.j import { restoreTokenSelectionStep } from '../../../src/identity/manager/restore/discover.js' import { runRestoreFetch } from '../../../src/identity/manager/restore/fetch.js' import { downloadProgress } from '../../../src/identity/manager/restore/progress.js' -import { awaitConfirmedReceipt, awaitOptionalReceipt } from '../../../src/identity/manager/shared/effects/receipts.js' +import { awaitConfirmedReceipt } from '../../../src/identity/manager/shared/effects/receipts.js' import { scopeCallbacks, type EffectCallbacks, type RestoreProgress } from '../../../src/identity/manager/shared/effects/types.js' import type { Step } from '../../../src/identity/manager/reducer.js' import { @@ -344,6 +344,5 @@ test('receipt waits carry no hidden deadline: a slow transaction is not reported }, } await awaitConfirmedReceipt(client as never, '0xabc', 'Agent registration') - await awaitOptionalReceipt(client as never, '0xdef', 'Operator sync') - assert.deepEqual(seen, [{ hash: '0xabc', timeout: 0 }, { hash: '0xdef', timeout: 0 }]) + assert.deepEqual(seen, [{ hash: '0xabc', timeout: 0 }]) }) diff --git a/test/identity/registry/fieldParsers.test.ts b/test/identity/registry/fieldParsers.test.ts index e4ec01b..987ecf0 100644 --- a/test/identity/registry/fieldParsers.test.ts +++ b/test/identity/registry/fieldParsers.test.ts @@ -2,7 +2,6 @@ import test from 'node:test' import assert from 'node:assert/strict' import { arrayField, - booleanField, numberField, objectField, stringField, @@ -48,16 +47,6 @@ test('arrayField returns arrays, otherwise null', () => { assert.equal(arrayField(null, 'a'), null) }) -test('booleanField returns booleans, otherwise undefined', () => { - assert.equal(booleanField({ a: true }, 'a'), true) - assert.equal(booleanField({ a: false }, 'a'), false) - assert.equal(booleanField({ a: 'true' }, 'a'), undefined) - assert.equal(booleanField({ a: 1 }, 'a'), undefined) - assert.equal(booleanField({ a: null }, 'a'), undefined) - assert.equal(booleanField({}, 'a'), undefined) - assert.equal(booleanField(null, 'a'), undefined) -}) - test('field parsers compose for nested registry payloads', () => { const payload: Record = { 'x-ethagent': { diff --git a/test/identity/storage/identityStorage.test.ts b/test/identity/storage/identityStorage.test.ts index 988a387..037beb9 100644 --- a/test/identity/storage/identityStorage.test.ts +++ b/test/identity/storage/identityStorage.test.ts @@ -3,9 +3,9 @@ import assert from 'node:assert/strict' import fs from 'node:fs/promises' import os from 'node:os' import path from 'node:path' -import { setTokenIdentity, getIdentityStatus, clearIdentity } from '../../../src/storage/identity.js' +import { setTokenIdentity, getIdentityStatus } from '../../../src/storage/identity.js' import { saveConfig, type EthagentConfig } from '../../../src/storage/config.js' -import { getSecret, setSecret } from '../../../src/storage/secrets.js' +import { getSecret } from '../../../src/storage/secrets.js' const IDENTITY_ACCOUNT = 'ethereum:default' @@ -106,57 +106,3 @@ test('setTokenIdentity preserves an existing erc8004 block, including operatorVa assert.equal(updated.erc8004?.operatorVaults?.['1'], '0x1111111111111111111111111111111111111111') }) }) - -test('clearIdentity removes ERC-8004 identity metadata without touching other config', async () => { - await withTempHome(async () => { - const config = { - ...baseConfig(), - selectedNetwork: 'base' as const, - erc8004: { - chainId: 8453, - rpcUrl: 'https://base.publicnode.com', - identityRegistryAddress: '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432', - }, - } - await saveConfig(config) - const identity = { - source: 'erc8004' as const, - address: '0x000000000000000000000000000000000000dEaD', - ownerAddress: '0x000000000000000000000000000000000000dEaD', - createdAt: new Date(0).toISOString(), - chainId: 8453, - rpcUrl: 'https://base.publicnode.com', - identityRegistryAddress: '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432', - agentId: '42', - agentUri: 'ipfs://bafy-agent', - state: { name: 'agent' }, - } - const withToken = await setTokenIdentity(config, identity) - - const cleared = await clearIdentity(withToken) - - assert.equal(cleared.identity, undefined) - assert.equal(cleared.selectedNetwork, 'base') - assert.equal(cleared.erc8004?.chainId, 8453) - }) -}) - -test('clearIdentity also removes any legacy local private key secret', async () => { - await withTempHome(async () => { - const config = baseConfig() - await saveConfig(config) - await setSecret(IDENTITY_ACCOUNT, 'legacy-private-key') - assert.equal(await getSecret(IDENTITY_ACCOUNT), 'legacy-private-key') - - await clearIdentity({ - ...config, - identity: { - address: '0x000000000000000000000000000000000000dEaD', - createdAt: new Date(0).toISOString(), - source: 'local-key', - }, - }) - - assert.equal(await getSecret(IDENTITY_ACCOUNT), null) - }) -}) diff --git a/test/run.ts b/test/run.ts index d8433a4..26f4e54 100644 --- a/test/run.ts +++ b/test/run.ts @@ -46,7 +46,6 @@ import './identity/ens/ensLookup.test.js' import './identity/ens/ensAutomation.test.js' import './identity/ens/ensFlowSmoke.test.js' import './identity/ens/operatorDriftDetection.test.js' -import './identity/ens/resolverDelegation.test.js' import './identity/storage/identityStorage.test.js' import './identity/storage/identityIpfs.test.js' import './identity/storage/ipfsRead.test.js' From 43056df90af250fcb0c26c6e2ba761b3233474f0 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 22:09:38 +0000 Subject: [PATCH 2/2] Add npm run test:coverage Compiles the tests with source maps and runs them under Node's built-in coverage, so the report points at the original src files. Test and node_modules files are excluded from the report. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01GjuD2e7tgdWWdpmUMkcZcS --- package.json | 1 + test/run-tests.mjs | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4c90837..fb45922 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "build": "tsc --noEmit", "typecheck": "tsc --noEmit", "test": "node test/run-tests.mjs", + "test:coverage": "node test/run-tests.mjs --coverage", "previews": "tsx scripts/render-previews.ts", "prepack": "npm run build", "contracts:build": "cd contracts && forge build", diff --git a/test/run-tests.mjs b/test/run-tests.mjs index 741bfeb..3bcf205 100644 --- a/test/run-tests.mjs +++ b/test/run-tests.mjs @@ -5,13 +5,14 @@ import { spawnSync } from 'node:child_process' const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') const outDir = path.join(root, '.test-dist') +const coverage = process.argv.includes('--coverage') await fs.rm(outDir, { recursive: true, force: true }) const tscBin = path.join(root, 'node_modules', 'typescript', 'bin', 'tsc') const compile = spawnSync( process.execPath, - [tscBin, '--outDir', outDir, '--noEmit', 'false'], + [tscBin, '--outDir', outDir, '--noEmit', 'false', ...(coverage ? ['--sourceMap', 'true'] : [])], { cwd: root, stdio: 'inherit' }, ) @@ -20,9 +21,18 @@ if (compile.status !== 0) { process.exit(compile.status ?? 1) } +const coverageFlags = coverage + ? [ + '--enable-source-maps', + '--experimental-test-coverage', + '--test-coverage-exclude=**/test/**', + '--test-coverage-exclude=**/node_modules/**', + ] + : [] + const run = spawnSync( process.execPath, - [path.join(outDir, 'test', 'run.js')], + [...coverageFlags, path.join(outDir, 'test', 'run.js')], { cwd: root, stdio: 'inherit' }, )