@fieldwangai/agentflow 0.1.136 → 0.1.138

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,8 +15,8 @@
15
15
  href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0"
16
16
  rel="stylesheet"
17
17
  />
18
- <script type="module" crossorigin src="/assets/index-HdswcJWY.js"></script>
19
- <link rel="stylesheet" crossorigin href="/assets/index-KIGufzQf.css">
18
+ <script type="module" crossorigin src="/assets/index-YS4XOpXF.js"></script>
19
+ <link rel="stylesheet" crossorigin href="/assets/index-COX1zMwq.css">
20
20
  </head>
21
21
  <body>
22
22
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fieldwangai/agentflow",
3
- "version": "0.1.136",
3
+ "version": "0.1.138",
4
4
  "description": "Orchestration system for long-running complex agent tasks using Cursor, OpenCode, Claude Code, or Codex as execution backends",
5
5
  "type": "module",
6
6
  "main": "bin/agentflow.mjs",
@@ -120,7 +120,9 @@ node skills/agentflow-cli/scripts/agentflow-cli.mjs display-outputs --flow-id Te
120
120
 
121
121
  ## Workflow reporting
122
122
 
123
- The reusable transport lives in `scripts/workflow-report-client.mjs`. The CLI exposes it through `workflow-get`, `workflow-report`, and `workflow-artifact-publish`; their state model, extension contract, concurrency rules, and AI procedure belong to the separate [`agentflow-workflow-report`](../agentflow-workflow-report/SKILL.md) skill. Use that skill whenever reading or mutating Workflow state; do not reconstruct the protocol from this general CLI guide.
123
+ The reusable transport lives in `scripts/workflow-report-client.mjs`. The CLI exposes it through `workflow-access-sync`, `workflow-get`, `workflow-report`, and `workflow-artifact-publish`; their permission model, state model, extension contract, concurrency rules, and AI procedure belong to the separate [`agentflow-workflow-report`](../agentflow-workflow-report/SKILL.md) skill. Use that skill whenever synchronizing access or reading/mutating Workflow state; do not reconstruct the protocol from this general CLI guide.
124
+
125
+ Every write requires the real business adapter `source`. Put the key-level `expectedVersions` map in the JSON file; use `absent` for a new resource key. `--expected-revision` is retained only for legacy whole-Workflow locking and should not be used by new integrations.
124
126
 
125
127
  ## Workflow
126
128
 
@@ -42,8 +42,9 @@ Commands:
42
42
  display-outputs --flow-id <id> [--flow-source user]
43
43
  sync-workspace --workspace <id>
44
44
  workflow-get --workflow tapd:<id> [--flow-id <id>] [--runtime-only]
45
- workflow-report --workflow tapd:<id> --file <report.json> [--expected-revision <revision>]
46
- workflow-artifact-publish --workflow tapd:<id> --file <artifact.json>
45
+ workflow-access-sync --workflow tapd:<id> --file <access.json>
46
+ workflow-report --workflow tapd:<id> --file <report.json> [--source <adapter>] [--expected-revision <revision>] [--idempotency-key <key>]
47
+ workflow-artifact-publish --workflow tapd:<id> --file <artifact.json> [--source <adapter>] [--expected-revision <revision>] [--idempotency-key <key>]
47
48
  `;
48
49
  }
49
50
 
@@ -401,17 +402,22 @@ async function main() {
401
402
 
402
403
  if (command === "workflow-report") {
403
404
  const body = readJsonFile(option(args, "file"));
404
- const workflow = workflowReferenceFromArgs(args, false) || parseWorkflowReference(body?.workflow?.key || "");
405
+ const workflow = workflowReferenceFromArgs(args, false) || parseWorkflowReference(
406
+ typeof body?.workflow === "string" ? body.workflow : body?.workflow?.key || "",
407
+ );
405
408
  if (!workflow && !(body?.workflow?.namespace && body?.workflow?.id)) {
406
409
  throw new Error("Missing workflow reference. Pass --workflow namespace:id or include workflow.namespace and workflow.id in the JSON file.");
407
410
  }
408
411
  if (workflow) body.workflow = workflow;
409
412
  const expectedRevision = option(args, "expected-revision");
410
413
  const idempotencyKey = option(args, "idempotency-key");
414
+ const reportSource = option(args, "source");
411
415
  const flowId = option(args, "flow-id") || option(args, "flow");
412
416
  const flowSource = option(args, "flow-source");
413
417
  if (expectedRevision) body.expectedRevision = expectedRevision;
414
418
  if (idempotencyKey) body.idempotencyKey = idempotencyKey;
419
+ if (reportSource) body.source = reportSource;
420
+ if (!String(body.source || "").trim()) throw new Error("Missing Workflow report source. Pass --source <adapter> or include source in the JSON file.");
415
421
  if (flowId) body.flowId = flowId;
416
422
  if (flowSource) body.flowSource = flowSource;
417
423
  const client = createWorkflowReportClient({ baseUrl: normalizedBaseUrl(args), token: authToken(args) });
@@ -419,17 +425,40 @@ async function main() {
419
425
  return;
420
426
  }
421
427
 
428
+ if (command === "workflow-access-sync") {
429
+ const body = readJsonFile(option(args, "file"));
430
+ const workflow = workflowReferenceFromArgs(args, false) || parseWorkflowReference(
431
+ typeof body?.workflow === "string" ? body.workflow : body?.workflow?.key || "",
432
+ );
433
+ if (!workflow && !(body?.workflow?.namespace && body?.workflow?.id)) {
434
+ throw new Error("Missing workflow reference. Pass --workflow namespace:id or include workflow.namespace and workflow.id in the JSON file.");
435
+ }
436
+ if (workflow) body.workflow = workflow;
437
+ const client = createWorkflowReportClient({ baseUrl: normalizedBaseUrl(args), token: authToken(args) });
438
+ printJson(await client.syncAccess(body));
439
+ return;
440
+ }
441
+
422
442
  if (command === "workflow-artifact-publish") {
423
443
  const body = readJsonFile(option(args, "file"));
424
- const workflow = workflowReferenceFromArgs(args, false) || parseWorkflowReference(body?.workflow?.key || "");
444
+ const workflow = workflowReferenceFromArgs(args, false) || parseWorkflowReference(
445
+ typeof body?.workflow === "string" ? body.workflow : body?.workflow?.key || "",
446
+ );
425
447
  if (!workflow && !(body?.workflow?.namespace && body?.workflow?.id)) {
426
448
  throw new Error("Missing workflow reference. Pass --workflow namespace:id or include workflow.namespace and workflow.id in the JSON file.");
427
449
  }
428
450
  if (workflow) body.workflow = workflow;
451
+ const expectedRevision = option(args, "expected-revision");
452
+ const idempotencyKey = option(args, "idempotency-key");
453
+ const reportSource = option(args, "source");
429
454
  const flowId = option(args, "flow-id") || option(args, "flow");
430
455
  const flowSource = option(args, "flow-source");
431
456
  if (flowId) body.flowId = flowId;
432
457
  if (flowSource) body.flowSource = flowSource;
458
+ if (expectedRevision) body.expectedRevision = expectedRevision;
459
+ if (idempotencyKey) body.idempotencyKey = idempotencyKey;
460
+ if (reportSource) body.source = reportSource;
461
+ if (!String(body.source || "").trim()) throw new Error("Missing Workflow artifact source. Pass --source <adapter> or include source in the JSON file.");
433
462
  const client = createWorkflowReportClient({ baseUrl: normalizedBaseUrl(args), token: authToken(args) });
434
463
  printJson(await client.publishArtifact(body));
435
464
  return;
@@ -58,6 +58,9 @@ export function createWorkflowReportClient({ baseUrl, token, fetchImpl = globalT
58
58
  runtimeOnly: runtimeOnly ? "1" : "",
59
59
  })}`);
60
60
  },
61
+ syncAccess(body = {}) {
62
+ return request("/api/workflows/access/sync", { method: "POST", body });
63
+ },
61
64
  report(body = {}) {
62
65
  return request("/api/workflows/report", { method: "POST", body });
63
66
  },
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: agentflow-workflow-report
3
- description: Safely read, merge, and report AgentFlow Workflow actions, artifacts, producer-owned global state, and generic timeline projections through the AgentFlow CLI and HTTP protocol. Use when an AI agent or producer such as prd-flow needs to integrate Workflow reporting, publish progress or evidence, update globalState, assign version/sprint/milestone timeline membership, clear projections, or resolve revision and idempotency conflicts.
3
+ description: Safely synchronize TAPD-derived Workflow access, then read, merge, and report AgentFlow Workflow actions, artifacts, producer-owned global state, and generic timeline projections through the AgentFlow CLI and HTTP protocol. Use when an AI agent or producer such as prd-flow needs to integrate Workflow reporting, map TAPD Owner and participants, publish progress or evidence, update globalState, assign version/sprint/milestone timeline membership, clear projections, or resolve revision and idempotency conflicts.
4
4
  ---
5
5
 
6
6
  # AgentFlow Workflow Report
@@ -22,7 +22,8 @@ Read [references/protocol.md](references/protocol.md) completely before implemen
22
22
  ## Required sequence
23
23
 
24
24
  1. Resolve a canonical Workflow reference such as `tapd:1015046`. The report schema is producer-generic, but the current AgentFlow identity adapter accepts only the `tapd` namespace. Do not claim that arbitrary Workflow namespaces already work.
25
- 2. Read the current materialized state and retain `snapshot.runtimeRevision`:
25
+ 2. If the Adapter reads TAPD personnel, synchronize its authority snapshot with `POST /api/workflows/access/sync` before reporting: TAPD Owner becomes Workflow Owner and matched TAPD participants become derived Viewers. Keep this permission control-plane call separate from runtime report data.
26
+ 3. Read the current materialized state and retain `snapshot.resourceVersions` for every resource key the operation will touch:
26
27
 
27
28
  ```bash
28
29
  node skills/agentflow-cli/scripts/agentflow-cli.mjs workflow-get \
@@ -30,20 +31,19 @@ node skills/agentflow-cli/scripts/agentflow-cli.mjs workflow-get \
30
31
  --runtime-only
31
32
  ```
32
33
 
33
- 3. Compute only the intended semantic update. Choose one stable lowercase `source` for the business adapter (for example `prd-flow` or `release-bot`). `agentflow-cli` is only transport and must not replace the real producer identity.
34
- 4. Preserve unrelated `globalState` fields. Never infer or rewrite a producer's private schema.
35
- 5. When timeline membership changes, derive the complete current `projections.timeline` array from producer state. Use `[]` to clear it.
36
- 6. Write the payload to a JSON file and report it with the retained revision and a stable operation key:
34
+ 4. Compute only the intended semantic update. Choose one stable lowercase `source` for the business adapter (for example `prd-flow` or `release-bot`). `agentflow-cli` is only transport and must not replace the real producer identity.
35
+ 5. Preserve unrelated `globalState` fields. Never infer or rewrite a producer's private schema.
36
+ 6. When timeline membership changes, derive the complete producer-owned `projections.timeline` slice. AgentFlow preserves entries owned by other sources; use `[]` to clear only the current source's memberships.
37
+ 7. Put `expectedVersions` for every touched Action, Artifact, GlobalState path, Projection, Extension path, or Observation into the JSON payload. Use `"absent"` when creating a new key. Report it with a stable operation key:
37
38
 
38
39
  ```bash
39
40
  node skills/agentflow-cli/scripts/agentflow-cli.mjs workflow-report \
40
41
  --workflow tapd:1015046 \
41
42
  --file workflow-report.json \
42
- --expected-revision 'runtime:current-revision' \
43
43
  --idempotency-key 'implementation-finished:android:issue-2:v1'
44
44
  ```
45
45
 
46
- 7. On HTTP 409, fetch the latest state, reapply the intended semantic update, and retry once with the new revision. Never send a client field named `snapshot`; use `observation.state` for a complete producer observation and treat returned `snapshot` as server output.
46
+ 8. On HTTP 409, refresh only the resource keys listed in `conflict.conflicts`, recompute the intended update, and retry once with their new versions. Never send a client field named `snapshot`; use `observation.state` for a complete producer observation and treat returned `snapshot` as server output.
47
47
 
48
48
  ## Report selection
49
49
 
@@ -72,29 +72,29 @@ For local Markdown or other content that must become a browser URL, publish it f
72
72
  - Keep `schemaVersion` at `1` unless the server advertises another version.
73
73
  - Keep the runtime chain singular: producer adapter → Workflow Report client → AgentFlow. The Skill is guidance, not a transport hop.
74
74
  - Give every action a stable `key`.
75
- - Send a stable lowercase `source` on every report and Markdown publish. Action, idempotency, and Artifact identities are isolated by `source + key`; `globalState` and the complete timeline remain shared read-merge-write regions.
75
+ - Send a stable lowercase `source` on every report and Markdown publish; it is required. Action, idempotency, Artifact, Projection, Extension, Observation, and GlobalState ownership are isolated by source-aware resource keys.
76
76
  - Give every timeline entry stable `kind` and `id` values.
77
77
  - Treat `dimensions` as opaque facets; do not hardcode Android, iOS, version, or prd-flow fields into AgentFlow state.
78
78
  - Treat `globalState` as the source of truth owned by the producer; treat projections as replaceable derived views.
79
- - Send the complete current timeline array whenever changing it. Omitting `projections` means no projection change.
80
- - Use `expectedRevision` for state or projection changes and a stable `idempotencyKey` for every logical operation.
79
+ - Send the complete current source-owned timeline slice whenever changing it. Omitting `projections` means no projection change.
80
+ - Use `expectedVersions` for key-level concurrency and a stable `idempotencyKey` for every logical operation. `expectedRevision` remains a whole-Workflow compatibility lock only when `expectedVersions` is absent.
81
81
  - Do not include credentials, tokens, cookies, or private environment values in actions, artifacts, state, projections, or logs.
82
82
 
83
83
  ## Permissions and overwrite semantics
84
84
 
85
- - Treat the first authenticated reporter as owner when the Workflow has no collaboration record.
86
- - Allow owner and explicit editor writes. Treat explicit viewer, same-team viewer, share-link viewer, and admin review as read-only.
85
+ - Treat TAPD personnel as derived authority when the Adapter can read them: TAPD Owner maps to Workflow Owner and registered TAPD participants map to Viewer.
86
+ - Keep explicit grants separate from derived TAPD membership. Allow Owner and explicit Reporter writes. Treat TAPD participant Viewer, explicit Viewer, same-team Viewer, share-link Viewer, and admin review as read-only. Accept legacy `editor` only as a compatibility alias for Reporter.
87
87
  - `observation.state` replaces the complete previous observation for the same `clientId`.
88
- - `globalState.patch` recursively merges objects; arrays and scalars replace; `null` and `remove` delete explicit paths.
88
+ - `globalState.patch` recursively merges objects; arrays and scalars replace; `null` and `remove` delete explicit paths. The first reporting source to write a path owns it; another source cannot overwrite an owned path.
89
89
  - Reusing an `action.key` updates the same semantic stage. Do not create a new key for refreshes or retries.
90
- - `projections.timeline` replaces the complete array. Read first and preserve entries outside the producer's ownership.
91
- - `extensions` recursively merge within valid namespaces; arrays and scalars replace, and `null` deletes producer-owned fields.
90
+ - `projections.timeline` replaces only the current source's entries; AgentFlow preserves other sources atomically.
91
+ - `extensions` can update only `extensions[source]`; objects recursively merge, arrays/scalars replace, and `null` deletes producer-owned fields.
92
92
  - Publishing Markdown creates or updates a preview Artifact and review copy; it does not confirm a document or advance an Action.
93
93
 
94
94
  ## Failure handling
95
95
 
96
96
  - Missing token: stop and ask the user to configure `AGENTFLOW_TOKEN`.
97
97
  - HTTP 401/403: stop; do not retry with a token printed in a command or answer.
98
- - HTTP 409: follow the single read-merge-retry sequence.
98
+ - HTTP 409: inspect `workflow-resource-conflict` or `workflow-resource-ownership-conflict`, refresh the listed keys, and follow the single read-merge-retry sequence.
99
99
  - HTTP 400: fix the payload against the protocol reference; do not weaken validation.
100
100
  - Replayed idempotency key from the same `source`: accept `alreadyApplied: true` as success. Markdown publish returns the previously created preview instead of creating another copy.
@@ -3,7 +3,7 @@
3
3
  ## 目录
4
4
 
5
5
  1. 接入边界
6
- 2. 认证、身份与权限
6
+ 2. 认证、身份与权限(含 POST /api/workflows/access/sync)
7
7
  3. 数据区域模型
8
8
  4. GET /api/workflows/state
9
9
  5. POST /api/workflows/report
@@ -17,13 +17,14 @@
17
17
 
18
18
  ## 1. 接入边界
19
19
 
20
- 新接入只使用以下三个正式接口:
20
+ 新接入使用三个运行态数据接口,以及一个独立的权限控制面接口:
21
21
 
22
22
  | 方法 | 路径 | 用途 | 是否修改 Workflow |
23
23
  | --- | --- | --- | --- |
24
- | `GET` | `/api/workflows/state` | 读取当前快照和并发 revision | 否 |
24
+ | `GET` | `/api/workflows/state` | 读取当前快照和资源 key 版本 | 否 |
25
25
  | `POST` | `/api/workflows/report` | 上报全局信息、Action、普通产物、迭代归属和自定义区域 | 是 |
26
26
  | `POST` | `/api/workflow-artifacts/publish` | 把本地 Markdown 内容发布成浏览器可访问的预览链接 | 是 |
27
+ | `POST` | `/api/workflows/access/sync` | 同步 TAPD Owner 和参与人的派生权限 | 只修改权限 |
27
28
 
28
29
  `agentflow-workflow-report` 是接入规格;`workflow-report-client.mjs` 是可复用客户端;`agentflow-cli` 是命令行包装;AgentFlow 服务才负责鉴权、存储、合并和展示。Skill 不参与运行时传输,CLI 也不是数据生产方。
29
30
 
@@ -48,14 +49,53 @@ CLI 从 `AGENTFLOW_TOKEN` 或 `AGENTFLOW_SESSION_TOKEN` 读取凭证。不得把
48
49
 
49
50
  | 身份 | 读取 | 上报 / 发布预览 | 管理成员与分享 |
50
51
  | --- | --- | --- | --- |
51
- | Workflow owner | 是 | 是 | 是 |
52
- | 显式 editor | 是 | 是 | 否 |
53
- | 显式 viewer | 是 | 否 | 否 |
52
+ | TAPD Owner / Workflow Owner | 是 | 是 | 是 |
53
+ | 显式 Reporter | 是 | 是 | 否 |
54
+ | TAPD 参与人 | 是 | 否 | 否 |
55
+ | 显式 Viewer | 是 | 否 | 否 |
54
56
  | owner 同团队成员 | 是,团队视图自动获得 viewer 权限 | 否 | 否 |
55
57
  | 分享链接访问者 | 是 | 否 | 否 |
56
58
  | 超级管理员代看 | 是 | 否,只读审阅 | 否 |
57
59
 
58
- 首次由已认证用户上报一个尚未登记的 TAPD ID 时,该用户成为这个 Workflow owner。后续写入解析到 owner 的状态空间;没有写权限的调用返回 `403`,不会回退成调用者自己的副本。
60
+ 完成权限同步后,TAPD 需求 Owner 就是 Workflow Owner。TAPD 参与人匹配到已注册的 AgentFlow 账号后,默认得到派生 Viewer,不会自动获得上报权限。Owner 可在 AgentFlow 中显式授予 Reporter 或 Viewer。
61
+
62
+ 派生权限和显式授权分开保存:后续 TAPD 刷新可以增加或移除派生 Viewer,但不能抹掉 Owner 主动给出的显式授权。尚未同步 TAPD 人员的历史 Workflow 保留已有 Owner,避免升级时突然撤销权限。兼容客户端若跳过 access sync,首次上报仍会建立 `legacy` Owner;新接入不得依赖这个回退,应先同步 TAPD 权限。旧角色字符串 `editor` 作为兼容别名继续接受,并统一物化为 `reporter`。没有写权限的调用返回 `403`,不会回退成调用者自己的副本。
63
+
64
+ ### 2.3 TAPD 权限同步
65
+
66
+ Adapter 读取 TAPD Story 后、上报运行态之前,调用 `POST /api/workflows/access/sync`:
67
+
68
+ ```json
69
+ {
70
+ "workflow": { "namespace": "tapd", "id": "1020124" },
71
+ "authority": {
72
+ "type": "tapd",
73
+ "owner": { "username": "alice" },
74
+ "participants": ["alice", "bob", "carol"],
75
+ "observedAt": "2026-08-05T08:00:00.000Z",
76
+ "revision": "tapd-story-modified-at-or-content-digest"
77
+ }
78
+ }
79
+ ```
80
+
81
+ | 字段 | 必填 | 含义 |
82
+ | --- | --- | --- |
83
+ | `workflow` | 是 | 规范身份;当前仅支持 `tapd:<short-id>` |
84
+ | `authority.type` | 是 | 当前固定为 `tapd` |
85
+ | `authority.owner` | 是 | TAPD Owner username/userId;必须已注册或登录过 AgentFlow |
86
+ | `authority.participants` | 否 | TAPD 参与人 username/userId;匹配后成为派生 Viewer |
87
+ | `authority.observedAt` | 建议 | 读取人员快照的时间;旧于已保存快照时返回 `409` |
88
+ | `authority.revision` | 建议 | TAPD `modified` 值或人员内容摘要,用于审计和排查 |
89
+
90
+ 新 Workflow 首次同步时,当前登录用户必须是映射后的 TAPD Owner;超级管理员可代为初始化。后续同步只允许当前 Workflow Owner 或超级管理员执行。Owner 发生变化时只更新管理身份,Workflow 使用稳定的内部状态空间,已有 Action、产物和全局信息不会搬迁或变空。未注册的参与人会出现在响应的 `unresolvedParticipants` 中;他们注册并在后续同步被匹配前不获得权限。
91
+
92
+ CLI 等价命令:
93
+
94
+ ```bash
95
+ node skills/agentflow-cli/scripts/agentflow-cli.mjs workflow-access-sync \
96
+ --workflow tapd:1020124 \
97
+ --file workflow-access.json
98
+ ```
59
99
 
60
100
  ## 3. 数据区域模型
61
101
 
@@ -166,9 +206,24 @@ AI Docs / Issues 不是通用固定字段。当前唯一注册的 extension rend
166
206
 
167
207
  section key 为 `progress` 时使用紧凑响应式网格;其他 section 默认纵向排列。空 value 不渲染,未知 type 回退为 `text`。
168
208
 
209
+ ### 3.5 资源 key
210
+
211
+ 并发冲突与所有权都落在稳定资源 key,而不是整份 JSON:
212
+
213
+ ```text
214
+ action:<source>:<action.key>
215
+ artifact:<source>:<artifact.key>
216
+ projection:<source>:<kind>:<id>
217
+ global:<dot.path>
218
+ extension:<source>:<dot.path>
219
+ observation:<source>:<clientId>
220
+ ```
221
+
222
+ 同一请求可以触及多个 key。服务端在 Workflow 写锁内一次性校验全部 key;任意一个 key 冲突时整次请求不落库。`resourceKeys` 会随成功响应返回,便于接入方记录实际写入边界。
223
+
169
224
  ## 4. GET /api/workflows/state
170
225
 
171
- 读取当前物化快照。任何写入前都应先调用它,并保存 `snapshot.runtimeRevision`。
226
+ 读取当前物化快照。任何写入前都应先调用它,并保存本次会触及 key 对应的 `snapshot.resourceVersions`。
172
227
 
173
228
  ### 4.1 请求
174
229
 
@@ -196,6 +251,10 @@ Authorization: Bearer <AGENTFLOW_TOKEN>
196
251
  "workflow": { "namespace": "tapd", "id": "1020124", "key": "tapd:1020124" },
197
252
  "snapshot": {
198
253
  "runtimeRevision": "runtime:...",
254
+ "resourceVersions": {
255
+ "action:my-adapter:implementation:issue-1": "rv:...",
256
+ "projection:my-adapter:version:android-123": "rv:..."
257
+ },
199
258
  "globalState": {},
200
259
  "actions": [],
201
260
  "artifacts": [],
@@ -205,7 +264,7 @@ Authorization: Bearer <AGENTFLOW_TOKEN>
205
264
  }
206
265
  ```
207
266
 
208
- `snapshot` 只由服务端返回。客户端不得把一份旧 `snapshot` 原样 POST 回去。
267
+ `snapshot` 只由服务端返回。`runtimeRevision` 用于页面缓存和旧客户端的整 Workflow 严格锁;新接入使用 `resourceVersions` 做 key 级并发控制。客户端不得把一份旧 `snapshot` 原样 POST 回去。
209
268
 
210
269
  ## 5. POST /api/workflows/report
211
270
 
@@ -218,7 +277,9 @@ Authorization: Bearer <AGENTFLOW_TOKEN>
218
277
  "schemaVersion": 1,
219
278
  "workflow": { "namespace": "tapd", "id": "1020124" },
220
279
  "source": "my-adapter",
221
- "expectedRevision": "runtime:...",
280
+ "expectedVersions": {
281
+ "action:my-adapter:implementation:android:issue-1": "rv:..."
282
+ },
222
283
  "idempotencyKey": "implementation-finished:android:issue-1:v1",
223
284
  "observation": {},
224
285
  "action": {},
@@ -233,14 +294,15 @@ Authorization: Bearer <AGENTFLOW_TOKEN>
233
294
  | --- | --- | --- | --- |
234
295
  | `schemaVersion` | number | 否 | 当前固定为 `1` |
235
296
  | `workflow` | object/string | 是 | `{namespace,id}` 或规范 key;当前 namespace 仅支持 `tapd` |
236
- | `source` | string | 强烈建议 | 小写稳定的业务 Adapter 名称;默认 `agentflow-cli` 仅用于兼容,不应作为正式接入的生产方身份 |
237
- | `expectedRevision` | string | 修改已有状态时建议必填 | 最近一次 GET 返回的 runtime revision |
297
+ | `source` | string | | 小写稳定的业务 Adapter 名称;`agentflow-cli` 只是传输工具,不能作为默认生产方身份 |
298
+ | `expectedVersions` | object | 修改已有资源时建议必填 | 本次触及的全部资源 key 及 GET 返回的版本;创建新 key 使用 `absent` |
299
+ | `expectedRevision` | string | 兼容字段 | 仅在没有 `expectedVersions` 时启用的整 Workflow 严格锁;新接入不要使用 |
238
300
  | `idempotencyKey` | string | 强烈建议 | 一次业务语义操作的稳定身份,不使用时间戳或随机 UUID |
239
301
  | `observation` | object | 条件必填 | 同一 `clientId` 的完整生产方观察 |
240
302
  | `action` | object | 条件必填 | 一条关键业务阶段 |
241
303
  | `artifacts` | array | 条件必填 | Action 证据或全局证据 |
242
304
  | `globalState` | object | 条件必填 | 生产方事实的 merge patch / remove |
243
- | `projections` | object | 条件必填 | 通用迭代索引;当前包含完整 `timeline` 数组 |
305
+ | `projections` | object | 条件必填 | 通用迭代索引;提交当前 source 的完整 `timeline` 切片 |
244
306
  | `extensions` | object | 条件必填 | 按生产方 namespace 组织的自定义区域数据 |
245
307
  | `flowId` | string | 否 | 关联项目 ID |
246
308
  | `flowSource` | string | 否 | 关联项目来源,默认 `user` |
@@ -254,13 +316,19 @@ Authorization: Bearer <AGENTFLOW_TOKEN>
254
316
  "ok": true,
255
317
  "alreadyApplied": false,
256
318
  "report": {},
319
+ "resourceKeys": ["action:my-adapter:implementation:android:issue-1"],
257
320
  "event": {},
258
321
  "observation": { "accepted": true, "clientId": "my-adapter" },
259
- "snapshot": { "runtimeRevision": "runtime:new-revision" }
322
+ "snapshot": {
323
+ "runtimeRevision": "runtime:new-revision",
324
+ "resourceVersions": {
325
+ "action:my-adapter:implementation:android:issue-1": "rv:new-resource-version"
326
+ }
327
+ }
260
328
  }
261
329
  ```
262
330
 
263
- 没有 `observation` 时,响应中的 `observation` 为 `null`。同一 `source + idempotencyKey` 的幂等重放返回 `alreadyApplied: true`,应按成功处理;不同 source 可以安全复用相同业务 key。
331
+ 没有 `observation` 时,响应中的 `observation` 为 `null`。同一 `workflow + source + operation + idempotencyKey` 的幂等重放返回 `alreadyApplied: true`,应按成功处理;Report Artifact Publish 使用独立操作域。
264
332
 
265
333
  ## 6. POST /api/workflow-artifacts/publish:发布 Markdown 预览
266
334
 
@@ -281,7 +349,7 @@ Authorization: Bearer <AGENTFLOW_TOKEN>
281
349
  "artifactLabel": "方案预览",
282
350
  "durability": "temporary",
283
351
  "ttlDays": 7,
284
- "expectedRevision": "runtime:...",
352
+ "expectedVersions": { "artifact:my-adapter:plan:runtime-hook:android": "absent" },
285
353
  "idempotencyKey": "review:plan:runtime-hook:android:<content-digest>"
286
354
  }
287
355
  ```
@@ -289,7 +357,7 @@ Authorization: Bearer <AGENTFLOW_TOKEN>
289
357
  | 字段 | 类型 | 必填 | 含义 |
290
358
  | --- | --- | --- | --- |
291
359
  | `workflow` | object/string | 是 | 目标 Workflow |
292
- | `source` | string | 强烈建议 | 真实业务 Adapter 的稳定名称;不是 `agentflow-cli` |
360
+ | `source` | string | | 真实业务 Adapter 的稳定名称;不是 `agentflow-cli` |
293
361
  | `title` | string | 是 | Review 页面标题 |
294
362
  | `markdown` | string | 是 | Markdown 实际内容,不是本地路径 |
295
363
  | `stage` / `stageKey` | string | 建议 | 关联的稳定 Action 阶段 |
@@ -298,8 +366,9 @@ Authorization: Bearer <AGENTFLOW_TOKEN>
298
366
  | `artifactKey` | string | 是 | 预览 Artifact 的稳定槽位 |
299
367
  | `artifactLabel` | string | 否 | 页面按钮文案,默认 `Markdown Review` |
300
368
  | `durability` | string | 否 | `temporary` 或 `durable`;默认临时 |
301
- | `ttlDays` | number | 临时预览建议 | 临时副本有效天数,通常为 7 |
302
- | `expectedRevision` | string | 修改已有状态时建议必填 | 最近一次 GET 返回的 runtime revision;过期返回 `409` |
369
+ | `ttlDays` | number | 临时预览建议 | 1–30 的整数,通常为 7 |
370
+ | `expectedVersions` | object | 修改已有 Artifact 时建议必填 | 只需包含目标 `artifact:source:key`;创建时使用 `absent` |
371
+ | `expectedRevision` | string | 兼容字段 | 仅在没有 `expectedVersions` 时使用整 Workflow 严格锁 |
303
372
  | `idempotencyKey` | string | 强烈建议 | 建议包含内容摘要;同一 source + key 重放返回同一个预览,不创建新副本 |
304
373
 
305
374
  ### 6.2 成功响应
@@ -312,7 +381,7 @@ Authorization: Bearer <AGENTFLOW_TOKEN>
312
381
  - `event`:辅助运行态事件,不推进业务阶段。
313
382
  - `snapshot`:发布后的最新 Workflow 快照。
314
383
 
315
- 发布预览不会确认方案、修改本地文件、提交 ai-doc、创建 GitLab Issue 或推进 Action。外部系统已经提供 HTTP URL 时,不需要调用本接口,直接在 `/api/workflows/report` 的 `artifacts` 中上报即可。
384
+ 发布预览不会确认方案、修改本地文件、提交 ai-doc、创建 GitLab Issue 或推进 Action。Markdown 最大 500,000 bytes;`durability` 只能是 `temporary/durable`。外部系统已经提供 HTTP URL 时,不需要调用本接口,直接在 `/api/workflows/report` 的 `artifacts` 中上报即可。
316
385
 
317
386
  ## 7. 字段模型
318
387
 
@@ -357,7 +426,7 @@ Authorization: Bearer <AGENTFLOW_TOKEN>
357
426
  | --- | --- | --- |
358
427
  | `key` | 是 | 稳定阶段身份;同 key 更新同一阶段 |
359
428
  | `title` | 否 | 卡片标题,默认 key |
360
- | `detail` | 否 | 阶段摘要,最长按服务端约束截断 |
429
+ | `detail` | 否 | 阶段摘要,最多 4,000 字符;超限返回 `400`,不会截断 |
361
430
  | `status` | 否 | `pending/running/done/error/conflict/skipped/cancelled/observed` |
362
431
  | `group` | 否 | 阶段分组,例如 `implementation` |
363
432
  | `scope` | 否 | 业务范围 |
@@ -366,7 +435,7 @@ Authorization: Bearer <AGENTFLOW_TOKEN>
366
435
  | `tags` | 否 | 字符串数组 |
367
436
  | `occurredAt` | 否 | 业务发生时间;不要用重试时间覆盖它 |
368
437
 
369
- `completed/success` 会规范化为 `done`,`failed` 会规范化为 `error`,未知状态回退为 `pending`。
438
+ `completed/success` 会规范化为 `done`,`failed` 会规范化为 `error`;未知状态返回 `400`,不会静默回退。
370
439
 
371
440
  ### 7.3 artifacts:Action 或全局证据
372
441
 
@@ -386,7 +455,7 @@ Authorization: Bearer <AGENTFLOW_TOKEN>
386
455
  | `key` | 强烈建议 | 稳定证据身份;不要依赖标题去重 |
387
456
  | `type` | 否 | 产物类型,默认 `artifact` |
388
457
  | `title` | 否 | 展示标题 |
389
- | `url` / `path` | 至少一个 | 外部 URL 或可识别路径 |
458
+ | `url` / `path` | 至少一个 | URL 只允许 `http/https` 或站内绝对路径;本地文件必须先 Publish,不能直接形成可访问链接 |
390
459
  | `scope` | 否 | `action` 或 `global`;有 Action 时默认 `action` |
391
460
  | `status` | 否 | 生产方定义的证据状态 |
392
461
 
@@ -430,7 +499,7 @@ Authorization: Bearer <AGENTFLOW_TOKEN>
430
499
  | `dimensions` | 否 | 不透明筛选维度,例如 platform/team |
431
500
  | `order` | 否 | 日期缺失或相同时的稳定顺序 |
432
501
 
433
- 每次最多保留 100 条合法 timeline 项。
502
+ 每个 source 最多上报 100 条合法 timeline 项;超过限制返回错误,不会截断。
434
503
 
435
504
  ### 7.6 extensions:自定义区域
436
505
 
@@ -443,32 +512,33 @@ Authorization: Bearer <AGENTFLOW_TOKEN>
443
512
  }
444
513
  ```
445
514
 
446
- 扩展字段由生产方 schema 定义。AgentFlow 通用协议只校验 namespace 和顶层对象,不解释 `aiDocs`、`issues` 等私有字段。
515
+ 扩展字段由生产方 schema 定义。一次请求只能更新 `extensions[source]`;AgentFlow 通用协议不解释 `aiDocs`、`issues` 等私有字段。
447
516
 
448
517
  ## 8. 覆盖、合并与删除规则
449
518
 
450
519
  | 区域 | 省略字段 | 重复上报 | 删除 / 清空 |
451
520
  | --- | --- | --- | --- |
452
521
  | `observation.state` | 保持旧观察 | 同一 `clientId` 的完整 state 替换旧观察 | 上报生产方定义的空值结构;不要用它删除其他 client 的观察 |
453
- | `globalState` | 不修改 | 对象递归 merge;数组和标量整体替换 | patch 中 `null` 删除字段;`remove` 在 patch 后删除 dot path |
522
+ | `globalState` | 不修改 | 对象递归 merge;数组和标量整体替换;首次写入路径的 source 获得该路径所有权 | patch 中 `null` 删除字段;`remove` 在 patch 后删除 dot path;其他 source 不能改写已归属路径 |
454
523
  | `action` | 不修改 Action | 同 `source + action.key` 更新同一业务阶段的可见状态 | 当前协议不提供物理删除 Action;用业务状态表达取消/跳过 |
455
524
  | `artifacts` | 不修改产物 | 同 `source + stable key` 更新/归并同一可见证据 | 当前协议不提供通用物理删除;不要通过改 key 伪造删除 |
456
- | `projections.timeline` | 不修改 | **整数组替换**,不是按 key merge | `[]` 清空全部迭代归属 |
457
- | `extensions` | 不修改扩展 | namespace 内对象递归 merge;数组/标量替换 | 对应字段上报 `null` 删除;不要覆盖别人的 namespace |
525
+ | `projections.timeline` | 不修改 | 替换当前 `source` 拥有的完整切片,服务端原子保留其他 source | `[]` 只清空当前 source 的迭代归属 |
526
+ | `extensions` | 不修改扩展 | 只允许 `extensions[source]` 内对象递归 merge;数组/标量替换 | 对应字段上报 `null` 删除 |
458
527
 
459
- 更新 `projections.timeline` 前必须先 GET,保留不属于当前生产方的条目,再替换当前生产方拥有的 key。服务端不会自动按 `source` 帮你合并。
528
+ 客户端可以在兼容 payload 中携带未修改的其他 source 条目,但服务端只接受完全一致的副本且不会使用它覆盖现状。推荐只发送当前 source 的完整切片,由服务端按 `source + kind + id` 合并。
460
529
 
461
530
  ## 9. 并发、幂等与错误码
462
531
 
463
532
  ### 9.1 安全写入顺序
464
533
 
465
534
  1. GET 当前 Workflow。
466
- 2. 保存 `snapshot.runtimeRevision`。
467
- 3. 基于最新快照计算语义 patch,以及完整 timeline 数组。
468
- 4. POST 时带稳定 `source`、`expectedRevision` 与 `idempotencyKey`。
469
- 5. 收到 `409` 后重新 GET、重新合并,只重试一次。
535
+ 2. 根据本次业务操作计算会触及的 `resourceKeys`。
536
+ 3. `snapshot.resourceVersions` 复制这些 key 的版本;不存在的 key 使用 `absent`。
537
+ 4. POST 时带稳定 `source`、完整的 `expectedVersions` 与 `idempotencyKey`。
538
+ 5. 服务端在同一 Workflow 写锁内原子执行“校验所有 key → 合并 → 落盘”;无关 key 的变化不会冲突。
539
+ 6. 收到 `409` 后只刷新 `conflict.conflicts` 列出的 key,重新计算并重试一次。
470
540
 
471
- 不得在 revision 冲突后原样重放旧的完整数组。
541
+ 不得在资源 key 冲突后原样重放旧 payload。
472
542
 
473
543
  ### 9.2 幂等键
474
544
 
@@ -485,7 +555,7 @@ implementation-finished:android:runtime-hook:v1
485
555
  timeline-membership:tapd-1020124:android-version-1133202860001000338:v1
486
556
  ```
487
557
 
488
- 同一 `source + idempotencyKey` Workflow Report Artifact Publish 重放返回 `alreadyApplied: true`。Publish 会返回第一次创建的预览,不会先生成一个新文件再去重。业务内容发生变化时提高语义版本或使用内容摘要;不要使用请求时间。
558
+ 同一 `workflow + source + operation + idempotencyKey` 的重放返回 `alreadyApplied: true`,包括 `running/error/pending` Action。`report` `artifact.publish` 可以安全复用同一业务 key。Publish 会返回第一次创建的预览,不会先生成一个新文件再去重。业务内容发生变化时提高语义版本或使用内容摘要;不要使用请求时间。
489
559
 
490
560
  ### 9.3 错误码
491
561
 
@@ -493,9 +563,9 @@ timeline-membership:tapd-1020124:android-version-1133202860001000338:v1
493
563
  | --- | --- | --- |
494
564
  | `400` | JSON、namespace、字段或 schema 不合法 | 按协议修正;不要降级校验 |
495
565
  | `401` | 缺少或无效认证 | 停止并配置 Token;不要把 Token 打印出来 |
496
- | `403` | 当前用户只有 viewer 权限或无权访问目标项目 | 停止;由 owner 授予 editor 或改用正确身份 |
566
+ | `403` | 当前用户只有 viewer 权限或无权访问目标项目 | 停止;由 Owner 授予 Reporter 或改用正确身份 |
497
567
  | `404` | 分享链接、owner 或目标资源不存在 | 重新解析目标,不要创建影子副本 |
498
- | `409` | runtime revision 已变化 | GET 最新状态、重新合并、重试一次 |
568
+ | `409` | 同一资源 key 已变化,或路径属于其他 source | 读取 `conflict.conflicts`,只刷新冲突资源并重试一次;所有 key 通过前请求不会部分落库 |
499
569
  | `500` | 服务端异常 | 保留幂等键,记录脱敏上下文后重试或上报 |
500
570
 
501
571
  ## 10. 三个关键接入场景
@@ -503,10 +573,10 @@ timeline-membership:tapd-1020124:android-version-1133202860001000338:v1
503
573
  ### 10.1 更新迭代:绑定或切换版本
504
574
 
505
575
  1. 从业务系统取得稳定版本 ID、标题、日期和平台。
506
- 2. GET 当前 Workflow
576
+ 2. GET 当前 Workflow,并读取对应 GlobalState 路径与 Projection key 的资源版本。
507
577
  3. 用 `globalState.patch` 保存生产方拥有的完整版本事实。
508
- 4. 从现有 timeline 中保留其他生产方条目,替换自己的 `kind=version` 条目。
509
- 5. 使用最新 revision 上报。
578
+ 4. 发送当前 source 的完整 `kind=version` 切片;服务端保留其他 source 的 Sprint/Version。
579
+ 5. 使用这些 key 的 `expectedVersions` 上报。
510
580
 
511
581
  版本改名或改期时保持 `id/key` 不变,只改 `title/date`;切换版本时移除旧自有 key、加入新 key;取消归属时只移除自己的版本条目。
512
582
 
@@ -523,7 +593,7 @@ timeline-membership:tapd-1020124:android-version-1133202860001000338:v1
523
593
  1. 先判断 `globalState.sections` 的 `text/user/chips/list/link` 是否足够;足够时直接使用通用渲染器。
524
594
  2. 只有通用组件不能表达时,才定义稳定 namespace 和版本化扩展 schema。
525
595
  3. 把专用结构化事实放入 `extensions[namespace]`。
526
- 4. 注册对应页面渲染器;否则数据只会被保存,不会自动出现专用 UI。当前只有 `extensions["prd-flow"]` 已注册。
596
+ 4. AgentFlow 前端代码中注册对应页面渲染器并重新发布;当前不是运行时插件注册。否则数据只会被保存,不会自动出现专用 UI。当前只有 `extensions["prd-flow"]` 已注册。
527
597
  5. 更新数组时发送该数组的完整新值;更新对象字段时可以递归 merge;用 `null` 删除自有字段。
528
598
 
529
599
  ## 11. prd-flow 参考映射
@@ -533,6 +603,7 @@ prd-flow 只是一个接入实现,不是协议依赖:
533
603
  | prd-flow 事实 | 通用协议位置 | 页面结果 |
534
604
  | --- | --- | --- |
535
605
  | TAPD short ID | `workflow = tapd:<id>` | 串起同一需求、权限和分享 |
606
+ | TAPD Owner / 参与人 | `POST /api/workflows/access/sync` | Owner 管理权限;参与人默认只读 |
536
607
  | 计算出的完整当前状态 | `observation.state` | Workflow 全局概览和当前指针 |
537
608
  | TAPD 当前版本原始信息 | `globalState.tapdCurrentVersion` | 保留版本业务事实 |
538
609
  | 由版本事实派生的归属 | `projections.timeline[kind=version]` | 个人/团队迭代时间线 |
@@ -545,14 +616,16 @@ prd-flow 只是一个接入实现,不是协议依赖:
545
616
 
546
617
  ## 12. 验收清单
547
618
 
548
- - 能使用 Token GET 当前 Workflow,并读到 runtime revision。
549
- - 首次写入能建立 ownereditor 能写,viewer、团队成员、分享链接和管理员代看不能写。
550
- - `globalState` 更新不会覆盖其他生产方路径,数组替换行为符合预期。
619
+ - 能使用 Token GET 当前 Workflow,并读到 `snapshot.resourceVersions`。
620
+ - TAPD Owner 同步后成为 Workflow OwnerTAPD 参与人自动成为 Viewer;显式 Reporter 能写,Viewer、团队成员、分享链接和管理员代看不能写。
621
+ - TAPD 派生参与人刷新不会覆盖显式授权;过期的权限快照返回 `409`。
622
+ - `globalState` 更新不会覆盖其他生产方拥有的路径,数组替换行为符合预期。
551
623
  - 同一 Action key 重报不产生重复业务阶段。
552
624
  - Action 下能看到稳定 key 的 MR、构建或测试产物。
553
625
  - Markdown Publish 返回可访问 URL,但不会推进业务状态。
554
626
  - 版本改名/改期不产生新迭代节点,版本切换不会删除第三方 Sprint。
555
627
  - 自定义 extension 能保存;注册渲染器后能显示对应文档区 / Issue 区。
556
- - 409 会触发一次 read re-merge retry
628
+ - 不同资源 key 可并发更新;同 key 旧版本返回包含具体 `resourceKey` 的 409
629
+ - 409 会触发一次 key 级 read → re-merge → retry,且失败请求不会部分落库。
557
630
  - 幂等重放返回成功且不重复应用。
558
631
  - Token 不出现在 JSON、日志、Artifact 或最终输出中。