@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,195 @@
1
+ 'use strict';
2
+
3
+ const { compileScriptSpec, scriptHash } = require('./script-spec');
4
+ const { scriptError } = require('./script-errors');
5
+ const { readScriptResult } = require('./script-result');
6
+ const { verifyChecksum, checksumOf } = require('../shared-kernel/evidence-schema');
7
+
8
+ const RESTORE_CONTEXT = Symbol('scriptRestoreContext');
9
+
10
+ function unmatchedPreparedAction(store, operationId) {
11
+ const records = store.list(operationId);
12
+ return records.some((marker) => {
13
+ if (marker.kind !== 'dispatch-marker' || marker.state !== 'prepared') return false;
14
+ const receipt = records.find((item) => item.kind === 'action-receipt' && item.actionId === marker.actionId);
15
+ return !receipt || receipt.ambiguous === true;
16
+ });
17
+ }
18
+
19
+ function loadDurableCheckpoint(store, operationId) {
20
+ if (!store || typeof store.latest !== 'function' || typeof store.list !== 'function' || !operationId) {
21
+ return scriptError('unknown_operation', { operationId: operationId || null });
22
+ }
23
+ const records = store.list(operationId);
24
+ for (const record of records) {
25
+ if (!verifyChecksum(record).ok) return scriptError('checksum_mismatch', { operationId });
26
+ }
27
+ const checkpoint = records.filter((item) => item.kind === 'checkpoint').at(-1);
28
+ if (!checkpoint) return scriptError('unknown_operation', { operationId });
29
+ if (unmatchedPreparedAction(store, operationId)) {
30
+ return scriptError('ambiguous', { operationId, status: 'ambiguous', pauseReason: 'ambiguous' });
31
+ }
32
+ const actionSequence = records.reduce((maximum, item) => Math.max(maximum, item.actionSequence || 0), checkpoint.actionSequence || 0);
33
+ return { ok: true, checkpoint, records, actionSequence };
34
+ }
35
+
36
+ function planCodeRestore({ checkpoint, script } = {}) {
37
+ if (!checkpoint) return scriptError('unknown_operation');
38
+ if (checkpoint.script && Array.isArray(checkpoint.script.steps)) {
39
+ return scriptError('script_format_removed');
40
+ }
41
+ const candidate = script;
42
+ if (candidate?.target && checkpoint.target && checksumOf(candidate.target) !== checksumOf(checkpoint.target)) {
43
+ return scriptError('script_target_mismatch');
44
+ }
45
+ if (candidate?.permissions && checkpoint.permissions && checksumOf(candidate.permissions) !== checksumOf(checkpoint.permissions)) {
46
+ return scriptError('script_permissions_mismatch');
47
+ }
48
+ let input = candidate;
49
+ if (input === undefined) {
50
+ if (!checkpoint.source || !checkpoint.language) return scriptError('script_source_required', { hash: checkpoint.hash || null });
51
+ input = { schemaVersion: 'aab.code-script/v1' };
52
+ // The durable record owns the frozen program. Omit absent optional fields
53
+ // instead of injecting undefined or mixing it with a caller's program.
54
+ for (const field of ['name', 'language', 'source', 'target', 'permissions', 'policy', 'inputs', 'entrypoint']) {
55
+ if (Object.hasOwn(checkpoint, field)) input[field] = checkpoint[field];
56
+ }
57
+ }
58
+ const compiled = compileScriptSpec(input);
59
+ if (!compiled.ok) return compiled;
60
+ if (candidate === undefined && checkpoint.sourcePath) {
61
+ compiled.spec.sourcePath = checkpoint.sourcePath;
62
+ compiled.hash = scriptHash(compiled.spec);
63
+ }
64
+ if (checkpoint.hash && compiled.hash !== checkpoint.hash) {
65
+ return scriptError('script_hash_mismatch', {
66
+ hash: checkpoint.hash,
67
+ provided: compiled.hash,
68
+ });
69
+ }
70
+ return { ok: true, format: 'code', compiled, checkpoint };
71
+ }
72
+
73
+ function codeStartCheckpoint(compiled, operationId) {
74
+ const spec = compiled.spec;
75
+ const body = {
76
+ operationId,
77
+ revision: 1,
78
+ stepId: 'start',
79
+ status: 'running',
80
+ hash: compiled.hash,
81
+ language: spec.language,
82
+ sourcePath: spec.sourcePath,
83
+ name: spec.name,
84
+ target: spec.target,
85
+ permissions: spec.permissions,
86
+ policy: spec.policy,
87
+ inputs: spec.inputs,
88
+ entrypoint: spec.entrypoint,
89
+ actionSequence: 0,
90
+ };
91
+ if (spec.policy.restartPolicy === 'checkpoint') {
92
+ body.source = spec.source;
93
+ }
94
+ return body;
95
+ }
96
+
97
+ function codeRuntimeCheckpoint(record, checkpoint, revision) {
98
+ return {
99
+ ...codeStartCheckpoint({ spec: record.spec, hash: record.hash }, record.operationId),
100
+ revision,
101
+ stepId: checkpoint.name,
102
+ checkpoint,
103
+ actionSequence: record.actionSequence,
104
+ checkpointActionSequence: record.actionSequence,
105
+ };
106
+ }
107
+
108
+ function specFromCompiled(compiled) {
109
+ const spec = compiled.spec;
110
+ return {
111
+ schemaVersion: 'aab.code-script/v1',
112
+ name: spec.name,
113
+ language: spec.language,
114
+ source: spec.source,
115
+ target: spec.target,
116
+ permissions: spec.permissions,
117
+ policy: spec.policy,
118
+ inputs: spec.inputs,
119
+ entrypoint: spec.entrypoint,
120
+ };
121
+ }
122
+
123
+ function restartGate(checkpoint, records = []) {
124
+ if (checkpoint.policy?.restartPolicy !== 'checkpoint' || !checkpoint.checkpoint) {
125
+ return scriptError('not_resumable', { restartPolicy: checkpoint.policy?.restartPolicy || 'none' });
126
+ }
127
+ if (checkpoint.status === 'completed' || checkpoint.status === 'cancelled'
128
+ || (checkpoint.status === 'failed' && !['child_crashed', 'runtime_lost'].includes(checkpoint.error))) {
129
+ return scriptError('not_resumable', { status: checkpoint.status });
130
+ }
131
+ // A receipt can confirm an effect while the language state still precedes it.
132
+ // Re-entering that checkpoint would repeat the effect, so require reconciliation.
133
+ const boundary = checkpoint.checkpointActionSequence || 0;
134
+ if (records.some((item) => item.kind === 'action-receipt'
135
+ && item.actionSequence > boundary && item.dispatched !== false)) {
136
+ return scriptError('ambiguous', { status: 'ambiguous', pauseReason: 'checkpoint_precedes_action' });
137
+ }
138
+ return { ok: true };
139
+ }
140
+
141
+ async function restoreUnknownOperation(args = {}) {
142
+ const { store, operationId, script, supervisor } = args;
143
+ const loaded = loadDurableCheckpoint(store, operationId);
144
+ if (!loaded.ok) return loaded;
145
+ const checkpoint = loaded.checkpoint;
146
+ if (checkpoint.script && Array.isArray(checkpoint.script.steps)) return scriptError('script_format_removed');
147
+ const op = args.operation || 'status';
148
+ const gate = restartGate(checkpoint, loaded.records);
149
+ if (op === 'status' || op === 'wait') {
150
+ const terminal = ['completed', 'cancelled', 'failed'].includes(checkpoint.status);
151
+ const result = checkpoint.status === 'completed' && checkpoint.resultRef
152
+ ? readScriptResult({ store, operationId }) : null;
153
+ if (result?.ok === false) return { ...result, status: 'failed', restored: true, resumable: false };
154
+ return {
155
+ ok: true,
156
+ command: 'script',
157
+ operationId,
158
+ status: terminal ? checkpoint.status : 'failed',
159
+ error: terminal ? checkpoint.error || null : 'runtime_lost',
160
+ persisted: true,
161
+ restored: true,
162
+ resumeMode: gate.ok ? 'checkpoint' : 'none',
163
+ resumable: gate.ok,
164
+ hash: checkpoint.hash || null,
165
+ resultRef: checkpoint.resultRef ?? null,
166
+ };
167
+ }
168
+ if (op !== 'resume') return scriptError('not_resumable', { operationId });
169
+ if (!gate.ok) return { ...gate, operationId };
170
+ const planned = planCodeRestore({ checkpoint, script });
171
+ if (!planned.ok) return planned;
172
+ return supervisor.handle({
173
+ ...args,
174
+ operation: 'start',
175
+ script: specFromCompiled(planned.compiled),
176
+ [RESTORE_CONTEXT]: {
177
+ compiled: planned.compiled,
178
+ checkpoint: checkpoint.checkpoint,
179
+ revision: checkpoint.revision,
180
+ actionSequence: loaded.actionSequence,
181
+ checkpointActionSequence: checkpoint.checkpointActionSequence || 0,
182
+ },
183
+ });
184
+ }
185
+
186
+ module.exports = {
187
+ unmatchedPreparedAction,
188
+ loadDurableCheckpoint,
189
+ planCodeRestore,
190
+ codeStartCheckpoint,
191
+ codeRuntimeCheckpoint,
192
+ restoreUnknownOperation,
193
+ RESTORE_CONTEXT,
194
+ restartGate,
195
+ };
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const { handleCodeOrRemoved } = require('./script-entry-route');
4
+ const { createScriptHostPort } = require('./script-host-port');
5
+ const { createScriptSupervisor } = require('./script-supervisor');
6
+ const { getProcessDeviceMutationLease } = require('../shared-kernel/device-mutation-lease');
7
+
8
+ function createProductionHost(opts = {}) {
9
+ if (typeof opts.actions !== 'function') {
10
+ throw new TypeError('host_actions_required');
11
+ }
12
+ return createScriptHostPort({
13
+ ...opts,
14
+ mutationLease: opts.mutationLease || getProcessDeviceMutationLease(),
15
+ });
16
+ }
17
+
18
+ const codeSupervisor = createScriptSupervisor({ createHost: createProductionHost });
19
+
20
+ function handle(args = {}) {
21
+ return handleCodeOrRemoved(args, args.supervisor || codeSupervisor);
22
+ }
23
+
24
+ function resetScriptOperations() {}
25
+
26
+ module.exports = { handle, resetScriptOperations, createProductionHost, cancelActiveScripts: () => codeSupervisor.cancelAll() };
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ const { restoreUnknownOperation } = require('./script-durable-restore');
4
+ const { scriptError } = require('./script-errors');
5
+ const { isRemovedScriptFormat, removedScriptFormatError } = require('./script-format-removed');
6
+ const { isCodeScript } = require('./script-spec');
7
+ const { readScriptResult } = require('./script-result');
8
+
9
+ function scriptInput(args) {
10
+ return args.script;
11
+ }
12
+
13
+ async function handleCodeOrRemoved(args = {}, supervisor) {
14
+ const script = scriptInput(args);
15
+ if (args.operation === 'result' && args.store) return readScriptResult(args);
16
+ if ((args.operation || 'start') === 'start') {
17
+ if (isRemovedScriptFormat(script)) return removedScriptFormatError();
18
+ if (!isCodeScript(script)) return scriptError('invalid_script', { field: 'schemaVersion' });
19
+ return supervisor.handle(args);
20
+ }
21
+ if (args.operation === 'runtime-status' || (args.operationId && supervisor.knownOperation(args.operationId))) {
22
+ return supervisor.handle(args);
23
+ }
24
+ if (args.operationId && args.store) {
25
+ const restored = await restoreUnknownOperation({
26
+ ...args,
27
+ store: args.store,
28
+ operationId: args.operationId,
29
+ script,
30
+ supervisor,
31
+ operation: args.operation || 'status',
32
+ });
33
+ return restored;
34
+ }
35
+ return scriptError('unknown_operation', { operationId: args.operationId || null });
36
+ }
37
+
38
+ module.exports = { handleCodeOrRemoved };
@@ -0,0 +1,3 @@
1
+ 'use strict';
2
+
3
+ module.exports = require('./script-entry-code');
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ function emptyTimings() {
4
+ return {
5
+ workerOverheadMs: 0,
6
+ targetLeaseWaitMs: 0,
7
+ providerAcquireMs: 0,
8
+ evidenceCommitMs: 0,
9
+ summaryMs: 0,
10
+ decisionWaitMs: 0,
11
+ actionMs: 0,
12
+ receiptCommitMs: 0,
13
+ totalMs: 0,
14
+ };
15
+ }
16
+
17
+ function scriptError(error, extra = {}) {
18
+ return {
19
+ ok: false,
20
+ command: 'script',
21
+ error,
22
+ persisted: extra.persisted === true,
23
+ latestEvidenceIds: extra.latestEvidenceIds || {},
24
+ timings: extra.timings || emptyTimings(),
25
+ ...extra,
26
+ };
27
+ }
28
+
29
+ module.exports = { scriptError, emptyTimings };
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ const { createEvidenceStore } = require('../shared-kernel/evidence-store');
4
+ const { randomUUID } = require('node:crypto');
5
+
6
+ function createScriptEvidenceStore(options = {}) {
7
+ const store = createEvidenceStore({ maxBytes: 65 * 1024 * 1024, ...options, namespace: 'script' });
8
+ // Store instances restart their local counter. IDs must not overwrite an earlier run.
9
+ const identified = (kind, record) => ({
10
+ ...record,
11
+ evidenceId: record.evidenceId || `script:${kind}:${record.operationId}:${randomUUID()}`,
12
+ });
13
+ const persist = (kind, record) => store.persist(kind, identified(kind, record));
14
+ return {
15
+ ...store,
16
+ persist,
17
+ commit: persist,
18
+ offer: (kind, record) => store.offer(kind, identified(kind, record)),
19
+ };
20
+ }
21
+
22
+ module.exports = { createScriptEvidenceStore };
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const { scriptError } = require('./script-errors');
4
+
5
+ function isRemovedScriptFormat(input) {
6
+ if (typeof input === 'string') {
7
+ const trimmed = input.trim();
8
+ if (!trimmed) return false;
9
+ if (trimmed.startsWith('{')) {
10
+ try {
11
+ return isRemovedScriptFormat(JSON.parse(trimmed));
12
+ } catch {
13
+ return false;
14
+ }
15
+ }
16
+ return /(^|\n)[ \t]*steps\s*:/.test(trimmed);
17
+ }
18
+ if (!input || typeof input !== 'object') return false;
19
+ return Array.isArray(input.steps);
20
+ }
21
+
22
+ function removedScriptFormatError() {
23
+ return scriptError('script_format_removed', { field: 'steps' });
24
+ }
25
+
26
+ module.exports = { isRemovedScriptFormat, removedScriptFormatError };