@aomi-labs/client 0.1.2 → 0.1.6

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/README.md CHANGED
@@ -169,7 +169,7 @@ before sending the message and updates that persisted state as well.
169
169
  The backend builds transactions; the CLI persists and signs them:
170
170
 
171
171
  ```
172
- $ npx @aomi-labs/client chat "swap 1 ETH for USDC on Uniswap" --public-key 0xYourAddr
172
+ $ npx @aomi-labs/client chat "swap 1 ETH for USDC on Uniswap" --public-key 0xYourAddr --chain 1
173
173
  ⚡ Wallet request queued: tx-1
174
174
  to: 0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD
175
175
  value: 1000000000000000000
@@ -257,6 +257,7 @@ All config can be passed as flags (which take priority over env vars):
257
257
  | `--public-key` | `AOMI_PUBLIC_KEY` | — | Wallet address (tells agent your wallet) |
258
258
  | `--private-key` | `PRIVATE_KEY` | — | Hex private key for `aomi sign` |
259
259
  | `--rpc-url` | `CHAIN_RPC_URL` | — | RPC URL for transaction submission |
260
+ | `--chain` | `AOMI_CHAIN_ID` | `1` | Chain ID (1, 137, 42161, 8453, 10, 11155111) |
260
261
  | `--verbose`, `-v` | — | — | Stream tool calls and agent responses live |
261
262
 
262
263
  ```bash
@@ -285,6 +286,7 @@ persists a small JSON file to `$TMPDIR/aomi-session.json`:
285
286
  | `sessionId` | Which conversation to continue |
286
287
  | `model` | Last successfully applied model for the session |
287
288
  | `publicKey` | Wallet address (from `--public-key`) |
289
+ | `chainId` | Active chain ID (from `--chain`) |
288
290
  | `pendingTxs` | Unsigned transactions waiting for `aomi sign <id>` |
289
291
  | `signedTxs` | Completed transactions with hashes/signatures |
290
292
 
package/dist/cli.js CHANGED
@@ -19,7 +19,54 @@ var __spreadValues = (a, b) => {
19
19
  };
20
20
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
21
21
 
22
+ // src/cli/errors.ts
23
+ var CliExit = class extends Error {
24
+ constructor(code) {
25
+ super();
26
+ this.code = code;
27
+ }
28
+ };
29
+ function fatal(message) {
30
+ const RED = "\x1B[31m";
31
+ const DIM3 = "\x1B[2m";
32
+ const RESET3 = "\x1B[0m";
33
+ const lines = message.split("\n");
34
+ const [headline, ...details] = lines;
35
+ console.error(`${RED}\u274C ${headline}${RESET3}`);
36
+ for (const detail of details) {
37
+ if (!detail.trim()) {
38
+ console.error("");
39
+ continue;
40
+ }
41
+ console.error(`${DIM3}${detail}${RESET3}`);
42
+ }
43
+ throw new CliExit(1);
44
+ }
45
+
22
46
  // src/cli/args.ts
47
+ var SUPPORTED_CHAIN_IDS = [1, 137, 42161, 8453, 10, 11155111];
48
+ var CHAIN_NAMES = {
49
+ 1: "Ethereum",
50
+ 137: "Polygon",
51
+ 42161: "Arbitrum One",
52
+ 8453: "Base",
53
+ 10: "Optimism",
54
+ 11155111: "Sepolia"
55
+ };
56
+ function parseChainId(value) {
57
+ if (value === void 0) return void 0;
58
+ const n = parseInt(value, 10);
59
+ if (Number.isNaN(n)) return void 0;
60
+ if (!SUPPORTED_CHAIN_IDS.includes(n)) {
61
+ const list = SUPPORTED_CHAIN_IDS.map(
62
+ (id) => ` ${id} (${CHAIN_NAMES[id]})`
63
+ ).join("\n");
64
+ fatal(`Unsupported chain ID: ${n}
65
+ Supported chains:
66
+ ${list}`);
67
+ }
68
+ return n;
69
+ }
23
70
  function parseArgs(argv) {
24
71
  const raw = argv.slice(2);
25
72
  const command = raw[0] && !raw[0].startsWith("--") ? raw[0] : void 0;
@@ -49,15 +96,16 @@ function parseArgs(argv) {
49
96
  return { command, positional, flags };
50
97
  }
51
98
  function getConfig(parsed) {
52
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i;
99
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j;
53
100
  return {
54
101
  baseUrl: (_b = (_a3 = parsed.flags["backend-url"]) != null ? _a3 : process.env.AOMI_BASE_URL) != null ? _b : "https://api.aomi.dev",
55
102
  apiKey: (_c = parsed.flags["api-key"]) != null ? _c : process.env.AOMI_API_KEY,
56
- namespace: (_e = (_d = parsed.flags["namespace"]) != null ? _d : process.env.AOMI_NAMESPACE) != null ? _e : "default",
103
+ app: (_e = (_d = parsed.flags["app"]) != null ? _d : process.env.AOMI_APP) != null ? _e : "default",
57
104
  model: (_f = parsed.flags["model"]) != null ? _f : process.env.AOMI_MODEL,
58
105
  publicKey: (_g = parsed.flags["public-key"]) != null ? _g : process.env.AOMI_PUBLIC_KEY,
59
106
  privateKey: (_h = parsed.flags["private-key"]) != null ? _h : process.env.PRIVATE_KEY,
60
- chainRpcUrl: (_i = parsed.flags["rpc-url"]) != null ? _i : process.env.CHAIN_RPC_URL
107
+ chainRpcUrl: (_i = parsed.flags["rpc-url"]) != null ? _i : process.env.CHAIN_RPC_URL,
108
+ chain: parseChainId((_j = parsed.flags["chain"]) != null ? _j : process.env.AOMI_CHAIN_ID)
61
109
  };
62
110
  }
63
111
  function createRuntime(argv) {
@@ -106,10 +154,11 @@ function toCliSessionState(stored) {
106
154
  return {
107
155
  sessionId: stored.sessionId,
108
156
  baseUrl: stored.baseUrl,
109
- namespace: stored.namespace,
157
+ app: stored.app,
110
158
  model: stored.model,
111
159
  apiKey: stored.apiKey,
112
160
  publicKey: stored.publicKey,
161
+ chainId: stored.chainId,
113
162
  pendingTxs: stored.pendingTxs,
114
163
  signedTxs: stored.signedTxs
115
164
  };
@@ -126,10 +175,11 @@ function readStoredSession(path) {
126
175
  return {
127
176
  sessionId: parsed.sessionId,
128
177
  baseUrl: parsed.baseUrl,
129
- namespace: parsed.namespace,
178
+ app: parsed.app,
130
179
  model: parsed.model,
131
180
  apiKey: parsed.apiKey,
132
181
  publicKey: parsed.publicKey,
182
+ chainId: parsed.chainId,
133
183
  pendingTxs: parsed.pendingTxs,
134
184
  signedTxs: parsed.signedTxs,
135
185
  localId: typeof parsed.localId === "number" && parsed.localId > 0 ? parsed.localId : fallbackLocalId,
@@ -699,9 +749,9 @@ var AomiClient = class {
699
749
  */
700
750
  async sendMessage(sessionId, message, options) {
701
751
  var _a3, _b;
702
- const namespace = (_a3 = options == null ? void 0 : options.namespace) != null ? _a3 : "default";
752
+ const app = (_a3 = options == null ? void 0 : options.app) != null ? _a3 : "default";
703
753
  const apiKey = (_b = options == null ? void 0 : options.apiKey) != null ? _b : this.apiKey;
704
- const payload = { message, namespace };
754
+ const payload = { message, app };
705
755
  if (options == null ? void 0 : options.publicKey) {
706
756
  payload.public_key = options.publicKey;
707
757
  }
@@ -874,11 +924,11 @@ var AomiClient = class {
874
924
  // Control API
875
925
  // ===========================================================================
876
926
  /**
877
- * Get available namespaces.
927
+ * Get available apps.
878
928
  */
879
- async getNamespaces(sessionId, options) {
929
+ async getApps(sessionId, options) {
880
930
  var _a3;
881
- const url = new URL("/api/control/namespaces", this.baseUrl);
931
+ const url = new URL("/api/control/apps", this.baseUrl);
882
932
  if (options == null ? void 0 : options.publicKey) {
883
933
  url.searchParams.set("public_key", options.publicKey);
884
934
  }
@@ -889,7 +939,7 @@ var AomiClient = class {
889
939
  }
890
940
  const response = await fetch(url.toString(), { headers });
891
941
  if (!response.ok) {
892
- throw new Error(`Failed to get namespaces: HTTP ${response.status}`);
942
+ throw new Error(`Failed to get apps: HTTP ${response.status}`);
893
943
  }
894
944
  return await response.json();
895
945
  }
@@ -919,8 +969,8 @@ var AomiClient = class {
919
969
  var _a3;
920
970
  const apiKey = (_a3 = options == null ? void 0 : options.apiKey) != null ? _a3 : this.apiKey;
921
971
  const payload = { rig };
922
- if (options == null ? void 0 : options.namespace) {
923
- payload.namespace = options.namespace;
972
+ if (options == null ? void 0 : options.app) {
973
+ payload.app = options.app;
924
974
  }
925
975
  return postState(this.baseUrl, "/api/control/model", payload, sessionId, apiKey);
926
976
  }
@@ -1054,7 +1104,7 @@ function getToolArgs(payload) {
1054
1104
  const nestedArgs = asRecord(root == null ? void 0 : root.args);
1055
1105
  return (_a3 = nestedArgs != null ? nestedArgs : root) != null ? _a3 : {};
1056
1106
  }
1057
- function parseChainId(value) {
1107
+ function parseChainId2(value) {
1058
1108
  if (typeof value === "number" && Number.isFinite(value)) return value;
1059
1109
  if (typeof value !== "string") return void 0;
1060
1110
  const trimmed = value.trim();
@@ -1076,7 +1126,7 @@ function normalizeTxPayload(payload) {
1076
1126
  const valueRaw = args.value;
1077
1127
  const value = typeof valueRaw === "string" ? valueRaw : typeof valueRaw === "number" && Number.isFinite(valueRaw) ? String(Math.trunc(valueRaw)) : void 0;
1078
1128
  const data = typeof args.data === "string" ? args.data : void 0;
1079
- const chainId = (_c = (_b = (_a3 = parseChainId(args.chainId)) != null ? _a3 : parseChainId(args.chain_id)) != null ? _b : parseChainId(ctx == null ? void 0 : ctx.user_chain_id)) != null ? _c : parseChainId(ctx == null ? void 0 : ctx.userChainId);
1129
+ const chainId = (_c = (_b = (_a3 = parseChainId2(args.chainId)) != null ? _a3 : parseChainId2(args.chain_id)) != null ? _b : parseChainId2(ctx == null ? void 0 : ctx.user_chain_id)) != null ? _c : parseChainId2(ctx == null ? void 0 : ctx.userChainId);
1080
1130
  return { to, value, data, chainId };
1081
1131
  }
1082
1132
  function normalizeEip712Payload(payload) {
@@ -1101,7 +1151,38 @@ function normalizeEip712Payload(payload) {
1101
1151
  }
1102
1152
 
1103
1153
  // src/session.ts
1104
- var Session = class extends TypedEventEmitter {
1154
+ function sortJson(value) {
1155
+ if (Array.isArray(value)) {
1156
+ return value.map((entry) => sortJson(entry));
1157
+ }
1158
+ if (value && typeof value === "object") {
1159
+ return Object.keys(value).sort().reduce((acc, key) => {
1160
+ acc[key] = sortJson(value[key]);
1161
+ return acc;
1162
+ }, {});
1163
+ }
1164
+ return value;
1165
+ }
1166
+ function isSubsetMatch(expected, actual) {
1167
+ if (Array.isArray(expected)) {
1168
+ if (!Array.isArray(actual) || expected.length !== actual.length) {
1169
+ return false;
1170
+ }
1171
+ return expected.every(
1172
+ (entry, index) => isSubsetMatch(entry, actual[index])
1173
+ );
1174
+ }
1175
+ if (expected && typeof expected === "object") {
1176
+ if (!actual || typeof actual !== "object" || Array.isArray(actual)) {
1177
+ return false;
1178
+ }
1179
+ return Object.entries(expected).every(
1180
+ ([key, value]) => isSubsetMatch(value, actual[key])
1181
+ );
1182
+ }
1183
+ return expected === actual;
1184
+ }
1185
+ var ClientSession = class extends TypedEventEmitter {
1105
1186
  constructor(clientOrOptions, sessionOptions) {
1106
1187
  var _a3, _b, _c;
1107
1188
  super();
@@ -1117,7 +1198,7 @@ var Session = class extends TypedEventEmitter {
1117
1198
  this.pendingResolve = null;
1118
1199
  this.client = clientOrOptions instanceof AomiClient ? clientOrOptions : new AomiClient(clientOrOptions);
1119
1200
  this.sessionId = (_a3 = sessionOptions == null ? void 0 : sessionOptions.sessionId) != null ? _a3 : crypto.randomUUID();
1120
- this.namespace = (_b = sessionOptions == null ? void 0 : sessionOptions.namespace) != null ? _b : "default";
1201
+ this.app = (_b = sessionOptions == null ? void 0 : sessionOptions.app) != null ? _b : "default";
1121
1202
  this.publicKey = sessionOptions == null ? void 0 : sessionOptions.publicKey;
1122
1203
  this.apiKey = sessionOptions == null ? void 0 : sessionOptions.apiKey;
1123
1204
  this.userState = sessionOptions == null ? void 0 : sessionOptions.userState;
@@ -1143,11 +1224,12 @@ var Session = class extends TypedEventEmitter {
1143
1224
  async send(message) {
1144
1225
  this.assertOpen();
1145
1226
  const response = await this.client.sendMessage(this.sessionId, message, {
1146
- namespace: this.namespace,
1227
+ app: this.app,
1147
1228
  publicKey: this.publicKey,
1148
1229
  apiKey: this.apiKey,
1149
1230
  userState: this.userState
1150
1231
  });
1232
+ this.assertUserStateAligned(response.user_state);
1151
1233
  this.applyState(response);
1152
1234
  if (!response.is_processing && this.walletRequests.length === 0) {
1153
1235
  return { messages: this._messages, title: this._title };
@@ -1166,11 +1248,12 @@ var Session = class extends TypedEventEmitter {
1166
1248
  async sendAsync(message) {
1167
1249
  this.assertOpen();
1168
1250
  const response = await this.client.sendMessage(this.sessionId, message, {
1169
- namespace: this.namespace,
1251
+ app: this.app,
1170
1252
  publicKey: this.publicKey,
1171
1253
  apiKey: this.apiKey,
1172
1254
  userState: this.userState
1173
1255
  });
1256
+ this.assertUserStateAligned(response.user_state);
1174
1257
  this.applyState(response);
1175
1258
  if (response.is_processing) {
1176
1259
  this._isProcessing = true;
@@ -1283,6 +1366,23 @@ var Session = class extends TypedEventEmitter {
1283
1366
  getIsProcessing() {
1284
1367
  return this._isProcessing;
1285
1368
  }
1369
+ resolveUserState(userState) {
1370
+ this.userState = userState;
1371
+ const address = userState["address"];
1372
+ if (typeof address === "string" && address.length > 0) {
1373
+ this.publicKey = address;
1374
+ }
1375
+ }
1376
+ resolveWallet(address, chainId) {
1377
+ this.resolveUserState({ address, chainId: chainId != null ? chainId : 1, isConnected: true });
1378
+ }
1379
+ async syncUserState() {
1380
+ this.assertOpen();
1381
+ const state = await this.client.fetchState(this.sessionId, this.userState);
1382
+ this.assertUserStateAligned(state.user_state);
1383
+ this.applyState(state);
1384
+ return state;
1385
+ }
1286
1386
  // ===========================================================================
1287
1387
  // Internal — Polling (ported from PollingController)
1288
1388
  // ===========================================================================
@@ -1311,6 +1411,7 @@ var Session = class extends TypedEventEmitter {
1311
1411
  this.userState
1312
1412
  );
1313
1413
  if (!this.pollTimer) return;
1414
+ this.assertUserStateAligned(state.user_state);
1314
1415
  this.applyState(state);
1315
1416
  if (!state.is_processing && this.walletRequests.length === 0) {
1316
1417
  this.stopPolling();
@@ -1412,6 +1513,18 @@ var Session = class extends TypedEventEmitter {
1412
1513
  throw new Error("Session is closed");
1413
1514
  }
1414
1515
  }
1516
+ assertUserStateAligned(actualUserState) {
1517
+ if (!this.userState || !actualUserState) {
1518
+ return;
1519
+ }
1520
+ if (!isSubsetMatch(this.userState, actualUserState)) {
1521
+ const expected = JSON.stringify(sortJson(this.userState));
1522
+ const actual = JSON.stringify(sortJson(actualUserState));
1523
+ throw new Error(
1524
+ `Backend user_state mismatch. expected subset=${expected} actual=${actual}`
1525
+ );
1526
+ }
1527
+ }
1415
1528
  };
1416
1529
 
1417
1530
  // src/cli/context.ts
@@ -1422,9 +1535,10 @@ function getOrCreateSession(runtime) {
1422
1535
  state = {
1423
1536
  sessionId: crypto.randomUUID(),
1424
1537
  baseUrl: config.baseUrl,
1425
- namespace: config.namespace,
1538
+ app: config.app,
1426
1539
  apiKey: config.apiKey,
1427
- publicKey: config.publicKey
1540
+ publicKey: config.publicKey,
1541
+ chainId: config.chain
1428
1542
  };
1429
1543
  writeState(state);
1430
1544
  } else {
@@ -1433,8 +1547,8 @@ function getOrCreateSession(runtime) {
1433
1547
  state.baseUrl = config.baseUrl;
1434
1548
  changed = true;
1435
1549
  }
1436
- if (config.namespace !== state.namespace) {
1437
- state.namespace = config.namespace;
1550
+ if (config.app !== state.app) {
1551
+ state.app = config.app;
1438
1552
  changed = true;
1439
1553
  }
1440
1554
  if (config.apiKey !== void 0 && config.apiKey !== state.apiKey) {
@@ -1445,22 +1559,24 @@ function getOrCreateSession(runtime) {
1445
1559
  state.publicKey = config.publicKey;
1446
1560
  changed = true;
1447
1561
  }
1562
+ if (config.chain !== void 0 && config.chain !== state.chainId) {
1563
+ state.chainId = config.chain;
1564
+ changed = true;
1565
+ }
1448
1566
  if (changed) writeState(state);
1449
1567
  }
1450
- const session = new Session(
1568
+ const session = new ClientSession(
1451
1569
  { baseUrl: state.baseUrl, apiKey: state.apiKey },
1452
1570
  {
1453
1571
  sessionId: state.sessionId,
1454
- namespace: state.namespace,
1572
+ app: state.app,
1455
1573
  apiKey: state.apiKey,
1456
- publicKey: state.publicKey,
1457
- userState: state.publicKey ? {
1458
- address: state.publicKey,
1459
- chainId: 1,
1460
- isConnected: true
1461
- } : void 0
1574
+ publicKey: state.publicKey
1462
1575
  }
1463
1576
  );
1577
+ if (state.publicKey) {
1578
+ session.resolveWallet(state.publicKey, state.chainId);
1579
+ }
1464
1580
  return { session, state };
1465
1581
  }
1466
1582
  function createControlClient(runtime) {
@@ -1471,7 +1587,7 @@ function createControlClient(runtime) {
1471
1587
  }
1472
1588
  async function applyModelSelection(session, state, model) {
1473
1589
  await session.client.setModel(state.sessionId, model, {
1474
- namespace: state.namespace,
1590
+ app: state.app,
1475
1591
  apiKey: state.apiKey
1476
1592
  });
1477
1593
  state.model = model;
@@ -1485,30 +1601,6 @@ async function applyRequestedModelIfPresent(runtime, session, state) {
1485
1601
  await applyModelSelection(session, state, requestedModel);
1486
1602
  }
1487
1603
 
1488
- // src/cli/errors.ts
1489
- var CliExit = class extends Error {
1490
- constructor(code) {
1491
- super();
1492
- this.code = code;
1493
- }
1494
- };
1495
- function fatal(message) {
1496
- const RED = "\x1B[31m";
1497
- const DIM3 = "\x1B[2m";
1498
- const RESET3 = "\x1B[0m";
1499
- const lines = message.split("\n");
1500
- const [headline, ...details] = lines;
1501
- console.error(`${RED}\u274C ${headline}${RESET3}`);
1502
- for (const detail of details) {
1503
- if (!detail.trim()) {
1504
- console.error("");
1505
- continue;
1506
- }
1507
- console.error(`${DIM3}${detail}${RESET3}`);
1508
- }
1509
- throw new CliExit(1);
1510
- }
1511
-
1512
1604
  // src/cli/transactions.ts
1513
1605
  function walletRequestToPendingTx(request) {
1514
1606
  if (request.kind === "transaction") {
@@ -1558,17 +1650,7 @@ async function chatCommand(runtime) {
1558
1650
  try {
1559
1651
  await applyRequestedModelIfPresent(runtime, session, state);
1560
1652
  if (state.publicKey) {
1561
- await session.client.sendSystemMessage(
1562
- session.sessionId,
1563
- JSON.stringify({
1564
- type: "wallet:state_changed",
1565
- payload: {
1566
- address: state.publicKey,
1567
- chainId: 1,
1568
- isConnected: true
1569
- }
1570
- })
1571
- );
1653
+ session.resolveWallet(state.publicKey, state.chainId);
1572
1654
  }
1573
1655
  const capturedRequests = [];
1574
1656
  let printedAgentCount = 0;
@@ -1716,7 +1798,7 @@ async function statusCommand(runtime) {
1716
1798
  {
1717
1799
  sessionId: state.sessionId,
1718
1800
  baseUrl: state.baseUrl,
1719
- namespace: state.namespace,
1801
+ app: state.app,
1720
1802
  model: (_a3 = state.model) != null ? _a3 : null,
1721
1803
  isProcessing: (_b = apiState.is_processing) != null ? _b : false,
1722
1804
  messageCount: (_d = (_c = apiState.messages) == null ? void 0 : _c.length) != null ? _d : 0,
@@ -2154,8 +2236,51 @@ Run \`aomi tx\` to see available IDs.`
2154
2236
  }
2155
2237
  return pendingTx;
2156
2238
  }
2239
+ function rewriteSessionState(runtime, state) {
2240
+ let changed = false;
2241
+ if (runtime.config.baseUrl !== state.baseUrl) {
2242
+ state.baseUrl = runtime.config.baseUrl;
2243
+ changed = true;
2244
+ }
2245
+ if (runtime.config.app !== state.app) {
2246
+ state.app = runtime.config.app;
2247
+ changed = true;
2248
+ }
2249
+ if (runtime.config.apiKey !== void 0 && runtime.config.apiKey !== state.apiKey) {
2250
+ state.apiKey = runtime.config.apiKey;
2251
+ changed = true;
2252
+ }
2253
+ if (runtime.config.chain !== void 0 && runtime.config.chain !== state.chainId) {
2254
+ state.chainId = runtime.config.chain;
2255
+ changed = true;
2256
+ }
2257
+ if (changed) {
2258
+ writeState(state);
2259
+ }
2260
+ }
2261
+ function createSessionFromState(state) {
2262
+ const session = new ClientSession(
2263
+ { baseUrl: state.baseUrl, apiKey: state.apiKey },
2264
+ {
2265
+ sessionId: state.sessionId,
2266
+ app: state.app,
2267
+ apiKey: state.apiKey,
2268
+ publicKey: state.publicKey
2269
+ }
2270
+ );
2271
+ if (state.publicKey) {
2272
+ session.resolveWallet(state.publicKey, state.chainId);
2273
+ }
2274
+ return session;
2275
+ }
2276
+ async function persistResolvedSignerState(session, state, address, chainId) {
2277
+ state.publicKey = address;
2278
+ writeState(state);
2279
+ session.resolveWallet(address, chainId);
2280
+ await session.syncUserState();
2281
+ }
2157
2282
  async function signCommand(runtime) {
2158
- var _a3, _b, _c;
2283
+ var _a3, _b, _c, _d;
2159
2284
  const txId = runtime.parsed.positional[0];
2160
2285
  if (!txId) {
2161
2286
  fatal(
@@ -2177,15 +2302,22 @@ async function signCommand(runtime) {
2177
2302
  if (!state) {
2178
2303
  fatal("No active session. Run `aomi chat` first.");
2179
2304
  }
2305
+ rewriteSessionState(runtime, state);
2180
2306
  const pendingTx = requirePendingTx(state, txId);
2181
- const { session } = getOrCreateSession(runtime);
2307
+ const session = createSessionFromState(state);
2182
2308
  try {
2183
2309
  const account = privateKeyToAccount(privateKey);
2310
+ if (state.publicKey && account.address.toLowerCase() !== state.publicKey.toLowerCase()) {
2311
+ console.log(
2312
+ `\u26A0\uFE0F Signer ${account.address} differs from session public key ${state.publicKey}`
2313
+ );
2314
+ console.log(" Updating session to match the signing key...");
2315
+ }
2184
2316
  const rpcUrl = runtime.config.chainRpcUrl;
2185
- const targetChainId = (_a3 = pendingTx.chainId) != null ? _a3 : 1;
2186
- const chain = (_b = Object.values(viemChains).find(
2317
+ const targetChainId = (_b = (_a3 = pendingTx.chainId) != null ? _a3 : state.chainId) != null ? _b : 1;
2318
+ const chain = (_c = Object.values(viemChains).find(
2187
2319
  (candidate) => typeof candidate === "object" && candidate !== null && "id" in candidate && candidate.id === targetChainId
2188
- )) != null ? _b : {
2320
+ )) != null ? _c : {
2189
2321
  id: targetChainId,
2190
2322
  name: `Chain ${targetChainId}`,
2191
2323
  nativeCurrency: {
@@ -2207,6 +2339,8 @@ async function signCommand(runtime) {
2207
2339
  console.log(`Signer: ${account.address}`);
2208
2340
  console.log(`ID: ${pendingTx.id}`);
2209
2341
  console.log(`Kind: ${pendingTx.kind}`);
2342
+ let signedRecord;
2343
+ let backendNotification;
2210
2344
  if (pendingTx.kind === "transaction") {
2211
2345
  console.log(`To: ${pendingTx.to}`);
2212
2346
  if (pendingTx.value) console.log(`Value: ${pendingTx.value}`);
@@ -2218,12 +2352,10 @@ async function signCommand(runtime) {
2218
2352
  const hash = await walletClient.sendTransaction({
2219
2353
  to: pendingTx.to,
2220
2354
  value: pendingTx.value ? BigInt(pendingTx.value) : /* @__PURE__ */ BigInt("0"),
2221
- data: (_c = pendingTx.data) != null ? _c : void 0
2355
+ data: (_d = pendingTx.data) != null ? _d : void 0
2222
2356
  });
2223
2357
  console.log(`\u2705 Sent! Hash: ${hash}`);
2224
- removePendingTx(state, txId);
2225
- const freshState = readState();
2226
- addSignedTx(freshState, {
2358
+ signedRecord = {
2227
2359
  id: txId,
2228
2360
  kind: "transaction",
2229
2361
  txHash: hash,
@@ -2232,14 +2364,11 @@ async function signCommand(runtime) {
2232
2364
  value: pendingTx.value,
2233
2365
  chainId: pendingTx.chainId,
2234
2366
  timestamp: Date.now()
2235
- });
2236
- await session.client.sendSystemMessage(
2237
- state.sessionId,
2238
- JSON.stringify({
2239
- type: "wallet:tx_complete",
2240
- payload: { txHash: hash, status: "success" }
2241
- })
2242
- );
2367
+ };
2368
+ backendNotification = {
2369
+ type: "wallet:tx_complete",
2370
+ payload: { txHash: hash, status: "success" }
2371
+ };
2243
2372
  } else {
2244
2373
  const typedData = pendingTx.payload.typed_data;
2245
2374
  if (!typedData) {
@@ -2262,28 +2391,36 @@ async function signCommand(runtime) {
2262
2391
  message
2263
2392
  });
2264
2393
  console.log(`\u2705 Signed! Signature: ${signature.slice(0, 20)}...`);
2265
- removePendingTx(state, txId);
2266
- const freshState = readState();
2267
- addSignedTx(freshState, {
2394
+ signedRecord = {
2268
2395
  id: txId,
2269
2396
  kind: "eip712_sign",
2270
2397
  signature,
2271
2398
  from: account.address,
2272
2399
  description: pendingTx.description,
2273
2400
  timestamp: Date.now()
2274
- });
2275
- await session.client.sendSystemMessage(
2276
- state.sessionId,
2277
- JSON.stringify({
2278
- type: "wallet_eip712_response",
2279
- payload: {
2280
- status: "success",
2281
- signature,
2282
- description: pendingTx.description
2283
- }
2284
- })
2285
- );
2401
+ };
2402
+ backendNotification = {
2403
+ type: "wallet_eip712_response",
2404
+ payload: {
2405
+ status: "success",
2406
+ signature,
2407
+ description: pendingTx.description
2408
+ }
2409
+ };
2286
2410
  }
2411
+ await persistResolvedSignerState(
2412
+ session,
2413
+ state,
2414
+ account.address,
2415
+ targetChainId
2416
+ );
2417
+ removePendingTx(state, txId);
2418
+ const freshState = readState();
2419
+ addSignedTx(freshState, signedRecord);
2420
+ await session.client.sendSystemMessage(
2421
+ state.sessionId,
2422
+ JSON.stringify(backendNotification)
2423
+ );
2287
2424
  console.log("Backend notified.");
2288
2425
  } catch (err) {
2289
2426
  if (err instanceof CliExit) throw err;
@@ -2320,8 +2457,8 @@ Usage:
2320
2457
 
2321
2458
  Options:
2322
2459
  --backend-url <url> Backend URL (default: https://api.aomi.dev)
2323
- --api-key <key> API key for non-default namespaces
2324
- --namespace <ns> Namespace (default: "default")
2460
+ --api-key <key> API key for non-default apps
2461
+ --app <name> App (default: "default")
2325
2462
  --model <rig> Set the active model for this session
2326
2463
  --public-key <addr> Wallet address (so the agent knows your wallet)
2327
2464
  --private-key <key> Hex private key for signing
@@ -2331,7 +2468,7 @@ Options:
2331
2468
  Environment (overridden by flags):
2332
2469
  AOMI_BASE_URL Backend URL
2333
2470
  AOMI_API_KEY API key
2334
- AOMI_NAMESPACE Namespace
2471
+ AOMI_APP App
2335
2472
  AOMI_MODEL Model rig
2336
2473
  AOMI_PUBLIC_KEY Wallet address
2337
2474
  PRIVATE_KEY Hex private key for signing