fix: restore master agent relay guidance
This commit is contained in:
@@ -53,7 +53,7 @@ public class AiAccountsActivity extends BossScreenActivity {
|
||||
this,
|
||||
"AI 账号",
|
||||
"这里统一管理主 GPT、备用 GPT 与 API 容灾账号。",
|
||||
"轻点条目可编辑,按钮可切换、校验或删除。",
|
||||
"主 GPT 的登录发生在绑定设备上的 Codex / ChatGPT Plus,会在这里给登录指引。",
|
||||
null,
|
||||
null
|
||||
));
|
||||
@@ -143,16 +143,44 @@ public class AiAccountsActivity extends BossScreenActivity {
|
||||
activate.setEnabled(!account.optBoolean("isActive"));
|
||||
activate.setOnClickListener(v -> activateAccount(account));
|
||||
|
||||
Button loginGuide = null;
|
||||
if ("master_codex_node".equals(account.optString("provider"))) {
|
||||
loginGuide = BossUi.buildMiniActionButton(this, "登录指引", false);
|
||||
loginGuide.setOnClickListener(v -> showMasterNodeLoginGuide(account));
|
||||
}
|
||||
|
||||
Button validate = BossUi.buildMiniActionButton(this, "校验连接", false);
|
||||
validate.setOnClickListener(v -> validateAccount(account));
|
||||
|
||||
Button delete = BossUi.buildMiniActionButton(this, "删除账号", false);
|
||||
delete.setOnClickListener(v -> confirmDeleteAccount(account));
|
||||
card.addView(BossUi.buildInlineActionRow(this, activate, validate, delete));
|
||||
card.addView(loginGuide == null
|
||||
? BossUi.buildInlineActionRow(this, activate, validate, delete)
|
||||
: BossUi.buildInlineActionRow(this, activate, loginGuide, validate, delete));
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
private void showMasterNodeLoginGuide(JSONObject account) {
|
||||
String nodeLabel = account.optString("nodeLabel");
|
||||
if (nodeLabel == null || nodeLabel.trim().isEmpty()) {
|
||||
nodeLabel = account.optString("nodeId");
|
||||
}
|
||||
if (nodeLabel == null || nodeLabel.trim().isEmpty()) {
|
||||
nodeLabel = "绑定设备";
|
||||
}
|
||||
|
||||
String message = "主 GPT 不在手机里直接登录。\n\n"
|
||||
+ "请到绑定设备 " + nodeLabel + " 上打开 Codex / ChatGPT Plus 会话完成登录。\n"
|
||||
+ "登录完成后,回到这里点“校验连接”,确认主 Agent relay 已经接通。";
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("主 GPT 登录指引")
|
||||
.setMessage(message)
|
||||
.setPositiveButton("知道了", null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void openAccountEditor(@Nullable JSONObject existing, @Nullable String apiKeyHint) {
|
||||
final android.widget.EditText labelInput = BossUi.buildInput(this, "标签,例如 主 GPT", false);
|
||||
final android.widget.EditText displayNameInput = BossUi.buildInput(this, "显示名称", false);
|
||||
@@ -313,7 +341,7 @@ public class AiAccountsActivity extends BossScreenActivity {
|
||||
BossApiClient.ApiResponse response = apiClient.validateAccount(account.optString("accountId"));
|
||||
if (!response.ok()) throw new IllegalStateException(response.message());
|
||||
runOnUiThread(() -> {
|
||||
showMessage("账号校验成功");
|
||||
showMessage(response.message());
|
||||
reload();
|
||||
});
|
||||
} catch (Exception error) {
|
||||
|
||||
@@ -27,6 +27,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class BossApiClient {
|
||||
private static final int DEFAULT_CONNECT_TIMEOUT_MS = 12000;
|
||||
private static final int DEFAULT_READ_TIMEOUT_MS = 12000;
|
||||
private static final int MASTER_AGENT_READ_TIMEOUT_MS = 65000;
|
||||
private static final String PREFS_NAME = "boss_native_client";
|
||||
private static final String KEY_SESSION_COOKIE = "session_cookie";
|
||||
private static final String KEY_RESTORE_TOKEN = "restore_token";
|
||||
@@ -130,7 +133,14 @@ public class BossApiClient {
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("body", body);
|
||||
payload.put("kind", kind);
|
||||
return requestWithRestore("POST", "/api/v1/projects/" + encode(projectId) + "/messages", payload);
|
||||
int readTimeoutMs = "master-agent".equals(projectId) ? MASTER_AGENT_READ_TIMEOUT_MS : DEFAULT_READ_TIMEOUT_MS;
|
||||
return requestWithRestoreRaw(
|
||||
"POST",
|
||||
"/api/v1/projects/" + encode(projectId) + "/messages",
|
||||
payload.toString(),
|
||||
DEFAULT_CONNECT_TIMEOUT_MS,
|
||||
readTimeoutMs
|
||||
);
|
||||
}
|
||||
|
||||
public ApiResponse uploadAttachment(
|
||||
@@ -157,7 +167,7 @@ public class BossApiClient {
|
||||
String sourceType
|
||||
) throws IOException, JSONException {
|
||||
HttpURLConnection connection = openConnection("/api/v1/projects/" + encode(projectId) + "/attachments");
|
||||
prepareConnection(connection, "POST");
|
||||
prepareConnection(connection, "POST", DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
|
||||
connection.setDoOutput(true);
|
||||
|
||||
String boundary = "BossBoundary" + System.currentTimeMillis();
|
||||
@@ -373,27 +383,61 @@ public class BossApiClient {
|
||||
}
|
||||
|
||||
private ApiResponse requestWithRestore(String method, String path, JSONObject body) throws IOException, JSONException {
|
||||
return requestWithRestoreRaw(method, path, body == null ? null : body.toString());
|
||||
return requestWithRestoreRaw(
|
||||
method,
|
||||
path,
|
||||
body == null ? null : body.toString(),
|
||||
DEFAULT_CONNECT_TIMEOUT_MS,
|
||||
DEFAULT_READ_TIMEOUT_MS
|
||||
);
|
||||
}
|
||||
|
||||
private ApiResponse requestWithRestoreRaw(String method, String path, @Nullable String body) throws IOException, JSONException {
|
||||
ApiResponse response = requestRaw(method, path, body, true);
|
||||
return requestWithRestoreRaw(method, path, body, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
private ApiResponse requestWithRestoreRaw(
|
||||
String method,
|
||||
String path,
|
||||
@Nullable String body,
|
||||
int connectTimeoutMs,
|
||||
int readTimeoutMs
|
||||
) throws IOException, JSONException {
|
||||
ApiResponse response = requestRaw(method, path, body, true, connectTimeoutMs, readTimeoutMs);
|
||||
if (response.statusCode == 401 && !getRestoreToken().isEmpty()) {
|
||||
ApiResponse restored = restoreSession();
|
||||
if (restored.ok()) {
|
||||
return requestRaw(method, path, body, true);
|
||||
return requestRaw(method, path, body, true, connectTimeoutMs, readTimeoutMs);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private ApiResponse request(String method, String path, JSONObject body, boolean expectProtected) throws IOException, JSONException {
|
||||
return requestRaw(method, path, body == null ? null : body.toString(), expectProtected);
|
||||
return requestRaw(
|
||||
method,
|
||||
path,
|
||||
body == null ? null : body.toString(),
|
||||
expectProtected,
|
||||
DEFAULT_CONNECT_TIMEOUT_MS,
|
||||
DEFAULT_READ_TIMEOUT_MS
|
||||
);
|
||||
}
|
||||
|
||||
private ApiResponse requestRaw(String method, String path, @Nullable String body, boolean expectProtected) throws IOException, JSONException {
|
||||
return requestRaw(method, path, body, expectProtected, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
private ApiResponse requestRaw(
|
||||
String method,
|
||||
String path,
|
||||
@Nullable String body,
|
||||
boolean expectProtected,
|
||||
int connectTimeoutMs,
|
||||
int readTimeoutMs
|
||||
) throws IOException, JSONException {
|
||||
HttpURLConnection connection = openConnection(path);
|
||||
prepareConnection(connection, method);
|
||||
prepareConnection(connection, method, connectTimeoutMs, readTimeoutMs);
|
||||
|
||||
if (body != null) {
|
||||
connection.setDoOutput(true);
|
||||
@@ -411,10 +455,15 @@ public class BossApiClient {
|
||||
return (HttpURLConnection) new URL(baseUrl + path).openConnection();
|
||||
}
|
||||
|
||||
private void prepareConnection(HttpURLConnection connection, String method) throws IOException {
|
||||
private void prepareConnection(
|
||||
HttpURLConnection connection,
|
||||
String method,
|
||||
int connectTimeoutMs,
|
||||
int readTimeoutMs
|
||||
) throws IOException {
|
||||
connection.setRequestMethod(method);
|
||||
connection.setConnectTimeout(12000);
|
||||
connection.setReadTimeout(12000);
|
||||
connection.setConnectTimeout(connectTimeoutMs);
|
||||
connection.setReadTimeout(readTimeoutMs);
|
||||
connection.setUseCaches(false);
|
||||
connection.setDoInput(true);
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
@@ -500,7 +549,7 @@ public class BossApiClient {
|
||||
boolean expectProtected
|
||||
) throws IOException {
|
||||
HttpURLConnection connection = openConnection("/api/v1/attachments/" + encode(attachmentId) + "/download");
|
||||
prepareConnection(connection, "GET");
|
||||
prepareConnection(connection, "GET", DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
|
||||
int statusCode = connection.getResponseCode();
|
||||
captureSessionCookie(connection.getHeaderFields());
|
||||
|
||||
|
||||
@@ -52,6 +52,20 @@ public class BossApiClientDispatchPlansTest {
|
||||
assertEquals("{\"approvedTargetProjectIds\":[\"target-1\",\"target-2\"]}", connection.requestBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendProjectMessageUsesExtendedReadTimeoutForMasterAgent() throws Exception {
|
||||
RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/projects/master-agent/messages"));
|
||||
RecordingBossApiClient apiClient = new RecordingBossApiClient(connection);
|
||||
|
||||
BossApiClient.ApiResponse response = apiClient.sendProjectMessage("master-agent", "你好", "text");
|
||||
|
||||
assertEquals(200, response.statusCode);
|
||||
assertEquals("/api/v1/projects/master-agent/messages", apiClient.lastPath);
|
||||
assertEquals("POST", connection.requestMethodValue);
|
||||
assertEquals(12000, connection.connectTimeoutValue);
|
||||
assertEquals(65000, connection.readTimeoutValue);
|
||||
}
|
||||
|
||||
private static final class RecordingBossApiClient extends BossApiClient {
|
||||
private final RecordingConnection connection;
|
||||
private String lastPath = "";
|
||||
@@ -82,6 +96,8 @@ public class BossApiClientDispatchPlansTest {
|
||||
private final ByteArrayOutputStream requestBody = new ByteArrayOutputStream();
|
||||
private final Map<String, String> requestHeaders = new HashMap<>();
|
||||
private String requestMethodValue = "GET";
|
||||
private int connectTimeoutValue = 0;
|
||||
private int readTimeoutValue = 0;
|
||||
|
||||
RecordingConnection(URL url) {
|
||||
super(url);
|
||||
@@ -108,6 +124,16 @@ public class BossApiClientDispatchPlansTest {
|
||||
requestHeaders.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConnectTimeout(int timeout) {
|
||||
connectTimeoutValue = timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadTimeout(int timeout) {
|
||||
readTimeoutValue = timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestProperty(String key) {
|
||||
return requestHeaders.get(key);
|
||||
|
||||
Reference in New Issue
Block a user