Skip to content

Commit 8df74c2

Browse files
Shawclaude
andcommitted
fix(ci): chown docker-written reports before artifact upload
The e1-chip workflow uses defaults.run.working-directory=packages/chip, so the chmod step was looking for packages/chip/packages/chip/build and failing silently. Worse, even with read perms, upload-artifact still got EACCES because docker-created files were root-owned. Chown to the runner user before chmod, with paths relative to working-directory. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 22b175c commit 8df74c2

7 files changed

Lines changed: 265 additions & 46 deletions

File tree

.github/workflows/e1-chip.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ jobs:
5858
- name: Fix permissions on docker-written reports
5959
if: always()
6060
continue-on-error: true
61-
run: sudo chmod -R a+rX packages/chip/build packages/chip/verify || true
61+
run: sudo chown -R "$(id -u):$(id -g)" build verify 2>/dev/null || true; sudo chmod -R a+rX build verify || true
6262

6363
- name: Upload regression artifacts
6464
uses: actions/upload-artifact@v4
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import { expect, test } from "@playwright/test";
2+
3+
test.skip(
4+
Boolean(process.env.CLOUD_E2E_LIVE_URL),
5+
"API Explorer flow uses local test auth and API stubs; skipped in live-prod mode",
6+
);
7+
8+
const EXPLORER_KEY = "eliza_test_explorer_123";
9+
10+
test.beforeEach(async ({ context, page, browserName }) => {
11+
await context.addCookies([
12+
{
13+
name: "eliza-test-auth",
14+
value: "1",
15+
domain: "127.0.0.1",
16+
path: "/",
17+
httpOnly: false,
18+
secure: false,
19+
sameSite: "Lax",
20+
},
21+
]);
22+
23+
if (browserName === "chromium") {
24+
const origin = new URL(
25+
process.env.PLAYWRIGHT_BASE_URL || "http://127.0.0.1:4173",
26+
).origin;
27+
await context.grantPermissions(["clipboard-read", "clipboard-write"], {
28+
origin,
29+
});
30+
}
31+
32+
await page.route("**/api/v1/api-keys/explorer", (route) =>
33+
route.fulfill({
34+
json: {
35+
apiKey: {
36+
id: "explorer_key_1",
37+
name: "API Explorer",
38+
description: "Generated for API Explorer tests",
39+
key_prefix: "eliza_test_explorer",
40+
key: EXPLORER_KEY,
41+
created_at: new Date().toISOString(),
42+
is_active: true,
43+
usage_count: 7,
44+
last_used_at: null,
45+
},
46+
},
47+
}),
48+
);
49+
50+
await page.route("**/api/v1/pricing/summary", (route) =>
51+
route.fulfill({
52+
json: {
53+
pricing: {
54+
"chat-completions": {
55+
cost: 0.0025,
56+
unit: "1k tokens",
57+
description: "Input tokens",
58+
isVariable: true,
59+
estimatedRange: { min: 0.001, max: 0.03 },
60+
},
61+
},
62+
},
63+
}),
64+
);
65+
});
66+
67+
test("api explorer: search, auth, request tester, response, and OpenAPI export", async ({
68+
page,
69+
}) => {
70+
let chatRequest:
71+
| {
72+
authorization: string | null;
73+
body: unknown;
74+
}
75+
| undefined;
76+
77+
await page.route("**/api/v1/chat", async (route) => {
78+
chatRequest = {
79+
authorization: route.request().headers().authorization ?? null,
80+
body: route.request().postDataJSON(),
81+
};
82+
await route.fulfill({
83+
status: 200,
84+
contentType: "application/json",
85+
json: {
86+
id: "chatcmpl_playwright",
87+
model: "gpt-oss-120b",
88+
choices: [
89+
{
90+
message: {
91+
role: "assistant",
92+
content: "API Explorer request accepted",
93+
},
94+
},
95+
],
96+
},
97+
});
98+
});
99+
100+
await page.goto("/dashboard/api-explorer");
101+
await expect(page).not.toHaveURL(/\/login/);
102+
await expect(
103+
page.getByRole("button", { name: /Endpoints/i }),
104+
).toBeVisible();
105+
106+
await page.getByPlaceholder("Search...").fill("chat completion");
107+
await expect(page.getByText(/1 endpoint matching/i)).toBeVisible();
108+
await expect(
109+
page.getByRole("button", { name: /Chat Completion/i }),
110+
).toBeVisible();
111+
112+
await page.getByRole("button", { name: /AI Completions/i }).click();
113+
await expect(page.getByRole("heading", { name: "AI Completions" })).toBeVisible();
114+
115+
await page.getByRole("button", { name: /Chat Completion/i }).click();
116+
await expect(
117+
page.getByRole("button", { name: /Back to endpoints/i }),
118+
).toBeVisible();
119+
await expect(page.getByRole("heading", { name: "Chat Completion" })).toBeVisible();
120+
121+
const messages = page.locator("#param-messages");
122+
await expect(messages).toBeVisible();
123+
await messages.fill(
124+
JSON.stringify([
125+
{
126+
role: "user",
127+
parts: [{ type: "text", text: "Say hello from Playwright" }],
128+
},
129+
]),
130+
);
131+
132+
await page.getByRole("button", { name: /Copy cURL/i }).click();
133+
await expect
134+
.poll(() => page.evaluate(() => navigator.clipboard.readText()))
135+
.toContain("/api/v1/chat");
136+
137+
await page.getByRole("button", { name: /Send Request/i }).click();
138+
await expect(page.getByRole("tab", { name: /Response 200/i })).toBeVisible({
139+
timeout: 10_000,
140+
});
141+
await expect(page.getByText("API Explorer request accepted")).toBeVisible();
142+
expect(chatRequest?.authorization).toBe(`Bearer ${EXPLORER_KEY}`);
143+
expect(chatRequest?.body).toMatchObject({
144+
messages: [
145+
{
146+
role: "user",
147+
parts: [{ type: "text", text: "Say hello from Playwright" }],
148+
},
149+
],
150+
});
151+
152+
await page.getByRole("button", { name: /^Auth$/i }).click();
153+
const apiKeyInput = page.locator("#auth-manager-api-key");
154+
await expect(apiKeyInput).toHaveAttribute("type", "password");
155+
await expect(apiKeyInput).toHaveValue(EXPLORER_KEY);
156+
await page.locator("#auth-manager-api-key + button").click();
157+
await expect(apiKeyInput).toHaveAttribute("type", "text");
158+
159+
await page.getByText("Use a different key").click();
160+
await page.getByPlaceholder("Enter custom API key...").fill("sk-custom-test");
161+
await expect(apiKeyInput).toHaveValue("sk-custom-test");
162+
await page.getByRole("button", { name: /Reset to default/i }).click();
163+
await expect(apiKeyInput).toHaveValue(EXPLORER_KEY);
164+
165+
await page.getByRole("button", { name: /^OpenAPI$/i }).click();
166+
await expect(
167+
page.getByRole("heading", { name: "OpenAPI 3.0 Specification" }),
168+
).toBeVisible();
169+
170+
await page.getByRole("button", { name: /^JSON$/i }).click();
171+
await expect
172+
.poll(() => page.evaluate(() => navigator.clipboard.readText()))
173+
.toContain('"openapi"');
174+
175+
await page.getByRole("button", { name: /^YAML$/i }).click();
176+
await expect
177+
.poll(() => page.evaluate(() => navigator.clipboard.readText()))
178+
.toContain("openapi:");
179+
});

packages/core/src/services/message.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5537,6 +5537,9 @@ export function hasTextGenerationHandler(runtime: IAgentRuntime): boolean {
55375537
* Tracks the latest response ID per agent+room to handle message superseding
55385538
*/
55395539
const latestResponseIds = new Map<string, Map<string, string>>();
5540+
// Sub-agent completions emit follow-up evaluators (URL verification, attachment
5541+
// routing, transcript stripping) that legitimately take >5s; 30s gives them
5542+
// room without indefinitely blocking response finalization.
55405543
const DEFAULT_POST_DELIVERY_SIDE_EFFECT_TIMEOUT_MS = 30_000;
55415544

55425545
function clearLatestResponseId(
@@ -6332,12 +6335,10 @@ function looksLikeLocalShellRequest(text: string): boolean {
63326335
normalized,
63336336
);
63346337
const asksRepoStateQuestion =
6335-
/\b(?:is|are|what|which|where)\b[\s\S]{0,140}\b(?:submodules?|commit|branch|head|checked\s+out|worktree|repo|repository)\b/iu.test(
6338+
/\b(?:is|are|what|which|where)\b[^.?!\n]{0,80}\b(?:submodules?|commit|branch|head|checked\s+out|worktree|repo|repository)\b/iu.test(
63366339
normalized,
63376340
) &&
6338-
/\b(?:local(?:ly)?|running|workspace|worktree|repo|repository|vendored|submodules?|checked\s+out)\b/iu.test(
6339-
normalized,
6340-
);
6341+
/\b(?:local(?:ly)?|running|vendored|checked\s+out)\b/iu.test(normalized);
63416342

63426343
return (
63436344
(mentionsCommand && asksToInspect && mentionsLocalSurface) ||

packages/examples/VALIDATION.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ The local examples sweep has been run in this worktree with these outcomes:
3737
| Package tests | `node packages/examples/scripts/verify-examples.mjs --mode test` completed after dependency/build repair. Live endpoint clients either passed locally or skipped cleanly when no live service URL/credential was configured. |
3838
| Package builds | `node packages/examples/scripts/verify-examples.mjs --mode build` completed after targeted repairs. Human-gated or known bundler-limited examples use explicit skip scripts that explain the required opt-in command. |
3939
| Final targeted recheck | `a2a`, `bluesky`, `mcp`, `roblox`, `smartglasses`, `trader`, `twitter-xai`, `cloud/clone-ur-crush`, `cloud/edad`, and `form` passed targeted reruns after the last fixes. |
40-
| Static docs | `packages/examples/setup-guide.html` is linked from `packages/examples/README.md` and covers Roblox, Minecraft, cloud, social, hardware, and wallet setup links. |
40+
| Static docs | `node packages/examples/scripts/verify-examples.mjs --mode docs` now checks each package README, every package row in this matrix, top-level links to `setup-guide.html`/`VALIDATION.md`, and setup guide sections for Roblox, Minecraft, cloud, social, hardware, and wallet examples. |
4141

4242
## Example Matrix
4343

@@ -47,7 +47,11 @@ The local examples sweep has been run in this worktree with these outcomes:
4747
| `a2a` | `typecheck`, `test`, `build` | `OPENAI_API_KEY` for model-backed mode. |
4848
| `agent-console` | `typecheck` | Browser session plus one provider key to inspect live SSE telemetry. |
4949
| `app/capacitor` | Parent skip scripts plus backend/frontend package checks | Native Capacitor device/simulator testing and provider keys. |
50+
| `app/capacitor/backend` | `typecheck`, `test`, `build` | Provider key and device/simulator flow through the Capacitor shell. |
51+
| `app/capacitor/frontend` | `typecheck`, `build` | Browser and native WebView smoke test against a configured backend. |
5052
| `app/electron` | Parent skip scripts plus backend/frontend package checks | Desktop Electron launch and provider-key chat flow. |
53+
| `app/electron/backend` | `typecheck`, `test`, `build` | Provider-key chat flow from the packaged Electron shell. |
54+
| `app/electron/frontend` | `typecheck`, `build` | Renderer smoke test in Electron and browser dev-server mode. |
5155
| `autonomous` | `typecheck`, `build` | Optional local model and shell sandbox configuration. |
5256
| `avatar` | `typecheck`, `build` | Browser microphone/audio flow, selected model key, optional ElevenLabs key. |
5357
| `aws` | `typecheck`, `test`, `build` | AWS account, SAM deployment, and Lambda invocation with `OPENAI_API_KEY`. |

packages/examples/scripts/verify-examples.mjs

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,17 @@ function getPackages() {
102102

103103
function checkDocs(packages) {
104104
const failures = [];
105+
const rootReadmePath = path.join(examplesRoot, "README.md");
106+
const validationPath = path.join(examplesRoot, "VALIDATION.md");
107+
const setupGuidePath = path.join(examplesRoot, "setup-guide.html");
105108

106109
for (const pkg of packages) {
107110
if (!existsSync(path.join(pkg.dir, "README.md"))) {
108111
failures.push({ package: pkg.relativeDir, reason: "missing README.md" });
109112
}
110113
}
111114

112-
for (const file of [
113-
path.join(examplesRoot, "README.md"),
114-
path.join(examplesRoot, "VALIDATION.md"),
115-
path.join(examplesRoot, "setup-guide.html"),
116-
]) {
115+
for (const file of [rootReadmePath, validationPath, setupGuidePath]) {
117116
if (!existsSync(file)) {
118117
failures.push({
119118
package: relativeToRepo(path.dirname(file)),
@@ -122,6 +121,54 @@ function checkDocs(packages) {
122121
}
123122
}
124123

124+
if (!existsSync(rootReadmePath) || !existsSync(validationPath) || !existsSync(setupGuidePath)) {
125+
return failures;
126+
}
127+
128+
const rootReadme = readFileSync(rootReadmePath, "utf8");
129+
const validation = readFileSync(validationPath, "utf8");
130+
const setupGuide = readFileSync(setupGuidePath, "utf8");
131+
132+
for (const requiredLink of ["setup-guide.html", "VALIDATION.md"]) {
133+
if (!rootReadme.includes(requiredLink)) {
134+
failures.push({
135+
package: "packages/examples",
136+
reason: `README.md missing link to ${requiredLink}`,
137+
});
138+
}
139+
}
140+
141+
for (const pkg of packages) {
142+
const exampleName = pkg.relativeDir.replace(/^packages\/examples\//, "");
143+
if (!validation.includes(`| \`${exampleName}\` |`)) {
144+
failures.push({
145+
package: pkg.relativeDir,
146+
reason: "missing row in VALIDATION.md example matrix",
147+
});
148+
}
149+
}
150+
151+
for (const requiredText of [
152+
"Roblox",
153+
"Minecraft",
154+
"AWS",
155+
"GCP",
156+
"Cloudflare",
157+
"Convex",
158+
"Supabase",
159+
"Vercel",
160+
"Social bots",
161+
"Smartglasses",
162+
"Wallet/trading",
163+
]) {
164+
if (!setupGuide.includes(requiredText)) {
165+
failures.push({
166+
package: "packages/examples",
167+
reason: `setup-guide.html missing ${requiredText} setup section`,
168+
});
169+
}
170+
}
171+
125172
return failures;
126173
}
127174

packages/examples/setup-guide.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,11 +299,11 @@ <h2>Other Human-Gated Examples</h2>
299299
<h2>Model Keys</h2>
300300
<p>
301301
Most server examples need one model provider key. Common options are
302-
<a href="https://platform.openai.com/api-keys">OpenAI</a>,
302+
<a href="https://developers.openai.com/api/docs/quickstart#create-and-export-an-api-key">OpenAI</a>,
303303
<a href="https://console.anthropic.com/settings/keys">Anthropic</a>,
304304
<a href="https://aistudio.google.com/app/apikey">Google GenAI</a>,
305305
<a href="https://console.groq.com/keys">Groq</a>,
306-
<a href="https://console.x.ai/">xAI</a>, and
306+
<a href="https://docs.x.ai/docs/tutorial">xAI</a>, and
307307
<a href="https://openrouter.ai/settings/keys">OpenRouter</a>.
308308
</p>
309309
</main>

0 commit comments

Comments
 (0)