@windsland52/maafw-live 0.3.0 → 0.4.0

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.
Files changed (118) hide show
  1. package/README.md +24 -9
  2. package/docs/daemon-protocol.md +15 -10
  3. package/docs/keyframe-retention-contract-v0.md +23 -34
  4. package/docs/session-unify-v0.md +115 -0
  5. package/lib/client/daemon.js +45 -3
  6. package/lib/commands/annotate.js +15 -8
  7. package/lib/commands/calibrate.js +3 -3
  8. package/lib/commands/color.js +3 -3
  9. package/lib/commands/crop.js +8 -8
  10. package/lib/commands/dispatch.js +121 -0
  11. package/lib/commands/env.js +200 -6
  12. package/lib/commands/index.js +7 -1
  13. package/lib/commands/input.js +71 -33
  14. package/lib/commands/kf.js +49 -13
  15. package/lib/commands/probe.js +70 -0
  16. package/lib/commands/reco.js +285 -56
  17. package/lib/commands/repl.js +1305 -502
  18. package/lib/commands/roi-check.js +273 -0
  19. package/lib/commands/roi-compare.js +388 -0
  20. package/lib/commands/runtime.js +295 -91
  21. package/lib/commands/timing.js +28 -10
  22. package/lib/daemon/framed.mjs +1014 -184
  23. package/lib/daemon/reco_child.mjs +150 -9
  24. package/lib/flags.js +2 -0
  25. package/lib/index.js +24 -20
  26. package/lib/interface/hotkey.js +142 -0
  27. package/lib/interface/index.js +1 -1
  28. package/lib/interface/load.js +107 -17
  29. package/lib/interface/override.js +218 -34
  30. package/lib/interface/plan.js +108 -17
  31. package/lib/option-args.js +29 -0
  32. package/lib/pkg.js +36 -0
  33. package/lib/protocol.js +4 -0
  34. package/lib/runtime/actions.js +127 -12
  35. package/lib/runtime/log-watch.js +160 -0
  36. package/lib/runtime/node-source.js +100 -0
  37. package/lib/runtime/ocr-model.js +45 -0
  38. package/lib/runtime/pi-env.js +34 -0
  39. package/lib/runtime/pretask.js +217 -0
  40. package/lib/runtime/prune.js +28 -0
  41. package/lib/runtime/receipts.js +57 -0
  42. package/lib/runtime/reco-args.js +38 -0
  43. package/lib/runtime/reco-verdict.js +82 -0
  44. package/lib/runtime/roi-compare.js +198 -0
  45. package/lib/runtime/session-forward.js +79 -0
  46. package/lib/runtime/session-ipc.js +471 -0
  47. package/lib/runtime/session.js +57 -8
  48. package/lib/runtime/signal-guard.js +35 -0
  49. package/lib/types/commands/dispatch.d.ts +22 -0
  50. package/lib/types/commands/env.d.ts +19 -0
  51. package/lib/types/commands/index.d.ts +1 -1
  52. package/lib/types/commands/probe.d.ts +15 -0
  53. package/lib/types/commands/reco.d.ts +11 -7
  54. package/lib/types/commands/repl.d.ts +8 -0
  55. package/lib/types/commands/roi-check.d.ts +3 -0
  56. package/lib/types/commands/roi-compare.d.ts +3 -0
  57. package/lib/types/commands/runtime.d.ts +20 -2
  58. package/lib/types/flags.d.ts +4 -0
  59. package/lib/types/interface/hotkey.d.ts +36 -0
  60. package/lib/types/interface/index.d.ts +1 -1
  61. package/lib/types/interface/load.d.ts +84 -7
  62. package/lib/types/interface/override.d.ts +58 -13
  63. package/lib/types/interface/plan.d.ts +23 -2
  64. package/lib/types/option-args.d.ts +19 -0
  65. package/lib/types/pkg.d.ts +8 -0
  66. package/lib/types/protocol.d.ts +16 -0
  67. package/lib/types/runtime/actions.d.ts +89 -7
  68. package/lib/types/runtime/log-watch.d.ts +28 -0
  69. package/lib/types/runtime/node-source.d.ts +25 -0
  70. package/lib/types/runtime/ocr-model.d.ts +8 -0
  71. package/lib/types/runtime/pi-env.d.ts +24 -0
  72. package/lib/types/runtime/pretask.d.ts +53 -0
  73. package/lib/types/runtime/prune.d.ts +4 -0
  74. package/lib/types/runtime/receipts.d.ts +7 -0
  75. package/lib/types/runtime/reco-args.d.ts +18 -0
  76. package/lib/types/runtime/reco-verdict.d.ts +50 -0
  77. package/lib/types/runtime/roi-compare.d.ts +117 -0
  78. package/lib/types/runtime/session-forward.d.ts +10 -0
  79. package/lib/types/runtime/session-ipc.d.ts +117 -0
  80. package/lib/types/runtime/session.d.ts +26 -2
  81. package/lib/types/runtime/signal-guard.d.ts +19 -0
  82. package/lib/usage.js +3 -3
  83. package/package.json +17 -4
  84. package/skills/maafw-live/SKILL.md +32 -2
  85. package/skills/maafw-live/references/pitfalls.md +97 -4
  86. package/skills/maafw-live/references/workflows.md +5 -0
  87. package/docs/keyframe-evidence-contract-v0.md +0 -61
  88. package/lib/client/daemon.js.map +0 -1
  89. package/lib/commands/annotate.js.map +0 -1
  90. package/lib/commands/calibrate.js.map +0 -1
  91. package/lib/commands/color.js.map +0 -1
  92. package/lib/commands/crop.js.map +0 -1
  93. package/lib/commands/env.js.map +0 -1
  94. package/lib/commands/index.js.map +0 -1
  95. package/lib/commands/input.js.map +0 -1
  96. package/lib/commands/kf.js.map +0 -1
  97. package/lib/commands/reco.js.map +0 -1
  98. package/lib/commands/repl.js.map +0 -1
  99. package/lib/commands/runtime.js.map +0 -1
  100. package/lib/commands/skill.js.map +0 -1
  101. package/lib/commands/timing.js.map +0 -1
  102. package/lib/commands/version.js.map +0 -1
  103. package/lib/exec.js.map +0 -1
  104. package/lib/flags.js.map +0 -1
  105. package/lib/image.js.map +0 -1
  106. package/lib/index.js.map +0 -1
  107. package/lib/interface/index.js.map +0 -1
  108. package/lib/interface/jsonc.js.map +0 -1
  109. package/lib/interface/load.js.map +0 -1
  110. package/lib/interface/override.js.map +0 -1
  111. package/lib/interface/plan.js.map +0 -1
  112. package/lib/pkg.js.map +0 -1
  113. package/lib/protocol.js.map +0 -1
  114. package/lib/runtime/actions.js.map +0 -1
  115. package/lib/runtime/keyframes.js.map +0 -1
  116. package/lib/runtime/session.js.map +0 -1
  117. package/lib/update-check.js.map +0 -1
  118. package/lib/usage.js.map +0 -1
@@ -0,0 +1,35 @@
1
+ /**
2
+ * run 阻塞期信号处理(审计 #6/#9):Ctrl-C / SIGTERM 时不许让 daemon 里的任务裸跑。
3
+ *
4
+ * 第一下:向 daemon 发 run_stop(post_stop),run 回执照常落盘,退出码走 128+signo
5
+ * (SIGINT=130 / SIGTERM=143)。第二下:直接退出——第一次没杀掉说明 daemon 已不应答,
6
+ * 再等没有现场可留(daemon 侧另有 onHostGone 兜底清 agent 树)。
7
+ *
8
+ * 为什么抽出来:Windows 没法跨进程投递 Ctrl-C,内联在命令里就没法测;
9
+ * process.emit('SIGINT') 会同步触发监听,单测只验"旗标立了、run_stop 发了"。
10
+ */
11
+ import { EXIT } from '../protocol.js';
12
+ /** 挂载信号守卫;`runStop` 是"给 daemon 发停止"的那个异步调用。 */
13
+ export function installRunSignalGuard(runStop) {
14
+ let code = 0;
15
+ const on = (c) => () => {
16
+ if (code) {
17
+ process.exit(c);
18
+ } // 第二下:daemon 不应答,不再等
19
+ code = c;
20
+ void runStop().catch(() => { });
21
+ };
22
+ const sigint = on(EXIT.SIGINT);
23
+ const sigterm = on(EXIT.SIGTERM);
24
+ process.on('SIGINT', sigint);
25
+ process.on('SIGTERM', sigterm);
26
+ return {
27
+ get code() { return code; },
28
+ restore() {
29
+ process.off('SIGINT', sigint);
30
+ process.off('SIGTERM', sigterm);
31
+ return code;
32
+ },
33
+ };
34
+ }
35
+ //# sourceMappingURL=signal-guard.js.map
@@ -0,0 +1,22 @@
1
+ import type { DaemonClient } from '../client/daemon.js';
2
+ import { type GlobalFlags } from '../flags.js';
3
+ import { type Command, type CommandResult } from '../protocol.js';
4
+ export interface DispatchEnv {
5
+ /** 复用既有 daemon(命名会话 / REPL 注入);一次性路径留空,由 withDaemon 现起现关。 */
6
+ client?: DaemonClient;
7
+ /** 命令看到的工作目录(一次性 CLI 是 --cwd 解析结果;会话侧是 serve 进程的 cwd) */
8
+ cwd: string;
9
+ }
10
+ export interface DispatchOutcome {
11
+ cmd: Command | null;
12
+ /** emit 需要的全局旗标(--json / --color 也在这里解析过) */
13
+ flags: GlobalFlags;
14
+ result: CommandResult;
15
+ }
16
+ /**
17
+ * 分发一条 argv(含命令名,即 `argv[0]` 是命令名)。
18
+ * 永不抛异常:BAD_ARGUMENTS / UNKNOWN_COMMAND / SESSION_REQUIRED / UNEXPECTED 都收敛成
19
+ * CommandResult——会话常驻进程不能因为一条坏命令挂掉。
20
+ */
21
+ export declare function dispatchArgv(argv: string[], env: DispatchEnv): Promise<DispatchOutcome>;
22
+ export declare function isDirectory(p: string): boolean;
@@ -1,2 +1,21 @@
1
1
  import { type Command } from '../protocol.js';
2
+ /**
3
+ * qualifier 归一化:同一个 pre-release 在两个生态里写法不同,比较前先落到同一形态。
4
+ * `-beta.1` / `beta.1` / `beta1` / `b1` → `b1`;`alpha` → `a`;`rc` / `pre` / `preview` → `rc`;
5
+ * `.dev1` → `dev1`;`.post1` → `post1`;没有后缀 → 空串(正式版)。
6
+ * 认不出的后缀原样保留:宁可把它判成不一致,也不要凭猜测把两个不相干的后缀当同一个。
7
+ */
8
+ export declare function normalizeQualifier(qualifier: string): string;
9
+ /**
10
+ * 把精确版本拆成 release 线与**归一化后的** qualifier:`5.15.0b1` → `5.15.0` + `b1`,
11
+ * `5.15.0-beta.1` 同样落到 `5.15.0` + `b1`。
12
+ *
13
+ * 对账要分别看两段:release 线不同是换了个版本;release 相同而 qualifier 不同是**同一条线上的
14
+ * 另一份构建**(正式版 vs 预发布、b1 vs rc1 都是)。只比字符串会把两种拼写当不一致;
15
+ * 只比 release 又会把正式版与预发布报成一致——两头都错。
16
+ */
17
+ export declare function splitRelease(version: string): {
18
+ release: string;
19
+ qualifier: string;
20
+ } | null;
2
21
  export declare const envCommand: Command;
@@ -6,7 +6,7 @@
6
6
  * help 描述未来就会过期撒谎,只描述现状。
7
7
  */
8
8
  import type { Command } from '../protocol.js';
9
- /** 展示顺序:探针 → 运行时/设备 → 输入 → 识别 → 探色 → 裁剪 → SoM → 校准 → 关键帧 → timing → 会话 → skill 自检。 */
9
+ /** 展示顺序:探针 → 运行时/设备 → 输入 → 识别 → 验真 → 探色 → 裁剪 → SoM → 校准 → 关键帧 → timing → 会话 → skill 自检。 */
10
10
  export declare const COMMANDS: Command[];
11
11
  export declare function findCommand(name: string): Command | undefined;
12
12
  /** 未知命令时给出「最接近的名字」建议(编辑距离),避免用户猜 */
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `maafw-live screencap-probe` —— 截图能力探测:判定"后台可截"(A 路前提)。
3
+ *
4
+ * 方法(契约 demo-trace §3):用另一个窗口把目标**完全盖住**,两次 `post_screencap`
5
+ * 间隔比 blockHash 与亮度。探针不知道"此刻是否真的遮挡"——所以 `--occluded` 让用户
6
+ * 显式声明;不给时判定结果如实标 `occluded:null`,不把"未声明遮挡"混成"前台结论"。
7
+ *
8
+ * 判据:
9
+ * changed==true + occluded → 后台可截(A 路前提成立)
10
+ * changed==true + !occluded → 像素在变——要判"后台可截"得在遮挡下重测
11
+ * changed==false + 正常亮度 → 静止(目标停止渲染?改应用设置)
12
+ * 黑图 / post_screencap 报错 → 截图方法不兼容,换 `win32.screencap` 方法
13
+ */
14
+ import { type Command } from '../protocol.js';
15
+ export declare const screencapProbeCommand: Command;
@@ -1,10 +1,3 @@
1
- /**
2
- * 识别单测:maafw-live reco。
3
- *
4
- * 走 daemon 的 reco_test —— 它把识别放进一次性子进程执行(beta 绑定在无效模板上会原生崩溃,
5
- * 子进程炸掉只损失一次测试,不带走调用方)。支持阈值扫描:一次调用给出阈值-命中曲线。
6
- * 项目模式下资源目录从 interface.json 解析,避免手写路径与项目声明不一致。
7
- */
8
1
  import { type Command } from '../protocol.js';
9
2
  /** threshold 起点建议(maafw-pipeline 技能口径):实测最高置信度 − 0.1 留余量,换场景会掉。
10
3
  * 得分在识别详情内层(detail.detail.best/all——miss 时 best 为 null,all 仍带各候选得分);
@@ -19,5 +12,16 @@ export declare function suggestThreshold(results: Array<{
19
12
  ok: boolean;
20
13
  detail?: unknown;
21
14
  }>): ThresholdSuggestion | null;
15
+ /**
16
+ * OCR 模型名:框架按 `model/ocr/<name>/` 找模型,缺省空串 = 直接放在 `model/ocr/` 下。
17
+ * 优先级:资源 dump(V2 缺省也填满)> 手写节点的 V2 param / V1 顶层 model > `--type` 的 `--param`。
18
+ */
19
+ export declare function ocrModelName(node: Record<string, unknown> | undefined, param: Record<string, unknown> | undefined, dump: {
20
+ recognition?: {
21
+ param?: {
22
+ model?: unknown;
23
+ };
24
+ };
25
+ } | undefined): string | undefined;
22
26
  export declare const recoCommand: Command;
23
27
  export declare const RECO_COMMANDS: Command[];
@@ -1,2 +1,10 @@
1
1
  import { type Command } from '../protocol.js';
2
+ /** repl `reco <type> k=v …` 的参数解析:沿用既有惯例(数字转 number),列表参数转数组。 */
3
+ export declare function recoParam(tokens: string[]): Record<string, unknown>;
2
4
  export declare const replCommand: Command;
5
+ /**
6
+ * REPL 行 → argv 的方言翻译表。翻不了(REPL 私有命令)返回 null,调用方走 line 路径。
7
+ * 这是会话"两个实现漂移"的止血带:翻译完进的是 dispatchArgv,与一次性 CLI 同一份
8
+ * 实现——`run T --option` 在 --cmd 里从此生效(P0-3)。
9
+ */
10
+ export declare function replToArgv(line: string): string[] | null;
@@ -0,0 +1,3 @@
1
+ import { type Command } from '../protocol.js';
2
+ export declare const roiCheckCommand: Command;
3
+ export declare const ROI_CHECK_COMMANDS: Command[];
@@ -0,0 +1,3 @@
1
+ import { type Command } from '../protocol.js';
2
+ export declare const roiCompareCommand: Command;
3
+ export declare const ROI_COMPARE_COMMANDS: Command[];
@@ -32,15 +32,33 @@ export declare const CONNECT_OPTIONS: {
32
32
  raw: {
33
33
  type: "boolean";
34
34
  };
35
+ 'no-pretask': {
36
+ type: "boolean";
37
+ };
35
38
  };
36
- export declare function sessionOptions(values: Record<string, unknown>): SessionOptions;
37
- /** daemon 抛出的错误一律翻译成结构化失败,不让堆栈进模型上下文。 */
39
+ /**
40
+ * 路径参数的解析基准:调用方给的目录就是 `ctx.cwd`(CLI 的 --cwd),不是 process.cwd()。
41
+ * 只处理**纯路径参数**(--project / --resource-dir / --out / --image / --frames-dir 等)——
42
+ * `--resource` 同时是"资源名",能不能按路径解析要留给 resolveResourcePaths(名字优先),
43
+ * 所以它在 SessionOptions.cwd 里透传,不在这里 resolve。
44
+ */
45
+ export declare function argPath(ctx: {
46
+ cwd: string;
47
+ }, v: string | undefined): string | undefined;
48
+ export declare function sessionOptions(ctx: {
49
+ values: Record<string, unknown>;
50
+ cwd: string;
51
+ }): SessionOptions;
52
+ /** daemon 抛出的错误翻译成结构化失败。退出码按错误的**性质**分:
53
+ * 前置缺失(脚本找不到 / 会话规划失败)退 4;运行时故障(调用超时、进程退出、发送失败)退 1——
54
+ * 同一个 code 在所有路径退同一个码,agent 才不会按"缺环境"去查"daemon 卡住"。 */
38
55
  export declare function daemonFail(e: unknown, hint?: string): CommandResult;
39
56
  export declare const probeCommand: Command;
40
57
  export declare const deviceCommand: Command;
41
58
  export declare const connectCommand: Command;
42
59
  export declare const disconnectCommand: Command;
43
60
  export declare const screencapCommand: Command;
61
+ export declare const eventsCommand: Command;
44
62
  export declare const frameCommand: Command;
45
63
  export declare const waitCommand: Command;
46
64
  export declare const runCommand: Command;
@@ -31,6 +31,10 @@ export declare const GLOBAL_OPTIONS: {
31
31
  type: "boolean";
32
32
  short: string;
33
33
  };
34
+ /** 把命令转发到命名会话的常驻 daemon(`repl --session <name>` 起的那个) */
35
+ session: {
36
+ type: "string";
37
+ };
34
38
  };
35
39
  export interface GlobalFlags {
36
40
  json: boolean;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * hotkey 占位符 → 当前控制器的虚拟按键码(PI v2.8.0+)。
3
+ *
4
+ * 协议只给规则、不给表:`{名称}` / `{名称}.primary` / `{名称}.modifier1` / `{名称}.modifier2`
5
+ * 要替换成"**当前控制器 type** 支持的虚拟按键码",且"映射规则应与 ClickKey、KeyDown 等动作所用
6
+ * 键码表一致"(MaaFW 5.14.2 `3.3-ProjectInterfaceV2协议`,pinned commit 8061d5bc)。
7
+ *
8
+ * 这里四张表的数值是各 OS **公开的键码常量**,不是谁发明的东西:
9
+ * - `Win32` → Windows Virtual-Key Codes(`VK_*`,见 winuser.h)
10
+ * - `Adb` → Android `KeyEvent`(`KEYCODE_*`)
11
+ * - `MacOS` → Carbon `Events.h`(`kVK_*`,ANSI 键盘物理位置)
12
+ * - `WlRoots`/`Linux` → Linux `input-event-codes.h`(`KEY_*`/evdev)
13
+ *
14
+ * 取值与 MXU 的实现交叉核对过(MXU 是 AGPL-3.0,本仓 MIT:**只核数值,不搬代码**)。
15
+ * 键名按 MXU 的持久化约定规范化(大写、`+` 连接、末段为主键),因为它就是协议里"人类可读
16
+ * 快捷键字符串"的实际来源之一。
17
+ */
18
+ /** 一张表:规范化键名 → 虚拟按键码 */
19
+ export type HotkeyTable = Record<string, number>;
20
+ /** 按控制器 **type** 取表(不是 name)。没有表的类型返回 null——调用方要告警,不能退回 Win32 猜。 */
21
+ export declare function hotkeyTableFor(controllerType: string | null, useWin32VkCode?: boolean): HotkeyTable | null;
22
+ /** "Ctrl+Shift+A" → { primary: "A", modifiers: ["Ctrl","Shift"] };末段为主键。 */
23
+ export declare function splitHotkeyCombo(value: string): {
24
+ primary: string;
25
+ modifiers: string[];
26
+ };
27
+ /**
28
+ * 把一个 hotkey 字段解析成四种占位符的取值。
29
+ *
30
+ * 缺修饰键不是错(单键 "E" 本来就没有修饰键),补 0 = 无按键;**主键查不到是错**——
31
+ * 写个 0 进去等于"按了空键",任务会静默点不动。
32
+ */
33
+ export declare function resolveHotkeyTokens(fieldName: string, raw: string, table: HotkeyTable): {
34
+ tokens: Record<string, number>;
35
+ error?: string;
36
+ };
@@ -8,4 +8,4 @@
8
8
  export { parseJsonc } from './jsonc.js';
9
9
  export { loadInterface, findInterfaceFile, type DisplayDecl, type GamepadDecl, type InterfaceController, type InterfaceOption, type InterfacePreset, type InterfaceProblem, type InterfaceResource, type InterfaceTask, type LoadedInterface, type OptionCase, type Win32Decl, } from './load.js';
10
10
  export { planController, resolveResourcePaths, type ControllerPlan, type DeviceLike, type PlanOverrides, type ResourcePlan, } from './plan.js';
11
- export { computePipelineOverride, mergeOverride, type OverrideInput, type OverrideResult } from './override.js';
11
+ export { computePipelineOverride, appendOverride, type OverrideInput, type OverrideResult } from './override.js';
@@ -21,6 +21,12 @@ export interface GamepadDecl {
21
21
  gamepadType?: string;
22
22
  screencap?: string;
23
23
  }
24
+ /** MacOS 控制器声明(PI schema:`macos.{title_regex,screencap,input}`) */
25
+ export interface MacOSDecl {
26
+ titleRegex?: string;
27
+ screencap?: string;
28
+ input?: string;
29
+ }
24
30
  export interface InterfaceController {
25
31
  name: string;
26
32
  /** Adb / Win32 / Gamepad / MacOS / PlayCover / Linux */
@@ -28,10 +34,23 @@ export interface InterfaceController {
28
34
  display?: DisplayDecl;
29
35
  win32?: Win32Decl;
30
36
  gamepad?: GamepadDecl;
37
+ macos?: MacOSDecl;
31
38
  /** v2.2.0:在 resource.path 之后追加加载的资源目录(相对 interface.json 所在目录) */
32
39
  attachResourcePath: string[];
33
40
  /** v2.3.0:控制器级配置项名单(override 链第三级) */
34
41
  option?: string[];
42
+ /** Linux 控制器声明(PI schema):`use_win32_vk_code` 决定服务端按 Win32 VK 还是 evdev 解释键码;
43
+ * `screencap`/`input`/`pipewire_source` 等交给 daemon 按 maa-support-extension 规则组 config JSON */
44
+ linux?: {
45
+ useWin32VkCode?: boolean;
46
+ screencap?: string;
47
+ input?: string;
48
+ pipewireSource?: string;
49
+ };
50
+ /** `permission_required`(schema 字段):控制器需要管理员/root 权限运行——连接失败时的排查提示 */
51
+ permissionRequired?: boolean;
52
+ /** 原始条目(未裁剪):PI_CONTROLLER 要传"被选中那条的完整对象",不能按本工具用到的字段重建 */
53
+ raw?: unknown;
35
54
  }
36
55
  export interface InterfaceResource {
37
56
  name: string;
@@ -41,6 +60,10 @@ export interface InterfaceResource {
41
60
  controllers: string[];
42
61
  /** v2.3.0:资源级配置项名单(override 链第二级) */
43
62
  option?: string[];
63
+ /** v2.6.0:资源完整性校验值(仅加载 path 后的 MaaResourceGetHash;不匹配只警告不阻断) */
64
+ hash?: string;
65
+ /** 原始条目(未裁剪):PI_RESOURCE 要传"被选中那条的完整对象" */
66
+ raw?: unknown;
44
67
  }
45
68
  export interface InterfaceTask {
46
69
  name: string;
@@ -52,6 +75,25 @@ export interface InterfaceTask {
52
75
  resource?: string[];
53
76
  /** 配置项键名(按展示顺序);对应 InterfaceOption 字典的键 */
54
77
  option?: string[];
78
+ /** task 级覆盖:键=节点名、值=字段覆盖(与 case 级同构,但**没有** `{字段名}` 替换语义) */
79
+ pipelineOverride?: Record<string, unknown>;
80
+ }
81
+ /** input option 的输入字段:`{name}` 占位引用的就是这里的 `name`。 */
82
+ export interface InputField {
83
+ name: string;
84
+ /** 初始值。协议只把它定义成"默认值",没说 Client 在无人输入时该不该拿它替换——本工具用。 */
85
+ default?: string;
86
+ /** 替换时的转型类型;枚举只有 `string` / `int` / `bool`(**没有** float) */
87
+ pipelineType?: string;
88
+ /** 正则校验(协议把它归给 Client 的输入校验职责) */
89
+ verify?: string;
90
+ /** `password`(v2.10.0):值为密码——不得把原文写进输出/日志,与 `default` 互斥(schema 已禁) */
91
+ password?: boolean;
92
+ }
93
+ /** hotkey option 的快捷键字段(v2.8.0):持久化值是人类可读字符串,如 `E` / `Ctrl+A`(末段为主键)。 */
94
+ export interface HotkeyField {
95
+ name: string;
96
+ default?: string;
55
97
  }
56
98
  /** option 字典条目:switch/select/checkbox 的 case 携带 pipeline_override。 */
57
99
  export interface OptionCase {
@@ -64,11 +106,26 @@ export interface InterfaceOption {
64
106
  name: string;
65
107
  type: string;
66
108
  cases: OptionCase[];
109
+ /**
110
+ * input / hotkey 专用的覆盖模板:**整个 option 一份**,值里含 `{字段名}` 占位。
111
+ *
112
+ * 与 `cases[].pipeline_override` 是协议里两处不同字段(select/switch/checkbox 走 case,
113
+ * input/hotkey 走这里)。只建模前者会永远读不到后者的模板。
114
+ */
115
+ pipelineOverride?: Record<string, unknown>;
116
+ /** input 的输入字段(`inputs[]`) */
117
+ inputs?: InputField[];
118
+ /** hotkey 的快捷键字段(`hotkeys[]`,v2.8.0)——容器不是 inputs */
119
+ hotkeys?: HotkeyField[];
67
120
  /** 初始选中:switch/select 单 case 名;checkbox 为 case 名数组 */
68
121
  defaultCase?: string | string[];
69
122
  /** 适用性过滤(v2.3.1:不满足的 option 不得产生 pipeline_override) */
70
123
  controller?: string[];
71
124
  resource?: string[];
125
+ /** checkbox 最少选中数(v2.10.1,默认 0) */
126
+ minCount?: number;
127
+ /** checkbox 最多选中数(v2.10.1,缺省 = 不限) */
128
+ maxCount?: number;
72
129
  }
73
130
  /** preset:任务与 option 取值的快照(v2.3.0),应用时覆盖 default_case。 */
74
131
  export interface InterfacePreset {
@@ -80,10 +137,30 @@ export interface InterfacePreset {
80
137
  }>;
81
138
  }
82
139
  /** PI `agent` 声明:宿主侧子进程(如 uv run python agent/main.py),经 agent 协议注册 custom
83
- * action / recognition。maa-node 绑定未暴露 AgentClient 桥接——本工具只能诊断,不能 spawn。 */
140
+ * action / recognition。daemon 的 run 会 spawn 并桥接(bind_resource + sink 注册 + connect)。 */
84
141
  export interface InterfaceAgent {
85
142
  exec: string;
86
143
  args: string[];
144
+ /**
145
+ * `identifier`(schema 字段):连接标识符——项目声明固定值时 daemon 把它原样交给
146
+ * `Client` 并追加为子进程末参(socket 同名)。注意语义是"上次断线可重连",不是
147
+ * "本次独占":固定 identifier 残留旧 socket 时 connect 会等满超时才败(上游已知坑)。
148
+ */
149
+ identifier?: string;
150
+ }
151
+ /** PI `pretask` 声明(v2.7.0+):连接 Controller **之前**跑一次的子进程。 */
152
+ export interface InterfacePretask {
153
+ name?: string;
154
+ /** 必填:可执行程序(字符串,不是数组) */
155
+ exec: string;
156
+ /** 追加在 exec 之后的参数 */
157
+ args: string[];
158
+ /** 这些 option 的取值会被序列化成末位 JSON 参数(不参与 pipeline_override) */
159
+ option: string[];
160
+ /** v2.8.1 过滤:空/缺省 = 全部控制器适用 */
161
+ controller?: string[];
162
+ /** v2.8.1 过滤:空/缺省 = 全部资源适用 */
163
+ resource?: string[];
87
164
  }
88
165
  export interface LoadedInterface {
89
166
  /** interface.json 绝对路径;未找到为 null */
@@ -91,6 +168,8 @@ export interface LoadedInterface {
91
168
  /** 所在目录——resource.path 与 attach_resource_path 的相对基准 */
92
169
  dir: string | null;
93
170
  version: number | null;
171
+ /** 顶层 `version` 字符串(**项目**版本,与 interface_version 无关):PI_VERSION 就是它 */
172
+ projectVersion: string | null;
94
173
  name: string | null;
95
174
  controllers: InterfaceController[];
96
175
  resources: InterfaceResource[];
@@ -100,12 +179,10 @@ export interface LoadedInterface {
100
179
  presets: InterfacePreset[];
101
180
  /** 全局配置项键名(override 链最低级) */
102
181
  globalOption: string[];
103
- /** controller 启动前任务;CLI 不执行,仅用于警告 */
104
- pretask: Array<{
105
- name?: string;
106
- exec?: string;
107
- }>;
108
- /** agent 子进程声明;CLI 未桥接(绑定无 AgentClient),仅用于警告与失败归因 */
182
+ /** controller 启动前任务;连接前按声明序执行(非零退出即中止本次连接) */
183
+ pretask: InterfacePretask[];
184
+ /** agent 子进程声明;run 时由 daemon 桥接(spawn 子进程 + bind_resource + sink 注册 + connect,
185
+ * 见 framed.mjs setupAgents)。此处只负责读出 exec/args。 */
109
186
  agents: InterfaceAgent[];
110
187
  problems: InterfaceProblem[];
111
188
  }
@@ -1,33 +1,78 @@
1
1
  /**
2
- * pipeline_override 四级合并链(PI v2 v2.3.x):global_option → resource.option →
3
- * controller.option → task.option,后合并覆盖先合并;适用性过滤(option 自带的
4
- * controller/resource 条件不满足时不产生 override);checkbox 多选按 cases 声明序合并;
5
- * 嵌套 option 在其父 case 之后合并。
2
+ * pipeline_override 合成(PI v2)。
6
3
  *
7
- * 取值解析顺序:preset 显式值 > default_case。两级都没有 → 警告并跳过(不猜)。
8
- * 本 CLI 是无人值守消费面:不做交互选择,preset 是把 MaaPiCli 用户选择带进来的唯一通道。
4
+ * **输出是分层数组,不是预合并对象**(上游语义,MaaFramework @8061d5b 已核):
5
+ * `RuntimeParam::Task.pipeline_override` 是 `json::array`,Configurator 把 task 基底放
6
+ * `[0]`、每个 option case / input 模板各占一层;`Context::override_pipeline` 逐层
7
+ * `parse_node(node, layer, default=上一层结果)`——缺失字段继承前值,`recognition`/`action`
8
+ * 的 `param` 在同 type 时按键继承。**边界在层间**:上一层把 recognition.type 换成别种,
9
+ * param 就不能继承上一层的字段。若我们客户端先摊平成一个对象,跨层"换 type"后的
10
+ * param 会被错认为"同 type 继承"——只有分层数组能保边界。
11
+ *
12
+ * 层序(与上游 Configurator 一致,后者覆盖先者):
13
+ * 1. **task 级 `pipeline_override`** 作 `[0]`(上游同样放数组首位)。
14
+ * 2. **option 四级链**:global_option → resource.option → controller.option → task.option,
15
+ * 每个生效 case / input 模板各占一层;进链前先按 option 的 controller/resource
16
+ * 条件过滤(v2.3.0 / v2.3.1)。checkbox 多选按 cases 声明序各成一层;
17
+ * 嵌套 option 紧跟其父 case 之后。
18
+ * 3. **input / hotkey 的模板替换**:`option.<键>.pipeline_override` 是**整个 option 一份**
19
+ * 的模板,里面有 `{字段名}` 占位——注意它与 `cases[].pipeline_override` 是协议里两处不同字段。
20
+ *
21
+ * 取值优先级(**协议未规定**,本工具自定):CLI `--option` > preset > `default_case`。
22
+ * preset 是把 GUI 用户选择搬进来的通道;CLI 表示"这一跑就按这个",所以压过 preset。
23
+ * input 字段还有第三档来源:`inputs[].default`(协议把它定义成默认值,本工具在无人输入时拿它替换)。
9
24
  */
10
- import type { LoadedInterface } from './load.js';
25
+ import type { InterfaceOption, LoadedInterface } from './load.js';
11
26
  export interface OverrideInput {
12
27
  loaded: LoadedInterface;
13
28
  controllerName: string | null;
14
29
  resourceName: string | null;
15
30
  taskName: string | null;
16
31
  presetName?: string | null;
32
+ /** CLI 按次取值:option 键 → case 名(select/switch 单值;checkbox 数组) */
33
+ optionCases?: Record<string, string | string[]>;
34
+ /** CLI 按次取值:option 键 → { 字段名: 原文 }(input / hotkey 的字段映射) */
35
+ optionFields?: Record<string, Record<string, string>>;
17
36
  }
18
37
  export interface OverrideResult {
19
- override: Record<string, unknown>;
38
+ /** 分层 override:逐元素交给框架 `override_pipeline`,层间字段继承(数组尾=最高优先级) */
39
+ override: Record<string, unknown>[];
20
40
  /** 需要人工注意的事项(选项无取值、preset 未找到等) */
21
41
  warns: string[];
22
42
  /** 人类可读的生效记录:option=值(级别) */
23
43
  applied: string[];
24
44
  }
25
45
  /**
26
- * 把一份 pipeline_override 并入累计结果:同节点字段替换(与协议"不深度合并"一致)。
46
+ * 把调用方的覆盖(`--override`)追加到分层数组尾部 = 最高优先级。
47
+ * 对象 = 一层;数组 = 逐层(框架本来就接受数组,逐层继承,别在这里预合并)。
48
+ * 四级链、`run --override`、`timing --override` 共用这一个追加口——三处曾经各写一份,
49
+ * 代价是同一个参数在不同命令里表现不同。
50
+ */
51
+ export declare function appendOverride(layers: Record<string, unknown>[], ov: Record<string, unknown> | Record<string, unknown>[] | undefined): void;
52
+ export interface OptionValueCtx {
53
+ /** 命中的 preset 任务条目(`preset[].task[]`):取值快照 */
54
+ presetTask?: {
55
+ option?: Record<string, unknown>;
56
+ };
57
+ /** CLI 按次取值:case 名 */
58
+ optionCases?: Record<string, string | string[]>;
59
+ /** CLI 按次取值:字段映射 */
60
+ optionFields?: Record<string, Record<string, string>>;
61
+ }
62
+ export type ResolvedOptionValue = {
63
+ kind: 'cases';
64
+ cases: string[];
65
+ via: string;
66
+ } | {
67
+ kind: 'fields';
68
+ fields: Record<string, string>;
69
+ via: string;
70
+ };
71
+ /**
72
+ * 解析一个 option 的当前取值(**唯一一份实现**):pipeline_override 合成与 pretask 的末位 JSON
73
+ * 都走这里,两边各写一套迟早会在"preset 的 input 值是对象还是字符串"上分叉。
27
74
  *
28
- * **唯一一份实现**:四级链、`run --override`、`timing --override` 都走这里。这三处曾经各写一份,
29
- * 代价是同一个参数在不同命令里表现不同。非对象值必须**原样替换、不能展开**:字符串会被摊成
30
- * `{0:'a',1:'b'}`,`null` / 数字会静默变成空对象——等于把用户写的节点悄悄丢掉。
75
+ * 优先级(协议未规定,本地约定):CLI > preset > 声明里的初始值(`default_case` / `inputs[].default`)。
31
76
  */
32
- export declare function mergeOverride(acc: Record<string, unknown>, po: Record<string, unknown>): void;
77
+ export declare function resolveOptionValue(o: InterfaceOption, ctx: OptionValueCtx): ResolvedOptionValue | null;
33
78
  export declare function computePipelineOverride(input: OverrideInput): OverrideResult;
@@ -17,7 +17,7 @@ export interface PlanOverrides {
17
17
  adb?: {
18
18
  address?: string | null;
19
19
  };
20
- /** 手动 target(--target):playcover=<address>/<uuid>;linux=<config JSON> */
20
+ /** 手动 target(--target):playcover=<address>/<uuid>;macos=<窗口 id 或标题子串>;linux=<config JSON> */
21
21
  manualTarget?: string | null;
22
22
  }
23
23
  export interface ControllerPlan {
@@ -36,6 +36,22 @@ export interface ResourcePlan {
36
36
  names: string[];
37
37
  /** 本次实际选中的 resource 名(attach_resource_path 之外的来源条目) */
38
38
  selected: string | null;
39
+ /** 需要人工注意的事项:多个条目都适用时说明选了哪一个(resource[] 是互斥单选,选了就只能用一个) */
40
+ notes?: string[];
41
+ /**
42
+ * v2.6.0 资源完整性校验:声明了 resource.hash 时给出。`pathDirs` 是**只有** `path[]`
43
+ * 的那段(不含 attach),`attachDirs` 是控制器 `attach_resource_path`——校验时机是
44
+ * 加载完 path 后、加载 attach 之前,两段必须分开才能只在边界处读 `res.hash`。
45
+ */
46
+ hashCheck?: {
47
+ resourceName: string;
48
+ expected: string;
49
+ pathDirs: string[];
50
+ attachDirs: string[];
51
+ };
52
+ /** --resource 指到的值没匹配上任何条目,且不是磁盘上存在的目录:拒绝启动
53
+ * (打错一个字 = selected=null = 资源级 option/pretask 过滤全部跳过,比报错更贵)。 */
54
+ error?: string;
39
55
  }
40
56
  export declare function planController(loaded: LoadedInterface, overrides: PlanOverrides, devices: DeviceLike[]): ControllerPlan;
41
57
  /**
@@ -43,5 +59,10 @@ export declare function planController(loaded: LoadedInterface, overrides: PlanO
43
59
  * 所选控制器的条目,条目自身多路径按声明顺序叠加(如"B 服"= base + bilibili);
44
60
  * attach_resource_path 在选中控制器后追加。override(--resource)先按资源名匹配,
45
61
  * 匹配不上再按文件系统路径解释(原有行为)。
62
+ *
63
+ * 有多个条目都适用时,选中的那个与备选一起进 notes——"取第一个"是语义,但静默取第一个
64
+ * 会让用错资源包的人查不出原因。
46
65
  */
47
- export declare function resolveResourcePaths(loaded: LoadedInterface, controllerName?: string, override?: string): ResourcePlan;
66
+ export declare function resolveResourcePaths(loaded: LoadedInterface, controllerName?: string, override?: string,
67
+ /** `--resource` 当目录路径时的解析基准(CLI 的 ctx.cwd;缺省 = process.cwd()) */
68
+ cwd?: string): ResourcePlan;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `run --option` 的 argv 解析。
3
+ *
4
+ * 只做**切分**,不判 option 类型、不查 case 是否存在——类型、适用性、case 名合法性只有 interface
5
+ * 知道,判定收口在 `computePipelineOverride` 一处,CLI 与 REPL 各写一套就会分叉。
6
+ *
7
+ * 两种写法:
8
+ * - `<键>=<值>`:select / switch 的 case 名;checkbox 用逗号分隔多个 case
9
+ * - `<键>.<字段>=<值>`:input 的输入字段(占位符 `{字段名}` 引用的就是它)
10
+ */
11
+ export interface OptionArgs {
12
+ /** option 键 → case 名(单值或数组) */
13
+ cases: Record<string, string | string[]>;
14
+ /** option 键 → { 字段名: 原文 } */
15
+ fields: Record<string, Record<string, string>>;
16
+ /** 写法非法时的原因(调用方决定怎么报) */
17
+ error?: string;
18
+ }
19
+ export declare function parseOptionArgs(list: unknown): OptionArgs;
@@ -4,3 +4,11 @@ export interface PackageInfo {
4
4
  description?: string;
5
5
  }
6
6
  export declare function cliPackage(): PackageInfo;
7
+ /**
8
+ * 运行中**实际加载**的 maa-node 版本(PI_CLIENT_MAAFW_VERSION)。
9
+ *
10
+ * 为什么不用 package.json 的声明值:那里写的是范围(^5.14.2),而 agent 侧拿这个变量是要判协议
11
+ * 兼容的——报范围等于没报。maa-node 不导出 `version`,也不保证导出 package.json,
12
+ * 所以从入口向上找包根。拿不到返回空串(调用方据此不注入,而不是编一个)。
13
+ */
14
+ export declare function maaNodeVersion(): string;
@@ -30,6 +30,10 @@ export declare const EXIT: {
30
30
  readonly FINDINGS: 3;
31
31
  /** 前置环境缺失(缺 maa-node / 缺 checkout / 缺设备) */
32
32
  readonly ENV: 4;
33
+ /** 被 SIGINT(Ctrl-C)中断:128+2 */
34
+ readonly SIGINT: 130;
35
+ /** 被 SIGTERM 中断:128+15 */
36
+ readonly SIGTERM: 143;
33
37
  };
34
38
  export type ExitCode = (typeof EXIT)[keyof typeof EXIT];
35
39
  export interface CliError {
@@ -80,6 +84,11 @@ export interface CommandContext {
80
84
  flags: GlobalFlags;
81
85
  /** 已解析并校验过存在性的工作目录(绝对路径) */
82
86
  cwd: string;
87
+ /**
88
+ * 复用既有 daemon 客户端(REPL / 命名会话 dispatch 时注入)。一次性命令路径此字段为空,
89
+ * `withDaemon` 才会新起一个 daemon、用完即关——两条路径跑的是同一份 Command.run。
90
+ */
91
+ client?: import('./client/daemon.js').DaemonClient;
83
92
  }
84
93
  export interface Command {
85
94
  name: string;
@@ -89,6 +98,13 @@ export interface Command {
89
98
  usage: string;
90
99
  /** 命令私有选项;全局选项由入口自动合并 */
91
100
  options?: ParseArgsOptionsConfig;
101
+ /**
102
+ * 需要会话态才能工作的命令(会话 daemon 才有帧流 / 事件环 / 在跑任务)。
103
+ * `true` 整命令要;谓词形态按子命令分(frame get 要、frame status 不要;kf promote 要、
104
+ * kf list 不要)。一次性形态下入口直接退 2 + 指向 `--session <name>` 的提示,
105
+ * 不让"看起来成功了"的空跑存在(stop 停空 daemon、frame get 恒 NO_FRAME 这类)。
106
+ */
107
+ requiresSession?: boolean | ((ctx: CommandContext) => boolean);
92
108
  run(ctx: CommandContext): Promise<CommandResult>;
93
109
  }
94
110
  export declare function toEnvelope(command: string, r: CommandResult): Envelope;