fix: 用 Aipod prompt 禁止 Artificer 递归派单

This commit is contained in:
AgentRun Codex
2026-07-13 06:27:37 +02:00
parent 9cdf803959
commit 694242c46d
3 changed files with 46 additions and 2 deletions
+5 -1
View File
@@ -124,7 +124,11 @@ spec:
lfs: false
payloadDefaults:
title: Artificer distributed development task
prompt: Follow the bundled dad-dev and UniDesk skills. Report progress to the relevant GitHub issue before risky or long operations.
prompt: |-
你就是当前已派发任务的 Artificer 执行者,必须直接在 primary workspace 完成被分配的调查、修改、测试和 PR。
禁止通过 `agentrun create`、`agentrun apply`、`agentrun dispatch`、Code Queue 或任何等价入口创建或派发另一个 Artificer 来完成当前任务。
允许只读查询 AgentRun task、session 和 run 状态;只读查询不属于二次派单。
遵循随资源包提供的 dad-dev 与 UniDesk skills;执行高风险或长时间操作前,先向相关 GitHub issue 报告进展。
references: []
metadata:
aipod: Artificer
+7 -1
View File
@@ -136,7 +136,9 @@ export function renderAipodSpec(record: AipodSpecRecord, input: RenderAipodInput
const metadata = mergeRecords(spec.metadata, input.metadata, { aipod: record.name, aipodSpecHash: record.specHash, aipodImageRef: imageRef });
const inputPayload = input.payload === undefined ? undefined : validateAipodPayload(input.payload, "aipodRender.payload");
const payload = mergeRecords(spec.payloadDefaults, inputPayload);
if (typeof input.prompt === "string" && input.prompt.trim().length > 0) payload.prompt = input.prompt;
const defaultPrompt = nonEmptyText(spec.payloadDefaults?.prompt);
const userPrompt = nonEmptyText(input.prompt) ?? nonEmptyText(inputPayload?.prompt);
if (userPrompt !== null) payload.prompt = defaultPrompt === null ? userPrompt : `${defaultPrompt}\n\n${userPrompt}`;
applyEffectiveModelPayload(payload, effectiveModel);
const sessionRef = renderSessionRef(spec.sessionRef ?? null, input, record, payload);
const queueTask = validateCreateQueueTask({
@@ -443,6 +445,10 @@ function stringValue(value: unknown): string | null {
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}
function nonEmptyText(value: unknown): string | null {
return typeof value === "string" && value.trim().length > 0 ? value : null;
}
function isJsonRecord(value: unknown): value is JsonRecord {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
@@ -0,0 +1,34 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { parseAipodSpecYaml, renderAipodSpec } from "../../common/aipod-specs.js";
import type { JsonRecord } from "../../common/types.js";
import type { SelfTestCase } from "../harness.js";
const selfTest: SelfTestCase = async (context) => {
const source = path.join(context.root, "config", "aipods", "artificer.yaml");
const spec = parseAipodSpecYaml(await readFile(source, "utf8"), source);
const userPrompt = "调查 Monitor issue #1888 的迁移失败。\n保留这段用户业务提示的完整内容。";
const rendered = renderAipodSpec({
name: spec.metadata.name,
spec,
specHash: "selftest-artificer-nonrecursive-prompt",
source,
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
}, { prompt: userPrompt });
const effectivePrompt = String(((rendered.queueTask as JsonRecord).payload as JsonRecord).prompt);
assert.match(effectivePrompt, / Artificer /u);
assert.match(effectivePrompt, /agentrun create/u);
assert.match(effectivePrompt, /agentrun apply/u);
assert.match(effectivePrompt, /agentrun dispatch/u);
assert.match(effectivePrompt, /Code Queue/u);
assert.match(effectivePrompt, / AgentRun tasksession run /u);
assert.equal(effectivePrompt.endsWith(userPrompt), true, "rendered prompt must preserve the complete user business prompt");
assert.equal(effectivePrompt.split(userPrompt).length - 1, 1, "rendered prompt must include the user business prompt exactly once");
return { name: "artificer-nonrecursive-prompt", tests: ["artificer-yaml-nonrecursive-prompt-render", "artificer-user-prompt-preserved"] };
};
export default selfTest;