agent-device 0.21.15 → 0.21.17
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/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.15.apk → agent-device-android-ime-helper-0.21.17.apk} +0 -0
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.17.apk.sha256 +1 -0
- package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.15.manifest.json → agent-device-android-ime-helper-0.21.17.manifest.json} +4 -4
- package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.15.apk → agent-device-android-snapshot-helper-0.21.17.apk} +0 -0
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.17.apk.sha256 +1 -0
- package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.15.manifest.json → agent-device-android-snapshot-helper-0.21.17.manifest.json} +6 -6
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +1 -4
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedTextEntry.h +9 -9
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedTextEntry.m +13 -22
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSystemSurfaceHostPolicy.swift +0 -9
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandDispatch.swift +31 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +1 -1
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +1 -1
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +9 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +40 -6
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedTextEntry.swift +202 -73
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +17 -2
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift +109 -128
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +4 -0
- package/dist/src/adapter.js +1 -1
- package/dist/src/adb-executor.js +1 -1
- package/dist/src/adb-failure.js +1 -1
- package/dist/src/adb-transport.js +1 -1
- package/dist/src/agent-browser-process-record.js +1 -1
- package/dist/src/agent-browser-provider.js +1 -1
- package/dist/src/agent-browser-tool.js +1 -1
- package/dist/src/agent-device-client.js +2 -2
- package/dist/src/ai-sdk.js +1 -1
- package/dist/src/alert-runtime.js +1 -1
- package/dist/src/android-input-ownership.js +1 -1
- package/dist/src/app-catalog.js +1 -1
- package/dist/src/app-deployment-resolution.js +1 -1
- package/dist/src/app-inventory.js +1 -1
- package/dist/src/app-launch.js +1 -1
- package/dist/src/app-lifecycle.js +1 -1
- package/dist/src/app-lifecycle2.js +1 -1
- package/dist/src/app-log-files.js +1 -1
- package/dist/src/app-log-runtime2.js +1 -1
- package/dist/src/app-resolution.js +2 -2
- package/dist/src/apple-runner-platform.js +1 -1
- package/dist/src/application-lifecycle-interaction.js +1 -1
- package/dist/src/archive.js +1 -1
- package/dist/src/artifact-download.js +1 -1
- package/dist/src/auth-session.js +1 -1
- package/dist/src/auth.js +1 -1
- package/dist/src/back-mode.js +1 -1
- package/dist/src/backend-snapshot-options.js +1 -1
- package/dist/src/boot-diagnostics.js +1 -1
- package/dist/src/cache.js +1 -0
- package/dist/src/capture.js +1 -1
- package/dist/src/cli-config.js +1 -1
- package/dist/src/cli-help.js +9 -7
- package/dist/src/cli-runner.js +1 -1
- package/dist/src/cli.js +4 -4
- package/dist/src/click-button.js +1 -1
- package/dist/src/client-metro-companion.js +1 -1
- package/dist/src/client-metro.js +1 -1
- package/dist/src/client-types.d.ts +6 -6
- package/dist/src/clipboard-shell-response.js +1 -1
- package/dist/src/code-signature-cache.js +1 -1
- package/dist/src/command-schema.js +3 -3
- package/dist/src/command-tools.js +3 -3
- package/dist/src/connection-runtime.js +1 -1
- package/dist/src/connection.js +5 -5
- package/dist/src/contracts.js +1 -1
- package/dist/src/daemon-client-lease-beat.js +1 -0
- package/dist/src/daemon-client-lifecycle.js +1 -1
- package/dist/src/daemon-client-transport.js +1 -0
- package/dist/src/daemon-policy-file.js +1 -0
- package/dist/src/daemon-process.js +1 -1
- package/dist/src/daemon-registration.js +1 -1
- package/dist/src/daemon-shutdown-report.js +1 -1
- package/dist/src/daemon.js +1 -1
- package/dist/src/debug-symbols-facade.js +1 -1
- package/dist/src/device-claim-inspection.js +1 -1
- package/dist/src/device-claim-rule.js +1 -2
- package/dist/src/device-inventory-context.js +1 -1
- package/dist/src/{stop-sequence.js → device-readiness-runtime.js} +1 -1
- package/dist/src/device-rotation.js +1 -1
- package/dist/src/device-selection-resolver.js +1 -1
- package/dist/src/device-session.js +1 -1
- package/dist/src/device-shell.js +1 -1
- package/dist/src/device.js +1 -1
- package/dist/src/device2.js +1 -1
- package/dist/src/devicectl.js +1 -1
- package/dist/src/diagnostics.js +1 -1
- package/dist/src/dispatch-resolve.js +1 -1
- package/dist/src/doctor-facade.js +1 -1
- package/dist/src/doctor2.js +1 -1
- package/dist/src/durable-resource.js +1 -1
- package/dist/src/engine-eval-script.js +1 -1
- package/dist/src/error.js +1 -1
- package/dist/src/exec.js +1 -1
- package/dist/src/file.js +1 -1
- package/dist/src/find.js +1 -1
- package/dist/src/format.js +1 -2
- package/dist/src/gesture-admission.js +1 -1
- package/dist/src/gesture-plan.js +1 -1
- package/dist/src/gesture-runtime.js +1 -1
- package/dist/src/harmonyos.js +1 -1
- package/dist/src/helper.js +2 -2
- package/dist/src/host.js +1 -1
- package/dist/src/human-control-contract.js +1 -1
- package/dist/src/human-control.js +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/input-actions.js +1 -1
- package/dist/src/input-actions2.js +1 -1
- package/dist/src/install-artifact-facade.js +1 -1
- package/dist/src/install-source-network-transport.js +1 -1
- package/dist/src/install-source2.js +1 -1
- package/dist/src/interaction-positionals.js +1 -1
- package/dist/src/interaction-ref-policy.js +1 -1
- package/dist/src/interaction2.js +1 -1
- package/dist/src/interactor-operation-binding.js +1 -1
- package/dist/src/interactor.js +5 -4
- package/dist/src/interactor2.js +1 -1
- package/dist/src/interactors.js +1 -1
- package/dist/src/internal/bin.js +1 -1
- package/dist/src/internal/daemon.js +2 -2
- package/dist/src/internal/png-worker.js +1 -1
- package/dist/src/internal/run-script-http-child.js +1 -1
- package/dist/src/inventory.js +1 -1
- package/dist/src/inventory2.js +1 -1
- package/dist/src/inventory3.js +1 -1
- package/dist/src/inventory5.js +1 -1
- package/dist/src/ios-snapshot-engine.js +1 -1
- package/dist/src/ios-snapshot-runtime.js +1 -1
- package/dist/src/lease-context.js +1 -1
- package/dist/src/lease-scope.js +1 -1
- package/dist/src/lease.js +1 -1
- package/dist/src/legacy-xctest-device-set.js +1 -0
- package/dist/src/limrun-download-file.js +1 -1
- package/dist/src/limrun-runtime-dependencies.js +1 -1
- package/dist/src/limrun.d.ts +1 -1
- package/dist/src/linux.js +1 -1
- package/dist/src/location-coordinates.js +1 -1
- package/dist/src/managed-device-scope.js +1 -1
- package/dist/src/mechanics.js +16 -16
- package/dist/src/multitouch-support.js +1 -1
- package/dist/src/observation.js +1 -1
- package/dist/src/overlay.js +3 -3
- package/dist/src/owner-identity.js +3 -3
- package/dist/src/parameterized-recorded-fill.js +1 -1
- package/dist/src/perf-facade.js +1 -1
- package/dist/src/perf-runtime-operation-builder.js +1 -1
- package/dist/src/perf-runtime-plan.js +1 -1
- package/dist/src/perf-target.js +1 -1
- package/dist/src/perf.js +1 -1
- package/dist/src/physical-device-control.js +1 -1
- package/dist/src/platform-request-scope.js +1 -1
- package/dist/src/platform-runtime-app-log-process.js +1 -1
- package/dist/src/platform-runtime-managed-owner.js +1 -1
- package/dist/src/platform-runtime-open-target.js +1 -1
- package/dist/src/platform-runtime-operation-host.js +2 -2
- package/dist/src/platform-runtime-runtime-hints.js +1 -1
- package/dist/src/platform-runtime-screen-recording-apple-simulator-host.js +1 -1
- package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
- package/dist/src/platform-runtime-screen-recording-process-host.js +1 -1
- package/dist/src/platform-runtime-unavailable.js +1 -1
- package/dist/src/platform-runtime.js +1 -1
- package/dist/src/platform-runtime2.js +1 -1
- package/dist/src/png-crop-bytes.js +1 -1
- package/dist/src/png-size.js +1 -1
- package/dist/src/png-worker-client.js +1 -1
- package/dist/src/png.js +1 -1
- package/dist/src/prepare-kind.js +1 -1
- package/dist/src/process.js +1 -1
- package/dist/src/provider-session-ownership.js +1 -1
- package/dist/src/provider-webdriver.js +1 -1
- package/dist/src/proxy.js +2 -2
- package/dist/src/quality-warnings.js +1 -1
- package/dist/src/react-native.js +1 -1
- package/dist/src/record-runtime.js +1 -1
- package/dist/src/recording.js +1 -1
- package/dist/src/rect-center.js +1 -1
- package/dist/src/register-builtins.js +1 -1
- package/dist/src/registry.js +36 -36
- package/dist/src/remote-config2.js +1 -1
- package/dist/src/replay-divergence.js +1 -1
- package/dist/src/replay.js +1 -1
- package/dist/src/reporting.js +1 -1
- package/dist/src/request-admission.js +1 -1
- package/dist/src/request-cancel.js +1 -1
- package/dist/src/request-runtime-binding.js +1 -1
- package/dist/src/result-serialization.js +1 -1
- package/dist/src/retry.js +1 -1
- package/dist/src/runner-artifact-manifest.js +1 -0
- package/dist/src/runner-cache-metadata.js +3 -2
- package/dist/src/runner-client.js +1 -1
- package/dist/src/runner-command-traits.js +1 -0
- package/dist/src/runner-dev-tools-security.js +1 -1
- package/dist/src/runner-device-readiness.js +1 -1
- package/dist/src/runner-disposal.js +1 -1
- package/dist/src/runner-error-classification.js +3 -3
- package/dist/src/runner-exchange.js +1 -0
- package/dist/src/runner-operations-facade.js +1 -1
- package/dist/src/runner-owner-facade.js +1 -1
- package/dist/src/runner-session-types.js +1 -0
- package/dist/src/runner-start-budget.js +1 -0
- package/dist/src/runner-startup-transport.js +1 -0
- package/dist/src/runner-xctestrun.js +1 -0
- package/dist/src/runner.js +1 -1
- package/dist/src/runtime-transport-hints.js +1 -1
- package/dist/src/runtime.js +1 -1
- package/dist/src/runtime10.js +2 -2
- package/dist/src/runtime2.js +3 -3
- package/dist/src/runtime3.js +1 -1
- package/dist/src/runtime4.js +5 -5
- package/dist/src/runtime5.js +1 -1
- package/dist/src/runtime6.js +1 -1
- package/dist/src/runtime7.js +1 -1
- package/dist/src/runtime8.js +1 -1
- package/dist/src/runtime9.js +1 -1
- package/dist/src/screen-recording-admission-ledger.js +1 -1
- package/dist/src/screen-recording-completion.js +1 -1
- package/dist/src/screen-recording-options.js +1 -1
- package/dist/src/screen-recording-resource-recovery.js +1 -1
- package/dist/src/screen-recording-transport.js +1 -1
- package/dist/src/screenshot-density.js +1 -1
- package/dist/src/screenshot-image.js +1 -1
- package/dist/src/screenshot-runtime.js +1 -1
- package/dist/src/screenshot.js +1 -1
- package/dist/src/screenshot3.js +1 -1
- package/dist/src/script.js +1 -1
- package/dist/src/scroll-command.d.ts +4 -1
- package/dist/src/scroll-command.js +1 -1
- package/dist/src/scroll-gesture.js +1 -1
- package/dist/src/sdk-batch-runner.js +2 -2
- package/dist/src/sdk-batch.js +2 -2
- package/dist/src/sdk-contracts.d.ts +10 -0
- package/dist/src/sdk-contracts.js +2 -2
- package/dist/src/sdk-device.js +1 -1
- package/dist/src/sdk-io.js +1 -1
- package/dist/src/sdk-remote-config.js +1 -1
- package/dist/src/selection.js +0 -0
- package/dist/src/selector-runtime.js +1 -1
- package/dist/src/server.js +1 -1
- package/dist/src/session-allocation.js +1 -1
- package/dist/src/session-routing.js +1 -1
- package/dist/src/session-runtime-admission.js +1 -1
- package/dist/src/session-snapshot.js +1 -1
- package/dist/src/session-store.js +3 -3
- package/dist/src/session2.js +5 -4
- package/dist/src/settings.js +1 -1
- package/dist/src/settings2.js +1 -1
- package/dist/src/simctl.js +1 -1
- package/dist/src/simulator-boot.js +1 -1
- package/dist/src/simulator.js +1 -1
- package/dist/src/snapshot-runtime-binding.js +1 -1
- package/dist/src/snapshot-runtime.js +1 -1
- package/dist/src/snapshot-state.js +1 -1
- package/dist/src/snapshot2.js +1 -1
- package/dist/src/snapshot3.js +1 -1
- package/dist/src/snapshot4.js +1 -1
- package/dist/src/src.js +1 -1
- package/dist/src/src7.js +1 -1
- package/dist/src/src8.js +2 -2
- package/dist/src/string-enum.js +1 -1
- package/dist/src/takeover.js +1 -1
- package/dist/src/target-evidence.js +1 -0
- package/dist/src/text-entry-focus.js +1 -1
- package/dist/src/tool-provider.js +1 -1
- package/dist/src/tool-provider2.js +1 -1
- package/dist/src/touch-runtime.js +1 -1
- package/dist/src/transport.js +2 -2
- package/dist/src/tv-remote.js +1 -1
- package/dist/src/type-text-runtime.js +1 -1
- package/dist/src/vars.js +2 -0
- package/dist/src/verified-file.js +1 -1
- package/dist/src/video.js +1 -1
- package/dist/src/viewport.js +1 -1
- package/dist/src/web.js +1 -1
- package/dist/src/web2.js +1 -1
- package/dist/src/webdriver-source.js +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.15.apk.sha256 +0 -1
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.15.apk.sha256 +0 -1
- package/dist/src/capture-disclosure.js +0 -1
- package/dist/src/fold-runtime.js +0 -1
- package/dist/src/native-build-cache.js +0 -1
|
@@ -42,8 +42,16 @@ extension RunnerTests {
|
|
|
42
42
|
#if os(iOS)
|
|
43
43
|
let postStartedAt = Date()
|
|
44
44
|
let result = replacingExistingText
|
|
45
|
-
? RunnerSynthesizedTextEntry.replaceText(
|
|
46
|
-
|
|
45
|
+
? RunnerSynthesizedTextEntry.replaceText(
|
|
46
|
+
withApplication: app,
|
|
47
|
+
text: text,
|
|
48
|
+
charactersPerSecond: TextEntryTiming.synthesizedCharactersPerSecond
|
|
49
|
+
)
|
|
50
|
+
: RunnerSynthesizedTextEntry.synthesizeText(
|
|
51
|
+
withApplication: app,
|
|
52
|
+
text: text,
|
|
53
|
+
charactersPerSecond: TextEntryTiming.synthesizedCharactersPerSecond
|
|
54
|
+
)
|
|
47
55
|
NSLog(
|
|
48
56
|
"[DEBUG-1874] synthesize posted %d chars status=%d tookMs=%.0f",
|
|
49
57
|
text.count, result.status.rawValue, postStartedAt.timeIntervalSinceNow * -1000
|
|
@@ -73,90 +81,206 @@ extension RunnerTests {
|
|
|
73
81
|
#endif
|
|
74
82
|
}
|
|
75
83
|
|
|
76
|
-
struct SynthesizedReplacementStep: Equatable {
|
|
77
|
-
let text: String
|
|
78
|
-
let replacesExistingText: Bool
|
|
79
|
-
}
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
struct SynthesizedTextPlan: Equatable {
|
|
89
|
+
|
|
90
|
+
struct Step: Equatable {
|
|
91
|
+
let characterCount: Int
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
var replacesExistingText = false
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
var pauseAfterSeconds: TimeInterval = 0
|
|
100
|
+
|
|
101
|
+
var warmsUpField = false
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
var synthesizeCallCount: Int {
|
|
105
|
+
replacesExistingText ? 2 : 1
|
|
106
|
+
}
|
|
88
107
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
108
|
+
|
|
109
|
+
let steps: [Step]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
static func spacedSteps(
|
|
114
|
+
characterCount: Int,
|
|
115
|
+
delaySeconds: Double,
|
|
116
|
+
replacesExistingTextOnFirstPost: Bool = false
|
|
117
|
+
) -> [Step] {
|
|
118
|
+
(0..<characterCount).map { index in
|
|
119
|
+
Step(
|
|
120
|
+
characterCount: 1,
|
|
121
|
+
replacesExistingText: replacesExistingTextOnFirstPost && index == 0,
|
|
122
|
+
pauseAfterSeconds: index + 1 < characterCount ? delaySeconds : 0
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
var seconds: TimeInterval {
|
|
130
|
+
steps.reduce(0) { total, step in
|
|
131
|
+
total
|
|
132
|
+
+ Double(step.characterCount) * TextEntryTiming.synthesizedCharacterInterval
|
|
133
|
+
+ Double(step.synthesizeCallCount) * TextEntryTiming.synthesizeCallOverhead
|
|
134
|
+
+ step.pauseAfterSeconds
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
var pacesEveryCharacter: Bool {
|
|
142
|
+
steps.count > 1 && !steps.contains(where: \.warmsUpField)
|
|
94
143
|
}
|
|
95
144
|
}
|
|
96
145
|
|
|
97
146
|
|
|
98
147
|
|
|
99
|
-
static func
|
|
148
|
+
static func synthesizedDeliveryIsSpaced(characterCount: Int, delaySeconds: Double) -> Bool {
|
|
100
149
|
delaySeconds > 0 && characterCount > 1
|
|
101
150
|
}
|
|
102
151
|
|
|
103
152
|
|
|
104
153
|
|
|
105
154
|
|
|
106
|
-
enum SynthesizedDeliveryBudget {
|
|
107
155
|
|
|
108
|
-
static var characterInterval: TimeInterval {
|
|
109
|
-
1.0 / Double(RunnerSynthesizedTextEntry.typingSpeedCharactersPerSecond())
|
|
110
|
-
}
|
|
111
156
|
|
|
112
157
|
|
|
113
158
|
|
|
114
159
|
|
|
160
|
+
static func synthesizedTextPlan(
|
|
161
|
+
characterCount: Int,
|
|
162
|
+
delaySeconds: Double,
|
|
163
|
+
selectsExistingText: Bool,
|
|
164
|
+
peelsWarmupCharacter: Bool = false
|
|
165
|
+
) -> SynthesizedTextPlan {
|
|
166
|
+
if synthesizedDeliveryIsSpaced(characterCount: characterCount, delaySeconds: delaySeconds) {
|
|
167
|
+
return SynthesizedTextPlan(
|
|
168
|
+
steps: SynthesizedTextPlan.spacedSteps(
|
|
169
|
+
characterCount: characterCount,
|
|
170
|
+
delaySeconds: delaySeconds,
|
|
171
|
+
replacesExistingTextOnFirstPost: selectsExistingText
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
guard peelsWarmupCharacter && characterCount > 1 else {
|
|
176
|
+
return SynthesizedTextPlan(
|
|
177
|
+
steps: [
|
|
178
|
+
SynthesizedTextPlan.Step(
|
|
179
|
+
characterCount: characterCount,
|
|
180
|
+
replacesExistingText: selectsExistingText
|
|
181
|
+
),
|
|
182
|
+
]
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
return SynthesizedTextPlan(
|
|
186
|
+
steps: [
|
|
187
|
+
SynthesizedTextPlan.Step(
|
|
188
|
+
characterCount: 1,
|
|
189
|
+
pauseAfterSeconds: TextEntryTiming.pollInterval,
|
|
190
|
+
warmsUpField: true
|
|
191
|
+
),
|
|
192
|
+
SynthesizedTextPlan.Step(characterCount: characterCount - 1),
|
|
193
|
+
]
|
|
194
|
+
)
|
|
195
|
+
}
|
|
115
196
|
|
|
116
197
|
|
|
117
|
-
static func projectedSeconds(
|
|
118
|
-
textLength: Int,
|
|
119
|
-
delaySeconds: TimeInterval,
|
|
120
|
-
typeWarmup: Bool = false
|
|
121
|
-
) -> TimeInterval {
|
|
122
|
-
let spaced = synthesizedReplacementIsSpaced(characterCount: textLength, delaySeconds: delaySeconds)
|
|
123
|
-
let warmupSplit = typeWarmup && textLength > 1 && !spaced
|
|
124
|
-
let calls = spaced ? textLength : (warmupSplit ? 2 : 1)
|
|
125
|
-
return Double(textLength) * characterInterval
|
|
126
|
-
+ Double(calls) * TextEntryTiming.synthesizeCallOverhead
|
|
127
|
-
+ Double(calls - 1) * delaySeconds
|
|
128
|
-
+ (warmupSplit ? TextEntryTiming.pollInterval : 0)
|
|
129
|
-
}
|
|
130
198
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
typeWarmup: Bool = false
|
|
135
|
-
) -> Bool {
|
|
136
|
-
projectedSeconds(textLength: textLength, delaySeconds: delaySeconds, typeWarmup: typeWarmup)
|
|
137
|
-
> TextEntryTiming.synthesizedDeliveryCeiling
|
|
199
|
+
enum SynthesizedDeliveryBudget {
|
|
200
|
+
static func exceeds(_ plan: SynthesizedTextPlan) -> Bool {
|
|
201
|
+
plan.seconds > TextEntryTiming.synthesizedDeliveryCeiling
|
|
138
202
|
}
|
|
139
203
|
|
|
140
204
|
|
|
205
|
+
|
|
206
|
+
|
|
141
207
|
static func maxTextLength(delaySeconds: TimeInterval) -> Int {
|
|
142
208
|
var length = 1
|
|
143
|
-
while !exceeds(
|
|
209
|
+
while !exceeds(
|
|
210
|
+
synthesizedTextPlan(
|
|
211
|
+
characterCount: length + 1,
|
|
212
|
+
delaySeconds: delaySeconds,
|
|
213
|
+
selectsExistingText: true
|
|
214
|
+
)
|
|
215
|
+
) {
|
|
144
216
|
length += 1
|
|
145
217
|
}
|
|
146
218
|
return length
|
|
147
219
|
}
|
|
148
220
|
}
|
|
149
221
|
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
enum SynthesizedStepDispatch {
|
|
225
|
+
case posted
|
|
226
|
+
case stop
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
struct SynthesizedPlanRun {
|
|
230
|
+
let postedCharacterCount: Int
|
|
231
|
+
|
|
232
|
+
let stoppedEarly: Bool
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@MainActor
|
|
246
|
+
func runSynthesizedTextPlan(
|
|
247
|
+
_ plan: SynthesizedTextPlan,
|
|
248
|
+
text: String,
|
|
249
|
+
post: (_ characters: String, _ step: SynthesizedTextPlan.Step) -> SynthesizedStepDispatch,
|
|
250
|
+
waitAfterWarmupCharacter: (_ characters: String) -> Void,
|
|
251
|
+
didPostStep: (_ step: SynthesizedTextPlan.Step) -> Void = { _ in }
|
|
252
|
+
) -> SynthesizedPlanRun {
|
|
253
|
+
let characters = Array(text)
|
|
254
|
+
var postedCount = 0
|
|
255
|
+
for step in plan.steps {
|
|
256
|
+
let nextCount = postedCount + step.characterCount
|
|
257
|
+
if post(String(characters[postedCount..<nextCount]), step) == .stop {
|
|
258
|
+
|
|
259
|
+
return SynthesizedPlanRun(postedCharacterCount: postedCount, stoppedEarly: true)
|
|
260
|
+
}
|
|
261
|
+
postedCount = nextCount
|
|
262
|
+
didPostStep(step)
|
|
263
|
+
if step.warmsUpField {
|
|
264
|
+
waitAfterWarmupCharacter(String(characters[postedCount - step.characterCount..<postedCount]))
|
|
265
|
+
} else if step.pauseAfterSeconds > 0 {
|
|
266
|
+
sleepFor(step.pauseAfterSeconds)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return SynthesizedPlanRun(postedCharacterCount: postedCount, stoppedEarly: false)
|
|
270
|
+
}
|
|
271
|
+
|
|
150
272
|
@MainActor
|
|
151
273
|
func runSynthesizedReplacementRoute(
|
|
152
274
|
_ request: SynthesizedReplacementRequest
|
|
153
275
|
) -> SynthesizedReplacementRouteOutcome {
|
|
154
276
|
#if os(iOS)
|
|
155
277
|
NSLog("AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=synthesized-first-responder-replacement")
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
delaySeconds: request.delaySeconds
|
|
159
|
-
|
|
278
|
+
let plan = Self.synthesizedTextPlan(
|
|
279
|
+
characterCount: request.text.count,
|
|
280
|
+
delaySeconds: request.delaySeconds,
|
|
281
|
+
selectsExistingText: true
|
|
282
|
+
)
|
|
283
|
+
if SynthesizedDeliveryBudget.exceeds(plan) {
|
|
160
284
|
NSLog(
|
|
161
285
|
"AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=synthesized-first-responder-replacement "
|
|
162
286
|
+ "reason=delivery-budget-refused chars=%d budgetChars=%d",
|
|
@@ -174,33 +298,38 @@ extension RunnerTests {
|
|
|
174
298
|
)
|
|
175
299
|
)
|
|
176
300
|
}
|
|
177
|
-
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
let synthesisAvailable = runSynthesizedTextPlan(
|
|
304
|
+
plan,
|
|
178
305
|
text: request.text,
|
|
179
|
-
|
|
306
|
+
post: { slice, step in
|
|
307
|
+
switch request.synthesizer.enterText(
|
|
308
|
+
app: request.app,
|
|
309
|
+
text: slice,
|
|
310
|
+
replacingExistingText: step.replacesExistingText
|
|
311
|
+
) {
|
|
312
|
+
case .continueTyping:
|
|
313
|
+
return .posted
|
|
314
|
+
case .fallback:
|
|
315
|
+
return .stop
|
|
316
|
+
case .raise(let message):
|
|
317
|
+
NSException(
|
|
318
|
+
name: NSExceptionName.internalInconsistencyException,
|
|
319
|
+
reason: message ?? "private XCTest text synthesis failed"
|
|
320
|
+
).raise()
|
|
321
|
+
return .stop
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
waitAfterWarmupCharacter: { _ in }
|
|
180
326
|
)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
)
|
|
187
|
-
case .fallback:
|
|
188
|
-
NSLog("AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=verified-fallback reason=synthesis-unavailable")
|
|
189
|
-
guard let point = request.target.refreshPoint else { return .notApplicable }
|
|
190
|
-
return .fallback(
|
|
191
|
-
focusTextInputForTextEntry(app: request.app, x: point.x, y: point.y)
|
|
192
|
-
)
|
|
193
|
-
case .raise(let message):
|
|
194
|
-
NSException(
|
|
195
|
-
name: NSExceptionName.internalInconsistencyException,
|
|
196
|
-
reason: message ?? "private XCTest text synthesis failed"
|
|
197
|
-
).raise()
|
|
198
|
-
case .continueTyping:
|
|
199
|
-
break
|
|
200
|
-
}
|
|
201
|
-
if index + 1 < steps.count {
|
|
202
|
-
sleepFor(request.delaySeconds)
|
|
203
|
-
}
|
|
327
|
+
if synthesisAvailable.stoppedEarly {
|
|
328
|
+
NSLog("AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=verified-fallback reason=synthesis-unavailable")
|
|
329
|
+
guard let point = request.target.refreshPoint else { return .notApplicable }
|
|
330
|
+
return .fallback(
|
|
331
|
+
focusTextInputForTextEntry(app: request.app, x: point.x, y: point.y)
|
|
332
|
+
)
|
|
204
333
|
}
|
|
205
334
|
|
|
206
335
|
|
package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift
CHANGED
|
@@ -36,7 +36,11 @@ extension RunnerTests {
|
|
|
36
36
|
let recoveryBudget = SynthesizedDeliveryBudget.maxTextLength(
|
|
37
37
|
delaySeconds: Double(recoveryDelay) / 1000
|
|
38
38
|
)
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
return "Fill at most \(SynthesizedDeliveryBudget.maxTextLength(delaySeconds: 0)) characters per command without --delay-ms and append the rest with separate type commands. --delay-ms lowers the limit: each character then gets its own synthesize call and each gap pays the delay, so \(recoveryDelay) ms fits \(recoveryBudget). This route is chosen when the accessibility channel is already degraded, so a longer timeout does not help."
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
46
|
}
|
|
@@ -76,7 +80,18 @@ extension RunnerTests {
|
|
|
76
80
|
|
|
77
81
|
|
|
78
82
|
|
|
79
|
-
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
static let synthesizedCharactersPerSecond: UInt = 12
|
|
93
|
+
|
|
94
|
+
static let synthesizedCharacterInterval: TimeInterval = 1.0 / Double(synthesizedCharactersPerSecond)
|
|
80
95
|
|
|
81
96
|
|
|
82
97
|
|
package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift
CHANGED
|
@@ -136,6 +136,39 @@ extension RunnerTests {
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
let synthesizedTypePlan = Self.synthesizedTextPlan(
|
|
145
|
+
characterCount: text.count,
|
|
146
|
+
delaySeconds: delaySeconds,
|
|
147
|
+
selectsExistingText: false,
|
|
148
|
+
peelsWarmupCharacter: repairMode != .none
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
let synthesizedTypeCarriesTheCommand = !SynthesizedDeliveryBudget.exceeds(synthesizedTypePlan)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
func typeApplicationWide(_ value: String, reason: String) -> (element: XCUIElement?, failure: TextEntryFailure?) {
|
|
161
|
+
NSLog(
|
|
162
|
+
"AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=xctest-application-fallback reason=%@ chars=%d commandChars=%d",
|
|
163
|
+
reason,
|
|
164
|
+
value.count,
|
|
165
|
+
text.count
|
|
166
|
+
)
|
|
167
|
+
textEntryRoute = "xctest-application-fallback"
|
|
168
|
+
app.typeText(value)
|
|
169
|
+
return (resolveTextEntryElement(app: app, target: activeTarget), nil)
|
|
170
|
+
}
|
|
171
|
+
|
|
139
172
|
func typeIntoCurrentTarget(_ value: String) -> (element: XCUIElement?, failure: TextEntryFailure?) {
|
|
140
173
|
#if os(iOS)
|
|
141
174
|
if shouldUseSynthesizedFirstResponderType {
|
|
@@ -167,35 +200,14 @@ extension RunnerTests {
|
|
|
167
200
|
|
|
168
201
|
|
|
169
202
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if SynthesizedDeliveryBudget.exceeds(
|
|
175
|
-
textLength: text.count,
|
|
176
|
-
delaySeconds: delaySeconds,
|
|
177
|
-
typeWarmup: repairMode != .none
|
|
178
|
-
) {
|
|
179
|
-
textEntryRoute = "xctest-application-fallback"
|
|
180
|
-
NSLog(
|
|
181
|
-
"AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=xctest-application-fallback "
|
|
182
|
-
+ "reason=delivery-budget chars=%d",
|
|
183
|
-
value.count
|
|
184
|
-
)
|
|
185
|
-
app.typeText(value)
|
|
186
|
-
return (resolveTextEntryElement(app: app, target: activeTarget), nil)
|
|
203
|
+
guard synthesizedTypeCarriesTheCommand else {
|
|
204
|
+
return typeApplicationWide(value, reason: "delivery-budget")
|
|
187
205
|
}
|
|
188
206
|
textEntryRoute = "synthesized-first-responder"
|
|
189
207
|
NSLog("AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=synthesized-first-responder")
|
|
190
|
-
|
|
191
|
-
app: app,
|
|
192
|
-
text: value,
|
|
193
|
-
replacingExistingText: false
|
|
194
|
-
)
|
|
195
|
-
switch action {
|
|
208
|
+
switch synthesizer.enterText(app: app, text: value, replacingExistingText: false) {
|
|
196
209
|
case .fallback:
|
|
197
|
-
|
|
198
|
-
app.typeText(value)
|
|
210
|
+
return typeApplicationWide(value, reason: "synthesis-unavailable")
|
|
199
211
|
case .raise(let message):
|
|
200
212
|
NSException(
|
|
201
213
|
name: NSExceptionName.internalInconsistencyException,
|
|
@@ -223,131 +235,100 @@ extension RunnerTests {
|
|
|
223
235
|
)
|
|
224
236
|
}
|
|
225
237
|
|
|
226
|
-
func
|
|
227
|
-
|
|
238
|
+
func readBackWarmupCharacter(_ peeledCharacter: String) {
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
let warmupExpectedText = expectedTextEntryValue(
|
|
243
|
+
typedText: peeledCharacter,
|
|
244
|
+
mode: repairMode,
|
|
245
|
+
initialText: initialText
|
|
246
|
+
)
|
|
247
|
+
guard let warmupExpectedText else {
|
|
228
248
|
sleepFor(TextEntryTiming.pollInterval)
|
|
229
249
|
return
|
|
230
250
|
}
|
|
231
251
|
let deadline = Date().addingTimeInterval(TextEntryTiming.warmupValueTimeout)
|
|
232
252
|
while Date() < deadline {
|
|
233
|
-
if editableTextValue(for: resolveTextEntryElement(app: app, target:
|
|
253
|
+
if editableTextValue(for: resolveTextEntryElement(app: app, target: activeTarget)) == warmupExpectedText {
|
|
234
254
|
return
|
|
235
255
|
}
|
|
236
256
|
sleepFor(TextEntryTiming.pollInterval)
|
|
237
257
|
}
|
|
238
258
|
}
|
|
239
259
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
var typedTarget: XCUIElement?
|
|
263
|
+
var sawWarmupPost = false
|
|
264
|
+
var planFailure: TextEntryResult?
|
|
265
|
+
var stepStartedAt = Date()
|
|
266
|
+
let pacedStartedAt = Date()
|
|
267
|
+
runSynthesizedTextPlan(
|
|
268
|
+
synthesizedTypePlan,
|
|
269
|
+
text: text,
|
|
270
|
+
post: { slice, step in
|
|
271
|
+
stepStartedAt = Date()
|
|
272
|
+
let dispatch = typeIntoCurrentTarget(slice)
|
|
246
273
|
if let failure = dispatch.failure {
|
|
247
|
-
|
|
274
|
+
planFailure = dispatchFailureResult(failure)
|
|
275
|
+
return .stop
|
|
248
276
|
}
|
|
249
277
|
typedTarget = dispatch.element ?? typedTarget
|
|
250
|
-
if
|
|
251
|
-
|
|
278
|
+
if step.warmsUpField {
|
|
279
|
+
|
|
280
|
+
activeTarget = activeTarget.withElement(typedTarget)
|
|
252
281
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
282
|
+
return .posted
|
|
283
|
+
},
|
|
284
|
+
waitAfterWarmupCharacter: { peeledCharacter in
|
|
285
|
+
let warmupStartedAt = Date()
|
|
286
|
+
readBackWarmupCharacter(peeledCharacter)
|
|
287
|
+
logTextEntryPhase(
|
|
288
|
+
commandId: commandId,
|
|
289
|
+
phase: "warmup",
|
|
290
|
+
startedAt: warmupStartedAt,
|
|
291
|
+
chars: 1,
|
|
292
|
+
mode: repairMode
|
|
293
|
+
)
|
|
294
|
+
},
|
|
295
|
+
didPostStep: { step in
|
|
296
|
+
guard !synthesizedTypePlan.pacesEveryCharacter else {
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
|
+
let phase: String
|
|
300
|
+
if step.warmsUpField {
|
|
301
|
+
phase = "type-first"
|
|
302
|
+
sawWarmupPost = true
|
|
303
|
+
} else {
|
|
304
|
+
phase = sawWarmupPost ? "type-remaining" : "type-all"
|
|
305
|
+
}
|
|
306
|
+
logTextEntryPhase(
|
|
307
|
+
commandId: commandId,
|
|
308
|
+
phase: phase,
|
|
309
|
+
startedAt: stepStartedAt,
|
|
310
|
+
chars: step.characterCount,
|
|
311
|
+
mode: repairMode
|
|
269
312
|
)
|
|
270
313
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
expectedText: expectedText,
|
|
276
|
-
repairMode: repairMode
|
|
277
|
-
)
|
|
314
|
+
)
|
|
315
|
+
if synthesizedTypePlan.pacesEveryCharacter {
|
|
316
|
+
|
|
317
|
+
|
|
278
318
|
logTextEntryPhase(
|
|
279
319
|
commandId: commandId,
|
|
280
|
-
phase: "
|
|
281
|
-
startedAt:
|
|
282
|
-
chars:
|
|
320
|
+
phase: "type-delayed",
|
|
321
|
+
startedAt: pacedStartedAt,
|
|
322
|
+
chars: text.count,
|
|
283
323
|
mode: repairMode
|
|
284
324
|
)
|
|
285
|
-
logTextEntryPhase(commandId: commandId, phase: "total", startedAt: totalStartedAt, chars: text.count, mode: repairMode)
|
|
286
|
-
result.textEntryRoute = textEntryRoute
|
|
287
|
-
return result
|
|
288
325
|
}
|
|
289
326
|
|
|
290
|
-
|
|
291
|
-
if
|
|
292
|
-
|
|
293
|
-
let firstStartedAt = Date()
|
|
294
|
-
let firstDispatch = typeIntoCurrentTarget(firstCharacter)
|
|
295
|
-
if let failure = firstDispatch.failure {
|
|
296
|
-
return dispatchFailureResult(failure)
|
|
297
|
-
}
|
|
298
|
-
var firstTypedTarget = firstDispatch.element
|
|
299
|
-
logTextEntryPhase(
|
|
300
|
-
commandId: commandId,
|
|
301
|
-
phase: "type-first",
|
|
302
|
-
startedAt: firstStartedAt,
|
|
303
|
-
chars: 1,
|
|
304
|
-
mode: repairMode
|
|
305
|
-
)
|
|
306
|
-
activeTarget = activeTarget.withElement(firstTypedTarget)
|
|
307
|
-
let warmupExpectedText = expectedTextEntryValue(
|
|
308
|
-
typedText: firstCharacter,
|
|
309
|
-
mode: repairMode,
|
|
310
|
-
initialText: initialText
|
|
311
|
-
)
|
|
312
|
-
let warmupStartedAt = Date()
|
|
313
|
-
waitForWarmupValue(warmupExpectedText, target: activeTarget)
|
|
314
|
-
logTextEntryPhase(
|
|
315
|
-
commandId: commandId,
|
|
316
|
-
phase: "warmup",
|
|
317
|
-
startedAt: warmupStartedAt,
|
|
318
|
-
chars: 1,
|
|
319
|
-
mode: repairMode
|
|
320
|
-
)
|
|
321
|
-
let remainingText = String(characters.dropFirst())
|
|
322
|
-
let remainingStartedAt = Date()
|
|
323
|
-
let remainingDispatch = typeIntoCurrentTarget(remainingText)
|
|
324
|
-
if let failure = remainingDispatch.failure {
|
|
325
|
-
return dispatchFailureResult(failure)
|
|
326
|
-
}
|
|
327
|
-
firstTypedTarget = remainingDispatch.element ?? firstTypedTarget
|
|
328
|
-
logTextEntryPhase(
|
|
329
|
-
commandId: commandId,
|
|
330
|
-
phase: "type-remaining",
|
|
331
|
-
startedAt: remainingStartedAt,
|
|
332
|
-
chars: characters.count - 1,
|
|
333
|
-
mode: repairMode
|
|
334
|
-
)
|
|
335
|
-
typedTarget = firstTypedTarget
|
|
336
|
-
} else {
|
|
337
|
-
let typeStartedAt = Date()
|
|
338
|
-
let dispatch = typeIntoCurrentTarget(text)
|
|
339
|
-
if let failure = dispatch.failure {
|
|
340
|
-
return dispatchFailureResult(failure)
|
|
341
|
-
}
|
|
342
|
-
typedTarget = dispatch.element
|
|
343
|
-
logTextEntryPhase(
|
|
344
|
-
commandId: commandId,
|
|
345
|
-
phase: "type-all",
|
|
346
|
-
startedAt: typeStartedAt,
|
|
347
|
-
chars: characters.count,
|
|
348
|
-
mode: repairMode
|
|
349
|
-
)
|
|
327
|
+
|
|
328
|
+
if let failure = planFailure {
|
|
329
|
+
return failure
|
|
350
330
|
}
|
|
331
|
+
activeTarget = activeTarget.withElement(typedTarget)
|
|
351
332
|
if repairMode == .none {
|
|
352
333
|
logTextEntryPhase(commandId: commandId, phase: "total", startedAt: totalStartedAt, chars: text.count, mode: repairMode)
|
|
353
334
|
return TextEntryResult(
|
|
@@ -361,7 +342,7 @@ extension RunnerTests {
|
|
|
361
342
|
let verifyStartedAt = Date()
|
|
362
343
|
var result = verifyTextEntryWithRepairIfNeeded(
|
|
363
344
|
app: app,
|
|
364
|
-
target: activeTarget
|
|
345
|
+
target: activeTarget,
|
|
365
346
|
expectedText: expectedText,
|
|
366
347
|
repairMode: repairMode
|
|
367
348
|
)
|
|
@@ -369,7 +350,7 @@ extension RunnerTests {
|
|
|
369
350
|
commandId: commandId,
|
|
370
351
|
phase: "verify",
|
|
371
352
|
startedAt: verifyStartedAt,
|
|
372
|
-
chars:
|
|
353
|
+
chars: text.count,
|
|
373
354
|
mode: repairMode
|
|
374
355
|
)
|
|
375
356
|
logTextEntryPhase(commandId: commandId, phase: "total", startedAt: totalStartedAt, chars: text.count, mode: repairMode)
|