@mobileaidev/ai-app-bridge 0.3.0-rc.3 → 0.3.1

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.
package/README.md CHANGED
@@ -7,10 +7,10 @@ discovery. Every request checks the mapping before dispatch. Mutating requests
7
7
  are never replayed after a missing route or uncertain result. For manual cleanup,
8
8
  pass the exact serial and returned Host port to `remove-forward`.
9
9
 
10
- This release candidate is `0.3.0-rc.3`, distributed through the npm `next`
11
- dist-tag. Install this exact version or `@next` to use the candidate Script/Intent
12
- and capture contracts below; `@latest` remains the previous stable release.
13
- The supported Node range is `>=26.3.0 <27`; this candidate was checked on 26.3.0.
10
+ This release is `0.3.1`, distributed through the npm `latest` dist-tag.
11
+ The default installation includes the Script/Intent and capture contracts below.
12
+ The `next` dist-tag also points to this release until a newer candidate is published.
13
+ The supported Node range is `>=26.3.0 <27`; this release was checked on 26.3.0.
14
14
  See [the release guide](docs/RELEASE.md) for local packaging and coordinated publication.
15
15
 
16
16
  AI App Bridge CLI/MCP supports Android native apps, Android WebView/H5/CDP,
@@ -55,8 +55,8 @@ MCP exposes exactly two tools: call `capabilities` to discover
55
55
  domains, commands, and options, then call `run` with the selected command.
56
56
 
57
57
  ```bash
58
- # After candidate publication; see docs/RELEASE.md for local packaging.
59
- npm install -g @mobileaidev/ai-app-bridge@0.3.0-rc.3
58
+ # Install the current stable release; see docs/RELEASE.md for packaging.
59
+ npm install -g @mobileaidev/ai-app-bridge@0.3.1
60
60
 
61
61
  ai-app-bridge status --package-name io.github.mobileaidev.aiappbridge.sample
62
62
  ai-app-bridge tree --package-name io.github.mobileaidev.aiappbridge.sample
@@ -264,7 +264,7 @@ describes hashes, source binding, retention and the excluded external payloads.
264
264
  - `committed` means that the fact writer has made the record readable. The
265
265
  mobile store uses group flushing; this does not promise survival of an
266
266
  arbitrary power loss before flush. Cold disk reads and cache performance
267
- are measured separately; this candidate has not passed the old hot-query
267
+ are measured separately; this release has not passed the old hot-query
268
268
  latency target on all devices and retained-store sizes.
269
269
 
270
270
  Script defaults to `restartPolicy: "none"`. Opt into `"checkpoint"` only for
@@ -2,7 +2,8 @@
2
2
  'use strict';
3
3
 
4
4
  const { CommandError, commandFailure } = require('./command-errors');
5
- const { commandDefinitions, isolatedCommandDefinitions, commandSchema, parseCliOptions } = require('./command-registry');
5
+ const { commandDefinitions, isolatedCommandDefinitions, parseCliOptions } = require('./command-registry');
6
+ const { commandInputSchema } = require('./command-discovery');
6
7
  const { encodeReply } = require('./runtime-protocol');
7
8
  const runtime = require('./runtime-client');
8
9
 
@@ -18,6 +19,8 @@ ${[...isolatedCommandDefinitions, ...commandDefinitions].map(d => ` ${d.command
18
19
  help Show this help.
19
20
 
20
21
  Use --help <command> to inspect its JSON input schema.
22
+ For intent/script/evidence, add --operation to read only that operation.
23
+ Intent decide also accepts --platform, --provider and --action schema filters.
21
24
  CLI flags use kebab-case, for example --package-name, --tap-x, --timeout-ms.
22
25
  Objects, arrays, nullable objects and Script decisions use JSON flag values.
23
26
  Only --category and --extra repeat as individual strings.
@@ -38,7 +41,9 @@ async function main() {
38
41
  command = parsed.command;
39
42
  if (parsed.options.help || command === 'help') {
40
43
  const name = command === 'help' ? '' : command;
41
- process.stdout.write(name ? `${JSON.stringify(commandSchema(name), null, 2)}\n` : `${helpText}\n`);
44
+ const { help, ...filters } = parsed.options;
45
+ if (!name && Object.keys(filters).length) throw new CommandError('invalid_argument', 'Schema filters require a command after --help.', { field: Object.keys(filters)[0] });
46
+ process.stdout.write(name ? `${JSON.stringify(commandInputSchema(name, filters), null, 2)}\n` : `${helpText}\n`);
42
47
  return;
43
48
  }
44
49
  command ||= 'status';
@@ -1,6 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  const { commandDefinitions, isolatedCommandDefinitions, commandByName, isolatedByName, commandSchema, commandContract } = require('./command-registry');
4
+ const { CommandError, commandFailure } = require('./command-errors');
5
+ const { variants, intentDecisionSchema } = require('./shared-kernel/execution-contracts');
6
+ const schemaFilterNames = ['operation', 'platform', 'provider', 'action'];
4
7
  const supportedTargets = [
5
8
  'Android native apps',
6
9
  'Android WebView/H5/CDP',
@@ -24,24 +27,31 @@ const commandDomains = {
24
27
  };
25
28
  const supportedTargetsText = `AI App Bridge supports ${supportedTargets.join('; ')}.`;
26
29
  const commandDomainsText = `Command domains: ${Object.entries(commandDomains).map(([domain, summary]) => `${domain}(${summary})`).join('; ')}.`;
27
- const discoveryText = 'Discover domains, commands and argument schemas with capabilities, then run the selected command with its arguments.';
30
+ const discoveryText = 'Use capabilities with a domain for a command directory, then request one command and operation when applicable. For intent decide, narrow by the actual platform, provider and action. Omit filters for the complete command contract; includeOptions:true explicitly expands a directory. Execute with run and command-specific arguments.';
28
31
  function capabilityPayload(args = {}) {
29
32
  if (!args || typeof args !== 'object' || Array.isArray(args)) return { ok: false, error: 'invalid_argument', field: 'arguments', dispatched: false, ambiguous: false };
30
- const invalidKey = Object.keys(args).find(key => !['command', 'domain', 'includeOptions'].includes(key));
33
+ const invalidKey = Object.keys(args).find(key => !['command', 'domain', 'includeOptions', ...schemaFilterNames].includes(key));
31
34
  if (invalidKey) return { ok: false, error: 'unsupported_argument', field: invalidKey, dispatched: false, ambiguous: false };
32
- for (const [key, type] of [['command', 'string'], ['domain', 'string'], ['includeOptions', 'boolean']]) {
33
- if (Object.hasOwn(args, key) && typeof args[key] !== type) return { ok: false, error: 'invalid_argument', field: key, dispatched: false, ambiguous: false };
35
+ for (const [key, type] of [['command', 'string'], ['domain', 'string'], ['includeOptions', 'boolean'], ...schemaFilterNames.map(key => [key, 'string'])]) {
36
+ if (Object.hasOwn(args, key) && (typeof args[key] !== type || type === 'string' && !args[key])) return { ok: false, error: 'invalid_argument', field: key, dispatched: false, ambiguous: false };
34
37
  }
35
38
  if (args.command && args.domain) return { ok: false, error: 'invalid_argument', field: 'domain', message: 'Use command or domain, not both.', dispatched: false, ambiguous: false };
39
+ const filters = Object.fromEntries(schemaFilterNames.filter(key => Object.hasOwn(args, key)).map(key => [key, args[key]]));
40
+ if (Object.keys(filters).length && !args.command) return { ok: false, error: 'invalid_argument', field: Object.keys(filters)[0], message: 'Schema filters require command.', dispatched: false, ambiguous: false };
36
41
  const includeOptions = args.includeOptions === true;
37
42
  const requestedCommand = args.command ? normalizeCommandName(args.command) : '';
38
43
  if (requestedCommand) {
39
44
  const definition = commandByName.get(requestedCommand) || isolatedByName.get(requestedCommand);
40
- return {
41
- ok: Boolean(definition),
42
- command: requestedCommand,
43
- ...(definition ? shapeCommandDefinition(definition, true) : { error: 'unknown_command' }),
44
- };
45
+ try {
46
+ return {
47
+ ok: Boolean(definition),
48
+ command: requestedCommand,
49
+ ...(definition ? shapeCommandDefinition(definition, true, filters) : { error: 'unknown_command' }),
50
+ };
51
+ } catch (error) {
52
+ if (!(error instanceof CommandError)) throw error;
53
+ return commandFailure(error, requestedCommand);
54
+ }
45
55
  }
46
56
 
47
57
  const requestedDomain = args.domain ? String(args.domain) : '';
@@ -63,13 +73,53 @@ function capabilityPayload(args = {}) {
63
73
  };
64
74
  }
65
75
 
66
- function shapeCommandDefinition(definition, includeOptions) {
76
+ function commandInputSchema(command, filters = {}) {
77
+ for (const [key, value] of Object.entries(filters)) {
78
+ if (!schemaFilterNames.includes(key)) throw new CommandError('unsupported_argument', `Unknown schema filter: ${key}.`, { field: key });
79
+ if (typeof value !== 'string' || !value) throw new CommandError('invalid_argument', `${key} must be a non-empty string.`, { field: key });
80
+ }
81
+ const schema = commandSchema(command);
82
+ if (!Object.keys(filters).length) return schema;
83
+ const scope = ['platform', 'provider', 'action'].find(key => Object.hasOwn(filters, key));
84
+ if (scope && (command !== 'intent' || filters.operation !== 'decide')) {
85
+ throw new CommandError('invalid_argument', `${scope} is available only with command=intent and operation=decide.`, { field: scope });
86
+ }
87
+ if (!['intent', 'script', 'evidence'].includes(command)) {
88
+ throw new CommandError('invalid_argument', 'Operation schema selection supports intent, script and evidence.', { field: 'operation' });
89
+ }
90
+ const selected = schema.anyOf.filter(branch => branch.properties.operation.const === filters.operation);
91
+ if (!selected.length) throw new CommandError('invalid_argument', `Unknown ${command} operation: ${filters.operation}.`, { field: 'operation' });
92
+ if (!scope) return selected.length === 1 ? selected[0] : variants('operation', selected);
93
+
94
+ const providersByPlatform = commandContract('intent').providersByPlatform;
95
+ if (filters.platform && !Object.hasOwn(providersByPlatform, filters.platform)) {
96
+ throw new CommandError('invalid_argument', 'platform must be android, ios or web.', { field: 'platform' });
97
+ }
98
+ const providers = filters.platform ? providersByPlatform[filters.platform] : [...new Set(Object.values(providersByPlatform).flat())];
99
+ if (filters.provider && !providers.includes(filters.provider)) {
100
+ throw new CommandError('invalid_argument', `provider must be one of: ${providers.join(', ')}.`, { field: 'provider' });
101
+ }
102
+ const decisions = (filters.provider ? [filters.provider] : providers).map(provider => intentDecisionSchema(provider, filters.platform));
103
+ let actions = decisions.flatMap(decision => decision.properties.action.anyOf);
104
+ if (filters.action) {
105
+ actions = actions.filter(branch => branch.properties.action.const === filters.action);
106
+ if (!actions.length) throw new CommandError('invalid_argument', `Action ${filters.action} is unavailable for the selected platform/provider.`, { field: 'action' });
107
+ }
108
+ const decision = variants('agentDecision', decisions[0].anyOf.map(branch => branch.properties.agentDecision.const === 'act'
109
+ ? { ...branch, properties: { ...branch.properties, action: actions.length === 1 ? actions[0] : variants('action', actions) } } : branch));
110
+ return { ...selected[0], properties: { ...selected[0].properties, decision } };
111
+ }
112
+
113
+ function shapeCommandDefinition(definition, includeOptions, filters = {}) {
114
+ if (!includeOptions) return { command: definition.command, summary: definition.summary };
115
+ const inputSchema = commandInputSchema(definition.command, filters);
67
116
  const shaped = {
68
117
  command: definition.command,
69
118
  summary: definition.summary,
70
119
  targetApp: Boolean(definition.targetApp),
71
120
  ...commandContract(definition.command),
72
- ...(includeOptions ? { options: Object.keys(commandSchema(definition.command).properties), inputSchema: commandSchema(definition.command) } : {}),
121
+ options: Object.keys(inputSchema.properties), inputSchema,
122
+ ...(Object.keys(filters).length ? { selection: filters } : {}),
73
123
  contractVersion: 'aab.command/v1',
74
124
  };
75
125
  if (definition.command === 'script') {
@@ -83,4 +133,4 @@ function shapeCommandDefinition(definition, includeOptions) {
83
133
 
84
134
 
85
135
  function normalizeCommandName(value) { return typeof value === 'string' ? value : ''; }
86
- module.exports = { capabilities: capabilityPayload, commandDomains, supportedTargets, supportedTargetsText, commandDomainsText, discoveryText };
136
+ module.exports = { capabilities: capabilityPayload, commandInputSchema, commandDomains, supportedTargets, supportedTargetsText, commandDomainsText, discoveryText };
package/bin/mcp-server.js CHANGED
@@ -11,13 +11,12 @@ const serverInstructions = [
11
11
  'Intent, Script and individual commands share one runtime across CLI and MCP. Disconnecting a client leaves operations running; explicit task cancel or runtime stop owns cancellation. Platform capabilities do not imply full complex-App acceptance.',
12
12
  'Use script status/wait for progress and resultRef; read the final output with script operation=result and the same operationId, including after runtime restart. A completed execution is separate from the business verdict.',
13
13
  supportedTargetsText,
14
- commandDomainsText,
15
14
  discoveryText,
16
15
  'Prefer AI App Bridge over raw adb, devicectl, or browser-specific scripts when inspecting UI, text, WebView/WKWebView, logs, network, app install, launch, permissions, or app-level Web evidence.',
17
16
  'Always pass packageName for Android app-specific commands. Port only selects the host forwarding port. For iOS, pass bundleId plus deviceId when more than one iPhone is connected.',
18
17
  'WDA App actions require the exact deviceId, wdaRunnerBundleId and wdaSessionId. A forwarded wdaUrl selects transport only; discover per-command requirements through capabilities.',
19
18
  'For Web Bridge sessions, start the provider, connect the browser SDK, then pass sessionId and targetId when needed.',
20
- 'Use freeze-app/thaw-app only as an optional stabilization control for dynamic or transient screens: thaw before reads/actions/captures, freeze after evidence capture only when it helps reasoning, and thaw before the next operation or before finishing so the app is not left frozen.',
19
+ 'Freeze only when needed to stabilize evidence; thaw before further work and before finishing.',
21
20
  ].join(' ');
22
21
  const mcpHelpText = `Usage: ai-app-bridge-mcp [--help]
23
22
 
@@ -40,7 +39,8 @@ Target ids:
40
39
  Web Bridge commands use sessionId; add targetId for multi-target pages.
41
40
 
42
41
  Examples:
43
- capabilities { "domain": "webview", "includeOptions": true }
42
+ capabilities { "domain": "webview" }
43
+ capabilities { "command": "intent", "operation": "start" }
44
44
  run { "command": "screenshot", "arguments": { "packageName": "com.example.app" } }
45
45
  run { "command": "web-session-start", "arguments": { "webPort": 18180 } }
46
46
  `;
@@ -239,9 +239,13 @@ function negotiateProtocolVersion(requestedVersion) {
239
239
 
240
240
  function toolDefinitions() {
241
241
  return [
242
- { name: 'capabilities', description: 'Discover commands and their exact argument schemas. Include options or select one command for the complete contract.',
242
+ { name: 'capabilities', description: 'Discover a light command directory, then request one command and operation. For intent decide, narrow by the actual platform, provider and action. Omit filters for the full command schema; includeOptions:true explicitly expands a directory.',
243
243
  inputSchema: { type: 'object', additionalProperties: false, properties: {
244
244
  domain: { type: 'string' }, command: { type: 'string' }, includeOptions: { type: 'boolean' },
245
+ operation: { type: 'string', description: 'With command=intent, script or evidence, select one operation.' },
246
+ platform: { enum: ['android', 'ios', 'web'], description: 'Intent decide schema scope only.' },
247
+ provider: { enum: ['native', 'uia', 'flutter', 'h5'], description: 'Intent decide schema scope only; must be supported by the selected platform.' },
248
+ action: { type: 'string', description: 'Intent decide schema scope only, for example tap or inputText.' },
245
249
  } } },
246
250
  { name: 'run', description: 'Execute a command from capabilities. All command parameters, including target identity, belong in arguments.',
247
251
  inputSchema: { type: 'object', additionalProperties: false, required: ['command'], properties: {
@@ -16,14 +16,17 @@ const jsonObject = { type: 'object', additionalProperties: true, description: 'A
16
16
  const array = (items, minItems = 0, maxItems = 1000) => ({ type: 'array', items, minItems, maxItems });
17
17
 
18
18
  function variants(tag, branches) {
19
- const properties = {};
19
+ const choices = {};
20
20
  for (const branch of branches) {
21
21
  for (const [name, rule] of Object.entries(branch.properties)) {
22
- const prior = properties[name];
23
- if (!prior) properties[name] = rule;
24
- else if (JSON.stringify(prior) !== JSON.stringify(rule)) properties[name] = { anyOf: [prior, rule] };
22
+ choices[name] ||= new Map();
23
+ choices[name].set(JSON.stringify(rule), rule);
25
24
  }
26
25
  }
26
+ const properties = Object.fromEntries(Object.entries(choices).map(([name, choices]) => {
27
+ const rules = [...choices.values()];
28
+ return [name, rules.length === 1 ? rules[0] : { anyOf: rules }];
29
+ }));
27
30
  properties[tag] = { type: 'string', enum: [...new Set(branches.flatMap(branch => branch.properties[tag].enum || [branch.properties[tag].const]))] };
28
31
  return { ...object(properties, [tag]), anyOf: branches };
29
32
  }
@@ -8,12 +8,22 @@ remains useful for observation, interaction, fixture setup and diagnosis.
8
8
 
9
9
  ## Discovery and entrypoints
10
10
 
11
- MCP exposes exactly `capabilities` and `run`. Use `capabilities {"command":"tap-text"}`
11
+ MCP exposes exactly `capabilities` and `run`. A default or domain query returns
12
+ a light command directory. Use `capabilities {"command":"tap-text"}`
12
13
  for its current `inputSchema`, platform, role and supported entrypoints. Domain
13
14
  `execution` contains Intent, Script, runtime lifecycle and device ownership;
14
15
  `evidence` contains archive operations.
15
16
  `capabilities {"includeOptions":true}` returns every current command schema.
16
17
 
18
+ Load only the operation needed for Intent, Script or evidence, for example
19
+ `capabilities {"command":"intent","operation":"start"}`. To inspect an Intent
20
+ action, add the actual platform, provider and action:
21
+ `capabilities {"command":"intent","operation":"decide","platform":"android","provider":"native","action":"tap"}`.
22
+ These filters narrow discovery only; execution still validates against the full
23
+ runtime contract. Intent terminal decisions remain available in the selected
24
+ decision schema. Unsupported operations or scope combinations return an error
25
+ with the offending field. Omit filters to read the complete command contract.
26
+
17
27
  All parameters are under `run.arguments`:
18
28
 
19
29
  ```json
@@ -30,7 +40,9 @@ fail with `unexpected_argument`; repeated single-value flags fail with
30
40
  `duplicate_argument` before device access, so a later value cannot replace the
31
41
  original target. Only `--category` and `--extra` accept repeated values. Parse
32
42
  failures return the same JSON error envelope and exit code 1 as validation errors.
33
- The CLI's `--help` and `--help COMMAND` come from the same registry. All
43
+ The CLI's `--help` and `--help COMMAND` come from the same registry. Help accepts
44
+ the same filters, such as `--help intent --operation decide --platform android
45
+ --provider native --action tap`, without starting a Runtime. All
34
46
  registered commands are available through CLI and MCP, including Intent, Script,
35
47
  installation, permission dialogs, evidence and Web sessions. Both adapters call
36
48
  `runtime-client.js`; one independent `execution-runtime.js` owns the protocol-neutral
package/docs/RELEASE.md CHANGED
@@ -1,17 +1,24 @@
1
- # 0.3.0-rc.3 发行与接入交接
1
+ # 0.3.1 发行与接入交接
2
2
 
3
- 本文件记录候选版的依赖关系和出仓库交付入口。封版要求是同一提交的源码、发行包与公开接入合同一致;单个样本的测试进度不改变包版本或发布状态。推送 Git、创建远端标签及发布 npm/pub 包由维护者执行。
3
+ 本文件记录正式版的依赖关系和出仓库交付入口。封版要求是同一提交的源码、发行包与公开接入合同一致;单个样本的测试进度不改变包版本或发布状态。推送 Git、创建远端标签及发布 npm/pub 包由维护者执行。
4
+
5
+ ## 0.3.1 变更
6
+
7
+ - CLI/MCP 默认发现返回精简目录;按 operation,以及 Intent decide 的 platform/provider/action 查询合同。完整合同仍可显式获取。
8
+ - 合同中的重复 union 规则去重并展平;保留实际执行校验。
9
+ - 随包 skill 保留目标、观察、回执和结果合同,详细文档按需读取。
10
+ - Android、iOS、Flutter、Web 同步版本与固定依赖;设备执行行为没有新增改动。
4
11
 
5
12
  ## 版本与消费方式
6
13
 
7
- | 交付物 | 候选版本 | 独立消费入口 | 发布依赖 |
14
+ | 交付物 | 发行版本 | 独立消费入口 | 发布依赖 |
8
15
  | --- | --- | --- | --- |
9
- | Android SDK | `0.3.0-rc.3` | JitPack `com.github.mobileAiDev.ai-app-bridge:ai-app-bridge-android:0.3.0-rc.3` | 同名 Git tag,JitPack 对该提交成功构建 |
10
- | Android Gradle 插件 | `0.3.0-rc.3` | JitPack `ai-app-bridge-gradle-plugin` 模块及插件 ID `io.github.mobileaidev.aiappbridge.android` | 与 SDK 相同的 Git tag;不再使用旧默认 `0.2.8` |
11
- | 原生 iOS SDK | Git tag `0.3.0-rc.3` | Git URL 的仓库根 `Package.swift`,产品 `AiAppBridgeIOS` | 根清单包含 Swift runtime、C adapter 和 segmented C store,无外部 C 包路径 |
12
- | Flutter 插件 | `0.3.0-rc.3` | pub `ai_app_bridge_flutter` | Android 固定依赖上述 SDK;iOS Swift/C 源码随插件分发 |
13
- | Desktop CLI/MCP | `0.3.0-rc.3` | npm `@mobileaidev/ai-app-bridge` | 包含 UIA bundle、WDA 模板和 native store 源码;WDA 上游固定 `14.1.1` |
14
- | Web SDK | `0.3.0-rc.3` | npm `@mobileaidev/ai-app-bridge-web` | 独立浏览器源码包,无 npm 对 CLI 的安装依赖 |
16
+ | Android SDK | `0.3.1` | JitPack `com.github.mobileAiDev.ai-app-bridge:ai-app-bridge-android:0.3.1` | 同名 Git tag,JitPack 对该提交成功构建 |
17
+ | Android Gradle 插件 | `0.3.1` | JitPack `ai-app-bridge-gradle-plugin` 模块及插件 ID `io.github.mobileaidev.aiappbridge.android` | 与 SDK 相同的 Git tag;不再使用旧默认 `0.2.8` |
18
+ | 原生 iOS SDK | Git tag `0.3.1` | Git URL 的仓库根 `Package.swift`,产品 `AiAppBridgeIOS` | 根清单包含 Swift runtime、C adapter 和 segmented C store,无外部 C 包路径 |
19
+ | Flutter 插件 | `0.3.1` | pub `ai_app_bridge_flutter` | Android 固定依赖上述 SDK;iOS Swift/C 源码随插件分发 |
20
+ | Desktop CLI/MCP | `0.3.1` | npm `@mobileaidev/ai-app-bridge` | 包含 UIA bundle、WDA 模板和 native store 源码;WDA 上游固定 `14.1.1` |
21
+ | Web SDK | `0.3.1` | npm `@mobileaidev/ai-app-bridge-web` | 独立浏览器源码包,无 npm 对 CLI 的安装依赖 |
15
22
  | Native store | `0.1.0` | 随 CLI 的 bundled dependency 安装 | 不要求另行发布到 npm;`file:../../native/segmented-fact-store` 是工作区构建入口,最终 tarball 必须包含该依赖源码 |
16
23
 
17
24
  Flutter 的 podspec 是随 pub 插件消费的本地 podspec,不是独立 CocoaPods trunk 发布包;原生 iOS 使用根 Swift package。Flutter SwiftPM 的 `../FlutterFramework` 由 Flutter 的集成生成,不能当作本仓库的外部私有依赖,也不应将本机 Flutter framework 打包进插件。
@@ -20,13 +27,14 @@ Host 支持范围声明为 Node `>=26.3.0 <27`,本轮实际验证基线是 **2
20
27
 
21
28
  ## 发布顺序
22
29
 
23
- 1. 完成源码审阅并冻结一个提交,核对以下命令的产物确实来自它;包含当前 untracked 的实际源码、测试和文档,排除本机生成目录。所有候选对外版本使用同一个 `0.3.0-rc.3`,若需要改版本,先同时更新上表涉及的 manifest 与固定依赖。
24
- 2. 维护者推送提交与 `0.3.0-rc.3` 标签,让 JitPack 构建 Android SDK/插件。确认两条公开坐标可解析后,再发布依赖它们的 Flutter 包。本地 Gradle project/path/AAR 替换不能证明 JitPack 坐标可消费。
30
+ 1. 完成源码审阅并冻结一个提交,核对以下命令的产物确实来自它;包含当前 untracked 的实际源码、测试和文档,排除本机生成目录。所有对外发行版本使用同一个 `0.3.1`,若需要改版本,先同时更新上表涉及的 manifest 与固定依赖。
31
+ 2. 维护者推送提交与 `0.3.1` 标签,让 JitPack 构建 Android SDK/插件。确认两条公开坐标可解析后,再发布依赖它们的 Flutter 包。本地 Gradle project/path/AAR 替换不能证明 JitPack 坐标可消费。
25
32
  3. 原生 iOS 消费相同 Git tag 的根 package;完成根 package 的 iOS 构建,不仅构建 `ios/ai-app-bridge-ios/Package.swift`。Flutter iOS 则检查实际 pub 包内 Swift/C 源码与声明相符。
26
- 4. CLI 与 Web SDK 可分别发布到 npm 的候选 dist-tag。CLI 的 native store 已打包随行,不等待一个不存在的单独 registry 依赖。Flutter 包发布以第 2 步完成为前提。
27
- 5. registry/tag 安装刚发布的确切版本,读取 `capabilities` 和版本,核对来源及支持范围,再按发布策略提升正式 dist-tag。候选发布不自动等于全平台生产验收完成。
33
+ 4. CLI 与 Web SDK 可分别发布到 npm `latest` dist-tag。CLI 的 native store 已打包随行,不等待一个不存在的单独 registry 依赖。Flutter 包发布以第 2 步完成为前提。
34
+ 5. 同步 npm `next` 指向 `0.3.1`,让已有候选入口也使用本次正式版。将 GitHub `main` 与发行提交同步,并创建非预发布的 GitHub Release。
35
+ 6. 从 registry/tag 安装刚发布的确切版本,读取 `capabilities` 和版本,核对来源及支持范围,确认默认安装入口指向本次发行版本。正式发布不自动等于全平台生产验收完成。
28
36
 
29
- 候选发布命令需在对应目录由维护者执行,例如 npm 使用 `npm publish --tag next`;pub 使用 `flutter pub publish`。这些命令属于发布动作,不能混入本地验证脚本。
37
+ 正式发布命令需在对应目录由维护者执行,例如 npm 使用 `npm publish --tag latest`;pub 使用 `flutter pub publish`。这些命令属于发布动作,不能混入本地验证脚本。
30
38
 
31
39
  ## 本地检查与最终包验证
32
40
 
@@ -1,8 +1,8 @@
1
1
  # Authoring a code Script
2
2
 
3
3
  This describes the current `aab.code-script/v1` implementation. Discover the
4
- running server's command allowlist with `capabilities({command:"script"})` and
5
- each command's arguments with `capabilities({command: name, includeOptions:true})`.
4
+ running server's command allowlist and start contract with
5
+ `capabilities({command:"script", operation:"start"})`, and each device command's arguments with `capabilities({command: name})`.
6
6
  An installed server can differ from a development checkout.
7
7
 
8
8
  ## Start and observe
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mobileaidev/ai-app-bridge",
3
- "version": "0.3.0-rc.3",
3
+ "version": "0.3.1",
4
4
  "description": "Desktop CLI and MCP server for AI App Bridge across Android, iOS, Flutter, WebView, and Web targets.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,48 +1,60 @@
1
1
  ---
2
2
  name: ai-app-bridge-use
3
- description: 通过 AI App Bridge 实际观察、操作和验证 App 时使用。用 Intent 进行观察与决策,用 Script 进行重复回归,用共享命令进行单次操作和诊断,并通过当前 MCP capabilities 核对参数与平台范围。开发 Bridge 本身时,以当前源码和命令合同为准。
3
+ description: 使用 AI App Bridge 观察、操作和验证 Android、iOS、Flutter、WebView Web App;适用于真实交互、流程自动化和设备诊断。
4
4
  ---
5
5
 
6
6
  # AI App Bridge Use
7
7
 
8
- ## 选择执行方式
8
+ ## 选择入口
9
9
 
10
- Intent 和 Script 是一等执行入口。日常页面操作、未知流程和系统窗口交互使用 Intent 的观察与决策合同;固定流程使用 JavaScript/Python Script。单次观察、动作、安装、权限夹具和诊断命令同样有独立价值。
10
+ - **Intent**:日常操作和未知流程,观察与 Agent 决策绑定;适合页面探索、系统窗口交互。
11
+ - **Script**:固定流程和重复运行,自由编写 JavaScript/Python(trusted-local-code),用 `ctx.call` 调用设备能力;权限声明不是 OS 沙箱。
12
+ - **单次命令**:观察、单步动作、安装、权限夹具和诊断可独立调用,无须包装成完整流程。
11
13
 
12
- 1. 从任务、构建产物和设备状态确定目标。Android 动作明确传 `serial`,App 操作传 `packageName`;iOS 使用 `deviceId`/`bundleId`,Native Intent 还需绑定 `wdaRunnerBundleId`/`wdaSessionId`;Web 使用当前文档的 `sessionId`/`runtimeEpoch`/`targetId`。
13
- 2. 调用 `capabilities` 查询本次实际运行版本的命令。指定 `command` 时返回完整 `inputSchema`、`role`、`entrypoints` 和 Script 能力声明。先核对平台与参数,再派发。
14
- 3. MCP 只有 `capabilities` 和 `run`。所有命令参数放在 `run.arguments`,命令名使用 capabilities 原样返回的名称。JSON 数字、布尔值不写成字符串。Intent/Script/evidence 必须明确 operation;嵌套控制参数也以当前 inputSchema 为准,错误中的 field 指向需修正的字段。
15
- 4. 从当前观察取得 selector 或坐标。`tap-text` 的 `provider:auto` 按 Native、Flutter、UIAutomator 顺序观察并选择一次动作;`provider:native|flutter|uia` 可固定回归来源。结果中的 observations/provider 说明选择依据。动作前会重新定位,多重匹配、语义身份或前台变化应重新观察。UIA 同名控件可用当前 `uia-tree` 的完整 `targetRef`,或当前 Intent revision 的 `selector.nodeRef`;引用失效后重新观察。Native 前台弹窗会阻挡后台 Flutter 文字动作。
16
- 5. 按任务的真实结果验证。执行完成、机械动作成功、UI 变化、业务状态和证据覆盖分别判断;操作后使用本轮新证据。没有足够证据的断言保留为 inconclusive。
14
+ ## 共享调用合同
17
15
 
18
- ## 最小调用
16
+ MCP 入口是 `capabilities` 和 `run`;命令参数全部放在 `run.arguments`,包括目标和 operation,使用当前命令名与 JSON 类型。默认 capabilities 或 domain 查询只取目录;用 `command` 查合同,Intent/Script/evidence 加 `operation` 只取当前操作。Intent decide 可再加实际 `platform`、`provider`、`action`,例如 `{"command":"intent","operation":"decide","platform":"android","provider":"native","action":"tap"}`。CLI `--help COMMAND` 接受相同筛选;`includeOptions:true` 才展开整个目录,按需使用。
19
17
 
20
- ```json
21
- {"command":"tap-text","arguments":{"serial":"DEVICE","packageName":"com.example.app","targetText":"设置","provider":"auto"}}
22
- ```
18
+ CLI 与 MCP 共用独立执行 Runtime、命令合同和 operationId。CLI JSON 响应的业务值在 `value`;Script 的调用返回值另见下文。客户端退出不会取消任务,用原 operationId 显式 cancel。取消不撤销已派发效果;更换 Runtime 版本或环境前先显式 stop。
23
19
 
24
- `wait-text` 使用 `timeoutMs`(默认 10000),文字精确匹配;`requireText`/`absentText` 是字符串数组,不接受 CSV。所有条件必须由同一次前台 provider 观察证明。纯消失等待必须明确 `provider`,读取失败不能证明消失。`requireActivity` 是完整类名。Native Intent 输入必须有 SDK `editable:true`。
20
+ MCP 实例可能与已安装 CLI 不同。缺少 Intent/Script 或参数不匹配时,核对实际入口版本,选用支持当前合同的入口;不要套用旧 batch、工具别名或外层参数。
25
21
 
26
- `tap` 是物理坐标;`tap-flutter` 是 Flutter 逻辑坐标。显式 App 目标必须匹配前台。系统 UI 使用已观察的系统目标与 device scope。已派发但结果未知时,先重新观察,不通过另一个 provider 重试同一动作。
22
+ ## 目标与动作
27
23
 
28
- CLI MCP 共用独立执行 Runtime、命令 schema operationId。客户端退出后任务继续运行;用任务 cancel 停止一个任务,或 `runtime operation:stop` 等待全部已拥有工作收尾。更换版本或 Runtime 环境先显式 stop,再启动。详细生命周期见 CLI 包内 `docs/COMMAND_CONTRACT.md`(仓库路径 `desktop/ai-app-bridge-cli/docs/COMMAND_CONTRACT.md`)。
24
+ - Android:明确 `serial` 和 `packageName`。iOS:`deviceId`/`bundleId`;Native Intent 还需原 WDA Runner/session 绑定。Web:从当前连接取得 `sessionId`/`runtimeEpoch`/`targetId`。
25
+ - Intent/Script 的目标带 `platform`。目标标识、当前前台和返回的观察必须对应;更多绑定按平台合同补齐。
26
+ - selector、nodeRef、pageRef 和坐标来自当前观察。多重匹配、过期引用、前台变化或 `reobserve_required` 需要重新观察;Intent 切 provider 通过 `observe` 完成。
27
+ - 已派发但结果未知(`ambiguous`)时先观察,避免换 provider 或端点重放动作。保留原 `error`、`message`、`dispatched` 和操作状态。
28
+ - 若使用 freeze,后续操作和结束交付前先 thaw。
29
29
 
30
- `script` 的 start/status/wait/result/pause/resume/decide/cancel/runtime-status 操作用于连续执行和控制。start 使用 script 及其内部 target,语言明确为 javascript 或 python;调用失败和断言结果由源码处理。status/wait 和完成事件保留小型 resultRef;最终返回值通过 `script operation:result` 加原 operationId 从持久存储读取,核对 representation 和 hash。编写脚本前查询 `capabilities {"command":"script"}`;按需读取 CLI 包内 `docs/SCRIPT_AUTHORING.md`(仓库路径 `desktop/ai-app-bridge-cli/docs/SCRIPT_AUTHORING.md`)。`app.lifecycle` 和 `app.permissions` 是可信调用者为回归夹具显式选择的额外能力。JS/Python 是 trusted-local-code,权限声明不是 OS 沙箱。
30
+ ## Intent
31
31
 
32
- 普通 Android 命令的 timeoutMs 是包括排队和 Provider 调用的同一 Host 预算,默认 30000 ms;不能用内部重试延长。Script 的 policy.timeoutMs 限制整个运行,单个调用只能缩短预算。取消期间状态为 cancelling,已拥有的调用、回执和检查点收尾后才写 cancelled;已提交但未确认的动作仍是 ambiguous,不能宣称撤销成功。安装/权限 Intent、iOS/Web 及等待条件的范围按包内 COMMAND_CONTRACT.md 判断。
32
+ `start` 提供 `goal`、显式 `target` 和所需 provider;默认 supervised。
33
+ 保留返回的 operationId,读取当前观察后以 `decide` 提交决策。
34
+ `decision` 包含唯一 `decisionId`、当前 `basedOnRevision` 和 `agentDecision`;`act` 的 action 遵循该观察的 provider 合同,控件动作使用唯一 selector。
35
+ 需要刷新或切换 provider 时用 `observe`,随后使用新 revision。
36
+ `complete`/`fail`/`inconclusive` 也需要当前 revision,且不带 action;完成决策不能代替实际结果证据。
37
+ 安装与权限弹窗命令会返回受监督 Intent,须继续观察和决策;具体收尾条件见对应合同章节。
33
38
 
34
- 普通 Intent 的 timeoutMs 默认 300000,覆盖初次观察、等待决策、暂停和后续动作。cancel 会中止并等待已拥有的操作收尾,终态必须先写入检查点;写入失败是 blocked_evidence_store。重启后的 intent status 从持久证据恢复终态,live:false、recovered:true,不自动重放;缺少终态时报告 interrupted/runtime_restarted。最后一个动作的 dispatched/ambiguous 仍需独立检查。complete/fail/inconclusive 决策同样必须提供当前 basedOnRevision。
39
+ ## Script
35
40
 
36
- `install-apk` 通过 CLI MCP 返回一个 supervised Intent 操作。调用者用该 operationId 观察系统页面并提交基于 revision 的唯一 selector 决策;没有 Agent 决策不会点击按钮。安装完成由 ADB 回执和独立 APK 身份核验共同确定。安装作为 Script 前置准备,后续客户端可继续观察和决策;同步 ctx.call 不提供安装入口,详见命令合同。
41
+ `start` `script` 内提供 `target`、`language`(`javascript` `python`),以及 `source`/`sourcePath` 二选一。源码入口、权限和 API 按需查 `SCRIPT_AUTHORING.md`。
42
+ `ctx.call` 返回 envelope:先检查 `ok`,设备数据在 `result`;调用失败和 `ctx.assert` 的 verdict 由源码处理。
43
+ 用原 operationId 查询 `status`/`wait`;连续等待使用上一响应的 `eventSequence` 作为 `afterSequence`。
44
+ `completed` 仅说明源码返回并持久化;status/wait 的 `resultRef` 不是最终值。
45
+ 完成后调用 `script` 的 `operation:"result"` 读取 `result`、`resultRef` 和 `persisted`,检查 representation 及实际断言结果。读取失败保留错误,不从进度事件拼出返回值。
37
46
 
38
- `permission-dialog` 同样返回 Intent 操作:先由 App 触发权限请求,再明确 packageName、permission 和 outcome(allow / allow-once / deny / dismiss)。依据实际弹窗决定 selector,以 PackageManager 状态及原 Activity 关闭核验结果;pending:activity_closure 时用 intent observe 继续核验。intent cancel 只停止任务;dismiss 才表示操作界面关闭弹窗。权限状态与 grant/revoke 夹具继续可直接调用;详情见包内 docs/COMMAND_CONTRACT.md。
47
+ ## 验证与证据
39
48
 
40
- ## 证据与平台
49
+ 按用户要求的结果选取本轮证据;动作回执、UI 变化、业务结果和证据覆盖分别判断,证据不足保留为 inconclusive。
50
+ 要保留可移交的过程文件,在 Intent/Script start 时设置新的 `recordingDir`;已有操作的保留记录可通过 `evidence` 导出。
51
+ `evidence export` 使用原 operationId 和对应 namespace(intent/script);包含已记录文件需 `includeRecordedPayloads:true`。保存返回的 manifestSha256,交给离线 `evidence verify`。
52
+ 归档校验只证明保留内容的完整性和覆盖范围;缺页、缺引用或已淘汰记录仍需如实报告,不能据此推断业务通过。
41
53
 
42
- Android 和 iOS 四流在持久后端接入后读取手机 FactStore;Host 记录执行与观察,Web 采集在接收时写入 Host FactStore。核对实际 refs、epoch、目标、窗口、coverage 和分页,保留失败或不完整覆盖。Script 和 Intent 支持 Android、iOS 和 Web,具体 provider 与动作范围以当前 schema 为准;支持平台不等于该 App 业务已经验证。
54
+ ## 按需文档
43
55
 
44
- `evidence` 导出并离线核验保留的执行记录与文件;归档完整不等于业务通过。错误返回保留 `error`、`message`、`dispatched`、`ambiguous` 和操作状态。在冻结 App 的诊断中,完成后恢复运行。
56
+ `command -v ai-app-bridge` 取得入口并解析符号链接;其 `bin/..` CLI 发布包根目录。源码仓库中为 `desktop/ai-app-bridge-cli`。以下路径均相对此包根目录,不相对本技能;先查标题或关键词,只读相关章节。
45
57
 
46
- ## 版本差异
47
-
48
- 当前候选版移除了 batch、样例专用启动/smoke 命令、旧 MCP 工具别名和外层重复参数。连续步骤写成 Script,Flutter 普通启动用 launch-app。运行版本不支持新合同时,明确报告版本差异;以实际发现的能力继续任务,不能把旧证据描述为本轮验证。
58
+ - `docs/COMMAND_CONTRACT.md`:入口与 Runtime 看 **Discovery and entrypoints**;Intent 看 **Execution operation contracts**;目标看 **Target and dispatch** 及对应 iOS/H5/Web 章节;安装/权限看 **Installation is an Intent operation** / **Runtime permission requests use Intent**。
59
+ - `docs/SCRIPT_AUTHORING.md`:首次写脚本看 **Start and observe**、**Calls and assertions**;命令准入看 **Capability selection**;采集或暂停需求再读对应章节。
60
+ - `docs/EVIDENCE_ARCHIVE.md`:需要记录、导出或离线校验时读取,包含文件范围与 coverage 的具体边界。
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "AI App Bridge Use"
3
- short_description: "Android/iOS/Flutter/Web 全域观察、操作、验证能力索引。"
4
- default_prompt: "使用 $ai-app-bridge-use 观察、操作和验证 Android native、Android WebView/H5/CDP、Flutter、iOS native/WKWebView/WDA/XCUITest 或桌面 Web Bridge;先用 capabilities 查 core/app/action/flutter/webview/ios/web/diagnostics/advanced,再用 run 执行。Android 传 packageName,iOS 传 bundleId/deviceId 并在 full-control 时使用 WDA,Web 先建立 sessionId,只有移动端动态画面需要稳定证据时才使用 freeze/thaw。"
3
+ short_description: "通过 Intent、Script 或单次命令操作和验证 App。"
4
+ default_prompt: "使用 $ai-app-bridge-use 完成本次 App 任务:探索用 Intent,固定流程用 Script,单次操作直接调用命令;按需读取接口合同,并核对实际结果与证据。"