规范与代码出入:定案记录(原「待定」)
什么时候读:想知道某个曾经待定的出入是怎么定的、平台侧还剩什么落地项时。 2026-10 按 routers + 平台核心层(executor / pipeline / compat / actor_ctx)现行代码统一定案,原则「按正式代码来」: 文档跟代码走;代码自相矛盾的,以已在线上执行的那条为准,另一条列为平台落地项。 技能作者只需看每条的「技能侧怎么写」;「平台落地项」是给平台维护者的,不影响技能写法。
ISSUE-1 · workspace_dir —— 已定案:由 executor 注入(2026-10 二次核对推翻首轮结论)
定案:workspace_dir 是注入键,注入点在 backend/core/skills/executor.py 的 execute() 末段:
_w = compat.workspace_dir(merged_ctx) # = $AGENT_WORKSPACE_DIR/<role_id>/<session_id 或 shared>
if _w is not None:
merged_ctx.setdefault("workspace_dir", str(_w))
为什么之前判错:2026-08 的 grep 只搜了 "workspace_dir": 与 ["workspace_dir"] = 两种写法,漏了 setdefault(...);
2026-10 首轮只拿到 routers,看到 build_runtime_ctx / _build_skill_runtime_ctx / _run_async_skill 三处都不写这个键,
就沿用了「不注入」。拿到 executor.py 后确认:这三条线最后都调 executor.execute,键在那里补上。
覆盖面:对话线、call_skill、页面面板、试运行、链、异步后台全部经 executor.execute,都有。
唯一缺失:合并后的 role_id 为空(如面板请求没带 role_id 时 _build_skill_runtime_ctx 写入的 role_id="" 覆盖了执行器参数)。
子进程:SKILL_CONTEXT 里有同名键(字符串可序列化),另有环境变量 SKILL_WORKSPACE(compat.build_skill_env,同值)。
技能侧怎么写:ctx.get("workspace_dir"),缺则按同式自推兜底(code-facts §4 _workspace(ctx))。
连带结论:此前点名的两个市场技能不需要为工作区路径修改——
trade-macro 的 (ctx or {}).get("workspace_dir") or os.getcwd() 与 xkeyai_image 的 _workspace_dir() 都先读
workspace_dir,正常执行时拿到的就是注入值,回退分支走不到。它们当初被点名,只是因为 2026-08 grep 时恰好搜到这两处在读
这个键,被当作「读了却拿空」的样本——这个前提不成立。(xkeyai_image 另有与工作区无关的信封问题,见其修订说明。)
平台落地项:无。
ISSUE-2 · datatable 结构类鉴权 —— 已定案:owner / admin
定案:结构类操作(create_table / add_field / update_field / delete_field / delete_table,及 SQL 通道的 CREATE /
结构变更)仅 team_role ∈ {owner, admin}。即规范 §7.2 原文。
证据(2026-10):
- 正式在线接口
backend/api/routers/nocodb.py(Web 数据管理,/api/v1/nocodb/*)对建表 / 加改删字段 / 删表 / 只读 SQL / 授权管理全部走_require_struct_admin:team_role not in {"owner","admin"}→ 403。 team_role判定(nocodb._resolve_team_role,与对话线roles_chat_helpers解析SkillRunContext.team_role同口径): 用户所属团队Team.owner_id == user_id→owner;否则TeamMember.role;都取不到 →member。- 2026-08 记录的「团队关联角色(含 member)可建表、子账号不可」在任何代码里都没有对应实现:代码里「子账号」=
非 owner 的团队成员(
topology.py/org_backup.py),而线上接口恰恰允许 admin 子账号改结构、禁止 member。 该规则与正式代码冲突,撤回。
技能侧怎么写:
- 技能线(datatable handler)的两处判断仍是注释状态(见下方落地项),经 call_skill 调用时任何角色都能建表—— 不要把「会被拒」当安全边界。
- 只该由管理员做的结构操作,技能自己先判:
ctx.get("team_role") in ("owner", "admin"),否则返回{"success": False, "error_type": "config", "non_retriable": True, "error": "仅团队 owner/admin 可修改表结构", "user_message": …}。 页面面板线不注入team_role,按缺失拒绝。
平台落地项:恢复 skills/builtin/datatable/handler.py 两处注释(约 755、1010 行),并对缺失 team_role fail-closed:
_tr = (ctx.get("team_role") or "").strip()
if action in _STRUCT_ACTIONS and _tr not in _ADMIN_TEAM_ROLES:
return {"success": False, "error_type": "config", "non_retriable": True,
"error": f"结构类操作仅 owner/admin,当前 team_role={_tr or '空'}",
"user_message": "只有团队管理员(owner/admin)可以新建表、修改结构或删除表。"}
# SQL 建表分支(_stmt in ("create","alter_add_unique"))同样接一份
各执行线的 team_role 现状(2026-10 按 pipeline.py / skills.py / roles_chat_helpers.py 核对)——恢复鉴权后,
datatable 读 ctx.get("team_role", "member"),拿不到就是 member(会被拒):
| 线 | team_role 来源 | 恢复后 owner/admin 能否改结构 |
|---|---|---|
| 对话线 | roles_chat_helpers 解析(Team.owner_id / TeamMember.role)写进 SkillRunContext | 能 |
| 技能内 call_skill | _CALL_PASS_BASE 透传调用方的值 | 能(随调用方) |
页面面板 / 试运行 / 下拉选项 / 链试跑(_build_skill_runtime_ctx) | 原先不注入 → member。本次随附 skills.py 补丁,按同口径注入 | 打补丁后能 |
| 异步后台 | 固定上下文只含词表键,无 team_role | 不能(datatable 不是异步技能,不受影响) |
技能链(chain 型技能 / /chain/test) | runner _to_run_context 原先缺失时塞 member;本次 runner.py 补丁改为留空,交给 pipeline 解析 | 能(按链 ctx 里的 user_id) |
定时任务 steps(skill_schedule) | 已核对 scheduler_handlers.py:不走 runner,自己逐步调 pipeline.execute_skill;SkillRunContext 填了 user_id=action["user_id"](任务创建人),未填 team_role → 由 pipeline 按创建人解析 | 创建人是 owner/admin 就能 |
定时任务(instruction 型) | 起一条 scheduler 渠道会话(user_id = 创建人)跑完整对话循环,team_role 走对话线解析 | 同上 |
定时触发工作流(workflow 型) | trigger_workflow 不带 user_id;工作流上下文里没有 user_id 时按 member | 不能(如需要,由 extra_context 传入 user_id,未核对 automate 是否读取) |
| 工作流「执行技能」 | automate._run_skill_node 构造 SkillRunContext 不填 team_role → 由 pipeline 按工作流上下文的 user_id 解析;没有 user_id 则 member | 取决于工作流的 user_id |
统一兜底(本次 pipeline.py 补丁):SkillRunContext.team_role 默认值由 member 改为空串;build_runtime_ctx 发现为空时按
user_id 用同一口径解析(resolve_team_role),解析不出才 member。这样链、定时任务、工作流不用各自改,automate.py 无需改动。
定时任务执行端已于 2026-10 按 scheduler_handlers.py 核对:skill_schedule 直接把任务记录里的 user_id 放进 SkillRunContext,
无需额外修改。剩余边角:定时触发的工作流拿不到 user_id,其中的建表步骤按 member 处理(多数工作流只读写数据,不受影响)。
ISSUE-3 · ctx["db"] 预注入下线 —— 规则已定,迁移进行中
定案:新技能直连库一律 db_factory 现开现还、不读 ctx["db"](spec §7A、code-facts §3.7)。这是写法约束,不是运行时强制。
现状(2026-10 按 executor.py / pipeline.py 核对):SELF_ACQUIRE_SKILLS 已扩到 9 个——datatable trade-bot trade-hltrade
trade-research trade-factor trade-regimecalc trade-macro trade-okxbn-kline trade-backtest。名单内技能在 call_skill 不预开、
在 pipeline 不开 skill_scope(db=None)。名单外技能仍会拿到预开的 ctx["db"];页面 / 试运行线(请求级会话)、异步后台(懒会话)也注入。
下线路径:
- 各直连技能逐个改成
_db()现开现还。 - 每迁一个,加入 executor 的
SELF_ACQUIRE_SKILLS,使 call_skill / pipeline 不再为它预开。 - 全部迁完后移除各处预开 / 注入,runtime-context / spec §4.3 的
db行改「已移除」。
在迁完前:检查器对读 ctx["db"] 报 WARN(建议改 db_factory),不报 ERROR。
2026-10 新增定案(routers 核对,详见 code-facts.md §5)
| 出入点 | 定案(按代码) | 改在哪 |
|---|---|---|
| 入口函数签名 | 安装预检要求 async def execute(input_data, runtime_context);create_skill / edit_skill 把预检问题当拦截 | SKILL.md 第 1 条、type-handler-python.md、模板、检查器 |
| 页面动作网关 | 只按 action.enum 挡未知动作;x-actions / invocable_from / structural 不再校验(40303 已不存在) | ui-panel.md、input-schema.md §8 |
| 页面驱动身份 | 带可信 session_id 时解析完整 actor_*;不再「工号强制为空」;team_role 原不注入(本次随附 skills.py 补丁注入);channel 不可依赖 | ui-panel.md §5、runtime-context.md §9 |
| 外部面板 JWT | header 无 kid;aud = URL netloc;公钥端点 /api/v1/skills/panel/public-key(PEM) | ui-panel.md §8 |
| 失败预算 | FAIL_PER_TOOL 默认 4、FAIL_TOTAL 默认 8、同参第 4 次判死循环;可恢复类不计数 | output-envelope.md §2、spec §6.1 |
| 墙钟 | 技能墙钟默认 600 秒(SKILL_WALL_DEFAULT,可声明 wall_sec 至 1800);对话线外层 TOOL_WALL_SEC 1800(原写 240) | output-envelope.md §7、spec §8 |
display_only | 对话线未实现,只有追加语义 | output-envelope.md §3、spec §6.2 |
| attachments 结构 | 经 build_ctx 归一为 {type, att_type, filename, local_path, url}——规范原文正确(首轮只看 routers 误判,已撤回) | runtime-context.md §7 |
| async 后台 provider | 后台同走 executor.execute,provider 照常注入 | llm-call.md、spec §10.5 |
| 配置项声明 | 控制台表单读顶层 env 等键;未声明时按正文嗅探 | config-secrets.md |
| frontmatter 读取 | 平台从顶层读 category / cost-tier;category 只认 13 个值;YAML 失败退逐行解析 | SKILL.md、模板、checklist |
| 技能链保存校验 | 链名 ^[a-z][a-z0-9_]*$、别名规则、{入参} 必须声明且被用、$steps 只能引前序、按 output_schema 校验字段、禁链套链 | chain.md |
| API 型文档技能 | 有 openapi / endpoints_whitelist 或 skillClass: *api-skill 即走端点执行器 | type-skill-md.md |
已定案速查(2026-08,在 spec-revised.md 标 【订正 2026-08】)
| 出入点 | 定案 |
|---|---|
create_table 字段类型枚举(无 JSON/高精度数字) | 改文档 §7.5 |
create_table.fields 形状 | 补文档 §7.3 |
call_skill 返回不包 data | 改文档 §9.1 |
send_message web 用 session_id / 成功判据 | 改文档 §9.2 |
user_message 可当失败原因 | 补文档 §6.1 |
| filters 残留引用 non_retriable | 补文档 §7.4 |
allow_sql 保留未启用 | 改文档 §4.3 标注 |
| 入口第二参数默认值 | 文档从严,维持「不得给」,标订正 |
| 直连库:db_factory 现开现还、不读 ctx["db"] | 新增 spec §7A;§4.3 / §7.8 / §11 订正;下线迁移见 ISSUE-3 |