Skip to content

Commit dec231c

Browse files
Shawclaude
andcommitted
fix(cloud): unblock credits-balance CORS + restore wallet reconnect on /bsc
Two visible regressions on the live BSC flow: 1) CORS preflight rejection on `/api/credits/balance` CreditsProvider was sending `Pragma: no-cache` alongside `Cache-Control: no-cache, no-store, must-revalidate`. The `Cache-Control` value already disables caching for any modern proxy; `Pragma` is HTTP/1.0-era cruft kept "just in case". Sending it on a cross-origin call to `api.elizacloud.ai` forces a CORS preflight to list `pragma` in `Access-Control-Allow-Headers`, which the Worker's CORS config doesn't, so the actual GET was rejected with `Request header field pragma is not allowed`. Dropping the header from the request is the correct fix — no protocol semantics change, and the API CORS allowlist stays narrow. 2) Wallet stuck on "Connect Wallet" after a successful MetaMask connect `StewardWalletProviders` was setting `reconnectOnMount={false}` on the `WagmiProvider`. With that flag, wagmi refuses to restore an existing connector session when the provider mounts. On /bsc this surfaced as: the user connects via the RainbowKit modal, MetaMask shows "site connected", but as soon as React re-renders past the connect event (e.g. when the wallet attach flow swaps the AttachWalletCard for the DirectCryptoCreditCard), the new component's `useAccount` / `ConnectButton.Custom` boots with `account = undefined` and the button regresses to "Connect Wallet" with no clickable recovery. The pay handler then bails with "Connect your BSC wallet first" because `isConnected` is false. Default behavior (`reconnectOnMount: true`) is what the BSC promo, dashboard billing, and any return-visit flow want. The login surface already gates SIWE on an explicit user click via `pendingSignRef`, so auto-reconnect can't accidentally fire a signature prompt — flipping the default is safe across all consumers of these providers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 641788b commit dec231c

29 files changed

Lines changed: 503 additions & 66 deletions

packages/examples/VALIDATION.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,21 @@ The local examples sweep has been run in this worktree with these outcomes:
4646
| --- | --- | --- |
4747
| `_plugin` | `typecheck`, `test`, `build` | Optional manual Cypress flow via `test:e2e:manual`. |
4848
| `a2a` | `typecheck`, `test`, `build` | `OPENAI_API_KEY` for model-backed mode. |
49-
| `agent-console` | `typecheck` | Browser session plus one provider key to inspect live SSE telemetry. |
49+
| `agent-console` | `test`, `typecheck` | Action scanner fixture test; browser session plus one provider key to inspect live SSE telemetry. |
5050
| `app/capacitor` | Parent skip scripts plus backend/frontend package checks | Native Capacitor device/simulator testing and provider keys. |
5151
| `app/capacitor/backend` | `typecheck`, `test`, `build` | Provider key and device/simulator flow through the Capacitor shell. |
5252
| `app/capacitor/frontend` | `typecheck`, `build` | Browser and native WebView smoke test against a configured backend. |
5353
| `app/electron` | Parent skip scripts plus backend/frontend package checks | Desktop Electron launch and provider-key chat flow. |
5454
| `app/electron/backend` | `typecheck`, `test`, `build` | Provider-key chat flow from the packaged Electron shell. |
5555
| `app/electron/frontend` | `typecheck`, `build` | Renderer smoke test in Electron and browser dev-server mode. |
56-
| `autonomous` | `typecheck`, `build` | Optional local model and shell sandbox configuration. |
56+
| `autonomous` | `test`, `typecheck`, `build` | Decision parser, shell allowlist, and prompt tests; optional local model and shell sandbox configuration. |
5757
| `avatar` | `typecheck`, `build` | Browser microphone/audio flow, selected model key, optional ElevenLabs key. |
5858
| `aws` | `typecheck`, `test`, `build` | AWS account, SAM deployment, and Lambda invocation with `OPENAI_API_KEY`. |
5959
| `bluesky` | `typecheck`, `test`, `build` | `LIVE_TEST=true` with Bluesky credentials and dry-run/posting flags. |
6060
| `browser-extension` | Parent typecheck skip and documented Chrome/Safari package checks | Load unpacked Chrome extension; Safari requires Xcode signing/install. |
6161
| `browser-extension/chrome` | `typecheck`, explicit build skip | `build:tsup` only after resolving browser bundling of Node-only workspace deps; load unpacked for runtime validation. |
6262
| `browser-extension/safari` | Typecheck skip, scripted Safari build path | Xcode and Safari extension signing. |
63-
| `chat` | `typecheck`, `build` | One configured provider key for live chat. |
63+
| `chat` | `test`, `typecheck`, `build` | Provider-selection tests; one configured provider key for live chat. |
6464
| `cloud/clone-ur-crush` | `typecheck`, `test`, `build` | Live Next.js flow with required model/image provider keys. |
6565
| `cloud/edad` | `typecheck`, `test`, `build` | Manual server launch with Eliza Cloud app ID, affiliate code, and signed-in user token. |
6666
| `cloudflare` | `typecheck`, `test`, `build` | Wrangler login, Worker secret, deployed or local Worker endpoint. |
@@ -86,11 +86,11 @@ The local examples sweep has been run in this worktree with these outcomes:
8686
| `roblox` | `typecheck`, `test`, `build` | Roblox Studio place, Open Cloud key, tunnel/shared-secret bridge test. |
8787
| `smartglasses` | `typecheck`, `test` | Even Realities simulator or BLE hardware evidence report. |
8888
| `supabase` | Static review; no package scripts | Supabase CLI/Deno function serve or deployment with anon key and `OPENAI_API_KEY`. |
89-
| `telegram` | `typecheck`, `build` | Telegram bot token and provider key. |
90-
| `text-adventure` | `typecheck`, `build` | Optional manual CLI playthrough. |
89+
| `telegram` | `test`, `typecheck`, `build` | Env and character wiring tests; Telegram bot token and provider key for live run. |
90+
| `text-adventure` | `test`, `typecheck`, `build` | Deterministic no-LLM dungeon-engine playthrough; optional manual CLI playthrough requires `OPENAI_API_KEY`. |
9191
| `tic-tac-toe` | `typecheck`, `test`, `build` | Test runs the non-interactive bench mode. |
9292
| `trader` | `typecheck`, `build` | Paper-trading UI flow, then isolated-wallet live testing only when intended. |
93-
| `twitter-xai` | `typecheck`, `build` | X/xAI credentials; start with `TWITTER_DRY_RUN=true`. |
93+
| `twitter-xai` | `test`, `typecheck`, `build` | Credential-mode validation tests; X/xAI credentials for live run, starting with `TWITTER_DRY_RUN=true`. |
9494
| `vercel` | `typecheck`, `test`, `build` | Vercel project/env plus deployed or `vercel dev` API endpoint. |
9595

9696
## Not Yet Proven By Local Automation

packages/examples/agent-console/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ providers used here do not expose an embeddings endpoint.
3333
## Validate
3434

3535
```bash
36+
bun run test
3637
bun run typecheck
3738
```
3839

39-
The example has no external service test because it requires a live model
40-
provider key and an interactive browser session.
40+
The local test covers the action scanner against a fixture repository. A full
41+
dashboard session still requires a live model provider key and an interactive
42+
browser.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { expect, test } from "bun:test";
2+
import { execFileSync } from "node:child_process";
3+
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
4+
import { tmpdir } from "node:os";
5+
import { join } from "node:path";
6+
import { scanRepoActions } from "./action-scanner";
7+
8+
test("scanRepoActions discovers action metadata and subaction links", () => {
9+
const repoRoot = mkdtempSync(join(tmpdir(), "agent-console-scan-"));
10+
execFileSync("git", ["init"], { cwd: repoRoot, stdio: "ignore" });
11+
const fixtureDir = join(repoRoot, "packages", "fixture");
12+
mkdirSync(fixtureDir, { recursive: true });
13+
writeFileSync(
14+
join(fixtureDir, "actions.ts"),
15+
`
16+
const childAction = {
17+
name: "lookup_weather",
18+
description: "Look up weather for a city",
19+
parameters: [
20+
{
21+
name: "city",
22+
description: "City to query",
23+
required: true,
24+
schema: { type: "string" },
25+
},
26+
],
27+
validate: async () => true,
28+
handler: async () => undefined,
29+
};
30+
31+
export const parentAction = {
32+
name: "travel_plan",
33+
description: "Plan a trip",
34+
parameters: [
35+
{
36+
name: "mode",
37+
description: "Operation to perform",
38+
schema: { type: "string", enum: ["lookup_weather"] },
39+
},
40+
],
41+
subActions: ["lookup_weather"],
42+
validate: async (runtime) => Boolean(runtime),
43+
handler: async () => undefined,
44+
};
45+
`,
46+
);
47+
48+
const result = scanRepoActions({ repoRoot });
49+
const parent = result.actions.find((action) => action.name === "travel_plan");
50+
const child = result.actions.find(
51+
(action) => action.name === "lookup_weather",
52+
);
53+
54+
expect(result.filesScanned).toBe(1);
55+
expect(result.actionCount).toBe(2);
56+
expect(parent?.validation).toBe("conditional");
57+
expect(parent?.resolvedSubActions).toEqual([
58+
expect.objectContaining({
59+
found: true,
60+
name: "lookup_weather",
61+
targetId: child?.id,
62+
}),
63+
]);
64+
expect(parent?.inferredSubActions).toEqual([
65+
{
66+
name: "lookup_weather",
67+
parameter: "mode",
68+
source: "parameter-enum",
69+
},
70+
]);
71+
expect(child?.parameterSummary).toEqual(["city:string required"]);
72+
});

packages/examples/agent-console/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"scripts": {
88
"start": "bun run server.ts",
99
"dev": "bun --hot run server.ts",
10+
"test": "bun test action-scanner.test.ts",
1011
"typecheck": "tsc --noEmit -p tsconfig.json"
1112
},
1213
"dependencies": {

packages/examples/autonomous/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,13 @@ cd packages/examples/autonomous
5858
bun install
5959
bun run start
6060
```
61+
62+
## Validate
63+
64+
```bash
65+
bun run test
66+
bun run typecheck
67+
```
68+
69+
The test suite covers the local decision parser, shell-command allowlist, and
70+
prompt construction without starting local inference or shell services.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { expect, test } from "bun:test";
2+
import { decisionPrompt, isCommandAllowed, parseDecision } from "./autonomous";
3+
4+
test("parseDecision accepts valid JSON decisions and clamps sleep", () => {
5+
expect(
6+
parseDecision('{"action":"RUN","command":"ls","note":"inspect"}'),
7+
).toEqual({
8+
action: "RUN",
9+
command: "ls",
10+
note: "inspect",
11+
});
12+
expect(parseDecision('{"action":"SLEEP","sleepMs":1,"note":"wait"}')).toEqual(
13+
{
14+
action: "SLEEP",
15+
sleepMs: 100,
16+
note: "wait",
17+
},
18+
);
19+
expect(parseDecision('{"action":"STOP","note":"done"}')).toEqual({
20+
action: "STOP",
21+
note: "done",
22+
});
23+
});
24+
25+
test("parseDecision rejects incomplete or malformed decisions", () => {
26+
expect(parseDecision("not json")).toBeNull();
27+
expect(parseDecision('{"action":"RUN","note":"missing command"}')).toBeNull();
28+
expect(parseDecision('{"action":"SLEEP","sleepMs":"nope"}')).toBeNull();
29+
expect(parseDecision('{"action":"DELETE","note":"bad action"}')).toBeNull();
30+
});
31+
32+
test("isCommandAllowed blocks shell metacharacters and unknown commands", () => {
33+
const allowed = ["ls", "pwd", "cat", "echo"];
34+
35+
expect(isCommandAllowed("ls -la", allowed)).toBe(true);
36+
expect(isCommandAllowed(" echo hello ", allowed)).toBe(true);
37+
expect(isCommandAllowed("rm file", allowed)).toBe(false);
38+
expect(isCommandAllowed("ls && rm file", allowed)).toBe(false);
39+
expect(isCommandAllowed("cat file | grep x", allowed)).toBe(false);
40+
expect(isCommandAllowed("echo hello\npwd", allowed)).toBe(false);
41+
});
42+
43+
test("decisionPrompt includes sandbox, commands, goal, and history", () => {
44+
const prompt = decisionPrompt({
45+
goal: "Write STATUS.txt",
46+
allowedDirectory: "/tmp/sandbox",
47+
allowedCommands: ["ls", "echo"],
48+
recentSteps: "[step 1] SLEEP",
49+
});
50+
51+
expect(prompt).toContain("Write STATUS.txt");
52+
expect(prompt).toContain("/tmp/sandbox");
53+
expect(prompt).toContain("ls, echo");
54+
expect(prompt).toContain("[step 1] SLEEP");
55+
expect(prompt).toContain('"action": "RUN|SLEEP|STOP"');
56+
});

packages/examples/autonomous/autonomous.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,6 @@ import {
1717
} from "@elizaos/core";
1818
import { v4 as uuidv4 } from "uuid";
1919

20-
const { default: inmemorydbPlugin } = await import(
21-
"@elizaos/plugin-inmemorydb"
22-
);
23-
const { default: localInferencePlugin } = await import(
24-
"@elizaos/plugin-local-inference"
25-
);
26-
const { shellPlugin } = await import("@elizaos/plugin-shell");
27-
2820
type ShellService = {
2921
executeCommand(
3022
command: string,
@@ -109,7 +101,7 @@ function readStringField(
109101
return null;
110102
}
111103

112-
function parseDecision(raw: string): AgentDecision | null {
104+
export function parseDecision(raw: string): AgentDecision | null {
113105
const parsed = parseJSONObjectFromText(raw);
114106
if (!parsed) return null;
115107

@@ -147,7 +139,7 @@ function baseCommand(command: string): string {
147139
return space === -1 ? trimmed : trimmed.slice(0, space);
148140
}
149141

150-
function isCommandAllowed(
142+
export function isCommandAllowed(
151143
command: string,
152144
allowedBaseCommands: readonly string[],
153145
): boolean {
@@ -163,7 +155,7 @@ function isCommandAllowed(
163155
return allowedBaseCommands.includes(cmd);
164156
}
165157

166-
function decisionPrompt(params: {
158+
export function decisionPrompt(params: {
167159
goal: string;
168160
allowedDirectory: string;
169161
allowedCommands: readonly string[];
@@ -202,6 +194,14 @@ Rules:
202194
}
203195

204196
async function main(): Promise<void> {
197+
const { default: inmemorydbPlugin } = await import(
198+
"@elizaos/plugin-inmemorydb"
199+
);
200+
const { default: localInferencePlugin } = await import(
201+
"@elizaos/plugin-local-inference"
202+
);
203+
const { shellPlugin } = await import("@elizaos/plugin-shell");
204+
205205
const here = path.dirname(fileURLToPath(import.meta.url));
206206
const repoRoot = path.resolve(here, "..", "..", "..");
207207

@@ -477,4 +477,6 @@ async function main(): Promise<void> {
477477
await runtime.stop();
478478
}
479479

480-
await main();
480+
if (import.meta.main) {
481+
await main();
482+
}

packages/examples/autonomous/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"scripts": {
77
"start": "bun run autonomous.ts",
88
"dev": "bun --watch run autonomous.ts",
9+
"test": "bun test",
910
"lint": "bunx @biomejs/biome check --write --unsafe .",
1011
"typecheck": "tsc --noEmit",
1112
"build": "bun run typecheck"

packages/examples/autonomous/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"resolveJsonModule": true,
1111
"allowSyntheticDefaultImports": true,
1212
"forceConsistentCasingInFileNames": true,
13-
"types": ["node"],
13+
"types": ["node", "bun-types"],
1414
"paths": {
1515
"@elizaos/plugin-capacitor-bridge": [
1616
"../../../plugins/plugin-capacitor-bridge/src/index.ts"

packages/examples/chat/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ Run with hot reload:
8282
bun run dev
8383
```
8484

85+
Run the local provider-selection checks:
86+
```bash
87+
bun run test
88+
```
89+
8590
## License
8691

8792
MIT

0 commit comments

Comments
 (0)