Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9e0fcb2
fix(core): preserve null workflow run timestamps
creegon2 Oct 16, 2026
6c92e30
feat(core): bind workflow confirms to real run records
creegon2 Oct 16, 2026
7c63e82
feat(core): project workflow states into issue activity
creegon2 Oct 16, 2026
006078c
feat(frontend): link confirmed workflow runs and reuse interactions
creegon2 Oct 16, 2026
09b75af
fix(workflow): address invocation migration and form review
creegon2 Oct 17, 2026
7845870
test(frontend): share the field-divergence confirm setup
creegon2 Oct 17, 2026
706d77c
merge: sync main (session dashboard) into invocation linkage
creegon2 Oct 17, 2026
ea57c3b
merge: sync workflow asset import for invocation evidence
creegon2 Oct 17, 2026
7088e26
test(workflow): cover imported snapshots in issue invocations
creegon2 Oct 17, 2026
af0ee56
style(test): format the imported snapshot invocation test
creegon2 Oct 17, 2026
1955dbb
merge: sync main (git-native bindings, ADR citations) into invocation…
creegon2 Oct 17, 2026
9c7b396
merge: sync main (docs rebuild, workflow scheduler) into invocation l…
creegon2 Oct 18, 2026
ecd2589
merge: sync main (blocked Workspace abandonment) into invocation linkage
creegon2 Oct 18, 2026
52314da
docs(workflow): 修正快照表单来源与字段差异限制
creegon2 Oct 19, 2026
889a84d
merge(workflow): 合并 main 并保留调用创建的完整运行身份
creegon2 Oct 19, 2026
3914c49
merge(workflow): 合并外部 Git 主线并顺延调用引用迁移
creegon2 Oct 19, 2026
1ae5e42
merge: sync main (condition parity, model attempts, slot route fix) i…
creegon2 Oct 20, 2026
713b191
feat(core): project workflow run states onto linked issue runs
creegon2 Oct 20, 2026
fb34d82
docs(workflows): describe linked issue run status projection
creegon2 Oct 20, 2026
f833135
merge: sync main (agent declaration catalog) into invocation linkage
creegon2 Oct 20, 2026
ebb93cd
merge: sync main (task conversation) into invocation linkage
creegon2 Oct 20, 2026
07136b0
merge: sync main (module README maps) into invocation linkage
creegon2 Oct 20, 2026
140849f
merge: sync main (workflow session end, run statistics) into invocati…
creegon2 Oct 20, 2026
a302856
Merge upstream main into feat/158-workflow-snapshot-confirm
creegon2 Oct 20, 2026
8d1f3c7
Merge upstream main into feat/158-workflow-snapshot-confirm
creegon2 Oct 20, 2026
08102c2
Merge upstream main into feat/158-workflow-snapshot-confirm
creegon2 Oct 20, 2026
6699540
docs(migrations): count 0052 and name its tenant-unique prerequisite
creegon2 Oct 20, 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
26 changes: 20 additions & 6 deletions docs/collaboration/interactions.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,17 @@ Cloud's own background loops, see [Agent IssueRuns](../agent-runs/issue-runs.en.
A workflow has exactly one execution boundary: an explicit **confirm**.

```text
select workflow ──▶ read form descriptor ──▶ fill in (optional AI assist) ──▶ confirm ──▶ IssueRun
select workflow ──▶ read form descriptor ──▶ fill in (optional AI assist) ──▶ confirm ──▶ WorkflowRun + IssueRun
no execution no execution no execution, no run the only execution boundary
```

- The **form descriptor** (`GET /tenants/:tid/collaboration/forms/:formRef?issueId=…`) is an
Issue-facing rendering description: `formRef`, title, description and a list of fields. It is not the
workflow's schema and binds to no schema technology. Fields come in three bands: platform fields, the
variables declared by the workflow's Start node, and the graph's global variables.
Opening the form initially reads the current document. Selecting a published version in the platform
version selector reloads the chosen snapshot's form through
`GET /tenants/:tid/workflows/:wfid/snapshots/:snapshotId/form?issueId=…`.
- **Platform fields** are injected only when the form is opened for an Issue: repository URL and branch
(defaulting to the repository and default branch of the Issue's Project; required), a prompt, the
published version to run `snapshotId`, and additional context references `context_refs`. When the
Expand All @@ -90,13 +93,18 @@ select workflow ──▶ read form descriptor ──▶ fill in (optional AI as
- Frontend validation is only for user experience. On confirm Cloud resolves the target **again** and
validates the submitted values against the form of **the published version being confirmed**; a
workflow with no published version cannot be confirmed, because an unpublished draft cannot run.
- The form the user saw comes from the current document; if its fields no longer match the confirmed
version's, the confirm is refused with the changed fields listed
(`409 workflow_version_fields_changed`), rather than validating the user's values against a schema
they never saw.
- Cloud still compares the live draft's fields with the selected published version's fields. A
difference rejects confirm and lists the changed fields (`409 workflow_version_fields_changed`),
even when the frontend has rendered the selected snapshot's form. The frontend asks the user to
publish the current draft or choose another version; reading the snapshot's form does not remove
this check.
- The submitted values are kept as the interaction's input and the executed version is frozen into the
run input, so the run still knows which version it executed after the workflow is edited or
published again.
- One WorkflowRun and one IssueRun are created in the same transaction, and the IssueRun's
`workflowInvocationRef` points at that WorkflowRun (a unique composite foreign key with the tenant).
Direct runs, manual workflow IssueRuns and confirm share one creation function. A linked IssueRun is
never handed to `ExecutionDispatcher`.
- The interaction is claimed with a compare-and-set on `run_id IS NULL`; a repeated confirm returns
`409 interaction_already_confirmed`, and confirming a non-form interaction returns
`409 interaction_not_confirmable`.
Expand All @@ -112,12 +120,18 @@ defining what those capabilities are internally:
| `FormDescriptorProvider` | Produce the form descriptor for a `formRef` | Forms projected from workflow documents |
| `InputAssistProvider` | AI assist for forms | Not wired (503) |
| `ContextBuilder` | Assemble a run's context snapshot | Not wired; falls back to a deterministic snapshot of task, form values, target and raw context references |
| `ExecutionDispatcher`, `ExecutionObserver` | Hand queued runs to an external executor and observe their progress | Not wired: team runs and workflow runs confirmed from an Issue stay `queued` in production |
| `ExecutionDispatcher`, `ExecutionObserver` | Hand queued runs to an external executor and observe their progress | Not wired: team runs stay `queued` in production; workflow IssueRuns bypass it and execute through their linked WorkflowRun |

Agent runs do not go through `ExecutionDispatcher`; Cloud's own scheduling carries them (see
[Agent IssueRuns](../agent-runs/issue-runs.en.md)). Runs started directly from a workflow are a different
aggregate, WorkflowRun; see [Workflows](../workflows/workflows.en.md).

For a linked workflow IssueRun, actual WorkflowRun status updates advance it in the same transaction:
updates affecting no rows do not project, `awaiting_input` leaves it unchanged, and contract conflicts
roll back the transaction. Output summaries are `system` `run.message` activities bounded to 20000
bytes of valid UTF-8, never workflow-authored comments. Direct workflow runs without a linked IssueRun
create none of these collaboration records.

**Development fixtures.** `collaboration.development_fixtures: true` (environment variable
`CLOUD_COLLABORATION_DEVELOPMENT_FIXTURES`) installs the in-process doubles from `internal/collab` on the
Store: Agent and team targets with fixed IDs, a deterministic context builder (at most the 10 latest
Expand Down
15 changes: 11 additions & 4 deletions docs/collaboration/interactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,25 @@ Agent 名册(`space_agents`,由已安装的 Agent 插件形成,见 [插件
工作流的执行边界只有一个:显式的**确认**。

```text
选中工作流 ──▶ 读取表单描述 ──▶ 填写(可选 AI 辅助)──▶ 确认 ──▶ IssueRun
选中工作流 ──▶ 读取表单描述 ──▶ 填写(可选 AI 辅助)──▶ 确认 ──▶ WorkflowRun + IssueRun
不执行 不执行 不执行、不建运行 唯一的执行边界
```

- **表单描述**(`GET /tenants/:tid/collaboration/forms/:formRef?issueId=…`)是面向 Issue 的渲染描述:`formRef`、标题、说明和
字段列表。它不是工作流的 schema,也不绑定任何 schema 技术。字段分三段:平台字段、工作流 Start 节点声明的变量、图的全局变量。
初次打开时读取当前文档;在平台版本选择器中选择已发布版本后,前端通过
`GET /tenants/:tid/workflows/:wfid/snapshots/:snapshotId/form?issueId=…` 重新读取所选快照的表单。
- **平台字段**只在带着 Issue 打开表单时注入:仓库地址和分支(默认取 Issue 关联 Project 的仓库与默认分支,必填)、提示词、要执行
的已发布版本 `snapshotId`、附加的上下文引用 `context_refs`。工作流自己声明了同名变量时,以工作流的声明为准。
- **AI 辅助**(`POST .../issues/:iid/collaboration/assist`)只返回字段级的建议补丁和建议的上下文引用:从不创建运行,也不写入
持久的上下文引用。应用建议是用户的动作。生产环境没有接入辅助提供方,返回 `503 assist_unavailable`。
- **确认**(`POST .../issues/:iid/interactions/:ixid/confirm`)是唯一把工作流交互变成执行意图的转换:
- 前端校验只是体验,Cloud 在确认时**重新**解析目标,并按**被确认的那个已发布版本**的表单校验提交的值;没有任何已发布版本的
工作流不能确认,因为不能执行未发布的草稿。
- 用户看到的表单来自当前文档;如果它与被确认版本的字段已经不一致,确认被拒绝并列出变化的字段
(`409 workflow_version_fields_changed`),而不是用用户没见过的 schema 校验他填的值。
- Cloud 仍比较当前活稿与所选发布版本的字段;即使前端已展示所选快照的表单,两者不一致时仍拒绝确认并列出变化的字段
(`409 workflow_version_fields_changed`)。前端提示先发布当前活稿或选择其他版本;按快照读取表单并未取消这条检查。
- 提交的值作为交互的输入保存,被执行的版本冻结进运行输入;运行之后工作流再被编辑或发布,它仍然知道自己执行的是哪个版本。
- 同一事务里创建一个 WorkflowRun 和一个 IssueRun,IssueRun 的 `workflowInvocationRef` 指向该 WorkflowRun(与租户一起的复合外键,且唯一);直接运行、manual 工作流 IssueRun 与确认共用同一个创建函数。已关联的 IssueRun 不交给 `ExecutionDispatcher`。
- 用 `run_id IS NULL` 的比较并交换认领交互,重复确认返回 `409 interaction_already_confirmed`;对非表单交互确认返回
`409 interaction_not_confirmable`。

Expand All @@ -83,11 +86,15 @@ Issue 协作通过一组端口向外部能力请求"做一件事",而不定义
| `FormDescriptorProvider` | 按 `formRef` 给出表单描述 | 工作流文档投影成的表单 |
| `InputAssistProvider` | 表单的 AI 辅助 | 未接入(503) |
| `ContextBuilder` | 组装运行的上下文快照 | 未接入,退回到"任务、表单值、目标、原始上下文引用"的确定性快照 |
| `ExecutionDispatcher`、`ExecutionObserver` | 把排队的运行交给外部执行者,并观察其进度 | 未接入:团队运行和从 Issue 确认的工作流运行在生产中保持 `queued` |
| `ExecutionDispatcher`、`ExecutionObserver` | 把排队的运行交给外部执行者,并观察其进度 | 未接入:团队运行在生产中保持 `queued`;工作流 IssueRun 不经过这里,由关联的 WorkflowRun 执行 |

Agent 运行不走 `ExecutionDispatcher`,由 Cloud 自己的调度推进(见 [Agent IssueRun](../agent-runs/issue-runs.md))。直接从工作流
发起的运行是另一个聚合 WorkflowRun,见 [工作流](../workflows/workflows.md)。

关联工作流的 IssueRun 由 WorkflowRun 的实际状态更新在同一事务内推进:未更新到行时不投影,`awaiting_input` 不改变 IssueRun,
契约冲突使事务回滚。输出摘要写成 `system` 的 `run.message` 活动,最多 20000 字节且保持合法 UTF-8,不创建工作流作者评论。
没有关联 IssueRun 的直接工作流运行不产生这些协作记录。

**开发夹具。** `collaboration.development_fixtures: true`(环境变量 `CLOUD_COLLABORATION_DEVELOPMENT_FIXTURES`)在 Store 上装入
`internal/collab` 的进程内替身:固定 ID 的 Agent 和团队目标、确定性的上下文构建器(最近至多 10 条评论,不用 AI)、给出固定输出的
派发器和 AI 辅助。工作流目标仍然来自数据库。这个开关只用于开发和演示,生产默认关闭;它与认证完全独立:开启 GitHub 登录不会打开
Expand Down
17 changes: 12 additions & 5 deletions docs/workflows/workflows.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ Workflows have three start entries, all sharing one admission gate:
| Entry | Produces |
|---|---|
| `POST .../workflows/:wfid/runs` | A WorkflowRun (`workflow_runs`) |
| `POST .../issues/:iid/runs` with `executorType = workflow` | A workflow IssueRun |
| Confirming an @workflow form on an Issue | A workflow IssueRun (see [Issue collaboration](../collaboration/interactions.en.md#form-mode-workflow)) |
| `POST .../issues/:iid/runs` with `executorType = workflow` | A WorkflowRun and the workflow IssueRun linked to it |
| Confirming an @workflow form on an Issue | A WorkflowRun and the workflow IssueRun linked to it (see [Issue collaboration](../collaboration/interactions.en.md#form-mode-workflow)) |

**Admission** is decided once, in the caller's transaction:

Expand All @@ -77,8 +77,10 @@ Workflows have three start entries, all sharing one admission gate:
If any check fails, no run, claimed interaction or activity is left behind. The executed version is frozen onto the
run, so the run still knows which version it executed after the workflow is edited or published again.

**A run's identity is frozen at creation.** A direct run must name its `projectId`: a missing one is `409
workflow_run_project_required`, an unusable, archived or foreign-space Project is refused as `404`, and one being
**A run's identity is frozen at creation.** All three entries share one creation function: a direct
start takes the Project from the request body's `projectId`, while Confirm/manual take it from the
Issue's `project_ref`. A missing Project is `409 workflow_run_project_required` for every entry;
an unusable, archived or foreign-space Project is refused as `404`, and one being
deleted is `409 resource_unavailable`. The run writes the initiating user (`created_by`) and that Project
(`project_id`) into its row and never changes them — a workflow document belongs to a tenant, not to a Project, so
there is nothing to default to, and the run's Workspace, its clone and its whole checkout land in the Project that
Expand Down Expand Up @@ -151,7 +153,12 @@ seconds):

Development fixtures (`collaboration.development_fixtures`) install a deterministic run simulator that fills in the
execution trace at run creation and says in its output that nothing really ran; production never installs it.
Workflow IssueRuns currently have no executor in production and stay `queued` (see
A workflow IssueRun is linked through `workflow_invocation_ref` to the WorkflowRun created in the same transaction,
and the scheduling loop above advances that run. After a WorkflowRun status update affects a row, its state,
timestamps and errors are projected onto the linked IssueRun in that same transaction. An update affecting no rows
does not project; contract conflicts roll back the transaction. `awaiting_input` leaves the IssueRun unchanged,
and direct runs without a linked IssueRun create no collaboration records. Output summaries are `system`
`run.message` activities, bounded to 20000 bytes of valid UTF-8, never workflow-authored comments (see
[Issue collaboration](../collaboration/interactions.en.md#ports-issues-own-only-the-consuming-side-contract)).

## Plugin asset import
Expand Down
12 changes: 8 additions & 4 deletions docs/workflows/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ Agent 节点不在图里写死 Agent CLI 或模型 ID,而是引用一个**执
| 入口 | 产生 |
|---|---|
| `POST .../workflows/:wfid/runs` | 一个 WorkflowRun(`workflow_runs`) |
| `POST .../issues/:iid/runs`,`executorType = workflow` | 一个工作流 IssueRun |
| 在 Issue 上确认 @工作流 的表单 | 一个工作流 IssueRun(见 [Issue 协作](../collaboration/interactions.md#form-mode工作流)) |
| `POST .../issues/:iid/runs`,`executorType = workflow` | 一个 WorkflowRun 和关联它的工作流 IssueRun |
| 在 Issue 上确认 @工作流 的表单 | 一个 WorkflowRun 和关联它的工作流 IssueRun(见 [Issue 协作](../collaboration/interactions.md#form-mode工作流)) |

**准入**在调用方的事务里一次判定:

Expand All @@ -58,7 +58,8 @@ Agent 节点不在图里写死 Agent CLI 或模型 ID,而是引用一个**执

任何一项不通过都不会留下运行、已认领的交互或活动。被执行的版本冻结在运行上,运行之后工作流再被编辑或发布,它仍然知道自己执行的是哪个版本。

**运行的身份在创建时冻结。** 直接创建运行时 `projectId` 必填:缺失返回 `409 workflow_run_project_required`,不可用、已归档或属于其他空间的 Project
**运行的身份在创建时冻结。** 三个入口共用创建函数:直接启动的 Project 来自请求体 `projectId`,Confirm/manual 来自 Issue 的 `project_ref`。
Project 缺失时统一返回 `409 workflow_run_project_required`,不可用、已归档或属于其他空间的 Project
按 `404` 拒绝,正在删除的返回 `409 resource_unavailable`。运行把发起人(`created_by`)和这个 Project(`project_id`)写进运行行,之后不再变化——工作流文档
属于租户而不是某个 Project,没有可以默认的对象,而运行的 Workspace、它的 clone 和整份检出都落在被指定的 Project 里。运行 Workspace 归属于创建它的那次运行:
`workspaces.workflow_run_id` 与 IssueRun 的 `issue_run_id` 互斥,运行时控制里为它预留的 `maintenance` 同样按运行身份记录(见
Expand Down Expand Up @@ -101,7 +102,10 @@ WorkflowRun 由 Cloud server 里的调度循环推进(`AdvanceWorkflowRunsOnce
保留在执行行上,不改写已经终态的运行。

开发夹具(`collaboration.development_fixtures`)会装入一个确定性的运行模拟器,在创建运行时就填好执行轨迹,并在输出里说明没有真实执行;生产环境
不装它。工作流 IssueRun 在生产中目前没有执行端,保持 `queued`(见 [Issue 协作](../collaboration/interactions.md#端口issue-只拥有消费侧的契约))。
不装它。工作流 IssueRun 通过 `workflow_invocation_ref` 关联同一事务里创建的 WorkflowRun,执行由上面的调度循环推进。
WorkflowRun 的实际状态更新成功后,在同一事务里将状态、时间和错误投影回关联的 IssueRun;未更新到行时不投影,契约冲突使事务回滚。
`awaiting_input` 不改变 IssueRun,未关联 IssueRun 的直接运行也不产生协作记录。输出摘要作为 `system` 的 `run.message` 活动写入,
最多 20000 字节且保持合法 UTF-8,不创建工作流作者评论(见 [Issue 协作](../collaboration/interactions.md#端口issue-只拥有消费侧的契约))。

## 插件资产导入

Expand Down
6 changes: 6 additions & 0 deletions frontend/src/features/issues/README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,9 @@ MSW stands in for the real Cloud API; under `onUnhandledRequest: 'error'` every
Recovery reloads and property saves are mutually exclusive: properties and the reload button remain locked until the original save error has been handled. Failed reloads retain the error; a later save uses the loaded version and reports its own failure independently.

Detail tests use SessionProvider and CurrentSpaceProvider to cover versioned association/removal, retained values and GET reload after conflicts, delayed GET/PUT exclusion and error visibility, ordinary comments creating no Run, and the Thread of a Run created through Task Mode. Component evidence never substitutes for real-provider multi-turn, terminal-state and resource-release acceptance.

`IssueRun.workflowInvocationRef` retains the server's WorkflowRun reference for activity status and navigation; the API test checks that list reads preserve it. Workflow confirmation exclusion, retry and detail-link interaction tests live in `components/`.

`useWorkflowSnapshotForm` reuses the generated snapshot-form GET with tenant/workflow/snapshot/issue-scoped query keys. The API test varies every scope so versions and Issues cannot share the wrong field defaults.

`useWorkflowPublishedSnapshotIds` caches all published ids by tenant/workflow, following the generated list endpoint's cursors with a page limit of 100 and the caller's abort signal; it stores no graph copies. The activity form uses those ids to identify published-version options. API tests cover the second-page cursor, complete id list and tenant/workflow isolation.
6 changes: 6 additions & 0 deletions frontend/src/features/issues/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,9 @@ MSW 模拟真实 Cloud 接口;`onUnhandledRequest: 'error'` 下每个测试都
恢复重读与属性保存互斥:重读期间锁住属性和重读按钮,处理原保存错误后才解锁;失败重读保留错误,后续保存仍以读取到的版本提交并独立显示失败。

详情测试经过 SessionProvider 和 CurrentSpaceProvider,验证关联/取消关联的版本号、冲突后保留及 GET 重读、延迟 GET/PUT 的互斥与错误可见性,以及普通评论不创建 Run、Task Mode 创建的同 Run Thread 可见。组件证据不替代真实供应商多轮、终态和资源释放的联合验收。

`IssueRun.workflowInvocationRef` 保留服务端返回的 WorkflowRun 引用,供活动栏读取真实运行状态并导航;API 测试检查列表没有丢失该关联。Workflow 确认去重、失败重试和详情链接的交互测试位于 `components/`。

`useWorkflowSnapshotForm` 复用生成的 snapshot form GET,查询键包含 tenant/workflow/snapshot/issue;API 测试逐项切换这些范围,避免不同版本或 Issue 共用表单默认值。

`useWorkflowPublishedSnapshotIds` 按 tenant/workflow 缓存全部已发布版本 id,使用生成的列表接口逐页读取(每页 100,传递取消信号),不缓存图副本。活动表单用它判断下拉选项是否都属于已发布版本;API 测试验证第二页游标、完整 id 列表及租户/Workflow 隔离。
Loading
Loading