octopus-rpa-app-runner 技能升级技术方案 v2

原始文件:wecom_e0c326ea_octopus-rpa-app-runner_技能升级技术方案_v2.md

octopus-rpa-app-runner 技能升级技术方案 v2

基于 octopus-rpa-app-runner_技能升级PRD_20260806.mdoctopus-rpa-app-runner_技能升级需求_v2.md 与当前技能代码形成。本文是增量实施方案,不包含代码修改。

1. 文档目标与实施原则

1.1 目标

本方案用于指导多个开发 Agent 并行完成以下升级:

  1. 将参数弹窗“不填参数直接运行”收敛为正式、安全、可审计的 CLI 能力。
  2. 将“先尝试停止,再关闭运行浮窗并恢复 Studio”收敛为正式 CLI 能力,同时不把“窗口关闭”误报为“任务停止”。
  3. 为所有主动作建立统一结果对象、文本/JSON 输出、错误码和退出码。
  4. 在不重写现有 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.mdAgent 使用说明、安全边界、命令示例未描述 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.ps1UIAutomation 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-ActionResultComplete-ActionThrow-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=VALUEARG_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、不写参数、不创建目录、不写配置。

流程:

  1. 枚举所有属于 OctopusRPA.Studio 的顶层候选。
  2. 仅接受具有参数区域标识、唯一可用“运行应用”按钮的弹窗;0 个报 PARAM_DIALOG_NOT_FOUND,多个报 PARAM_DIALOG_AMBIGUOUS
  3. 建立弹窗快照/指纹,输出输入框总数、可写数、empty/non-empty 数,不输出值。
  4. --yes-direct-run 存在则确认 direct_parameter_continue scope;否则展示风险并交互确认;非交互 stdin 不可用则 CONFIRMATION_REQUIRED/exit 4。
  5. 确认后重新枚举并比较 pid、handle、按钮标识、输入摘要指纹。
  6. 指纹一致才点击一次。调用返回后即标记副作用已发生,验证超时也不得重试。
  7. 观测新浮窗/运行列表/进程差异,得到 start_observedstart_unverified

集成 policy=direct 与独立 direct 不同:它在运行计划开始前已声明,--yes-run 可授权 initial_run,direct_parameter_continue;最终结果必须输出 CONFIRM_SCOPE。运行过程中绝不允许从 auto 动态降级到 direct。

5.2 独立 close

定义:恢复 Studio 的显式动作;计划为“快照确认 -> 尝试 stop -> 若浮窗仍在则 close -> 验证”,但关闭窗口不等于证明任务停止

流程:

  1. 若无浮窗且 Studio 可访问,返回 already_satisfiedchanged=false、exit 0,不点击。
  2. 若浮窗和 Studio 都无法识别,返回 RUN_WINDOW_NOT_FOUND 或状态未知;不得启动 Studio。
  3. 获取浮窗快照并展示完整降级风险。独立动作仅接受 --yes-close-run-window 或交互肯定;其他 yes 标志无效。
  4. 确认后复核 title/pid/handle;变化返回 STALE_CONFIRMATION
  5. 尝试一次 stop;找不到按钮时记录 STOP_BUTTON_NOT_FOUND 证据,但因为 close 专用确认已覆盖降级风险,可以继续 close。
  6. stop 后轮询;浮窗已消失则不得再操作旧 handle,直接验证 Studio。
  7. 浮窗仍在才重新查询当前对象并 close;不得杀进程。
  8. 重新枚举浮窗与 Studio,再在 Studio 可访问时查询运行列表。
  9. 仅可信 empty 可令 RUN_STOP_VERIFIED=trueunknown 或无法查询为 partial/exit 2;active 为 failed 或 partial/exit 2/6,且不得输出“停止成功”。

必须分别输出:STOP_ATTEMPTEDSTOP_CLICKEDWINDOW_CLOSE_ATTEMPTEDWINDOW_CLOSEDSTUDIO_RESTOREDRUN_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

  1. 解析全部 KEY=VALUE,先构建完整映射计划,任何一项歧义时一项都不写。
  2. key 优先级:AutomationId 精确匹配 > 关联 Label 精确匹配 > accessible name 精确匹配。
  3. 仅显式 index:N 才允许按 1-based 控件序号匹配;不得隐式按顺序填充。
  4. 同层多个候选、同一控件被多个 key 命中、存在未映射的必需性未知输入时均阻断。由于 UIA 无可靠 required 元数据,本版本默认要求调用方明确处理所有可写输入;若允许保留某字段原值,应通过 KEY= 或后续明确的 leave-existing 语法,不在本次暗中推断。
  5. 日志仅输出 key、目标控件摘要及 empty/non-empty,不输出疑似 password/token/secret 字段值;建议所有参数值默认不进入日志。
  6. 逐项写入并回读;任一失败停止,不点击运行应用。
  7. 写入完成后重取快照。由于值的 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_observedleave 只是不等待/处理参数弹窗,不应阻止对立即出现证据的非副作用观测。

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_observedstart_unverifiedcancelledfailedPARAM_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=falseverified=true
  • cancelled exit 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 零匹配或多匹配
6UI 动作失败/竞态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_FOUNDAPP_MATCH_AMBIGUOUS:exit 5。
  • BATCH_RUN_UNSUPPORTED:CLI 非交互输入视为 exit 3;互动输入则提示重选且不结束操作。
  • RUN_APP_BUTTON_NOT_FOUNDRUN_INITIAL_CLICK_FAILED:exit 6。
  • RUN_START_UNVERIFIED:exit 2。
  • RUN_LIST_UNKNOWN:exit 2。

9.3 参数弹窗

  • PARAM_DIALOG_NOT_FOUNDPARAM_DIALOG_AMBIGUOUS:exit 5。
  • PARAM_INPUT_NOT_FOUNDPARAM_INPUT_AMBIGUOUS:exit 5。
  • PARAM_MAPPING_FAILED:计划阶段歧义为 exit 5;控件变化/执行失败为 exit 6。
  • PARAM_VALUE_UNVERIFIED:exit 6。
  • RUN_APP_BUTTON_NOT_FOUNDDIRECT_RUN_CLICK_FAILED:exit 6。

9.4 浮窗与环境

  • RUN_WINDOW_NOT_FOUND:目标必须存在的动作中 exit 5;幂等 close/stop 可转 already_satisfied。
  • STOP_BUTTON_NOT_FOUNDSTOP_CLICK_FAILED:独立 stop exit 6;close 中作为 evidence,是否最终失败由后置验证决定。
  • RUN_WINDOW_CLOSE_FAILEDSTUDIO_RESTORE_FAILED:exit 6。
  • RUN_STOP_UNVERIFIED:exit 2。
  • CONFIG_INVALID:读时警告并使用安全默认,但不得覆盖损坏文件;显式配置动作可 exit 6。
  • CONFIG_WRITE_FAILED:exit 6。
  • DEPENDENCY_MISSINGUI_SESSION_UNAVAILABLE:exit 7。
  • INTERNAL_ERROR:exit 1。

错误映射表必须集中在 Python runner_contract.py 和 PowerShell 顶部常量区;测试以表驱动校验,不允许各动作自行选择退出码。

10. 启动与停止的验证证据

10.1 启动证据优先级

  1. run_list_target:运行列表显示目标应用,能验证具体目标。
  2. new_run_window:动作后出现新的合法运行浮窗,只证明启动已观测,不能证明目标名称。
  3. new_octopus_process_tree:BrowserBridge/受控 Edge 相比动作前新增,只作为弱证据。
  4. none:点击已发生但未观测证据,start_unverified/exit 2。

独立 direct 未必知道目标应用名,因此最高只能声明“启动已观测”,除非运行列表提供唯一名称;不能将进程变化描述成具体应用成功。

10.2 停止证据

  • 可信运行列表 emptyRUN_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错误码到退出码唯一映射
OperationContextoperation 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)、未知异常。

弹窗模型与原语:

  • 新增 ParameterInputDescriptorParameterDialogSnapshot
  • 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 区增加 ClickRunAppDirectYesDirectRunCloseRunWindowYesCloseRunWindowParameterDialogPolicyParameterCreateMissingDirectoriesOutput 和 timeout。
  • 新增 Resolve-CliPlan,在 Add-Type 和 UI 初始化前尽可能校验;为真正保证参数冲突零 UI,可将 Add-Type 延后到 action 分发后。
  • 新增 New-OperationContextNew-ActionResultComplete-ActionNew-RunnerException
  • Find-RunParameterDialogRoot 改成候选枚举;新增 Get-ParameterDialogSnapshotTest-ParameterDialogSnapshotSame
  • 新增 Invoke-DirectParameterRunActionInvoke-CloseRunWindowAction
  • 拆分 Stop-OctopusRunWindowIfPresentClose-OctopusRunWindowIfPresent 的单步状态;不再用 bool 代表整体成功。
  • 修正顶层 -StopRunWindow 退出语义。
  • 新增 Write-ResultTextWrite-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 现场验收

仅由业务负责人指定白名单应用并逐动作确认:

  1. 多输入框 direct:先无确认验证零点击,再专用确认运行,记录 clicked/start evidence,不能宣称业务完成。
  2. 无 stop 按钮 close:确认前零动作;确认后记录 stop=false、window closed、Studio restored、run-list verification。
  3. PowerShell fallback 在 Python P0 通过后抽测;不重复对同一未知状态动作。

15. 并行实现工作包

以下拆分以文件所有权降低冲突。所有 Agent 先读取本方案和 v2 需求,不得自行改变契约值域。

WP-0:契约与测试骨架(先行,短周期)

  • 所有者文件:新增 scripts/runner_contract.py、新增 tests/test_contract.pytests/fakes.py
  • 内容:结果对象、错误/退出码、序列化、operation id、基础 fake 协议。
  • 交付门槛:无 pywinauto 环境可运行;text/JSON golden tests 通过。
  • 冲突面:仅新增文件,最低冲突。
  • 依赖:无。

WP-1:Python CLI、direct 与参数策略

  • 所有者文件scripts/OctopusRpaAppRunner.py;新增 tests/test_cli_validation.pytests/test_parameter_policy.pytests/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.pytests/test_config.py。推荐不为并行而强拆,避免过度重构。

WP-3:PowerShell fallback

  • 所有者文件scripts/OctopusRpaAppRunner.ps1;新增 tests/test_powershell_contract.pytests/powershell_contract_tests.ps1
  • 内容:P0 direct/close、安全确认、互斥、结果契约、stop 退出修复、JSON、fallback unsupported、原子配置。
  • 不做:不修改 Python 文件。
  • 依赖:WP-0 冻结的书面契约,不依赖 Python UI 实现完成。
  • 冲突面:独占 ps1,低冲突。

WP-4:文档、wrapper 与迁移脚本

  • 所有者文件SKILL.mdscripts/run_pywinauto.cmdscripts/click_run_app_direct.pyscripts/close_run_window_direct.py
  • 内容:能力矩阵、确认说明、命令示例、退出码、弃用转发、wrapper exit 7。
  • 依赖:CLI 名称和契约冻结即可,可与 WP-1/WP-3 并行。
  • 冲突面:不碰核心 runner,低冲突。

WP-5:独立验收与回归测试

  • 所有者文件:新增 tests/test_run_state_machine.pytests/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. 集成顺序与门禁

  1. 合入 WP-0:冻结 contract version、值域、错误码、退出码、JSON schema;未冻结前其他包只可本地开发。
  2. 合入 WP-1+WP-2 Python 核心:先跑 Python 单元/mock,确认参数错误不初始化 UI、direct/close 不重试。
  3. 合入 WP-3 PowerShell:运行跨实现契约 fixture;P0 不一致不得进入文档集成。
  4. 合入 WP-4 文档与迁移:以实际 --help 和测试结果校正文档,确保迁移脚本不自动添加 yes 标志。
  5. 合入 WP-5 黑盒回归:替换脆弱源码字符串断言,保留必要静态 guard;跑全量无副作用测试。
  6. 只读桌面 smoke:Python 必测,PowerShell 抽测;审计无 UI 副作用。
  7. 受控现场验收:direct 与 close 分别单独授权,不在状态未知后自动 fallback 重做。
  8. 发布检查:版本说明列出 exit 码扩展、stop 行为收紧、互动单选、legacy alias 弃用期。

每一步失败只回退当前工作包,不允许以禁用确认、放宽 unknown 或恢复临时直点脚本来“修测试”。

17. 回滚方案

17.1 发布前准备

  • 发布构建保留上一稳定版本完整技能目录或制品,不依赖 git reset
  • 记录 config schema;v2 只增加字段,旧版应忽略未知 schema_versionDataDirectory 保持原键名,因此配置可向后读取。
  • 现场动作前记录只读窗口/运行列表/进程快照;不得将进程快照用于自动杀进程。

17.2 代码回滚

若 v2 CLI/输出出现阻断性回归:

  1. 停止发起新的副作用命令。
  2. 仅在确认没有“点击结果未知”的在途操作时,将技能目录切回上一稳定制品。
  3. 保留 v2 config.json;上一版读取 DataDirectory 仍兼容。若上一版解析 schema 字段有问题,使用 v2 自动写入前保留的配置备份恢复,而不是手工截断正在使用的文件。
  4. 临时脚本不得恢复为无确认实现;回滚版本若缺 direct/close,则这些能力应标记不可用,并要求人工操作。
  5. 对已发生 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、测试和迁移说明与实现一致。
  • 现场验收明确区分“点击成功”“启动已观测”“业务结果未知”。