重构世界模拟器为模块化配方架构,完善创作编排、会话运行时与 Web UI,并清理过时技能。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
import type { Blackboard } from "../blackboard/blackboard.js";
|
||||
|
||||
/** 下一 worker / 总管可读的定稿摘要 tag */
|
||||
export const CONTEXT_BRIEF_TAG = "上下文.定稿摘要";
|
||||
|
||||
export type CompressWorkerResult = {
|
||||
archivedTags: string[];
|
||||
finals: Array<{ tag: string; chars: number; preview: string }>;
|
||||
briefText: string;
|
||||
};
|
||||
|
||||
const ALWAYS_ARCHIVE_AFTER_ACCEPT = ["用户.worker答复", "用户.修订说明"];
|
||||
|
||||
/**
|
||||
* Worker 产物经用户验收(或自动完工)后:
|
||||
* - 终产物打 final
|
||||
* - 过程/草稿 tag 归档(不再进入后续 worker 取数)
|
||||
* - 写入定稿摘要,供下一阶段「指点」用
|
||||
*/
|
||||
export function compressAfterWorkerAccept(params: {
|
||||
blackboard: Blackboard;
|
||||
workerId: string;
|
||||
outputTags: string[];
|
||||
summary?: string;
|
||||
/**
|
||||
* final = 终稿验收(归档草稿、写定稿摘要)
|
||||
* unit = 创作单位验收(只归档过程答复,保留 设计.worker集.草稿)
|
||||
*/
|
||||
mode?: "final" | "unit";
|
||||
}): CompressWorkerResult {
|
||||
const { blackboard, workerId, outputTags, summary } = params;
|
||||
const mode = params.mode ?? "final";
|
||||
const finals: CompressWorkerResult["finals"] = [];
|
||||
const archivedTags: string[] = [];
|
||||
|
||||
const uniqueOutputs = [...new Set(outputTags.map((t) => t.trim()).filter(Boolean))];
|
||||
|
||||
if (mode === "final") {
|
||||
for (const tag of uniqueOutputs) {
|
||||
const content = blackboard.getContentByTag(tag);
|
||||
if (content == null) continue;
|
||||
blackboard.write({
|
||||
tag,
|
||||
content,
|
||||
source: workerId,
|
||||
metadata: {
|
||||
role: "final",
|
||||
acceptedAt: new Date().toISOString(),
|
||||
workerId,
|
||||
},
|
||||
});
|
||||
finals.push({
|
||||
tag,
|
||||
chars: content.length,
|
||||
preview: previewText(content, 120),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 单位验收:草稿保持活跃,终产物列表仅作面板提示
|
||||
for (const tag of uniqueOutputs) {
|
||||
if (tag === "用户.需求" || tag === "创作.当前单位") continue;
|
||||
const content = blackboard.getContentByTag(tag);
|
||||
if (content == null) continue;
|
||||
finals.push({
|
||||
tag,
|
||||
chars: content.length,
|
||||
preview: previewText(content, 120),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const toArchive = new Set<string>(ALWAYS_ARCHIVE_AFTER_ACCEPT);
|
||||
|
||||
if (mode === "final") {
|
||||
if (blackboard.getContentByTag("设计.worker集")?.trim()) {
|
||||
toArchive.add("设计.worker集.草稿");
|
||||
}
|
||||
for (const tag of uniqueOutputs) {
|
||||
if (tag.endsWith(".草稿")) continue;
|
||||
const draft = `${tag}.草稿`;
|
||||
if (blackboard.getContentByTag(draft) != null) toArchive.add(draft);
|
||||
}
|
||||
}
|
||||
|
||||
for (const tag of toArchive) {
|
||||
if (uniqueOutputs.includes(tag) && mode === "final") continue;
|
||||
if (mode === "unit" && tag.endsWith(".草稿")) continue;
|
||||
const content = blackboard.getContentByTag(tag);
|
||||
if (content == null) continue;
|
||||
blackboard.write({
|
||||
tag,
|
||||
content: `(已压缩归档)原过程内容已折叠。定稿见:${
|
||||
finals.map((f) => f.tag).join("、") || "(无)"
|
||||
}\n\n---\n${previewText(content, 400)}`,
|
||||
source: "compress",
|
||||
metadata: {
|
||||
role: "archived",
|
||||
archivedAt: new Date().toISOString(),
|
||||
fromWorker: workerId,
|
||||
compressMode: mode,
|
||||
},
|
||||
});
|
||||
archivedTags.push(tag);
|
||||
}
|
||||
|
||||
const briefText =
|
||||
mode === "unit"
|
||||
? buildUnitBriefText({ workerId, summary, finals, archivedTags })
|
||||
: buildBriefText({
|
||||
workerId,
|
||||
summary,
|
||||
finals,
|
||||
archivedTags,
|
||||
});
|
||||
|
||||
if (mode === "final") {
|
||||
blackboard.write({
|
||||
tag: CONTEXT_BRIEF_TAG,
|
||||
content: briefText,
|
||||
source: "compress",
|
||||
metadata: { role: "final", workerId },
|
||||
});
|
||||
}
|
||||
|
||||
return { archivedTags, finals, briefText };
|
||||
}
|
||||
|
||||
function buildUnitBriefText(params: {
|
||||
workerId: string;
|
||||
summary?: string;
|
||||
finals: CompressWorkerResult["finals"];
|
||||
archivedTags: string[];
|
||||
}): string {
|
||||
return [
|
||||
`## 创作单位已验收(${params.workerId})`,
|
||||
"",
|
||||
params.summary?.trim() ? `摘要:${params.summary.trim()}` : "摘要:(无)",
|
||||
"",
|
||||
"草稿仍保留在 `设计.worker集.草稿`;下一单位继续增量,勿依赖已删的过程对话。",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function buildBriefText(params: {
|
||||
workerId: string;
|
||||
summary?: string;
|
||||
finals: CompressWorkerResult["finals"];
|
||||
archivedTags: string[];
|
||||
}): string {
|
||||
const lines = [
|
||||
`## 上一阶段定稿(${params.workerId})`,
|
||||
"",
|
||||
params.summary?.trim()
|
||||
? `摘要:${params.summary.trim()}`
|
||||
: "摘要:(无)",
|
||||
"",
|
||||
"### 保留的终产物 tag",
|
||||
];
|
||||
if (params.finals.length === 0) {
|
||||
lines.push("- (无)");
|
||||
} else {
|
||||
for (const f of params.finals) {
|
||||
lines.push(`- \`${f.tag}\`(${f.chars} 字)`);
|
||||
lines.push(` ${f.preview}`);
|
||||
}
|
||||
}
|
||||
if (params.archivedTags.length) {
|
||||
lines.push("", "### 已压缩的过程 tag");
|
||||
for (const t of params.archivedTags) {
|
||||
lines.push(`- \`${t}\`(归档,后续 worker 默认不读)`);
|
||||
}
|
||||
}
|
||||
lines.push(
|
||||
"",
|
||||
"下一 worker 应以以上终产物为准继续;勿依赖已归档的过程讨论。",
|
||||
);
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
export function previewText(content: string, max: number): string {
|
||||
const t = content.replace(/\s+/g, " ").trim();
|
||||
if (t.length <= max) return t;
|
||||
return `${t.slice(0, max)}…`;
|
||||
}
|
||||
|
||||
/** 供 UI:黑板可读条目(默认隐藏 archived 全文,只给索引) */
|
||||
export function buildBoardPanel(blackboard: Blackboard): {
|
||||
finals: Array<{ tag: string; source: string; preview: string; updatedAt: string }>;
|
||||
active: Array<{ tag: string; source: string; preview: string; updatedAt: string }>;
|
||||
archivedCount: number;
|
||||
brief?: string;
|
||||
} {
|
||||
const items = latestItemsByTag(blackboard.exportItems());
|
||||
const finals: Array<{
|
||||
tag: string;
|
||||
source: string;
|
||||
preview: string;
|
||||
updatedAt: string;
|
||||
}> = [];
|
||||
const active: typeof finals = [];
|
||||
let archivedCount = 0;
|
||||
let brief: string | undefined;
|
||||
|
||||
for (const item of items.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))) {
|
||||
const role = String(item.metadata?.role ?? "");
|
||||
if (item.tag === CONTEXT_BRIEF_TAG) {
|
||||
brief = item.content;
|
||||
continue;
|
||||
}
|
||||
if (role === "archived") {
|
||||
archivedCount += 1;
|
||||
continue;
|
||||
}
|
||||
const row = {
|
||||
tag: item.tag,
|
||||
source: item.source,
|
||||
preview: previewText(item.content, 160),
|
||||
updatedAt: item.updatedAt,
|
||||
};
|
||||
if (role === "final") finals.push(row);
|
||||
else active.push(row);
|
||||
}
|
||||
|
||||
return { finals, active, archivedCount, brief };
|
||||
}
|
||||
|
||||
function latestItemsByTag(
|
||||
items: import("../types/blackboard.js").BlackboardItem[],
|
||||
): import("../types/blackboard.js").BlackboardItem[] {
|
||||
const byTag = new Map<string, (typeof items)[0]>();
|
||||
for (const item of items) {
|
||||
const prev = byTag.get(item.tag);
|
||||
if (!prev || item.updatedAt > prev.updatedAt) byTag.set(item.tag, item);
|
||||
}
|
||||
return [...byTag.values()];
|
||||
}
|
||||
@@ -60,7 +60,7 @@ export class RuntimeOrchestrator {
|
||||
payload: {
|
||||
presetId: this.session.presetId,
|
||||
flowId: this.session.flowId,
|
||||
availableSkills: [{ name: "basic", description: "fallback", category: "novel" }],
|
||||
availableSkills: [{ name: "world-simulator", description: "fallback", category: "dialogue" }],
|
||||
},
|
||||
});
|
||||
return this.session;
|
||||
|
||||
+211
-43
@@ -19,22 +19,56 @@ import type {
|
||||
WaitingReason,
|
||||
} from "../types/runtime.js";
|
||||
import type { ActiveSkillSnapshot } from "../types/runtime.js";
|
||||
import { effectiveStartupMode } from "../config/default-orchestrator.js";
|
||||
import {
|
||||
buildIntakeProgress,
|
||||
buildIntakeFollowUpMessage,
|
||||
readIntakeValues,
|
||||
synthesizeDemandText,
|
||||
} from "../intake/intake.js";
|
||||
import { normalizeQuestions } from "../skills/question-protocol.js";
|
||||
import type { QuestionItem } from "../types/questions.js";
|
||||
|
||||
function nowIso(): string {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
/** 挂在产物下的追问一律可选(required=false),用户可直接 Accept */
|
||||
function asOptionalSidecarQuestions(
|
||||
raw: QuestionItem[] | string[] | undefined,
|
||||
): QuestionItem[] | undefined {
|
||||
const normalized = normalizeQuestions(raw ?? []);
|
||||
if (!normalized.length) return undefined;
|
||||
return normalized.map((q) => ({ ...q, required: false }));
|
||||
}
|
||||
|
||||
/** 更新 updatedAt 时间戳 */
|
||||
function touch(session: RuntimeSession): RuntimeSession {
|
||||
return { ...session, updatedAt: nowIso() };
|
||||
}
|
||||
|
||||
/** 用户每条输入写入 用户.最新输入;首句同时写入需求 tag */
|
||||
function applyUserTextToSlots(
|
||||
slots: Record<string, unknown>,
|
||||
text: string,
|
||||
demandKey: string,
|
||||
opts: { initial?: boolean },
|
||||
): void {
|
||||
if (!text) return;
|
||||
slots["用户.最新输入"] = text;
|
||||
if (opts.initial) {
|
||||
slots[demandKey] = text;
|
||||
slots.startupCompleted = true;
|
||||
return;
|
||||
}
|
||||
if (!slots.startupCompleted) {
|
||||
slots[demandKey] = text;
|
||||
slots.startupCompleted = true;
|
||||
return;
|
||||
}
|
||||
mergeSlotText(slots, demandKey, text);
|
||||
}
|
||||
|
||||
/** 将用户补充文本追加到 slot(用于合并多轮 ask_user / worker 答复到需求 tag) */
|
||||
function mergeSlotText(
|
||||
slots: Record<string, unknown>,
|
||||
@@ -136,6 +170,7 @@ export function getAllowedEvents(
|
||||
"user_accepted_artifact",
|
||||
"user_rejected_artifact",
|
||||
"user_requested_revision",
|
||||
"user_resolved_sidecar_questions",
|
||||
"main_agent_decision_created",
|
||||
"runtime_failed",
|
||||
];
|
||||
@@ -179,6 +214,11 @@ export function canApplyEvent(
|
||||
case "user_accepted_artifact":
|
||||
case "user_rejected_artifact":
|
||||
return reason?.kind === "review_artifact";
|
||||
case "user_resolved_sidecar_questions":
|
||||
return (
|
||||
reason?.kind === "review_artifact" &&
|
||||
Boolean(reason.questions?.length)
|
||||
);
|
||||
case "user_requested_revision":
|
||||
return (
|
||||
reason?.kind === "review_artifact" || reason?.kind === "approve_step"
|
||||
@@ -272,7 +312,7 @@ export function applyEvent(
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
// ── 启动:选 skill ──
|
||||
// ── 启动:默认 orchestrator 或 legacy 选包 ──
|
||||
case "session_started": {
|
||||
const next = appendHistory(
|
||||
{
|
||||
@@ -283,40 +323,22 @@ export function applyEvent(
|
||||
event,
|
||||
);
|
||||
const skills = event.payload.availableSkills;
|
||||
const initialSkill = event.payload.initialSkill;
|
||||
if (initialSkill) {
|
||||
return applySkillBinding(next, initialSkill, event);
|
||||
}
|
||||
return {
|
||||
session: waiting(next, {
|
||||
kind: "skill_selection",
|
||||
availableSkills: skills,
|
||||
}),
|
||||
effects: [
|
||||
{
|
||||
type: "emit_message",
|
||||
message: formatSkillSelectionPrompt(skills),
|
||||
},
|
||||
],
|
||||
effects: [{ type: "emit_message", message: formatSkillSelectionPrompt(skills) }],
|
||||
};
|
||||
}
|
||||
|
||||
case "skill_selected": {
|
||||
const { skill } = event.payload;
|
||||
const next = appendHistory(
|
||||
{
|
||||
...session,
|
||||
flowId: skill.defaultFlowId ?? session.flowId,
|
||||
slots: {
|
||||
...session.slots,
|
||||
activeSkill: skill,
|
||||
},
|
||||
},
|
||||
event,
|
||||
);
|
||||
return {
|
||||
session: waiting(next, {
|
||||
kind: "intake",
|
||||
prompt: skill.startupPrompt,
|
||||
}),
|
||||
effects: [{ type: "emit_message", message: skill.startupPrompt }],
|
||||
};
|
||||
return applySkillBinding(session, skill, event);
|
||||
}
|
||||
|
||||
case "user_confirmed_intake": {
|
||||
@@ -366,6 +388,21 @@ export function applyEvent(
|
||||
|
||||
if (session.waitingReason?.kind === "intake") {
|
||||
const skill = activeSkill as ActiveSkillSnapshot | undefined;
|
||||
|
||||
if (skill && effectiveStartupMode(skill) === "agent-first" && text) {
|
||||
const key = skill.startupTargetKey || "用户.需求";
|
||||
applyUserTextToSlots(slots, text, key, { initial: true });
|
||||
slots.lastUserInput = text;
|
||||
const next = appendHistory(
|
||||
{ ...session, slots, resumeContext: undefined },
|
||||
event,
|
||||
);
|
||||
return {
|
||||
session: touch(running(next)),
|
||||
effects: [{ type: "invoke_main_agent" }],
|
||||
};
|
||||
}
|
||||
|
||||
const intakeValues =
|
||||
event.payload.intakeValues ??
|
||||
readIntakeValues(session.slots);
|
||||
@@ -419,16 +456,17 @@ export function applyEvent(
|
||||
|
||||
if (session.waitingReason?.kind === "worker_questions") {
|
||||
slots["用户.worker答复"] = text;
|
||||
if (text) slots["用户.最新输入"] = text;
|
||||
if (demandKey && text) {
|
||||
mergeSlotText(slots, demandKey, text);
|
||||
}
|
||||
} else if (demandKey && session.waitingReason?.kind === "input" && text) {
|
||||
if (!session.slots.startupCompleted) {
|
||||
slots[demandKey] = text;
|
||||
slots.startupCompleted = true;
|
||||
} else {
|
||||
mergeSlotText(slots, demandKey, text);
|
||||
}
|
||||
} else if (session.waitingReason?.kind === "input" && text) {
|
||||
const key = demandKey || "用户.需求";
|
||||
applyUserTextToSlots(slots, text, key, {
|
||||
initial: !session.slots.startupCompleted,
|
||||
});
|
||||
} else if (text) {
|
||||
slots["用户.最新输入"] = text;
|
||||
}
|
||||
const next = appendHistory(
|
||||
{
|
||||
@@ -465,14 +503,44 @@ export function applyEvent(
|
||||
|
||||
switch (decision.action) {
|
||||
case "ask_user":
|
||||
case "review_blackboard":
|
||||
case "review_blackboard": {
|
||||
const questions = decision.questions?.length
|
||||
? decision.questions.map((q) => ({ ...q, required: false }))
|
||||
: undefined;
|
||||
const assessment =
|
||||
decision.action === "ask_user"
|
||||
? decision.assessment?.trim() || undefined
|
||||
: undefined;
|
||||
const effects: PhaseEffect[] = [];
|
||||
if (assessment) {
|
||||
effects.push({
|
||||
type: "emit_message",
|
||||
message: `[Agent] 内容评价:\n${assessment}`,
|
||||
});
|
||||
}
|
||||
if (questions?.length) {
|
||||
effects.push({
|
||||
type: "emit_message",
|
||||
message: `[Agent] 可选追问(可跳过):\n${questions.map((q) => `- ${q.prompt}`).join("\n")}`,
|
||||
});
|
||||
}
|
||||
const inputMessage = assessment
|
||||
? assessment
|
||||
: decision.action === "review_blackboard" || !questions?.length
|
||||
? decision.reason
|
||||
: undefined;
|
||||
return {
|
||||
session: waiting(
|
||||
{ ...next, pendingDecision: undefined },
|
||||
{ kind: "input", message: decision.reason },
|
||||
{
|
||||
kind: "input",
|
||||
message: inputMessage,
|
||||
questions,
|
||||
},
|
||||
),
|
||||
effects: [],
|
||||
effects,
|
||||
};
|
||||
}
|
||||
case "finish":
|
||||
return {
|
||||
session: touch({
|
||||
@@ -580,13 +648,12 @@ export function applyEvent(
|
||||
}
|
||||
|
||||
case "worker_needs_input": {
|
||||
const questions = event.payload.questions
|
||||
.map((q) => q.trim())
|
||||
.filter(Boolean);
|
||||
const normalized =
|
||||
questions.length > 0
|
||||
? questions
|
||||
: ["请补充当前步骤所需的信息(情境、参数或你的具体设想)。"];
|
||||
let normalized = normalizeQuestions(event.payload.questions);
|
||||
if (normalized.length === 0) {
|
||||
normalized = normalizeQuestions([
|
||||
"请补充当前步骤所需的信息(情境、参数或你的具体设想)。",
|
||||
]);
|
||||
}
|
||||
const ctx: ResumeContext = {
|
||||
workerId: event.payload.workerId,
|
||||
stepId: event.payload.stepId ?? session.currentStepId,
|
||||
@@ -608,7 +675,7 @@ export function applyEvent(
|
||||
effects: [
|
||||
{
|
||||
type: "emit_message",
|
||||
message: `[Worker] ${event.payload.workerId} 提问:\n${normalized.map((q) => `- ${q}`).join("\n")}`,
|
||||
message: `[Worker] ${event.payload.workerId} 提问:\n${normalized.map((q) => `- ${q.prompt}`).join("\n")}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -618,23 +685,64 @@ export function applyEvent(
|
||||
const result = handleWorkerCompleted(session, event);
|
||||
const mode = session.acceptanceMode ?? "user_confirmed";
|
||||
if (mode === "user_confirmed" && result.session.pendingArtifactId) {
|
||||
const sidecar = asOptionalSidecarQuestions(event.payload.questions);
|
||||
const effects = [...result.effects];
|
||||
if (sidecar?.length) {
|
||||
effects.push({
|
||||
type: "emit_message",
|
||||
message: `[Worker] 可选追问(可跳过,直接接受目前产物):\n${sidecar.map((q) => `- ${q.prompt}`).join("\n")}`,
|
||||
});
|
||||
}
|
||||
return {
|
||||
...result,
|
||||
effects,
|
||||
session: waiting(result.session, {
|
||||
kind: "review_artifact",
|
||||
artifactId: result.session.pendingArtifactId,
|
||||
questions: sidecar,
|
||||
}),
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
case "user_resolved_sidecar_questions": {
|
||||
const reason = session.waitingReason;
|
||||
if (reason?.kind !== "review_artifact") {
|
||||
return fail(session, "sidecar questions require review_artifact");
|
||||
}
|
||||
const answersText = event.payload.answersText?.trim();
|
||||
const slots: Record<string, unknown> = { ...session.slots };
|
||||
const effects: PhaseEffect[] = [];
|
||||
if (answersText) {
|
||||
slots["用户.worker答复"] = answersText;
|
||||
slots["用户.最新输入"] = answersText;
|
||||
effects.push({
|
||||
type: "emit_message",
|
||||
message: `[用户] 已补充可选追问(产物仍待验收)`,
|
||||
});
|
||||
}
|
||||
const next = appendHistory({ ...session, slots }, event);
|
||||
return {
|
||||
session: waiting(touch(next), {
|
||||
kind: "review_artifact",
|
||||
artifactId: reason.artifactId,
|
||||
}),
|
||||
effects,
|
||||
};
|
||||
}
|
||||
|
||||
// ── 产物验收 ──
|
||||
case "user_accepted_artifact": {
|
||||
const artifact = findArtifact(session, event.payload.artifactId);
|
||||
if (!artifact) {
|
||||
return fail(session, `Artifact not found: ${event.payload.artifactId}`);
|
||||
}
|
||||
const slots: Record<string, unknown> = { ...session.slots };
|
||||
// 仅终稿 tag「设计.worker集」表示完整 Worker 集验收;草稿单位验收不得开 play
|
||||
if (artifact.outputTags.some((tag) => tag === "设计.worker集")) {
|
||||
slots.designInstanceReady = true;
|
||||
}
|
||||
const next = appendHistory(
|
||||
updateArtifact(session, artifact.id, { status: "accepted" }),
|
||||
event,
|
||||
@@ -643,6 +751,7 @@ export function applyEvent(
|
||||
session: touch(
|
||||
running({
|
||||
...next,
|
||||
slots,
|
||||
pendingArtifactId: undefined,
|
||||
pendingDecision: undefined,
|
||||
}),
|
||||
@@ -791,3 +900,62 @@ function formatSkillSelectionPrompt(
|
||||
const lines = skills.map((s, i) => ` ${i + 1}. ${s.name} — ${s.description}`);
|
||||
return ["请选择创作 skill(输入 name 或编号):", ...lines].join("\n");
|
||||
}
|
||||
|
||||
/** agent-first:仅 UI 引导,等用户首句后再 invoke 总管 */
|
||||
function enterAwaitFirstInput(
|
||||
session: RuntimeSession,
|
||||
skill: ActiveSkillSnapshot,
|
||||
event: RuntimeEvent,
|
||||
): ApplyEventResult {
|
||||
const next = appendHistory(
|
||||
{
|
||||
...session,
|
||||
flowId: skill.defaultFlowId ?? session.flowId,
|
||||
slots: {
|
||||
...session.slots,
|
||||
activeSkill: skill,
|
||||
},
|
||||
},
|
||||
event,
|
||||
);
|
||||
return {
|
||||
session: waiting(next, { kind: "input" }),
|
||||
effects: [],
|
||||
};
|
||||
}
|
||||
|
||||
function enterIntakeWaiting(
|
||||
session: RuntimeSession,
|
||||
skill: ActiveSkillSnapshot,
|
||||
event: RuntimeEvent,
|
||||
): ApplyEventResult {
|
||||
const next = appendHistory(
|
||||
{
|
||||
...session,
|
||||
flowId: skill.defaultFlowId ?? session.flowId,
|
||||
slots: {
|
||||
...session.slots,
|
||||
activeSkill: skill,
|
||||
},
|
||||
},
|
||||
event,
|
||||
);
|
||||
return {
|
||||
session: waiting(next, {
|
||||
kind: "intake",
|
||||
prompt: skill.startupPrompt,
|
||||
}),
|
||||
effects: [{ type: "emit_message", message: skill.startupPrompt }],
|
||||
};
|
||||
}
|
||||
|
||||
function applySkillBinding(
|
||||
session: RuntimeSession,
|
||||
skill: ActiveSkillSnapshot,
|
||||
event: RuntimeEvent,
|
||||
): ApplyEventResult {
|
||||
if (effectiveStartupMode(skill) === "agent-first") {
|
||||
return enterAwaitFirstInput(session, skill, event);
|
||||
}
|
||||
return enterIntakeWaiting(session, skill, event);
|
||||
}
|
||||
|
||||
+778
-61
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { ParsedToolCall } from "../llm/client.js";
|
||||
import type { MainAgentDecision } from "../types/runtime.js";
|
||||
import { normalizeQuestions } from "../skills/question-protocol.js";
|
||||
import {
|
||||
isMainAgentLoopTool,
|
||||
isMainAgentTerminalTool,
|
||||
@@ -60,11 +61,15 @@ export function toolCallToDecision(call: ParsedToolCall): MainAgentDecision {
|
||||
switch (name as MainAgentToolName) {
|
||||
case "ask_user": {
|
||||
const reason = requireString(args, "reason");
|
||||
const message = optionalString(args, "message");
|
||||
const assessment =
|
||||
optionalString(args, "assessment") ?? optionalString(args, "message");
|
||||
const questions = normalizeQuestions(args.questions);
|
||||
return {
|
||||
id: randomUUID(),
|
||||
action: "ask_user",
|
||||
reason: message ? `${reason}\n${message}` : reason,
|
||||
reason,
|
||||
assessment: assessment || undefined,
|
||||
questions: questions.length ? questions : undefined,
|
||||
requiresApproval: false,
|
||||
statePatchAllowed: false,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user