mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-10-06 12:33:43 +08:00
fix: set bundled skill source to real filesystem path (#1310)
* fix: set bundled skill source to real filesystem path
Bundled public skills were sent to the Python agent-server with
`source: "public"`, which the SDK uses as the skill's `location` in
SkillKnowledge. Any skill whose SKILL.md references bundled resources
(scripts/, references/) is therefore unable to resolve those files —
the agent sees 'Skill location: public' instead of an actual path.
Fix: compute the absolute path to the skills directory inside the
`@openhands/extensions` node_modules package at Vite build/serve time
and inject it as `__EXTENSIONS_SKILLS_DIR__` via Vite's `define`.
`buildBundledSkills()` now sets `source` to:
`${__EXTENSIONS_SKILLS_DIR__}/${name}/SKILL.md`
Library builds receive an empty string so consumers are not bound to
this machine's node_modules path; the adapter falls back to 'public'
when the value is falsy.
Co-authored-by: openhands <openhands@all-hands.dev>
* fix: declare __EXTENSIONS_SKILLS_DIR__ global for TypeScript
Add missing 'declare const __EXTENSIONS_SKILLS_DIR__: string' to
src/react-app-env.d.ts so tsc can resolve the Vite-injected constant
used in buildBundledSkills().
Co-authored-by: openhands <openhands@all-hands.dev>
* test: assert skill source is absolute path ending in /<name>/SKILL.md
Replace the loose 'typeof === string' / toBeTruthy checks with two specific
assertions that directly verify the intent of buildBundledSkills():
- source starts with '/' (absolute path the agent-server can resolve)
- source ends with '/<name>/SKILL.md' (points to the right file)
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
committed by
GitHub
co-authored by
openhands
parent
5076bb3517
commit
bee6cee418
@@ -135,7 +135,13 @@ describe("buildStartConversationRequest", () => {
|
||||
for (const skill of skills) {
|
||||
expect(skill).toHaveProperty("name");
|
||||
expect(skill).toHaveProperty("content");
|
||||
expect(skill).toHaveProperty("source", "public");
|
||||
// source must be an absolute path to the skill's SKILL.md so the
|
||||
// Python agent-server can resolve bundled resources (scripts/, references/).
|
||||
const source = skill.source as string;
|
||||
expect(source).toMatch(/^\//);
|
||||
expect(source).toMatch(
|
||||
new RegExp(`/${skill.name as string}/SKILL\\.md$`),
|
||||
);
|
||||
expect(skill).toHaveProperty("is_agentskills_format", true);
|
||||
// trigger is either null (always-active) or { type, keywords }
|
||||
if (skill.trigger !== null) {
|
||||
|
||||
@@ -544,7 +544,7 @@ interface BundledSkill {
|
||||
name: string;
|
||||
content: string;
|
||||
trigger: { type: "keyword"; keywords: string[] } | null;
|
||||
source: "public";
|
||||
source: string;
|
||||
description: string | null;
|
||||
is_agentskills_format: true;
|
||||
license?: string;
|
||||
@@ -566,11 +566,18 @@ function buildBundledSkills(): BundledSkill[] {
|
||||
? { type: "keyword", keywords: entry.triggers }
|
||||
: null;
|
||||
|
||||
// Use the absolute path to the skill's SKILL.md so the Python
|
||||
// agent-server can resolve bundled resources (scripts/, references/).
|
||||
// Falls back to "public" in library builds where the path isn't known.
|
||||
const source = __EXTENSIONS_SKILLS_DIR__
|
||||
? `${__EXTENSIONS_SKILLS_DIR__}/${entry.name}/SKILL.md`
|
||||
: "public";
|
||||
|
||||
return {
|
||||
name: entry.name,
|
||||
content: entry.content,
|
||||
trigger,
|
||||
source: "public" as const,
|
||||
source,
|
||||
description: entry.description ?? null,
|
||||
is_agentskills_format: true as const,
|
||||
...(entry.license ? { license: entry.license } : {}),
|
||||
|
||||
Vendored
+5
@@ -1,5 +1,10 @@
|
||||
/// <reference types="react-scripts" />
|
||||
|
||||
// Injected by vite.config.ts `define` — absolute path to the
|
||||
// @openhands/extensions skills directory in node_modules, or an
|
||||
// empty string in library builds.
|
||||
declare const __EXTENSIONS_SKILLS_DIR__: string;
|
||||
|
||||
interface Window {
|
||||
posthog?: {
|
||||
capture: (event: string, properties?: Record<string, unknown>) => void;
|
||||
|
||||
Vendored
+7
@@ -1 +1,8 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
/**
|
||||
* Absolute filesystem path to the bundled extensions skills directory,
|
||||
* injected by Vite at build time via `define` in vite.config.ts.
|
||||
* Empty string in library builds; always a real path in app/dev builds.
|
||||
*/
|
||||
declare const __EXTENSIONS_SKILLS_DIR__: string;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/// <reference types="vitest" />
|
||||
/// <reference types="vite-plugin-svgr/client" />
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createRequire } from "node:module";
|
||||
import { resolve, dirname } from "node:path";
|
||||
import { defineConfig, loadEnv } from "vite";
|
||||
import svgr from "vite-plugin-svgr";
|
||||
import { reactRouter } from "@react-router/dev/vite";
|
||||
@@ -22,6 +24,16 @@ const LIB_EXTERNALS = [
|
||||
];
|
||||
const APP_CHUNK_MAX_BYTES = 450 * 1024;
|
||||
|
||||
// Absolute path to the bundled extensions skills directory in node_modules.
|
||||
// Injected as __EXTENSIONS_SKILLS_DIR__ so agent-server-adapter.ts can pass
|
||||
// real filesystem paths to the Python agent-server (which uses them to
|
||||
// resolve bundled skill resources like scripts/ and references/).
|
||||
const _require = createRequire(import.meta.url);
|
||||
const EXTENSIONS_SKILLS_DIR = resolve(
|
||||
dirname(_require.resolve("@openhands/extensions/package.json")),
|
||||
"skills",
|
||||
);
|
||||
|
||||
const appBuildConfig = {
|
||||
rolldownOptions: {
|
||||
output: {
|
||||
@@ -59,6 +71,14 @@ export default defineConfig(({ mode }) => {
|
||||
const FE_PORT = Number.parseInt(VITE_FRONTEND_PORT, 10);
|
||||
|
||||
return {
|
||||
define: {
|
||||
// Empty string for library builds so consumers aren't bound to this
|
||||
// machine's node_modules path; agent-server-adapter falls back to
|
||||
// "public" when the value is falsy.
|
||||
__EXTENSIONS_SKILLS_DIR__: JSON.stringify(
|
||||
isLibraryBuild ? "" : EXTENSIONS_SKILLS_DIR,
|
||||
),
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
name: "suppress-chrome-devtools-well-known",
|
||||
|
||||
Reference in New Issue
Block a user