@fieldwangai/agentflow 0.1.142 → 0.1.144

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-pLsH0Uyj.js"></script>
19
- <link rel="stylesheet" crossorigin href="/assets/index-CtG65J3e.css">
18
+ <script type="module" crossorigin src="/assets/index-BnXKlBQj.js"></script>
19
+ <link rel="stylesheet" crossorigin href="/assets/index-5wTTj_jN.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.142",
3
+ "version": "0.1.144",
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",
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: agentflow-author-flow
3
+ description: Generate or revise an AgentFlow flow locally from a natural-language request, validate its flow.yaml, open a single-file platform-style preview, and publish it to personal, workspace, or team scope after user confirmation. Use when a user asks Codex, Cursor, Claude Code, or another coding agent to create, draw, preview, sync, upload, or publish an AgentFlow flow without using AI Composer or manually dragging nodes.
4
+ ---
5
+
6
+ # Author an AgentFlow
7
+
8
+ Turn the user's request into a locally reviewable Flow and handle the commands on their behalf. Do not ask the user to run preview or publish commands.
9
+
10
+ ## Required references
11
+
12
+ Before creating or changing node schemas or edges, read:
13
+
14
+ - [Builtin node schemas](../agentflow-node-reference/references/builtin-nodes.md)
15
+ - [Control capabilities](../../reference/flow-control-capabilities.md)
16
+ - [Flow layout](../../reference/flow-layout.md)
17
+
18
+ Read [prompt and handler checks](../../reference/flow-prompt-handler-check.md) when the Flow contains agent nodes. Read [standard recipes](../agentflow-flow-recipes/references/recipes.md) when a recipe matches the request.
19
+
20
+ ## Workflow
21
+
22
+ 1. Convert the request into a short node plan. Ask only for a business choice that materially changes behavior; do not ask for YAML details.
23
+ 2. Choose a stable lowercase flow ID using letters, digits, hyphens, or underscores.
24
+ 3. Create the draft at `.workspace/agentflow/pipelines/<flow-id>/flow.yaml`. Start from the packaged `builtin/pipelines/new/flow.yaml` when available. Preserve any unrelated workspace files.
25
+ 4. Build nodes from authoritative definitions. Never invent `definitionId`, slot order, slot type, handle index, or control semantics. Give every instance a unique position and keep the main path left-to-right.
26
+ 5. Validate the draft with `agentflow validate <flow-id> --json`. In this repository, use `node bin/agentflow.mjs validate <flow-id> --json`. Fix all errors before continuing; surface warnings that affect behavior.
27
+ 6. Generate and open the static preview with `agentflow flow preview <path-to-flow.yaml>`. In this repository, use `node bin/agentflow.mjs flow preview <path-to-flow.yaml>`. This command must exit after opening the generated `file://` HTML; do not start `agentflow ui` or another server.
28
+ 7. Report the draft path and a compact node/edge summary, then wait for the user's visual confirmation. Do not write to the AgentFlow platform before confirmation unless the user explicitly requested direct publish without review.
29
+ 8. Ask for `personal`, `workspace`, or `team` only if the user has not already chosen the destination. `team` means a workspace Flow shared as editor with the current account's active team.
30
+ 9. Publish through the sibling `agentflow-cli` skill:
31
+
32
+ ```bash
33
+ node ../agentflow-cli/scripts/agentflow-cli.mjs publish-flow \
34
+ --flow-id <flow-id> \
35
+ --file <path-to-flow.yaml> \
36
+ --target-space <personal|workspace|team>
37
+ ```
38
+
39
+ Resolve the script relative to this `SKILL.md`. Never expose the token.
40
+ 10. If the server reports that the Flow already exists, stop and explain the conflict. Use `--replace` only after the user explicitly confirms updating that exact Flow and destination. The CLI reads the current revision before replacement.
41
+ 11. Verify the published graph with `get-graph --flow-id <flow-id> --flow-source <user|workspace>` and report the platform Flow ID, scope, and result.
42
+
43
+ ## Safety rules
44
+
45
+ - Treat local generation and preview as reversible; treat platform publish and replacement as external writes.
46
+ - Never silently convert a personal Flow into a shared Flow.
47
+ - Never create a temporary test Flow on the production platform merely to validate tooling. Use an isolated local AgentFlow server or mock API for integration tests.
48
+ - Do not run the Flow unless the user asks to execute it; publishing is not execution.
49
+ - Keep a failed draft on disk so it can be inspected and repaired.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "AgentFlow Flow Authoring"
3
+ short_description: "Generate, preview, confirm, and publish AgentFlow flows"
4
+ default_prompt: "Use $agentflow-author-flow to generate a local AgentFlow, open its platform-style preview, and publish it after I confirm."
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: agentflow-cli
3
- description: Direct AgentFlow platform operation through a bundled token-backed CLI, without MCP. Use when Codex needs to list AgentFlow workspaces or flows, start or inspect runs, read graphs and logs, or fetch display outputs through AgentFlow HTTP APIs using AGENTFLOW_TOKEN from env or .env. Default AgentFlow base URL is http://ai.mengma.bigo.inner/.
3
+ description: Direct AgentFlow platform operation through a bundled token-backed CLI, without MCP. Use when Codex needs to list or publish AgentFlow flows, start or inspect runs, read graphs and logs, or fetch display outputs through AgentFlow HTTP APIs using AGENTFLOW_TOKEN from env or .env. Default AgentFlow base URL is http://ai.mengma.bigo.inner/.
4
4
  ---
5
5
 
6
6
  # AgentFlow CLI
@@ -71,6 +71,17 @@ List flows:
71
71
  node skills/agentflow-cli/scripts/agentflow-cli.mjs list-flows
72
72
  ```
73
73
 
74
+ Publish a new local Flow after the user has reviewed it:
75
+
76
+ ```bash
77
+ node skills/agentflow-cli/scripts/agentflow-cli.mjs publish-flow \
78
+ --flow-id release-check \
79
+ --file .workspace/agentflow/pipelines/release-check/flow.yaml \
80
+ --target-space personal
81
+ ```
82
+
83
+ Destinations are `personal`, `workspace`, and `team`. `team` creates a workspace Flow and shares it as editor with the current account's active team. Publishing is create-only by default. If the exact Flow already exists, stop and ask whether to update it; only after explicit confirmation rerun with `--replace`. Replacement first reads the server revision and submits it with the update.
84
+
74
85
  Read one flow graph:
75
86
 
76
87
  ```bash
@@ -124,16 +135,23 @@ The reusable transport lives in `scripts/workflow-report-client.mjs`. The CLI ex
124
135
 
125
136
  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.
126
137
 
138
+ The only admin write exception is audited version-membership repair. Read its strict revision with
139
+ `workflow-get --runtime-only --admin-operation repair-version-membership`, then send the matching
140
+ `workflow-report --admin-operation repair-version-membership` request as defined by
141
+ `agentflow-workflow-report`. This flag does not grant general Workflow read or write access.
142
+
127
143
  ## Workflow
128
144
 
129
145
  1. Check token availability with `config`.
130
- 2. Use `list-workspace` or `list-flows` to discover targets.
146
+ 2. Use `list-workspace` or `list-flows` to discover targets. Use `publish-flow` only after a local Flow has passed validation and the user has confirmed the preview.
131
147
  3. Use `run` to start the flow. If the task needs the generated page/text, inspect returned `displayOutputs` or call `display-outputs`.
132
148
  4. Use `status`, `list-run-by-workspace`, and `logs` when a run is active, failed, or needs debugging.
133
149
 
134
150
  ## Failure Handling
135
151
 
136
152
  - If the CLI says the token is missing, ask the user to set `AGENTFLOW_TOKEN` in env or `.env`.
153
+ - If `publish-flow` returns 409, do not add `--replace` automatically. Ask the user to confirm updating the existing Flow.
154
+ - If team publishing says no active team is assigned, keep the local draft and ask the user to choose personal/workspace or have an admin assign the account to a team.
137
155
  - If the API returns 401/403, do not retry with a printed token. Ask the user to refresh the token.
138
156
  - If `run` fails because a flow is already running, call `status` and `list-run-by-workspace` before retrying.
139
157
  - If local debugging is needed, override `AGENTFLOW_BASE_URL`; otherwise keep the default internal URL.
@@ -33,6 +33,7 @@ Commands:
33
33
  config
34
34
  list-workspace | list-workspaces
35
35
  list-flows
36
+ publish-flow --flow-id <id> --file <flow.yaml> [--target-space personal|workspace|team] [--replace]
36
37
  get-graph --flow-id <id> [--flow-source user]
37
38
  run --flow-id <id> [--flow-source user] [--run-node-id <id>] [--input k=v]
38
39
  status --flow-id <id> [--flow-source user]
@@ -41,9 +42,9 @@ Commands:
41
42
  logs --run-id <id>
42
43
  display-outputs --flow-id <id> [--flow-source user]
43
44
  sync-workspace --workspace <id>
44
- workflow-get --workflow tapd:<id> [--flow-id <id>] [--runtime-only]
45
+ workflow-get --workflow tapd:<id> [--flow-id <id>] [--runtime-only] [--admin-operation repair-version-membership]
45
46
  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-report --workflow tapd:<id> --file <report.json> [--source <adapter>] [--expected-revision <revision>] [--idempotency-key <key>] [--admin-operation repair-version-membership]
47
48
  workflow-artifact-publish --workflow tapd:<id> --file <artifact.json> [--source <adapter>] [--expected-revision <revision>] [--idempotency-key <key>]
48
49
  `;
49
50
  }
@@ -166,7 +167,36 @@ async function httpJson(args, pathname, { method = "GET", body, tokenRequired =
166
167
  }
167
168
  if (!response.ok) {
168
169
  const message = data?.error || data?.message || text || `HTTP ${response.status}`;
169
- throw new Error(`${method} ${url.pathname} failed: ${message}`);
170
+ const error = new Error(`${method} ${url.pathname} failed: ${message}`);
171
+ error.status = response.status;
172
+ error.response = data;
173
+ throw error;
174
+ }
175
+ return data;
176
+ }
177
+
178
+ async function httpMultipart(args, pathname, form) {
179
+ const token = authToken(args);
180
+ const url = new URL(pathname, normalizedBaseUrl(args));
181
+ const headers = { Accept: "application/json" };
182
+ if (token) {
183
+ headers.Authorization = `Bearer ${token}`;
184
+ headers.Cookie = `af_session=${encodeURIComponent(token)}`;
185
+ }
186
+ const response = await fetch(url, { method: "POST", headers, body: form });
187
+ const text = await response.text();
188
+ let data = null;
189
+ try {
190
+ data = text ? JSON.parse(text) : null;
191
+ } catch {
192
+ data = { text };
193
+ }
194
+ if (!response.ok) {
195
+ const message = data?.error || data?.message || text || `HTTP ${response.status}`;
196
+ const error = new Error(`POST ${url.pathname} failed: ${message}`);
197
+ error.status = response.status;
198
+ error.response = data;
199
+ throw error;
170
200
  }
171
201
  return data;
172
202
  }
@@ -277,6 +307,54 @@ function readJsonFile(filePath) {
277
307
  }
278
308
  }
279
309
 
310
+ function readFlowYamlFile(filePath) {
311
+ const requested = String(filePath || "").trim();
312
+ if (!requested) throw new Error("Missing --file <flow.yaml>.");
313
+ const resolved = path.resolve(requested);
314
+ let flowYaml;
315
+ try {
316
+ flowYaml = fs.readFileSync(resolved, "utf8");
317
+ } catch (error) {
318
+ throw new Error(`Cannot read flow file ${resolved}: ${error?.message || String(error)}`);
319
+ }
320
+ if (!flowYaml.trim()) throw new Error(`Flow file is empty: ${resolved}`);
321
+ return { resolved, flowYaml };
322
+ }
323
+
324
+ function targetDestinationFromArgs(args) {
325
+ const requested = (option(args, "target-space") || option(args, "flow-source") || "personal").toLowerCase();
326
+ if (requested === "personal" || requested === "user") return { flowSource: "user", shareWithTeam: false };
327
+ if (requested === "workspace") return { flowSource: "workspace", shareWithTeam: false };
328
+ if (requested === "team") return { flowSource: "workspace", shareWithTeam: true };
329
+ throw new Error("Invalid --target-space. Use personal|workspace|team (alias: user).");
330
+ }
331
+
332
+ async function importFlow(args, { flowId, targetSpace, resolved, flowYaml }) {
333
+ const form = new FormData();
334
+ form.set("flowId", flowId);
335
+ form.set("targetSpace", targetSpace);
336
+ form.set("file", new Blob([flowYaml], { type: "application/yaml" }), path.basename(resolved));
337
+ return httpMultipart(args, "/api/flows/import", form);
338
+ }
339
+
340
+ async function resolvePublishTeam(args, shareWithTeam) {
341
+ if (!shareWithTeam) return null;
342
+ const result = await httpJson(args, "/api/teams/me");
343
+ if (!result?.team?.id) {
344
+ throw new Error("Cannot publish to team: the current AgentFlow account is not assigned to an active team.");
345
+ }
346
+ return result.team;
347
+ }
348
+
349
+ async function sharePublishedFlowWithTeam(args, { flowId, flowSource, team }) {
350
+ if (!team) return null;
351
+ const result = await httpJson(args, "/api/workspace/collaboration/team-share", {
352
+ method: "POST",
353
+ body: { flowId, flowSource, teamId: team.id, role: "editor" },
354
+ });
355
+ return result?.team || team;
356
+ }
357
+
280
358
  async function main() {
281
359
  loadEnvFiles();
282
360
  const args = parseArgv(process.argv.slice(2));
@@ -312,6 +390,47 @@ async function main() {
312
390
  return;
313
391
  }
314
392
 
393
+ if (command === "publish-flow") {
394
+ const flowId = requireFlowId(args);
395
+ const destination = targetDestinationFromArgs(args);
396
+ const targetSpace = destination.flowSource;
397
+ const source = readFlowYamlFile(option(args, "file"));
398
+ const replace = args.replace === true;
399
+ const team = await resolvePublishTeam(args, destination.shareWithTeam);
400
+
401
+ if (!replace) {
402
+ const result = await importFlow(args, { flowId, targetSpace, ...source });
403
+ const sharedTeam = await sharePublishedFlowWithTeam(args, { flowId, flowSource: targetSpace, team });
404
+ printJson({ ...result, action: "created", targetSpace: team ? "team" : targetSpace, team: sharedTeam, file: source.resolved });
405
+ return;
406
+ }
407
+
408
+ let current = null;
409
+ try {
410
+ current = await httpJson(args, `/api/flow${query({ flowId, flowSource: targetSpace })}`);
411
+ } catch (error) {
412
+ if (error?.status !== 404) throw error;
413
+ }
414
+ if (!current) {
415
+ const result = await importFlow(args, { flowId, targetSpace, ...source });
416
+ const sharedTeam = await sharePublishedFlowWithTeam(args, { flowId, flowSource: targetSpace, team });
417
+ printJson({ ...result, action: "created", targetSpace: team ? "team" : targetSpace, team: sharedTeam, file: source.resolved });
418
+ return;
419
+ }
420
+ const result = await httpJson(args, "/api/flow", {
421
+ method: "POST",
422
+ body: {
423
+ flowId,
424
+ flowSource: targetSpace,
425
+ flowYaml: source.flowYaml,
426
+ baseRevision: current.revision,
427
+ },
428
+ });
429
+ const sharedTeam = await sharePublishedFlowWithTeam(args, { flowId, flowSource: targetSpace, team });
430
+ printJson({ ...result, flowId, flowSource: targetSpace, action: "updated", targetSpace: team ? "team" : targetSpace, team: sharedTeam, file: source.resolved });
431
+ return;
432
+ }
433
+
315
434
  if (command === "get-graph") {
316
435
  const flowId = requireFlowId(args);
317
436
  const flowSource = option(args, "flow-source") || "user";
@@ -390,12 +509,14 @@ async function main() {
390
509
  const flowId = option(args, "flow-id") || option(args, "flow");
391
510
  const flowSource = option(args, "flow-source") || "user";
392
511
  const runtimeOnly = args["runtime-only"] === true || args.cached === true ? "1" : "";
512
+ const adminOperation = option(args, "admin-operation") || "";
393
513
  const client = createWorkflowReportClient({ baseUrl: normalizedBaseUrl(args), token: authToken(args) });
394
514
  printJson(await client.getState({
395
515
  workflow: workflow.key,
396
516
  flowId,
397
517
  flowSource,
398
518
  runtimeOnly: runtimeOnly === "1",
519
+ adminOperation,
399
520
  }));
400
521
  return;
401
522
  }
@@ -412,11 +533,13 @@ async function main() {
412
533
  const expectedRevision = option(args, "expected-revision");
413
534
  const idempotencyKey = option(args, "idempotency-key");
414
535
  const reportSource = option(args, "source");
536
+ const adminOperation = option(args, "admin-operation");
415
537
  const flowId = option(args, "flow-id") || option(args, "flow");
416
538
  const flowSource = option(args, "flow-source");
417
539
  if (expectedRevision) body.expectedRevision = expectedRevision;
418
540
  if (idempotencyKey) body.idempotencyKey = idempotencyKey;
419
541
  if (reportSource) body.source = reportSource;
542
+ if (adminOperation) body.adminOperation = adminOperation;
420
543
  if (!String(body.source || "").trim()) throw new Error("Missing Workflow report source. Pass --source <adapter> or include source in the JSON file.");
421
544
  if (flowId) body.flowId = flowId;
422
545
  if (flowSource) body.flowSource = flowSource;
@@ -50,12 +50,13 @@ export function createWorkflowReportClient({ baseUrl, token, fetchImpl = globalT
50
50
  };
51
51
 
52
52
  return {
53
- getState({ workflow, flowId = "", flowSource = "user", runtimeOnly = false } = {}) {
53
+ getState({ workflow, flowId = "", flowSource = "user", runtimeOnly = false, adminOperation = "" } = {}) {
54
54
  return request(`/api/workflows/state${workflowQuery({
55
55
  workflow,
56
56
  flowId,
57
57
  flowSource,
58
58
  runtimeOnly: runtimeOnly ? "1" : "",
59
+ adminOperation,
59
60
  })}`);
60
61
  },
61
62
  syncAccess(body = {}) {
@@ -91,6 +91,9 @@ For local Markdown or other content that must become a browser URL, publish it f
91
91
 
92
92
  - 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.
93
93
  - 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 ordinary admin review as read-only. The only admin write exception is an explicit audited `adminOperation=repair-version-membership` Report containing only `kind=version` timeline projections, with required `expectedRevision` and `idempotencyKey`; it must never admit Action, Artifact, Observation, GlobalState, Extension, Checklist, or non-version projection writes. Accept legacy `editor` only as a compatibility alias for Reporter.
94
+ - An admin obtaining the strict revision for that repair must explicitly read state with
95
+ `adminOperation=repair-version-membership` (CLI: `workflow-get --runtime-only --admin-operation repair-version-membership`). The server resolves only that read as `admin-version-repair`; it does not grant ordinary admin CLI access to unrelated Workflows.
96
+ - Admin management UI must provide a concise `adminReason` for the audit event. Keep it optional for legacy CLI compatibility, but never omit it from new interactive management flows.
94
97
  - `observation.state` replaces the complete previous observation for the same `clientId`.
95
98
  - `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.
96
99
  - Reusing an `action.key` updates the same semantic stage. Do not create a new key for refreshes or retries.
@@ -240,12 +240,20 @@ GET /api/workflows/state?workflow=tapd%3A1020124&runtimeOnly=1
240
240
  Authorization: Bearer <AGENTFLOW_TOKEN>
241
241
  ```
242
242
 
243
+ 管理员为版本归属修复读取严格锁时,必须显式声明同一受限意图:
244
+
245
+ ```http
246
+ GET /api/workflows/state?workflow=tapd%3A1013667&runtimeOnly=1&adminOperation=repair-version-membership
247
+ Authorization: Bearer <AGENTFLOW_TOKEN>
248
+ ```
249
+
243
250
  | Query 参数 | 类型 | 必填 | 含义 |
244
251
  | --- | --- | --- | --- |
245
252
  | `workflow` | string | 与 namespace/id 二选一 | 规范 key,例如 `tapd:1020124` |
246
253
  | `namespace` | string | 与 workflow 二选一 | 当前仅支持 `tapd` |
247
254
  | `id` | string | 与 workflow 二选一 | TAPD short ID |
248
255
  | `runtimeOnly` | `0 \| 1` | 否 | `1` 只读取已保存运行态,不主动刷新上游;CLI 的 `--runtime-only` 使用它 |
256
+ | `adminOperation` | string | 管理员版本修复时必填 | 仅 `repair-version-membership`;只授予取得严格锁所需的受限读取,不授予普通 Workflow 写权限;服务端强制按 runtime-only 读取 |
249
257
  | `flowId` | string | 否 | 关联 AgentFlow 项目时指定项目 ID |
250
258
  | `flowSource` | string | 否 | 项目来源,默认 `user` |
251
259
  | `workspaceId` | string | 否 | 项目工作区上下文 |
@@ -308,6 +316,7 @@ Authorization: Bearer <AGENTFLOW_TOKEN>
308
316
  | `expectedRevision` | string | 兼容字段 | 仅在没有 `expectedVersions` 时启用的整 Workflow 严格锁;新接入不要使用 |
309
317
  | `idempotencyKey` | string | 强烈建议 | 一次业务语义操作的稳定身份,不使用时间戳或随机 UUID |
310
318
  | `adminOperation` | string | 管理员特例 | 仅 `repair-version-membership`;详见 5.3 |
319
+ | `adminReason` | string | 管理员 UI 建议必填 | 版本归属修复原因,最多 500 字符;写入审计事件 |
311
320
  | `observation` | object | 条件必填 | 同一 `clientId` 的完整生产方观察 |
312
321
  | `action` | object | 条件必填 | 一条关键业务阶段 |
313
322
  | `artifacts` | array | 条件必填 | Action 证据或全局证据 |
@@ -350,17 +359,19 @@ Authorization: Bearer <AGENTFLOW_TOKEN>
350
359
  "workflow": "tapd:1013667",
351
360
  "source": "prd-flow",
352
361
  "adminOperation": "repair-version-membership",
362
+ "adminReason": "清理测试版本并归属到正式迭代",
353
363
  "projections": {
354
364
  "timeline": [{
355
365
  "kind": "version",
356
- "id": "1013667",
366
+ "id": "1133202860001000338",
367
+ "key": "prd-flow:tapd-current-version:1133202860001000338",
357
368
  "title": "Likee Android&iOS V5.63",
358
369
  "date": "2026-08-11",
359
370
  "source": "prd-flow"
360
371
  }]
361
372
  },
362
373
  "expectedRevision": "runtime:<revision-from-get>",
363
- "idempotencyKey": "admin-version-repair:1013667:v563"
374
+ "idempotencyKey": "admin-version-repair:1013667:version-1133202860001000338"
364
375
  }
365
376
  ```
366
377
 
@@ -371,6 +382,7 @@ Authorization: Bearer <AGENTFLOW_TOKEN>
371
382
  - 所有新投影必须是 `kind=version`;当前 source 的非版本投影和其他 source 的全部投影原子保留。
372
383
  - `expectedRevision` 与 `idempotencyKey` 必填。并发变化返回 `409`,失败时不产生部分写入。
373
384
  - 运行态事件写入 `administrativeRepair.kind=version-attribution`、管理员 actor 和时间,供审计追踪。
385
+ - 管理 UI 必须提交 `adminReason`;旧 CLI 请求可暂时省略以保持兼容。
374
386
  - 空 `timeline` 表示清空该 source 的版本归属,但仍保留该 source 的 Sprint/Milestone 等非版本条目。
375
387
 
376
388
  ## 6. POST /api/workflow-artifacts/publish:发布 Markdown 预览
@@ -708,7 +720,7 @@ timeline-membership:tapd-1020124:version-1133202860001000338:v1
708
720
  ### 10.5 管理员修复版本归属
709
721
 
710
722
  1. 仅在批量治理版本归属时使用;普通业务状态仍由 Owner/Reporter 上报。
711
- 2. 管理员 GET 当前 Workflow,人工或程序核对目标版本并保存 `runtimeRevision`。
723
+ 2. 管理员使用 `adminOperation=repair-version-membership` GET 当前 Workflow,人工或程序核对目标版本自身的稳定 ID,并保存 `runtimeRevision`;不得把 TAPD 需求 ID 当作版本 ID。
712
724
  3. 发送只含 `kind=version` timeline 的 `repair-version-membership` 请求。
713
725
  4. 验证响应包含 `administrativeRepair`,事件 actor 是操作管理员,且非版本/其他 source 投影未变化。
714
726
  5. `409` 时重新读取、重新核对并只重试一次;不得绕过严格锁。
@@ -748,7 +760,7 @@ prd-flow 只是一个接入实现,不是协议依赖:
748
760
  - Action 下能看到稳定 key 的 MR、构建或测试产物。
749
761
  - Markdown Publish 返回可访问 URL,但不会推进业务状态。
750
762
  - 版本改名/改期不产生新迭代节点,版本切换不会删除第三方 Sprint。
751
- - 管理员版本归属修复只能改 `kind=version` 投影,要求 runtimeRevision/幂等键并留下管理员 actor 审计;普通 Report 仍返回 403。
763
+ - 管理员只有显式携带版本修复意图时才能 GET 当前 runtimeRevision;修复只能改 `kind=version` 投影,要求 runtimeRevision/幂等键并留下管理员 actor 审计;无意图读取与普通 Report 仍返回 403。
752
764
  - 自定义 extension 能保存;注册渲染器后能显示对应文档区 / Issue 区。
753
765
  - 不同资源 key 可并发更新;同 key 旧版本返回包含具体 `resourceKey` 的 409。
754
766
  - 409 会触发一次 key 级 read → re-merge → retry,且失败请求不会部分落库。