Play rounds end in present.v1 body instead of creation HITL.

Narrator uses capability-probed structured delivery and falls back to shell body on failure; play UI hides worker questions and keeps thinking on process rows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-02 01:07:43 +08:00
co-authored by Cursor
parent f37388761b
commit 92b2d88738
11 changed files with 828 additions and 327 deletions
+53 -13
View File
@@ -130,7 +130,14 @@ import {
import {
readPlayTurnQueue,
withPlayTurnQueue,
isPlayLayerActive,
} from "../skills/play-turn.js";
import {
PRESENT_TAG,
isPlayPresentWorker,
parseShellAdaptationFromReplyFormat,
presentOutputFromFallback,
} from "../skills/present-packet.js";
import { debugLog, labelAction, logStateChange, sessionSnap } from "../log.js";
import {
executeChance,
@@ -1001,6 +1008,37 @@ export class PhaseRuntime {
return true;
}
/**
* 游玩层不走创作 HITL:转述必须交出用户可见正文,其它执行单元空产物也继续。
*/
private settlePlayWorkerResult(
workerId: string,
result: import("../worker/executor.js").WorkerRunResult,
): import("../worker/executor.js").WorkerRunResult {
if (!isPlayLayerActive(this.session.slots)) return result;
const outputs = { ...result.outputs };
if (Object.keys(outputs).length === 0 && isPlayPresentWorker(workerId)) {
const shell =
parseShellAdaptationFromReplyFormat(
this.blackboard.getContentByTag("设计.正文组成") ??
this.blackboard.getContentByTag("设计.回复格式"),
)?.shell_id ?? "prose";
const preview = result.preview?.trim() ?? "";
const looksAsk =
/可验收|追问|请再补|askUser/.test(preview) || /^\s*-\s/.test(preview);
outputs[PRESENT_TAG] = presentOutputFromFallback(
looksAsk ? "" : preview,
shell,
);
}
return {
...result,
outputs,
askUser: undefined,
askAssessment: undefined,
};
}
/** worker 调用 ask_user 能力时,由外层触发此事件(仅无产物时阻塞) */
async workerAsk(
questions: import("../types/questions.js").QuestionItem[] | string[],
@@ -1802,20 +1840,22 @@ export class PhaseRuntime {
},
});
if (result.askUser?.length && Object.keys(result.outputs).length === 0) {
const settled = this.settlePlayWorkerResult(workerId, result);
if (settled.askUser?.length && Object.keys(settled.outputs).length === 0) {
// 无产物:阻塞追问(信息不足,必须补)
if (await this.restoreRevisionTargetForReview(workerId, result.askUser)) {
if (await this.restoreRevisionTargetForReview(workerId, settled.askUser)) {
return;
}
await this.workerAsk(result.askUser);
await this.workerAsk(settled.askUser);
return;
}
if (
workerId === "design-step" &&
Object.keys(result.outputs).length === 0
Object.keys(settled.outputs).length === 0
) {
const retryQuestions = result.askUser?.length
? result.askUser
const retryQuestions = settled.askUser?.length
? settled.askUser
: [
{
id: "design-step-retry",
@@ -1834,7 +1874,7 @@ export class PhaseRuntime {
slots = { ...this.session.slots };
const writtenTags: string[] = [];
for (const [tag, content] of Object.entries(result.outputs)) {
for (const [tag, content] of Object.entries(settled.outputs)) {
if (isProgressPointerTag(tag)) continue;
const written = this.writeWorkerTagContent(tag, content, workerId);
slots[tag] = written;
@@ -1848,16 +1888,16 @@ export class PhaseRuntime {
outputTags: (writtenTags.length > 0 ? writtenTags : worker.outputTags).filter(
(t) => !isProgressPointerTag(t),
),
summary: result.summary,
summary: settled.summary,
});
this.session = { ...this.session, artifacts: [...this.session.artifacts, artifact] };
const hasPlayVisible = Object.keys(result.outputs).some(
const hasPlayVisible = Object.keys(settled.outputs).some(
(t) => t === "输出.用户展示" || t === "输出.开场白",
);
this.onMessage(
`[Worker] ${workerId} 已完成\n\n${result.preview}${
!hasPlayVisible && result.preview.length >= 4000 ? "\n\n…" : ""
`[Worker] ${workerId} 已完成\n\n${settled.preview}${
!hasPlayVisible && settled.preview.length >= 4000 ? "\n\n…" : ""
}`,
);
@@ -1866,8 +1906,8 @@ export class PhaseRuntime {
payload: {
artifactId: artifact.id,
// 有产物时 askUser 挂到验收态,不阻断 Accept
questions: this.freshSidecarQuestions(result.askUser),
assessment: result.askAssessment?.trim() || undefined,
questions: this.freshSidecarQuestions(settled.askUser),
assessment: settled.askAssessment?.trim() || undefined,
},
});
this.maybeCompressAcceptedArtifact(artifact.id);