feat: assemble resource prompts and skills

This commit is contained in:
Codex
2026-06-02 20:40:14 +08:00
parent a53f5b8a0d
commit 3018b8a937
11 changed files with 394 additions and 15 deletions
+81 -4
View File
@@ -11,7 +11,7 @@ import type { JsonRecord, ResourceBundleRef } from "../../common/types.js";
import { assertNoSecretLeak, type SelfTestCase, type SelfTestContext } from "../harness.js";
const execFile = promisify(execFileCallback);
type LocalBundle = { repoUrl: string; commitId: string; toolAliases?: ResourceBundleRef["toolAliases"] };
type LocalBundle = { repoUrl: string; commitId: string; toolAliases?: ResourceBundleRef["toolAliases"]; promptRefs?: ResourceBundleRef["promptRefs"]; skillRefs?: ResourceBundleRef["skillRefs"] };
const selfTest: SelfTestCase = async (context) => {
const containerfile = await readFile(path.join(context.root, "deploy/container/Containerfile"), "utf8");
@@ -45,6 +45,11 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
try {
const client = new ManagerClient(server.baseUrl);
const bundle = await createLocalGitBundle(context);
const assemblyBundle: LocalBundle = {
...bundle,
promptRefs: [{ name: "hwlab-v02-runtime", path: "internal/agent/prompts/hwlab-v02-runtime.md", inject: "thread-start", required: true }],
skillRefs: [{ name: "device-pod-cli", path: "skills/device-pod-cli/SKILL.md", required: true, aggregateAs: "device-pod-cli" }],
};
const first = await createHwlabRun(client, context, bundle, "hwlab-session-1", "hello bundle", "hwlab-command-1");
const created = await client.post(`/api/v1/runs/${first.runId}/runner-jobs`, { commandId: first.commandId, idempotencyKey: "hwlab-trace-1" }) as JsonRecord;
const replay = await client.post(`/api/v1/runs/${first.runId}/runner-jobs`, { commandId: first.commandId, idempotencyKey: "hwlab-trace-1" }) as JsonRecord;
@@ -97,6 +102,49 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
assert.deepEqual(((materialized.toolAliases as JsonRecord).names), ["hwpod"]);
assertNoSecretLeak(resultEnvelope);
const assemblyRun = await createHwlabRun(client, context, assemblyBundle, "hwlab-session-assembly", "list visible bundle skills without tools", "hwlab-command-assembly-1");
const assemblyInputFile = path.join(context.tmp, "fake-codex-turn-input-assembly.jsonl");
const assemblyRunner = runOnce({ managerUrl: server.baseUrl, runId: assemblyRun.runId, commandId: assemblyRun.commandId, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome, AGENTRUN_WORKSPACE_ROOT: path.join(context.tmp, "workspaces-assembly"), AGENTRUN_FAKE_CODEX_TURN_INPUT_FILE: assemblyInputFile }, idleTimeoutMs: 500, pollIntervalMs: 50 });
await waitForCommandState(client, assemblyRun.runId, assemblyRun.commandId, "completed");
const assemblySecond = await client.post(`/api/v1/runs/${assemblyRun.runId}/commands`, { type: "turn", payload: { prompt: "second turn should resume without initial prompt", traceId: "hwlab-command-assembly-2" }, idempotencyKey: "hwlab-command-assembly-2" }) as { id: string };
await waitForCommandState(client, assemblyRun.runId, assemblySecond.id, "completed");
const assemblyRunnerResult = await assemblyRunner as JsonRecord;
assert.equal(assemblyRunnerResult.commandsProcessed, 2);
const assemblyInputs = (await readTextIfExists(assemblyInputFile)).trim().split("\n").filter(Boolean).map((line) => JSON.parse(line) as JsonRecord);
assert.equal(assemblyInputs.length, 2);
const firstAssemblyInput = turnInputText(assemblyInputs[0]);
const secondAssemblyInput = turnInputText(assemblyInputs[1]);
assert.match(firstAssemblyInput, /HWLAB v0\.2 runtime prompt self-test/u);
assert.match(firstAssemblyInput, /device-pod-cli/u);
assert.match(firstAssemblyInput, /hwpod/u);
assert.match(firstAssemblyInput, /list visible bundle skills/u);
assert.match(secondAssemblyInput, /second turn should resume/u);
assert.doesNotMatch(secondAssemblyInput, /HWLAB v0\.2 runtime prompt self-test/u);
assert.doesNotMatch(secondAssemblyInput, /Resource Skills/u);
const assemblyEventsResponse = await client.get(`/api/v1/runs/${assemblyRun.runId}/events?afterSeq=0&limit=200`) as { items?: Array<{ type?: string; payload?: JsonRecord }> };
const assemblyEvents = assemblyEventsResponse.items ?? [];
assert.ok(assemblyEvents.some((event) => event.type === "backend_status" && event.payload?.phase === "initial-prompt-assembly" && event.payload?.commandId === assemblyRun.commandId && event.payload?.initialPromptInjected === true));
assert.ok(assemblyEvents.some((event) => event.type === "backend_status" && event.payload?.phase === "initial-prompt-assembly" && event.payload?.commandId === assemblySecond.id && event.payload?.initialPromptInjected === false && event.payload?.reason === "thread-resume"));
const assemblyEnvelope = await client.get(`/api/v1/runs/${assemblyRun.runId}/commands/${assemblyRun.commandId}/result`) as JsonRecord;
const assemblyResource = assemblyEnvelope.resourceBundleRef as JsonRecord;
assert.deepEqual(((assemblyResource.promptRefs as JsonRecord).names), ["hwlab-v02-runtime"]);
assert.deepEqual(((assemblyResource.skillRefs as JsonRecord).names), ["device-pod-cli"]);
const assemblyMaterialized = assemblyResource.materialized as JsonRecord;
assert.deepEqual(((assemblyMaterialized.promptRefs as JsonRecord).names), ["hwlab-v02-runtime"]);
assert.deepEqual(((assemblyMaterialized.skillRefs as JsonRecord).names), ["device-pod-cli"]);
assert.equal(((assemblyMaterialized.initialPrompt as JsonRecord).available), true);
assertNoSecretLeak(assemblyEnvelope);
const missingPromptRun = await createHwlabRun(client, context, { ...bundle, promptRefs: [{ name: "missing-prompt", path: "internal/agent/prompts/missing.md", inject: "thread-start", required: true }] }, "hwlab-session-missing-prompt", "missing prompt", "hwlab-command-missing-prompt");
const missingPromptResult = await runOnce({ managerUrl: server.baseUrl, runId: missingPromptRun.runId, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome, AGENTRUN_WORKSPACE_ROOT: path.join(context.tmp, "workspaces-missing-prompt") }, oneShot: true }) as JsonRecord;
assert.equal(missingPromptResult.terminalStatus, "blocked");
assert.equal(missingPromptResult.failureKind, "prompt-unavailable");
const missingSkillRun = await createHwlabRun(client, context, { ...bundle, skillRefs: [{ name: "missing-skill", path: "skills/missing-skill/SKILL.md", required: true }] }, "hwlab-session-missing-skill", "missing skill", "hwlab-command-missing-skill");
const missingSkillResult = await runOnce({ managerUrl: server.baseUrl, runId: missingSkillRun.runId, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome, AGENTRUN_WORKSPACE_ROOT: path.join(context.tmp, "workspaces-missing-skill") }, oneShot: true }) as JsonRecord;
assert.equal(missingSkillResult.terminalStatus, "blocked");
assert.equal(missingSkillResult.failureKind, "skill-unavailable");
const resumed = await createHwlabRun(client, context, bundle, "hwlab-session-resume", "hello resumed", "hwlab-command-session-resumed");
const resumedRun = await client.get(`/api/v1/runs/${resumed.runId}`) as JsonRecord;
assert.equal(((resumedRun.sessionRef as JsonRecord).threadId), "thread_selftest_1");
@@ -155,7 +203,7 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
const runningResult = await running;
assert.equal(runningResult.terminalStatus, "cancelled");
return { name: "hwlab-manual-dispatch", tests: ["runner-job-idempotency", "pending-cancel", "result-envelope", "session-ref-resume", "resource-bundle-materialization", "resource-bundle-tool-alias", "same-run-runner-multiturn", "running-steer", "running-cancel"] };
return { name: "hwlab-manual-dispatch", tests: ["runner-job-idempotency", "pending-cancel", "result-envelope", "session-ref-resume", "resource-bundle-materialization", "resource-bundle-tool-alias", "resource-prompt-skill-assembly", "resource-prompt-skill-required-blockers", "same-run-runner-multiturn", "running-steer", "running-cancel"] };
} finally {
await new Promise<void>((resolve) => server.server.close(() => resolve()));
}
@@ -168,7 +216,23 @@ async function createLocalGitBundle(context: SelfTestContext): Promise<LocalBund
await writeFile(path.join(repo, "README.md"), "HWLAB bundle self-test\n", "utf8");
await mkdir(path.join(repo, "tools"), { recursive: true });
await writeFile(path.join(repo, "tools", "device-pod-cli.mjs"), "console.log(JSON.stringify({ ok: true, cli: 'hwpod-selftest', argv: process.argv.slice(2) }));\n", "utf8");
await execFile("git", ["add", "README.md", "tools/device-pod-cli.mjs"], { cwd: repo });
await mkdir(path.join(repo, "internal", "agent", "prompts"), { recursive: true });
await writeFile(path.join(repo, "internal", "agent", "prompts", "hwlab-v02-runtime.md"), [
"HWLAB v0.2 runtime prompt self-test",
"Use the hwpod alias for device-pod work.",
"Do not invent fallback hardware paths.",
].join("\n"), "utf8");
await mkdir(path.join(repo, "skills", "device-pod-cli", "scripts"), { recursive: true });
await writeFile(path.join(repo, "skills", "device-pod-cli", "SKILL.md"), [
"---",
"name: device-pod-cli",
"description: Use hwpod for HWLAB device-pod compile, status, job polling, and output inspection.",
"---",
"# device-pod-cli",
"Run `hwpod` from PATH for all device-pod operations.",
].join("\n"), "utf8");
await writeFile(path.join(repo, "skills", "device-pod-cli", "scripts", "device-pod-cli.mjs"), "console.log(JSON.stringify({ ok: true, cli: 'device-pod-skill-selftest' }));\n", "utf8");
await execFile("git", ["add", "README.md", "tools/device-pod-cli.mjs", "internal/agent/prompts/hwlab-v02-runtime.md", "skills/device-pod-cli/SKILL.md", "skills/device-pod-cli/scripts/device-pod-cli.mjs"], { cwd: repo });
await execFile("git", ["-c", "user.email=selftest@example.invalid", "-c", "user.name=AgentRun SelfTest", "commit", "-m", "bundle selftest"], { cwd: repo });
const { stdout } = await execFile("git", ["rev-parse", "HEAD"], { cwd: repo });
return { repoUrl: repo, commitId: stdout.trim() };
@@ -176,12 +240,15 @@ async function createLocalGitBundle(context: SelfTestContext): Promise<LocalBund
async function createHwlabRun(client: ManagerClient, context: SelfTestContext, bundle: LocalBundle, sessionId: string, prompt: string, idempotencyKey: string, timeoutMs = 15_000): Promise<{ runId: string; commandId: string }> {
const toolAliases = bundle.toolAliases ?? [{ name: "hwpod", path: "tools/device-pod-cli.mjs", kind: "node-script" }];
const resourceBundleRef: ResourceBundleRef = { kind: "git", repoUrl: bundle.repoUrl, commitId: bundle.commitId, toolAliases, submodules: false, lfs: false };
if (bundle.promptRefs) resourceBundleRef.promptRefs = bundle.promptRefs;
if (bundle.skillRefs) resourceBundleRef.skillRefs = bundle.skillRefs;
const run = await client.post("/api/v1/runs", {
tenantId: "hwlab",
projectId: "pikasTech/HWLAB",
workspaceRef: { kind: "opaque", repo: "pikasTech/HWLAB" },
sessionRef: { sessionId, conversationId: sessionId },
resourceBundleRef: { kind: "git", repoUrl: bundle.repoUrl, commitId: bundle.commitId, toolAliases, submodules: false, lfs: false },
resourceBundleRef,
providerId: "G14",
backendProfile: "codex",
executionPolicy: {
@@ -220,6 +287,16 @@ async function readTextIfExists(filePath: string): Promise<string> {
}
}
function turnInputText(record: JsonRecord | undefined): string {
const input = record?.input;
if (!Array.isArray(input)) return "";
return input.flatMap((item) => {
if (typeof item !== "object" || item === null || Array.isArray(item)) return [];
const text = (item as JsonRecord).text;
return typeof text === "string" ? [text] : [];
}).join("\n");
}
function runnerEnvValue(manifest: JsonRecord, name: string): unknown {
const spec = manifest.spec as JsonRecord;
const template = spec.template as JsonRecord;