preflite 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +144 -0
- package/dist/adapter-spi/artifact/index.js +1 -0
- package/dist/adapter-spi/command/index.js +1 -0
- package/dist/adapter-spi/install/index.js +1 -0
- package/dist/adapter-spi/lifecycle/index.js +1 -0
- package/dist/adapter-spi/resource/index.js +1 -0
- package/dist/adapter-spi/snapshot/index.js +1 -0
- package/dist/application/agent/AgentCommandPollLoop.js +48 -0
- package/dist/application/agent/AgentRuntimeService.js +27 -0
- package/dist/application/app-package/AppPackageApplicationService.js +97 -0
- package/dist/application/artifact/ArtifactApplicationService.js +13 -0
- package/dist/application/debug/DebugApplicationService.js +117 -0
- package/dist/application/health/HealthMetricsService.js +47 -0
- package/dist/application/lease/LeaseApplicationService.js +79 -0
- package/dist/application/query/ObservationQueryService.js +48 -0
- package/dist/application/reporter/ReporterApplicationService.js +41 -0
- package/dist/application/resource/ResourceOccupationReleaseService.js +49 -0
- package/dist/application/resource/ResourceRegistryService.js +113 -0
- package/dist/application/session/SessionApplicationService.js +39 -0
- package/dist/application/task/TaskApplicationService.js +378 -0
- package/dist/client/agentAppPackageClient.js +91 -0
- package/dist/domain/agent/AgentNode.js +12 -0
- package/dist/domain/agent/AgentRuntime.js +6 -0
- package/dist/domain/artifact/ArtifactPipeline.js +6 -0
- package/dist/domain/artifact/ArtifactRef.js +12 -0
- package/dist/domain/event/AgentEvent.js +10 -0
- package/dist/domain/event/Reporter.js +6 -0
- package/dist/domain/health/HealthMetrics.js +8 -0
- package/dist/domain/lease/Lease.js +28 -0
- package/dist/domain/lease/LeaseManager.js +6 -0
- package/dist/domain/repositories/index.js +1 -0
- package/dist/domain/resource/DeviceDetails.js +23 -0
- package/dist/domain/resource/DeviceResource.js +16 -0
- package/dist/domain/resource/ResourceRegistry.js +6 -0
- package/dist/domain/runtime/interfaces.js +1 -0
- package/dist/domain/session/BaseSession.js +16 -0
- package/dist/domain/session/DebugSession.js +3 -0
- package/dist/domain/session/ExecutionSession.js +3 -0
- package/dist/domain/session/SessionManager.js +8 -0
- package/dist/domain/task/TaskRecord.js +14 -0
- package/dist/domain/task/TaskSpec.js +12 -0
- package/dist/infrastructure/adapters/AdapterRegistry.js +10 -0
- package/dist/infrastructure/adapters/BridgeAdapters.js +6 -0
- package/dist/infrastructure/adapters/android/AndroidResourceAdapter.js +51 -0
- package/dist/infrastructure/adapters/deviceDetailsProbe.js +229 -0
- package/dist/infrastructure/adapters/harmony/HarmonyResourceAdapter.js +40 -0
- package/dist/infrastructure/adapters/ios/IOSResourceAdapter.js +182 -0
- package/dist/infrastructure/adapters/real/ShellCommandAndSnapshot.js +41 -0
- package/dist/infrastructure/airtest/AirtestRuntime.js +168 -0
- package/dist/infrastructure/app-package/AppPackageUrlCache.js +191 -0
- package/dist/infrastructure/app-package/appPackageDownloadDir.js +13 -0
- package/dist/infrastructure/bootstrap/BuildRuntimeContext.js +88 -0
- package/dist/infrastructure/cache/DirCapacityWatchdog.js +150 -0
- package/dist/infrastructure/config/agentConfigFile.js +96 -0
- package/dist/infrastructure/device/DeviceAppPackageOps.js +146 -0
- package/dist/infrastructure/ios/IOSWdaWatchdog.js +207 -0
- package/dist/infrastructure/live-debug/LiveDebugSessionManager.js +74 -0
- package/dist/infrastructure/live-debug/RuntimeLiveDebugAdapters.js +19 -0
- package/dist/infrastructure/midscene/DebugRuntimeImpl.js +533 -0
- package/dist/infrastructure/midscene/MidsceneRuntimeMock.js +22 -0
- package/dist/infrastructure/midscene/MidsceneRuntimeReal.js +552 -0
- package/dist/infrastructure/midscene/executionDumpWatcher.js +219 -0
- package/dist/infrastructure/midscene/videoRecorder.js +365 -0
- package/dist/infrastructure/midscene/zipReportDir.js +36 -0
- package/dist/infrastructure/persistence/InMemoryRepositories.js +94 -0
- package/dist/infrastructure/resilience/DeliveryIdDeduper.js +26 -0
- package/dist/infrastructure/system/CommandRunner.js +128 -0
- package/dist/infrastructure/transport/http/AgentEventHttpIngestClient.js +52 -0
- package/dist/infrastructure/transport/http/CallbackOutboxStore.js +106 -0
- package/dist/infrastructure/transport/http/PlatformCallbackClient.js +113 -0
- package/dist/infrastructure/transport/http/PlatformCommandPollClient.js +89 -0
- package/dist/infrastructure/transport/http/ResilientPlatformCallbackClient.js +117 -0
- package/dist/infrastructure/transport/midscenePaths.js +28 -0
- package/dist/infrastructure/transport/ws/ResilientWsOrHttpEventPublisher.js +29 -0
- package/dist/infrastructure/transport/ws/WsClient.js +182 -0
- package/dist/infrastructure/transport/ws/WsEventPublisher.js +36 -0
- package/dist/interfaces/http/HttpServer.js +227 -0
- package/dist/interfaces/websocket/AgentWsGateway.js +227 -0
- package/dist/main.js +368 -0
- package/dist/mcp/agentHttpClient.js +82 -0
- package/dist/mcp/agentRuntime.js +184 -0
- package/dist/mcp/cli.js +36 -0
- package/dist/mcp/doctor.js +124 -0
- package/dist/mcp/evidence.js +57 -0
- package/dist/mcp/exploration/index.js +129 -0
- package/dist/mcp/exploration/sessionManager.js +122 -0
- package/dist/mcp/exploration/tools-atomic.js +34 -0
- package/dist/mcp/exploration/tools-intelligent.js +33 -0
- package/dist/mcp/exploration/tools-session.js +276 -0
- package/dist/mcp/exploration/types.js +1 -0
- package/dist/mcp/flowStepEvents.js +114 -0
- package/dist/mcp/liveViewer.js +156 -0
- package/dist/mcp/reportReader.js +157 -0
- package/dist/mcp/runManager.js +161 -0
- package/dist/mcp/runSummary.js +72 -0
- package/dist/mcp/runtimeInstall.js +44 -0
- package/dist/mcp/server.js +260 -0
- package/dist/mcp/setup.js +185 -0
- package/dist/mcp/types.js +1 -0
- package/dist/mcp/userConfig.js +45 -0
- package/dist/mcp/visual-flow/codegen.js +576 -0
- package/dist/mcp/visual-flow/index.js +14 -0
- package/dist/mcp/visual-flow/types.js +5 -0
- package/dist/mcp/visual-flow/validate.js +617 -0
- package/dist/protocol-contracts/commands/envelope.js +24 -0
- package/dist/protocol-contracts/commands/index.js +1 -0
- package/dist/protocol-contracts/dto/index.js +1 -0
- package/dist/protocol-contracts/events/index.js +1 -0
- package/dist/protocol-contracts/queries/index.js +1 -0
- package/dist/shared-kernel/enums/index.js +70 -0
- package/dist/shared-kernel/errors/index.js +21 -0
- package/dist/shared-kernel/ids/index.js +18 -0
- package/dist/shared-kernel/time/index.js +3 -0
- package/dist/shared-kernel/value-objects/index.js +1 -0
- package/dist/utils/appPackageLocalPath.js +75 -0
- package/dist/utils/deviceResourceRouting.js +24 -0
- package/dist/utils/harmonyAgentDebugDevice.js +60 -0
- package/dist/utils/harmonyHdcDeviceId.js +134 -0
- package/dist/utils/iosAgentDebugDevice.js +72 -0
- package/dist/utils/iosMjpegCapture.js +90 -0
- package/dist/utils/liveDebugForegroundParse.js +71 -0
- package/dist/utils/midscene-device-session.js +353 -0
- package/dist/utils/midscene-task-cache-env.js +15 -0
- package/dist/utils/midsceneReportConstants.js +49 -0
- package/dist/utils/seedMidsceneTaskCache.js +61 -0
- package/dist/utils/task-runners/context/androidTaskRunnerContext.js +1 -0
- package/dist/utils/task-runners/context/harmonyTaskRunnerContext.js +1 -0
- package/dist/utils/task-runners/context/iosTaskRunnerContext.js +1 -0
- package/dist/utils/task-runners/runAndroidNativeAppTask.js +29 -0
- package/dist/utils/task-runners/runHarmonyNativeAppTask.js +36 -0
- package/dist/utils/task-runners/runIosNativeAppTask.js +30 -0
- package/dist/utils/task-runners/taskAppPackage.js +20 -0
- package/dist/utils/wrapper/resolveTaskRunnerImport.js +11 -0
- package/dist/utils/wrapper/wrapAndroidTaskScript.js +38 -0
- package/dist/utils/wrapper/wrapHarmonyTaskScript.js +42 -0
- package/dist/utils/wrapper/wrapIosTaskScript.js +30 -0
- package/package.json +46 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { parseInboundCommandJson } from "../../protocol-contracts/commands/envelope.js";
|
|
2
|
+
import { DeliveryIdDeduper } from "../../infrastructure/resilience/DeliveryIdDeduper.js";
|
|
3
|
+
import { asLeaseId, asSessionId, asTaskId } from "../../shared-kernel/ids/index.js";
|
|
4
|
+
export class AgentWsGateway {
|
|
5
|
+
wsClient;
|
|
6
|
+
leaseService;
|
|
7
|
+
taskService;
|
|
8
|
+
debugService;
|
|
9
|
+
appPackageService;
|
|
10
|
+
occupationRelease;
|
|
11
|
+
commandChain = Promise.resolve();
|
|
12
|
+
deduper;
|
|
13
|
+
onCommandError(error) {
|
|
14
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
15
|
+
console.error(`[AgentWsGateway] command failed: ${message}`);
|
|
16
|
+
}
|
|
17
|
+
summarizeCommand(command) {
|
|
18
|
+
if (command.type === "CreateTaskCommand") {
|
|
19
|
+
return JSON.stringify({
|
|
20
|
+
type: command.type,
|
|
21
|
+
taskId: command.taskId,
|
|
22
|
+
requiredPlatform: command.requiredPlatform,
|
|
23
|
+
resourceId: command.resourceId ?? "",
|
|
24
|
+
selector: command.selector ?? null,
|
|
25
|
+
scriptLength: command.script?.length ?? 0,
|
|
26
|
+
runtimeEnvKeys: Object.keys(command.runtimeEnv ?? {}).length,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (command.type === "AcquireLeaseCommand") {
|
|
30
|
+
return JSON.stringify({
|
|
31
|
+
type: command.type,
|
|
32
|
+
leaseId: command.leaseId,
|
|
33
|
+
resourceId: command.resourceId,
|
|
34
|
+
ownerId: command.ownerId,
|
|
35
|
+
ownerType: command.ownerType,
|
|
36
|
+
ttlSeconds: command.ttlSeconds,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (command.type === "StartLiveDebugSessionCommand") {
|
|
40
|
+
return JSON.stringify({
|
|
41
|
+
type: command.type,
|
|
42
|
+
sessionId: command.sessionId,
|
|
43
|
+
resourceId: command.resourceId,
|
|
44
|
+
frameIntervalMs: command.frameIntervalMs ?? null,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (command.type === "SendLiveDebugInputCommand") {
|
|
48
|
+
return JSON.stringify({
|
|
49
|
+
type: command.type,
|
|
50
|
+
sessionId: command.sessionId,
|
|
51
|
+
action: command.action,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (command.type === "InstallAppCommand") {
|
|
55
|
+
const ref = command.appRef ?? "";
|
|
56
|
+
const summarized = /^https?:\/\//i.test(ref.trim()) ? `http(s) url (length=${ref.length})` : ref.length > 160 ? `${ref.slice(0, 160)}…` : ref;
|
|
57
|
+
return JSON.stringify({
|
|
58
|
+
type: command.type,
|
|
59
|
+
resourceId: command.resourceId,
|
|
60
|
+
appRef: summarized,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (command.type === "UninstallAppCommand") {
|
|
64
|
+
return JSON.stringify({
|
|
65
|
+
type: command.type,
|
|
66
|
+
resourceId: command.resourceId,
|
|
67
|
+
bundleId: command.bundleId,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return JSON.stringify(command);
|
|
71
|
+
}
|
|
72
|
+
constructor(wsClient, leaseService, taskService, debugService, appPackageService, occupationRelease, deduperMaxSize = 4096) {
|
|
73
|
+
this.wsClient = wsClient;
|
|
74
|
+
this.leaseService = leaseService;
|
|
75
|
+
this.taskService = taskService;
|
|
76
|
+
this.debugService = debugService;
|
|
77
|
+
this.appPackageService = appPackageService;
|
|
78
|
+
this.occupationRelease = occupationRelease;
|
|
79
|
+
this.deduper = new DeliveryIdDeduper(deduperMaxSize);
|
|
80
|
+
}
|
|
81
|
+
/** HTTP poll:返回是否重复投递(仍应对应 ACK),以及是否处理失败 */
|
|
82
|
+
async handlePollDelivery(item) {
|
|
83
|
+
const { deliveryId, command } = item;
|
|
84
|
+
if (this.deduper.isProcessed(deliveryId)) {
|
|
85
|
+
return { duplicate: true, ok: true };
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
await this.ingestCommand(command, deliveryId, { mode: "awaitAll" });
|
|
89
|
+
return { duplicate: false, ok: true };
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
this.onCommandError(error);
|
|
93
|
+
return { duplicate: false, ok: false };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
start() {
|
|
97
|
+
this.wsClient.onMessage(async (raw) => {
|
|
98
|
+
try {
|
|
99
|
+
const { deliveryId, command } = parseInboundCommandJson(raw);
|
|
100
|
+
console.info(`[AgentWsGateway] received command=${this.summarizeCommand(command)}`);
|
|
101
|
+
await this.ingestCommand(command, deliveryId, { mode: "ws" });
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
this.onCommandError(error);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
this.wsClient.connect();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* mode ws:CreateTask 不阻塞后续 WS 消息;awaitAll:poll 路径一律等待 route 完成(含长任务)。
|
|
111
|
+
*/
|
|
112
|
+
async ingestCommand(command, deliveryId, opts) {
|
|
113
|
+
if (deliveryId && this.deduper.isProcessed(deliveryId)) {
|
|
114
|
+
console.info(`[AgentWsGateway] skip duplicate deliveryId=${deliveryId}`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const finish = async () => {
|
|
118
|
+
await this.route(command);
|
|
119
|
+
if (deliveryId)
|
|
120
|
+
this.deduper.markProcessed(deliveryId);
|
|
121
|
+
};
|
|
122
|
+
const fireAndForget = opts.mode === "ws" &&
|
|
123
|
+
(command.type === "CreateTaskCommand" ||
|
|
124
|
+
command.type === "InstallAppCommand" ||
|
|
125
|
+
command.type === "UninstallAppCommand");
|
|
126
|
+
if (fireAndForget) {
|
|
127
|
+
void finish().catch((error) => this.onCommandError(error));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (opts.mode === "ws" && command.type !== "CreateTaskCommand") {
|
|
131
|
+
this.commandChain = this.commandChain
|
|
132
|
+
.then(() => finish())
|
|
133
|
+
.catch((error) => this.onCommandError(error));
|
|
134
|
+
await this.commandChain;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
await finish();
|
|
138
|
+
}
|
|
139
|
+
async route(command) {
|
|
140
|
+
switch (command.type) {
|
|
141
|
+
case "AcquireLeaseCommand":
|
|
142
|
+
await this.leaseService.acquire(command.leaseId, command.resourceId, command.ownerId, command.ownerType, command.ttlSeconds, {
|
|
143
|
+
username: command.occupantUsername,
|
|
144
|
+
displayName: command.occupantDisplayName,
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
case "RenewLeaseCommand":
|
|
148
|
+
await this.leaseService.renew(asLeaseId(command.leaseId), command.ttlSeconds);
|
|
149
|
+
return;
|
|
150
|
+
case "ReleaseLeaseCommand":
|
|
151
|
+
await this.leaseService.release(asLeaseId(command.leaseId));
|
|
152
|
+
return;
|
|
153
|
+
case "RevokeLeaseByResourceCommand":
|
|
154
|
+
await this.occupationRelease.forceRelease(command.resourceId);
|
|
155
|
+
return;
|
|
156
|
+
case "CreateTaskCommand":
|
|
157
|
+
await this.taskService.dispatch(command);
|
|
158
|
+
return;
|
|
159
|
+
case "CancelTaskCommand":
|
|
160
|
+
await this.taskService.cancel(asTaskId(command.taskId));
|
|
161
|
+
return;
|
|
162
|
+
case "CreateDebugSessionCommand":
|
|
163
|
+
await this.debugService.createDebugSession({
|
|
164
|
+
sessionId: command.sessionId,
|
|
165
|
+
resourceId: command.resourceId,
|
|
166
|
+
leaseId: `lease-${command.sessionId}`,
|
|
167
|
+
ownerId: command.ownerId,
|
|
168
|
+
});
|
|
169
|
+
return;
|
|
170
|
+
case "SendDebugCommand":
|
|
171
|
+
await this.debugService.sendCommand(command.sessionId, command.command);
|
|
172
|
+
return;
|
|
173
|
+
case "CloseDebugSessionCommand":
|
|
174
|
+
await this.debugService.close(asSessionId(command.sessionId));
|
|
175
|
+
return;
|
|
176
|
+
case "StartLiveDebugSessionCommand":
|
|
177
|
+
await this.debugService.startLiveSession({
|
|
178
|
+
sessionId: command.sessionId,
|
|
179
|
+
resourceId: command.resourceId,
|
|
180
|
+
frameIntervalMs: command.frameIntervalMs,
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
183
|
+
case "SendLiveDebugInputCommand":
|
|
184
|
+
if (command.action === "tap") {
|
|
185
|
+
await this.debugService.sendLiveInput(command.sessionId, {
|
|
186
|
+
action: "tap",
|
|
187
|
+
x: command.x ?? 0,
|
|
188
|
+
y: command.y ?? 0,
|
|
189
|
+
coordinateSpace: command.coordinateSpace,
|
|
190
|
+
sourceWidth: command.sourceWidth,
|
|
191
|
+
sourceHeight: command.sourceHeight,
|
|
192
|
+
});
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (command.action === "swipe") {
|
|
196
|
+
await this.debugService.sendLiveInput(command.sessionId, {
|
|
197
|
+
action: "swipe",
|
|
198
|
+
x: command.x ?? 0,
|
|
199
|
+
y: command.y ?? 0,
|
|
200
|
+
x2: command.x2 ?? 0,
|
|
201
|
+
y2: command.y2 ?? 0,
|
|
202
|
+
durationMs: command.durationMs,
|
|
203
|
+
coordinateSpace: command.coordinateSpace,
|
|
204
|
+
sourceWidth: command.sourceWidth,
|
|
205
|
+
sourceHeight: command.sourceHeight,
|
|
206
|
+
});
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
await this.debugService.sendLiveInput(command.sessionId, {
|
|
210
|
+
action: "key",
|
|
211
|
+
key: command.key ?? "",
|
|
212
|
+
});
|
|
213
|
+
return;
|
|
214
|
+
case "StopLiveDebugSessionCommand":
|
|
215
|
+
await this.debugService.stopLiveSession(command.sessionId);
|
|
216
|
+
return;
|
|
217
|
+
case "InstallAppCommand":
|
|
218
|
+
await this.appPackageService.install(command.resourceId, command.appRef);
|
|
219
|
+
return;
|
|
220
|
+
case "UninstallAppCommand":
|
|
221
|
+
await this.appPackageService.uninstall(command.resourceId, command.bundleId);
|
|
222
|
+
return;
|
|
223
|
+
default:
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { AgentCommandPollLoop } from "./application/agent/AgentCommandPollLoop.js";
|
|
2
|
+
import { AgentRuntimeService } from "./application/agent/AgentRuntimeService.js";
|
|
3
|
+
import { ArtifactApplicationService } from "./application/artifact/ArtifactApplicationService.js";
|
|
4
|
+
import { DebugApplicationService } from "./application/debug/DebugApplicationService.js";
|
|
5
|
+
import { HealthMetricsService } from "./application/health/HealthMetricsService.js";
|
|
6
|
+
import { LeaseApplicationService } from "./application/lease/LeaseApplicationService.js";
|
|
7
|
+
import { ObservationQueryService } from "./application/query/ObservationQueryService.js";
|
|
8
|
+
import { ReporterApplicationService } from "./application/reporter/ReporterApplicationService.js";
|
|
9
|
+
import { ResourceOccupationReleaseService } from "./application/resource/ResourceOccupationReleaseService.js";
|
|
10
|
+
import { ResourceRegistryService } from "./application/resource/ResourceRegistryService.js";
|
|
11
|
+
import { SessionApplicationService } from "./application/session/SessionApplicationService.js";
|
|
12
|
+
import { TaskApplicationService } from "./application/task/TaskApplicationService.js";
|
|
13
|
+
import { AppPackageApplicationService } from "./application/app-package/AppPackageApplicationService.js";
|
|
14
|
+
import { AppPackageUrlCache } from "./infrastructure/app-package/AppPackageUrlCache.js";
|
|
15
|
+
import { resolveAppPackageDownloadDir } from "./infrastructure/app-package/appPackageDownloadDir.js";
|
|
16
|
+
import { DebugRuntimeImpl } from "./infrastructure/midscene/DebugRuntimeImpl.js";
|
|
17
|
+
import { DeviceAppPackageOps } from "./infrastructure/device/DeviceAppPackageOps.js";
|
|
18
|
+
import { buildRuntimeContext } from "./infrastructure/bootstrap/BuildRuntimeContext.js";
|
|
19
|
+
import { parsePositiveByteCount, startDirCapacityWatchdog } from "./infrastructure/cache/DirCapacityWatchdog.js";
|
|
20
|
+
import { IOSWdaWatchdog } from "./infrastructure/ios/IOSWdaWatchdog.js";
|
|
21
|
+
import { getMidsceneReportRootDir } from "./infrastructure/transport/midscenePaths.js";
|
|
22
|
+
import { InMemoryAgentRepository, InMemoryArtifactRepository, InMemoryEventRepository, InMemoryLeaseRepository, InMemoryResourceRepository, InMemorySessionRepository, InMemoryTaskRepository, } from "./infrastructure/persistence/InMemoryRepositories.js";
|
|
23
|
+
import { AgentEventHttpIngestClient } from "./infrastructure/transport/http/AgentEventHttpIngestClient.js";
|
|
24
|
+
import { WsClient } from "./infrastructure/transport/ws/WsClient.js";
|
|
25
|
+
import { ResilientWsOrHttpEventPublisher } from "./infrastructure/transport/ws/ResilientWsOrHttpEventPublisher.js";
|
|
26
|
+
import { WsEventPublisher } from "./infrastructure/transport/ws/WsEventPublisher.js";
|
|
27
|
+
import { PlatformCommandPollClient } from "./infrastructure/transport/http/PlatformCommandPollClient.js";
|
|
28
|
+
import { PlatformCallbackClient } from "./infrastructure/transport/http/PlatformCallbackClient.js";
|
|
29
|
+
import { CallbackOutboxStore } from "./infrastructure/transport/http/CallbackOutboxStore.js";
|
|
30
|
+
import { ResilientPlatformCallbackClient } from "./infrastructure/transport/http/ResilientPlatformCallbackClient.js";
|
|
31
|
+
import { HttpServer } from "./interfaces/http/HttpServer.js";
|
|
32
|
+
import { AgentWsGateway } from "./interfaces/websocket/AgentWsGateway.js";
|
|
33
|
+
import { EventType } from "./shared-kernel/enums/index.js";
|
|
34
|
+
import { applyAgentConfigFileToProcessEnv, applyMutualAgentAuthTokenFallbackToProcessEnv, } from "./infrastructure/config/agentConfigFile.js";
|
|
35
|
+
import { isIPv4 } from "node:net";
|
|
36
|
+
import os from "node:os";
|
|
37
|
+
import path from "node:path";
|
|
38
|
+
applyAgentConfigFileToProcessEnv();
|
|
39
|
+
applyMutualAgentAuthTokenFallbackToProcessEnv();
|
|
40
|
+
if ((process.env.LOCAL_MCP_MODE ?? "").trim() === "1") {
|
|
41
|
+
process.env.AGENT_HTTP_TOKEN = "";
|
|
42
|
+
process.env.PLATFORM_WS_TOKEN = "";
|
|
43
|
+
process.env.PLATFORM_AGENT_CALLBACK_TOKEN = "";
|
|
44
|
+
process.env.PLATFORM_CALLBACK_ENDPOINT = "";
|
|
45
|
+
process.env.PLATFORM_COMMAND_POLL_BASE_URL = "";
|
|
46
|
+
process.env.PLATFORM_AGENT_EVENTS_HTTP_BASE_URL = "";
|
|
47
|
+
}
|
|
48
|
+
const rawPlatformWsEndpoint = process.env.PLATFORM_WS_ENDPOINT ?? "ws://127.0.0.1:18999";
|
|
49
|
+
const httpPort = Number(process.env.AGENT_HTTP_PORT ?? "18998");
|
|
50
|
+
const agentId = process.env.AGENT_ID ?? "agent-local-1";
|
|
51
|
+
const localMcpMode = (process.env.LOCAL_MCP_MODE ?? "").trim() === "1";
|
|
52
|
+
function pickAdvertiseHost() {
|
|
53
|
+
const env = process.env.AGENT_ADVERTISE_HOST?.trim();
|
|
54
|
+
if (env)
|
|
55
|
+
return env;
|
|
56
|
+
const nets = os.networkInterfaces();
|
|
57
|
+
for (const name of Object.keys(nets)) {
|
|
58
|
+
const addrs = nets[name];
|
|
59
|
+
if (!addrs)
|
|
60
|
+
continue;
|
|
61
|
+
for (const a of addrs) {
|
|
62
|
+
if (!a.internal && isIPv4(a.address)) {
|
|
63
|
+
return a.address;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return "127.0.0.1";
|
|
68
|
+
}
|
|
69
|
+
const advertiseHost = pickAdvertiseHost();
|
|
70
|
+
/** 经 `AgentSocketPresence` 上报,供平台「运行系统」列展示 */
|
|
71
|
+
const agentSocketOs = os.type();
|
|
72
|
+
const agentSocketPlatform = `${process.platform}/${process.arch}`;
|
|
73
|
+
/** 测试服务等多连接场景:若 URL 未带 agentId,则附加 AGENT_ID,便于与 /ws/agent?agentId= 对齐 */
|
|
74
|
+
function platformWsEndpointWithAgentIdQuery(endpoint, id) {
|
|
75
|
+
try {
|
|
76
|
+
const u = new URL(endpoint);
|
|
77
|
+
if (!u.searchParams.get("agentId")?.trim() && id.trim()) {
|
|
78
|
+
u.searchParams.set("agentId", id.trim());
|
|
79
|
+
}
|
|
80
|
+
return u.toString();
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return endpoint;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const wsEndpoint = platformWsEndpointWithAgentIdQuery(rawPlatformWsEndpoint, agentId);
|
|
87
|
+
const resourcesRefreshIntervalMs = Number(process.env.RESOURCES_REFRESH_INTERVAL_MS ?? "5000");
|
|
88
|
+
const httpAuthToken = process.env.AGENT_HTTP_TOKEN?.trim() || undefined;
|
|
89
|
+
const wsAuthToken = process.env.PLATFORM_WS_TOKEN?.trim() || httpAuthToken;
|
|
90
|
+
const platformCallbackEndpoint = process.env.PLATFORM_CALLBACK_ENDPOINT?.trim() || undefined;
|
|
91
|
+
const platformCallbackToken = process.env.PLATFORM_AGENT_CALLBACK_TOKEN?.trim() || httpAuthToken;
|
|
92
|
+
const platformCallbackFallbackEndpoint = process.env.PLATFORM_CALLBACK_FALLBACK_ENDPOINT?.trim() || undefined;
|
|
93
|
+
const platformCallbackFallbackToken = process.env.PLATFORM_CALLBACK_FALLBACK_TOKEN?.trim() || undefined;
|
|
94
|
+
const platformAgentEventsHttpBaseUrl = process.env.PLATFORM_AGENT_EVENTS_HTTP_BASE_URL?.trim() || undefined;
|
|
95
|
+
const debugAndroidAdbHost = process.env.MIDSCENE_ANDROID_ADB_HOST?.trim() || process.env.AGENT_ANDROID_ADB_HOST?.trim() || "127.0.0.1";
|
|
96
|
+
const debugAndroidAdbPort = Number(process.env.MIDSCENE_ANDROID_ADB_PORT ?? process.env.AGENT_ANDROID_ADB_PORT ?? "5037");
|
|
97
|
+
const debugAndroidScrcpyMaxSize = Number(process.env.MIDSCENE_ANDROID_SCRCPY_MAX_SIZE ?? "0");
|
|
98
|
+
const debugAndroidScrcpyVideoBitRate = Number(process.env.MIDSCENE_ANDROID_SCRCPY_VIDEO_BIT_RATE ?? "40000000");
|
|
99
|
+
const debugAndroidScrcpyIdleTimeoutMs = Number(process.env.MIDSCENE_ANDROID_SCRCPY_IDLE_TIMEOUT_MS ?? "120000");
|
|
100
|
+
const repoRoot = process.cwd();
|
|
101
|
+
const iosWdaStartCommandTemplate = process.env.IOS_WDA_START_COMMAND_TEMPLATE?.trim() ||
|
|
102
|
+
`bash "${path.join(repoRoot, "scripts", "start-ios-wda.sh")}" "{deviceId}" "{wdaPort}"`;
|
|
103
|
+
const iosWdaStopCommand = process.env.IOS_WDA_STOP_COMMAND?.trim() || `bash "${path.join(repoRoot, "scripts", "stop-ios-wda.sh")}"`;
|
|
104
|
+
const iosWdaDiscoveryCommand = process.env.IOS_DISCOVERY_COMMAND ?? "xcrun xctrace list devices";
|
|
105
|
+
const iosWdaWatchdogEnabled = (process.env.IOS_WDA_WATCHDOG_ENABLED ?? "1").trim() !== "0";
|
|
106
|
+
const iosWdaWatchdogIntervalMs = Number(process.env.IOS_WDA_WATCHDOG_INTERVAL_MS ?? "5000");
|
|
107
|
+
const iosWdaStartupTimeoutMs = Number(process.env.IOS_WDA_STARTUP_TIMEOUT_MS ?? "120000");
|
|
108
|
+
const iosWdaPortRangeStart = Number(process.env.IOS_WDA_PORT_RANGE_START ?? "8200");
|
|
109
|
+
const iosWdaPortRangeEnd = Number(process.env.IOS_WDA_PORT_RANGE_END ?? "8399");
|
|
110
|
+
const iosWdaPortMapFilePath = process.env.IOS_WDA_PORT_MAP_FILE_PATH?.trim() || path.join(repoRoot, ".wda-agent-state", "wda-port-map.json");
|
|
111
|
+
const platformCommandPollBaseUrl = process.env.PLATFORM_COMMAND_POLL_BASE_URL?.trim() || undefined;
|
|
112
|
+
const commandPollIntervalMs = Number(process.env.COMMAND_POLL_INTERVAL_MS ?? "3000");
|
|
113
|
+
const wsReconnectBackoffMinMs = Number(process.env.WS_RECONNECT_BACKOFF_MIN_MS ?? "2000");
|
|
114
|
+
const wsReconnectBackoffMaxMs = Number(process.env.WS_RECONNECT_BACKOFF_MAX_MS ?? "60000");
|
|
115
|
+
const wsPendingQueueMaxItems = Number(process.env.WS_PENDING_QUEUE_MAX_ITEMS ?? "10000");
|
|
116
|
+
const appInstallTimeoutMs = Number(process.env.AGENT_APP_INSTALL_TIMEOUT_MS ?? "600000");
|
|
117
|
+
const appUninstallTimeoutMs = Number(process.env.AGENT_APP_UNINSTALL_TIMEOUT_MS ?? "120000");
|
|
118
|
+
const appAutoLeaseTtlSeconds = Number(process.env.AGENT_APP_AUTO_LEASE_TTL_SECONDS ?? "300");
|
|
119
|
+
const cacheWatchdogEnabled = (process.env.CACHE_WATCHDOG_ENABLED ?? "0").trim() === "1";
|
|
120
|
+
const cacheWatchdogIntervalMs = Number(process.env.CACHE_WATCHDOG_INTERVAL_MS ?? "3600000");
|
|
121
|
+
let appStopping = false;
|
|
122
|
+
let cacheWatchdogStop;
|
|
123
|
+
async function bootstrap() {
|
|
124
|
+
const agentRepo = new InMemoryAgentRepository();
|
|
125
|
+
const resourceRepo = new InMemoryResourceRepository();
|
|
126
|
+
const leaseRepo = new InMemoryLeaseRepository();
|
|
127
|
+
const sessionRepo = new InMemorySessionRepository();
|
|
128
|
+
const taskRepo = new InMemoryTaskRepository();
|
|
129
|
+
const artifactRepo = new InMemoryArtifactRepository();
|
|
130
|
+
const eventRepo = new InMemoryEventRepository();
|
|
131
|
+
const runtimeContext = buildRuntimeContext(process.env);
|
|
132
|
+
const adapterRegistry = runtimeContext.adapterRegistry;
|
|
133
|
+
const outbox = new CallbackOutboxStore();
|
|
134
|
+
const rawCallbackClient = new PlatformCallbackClient({
|
|
135
|
+
endpoint: platformCallbackEndpoint,
|
|
136
|
+
authToken: platformCallbackToken || wsAuthToken,
|
|
137
|
+
});
|
|
138
|
+
const fallbackCallbackClient = platformCallbackFallbackEndpoint
|
|
139
|
+
? new PlatformCallbackClient({
|
|
140
|
+
endpoint: platformCallbackFallbackEndpoint,
|
|
141
|
+
authToken: platformCallbackFallbackToken || platformCallbackToken || wsAuthToken,
|
|
142
|
+
})
|
|
143
|
+
: undefined;
|
|
144
|
+
const callbackForTasks = new ResilientPlatformCallbackClient(rawCallbackClient, fallbackCallbackClient, outbox);
|
|
145
|
+
if (!platformCallbackEndpoint) {
|
|
146
|
+
console.warn("[main] PLATFORM_CALLBACK_ENDPOINT 未设置:HTTP 回调(状态 / 日志 / 报告)已禁用;仅连接 WS 时也无法投递平台侧回调。");
|
|
147
|
+
}
|
|
148
|
+
else if (!platformCallbackToken?.trim() && !wsAuthToken?.trim() && !httpAuthToken?.trim()) {
|
|
149
|
+
console.warn("[main] AGENT_HTTP_TOKEN / PLATFORM_WS_TOKEN / PLATFORM_AGENT_CALLBACK_TOKEN 均未设置:回调请求无 Bearer,平台将返回 401。");
|
|
150
|
+
}
|
|
151
|
+
const health = new HealthMetricsService();
|
|
152
|
+
let wsClient;
|
|
153
|
+
wsClient = new WsClient({
|
|
154
|
+
endpoint: wsEndpoint,
|
|
155
|
+
heartbeatMs: 5000,
|
|
156
|
+
reconnectMs: 2000,
|
|
157
|
+
reconnectBackoffMinMs: Number.isFinite(wsReconnectBackoffMinMs) && wsReconnectBackoffMinMs > 0 ? Math.floor(wsReconnectBackoffMinMs) : 2000,
|
|
158
|
+
reconnectBackoffMaxMs: Number.isFinite(wsReconnectBackoffMaxMs) && wsReconnectBackoffMaxMs > 0 ? Math.floor(wsReconnectBackoffMaxMs) : 60_000,
|
|
159
|
+
pendingQueueMaxItems: Number.isFinite(wsPendingQueueMaxItems) && wsPendingQueueMaxItems > 0 ? Math.floor(wsPendingQueueMaxItems) : 10_000,
|
|
160
|
+
authToken: wsAuthToken,
|
|
161
|
+
onOpen: () => {
|
|
162
|
+
void callbackForTasks.flushOutbox();
|
|
163
|
+
void wsClient.send(JSON.stringify({
|
|
164
|
+
type: "AgentSocketPresence",
|
|
165
|
+
agentId,
|
|
166
|
+
advertiseHost,
|
|
167
|
+
httpListenPort: httpPort,
|
|
168
|
+
os: agentSocketOs,
|
|
169
|
+
platform: agentSocketPlatform,
|
|
170
|
+
}));
|
|
171
|
+
},
|
|
172
|
+
onConnectionState: (state) => health.setWsConnectionState(state),
|
|
173
|
+
onPendingDepth: (depth) => health.setPendingSendDepth(depth),
|
|
174
|
+
onReconnectScheduled: (delayMs) => health.onReconnectScheduled(delayMs),
|
|
175
|
+
onPendingDropped: (count) => health.onPendingDropped(count),
|
|
176
|
+
});
|
|
177
|
+
const eventHttpIngest = new AgentEventHttpIngestClient({
|
|
178
|
+
baseUrl: platformAgentEventsHttpBaseUrl,
|
|
179
|
+
agentId,
|
|
180
|
+
authToken: platformCallbackToken || wsAuthToken,
|
|
181
|
+
});
|
|
182
|
+
const wsPublisherCore = new WsEventPublisher(wsClient);
|
|
183
|
+
const publisher = localMcpMode
|
|
184
|
+
? {
|
|
185
|
+
publish: async () => { },
|
|
186
|
+
publishLiveDebugFrame: async () => { },
|
|
187
|
+
}
|
|
188
|
+
: new ResilientWsOrHttpEventPublisher(wsClient, wsPublisherCore, eventHttpIngest);
|
|
189
|
+
const reporter = new ReporterApplicationService(eventRepo, publisher);
|
|
190
|
+
const resourceService = new ResourceRegistryService(resourceRepo, adapterRegistry.resourceAdapters, reporter, leaseRepo, taskRepo, sessionRepo);
|
|
191
|
+
const leaseService = new LeaseApplicationService(leaseRepo, reporter);
|
|
192
|
+
const sessionService = new SessionApplicationService(sessionRepo);
|
|
193
|
+
const artifactService = new ArtifactApplicationService(artifactRepo);
|
|
194
|
+
const taskService = new TaskApplicationService(taskRepo, resourceRepo, leaseService, sessionService, artifactService, reporter, runtimeContext.midsceneRuntime, callbackForTasks, agentId, process.env.TASK_AGENT_HTTP_BASE_URL?.trim() ||
|
|
195
|
+
`http://127.0.0.1:${Number.isFinite(httpPort) && httpPort > 0 ? Math.floor(httpPort) : 18998}`, process.env.TASK_AGENT_HTTP_TOKEN?.trim() || httpAuthToken || undefined);
|
|
196
|
+
const debugService = new DebugApplicationService(leaseService, sessionService, new DebugRuntimeImpl(adapterRegistry.commandExecutor, adapterRegistry.snapshotProvider, runtimeContext.commandRunner, {
|
|
197
|
+
androidAdbHost: debugAndroidAdbHost,
|
|
198
|
+
androidAdbPort: Number.isFinite(debugAndroidAdbPort) && debugAndroidAdbPort > 0 ? Math.floor(debugAndroidAdbPort) : 5037,
|
|
199
|
+
androidScrcpyMaxSize: Number.isFinite(debugAndroidScrcpyMaxSize) && debugAndroidScrcpyMaxSize >= 0
|
|
200
|
+
? Math.floor(debugAndroidScrcpyMaxSize)
|
|
201
|
+
: 0,
|
|
202
|
+
androidScrcpyVideoBitRate: Number.isFinite(debugAndroidScrcpyVideoBitRate) && debugAndroidScrcpyVideoBitRate > 0
|
|
203
|
+
? Math.floor(debugAndroidScrcpyVideoBitRate)
|
|
204
|
+
: 40_000_000,
|
|
205
|
+
androidScrcpyIdleTimeoutMs: Number.isFinite(debugAndroidScrcpyIdleTimeoutMs) && debugAndroidScrcpyIdleTimeoutMs > 0
|
|
206
|
+
? Math.floor(debugAndroidScrcpyIdleTimeoutMs)
|
|
207
|
+
: 120_000,
|
|
208
|
+
harmonyHdcPath: runtimeContext.harmonyDebugHdc.path,
|
|
209
|
+
harmonyHdcHost: runtimeContext.harmonyDebugHdc.host,
|
|
210
|
+
harmonyHdcPort: runtimeContext.harmonyDebugHdc.port,
|
|
211
|
+
iosWdaHost: runtimeContext.iosDebugWda.host,
|
|
212
|
+
iosWdaPortMapFilePath: runtimeContext.iosDebugWda.portMapFilePath,
|
|
213
|
+
iosWdaPortRangeStart: runtimeContext.iosDebugWda.portRangeStart,
|
|
214
|
+
iosWdaPortRangeEnd: runtimeContext.iosDebugWda.portRangeEnd,
|
|
215
|
+
iosWdaExplicitPort: runtimeContext.iosDebugWda.explicitWdaPort,
|
|
216
|
+
iosWdaMjpegPort: runtimeContext.iosDebugWda.mjpegPort,
|
|
217
|
+
}), reporter);
|
|
218
|
+
const appPackageOps = new DeviceAppPackageOps(runtimeContext.commandRunner, {
|
|
219
|
+
ideviceinstallerExe: process.env.IOS_IDEVICEINSTALLER_EXE?.trim() || undefined,
|
|
220
|
+
adbHost: debugAndroidAdbHost,
|
|
221
|
+
adbPort: Number.isFinite(debugAndroidAdbPort) && debugAndroidAdbPort > 0 ? Math.floor(debugAndroidAdbPort) : 5037,
|
|
222
|
+
harmonyHdcPath: runtimeContext.harmonyDebugHdc.path,
|
|
223
|
+
harmonyHdcHost: runtimeContext.harmonyDebugHdc.host,
|
|
224
|
+
harmonyHdcPort: runtimeContext.harmonyDebugHdc.port,
|
|
225
|
+
});
|
|
226
|
+
const appPackageDownloadDir = resolveAppPackageDownloadDir(process.env.AGENT_APP_DOWNLOAD_DIR);
|
|
227
|
+
const appPackageUrlCache = new AppPackageUrlCache({
|
|
228
|
+
downloadDir: appPackageDownloadDir,
|
|
229
|
+
agentId,
|
|
230
|
+
onChanged: async (payload) => {
|
|
231
|
+
try {
|
|
232
|
+
await reporter.emit(EventType.APP_PACKAGE_CACHE_CHANGED, {
|
|
233
|
+
agentId: payload.agentId,
|
|
234
|
+
items: payload.items.map((i) => ({
|
|
235
|
+
url: i.url,
|
|
236
|
+
platform: i.platform,
|
|
237
|
+
localPath: i.localPath,
|
|
238
|
+
byteSize: i.byteSize,
|
|
239
|
+
downloadedAt: i.downloadedAt,
|
|
240
|
+
})),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
console.warn(`[main] APP_PACKAGE_CACHE_CHANGED emit failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
await appPackageUrlCache.bootstrap();
|
|
249
|
+
const appPackageService = new AppPackageApplicationService(leaseService, resourceRepo, appPackageOps, {
|
|
250
|
+
downloadDir: appPackageDownloadDir,
|
|
251
|
+
installTimeoutMs: Number.isFinite(appInstallTimeoutMs) && appInstallTimeoutMs > 0 ? Math.floor(appInstallTimeoutMs) : undefined,
|
|
252
|
+
uninstallTimeoutMs: Number.isFinite(appUninstallTimeoutMs) && appUninstallTimeoutMs > 0 ? Math.floor(appUninstallTimeoutMs) : undefined,
|
|
253
|
+
autoLeaseTtlSeconds: Number.isFinite(appAutoLeaseTtlSeconds) && appAutoLeaseTtlSeconds > 0 ? Math.floor(appAutoLeaseTtlSeconds) : undefined,
|
|
254
|
+
urlCache: appPackageUrlCache,
|
|
255
|
+
});
|
|
256
|
+
const agentRuntime = new AgentRuntimeService(agentRepo, reporter);
|
|
257
|
+
const observationQueryService = new ObservationQueryService(taskRepo, sessionRepo, leaseRepo, artifactRepo, eventRepo);
|
|
258
|
+
const occupationRelease = new ResourceOccupationReleaseService(taskService, debugService, leaseService, sessionRepo, taskRepo);
|
|
259
|
+
const gateway = new AgentWsGateway(wsClient, leaseService, taskService, debugService, appPackageService, occupationRelease);
|
|
260
|
+
const pollClient = new PlatformCommandPollClient({
|
|
261
|
+
baseUrl: platformCommandPollBaseUrl,
|
|
262
|
+
agentId,
|
|
263
|
+
authToken: platformCallbackToken || wsAuthToken,
|
|
264
|
+
});
|
|
265
|
+
const commandPollLoop = new AgentCommandPollLoop(pollClient, gateway, wsClient, health, Number.isFinite(commandPollIntervalMs) && commandPollIntervalMs > 0 ? Math.floor(commandPollIntervalMs) : 3000);
|
|
266
|
+
const httpServer = new HttpServer(resourceService, observationQueryService, health, httpPort, httpAuthToken, agentId, leaseService, taskService, appPackageService, occupationRelease, appPackageUrlCache);
|
|
267
|
+
const iosWdaWatchdog = iosWdaWatchdogEnabled
|
|
268
|
+
? new IOSWdaWatchdog(runtimeContext.commandRunner, {
|
|
269
|
+
discoveryCommand: iosWdaDiscoveryCommand,
|
|
270
|
+
startCommandTemplate: iosWdaStartCommandTemplate,
|
|
271
|
+
stopCommand: iosWdaStopCommand,
|
|
272
|
+
intervalMs: Number.isFinite(iosWdaWatchdogIntervalMs) && iosWdaWatchdogIntervalMs > 0 ? Math.floor(iosWdaWatchdogIntervalMs) : 5_000,
|
|
273
|
+
startupTimeoutMs: Number.isFinite(iosWdaStartupTimeoutMs) && iosWdaStartupTimeoutMs > 0 ? Math.floor(iosWdaStartupTimeoutMs) : 120_000,
|
|
274
|
+
portRangeStart: Number.isFinite(iosWdaPortRangeStart) && iosWdaPortRangeStart > 0 ? Math.floor(iosWdaPortRangeStart) : 8200,
|
|
275
|
+
portRangeEnd: Number.isFinite(iosWdaPortRangeEnd) && iosWdaPortRangeEnd > 0 ? Math.floor(iosWdaPortRangeEnd) : 8399,
|
|
276
|
+
stateFilePath: iosWdaPortMapFilePath,
|
|
277
|
+
})
|
|
278
|
+
: null;
|
|
279
|
+
if (iosWdaWatchdog) {
|
|
280
|
+
void iosWdaWatchdog.start().catch((error) => {
|
|
281
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
282
|
+
console.error(`[main] ios watchdog start failed: ${message}`);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (cacheWatchdogEnabled) {
|
|
286
|
+
const maxBytes = parsePositiveByteCount(process.env.CACHE_WATCHDOG_MAX_BYTES?.trim());
|
|
287
|
+
if (maxBytes <= 0) {
|
|
288
|
+
console.warn("[main] CACHE_WATCHDOG_ENABLED=1 但 CACHE_WATCHDOG_MAX_BYTES 无效(示例:5368709120 或 5G),已跳过磁盘缓存 watchdog");
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
const rootsFromEnv = process.env.CACHE_WATCHDOG_ROOTS?.trim();
|
|
292
|
+
const defaultRoots = [getMidsceneReportRootDir(repoRoot)];
|
|
293
|
+
const downloadDir = resolveAppPackageDownloadDir(process.env.AGENT_APP_DOWNLOAD_DIR);
|
|
294
|
+
if (downloadDir) {
|
|
295
|
+
defaultRoots.push(downloadDir);
|
|
296
|
+
}
|
|
297
|
+
const roots = rootsFromEnv && rootsFromEnv.length > 0
|
|
298
|
+
? rootsFromEnv.split(",").map((s) => s.trim()).filter(Boolean)
|
|
299
|
+
: defaultRoots;
|
|
300
|
+
const interval = Number.isFinite(cacheWatchdogIntervalMs) && cacheWatchdogIntervalMs >= 10_000
|
|
301
|
+
? Math.floor(cacheWatchdogIntervalMs)
|
|
302
|
+
: 3_600_000;
|
|
303
|
+
cacheWatchdogStop = startDirCapacityWatchdog({
|
|
304
|
+
roots,
|
|
305
|
+
maxBytes,
|
|
306
|
+
intervalMs: interval,
|
|
307
|
+
log: (line) => {
|
|
308
|
+
console.info(line);
|
|
309
|
+
},
|
|
310
|
+
}).stop;
|
|
311
|
+
console.info(`[main] 磁盘缓存 watchdog 已启用:每 ${interval}ms 巡检;各 root 独立上限 ${maxBytes} 字节;roots=${roots.join(",")}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
await resourceService.refresh();
|
|
315
|
+
if (!localMcpMode) {
|
|
316
|
+
await agentRuntime.register(agentId);
|
|
317
|
+
gateway.start();
|
|
318
|
+
commandPollLoop.start();
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
console.info("[main] LOCAL_MCP_MODE=1:平台 WS、命令轮询和平台回调已禁用,仅保留本地 HTTP / 设备 / 任务能力。");
|
|
322
|
+
}
|
|
323
|
+
httpServer.start();
|
|
324
|
+
if (!localMcpMode) {
|
|
325
|
+
setInterval(async () => {
|
|
326
|
+
try {
|
|
327
|
+
await agentRuntime.heartbeat();
|
|
328
|
+
health.markHeartbeat();
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
332
|
+
}
|
|
333
|
+
}, 5000);
|
|
334
|
+
}
|
|
335
|
+
setInterval(async () => {
|
|
336
|
+
try {
|
|
337
|
+
await resourceService.refresh();
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
341
|
+
}
|
|
342
|
+
}, resourcesRefreshIntervalMs);
|
|
343
|
+
setInterval(() => {
|
|
344
|
+
void callbackForTasks.flushOutbox();
|
|
345
|
+
}, (() => {
|
|
346
|
+
const raw = Number(process.env.CALLBACK_OUTBOX_INTERVAL_MS ?? "120000");
|
|
347
|
+
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 120_000;
|
|
348
|
+
})());
|
|
349
|
+
const shutdown = async (signal) => {
|
|
350
|
+
if (appStopping)
|
|
351
|
+
return;
|
|
352
|
+
appStopping = true;
|
|
353
|
+
console.info(`[main] shutting down, signal=${signal}`);
|
|
354
|
+
if (iosWdaWatchdog) {
|
|
355
|
+
await iosWdaWatchdog.stop();
|
|
356
|
+
}
|
|
357
|
+
cacheWatchdogStop?.();
|
|
358
|
+
commandPollLoop.stop();
|
|
359
|
+
process.exit(0);
|
|
360
|
+
};
|
|
361
|
+
process.once("SIGINT", () => {
|
|
362
|
+
void shutdown("SIGINT");
|
|
363
|
+
});
|
|
364
|
+
process.once("SIGTERM", () => {
|
|
365
|
+
void shutdown("SIGTERM");
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
void bootstrap();
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export class AgentHttpClient {
|
|
2
|
+
config;
|
|
3
|
+
constructor(config) {
|
|
4
|
+
this.config = config;
|
|
5
|
+
}
|
|
6
|
+
async health() {
|
|
7
|
+
try {
|
|
8
|
+
const raw = await this.getJson("/health");
|
|
9
|
+
return { ok: true, raw };
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async listDevices() {
|
|
16
|
+
return this.getJson("/resources");
|
|
17
|
+
}
|
|
18
|
+
async installApp(resourceId, appRef) {
|
|
19
|
+
return this.postCommand({
|
|
20
|
+
type: "InstallAppCommand",
|
|
21
|
+
resourceId,
|
|
22
|
+
appRef,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
async createTask(input) {
|
|
26
|
+
return this.postCommand({
|
|
27
|
+
type: "CreateTaskCommand",
|
|
28
|
+
taskId: input.taskId,
|
|
29
|
+
requiredPlatform: input.requiredPlatform,
|
|
30
|
+
script: input.script,
|
|
31
|
+
scriptKind: input.scriptKind,
|
|
32
|
+
resourceId: input.resourceId,
|
|
33
|
+
runtimeEnv: input.runtimeEnv,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async getTask(taskId) {
|
|
37
|
+
try {
|
|
38
|
+
return await this.getJson(`/tasks/${encodeURIComponent(taskId)}`);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error instanceof AgentHttpError && error.status === 404)
|
|
42
|
+
return undefined;
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async listEvents(taskId, limit = 100) {
|
|
47
|
+
return this.getJson(`/events?taskId=${encodeURIComponent(taskId)}&limit=${encodeURIComponent(String(limit))}`);
|
|
48
|
+
}
|
|
49
|
+
async listArtifacts(taskId) {
|
|
50
|
+
return this.getJson(`/artifacts?taskId=${encodeURIComponent(taskId)}`);
|
|
51
|
+
}
|
|
52
|
+
async postCommand(command) {
|
|
53
|
+
return this.fetchJson("/platform/commands", {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: { "Content-Type": "application/json; charset=utf-8" },
|
|
56
|
+
body: JSON.stringify({ deliveryId: `mcp-${Date.now()}-${Math.random().toString(36).slice(2)}`, command }),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async getJson(path) {
|
|
60
|
+
return this.fetchJson(path, { method: "GET" });
|
|
61
|
+
}
|
|
62
|
+
async fetchJson(path, init) {
|
|
63
|
+
const url = new URL(path, this.config.baseUrl);
|
|
64
|
+
const headers = new Headers(init.headers);
|
|
65
|
+
if (this.config.token)
|
|
66
|
+
headers.set("Authorization", `Bearer ${this.config.token}`);
|
|
67
|
+
const resp = await fetch(url, { ...init, headers });
|
|
68
|
+
const text = await resp.text();
|
|
69
|
+
const body = text ? JSON.parse(text) : null;
|
|
70
|
+
if (!resp.ok) {
|
|
71
|
+
throw new AgentHttpError(resp.status, body && typeof body === "object" ? JSON.stringify(body) : text);
|
|
72
|
+
}
|
|
73
|
+
return body;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export class AgentHttpError extends Error {
|
|
77
|
+
status;
|
|
78
|
+
constructor(status, message) {
|
|
79
|
+
super(`Agent HTTP ${status}: ${message}`);
|
|
80
|
+
this.status = status;
|
|
81
|
+
}
|
|
82
|
+
}
|