agent-device 0.15.0 → 0.15.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.
- package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.15.0.apk → agent-device-android-snapshot-helper-0.15.2.apk} +0 -0
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.15.2.apk.sha256 +1 -0
- package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.15.0.manifest.json → agent-device-android-snapshot-helper-0.15.2.manifest.json} +6 -6
- package/dist/src/1393.js +1 -0
- package/dist/src/1769.js +7 -7
- package/dist/src/1974.js +2 -2
- package/dist/src/208.js +1 -1
- package/dist/src/2151.js +31 -22
- package/dist/src/221.js +3 -3
- package/dist/src/3572.js +1 -1
- package/dist/src/4829.js +1 -1
- package/dist/src/9542.js +2 -2
- package/dist/src/989.js +1 -1
- package/dist/src/android-adb.js +1 -1
- package/dist/src/cli.js +45 -45
- package/dist/src/index.d.ts +38 -5
- package/dist/src/internal/daemon.js +46 -45
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +155 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +3 -25
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +5 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +1 -14
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +3 -3
- package/ios-runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +7 -1
- package/package.json +6 -1
- package/server.json +2 -2
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.15.0.apk.sha256 +0 -1
- package/dist/src/840.js +0 -2
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
|
|
3
|
+
extension RunnerTests {
|
|
4
|
+
struct RunnerAlert {
|
|
5
|
+
let root: XCUIElement
|
|
6
|
+
let ownerApp: XCUIApplication
|
|
7
|
+
let buttons: [XCUIElement]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
func resolveAlert(app activeApp: XCUIApplication) -> RunnerAlert? {
|
|
11
|
+
if let alert = firstExistingElement(in: activeApp.alerts.allElementsBoundByIndex) {
|
|
12
|
+
return runnerAlert(root: alert, ownerApp: activeApp)
|
|
13
|
+
}
|
|
14
|
+
if let popup = firstDismissPopupWindow(in: activeApp) {
|
|
15
|
+
return runnerAlert(root: popup, ownerApp: activeApp)
|
|
16
|
+
}
|
|
17
|
+
#if os(macOS)
|
|
18
|
+
return nil
|
|
19
|
+
#else
|
|
20
|
+
if let systemModal = firstBlockingSystemModal(in: springboard) {
|
|
21
|
+
return runnerAlert(root: systemModal, ownerApp: springboard)
|
|
22
|
+
}
|
|
23
|
+
return nil
|
|
24
|
+
#endif
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func handleAlert(_ alert: RunnerAlert, action: String) -> Response {
|
|
28
|
+
if action == "accept" || action == "dismiss" {
|
|
29
|
+
guard let button = chooseAlertButton(alert.buttons, action: action) else {
|
|
30
|
+
return Response(ok: false, error: ErrorPayload(message: "alert \(action) button not found"))
|
|
31
|
+
}
|
|
32
|
+
let outcome = activateElement(app: alert.ownerApp, element: button, action: "alert \(action)")
|
|
33
|
+
if let response = unsupportedResponse(for: outcome) {
|
|
34
|
+
return response
|
|
35
|
+
}
|
|
36
|
+
return Response(ok: true, data: DataPayload(message: action == "accept" ? "accepted" : "dismissed"))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return Response(
|
|
40
|
+
ok: true,
|
|
41
|
+
data: DataPayload(
|
|
42
|
+
message: preferredAlertTitle(alert.root, buttons: alert.buttons),
|
|
43
|
+
items: alert.buttons.map { $0.label.trimmingCharacters(in: .whitespacesAndNewlines) }
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private func runnerAlert(root: XCUIElement, ownerApp: XCUIApplication) -> RunnerAlert? {
|
|
49
|
+
let buttons = actionableElements(in: root).filter { isEnabledElement($0) }
|
|
50
|
+
guard !buttons.isEmpty else {
|
|
51
|
+
return nil
|
|
52
|
+
}
|
|
53
|
+
return RunnerAlert(root: root, ownerApp: ownerApp, buttons: buttons)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private func firstExistingElement(in elements: [XCUIElement]) -> XCUIElement? {
|
|
57
|
+
elements.first { isVisibleElement($0) }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private func firstDismissPopupWindow(in app: XCUIApplication) -> XCUIElement? {
|
|
61
|
+
safeElementsQuery {
|
|
62
|
+
app.windows.allElementsBoundByIndex
|
|
63
|
+
}.first { window in
|
|
64
|
+
if !isVisibleElement(window) { return false }
|
|
65
|
+
if isDismissPopupMarker(window.label) || isDismissPopupMarker(window.identifier) {
|
|
66
|
+
return true
|
|
67
|
+
}
|
|
68
|
+
return safeElementsQuery {
|
|
69
|
+
window.descendants(matching: .any).allElementsBoundByIndex
|
|
70
|
+
}.contains { descendant in
|
|
71
|
+
isDismissPopupMarker(descendant.label) || isDismissPopupMarker(descendant.identifier)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private func chooseAlertButton(_ buttons: [XCUIElement], action: String) -> XCUIElement? {
|
|
77
|
+
if action == "accept" {
|
|
78
|
+
if let accept = buttons.first(where: { isAcceptButton($0.label) }) {
|
|
79
|
+
return accept
|
|
80
|
+
}
|
|
81
|
+
return buttons.count == 1 && !isDismissButton(buttons[0].label) ? buttons[0] : nil
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return buttons.first(where: { isDismissButton($0.label) }) ?? buttons.last
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private func isAcceptButton(_ label: String) -> Bool {
|
|
88
|
+
let normalized = label.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
|
89
|
+
return [
|
|
90
|
+
"ok",
|
|
91
|
+
"allow",
|
|
92
|
+
"yes",
|
|
93
|
+
"continue",
|
|
94
|
+
"done",
|
|
95
|
+
"open settings"
|
|
96
|
+
].contains(normalized) || normalized.hasPrefix("confirm")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private func isDismissButton(_ label: String) -> Bool {
|
|
100
|
+
[
|
|
101
|
+
"cancel",
|
|
102
|
+
"close",
|
|
103
|
+
"dismiss",
|
|
104
|
+
"don't allow",
|
|
105
|
+
"don’t allow",
|
|
106
|
+
"not now",
|
|
107
|
+
"no",
|
|
108
|
+
"keep browsing",
|
|
109
|
+
"later"
|
|
110
|
+
].contains(label.trimmingCharacters(in: .whitespacesAndNewlines).lowercased())
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private func preferredAlertTitle(_ element: XCUIElement, buttons: [XCUIElement]) -> String {
|
|
114
|
+
let buttonLabels = Set(buttons.map { $0.label.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() })
|
|
115
|
+
let descendants = element.descendants(matching: .any).allElementsBoundByIndex
|
|
116
|
+
for descendant in descendants {
|
|
117
|
+
let text = descendant.label.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
118
|
+
if text.isEmpty ||
|
|
119
|
+
isGenericAlertLabel(text) ||
|
|
120
|
+
buttonLabels.contains(text.lowercased()) ||
|
|
121
|
+
descendant.elementType == .navigationBar ||
|
|
122
|
+
actionableTypes.contains(descendant.elementType)
|
|
123
|
+
{
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
return text
|
|
127
|
+
}
|
|
128
|
+
let label = element.label.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
129
|
+
return label.isEmpty || isGenericAlertLabel(label) ? "Alert" : label
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private func isGenericAlertLabel(_ label: String) -> Bool {
|
|
133
|
+
let normalized = label.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
|
134
|
+
return isDismissPopupMarker(normalized) ||
|
|
135
|
+
normalized.hasPrefix("vertical scroll bar") ||
|
|
136
|
+
normalized.hasPrefix("horizontal scroll bar") ||
|
|
137
|
+
normalized == "tab bar"
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private func isVisibleElement(_ element: XCUIElement) -> Bool {
|
|
141
|
+
element.exists && !element.frame.isNull && !element.frame.isEmpty
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private func isEnabledElement(_ element: XCUIElement) -> Bool {
|
|
145
|
+
var enabled = false
|
|
146
|
+
_ = RunnerObjCExceptionCatcher.catchException({
|
|
147
|
+
enabled = element.exists && element.isEnabled
|
|
148
|
+
})
|
|
149
|
+
return enabled
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private func isDismissPopupMarker(_ label: String) -> Bool {
|
|
153
|
+
label.trimmingCharacters(in: .whitespacesAndNewlines).caseInsensitiveCompare("dismiss popup") == .orderedSame
|
|
154
|
+
}
|
|
155
|
+
}
|
package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift
CHANGED
|
@@ -13,7 +13,7 @@ extension RunnerTests {
|
|
|
13
13
|
return (gestureStartUptimeMs, currentUptimeMs())
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
func unsupportedResponse(for outcome: RunnerInteractionOutcome) -> Response? {
|
|
17
17
|
switch outcome {
|
|
18
18
|
case .performed:
|
|
19
19
|
return nil
|
|
@@ -731,32 +731,10 @@ extension RunnerTests {
|
|
|
731
731
|
)
|
|
732
732
|
case .alert:
|
|
733
733
|
let action = (command.action ?? "get").lowercased()
|
|
734
|
-
let alert = activeApp
|
|
735
|
-
if !alert.exists {
|
|
734
|
+
guard let alert = resolveAlert(app: activeApp) else {
|
|
736
735
|
return Response(ok: false, error: ErrorPayload(message: "alert not found"))
|
|
737
736
|
}
|
|
738
|
-
|
|
739
|
-
guard let button = alert.buttons.allElementsBoundByIndex.first else {
|
|
740
|
-
return Response(ok: false, error: ErrorPayload(message: "alert accept button not found"))
|
|
741
|
-
}
|
|
742
|
-
let outcome = activateElement(app: activeApp, element: button, action: "alert accept")
|
|
743
|
-
if let response = unsupportedResponse(for: outcome) {
|
|
744
|
-
return response
|
|
745
|
-
}
|
|
746
|
-
return Response(ok: true, data: DataPayload(message: "accepted"))
|
|
747
|
-
}
|
|
748
|
-
if action == "dismiss" {
|
|
749
|
-
guard let button = alert.buttons.allElementsBoundByIndex.last else {
|
|
750
|
-
return Response(ok: false, error: ErrorPayload(message: "alert dismiss button not found"))
|
|
751
|
-
}
|
|
752
|
-
let outcome = activateElement(app: activeApp, element: button, action: "alert dismiss")
|
|
753
|
-
if let response = unsupportedResponse(for: outcome) {
|
|
754
|
-
return response
|
|
755
|
-
}
|
|
756
|
-
return Response(ok: true, data: DataPayload(message: "dismissed"))
|
|
757
|
-
}
|
|
758
|
-
let buttonLabels = alert.buttons.allElementsBoundByIndex.map { $0.label }
|
|
759
|
-
return Response(ok: true, data: DataPayload(message: alert.label, items: buttonLabels))
|
|
737
|
+
return handleAlert(alert, action: action)
|
|
760
738
|
case .pinch:
|
|
761
739
|
guard let scale = command.scale, scale > 0 else {
|
|
762
740
|
return Response(ok: false, error: ErrorPayload(message: "pinch requires scale > 0"))
|
|
@@ -1359,12 +1359,17 @@ extension RunnerTests {
|
|
|
1359
1359
|
|
|
1360
1360
|
#if !os(tvOS)
|
|
1361
1361
|
private func interactionCoordinate(app: XCUIApplication, x: Double, y: Double) -> XCUICoordinate {
|
|
1362
|
+
#if os(iOS)
|
|
1363
|
+
let origin = app.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0))
|
|
1364
|
+
return origin.withOffset(CGVector(dx: x, dy: y))
|
|
1365
|
+
#else
|
|
1362
1366
|
let root = interactionRoot(app: app)
|
|
1363
1367
|
let origin = root.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0))
|
|
1364
1368
|
let rootFrame = root.frame
|
|
1365
1369
|
let offsetX = x - Double(rootFrame.origin.x)
|
|
1366
1370
|
let offsetY = y - Double(rootFrame.origin.y)
|
|
1367
1371
|
return origin.withOffset(CGVector(dx: offsetX, dy: offsetY))
|
|
1372
|
+
#endif
|
|
1368
1373
|
}
|
|
1369
1374
|
#endif
|
|
1370
1375
|
|
|
@@ -147,7 +147,7 @@ extension RunnerTests {
|
|
|
147
147
|
? (target.value(forKey: "waitForIdleTimeout") as? NSNumber)
|
|
148
148
|
: nil
|
|
149
149
|
if supportsWaitForIdleTimeout {
|
|
150
|
-
target.setValue(
|
|
150
|
+
target.setValue(scrollInteractionIdleTimeoutDefault, forKey: "waitForIdleTimeout")
|
|
151
151
|
}
|
|
152
152
|
defer {
|
|
153
153
|
if let previous {
|
|
@@ -191,19 +191,6 @@ extension RunnerTests {
|
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
private func resolveScrollInteractionIdleTimeout() -> TimeInterval {
|
|
195
|
-
guard
|
|
196
|
-
let raw = ProcessInfo.processInfo.environment["AGENT_DEVICE_IOS_INTERACTION_IDLE_TIMEOUT"],
|
|
197
|
-
!raw.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
198
|
-
else {
|
|
199
|
-
return scrollInteractionIdleTimeoutDefault
|
|
200
|
-
}
|
|
201
|
-
guard let parsed = Double(raw), parsed >= 0 else {
|
|
202
|
-
return scrollInteractionIdleTimeoutDefault
|
|
203
|
-
}
|
|
204
|
-
return min(parsed, 30)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
194
|
func shouldRetryCommand(_ command: Command) -> Bool {
|
|
208
195
|
if RunnerEnv.isTruthy("AGENT_DEVICE_RUNNER_DISABLE_READONLY_RETRY") {
|
|
209
196
|
return false
|
|
@@ -46,7 +46,7 @@ extension RunnerTests {
|
|
|
46
46
|
#endif
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
func firstBlockingSystemModal(in springboard: XCUIApplication) -> XCUIElement? {
|
|
50
50
|
let disableSafeProbe = RunnerEnv.isTruthy("AGENT_DEVICE_RUNNER_DISABLE_SAFE_MODAL_PROBE")
|
|
51
51
|
let queryElements: (() -> [XCUIElement]) -> [XCUIElement] = { fetch in
|
|
52
52
|
if disableSafeProbe {
|
|
@@ -76,7 +76,7 @@ extension RunnerTests {
|
|
|
76
76
|
return nil
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
func safeElementsQuery(_ fetch: () -> [XCUIElement]) -> [XCUIElement] {
|
|
80
80
|
var elements: [XCUIElement] = []
|
|
81
81
|
let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
|
|
82
82
|
elements = fetch()
|
|
@@ -120,7 +120,7 @@ extension RunnerTests {
|
|
|
120
120
|
return true
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
func actionableElements(in element: XCUIElement) -> [XCUIElement] {
|
|
124
124
|
var seen = Set<String>()
|
|
125
125
|
var actions: [XCUIElement] = []
|
|
126
126
|
let descendants = safeElementsQuery {
|
|
@@ -145,7 +145,13 @@ func run() throws {
|
|
|
145
145
|
in: parentLayer
|
|
146
146
|
)
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
// Overlay burn-in forces a full re-encode; medium quality keeps simulator videos readable
|
|
149
|
+
// while avoiding very slow highest-quality exports.
|
|
150
|
+
let presetName = AVAssetExportSession.exportPresets(compatibleWith: composition)
|
|
151
|
+
.contains(AVAssetExportPresetMediumQuality)
|
|
152
|
+
? AVAssetExportPresetMediumQuality
|
|
153
|
+
: AVAssetExportPresetHighestQuality
|
|
154
|
+
guard let exporter = AVAssetExportSession(asset: composition, presetName: presetName) else {
|
|
149
155
|
throw OverlayError.exportFailed("Failed to create export session.")
|
|
150
156
|
}
|
|
151
157
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-device",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.2",
|
|
4
4
|
"description": "Agent-native CLI for AI mobile testing and app automation across iOS, Android, tvOS, Android TV, macOS, and Linux.",
|
|
5
5
|
"mcpName": "io.github.callstackincubator/agent-device",
|
|
6
6
|
"license": "MIT",
|
|
@@ -111,6 +111,11 @@
|
|
|
111
111
|
"test-app:ios": "pnpm --dir examples/test-app ios",
|
|
112
112
|
"test-app:android": "pnpm --dir examples/test-app android",
|
|
113
113
|
"test-app:typecheck": "pnpm --dir examples/test-app typecheck",
|
|
114
|
+
"test-app:replay:ios": "pnpm ad test examples/test-app/replays --platform ios --artifacts-dir .tmp/test-app-replay/ios",
|
|
115
|
+
"test-app:replay:android": "pnpm ad test examples/test-app/replays --platform android --artifacts-dir .tmp/test-app-replay/android",
|
|
116
|
+
"test-app:maestro": "node scripts/run-test-app-maestro-suite.mjs",
|
|
117
|
+
"test-app:maestro:ios": "node scripts/run-test-app-maestro-suite.mjs --platform ios",
|
|
118
|
+
"test-app:maestro:android": "node scripts/run-test-app-maestro-suite.mjs --platform android",
|
|
114
119
|
"test": "vitest run --project unit",
|
|
115
120
|
"test:unit": "vitest run --project unit",
|
|
116
121
|
"test:coverage": "vitest run --coverage",
|
package/server.json
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/callstackincubator/agent-device",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.15.
|
|
10
|
+
"version": "0.15.2",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "agent-device",
|
|
15
|
-
"version": "0.15.
|
|
15
|
+
"version": "0.15.2",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
5fdbdcd5f57e6c152a6a47967748903c0bd2e6655edf19ff0757d815e1975762 agent-device-android-snapshot-helper-0.15.0.apk
|
package/dist/src/840.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{AppError as e}from"./9152.js";import{emitDiagnostic as t}from"./7599.js";let s="user-installed";function a(e){return e??s}function n(t){if(void 0===t)throw new e("INVALID_ARGS","appsFilter must be resolved before executing the apps command");return t}let r=i(process.env.AGENT_DEVICE_RETRY_LOGS);function i(e){return["1","true","yes","on"].includes((e??"").trim().toLowerCase())}let d={ios_boot:{startupMs:12e4,operationMs:2e4,totalMs:12e4},ios_runner_connect:{startupMs:12e4,operationMs:15e3,totalMs:12e4},android_boot:{startupMs:6e4,operationMs:1e4,totalMs:6e4}};class l{startedAtMs;expiresAtMs;constructor(e,t){this.startedAtMs=e,this.expiresAtMs=e+Math.max(0,t)}static fromTimeoutMs(e,t=Date.now()){return new l(t,e)}remainingMs(e=Date.now()){return Math.max(0,this.expiresAtMs-e)}elapsedMs(e=Date.now()){return Math.max(0,e-this.startedAtMs)}isExpired(e=Date.now()){return 0>=this.remainingMs(e)}}async function o(t,s={},a={}){let n,r={maxAttempts:s.maxAttempts??3,baseDelayMs:s.baseDelayMs??200,maxDelayMs:s.maxDelayMs??2e3,jitter:s.jitter??.2,shouldRetry:s.shouldRetry};for(let s=1;s<=r.maxAttempts;s+=1){if(a.signal?.aborted)throw new e("COMMAND_FAILED","request canceled",{reason:"request_canceled"});if(a.deadline?.isExpired()&&s>1)break;try{let e=await t({attempt:s,maxAttempts:r.maxAttempts,deadline:a.deadline});return a.onEvent?.({phase:a.phase,event:"succeeded",attempt:s,maxAttempts:r.maxAttempts,elapsedMs:a.deadline?.elapsedMs(),remainingMs:a.deadline?.remainingMs()}),p({phase:a.phase,event:"succeeded",attempt:s,maxAttempts:r.maxAttempts,elapsedMs:a.deadline?.elapsedMs(),remainingMs:a.deadline?.remainingMs()}),e}catch(o){n=o;let e=a.classifyReason?.(o),t={phase:a.phase,event:"attempt_failed",attempt:s,maxAttempts:r.maxAttempts,elapsedMs:a.deadline?.elapsedMs(),remainingMs:a.deadline?.remainingMs(),reason:e};if(a.onEvent?.(t),p(t),s>=r.maxAttempts||r.shouldRetry&&!r.shouldRetry(o,s))break;let i=function(e,t,s,a){let n=Math.min(t,e*2**(a-1));return Math.max(0,n+n*s*(2*Math.random()-1))}(r.baseDelayMs,r.maxDelayMs,r.jitter,s),d=a.deadline?Math.min(i,a.deadline.remainingMs()):i;if(d<=0)break;let l={phase:a.phase,event:"retry_scheduled",attempt:s,maxAttempts:r.maxAttempts,delayMs:d,elapsedMs:a.deadline?.elapsedMs(),remainingMs:a.deadline?.remainingMs(),reason:e};a.onEvent?.(l),p(l),await function(e,t){return new Promise(s=>{if(t?.aborted)return void s();let a=!1,n=()=>{a||(a=!0,t&&t.removeEventListener("abort",i),s())},r=setTimeout(n,e);function i(){clearTimeout(r),n()}t&&t.addEventListener("abort",i,{once:!0})})}(d,a.signal)}}let i={phase:a.phase,event:"exhausted",attempt:r.maxAttempts,maxAttempts:r.maxAttempts,elapsedMs:a.deadline?.elapsedMs(),remainingMs:a.deadline?.remainingMs(),reason:a.classifyReason?.(n)};if(a.onEvent?.(i),p(i),n)throw n;throw new e("COMMAND_FAILED","retry failed")}async function m(e,t={}){return o(()=>e(),{maxAttempts:t.attempts,baseDelayMs:t.baseDelayMs,maxDelayMs:t.maxDelayMs,jitter:t.jitter,shouldRetry:t.shouldRetry})}function p(e){t({level:"attempt_failed"===e.event||"exhausted"===e.event?"warn":"debug",phase:"retry",data:{...e}}),r&&process.stderr.write(`[agent-device][retry] ${JSON.stringify(e)}
|
|
2
|
-
`)}export{s as DEFAULT_APPS_FILTER,l as Deadline,d as TIMEOUT_PROFILES,n as assertResolvedAppsFilter,i as isEnvTruthy,a as resolveAppsFilter,o as retryWithPolicy,m as withRetry};
|