@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 +7 -7
- package/bin/ai-app-bridge.js +7 -2
- package/bin/command-discovery.js +62 -12
- package/bin/mcp-server.js +8 -4
- package/bin/shared-kernel/execution-contracts.js +7 -4
- package/docs/COMMAND_CONTRACT.md +14 -2
- package/docs/RELEASE.md +22 -14
- package/docs/SCRIPT_AUTHORING.md +2 -2
- package/package.json +1 -1
- package/skills/ai-app-bridge-use/SKILL.md +38 -26
- package/skills/ai-app-bridge-use/agents/openai.yaml +2 -2
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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
The supported Node range is `>=26.3.0 <27`; this
|
|
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
|
-
#
|
|
59
|
-
npm install -g @mobileaidev/ai-app-bridge@0.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
|
|
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
|
package/bin/ai-app-bridge.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const { CommandError, commandFailure } = require('./command-errors');
|
|
5
|
-
const { commandDefinitions, isolatedCommandDefinitions,
|
|
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
|
-
|
|
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';
|
package/bin/command-discovery.js
CHANGED
|
@@ -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 = '
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
'
|
|
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"
|
|
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
|
|
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
|
|
19
|
+
const choices = {};
|
|
20
20
|
for (const branch of branches) {
|
|
21
21
|
for (const [name, rule] of Object.entries(branch.properties)) {
|
|
22
|
-
|
|
23
|
-
|
|
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
|
}
|
package/docs/COMMAND_CONTRACT.md
CHANGED
|
@@ -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`.
|
|
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.
|
|
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.
|
|
1
|
+
# 0.3.1 发行与接入交接
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
10
|
-
| Android Gradle 插件 | `0.3.
|
|
11
|
-
| 原生 iOS SDK | Git tag `0.3.
|
|
12
|
-
| Flutter 插件 | `0.3.
|
|
13
|
-
| Desktop CLI/MCP | `0.3.
|
|
14
|
-
| Web SDK | `0.3.
|
|
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
|
|
24
|
-
2. 维护者推送提交与 `0.3.
|
|
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
|
|
27
|
-
5.
|
|
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
|
-
|
|
37
|
+
正式发布命令需在对应目录由维护者执行,例如 npm 使用 `npm publish --tag latest`;pub 使用 `flutter pub publish`。这些命令属于发布动作,不能混入本地验证脚本。
|
|
30
38
|
|
|
31
39
|
## 本地检查与最终包验证
|
|
32
40
|
|
package/docs/SCRIPT_AUTHORING.md
CHANGED
|
@@ -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
|
|
5
|
-
each command's arguments with `capabilities({command: name
|
|
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,48 +1,60 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ai-app-bridge-use
|
|
3
|
-
description:
|
|
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
|
-
|
|
10
|
+
- **Intent**:日常操作和未知流程,观察与 Agent 决策绑定;适合页面探索、系统窗口交互。
|
|
11
|
+
- **Script**:固定流程和重复运行,自由编写 JavaScript/Python(trusted-local-code),用 `ctx.call` 调用设备能力;权限声明不是 OS 沙箱。
|
|
12
|
+
- **单次命令**:观察、单步动作、安装、权限夹具和诊断可独立调用,无须包装成完整流程。
|
|
11
13
|
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
+
旧 MCP 实例可能与已安装 CLI 不同。缺少 Intent/Script 或参数不匹配时,核对实际入口版本,选用支持当前合同的入口;不要套用旧 batch、工具别名或外层参数。
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
## 目标与动作
|
|
27
23
|
|
|
28
|
-
|
|
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
|
-
|
|
30
|
+
## Intent
|
|
31
31
|
|
|
32
|
-
|
|
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
|
-
|
|
39
|
+
## Script
|
|
35
40
|
|
|
36
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
54
|
+
## 按需文档
|
|
43
55
|
|
|
44
|
-
|
|
56
|
+
从 `command -v ai-app-bridge` 取得入口并解析符号链接;其 `bin/..` 是 CLI 发布包根目录。源码仓库中为 `desktop/ai-app-bridge-cli`。以下路径均相对此包根目录,不相对本技能;先查标题或关键词,只读相关章节。
|
|
45
57
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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: "
|
|
4
|
-
default_prompt: "使用 $ai-app-bridge-use
|
|
3
|
+
short_description: "通过 Intent、Script 或单次命令操作和验证 App。"
|
|
4
|
+
default_prompt: "使用 $ai-app-bridge-use 完成本次 App 任务:探索用 Intent,固定流程用 Script,单次操作直接调用命令;按需读取接口合同,并核对实际结果与证据。"
|