feat(contracts,db): agent.plan / agent.propose_options, interaction events, proposeFirst replaces run mode

- agent_run_event gains plan_updated, options_proposed/resolved, approval_requested/resolved (migration 0040)
- AgentRunStartInput.proposeFirst; mode deprecated (plan_only maps to proposeFirst); mode dropped from AgentRun API
- POST /agent/runs/{id}/choice; agent.optionsTimeoutSec/optionsTimeoutAction/approvalTimeoutSec settings

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 21:07:01 +08:00
1 parent d931e75310
commit 74c2ed45aa
12 files changed
+379 -12

No files matched your search

@@ -15,8 +15,8 @@ const TiB = 1024 ** 4;
const SITE_IDS = ["site.list", "site.health_check", "site.refresh_login", "site.user_data"] as const;
describe("站点能力规格", () => {
it("进入首批能力目录(17 + 4 站点 + 4 浏览器 = 25);类别 / 风险 / 重放按约定", () => {
expect(CAPABILITY_IDS).toHaveLength(25);
it("进入首批能力目录(17 + 4 站点 + 4 浏览器 + 2 会话交互 = 27);类别 / 风险 / 重放按约定", () => {
expect(CAPABILITY_IDS).toHaveLength(27);
for (const id of SITE_IDS) expect(CAPABILITY_IDS).toContain(id);
expect(capabilitySpec("site.list")).toMatchObject({ kind: "read", riskLevel: "low", replay: "safe" });
expect(capabilitySpec("site.user_data")).toMatchObject({ kind: "read", riskLevel: "low", replay: "safe" });
+142
View File
@@ -19,6 +19,11 @@ export const AgentRunEventType = z.enum([
"subagent_spawned",
"subagent_event",
"subagent_completed",
"plan_updated",
"options_proposed",
"options_resolved",
"approval_requested",
"approval_resolved",
]);
export type AgentRunEventType = z.infer<typeof AgentRunEventType>;
@@ -111,6 +116,90 @@ const SubagentCompleted = z.object({
toolCalls: NonNegInt,
});
// 自动化优先的会话交互(AG-2):模型用 agent.plan 维护步骤清单,用 agent.propose_options 让用户在方案里选;
// 需审批的能力在对应步骤处出审批卡。这些事件都由会话的运行进程写入(单写者):用户在挂起期间作的选择 / 审批,
// 在会话恢复、重放挂起的调用时才记为 *_resolved。
export const AgentPlanStepStatus = z.enum(["pending", "running", "done", "skipped", "failed"]);
export type AgentPlanStepStatus = z.infer<typeof AgentPlanStepStatus>;
/** 步骤清单的一步;id 由模型给,同一会话内稳定(更新时按 id 合并) */
export const AgentPlanStep = z.object({
id: z.string().trim().min(1).max(40),
title: z.string().trim().min(1).max(200),
status: AgentPlanStepStatus,
note: z.string().max(500).nullable().default(null),
});
export type AgentPlanStep = z.infer<typeof AgentPlanStep>;
/** 给用户选的一个方案 */
export const AgentOptionItem = z.object({
id: z.string().trim().min(1).max(40),
title: z.string().trim().min(1).max(200),
summary: z.string().trim().min(1).max(1000),
risks: z.array(z.string().max(300)).max(8).default([]),
/** 估算代价(体积 / 时长 / 费用),模型给出的文本,未经核查 */
estimatedCost: z.string().max(200).nullable().default(null),
});
export type AgentOptionItem = z.infer<typeof AgentOptionItem>;
/** 超时未选时的处理:recommended = 自动选推荐项;stop = 不选,模型收尾 */
export const AgentOptionsTimeoutAction = z.enum(["recommended", "stop"]);
export type AgentOptionsTimeoutAction = z.infer<typeof AgentOptionsTimeoutAction>;
const PlanUpdated = z.object({
type: z.literal("plan_updated"),
steps: z.array(AgentPlanStep),
/** 本会话第几版计划(从 1 起) */
revision: z.number().int().min(1),
});
const OptionsProposed = z.object({
type: z.literal("options_proposed"),
/** 挂起的工具调用(恢复时重放) */
invocationId: z.string(),
/** 对应的待处理事项(用户也可以在待处理中心选) */
decisionId: Id,
question: z.string(),
options: z.array(AgentOptionItem),
recommendedId: z.string().nullable(),
/** 出现在哪一步(提出时正在进行的步骤) */
stepId: z.string().nullable(),
expiresAt: Timestamp,
timeoutAction: AgentOptionsTimeoutAction,
});
const OptionsResolved = z.object({
type: z.literal("options_resolved"),
invocationId: z.string(),
decisionId: Id,
/** chosen = 选了 choice;rejected = 都不要(reason 是用户说明);stop = 超时按设置停止 */
outcome: z.enum(["chosen", "rejected", "stop"]),
choice: z.string().nullable(),
reason: z.string().nullable(),
by: z.enum(["user", "timeout"]),
});
const ApprovalRequested = z.object({
type: z.literal("approval_requested"),
approvalId: Id,
decisionId: Id.nullable(),
invocationId: z.string(),
capabilityId: z.string(),
planType: z.string(),
planId: Id,
payloadHash: z.string(),
expectedRevision: z.number().int().min(0),
riskLevel: z.enum(["low", "medium", "high"]),
/** 计划摘要(已脱敏截断) */
summary: z.string(),
stepId: z.string().nullable(),
expiresAt: Timestamp,
});
const ApprovalResolved = z.object({
type: z.literal("approval_resolved"),
approvalId: Id,
status: z.enum(["approved", "rejected", "expired", "revoked", "invalid"]),
reason: z.string().nullable(),
decidedBy: z.string().nullable(),
});
/** 事件主体(不含 runId/seq/at) */
export const AgentRunEventBody = z.discriminatedUnion("type", [
RunStarted,
@@ -125,6 +214,11 @@ export const AgentRunEventBody = z.discriminatedUnion("type", [
SubagentSpawned,
SubagentEvent,
SubagentCompleted,
PlanUpdated,
OptionsProposed,
OptionsResolved,
ApprovalRequested,
ApprovalResolved,
]);
export type AgentRunEventBody = z.infer<typeof AgentRunEventBody>;
@@ -150,6 +244,11 @@ export const AgentRunEvent = z.discriminatedUnion("type", [
SubagentSpawned.extend(EventMeta),
SubagentEvent.extend(EventMeta),
SubagentCompleted.extend(EventMeta),
PlanUpdated.extend(EventMeta),
OptionsProposed.extend(EventMeta),
OptionsResolved.extend(EventMeta),
ApprovalRequested.extend(EventMeta),
ApprovalResolved.extend(EventMeta),
]);
export type AgentRunEvent = AgentRunEventBody & { runId: string; seq: number; at: string };
@@ -230,6 +329,20 @@ export function agentRunEventToBusEvent(e: AgentRunEvent): TypedEvent | null {
payload: { runId: e.runId, runSeq: e.seq, capabilityId: "agent.spawn", phase, summary: summary.slice(0, 600) },
};
}
// 计划 / 方案 / 审批:借 agent.tool_call 提示会话页"有新事件"(页面按 seq 补读),摘要给全局通知用
const hint = interactionHint(e);
if (hint) {
return {
type: "agent.tool_call",
payload: {
runId: e.runId,
runSeq: e.seq,
capabilityId: hint.capabilityId,
phase: hint.phase,
summary: hint.summary,
},
};
}
if (e.type === "text_delta") {
return { type: "agent.text_delta", payload: { runId: e.runId, runSeq: e.seq, delta: e.delta } };
}
@@ -240,3 +353,32 @@ export function agentRunEventToBusEvent(e: AgentRunEvent): TypedEvent | null {
}
return null;
}
function interactionHint(
e: AgentRunEventBody,
): { capabilityId: string; phase: "started" | "succeeded" | "failed" | "denied"; summary: string } | null {
switch (e.type) {
case "plan_updated": {
const done = e.steps.filter((s) => s.status === "done").length;
return { capabilityId: "agent.plan", phase: "succeeded", summary: `步骤 ${done}/${e.steps.length}` };
}
case "options_proposed":
return { capabilityId: "agent.propose_options", phase: "started", summary: e.question.slice(0, 600) };
case "options_resolved":
return {
capabilityId: "agent.propose_options",
phase: e.outcome === "chosen" ? "succeeded" : "failed",
summary: e.outcome === "chosen" ? `已选 ${e.choice}` : e.outcome === "rejected" ? "都不要" : "超时未选",
};
case "approval_requested":
return { capabilityId: e.capabilityId, phase: "started", summary: `等待审批:${e.summary}`.slice(0, 600) };
case "approval_resolved":
return {
capabilityId: "approval",
phase: e.status === "approved" ? "succeeded" : "denied",
summary: `审批${e.status}${e.reason ? `:${e.reason}` : ""}`.slice(0, 600),
};
default:
return null;
}
}
+24 -5
View File
@@ -17,7 +17,11 @@ export const AgentRunStatus = z.enum([
"budget_exceeded",
]);
export type AgentRunStatus = z.infer<typeof AgentRunStatus>;
/** execute:授权范围内边执行边汇报;plan_only:"先给方案",只允许 read/preview/propose 类能力 */
/**
* 内部能力上限(agent_run.mode 列,不再出现在 API 与界面上):execute = 按白名单与授权执行;
* plan_only = 只允许 read/preview/propose 类能力(后台只读调查、只读子会话用)。
* 用户会话一律 execute;"先看方案再执行"是会话选项(scope.proposeFirst),见 AgentRunStartInput。
*/
export const AgentRunMode = z.enum(["execute", "plan_only"]);
export type AgentRunMode = z.infer<typeof AgentRunMode>;
@@ -65,7 +69,6 @@ export const AgentRun = EntityBase.extend({
startedAt: Timestamp.nullable(),
finishedAt: Timestamp.nullable(),
createdBy: z.string().nullable(),
mode: AgentRunMode,
/** 后台触发按根因去重(如 investigate:<decisionId>);用户委托为 null */
dedupeKey: z.string().nullable(),
/** 派生深度:用户/后台发起的会话为 0,agent.spawn 派生的子会话 = 父 + 1 */
@@ -321,8 +324,14 @@ export type AutomationGrantUpdate = z.infer<typeof AutomationGrantUpdate>;
/** POST /agent/runs:建 agent_run + 入队持久任务 runAgent(不在 HTTP 请求里跑模型) */
export const AgentRunStartInput = z.object({
goal: z.string().trim().min(1).max(8000),
mode: AgentRunMode.default("execute"),
/** 能力白名单(缺省按模式给全部可用能力);plan_only 下超出 read/preview/propose 的会被拒绝 */
/**
* 先看方案再执行:模型先调查,用 agent.propose_options 给方案,用户选定前服务端拒绝有副作用的执行类能力。
* 缺省关(自动化优先:按步骤清单直接执行,需授权的动作在步骤处审批)。
*/
proposeFirst: z.boolean().default(false),
/** @deprecated 已取消运行模式;plan_only 按 proposeFirst=true 处理,execute 忽略 */
mode: AgentRunMode.optional().describe("已弃用:plan_only 等同 proposeFirst=true"),
/** 能力白名单(缺省全部可用能力) */
capabilityAllowlist: z.array(KnownCapabilityId).min(1).max(50).optional(),
});
export type AgentRunStartInput = z.infer<typeof AgentRunStartInput>;
@@ -330,6 +339,17 @@ export type AgentRunStartInput = z.infer<typeof AgentRunStartInput>;
export const AgentRunMessageInput = z.object({ text: z.string().trim().min(1).max(8000) });
export type AgentRunMessageInput = z.infer<typeof AgentRunMessageInput>;
/**
* 回答 agent.propose_options:选一个方案(optionId),或都不要(optionId=null,reason 写原因)。
* 会话挂起在这个问题上;回答后会话从这里继续。
*/
export const AgentRunChoiceInput = z.object({
decisionId: Id,
optionId: z.string().min(1).max(40).nullable(),
reason: z.string().trim().max(1000).optional(),
});
export type AgentRunChoiceInput = z.infer<typeof AgentRunChoiceInput>;
export const AgentRunAbortInput = z.object({ reason: z.string().max(500).optional() });
export type AgentRunAbortInput = z.infer<typeof AgentRunAbortInput>;
@@ -398,7 +418,6 @@ export const AgentRunNode = z.object({
parentRunId: Id.nullable(),
goal: z.string(),
status: AgentRunStatus,
mode: AgentRunMode,
depth: NonNegInt,
spawnReason: z.string().nullable(),
toolCallCount: NonNegInt,
+11 -1
View File
@@ -1206,7 +1206,7 @@ export const Api = {
method: "post",
path: "/agent/runs",
scope: "organize.execute",
summary: "委托目标:建 agent_run 并入队 runAgent(execute | plan_only)",
summary: "委托目标:建 agent_run 并入队 runAgent(proposeFirst = 先看方案再执行;mode 已弃用)",
body: agent.AgentRunStartInput,
response: agent.AgentRun,
}),
@@ -1219,6 +1219,16 @@ export const Api = {
body: agent.AgentRunMessageInput,
response: agent.AgentRun,
}),
// 回答 agent.propose_options(会话挂起等待中):选一个方案或都不要,会话随即继续
choose: ep({
method: "post",
path: "/agent/runs/{id}/choice",
scope: "organize.execute",
summary: "回答方案选择:选一个方案或都不要(说明原因),挂起的会话随即继续",
params: IdParam,
body: agent.AgentRunChoiceInput,
response: agent.AgentRun,
}),
abort: ep({
method: "post",
path: "/agent/runs/{id}/abort",
+43
View File
@@ -1,4 +1,5 @@
import { z } from "zod";
import { AgentOptionItem, AgentPlanStep, AgentPlanStepStatus } from "./agent-events.ts";
import { MediaAsset } from "./assets.ts";
import { BrowserController, BrowserDownloadVerification, BrowserSnapshot } from "./browser.ts";
import { CAPABILITY_CATALOG, type CapabilitySpec, type KnownCapabilityId } from "./capability.ts";
@@ -252,6 +253,42 @@ export const AgentAwaitInput = z.object({
});
export const AgentAwaitOutput = z.object({ results: z.array(SubagentResult), allDone: z.boolean() });
// ── 会话交互(AG-2):步骤清单 / 方案选择 ──
export const AgentPlanInput = z.object({
/** 新增或更新的步骤(按 id 合并;title 缺省沿用已有步骤的标题) */
steps: z
.array(
z.object({
id: z.string().trim().min(1).max(40),
title: z.string().trim().min(1).max(200).optional(),
/** 缺省:新步骤 pending,已有步骤不变 */
status: AgentPlanStepStatus.optional(),
note: z.string().max(500).nullable().optional(),
}),
)
.min(1)
.max(20),
/** true:用这批步骤整体替换原计划 */
replace: z.boolean().default(false),
});
export const AgentPlanOutput = z.object({ steps: z.array(AgentPlanStep), revision: z.number().int().min(1) });
export const AgentProposeOptionsInput = z.object({
question: z.string().trim().min(1).max(500),
options: z.array(AgentOptionItem).min(2).max(4),
/** 推荐项 id(超时自动选择时用它) */
recommendedId: z.string().max(40).optional(),
});
export const AgentProposeOptionsOutput = z.object({
/** chosen = 选了 choice;rejected = 都不要(看 reason);stop = 超时按设置停止,简要汇报后结束 */
outcome: z.enum(["chosen", "rejected", "stop"]),
choice: z.string().nullable(),
option: AgentOptionItem.nullable(),
reason: z.string().nullable(),
by: z.enum(["user", "timeout"]),
});
// ── 站点能力(W10-pre2):输出只给状态与统计,不含凭证、Cookie、secret 引用、登录名与站点 config ──
/** 账号摘要:凭证只给"有没有" */
@@ -433,6 +470,8 @@ export const CAPABILITY_IO = {
"browser.navigate": { input: BrowserNavigateInput, output: BrowserNavigateOutput },
"browser.interact": { input: BrowserInteractInput, output: BrowserInteractOutput },
"browser.download": { input: BrowserDownloadInput, output: BrowserDownloadOutput },
"agent.plan": { input: AgentPlanInput, output: AgentPlanOutput },
"agent.propose_options": { input: AgentProposeOptionsInput, output: AgentProposeOptionsOutput },
} as const satisfies Record<KnownCapabilityId, { input: z.ZodType; output: z.ZodType }>;
type CapabilityIo = typeof CAPABILITY_IO;
@@ -487,6 +526,10 @@ export type AgentSpawnOutput = z.infer<typeof AgentSpawnOutput>;
export type AgentAwaitInput = z.infer<typeof AgentAwaitInput>;
export type AgentAwaitOutput = z.infer<typeof AgentAwaitOutput>;
export type SubagentResult = z.infer<typeof SubagentResult>;
export type AgentPlanInput = z.infer<typeof AgentPlanInput>;
export type AgentPlanOutput = z.infer<typeof AgentPlanOutput>;
export type AgentProposeOptionsInput = z.infer<typeof AgentProposeOptionsInput>;
export type AgentProposeOptionsOutput = z.infer<typeof AgentProposeOptionsOutput>;
export type SiteListInput = z.infer<typeof SiteListInput>;
export type SiteListOutput = z.infer<typeof SiteListOutput>;
export type SiteHealthCheckInput = z.infer<typeof SiteHealthCheckInput>;
+27
View File
@@ -76,6 +76,8 @@ export const CAPABILITY_IDS = [
"browser.navigate",
"browser.interact",
"browser.download",
"agent.plan",
"agent.propose_options",
] as const;
export const KnownCapabilityId = z.enum(CAPABILITY_IDS);
export type KnownCapabilityId = z.infer<typeof KnownCapabilityId>;
@@ -398,6 +400,31 @@ export const CAPABILITY_CATALOG: Record<KnownCapabilityId, CapabilityDescriptor>
false,
false,
),
// AG-2 自动化优先的会话交互:步骤清单与方案选择。只在 Agent 会话里可用(HTTP / MCP 调用直接报错)
"agent.plan": d(
"agent.plan",
"更新步骤清单",
"开始时列出 3–7 个步骤,之后每开始、完成、跳过或失败一步就更新(只传变化的步骤,按 id 合并;replace=true 整体替换)。步骤清单实时显示在会话页顶部",
"read",
"AgentPlan",
"read",
"low",
true,
false,
),
// 类别 propose:只登记一个待用户选择的待处理事项,不产生外部副作用;等待期间会话挂起(不占 worker、不调用模型)。
// 与 agent.spawn 一样在注册时声明 replay=safe(恢复后重放本调用取用户的选择)
"agent.propose_options": d(
"agent.propose_options",
"给出方案",
"有明显取舍时给用户 2–3 个方案(各写摘要、风险、估算代价,标出推荐项),等用户选定后返回所选方案;用户也可以都不要并说明原因。超时按设置自动选推荐项或停止",
"propose",
"AgentProposeOptions",
"organize.execute",
"low",
true,
false,
),
};
function d(
+20
View File
@@ -137,6 +137,20 @@ export const AgentRuntimeSettings = z.object({
.number()
.min(0)
.max(24 * 30),
/** agent.propose_options 等用户选择的最长时间(秒);超时按 optionsTimeoutAction 处理 */
optionsTimeoutSec: z
.number()
.int()
.min(5)
.max(7 * 86400),
/** 方案选择超时:recommended = 自动选推荐项(没有推荐项时选第一个);stop = 不选,模型收尾 */
optionsTimeoutAction: z.enum(["recommended", "stop"]),
/** 会话内审批卡的有效期(秒);过期未批准的调用按拒绝处理 */
approvalTimeoutSec: z
.number()
.int()
.min(60)
.max(30 * 86400),
/** fake 模式脚本(JSON,FakeScript 的可序列化子集);null 用内置演示脚本 */
fakeScript: z.string().max(100_000).nullable(),
/**
@@ -350,6 +364,9 @@ export const DEFAULT_SETTINGS: GlobalSettings = {
investigateMaxToolCalls: 8,
goalPollSec: 60,
goalWaitHours: 72,
optionsTimeoutSec: 1800,
optionsTimeoutAction: "recommended",
approvalTimeoutSec: 86400,
fakeScript: null,
sessionDir: null,
},
@@ -607,6 +624,9 @@ export const SETTINGS_SECTIONS: readonly SettingsSectionDef[] = [
"agent.investigateMaxToolCalls",
"agent.goalPollSec",
"agent.goalWaitHours",
"agent.optionsTimeoutSec",
"agent.optionsTimeoutAction",
"agent.approvalTimeoutSec",
"agent.fakeScript",
"agent.sessionDir",
],
+53 -1
View File
@@ -40,10 +40,57 @@ const samples: C.AgentRunEventBody[] = [
summary: "{}",
},
{ type: "subagent_completed", childRunId: RUN, status: "succeeded", summary: "ok", usage, toolCalls: 1 },
{
type: "plan_updated",
steps: [
{ id: "s1", title: "查库存", status: "done", note: null },
{ id: "s2", title: "搜资源", status: "running", note: "只查已开启站点" },
],
revision: 2,
},
{
type: "options_proposed",
invocationId: "inv-1",
decisionId: RUN,
question: "收哪一个版本?",
options: [
{ id: "a", title: "REMUX", summary: "画质最好", risks: ["体积大"], estimatedCost: "78 GB" },
{ id: "b", title: "WEB-DL", summary: "体积小", risks: [], estimatedCost: null },
],
recommendedId: "a",
stepId: "s2",
expiresAt: AT,
timeoutAction: "recommended",
},
{
type: "options_resolved",
invocationId: "inv-1",
decisionId: RUN,
outcome: "chosen",
choice: "a",
reason: null,
by: "user",
},
{
type: "approval_requested",
approvalId: RUN,
decisionId: null,
invocationId: "inv-2",
capabilityId: "download.submit",
planType: "acquisition_plan",
planId: RUN,
payloadHash: "h",
expectedRevision: 1,
riskLevel: "high",
summary: "下载《沙丘2》",
stepId: "s3",
expiresAt: AT,
},
{ type: "approval_resolved", approvalId: RUN, status: "rejected", reason: "太大", decidedBy: "user:1" },
];
describe("agent 运行事件", () => {
it("样例覆盖全部 12 种事件类型", () => {
it("样例覆盖全部 17 种事件类型", () => {
expect(samples.map((s) => s.type).sort()).toEqual([...C.AgentRunEventType.options].sort());
});
@@ -81,6 +128,11 @@ describe("agent 运行事件", () => {
"agent.tool_call:started",
"agent.tool_call:started",
"agent.tool_call:succeeded",
"agent.tool_call:succeeded",
"agent.tool_call:started",
"agent.tool_call:succeeded",
"agent.tool_call:started",
"agent.tool_call:denied",
]);
for (const b of bus) if (b) expect(C.parseEventPayload(b.type, b.payload), b.type).not.toBeNull();
});
+3 -2
View File
@@ -39,8 +39,9 @@ type Obj = z.ZodObject;
const ROWS: Record<string, { row: Obj; dbOnly?: string[]; apiOnly?: string[] }> = {
AcquisitionPlan: { row: C.AcquisitionPlan },
AcquisitionSlot: { row: C.AcquisitionSlot, apiOnly: ["currentAsset"] },
// upstream_id 只在服务端使用;await_wake 是挂起等待子会话时的唤醒信号(C1-B),都不进 AgentRun 视图
AgentRun: { row: C.AgentRun, dbOnly: ["upstreamId", "awaitWake"] },
// upstream_id 只在服务端使用;await_wake 是挂起等待子会话 / 用户选择 / 审批时的唤醒信号(C1-B);
// mode 是内部能力上限(AG-2 取消运行模式后不再出 API):都不进 AgentRun 视图
AgentRun: { row: C.AgentRun, dbOnly: ["upstreamId", "awaitWake", "mode"] },
AgentRunEvent: { row: C.AgentRunEventRow },
AgentRunMessage: { row: C.AgentRunMessage },
CapabilityInvocation: { row: C.CapabilityInvocation },
+5
View File
@@ -103,6 +103,11 @@ const OVERRIDES: Record<string, (v: Record<string, unknown>) => Record<string, u
ReleaseIdentityAssign: (v) => ({ ...v, external: undefined, candidateIndex: undefined }),
CacheSettings: (v) => ({ ...v, hardThresholdPercent: 90, resumePercent: 85, cleanupTargetPercent: 80 }),
GlobalSettings: () => C.DEFAULT_SETTINGS,
// 方案至少 2 个(id 不同)
AgentProposeOptionsInput: (v) => {
const o = (v.options as Record<string, unknown>[])[0]!;
return { ...v, options: [o, { ...o, id: "b" }] };
},
EngineStats: (v) => ({ ...v, byStatus: { pending: 1 } }),
TypedEventEnvelope: () => ({
seq: 1,
@@ -0,0 +1,43 @@
import type { Kysely } from "kysely";
import { execDdl, oneOf } from "../src/migration-helpers.ts";
// AG-2 Agent 交互改为自动化优先:取消 plan_only / execute 运行模式(agent_run.mode 列保留为内部能力上限),
// agent_run_event 增加会话交互事件:
// plan_updated(agent.plan 步骤清单)、options_proposed / options_resolved(agent.propose_options 方案选择)、
// approval_requested / approval_resolved(需审批的能力在步骤处的审批卡)。
const OLD_TYPES = [
"run_started",
"text_delta",
"thinking_delta",
"tool_call_start",
"tool_call_end",
"tool_call_rejected",
"compaction",
"run_completed",
"run_failed",
"subagent_spawned",
"subagent_event",
"subagent_completed",
];
const ADDED = ["plan_updated", "options_proposed", "options_resolved", "approval_requested", "approval_resolved"];
const swap = (values: readonly string[]) => `
alter table agent_run_event drop constraint agent_run_event_type_check;
alter table agent_run_event add constraint agent_run_event_type_check ${oneOf("type", values)};
`;
// biome-ignore lint/suspicious/noExplicitAny: 迁移不依赖应用表类型
export async function up(db: Kysely<any>): Promise<void> {
await execDdl(db, swap([...OLD_TYPES, ...ADDED]));
}
// biome-ignore lint/suspicious/noExplicitAny: 同上
export async function down(db: Kysely<any>): Promise<void> {
await execDdl(
db,
`
delete from agent_run_event where type in (${ADDED.map((t) => `'${t}'`).join(", ")});
${swap(OLD_TYPES)}`,
);
}
+6 -1
View File
@@ -50,7 +50,12 @@ export type AgentRunEventType =
| "run_failed"
| "subagent_spawned"
| "subagent_event"
| "subagent_completed";
| "subagent_completed"
| "plan_updated"
| "options_proposed"
| "options_resolved"
| "approval_requested"
| "approval_resolved";
export type AgentRunMessageStatus = "pending" | "consumed";
export type ApprovalStatus = "pending" | "approved" | "rejected" | "revoked" | "expired" | "consumed";
export type ArtifactKind = "evidence" | "plan" | "proposal" | "report" | "snapshot" | "log";