@mobileaidev/ai-app-bridge 0.2.15 → 0.3.0-rc.2

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 (156) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +7 -0
  3. package/README.md +306 -53
  4. package/bin/ai-app-bridge.js +51 -4504
  5. package/bin/android-permissions.js +152 -0
  6. package/bin/android-uia-xml.js +74 -0
  7. package/bin/artifact-paths.js +127 -1
  8. package/bin/bridge-forward.js +56 -0
  9. package/bin/command-discovery.js +86 -0
  10. package/bin/command-errors.js +60 -0
  11. package/bin/command-registry.js +504 -0
  12. package/bin/command-request.js +14 -0
  13. package/bin/command-router.js +80 -0
  14. package/bin/connection-cache.js +119 -0
  15. package/bin/device-provider.js +2934 -0
  16. package/bin/execution-host.js +596 -0
  17. package/bin/execution-runtime.js +101 -0
  18. package/bin/fact-codec.js +321 -0
  19. package/bin/fact-recorder.js +691 -0
  20. package/bin/fact-store.js +226 -0
  21. package/bin/feedback-probe.js +285 -0
  22. package/bin/intent/install-intent.js +267 -0
  23. package/bin/intent/intent-action-executor.js +129 -0
  24. package/bin/intent/intent-autonomous-adapter.js +46 -0
  25. package/bin/intent/intent-capture-port.js +64 -0
  26. package/bin/intent/intent-entry.js +226 -0
  27. package/bin/intent/intent-errors.js +28 -0
  28. package/bin/intent/intent-evidence-store.js +121 -0
  29. package/bin/intent/intent-lifetime.js +62 -0
  30. package/bin/intent/intent-observation-target.js +33 -0
  31. package/bin/intent/intent-observer.js +194 -0
  32. package/bin/intent/intent-production-adapter.js +334 -0
  33. package/bin/intent/intent-provider.js +27 -0
  34. package/bin/intent/intent-runtime.js +63 -0
  35. package/bin/intent/intent-worker.js +432 -0
  36. package/bin/intent/ios-intent-adapter.js +90 -0
  37. package/bin/intent/permission-intent.js +239 -0
  38. package/bin/intent/web-intent-adapter.js +31 -0
  39. package/bin/ios-device-outcome.js +47 -0
  40. package/bin/ios-execution.js +108 -0
  41. package/bin/ios-provider.js +497 -583
  42. package/bin/ios-runtime-binding.js +54 -0
  43. package/bin/ios-wda-execution.js +70 -0
  44. package/bin/ios-wda-port.js +98 -0
  45. package/bin/ios-wda-project.js +79 -0
  46. package/bin/mcp-server.js +83 -1040
  47. package/bin/mmap-scan-index.js +329 -0
  48. package/bin/observation-collector.js +862 -0
  49. package/bin/runtime-client.js +154 -0
  50. package/bin/runtime-directory.js +107 -0
  51. package/bin/runtime-protocol.js +36 -0
  52. package/bin/script/bounded-script-registry.js +103 -0
  53. package/bin/script/node-runtime-adapter.js +233 -0
  54. package/bin/script/progress-projector.js +63 -0
  55. package/bin/script/python-runtime-adapter.js +111 -0
  56. package/bin/script/rolling-summary.js +134 -0
  57. package/bin/script/script-agent-port.js +40 -0
  58. package/bin/script/script-assert.js +95 -0
  59. package/bin/script/script-capture-port.js +76 -0
  60. package/bin/script/script-catalog.js +85 -0
  61. package/bin/script/script-durable-restore.js +195 -0
  62. package/bin/script/script-entry-code.js +26 -0
  63. package/bin/script/script-entry-route.js +38 -0
  64. package/bin/script/script-entry.js +3 -0
  65. package/bin/script/script-errors.js +29 -0
  66. package/bin/script/script-evidence-store.js +22 -0
  67. package/bin/script/script-format-removed.js +26 -0
  68. package/bin/script/script-host-port.js +397 -0
  69. package/bin/script/script-ledger.js +64 -0
  70. package/bin/script/script-result.js +57 -0
  71. package/bin/script/script-sdk.js +152 -0
  72. package/bin/script/script-sdk.py +153 -0
  73. package/bin/script/script-session-channel.js +127 -0
  74. package/bin/script/script-spec.js +86 -0
  75. package/bin/script/script-supervisor.js +919 -0
  76. package/bin/script/templates/checkpoint-reentry.js +13 -0
  77. package/bin/segment-index.js +481 -0
  78. package/bin/segmented-fact-store.js +1571 -0
  79. package/bin/shared-kernel/android-h5-target.js +10 -0
  80. package/bin/shared-kernel/android-install-execution.js +176 -0
  81. package/bin/shared-kernel/android-sdk-endpoint.js +42 -0
  82. package/bin/shared-kernel/android-shell-execution.js +195 -0
  83. package/bin/shared-kernel/argument-schema.js +117 -0
  84. package/bin/shared-kernel/canonical-path.js +17 -0
  85. package/bin/shared-kernel/device-acknowledgements.js +53 -0
  86. package/bin/shared-kernel/device-completion-history.js +52 -0
  87. package/bin/shared-kernel/device-mutation-lease.js +219 -0
  88. package/bin/shared-kernel/device-ownership-recovery.js +95 -0
  89. package/bin/shared-kernel/device-ownership-store.js +94 -0
  90. package/bin/shared-kernel/evidence-adapters.js +251 -0
  91. package/bin/shared-kernel/evidence-archive.js +329 -0
  92. package/bin/shared-kernel/evidence-recording.js +131 -0
  93. package/bin/shared-kernel/evidence-schema.js +194 -0
  94. package/bin/shared-kernel/evidence-store.js +190 -0
  95. package/bin/shared-kernel/execution-admission.js +22 -0
  96. package/bin/shared-kernel/execution-contracts.js +171 -0
  97. package/bin/shared-kernel/execution-io.js +106 -0
  98. package/bin/shared-kernel/execution-ledger.js +125 -0
  99. package/bin/shared-kernel/execution-scope.js +87 -0
  100. package/bin/shared-kernel/execution-target.js +115 -0
  101. package/bin/shared-kernel/flutter-execution.js +13 -0
  102. package/bin/shared-kernel/flutter-h5-port.js +60 -0
  103. package/bin/shared-kernel/flutter-h5-target.js +9 -0
  104. package/bin/shared-kernel/flutter-target.js +75 -0
  105. package/bin/shared-kernel/h5-execution.js +11 -0
  106. package/bin/shared-kernel/h5-target.js +31 -0
  107. package/bin/shared-kernel/host-fact-store.js +49 -0
  108. package/bin/shared-kernel/ios-h5-target.js +9 -0
  109. package/bin/shared-kernel/ios-native-target.js +71 -0
  110. package/bin/shared-kernel/live-capture-query.js +115 -0
  111. package/bin/shared-kernel/managed-sdk-execution.js +78 -0
  112. package/bin/shared-kernel/native-execution.js +13 -0
  113. package/bin/shared-kernel/native-target.js +156 -0
  114. package/bin/shared-kernel/provider-command-contracts.js +55 -0
  115. package/bin/shared-kernel/recorded-payload-archive.js +195 -0
  116. package/bin/shared-kernel/request-context.js +35 -0
  117. package/bin/shared-kernel/semantic-node.js +55 -0
  118. package/bin/shared-kernel/summary-transformer.js +352 -0
  119. package/bin/shared-kernel/target-lease-protocol.js +47 -0
  120. package/bin/shared-kernel/text-wait.js +111 -0
  121. package/bin/shared-kernel/uia-execution.js +96 -0
  122. package/bin/shared-kernel/uia-protocol.js +214 -0
  123. package/bin/shared-kernel/uia-runtime-port.js +377 -0
  124. package/bin/shared-kernel/uia-target.js +39 -0
  125. package/bin/shared-kernel/web-dom-target.js +44 -0
  126. package/bin/shared-kernel/xml-attributes.js +25 -0
  127. package/bin/target-execution.js +275 -0
  128. package/bin/web/command-schema.js +60 -0
  129. package/bin/web/session-store.js +157 -0
  130. package/bin/web-provider.js +334 -553
  131. package/docs/COMMAND_CONTRACT.md +1563 -0
  132. package/docs/EVIDENCE_ARCHIVE.md +214 -0
  133. package/docs/INTENT_FOREGROUND.md +71 -0
  134. package/docs/INTENT_NATIVE_EDITING.md +79 -0
  135. package/docs/RELEASE.md +59 -0
  136. package/docs/SCRIPT_AUTHORING.md +489 -0
  137. package/node_modules/@mobileaidev/segmented-fact-store-native/LICENSE +201 -0
  138. package/node_modules/@mobileaidev/segmented-fact-store-native/NOTICE +7 -0
  139. package/node_modules/@mobileaidev/segmented-fact-store-native/binding.gyp +36 -0
  140. package/node_modules/@mobileaidev/segmented-fact-store-native/bindings/node/sfs_node.c +597 -0
  141. package/node_modules/@mobileaidev/segmented-fact-store-native/include/sfs.h +178 -0
  142. package/node_modules/@mobileaidev/segmented-fact-store-native/index.js +5 -0
  143. package/node_modules/@mobileaidev/segmented-fact-store-native/package.json +24 -0
  144. package/node_modules/@mobileaidev/segmented-fact-store-native/src/sfs.c +2349 -0
  145. package/package.json +60 -5
  146. package/runtime/ios-wda/AABWDABinding.h +19 -0
  147. package/runtime/ios-wda/AABWDABinding.m +97 -0
  148. package/runtime/ios-wda/AABWDAExecution.h +26 -0
  149. package/runtime/ios-wda/AABWDAExecution.m +172 -0
  150. package/runtime/ios-wda/AABWDAIntegration.h +71 -0
  151. package/runtime/ios-wda/AABWDAManagedRoutes.h +392 -0
  152. package/runtime/ios-wda/AABWDAReceiptStore.h +10 -0
  153. package/runtime/ios-wda/AABWDAReceiptStore.m +116 -0
  154. package/runtime/uia/ai-app-bridge-uia.jar +0 -0
  155. package/runtime/uia/manifest.json +22 -0
  156. package/skills/ai-app-bridge-use/SKILL.md +23 -360
@@ -0,0 +1,432 @@
1
+ 'use strict';
2
+
3
+ const { executeDecisionAction } = require('./intent-action-executor');
4
+ const { intentError } = require('./intent-errors');
5
+ const { intentProviderError } = require('./intent-provider');
6
+ const { observeAndCommit } = require('./intent-observer');
7
+ const { createIntentRuntime } = require('./intent-runtime');
8
+ const { createIntentLifetime, awaitAgentReply } = require('./intent-lifetime');
9
+ const { checkExecution } = require('../shared-kernel/execution-scope');
10
+ const { validateValue } = require('../shared-kernel/argument-schema');
11
+ const { intentDecisionSchema } = require('../shared-kernel/execution-contracts');
12
+ const { CommandError } = require('../command-errors');
13
+ const { normalizeIntentTarget, normalizeObservationTarget } = require('./intent-observation-target');
14
+
15
+ function createIntentWorker({
16
+ operationId,
17
+ goal,
18
+ target: requestedTarget,
19
+ provider = 'native',
20
+ observationTarget = null,
21
+ store,
22
+ adapter,
23
+ mode = 'supervised',
24
+ agent = null,
25
+ budget = null,
26
+ timeoutMs = 300000,
27
+ ownsOutcome = true,
28
+ now = Date.now,
29
+ capturePort = null,
30
+ captureRequirements = null,
31
+ recording = null,
32
+ } = {}) {
33
+ const target = normalizeIntentTarget(requestedTarget);
34
+ const runtime = createIntentRuntime({ operationId, now });
35
+ runtime.state.mode = mode;
36
+ const context = {
37
+ operationId,
38
+ revision: 1,
39
+ target,
40
+ provider,
41
+ observationTarget: normalizeObservationTarget(target, provider, observationTarget),
42
+ observationFailure: null,
43
+ store,
44
+ adapter,
45
+ mode,
46
+ now,
47
+ capturePort,
48
+ captureRequirements,
49
+ recording,
50
+ canDispatch: () => !stopping && !timedOut() && runtime.state.status === 'decision_committed',
51
+ latestSummary: null,
52
+ latestEvidenceId: null,
53
+ latestEvidenceIds: {},
54
+ timings: {
55
+ workerOverheadMs: 0,
56
+ targetLeaseWaitMs: 0,
57
+ providerAcquireMs: 0,
58
+ evidenceCommitMs: 0,
59
+ summaryMs: 0,
60
+ decisionWaitMs: 0,
61
+ actionMs: 0,
62
+ receiptCommitMs: 0,
63
+ totalMs: 0,
64
+ },
65
+ };
66
+ let stopAutonomous = false;
67
+ let actionSteps = 0;
68
+ let agentCalls = 0;
69
+ let startedAt = now();
70
+ let stopping = null;
71
+ let finalizing = null;
72
+ let finished = false;
73
+ let agentController = null;
74
+ let terminalEvidenceId = null;
75
+ const effectiveTimeoutMs = timeoutMs === null ? null : Math.min(timeoutMs, budget?.maxDurationMs ?? Infinity);
76
+ const lifetime = createIntentLifetime({ timeoutMs: effectiveTimeoutMs, onDeadline: () => {
77
+ requestStop('timeout', 'deadline_exceeded'); void finish();
78
+ } });
79
+
80
+ function requestStop(status, error = null) {
81
+ if (stopping || finished) return;
82
+ stopping = { status, error };
83
+ stopAutonomous = true;
84
+ runtime.state.status = status === 'cancelled' ? 'cancelling' : 'finishing';
85
+ runtime.state.error = error;
86
+ const code = status === 'timeout' ? 'deadline_exceeded' : status === 'cancelled' ? 'cancelled' : 'runtime_stopped';
87
+ lifetime.stop(code);
88
+ agentController?.abort({ code });
89
+ runtime.emit('stop_requested', { requestedStatus: status, error });
90
+ }
91
+
92
+ function finish() {
93
+ if (finalizing) return finalizing;
94
+ if (!stopping) return Promise.resolve(snapshotResult(false));
95
+ finalizing = (async () => {
96
+ await lifetime.drain();
97
+ try {
98
+ if (ownsOutcome) {
99
+ const payloadSummary = { lifecycle: 'aab.intent-lifecycle/v1', ...stopping, goal, target, provider: context.provider,
100
+ observationTarget: context.observationTarget, observationFailure: context.observationFailure,
101
+ mode: context.mode, revision: runtime.state.revision, lastDecisionId: runtime.state.lastDecisionId,
102
+ startedAtMs: startedAt, deadlineMs: lifetime.deadlineMs, latestEvidenceIds: { ...context.latestEvidenceIds },
103
+ lastAction: context.lastAction || null, agentCalls, actionSteps, timings: { ...context.timings, totalMs: now() - startedAt } };
104
+ const persisted = await context.store.persist('checkpoint', { operationId, revision: runtime.state.revision,
105
+ stepId: 'intent-terminal', target, timestampMs: now(), payloadSummary });
106
+ if (!persisted.ok) throw Object.assign(new Error(persisted.error), { code: persisted.error || 'terminal_not_persisted' });
107
+ terminalEvidenceId = persisted.evidenceId;
108
+ context.latestEvidenceIds.checkpoint = persisted.evidenceId;
109
+ }
110
+ runtime.state.status = stopping.status;
111
+ runtime.state.error = stopping.error;
112
+ runtime.emit('terminal', { evidenceId: terminalEvidenceId, status: stopping.status });
113
+ } catch (error) {
114
+ runtime.state.status = 'blocked_evidence_store';
115
+ runtime.state.error = error.code || 'terminal_not_persisted';
116
+ runtime.emit('terminal_persistence_failed', { error: runtime.state.error, requestedStatus: stopping.status });
117
+ }
118
+ finished = true;
119
+ return snapshotResult(runtime.state.status === 'completed');
120
+ })();
121
+ return finalizing;
122
+ }
123
+
124
+ async function controlled(action, mutation = false) {
125
+ if (stopping || finished) return intentError('operation_stopped', snapshotResult(false));
126
+ let result;
127
+ try { result = await lifetime.run(action, mutation); }
128
+ catch (error) {
129
+ if (!stopping) requestStop(error.code === 'deadline_exceeded' ? 'timeout' : 'failed', error.code || 'intent_execution_failed');
130
+ }
131
+ if (stopping) return finish();
132
+ return result;
133
+ }
134
+
135
+ function timedOut() {
136
+ return effectiveTimeoutMs != null && (now() - startedAt >= effectiveTimeoutMs || lifetime.deadlineMs !== null && Date.now() >= lifetime.deadlineMs);
137
+ }
138
+
139
+ function enterTimeout() {
140
+ requestStop('timeout', 'deadline_exceeded');
141
+ return snapshotResult(false);
142
+ }
143
+
144
+ function observationFailure(observed) {
145
+ if (observed.stage === 'provider') { runtime.state.status = 'waiting_for_observation'; runtime.state.error = observed.error; }
146
+ else requestStop('blocked_evidence_store', observed.error);
147
+ runtime.emit('blocked', { error: observed.error });
148
+ return snapshotResult(false);
149
+ }
150
+
151
+ async function start() {
152
+ if (runtime.state.status !== 'created') return intentError('operation_already_started', snapshotResult(false));
153
+ startedAt = now(); lifetime.start();
154
+ runtime.state.status = 'observing';
155
+ if (ownsOutcome) {
156
+ const opened = await context.store.persist('checkpoint', { operationId, revision: 1, stepId: 'intent-started', target,
157
+ timestampMs: now(), payloadSummary: { lifecycle: 'aab.intent-lifecycle/v1', status: 'observing', goal, target,
158
+ provider, observationTarget: context.observationTarget, mode, startedAtMs: startedAt, deadlineMs: lifetime.deadlineMs, restartPolicy: 'none' } });
159
+ if (!opened.ok) { requestStop('blocked_evidence_store', opened.error || 'start_not_persisted'); return snapshotResult(false); }
160
+ context.latestEvidenceIds.checkpoint = opened.evidenceId;
161
+ checkExecution();
162
+ }
163
+ runtime.emit('intent_started', { goal: goal || null });
164
+ const observed = await observeAndCommit(context);
165
+ if (stopping) return snapshotResult(false);
166
+ checkExecution();
167
+ if (!observed.ok) {
168
+ return observationFailure(observed);
169
+ }
170
+ runtime.state.revision = observed.revision;
171
+ runtime.state.status = 'waiting_for_decision';
172
+ runtime.emit('waiting_for_decision', { revision: observed.revision, evidenceId: observed.evidenceId });
173
+ if (timedOut()) return enterTimeout();
174
+ if (mode === 'autonomous') {
175
+ return runAutonomous();
176
+ }
177
+ return snapshotResult(true);
178
+ }
179
+
180
+ async function runAutonomous() {
181
+ runtime.state.mode = 'autonomous';
182
+ context.mode = 'autonomous';
183
+ while (runtime.state.status === 'waiting_for_decision' && !stopAutonomous) {
184
+ if (timedOut()) return enterTimeout();
185
+ if (!agent || typeof agent.decide !== 'function') {
186
+ requestStop('intervention_required', 'agent_adapter_unavailable');
187
+ runtime.emit('intervention_required', { reason: 'agent_adapter_unavailable' });
188
+ return snapshotResult(false);
189
+ }
190
+ const gate = budget
191
+ ? budget.check({ steps: actionSteps, agentCalls, atMs: now() })
192
+ : { ok: true };
193
+ if (!gate.ok) {
194
+ requestStop('intervention_required', gate.reason);
195
+ runtime.emit('intervention_required', { reason: gate.reason });
196
+ return snapshotResult(false);
197
+ }
198
+ let decision;
199
+ try {
200
+ const waitStarted = now();
201
+ runtime.state.status = 'autonomous_deciding';
202
+ agentController = new AbortController();
203
+ decision = await awaitAgentReply(agent, {
204
+ goal,
205
+ revision: runtime.state.revision,
206
+ summary: context.latestSummary,
207
+ evidenceId: context.latestEvidenceId,
208
+ ...(context.latestCapture ? { capture: context.latestCapture } : {}),
209
+ }, agentController.signal);
210
+ context.timings.decisionWaitMs += now() - waitStarted;
211
+ agentCalls += 1;
212
+ } catch (error) {
213
+ if (stopping || runtime.state.status === 'paused') return snapshotResult(false);
214
+ runtime.state.status = 'waiting_for_decision';
215
+ runtime.state.error = 'agent_adapter_failed';
216
+ runtime.emit('agent_adapter_failed', { detail: error.message || String(error) });
217
+ return snapshotResult(false);
218
+ }
219
+ agentController = null;
220
+ if (runtime.state.status === 'autonomous_deciding') runtime.state.status = 'waiting_for_decision';
221
+ if (stopAutonomous || runtime.state.status !== 'waiting_for_decision') {
222
+ return snapshotResult(runtime.state.status === 'completed');
223
+ }
224
+ const result = await decide(decision);
225
+ if (result.ok && decision.agentDecision === 'act') actionSteps += 1;
226
+ if (result.ok !== true) return result;
227
+ if (['completed', 'failed', 'inconclusive', 'cancelled', 'ambiguous', 'intervention_required'].includes(runtime.state.status)) {
228
+ return result;
229
+ }
230
+ }
231
+ return snapshotResult(runtime.state.status === 'completed' || runtime.state.status === 'waiting_for_decision');
232
+ }
233
+
234
+ async function decide(decision = {}) {
235
+ if (timedOut()) return enterTimeout();
236
+ if (runtime.state.status !== 'waiting_for_decision') {
237
+ return intentError('not_waiting_for_decision', snapshotResult(false));
238
+ }
239
+ if (Number.isInteger(decision?.basedOnRevision) && decision.basedOnRevision > 0 && decision.basedOnRevision !== runtime.state.revision) {
240
+ return intentError('reobserve_required', { ...snapshotResult(false), dispatched: false, ambiguous: false });
241
+ }
242
+ try { validateValue(decision, intentDecisionSchema(context.latestProvider, context.target.platform), 'decision'); }
243
+ catch (error) {
244
+ if (!(error instanceof CommandError)) throw error;
245
+ return intentError(error.code, { ...snapshotResult(false), field: error.field, message: error.message,
246
+ ...(error.details ? { details: error.details } : {}), dispatched: false, ambiguous: false });
247
+ }
248
+ decision = structuredClone(decision);
249
+ if (mode === 'autonomous' && budget) {
250
+ const gate = budget.check({ steps: actionSteps, agentCalls, action: decision.action, atMs: now(), phase: 'decision' });
251
+ if (!gate.ok) {
252
+ requestStop('intervention_required', gate.reason);
253
+ runtime.emit('intervention_required', { reason: gate.reason });
254
+ return snapshotResult(false);
255
+ }
256
+ }
257
+ if (runtime.state.seenDecisionIds.has(decision.decisionId)) {
258
+ return intentError('duplicate_decision', { decisionId: decision.decisionId, ...snapshotResult(false) });
259
+ }
260
+ const agentDecision = decision.agentDecision;
261
+ if (['complete', 'fail', 'inconclusive'].includes(agentDecision)) {
262
+ if (decision.basedOnRevision !== runtime.state.revision) return intentError('reobserve_required', snapshotResult(false));
263
+ runtime.state.status = 'decision_committed';
264
+ const persisted = await context.store.persist('decision', {
265
+ decisionId: decision.decisionId,
266
+ operationId,
267
+ revision: runtime.state.revision,
268
+ basedOnEvidenceIds: [context.latestEvidenceId].filter(Boolean),
269
+ actionSpecHash: 'terminal',
270
+ agentDecision,
271
+ mode: context.mode,
272
+ reason: decision.reason,
273
+ timestampMs: context.now(),
274
+ target: context.target,
275
+ });
276
+ if (stopping) return snapshotResult(false);
277
+ checkExecution();
278
+ if (!persisted.ok) {
279
+ runtime.state.status = 'waiting_for_decision';
280
+ return intentError(persisted.error || 'plan_or_decision_not_persisted', snapshotResult(false));
281
+ }
282
+ runtime.state.seenDecisionIds.add(decision.decisionId);
283
+ runtime.state.lastDecisionId = decision.decisionId;
284
+ requestStop(agentDecision === 'complete' ? 'completed' : agentDecision === 'fail' ? 'failed' : 'inconclusive',
285
+ agentDecision === 'complete' ? null : agentDecision === 'fail' ? 'agent_declared_failure' : 'agent_declared_inconclusive');
286
+ context.latestEvidenceIds.decision = persisted.evidenceId;
287
+ runtime.emit('terminal_decision', { agentDecision, decisionId: decision.decisionId });
288
+ return snapshotResult(agentDecision === 'complete');
289
+ }
290
+ runtime.state.status = 'decision_committed';
291
+ const acted = await executeDecisionAction(context, decision);
292
+ if (stopping) return snapshotResult(false);
293
+ checkExecution();
294
+ if (!acted.ok) {
295
+ if (acted.error === 'reobserve_required') {
296
+ runtime.state.status = 'waiting_for_decision';
297
+ runtime.emit('reobserve_required', { decisionId: decision.decisionId });
298
+ return { ...snapshotResult(false), error: acted.error, revision: runtime.state.revision };
299
+ }
300
+ runtime.state.seenDecisionIds.add(decision.decisionId);
301
+ runtime.state.lastDecisionId = decision.decisionId;
302
+ requestStop(acted.ambiguous ? 'ambiguous' : 'failed', acted.error);
303
+ runtime.emit('paused', { error: acted.error, decisionId: decision.decisionId });
304
+ return snapshotResult(false);
305
+ }
306
+ runtime.state.seenDecisionIds.add(decision.decisionId);
307
+ runtime.state.lastDecisionId = decision.decisionId;
308
+ context.revision += 1;
309
+ runtime.state.revision = context.revision;
310
+ runtime.state.status = 'observing';
311
+ const observed = await observeAndCommit(context);
312
+ if (stopping) return snapshotResult(false);
313
+ checkExecution();
314
+ if (!observed.ok) {
315
+ return observationFailure(observed);
316
+ }
317
+ runtime.state.revision = observed.revision;
318
+ runtime.state.status = 'waiting_for_decision';
319
+ runtime.emit('waiting_for_decision', { revision: observed.revision, decisionId: decision.decisionId });
320
+ return snapshotResult(true);
321
+ }
322
+
323
+ function status(args = {}) {
324
+ const result = snapshotResult(
325
+ runtime.state.status === 'waiting_for_decision'
326
+ || runtime.state.status === 'completed'
327
+ || runtime.state.status === 'created',
328
+ );
329
+ if (context.store.ledger) {
330
+ const afterSequence = args.afterSequence == null ? 0 : args.afterSequence;
331
+ result.history = context.store.ledger.query(context.operationId, afterSequence, args.limit);
332
+ }
333
+ return result;
334
+ }
335
+
336
+ async function observe(args = {}) {
337
+ if (!['waiting_for_decision', 'waiting_for_observation'].includes(runtime.state.status)) return intentError('not_waiting_for_decision', snapshotResult(false));
338
+ if (args.basedOnRevision !== undefined && args.basedOnRevision !== runtime.state.revision) return intentError('reobserve_required', snapshotResult(false));
339
+ if (timedOut()) return enterTimeout();
340
+ const selectedProvider = args.provider ?? context.provider;
341
+ const providerError = intentProviderError(context.target, selectedProvider);
342
+ if (providerError) return intentError(providerError.error, { ...snapshotResult(false), ...providerError, dispatched: false, ambiguous: false });
343
+ let selectedObservationTarget;
344
+ try {
345
+ selectedObservationTarget = normalizeObservationTarget(context.target, selectedProvider,
346
+ args.observationTarget !== undefined ? args.observationTarget : selectedProvider === context.provider ? context.observationTarget : null);
347
+ } catch (error) {
348
+ return intentError(error.code, { ...snapshotResult(false), field: error.field, message: error.message, dispatched: false, ambiguous: false });
349
+ }
350
+ runtime.state.status = 'observing';
351
+ context.revision += 1;
352
+ runtime.emit('observing', { revision: context.revision, provider: selectedProvider, observationTarget: selectedObservationTarget, reason: 'explicit_reobserve' });
353
+ const observed = await observeAndCommit(context, selectedProvider, selectedObservationTarget);
354
+ if (runtime.state.status !== 'observing') return snapshotResult(false);
355
+ if (!observed.ok) {
356
+ return observationFailure(observed);
357
+ }
358
+ runtime.state.revision = observed.revision;
359
+ runtime.state.status = 'waiting_for_decision';
360
+ runtime.state.error = null;
361
+ runtime.emit('waiting_for_decision', { revision: observed.revision, evidenceId: observed.evidenceId, reason: 'explicit_reobserve' });
362
+ return snapshotResult(true);
363
+ }
364
+
365
+ function pause() {
366
+ if (runtime.state.status === 'waiting_for_decision' || runtime.state.status === 'autonomous_deciding') {
367
+ stopAutonomous = true;
368
+ runtime.state.status = 'paused';
369
+ agentController?.abort({ code: 'paused' });
370
+ runtime.emit('paused');
371
+ return snapshotResult(true);
372
+ }
373
+ return intentError('operation_busy', snapshotResult(false));
374
+ }
375
+
376
+ function resume() {
377
+ if (lifetime.pendingCount > 1) return intentError('operation_busy', snapshotResult(false));
378
+ if (runtime.state.status === 'paused') {
379
+ runtime.state.status = 'waiting_for_decision';
380
+ runtime.emit('resumed');
381
+ stopAutonomous = false;
382
+ if (mode === 'autonomous') return runAutonomous();
383
+ }
384
+ return snapshotResult(true);
385
+ }
386
+
387
+ function cancel() {
388
+ if (finished) return Promise.resolve(snapshotResult(runtime.state.status === 'completed'));
389
+ requestStop('cancelled', 'cancelled');
390
+ return finish();
391
+ }
392
+
393
+ function intervene(reason = 'intervened') {
394
+ requestStop('intervention_required', reason);
395
+ return finish();
396
+ }
397
+
398
+ function quiesce() {
399
+ if (ownsOutcome) throw new Error('Only an external workflow owner can quiesce the decision worker.');
400
+ requestStop('finishing');
401
+ return finish();
402
+ }
403
+
404
+ function snapshotResult(ok) {
405
+ const result = {
406
+ ok,
407
+ ...runtime.snapshot(),
408
+ provider: context.provider,
409
+ observationTarget: context.observationTarget,
410
+ observationFailure: context.observationFailure,
411
+ summary: context.latestSummary,
412
+ evidenceId: context.latestEvidenceId,
413
+ latestEvidenceIds: { observation: context.latestEvidenceId || null, ...context.latestEvidenceIds },
414
+ timings: { ...context.timings, totalMs: now() - startedAt },
415
+ agentCalls,
416
+ actionSteps,
417
+ deadlineMs: lifetime.deadlineMs,
418
+ pendingOperations: lifetime.pendingCount,
419
+ terminalEvidenceId,
420
+ lastAction: context.lastAction || null,
421
+ };
422
+ if (context.latestCapture) result.capture = context.latestCapture;
423
+ if (context.recording) result.recording = context.recording.info();
424
+ return result;
425
+ }
426
+
427
+ return { operationId, start: () => { lifetime.start(); return controlled(start); }, decide: decision => controlled(() => decide(decision), true),
428
+ observe: args => controlled(() => observe(args)), status, pause, resume: () => controlled(resume), cancel, intervene,
429
+ quiesce, isFinished: () => finished, runtime, context };
430
+ }
431
+
432
+ module.exports = { createIntentWorker };
@@ -0,0 +1,90 @@
1
+ 'use strict';
2
+
3
+ const { isDeepStrictEqual } = require('node:util');
4
+ const { selectH5Node, schema: h5Schema } = require('../shared-kernel/ios-h5-target');
5
+ const { IOSBridgeProvider } = require('../ios-provider');
6
+ const { selectFlutterNode, flutterNodeIdentity, flutterTargetRequest } = require('../shared-kernel/flutter-target');
7
+ const { checkExecution } = require('../shared-kernel/execution-scope');
8
+ const { selectNativeNode, sessionRef, schema: nativeSchema } = require('../shared-kernel/ios-native-target');
9
+
10
+ function createIOSIntentDeviceAdapter({ provider = new IOSBridgeProvider() } = {}) {
11
+ const reject = error => ({ ok: false, error, dispatched: false, ambiguous: false, mechanicalStatus: 'failed' });
12
+ function argumentsFor(target, native = false) {
13
+ return Object.fromEntries(['deviceId', 'bundleId', 'devicectl', ...(native
14
+ ? ['wdaUrl', 'wdaRunnerBundleId', 'wdaSessionId'] : ['runtimeUrl', 'iosHost', 'iosPort'])]
15
+ .filter(key => target[key] !== undefined).map(key => [key, target[key]]));
16
+ }
17
+ return {
18
+ async observe(target) {
19
+ if (!['native', 'h5', 'flutter'].includes(target.provider)) return reject('unsupported_ios_intent_provider');
20
+ const native = target.provider === 'native';
21
+ const rawTree = await provider.run(native ? 'ios-uia-tree' : target.provider === 'h5' ? 'ios-h5-dom' : 'ios-flutter-nodes',
22
+ { ...argumentsFor(target, native), ...(target.observationTarget === null || target.observationTarget === undefined ? {} : target.observationTarget) });
23
+ if (rawTree.ok !== true) return rawTree;
24
+ if (native && rawTree.nativeTargetSchema !== nativeSchema) return reject('ios_native_target_schema_required');
25
+ if (target.provider === 'h5' && rawTree.h5TargetSchema !== h5Schema) return reject('ios_h5_target_schema_required');
26
+ return { ok: true, provider: target.provider, rawTreeId: target.rawTreeId, rawTree,
27
+ foregroundTarget: target.bundleId };
28
+ },
29
+ async action(target) {
30
+ const { spec, rawTree, actionId } = target;
31
+ if (spec.provider === 'h5') {
32
+ const commands = { tap: 'ios-h5-click', inputText: 'ios-h5-input', scroll: 'ios-h5-scroll' };
33
+ if (!commands[spec.action]) return reject('unsupported_ios_intent_action');
34
+ const observed = selectH5Node(rawTree, spec.selector);
35
+ if (!observed.ok) return observed;
36
+ const result = await provider.run(commands[spec.action], { ...argumentsFor(target),
37
+ webViewId: observed.targetRef.pageRef.webViewId, selector: { elementId: observed.node.elementId },
38
+ expectedTarget: observed.targetRef, ...(spec.action === 'inputText' ? { text: spec.value } : {}), runtimeActionId: actionId });
39
+ return { ...result, mechanicalStatus: result.ok === true ? 'ok' : 'failed' };
40
+ }
41
+ if (spec.provider === 'native') {
42
+ if (spec.action === 'setOrientation') {
43
+ if (rawTree?.nativeTargetSchema !== nativeSchema) return reject('ios_native_target_schema_required');
44
+ const result = await provider.run('ios-set-orientation', { ...argumentsFor(target, true),
45
+ orientation: spec.orientation, expectedSession: sessionRef(rawTree), runtimeActionId: actionId });
46
+ return { ...result, mechanicalStatus: result.ok === true ? 'ok' : 'failed' };
47
+ }
48
+ if (!['tap', 'inputText'].includes(spec.action)) return reject('unsupported_ios_intent_action');
49
+ const observed = selectNativeNode(rawTree, spec.selector);
50
+ if (!observed.ok) return observed;
51
+ const result = await provider.run(spec.action === 'tap' ? 'ios-tap-native' : 'ios-input-native-text', {
52
+ ...argumentsFor(target, true), selector: spec.selector, expectedTarget: observed.targetRef,
53
+ ...(spec.action === 'inputText' ? { text: spec.value } : {}), runtimeActionId: actionId,
54
+ });
55
+ return { ...result, mechanicalStatus: result.ok === true ? 'ok' : 'failed' };
56
+ }
57
+ if (spec.provider !== 'flutter') return reject('unsupported_ios_intent_provider');
58
+ if (!['tap', 'inputText', 'scrollBy', 'back', 'hideKeyboard'].includes(spec.action)) return reject('unsupported_ios_intent_action');
59
+ let payload;
60
+ if (spec.action === 'back' || spec.action === 'hideKeyboard') payload = { action: spec.action };
61
+ else {
62
+ const observed = selectFlutterNode(rawTree, spec);
63
+ if (!observed.ok) return observed;
64
+ const fresh = await provider.run('ios-flutter-nodes', argumentsFor(target));
65
+ if (fresh.ok !== true) return fresh;
66
+ const current = selectFlutterNode(fresh, spec);
67
+ checkExecution();
68
+ if (!current.ok) return current;
69
+ if (!isDeepStrictEqual(flutterNodeIdentity(observed.node), flutterNodeIdentity(current.node))) {
70
+ return reject('reobserve_required');
71
+ }
72
+ const guarded = flutterTargetRequest(current.node, spec.selector);
73
+ if (!guarded.ok) return guarded;
74
+ payload = { ...guarded.request, ...(spec.action === 'tap' ? { action: 'tapTarget' }
75
+ : spec.action === 'inputText' ? { action: 'inputText', text: spec.value }
76
+ : { action: 'scrollBy', delta: spec.delta }) };
77
+ }
78
+ checkExecution();
79
+ const result = await provider.run('ios-flutter-action', {
80
+ ...argumentsFor(target), payload, runtimeActionId: actionId,
81
+ });
82
+ if (result.ok === true && spec.action === 'back' && result.handled === false) {
83
+ return { ...result, ok: false, error: 'back_not_handled', mechanicalStatus: 'failed' };
84
+ }
85
+ return { ...result, mechanicalStatus: result.ok === true ? 'ok' : 'failed' };
86
+ },
87
+ };
88
+ }
89
+
90
+ module.exports = { createIOSIntentDeviceAdapter };