Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
468a418d3a | ||
|
|
71b45b1eab |
@@ -17,13 +17,17 @@ metadata:
|
||||
|
||||
## 加载与路由顺序
|
||||
|
||||
1. 命中下方高频意图时直接使用精确骨架,不先查 Help 或产品级 Schema。
|
||||
2. 路由优先级固定为:精确骨架 / recipe > 匹配的公开 Shortcut > 原子命令。脚本只用于 Runtime 尚未覆盖的批量、文件传输或异步编排,不与普通原子命令竞争默认入口。
|
||||
1. 先定义目标对象、父容器、用户要求的阶段和完成证据;命中下方高频意图时,把精确
|
||||
骨架作为首选起点,不先查 Help 或产品级 Schema。
|
||||
2. 首选顺序为:精确骨架 / recipe > 匹配的公开 Shortcut > 原子命令。它用于降低
|
||||
首调用成本,不是禁止依据 Runtime 新证据调整路径。脚本只用于 Runtime 尚未覆盖的
|
||||
批量、文件传输或异步编排,不与普通原子命令竞争默认入口。
|
||||
3. 参数、约束或安全语义不确定时只读 leaf Schema:`dws schema --cli-path "aitable <leaf>" --format json`;只有当前 Cobra flag 不确定时才读对应 `--help`。
|
||||
4. 复杂字段、筛选、导入导出、视图、权限或工作流任务,按“低频能力与 Reference”只加载相关文件,不预读整个 `references/aitable/`。
|
||||
5. 现有骨架和 reference 都无法定位能力时,才用 Runtime Shortcut Catalog 做最后发现;不得猜 `cli_path` 或 flag。
|
||||
6. Schema、Help、reference 与实际返回冲突时采用更安全的解释并报告契约漂移;`confirmation=user_required` 时先确认,再添加 `--yes`。
|
||||
7. 用户已给足目标、字段和数据时,按依赖链连续执行;中间结果只用于提取真实 ID 和判断停止条件,全部完成后统一回读并答复。
|
||||
7. 用户已给足目标、字段和数据时,按依赖链连续执行;保留已成功步骤及真实 ID,只从
|
||||
未完成缺口继续。每个关键结论用语义等价证据关闭后再统一答复。
|
||||
|
||||
<!-- VISIBLE_SHORTCUTS_START -->
|
||||
## Shortcut 发现(按需)
|
||||
@@ -58,12 +62,13 @@ metadata:
|
||||
| 按名称找 Table | `dws aitable +resolve-table --base <baseId> --name "<表名>" --format json` | `baseId` 必须来自上一步真实返回 |
|
||||
| 取表、字段与视图目录 | `dws aitable +table-get --base-id <baseId> [--table-ids <tableId>] --format json` | `tables[].fields[]` 是字段目录;完整类型/config 再用 `+field-get` |
|
||||
| 取字段完整配置 | `dws aitable +field-get --base-id <baseId> --table-id <tableId> [--field-ids <ids>] --format json` | 写入前核对类型、只读性和 select options;按需展开以控制返回体 |
|
||||
| 查/搜/筛记录 | `dws aitable +record-query --base-id <baseId> --table-id <tableId> [--query <词>\|--filters '<JSON>'\|--record-ids <ids>] --format json` | ID 模式忽略 filter/sort;全量结论必须完整分页 |
|
||||
| 查/搜/筛记录 | `dws aitable +record-query --base-id <baseId> --table-id <tableId> [--query <词>\|--filters '<JSON>'\|--record-ids <ids>] --format json` | ID 模式忽略 filter/sort;不要猜 `--page-limit`,分页使用返回的 cursor 与 leaf Help 中的真实分页 flag |
|
||||
| 新增记录 | `dws aitable record create --base-id <baseId> --table-id <tableId> --records '[{"cells":{"<fieldId>":<值>}}]' --format json` | 单次最多 100;取 `data.newRecordIds[]` 后立即按 ID 回读 |
|
||||
| 更新记录 | `dws aitable record update --base-id <baseId> --table-id <tableId> --records '[{"recordId":"<id>","cells":{"<fieldId>":<值>}}]' --format json` | 先 query 拿 recordId;只传需改字段;取 `data.recordIds[]` 后回读 |
|
||||
| 删除记录 | 先 `dws aitable +record-query ...` 定位,再 `dws aitable record delete --base-id <baseId> --table-id <tableId> --record-ids <ids>` | 展示目标与影响,得到明确确认后才加 `--yes` |
|
||||
| 创建 Base / Table | `dws aitable base create --name "<名>"` / `dws aitable table create --base-id <id> --name "<名>" --fields '[...]'` | 使用创建返回的真实 ID;系统改名/加后缀时不得继续猜原名 |
|
||||
| 复制视图 | `dws aitable view duplicate --base-id <baseId> --table-id <tableId> --view-id <viewId> --new-name "<名>"` | `viewId` 必须属于当前表;不能用复制 Table 或新建 Dashboard 替代 |
|
||||
| 创建图表 | 先读 `dws aitable chart widgets-example --format json`,再执行 `chart create ... --config '<JSON>' --layout '<JSON>'` | `--layout` 是必填项;不能只传 config 后依据退出码声称图表已创建 |
|
||||
| 批量追加 CSV / JSON 到已有表 | `python3 scripts/import_records.py <baseId> <tableId> <file> [batch_size]` | CSV 表头必须是 fieldId;脚本返回不完整 ledger 时不得宣称全成功 |
|
||||
| 文件导入为新数据表 | `python3 scripts/aitable_import_via_task.py <baseId> <file>` | 与“追加已有 table”不同;走 prepare → PUT → import task |
|
||||
| 批量创建字段 | `python3 scripts/bulk_add_fields.py <baseId> <tableId> fields.json` | 单次最多 15;逐项检查成功/失败结果 |
|
||||
@@ -74,9 +79,10 @@ metadata:
|
||||
|
||||
- `record create/update` 前必须获取目标字段的 `fieldId`、`type` 与 `config`;`filterUp`、`lookup` 等只读字段不可写。完整格式只在需要时读 [aitable-cell-value.md](references/aitable/aitable-cell-value.md)。
|
||||
- 筛选和排序字段使用 `fieldId`;`--filters` 最外层是 `and|or + operands`,`--sort` 使用 `direction: asc|desc`。日期和跨表字段规则按需读 [aitable-filter-sort.md](references/aitable/aitable-filter-sort.md)。
|
||||
- `record query --all` 仍受 `--page-limit` 约束;分页中断或局部富化失败时保留已有结果,输出 completeness 与逐项失败 ledger,不把部分结果描述为全量。
|
||||
- 记录分页不能凭经验拼 `--page-limit`。先用当前 leaf Help 确认 page-size/cursor 的真实名称;每页读取返回 cursor,直到明确终止。分页中断或局部富化失败时保留已有结果,输出 completeness 与逐项失败 ledger,不把部分结果描述为全量。
|
||||
- 创建、更新、导入、批量建字段等写操作必须检查业务 `status`、逐项结果与返回 ID;普通写入按用户明确要求执行后回读,不能只凭退出码宣称成功。
|
||||
- 长 JSON 使用 `--records-file` / 任务文件;不得为绕过字段错误而静默丢列、改类型或删除失败项。
|
||||
- `table create --fields` 的键固定为 `fieldName` / `type` / `config`,不能写成 `name`;单选/多选类型固定为 `singleSelect` / `multipleSelect`,不能写 `select`。number formatter 不确定时先读 leaf Help,禁止猜 `INTEGER` 等值。
|
||||
|
||||
## 写入计划与验证
|
||||
|
||||
@@ -94,6 +100,11 @@ metadata:
|
||||
|
||||
退出码 0、`status=success`、空对象或仅有 taskId 都不能单独证明业务完成。字段未生效、回读不一致、分页不完整或异步任务未完成时,报告失败、部分完成或进行中,不得声称“全部完成”。
|
||||
|
||||
验证必须与用户点名的操作语义一致:Base 搜索只能由搜索结果中的目标命中关闭,最近
|
||||
访问列表或创建回读只能证明对象存在;复制必须有复制操作产生的新对象证据;移动必须
|
||||
回读新的父级关系;删除必须核对精确目标并确认其已不存在。邻近证据可用于诊断,不能
|
||||
替代目标步骤的完成证据。
|
||||
|
||||
## 低频能力与 Reference
|
||||
|
||||
| 场景 | 按需读取 |
|
||||
@@ -108,10 +119,18 @@ metadata:
|
||||
|
||||
## 错误恢复
|
||||
|
||||
- 路径或 flag 错误:按既定的 leaf Schema → leaf Help 顺序校正一次;仍失败则停止,不连续尝试猜测别名。
|
||||
- 不使用全局“只准纠正一次”的硬限制。恢复预算由已观察状态和副作用风险决定,只在
|
||||
新错误信息、leaf Schema/Help 或回读产生新证据时继续;重复同一假设或不再增加证据
|
||||
时停止。
|
||||
- 路径、flag 或 payload 在写入前被校验拒绝:吸收精确 hint,保持目标和容器不变后
|
||||
修正。只读调用可做有界替代探测,但不得用相邻能力冒充目标能力。
|
||||
- 命令非零、输出非 JSON、业务 `status != success`、必需 ID 缺失、批处理部分失败均视为失败;保留成功项与 ledger,禁止吞错。
|
||||
- 同名歧义、权限不足、资源不存在、字段类型漂移、分页无法推进或 Schema/Help 冲突时停止并报告。具体恢复动作按需读 [aitable-error-recovery.md](references/aitable/aitable-error-recovery.md)。
|
||||
- 已确认远端未写入且契约声明可重试时,才从最新实际输出重新提取 ID 后重试;写入状态未知、删除和其他 `confirmation=user_required` 操作不得自动重试或静默确认。
|
||||
- 写入已返回 ID 时保留成功状态,只补字段、记录、视图、图表或验证缺口。写入超时或
|
||||
状态未知时先回读当前 Base/Table;只有明确未写入且契约允许时才重试。删除和其他
|
||||
`confirmation=user_required` 操作不得自动重试或静默确认。
|
||||
- 不要重复猜同一 flag、formatter、folderId 或图表配置。目标能力已有原生命令时,
|
||||
不要以手工重建相似对象掩盖原命令失败。
|
||||
|
||||
## 跨产品协作
|
||||
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
## 建议操作顺序
|
||||
|
||||
```bash
|
||||
# 1) 先看配置模板(JSONC)
|
||||
# 1) 先看图表配置模板和当前 leaf 契约
|
||||
dws aitable dashboard config-example --format json
|
||||
dws aitable chart widgets-example --format json
|
||||
dws aitable chart create --help
|
||||
|
||||
# 2) 先拿 dashboard,再拿 chart 详情
|
||||
dws aitable dashboard get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --format json
|
||||
@@ -36,11 +37,15 @@ dws aitable chart get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-
|
||||
| 命令 | 用途 | 必填参数 |
|
||||
|------|------|----------|
|
||||
| `chart get` | 获取图表详情 | `--base-id` `--dashboard-id` `--chart-id` |
|
||||
| `chart create` | 创建图表 | `--base-id` `--dashboard-id` `--config` |
|
||||
| `chart create` | 创建图表 | `--base-id` `--dashboard-id` `--config` `--layout` |
|
||||
| `chart update` | 更新图表配置 | `--base-id` `--dashboard-id` `--chart-id` `--config` |
|
||||
| `chart delete` | 删除图表 | `--base-id` `--dashboard-id` `--chart-id` `--yes` |
|
||||
| `chart widgets-example` | 查看图表 widgets 配置模板 | 无 |
|
||||
|
||||
## 配置获取流程
|
||||
|
||||
创建图表前,必须先调用 `chart widgets-example` 查看配置模板,了解每种图表类型需要的字段结构,然后根据实际 tableId 和 fieldId 填充配置。
|
||||
创建图表前,先调用当前 Runtime 的 `chart widgets-example`(如未来命令发生变化,
|
||||
以 leaf Schema/Help 的真实命令为准),再根据实际 tableId 和 fieldId 填充配置,并
|
||||
在第一次创建时同时传入 `--config` 与 `--layout`。创建返回
|
||||
`chartId` 后用 `dashboard get`/`chart get` 回读;只有 Dashboard 存在但图表缺失时,
|
||||
图表阶段仍未完成。
|
||||
|
||||
@@ -70,22 +70,27 @@
|
||||
| 导入文件格式错误 | 不支持的文件格式或文件损坏 | 确认文件为 .xlsx 格式且未加密 |
|
||||
| 批处理只成功一部分 | 某批或某个字段返回失败 | 保留成功 ID,输出失败 batch/item ledger,以非零状态结束;不要整批重放 |
|
||||
|
||||
## 3. 重试策略
|
||||
## 3. 状态感知恢复策略
|
||||
|
||||
### 3.1 可重试的错误
|
||||
不要用一个全局次数限制处理所有错误。先判断上一次调用是否可能产生副作用,再决定
|
||||
探测、继续或停止。
|
||||
|
||||
| 错误类型 | 重试方式 | 最大重试次数 |
|
||||
|---------|---------|------------|
|
||||
| 网络超时 / 5xx | 等待 2s 后原样重试 | 2 |
|
||||
| 导出任务未完成 | 轮询 task-id | 5(间隔 3s) |
|
||||
| 并发写入冲突 | 串行重试 | 1 |
|
||||
| 已观察状态 | 恢复动作 |
|
||||
|---|---|
|
||||
| 只读调用失败 | 根据精确错误或 leaf 契约做有界探测;目标和父容器保持不变 |
|
||||
| 写入前参数校验失败 | 确认无副作用后修正 payload/flag;不原样重试 |
|
||||
| 网络超时 / 5xx,写入状态未知 | 先回读目标 Base/Table,确定未生效后才考虑重试 |
|
||||
| 已返回新对象或记录 ID | 绑定该 ID,只继续尚未完成的步骤 |
|
||||
| 异步任务未完成 | 继续查询同一个 taskId/importId,直到契约规定的终态或总超时 |
|
||||
| 并发冲突且明确未写入 | 降低并发或串行重试;再次冲突时停止并报告 |
|
||||
| 删除或高影响操作状态未知 | 停止并回读,不自动重放 |
|
||||
|
||||
### 3.2 不可重试的错误(立即停止)
|
||||
|
||||
| 错误类型 | 原因 | 处理方式 |
|
||||
|---------|------|---------|
|
||||
| 权限不足 / 403 | 用户对该 Base 无权限 | 停止操作,提示用户确认权限 |
|
||||
| 参数格式错误 | 请求结构不合法 | 修正参数后重试,不要原样重试 |
|
||||
| 参数格式错误 | 请求结构不合法 | 只在校验明确未写入且有新契约证据时修正,不要原样重试 |
|
||||
| 资源不存在 / 404 | ID 错误或资源已删除 | 重新查询定位资源 |
|
||||
| 配额超限 / 429 | API 调用频率过高 | 等待后重试,并降低并发 |
|
||||
|
||||
@@ -94,7 +99,12 @@
|
||||
在重试前,先确认:
|
||||
1. ❓ 错误是暂时性的还是永久性的?
|
||||
2. ❓ 参数有没有明显错误需要修正?
|
||||
3. ❓ 是否需要先查询最新状态再重试?
|
||||
3. ❓ 上次调用是否可能已经写入?
|
||||
4. ❓ 是否已经获得对象 ID,应该从后续缺口继续?
|
||||
5. ❓ 新尝试是否基于新证据,而不是重复同一猜测?
|
||||
|
||||
当没有新证据、开始重复同一错误、目标发生变化或副作用无法判断时停止。Base 搜索
|
||||
失败时,`base list` 只能帮助确认对象存在,不能作为搜索完成证据。
|
||||
|
||||
## 4. 调试技巧
|
||||
|
||||
|
||||
@@ -40,8 +40,8 @@ dws aitable import upload --base-id <BASE_ID> \
|
||||
--file-name data.xlsx --file-size <字节数> --format json
|
||||
# → 返回 uploadUrl 和 importId
|
||||
|
||||
# 第 2 步:上传文件到 OSS(注意:Content-Type 必须设为空)
|
||||
curl -X PUT "<uploadUrl>" -H "Content-Type:" --data-binary @data.xlsx
|
||||
# 第 2 步:由受测 helper 上传到 OSS(Content-Type 为空)
|
||||
# 正常 Agent 执行使用 aitable_import_via_task.py,不手工拆出 curl fallback。
|
||||
|
||||
# 第 3 步:触发导入(新建表模式)
|
||||
dws aitable import data --import-id <importId> --format json
|
||||
@@ -57,7 +57,7 @@ dws aitable import data --import-id <importId> --table-id <TABLE_ID> --format js
|
||||
| 步骤 | 命令 | 说明 |
|
||||
|------|------|------|
|
||||
| 申请上传凭证 | `import upload --base-id <ID> --file-name <名称> --file-size <字节>` | `--file-size` 必须与实际文件大小一致 |
|
||||
| 上传文件 | HTTP PUT(curl 等) | **必须** 带 `-H "Content-Type:"` 将 Content-Type 设为空,否则 OSS 返回 403 |
|
||||
| 上传文件 | 受测 helper 的 HTTP PUT | Content-Type 必须为空,否则 OSS 返回 403;helper 需校验 OSS host,并将受信 HTTP signed URL 升级为 HTTPS |
|
||||
| 触发导入 | `import data --import-id <ID> [--table-id <TABLE_ID>]` | 同步等待,大多一次调用即返回结果;超时可用相同 importId 重试 |
|
||||
|
||||
### import data 参数
|
||||
@@ -104,7 +104,9 @@ dws aitable import data --import-id <importId> --table-id <TABLE_ID> --format js
|
||||
> **解决方案**:
|
||||
> 1. **首选**:创建目标表时,字段名与 Excel 表头列名**保持完全一致**
|
||||
> 2. **备选**:传 `--field-mapping '{"目标字段名":"Excel列名"}'` 手动指定映射
|
||||
> 3. **兜底**:如果 import data 多次失败,改用 `record create` 逐条写入
|
||||
> 3. 如果 import data 失败,先按错误和任务状态判断是否已写入;只有用户目标允许
|
||||
> “追加已有表”、文件内容可安全解析且新建表导入明确未生效时,才提出改用
|
||||
> `record create`。逐条写入不等价于文件级导入,不能静默替代并宣称导入成功。
|
||||
|
||||
### 适用场景
|
||||
|
||||
|
||||
@@ -38,6 +38,15 @@
|
||||
|
||||
## 字段创建时设置 config(重要)
|
||||
|
||||
字段 JSON 的命名和枚举是严格契约:
|
||||
|
||||
- 批量 `--fields` 每项使用 `fieldName`,不是 `name`。
|
||||
- 单选是 `singleSelect`,多选是 `multipleSelect`;不存在通用的 `select` 类型。
|
||||
- number/date/currency formatter 只使用当前 `table create --help` 或 leaf Schema 列出的枚举;不要从别的产品或旧样例猜 `INTEGER`。
|
||||
- 返回明确 hint 时,只修改 hint 指向的字段契约并保持目标不变。只要写入前校验明确
|
||||
未产生副作用且新的 Schema/Help 证据仍在收敛,就可继续修正;重复同一假设或不再
|
||||
获得新证据时停止并保留部分成功 ledger。
|
||||
|
||||
创建 singleSelect/multipleSelect 字段时,**必须设置选项 (options)**:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -17,6 +17,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import ssl
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
@@ -26,7 +27,7 @@ from urllib.error import HTTPError, URLError
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
RESOURCE_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]{8,128}$")
|
||||
RESOURCE_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]{6,128}$")
|
||||
ALLOWED_FORMATS = {"excel", "attachment", "excel_and_attachment", "excel_with_inline_images"}
|
||||
|
||||
|
||||
@@ -64,7 +65,12 @@ def normalize_download_url(url: str) -> str:
|
||||
def download_file(url: str, output_path: Path) -> Tuple[bool, str]:
|
||||
req = Request(url, method="GET")
|
||||
try:
|
||||
with urlopen(req, timeout=180) as resp:
|
||||
try:
|
||||
import certifi
|
||||
context = ssl.create_default_context(cafile=certifi.where())
|
||||
except ImportError:
|
||||
context = ssl.create_default_context()
|
||||
with urlopen(req, timeout=180, context=context) as resp:
|
||||
redirected = urlparse(resp.geturl())
|
||||
if redirected.scheme != "https" or not redirected.hostname:
|
||||
return False, "download redirect is not HTTPS"
|
||||
|
||||
@@ -17,6 +17,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import ssl
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -25,7 +26,7 @@ from urllib.error import HTTPError, URLError
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
RESOURCE_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]{8,128}$")
|
||||
RESOURCE_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]{6,128}$")
|
||||
ALLOWED_EXTENSIONS = {".csv", ".xlsx", ".xls"}
|
||||
|
||||
|
||||
@@ -33,6 +34,24 @@ def validate_resource_id(resource_id: str) -> bool:
|
||||
return bool(resource_id and RESOURCE_ID_PATTERN.match(resource_id.strip()))
|
||||
|
||||
|
||||
def normalize_oss_upload_url(upload_url: str) -> str:
|
||||
"""Accept the signed OSS URL returned by Runtime and upgrade it to HTTPS."""
|
||||
parsed = urlparse(upload_url)
|
||||
if parsed.scheme not in {"http", "https"} or not parsed.hostname:
|
||||
raise ValueError("uploadUrl must be a valid HTTP(S) URL")
|
||||
if not parsed.hostname.endswith(".oss.aliyuncs.com"):
|
||||
raise ValueError("uploadUrl host is not an Aliyun OSS endpoint")
|
||||
return parsed._replace(scheme="https").geturl()
|
||||
|
||||
|
||||
def tls_context() -> ssl.SSLContext:
|
||||
try:
|
||||
import certifi
|
||||
return ssl.create_default_context(cafile=certifi.where())
|
||||
except ImportError:
|
||||
return ssl.create_default_context()
|
||||
|
||||
|
||||
def run_dws(dws_bin: str, args: list[str], timeout_sec: int = 120) -> Tuple[int, str, str]:
|
||||
cmd = [dws_bin] + args
|
||||
try:
|
||||
@@ -53,15 +72,16 @@ def parse_json_output(raw: str) -> Optional[Dict[str, Any]]:
|
||||
|
||||
|
||||
def put_file(upload_url: str, file_path: Path) -> Tuple[bool, str]:
|
||||
parsed = urlparse(upload_url)
|
||||
if parsed.scheme != "https" or not parsed.hostname:
|
||||
return False, "uploadUrl must be a valid HTTPS URL"
|
||||
try:
|
||||
upload_url = normalize_oss_upload_url(upload_url)
|
||||
except ValueError as exc:
|
||||
return False, str(exc)
|
||||
payload = file_path.read_bytes()
|
||||
req = Request(upload_url, data=payload, method="PUT")
|
||||
# 关键:清空 Content-Type,避免 SignatureDoesNotMatch。
|
||||
req.add_header("Content-Type", "")
|
||||
try:
|
||||
with urlopen(req, timeout=180) as resp:
|
||||
with urlopen(req, timeout=180, context=tls_context()) as resp:
|
||||
if resp.status == 200:
|
||||
return True, ""
|
||||
return False, f"unexpected HTTP status: {resp.status}"
|
||||
|
||||
@@ -25,13 +25,14 @@ import subprocess
|
||||
import os
|
||||
import mimetypes
|
||||
import re
|
||||
import ssl
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Any
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.parse import urlparse
|
||||
|
||||
RESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{8,128}$')
|
||||
RESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{6,128}$')
|
||||
MAX_FILE_SIZE = 100 * 1024 * 1024 # 100MB
|
||||
|
||||
|
||||
@@ -77,15 +78,24 @@ def run_dws(args: list, dws_bin: str = 'dws') -> Optional[Dict[str, Any]]:
|
||||
def upload_to_oss(upload_url: str, file_path: Path, mime_type: str) -> bool:
|
||||
"""通过 HTTP PUT 上传文件到 OSS。"""
|
||||
parsed = urlparse(upload_url)
|
||||
if parsed.scheme != 'https' or not parsed.hostname:
|
||||
print('错误:uploadUrl 必须是有效的 HTTPS URL', file=sys.stderr)
|
||||
if parsed.scheme not in {'http', 'https'} or not parsed.hostname:
|
||||
print('错误:uploadUrl 必须是有效的 HTTP(S) URL', file=sys.stderr)
|
||||
return False
|
||||
if not parsed.hostname.endswith('.oss.aliyuncs.com'):
|
||||
print('错误:uploadUrl 不是阿里云 OSS 地址', file=sys.stderr)
|
||||
return False
|
||||
upload_url = parsed._replace(scheme='https').geturl()
|
||||
file_data = file_path.read_bytes()
|
||||
req = Request(upload_url, data=file_data, method='PUT')
|
||||
req.add_header('Content-Type', mime_type)
|
||||
|
||||
try:
|
||||
with urlopen(req, timeout=120) as resp:
|
||||
try:
|
||||
import certifi
|
||||
context = ssl.create_default_context(cafile=certifi.where())
|
||||
except ImportError:
|
||||
context = ssl.create_default_context()
|
||||
with urlopen(req, timeout=120, context=context) as resp:
|
||||
if resp.status == 200:
|
||||
return True
|
||||
print(f"错误:OSS 上传失败,HTTP {resp.status}", file=sys.stderr)
|
||||
|
||||
@@ -39,6 +39,16 @@ skill 作为入口进行二级路由。
|
||||
- 创建、更新、发送等写操作后,按产品 skill 的要求回读或查询状态,不得仅凭退出码
|
||||
宣称成功。
|
||||
|
||||
## 先定义结果,再选择命令
|
||||
|
||||
执行前把请求压缩成一个结果契约:目标状态、资源类型与父容器、用户明确要求的阶段、
|
||||
副作用边界,以及每个关键结论需要的验证证据。命令只是达到结果的候选路径,不得用
|
||||
相似终态替代用户点名的操作:新建相似对象不等于复制成功,最近访问列表不等于搜索
|
||||
命中,发送请求返回成功也不等于对方已收到。
|
||||
|
||||
执行过程中维护轻量状态账本:已确认成功的步骤和 ID、未完成缺口、写入状态是否确定、
|
||||
下一步安全探测及最终验证。恢复时从缺口继续,不重放已经成功的写操作。
|
||||
|
||||
## 渐进加载
|
||||
|
||||
只读取当前任务需要的文件,不要一次性加载全部 shared references:
|
||||
@@ -82,8 +92,12 @@ skill 作为入口进行二级路由。
|
||||
|
||||
## 错误最短路径
|
||||
|
||||
1. `unknown command` / `unknown flag`:运行对应层级 `--help`,修正后最多重试一次。
|
||||
1. `unknown command` / `unknown flag`:读取错误中的 hint、leaf Schema 或对应层级
|
||||
`--help`,只在获得新证据时修正;不要重复尝试同一假设。
|
||||
2. 认证或权限错误:读取 `global-reference.md` 与 `error-codes.md` 对应章节。
|
||||
3. 其他错误:加 `--verbose` 重试一次;仍失败则停止并报告真实错误,不连续尝试替代
|
||||
命令。
|
||||
4. 明确不支持的能力:说明边界,不通过其他接口绕过。
|
||||
3. 只读调用或写入前参数校验失败:保持目标不变,依据新证据做有界恢复。
|
||||
4. 写入已返回 ID:保留成功状态,只补后续缺口;写入超时、返回不明或可能已生效时,
|
||||
先回读目标容器,禁止盲目重放。删除及高影响操作状态不明时停止并报告。
|
||||
5. 当后续尝试不再产生新证据、开始重复同一错误、改变目标或副作用变得不确定时停止;
|
||||
不用不同产品或相似对象掩盖原操作失败。
|
||||
6. 明确不支持的能力:说明边界,不通过其他接口绕过。
|
||||
|
||||
Reference in New Issue
Block a user