@qingflow-tech/qingflow-cli 1.1.45-beta.4 → 1.1.45-beta.6
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.
- package/README.md +3 -3
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-cli/SKILL.md +1 -1
- package/skills/qingflow-cli/reference/builder/90-workflow.md +4 -0
- package/skills/qingflow-cli/reference/builder/workflow/01-overview.md +3 -3
- package/skills/qingflow-cli/reference/builder/workflow/02-update-mode.md +2 -0
- package/src/qingflow_mcp/app_form_validator.py +223 -0
- package/src/qingflow_mcp/builder_facade/service.py +5 -0
- package/src/qingflow_mcp/cli/commands/builder.py +172 -8
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +0 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Qingflow CLI 是轻流面向开发者和 AI Agent 的命令行工具。它把轻
|
|
|
7
7
|
## 安装
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install @qingflow-tech/qingflow-cli@1.1.45-beta.
|
|
10
|
+
npm install @qingflow-tech/qingflow-cli@1.1.45-beta.6
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
推荐从官网复制这一条一键安装 Qingflow CLI:
|
|
@@ -21,7 +21,7 @@ npx qingflow-cli@latest install
|
|
|
21
21
|
## 运行
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
npx -y -p @qingflow-tech/qingflow-cli@1.1.45-beta.
|
|
24
|
+
npx -y -p @qingflow-tech/qingflow-cli@1.1.45-beta.6 qingflow
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
全局安装后可以直接运行:
|
|
@@ -62,7 +62,7 @@ qingflow-skills install --agent codex --scope user
|
|
|
62
62
|
如果你通过一次性 `npx -p` 执行 skills 安装,建议加 `--copy`,避免 symlink 指向 npm 临时执行缓存:
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
|
-
npx -y -p @qingflow-tech/qingflow-cli@1.1.45-beta.
|
|
65
|
+
npx -y -p @qingflow-tech/qingflow-cli@1.1.45-beta.6 qingflow-skills install --agent codex --scope user --copy
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
`qingflow-skills install` 默认使用 symlink,且遇到同名 skill 不会覆盖;只有显式传入 `--force` 才会替换。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qingflow-tech/qingflow-cli",
|
|
3
|
-
"version": "1.1.45-beta.
|
|
3
|
+
"version": "1.1.45-beta.6",
|
|
4
4
|
"description": "Human-friendly Qingflow command line interface for auth, record operations, import, tasks, and stable builder flows.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/pyproject.toml
CHANGED
|
@@ -444,7 +444,7 @@ qingflow auth logout --forget-persisted
|
|
|
444
444
|
| **app / portal / view / chart** | `app list [--query 关键词]` → **`app get --app-key`**(`accessible_views` / `view_id`、`import_capability`)· `builder package list [--query 关键词]` → `builder package get --package-id` · `portal list` · `portal get --dash-key` · `view get --view-id`(只读视图元信息;`baseInfo` 或 `viewConfig` 只有 40002 / 40027 / 404 这类权限/缺失边界可用另一侧降级,非权限类后端错误不隐藏;解析 app 仅依赖同源 view/form 与可见应用树,不回退旧应用搜索)· `chart get --chart-id`(图表 `baseInfo` / 数据 / 配置分阶段读取;单个辅助阶段 40002 / 40027 / 404 只作为 warning,非权限类后端错误不隐藏) |
|
|
445
445
|
| **record** | `record schema insert`(新建记录 insert-ready schema,仅 `--app-key`)· **`browse`/`list`/`access`:`--app-key` + `--view-id`(缺一常 CONFIG)** · `record list --query ... [--query-field FIELD_ID] [--page-size N]` 用于候选定位/样本浏览 · `record get --record-id` 读详情页首屏上下文 · `record logs --record-id` 读完整数据/流程日志 JSONL · `record insert --items-file`(单条也是 items 一行)· `schema update --record-id [--view-id]` / **`update`**(单条 `--fields-file` **或** 批量 `--items-file`/`--dry-run`,二者勿混)· `record delete --app-key --record-id --view-id system:*` / `--record-ids-file` · `export start/direct --view-id` 仅用于明确导出文件 · **大 schema**:先 **[FIELD_DATA_TYPES](./reference/core/QINGFLOW_CLI_FIELD_DATA_TYPES.md)** / **[RECORD_CREATE](./reference/record/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md)** / **[RECORD_UPDATE](./reference/record/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md)** / **[RECORD_DELETE](./reference/record/QINGFLOW_CLI_RECORD_DELETE_WORKFLOW.md)**,再 `-h` |
|
|
446
446
|
| **import** | `record schema import?` · `import template` → `verify` → `repair?` → `start`(**`being-enter-auditing` 必填**)→ `status`(**三者 selector 只选一**);导入权限预检的 `baseInfo` 只有 40002 / 40027 / 404 可降级为 unknown,非权限错误不能当成无导入权或继续执行;**链路与本机目录**只靠本节不够 → **[RECORD_IMPORT](./reference/record/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md)** |
|
|
447
|
-
| **builder** | **搭建/系统化配置**:先按 **[INDEX](./reference/00-INDEX.md)** / **[BUILDER](./reference/builder/README.md)** 选主路径,再读 `builder contract`。**完整系统/应用包**:`builder package apply --config-file` → 每个应用依次执行 `builder app-form schema`、更新时 `get --app-key APP_KEY --being-draft`、`validate --schema-version VERSION --file DECLARATION.json`、`apply --file DECLARATION.json`(固定版本、完整声明、记录返回的 `appKey`)→ 视图(按钮随视图 `action_buttons` 写)→ 门户(缺报表时写 `chart`)→ 发布/回读。旧 `schema apply` / `layout apply` 不在当前 CLI parser 中。超时、`partial_success`、`write_executed=true`、`safe_to_retry=false` 或回读不完整时先读取 AppForm draft/published,再恢复,不得重复创建或创建 V2/测试/随机后缀绕过重名。**单应用**:新建和更新遵循同一 AppForm CLI 链路,再按需求配置流程/视图。更新动作归入对应资源文档:AppForm [30](./reference/builder/30-schema-fields.md)、视图 [50](./reference/builder/50-views.md)、报表 [60](./reference/builder/60-charts.md)、门户 [70](./reference/builder/70-portal.md)、关联资源/非主链路按钮维护 [80](./reference/builder/80-buttons-associated-resources.md)、流程 [90](./reference/builder/90-workflow.md)、发布 [99](./reference/builder/99-publish-verify.md)
|
|
447
|
+
| **builder** | **搭建/系统化配置**:先按 **[INDEX](./reference/00-INDEX.md)** / **[BUILDER](./reference/builder/README.md)** 选主路径,再读 `builder contract`。**完整系统/应用包**:`builder package apply --config-file` → 每个应用依次执行 `builder app-form schema`、更新时 `get --app-key APP_KEY --being-draft`、`validate --schema-version VERSION --file DECLARATION.json`、`apply --file DECLARATION.json`(固定版本、完整声明、记录返回的 `appKey`)→ 视图(按钮随视图 `action_buttons` 写)→ 门户(缺报表时写 `chart`)→ 发布/回读。旧 `schema apply` / `layout apply` 不在当前 CLI parser 中。超时、`partial_success`、`write_executed=true`、`safe_to_retry=false` 或回读不完整时先读取 AppForm draft/published,再恢复,不得重复创建或创建 V2/测试/随机后缀绕过重名。**单应用**:新建和更新遵循同一 AppForm CLI 链路,再按需求配置流程/视图。更新动作归入对应资源文档:AppForm [30](./reference/builder/30-schema-fields.md)、视图 [50](./reference/builder/50-views.md)、报表 [60](./reference/builder/60-charts.md)、门户 [70](./reference/builder/70-portal.md)、关联资源/非主链路按钮维护 [80](./reference/builder/80-buttons-associated-resources.md)、流程 [90](./reference/builder/90-workflow.md)、发布 [99](./reference/builder/99-publish-verify.md)。**AI 公式/代码块校验**:`builder app-form validate-formula --app-key KEY --formula '<公式>' --test-case <文件路径>`(`--formula` 内联;`--test-case` **只收文件路径或 `-` stdin,不收内联 JSON**)与 `builder app-form validate-code-block --app-key KEY --code <内联文本|@文件路径> --test-case <文件路径>`(本地静态 + 后端真跑,输出 `staticErrors` / `runResult` / `runError`);两者是公式/代码块生成场景的真实校验命令,校验结论必须来自其真实执行,不得以本地模拟替代。代码内占位符 `qf_field.{字段名$$queId$$}`(**字段名不带引号**,带引号会被 CLI 自动剥离但勿依赖)与 `keyQueId` 均使用**十进制** queId(CLI 真跑前自动编码为运行时形态;错误回显自动还原为十进制,勿逆向编码 id)。test-case values 按类型:文本/电话=字符串、数字=数字字面量、日期=yyyy-MM-dd、附件=URL 字符串数组、单选/下拉/多选=选项文案(CLI 自动译为选项 id)、成员/部门=不可提供(代码判空,覆盖校验豁免)。 |
|
|
448
448
|
| **task**(须落盘见上文) | `task list [--task-box][--flow-status][--app-key][--query] --page --page-size`(**枚举、`--query`、与 `record` 视图口径勿混**:见上文 SOP · **[TASK_CONTEXT](./reference/task/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md)**)· **`get`/`log`/`report`/`action`**:默认只传 `task list` 返回的 **`data.items[].task_id`**;`report` 另须必选 **`--report-id`**;`action` **无 `--dry-run`** |
|
|
449
449
|
|
|
450
450
|
**高发必败**:旧应用搜索入口 · **`record list`/`browse`/`access` 缺 `--view-id`** · **`task report` 缺 `--report-id`** · **`record update` 批量与单条参数混用**。
|
|
@@ -20,6 +20,8 @@ Workflow examples depend on live `builder flow schema`, field ids, role ids, and
|
|
|
20
20
|
|
|
21
21
|
## Recommended write path
|
|
22
22
|
|
|
23
|
+
- Workflow editing is temporarily unavailable in the CLI. Before a flow write, the CLI reads the current `spec.nodes`; when the current node count exceeds 5, it stops without writing and reports that the feature is under maintenance. The submitted spec node count does not affect this decision.
|
|
24
|
+
- Writes remain available when the current flow has 5 or fewer nodes, including initial workflow creation.
|
|
23
25
|
- Before applying a workflow, verify the app has an explicit business status select field such as `状态`, `处理状态`, `审批状态`, `工单状态`, `计划状态`, `报工状态`, or `单据状态`. Domain result fields are not enough by themselves: for example, `检验结论` should be paired with `处理状态`.
|
|
24
26
|
- For workflow creation or replacement, always use `builder flow get` / `builder flow schema`, then submit a complete WorkflowSpec with `--spec-file`.
|
|
25
27
|
- Do not use `--nodes-file` + `--transitions-file` in skill-led workflow builds. Even simple linear workflows should be represented as WorkflowSpec.
|
|
@@ -30,6 +32,8 @@ Workflow examples depend on live `builder flow schema`, field ids, role ids, and
|
|
|
30
32
|
|
|
31
33
|
All workflow creation and replacement uses WorkflowSpec through `--spec-file`. Existing-node maintenance can use `--patch-nodes-file` after readback. This is the only skill-led workflow write path for linear, branching, Q-Robot, approval, filling, and cc flows.
|
|
32
34
|
|
|
35
|
+
While editing is under maintenance, the CLI blocks either input mode when the current flow has more than 5 nodes. It does not reject a creation because the new WorkflowSpec itself has more than 5 nodes.
|
|
36
|
+
|
|
33
37
|
## Detailed workflow docs
|
|
34
38
|
|
|
35
39
|
Use [workflow/README.md](./workflow/README.md) for full staged workflow modeling and [workflow/workflow-schema.json](./workflow/workflow-schema.json) for schema validation.
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
|------|------|
|
|
20
20
|
| `qingflow builder flow schema --json` | 获取最新 WorkflowSpecDTO JSON Schema |
|
|
21
21
|
| `qingflow builder flow get --app-key <KEY>` | 读取当前工作流 spec |
|
|
22
|
-
| `qingflow --json builder flow apply --app-key <KEY> --spec-file <FILE>` |
|
|
23
|
-
| `qingflow --json builder flow apply --app-key <KEY> --patch-nodes-file <PATCH>` |
|
|
22
|
+
| `qingflow --json builder flow apply --app-key <KEY> --spec-file <FILE>` | 主链路:创建 WorkflowSpec;当前流程节点数超过 5 时,编辑功能维护中 |
|
|
23
|
+
| `qingflow --json builder flow apply --app-key <KEY> --patch-nodes-file <PATCH>` | 局部修改现有节点;当前功能维护中 |
|
|
24
24
|
| `qingflow builder member search --query <关键词>` | 搜索成员 |
|
|
25
25
|
| `qingflow builder role search --keyword <关键词>` | 搜索角色 |
|
|
26
26
|
| `qingflow --json app get --app-key <KEY>` | 获取应用信息(字段列表等) |
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
---
|
|
44
44
|
|
|
45
|
-
无论线性、分支、Q-Robot、审批、填写还是抄送流程,新建和替换都使用 WorkflowSpec `--spec-file`;已有节点的小修改才使用 `--patch-nodes-file
|
|
45
|
+
无论线性、分支、Q-Robot、审批、填写还是抄送流程,新建和替换都使用 WorkflowSpec `--spec-file`;已有节点的小修改才使用 `--patch-nodes-file`。当前 CLI 会按调用前的 `spec.nodes` 数判断:超过 5 个节点即停止编辑并提示功能维护中,不以提交后的节点数判断;当前节点数为 5 个及以下时允许写入,初始流程创建包含在内。不要在技能主链路使用 `--nodes-file` + `--transitions-file`。
|
|
46
46
|
|
|
47
47
|
← 返回主流程:[../SKILL.md](../../../SKILL.md)
|
|
48
48
|
→ 下一步:[02-update-mode.md](02-update-mode.md)
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
当应用已有工作流时,进入**更新模式**。核心原则是**最小修改**——只改动必要的部分,保持其他结构不变。
|
|
4
4
|
|
|
5
|
+
当前 CLI 的流程编辑功能维护中:写入前读取当前 `spec.nodes`,节点数超过 5 时会停止操作并返回维护提示,不会发起流程写入。判定只基于更新前的节点数,不基于本次提交后会变成多少节点;当前节点数为 5 个及以下时仍允许写入,初始流程创建包含在内。
|
|
6
|
+
|
|
5
7
|
## 为什么要保持节点 ID 稳定
|
|
6
8
|
|
|
7
9
|
后端可能存储了 spec 中不支持的运行时配置(如审批策略、超时规则、高级权限等),这些配置与节点 ID 绑定。一旦修改节点 ID,后端会将其视为「删除旧节点 + 创建新节点」,导致不支持的配置全部丢失。
|
|
@@ -541,3 +541,226 @@ def validate_code_block_static(code: str) -> list[dict[str, str]]:
|
|
|
541
541
|
}
|
|
542
542
|
)
|
|
543
543
|
return errors
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
# ---------------------------------------------------------------------------
|
|
547
|
+
# 代码块字段占位符编码
|
|
548
|
+
#
|
|
549
|
+
# 后端 codeBlockTest 运行时通过 CustomPushContentConverter.getQueId 解码代码里的
|
|
550
|
+
# ``qf_field.{label$$<encoded>$$}`` 段(私有混淆编码,非十进制),再按解码出的十进制
|
|
551
|
+
# queId 匹配 --test-case 的 keyQueId 填值;匹配不上会静默跳过(占位符原样留跑)。
|
|
552
|
+
# AI 生成的代码按十进制 queId 书写(与 skill/编辑器上下文一致),本模块在真跑前把
|
|
553
|
+
# 纯数字的 ``$$<digits>$$`` 段编码为运行时形态——与后端采纳链路(encryptQueId)同一
|
|
554
|
+
# 编码、同一"纯数字才编码"规则,已编码(含字母)的段原样保留,避免双重编码。
|
|
555
|
+
# ---------------------------------------------------------------------------
|
|
556
|
+
|
|
557
|
+
_ENC_KEY = 1000013
|
|
558
|
+
_INT_TO_HEX = "0123456789ABCDEF"
|
|
559
|
+
_DECIMAL_PLACEHOLDER = re.compile(r"\$\$(-?\d+)\$\$")
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def encode_que_id(que_id: int) -> str:
|
|
563
|
+
"""Java ``CustomPushContentConverter.encryptQueId`` 的逐行为等价移植。"""
|
|
564
|
+
have_prefix = que_id < 0
|
|
565
|
+
abs_id = abs(que_id)
|
|
566
|
+
ran = abs_id * 17
|
|
567
|
+
inp = str(abs_id)
|
|
568
|
+
seeds: list[int] = []
|
|
569
|
+
salt_sum = 0
|
|
570
|
+
for _ in range(max(0, 4 - len(inp))):
|
|
571
|
+
tmp = (ran % 20) + 71
|
|
572
|
+
ran = ((ran + 13) * 17) % _ENC_KEY
|
|
573
|
+
salt_sum += tmp
|
|
574
|
+
seeds.append(tmp)
|
|
575
|
+
result = [(salt_sum + i + int(ch, 16)) % 15 for i, ch in enumerate(inp)]
|
|
576
|
+
for seed in seeds:
|
|
577
|
+
pos = (ran + seed) % len(result)
|
|
578
|
+
ran = ((ran + 13) * 17) % _ENC_KEY
|
|
579
|
+
result.insert(pos, seed)
|
|
580
|
+
encoded = "".join(chr(value) if value > 15 else _INT_TO_HEX[value] for value in result)
|
|
581
|
+
return ("-" if have_prefix else "") + encoded
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def encode_code_block_field_ids(code: str) -> str:
|
|
585
|
+
"""把代码里的十进制 queId 占位符段编码为后端运行时形态。
|
|
586
|
+
|
|
587
|
+
仅处理纯数字(可带负号)的 ``$$...$$`` 段;混有字母的段视为已编码,原样保留。
|
|
588
|
+
"""
|
|
589
|
+
if not isinstance(code, str):
|
|
590
|
+
return code
|
|
591
|
+
return _DECIMAL_PLACEHOLDER.sub(
|
|
592
|
+
lambda match: f"$${encode_que_id(int(match.group(1)))}$$", code
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def referenced_code_block_field_ids(code: str) -> set[int]:
|
|
597
|
+
"""提取代码里以十进制 queId 引用的字段 id 集合(供 test-case 覆盖校验)。
|
|
598
|
+
|
|
599
|
+
只统计纯数字(可带负号)的 ``$$...$$`` 段;已编码(含字母)段解码依赖后端实现,
|
|
600
|
+
不在此覆盖校验范围内。
|
|
601
|
+
"""
|
|
602
|
+
if not isinstance(code, str):
|
|
603
|
+
return set()
|
|
604
|
+
return {int(match.group(1)) for match in _DECIMAL_PLACEHOLDER.finditer(code)}
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
# ---------------------------------------------------------------------------
|
|
608
|
+
# 代码块校验的占位符容错与回显还原
|
|
609
|
+
#
|
|
610
|
+
# 现场问题(agent 实测):
|
|
611
|
+
# 1. 模型常把占位符写成 qf_field.{"字段名$$id$$"} —— 字段名带引号。后端替换正则要求
|
|
612
|
+
# 结尾恰为 `$$}`,带引号变成 `$$"}` 匹配失败,占位符原样留在代码里直接语法错误。
|
|
613
|
+
# 这里在编码归一化前剥离字段名两侧引号,带引号写法自动修正。
|
|
614
|
+
# 2. CLI 把 `$$十进制$$` 编码为混淆形态后,校验失败的错误回显里是编码 id,模型认不出
|
|
615
|
+
# 是自己写的 queId,会试图逆向"哈希"浪费大量轮次。这里记录 编码→十进制 映射,
|
|
616
|
+
# 输出前把错误文本里的编码段还原为十进制。
|
|
617
|
+
# ---------------------------------------------------------------------------
|
|
618
|
+
|
|
619
|
+
_QUOTED_PLACEHOLDER = re.compile(r'(qf_field\.\{)["\']([^$\'"{]+?)\$\$(-?\d+)\$\$["\'](\})')
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def strip_placeholder_quotes(code: str) -> tuple[str, int]:
|
|
623
|
+
"""剥离占位符字段名两侧引号:qf_field.{"名$$id$$"} → qf_field.{名$$id$$}。
|
|
624
|
+
|
|
625
|
+
Returns:
|
|
626
|
+
(修正后的代码, 修正处数)
|
|
627
|
+
"""
|
|
628
|
+
if not isinstance(code, str):
|
|
629
|
+
return code, 0
|
|
630
|
+
count = 0
|
|
631
|
+
|
|
632
|
+
def _strip(match: re.Match[str]) -> str:
|
|
633
|
+
nonlocal count
|
|
634
|
+
count += 1
|
|
635
|
+
return f"{match.group(1)}{match.group(2)}$${match.group(3)}$$}}"
|
|
636
|
+
|
|
637
|
+
return _QUOTED_PLACEHOLDER.sub(_strip, code), count
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def build_encoded_id_map(code: str) -> dict[str, str]:
|
|
641
|
+
"""对代码里的纯数字占位段构建 编码形态→十进制 映射(用于错误回显还原)。"""
|
|
642
|
+
if not isinstance(code, str):
|
|
643
|
+
return {}
|
|
644
|
+
return {
|
|
645
|
+
encode_que_id(int(match.group(1))): match.group(1)
|
|
646
|
+
for match in _DECIMAL_PLACEHOLDER.finditer(code)
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def restore_decimal_ids_in_text(text: str, id_map: dict[str, str]) -> str:
|
|
651
|
+
"""把文本里出现的 `$$<编码>$$` 还原为 `$$<十进制>$$`(错误回显可读化)。"""
|
|
652
|
+
if not isinstance(text, str) or not id_map:
|
|
653
|
+
return text
|
|
654
|
+
for encoded, decimal in id_map.items():
|
|
655
|
+
text = text.replace(f"$${encoded}$$", f"$${decimal}$$")
|
|
656
|
+
return text
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
# 选择类字段 test-case 值翻译:后端只认选项 optId(数字),传文案静默变 null;
|
|
660
|
+
# 模型不可能知道 optId,这里按字段定义把"选项文案"翻译成 optId。
|
|
661
|
+
_SELECT_FIELD_TYPES = {"single_choice", "single_select", "multi_select"}
|
|
662
|
+
# 值不可推导的字段类型(成员/部门要 uid/部门id,模型无从得知):覆盖校验跳过。
|
|
663
|
+
_UNDERIVABLE_FIELD_TYPES = {"member", "department"}
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
def translate_select_test_case_values(
|
|
667
|
+
fields: list[dict] | None, key_que_values: list[dict]
|
|
668
|
+
) -> tuple[list[dict], list[dict[str, str]], list[str]]:
|
|
669
|
+
"""把选择类字段的"选项文案"值翻译为 optId。
|
|
670
|
+
|
|
671
|
+
Args:
|
|
672
|
+
fields: 字段定义清单(含 que_id/type/option_details;可为 None 表示未取到)。
|
|
673
|
+
key_que_values: 已规范化的 test-case。
|
|
674
|
+
Returns:
|
|
675
|
+
(翻译后的 test-case, 翻译记录[{queId, from, to}], 无法翻译的选项错误清单)
|
|
676
|
+
"""
|
|
677
|
+
if not fields:
|
|
678
|
+
return key_que_values, [], []
|
|
679
|
+
option_lookup: dict[int, dict[str, object]] = {}
|
|
680
|
+
type_by_que: dict[int, str] = {}
|
|
681
|
+
for field in fields:
|
|
682
|
+
if not isinstance(field, dict):
|
|
683
|
+
continue
|
|
684
|
+
que_id = field.get("que_id") or field.get("queId")
|
|
685
|
+
if not isinstance(que_id, int):
|
|
686
|
+
continue
|
|
687
|
+
field_type = str(field.get("type") or "")
|
|
688
|
+
type_by_que[que_id] = field_type
|
|
689
|
+
details = field.get("option_details")
|
|
690
|
+
if isinstance(details, list):
|
|
691
|
+
by_value = {
|
|
692
|
+
str(item.get("value") or "").strip(): item.get("id")
|
|
693
|
+
for item in details
|
|
694
|
+
if isinstance(item, dict) and item.get("id") is not None
|
|
695
|
+
}
|
|
696
|
+
if by_value:
|
|
697
|
+
option_lookup[que_id] = by_value
|
|
698
|
+
|
|
699
|
+
translated = [dict(item) for item in key_que_values]
|
|
700
|
+
changes: list[dict[str, str]] = []
|
|
701
|
+
errors: list[str] = []
|
|
702
|
+
for item in translated:
|
|
703
|
+
key = item.get("keyQueId")
|
|
704
|
+
if not isinstance(key, int) or key not in option_lookup:
|
|
705
|
+
continue
|
|
706
|
+
by_value = option_lookup[key]
|
|
707
|
+
new_values: list[object] = []
|
|
708
|
+
for value in item.get("values") or []:
|
|
709
|
+
text = str(value).strip()
|
|
710
|
+
if text in by_value:
|
|
711
|
+
new_values.append(by_value[text])
|
|
712
|
+
changes.append({"queId": str(key), "from": text, "to": str(by_value[text])})
|
|
713
|
+
elif str(value).isdigit() and int(value) in set(by_value.values()):
|
|
714
|
+
new_values.append(int(value))
|
|
715
|
+
else:
|
|
716
|
+
errors.append(
|
|
717
|
+
f"field queId {key}: 选项「{text}」不存在,可选:{'/'.join(by_value.keys())}"
|
|
718
|
+
)
|
|
719
|
+
new_values.append(value)
|
|
720
|
+
item["values"] = new_values
|
|
721
|
+
return translated, changes, errors
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
# ---------------------------------------------------------------------------
|
|
725
|
+
# 公式校验的引用提取与选择类值翻译
|
|
726
|
+
#
|
|
727
|
+
# 实测(真实后端 /ai/coding/formula/validate):
|
|
728
|
+
# - 缺值字段静默变空(CONCAT 直接跳过)→ 空洞通过,需要覆盖门;
|
|
729
|
+
# - 单选/多选必须传 **[optId 数组]**:文案/optId 标量均报错;返回值为 optId 串(非文案);
|
|
730
|
+
# - 日期字段裸引用报错,套日期函数(YEAR 等)+ "yyyy-MM-dd" 字符串可用。
|
|
731
|
+
# ---------------------------------------------------------------------------
|
|
732
|
+
|
|
733
|
+
_FORMULA_REFERENCE = re.compile(r"#queId_(\d+)\$")
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
def referenced_formula_field_ids(formula: str) -> set[int]:
|
|
737
|
+
"""提取公式 `#queId_<id>$` 引用的字段 id 集合(供覆盖校验)。"""
|
|
738
|
+
if not isinstance(formula, str):
|
|
739
|
+
return set()
|
|
740
|
+
return {int(m.group(1)) for m in _FORMULA_REFERENCE.finditer(formula)}
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def translate_formula_test_case_values(
|
|
744
|
+
fields: list[dict] | None, test_case: dict
|
|
745
|
+
) -> tuple[dict, list[dict[str, str]], list[str]]:
|
|
746
|
+
"""公式 testCase 值归一化(全类型实测契约)。
|
|
747
|
+
|
|
748
|
+
实测核心规律:**裸引用/函数求值统一要求 [数组] 形态**,标量一律报错(含文本/数字/日期);
|
|
749
|
+
单选/多选传 ["选项文案"] 数组直接可过且求值结果是文案本身(优于内部 id)。
|
|
750
|
+
因此本函数只做一件事:把所有标量值包装成 [标量];已是数组原样保留。
|
|
751
|
+
|
|
752
|
+
Args:
|
|
753
|
+
fields: 字段定义(当前未用,保留签名兼容;数组包装与类型无关)。
|
|
754
|
+
test_case: {queId: value} 对象。
|
|
755
|
+
Returns:
|
|
756
|
+
(归一化 test-case, 包装记录, 错误清单——恒空)
|
|
757
|
+
"""
|
|
758
|
+
if not isinstance(test_case, dict):
|
|
759
|
+
return test_case, [], []
|
|
760
|
+
translated = dict(test_case)
|
|
761
|
+
changes: list[dict[str, str]] = []
|
|
762
|
+
for key, value in list(translated.items()):
|
|
763
|
+
if not isinstance(value, list):
|
|
764
|
+
translated[key] = [value]
|
|
765
|
+
changes.append({"queId": str(key), "from": f"scalar:{value}", "to": f"[{value}]"})
|
|
766
|
+
return translated, changes, []
|
|
@@ -21167,6 +21167,11 @@ def _compact_public_field_read(*, field: dict[str, Any], layout: dict[str, Any],
|
|
|
21167
21167
|
"required": bool(field.get("required")),
|
|
21168
21168
|
"section_id": None if nested else _find_field_section_id(layout, str(field.get("name") or "")),
|
|
21169
21169
|
}
|
|
21170
|
+
if field.get("type") in {FieldType.single_select.value, FieldType.multi_select.value} and field.get(
|
|
21171
|
+
"option_details"
|
|
21172
|
+
):
|
|
21173
|
+
# 选项 id 透出:validate-code-block 需要把"选项文案"翻译成 optId(后端只认 optId)
|
|
21174
|
+
payload["option_details"] = deepcopy(field.get("option_details"))
|
|
21170
21175
|
if field.get("type") == FieldType.relation.value:
|
|
21171
21176
|
payload["target_app_key"] = field.get("target_app_key")
|
|
21172
21177
|
payload["relation_mode"] = _normalize_relation_mode(field.get("relation_mode"))
|
|
@@ -7,6 +7,14 @@ from pathlib import Path
|
|
|
7
7
|
from ..context import CliContext
|
|
8
8
|
from ...app_form_validator import (
|
|
9
9
|
AppFormValidationError,
|
|
10
|
+
build_encoded_id_map,
|
|
11
|
+
referenced_formula_field_ids,
|
|
12
|
+
translate_formula_test_case_values,
|
|
13
|
+
encode_code_block_field_ids,
|
|
14
|
+
referenced_code_block_field_ids,
|
|
15
|
+
restore_decimal_ids_in_text,
|
|
16
|
+
strip_placeholder_quotes,
|
|
17
|
+
translate_select_test_case_values,
|
|
10
18
|
validate_code_block_static,
|
|
11
19
|
validate_declaration,
|
|
12
20
|
validate_formula_expression,
|
|
@@ -15,6 +23,11 @@ from ..json_io import load_json_value
|
|
|
15
23
|
from .common import load_list_arg, load_object_arg, parse_bool_text, raise_config_error, require_list_arg
|
|
16
24
|
|
|
17
25
|
|
|
26
|
+
# BEGIN temporary flow edit maintenance guard: remove this block when flow editing is restored.
|
|
27
|
+
FLOW_EDIT_MAINTENANCE_NODE_THRESHOLD = 5
|
|
28
|
+
# END temporary flow edit maintenance guard.
|
|
29
|
+
|
|
30
|
+
|
|
18
31
|
def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
19
32
|
parser = subparsers.add_parser("builder", aliases=["build"], help="稳定 builder 命令")
|
|
20
33
|
builder_subparsers = parser.add_subparsers(dest="builder_command", required=True)
|
|
@@ -441,14 +454,40 @@ def _handle_app_form_validate_formula(args: argparse.Namespace, context: CliCont
|
|
|
441
454
|
"--test-case must be a JSON object {queId: value}", error_code="INVALID_TEST_CASE"
|
|
442
455
|
)
|
|
443
456
|
|
|
457
|
+
# 字段定义:优先 --fields;缺省拉全量(保留 option_details 供选择类值翻译)
|
|
458
|
+
fields_path = getattr(args, "fields", None)
|
|
459
|
+
if fields_path:
|
|
460
|
+
fields = _load_fields_for_validation(args, context)
|
|
461
|
+
else:
|
|
462
|
+
fields = _load_code_block_validation_fields(args, context) or _load_fields_for_validation(args, context)
|
|
463
|
+
|
|
444
464
|
# ① 本地语法校验(函数名 + 字段引用存在性)
|
|
445
|
-
|
|
446
|
-
|
|
465
|
+
syntax_fields = [
|
|
466
|
+
{"queId": f.get("que_id") or f.get("queId"), "name": f.get("name")} for f in (fields or [])
|
|
467
|
+
] if fields else None
|
|
468
|
+
syntax_errors = validate_formula_expression(formula, syntax_fields)
|
|
469
|
+
|
|
470
|
+
# ①' 选择类值翻译:实测后端只认 [optId 数组],文案/标量 optId 均报错——把文案自动翻成 [optId]
|
|
471
|
+
test_case, select_changes, option_errors = translate_formula_test_case_values(fields, test_case)
|
|
472
|
+
|
|
473
|
+
# ①'' 覆盖校验:公式引用缺值字段会静默变空(CONCAT 直接跳过)→ 空洞通过,这里拦下
|
|
474
|
+
referenced_ids = referenced_formula_field_ids(formula)
|
|
475
|
+
underivable = _underivable_field_ids(fields)
|
|
476
|
+
covered_ids = {int(k) for k in test_case if str(k).isdigit()}
|
|
477
|
+
missing_ids = sorted((referenced_ids - covered_ids) - underivable)
|
|
478
|
+
coverage_errors = [
|
|
479
|
+
{
|
|
480
|
+
"pointer": "test-case",
|
|
481
|
+
"message": f"field queId {que_id} is referenced by formula but has no test-case value "
|
|
482
|
+
f"(reference would evaluate as empty)",
|
|
483
|
+
}
|
|
484
|
+
for que_id in missing_ids
|
|
485
|
+
]
|
|
447
486
|
|
|
448
|
-
# ②
|
|
487
|
+
# ② 后端求值校验(本地有错时不浪费后端调用)
|
|
449
488
|
evaluation = None
|
|
450
489
|
evaluation_error = None
|
|
451
|
-
if not syntax_errors:
|
|
490
|
+
if not syntax_errors and not coverage_errors and not option_errors:
|
|
452
491
|
result = context.builder.app_form_evaluate_formula(
|
|
453
492
|
profile=args.profile, app_key=args.app_key, formula=formula, test_case=test_case
|
|
454
493
|
)
|
|
@@ -457,6 +496,8 @@ def _handle_app_form_validate_formula(args: argparse.Namespace, context: CliCont
|
|
|
457
496
|
|
|
458
497
|
ok = bool(
|
|
459
498
|
not syntax_errors
|
|
499
|
+
and not coverage_errors
|
|
500
|
+
and not option_errors
|
|
460
501
|
and isinstance(evaluation, dict)
|
|
461
502
|
and evaluation.get("valid") is True
|
|
462
503
|
)
|
|
@@ -465,6 +506,9 @@ def _handle_app_form_validate_formula(args: argparse.Namespace, context: CliCont
|
|
|
465
506
|
"status": "success" if ok else "failed",
|
|
466
507
|
"ok": ok,
|
|
467
508
|
"syntaxErrors": syntax_errors,
|
|
509
|
+
"coverageErrors": coverage_errors,
|
|
510
|
+
"optionErrors": option_errors,
|
|
511
|
+
"autoFixes": {"selectValuesTranslated": select_changes},
|
|
468
512
|
"evaluation": evaluation,
|
|
469
513
|
"evaluationError": evaluation_error,
|
|
470
514
|
"message": "formula is valid" if ok else "formula validation failed",
|
|
@@ -472,7 +516,15 @@ def _handle_app_form_validate_formula(args: argparse.Namespace, context: CliCont
|
|
|
472
516
|
|
|
473
517
|
|
|
474
518
|
def _handle_app_form_validate_code_block(args: argparse.Namespace, context: CliContext) -> dict:
|
|
475
|
-
|
|
519
|
+
raw_code = _read_code_arg(args.code)
|
|
520
|
+
# 占位符容错:模型常给字段名加引号(qf_field.{"名$$id$$"}),后端替换正则要求结尾恰为
|
|
521
|
+
# `$$}`,带引号直接匹配失败→占位符原样进 JS→语法错误。先剥离引号再进编码。
|
|
522
|
+
code_after_quotes, quote_fixes = strip_placeholder_quotes(raw_code)
|
|
523
|
+
# 后端运行时按 encryptQueId 的编码形态解码代码里的 $$id$$ 段再匹配 test-case 填值;
|
|
524
|
+
# AI 生成的代码是十进制 queId,先编码归一化(含字母的已编码段原样保留,防双重编码)。
|
|
525
|
+
code = encode_code_block_field_ids(code_after_quotes)
|
|
526
|
+
# 错误回显还原映射:后端错误里出现的编码 id 还原为模型写的十进制,避免模型逆向混淆。
|
|
527
|
+
encoded_id_map = build_encoded_id_map(code_after_quotes)
|
|
476
528
|
test_case = load_json_value(
|
|
477
529
|
args.test_case, option_name="--test-case", max_bytes=1 * 1024 * 1024, max_depth=32
|
|
478
530
|
)
|
|
@@ -482,32 +534,108 @@ def _handle_app_form_validate_code_block(args: argparse.Namespace, context: CliC
|
|
|
482
534
|
error_code="INVALID_TEST_CASE",
|
|
483
535
|
)
|
|
484
536
|
|
|
537
|
+
# 字段定义(选项翻译/覆盖豁免用;拉取失败不阻断,只是少一层自动修正)
|
|
538
|
+
fields = _load_code_block_validation_fields(args, context)
|
|
539
|
+
|
|
485
540
|
# ① 本地静态校验(qf_output 赋值 + 基本语法)
|
|
486
541
|
static_errors = validate_code_block_static(code)
|
|
487
542
|
|
|
488
|
-
#
|
|
543
|
+
# ①' 选择类值翻译:后端只认选项 optId,传文案静默变 null——把文案自动翻成 optId。
|
|
489
544
|
key_que_values = _normalize_code_block_test_case(test_case)
|
|
545
|
+
key_que_values, select_changes, option_errors = translate_select_test_case_values(fields, key_que_values)
|
|
546
|
+
|
|
547
|
+
# ①'' 测试覆盖校验:代码引用的字段必须在 test-case 中给值(成员/部门类豁免——
|
|
548
|
+
# 其值是 uid/部门 id,模型无从得知,代码须自行 null 安全)。缺值时后端会把占位符
|
|
549
|
+
# 替换成字面量 null 静默跑通(空洞通过)——在这里拦下,逼 test-case 补全而不是放行。
|
|
550
|
+
covered_ids = _code_block_test_case_field_ids(key_que_values)
|
|
551
|
+
underivable = _underivable_field_ids(fields)
|
|
552
|
+
referenced_ids = referenced_code_block_field_ids(raw_code)
|
|
553
|
+
missing_ids = sorted((referenced_ids - covered_ids) - underivable)
|
|
554
|
+
coverage_errors = [
|
|
555
|
+
{
|
|
556
|
+
"pointer": "test-case",
|
|
557
|
+
"message": f"field queId {que_id} is referenced by code but has no test-case value "
|
|
558
|
+
f"(placeholder would run as literal null)",
|
|
559
|
+
}
|
|
560
|
+
for que_id in missing_ids
|
|
561
|
+
]
|
|
562
|
+
|
|
563
|
+
# ② 后端真跑校验(本地有错时不浪费后端调用)
|
|
490
564
|
run_result = None
|
|
491
565
|
run_error = None
|
|
492
|
-
if not static_errors:
|
|
566
|
+
if not static_errors and not coverage_errors and not option_errors:
|
|
493
567
|
result = context.builder.app_form_run_code_block(
|
|
494
568
|
profile=args.profile, app_key=args.app_key, code=code, key_que_values=key_que_values
|
|
495
569
|
)
|
|
496
570
|
run_result = result.get("runResult")
|
|
497
571
|
run_error = result.get("runError")
|
|
498
572
|
|
|
499
|
-
|
|
573
|
+
if isinstance(run_error, str):
|
|
574
|
+
run_error = restore_decimal_ids_in_text(run_error, encoded_id_map)
|
|
575
|
+
|
|
576
|
+
ok = not static_errors and not coverage_errors and not option_errors and run_result is not None
|
|
500
577
|
return {
|
|
501
578
|
"category": "validation",
|
|
502
579
|
"status": "success" if ok else "failed",
|
|
503
580
|
"ok": ok,
|
|
504
581
|
"staticErrors": static_errors,
|
|
582
|
+
"coverageErrors": coverage_errors,
|
|
583
|
+
"optionErrors": option_errors,
|
|
584
|
+
"autoFixes": {
|
|
585
|
+
"placeholderQuotesStripped": quote_fixes,
|
|
586
|
+
"selectValuesTranslated": select_changes,
|
|
587
|
+
},
|
|
505
588
|
"runResult": run_result,
|
|
506
589
|
"runError": run_error,
|
|
507
590
|
"message": "code block is valid" if ok else "code block validation failed",
|
|
508
591
|
}
|
|
509
592
|
|
|
510
593
|
|
|
594
|
+
def _load_code_block_validation_fields(
|
|
595
|
+
args: argparse.Namespace, context: CliContext
|
|
596
|
+
) -> list[dict] | None:
|
|
597
|
+
"""加载字段定义(类型+选项 optId),供选择类值翻译与成员/部门覆盖豁免;失败返回 None。"""
|
|
598
|
+
try:
|
|
599
|
+
fetched = context.builder.app_get_fields(profile=args.profile, app_key=args.app_key)
|
|
600
|
+
except Exception: # noqa: BLE001 - 字段拉取失败不阻断校验主流程
|
|
601
|
+
return None
|
|
602
|
+
if not isinstance(fetched, dict) or fetched.get("status") != "success":
|
|
603
|
+
return None
|
|
604
|
+
raw_fields = fetched.get("fields")
|
|
605
|
+
if not isinstance(raw_fields, list):
|
|
606
|
+
return None
|
|
607
|
+
return [field for field in raw_fields if isinstance(field, dict)]
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def _underivable_field_ids(fields: list[dict] | None) -> set[int]:
|
|
611
|
+
"""值不可由模型推导的字段(成员/部门):test-case 覆盖校验豁免。"""
|
|
612
|
+
if not fields:
|
|
613
|
+
return set()
|
|
614
|
+
result: set[int] = set()
|
|
615
|
+
for field in fields:
|
|
616
|
+
field_type = str(field.get("type") or "")
|
|
617
|
+
if field_type in {"member", "department"}:
|
|
618
|
+
que_id = field.get("que_id") or field.get("queId")
|
|
619
|
+
if isinstance(que_id, int):
|
|
620
|
+
result.add(que_id)
|
|
621
|
+
return result
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def _code_block_test_case_field_ids(key_que_values: list[dict]) -> set[int]:
|
|
625
|
+
"""test-case 覆盖的字段 id 集合:顶层 keyQueId + 表格 tableValues 行内 keyQueId。"""
|
|
626
|
+
covered: set[int] = set()
|
|
627
|
+
for item in key_que_values:
|
|
628
|
+
key = item.get("keyQueId")
|
|
629
|
+
if isinstance(key, int):
|
|
630
|
+
covered.add(key)
|
|
631
|
+
for row in item.get("tableValues") or []:
|
|
632
|
+
if isinstance(row, list):
|
|
633
|
+
for cell in row:
|
|
634
|
+
if isinstance(cell, dict) and isinstance(cell.get("keyQueId"), int):
|
|
635
|
+
covered.add(cell["keyQueId"])
|
|
636
|
+
return covered
|
|
637
|
+
|
|
638
|
+
|
|
511
639
|
def _load_fields_for_validation(args: argparse.Namespace, context: CliContext) -> list[dict] | None:
|
|
512
640
|
"""加载字段清单:优先 --fields 参数,缺省时从后端拉取应用字段。
|
|
513
641
|
|
|
@@ -1136,6 +1264,30 @@ def _handle_flow_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
1136
1264
|
"builder flow apply requires exactly one input mode: --spec-file or --patch-nodes-file.",
|
|
1137
1265
|
fix_hint="Use --spec-file for a complete WorkflowSpec, or --patch-nodes-file for targeted edits after builder flow get.",
|
|
1138
1266
|
)
|
|
1267
|
+
# BEGIN temporary flow edit maintenance guard: remove this block when flow editing is restored.
|
|
1268
|
+
current_flow = context.builder.app_flow_get(profile=args.profile, app_key=args.app_key)
|
|
1269
|
+
if str(current_flow.get("status") or "").lower() != "success":
|
|
1270
|
+
return current_flow
|
|
1271
|
+
current_node_count = _flow_spec_node_count(current_flow)
|
|
1272
|
+
if current_node_count is None:
|
|
1273
|
+
raise_config_error(
|
|
1274
|
+
"无法读取当前流程节点,未执行流程变更。",
|
|
1275
|
+
fix_hint="当前流程状态无法确认,未执行流程变更。请稍后重试。",
|
|
1276
|
+
error_code="FLOW_STATE_UNAVAILABLE",
|
|
1277
|
+
details={"app_key": args.app_key},
|
|
1278
|
+
)
|
|
1279
|
+
if current_node_count > FLOW_EDIT_MAINTENANCE_NODE_THRESHOLD:
|
|
1280
|
+
raise_config_error(
|
|
1281
|
+
"功能维护中,暂时不允许处理",
|
|
1282
|
+
fix_hint="当前流程节点超过 5 个,流程编辑功能维护中;请稍后重试。",
|
|
1283
|
+
error_code="FLOW_EDIT_UNDER_MAINTENANCE",
|
|
1284
|
+
details={
|
|
1285
|
+
"app_key": args.app_key,
|
|
1286
|
+
"current_node_count": current_node_count,
|
|
1287
|
+
"maintenance_node_threshold": FLOW_EDIT_MAINTENANCE_NODE_THRESHOLD,
|
|
1288
|
+
},
|
|
1289
|
+
)
|
|
1290
|
+
# END temporary flow edit maintenance guard.
|
|
1139
1291
|
if patch_nodes:
|
|
1140
1292
|
return context.builder.app_flow_apply(
|
|
1141
1293
|
profile=args.profile,
|
|
@@ -1160,6 +1312,18 @@ def _handle_flow_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
1160
1312
|
)
|
|
1161
1313
|
|
|
1162
1314
|
|
|
1315
|
+
# BEGIN temporary flow edit maintenance guard: remove this helper when flow editing is restored.
|
|
1316
|
+
def _flow_spec_node_count(flow_result: dict) -> int | None:
|
|
1317
|
+
spec = flow_result.get("spec")
|
|
1318
|
+
if not isinstance(spec, dict):
|
|
1319
|
+
return None
|
|
1320
|
+
nodes = spec.get("nodes")
|
|
1321
|
+
return len(nodes) if isinstance(nodes, list) else None
|
|
1322
|
+
|
|
1323
|
+
|
|
1324
|
+
# END temporary flow edit maintenance guard.
|
|
1325
|
+
|
|
1326
|
+
|
|
1163
1327
|
def _handle_flow_schema(args: argparse.Namespace, context: CliContext) -> dict:
|
|
1164
1328
|
return context.builder.app_flow_get_schema(profile=args.profile, schema_version=args.schema_version)
|
|
1165
1329
|
|
|
@@ -242,7 +242,6 @@ def build_question(field: dict[str, Any], temp_id: int) -> tuple[dict[str, Any],
|
|
|
242
242
|
{
|
|
243
243
|
"referenceConfig": build_reference_config(field, temp_id),
|
|
244
244
|
"queOriginType": 25,
|
|
245
|
-
"queDefaultType": 2,
|
|
246
245
|
}
|
|
247
246
|
)
|
|
248
247
|
if field_type == FieldType.code_block:
|