forked from bai/curriculum-project-hub
feat: interrupt running agent via card button with confirm dialog
Add a ⛔ 中断 button to live streaming cards; Feishu's native confirm
dialog is the confirmation step, so no extra card round-trip is needed.
- builder.ts: render interrupt action (danger button + confirm) while the
run is live; new `interrupted` flag renders a 已中断 footer instead of
完成/失败 on the complete card.
- streaming-card.ts: finish(text, { interrupted }) threads the flag into
the final patch; overflow cards suppress the button.
- runner.ts: RunRequest gains abortController, passed to the SDK query;
abort surfaces as RunStatus "interrupted" (no error) in the catch.
- trigger.ts: activeRuns registry maps runId → AbortController; onCardAction
resolves the interrupt button, authorizes agent.cancel, aborts the run.
Interrupted runs persist as CANCELED (spec RunState.canceled, terminal →
lock released per ADR-0002).
- authorizer.ts: agent.cancel now consults PermissionSettings.agentCancel
(MANAGE_ONLY/DISABLED) — previously only agentTrigger was honored, but
spec/PermissionGrant deliberately splits these knobs ("谁能触发" ≠
"谁能取消"). Default role remains MANAGE (Capability.normalCancel).
Tests: runner abort unit test, trigger interrupt + denied integration
tests, three agent.cancel authorization tests (manage allowed, edit denied,
DISABLED policy denies even manage). 26 files / 175 tests pass.
This commit is contained in:
@@ -59,9 +59,10 @@ export function buildAgentCard(params: {
|
||||
toolUseSteps: ToolUseTraceStep[];
|
||||
toolUseElapsedMs: number | undefined;
|
||||
isError: boolean | undefined;
|
||||
interrupted: boolean | undefined;
|
||||
runId: string | undefined;
|
||||
}): Record<string, unknown> {
|
||||
const { phase, text, reasoningText, toolUseSteps, toolUseElapsedMs, isError } = params;
|
||||
const { phase, text, reasoningText, toolUseSteps, toolUseElapsedMs, isError, interrupted } = params;
|
||||
const elements: unknown[] = [];
|
||||
|
||||
// Tool-use panel (always present if there are steps)
|
||||
@@ -100,10 +101,19 @@ export function buildAgentCard(params: {
|
||||
});
|
||||
}
|
||||
|
||||
// Interrupt button while the run is live. The button carries the run id in
|
||||
// its action value; the card action handler aborts the run. Feishu's native
|
||||
// `confirm` dialog is the "are you sure" step — the action only fires after
|
||||
// the user confirms, so no second round-trip is needed.
|
||||
if (phase !== "complete" && params.runId !== undefined) {
|
||||
elements.push(buildInterruptAction(params.runId));
|
||||
}
|
||||
|
||||
// Footer for complete state
|
||||
if (phase === "complete") {
|
||||
const footerParts: string[] = [];
|
||||
if (isError === true) footerParts.push("\u274C \u5931\u8D25");
|
||||
if (interrupted === true) footerParts.push("\u{1F6D1} \u5DF2\u4E2D\u65AD");
|
||||
else if (isError === true) footerParts.push("\u274C \u5931\u8D25");
|
||||
else footerParts.push("\u2705 \u5B8C\u6210");
|
||||
if (toolUseElapsedMs !== undefined) {
|
||||
footerParts.push(formatElapsed(toolUseElapsedMs));
|
||||
@@ -125,6 +135,28 @@ export function buildAgentCard(params: {
|
||||
};
|
||||
}
|
||||
|
||||
/** Action row with a single danger "interrupt" button for a live run. */
|
||||
function buildInterruptAction(runId: string): unknown {
|
||||
return {
|
||||
tag: "action",
|
||||
actions: [
|
||||
{
|
||||
tag: "button",
|
||||
text: { tag: "plain_text", content: "\u{1F6D1} \u4E2D\u65AD" },
|
||||
type: "danger",
|
||||
value: { interrupt_run: runId },
|
||||
confirm: {
|
||||
title: { tag: "plain_text", content: "\u786E\u8BA4\u4E2D\u65AD" },
|
||||
text: {
|
||||
tag: "plain_text",
|
||||
content: "\u786E\u5B9A\u8981\u4E2D\u65AD\u5F53\u524D\u8FD0\u884C\u5417\uFF1F\u4E2D\u65AD\u540E\u65E0\u6CD5\u6062\u590D\uFF0C\u5DF2\u4EA7\u51FA\u7684\u5185\u5BB9\u4F1A\u4FDD\u7559\u3002",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tool-use panel
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user