Add capability-probed structured outputs and a prototype/instance creation graph.

Probe API profiles for json_schema/tools, flatten worker artifacts onto a strict schema, and let users spawn or revise graph nodes instead of a one-shot DAG.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-31 01:57:22 +08:00
co-authored by Cursor
parent 872f742d3e
commit bd5d88abfa
65 changed files with 7625 additions and 942 deletions
+17
View File
@@ -435,6 +435,15 @@ export function buildFocus(
};
}
if (reason?.kind === "pick_creation_step") {
return {
actorType: "user",
actorLabel: "你",
action: "点选要做的节点",
detail: "点可进入的节点即确认并开始;虚线原型点一下增殖再进去。要改排在底栏写意见再发",
};
}
if (reason?.kind === "next_intent") {
return {
actorType: "user",
@@ -471,6 +480,14 @@ export function buildFocus(
if (reason?.kind === "review_artifact") {
const art = session.artifacts.find((a) => a.id === session.pendingArtifactId);
if (art?.workerId === "design-flow") {
return {
actorType: "user",
actorLabel: "你",
action: "点选要做的节点",
detail: "点可进入的节点即确认并开始;虚线原型点一下增殖再进去。要改排在底栏写意见再发",
};
}
const copy = reviewComposerCopy(art?.workerId, {
hasQuestions: Boolean(reason.questions?.length),
outputTags: art?.outputTags,
+13 -2
View File
@@ -123,6 +123,17 @@ export function formatAgentDisplayTitle(detail?: string): string {
return "编排器";
}
/** 流程编排产物待确认:图上点节点即确认,不走验收双按钮 */
export function isFlowPlanReview(view: {
waitingReason?: { kind?: string } | null;
reviewArtifact?: { workerId?: string | null } | null;
} | null | undefined): boolean {
return (
view?.waitingReason?.kind === "review_artifact" &&
view?.reviewArtifact?.workerId === "design-flow"
);
}
/** 底栏双按钮 + 工作面 kicker:随 waiting 产物种类变化 */
export type ReviewComposerCopy = {
/** 底栏短芯片 */
@@ -179,8 +190,8 @@ export function reviewComposerCopy(
taskLabel: "开场",
taskTitle: "选定开场白",
hint: hasQuestions
? "同意就选定此开场;要改就写意见。下方追问可选答。"
: "同意就选定此开场;要改就写意见。",
? "同意就选定此开场;要改开场白就写意见。下方追问可选答。"
: "同意就选定此开场;要改开场白就写意见。",
submitLabel: "按意见修改",
acceptLabel: "选定此开场",
placeholder: "修改意见…",
+324 -32
View File
@@ -81,8 +81,10 @@ import { Blackboard } from "../blackboard/blackboard.js";
import { parseWorkerSetYaml } from "../skills/worker-set-parse.js";
import { parseShellAdaptationFromReplyFormat } from "../skills/present-packet.js";
import {
CREATION_ACCEPTED_CONTENT_TAG,
CREATION_ACCEPTED_UNITS_TAG,
isDesignDiskWorker,
parseAcceptedContentStore,
parseAcceptedUnits,
SLOT_CREATION_ACCEPTED_UNITS,
} from "../skills/creation-units.js";
@@ -104,18 +106,26 @@ import {
CREATION_FLOW_TAG,
CREATION_PROPOSED_STEP_TAG,
CREATION_SELECTED_RECIPE_TAG,
DESIGN_FLOW_WORKER_ID,
creationFlowFromRecipeSeed,
findRecipeCatalogEntry,
formatCreationFlowForUser,
findModuleByName,
findStepByRef,
type FlowStepTitleHint,
isProgressPointerTag,
loadRecipeCatalog,
loadRecipeDetail,
loadModuleCatalog,
parseCreationFlow,
parseSelectedRecipeRef,
resolveDesignStepBinding,
stepUnitId,
stringifyCreationFlow,
stripReviseSteps,
type CreationFlow,
type CreationFlowUserView,
type ModuleCatalog,
type RecipeCatalogEntry,
} from "../skills/creation-flow.js";
import { loadSkill } from "../skills/loader.js";
@@ -260,6 +270,8 @@ export type SessionAction =
| { type: "approve"; label: string }
| { type: "accept"; label: string }
| { type: "reject"; label: string }
| { type: "replan"; label: string }
| { type: "leave_step"; label: string }
| { type: "run_outline"; label: string }
| { type: "finish"; label: string };
@@ -297,6 +309,8 @@ export type ProposedNextStepView = {
/** 步骤怎么跑:revise = 回头修改既有产物 */
mode?: "fresh" | "revise";
revises?: string;
/** 收口节点(开场白):确认开干前仍可改编排追加前序节点 */
closer?: boolean;
};
export type ReviewArtifactView = {
@@ -325,6 +339,8 @@ type ManagedSession = {
branchState: MessageBranchState;
/** 当前能力包可选初始配方(用户手动选) */
recipeOptions?: RecipeCatalogEntry[];
/** 模块目录(分层图 / 可反复增殖) */
moduleCatalog?: ModuleCatalog | null;
/** 开局模块 opening,缓存给意图页 */
openingGuide?: { stepId: string; stepName: string; text: string } | null;
/** 当前 LLM 请求的取消器(停止并重试) */
@@ -540,6 +556,7 @@ export class SessionManager {
if (bookId) {
this.activeBookSessions.set(bookId, id);
appendBookSession(bookId, id);
this.persist(managed);
}
return this.toView(id);
}
@@ -557,7 +574,9 @@ export class SessionManager {
}
get(id: string): SessionView | null {
if (!this.sessions.has(id)) return null;
const s = this.sessions.get(id);
if (!s) return null;
if (s.runtime.recoverOrphanedRun()) this.persist(s);
return this.toView(id);
}
@@ -825,7 +844,8 @@ export class SessionManager {
kind !== "review_artifact" &&
kind !== "approve_step" &&
kind !== "revision" &&
kind !== "next_intent"
kind !== "next_intent" &&
kind !== "pick_creation_step"
) {
return;
}
@@ -1079,8 +1099,7 @@ export class SessionManager {
} catch (err) {
const detail = err instanceof Error ? err.message : String(err);
console.error("[会话] 发送消息失败", detail);
s.messages.push(this.msg("system", formatRuntimeError(detail)));
if (s.bookId) this.syncBookPreview(s.bookId, s.messages);
await this.recoverFailedRun(s, detail);
}
this.syncCreationDialogue(s);
this.persist(s);
@@ -1315,8 +1334,7 @@ export class SessionManager {
} catch (err) {
const detail = err instanceof Error ? err.message : String(err);
console.error("[会话] 停止并重试失败", detail);
s.messages.push(this.msg("system", formatRuntimeError(detail)));
if (s.bookId) this.syncBookPreview(s.bookId, s.messages);
await this.recoverFailedRun(s, detail);
}
this.syncCreationDialogue(s);
this.persist(s);
@@ -1462,7 +1480,7 @@ export class SessionManager {
try {
const owned = await this.runExclusive(id, s, async () => {
if (waiting?.kind === "approve_step") {
await s.runtime.rejectStep(reason ?? "用户暂不执行");
await s.runtime.leaveCreationStep();
} else if (waiting?.kind === "review_artifact") {
await s.runtime.rejectArtifact(reason ?? "用户要求重新来");
} else {
@@ -1476,9 +1494,174 @@ export class SessionManager {
s.messages.push(this.msg("system", formatRuntimeError(detail)));
if (s.bookId) this.syncBookPreview(s.bookId, s.messages);
}
this.syncCreationDialogue(s);
this.persist(s);
return this.toView(id);
}
/** 离开当前技能步,回到分层图点选 */
async leaveStep(id: string): Promise<SessionView> {
const s = this.require(id);
try {
const owned = await this.runExclusive(id, s, () =>
s.runtime.leaveCreationStep(),
);
if (!owned) return this.toView(id);
} catch (err) {
const detail = err instanceof Error ? err.message : String(err);
console.error("[会话] 返回节点失败", detail);
s.messages.push(this.msg("system", formatRuntimeError(detail)));
if (s.bookId) this.syncBookPreview(s.bookId, s.messages);
}
this.syncCreationDialogue(s);
this.persist(s);
return this.toView(id);
}
/** 开场白等步骤上发现漏了前序节点:再进流程编排,不整局重开 */
async replan(id: string, reason?: string): Promise<SessionView> {
const s = this.require(id);
const note = reason?.trim() || "回到流程编排,追加或改排节点。";
this.clearAgentThinking(id);
recordPreMessageCheckpoint(
s.branchState,
s.messages.length,
this.captureCheckpoint(s),
);
s.messages.push(this.msg("user", note));
this.syncCreationDialogue(s);
if (s.bookId) this.syncBookPreview(s.bookId, s.messages);
try {
const owned = await this.runExclusive(id, s, () =>
s.runtime.replanCreationFlow(note),
);
if (!owned) return this.toView(id);
} catch (err) {
const detail = err instanceof Error ? err.message : String(err);
console.error("[会话] 再编排失败", detail);
s.messages.push(this.msg("system", formatRuntimeError(detail)));
if (s.bookId) this.syncBookPreview(s.bookId, s.messages);
}
this.syncCreationDialogue(s);
this.persist(s);
return this.toView(id);
}
async pickStep(
id: string,
stepId: string,
stepParams?: Record<string, unknown>,
reenter?: boolean,
): Promise<SessionView> {
const s = this.require(id);
try {
const owned = await this.runExclusive(id, s, async () => {
await this.confirmPendingFlowPlan(s);
const waiting = s.runtime.getSession().waitingReason?.kind;
if (waiting !== "pick_creation_step") {
throw new Error("当前不是点选节点的时机");
}
await s.runtime.pickCreationStep(stepId, stepParams, {
reenter: reenter === true,
});
});
if (!owned) return this.toView(id);
} catch (err) {
const detail = err instanceof Error ? err.message : String(err);
console.error("[会话] 点选节点失败", detail);
s.messages.push(this.msg("system", formatRuntimeError(detail)));
}
this.persist(s);
return this.toView(id);
}
async getStepArtifact(
id: string,
stepId: string,
): Promise<{
stepId: string;
name: string;
tag: string | null;
content: string;
}> {
const s = this.require(id);
const catalog = s.moduleCatalog;
const flow = parseCreationFlow(
s.runtime.getBlackboard().getContentByTag(CREATION_FLOW_TAG),
);
if (!flow) throw new Error("还没有工作流计划");
const cleaned = stripReviseSteps(flow);
const step = findStepByRef(cleaned, stepId.trim());
if (!step) throw new Error(`找不到节点:${stepId}`);
const mod = findModuleByName(catalog, step.name);
const tag = mod?.artifact?.trim() || null;
const content = tag
? s.runtime.getBlackboard().getContentByTag(tag)?.trim() || ""
: "";
return { stepId: step.id, name: step.name, tag, content };
}
async spawnStep(id: string, moduleName: string): Promise<SessionView> {
const s = this.require(id);
try {
const owned = await this.runExclusive(id, s, async () => {
await this.confirmPendingFlowPlan(s);
const waiting = s.runtime.getSession().waitingReason?.kind;
if (waiting !== "pick_creation_step") {
throw new Error("当前不是追加节点的时机");
}
await s.runtime.spawnCreationStep(moduleName);
});
if (!owned) return this.toView(id);
} catch (err) {
const detail = err instanceof Error ? err.message : String(err);
console.error("[会话] 追加节点失败", detail);
s.messages.push(this.msg("system", formatRuntimeError(detail)));
}
this.persist(s);
return this.toView(id);
}
/** 已点进技能步、尚未验收:可退回分层图(误点可增殖节点时用) */
private canOfferLeaveCreationStep(
s: ManagedSession,
reason: WaitingReason | undefined,
): boolean {
const session = s.runtime.getSession();
if (
isPlayLayerActive(session.slots) ||
inferLifecycleStage(session) === "play"
) {
return false;
}
const kind = reason?.kind;
if (
kind !== "worker_questions" &&
kind !== "input" &&
kind !== "revision"
) {
return false;
}
const stepId =
s.runtime.getBlackboard().getContentByTag(CREATION_CURRENT_STEP_TAG)?.trim() ||
session.currentStepId?.trim() ||
"";
return Boolean(stepId);
}
/** 流程编排还停在核对态时,点节点先收下计划再进入 */
private async confirmPendingFlowPlan(s: ManagedSession): Promise<void> {
const session = s.runtime.getSession();
if (session.waitingReason?.kind !== "review_artifact") return;
const art = session.pendingArtifactId
? session.artifacts.find((a) => a.id === session.pendingArtifactId)
: undefined;
if (art?.workerId !== DESIGN_FLOW_WORKER_ID) {
throw new Error("当前不是点选节点的时机");
}
await s.runtime.acceptArtifact();
}
async runOutline(id: string): Promise<SessionView> {
const s = this.require(id);
try {
@@ -1586,6 +1769,7 @@ export class SessionManager {
this.activeBookSessions.set(params.bookId, params.sessionId);
appendBookSession(params.bookId, params.sessionId);
await this.refreshRecipeOptions(managedRef);
runtime.recoverOrphanedRun();
this.persist(managedRef);
await this.maybeKickAgentAfterDemand(runtime);
@@ -1891,6 +2075,20 @@ export class SessionManager {
}
}
/** 执行失败后离开 running,避免界面一直转圈。 */
private async recoverFailedRun(
s: ManagedSession,
detail: string,
): Promise<void> {
const formatted = formatRuntimeError(detail);
if (s.runtime.getSession().phase === "running") {
await s.runtime.failRun(formatted);
} else {
s.messages.push(this.msg("system", formatted));
}
if (s.bookId) this.syncBookPreview(s.bookId, s.messages);
}
private enrichDisplayMessages(
session: RuntimeSession,
messages: ChatMessage[],
@@ -2202,11 +2400,21 @@ export class SessionManager {
} else {
actions.push({ type: "approve", label: "确认执行" });
}
actions.push({ type: "reject", label: "暂不执行" });
actions.push({ type: "reject", label: "返回节点" });
actions.push({
type: "send_message",
label: "说明意见",
placeholder: "改排、回头改某步,或说明不要这一步…",
placeholder: "改排、补节点(如 NPC)、回头改某步,或说明不要这一步…",
});
} else if (reason?.kind === "pick_creation_step") {
hints.push(
"点可进入的节点即确认并开始。虚线原型点一下会增殖一条实例再进去。要改排在底栏写意见再发。",
);
actions.push({ type: "replan", label: "发送" });
actions.push({
type: "send_message",
label: "发送",
placeholder: "例如:补舞台骨架、把某步改到开场白之前…",
});
} else if (reason?.kind === "next_intent") {
hints.push("下一步想写什么?可留空;也可说回头改某步。发送后会展示下一节点供你确认。");
@@ -2220,15 +2428,30 @@ export class SessionManager {
const copy = reviewComposerCopy(art?.workerId, {
hasQuestions: Boolean(reason.questions?.length),
});
actions.push({ type: "accept", label: copy.acceptLabel });
actions.push({ type: "reject", label: "不接受" });
actions.push({
type: "send_message",
label: copy.submitLabel,
placeholder: `${copy.placeholder};在现有产物上改,不整份重做…`,
});
if (art?.workerId === DESIGN_FLOW_WORKER_ID) {
hints.push(
"点可进入的节点即确认并开始。虚线原型点一下会增殖一条实例再进去。要改排在底栏写意见再发。",
);
actions.push({
type: "send_message",
label: "发送",
placeholder: "例如:补舞台骨架、把某步改到开场白之前…",
});
} else {
actions.push({ type: "accept", label: copy.acceptLabel });
actions.push({ type: "reject", label: "不接受" });
actions.push({
type: "send_message",
label: copy.submitLabel,
placeholder: `${copy.placeholder};在现有产物上改,不整份重做…`,
});
}
if (reason.questions?.length) {
hints.push(`下方追问可选;${copy.acceptLabel}即收起(表示无需再完善)`);
hints.push(
art?.workerId === DESIGN_FLOW_WORKER_ID
? "下方追问可选;点节点进入即收起"
: `下方追问可选;${copy.acceptLabel}即收起(表示无需再完善)`,
);
actions.push({ type: "answer_questions", label: "提交作答" });
actions.push({ type: "skip_questions", label: "跳过追问" });
}
@@ -2281,6 +2504,10 @@ export class SessionManager {
});
}
if (this.canOfferLeaveCreationStep(s, reason)) {
actions.push({ type: "leave_step", label: "返回节点" });
}
if (s.bookId) {
this.persist(s);
}
@@ -2330,7 +2557,12 @@ export class SessionManager {
effectiveStartupMode(activeSnap) === "agent-first"
? activeSnap.uiPrompt?.trim() || DEFAULT_UI_PROMPT
: undefined,
reviewArtifact: buildReviewArtifactView(session, messages, s.runtime.getBlackboard()),
reviewArtifact: buildReviewArtifactView(
session,
messages,
s.runtime.getBlackboard(),
s.moduleCatalog,
),
workerSetView: buildWorkerSetUserView(
s.runtime.getBlackboard(),
s.runtime.getSession(),
@@ -2338,6 +2570,7 @@ export class SessionManager {
creationFlowView: buildCreationFlowUserView(
s.runtime.getBlackboard(),
session.slots[SLOT_CREATION_ACCEPTED_UNITS],
s.moduleCatalog,
),
proposedNextStep:
reason?.kind === "approve_step"
@@ -2448,6 +2681,7 @@ export class SessionManager {
const skillName = s.runtime.getActiveSkill()?.name;
if (!skillName) {
s.recipeOptions = [];
s.moduleCatalog = null;
s.openingGuide = null;
return;
}
@@ -2455,13 +2689,19 @@ export class SessionManager {
const skill = await loadSkill(skillName);
if (!skill.skillPackRoot) {
s.recipeOptions = [];
s.moduleCatalog = null;
s.openingGuide = null;
return;
}
const catalog = await loadRecipeCatalog(skill.skillPackRoot);
const [catalog, modules] = await Promise.all([
loadRecipeCatalog(skill.skillPackRoot),
loadModuleCatalog(skill.skillPackRoot),
]);
s.recipeOptions = catalog?.recipes ?? [];
s.moduleCatalog = modules;
} catch {
s.recipeOptions = [];
s.moduleCatalog = null;
}
await this.ensureRecipeSeededFlow(s);
await this.refreshOpeningGuide(s);
@@ -2489,11 +2729,7 @@ export class SessionManager {
const detail = await loadRecipeDetail(packRoot, entry);
const seeded = creationFlowFromRecipeSeed(detail);
if (!seeded) return;
board.write({
tag: CREATION_FLOW_TAG,
content: stringifyCreationFlow(seeded),
source: "runtime",
});
await this.commitSeededCreationFlow(s, seeded);
} catch (err) {
console.error("[会话] 配方预置失败", err);
}
@@ -2553,6 +2789,7 @@ export class SessionManager {
s: ManagedSession,
reason: WaitingReason | undefined,
): { stepId: string; stepName: string; text: string } | null {
if (reason?.kind === "pick_creation_step") return null;
if (reason?.kind === "worker_questions") {
const qs = normalizeQuestions(reason.questions);
if (isModuleOpeningQuestions(qs)) {
@@ -2584,6 +2821,44 @@ export class SessionManager {
return s.openingGuide ?? null;
}
/** 写入剧本 seed,并立刻进入点选图(开局节点跟配方走)。 */
private async commitSeededCreationFlow(
s: ManagedSession,
seeded: CreationFlow,
): Promise<void> {
this.writeSeededCreationFlow(s, seeded);
await s.runtime.enterSeededCreationPick();
}
/** 配方预置 DAG:写入黑板并留下可回退的编排检查点(不必先跑 LLM) */
private writeSeededCreationFlow(s: ManagedSession, seeded: CreationFlow): void {
s.runtime.getBlackboard().write({
tag: CREATION_FLOW_TAG,
content: stringifyCreationFlow(seeded),
source: "runtime",
});
const alreadyRecorded = s.messages.some(
(m) =>
m.kind === "worker_output" &&
m.actor === "design-flow" &&
(m.text ?? "").includes("配方预置近期起点"),
);
if (alreadyRecorded) return;
const names = seeded.steps.map((step) => step.name).join(" → ");
recordPreMessageCheckpoint(
s.branchState,
s.messages.length,
this.captureCheckpoint(s),
);
s.messages.push(
this.msg(
"system",
`[Worker] design-flow 已完成\n\n配方预置近期起点${names ? `:${names}` : ""}。\n这是可回退的编排检查点;之后可再编排追加节点(如生成规则 / 具体实例)。`,
),
);
this.syncCreationDialogue(s);
}
private async applySelectedRecipe(
s: ManagedSession,
recipeId: string,
@@ -2606,7 +2881,7 @@ export class SessionManager {
source: "user",
});
// 剧本已钉死开局模块 → 默认写入近期起点,开局即坐在 DAG 第一步(不必先跑 design-flow)
// 开局节点跟剧本 seed:写入近期起点并进入点选图(不必先跑 design-flow / 先描述需求)
const existingFlow = board.getContentByTag(CREATION_FLOW_TAG)?.trim();
if (!existingFlow) {
const skillName = s.runtime.getActiveSkill()?.name;
@@ -2618,11 +2893,7 @@ export class SessionManager {
const detail = await loadRecipeDetail(packRoot, entry);
const seeded = creationFlowFromRecipeSeed(detail);
if (seeded) {
board.write({
tag: CREATION_FLOW_TAG,
content: stringifyCreationFlow(seeded),
source: "runtime",
});
await this.commitSeededCreationFlow(s, seeded);
}
}
} catch (err) {
@@ -2652,6 +2923,7 @@ function collectFilledTags(blackboard: Blackboard): string[] {
function buildCreationFlowUserView(
blackboard: Blackboard,
acceptedRaw?: unknown,
catalog?: ModuleCatalog | null,
): CreationFlowUserView | undefined {
const raw = blackboard.getContentByTag(CREATION_FLOW_TAG)?.trim();
if (!raw) return undefined;
@@ -2662,7 +2934,24 @@ function buildCreationFlowUserView(
const accepted = parseAcceptedUnits(
acceptedRaw ?? blackboard.getContentByTag(CREATION_ACCEPTED_UNITS_TAG),
);
return formatCreationFlowForUser(flow, undefined, accepted);
const titleHints: Record<string, FlowStepTitleHint> = {};
const store = parseAcceptedContentStore(
blackboard.getContentByTag(CREATION_ACCEPTED_CONTENT_TAG),
);
for (const [id, entry] of Object.entries(store.units)) {
if (!entry.summary && entry.content == null) continue;
titleHints[id] = {
...(entry.summary ? { summary: entry.summary } : {}),
...(entry.content != null ? { content: entry.content } : {}),
};
}
return formatCreationFlowForUser(
flow,
catalog,
accepted,
collectFilledTags(blackboard),
titleHints,
);
}
function readProposedNextStep(
@@ -2708,6 +2997,7 @@ function readProposedNextStep(
intent: typeof doc.intent === "string" ? doc.intent : undefined,
mode: doc.mode === "revise" ? "revise" : undefined,
revises: typeof doc.revises === "string" ? doc.revises : undefined,
closer: doc.closer === true ? true : undefined,
};
} catch {
return undefined;
@@ -2767,6 +3057,7 @@ function buildReviewArtifactView(
session: RuntimeSession,
messages: ChatMessage[],
blackboard: Blackboard,
catalog?: ModuleCatalog | null,
): ReviewArtifactView | undefined {
if (session.waitingReason?.kind !== "review_artifact") return undefined;
const art = session.artifacts.find((a) => a.id === session.pendingArtifactId);
@@ -2869,6 +3160,7 @@ function buildReviewArtifactView(
? buildCreationFlowUserView(
blackboard,
session.slots[SLOT_CREATION_ACCEPTED_UNITS],
catalog,
)
: undefined;
@@ -2896,7 +3188,7 @@ function formatRuntimeError(detail: string): string {
if (detail.includes("401") || detail.toLowerCase().includes("authentication")) {
return (
"[请求失败] API Key 无效或未授权。请到「设置 → API 配置」检查 Key 与 Base URL," +
"或点击「测试连接」验证。"
"或点击「探测」验证。"
);
}
if (detail.startsWith("LLM request failed")) {
+38 -11
View File
@@ -4,9 +4,13 @@ import {
deleteApiProfile,
getApiProfile,
listApiProfiles,
testApiProfile,
probeAndSaveProfileCapabilities,
updateApiProfile,
} from "../config/api-profiles.js";
import {
deliveryModeLabel,
resolveStructuredDeliveryMode,
} from "../llm/capabilities.js";
import {
ensureActiveProfileDefault,
loadAppSettings,
@@ -120,12 +124,14 @@ export async function handleSettingsApi(
baseUrl?: string;
apiKey?: string;
model?: string;
reasoningEffort?: string;
};
const profile = createApiProfile({
name: body.name ?? "新配置",
baseUrl: body.baseUrl ?? "https://api.deepseek.com",
apiKey: body.apiKey ?? "",
model: body.model ?? "deepseek-v4-pro",
reasoningEffort: body.reasoningEffort,
});
const activate = (body as { activate?: boolean }).activate === true;
if (activate) {
@@ -136,19 +142,34 @@ export async function handleSettingsApi(
return true;
}
const profileMatch = pathname.match(/^\/api\/profiles\/([^/]+)(\/test)?$/);
const profileMatch = pathname.match(
/^\/api\/profiles\/([^/]+)(\/probe-capabilities)?$/,
);
if (profileMatch) {
const id = decodeURIComponent(profileMatch[1]);
const isTest = profileMatch[2] === "/test";
const suffix = profileMatch[2];
if (isTest && req.method === "POST") {
const profile = getApiProfile(id);
if (!profile) {
json(res, 404, { error: "配置不存在" });
return true;
if (suffix === "/probe-capabilities" && req.method === "POST") {
try {
const profile = await probeAndSaveProfileCapabilities(id);
const mode = resolveStructuredDeliveryMode(profile.capabilities);
const settings = loadAppSettings();
const reloadedSessions =
settings.activeProfileId === id
? sessionManager.reloadAllLlms()
: 0;
json(res, 200, {
ok: profile.capabilities?.chat === "ok",
profile,
deliveryMode: mode,
deliveryModeLabel: deliveryModeLabel(mode),
reloadedSessions,
});
} catch (err) {
json(res, 400, {
error: err instanceof Error ? err.message : "能力探测失败",
});
}
const result = await testApiProfile(profile);
json(res, 200, result);
return true;
}
@@ -168,10 +189,16 @@ export async function handleSettingsApi(
baseUrl?: string;
apiKey?: string;
model?: string;
reasoningEffort?: string;
};
try {
const profile = updateApiProfile(id, body);
json(res, 200, { profile });
const settings = loadAppSettings();
const reloadedSessions =
settings.activeProfileId === id
? sessionManager.reloadAllLlms()
: 0;
json(res, 200, { profile, reloadedSessions });
} catch (err) {
json(res, 404, {
error: err instanceof Error ? err.message : "更新失败",
+41
View File
@@ -123,6 +123,18 @@ const server = createServer(async (req, res) => {
return;
}
if (req.method === "GET" && sub === "/step-artifact") {
try {
const stepId = url.searchParams.get("stepId") ?? "";
const data = await sessionManager.getStepArtifact(sessionId, stepId);
json(res, 200, data);
} catch (err) {
const msg = err instanceof Error ? err.message : "读取失败";
json(res, msg.includes("不存在") ? 404 : 400, { error: msg });
}
return;
}
if (req.method === "POST" && sub === "/messages") {
const body = JSON.parse(await readBody(req)) as {
text?: string;
@@ -445,6 +457,10 @@ const server = createServer(async (req, res) => {
action?: string;
stepParams?: Record<string, unknown>;
openingIndex?: number;
reason?: string;
stepId?: string;
moduleName?: string;
reenter?: boolean;
};
let view;
try {
@@ -474,6 +490,31 @@ const server = createServer(async (req, res) => {
case "reject":
view = await sessionManager.reject(sessionId);
break;
case "replan":
view = await sessionManager.replan(
sessionId,
typeof body.reason === "string" ? body.reason : undefined,
);
break;
case "leave_step":
view = await sessionManager.leaveStep(sessionId);
break;
case "pick_step":
view = await sessionManager.pickStep(
sessionId,
typeof body.stepId === "string" ? body.stepId : "",
body.stepParams && typeof body.stepParams === "object"
? body.stepParams
: undefined,
body.reenter === true,
);
break;
case "spawn_step":
view = await sessionManager.spawnStep(
sessionId,
typeof body.moduleName === "string" ? body.moduleName : "",
);
break;
case "run_outline":
view = await sessionManager.runOutline(sessionId);
break;