Skip to content

Commit eb0ab94

Browse files
committed
fix(recipe-harness): sync mobile agentic overlay
1 parent b3be5ad commit eb0ab94

3 files changed

Lines changed: 340 additions & 27 deletions

File tree

domains/agentic/skills/recipe-harness/adapters/mobile/app-overlay/app/core/AgenticService/AgentStepHud.tsx.patch

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ interface Step {
1515
}
1616

1717
function statusForStep(step: Step) {
18-
return String(step.status ?? step.id.split(/\s+/)[0] ?? '').toLowerCase();
18+
const id = typeof step.id === 'string' ? step.id : '';
19+
return String(step.status ?? id.split(/\s+/)[0] ?? '').toLowerCase();
1920
}
2021

2122
function progressForStep(step: Step) {
@@ -26,7 +27,8 @@ function progressForStep(step: Step) {
2627
return `${step.progress.current}/${step.progress.total}`;
2728
}
2829
const progressPattern = /\b\d+\s*\/\s*\d+\b/;
29-
const match = progressPattern.exec(step.id);
30+
const id = typeof step.id === 'string' ? step.id : '';
31+
const match = progressPattern.exec(id);
3032
return match ? match[0].replace(/\s+/g, '') : null;
3133
}
3234

domains/agentic/skills/recipe-harness/adapters/mobile/app-overlay/app/core/AgenticService/AgenticService.test.ts.patch

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,60 @@ import type {
1616
NavigationContainerRef,
1717
ParamListBase,
1818
} from '@react-navigation/native';
19+
import { AccountGroupType, AccountWalletType } from '@metamask/account-api';
1920

2021
const mockCreateWallet = jest.fn().mockResolvedValue(undefined);
22+
const mockCreateAccountGroups = jest.fn().mockResolvedValue([]);
2123
const mockImportAccount = jest.fn().mockResolvedValue(undefined);
24+
const mockIsUnlocked = jest.fn(() => true);
25+
const mockSubmitPassword = jest.fn().mockResolvedValue(undefined);
26+
const FIXTURE_WALLET_ID = 'entropy:keyring-1' as const;
27+
28+
function mockEvmAccount(id: string, address: string, name: string) {
29+
return {
30+
id,
31+
address,
32+
type: 'eip155:eoa' as const,
33+
options: {},
34+
scopes: ['eip155:1' as const],
35+
methods: [],
36+
metadata: {
37+
name,
38+
importTime: 0,
39+
keyring: { type: 'HD Key Tree' },
40+
},
41+
};
42+
}
43+
44+
function mockEntropyGroup(
45+
groupIndex: number,
46+
accountIds: [string, ...string[]],
47+
name: string,
48+
): {
49+
type: AccountGroupType.MultichainAccount;
50+
id: `${typeof FIXTURE_WALLET_ID}/${number}`;
51+
accounts: [string, ...string[]];
52+
metadata: {
53+
name: string;
54+
pinned: boolean;
55+
hidden: boolean;
56+
lastSelected: number;
57+
entropy: { groupIndex: number };
58+
};
59+
} {
60+
return {
61+
type: AccountGroupType.MultichainAccount,
62+
id: `${FIXTURE_WALLET_ID}/${groupIndex}` as const,
63+
accounts: accountIds,
64+
metadata: {
65+
name,
66+
pinned: false,
67+
hidden: false,
68+
lastSelected: 0,
69+
entropy: { groupIndex },
70+
},
71+
};
72+
}
2273

2374
jest.mock('../Engine', () => ({
2475
context: {
@@ -48,9 +99,13 @@ jest.mock('../Engine', () => ({
4899
MultichainAccountService: {
49100
createMultichainAccountWallet: (...args: unknown[]) =>
50101
mockCreateWallet(...args),
102+
createMultichainAccountGroups: (...args: unknown[]) =>
103+
mockCreateAccountGroups(...args),
51104
init: jest.fn().mockResolvedValue(undefined),
52105
},
53106
KeyringController: {
107+
isUnlocked: () => mockIsUnlocked(),
108+
submitPassword: (password: string) => mockSubmitPassword(password),
54109
importAccountWithStrategy: (...args: unknown[]) =>
55110
mockImportAccount(...(args as [string, string[]])),
56111
},
@@ -220,6 +275,13 @@ function installFiberHook(rootFiber: FiberNode) {
220275
};
221276
}
222277

278+
function resetMockAccountState() {
279+
Engine.context.AccountsController.state.internalAccounts.accounts = {
280+
a1: mockEvmAccount('a1', '0xABC', 'Account 1'),
281+
};
282+
Engine.context.AccountTreeController.state.accountTree = { wallets: {} };
283+
}
284+
223285
// ─── Fiber tree helper tests ────────────────────────────────────────────────
224286

225287
describe('walkFiber', () => {
@@ -766,8 +828,17 @@ describe('AgenticService.install', () => {
766828
describe('setupWallet', () => {
767829
beforeEach(() => {
768830
mockCreateWallet.mockClear();
831+
mockCreateAccountGroups.mockReset();
832+
mockCreateAccountGroups.mockResolvedValue([]);
769833
mockImportAccount.mockClear();
770834
mockDispatch.mockClear();
835+
mockIsUnlocked.mockReset();
836+
mockIsUnlocked.mockReturnValue(true);
837+
mockSubmitPassword.mockClear();
838+
(
839+
Engine.context.AccountTreeController.setAccountGroupName as jest.Mock
840+
).mockClear();
841+
resetMockAccountState();
771842
});
772843

773844
it('dispatches all onboarding flags', async () => {
@@ -792,6 +863,108 @@ describe('AgenticService.install', () => {
792863
expect(result.error).toBe('boom');
793864
});
794865

866+
it('recovers an existing fixture vault when auth leaves the keyring locked', async () => {
867+
mockIsUnlocked
868+
.mockReturnValueOnce(false)
869+
.mockReturnValueOnce(false)
870+
.mockReturnValue(true);
871+
872+
const result = await bridge().applyWalletFixture({
873+
password: 'test123',
874+
accounts: [],
875+
});
876+
877+
expect(result.ok).toBe(true);
878+
expect(mockSubmitPassword).toHaveBeenCalledWith('test123');
879+
expect(mockDispatch).toHaveBeenCalledWith({ type: 'PASSWORD_SET' });
880+
expect(mockDispatch).toHaveBeenCalledWith({
881+
type: 'SEED_PHRASE_BACKED_UP',
882+
});
883+
expect(mockDispatch).toHaveBeenCalledWith({
884+
type: 'SET_COMPLETED_ONBOARDING',
885+
});
886+
expect(mockDispatch).toHaveBeenCalledWith({ type: 'SET_EXISTING_USER' });
887+
expect(mockDispatch).toHaveBeenCalledWith({ type: 'LOG_IN' });
888+
});
889+
890+
it('creates missing fixture HD accounts with a batched account-group range', async () => {
891+
const mnemonic =
892+
'test test test test test test test test test test test junk';
893+
const group0Id = `${FIXTURE_WALLET_ID}/0` as const;
894+
Engine.context.AccountsController.state.internalAccounts.accounts = {
895+
a1: mockEvmAccount(
896+
'a1',
897+
'0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
898+
'dev1',
899+
),
900+
};
901+
Engine.context.AccountTreeController.state.accountTree = {
902+
wallets: {
903+
[FIXTURE_WALLET_ID]: {
904+
type: AccountWalletType.Entropy,
905+
id: FIXTURE_WALLET_ID,
906+
status: 'ready',
907+
metadata: { name: 'Fixture Wallet', entropy: { id: 'keyring-1' } },
908+
groups: {
909+
[group0Id]: mockEntropyGroup(0, ['a1'], 'dev1'),
910+
},
911+
},
912+
},
913+
};
914+
mockCreateAccountGroups.mockImplementationOnce(async () => {
915+
Engine.context.AccountsController.state.internalAccounts.accounts = {
916+
...Engine.context.AccountsController.state.internalAccounts.accounts,
917+
a2: mockEvmAccount(
918+
'a2',
919+
'0x0000000000000000000000000000000000000002',
920+
'dev2',
921+
),
922+
a3: mockEvmAccount(
923+
'a3',
924+
'0x0000000000000000000000000000000000000003',
925+
'dev3',
926+
),
927+
};
928+
const wallet = Engine.context.AccountTreeController.state.accountTree
929+
.wallets[FIXTURE_WALLET_ID] as {
930+
groups: Record<string, unknown>;
931+
};
932+
wallet.groups[`${FIXTURE_WALLET_ID}/1`] = mockEntropyGroup(
933+
1,
934+
['a2'],
935+
'dev2',
936+
);
937+
wallet.groups[`${FIXTURE_WALLET_ID}/2`] = mockEntropyGroup(
938+
2,
939+
['a3'],
940+
'dev3',
941+
);
942+
return [];
943+
});
944+
945+
const result = await bridge().setupWallet({
946+
password: 'test123',
947+
accounts: [
948+
{
949+
type: 'mnemonic',
950+
value: mnemonic,
951+
count: 3,
952+
names: ['dev1', 'dev2', 'dev3'],
953+
},
954+
],
955+
});
956+
957+
expect(result.ok).toBe(true);
958+
expect(mockCreateAccountGroups).toHaveBeenCalledWith({
959+
fromGroupIndex: 1,
960+
toGroupIndex: 2,
961+
entropySource: 'keyring-1',
962+
});
963+
expect(
964+
Engine.context.AccountTreeController.setAccountGroupName,
965+
).toHaveBeenCalledWith(`${FIXTURE_WALLET_ID}/2`, 'dev3');
966+
});
967+
795968
it('opts out of metametrics when specified', async () => {
796969
const { analytics } = jest.requireMock('../../util/analytics/analytics');
797970
await bridge().setupWallet({

0 commit comments

Comments
 (0)