Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b50ee15
fix(workflows): page the run history newest first and label the lates…
wanglongan587 Oct 25, 2026
c537508
feat(workflows): split the detail page into edit, runs and versions tabs
wanglongan587 Oct 25, 2026
22614bb
style: format the shared workflow snapshot fixture
wanglongan587 Oct 25, 2026
91ec1fe
fix(workflows): keep the latest-run label on list rows only
wanglongan587 Oct 25, 2026
065f38e
fix(workflows): anchor a run-history cursor only inside its own workflow
wanglongan587 Oct 25, 2026
52c5291
fix(workflows): refresh and merge every loaded run-history page
wanglongan587 Oct 25, 2026
ffcb019
fix(workflows): report later-page failures and show when a run ended
wanglongan587 Oct 25, 2026
de6fea5
fix(workflows): preserve initial history read failures
wanglongan587 Oct 26, 2026
31f18ff
fix(workflows): wait for draft save before editor navigation
wanglongan587 Oct 26, 2026
a2664b8
fix(workflows): retry failed run history refresh
wanglongan587 Oct 26, 2026
ef4bb8d
fix(workflows): show finish time for pre-start cancellation
wanglongan587 Oct 26, 2026
c57ef23
docs(workflows): clarify draft navigation and history retries
wanglongan587 Oct 26, 2026
3e52280
Merge upstream/main into feat/635-run-history-tabs
wanglongan587 Oct 26, 2026
4102c28
fix(workflows): let members leave after failed draft saves
wanglongan587 Oct 26, 2026
44d00fa
perf(workflows): bound latest run lookup to one row per workflow
wanglongan587 Oct 26, 2026
4efb158
docs(workflows): correct history refresh and translation comments
wanglongan587 Oct 26, 2026
3f25b25
Merge upstream/main into feat/635-run-history-tabs
wanglongan587 Oct 26, 2026
a1ddcc0
test(workflows): exercise run navigation with user interactions
wanglongan587 Oct 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions api/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -5788,6 +5788,58 @@
"format": "uuid",
"type": "string"
},
"latestRun": {
"additionalProperties": false,
"description": "List rows only. The workflow's most recent run (the order the run history walks), or null when it has never run.",
"nullable": true,
"properties": {
"createdAt": {
"format": "date-time",
"type": "string"
},
"finishedAt": {
"format": "date-time",
"nullable": true,
"type": "string"
},
"id": {
"format": "uuid",
"type": "string"
},
"name": {
"type": "string"
},
"snapshotId": {
"format": "uuid",
"type": "string"
},
"startedAt": {
"format": "date-time",
"nullable": true,
"type": "string"
},
"status": {
"enum": [
"pending",
"running",
"succeeded",
"failed",
"cancelled"
],
"type": "string"
}
},
"required": [
"id",
"name",
"status",
"snapshotId",
"createdAt",
"startedAt",
"finishedAt"
],
"type": "object"
},
"name": {
"type": "string"
},
Expand Down
20 changes: 20 additions & 0 deletions docs/workflows/workflows.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ and importing workflows from plugins.
checks nothing about completeness: a half-written draft is a legitimate document. Only **publishing** judges
whether a graph can execute, returning problems together with the node or slot at fault so the editor can mark
them. A frozen published version is a graph Cloud has agreed to execute.
Switching tabs or leaving through an editor toolbar link waits for an outstanding draft save. If it fails, the
author may stay in the editor or discard unsaved changes and leave, avoiding both a stale canvas on return and a
failed draft that traps them in the editor.
- **Versions are immutable.** Published versions (`workflow_snapshots`) are stored under a per-workflow increasing
version number and are never modified or soft-deleted, so the graph a run rendered never drifts with the document.
Publishing locks the workflow row, so two concurrent publishes never get the same version number. A snapshot's
Expand Down Expand Up @@ -138,6 +141,23 @@ was named. A run Workspace belongs to the run that created it: `workspaces.workf
with an IssueRun's `issue_run_id`, and the `maintenance` reserved for it in runtime control is recorded against the
same run identity (see [Runtime control](../runtime/runtime-control.en.md)).

**The run history pages by `(created_at, id)`, newest first.** `GET .../workflows/:wfid/runs` returns the newest run
first, caps `limit` at 100, and takes the last row's WorkflowRun UUID as `after`: the server resolves that row's
`created_at` and continues strictly below `(created_at, id)`, so several runs created in the same instant are ordered by
`id` and paging neither repeats nor skips one. The cursor is only a row UUID and carries no timestamp; a cursor that
resolves to no row (another workflow's, or a purged one) answers an empty page instead of restarting from the top, and a
value that is not a UUID answers `400 invalid_cursor`. Every workflow **list** row carries `latestRun`, the first run
of that same order — `null` when the workflow has never run. It holds the status, snapshot and timestamps only; a history
row is what carries node states and inputs. The detail read carries no such field: a run starting must not rewrite every
read of the workflow resource, so a reader that wants the label asks the list or the history.

The run-history page distinguishes loading, no runs, a retryable read failure, and missing permission. On initial entry,
workflow detail and run history are independent reads: a detail `403` also shows missing permission, retrying a transient
detail failure reads the detail again, and only `not_found` says the workflow is missing. A later paging or refresh
failure keeps the loaded history, and retry repeats the read that failed. History rows show start and finish
independently: a run cancelled before starting has no start time but still shows its finish time. Viewing history and
retrying a read never create a run.

**Cancellation is a request, not a transition.** `POST .../workflows/:wfid/runs/:rid/cancel` records the request on
the run row and answers `202` with the instant it was recorded; repeating the request (even under a fresh idempotency
key) returns the same instant. The request writes only `workflow_runs.cancel_requested_at` — never the run's status,
Expand Down
13 changes: 13 additions & 0 deletions docs/workflows/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

- **草稿与已发布分开。** 保存只要求请求体里的图是一个 JSON 对象,不检查完整性:写到一半的草稿是合法的文档。只有**发布**判断图能否执行,
把问题连同出错的节点或槽位一起返回,让编辑器标出来。被冻结的已发布版本就是 Cloud 同意执行的图。
编辑器中切换标签页或通过工具栏离开时,会等未完成的草稿保存结束;保存失败时让作者选择留在编辑器,或放弃未保存的修改并离开,避免返回时从旧缓存重建画布,也不会把作者困在失败的草稿里。
- **版本不可变。** 已发布版本(`workflow_snapshots`)按工作流内递增的版本号保存,从不修改、从不软删除;运行渲染的图因此不会随文档变化而
漂移。发布时锁定工作流行,两次并发发布不会拿到同一个版本号。快照名称默认取工作流名称。
- **恢复**把当前文档换成某个版本的图,并像其他编辑一样推进文档版本;它不改动任何已发布版本。
Expand Down Expand Up @@ -98,6 +99,18 @@ Project 缺失时统一返回 `409 workflow_run_project_required`,不可用、
`workspaces.workflow_run_id` 与 IssueRun 的 `issue_run_id` 互斥,运行时控制里为它预留的 `maintenance` 同样按运行身份记录(见
[运行时控制](../runtime/runtime-control.md))。

**运行历史按 `(created_at, id)` 倒序分页。** `GET .../workflows/:wfid/runs` 返回最新在前,`limit` 上限 100,
`after` 是上一页最后一行的工作流运行 UUID:服务端用它定位该行的 `created_at`,再按 `(created_at, id)` 严格小于它继续取,
所以同一时刻的多条运行由 `id` 决胜负,翻页不重不漏。游标只是行 UUID,不含时间戳;解析不到该行的游标(例如别的工作流或
已不存在的行)返回空页而不是从头部重来,非法 UUID 返回 `400 invalid_cursor`。工作流**列表**的每一行带 `latestRun`,
是这条顺序上的第一条运行,从未运行时为 `null`;它只含状态、快照与时间,运行历史行才带节点状态和输入。详情读不带这个
字段:运行开始不应该改写工作流资源的每一次读取,需要标签的读者读列表或运行历史。

运行记录页分别呈现加载中、没有记录、读取失败可重试与无权限。首次进入时,工作流详情和运行历史是独立的读取:
详情返回 `403` 也显示无权限,临时失败的重试重新读取详情,只有 `not_found` 才显示工作流不存在。
后续翻页或刷新失败保留已加载的历史,重试会重新执行失败的那次读取。历史行独立展示开始和结束时刻:开始前取消的运行没有开始时刻,
但仍显示已有的结束时刻。查看记录和重试读取都不创建运行。

**取消是请求,不是状态切换。** `POST .../workflows/:wfid/runs/:rid/cancel` 在运行行上记录请求,返回 `202` 和记录的时刻;重复请求(即使换一个幂等键)
返回同一个时刻。请求只写 `workflow_runs.cancel_requested_at`,不改动运行的状态、节点状态、会话或 Workspace——结算由调度器在下一轮完成(见下文)。
`pending` 和 `running` 的运行接受请求;已经进入终态的运行返回 `409 run_not_cancellable`。授权按运行上冻结的模型绑定判断:带绑定的运行只有发起人和
Expand Down
32 changes: 32 additions & 0 deletions frontend/src/api/generated.schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2563,6 +2563,33 @@ export interface User {
*/
export type WorkflowGraph = { [key: string]: unknown };

export type WorkflowLatestRunStatus = typeof WorkflowLatestRunStatus[keyof typeof WorkflowLatestRunStatus];


export const WorkflowLatestRunStatus = {
pending: 'pending',
running: 'running',
succeeded: 'succeeded',
failed: 'failed',
cancelled: 'cancelled',
} as const;

/**
* List rows only. The workflow's most recent run (the order the run history walks), or null when it has never run.
* @nullable
*/
export type WorkflowLatestRun = {
createdAt: string;
/** @nullable */
finishedAt: string | null;
id: string;
name: string;
snapshotId: string;
/** @nullable */
startedAt: string | null;
status: WorkflowLatestRunStatus;
} | null;

export interface Workflow {
createdAt: string;
/** @nullable */
Expand All @@ -2571,6 +2598,11 @@ export interface Workflow {
/** The authored graph document: nodes, edges, viewport, editor annotations and global variables. Stored and returned whole; the editor is its only reader. */
graph: WorkflowGraph;
id: string;
/**
* List rows only. The workflow's most recent run (the order the run history walks), or null when it has never run.
* @nullable
*/
latestRun?: WorkflowLatestRun;
name: string;
tenantId: string;
updatedAt: string;
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/features/workflows/README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ This module composes workflow listing, editing, and run pages and owns API queri
| `*-page.tsx` | Route page composition |
| `*translations.ts` | Feature copy and localized publication guidance |

Depends on generated APIs, primitives, identity, and space context; application routes consume this module. Publish errors map stable admission codes to actionable guidance without displaying raw server messages. Tests verify query boundaries and page behavior; known admission reasons must not collapse into generic guidance. The run page shows Loop and Iteration rounds: `run/run-rounds.ts` is the only place that projects the round document in `node_states` (archive plus the top-level active round) into rounds, so the final round appears once and members that did not run read as not reached; a successful Loop's recorded `reason` (`condition_satisfied` / `max_iterations` / `loop_exit`) is used when present; without it only committed facts are used, so at the ceiling under `succeed` it only says "completed".
Depends on generated APIs, primitives, identity, and space context; application routes consume this module. Publish errors map stable admission codes to actionable guidance without displaying raw server messages. Tests verify query boundaries and page behavior; known admission reasons must not collapse into generic guidance.
Leaving the editor waits for its draft save; after a failure the canvas remains visible while the author chooses to stay or discard changes and leave. Navigation must not retry a rejected save indefinitely.
The run page shows Loop and Iteration rounds: `run/run-rounds.ts` is the only place that projects the round document in `node_states` (archive plus the top-level active round) into rounds, so the final round appears once and members that did not run read as not reached; a successful Loop's recorded `reason` (`condition_satisfied` / `max_iterations` / `loop_exit`) is used when present; without it only committed facts are used, so at the ceiling under `succeed` it only says "completed".
4 changes: 3 additions & 1 deletion frontend/src/features/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@
| `*-page.tsx` | 路由页面组合 |
| `*translations.ts` | 功能文案与发布门禁的本地化指导 |

依赖生成 API、基础组件、身份与空间上下文;应用路由消费本模块。发布错误按稳定门禁码显示具体、可操作的原因,不展示原始服务端消息。测试通过查询边界和页面行为验证契约,不能用通用提示掩盖已知门禁原因。运行页按轮展示 Loop 与 Iteration:`run/run-rounds.ts` 是把 `node_states` 里的轮次文档(归档 + 顶层活动轮)投影成轮次的唯一入口,最后一轮只出现一次,未执行的成员显示为未到达;成功 Loop 的轮次文档里有 `reason`(`condition_satisfied` / `max_iterations` / `loop_exit`)就直接用;没有时只按已提交事实推断,到达上限且策略为 succeed 时只显示「已完成」。
依赖生成 API、基础组件、身份与空间上下文;应用路由消费本模块。发布错误按稳定门禁码显示具体、可操作的原因,不展示原始服务端消息。测试通过查询边界和页面行为验证契约,不能用通用提示掩盖已知门禁原因。
编辑页离开前等待草稿保存;失败时保留画布并让作者选择留下或放弃修改后离开,不能反复自动重试而困住导航。
运行页按轮展示 Loop 与 Iteration:`run/run-rounds.ts` 是把 `node_states` 里的轮次文档(归档 + 顶层活动轮)投影成轮次的唯一入口,最后一轮只出现一次,未执行的成员显示为未到达;成功 Loop 的轮次文档里有 `reason`(`condition_satisfied` / `max_iterations` / `loop_exit`)就直接用;没有时只按已提交事实推断,到达上限且策略为 succeed 时只显示「已完成」。
Loading
Loading