@qingflow-tech/qingflow-app-user-mcp 1.0.1 → 1.0.3

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.
Files changed (45) hide show
  1. package/README.md +2 -2
  2. package/docs/local-agent-install.md +9 -3
  3. package/npm/lib/runtime.mjs +10 -3
  4. package/package.json +1 -1
  5. package/pyproject.toml +1 -1
  6. package/skills/qingflow-app-user/SKILL.md +21 -12
  7. package/skills/qingflow-app-user/references/data-gotchas.md +1 -1
  8. package/skills/qingflow-app-user/references/public-surface-sync.md +70 -0
  9. package/skills/qingflow-app-user/references/record-patterns.md +1 -1
  10. package/skills/qingflow-record-analysis/SKILL.md +44 -2
  11. package/skills/qingflow-record-insert/SKILL.md +3 -0
  12. package/skills/qingflow-record-update/SKILL.md +3 -0
  13. package/skills/qingflow-task-ops/SKILL.md +31 -10
  14. package/src/qingflow_mcp/__init__.py +33 -1
  15. package/src/qingflow_mcp/builder_facade/models.py +14 -4
  16. package/src/qingflow_mcp/builder_facade/service.py +1582 -124
  17. package/src/qingflow_mcp/cli/commands/auth.py +69 -1
  18. package/src/qingflow_mcp/cli/commands/builder.py +4 -3
  19. package/src/qingflow_mcp/cli/commands/record.py +5 -5
  20. package/src/qingflow_mcp/cli/commands/task.py +74 -22
  21. package/src/qingflow_mcp/cli/commands/workspace.py +22 -0
  22. package/src/qingflow_mcp/cli/formatters.py +287 -48
  23. package/src/qingflow_mcp/cli/main.py +6 -1
  24. package/src/qingflow_mcp/cli/qingflow_login.py +116 -0
  25. package/src/qingflow_mcp/config.py +8 -0
  26. package/src/qingflow_mcp/errors.py +2 -2
  27. package/src/qingflow_mcp/id_utils.py +49 -0
  28. package/src/qingflow_mcp/public_surface.py +11 -1
  29. package/src/qingflow_mcp/response_trim.py +380 -9
  30. package/src/qingflow_mcp/server.py +4 -0
  31. package/src/qingflow_mcp/server_app_builder.py +11 -1
  32. package/src/qingflow_mcp/server_app_user.py +24 -0
  33. package/src/qingflow_mcp/session_store.py +69 -15
  34. package/src/qingflow_mcp/solution/compiler/form_compiler.py +2 -2
  35. package/src/qingflow_mcp/solution/executor.py +2 -2
  36. package/src/qingflow_mcp/tools/ai_builder_tools.py +48 -18
  37. package/src/qingflow_mcp/tools/app_tools.py +1 -0
  38. package/src/qingflow_mcp/tools/auth_tools.py +271 -12
  39. package/src/qingflow_mcp/tools/base.py +6 -2
  40. package/src/qingflow_mcp/tools/code_block_tools.py +2 -2
  41. package/src/qingflow_mcp/tools/import_tools.py +36 -2
  42. package/src/qingflow_mcp/tools/record_tools.py +410 -156
  43. package/src/qingflow_mcp/tools/resource_read_tools.py +114 -32
  44. package/src/qingflow_mcp/tools/task_context_tools.py +899 -141
  45. package/src/qingflow_mcp/tools/workspace_tools.py +141 -0
package/README.md CHANGED
@@ -3,13 +3,13 @@
3
3
  Install:
4
4
 
5
5
  ```bash
6
- npm install @qingflow-tech/qingflow-app-user-mcp@1.0.1
6
+ npm install @qingflow-tech/qingflow-app-user-mcp@1.0.3
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.1 qingflow-app-user-mcp
12
+ npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.3 qingflow-app-user-mcp
13
13
  ```
14
14
 
15
15
  Environment:
@@ -20,6 +20,12 @@
20
20
 
21
21
  `auth_use_credential` 是本地唯一鉴权主路径。
22
22
 
23
+ 补充说明:
24
+
25
+ - 对 stdio MCP 来说,主路径仍然只有 `auth_use_credential`。
26
+ - 如果你是在终端里直接使用 `qingflow` CLI,可以额外使用 `qingflow auth login` 作为“人类登录”入口;默认会提示轻流邮箱和隐藏密码,拿到 `token` 后建立本地 CLI 会话。
27
+ - 也就是说,这次新增的是 CLI 的登录入口,不是给 MCP 增加第二套会话模型。
28
+
23
29
  ## npm 安装器适用场景
24
30
 
25
31
  适合这类本地 agent / gateway:
@@ -211,7 +217,7 @@ qingflow-app-builder-mcp
211
217
  "command": "npx",
212
218
  "args": [
213
219
  "-y",
214
- "@qingflow-tech/qingflow-app-user-mcp"
220
+ "@josephyan/qingflow-app-user-mcp"
215
221
  ],
216
222
  "env": {
217
223
  "QINGFLOW_MCP_DEFAULT_BASE_URL": "https://qingflow.com/api",
@@ -224,7 +230,7 @@ qingflow-app-builder-mcp
224
230
  "command": "npx",
225
231
  "args": [
226
232
  "-y",
227
- "@qingflow-tech/qingflow-app-builder-mcp"
233
+ "@josephyan/qingflow-app-builder-mcp"
228
234
  ],
229
235
  "env": {
230
236
  "QINGFLOW_MCP_DEFAULT_BASE_URL": "https://qingflow.com/api",
@@ -266,7 +272,7 @@ npm install
266
272
 
267
273
  如果 MCP 客户端一调用工具就报 `Transport closed`,优先检查这几件事:
268
274
 
269
- 1. 不要混用不同版本的 `@qingflow-tech/qingflow-cli`、`@qingflow-tech/qingflow-app-user-mcp`、`@qingflow-tech/qingflow-app-builder-mcp`
275
+ 1. 不要混用不同版本的 `@josephyan/qingflow-cli`、`@josephyan/qingflow-app-user-mcp`、`@josephyan/qingflow-app-builder-mcp`
270
276
  2. 删除安装目录下的 `.npm-python`
271
277
  3. 重新执行 `npm install` 或重新安装对应 tgz/npm 包
272
278
  4. 再启动 MCP 客户端
@@ -287,7 +287,12 @@ function forwardSignal(child, signal) {
287
287
  });
288
288
  }
289
289
 
290
- export function spawnServer(packageRoot, args, commandName = "qingflow-mcp", { allowRuntimeBootstrap = false } = {}) {
290
+ export function spawnServer(
291
+ packageRoot,
292
+ args,
293
+ commandName = "qingflow-mcp",
294
+ { allowRuntimeBootstrap = false, stdio = "proxy" } = {},
295
+ ) {
291
296
  let runtime = inspectPythonEnv(packageRoot, commandName);
292
297
  let serverCommand = runtime.serverCommand;
293
298
 
@@ -315,12 +320,14 @@ export function spawnServer(packageRoot, args, commandName = "qingflow-mcp", { a
315
320
  }
316
321
 
317
322
  const child = spawn(serverCommand, args, {
318
- stdio: ["pipe", "pipe", "pipe"],
323
+ stdio: stdio === "inherit" ? "inherit" : ["pipe", "pipe", "pipe"],
319
324
  env: process.env,
320
325
  windowsHide: true,
321
326
  });
322
327
 
323
- proxyStreams(child);
328
+ if (stdio !== "inherit") {
329
+ proxyStreams(child);
330
+ }
324
331
  forwardSignal(child, "SIGINT");
325
332
  forwardSignal(child, "SIGTERM");
326
333
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qingflow-tech/qingflow-app-user-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Operational end-user MCP for Qingflow records, tasks, comments, and directory workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qingflow-mcp"
7
- version = "0.2.0b87"
7
+ version = "1.0.3"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -11,31 +11,35 @@ metadata:
11
11
 
12
12
  This skill is a lightweight router for operational Qingflow work.
13
13
  Assumes MCP is connected, authenticated, and on the correct workspace.
14
+ Before routing, skim the shared maintenance baseline: [public-surface-sync.md](references/public-surface-sync.md).
14
15
 
15
16
  ## Default Paths
16
17
 
17
18
  Route to exactly one of these specialized paths:
18
19
 
19
20
  1. Record insert
20
- Switch to [$qingflow-record-insert](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-insert/SKILL.md)
21
+ Switch to [$qingflow-record-insert](../qingflow-record-insert/SKILL.md)
21
22
 
22
23
  2. Record update
23
- Switch to [$qingflow-record-update](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-update/SKILL.md)
24
+ Switch to [$qingflow-record-update](../qingflow-record-update/SKILL.md)
24
25
 
25
26
  3. Record delete
26
- Switch to [$qingflow-record-delete](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-delete/SKILL.md)
27
+ Switch to [$qingflow-record-delete](../qingflow-record-delete/SKILL.md)
27
28
 
28
29
  4. Record import
29
- Switch to [$qingflow-record-import](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-import/SKILL.md)
30
+ Switch to [$qingflow-record-import](../qingflow-record-import/SKILL.md)
30
31
 
31
32
  5. Task workflow operations
32
- Switch to [$qingflow-task-ops](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-task-ops/SKILL.md)
33
+ Switch to [$qingflow-task-ops](../qingflow-task-ops/SKILL.md)
33
34
 
34
35
  6. Analysis
35
- Switch to [$qingflow-record-analysis](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-analysis/SKILL.md)
36
+ Switch to [$qingflow-record-analysis](../qingflow-record-analysis/SKILL.md)
36
37
 
37
38
  7. MCP connection / auth / workspace selection
38
- Switch to [$qingflow-mcp-setup](/Users/yanqidong/.codex/skills/qingflow-mcp-setup/SKILL.md)
39
+ Switch to [$qingflow-mcp-setup](../qingflow-mcp-setup/SKILL.md)
40
+
41
+ 8. App / view / workflow / chart / portal / package configuration
42
+ Switch to [$qingflow-app-builder](../qingflow-app-builder/SKILL.md)
39
43
 
40
44
  ## Routing Rules
41
45
 
@@ -46,11 +50,13 @@ Route to exactly one of these specialized paths:
46
50
  - If the task is about deleting records directly, switch to `$qingflow-record-delete`
47
51
  - If the task is about import templates, import capability discovery, import-file verification, authorized local file repair, import execution, or import status, switch to `$qingflow-record-import`
48
52
  - If the task is about todo discovery, task context, approval actions, rollback or transfer, associated report review, or workflow log review, switch to `$qingflow-task-ops`
53
+ - If the task is about package, app, field, layout, workflow, view, chart, portal, visibility, icon, or app base configuration, switch to `$qingflow-app-builder`
49
54
  - If the task involves member, department, or relation fields and the user only has natural names/titles, keep the same route; direct write now supports backend-native auto resolution and may return `needs_confirmation` with candidates instead of failing blind
50
55
  - If the task involves linked visibility, upstream/downstream field dependencies, reference-driven auto fill, or formula-driven defaulting, keep the same insert/update route and read field-level `linkage` from the schema before composing payloads
51
56
  - If the task is about subtable writes, still route to the matching insert/update skill, but shape the payload as parent subtable field -> row array; do not route users toward top-level leaf selectors
52
57
  - If the task is insert-focused and readback consistency matters, keep the same route and prefer `record_get / record_list` with `output_profile="normalized"` after the write
53
58
  - If the user sounds like an ordinary workflow assignee rather than a system operator, prefer `$qingflow-task-ops` over direct record mutation whenever both paths could fit
59
+ - If the task is about task discovery by natural language query, still route to `$qingflow-task-ops`; `task_list --query` now uses backend search first and only falls back to local matching when backend returns zero rows
54
60
  - If the task is about grouped distributions, ratios, rankings, trends, insights, or any final statistical conclusion, switch to `$qingflow-record-analysis`
55
61
  - If the MCP is not connected, authenticated, or bound to the right workspace, switch to `$qingflow-mcp-setup`
56
62
 
@@ -72,8 +78,11 @@ Route to exactly one of these specialized paths:
72
78
 
73
79
  ## Resources
74
80
 
75
- - Record insert: [$qingflow-record-insert](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-insert/SKILL.md)
76
- - Record update: [$qingflow-record-update](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-update/SKILL.md)
77
- - Record delete: [$qingflow-record-delete](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-delete/SKILL.md)
78
- - Record import: [$qingflow-record-import](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-import/SKILL.md)
79
- - Dedicated analysis workflow: [$qingflow-record-analysis](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-analysis/SKILL.md)
81
+ - Shared public-surface baseline: [public-surface-sync.md](references/public-surface-sync.md)
82
+ - Record insert: [$qingflow-record-insert](../qingflow-record-insert/SKILL.md)
83
+ - Record update: [$qingflow-record-update](../qingflow-record-update/SKILL.md)
84
+ - Record delete: [$qingflow-record-delete](../qingflow-record-delete/SKILL.md)
85
+ - Record import: [$qingflow-record-import](../qingflow-record-import/SKILL.md)
86
+ - Task workflow operations: [$qingflow-task-ops](../qingflow-task-ops/SKILL.md)
87
+ - Dedicated analysis workflow: [$qingflow-record-analysis](../qingflow-record-analysis/SKILL.md)
88
+ - Builder workflow: [$qingflow-app-builder](../qingflow-app-builder/SKILL.md)
@@ -1,6 +1,6 @@
1
1
  # Data Gotchas
2
2
 
3
- For final statistics, grouped distributions, rankings, trends, or insight-style conclusions, use [$qingflow-record-analysis](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-analysis/SKILL.md) instead of keeping that reasoning inside `$qingflow-app-user`.
3
+ For final statistics, grouped distributions, rankings, trends, or insight-style conclusions, use [$qingflow-record-analysis](../../qingflow-record-analysis/SKILL.md) instead of keeping that reasoning inside `$qingflow-app-user`.
4
4
 
5
5
  ## Record Reads
6
6
 
@@ -0,0 +1,70 @@
1
+ # Qingflow Core Public Surface Sync
2
+
3
+ Use this file as the maintenance baseline for the core Qingflow skills.
4
+ It is not a user-facing product spec. It exists to prevent skill drift.
5
+
6
+ ## Current Public Defaults
7
+
8
+ ### User data
9
+
10
+ - Read range first with `app_get`, then `record_browse_schema_get(view_id=...)`
11
+ - Standard flows:
12
+ - browse / export detail: `app_get -> record_browse_schema_get -> record_list / record_get`
13
+ - insert: `record_insert_schema_get -> record_insert`
14
+ - update: `record_update_schema_get -> record_update`
15
+ - analyze: `app_get -> record_browse_schema_get -> record_analyze`
16
+
17
+ ### Tasks
18
+
19
+ - Discovery stays on `task_list`
20
+ - `task_list --query` uses backend search first and only applies local fallback when backend returns zero rows
21
+ - Public actions are:
22
+ - `approve`
23
+ - `reject`
24
+ - `rollback`
25
+ - `transfer`
26
+ - `urge`
27
+ - `save_only`
28
+ - `reject` requires `payload.audit_feedback`
29
+ - `save_only` requires non-empty `fields`
30
+ - `TASK_RUNTIME_CONSUMED_AFTER_ACTION` is a normal post-success warning when the current node runtime is consumed and `46001` appears on re-read
31
+
32
+ ### Builder
33
+
34
+ - Official package entry: `package_get`, `package_apply`
35
+ - Official builder writes:
36
+ - `app_schema_apply`
37
+ - `app_layout_apply`
38
+ - `app_flow_apply`
39
+ - `app_views_apply`
40
+ - `app_charts_apply`
41
+ - `portal_apply`
42
+ - `app_publish_verify`
43
+ - `portal_apply` edit mode may omit `sections` for base-info-only updates
44
+ - `app_charts_apply.visibility` is a public capability and should be treated as a base-only visibility update
45
+ - `app_get.editability` uses:
46
+ - `can_edit_app_base`
47
+ - `can_edit_form`
48
+ - `can_edit_flow`
49
+ - `can_edit_views`
50
+ - `can_edit_charts`
51
+
52
+ ## Known High-Drift Areas
53
+
54
+ - Task actions, especially `save_only`, reject payload requirements, and `46001` post-action interpretation
55
+ - Package public tools: do not regress to `package_create` / `package_attach_app` as the public default story
56
+ - App editability: do not let `can_edit_form` imply app base-info writes
57
+ - Portal and chart visibility: keep the public story on `portal_apply` / `app_charts_apply`, not low-level internal writes
58
+ - Analysis fallback: standard path stays `record_analyze`, but complex tables may require explicit fallback modes
59
+
60
+ ## Release Checklist For Skill Maintenance
61
+
62
+ After each beta that changes public behavior, re-check:
63
+
64
+ 1. `public_surface.py`
65
+ 2. `README.md`
66
+ 3. `server_app_builder.py` and `server.py` top-level guidance
67
+ 4. CLI help for `task`, `builder package`, `builder portal`, `builder charts`
68
+ 5. Whether new warnings or verification fields need to be explained in skills
69
+
70
+ If a tool behavior changed but the public surface did not, prefer updating the relevant skill section instead of expanding this file.
@@ -1,6 +1,6 @@
1
1
  # Record Patterns
2
2
 
3
- If the task shifts into grouped analysis, ratio, ranking, trend, or any final statistical conclusion, switch to [$qingflow-record-analysis](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-analysis/SKILL.md).
3
+ If the task shifts into grouped analysis, ratio, ranking, trend, or any final statistical conclusion, switch to [$qingflow-record-analysis](../../qingflow-record-analysis/SKILL.md).
4
4
 
5
5
  ## Browse Pattern
6
6
 
@@ -17,7 +17,42 @@ If `app_get.accessible_views` marks a view with `analysis_supported=false`, do n
17
17
 
18
18
  This is the ONLY execution order. Never skip `app_get` when the browse range is unclear. Never call `record_analyze` without a browse schema.
19
19
 
20
- Core tools: `app_get`, `record_browse_schema_get`, `record_analyze`. Use `record_list`/`record_get` only for post-analysis samples; task/comment work stays in [$qingflow-task-ops](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-task-ops/SKILL.md).
20
+ Core tools: `app_get`, `record_browse_schema_get`, `record_analyze`. Use `record_list`/`record_get` only for post-analysis samples or an explicit fallback. Task/comment work stays in [$qingflow-task-ops](../qingflow-task-ops/SKILL.md).
21
+
22
+ ## Execution Modes
23
+
24
+ Choose the lightest mode that can still support a trustworthy conclusion:
25
+
26
+ 1. `server_aggregate`
27
+ - Default and preferred path
28
+ - Use `record_analyze` directly after `app_get -> record_browse_schema_get`
29
+
30
+ 2. `client_aggregate_from_record_list`
31
+ - Allowed fallback when `record_analyze` is unstable, unsupported on the target view, or the table is complex enough that service-side aggregation is not trustworthy
32
+ - Still requires `app_get -> record_browse_schema_get` first
33
+ - Use only after disclosing that the result is a fallback built from detail rows
34
+
35
+ 3. `cross_app_manual_reconcile`
36
+ - Use when the question depends on joining multiple apps, organization-history alias mapping, or other business logic that current public tools do not express directly
37
+ - Be explicit that the conclusion is based on manual reconciliation rules, not one single DSL execution
38
+
39
+ ## Fallback Ladder
40
+
41
+ Trigger a fallback when any of these are true:
42
+
43
+ - `record_analyze` is unstable or cannot complete the scan reliably
44
+ - the target view is unsupported for analysis
45
+ - field semantics are ambiguous enough that a server aggregate would be misleading
46
+ - the question requires cross-app reconciliation
47
+ - the question depends on organization-tree scope, historical department aliases, or other business rules that are not first-class MCP filters
48
+
49
+ When you fall back:
50
+
51
+ 1. Keep the standard read order: `app_get -> record_browse_schema_get`
52
+ 2. State which fallback mode you chose
53
+ 3. State whether the result is still full-scope or only a verified subset
54
+ 4. State the time field, organization scope, and any alias mapping used
55
+ 5. Prefer concrete numbers plus a conservative conclusion over broad wording
21
56
 
22
57
  ---
23
58
 
@@ -129,7 +164,7 @@ Top-level arguments:
129
164
  - Do not pass field titles, aliases, or guessed ids.
130
165
  - If `completeness.statement_scope=returned_groups_only` or `completeness.rows_truncated=true`, downgrade wording to returned groups only.
131
166
  - One DSL per question. Multiple small DSLs > one overloaded request.
132
- - `record_list` is NEVER the basis for final statistics.
167
+ - `record_list` is not the default basis for final statistics. Use it only in an explicit fallback mode and disclose that fallback in the final answer.
133
168
  - Set `alias` for any metric you will sort by, compare, or quote.
134
169
 
135
170
  ---
@@ -137,12 +172,18 @@ Top-level arguments:
137
172
  ## OUTPUT
138
173
 
139
174
  - Final answer must show concrete numbers.
175
+ - Final answer must state which execution mode was used whenever the answer is not a straightforward `server_aggregate`
140
176
  - If `result.rows` exists, list each returned row; if there are more than 20 rows, show Top 20 and say so.
141
177
  - 占比 = 行指标值 / `result.totals.metric_totals` 总值;如 `metric_totals` 缺失,用各行之和作分母。
142
178
  - Prefer the structured `ranking` block when it exists.
143
179
  - `safe_for_final_conclusion=true` → `全量可信结论`
144
180
  - Otherwise → `初步观察`
145
181
  - `rows_truncated=true` → 用 `前 N 个分组`, 不用 `全部`/`所有`
182
+ - If you used a fallback mode, explicitly disclose:
183
+ - whether this is full-scope or a manually curated subset
184
+ - which time field was used
185
+ - which organization scope was used
186
+ - whether any historical department aliases or cross-app joins were applied
146
187
 
147
188
  ## Feedback Escalation
148
189
 
@@ -152,6 +193,7 @@ Top-level arguments:
152
193
 
153
194
  ## Resources
154
195
 
196
+ - Shared public-surface baseline: [public-surface-sync.md](../qingflow-app-user/references/public-surface-sync.md)
155
197
  - DSL templates: [references/dsl-templates.md](references/dsl-templates.md)
156
198
  - Analysis patterns: [references/analysis-patterns.md](references/analysis-patterns.md)
157
199
  - Confidence reporting: [references/confidence-reporting.md](references/confidence-reporting.md)
@@ -36,6 +36,8 @@ metadata:
36
36
  13. If the write returns `status="needs_confirmation"`, stop and surface the candidates
37
37
  14. Retry with explicit ids / objects only after the user confirms
38
38
  15. Keep `verify_write=true` for production inserts
39
+ 16. If post-write readback consistency matters, prefer `record_get(..., output_profile="normalized")` and surface `normalized_ambiguous_fields` instead of pretending same-title columns are unambiguous
40
+ 17. Treat nested schema shape as guidance, not a brittle contract; do not hard-code transient implementation details like optional nested `field_id` shape when composing inserts
39
41
 
40
42
  ## Field Notes
41
43
 
@@ -56,3 +58,4 @@ metadata:
56
58
  - Do not invent missing required fields
57
59
  - Do not flatten subtable leaf fields to the top level
58
60
  - Do not silently guess member / department / relation ids
61
+ - Do not bind logic to a transient nested schema serialization detail when the field title and parent table already identify the legal payload shape
@@ -33,6 +33,8 @@ metadata:
33
33
  11. If the write returns `status="needs_confirmation"`, stop and surface the candidates
34
34
  12. Do not assume any arbitrary combination of writable fields will succeed; one single matched accessible view still has to cover the payload
35
35
  13. Do not look for any extra context bucket in update schema; lookup behavior stays inline on the field definitions themselves
36
+ 14. When update context feels unstable, trust `record_update_schema_get`'s route-aware matched-view result over guessed `view_id` or remembered UI scope
37
+ 15. If readback consistency matters, prefer `record_get(..., output_profile="normalized")` after the write and surface `normalized_ambiguous_fields` instead of hiding same-title conflicts
36
38
 
37
39
  ## Do Not
38
40
 
@@ -40,3 +42,4 @@ metadata:
40
42
  - Do not update fields missing from `writable_fields`
41
43
  - Do not resolve lookup fields against a guessed record context
42
44
  - Do not ignore `linkage.affects_fields` when changing source-like fields on complex forms
45
+ - Do not fall back to guessed browse scopes when `record_update_schema_get` already tells you which matched route can or cannot execute the payload
@@ -11,6 +11,7 @@ metadata:
11
11
 
12
12
  This skill is for task workflow operations only.
13
13
  Assumes MCP is connected, authenticated, and on the correct workspace.
14
+ Before executing, skim the shared maintenance baseline: [public-surface-sync.md](../qingflow-app-user/references/public-surface-sync.md).
14
15
 
15
16
  ## Default Paths
16
17
 
@@ -28,6 +29,9 @@ Use exactly one of these default paths:
28
29
  4. Execute workflow action
29
30
  `task_list -> exact target -> task_get -> task_action_execute`
30
31
 
32
+ 5. Execute a user-specified action on an already-clear target
33
+ `task_list -> exact target -> (optional task_get) -> task_action_execute`
34
+
31
35
  ## Core Tools
32
36
 
33
37
  - `task_list`
@@ -43,16 +47,25 @@ Use exactly one of these default paths:
43
47
 
44
48
  ## Standard Operating Order
45
49
 
46
- 1. Ensure auth exists
47
- 2. Ensure workspace is selected
48
- 3. Discover the exact target with `task_list`
49
- 4. Read node context with `task_get`
50
- 5. Before giving any approval recommendation, read `task_workflow_log_get`
51
- 6. If `task_get` returns any `associated_reports`, read every visible report through `task_associated_report_detail_get`
52
- 7. Give an approval recommendation only after reviewing the node context, workflow log, and associated report details
53
- 8. Wait for explicit user confirmation before `task_action_execute`
54
- 9. Execute through `task_action_execute`
55
- 10. After actions, report the exact `app_key`, `record_id`, `workflow_node_id`, and executed action
50
+ Use one of these two modes:
51
+
52
+ 1. Recommendation mode
53
+ 1. Ensure auth exists
54
+ 2. Ensure workspace is selected
55
+ 3. Discover the exact target with `task_list`
56
+ 4. Read node context with `task_get`
57
+ 5. Before giving any approval recommendation, read `task_workflow_log_get`
58
+ 6. If `task_get` returns any `associated_reports`, read every visible report through `task_associated_report_detail_get`
59
+ 7. Give a recommendation only after reviewing node context, workflow log, and associated reports
60
+ 8. Wait for explicit user confirmation before `task_action_execute`
61
+
62
+ 2. User-directed execution mode
63
+ 1. Ensure auth exists
64
+ 2. Ensure workspace is selected
65
+ 3. Discover the exact target with `task_list`
66
+ 4. If the target or action requirements are ambiguous, read `task_get`; otherwise go straight to `task_action_execute`
67
+ 5. Execute through `task_action_execute`
68
+ 6. After actions, report the exact `app_key`, `record_id`, `workflow_node_id`, executed action, and any warnings
56
69
 
57
70
  ## Task-Center Rules
58
71
 
@@ -74,6 +87,7 @@ Use exactly one of these default paths:
74
87
  - `unread`
75
88
  - `ended`
76
89
  - `task_list` is the only public task discovery path in this MCP surface
90
+ - `task_list --query` uses backend `searchKey` first; only when backend returns zero rows does MCP apply a local fallback match on normalized `app_name / workflow_node_name / app_key / record_id`
77
91
  - Treat `task_id` as a locator only; the action primary key is `app_key + record_id + workflow_node_id`
78
92
  - Default box usage:
79
93
  - `todo`: `task_list -> task_get -> task_workflow_log_get / task_associated_report_detail_get -> recommendation -> explicit user confirmation -> task_action_execute`
@@ -91,15 +105,20 @@ Use exactly one of these default paths:
91
105
  - `rollback`
92
106
  - `transfer`
93
107
  - `urge`
108
+ - `save_only`
94
109
  - Before any approve/reject/rollback/transfer recommendation, always review `task_workflow_log_get` when `task_get.visibility.audit_record_visible=true`
95
110
  - If `task_get` returns visible `associated_reports`, review each one with `task_associated_report_detail_get`; do not rely on report summary alone
96
111
  - Do not give an approval recommendation based only on `task_get`
97
112
  - Do not execute `task_action_execute` until the user explicitly confirms the chosen action
113
+ - Exception: if the user has already explicitly authorized a concrete action on exact targets, you may execute directly after exact target resolution
98
114
  - Avoid actions on ambiguous tasks or records
99
115
  - Summarize the final action and the exact `app_key / record_id / workflow_node_id`
116
+ - `reject` requires `payload.audit_feedback`
117
+ - `save_only` requires non-empty `fields` and is only available when the backend exposes editable fields for the current node
100
118
  - `task_action_execute` now distinguishes action execution from workflow continuation. Read `verification.runtime_continuation_verified` before claiming the workflow actually moved on.
101
119
  - If `task_action_execute` returns `partial_success` with `WORKFLOW_CONTINUATION_UNVERIFIED`, report the action as sent but the downstream continuation as unverified.
102
120
  - If `task_action_execute` returns `TASK_CONTEXT_VISIBILITY_UNVERIFIED` after a `46001`-style context loss, do not claim the task was already processed unless the workflow log or record state proves it.
121
+ - If `task_action_execute` returns `TASK_RUNTIME_CONSUMED_AFTER_ACTION`, treat that as a normal post-success state: the current node runtime was consumed, the workflow likely continued, and `46001` does not by itself mean the action failed
103
122
 
104
123
  ## Feedback Escalation
105
124
 
@@ -110,11 +129,13 @@ Use exactly one of these default paths:
110
129
  ## Response Interpretation
111
130
 
112
131
  - `task_list` returns normalized todo rows and is the only default discovery path
132
+ - `task_list` may return `TASK_LIST_QUERY_FALLBACK_APPLIED`; this means backend search missed the query and MCP recovered the result through local exact-field fallback
113
133
  - `task_get` returns node context summary, not full historical report data
114
134
  - `task_associated_report_detail_get` may return either:
115
135
  - `result_type=view_list`
116
136
  - `result_type=chart_data`
117
137
  - `task_workflow_log_get` returns workflow log detail only when the node grants log visibility
138
+ - A successful approve/reject/rollback/transfer may still lose the current-node runtime immediately; treat `record_state_readable=false + backend 46001` as a post-action runtime loss unless continuation verification says otherwise
118
139
  - Treat `request_route` as the source of truth for live route debugging
119
140
  - If only part of the requested work is completed, explicitly disclose which parts are done and which are not
120
141
 
@@ -1,5 +1,37 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from importlib.metadata import PackageNotFoundError, packages_distributions, version as _dist_version
4
+ from pathlib import Path
5
+
3
6
  __all__ = ["__version__"]
4
7
 
5
- __version__ = "0.2.0b87"
8
+ _FALLBACK_VERSION = "0.2.0b998"
9
+
10
+
11
+ def _resolve_local_pyproject_version() -> str | None:
12
+ module_path = Path(__file__).resolve()
13
+ for parent in module_path.parents:
14
+ candidate = parent / "pyproject.toml"
15
+ if not candidate.is_file():
16
+ continue
17
+ for line in candidate.read_text(encoding="utf-8").splitlines():
18
+ stripped = line.strip()
19
+ if stripped.startswith("version = "):
20
+ return stripped.split("=", 1)[1].strip().strip('"')
21
+ break
22
+ return None
23
+
24
+
25
+ def _resolve_runtime_version() -> str:
26
+ local_version = _resolve_local_pyproject_version()
27
+ if local_version:
28
+ return local_version
29
+ for dist_name in packages_distributions().get("qingflow_mcp", []):
30
+ try:
31
+ return _dist_version(dist_name)
32
+ except PackageNotFoundError:
33
+ continue
34
+ return _FALLBACK_VERSION
35
+
36
+
37
+ __version__ = _resolve_runtime_version()
@@ -819,6 +819,10 @@ class FieldMutation(StrictModel):
819
819
  validation_alias=AliasChoices("custom_button_text", "customButtonText", "custom_btn_text", "customBtnText"),
820
820
  )
821
821
  subfields: list[FieldPatch] | None = None
822
+ subfield_updates: list["FieldUpdatePatch"] | None = Field(
823
+ default=None,
824
+ validation_alias=AliasChoices("subfield_updates", "subfieldUpdates"),
825
+ )
822
826
 
823
827
  @model_validator(mode="after")
824
828
  def validate_shape(self) -> "FieldMutation":
@@ -848,8 +852,12 @@ class FieldMutation(StrictModel):
848
852
  or self.custom_button_text is not None
849
853
  ):
850
854
  raise ValueError("code_block_config, code_block_binding, auto_trigger, custom_button_text_enabled, and custom_button_text are only allowed for code_block fields")
851
- if self.type == PublicFieldType.subtable and not self.subfields:
852
- raise ValueError("subtable field requires subfields")
855
+ if self.type == PublicFieldType.subtable and not self.subfields and not self.subfield_updates:
856
+ raise ValueError("subtable field requires subfields or subfield_updates")
857
+ if self.type is not None and self.type != PublicFieldType.subtable and self.subfield_updates:
858
+ raise ValueError("subfield_updates are only allowed for subtable fields")
859
+ if self.subfields and self.subfield_updates:
860
+ raise ValueError("subfields and subfield_updates cannot be used together")
853
861
  return self
854
862
 
855
863
  @model_validator(mode="before")
@@ -1528,14 +1536,16 @@ class PortalApplyRequest(StrictModel):
1528
1536
  raise ValueError("package_tag_id is required when dash_key is empty")
1529
1537
  if not self.dash_key and not self.dash_name:
1530
1538
  raise ValueError("dash_name is required when creating a portal")
1531
- if not self.sections:
1532
- raise ValueError("portal apply requires a non-empty sections list")
1539
+ if not self.dash_key and not self.sections:
1540
+ raise ValueError("portal apply requires a non-empty sections list when creating a portal")
1533
1541
  if self.visibility is not None and self.auth is not None:
1534
1542
  raise ValueError("visibility and auth cannot be provided together")
1535
1543
  return self
1536
1544
 
1537
1545
 
1538
1546
  FieldPatch.model_rebuild()
1547
+ FieldMutation.model_rebuild()
1548
+ FieldUpdatePatch.model_rebuild()
1539
1549
 
1540
1550
 
1541
1551
  class AppGetResponse(StrictModel):