feat(web): live step list, option choice card and inline approval card; drop run mode switch
- session page shows the agent.plan TodoList (drawer: one-line progress); options and approval cards sit at their step; composer has a "先看方案再执行" toggle instead of the mode switch - settings expose option timeout, timeout action and approval validity; quick goals follow glossary Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
cf2c4ff0e6
commit
9649854ab3
15 files changed
+731
-205
No files matched your search
@@ -12,7 +12,6 @@ import {
|
||||
AgentRunDetail,
|
||||
type AgentRunEvent,
|
||||
type AgentRunMessage,
|
||||
AgentRunMode,
|
||||
type AgentRunStatus,
|
||||
Approval,
|
||||
type ApprovalStatus,
|
||||
@@ -33,9 +32,10 @@ import { usePiUpstreams } from "./pi.ts";
|
||||
|
||||
export const AGENT_BASE = "/api/v1/agent/runs";
|
||||
|
||||
/** 运行模式:execute = 授权内执行;plan_only = 只调查出方案,不执行写能力 */
|
||||
export const AgentMode = AgentRunMode;
|
||||
export type AgentMode = AgentRunMode;
|
||||
/** 会话选项(运行模式已取消):proposeFirst = 先看方案再执行 */
|
||||
export type AgentSessionOptions = { proposeFirst: boolean };
|
||||
/** 会话是否要求先看方案再执行(存在 run.scope 里) */
|
||||
export const isProposeFirst = (run: Pick<AgentRun, "scope">) => run.scope.proposeFirst === true;
|
||||
|
||||
export type AgentRunView = AgentRun;
|
||||
/** 服务端从业务表汇总的目标完成度(不采信模型文本) */
|
||||
@@ -196,9 +196,10 @@ export async function fetchRunEvents(id: string, after: number, signal?: AbortSi
|
||||
export function useCreateAgentRun() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (input: { goal: string; mode: AgentMode }) => {
|
||||
if (agentMockEnabled()) return mockCreateRun(input.goal, input.mode);
|
||||
return apiMutate(AGENT_BASE, { body: { goal: input.goal, mode: input.mode } }).then((d) => AgentRun.parse(d));
|
||||
mutationFn: async (input: { goal: string; proposeFirst?: boolean }) => {
|
||||
const proposeFirst = input.proposeFirst === true;
|
||||
if (agentMockEnabled()) return mockCreateRun(input.goal, { proposeFirst });
|
||||
return apiMutate(AGENT_BASE, { body: { goal: input.goal, proposeFirst } }).then((d) => AgentRun.parse(d));
|
||||
},
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: agentKeys.all }),
|
||||
});
|
||||
@@ -230,6 +231,21 @@ export function useAppendAgentMessage() {
|
||||
});
|
||||
}
|
||||
|
||||
/** 回答方案选择:选一个方案,或都不要(optionId=null + 原因);挂起的会话随即继续 */
|
||||
export function useChooseOption() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (input: { runId: string; decisionId: string; optionId: string | null; reason?: string }) => {
|
||||
if (agentMockEnabled()) return mockDetail(input.runId).run;
|
||||
return apiMutate(`${AGENT_BASE}/:id/choice`, {
|
||||
pathParams: { id: input.runId },
|
||||
body: { decisionId: input.decisionId, optionId: input.optionId, reason: input.reason },
|
||||
}).then((d) => AgentRun.parse(d));
|
||||
},
|
||||
onSuccess: (_d, v) => qc.invalidateQueries({ queryKey: agentKeys.run(v.runId) }),
|
||||
});
|
||||
}
|
||||
|
||||
export const RUN_STATUS_LABEL: Record<AgentRunStatus, string> = {
|
||||
queued: "排队中",
|
||||
running: "运行中",
|
||||
@@ -292,13 +308,33 @@ export function useDecideApproval() {
|
||||
id: string;
|
||||
status: Extract<ApprovalStatus, "approved" | "rejected">;
|
||||
payloadHash: string;
|
||||
/** 拒绝理由 / 建议:写进待处理事项,会话内审批时 Agent 按它调整 */
|
||||
reason?: string;
|
||||
}) =>
|
||||
apiMutate(`/approvals/:id`, {
|
||||
method: "PATCH",
|
||||
pathParams: { id: input.id },
|
||||
body: { status: input.status, payloadHash: input.payloadHash },
|
||||
body: {
|
||||
status: input.status,
|
||||
payloadHash: input.payloadHash,
|
||||
...(input.reason ? { reason: input.reason } : {}),
|
||||
},
|
||||
}).then((d) => Approval.parse(d)),
|
||||
onSuccess: (a) => qc.invalidateQueries({ queryKey: ["agent", "approvals", a.planId] }),
|
||||
onSuccess: (a) => {
|
||||
void qc.invalidateQueries({ queryKey: ["agent", "approvals", a.planId] });
|
||||
void qc.invalidateQueries({ queryKey: ["agent", "approval", a.id] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 单个审批的当前状态(会话内审批卡用;挂起期间事件流里还没有决定结果) */
|
||||
export function useApproval(id: string | null) {
|
||||
const mock = agentMockEnabled();
|
||||
return useQuery({
|
||||
queryKey: ["agent", "approval", id ?? ""],
|
||||
enabled: !!id && !mock,
|
||||
queryFn: ({ signal }) => apiParsed(Approval, "/approvals/:id", { pathParams: { id: id ?? "" }, signal }),
|
||||
refetchInterval: (q) => (q.state.data?.status === "pending" ? 5 * TTL_SECOND : false),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user