octopus-rpa-app-runner 技能升级技术方案 v2
原始文件:wecom_e0c326ea_octopus-rpa-app-runner_技能升级技术方案_v2.md
octopus-rpa-app-runner 技能升级技术方案 v2
基于octopus-rpa-app-runner_技能升级PRD_20260806.md、octopus-rpa-app-runner_技能升级需求_v2.md与当前技能代码形成。本文是增量实施方案,不包含代码修改。
1. 文档目标与实施原则
1.1 目标
本方案用于指导多个开发 Agent 并行完成以下升级:
- 将参数弹窗“不填参数直接运行”收敛为正式、安全、可审计的 CLI 能力。
- 将“先尝试停止,再关闭运行浮窗并恢复 Studio”收敛为正式 CLI 能力,同时不把“窗口关闭”误报为“任务停止”。
- 为所有主动作建立统一结果对象、文本/JSON 输出、错误码和退出码。
- 在不重写现有 runner 的前提下补齐参数策略、状态机、PowerShell fallback、自动化测试和迁移文档。
1.2 实施原则
- 增量演进:保留
OctopusPywinautoRunner和现有 UI 定位方法,不引入大型框架,不重写 pywinauto/PowerShell 两套实现。 - 先决策、后副作用:参数校验、动作互斥、确认范围和快照复核必须在 UI 点击、目录创建、配置写入之前完成。
- 默认阻断:歧义、未知、无法回读、无法验证均不得自动降级到更危险动作。
- 点击不等于成功:分别报告首次点击、参数提交、启动已观测、窗口关闭、停止已验证。
- 确认不可串用:首次运行、独立 direct、独立 close、恢复旧运行各自拥有独立授权边界。
- fallback 不改变语义:PowerShell 不支持的增强能力必须显式报错,不得静默采取近似动作。
- 无副作用测试优先:CI 仅运行静态、单元、mock、CLI 契约和只读测试;真实运行/停止/关闭必须单独授权。
2. 现有架构与调用链
2.1 文件与职责
| 文件 | 当前职责 | 主要问题 |
|---|---|---|
skills/octopus-rpa-app-runner/SKILL.md | Agent 使用说明、安全边界、命令示例 | 未描述 v2 direct/close 正式能力和统一结果契约;内部类说明不足 |
scripts/run_pywinauto.cmd | 定位技能本地 .venv,调用 Python runner,透传参数和退出码 | venv 缺失仅自由文本 + exit 2,与 v2 环境错误 exit 7 不一致 |
scripts/OctopusRpaAppRunner.py | 默认实现;CLI、业务决策、pywinauto UI、确认、配置、进程查询均在单文件 | 结果/异常无统一模型;动作不互斥;参数弹窗策略单一;direct/close 无正式入口 |
scripts/OctopusRpaAppRunner.ps1 | UIAutomation fallback,能力大体对应 Python | 参数、退出码、stop 行为与 Python 漂移;无 JSON/统一结果;direct/close 无正式入口 |
scripts/click_run_app_direct.py | 临时绕过:直接点击既有参数弹窗“运行应用” | 无专用确认、无快照复核、无统一输出,非安全正式入口 |
scripts/close_run_window_direct.py | 临时绕过:调用 close_run_window_if_present | 无专用确认;布尔返回混淆“窗口关闭”和“任务停止” |
tests_static.py | 纯函数断言和源码字符串检查 | 覆盖有限、行为 mock 不足、无法验证调用顺序和零副作用 |
config.json(运行时) | 记忆 DataDirectory | 无 schema 版本;非原子写入 |
2.2 Python 当前调用链
run_pywinauto.cmd
-> .venv\Scripts\python.exe OctopusRpaAppRunner.py <args>
-> __main__
-> main(argv)
-> argparse.parse_args
-> 部分维护/只读动作提前返回
-> OctopusPywinautoRunner(...)
-> 若运行请求且存在浮窗
-> run_window_snapshot
-> confirm_restore_existing_run
-> 快照复核
-> close_run_window_if_present
-> stop_run_window
-> win.close
-> read_all_apps
-> open_apps_page -> root -> find_main_window
-> get_flow_grid -> visible_apps -> scroll
-> exact/latest/interactive_picker 选择目标
-> confirm_run
-> run_app_by_name
-> bring_app_into_view
-> invoke 列表 running 按钮
-> handle_run_parameter_dialog
-> find_run_parameter_dialog_root
-> find_editable_input
-> confirm_run_parameter_dialog
-> 填目录 -> 点击“运行应用”
-> 自由文本 Done / int 退出码
关键现状:
find_run_parameter_dialog_root()只返回第一个满足条件的根,无法识别多个候选弹窗。find_editable_input()在多个 Edit 时抛错;没有auto|map|direct|leave策略。close_run_window_if_present()返回一个布尔值,内部将 stop、close、restore 混为一体,且没有后置运行列表验证。- 顶层
except Exception将绝大多数失败压缩为 exit 1。 --yes-run同时覆盖列表首击和现有单输入框弹窗继续,确认计划不够显式。
2.3 PowerShell 当前调用链
powershell -File OctopusRpaAppRunner.ps1 <params>
-> param + StrictMode + UIAutomation 初始化
-> 顶层 try
-> Restart/Stop/ReadOnly 等分支
-> 浮窗检测与 Confirm-RestoreExistingRunWindowForRun
-> Close-OctopusRunWindowIfPresent
-> Stop-OctopusRunWindowIfPresent
-> WindowPattern.Close / WM_CLOSE
-> Restore-OctopusMainWindow
-> Read-AllApps
-> 目标选择 + Confirm-RunSelection
-> Run-AppByName
-> Invoke-RunParameterDialogIfPresent
-> Write-RunLog + exit 0/1/2
PowerShell 已有多数 UI 基元,但存在三项关键漂移:-StopRunWindow 找不到按钮仍 exit 0;无 direct/close 专用授权;无稳定结果对象和 JSON 输出。
2.4 当前安全调用边界
list/current/process snapshot原则上只读;浮窗导致 Studio 页面不可访问时不得恢复、启动、停止或关闭。- 新应用运行前恢复旧浮窗已经使用单独的
--yes-restore-existing-run和快照复核,应保留。 restart-studio-clean只清理 BrowserBridge 及其拥有的 Edge 子树,保留 Bot;本次不得扩大范围。
3. 目标架构(最小增量)
3.1 分层
CLI/参数层
- argparse / PowerShell param
- 主动作互斥、组合校验、弃用别名归一化
- 输出模式选择
|
编排与状态层
- OperationContext / ActionResult / RunnerError
- run/direct/stop/close/list 等动作编排
- 确认范围、状态转换、后置验证
|
现有 UI 适配层(保留 OctopusPywinautoRunner)
- 窗口、控件、列表、弹窗枚举/快照
- invoke/set value/close 等单步原语
|
系统适配层
- config、PowerShell 进程查询、时间、stdout/stderr
3.2 建议的最小文件调整
- 新增
scripts/runner_contract.py:仅放无 pywinauto 依赖的枚举、结果对象、错误/退出码映射、文本/JSON 序列化、参数组合校验所需常量。 - 继续在
OctopusRpaAppRunner.py中保留 CLI 编排和OctopusPywinautoRunner,仅把大方法拆为少量可 mock 的动作方法;不建立复杂包结构。 - PowerShell 保持单文件,通过
New-ActionResult、Complete-Action、Throw-RunnerError等小函数模拟同一契约。 - 新增
tests/下行为测试;保留tests_static.py作为兼容入口,可改为发现并运行新测试或仅保留轻量 smoke。
该结构只新增一个小型共享契约模块,不试图让 PowerShell 导入 Python,也不把所有 UI 方法抽象成完整接口,避免过度重构。
3.3 核心对象
Python 建议定义:
OperationContext
- operation_id: UUID
- action: Action
- output: text|json
- interactive: bool
- confirmation_scopes: set[str]
- started_at / phase timings
- side_effect_started: bool
ActionResult
- contract_version: 2
- operation_id, action
- result: success|partial|cancelled|failed|already_satisfied
- stage
- changed, verified
- error: {code, message} | null
- evidence: dict[str, JSON scalar/list/object]
- warnings: list[str]
RunnerError
- code
- exit_code
- message
- stage
- evidence
约束:动作函数返回 ActionResult;预期业务错误抛 RunnerError,顶层统一转换;仅编程错误走 INTERNAL_ERROR/exit 1。PowerShell 的 PSCustomObject 字段和值域必须一致。
4. CLI 参数与兼容策略
4.1 主动作
所有主动作必须互斥,并在初始化 pywinauto、读取/写入配置、访问 UI 前校验:
| 规范动作 | Python 参数 | PowerShell 参数 | 说明 |
|---|---|---|---|
list_apps | --list-only | -ListOnly | 只读 |
run_exact | --run-exact-name NAME | -RunExactName NAME | 严格全名唯一匹配 |
run_latest | --run-latest-by-prefix KEY | -RunLatestByPrefix KEY | 保留旧名,增加歧义阻断 |
interactive | 无主动作参数 | 无主动作参数 | 默认动作;本次仅单选 |
current_running | --current-running / --check-run-list | -CheckRunList,可加 alias | 同一规范动作,两个 alias 不视为冲突 |
process_snapshot | --process-snapshot | 建议补 -ProcessSnapshot | 只读 |
stop_run_window | --stop-run-window | -StopRunWindow | 只 stop,不 close |
close_run_window | --close-run-window | -CloseRunWindow | 专用确认后 stop -> close -> verify |
direct_parameter_run | --click-run-app-direct | -ClickRunAppDirect | 仅接管已存在参数弹窗 |
restart_clean | --restart-studio-clean | -RestartStudioClean | 保持独立维护动作 |
set_default_only | --set-default-data-directory --data-directory PATH | 对应现有参数 | 组合归一为独立主动作 |
--remember-data-directory 不是主动作,只能附着于 run exact/latest/interactive;set-default-only 不得同时运行应用。
4.2 新增/调整参数
--parameter-dialog-policy auto|map|direct|leave 默认 auto
--parameter KEY=VALUE 可重复,仅 map
--create-missing-directories 仅 auto/map
--yes-direct-run 仅独立 direct
--yes-close-run-window 仅独立 close
--output text|json 默认 text
--parameter-dialog-timeout-seconds N 默认 12,上限建议 60
--verification-timeout-seconds N 默认 15,上限建议 120
PowerShell 使用对应 PascalCase 参数;若本版本不实现 map,必须在解析阶段返回 UNSUPPORTED_IN_FALLBACK/exit 3,不能忽略 -Parameter 或改用 direct。
4.3 参数组合校验
--parameter必须且只能与 run 动作 +policy=map使用。policy=map至少需要一个--parameter;参数 key 不得重复。--create-missing-directories只能与auto|map使用。--yes-direct-run只能与独立--click-run-app-direct使用。--yes-close-run-window只能与独立--close-run-window使用。--yes-restore-existing-run只能附着于 run exact/latest/interactive。--yes-run只能附着于 run exact/latest/interactive;不授权独立 direct/close。- 多个主动作、非法 timeout、非法
KEY=VALUE均ARG_CONFLICT/ARG_REQUIRED,exit 3,UI 调用计数为 0。 - JSON 模式中 stdout 只能包含最终 JSON;交互提示/日志写 stderr。若需要输入确认,仍从 stdin 读取。
4.4 兼容策略
| 旧参数/行为 | v2 行为 |
|---|---|
--yes / -SkipConfirm | 保留一个小版本,归一为 yes_run,stderr 输出弃用警告;权限绝不扩大 |
--skip-run-parameter-dialog / -SkipRunParameterDialog | 归一为 policy=leave;若同时显式给其他 policy 则 ARG_CONFLICT |
--check-run-list | 保留为 --current-running alias |
--stop-run-window | 名称保留;PowerShell 修正为未点击时非成功,属于安全收紧 |
| 默认无参数互动模式 | 保留,但多编号改为 BATCH_RUN_UNSUPPORTED 提示后重输 |
| 临时两个 Python 脚本 | 保留一个迁移版本,只打印弃用提示并 subprocess 转发正式 CLI;不得直接 import 后点击 |
| 旧调用方只认 0/1 | 发布说明要求升级;所有非 0 一律先视为未成功,逐步识别 2~7 |
run_pywinauto.cmd 应保持参数原样透传;venv 缺失时输出同契约的最小文本结果并 exit 7。JSON 参数检测在 cmd 中脆弱,不建议由 wrapper 手工生成 JSON;可以增加一个极小 Python bootstrap,或明确 wrapper 自身失败仍写 stderr + exit 7,而正式 runner 的 JSON 契约从 Python 启动成功后生效。推荐后者以避免批处理过度复杂化。
5. direct 与 close 的独立确认和安全语义
5.1 独立 direct
定义:只接管已经存在的合法 Octopus 参数弹窗;不查应用列表、不点击列表 running、不启动/恢复 Studio、不写参数、不创建目录、不写配置。
流程:
- 枚举所有属于
OctopusRPA.Studio的顶层候选。 - 仅接受具有参数区域标识、唯一可用“运行应用”按钮的弹窗;0 个报
PARAM_DIALOG_NOT_FOUND,多个报PARAM_DIALOG_AMBIGUOUS。 - 建立弹窗快照/指纹,输出输入框总数、可写数、empty/non-empty 数,不输出值。
--yes-direct-run存在则确认direct_parameter_continuescope;否则展示风险并交互确认;非交互 stdin 不可用则CONFIRMATION_REQUIRED/exit 4。- 确认后重新枚举并比较 pid、handle、按钮标识、输入摘要指纹。
- 指纹一致才点击一次。调用返回后即标记副作用已发生,验证超时也不得重试。
- 观测新浮窗/运行列表/进程差异,得到
start_observed或start_unverified。
集成 policy=direct 与独立 direct 不同:它在运行计划开始前已声明,--yes-run 可授权 initial_run,direct_parameter_continue;最终结果必须输出 CONFIRM_SCOPE。运行过程中绝不允许从 auto 动态降级到 direct。
5.2 独立 close
定义:恢复 Studio 的显式动作;计划为“快照确认 -> 尝试 stop -> 若浮窗仍在则 close -> 验证”,但关闭窗口不等于证明任务停止。
流程:
- 若无浮窗且 Studio 可访问,返回
already_satisfied、changed=false、exit 0,不点击。 - 若浮窗和 Studio 都无法识别,返回
RUN_WINDOW_NOT_FOUND或状态未知;不得启动 Studio。 - 获取浮窗快照并展示完整降级风险。独立动作仅接受
--yes-close-run-window或交互肯定;其他 yes 标志无效。 - 确认后复核 title/pid/handle;变化返回
STALE_CONFIRMATION。 - 尝试一次 stop;找不到按钮时记录
STOP_BUTTON_NOT_FOUND证据,但因为 close 专用确认已覆盖降级风险,可以继续 close。 - stop 后轮询;浮窗已消失则不得再操作旧 handle,直接验证 Studio。
- 浮窗仍在才重新查询当前对象并 close;不得杀进程。
- 重新枚举浮窗与 Studio,再在 Studio 可访问时查询运行列表。
- 仅可信
empty可令RUN_STOP_VERIFIED=true;unknown或无法查询为 partial/exit 2;active为 failed 或 partial/exit 2/6,且不得输出“停止成功”。
必须分别输出:STOP_ATTEMPTED、STOP_CLICKED、WINDOW_CLOSE_ATTEMPTED、WINDOW_CLOSED、STUDIO_RESTORED、RUN_STOP_VERIFIED。
5.3 运行新应用时恢复旧浮窗
继续使用 --yes-restore-existing-run,但复用 close 编排的“受控恢复子流程”,确认 scope 为 restore_existing_run,而不是独立 close_run_window。它只授权处理确认快照中的旧浮窗;新应用首击仍需 --yes-run。建议共用内部 restore_run_window(snapshot, authorization, verify=True),外层根据动作产生不同确认文案和结果。
6. 参数弹窗策略
6.1 弹窗与输入描述模型
新增轻量只读描述对象:
ParameterDialogSnapshot
- title, pid, handle
- run_button_automation_id/name/enabled
- inputs: [ParameterInputDescriptor]
- fingerprint
ParameterInputDescriptor
- automation_id
- accessible_name
- associated_label(可解析时)
- control_type, class_name
- writable
- empty(只输出布尔)
- index(1-based,仅摘要/显式索引映射使用)
指纹由规范化后的 pid + handle + button descriptor + sorted input descriptors 计算;不得包含输入原值。Python 可用 SHA-256 截断摘要,PowerShell 使用 SHA256;算法和规范化 JSON/连接格式必须写入测试固定向量。
6.2 auto
- 默认策略。
- 仅当恰有一个可写 Edit 且通过现有高置信规则时,使用
--data-directory、已保存目录或推荐默认目录。 - 目录创建必须有
--create-missing-directories或交互计划明确授权。为兼容旧行为,可在一个小版本中对交互模式展示“将创建目录”并确认;非交互--yes-run不应隐式授权新 v2 目录创建,调用方需加显式标志。 - 写入后回读一致才点击;无法回读则
PARAM_VALUE_UNVERIFIED。 - 零输入或多输入返回
PARAM_INPUT_NOT_FOUND/PARAM_INPUT_AMBIGUOUS,保留弹窗,不改值、不点击。
6.3 map
- 解析全部
KEY=VALUE,先构建完整映射计划,任何一项歧义时一项都不写。 - key 优先级:AutomationId 精确匹配 > 关联 Label 精确匹配 > accessible name 精确匹配。
- 仅显式
index:N才允许按 1-based 控件序号匹配;不得隐式按顺序填充。 - 同层多个候选、同一控件被多个 key 命中、存在未映射的必需性未知输入时均阻断。由于 UIA 无可靠 required 元数据,本版本默认要求调用方明确处理所有可写输入;若允许保留某字段原值,应通过
KEY=或后续明确的 leave-existing 语法,不在本次暗中推断。 - 日志仅输出 key、目标控件摘要及 empty/non-empty,不输出疑似 password/token/secret 字段值;建议所有参数值默认不进入日志。
- 逐项写入并回读;任一失败停止,不点击运行应用。
- 写入完成后重取快照。由于值的 empty 状态可能改变,点击前指纹比较应区分结构指纹与值状态:授权校验使用结构指纹;写入验证另存 value-state evidence,避免正常写值被误判 stale。
PowerShell P1 若排期不足,可以对 map 明确返回 UNSUPPORTED_IN_FALLBACK;P0 direct/close 不得缺失。
6.4 direct
不读写任何输入,不创建目录。必须输出 input count、empty/non-empty count 和 fingerprint。集成模式由运行计划中的 --parameter-dialog-policy direct + --yes-run 授权;独立模式必须 --yes-direct-run。
6.5 leave
完成列表首击后不处理弹窗,也不把流程标记为启动成功。输出:
STAGE=initial_clicked
RUN_STAGE=initial_clicked
START_VERIFIED=false
RESULT=partial
ERROR_CODE=RUN_START_UNVERIFIED
如果已经观测到明确启动证据且没有弹窗,可正常 start_observed;leave 只是不等待/处理参数弹窗,不应阻止对立即出现证据的非副作用观测。
7. 状态机设计
7.1 运行主状态机
INIT
-> VALIDATED
-> EXISTING_RUN_CHECKED
-> RESTORE_CONFIRM_REQUIRED -> RESTORING -> RESTORE_VERIFIED
-> APP_CATALOG_READ
-> APP_SELECTED
-> RUN_CONFIRM_REQUIRED
-> RUN_CONFIRMED
-> INITIAL_RUN_CLICKED
-> START_OBSERVED
-> PARAMETER_WAITING
-> AUTO_MAPPING -> PARAM_VALUES_VERIFIED -> PARAM_SUBMITTED
-> EXPLICIT_MAPPING -> PARAM_VALUES_VERIFIED -> PARAM_SUBMITTED
-> DIRECT_CONFIRM_REQUIRED -> DIRECT_CONFIRMED -> PARAM_SUBMITTED
-> LEFT_FOR_MANUAL -> START_UNVERIFIED
-> CANCELLED
-> START_UNVERIFIED
-> FAILED
允许的最终 stage:start_observed、start_unverified、cancelled、failed。PARAM_SUBMITTED 后必须验证;点击成功不能直接转 success。
7.2 独立 direct 状态机
INIT -> VALIDATED -> PARAM_DIALOG_DETECTED -> SNAPSHOT_CAPTURED
-> DIRECT_CONFIRM_REQUIRED -> DIRECT_CONFIRMED -> SNAPSHOT_REVALIDATED
-> PARAM_SUBMITTED -> START_OBSERVED | START_UNVERIFIED
确认取消:cancelled/exit 0;确认缺失:failed + CONFIRMATION_REQUIRED/exit 4;快照变化:exit 6。
7.3 stop 状态机
INIT -> RUN_WINDOW_DETECTED -> STOP_ATTEMPTED
-> STOP_CLICKED -> STOP_OBSERVED | STOP_UNVERIFIED
-> STOP_NOT_AVAILABLE
STOP_NOT_AVAILABLE 不得转 close。无浮窗可返回 already_satisfied(目标“没有可停止浮窗”已满足)或 RUN_WINDOW_NOT_FOUND;为兼容和幂等,建议前者 exit 0、changed=false,但 STOP_CLICKED=false。
7.4 close/restore 状态机
INIT
-> ALREADY_RESTORED
-> RUN_WINDOW_DETECTED -> CLOSE_CONFIRM_REQUIRED -> CLOSE_CONFIRMED
-> SNAPSHOT_REVALIDATED -> STOP_ATTEMPTED
-> STOP_CLICKED -> WAIT_FOR_DISAPPEAR
-> STOP_NOT_AVAILABLE
-> WINDOW_ALREADY_GONE -> VERIFY_STUDIO
-> WINDOW_CLOSE_ATTEMPTED -> WINDOW_CLOSED -> VERIFY_STUDIO
-> RESTORED_AND_STOP_VERIFIED
-> RESTORED_BUT_STOP_UNVERIFIED
-> STILL_ACTIVE
-> RESTORE_FAILED
关键不变量:确认前零点击;stop 先于 close;旧 handle 消失后不再 close;close 永不触发进程清理。
8. 统一结果、输出与退出码
8.1 结果不变量
- 每次命令从参数校验开始即生成一个
OPERATION_ID;参数错误也有 operation id。 changed=true仅表示已发生 UI/配置副作用,不代表结果成功。verified=true必须由动作对应后置条件支持。result=success要求动作目标已验证;无验证证据用partial。already_satisfied是成功类别,changed=false、verified=true。cancelledexit 0,但不得被序列化成 success。- 输出函数只能调用一次,防止 JSON 混入多段结果。
8.2 文本模式
日志写 stderr;stdout 最后只输出稳定结果块。为兼容人工阅读,可以在结果块前后使用固定 ASCII 标记,但机器解析只依赖 KEY=VALUE:
CONTRACT_VERSION=2
OPERATION_ID=<uuid>
ACTION=close_run_window
RESULT=partial
STAGE=studio_restored
CHANGED=true
VERIFIED=false
ERROR_CODE=RUN_STOP_UNVERIFIED
MESSAGE=Studio restored, but run stop could not be verified.
STOP_ATTEMPTED=true
STOP_CLICKED=false
WINDOW_CLOSE_ATTEMPTED=true
WINDOW_CLOSED=true
STUDIO_RESTORED=true
RUN_STOP_VERIFIED=false
RUN_LIST_STATE=unknown
值中的换行、回车必须替换为空格;复杂 evidence 在文本模式使用稳定的扁平字段。应用列表可用重复 APP_NAME=<name> 行,或 JSON 编码的 APPS_JSON=[...];建议保留现有逐行人类列表到 stderr,并在结果块使用 APPS_JSON,避免裸应用名破坏机器契约。
8.3 JSON 模式
stdout 只输出一个 UTF-8 JSON 对象,ensure_ascii=false;日志、弃用警告和交互提示全部写 stderr:
{
"contract_version": 2,
"operation_id": "...",
"action": "direct_parameter_run",
"result": "partial",
"stage": "start_unverified",
"changed": true,
"verified": false,
"error": {"code": "RUN_START_UNVERIFIED", "message": "Run application was clicked, but no start evidence was observed."},
"evidence": {
"param_dialog_found": true,
"param_input_count": 3,
"direct_run_clicked": true,
"start_evidence_level": "none"
},
"warnings": []
}
PowerShell 5.1 使用 ConvertTo-Json -Depth 8 -Compress。不得让 Write-Host 污染 JSON stdout;统一日志函数在 JSON 模式写 [Console]::Error。
8.4 退出码
| 码 | 类别 | 典型情况 |
|---|---|---|
| 0 | 成功、已满足、用户取消 | verified success、already restored、cancelled |
| 2 | 部分完成/状态未知 | run list unknown、启动未验证、关闭后停止未验证 |
| 3 | 参数/用法错误 | 动作冲突、非法 policy、fallback 不支持的增强参数 |
| 4 | 未授权 | 非交互缺少对应 yes 标志 |
| 5 | 目标不存在或歧义 | app/dialog/input 零匹配或多匹配 |
| 6 | UI 动作失败/竞态 | stale、点击失败、回读失败、close 失败 |
| 7 | 环境/依赖失败 | pywinauto/venv 缺失、桌面不可用 |
| 1 | 未分类内部错误 | 编程错误、未映射异常 |
UNSUPPORTED_IN_FALLBACK 归类 exit 3,因为调用方式对该实现无效;不得回落到 exit 1。
9. 错误码目录
9.1 参数与授权
ARG_CONFLICT:主动作或参数组合冲突,exit 3。ARG_REQUIRED:缺少 policy 配套参数,exit 3。UNSUPPORTED_IN_FALLBACK:PowerShell 未实现的增强能力,exit 3。CONFIRMATION_REQUIRED:缺少当前动作专用授权且无法交互,exit 4。USER_CANCELLED:用户取消,exit 0、result=cancelled。STALE_CONFIRMATION:确认后目标指纹变化,exit 6。
9.2 应用与运行
APP_NOT_FOUND、APP_MATCH_AMBIGUOUS:exit 5。BATCH_RUN_UNSUPPORTED:CLI 非交互输入视为 exit 3;互动输入则提示重选且不结束操作。RUN_APP_BUTTON_NOT_FOUND、RUN_INITIAL_CLICK_FAILED:exit 6。RUN_START_UNVERIFIED:exit 2。RUN_LIST_UNKNOWN:exit 2。
9.3 参数弹窗
PARAM_DIALOG_NOT_FOUND、PARAM_DIALOG_AMBIGUOUS:exit 5。PARAM_INPUT_NOT_FOUND、PARAM_INPUT_AMBIGUOUS:exit 5。PARAM_MAPPING_FAILED:计划阶段歧义为 exit 5;控件变化/执行失败为 exit 6。PARAM_VALUE_UNVERIFIED:exit 6。RUN_APP_BUTTON_NOT_FOUND、DIRECT_RUN_CLICK_FAILED:exit 6。
9.4 浮窗与环境
RUN_WINDOW_NOT_FOUND:目标必须存在的动作中 exit 5;幂等 close/stop 可转 already_satisfied。STOP_BUTTON_NOT_FOUND、STOP_CLICK_FAILED:独立 stop exit 6;close 中作为 evidence,是否最终失败由后置验证决定。RUN_WINDOW_CLOSE_FAILED、STUDIO_RESTORE_FAILED:exit 6。RUN_STOP_UNVERIFIED:exit 2。CONFIG_INVALID:读时警告并使用安全默认,但不得覆盖损坏文件;显式配置动作可 exit 6。CONFIG_WRITE_FAILED:exit 6。DEPENDENCY_MISSING、UI_SESSION_UNAVAILABLE:exit 7。INTERNAL_ERROR:exit 1。
错误映射表必须集中在 Python runner_contract.py 和 PowerShell 顶部常量区;测试以表驱动校验,不允许各动作自行选择退出码。
10. 启动与停止的验证证据
10.1 启动证据优先级
run_list_target:运行列表显示目标应用,能验证具体目标。new_run_window:动作后出现新的合法运行浮窗,只证明启动已观测,不能证明目标名称。new_octopus_process_tree:BrowserBridge/受控 Edge 相比动作前新增,只作为弱证据。none:点击已发生但未观测证据,start_unverified/exit 2。
独立 direct 未必知道目标应用名,因此最高只能声明“启动已观测”,除非运行列表提供唯一名称;不能将进程变化描述成具体应用成功。
10.2 停止证据
- 可信运行列表
empty:RUN_STOP_VERIFIED=true。 - 运行列表
active:明确不安全,RUN_STOP_VERIFIED=false。 unknown、页面不可读、超时:partial,不能推断为空。- 仅浮窗消失/Studio 恢复:证明 UI 恢复,不证明任务停止。
所有验证使用动作前后快照差异,避免把既存窗口/进程当作本次证据。
11. PowerShell fallback 方案
11.1 同版本必须实现(P0)
-ClickRunAppDirect [-YesDirectRun]。-CloseRunWindow [-YesCloseRunWindow]。- 主动作互斥及确认不可串用。
- 弹窗/浮窗快照与点击前复核。
- stop 不 close;close 分阶段证据和后置验证。
- 统一文本/JSON结果、错误码和退出类别。
- 只读模式保持零副作用。
11.2 可显式不支持(P1)
- 若无法按期安全实现通用
map,-ParameterDialogPolicy map直接返回UNSUPPORTED_IN_FALLBACK,exit 3。 - 不支持的
-Parameter、控件关联 Label 推导同样不得忽略。 auto/direct/leave应实现;其中 direct 是 P0,auto 保留现有能力并补回读,leave 对应弃用别名。
11.3 禁止自动 fallback 的情况
Python 已发生任意 UI 点击、参数写入、目录/配置写入后,不得自动调用 PowerShell 重做。只有在:
- Python 依赖在动作前不可用;
- 参数已验证为 fallback 支持;
- 能证明
side_effect_started=false;
才可以由上层 Agent 明确选择 PowerShell。runner 本身不自动链式回退,避免重复运行。
11.4 PowerShell 实现要点
- 将顶层
try/exit改为调用动作函数后统一Complete-Action;catch 中识别带错误码的异常数据。 Write-RunLog在 JSON 模式写 stderr。Find-RunParameterDialogRoot改为枚举并返回候选集合/描述,调用者决定零/一/多。- 使用 UIA RuntimeId(可得时)、handle、pid、AutomationId 和输入摘要构建指纹;按钮点击前重新查找。
Close-OctopusRunWindowIfPresent拆成单步原语,不再返回含糊布尔值。-StopRunWindow找不到按钮必须返回STOP_BUTTON_NOT_FOUND,不再无条件 exit 0。
12. 配置与日志
12.1 配置 schema
建议 schema:
{
"schema_version": 2,
"DataDirectory": "C:\\Users\\...\\Documents\\OctopusRPA\\BossResumes",
"UpdatedAt": "2026-08-06T12:00:00"
}
兼容读取无 schema_version 的旧文件为 v1。写入步骤:同目录临时文件 -> flush/close -> os.replace;PowerShell 使用临时文件后 [System.IO.File]::Replace,目标不存在时 Move。失败保留旧文件并返回 CONFIG_WRITE_FAILED。损坏文件不得被隐式覆盖。
12.2 敏感信息
--parameter值默认不记录、不持久化、不进入 fingerprint。- 字段名匹配
password|passwd|token|secret|cookie|credential|密码|令牌时仅记录redacted=true。 - 目录可以记录规范化路径,但 JSON/日志若可能外发,建议仅在明确需要时展示;本需求至少禁止记录通用参数原值。
- operation id、动作、快照摘要、确认 scope、阶段耗时可记录。
13. 模块/函数级变更清单
13.1 新增 scripts/runner_contract.py
| 符号 | 责任 |
|---|---|
Action, ResultKind, RunStage | 稳定值域枚举 |
EXIT_CODES, ERROR_EXIT_MAP | 错误码到退出码唯一映射 |
OperationContext | operation id、action、输出模式、确认 scope、side-effect 标志 |
ActionResult | 统一结果对象及 evidence/warnings |
RunnerError | 可预期错误对象 |
validate_cli_plan(normalized_args) | 无 UI 参数组合校验 |
render_text(result) | 单行 KEY=VALUE 结果块 |
render_json(result) | 单 JSON 对象 |
sanitize_message(value) | 去换行并防止破坏文本协议 |
该模块不得 import pywinauto,便于跨平台单测。
13.2 修改 OctopusRpaAppRunner.py
CLI/顶层:
build_parser():增加主动作互斥组、新参数、alias/deprecation;注意 argparse 默认无动作映射为 interactive。- 新增
normalize_args(args):将旧参数归一化,输出弃用警告,不访问 UI。 - 重写
main(argv):创建 context -> 校验 -> 分发 action -> 单次输出 -> 返回映射退出码。 __main__:分别处理RunnerError、KeyboardInterrupt(保留 130,可在结果中用INTERRUPTED)、未知异常。
弹窗模型与原语:
- 新增
ParameterInputDescriptor、ParameterDialogSnapshot。 - 将
find_run_parameter_dialog_root()演进为find_run_parameter_dialog_candidates();旧方法可保留一版兼容包装,但候选多时不得取首个。 - 新增
describe_parameter_input()、snapshot_parameter_dialog()、parameter_dialog_fingerprint()。 - 调整
find_editable_input():只负责候选/评分,不直接决定多输入异常文案。 - 新增
build_parameter_mapping()、write_and_verify_parameter()。 - 将
handle_run_parameter_dialog()改为handle_run_parameter_dialog(policy, parameters, context),返回阶段/evidence,不返回Optional[bool]。 - 新增
click_parameter_run_button(snapshot, context):点击前重查并复核结构指纹,仅点击一次。 - 新增
execute_direct_parameter_run(context):独立 direct 完整编排。
运行与验证:
run_app_by_name()改为返回阶段证据;首击后不直接打印 Done。- 新增
capture_start_evidence_baseline()、observe_run_start();复用run_window_snapshot()、run-list 分类和进程快照。 resolve_number_selection()可以保留纯函数,但 interactive 调用方必须拒绝多项;建议新增validate_single_selection()。latest_app_match()改为先返回候选分析对象,最高版本平局或跨业务候选时阻断;保留简单纯函数供兼容测试时要明确语义。
浮窗:
stop_run_window()从 bool 改为结构化单步结果,区分 not-found/button-not-found/clicked/click-failed。- 将
close_run_window_if_present()拆为close_current_run_window(snapshot)单步原语和execute_close_run_window(context, authorization_scope)编排;可保留兼容 wrapper 一版,但正式 CLI 不使用含糊 bool。 - 新增
verify_studio_restored_and_run_stopped()。 confirm_restore_existing_run()保留独立文案,但授权后调用共用恢复子流程。- 新增
confirm_direct_parameter_run()、confirm_close_run_window(),明确各自 scope。
配置/日志:
write_config()改为 schema v2 原子写。read_config()区分 absent/legacy/invalid;invalid 不自动覆盖。log()改为注入 output mode,统一写 stderr,避免 JSON 污染。
13.3 修改 OctopusRpaAppRunner.ps1
- param 区增加
ClickRunAppDirect、YesDirectRun、CloseRunWindow、YesCloseRunWindow、ParameterDialogPolicy、Parameter、CreateMissingDirectories、Output和 timeout。 - 新增
Resolve-CliPlan,在Add-Type和 UI 初始化前尽可能校验;为真正保证参数冲突零 UI,可将Add-Type延后到 action 分发后。 - 新增
New-OperationContext、New-ActionResult、Complete-Action、New-RunnerException。 Find-RunParameterDialogRoot改成候选枚举;新增Get-ParameterDialogSnapshot、Test-ParameterDialogSnapshotSame。- 新增
Invoke-DirectParameterRunAction、Invoke-CloseRunWindowAction。 - 拆分
Stop-OctopusRunWindowIfPresent、Close-OctopusRunWindowIfPresent的单步状态;不再用 bool 代表整体成功。 - 修正顶层
-StopRunWindow退出语义。 - 新增
Write-ResultText、Write-ResultJson,JSON 模式日志写 stderr。 - 配置写入增加 schema 和原子替换。
13.4 修改 wrapper 和迁移脚本
run_pywinauto.cmd:保持透传;venv 缺失 exit 7,stderr 给出安装提示。click_run_app_direct.py:仅输出弃用提示并转发OctopusRpaAppRunner.py --click-run-app-direct及用户提供的参数;不自动附加--yes-direct-run。close_run_window_direct.py:仅转发--close-run-window;不自动附加确认。
13.5 修改文档
SKILL.md 必须更新:
- 主类名是
OctopusPywinautoRunner,内部方法为非稳定 API。 - 不再推荐写临时脚本绕过确认。
- direct、close、四种参数策略、确认矩阵、文本/JSON结果和退出码。
- stop 与 close 的差异;窗口关闭不代表任务停止。
- Python 默认、PowerShell fallback 支持矩阵。
- 生产现场测试门禁和“业务结果未知”表述。
14. 测试设计
14.1 测试目录建议
skills/octopus-rpa-app-runner/
tests/
test_contract.py
test_cli_validation.py
test_parameter_policy.py
test_direct_action.py
test_close_action.py
test_run_state_machine.py
test_config.py
test_selection.py
test_powershell_contract.py
fakes.py
tests_static.py
优先使用标准库 unittest + unittest.mock,避免新增 pytest 依赖;如果仓库已有统一 pytest 再遵循现状。fakes.py 提供可记录调用序列的 Fake UI adapter,而不是模拟 pywinauto 每个底层对象。
14.2 Fake/Mock 边界
不要求彻底抽象现有 runner。测试中可构造不调用 __init__ 的 runner,并 mock 以下边界:
top_windows/find_*_candidates;snapshot_*与*_matches;invoke/set_edit_text/close;check_run_list或新的纯读取版本;get_process_snapshot、clock/sleep、stdin;- config 文件系统。
Fake 必须记录 read, confirm, write_value, click_initial, click_direct, click_stop, close_window, create_dir, write_config,用于断言顺序和确认前零副作用。
14.3 单元与契约测试
结果契约:
- 每个错误码映射唯一退出码。
- text 必备字段、单行转义、布尔小写/规范值。
- JSON stdout 单对象、中文值有效、日志仅 stderr。
- operation id 在所有事件/最终结果一致。
- cancelled exit 0 但 result 不是 success。
CLI:
- 每对主动作冲突均 exit 3,UI/config 调用为 0。
- yes 标志不可串用。
- legacy alias 归一化与警告。
- policy/parameter/create-directory 组合表驱动测试。
- 参数错误发生在
ensure_pywinauto()之前。
参数策略:
- auto:0/1/多个 Edit、只读 Edit、低置信、回读失败。
- map:AutomationId/Label/name 优先级、同层歧义、重复 key、重复控件、显式 index、整体校验失败零写入。
- direct:不写值、不建目录;输入摘要不泄露值。
- leave:不等待/不点击参数按钮,start_unverified。
- 结构指纹固定向量、确认后 handle/button/input 变化。
选择与运行:
- exact 零/一/多匹配。
- latest 版本比较、平局、跨业务候选歧义。
- interactive
1,3被拒且零点击。 - 初始 click 后验证超时不重试。
- 证据优先级和动作前后差异。
close/stop:
- 未确认时 stop/close 都为 0 次。
- 有 stop,stop 后浮窗消失,不调用 close。
- 无 stop,独立 stop 不 close;独立 close 已确认后继续 close。
- close 前快照变化,零点击。
- close 成功 + run list empty/unknown/active 三种结果。
- 无浮窗 + Studio 可用返回 already_satisfied。
- 无浮窗 + Studio 不可识别不得启动 Studio。
- close 永不调用进程清理。
配置:
- 旧
DataDirectory兼容读取。 - schema v2 原子更新。
- 临时写/replace 失败保留旧文件。
- 损坏配置不被隐式覆盖。
- parameter 值不落盘、不进入日志。
14.4 PowerShell 测试
- 使用
powershell -NoProfile -File ...做 help/参数互斥/unsupported/JSON 契约测试;这些用例在 UI 初始化前结束。 - 将可纯化函数置于脚本定义区,通过测试 harness dot-source 时需防止执行 main;建议增加
Invoke-OctopusRunnerMain并在非 dot-source 时调用,属于可控的小调整。 - P0 UI 动作使用函数 mock/harness 验证调用序列;若 PowerShell 5.1 无 Pester,不强制引入网络依赖,可使用独立
.ps1断言脚本。 - Python 与 PowerShell 针对同一场景 fixture 比较
RESULT/STAGE/ERROR_CODE/exit,不要求 MESSAGE 完全相同。
14.5 静态与只读集成
发布前命令建议:
python -m py_compile scripts/OctopusRpaAppRunner.py scripts/runner_contract.py
python -m unittest discover -s tests -p "test_*.py"
python tests_static.py
scripts\run_pywinauto.cmd --help
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\OctopusRpaAppRunner.ps1 -Help
在有 Octopus 的受控桌面环境再执行 --list-only、--current-running、--process-snapshot。只读测试必须审计没有 start/stop/close/create/write/cleanup 调用。
14.6 现场验收
仅由业务负责人指定白名单应用并逐动作确认:
- 多输入框 direct:先无确认验证零点击,再专用确认运行,记录 clicked/start evidence,不能宣称业务完成。
- 无 stop 按钮 close:确认前零动作;确认后记录 stop=false、window closed、Studio restored、run-list verification。
- PowerShell fallback 在 Python P0 通过后抽测;不重复对同一未知状态动作。
15. 并行实现工作包
以下拆分以文件所有权降低冲突。所有 Agent 先读取本方案和 v2 需求,不得自行改变契约值域。
WP-0:契约与测试骨架(先行,短周期)
- 所有者文件:新增
scripts/runner_contract.py、新增tests/test_contract.py、tests/fakes.py。 - 内容:结果对象、错误/退出码、序列化、operation id、基础 fake 协议。
- 交付门槛:无 pywinauto 环境可运行;text/JSON golden tests 通过。
- 冲突面:仅新增文件,最低冲突。
- 依赖:无。
WP-1:Python CLI、direct 与参数策略
- 所有者文件:
scripts/OctopusRpaAppRunner.py;新增tests/test_cli_validation.py、tests/test_parameter_policy.py、tests/test_direct_action.py。 - 内容:CLI 归一/互斥、弹窗 snapshot/fingerprint、auto/map/direct/leave、独立 direct、运行阶段和启动验证、单选约束。
- 不做:不修改 PowerShell、SKILL、临时脚本。
- 依赖:以 WP-0 接口为基线;WP-0 接口冻结后并行。
- 主要风险:同一 Python 文件改动较大,因此 Python close 也建议由本包完成,见下一项合并说明。
WP-2:Python close/stop 与配置
为避免两个 Agent 同时编辑 OctopusRpaAppRunner.py,不建议与 WP-1 真正并行提交同一文件。可采用二选一:
- 推荐:WP-1 与 WP-2 由同一 Python Agent 顺序完成,作为一个“Python 核心包”;其他包并行。
- 若必须并行:WP-2 仅新增
scripts/python_close_actions.py和测试,提供纯编排函数,WP-1 最后做很薄的接线。但这会增加模块边界,收益有限。
WP-2 内容:结构化 stop、close/restore 状态机、后置验证、原子配置、tests/test_close_action.py、tests/test_config.py。推荐不为并行而强拆,避免过度重构。
WP-3:PowerShell fallback
- 所有者文件:
scripts/OctopusRpaAppRunner.ps1;新增tests/test_powershell_contract.py或tests/powershell_contract_tests.ps1。 - 内容:P0 direct/close、安全确认、互斥、结果契约、stop 退出修复、JSON、fallback unsupported、原子配置。
- 不做:不修改 Python 文件。
- 依赖:WP-0 冻结的书面契约,不依赖 Python UI 实现完成。
- 冲突面:独占 ps1,低冲突。
WP-4:文档、wrapper 与迁移脚本
- 所有者文件:
SKILL.md、scripts/run_pywinauto.cmd、scripts/click_run_app_direct.py、scripts/close_run_window_direct.py。 - 内容:能力矩阵、确认说明、命令示例、退出码、弃用转发、wrapper exit 7。
- 依赖:CLI 名称和契约冻结即可,可与 WP-1/WP-3 并行。
- 冲突面:不碰核心 runner,低冲突。
WP-5:独立验收与回归测试
- 所有者文件:新增
tests/test_run_state_machine.py、tests/test_selection.py,最后协调更新tests_static.py。 - 内容:从需求 AC-001~022 建立黑盒/表驱动用例;Python/PowerShell 对等 fixture;检查只读零副作用和进程清理边界。
- 依赖:WP-0 fake 与契约;可先写预期失败测试,核心完成后接线。
- 冲突面:仅测试文件;
tests_static.py在集成阶段由单一 Agent 修改。
推荐并行拓扑
WP-0(契约冻结)
|-- WP-1+WP-2(同一 Agent:Python 核心)
|-- WP-3(PowerShell)
|-- WP-4(文档/迁移)
`-- WP-5(独立测试,先写 fixture)
实际可同时投入 4 个 Agent:Python 核心、PowerShell、文档迁移、独立测试。不要为了增加并行度让两个 Agent 同时编辑 Python 单体文件。
16. 集成顺序与门禁
- 合入 WP-0:冻结 contract version、值域、错误码、退出码、JSON schema;未冻结前其他包只可本地开发。
- 合入 WP-1+WP-2 Python 核心:先跑 Python 单元/mock,确认参数错误不初始化 UI、direct/close 不重试。
- 合入 WP-3 PowerShell:运行跨实现契约 fixture;P0 不一致不得进入文档集成。
- 合入 WP-4 文档与迁移:以实际
--help和测试结果校正文档,确保迁移脚本不自动添加 yes 标志。 - 合入 WP-5 黑盒回归:替换脆弱源码字符串断言,保留必要静态 guard;跑全量无副作用测试。
- 只读桌面 smoke:Python 必测,PowerShell 抽测;审计无 UI 副作用。
- 受控现场验收:direct 与 close 分别单独授权,不在状态未知后自动 fallback 重做。
- 发布检查:版本说明列出 exit 码扩展、stop 行为收紧、互动单选、legacy alias 弃用期。
每一步失败只回退当前工作包,不允许以禁用确认、放宽 unknown 或恢复临时直点脚本来“修测试”。
17. 回滚方案
17.1 发布前准备
- 发布构建保留上一稳定版本完整技能目录或制品,不依赖
git reset。 - 记录 config schema;v2 只增加字段,旧版应忽略未知
schema_version,DataDirectory保持原键名,因此配置可向后读取。 - 现场动作前记录只读窗口/运行列表/进程快照;不得将进程快照用于自动杀进程。
17.2 代码回滚
若 v2 CLI/输出出现阻断性回归:
- 停止发起新的副作用命令。
- 仅在确认没有“点击结果未知”的在途操作时,将技能目录切回上一稳定制品。
- 保留 v2
config.json;上一版读取DataDirectory仍兼容。若上一版解析 schema 字段有问题,使用 v2 自动写入前保留的配置备份恢复,而不是手工截断正在使用的文件。 - 临时脚本不得恢复为无确认实现;回滚版本若缺 direct/close,则这些能力应标记不可用,并要求人工操作。
- 对已发生 click 但未验证的操作,先人工/只读确认现状,不自动用旧版本重复执行。
17.3 功能降级开关
不建议新增长期 feature flag。必要时可在发布包层面:
- 暂时隐藏 direct/close 的 Agent 文档入口,但 CLI 仍安全阻断;
- PowerShell
map保持UNSUPPORTED_IN_FALLBACK; - 禁止现场副作用验收,仅保留 mock/只读能力。
不得通过把 direct 改回无确认、把 close 改回布尔成功、把 unknown 当 empty 来降级。
17.4 数据与运行态回滚
- 配置原子写失败自然保留旧文件;不得覆盖损坏配置。
- UI 动作不可事务回滚。首次运行、direct、stop、close 一旦发出,只能进入后置观测,不能自动做“反向点击”。
- close 后 Studio 恢复但任务状态未知时,保持 partial,交由用户决定下一步;不得自动 restart clean。
restart-studio-clean与本次 direct/close 完全隔离,回滚流程不会自动调用它。
18. 完成定义
版本完成必须同时满足:
- 原 PRD 两个 P0 场景已由正式 CLI 覆盖,临时脚本不再绕过确认。
- v2 P0/P1 自动化验收全部通过;若 PowerShell
map未实现,按约定明确 unsupported 且文档一致。 - Python/PowerShell 的 P0 确认边界、错误码和退出类别一致。
- JSON stdout 无日志污染,文本结果块字段稳定。
- direct/initial click 在结果未知时均不自动重试。
- close 的六个阶段字段完整,只有可信 empty 才声明停止已验证。
- list/current/process snapshot 保持只读;close 不清理进程;restart clean 不扩大 Edge/Bot 边界。
- SKILL.md、help、测试和迁移说明与实现一致。
- 现场验收明确区分“点击成功”“启动已观测”“业务结果未知”。