ds-harness-remote 0.5.5 → 0.5.7
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/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +16 -14
- package/dist/cli.js.map +1 -1
- package/dist/client-locale.d.ts +5 -0
- package/dist/client-locale.d.ts.map +1 -1
- package/dist/client-locale.js +10 -0
- package/dist/client-locale.js.map +1 -1
- package/dist/client-runtime.d.ts +2 -0
- package/dist/client-runtime.d.ts.map +1 -1
- package/dist/client-runtime.js +26 -2
- package/dist/client-runtime.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.github.js +242 -30
- package/dist/client.js +242 -30
- package/dist/client.js.map +1 -1
- package/dist/codex/app-server.js +2 -0
- package/dist/codex/app-server.js.map +1 -1
- package/dist/codex/domain.d.ts.map +1 -1
- package/dist/codex/domain.js +29 -7
- package/dist/codex/domain.js.map +1 -1
- package/dist/harness-remote-bridge.d.ts +1 -1
- package/dist/harness-remote-bridge.d.ts.map +1 -1
- package/dist/harness-remote-bridge.js +2 -0
- package/dist/harness-remote-bridge.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +382 -60
- package/dist/index.js.map +4 -4
- package/dist/plugin-route-bridge.d.ts +23 -0
- package/dist/plugin-route-bridge.d.ts.map +1 -0
- package/dist/plugin-route-bridge.js +82 -0
- package/dist/plugin-route-bridge.js.map +1 -0
- package/dist/plugin-route-client.d.ts +20 -0
- package/dist/plugin-route-client.d.ts.map +1 -0
- package/dist/plugin-route-client.js +242 -0
- package/dist/plugin-route-client.js.map +1 -0
- package/dist/plugin-route-dispatcher.d.ts +10 -0
- package/dist/plugin-route-dispatcher.d.ts.map +1 -0
- package/dist/plugin-route-dispatcher.js +61 -0
- package/dist/plugin-route-dispatcher.js.map +1 -0
- package/dist/rpc-router.d.ts +4 -1
- package/dist/rpc-router.d.ts.map +1 -1
- package/dist/rpc-router.js +11 -1
- package/dist/rpc-router.js.map +1 -1
- package/dist/server-api.d.ts +9 -1
- package/dist/server-api.d.ts.map +1 -1
- package/dist/server-api.js +61 -2
- package/dist/server-api.js.map +1 -1
- package/dist/service.d.ts +5 -2
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +18 -2
- package/dist/service.js.map +1 -1
- package/dist/tui-command.d.ts.map +1 -1
- package/dist/tui-command.js +58 -18
- package/dist/tui-command.js.map +1 -1
- package/dist/typert-gateway-contract.d.ts +4 -0
- package/dist/typert-gateway-contract.d.ts.map +1 -1
- package/dist/typert-gateway-switch.d.ts.map +1 -1
- package/dist/typert-gateway-switch.js +16 -1
- package/dist/typert-gateway-switch.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dsh-plugin.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4185,7 +4185,8 @@ var rpcMethods = [
|
|
|
4185
4185
|
"acp.session.prompt",
|
|
4186
4186
|
"acp.session.respond_permission",
|
|
4187
4187
|
"acp.session.cancel",
|
|
4188
|
-
"acp.session.set_mode"
|
|
4188
|
+
"acp.session.set_mode",
|
|
4189
|
+
"plugin.route.call"
|
|
4189
4190
|
];
|
|
4190
4191
|
var errorCodes = [
|
|
4191
4192
|
// Protocol / Version
|
|
@@ -6549,6 +6550,7 @@ var RtcDataChannelTransport = class {
|
|
|
6549
6550
|
bytesReceived = 0;
|
|
6550
6551
|
selected;
|
|
6551
6552
|
selectedMode;
|
|
6553
|
+
selectedRelayUrlValue;
|
|
6552
6554
|
selectedPathTelemetry;
|
|
6553
6555
|
localCandidateTelemetry = emptyCandidateTelemetry();
|
|
6554
6556
|
remoteCandidateTelemetry = emptyCandidateTelemetry();
|
|
@@ -6646,6 +6648,9 @@ var RtcDataChannelTransport = class {
|
|
|
6646
6648
|
selectedTransport() {
|
|
6647
6649
|
return this.selected;
|
|
6648
6650
|
}
|
|
6651
|
+
selectedRelayUrl() {
|
|
6652
|
+
return this.selectedRelayUrlValue;
|
|
6653
|
+
}
|
|
6649
6654
|
selectedPathMode() {
|
|
6650
6655
|
return this.selectedMode;
|
|
6651
6656
|
}
|
|
@@ -6856,6 +6861,7 @@ var RtcDataChannelTransport = class {
|
|
|
6856
6861
|
}
|
|
6857
6862
|
this.selected = selected?.transport;
|
|
6858
6863
|
this.selectedMode = selected?.mode;
|
|
6864
|
+
this.selectedRelayUrlValue = selected?.relayUrl;
|
|
6859
6865
|
this.selectedPathTelemetry = selectedPathTelemetry(selected);
|
|
6860
6866
|
this.emitDiagnostic({
|
|
6861
6867
|
type: "selected-path",
|
|
@@ -7022,6 +7028,7 @@ function inspectSelectedPath(stats) {
|
|
|
7022
7028
|
let selected;
|
|
7023
7029
|
if (localType === "relay" || remoteType === "relay")
|
|
7024
7030
|
selected = { transport: "turn", mode: "TURN" };
|
|
7031
|
+
const relayUrl = localType === "relay" ? stringStat(local, "url") : void 0;
|
|
7025
7032
|
if (local === void 0 && remote === void 0)
|
|
7026
7033
|
continue;
|
|
7027
7034
|
const lan = isLanCandidatePair(localType, remoteType, localAddressScope, remoteAddressScope);
|
|
@@ -7029,6 +7036,7 @@ function inspectSelectedPath(stats) {
|
|
|
7029
7036
|
const currentRoundTripTime = numberStat(pair, "currentRoundTripTime");
|
|
7030
7037
|
return {
|
|
7031
7038
|
...selected,
|
|
7039
|
+
...relayUrl === void 0 ? {} : { relayUrl },
|
|
7032
7040
|
localCandidateType: stringStat(local, "candidateType"),
|
|
7033
7041
|
remoteCandidateType: stringStat(remote, "candidateType"),
|
|
7034
7042
|
localAddressScope,
|
|
@@ -7181,6 +7189,7 @@ function selectedPathTelemetry(details) {
|
|
|
7181
7189
|
return {
|
|
7182
7190
|
...details.transport === void 0 ? {} : { transport: details.transport },
|
|
7183
7191
|
...details.mode === void 0 ? {} : { mode: details.mode },
|
|
7192
|
+
...details.relayUrl === void 0 ? {} : { relayUrl: details.relayUrl },
|
|
7184
7193
|
...details.localCandidateType === void 0 ? {} : { localCandidateType: details.localCandidateType },
|
|
7185
7194
|
...details.remoteCandidateType === void 0 ? {} : { remoteCandidateType: details.remoteCandidateType },
|
|
7186
7195
|
...details.localAddressScope === void 0 ? {} : { localAddressScope: details.localAddressScope },
|
|
@@ -7460,7 +7469,7 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
7460
7469
|
this.lastRtcDiagnostics = void 0;
|
|
7461
7470
|
if (wantWebRtc) {
|
|
7462
7471
|
try {
|
|
7463
|
-
const rtcSelected = await this.tryWebRtc();
|
|
7472
|
+
const rtcSelected = (await this.tryWebRtc()).transport;
|
|
7464
7473
|
const wireSelected = rtcSelected === "lan" && !preferred.includes("lan") && preferred.includes("p2p") ? "p2p" : rtcSelected;
|
|
7465
7474
|
if (!preferred.includes(wireSelected)) {
|
|
7466
7475
|
await this.rtc?.close();
|
|
@@ -7494,10 +7503,12 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
7494
7503
|
selected = "relay";
|
|
7495
7504
|
}
|
|
7496
7505
|
this.selected = selected;
|
|
7506
|
+
const turnNode = selected === "turn" ? this.rtc?.selectedRelayUrl() : void 0;
|
|
7497
7507
|
this.sendControl("transport.selected", {
|
|
7498
7508
|
connectionId: this.connectionId,
|
|
7499
7509
|
targetDeviceId: this.options.targetDeviceId,
|
|
7500
|
-
transport: selected
|
|
7510
|
+
transport: selected,
|
|
7511
|
+
...turnNode === void 0 ? {} : { turnNode }
|
|
7501
7512
|
});
|
|
7502
7513
|
this.finishConnection();
|
|
7503
7514
|
}
|
|
@@ -7558,7 +7569,7 @@ var AdaptiveTransport = class extends BaseTransport {
|
|
|
7558
7569
|
throw error;
|
|
7559
7570
|
}
|
|
7560
7571
|
this.lastRtcDiagnostics = rtc.diagnostics();
|
|
7561
|
-
return rtc.selectedTransport() ?? "p2p";
|
|
7572
|
+
return { transport: rtc.selectedTransport() ?? "p2p", relayUrl: rtc.selectedRelayUrl() };
|
|
7562
7573
|
}
|
|
7563
7574
|
sendRtcSignal(signal) {
|
|
7564
7575
|
if (this.connectionId === void 0)
|
|
@@ -15090,6 +15101,97 @@ async function readHarnessDistributionVersion(entrypoint = process.argv[1]) {
|
|
|
15090
15101
|
}
|
|
15091
15102
|
}
|
|
15092
15103
|
|
|
15104
|
+
// src/plugin-route-bridge.ts
|
|
15105
|
+
var PLUGIN_ROUTE_ALLOWLIST = ["api/bot"];
|
|
15106
|
+
var BOT_REMOTE_OPERATIONS = [
|
|
15107
|
+
"state",
|
|
15108
|
+
"models",
|
|
15109
|
+
"efforts",
|
|
15110
|
+
"exchange",
|
|
15111
|
+
"earlier",
|
|
15112
|
+
"usage",
|
|
15113
|
+
"room-progress",
|
|
15114
|
+
"schedules",
|
|
15115
|
+
"create-bot",
|
|
15116
|
+
"duplicate-bot",
|
|
15117
|
+
"update-bot",
|
|
15118
|
+
"delete-bot",
|
|
15119
|
+
"set-main",
|
|
15120
|
+
"set-flags",
|
|
15121
|
+
"create-room",
|
|
15122
|
+
"update-room",
|
|
15123
|
+
"delete-room",
|
|
15124
|
+
"add-agent",
|
|
15125
|
+
"agent-session",
|
|
15126
|
+
"agent-adopt",
|
|
15127
|
+
"agent-archive",
|
|
15128
|
+
"agent-rename",
|
|
15129
|
+
"remove-agent",
|
|
15130
|
+
"send",
|
|
15131
|
+
"answer-question",
|
|
15132
|
+
"dismiss-question",
|
|
15133
|
+
"approval-answer",
|
|
15134
|
+
"stop-member-work"
|
|
15135
|
+
];
|
|
15136
|
+
function isAllowedPluginRoute(route) {
|
|
15137
|
+
return PLUGIN_ROUTE_ALLOWLIST.some((allowed) => allowed === route);
|
|
15138
|
+
}
|
|
15139
|
+
var callSchema = external_exports.object({
|
|
15140
|
+
route: external_exports.enum(PLUGIN_ROUTE_ALLOWLIST),
|
|
15141
|
+
body: external_exports.object({
|
|
15142
|
+
endpoint: external_exports.enum(BOT_REMOTE_OPERATIONS),
|
|
15143
|
+
payload: external_exports.record(external_exports.string(), external_exports.unknown()).nullable().optional()
|
|
15144
|
+
}).strict().superRefine((body, ctx) => {
|
|
15145
|
+
if (body.endpoint === "approval-answer" && body.payload?.choice !== "allow" && body.payload?.choice !== "reject") {
|
|
15146
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, message: "Only one-time approval or rejection is supported." });
|
|
15147
|
+
}
|
|
15148
|
+
})
|
|
15149
|
+
}).strict();
|
|
15150
|
+
var PLUGIN_ROUTE_TIMEOUT_MS = 3e4;
|
|
15151
|
+
var PluginRouteBridge = class {
|
|
15152
|
+
constructor(dispatch, logger) {
|
|
15153
|
+
this.dispatch = dispatch;
|
|
15154
|
+
this.logger = logger;
|
|
15155
|
+
}
|
|
15156
|
+
async call(input2) {
|
|
15157
|
+
let params;
|
|
15158
|
+
try {
|
|
15159
|
+
params = callSchema.parse(input2);
|
|
15160
|
+
} catch (error) {
|
|
15161
|
+
if (error instanceof external_exports.ZodError) {
|
|
15162
|
+
throw new RpcError("INVALID_MESSAGE", "The plugin route call is invalid for the remote channel.");
|
|
15163
|
+
}
|
|
15164
|
+
throw error;
|
|
15165
|
+
}
|
|
15166
|
+
if (serializedBytes(params) > MAX_SECURE_MESSAGE_BYTES) {
|
|
15167
|
+
throw new RpcError("INVALID_MESSAGE", `The plugin route call exceeds the ${MAX_SECURE_MESSAGE_BYTES}-byte remote limit.`);
|
|
15168
|
+
}
|
|
15169
|
+
const startedAt = performance.now();
|
|
15170
|
+
let result;
|
|
15171
|
+
try {
|
|
15172
|
+
result = await this.dispatch(params.route, params.body, AbortSignal.timeout(PLUGIN_ROUTE_TIMEOUT_MS));
|
|
15173
|
+
} catch (error) {
|
|
15174
|
+
this.logger?.warn("plugin route call failed", {
|
|
15175
|
+
route: params.route,
|
|
15176
|
+
durationMs: Math.round(performance.now() - startedAt)
|
|
15177
|
+
});
|
|
15178
|
+
throw error;
|
|
15179
|
+
}
|
|
15180
|
+
if (serializedBytes(result) > MAX_SECURE_MESSAGE_BYTES) {
|
|
15181
|
+
throw new RpcError("RESPONSE_TOO_LARGE", "The plugin route response exceeds the bounded remote limit.");
|
|
15182
|
+
}
|
|
15183
|
+
this.logger?.debug("plugin route call ok", {
|
|
15184
|
+
route: params.route,
|
|
15185
|
+
status: result.status,
|
|
15186
|
+
durationMs: Math.round(performance.now() - startedAt)
|
|
15187
|
+
});
|
|
15188
|
+
return { status: result.status, json: result.json };
|
|
15189
|
+
}
|
|
15190
|
+
};
|
|
15191
|
+
function serializedBytes(value) {
|
|
15192
|
+
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
15193
|
+
}
|
|
15194
|
+
|
|
15093
15195
|
// src/remote-api-proxy.ts
|
|
15094
15196
|
var DIRECT_API_CALL_BYTES = 2 * 1024 * 1024;
|
|
15095
15197
|
var AGENT_PRESET_SETTINGS_NS = "agent-presets";
|
|
@@ -20243,7 +20345,7 @@ function normalizeServerUrl(value) {
|
|
|
20243
20345
|
}
|
|
20244
20346
|
|
|
20245
20347
|
// src/version.ts
|
|
20246
|
-
var PLUGIN_VERSION = "0.5.
|
|
20348
|
+
var PLUGIN_VERSION = "0.5.7";
|
|
20247
20349
|
|
|
20248
20350
|
// src/server-api.ts
|
|
20249
20351
|
var TERMINAL_CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/u;
|
|
@@ -20324,13 +20426,54 @@ var HostServerApi = class {
|
|
|
20324
20426
|
isAdmin: login2.isAdmin
|
|
20325
20427
|
};
|
|
20326
20428
|
}
|
|
20429
|
+
async oauthProviders() {
|
|
20430
|
+
let body;
|
|
20431
|
+
try {
|
|
20432
|
+
body = await this.publicRequest("/api/v1/auth/oauth/providers", { method: "GET" });
|
|
20433
|
+
} catch (error) {
|
|
20434
|
+
if (!(error instanceof ServerApiError) || error.status !== 404) throw error;
|
|
20435
|
+
const legacy = [
|
|
20436
|
+
{ id: "zhihu", displayName: "Zhihu", startUrl: "/api/v1/auth/oauth/start", statusUrl: "/api/v1/auth/oauth/status" },
|
|
20437
|
+
{ id: "github", displayName: "GitHub", startUrl: "/api/v1/auth/oauth/github/start", statusUrl: "/api/v1/auth/oauth/github/status" }
|
|
20438
|
+
];
|
|
20439
|
+
const items = await Promise.all(legacy.map(async ({ statusUrl, ...provider }) => {
|
|
20440
|
+
try {
|
|
20441
|
+
const status2 = requireRecord(await this.publicRequest(statusUrl, { method: "GET" }), "OAuth status");
|
|
20442
|
+
if (typeof status2.configured !== "boolean") throw invalidOAuthProviders();
|
|
20443
|
+
return status2.configured ? [{ ...provider, qrStartUrl: `/api/v1/auth/oauth/qr/start?provider=${provider.id}` }] : [];
|
|
20444
|
+
} catch (reason) {
|
|
20445
|
+
if (reason instanceof ServerApiError && reason.status === 404) return [];
|
|
20446
|
+
throw reason;
|
|
20447
|
+
}
|
|
20448
|
+
}));
|
|
20449
|
+
return items.flat();
|
|
20450
|
+
}
|
|
20451
|
+
const value = requireRecord(body, "OAuth providers");
|
|
20452
|
+
if (!Array.isArray(value.items)) throw invalidOAuthProviders();
|
|
20453
|
+
const ids = /* @__PURE__ */ new Set();
|
|
20454
|
+
return value.items.map((item) => {
|
|
20455
|
+
const provider = requireRecord(item, "OAuth provider");
|
|
20456
|
+
if (typeof provider.id !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/.test(provider.id) || ids.has(provider.id) || typeof provider.displayName !== "string" || !provider.displayName.trim() || provider.displayName.length > 128 || TERMINAL_CONTROL_CHARACTERS.test(provider.displayName) || !isOAuthEntryPath(provider.startUrl) || provider.qrStartUrl != null && !isOAuthEntryPath(provider.qrStartUrl)) throw invalidOAuthProviders();
|
|
20457
|
+
ids.add(provider.id);
|
|
20458
|
+
return {
|
|
20459
|
+
id: provider.id,
|
|
20460
|
+
displayName: provider.displayName,
|
|
20461
|
+
startUrl: provider.startUrl,
|
|
20462
|
+
...typeof provider.qrStartUrl === "string" ? { qrStartUrl: provider.qrStartUrl } : {}
|
|
20463
|
+
};
|
|
20464
|
+
});
|
|
20465
|
+
}
|
|
20327
20466
|
async startOAuthQrLogin(provider = "zhihu") {
|
|
20328
|
-
const
|
|
20467
|
+
const entry = (await this.oauthProviders()).find((item) => item.id === provider);
|
|
20468
|
+
if (entry?.qrStartUrl == null) {
|
|
20469
|
+
throw new ServerApiError("OAUTH_PROVIDER_UNAVAILABLE", "This OAuth provider is unavailable for QR login.", false);
|
|
20470
|
+
}
|
|
20471
|
+
const value = requireRecord(await this.publicRequest(entry.qrStartUrl, {
|
|
20329
20472
|
method: "POST",
|
|
20330
20473
|
body: "{}"
|
|
20331
20474
|
}), "QR login");
|
|
20332
20475
|
const scanUrl = normalizeOAuthScanUrl(value.scanUrl, this.baseUrl);
|
|
20333
|
-
if (typeof value.qrId !== "string" || value.qrId.length < 20 || scanUrl === void 0 || !Number.isSafeInteger(value.expiresIn) || provider
|
|
20476
|
+
if (typeof value.qrId !== "string" || value.qrId.length < 20 || scanUrl === void 0 || !Number.isSafeInteger(value.expiresIn) || provider !== "zhihu" && value.provider !== provider || value.provider !== void 0 && value.provider !== provider) {
|
|
20334
20477
|
throw new ServerApiError("INVALID_MESSAGE", "The Server returned an invalid QR login session.", false);
|
|
20335
20478
|
}
|
|
20336
20479
|
return { qrId: value.qrId, scanUrl, expiresIn: value.expiresIn };
|
|
@@ -20566,6 +20709,12 @@ var HostServerApi = class {
|
|
|
20566
20709
|
return this.identity;
|
|
20567
20710
|
}
|
|
20568
20711
|
};
|
|
20712
|
+
function isOAuthEntryPath(value) {
|
|
20713
|
+
return typeof value === "string" && /^\/api\/v1\/auth\/[^\s\\#]*$/.test(value) && !TERMINAL_CONTROL_CHARACTERS.test(value) && !value.split("?")[0]?.split("/").some((segment) => segment === "." || segment === ".." || /%/u.test(segment));
|
|
20714
|
+
}
|
|
20715
|
+
function invalidOAuthProviders() {
|
|
20716
|
+
return new ServerApiError("INVALID_MESSAGE", "The Server returned an invalid OAuth provider list.", false);
|
|
20717
|
+
}
|
|
20569
20718
|
function normalizeOAuthScanUrl(value, baseUrl) {
|
|
20570
20719
|
if (typeof value !== "string" || TERMINAL_CONTROL_CHARACTERS.test(value)) return void 0;
|
|
20571
20720
|
try {
|
|
@@ -20750,7 +20899,10 @@ var TypertGatewaySwitch = class {
|
|
|
20750
20899
|
this.runtime.stream = (request) => this.remoteTarget === void 0 || isLocalOnlyEndpoint(endpointOf(request)) ? this.localStream(request) : this.remoteTarget.open(endpointOf(request), { args: request.args }, request.signal ?? new AbortController().signal);
|
|
20751
20900
|
}
|
|
20752
20901
|
if (this.originalDispatch !== void 0) {
|
|
20753
|
-
this.runtime.dispatchRpc = (endpoint, payload, signal) => this.remoteTarget === void 0 || isLocalOnlyEndpoint(endpoint) ? this.localDispatch(endpoint, payload, signal) : this.remoteTarget.dispatch(endpoint, payload, signal)
|
|
20902
|
+
this.runtime.dispatchRpc = (endpoint, payload, signal) => this.remoteTarget === void 0 || isLocalOnlyEndpoint(endpoint) ? this.localDispatch(endpoint, payload, signal) : this.remoteTarget.dispatch(endpoint, payload, signal).then(
|
|
20903
|
+
(result) => workspaceConnectionResult(endpoint, result),
|
|
20904
|
+
(error) => ({ ok: false, error: this.failure(error) })
|
|
20905
|
+
);
|
|
20754
20906
|
}
|
|
20755
20907
|
if (this.originalOpen !== void 0) {
|
|
20756
20908
|
const open = this.originalOpen;
|
|
@@ -20826,6 +20978,18 @@ var TypertGatewaySwitch = class {
|
|
|
20826
20978
|
return { code, message: source.message, details };
|
|
20827
20979
|
}
|
|
20828
20980
|
};
|
|
20981
|
+
function workspaceConnectionResult(endpoint, result) {
|
|
20982
|
+
if (endpoint !== "workspaceFiles/readBytes" && endpoint !== "officeToPdf/render") return result;
|
|
20983
|
+
if (!result.ok || !isRecord9(result.value) || !(result.value.data instanceof Uint8Array)) return result;
|
|
20984
|
+
return {
|
|
20985
|
+
...result,
|
|
20986
|
+
value: { ...result.value, data: null },
|
|
20987
|
+
attachments: [
|
|
20988
|
+
...(result.attachments ?? []).filter((attachment) => attachment.path.length !== 1 || attachment.path[0] !== "data"),
|
|
20989
|
+
{ path: ["data"], bytes: result.value.data }
|
|
20990
|
+
]
|
|
20991
|
+
};
|
|
20992
|
+
}
|
|
20829
20993
|
function usesRc1Arity(open) {
|
|
20830
20994
|
return open.length >= 5;
|
|
20831
20995
|
}
|
|
@@ -22414,6 +22578,16 @@ var ClientModeRuntime = class {
|
|
|
22414
22578
|
}
|
|
22415
22579
|
return remote.client.rpc("fileviewer.call", { endpoint, payload }, signal);
|
|
22416
22580
|
}
|
|
22581
|
+
async pluginRoute(route, body, signal) {
|
|
22582
|
+
const remote = this.connected;
|
|
22583
|
+
if (remote === void 0 || this.status().mode !== "remote") {
|
|
22584
|
+
throw new ClientModeError("REMOTE_NOT_CONNECTED", "No Remote Host is selected.", true);
|
|
22585
|
+
}
|
|
22586
|
+
if (!remote.features.pluginRoute) {
|
|
22587
|
+
throw new ClientModeError("FEATURE_NOT_SUPPORTED", "The connected Host cannot forward plugin routes.");
|
|
22588
|
+
}
|
|
22589
|
+
return remote.client.rpc("plugin.route.call", { route, body }, signal);
|
|
22590
|
+
}
|
|
22417
22591
|
activeRemote() {
|
|
22418
22592
|
return this.connected;
|
|
22419
22593
|
}
|
|
@@ -22793,6 +22967,7 @@ var ClientModeRuntime = class {
|
|
|
22793
22967
|
try {
|
|
22794
22968
|
if (endpoint === "status") return ok2(await this.detailedStatus());
|
|
22795
22969
|
if (endpoint === "devices") return ok2(await this.devices());
|
|
22970
|
+
if (endpoint === "client.account.providers") return ok2(await this.server.oauthProviders());
|
|
22796
22971
|
if (endpoint === "client.account.login") {
|
|
22797
22972
|
const value = record5(payload);
|
|
22798
22973
|
if (typeof value.email !== "string" || typeof value.password !== "string") {
|
|
@@ -22803,8 +22978,8 @@ var ClientModeRuntime = class {
|
|
|
22803
22978
|
if (endpoint === "client.account.qr.start") {
|
|
22804
22979
|
const value = record5(payload);
|
|
22805
22980
|
const provider = value.provider ?? "zhihu";
|
|
22806
|
-
if (provider !== "
|
|
22807
|
-
throw new ClientModeError("INVALID_MESSAGE", "
|
|
22981
|
+
if (typeof provider !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/.test(provider)) {
|
|
22982
|
+
throw new ClientModeError("INVALID_MESSAGE", "An OAuth provider ID is required.");
|
|
22808
22983
|
}
|
|
22809
22984
|
return ok2(await this.startClientOAuthQrLogin(provider));
|
|
22810
22985
|
}
|
|
@@ -22946,6 +23121,14 @@ var ClientModeRuntime = class {
|
|
|
22946
23121
|
const host = this.requireLocalCodex();
|
|
22947
23122
|
return ok2(await host.codexRespond(value, signal));
|
|
22948
23123
|
}
|
|
23124
|
+
if (endpoint === "plugin.route") {
|
|
23125
|
+
const value = record5(payload);
|
|
23126
|
+
if (typeof value.route !== "string" || !isAllowedPluginRoute(value.route)) {
|
|
23127
|
+
throw new ClientModeError("INVALID_MESSAGE", "A valid plugin route is required.");
|
|
23128
|
+
}
|
|
23129
|
+
if (!isRecord10(value.body)) throw new ClientModeError("INVALID_MESSAGE", "A plugin route body object is required.");
|
|
23130
|
+
return ok2(await this.pluginRoute(value.route, value.body, signal));
|
|
23131
|
+
}
|
|
22949
23132
|
if (endpoint === "codex.stream.open") return ok2(await this.openCodexStream(payload, signal));
|
|
22950
23133
|
if (endpoint === "codex.stream.next") return ok2(await this.nextCodexFrames(payload, signal));
|
|
22951
23134
|
if (endpoint === "codex.stream.close") return ok2(await this.closeCodexStream(payload));
|
|
@@ -23128,7 +23311,8 @@ function remoteHostFeatures(clientVersion) {
|
|
|
23128
23311
|
remoteGateway: false,
|
|
23129
23312
|
codex: false,
|
|
23130
23313
|
cursor: false,
|
|
23131
|
-
antigravity: false
|
|
23314
|
+
antigravity: false,
|
|
23315
|
+
pluginRoute: false
|
|
23132
23316
|
};
|
|
23133
23317
|
}
|
|
23134
23318
|
async function probeRemoteHostFeatures(client, clientVersion) {
|
|
@@ -23172,7 +23356,8 @@ async function probeRemoteHostFeatures(client, clientVersion) {
|
|
|
23172
23356
|
cursor: cursor2,
|
|
23173
23357
|
antigravity,
|
|
23174
23358
|
...capabilities.has("agent.acp.antigravity.model-load.v1") ? { acpModelLoadsSession: true } : {},
|
|
23175
|
-
...workspaceTypes === void 0 ? {} : { workspaceTypes }
|
|
23359
|
+
...workspaceTypes === void 0 ? {} : { workspaceTypes },
|
|
23360
|
+
pluginRoute: capabilities.has("plugin.route.v1")
|
|
23176
23361
|
};
|
|
23177
23362
|
}
|
|
23178
23363
|
async function waitForCodexFrames(stream, signal) {
|
|
@@ -23794,6 +23979,7 @@ function safeUpstreamError(value) {
|
|
|
23794
23979
|
if (!isRecord11(value) || typeof value.message !== "string") return "Codex App Server rejected the request.";
|
|
23795
23980
|
const message = value.message.toLowerCase();
|
|
23796
23981
|
if (message.includes("active writer")) return "Codex thread already has an active writer.";
|
|
23982
|
+
if (message.includes("no rollout found")) return "Codex thread state is not persisted yet.";
|
|
23797
23983
|
return message.includes("not initialized") ? "Codex App Server is not initialized." : "Codex App Server rejected the request.";
|
|
23798
23984
|
}
|
|
23799
23985
|
function isRecord11(value) {
|
|
@@ -24634,13 +24820,23 @@ var CodexRemoteDomain = class {
|
|
|
24634
24820
|
try {
|
|
24635
24821
|
await this.callUpstream("thread/settings/update", { threadId, ...settings });
|
|
24636
24822
|
result = { thread: allowed.thread, ...settings, sandbox: settings.sandboxPolicy };
|
|
24637
|
-
} catch {
|
|
24638
|
-
|
|
24639
|
-
...
|
|
24640
|
-
|
|
24641
|
-
|
|
24642
|
-
|
|
24643
|
-
|
|
24823
|
+
} catch (error) {
|
|
24824
|
+
if (isUnpersistedThreadError(error)) {
|
|
24825
|
+
return { thread: allowed.thread, ...settings, sandbox: settings.sandboxPolicy };
|
|
24826
|
+
}
|
|
24827
|
+
try {
|
|
24828
|
+
result = await this.callUpstream("thread/resume", {
|
|
24829
|
+
...permission.params,
|
|
24830
|
+
...allowed.cwd === void 0 ? {} : { cwd: allowed.cwd },
|
|
24831
|
+
...codexThreadPermissionParams(permission),
|
|
24832
|
+
excludeTurns: true
|
|
24833
|
+
});
|
|
24834
|
+
} catch (resumeError) {
|
|
24835
|
+
if (isUnpersistedThreadError(resumeError)) {
|
|
24836
|
+
return { thread: allowed.thread, ...settings, sandbox: settings.sandboxPolicy };
|
|
24837
|
+
}
|
|
24838
|
+
throw resumeError;
|
|
24839
|
+
}
|
|
24644
24840
|
await this.assertResultThreadAllowed(result);
|
|
24645
24841
|
if (codexPermissionPresetFromResponse(result) !== params.permissionPreset) {
|
|
24646
24842
|
await this.callUpstream("thread/settings/update", { threadId, ...settings });
|
|
@@ -25324,6 +25520,9 @@ function maskId(value) {
|
|
|
25324
25520
|
function isActiveWriterMessage(message) {
|
|
25325
25521
|
return message.toLowerCase().includes("active writer");
|
|
25326
25522
|
}
|
|
25523
|
+
function isUnpersistedThreadError(error) {
|
|
25524
|
+
return error instanceof RpcError && error.code === "CODEX_UPSTREAM_ERROR" && error.message.toLowerCase().includes("not persisted yet");
|
|
25525
|
+
}
|
|
25327
25526
|
|
|
25328
25527
|
// src/workspace-bridge.ts
|
|
25329
25528
|
import { spawn as spawn3 } from "node:child_process";
|
|
@@ -29345,7 +29544,8 @@ var apiMethods = /* @__PURE__ */ new Set([
|
|
|
29345
29544
|
"agent.acp.transfer.chunk",
|
|
29346
29545
|
"agent.acp.transfer.commit",
|
|
29347
29546
|
"agent.acp.transfer.read",
|
|
29348
|
-
"agent.acp.transfer.close"
|
|
29547
|
+
"agent.acp.transfer.close",
|
|
29548
|
+
"plugin.route.call"
|
|
29349
29549
|
]);
|
|
29350
29550
|
var HOST_CAPABILITIES = [
|
|
29351
29551
|
"harness.api.v1",
|
|
@@ -29359,7 +29559,7 @@ var HOST_CAPABILITIES = [
|
|
|
29359
29559
|
"agent.acp.transfer.v1"
|
|
29360
29560
|
];
|
|
29361
29561
|
var RpcRouter = class {
|
|
29362
|
-
constructor(harnessApi, maxPending = 128, logger, fileViewer, harnessRemote, capabilities = () => HOST_CAPABILITIES, codex, acp, loopback, workspaceTypes) {
|
|
29562
|
+
constructor(harnessApi, maxPending = 128, logger, fileViewer, harnessRemote, capabilities = () => HOST_CAPABILITIES, codex, acp, loopback, workspaceTypes, pluginRoute) {
|
|
29363
29563
|
this.harnessApi = harnessApi;
|
|
29364
29564
|
this.maxPending = maxPending;
|
|
29365
29565
|
this.logger = logger;
|
|
@@ -29370,6 +29570,7 @@ var RpcRouter = class {
|
|
|
29370
29570
|
this.acp = acp;
|
|
29371
29571
|
this.loopback = loopback;
|
|
29372
29572
|
this.workspaceTypes = workspaceTypes;
|
|
29573
|
+
this.pluginRoute = pluginRoute;
|
|
29373
29574
|
}
|
|
29374
29575
|
active = 0;
|
|
29375
29576
|
async closePeerStreams() {
|
|
@@ -29518,6 +29719,8 @@ var RpcRouter = class {
|
|
|
29518
29719
|
return this.requireAcp().readTransfer(params);
|
|
29519
29720
|
case "agent.acp.transfer.close":
|
|
29520
29721
|
return this.requireAcp().closeTransfer(params);
|
|
29722
|
+
case "plugin.route.call":
|
|
29723
|
+
return this.requirePluginRoute().call(params);
|
|
29521
29724
|
default:
|
|
29522
29725
|
throw new RpcError("METHOD_NOT_FOUND", "The requested method does not exist.");
|
|
29523
29726
|
}
|
|
@@ -29546,6 +29749,12 @@ var RpcRouter = class {
|
|
|
29546
29749
|
}
|
|
29547
29750
|
return this.acp;
|
|
29548
29751
|
}
|
|
29752
|
+
requirePluginRoute() {
|
|
29753
|
+
if (this.pluginRoute === void 0) {
|
|
29754
|
+
throw new RpcError("FEATURE_NOT_SUPPORTED", "This Host cannot forward local plugin routes.");
|
|
29755
|
+
}
|
|
29756
|
+
return this.pluginRoute;
|
|
29757
|
+
}
|
|
29549
29758
|
};
|
|
29550
29759
|
function errorResponse2(requestId, error) {
|
|
29551
29760
|
const code = safeErrorCode(error);
|
|
@@ -29564,7 +29773,7 @@ var MAX_REMOTE_DIRECTORY_ENTRIES = 1e3;
|
|
|
29564
29773
|
|
|
29565
29774
|
// src/file-viewer-bridge.ts
|
|
29566
29775
|
var locatorSchema = external_exports.string().min(1).max(MAX_REMOTE_FILE_LOCATOR_CHARS);
|
|
29567
|
-
var
|
|
29776
|
+
var callSchema2 = external_exports.discriminatedUnion("endpoint", [
|
|
29568
29777
|
external_exports.object({
|
|
29569
29778
|
endpoint: external_exports.literal("stat"),
|
|
29570
29779
|
payload: external_exports.object({ path: locatorSchema }).strict()
|
|
@@ -29615,7 +29824,7 @@ var RemoteFileViewerBridge = class {
|
|
|
29615
29824
|
this.logger = logger;
|
|
29616
29825
|
}
|
|
29617
29826
|
async call(input2) {
|
|
29618
|
-
const params =
|
|
29827
|
+
const params = callSchema2.parse(input2);
|
|
29619
29828
|
const service = this.service();
|
|
29620
29829
|
if (service === void 0) {
|
|
29621
29830
|
throw new RpcError("FILE_VIEWER_UNAVAILABLE", "The Remote Host does not have DSH File Viewer available.");
|
|
@@ -30685,7 +30894,7 @@ function historyRequestPayload(payload, maxMessages) {
|
|
|
30685
30894
|
}
|
|
30686
30895
|
|
|
30687
30896
|
// src/harness-api-bridge.ts
|
|
30688
|
-
var
|
|
30897
|
+
var callSchema3 = external_exports.object({
|
|
30689
30898
|
method: external_exports.string().min(1).max(80),
|
|
30690
30899
|
rpcId: external_exports.string().min(1).max(128),
|
|
30691
30900
|
payload: external_exports.unknown()
|
|
@@ -30876,7 +31085,7 @@ var HarnessApiBridge = class {
|
|
|
30876
31085
|
host;
|
|
30877
31086
|
answer;
|
|
30878
31087
|
async call(input2) {
|
|
30879
|
-
const params =
|
|
31088
|
+
const params = callSchema3.parse(input2);
|
|
30880
31089
|
const signal = AbortSignal.timeout(NATIVE_CALL_TIMEOUT_MS);
|
|
30881
31090
|
const method = this.methods.get(params.method);
|
|
30882
31091
|
if (method === void 0) {
|
|
@@ -31010,7 +31219,7 @@ var HarnessApiBridge = class {
|
|
|
31010
31219
|
} catch {
|
|
31011
31220
|
throw new RpcError("INVALID_MESSAGE", "The Harness API transfer does not contain a valid request.");
|
|
31012
31221
|
}
|
|
31013
|
-
const nativeRequest =
|
|
31222
|
+
const nativeRequest = callSchema3.parse(request);
|
|
31014
31223
|
const response = await this.call(nativeRequest);
|
|
31015
31224
|
const responseBytes = new TextEncoder().encode(JSON.stringify(response));
|
|
31016
31225
|
if (responseBytes.byteLength <= INLINE_TRANSFER_RESPONSE_BYTES3) {
|
|
@@ -31456,7 +31665,7 @@ function concatChunks3(chunks, totalBytes) {
|
|
|
31456
31665
|
|
|
31457
31666
|
// src/harness-remote-bridge.ts
|
|
31458
31667
|
var endpointSchema = external_exports.string().min(1).max(128).regex(/^(?:\$events(?:\/result)?|[A-Za-z0-9_$.-]+\/[A-Za-z0-9_$.-]+)$/);
|
|
31459
|
-
var
|
|
31668
|
+
var callSchema4 = external_exports.object({ endpoint: endpointSchema, payload: external_exports.unknown() }).strict();
|
|
31460
31669
|
var streamOpenSchema4 = external_exports.object({
|
|
31461
31670
|
streamId: external_exports.string().min(1).max(128),
|
|
31462
31671
|
endpoint: endpointSchema,
|
|
@@ -31531,6 +31740,8 @@ var HARNESS_REMOTE_ALLOWLIST = [
|
|
|
31531
31740
|
"messageFeedback/put",
|
|
31532
31741
|
"pluginInventory/list",
|
|
31533
31742
|
"permissionPresets/catalog",
|
|
31743
|
+
"schedule/list",
|
|
31744
|
+
"schedule/catalog",
|
|
31534
31745
|
"session/attachment",
|
|
31535
31746
|
"session/cancel",
|
|
31536
31747
|
"session/canOpenWorkspacePath",
|
|
@@ -31597,7 +31808,7 @@ var HarnessRemoteBridge = class {
|
|
|
31597
31808
|
incomingTransfers = /* @__PURE__ */ new Map();
|
|
31598
31809
|
outgoingTransfers = /* @__PURE__ */ new Map();
|
|
31599
31810
|
async call(input2) {
|
|
31600
|
-
const params =
|
|
31811
|
+
const params = callSchema4.parse(input2);
|
|
31601
31812
|
this.assertAllowed(params.endpoint);
|
|
31602
31813
|
if (TERMINAL_STREAMS.has(params.endpoint)) throw new RpcError("METHOD_NOT_ALLOWED", "Use a stream for this terminal endpoint.");
|
|
31603
31814
|
if (["settings/update", "settings/replace", "settings/mutate"].includes(params.endpoint)) {
|
|
@@ -31717,7 +31928,7 @@ var HarnessRemoteBridge = class {
|
|
|
31717
31928
|
} catch {
|
|
31718
31929
|
throw new RpcError("INVALID_MESSAGE", "The Harness Remote transfer does not contain a valid request.");
|
|
31719
31930
|
}
|
|
31720
|
-
const response = await this.call(
|
|
31931
|
+
const response = await this.call(callSchema4.parse(request));
|
|
31721
31932
|
const responseBytes = new TextEncoder().encode(JSON.stringify(response));
|
|
31722
31933
|
if (responseBytes.byteLength <= INLINE_TRANSFER_RESPONSE_BYTES4) return { kind: "inline", response };
|
|
31723
31934
|
if (responseBytes.byteLength > MAX_HARNESS_API_TRANSFER_BYTES) {
|
|
@@ -31979,7 +32190,7 @@ var CodexWorkspaceBridge = class extends WorkspaceBridge {
|
|
|
31979
32190
|
|
|
31980
32191
|
// src/service.ts
|
|
31981
32192
|
var HostPluginRuntime = class {
|
|
31982
|
-
constructor(config, identities, apiProxy, logger, localGateway, fileViewerHost, terminalSpawner) {
|
|
32193
|
+
constructor(config, identities, apiProxy, logger, localGateway, fileViewerHost, terminalSpawner, pluginRoutes) {
|
|
31983
32194
|
this.config = config;
|
|
31984
32195
|
this.identities = identities;
|
|
31985
32196
|
this.apiProxy = apiProxy;
|
|
@@ -31987,6 +32198,7 @@ var HostPluginRuntime = class {
|
|
|
31987
32198
|
this.localGateway = localGateway;
|
|
31988
32199
|
this.fileViewerHost = fileViewerHost;
|
|
31989
32200
|
this.terminalSpawner = terminalSpawner;
|
|
32201
|
+
this.pluginRoutes = pluginRoutes;
|
|
31990
32202
|
this.terminalEnabled = config.terminal.enabled;
|
|
31991
32203
|
this.loopbackPorts = [...config.loopback.ports];
|
|
31992
32204
|
this.codex = new CodexRemoteDomain(config.codex, logger);
|
|
@@ -32025,6 +32237,8 @@ var HostPluginRuntime = class {
|
|
|
32025
32237
|
context,
|
|
32026
32238
|
(event, data2) => send(createEvent(event, data2))
|
|
32027
32239
|
);
|
|
32240
|
+
const dispatcher = this.pluginRoutes?.();
|
|
32241
|
+
const pluginRoute = dispatcher === void 0 ? void 0 : new PluginRouteBridge(dispatcher, this.logger);
|
|
32028
32242
|
return new RpcRouter(
|
|
32029
32243
|
harnessApi,
|
|
32030
32244
|
void 0,
|
|
@@ -32036,7 +32250,8 @@ var HostPluginRuntime = class {
|
|
|
32036
32250
|
cursor2,
|
|
32037
32251
|
// Handles and their lifetime belong to this connection; only policy is shared.
|
|
32038
32252
|
this.createLoopbackHost(),
|
|
32039
|
-
() => this.hostWorkspaceTypes()
|
|
32253
|
+
() => this.hostWorkspaceTypes(),
|
|
32254
|
+
pluginRoute
|
|
32040
32255
|
);
|
|
32041
32256
|
}, this.logger);
|
|
32042
32257
|
if (config.serverUrl !== void 0) {
|
|
@@ -32150,6 +32365,12 @@ var HostPluginRuntime = class {
|
|
|
32150
32365
|
}
|
|
32151
32366
|
this.serverConnection.reconnect();
|
|
32152
32367
|
}
|
|
32368
|
+
async hostOAuthProviders() {
|
|
32369
|
+
if (this.serverApi === void 0) {
|
|
32370
|
+
throw new ServerApiError("SERVER_NOT_CONFIGURED", "Remote Host Server is unavailable.", false);
|
|
32371
|
+
}
|
|
32372
|
+
return this.serverApi.oauthProviders();
|
|
32373
|
+
}
|
|
32153
32374
|
async startHostOAuthQrLogin(provider) {
|
|
32154
32375
|
if (this.serverApi === void 0) {
|
|
32155
32376
|
throw new ServerApiError("SERVER_NOT_CONFIGURED", "Remote Host Server is unavailable.", false);
|
|
@@ -32356,6 +32577,7 @@ var HostPluginRuntime = class {
|
|
|
32356
32577
|
capabilities.push("harness.api.v1", "harness.api.transfer.v1");
|
|
32357
32578
|
}
|
|
32358
32579
|
if (this.fileViewerHost?.() !== void 0) capabilities.push("fileviewer.read.v1");
|
|
32580
|
+
if (this.pluginRoutes?.() !== void 0) capabilities.push("plugin.route.v1");
|
|
32359
32581
|
if (this.codex.isAvailable()) capabilities.push("codex.appserver.v1", "codex.appserver.transfer.v1");
|
|
32360
32582
|
if (this.acp.enabledBackends().length > 0) {
|
|
32361
32583
|
capabilities.push("agent.acp.v1", "agent.acp.transfer.v1", "agent.acp.workspace-files.v1");
|
|
@@ -32436,6 +32658,61 @@ function isPlainRecord(value) {
|
|
|
32436
32658
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
32437
32659
|
}
|
|
32438
32660
|
|
|
32661
|
+
// src/plugin-route-dispatcher.ts
|
|
32662
|
+
function createPluginRouteDispatcher(readConnection, readPort) {
|
|
32663
|
+
let cookie;
|
|
32664
|
+
return async (route, body, signal) => {
|
|
32665
|
+
signal.throwIfAborted();
|
|
32666
|
+
const connection = readConnection();
|
|
32667
|
+
const handler = connection?.createSharedFetchHandler?.("/api");
|
|
32668
|
+
let response;
|
|
32669
|
+
if (handler !== void 0) {
|
|
32670
|
+
response = await withSignal(handler.fetch(new Request(`http://127.0.0.1/${route}`, {
|
|
32671
|
+
method: "POST",
|
|
32672
|
+
headers: { "content-type": "application/json" },
|
|
32673
|
+
body: JSON.stringify(body ?? {}),
|
|
32674
|
+
signal
|
|
32675
|
+
})), signal);
|
|
32676
|
+
} else {
|
|
32677
|
+
const port = readPort();
|
|
32678
|
+
if (connection?.authenticatedUrl === void 0 || typeof port !== "number") {
|
|
32679
|
+
return { status: 503, json: { ok: false, error: { code: "gateway/unavailable", message: "The local plugin route carrier is unavailable." } } };
|
|
32680
|
+
}
|
|
32681
|
+
const origin = `http://127.0.0.1:${port}`;
|
|
32682
|
+
if (cookie === void 0) {
|
|
32683
|
+
const launch = await fetch(connection.authenticatedUrl(`${origin}/`), { redirect: "manual", signal });
|
|
32684
|
+
const setCookie = typeof launch.headers.getSetCookie === "function" ? launch.headers.getSetCookie() : [];
|
|
32685
|
+
cookie = setCookie.map((item) => item.split(";", 1)[0]).join("; ") || void 0;
|
|
32686
|
+
}
|
|
32687
|
+
response = await fetch(`${origin}/${route}`, {
|
|
32688
|
+
method: "POST",
|
|
32689
|
+
headers: { "content-type": "application/json", ...cookie === void 0 ? {} : { cookie } },
|
|
32690
|
+
body: JSON.stringify(body ?? {}),
|
|
32691
|
+
signal
|
|
32692
|
+
});
|
|
32693
|
+
if (response.status === 401) cookie = void 0;
|
|
32694
|
+
}
|
|
32695
|
+
const text = await response.text();
|
|
32696
|
+
let json = null;
|
|
32697
|
+
if (text !== "") {
|
|
32698
|
+
try {
|
|
32699
|
+
json = JSON.parse(text);
|
|
32700
|
+
} catch {
|
|
32701
|
+
json = text;
|
|
32702
|
+
}
|
|
32703
|
+
}
|
|
32704
|
+
return { status: response.status, json };
|
|
32705
|
+
};
|
|
32706
|
+
}
|
|
32707
|
+
function withSignal(pending, signal) {
|
|
32708
|
+
return new Promise((resolve6, reject) => {
|
|
32709
|
+
const onAbort = () => reject(signal.reason);
|
|
32710
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
32711
|
+
if (signal.aborted) onAbort();
|
|
32712
|
+
void pending.then(resolve6, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
32713
|
+
});
|
|
32714
|
+
}
|
|
32715
|
+
|
|
32439
32716
|
// src/cli.ts
|
|
32440
32717
|
import { stat as stat7 } from "node:fs/promises";
|
|
32441
32718
|
import { hostname as hostname3 } from "node:os";
|
|
@@ -32473,16 +32750,20 @@ async function register(args, runtime) {
|
|
|
32473
32750
|
return 0;
|
|
32474
32751
|
}
|
|
32475
32752
|
async function login(args, runtime) {
|
|
32476
|
-
if (args.length > 1) throw new CliUsageError("Usage: ds-harness-remote login [
|
|
32477
|
-
const provider = args[0] ?? "zhihu";
|
|
32478
|
-
if (provider !== "github" && provider !== "zhihu") {
|
|
32479
|
-
throw new CliUsageError("Login provider must be github or zhihu.");
|
|
32480
|
-
}
|
|
32753
|
+
if (args.length > 1) throw new CliUsageError("Usage: ds-harness-remote login [provider|list]");
|
|
32481
32754
|
const context = await hostContext(runtime);
|
|
32755
|
+
const providers = (await context.api.oauthProviders()).filter((item) => item.qrStartUrl != null);
|
|
32756
|
+
if (args[0] === "list") {
|
|
32757
|
+
write(runtime.stdout, providers.length === 0 ? "No QR sign-in providers are available.\n" : providers.map((item) => `${item.displayName}: ds-harness-remote login ${item.id}
|
|
32758
|
+
`).join(""));
|
|
32759
|
+
return 0;
|
|
32760
|
+
}
|
|
32761
|
+
const selected = args[0] === void 0 ? providers.find((item) => item.id === "zhihu") ?? providers[0] : providers.find((item) => item.id === args[0]);
|
|
32762
|
+
if (selected === void 0) throw new CliUsageError('OAuth provider unavailable. Run "ds-harness-remote login list" to see available providers.');
|
|
32763
|
+
const provider = selected.id;
|
|
32482
32764
|
const session = await context.api.startOAuthQrLogin(provider);
|
|
32483
32765
|
const qr = await runtime.renderQr(session.scanUrl);
|
|
32484
|
-
|
|
32485
|
-
write(runtime.stdout, `Using ${providerName} QR login. ${alternativeProviderHint(provider)}
|
|
32766
|
+
write(runtime.stdout, `Using ${selected.displayName} QR login. Run "ds-harness-remote login list" for available providers.
|
|
32486
32767
|
`);
|
|
32487
32768
|
write(runtime.stdout, "Scan this QR code to authorize this Host:\n\n");
|
|
32488
32769
|
write(runtime.stdout, `${qr.trimEnd()}
|
|
@@ -32660,9 +32941,6 @@ async function renderCompactTerminalQr(url) {
|
|
|
32660
32941
|
function authorizedMessage(authorization) {
|
|
32661
32942
|
return authorization.account === void 0 ? "Remote Host login complete." : `Remote Host login complete for ${authorization.account}.`;
|
|
32662
32943
|
}
|
|
32663
|
-
function alternativeProviderHint(provider) {
|
|
32664
|
-
return provider === "zhihu" ? "GitHub is also supported: ds-harness-remote login github" : "Zhihu is also supported: ds-harness-remote login zhihu";
|
|
32665
|
-
}
|
|
32666
32944
|
function authorizationLabel(method) {
|
|
32667
32945
|
if (method === "host_registration_code") return "Host registration code";
|
|
32668
32946
|
if (method === "owned_device") return "owned device";
|
|
@@ -32679,13 +32957,13 @@ function cliErrorMessage(error) {
|
|
|
32679
32957
|
function helpText() {
|
|
32680
32958
|
return [
|
|
32681
32959
|
"Usage:",
|
|
32682
|
-
" ds-harness-remote login [
|
|
32960
|
+
" ds-harness-remote login [provider|list]",
|
|
32683
32961
|
" ds-harness-remote register <server-token>",
|
|
32684
32962
|
" ds-harness-remote status",
|
|
32685
32963
|
" ds-harness-remote logout",
|
|
32686
32964
|
"",
|
|
32687
32965
|
"Inside dsh-TUI, use /remote login, /remote status, or /remote logout.",
|
|
32688
|
-
"login
|
|
32966
|
+
"login uses an available QR provider (preferring Zhihu). Use login list to see available providers.",
|
|
32689
32967
|
`The Server is ${DEFAULT_REMOTE_SERVER_URL}.`,
|
|
32690
32968
|
"Host configuration is not exposed by this CLI yet. Restart dsh-tui after login or logout.",
|
|
32691
32969
|
""
|
|
@@ -32728,7 +33006,7 @@ function installTuiRemoteCommand(ctx, resolveTarget) {
|
|
|
32728
33006
|
const definition = {
|
|
32729
33007
|
name: "remote",
|
|
32730
33008
|
description: "Manage Remote Host login and connection status",
|
|
32731
|
-
input: { hint: "[status | login [
|
|
33009
|
+
input: { hint: "[status | login [provider|list] | logout]" },
|
|
32732
33010
|
recordInput: false,
|
|
32733
33011
|
handler: async ({ rawInput }) => {
|
|
32734
33012
|
const args = rawInput.trim().split(/\s+/u).filter(Boolean);
|
|
@@ -32738,9 +33016,19 @@ function installTuiRemoteCommand(ctx, resolveTarget) {
|
|
|
32738
33016
|
return { kind: "success", text: formatRemoteStatusInline(resolveTarget()) };
|
|
32739
33017
|
}
|
|
32740
33018
|
if (command === "login" && args.length <= 2) {
|
|
32741
|
-
const provider = args[1] ?? "
|
|
32742
|
-
if (provider
|
|
32743
|
-
|
|
33019
|
+
const provider = args[1] ?? "";
|
|
33020
|
+
if (provider === "list") {
|
|
33021
|
+
const target2 = resolveTarget();
|
|
33022
|
+
if (target2 === void 0) return remoteNotReady();
|
|
33023
|
+
try {
|
|
33024
|
+
const providers = await target2.runtime.hostOAuthProviders();
|
|
33025
|
+
return { kind: "success", text: providers.filter((item) => item.qrStartUrl != null).map((item) => `${item.displayName}: /remote login ${item.id}`).join("\n") || "No QR sign-in providers are available." };
|
|
33026
|
+
} catch (error) {
|
|
33027
|
+
return { kind: "error", text: errorCode5(error) };
|
|
33028
|
+
}
|
|
33029
|
+
}
|
|
33030
|
+
if (provider !== "" && !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/.test(provider)) {
|
|
33031
|
+
return { kind: "error", text: "Usage: /remote login [provider|list]" };
|
|
32744
33032
|
}
|
|
32745
33033
|
if (scenes === void 0) {
|
|
32746
33034
|
return {
|
|
@@ -32784,7 +33072,7 @@ function installTuiRemoteCommand(ctx, resolveTarget) {
|
|
|
32784
33072
|
en: "Manage Remote Host login and connection status",
|
|
32785
33073
|
zh: "\u7BA1\u7406 Remote Host \u767B\u5F55\u548C\u8FDE\u63A5\u72B6\u6001"
|
|
32786
33074
|
},
|
|
32787
|
-
children: remoteCommandChildren
|
|
33075
|
+
children: (path) => remoteCommandChildren(path, login2.completionProviders())
|
|
32788
33076
|
}));
|
|
32789
33077
|
const disposeScene = registerOptional(tuiContext, "login scene", () => scenes?.register({
|
|
32790
33078
|
id: REMOTE_LOGIN_SCENE_ID,
|
|
@@ -32828,7 +33116,7 @@ function registerOptional(ctx, feature, register2) {
|
|
|
32828
33116
|
};
|
|
32829
33117
|
}
|
|
32830
33118
|
}
|
|
32831
|
-
function remoteCommandChildren(canonicalPath) {
|
|
33119
|
+
function remoteCommandChildren(canonicalPath, providers) {
|
|
32832
33120
|
if (canonicalPath.length === 1 && canonicalPath[0] === "remote") {
|
|
32833
33121
|
return [
|
|
32834
33122
|
{
|
|
@@ -32838,8 +33126,8 @@ function remoteCommandChildren(canonicalPath) {
|
|
|
32838
33126
|
},
|
|
32839
33127
|
{
|
|
32840
33128
|
name: "login",
|
|
32841
|
-
description: "Authorize this Host with
|
|
32842
|
-
descriptions: { zh: "\u4F7F\u7528
|
|
33129
|
+
description: "Authorize this Host with an available OAuth QR provider",
|
|
33130
|
+
descriptions: { zh: "\u4F7F\u7528\u670D\u52A1\u7AEF\u63D0\u4F9B\u7684 OAuth \u4E8C\u7EF4\u7801\u6388\u6743\u5F53\u524D Host" }
|
|
32843
33131
|
},
|
|
32844
33132
|
{
|
|
32845
33133
|
name: "logout",
|
|
@@ -32850,8 +33138,12 @@ function remoteCommandChildren(canonicalPath) {
|
|
|
32850
33138
|
}
|
|
32851
33139
|
if (canonicalPath.length === 2 && canonicalPath[0] === "remote" && canonicalPath[1] === "login") {
|
|
32852
33140
|
return [
|
|
32853
|
-
{ name: "
|
|
32854
|
-
|
|
33141
|
+
{ name: "list", description: "List available sign-in providers", descriptions: { zh: "\u5217\u51FA\u53EF\u7528\u767B\u5F55\u65B9\u5F0F" } },
|
|
33142
|
+
...providers.filter((item) => item.qrStartUrl != null).map((item) => ({
|
|
33143
|
+
name: item.id,
|
|
33144
|
+
description: `Sign in with ${item.displayName}`,
|
|
33145
|
+
descriptions: { zh: `\u4F7F\u7528 ${item.displayName} \u767B\u5F55` }
|
|
33146
|
+
}))
|
|
32855
33147
|
];
|
|
32856
33148
|
}
|
|
32857
33149
|
return [];
|
|
@@ -32874,13 +33166,13 @@ function remoteStatusLines(target2) {
|
|
|
32874
33166
|
`Authorization: ${status2.authorized ? status2.account === void 0 ? "logged in" : `logged in (${status2.account})` : "logged out"}`,
|
|
32875
33167
|
`Server connection: ${connection}`,
|
|
32876
33168
|
...status2.error === void 0 ? [] : [`Connection error: ${status2.error}`],
|
|
32877
|
-
...status2.error === "CONNECTION_REPLACED" ? ["Another instance is using this Host identity. Stop it or use a separate DSH_HOME before reconnecting."] : status2.error === "SERVER_CREDENTIALS_BUSY" ? ["Credential refresh is locked. Stop all instances before removing an orphaned server-credentials.json.refresh-lock and authorizing again."] : status2.accountRequired ? ["Authorize again with /remote login [
|
|
33169
|
+
...status2.error === "CONNECTION_REPLACED" ? ["Another instance is using this Host identity. Stop it or use a separate DSH_HOME before reconnecting."] : status2.error === "SERVER_CREDENTIALS_BUSY" ? ["Credential refresh is locked. Stop all instances before removing an orphaned server-credentials.json.refresh-lock and authorizing again."] : status2.accountRequired ? ["Authorize again with /remote login [provider|list]."] : [],
|
|
32878
33170
|
`Harness Remote API: ${capabilities.has("harness.api.v1") ? "available (ApiProxy)" : capabilities.has("harness.remote.v3") ? "available (Typert Remote Session V3)" : capabilities.has("harness.remote.v1") ? "available (Typert Remote)" : "unavailable"}`,
|
|
32879
33171
|
`Remote clients: ${diagnostics.activeConnections}`,
|
|
32880
33172
|
`Codex Remote: ${codex.enabled ? codex.state : "disabled"}`,
|
|
32881
33173
|
`Agent ACP: ${cursor2.enabled ? cursor2.state : "disabled"}`,
|
|
32882
33174
|
"",
|
|
32883
|
-
"Commands: /remote login [
|
|
33175
|
+
"Commands: /remote login [provider|list] \xB7 /remote status \xB7 /remote logout"
|
|
32884
33176
|
];
|
|
32885
33177
|
}
|
|
32886
33178
|
function formatRemoteStatusInline(target2) {
|
|
@@ -32891,7 +33183,7 @@ function remoteCommandUsage() {
|
|
|
32891
33183
|
"Usage:",
|
|
32892
33184
|
" /remote",
|
|
32893
33185
|
" /remote status",
|
|
32894
|
-
" /remote login [
|
|
33186
|
+
" /remote login [provider|list]",
|
|
32895
33187
|
" /remote logout"
|
|
32896
33188
|
].join("\n");
|
|
32897
33189
|
}
|
|
@@ -32908,7 +33200,29 @@ var RemoteLoginController = class {
|
|
|
32908
33200
|
current = { phase: "idle", provider: "zhihu" };
|
|
32909
33201
|
listeners = /* @__PURE__ */ new Set();
|
|
32910
33202
|
attempt = 0;
|
|
33203
|
+
providers = [];
|
|
33204
|
+
providersLoading = false;
|
|
33205
|
+
providersCheckedAt = 0;
|
|
33206
|
+
providersServerUrl;
|
|
32911
33207
|
snapshot = () => this.current;
|
|
33208
|
+
completionProviders() {
|
|
33209
|
+
const target2 = this.resolveTarget();
|
|
33210
|
+
if (!this.providersLoading && target2 !== void 0 && (target2.config.serverUrl !== this.providersServerUrl || Date.now() - this.providersCheckedAt > 3e4)) {
|
|
33211
|
+
this.providersLoading = true;
|
|
33212
|
+
this.providersServerUrl = target2.config.serverUrl;
|
|
33213
|
+
this.providersCheckedAt = Date.now();
|
|
33214
|
+
this.providers = [];
|
|
33215
|
+
void target2.runtime.hostOAuthProviders().then((items) => {
|
|
33216
|
+
this.providers = items;
|
|
33217
|
+
}).catch(() => void 0).finally(() => {
|
|
33218
|
+
this.providersLoading = false;
|
|
33219
|
+
});
|
|
33220
|
+
}
|
|
33221
|
+
return this.providers;
|
|
33222
|
+
}
|
|
33223
|
+
providerName(provider) {
|
|
33224
|
+
return this.providers.find((item) => item.id === provider)?.displayName ?? provider;
|
|
33225
|
+
}
|
|
32912
33226
|
subscribe = (listener) => {
|
|
32913
33227
|
this.listeners.add(listener);
|
|
32914
33228
|
return () => {
|
|
@@ -32927,6 +33241,11 @@ var RemoteLoginController = class {
|
|
|
32927
33241
|
try {
|
|
32928
33242
|
const target2 = this.resolveTarget();
|
|
32929
33243
|
if (target2 === void 0) throw new Error("REMOTE_NOT_READY");
|
|
33244
|
+
this.providers = await target2.runtime.hostOAuthProviders();
|
|
33245
|
+
if (attempt !== this.attempt) return;
|
|
33246
|
+
const available = this.providers.filter((item) => item.qrStartUrl != null);
|
|
33247
|
+
if (provider === "") provider = (available.find((item) => item.id === "zhihu") ?? available[0])?.id ?? "";
|
|
33248
|
+
if (!available.some((item) => item.id === provider)) throw new Error("OAUTH_PROVIDER_UNAVAILABLE");
|
|
32930
33249
|
const session = await target2.runtime.startHostOAuthQrLogin(provider);
|
|
32931
33250
|
const [qr, compactQr] = await Promise.all([
|
|
32932
33251
|
renderTerminalQr(session.scanUrl),
|
|
@@ -32980,11 +33299,10 @@ function createRemoteLoginScene(controller) {
|
|
|
32980
33299
|
close();
|
|
32981
33300
|
}
|
|
32982
33301
|
});
|
|
32983
|
-
const provider = snapshot.provider
|
|
32984
|
-
const other = snapshot.provider === "github" ? "/remote login zhihu" : "/remote login github";
|
|
33302
|
+
const provider = controller.providerName(snapshot.provider);
|
|
32985
33303
|
const children = [
|
|
32986
33304
|
React.createElement(ui.Text, { key: "title", bold: true, color: "accent" }, "Remote Host login"),
|
|
32987
|
-
React.createElement(ui.Text, { key: "provider" }, `Provider: ${provider} \xB7
|
|
33305
|
+
React.createElement(ui.Text, { key: "provider" }, `Provider: ${provider} \xB7 Available providers: /remote login list`)
|
|
32988
33306
|
];
|
|
32989
33307
|
if (snapshot.phase === "loading") {
|
|
32990
33308
|
children.push(React.createElement(ui.Text, { key: "loading", color: "subtle" }, "Creating authorization QR code\u2026"));
|
|
@@ -33279,7 +33597,11 @@ async function activate(ctx, readConfig, entryId, tuiBinding) {
|
|
|
33279
33597
|
logger,
|
|
33280
33598
|
localTypertGateway,
|
|
33281
33599
|
() => ctx.get("fileViewerHost"),
|
|
33282
|
-
subprocess === void 0 ? void 0 : subprocessTerminalSpawner(subprocess)
|
|
33600
|
+
subprocess === void 0 ? void 0 : subprocessTerminalSpawner(subprocess),
|
|
33601
|
+
() => createPluginRouteDispatcher(
|
|
33602
|
+
() => ctx.get("connection"),
|
|
33603
|
+
() => ctx.get("webServer")?.port
|
|
33604
|
+
)
|
|
33283
33605
|
);
|
|
33284
33606
|
let clientRuntime;
|
|
33285
33607
|
const hostControl = runtime;
|