Merge pull request #735 from pikasTech/fix/issue734-prompt-assembly-panel
feat: skills 面板展示引导 prompt 装配
This commit is contained in:
@@ -23,6 +23,7 @@ export function createHwlabAgentRunAssemblySummary(options = {}) {
|
|||||||
const toolAliases = normalizeResourceToolAliases(options.toolAliases ?? DEFAULT_HWLAB_AGENTRUN_TOOL_ALIASES);
|
const toolAliases = normalizeResourceToolAliases(options.toolAliases ?? DEFAULT_HWLAB_AGENTRUN_TOOL_ALIASES);
|
||||||
const promptRefs = normalizeResourcePromptRefs(options.promptRefs ?? DEFAULT_HWLAB_AGENTRUN_PROMPT_REFS);
|
const promptRefs = normalizeResourcePromptRefs(options.promptRefs ?? DEFAULT_HWLAB_AGENTRUN_PROMPT_REFS);
|
||||||
const skillRefs = normalizeResourceSkillRefs(options.skillRefs ?? DEFAULT_HWLAB_AGENTRUN_SKILL_REFS);
|
const skillRefs = normalizeResourceSkillRefs(options.skillRefs ?? DEFAULT_HWLAB_AGENTRUN_SKILL_REFS);
|
||||||
|
const promptAssembly = createPromptAssemblySummary(promptRefs);
|
||||||
const repoUrl = optionalString(options.repoUrl ?? env.HWLAB_CODE_AGENT_AGENTRUN_REPO_URL ?? env.AGENTRUN_REPO_URL) ?? DEFAULT_HWLAB_AGENTRUN_REPO_URL;
|
const repoUrl = optionalString(options.repoUrl ?? env.HWLAB_CODE_AGENT_AGENTRUN_REPO_URL ?? env.AGENTRUN_REPO_URL) ?? DEFAULT_HWLAB_AGENTRUN_REPO_URL;
|
||||||
const branch = optionalString(options.branch ?? env.HWLAB_BOOT_REF ?? env.HWLAB_CODE_AGENT_AGENTRUN_BRANCH ?? env.AGENTRUN_BRANCH) ?? DEFAULT_HWLAB_AGENTRUN_BRANCH;
|
const branch = optionalString(options.branch ?? env.HWLAB_BOOT_REF ?? env.HWLAB_CODE_AGENT_AGENTRUN_BRANCH ?? env.AGENTRUN_BRANCH) ?? DEFAULT_HWLAB_AGENTRUN_BRANCH;
|
||||||
const commitId = optionalString(options.commitId ?? env.HWLAB_COMMIT_ID ?? env.HWLAB_GIT_SHA ?? env.HWLAB_REVISION);
|
const commitId = optionalString(options.commitId ?? env.HWLAB_COMMIT_ID ?? env.HWLAB_GIT_SHA ?? env.HWLAB_REVISION);
|
||||||
@@ -47,6 +48,7 @@ export function createHwlabAgentRunAssemblySummary(options = {}) {
|
|||||||
},
|
},
|
||||||
toolAliases: toolAliases.map((item) => ({ ...item })),
|
toolAliases: toolAliases.map((item) => ({ ...item })),
|
||||||
promptRefs: promptRefs.map((item) => ({ ...item })),
|
promptRefs: promptRefs.map((item) => ({ ...item })),
|
||||||
|
promptAssembly,
|
||||||
skillRefs: skillRefs.map((item) => ({ ...item })),
|
skillRefs: skillRefs.map((item) => ({ ...item })),
|
||||||
runtimeEnv: {
|
runtimeEnv: {
|
||||||
assembledRuntime: "HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME",
|
assembledRuntime: "HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME",
|
||||||
@@ -56,6 +58,30 @@ export function createHwlabAgentRunAssemblySummary(options = {}) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createPromptAssemblySummary(promptRefs) {
|
||||||
|
const refs = promptRefs.map((item) => ({
|
||||||
|
name: item.name,
|
||||||
|
path: item.path,
|
||||||
|
inject: item.inject,
|
||||||
|
stage: promptInjectStageLabel(item.inject),
|
||||||
|
required: item.required !== false,
|
||||||
|
source: "ResourceBundleRef.promptRefs"
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
status: refs.length > 0 ? "ready" : "missing",
|
||||||
|
count: refs.length,
|
||||||
|
requiredCount: refs.filter((item) => item.required).length,
|
||||||
|
injectStages: [...new Set(refs.map((item) => item.inject))],
|
||||||
|
refs,
|
||||||
|
valuesPrinted: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function promptInjectStageLabel(inject) {
|
||||||
|
if (inject === "thread-start") return "thread-start initial prompt";
|
||||||
|
return inject;
|
||||||
|
}
|
||||||
|
|
||||||
export const AGENTRUN_REUSABLE_CREDENTIAL_ENV_NAMES = Object.freeze([
|
export const AGENTRUN_REUSABLE_CREDENTIAL_ENV_NAMES = Object.freeze([
|
||||||
"AUTH_PASSWORD",
|
"AUTH_PASSWORD",
|
||||||
"CODEX_API_KEY",
|
"CODEX_API_KEY",
|
||||||
|
|||||||
@@ -61,6 +61,18 @@ test("cloud api skills upload/list/tree/preview keeps preinstalled and uploaded
|
|||||||
assert.equal(list.body.agentRunAssembly.resourceBundle.skillsDir, ".agents/skills");
|
assert.equal(list.body.agentRunAssembly.resourceBundle.skillsDir, ".agents/skills");
|
||||||
assert.deepEqual(list.body.agentRunAssembly.skillRefs.map((item) => item.name), ["device-pod-cli", "hwlab-agent-runtime"]);
|
assert.deepEqual(list.body.agentRunAssembly.skillRefs.map((item) => item.name), ["device-pod-cli", "hwlab-agent-runtime"]);
|
||||||
assert.deepEqual(list.body.agentRunAssembly.promptRefs.map((item) => item.name), ["hwlab-v02-runtime"]);
|
assert.deepEqual(list.body.agentRunAssembly.promptRefs.map((item) => item.name), ["hwlab-v02-runtime"]);
|
||||||
|
assert.equal(list.body.agentRunAssembly.promptAssembly.status, "ready");
|
||||||
|
assert.equal(list.body.agentRunAssembly.promptAssembly.valuesPrinted, false);
|
||||||
|
assert.equal(list.body.agentRunAssembly.promptAssembly.requiredCount, 1);
|
||||||
|
assert.deepEqual(list.body.agentRunAssembly.promptAssembly.injectStages, ["thread-start"]);
|
||||||
|
assert.deepEqual(list.body.agentRunAssembly.promptAssembly.refs, [{
|
||||||
|
name: "hwlab-v02-runtime",
|
||||||
|
path: "internal/agent/prompts/hwlab-v02-runtime.md",
|
||||||
|
inject: "thread-start",
|
||||||
|
stage: "thread-start initial prompt",
|
||||||
|
required: true,
|
||||||
|
source: "ResourceBundleRef.promptRefs"
|
||||||
|
}]);
|
||||||
assert.deepEqual(list.body.agentRunAssembly.toolAliases.map((item) => item.name), ["hwpod", "unidesk-ssh"]);
|
assert.deepEqual(list.body.agentRunAssembly.toolAliases.map((item) => item.name), ["hwpod", "unidesk-ssh"]);
|
||||||
const sameName = list.body.skills.filter((skill) => skill.name === "same-name");
|
const sameName = list.body.skills.filter((skill) => skill.name === "same-name");
|
||||||
assert.equal(sameName.length, 2);
|
assert.equal(sameName.length, 2);
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ function renderAgentRunAssembly() {
|
|||||||
const skillRefs = safeAssemblyItems(assembly.skillRefs);
|
const skillRefs = safeAssemblyItems(assembly.skillRefs);
|
||||||
const toolAliases = safeAssemblyItems(assembly.toolAliases);
|
const toolAliases = safeAssemblyItems(assembly.toolAliases);
|
||||||
const promptRefs = safeAssemblyItems(assembly.promptRefs);
|
const promptRefs = safeAssemblyItems(assembly.promptRefs);
|
||||||
|
const promptAssembly = safePromptAssembly(assembly.promptAssembly, promptRefs);
|
||||||
const wrap = document.createElement("div");
|
const wrap = document.createElement("div");
|
||||||
wrap.className = `skill-assembly-body tone-border-${toneClass(assembly.status || "source")}`;
|
wrap.className = `skill-assembly-body tone-border-${toneClass(assembly.status || "source")}`;
|
||||||
const head = document.createElement("div");
|
const head = document.createElement("div");
|
||||||
@@ -200,13 +201,91 @@ function renderAgentRunAssembly() {
|
|||||||
const detail = document.createElement("div");
|
const detail = document.createElement("div");
|
||||||
detail.className = "skill-meta";
|
detail.className = "skill-meta";
|
||||||
detail.append(
|
detail.append(
|
||||||
textSpan(`promptRefs=${promptRefs.map((item) => item.name).join(",") || "none"}`, "mono wrap"),
|
|
||||||
textSpan(`toolAliases=${toolAliases.map((item) => item.name).join(",") || "none"}`, "mono wrap")
|
textSpan(`toolAliases=${toolAliases.map((item) => item.name).join(",") || "none"}`, "mono wrap")
|
||||||
);
|
);
|
||||||
wrap.append(head, meta, skills, detail);
|
wrap.append(head, meta, skills, renderPromptAssembly(promptAssembly), detail);
|
||||||
replaceChildren(el.skillAssembly, wrap);
|
replaceChildren(el.skillAssembly, wrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderPromptAssembly(promptAssembly) {
|
||||||
|
const refs = promptAssembly.refs;
|
||||||
|
const section = document.createElement("div");
|
||||||
|
section.className = "skill-prompt-assembly";
|
||||||
|
const head = document.createElement("div");
|
||||||
|
head.className = "skill-prompt-head";
|
||||||
|
head.append(textSpan("引导 prompt 装配", "skill-prompt-title"), badge(promptAssembly.status || "unknown", promptAssembly.status || "source"));
|
||||||
|
const meta = document.createElement("div");
|
||||||
|
meta.className = "skill-prompt-meta";
|
||||||
|
meta.append(
|
||||||
|
textSpan(`refs=${Number(promptAssembly.count) || refs.length}`),
|
||||||
|
textSpan(`required=${Number(promptAssembly.requiredCount) || refs.filter((item) => item.required).length}`),
|
||||||
|
textSpan(`inject=${promptAssembly.injectStages.join(",") || "none"}`, "mono wrap")
|
||||||
|
);
|
||||||
|
const list = document.createElement("div");
|
||||||
|
list.className = "skill-prompt-list";
|
||||||
|
if (refs.length === 0) {
|
||||||
|
list.append(textSpan("未声明引导 prompt refs。", "skill-prompt-empty"));
|
||||||
|
} else {
|
||||||
|
list.append(...refs.map(renderPromptAssemblyRef));
|
||||||
|
}
|
||||||
|
section.append(head, meta, list);
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPromptAssemblyRef(item) {
|
||||||
|
const row = document.createElement("div");
|
||||||
|
row.className = "skill-prompt-row";
|
||||||
|
const head = document.createElement("div");
|
||||||
|
head.className = "skill-prompt-row-head";
|
||||||
|
head.append(textSpan(item.name, "skill-prompt-name mono wrap"), badge(item.required ? "required" : "optional", item.required ? "source" : "pending"));
|
||||||
|
const meta = document.createElement("div");
|
||||||
|
meta.className = "skill-prompt-meta";
|
||||||
|
meta.append(
|
||||||
|
textSpan(`path=${item.path}`, "mono wrap"),
|
||||||
|
textSpan(`inject=${item.inject}`, "mono wrap"),
|
||||||
|
textSpan(`stage=${item.stage}`, "mono wrap"),
|
||||||
|
textSpan(`source=${item.source}`, "mono wrap")
|
||||||
|
);
|
||||||
|
row.append(head, meta);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
function safePromptAssembly(promptAssembly, fallbackPromptRefs) {
|
||||||
|
const fallbackRefs = fallbackPromptRefs.map((item) => ({
|
||||||
|
...item,
|
||||||
|
path: String(item.path || "unknown"),
|
||||||
|
inject: String(item.inject || "thread-start"),
|
||||||
|
stage: String(item.stage || "thread-start initial prompt"),
|
||||||
|
required: item.required !== false,
|
||||||
|
source: String(item.source || "ResourceBundleRef.promptRefs")
|
||||||
|
}));
|
||||||
|
if (!promptAssembly || typeof promptAssembly !== "object") {
|
||||||
|
return createSafePromptAssembly(fallbackRefs);
|
||||||
|
}
|
||||||
|
const refs = safeAssemblyItems(promptAssembly.refs).map((item) => ({
|
||||||
|
...item,
|
||||||
|
path: String(item.path || "unknown"),
|
||||||
|
inject: String(item.inject || "thread-start"),
|
||||||
|
stage: String(item.stage || "thread-start initial prompt"),
|
||||||
|
required: item.required !== false,
|
||||||
|
source: String(item.source || "ResourceBundleRef.promptRefs")
|
||||||
|
}));
|
||||||
|
return createSafePromptAssembly(refs, promptAssembly);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSafePromptAssembly(refs, promptAssembly = {}) {
|
||||||
|
const injectStages = Array.isArray(promptAssembly.injectStages)
|
||||||
|
? promptAssembly.injectStages.map((item) => String(item)).filter(Boolean)
|
||||||
|
: [...new Set(refs.map((item) => item.inject).filter(Boolean))];
|
||||||
|
return {
|
||||||
|
status: String(promptAssembly.status || (refs.length > 0 ? "ready" : "missing")),
|
||||||
|
count: Number(promptAssembly.count) || refs.length,
|
||||||
|
requiredCount: Number(promptAssembly.requiredCount) || refs.filter((item) => item.required).length,
|
||||||
|
injectStages,
|
||||||
|
refs
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function safeAssemblyItems(items) {
|
function safeAssemblyItems(items) {
|
||||||
return Array.isArray(items)
|
return Array.isArray(items)
|
||||||
? items.filter((item) => item && typeof item === "object" && item.name).map((item) => ({ ...item, name: String(item.name) }))
|
? items.filter((item) => item && typeof item === "object" && item.name).map((item) => ({ ...item, name: String(item.name) }))
|
||||||
|
|||||||
@@ -73,6 +73,11 @@ assert.match(app, /function\s+initSkillsPanel\s*\(/u, "missing initSkillsPanel")
|
|||||||
assert.match(app, /function\s+loadSkillsSurface\s*\(/u, "missing loadSkillsSurface");
|
assert.match(app, /function\s+loadSkillsSurface\s*\(/u, "missing loadSkillsSurface");
|
||||||
assert.match(app, /function\s+uploadSelectedSkillFiles\s*\(/u, "missing uploadSelectedSkillFiles");
|
assert.match(app, /function\s+uploadSelectedSkillFiles\s*\(/u, "missing uploadSelectedSkillFiles");
|
||||||
assert.match(app, /function\s+renderSkillPreview\s*\(/u, "missing renderSkillPreview");
|
assert.match(app, /function\s+renderSkillPreview\s*\(/u, "missing renderSkillPreview");
|
||||||
|
assertIncludes(app, "promptAssembly", "skills frontend must render AgentRun prompt assembly summary");
|
||||||
|
assertIncludes(app, "引导 prompt 装配", "skills frontend must label the prompt assembly panel");
|
||||||
|
assertIncludes(app, "ResourceBundleRef.promptRefs", "skills frontend must expose prompt assembly source metadata");
|
||||||
|
assertIncludes(styles, ".skill-prompt-assembly", "skills frontend must style prompt assembly rows");
|
||||||
|
assertIncludes(styles, ".skill-prompt-row", "skills frontend must style prompt assembly refs");
|
||||||
console.log("hwlab-cloud-web check: skills contracts verified");
|
console.log("hwlab-cloud-web check: skills contracts verified");
|
||||||
|
|
||||||
for (const term of [
|
for (const term of [
|
||||||
|
|||||||
@@ -571,6 +571,72 @@ h3 {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.skill-prompt-assembly {
|
||||||
|
min-width: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
padding-top: 7px;
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-prompt-head,
|
||||||
|
.skill-prompt-row-head,
|
||||||
|
.skill-prompt-meta {
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-prompt-head,
|
||||||
|
.skill-prompt-row-head {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-prompt-list {
|
||||||
|
min-width: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-prompt-row {
|
||||||
|
min-width: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 7px 0;
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-prompt-row:first-child {
|
||||||
|
border-top: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-prompt-title,
|
||||||
|
.skill-prompt-name,
|
||||||
|
.skill-prompt-empty,
|
||||||
|
.skill-prompt-meta {
|
||||||
|
min-width: 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-prompt-title,
|
||||||
|
.skill-prompt-name {
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-prompt-empty,
|
||||||
|
.skill-prompt-meta {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-prompt-meta {
|
||||||
|
font-family: var(--mono);
|
||||||
|
}
|
||||||
|
|
||||||
.skill-row,
|
.skill-row,
|
||||||
.skill-notice,
|
.skill-notice,
|
||||||
.skill-tree-entry {
|
.skill-tree-entry {
|
||||||
|
|||||||
Reference in New Issue
Block a user