切换 AgentRun 资源装配到 gitbundle

This commit is contained in:
Codex Agent
2026-06-08 11:25:54 +08:00
parent c52ba0dac2
commit 9d76230bc3
17 changed files with 152 additions and 239 deletions
+29 -55
View File
@@ -6,26 +6,18 @@ export const DEFAULT_HWLAB_AGENTRUN_REPO_URL = "http://git-mirror-http.devops-in
export const DEFAULT_HWLAB_AGENTRUN_BRANCH = "v0.2";
export const DEFAULT_HWLAB_AGENTRUN_BACKEND_PROFILE = "deepseek";
export const DEFAULT_UNIDESK_MAIN_SERVER_ENV = "UNIDESK_MAIN_SERVER_IP";
export const DEFAULT_HWLAB_AGENTRUN_TOOL_ALIASES = Object.freeze([
{ name: "hwpod", path: "tools/hwpod-cli.ts", kind: "bun-script" },
{ name: "hwpod-ctl", path: "tools/hwpod-ctl.ts", kind: "bun-script" },
{ name: "hwpod-compiler", path: "tools/hwpod-compiler-cli.ts", kind: "bun-script" },
{ name: "unidesk-ssh", path: "tools/unidesk-ssh.mjs", kind: "bun-script" }
export const DEFAULT_HWLAB_AGENTRUN_GIT_BUNDLES = Object.freeze([
{ name: "hwlab-tools", subpath: "tools", target_path: "tools" },
{ name: "hwlab-agent-skills", subpath: "skills", target_path: ".agents/skills" }
]);
export const DEFAULT_HWLAB_AGENTRUN_PROMPT_REFS = Object.freeze([
{ name: "hwlab-v02-runtime", path: "internal/agent/prompts/hwlab-v02-runtime.md", inject: "thread-start", required: true }
]);
export const DEFAULT_HWLAB_AGENTRUN_SKILL_REFS = Object.freeze([
{ name: "hwpod-cli", path: "skills/hwpod-cli/SKILL.md", required: true, aggregateAs: "hwpod-cli" },
{ name: "hwpod-ctl", path: "skills/hwpod-ctl/SKILL.md", required: true, aggregateAs: "hwpod-ctl" },
{ name: "hwlab-agent-runtime", path: "skills/hwlab-agent-runtime/SKILL.md", required: true, aggregateAs: "hwlab-agent-runtime" }
]);
export function createHwlabAgentRunAssemblySummary(options = {}) {
const env = options.env ?? process.env;
const toolAliases = normalizeResourceToolAliases(options.toolAliases ?? DEFAULT_HWLAB_AGENTRUN_TOOL_ALIASES);
const bundles = normalizeResourceGitBundles(options.bundles ?? DEFAULT_HWLAB_AGENTRUN_GIT_BUNDLES);
const promptRefs = normalizeResourcePromptRefs(options.promptRefs ?? DEFAULT_HWLAB_AGENTRUN_PROMPT_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 branch = optionalString(options.branch ?? env.HWLAB_BOOT_REF ?? env.HWLAB_CODE_AGENT_AGENTRUN_BRANCH ?? env.AGENTRUN_BRANCH) ?? DEFAULT_HWLAB_AGENTRUN_BRANCH;
@@ -33,26 +25,24 @@ export function createHwlabAgentRunAssemblySummary(options = {}) {
const namespace = optionalString(options.namespace ?? env.AGENTRUN_RUNTIME_NAMESPACE ?? env.HWLAB_CODE_AGENT_AGENTRUN_NAMESPACE) ?? DEFAULT_AGENTRUN_NAMESPACE;
const managerUrl = optionalString(options.managerUrl ?? env.HWLAB_CODE_AGENT_AGENTRUN_MGR_URL ?? env.AGENTRUN_MANAGER_URL) ?? DEFAULT_AGENTRUN_MANAGER_URL;
return {
status: skillRefs.length > 0 ? "ready" : "degraded",
status: bundles.length > 0 ? "ready" : "degraded",
provider: "agentrun-v01",
namespace,
managerUrl,
resourceBundle: {
kind: "git",
kind: "gitbundle",
repoUrl,
branch,
commitId,
skillsDir: ".agents/skills"
},
counts: {
toolAliases: toolAliases.length,
promptRefs: promptRefs.length,
skillRefs: skillRefs.length
bundles: bundles.length,
promptRefs: promptRefs.length
},
toolAliases: toolAliases.map((item) => ({ ...item })),
bundles: bundles.map((item) => ({ ...item })),
promptRefs: promptRefs.map((item) => ({ ...item })),
promptAssembly,
skillRefs: skillRefs.map((item) => ({ ...item })),
runtimeEnv: {
assembledRuntime: "HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME",
skillsDirs: "HWLAB_CODE_AGENT_SKILLS_DIRS"
@@ -163,9 +153,8 @@ export function createHwlabAgentRunDispatchAssembly(options = {}) {
const branch = nonEmptyString(options.branch ?? DEFAULT_HWLAB_AGENTRUN_BRANCH, "branch");
const timeoutMs = positiveInteger(options.timeoutMs ?? 600000, "timeoutMs");
const unideskMainServerIp = optionalString(options.unideskMainServerIp ?? env.UNIDESK_MAIN_SERVER_IP ?? env.UNIDESK_MAIN_SERVER_HOST);
const toolAliases = normalizeResourceToolAliases(options.toolAliases ?? DEFAULT_HWLAB_AGENTRUN_TOOL_ALIASES);
const bundles = normalizeResourceGitBundles(options.bundles ?? DEFAULT_HWLAB_AGENTRUN_GIT_BUNDLES);
const promptRefs = normalizeResourcePromptRefs(options.promptRefs ?? DEFAULT_HWLAB_AGENTRUN_PROMPT_REFS);
const skillRefs = normalizeResourceSkillRefs(options.skillRefs ?? DEFAULT_HWLAB_AGENTRUN_SKILL_REFS);
if (includeUnideskSshToolCredential && !unideskMainServerIp) {
throw new Error("unideskMainServerIp is required when UniDesk SSH passthrough is enabled");
}
@@ -178,14 +167,11 @@ export function createHwlabAgentRunDispatchAssembly(options = {}) {
const transientEnv = normalizeTransientEnv(transientEnvWithUnideskMainServer(options.transientEnv ?? [], unideskMainServerIp));
const sessionRef = createSessionRef(options);
const resourceBundleRef = {
kind: "git",
kind: "gitbundle",
repoUrl,
commitId,
...(options.subdir ? { subdir: nonEmptyString(options.subdir, "subdir") } : {}),
...(Array.isArray(options.sparsePaths) ? { sparsePaths: options.sparsePaths.map((item, index) => nonEmptyString(item, `sparsePaths[${index}]`)) } : {}),
...(toolAliases.length > 0 ? { toolAliases } : {}),
bundles,
...(promptRefs.length > 0 ? { promptRefs } : {}),
...(skillRefs.length > 0 ? { skillRefs } : {}),
...(options.resourceCredentialRef ? { credentialRef: options.resourceCredentialRef } : {})
};
@@ -246,9 +232,8 @@ export function createHwlabAgentRunDispatchAssembly(options = {}) {
githubCredentialSource: includeGithubToolCredential ? "toolCredentials" : null,
unideskSshCredentialSource: includeUnideskSshToolCredential ? "toolCredentials" : null,
unideskMainServerSource: unideskMainServerIp ? "transientEnv" : null,
toolAliases: toolAliases.map((item) => item.name),
bundles: bundles.map((item) => item.name),
promptRefs: promptRefs.map((item) => item.name),
skillRefs: skillRefs.map((item) => item.name),
transientEnvNames: transientEnv.map((item) => item.name),
forbiddenTransientEnvNames: AGENTRUN_REUSABLE_CREDENTIAL_ENV_NAMES.slice()
}
@@ -325,21 +310,19 @@ function normalizeToolCredentials(items) {
});
}
function normalizeResourceToolAliases(items) {
if (!Array.isArray(items)) throw new Error("toolAliases must be an array");
const allowedKinds = new Set(["node-script", "bun-script", "sh-script", "executable"]);
function normalizeResourceGitBundles(items) {
if (!Array.isArray(items)) throw new Error("bundles must be an array");
const seen = new Set();
return items.map((item, index) => {
if (!item || typeof item !== "object") throw new Error(`toolAliases[${index}] must be an object`);
const name = nonEmptyString(item.name, `toolAliases[${index}].name`);
const aliasPath = nonEmptyString(item.path, `toolAliases[${index}].path`);
const kind = nonEmptyString(item.kind, `toolAliases[${index}].kind`);
if (!/^[a-z][a-z0-9._-]{0,62}$/u.test(name)) throw new Error(`toolAliases[${index}].name must be a lowercase command name`);
if (seen.has(name)) throw new Error(`toolAliases name ${name} is duplicated`);
if (aliasPath.startsWith("/") || aliasPath.includes("..")) throw new Error(`toolAliases[${index}].path must stay within the checkout`);
if (!allowedKinds.has(kind)) throw new Error(`toolAliases[${index}].kind is not supported`);
if (!item || typeof item !== "object") throw new Error(`bundles[${index}] must be an object`);
const name = resourceName(item.name, `bundles[${index}].name`);
if (seen.has(name)) throw new Error(`bundles name ${name} is duplicated`);
seen.add(name);
return { name, path: aliasPath, kind };
return {
name,
subpath: resourcePath(item.subpath, `bundles[${index}].subpath`),
target_path: workspaceTargetPath(item.target_path ?? item.targetPath, `bundles[${index}].target_path`)
};
});
}
@@ -358,21 +341,6 @@ function normalizeResourcePromptRefs(items) {
});
}
function normalizeResourceSkillRefs(items) {
if (!Array.isArray(items)) throw new Error("skillRefs must be an array");
const seen = new Set();
return items.map((item, index) => {
if (!item || typeof item !== "object") throw new Error(`skillRefs[${index}] must be an object`);
const name = resourceName(item.name, `skillRefs[${index}].name`);
if (seen.has(name)) throw new Error(`skillRefs name ${name} is duplicated`);
seen.add(name);
const refPath = resourcePath(item.path, `skillRefs[${index}].path`);
if (!refPath.endsWith("SKILL.md")) throw new Error(`skillRefs[${index}].path must point to SKILL.md`);
const aggregateAs = optionalString(item.aggregateAs);
return { name, path: refPath, required: item.required !== false, ...(aggregateAs ? { aggregateAs: resourceName(aggregateAs, `skillRefs[${index}].aggregateAs`) } : {}) };
});
}
function resourceName(value, fieldName) {
const name = nonEmptyString(value, fieldName);
if (!/^[a-z][a-z0-9._-]{0,62}$/u.test(name)) throw new Error(`${fieldName} must be a lowercase resource name`);
@@ -385,6 +353,12 @@ function resourcePath(value, fieldName) {
return refPath;
}
function workspaceTargetPath(value, fieldName) {
const targetPath = nonEmptyString(value, fieldName);
if (targetPath === "." || targetPath.startsWith("/") || targetPath.includes("..")) throw new Error(`${fieldName} must stay within the workspace`);
return targetPath;
}
async function postJson(fetchImpl, managerUrl, path, payload) {
const response = await fetchImpl(`${managerUrl}${path}`, {
method: "POST",