pi-harness-runtime 1.1.387 → 1.1.388
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/AGENTS.md +9 -4
- package/index.ts +20 -18
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/dist/workspace-env-format.d.ts +71 -0
- package/packages/contracts/dist/workspace-env-format.js +120 -0
- package/packages/contracts/dist/workspace-env.d.ts +7 -30
- package/packages/contracts/dist/workspace-env.js +18 -57
- package/packages/contracts/package.json +1 -1
- package/packages/contracts/src/workspace-env-format.ts +165 -0
- package/packages/contracts/src/workspace-env.ts +27 -99
- package/packages/contracts/test/workspace-env.test.ts +144 -0
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/dist/index.d.ts +3 -0
- package/packages/relay/dist/index.js +3 -0
- package/packages/relay/dist/relay-endpoint.d.ts +94 -0
- package/packages/relay/dist/relay-endpoint.js +239 -0
- package/packages/relay/package.json +1 -1
- package/packages/relay/src/index.ts +17 -0
- package/packages/relay/src/relay-endpoint.ts +304 -0
- package/packages/relay/test/relay-endpoint.test.ts +278 -0
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/tui/test/harness-footer.test.ts +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/scripts/relay-client.ts +24 -4
- package/scripts/unified-webhook-server.ts +29 -22
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-harness/provider-router",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.388",
|
|
4
4
|
"description": "Enhanced Provider Router for pi-harness-runtime — intelligent routing with capabilities, costs, and quotas",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/provider-router.js",
|
|
@@ -12,5 +12,8 @@ export { RelayServer, createRelayServer } from './server.js';
|
|
|
12
12
|
export type { RelayServerOptions } from './server.js';
|
|
13
13
|
export { RelayClient } from './client.js';
|
|
14
14
|
export type { RelayClientCallbacks } from './client.js';
|
|
15
|
+
export { resolveRelayEndpoint, deriveRelayWsUrl, isRelayWsUrl, describeRelayEndpoint, } from './relay-endpoint.js';
|
|
16
|
+
export type { RelayEndpointEnv, RelayEndpointResolution, ResolveRelayEndpointOptions, } from './relay-endpoint.js';
|
|
17
|
+
export { RELAY_URL_SOURCES, RELAY_TOKEN_SOURCES, } from './relay-endpoint.js';
|
|
15
18
|
export { PROTOCOL_VERSION, generateMessageId, createTimestamp, } from './types.js';
|
|
16
19
|
export type { RelayConfig, RelayTunnel, ConnectionState, ConnectionStateChange, BaseMessage, SnapshotMessage, SynchronizedMessage, CallbackPayload, CallbackMessage, StatusKind, StatusMessage, PingMessage, PongMessage, AuthRequestMessage, AuthResponseMessage, SubscribeMessage, ConnectedMessage, ServerMessage, ClientMessage, RelayMessage, } from './types.js';
|
|
@@ -12,5 +12,8 @@
|
|
|
12
12
|
export { RelayServer, createRelayServer } from './server.js';
|
|
13
13
|
// Client
|
|
14
14
|
export { RelayClient } from './client.js';
|
|
15
|
+
// Endpoint resolution (relay URL + auth token discovery)
|
|
16
|
+
export { resolveRelayEndpoint, deriveRelayWsUrl, isRelayWsUrl, describeRelayEndpoint, } from './relay-endpoint.js';
|
|
17
|
+
export { RELAY_URL_SOURCES, RELAY_TOKEN_SOURCES, } from './relay-endpoint.js';
|
|
15
18
|
// Types
|
|
16
19
|
export { PROTOCOL_VERSION, generateMessageId, createTimestamp, } from './types.js';
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relay endpoint resolution.
|
|
3
|
+
*
|
|
4
|
+
* Resolves the relay WebSocket URL and the auth token from the SAME sources
|
|
5
|
+
* the relay server uses (see scripts/unified-webhook-server.ts), so a client
|
|
6
|
+
* and the server always agree:
|
|
7
|
+
*
|
|
8
|
+
* URL: RELAY_WS_URL env -> keys/relay-ws-url.txt -> derived from keys/telegram-webhook-url.txt
|
|
9
|
+
* Token: RELAY_AUTH_TOKEN env -> keys/relay-auth-token.txt -> keys/telegram-webhook-secret.txt
|
|
10
|
+
*
|
|
11
|
+
* The token value is never logged: only the NAME of the source it came from.
|
|
12
|
+
*/
|
|
13
|
+
/** Environment variables consulted by the resolver. */
|
|
14
|
+
export interface RelayEndpointEnv {
|
|
15
|
+
RELAY_WS_URL?: string;
|
|
16
|
+
RELAY_AUTH_TOKEN?: string;
|
|
17
|
+
}
|
|
18
|
+
/** Result of a relay endpoint resolution. */
|
|
19
|
+
export interface RelayEndpointResolution {
|
|
20
|
+
/** WebSocket URL, or null when it could not be resolved. */
|
|
21
|
+
url: string | null;
|
|
22
|
+
/** Where the URL came from (e.g. "env RELAY_WS_URL"). Never contains a secret. */
|
|
23
|
+
urlSource: string | null;
|
|
24
|
+
/** Auth token, or null when it could not be resolved. */
|
|
25
|
+
token: string | null;
|
|
26
|
+
/** Where the token came from (e.g. "keys/telegram-webhook-secret.txt"). Never contains the token. */
|
|
27
|
+
tokenSource: string | null;
|
|
28
|
+
/** Human readable description of what is missing (never contains a token). */
|
|
29
|
+
missing: string[];
|
|
30
|
+
/** Human readable caveats about the resolved endpoint (never contains a token). */
|
|
31
|
+
notes: string[];
|
|
32
|
+
}
|
|
33
|
+
/** Options for {@link resolveRelayEndpoint}. */
|
|
34
|
+
export interface ResolveRelayEndpointOptions {
|
|
35
|
+
/** Environment to read (defaults to process.env). */
|
|
36
|
+
env?: RelayEndpointEnv;
|
|
37
|
+
/** Directory holding the key files (e.g. ~/.pi-harness-runtime/keys). */
|
|
38
|
+
keysDir: string;
|
|
39
|
+
/** Optional file reader override (used by the tests). */
|
|
40
|
+
readFile?: (path: string) => string | undefined;
|
|
41
|
+
}
|
|
42
|
+
/** Source names, in the order they are consulted. */
|
|
43
|
+
export declare const RELAY_URL_SOURCES: readonly ["env RELAY_WS_URL", "keys/relay-ws-url.txt", "keys/telegram-webhook-url.txt (derived)"];
|
|
44
|
+
export declare const RELAY_TOKEN_SOURCES: readonly ["env RELAY_AUTH_TOKEN", "keys/relay-auth-token.txt", "keys/telegram-webhook-secret.txt"];
|
|
45
|
+
/**
|
|
46
|
+
* The token sources are the SAME chain the relay server uses, so the client
|
|
47
|
+
* and the server can never disagree about the credential.
|
|
48
|
+
*
|
|
49
|
+
* Note: `keys/telegram-webhook-secret.txt` is also the secret the Telegram
|
|
50
|
+
* webhook expects in its `x-telegram-bot-api-secret-token` header. Sharing it
|
|
51
|
+
* with the WebSocket relay widens the blast radius of that secret - put a
|
|
52
|
+
* dedicated token in `keys/relay-auth-token.txt` (it is preferred) when the
|
|
53
|
+
* relay and the webhook do not have to share one.
|
|
54
|
+
*
|
|
55
|
+
* The Telegram webhook secret itself may also come from the environment
|
|
56
|
+
* variable TELEGRAM_WEBHOOK_SECRET; that variable belongs to the HTTP
|
|
57
|
+
* webhook, not to the relay, and is therefore not part of this chain.
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* Derive the relay WebSocket URL from the Telegram webhook URL.
|
|
61
|
+
*
|
|
62
|
+
* The derivation keeps the host (and port) of the webhook URL and replaces the
|
|
63
|
+
* path with /ws, because the relay is exposed on the same origin as the
|
|
64
|
+
* webhook:
|
|
65
|
+
*
|
|
66
|
+
* https://webhook.bunchee.online/telegram -> wss://webhook.bunchee.online/ws
|
|
67
|
+
*
|
|
68
|
+
* That only works when the origin serves /ws, i.e. behind the reverse proxy
|
|
69
|
+
* that maps /ws to the relay port (nginx: location /ws -> 127.0.0.1:8444).
|
|
70
|
+
* When the webhook URL addresses the application DIRECTLY, the relay listens
|
|
71
|
+
* on WS_PORT (default 8444), not on the HTTP port - use RELAY_WS_URL or
|
|
72
|
+
* keys/relay-ws-url.txt for that setup.
|
|
73
|
+
*/
|
|
74
|
+
export declare function deriveRelayWsUrl(webhookUrl: string): string | null;
|
|
75
|
+
/**
|
|
76
|
+
* True when the value looks like a WebSocket URL.
|
|
77
|
+
*
|
|
78
|
+
* Credentials ("wss://user:pass@host") and query strings are rejected: the
|
|
79
|
+
* resolved URL is written to the log, and neither belongs there.
|
|
80
|
+
*/
|
|
81
|
+
export declare function isRelayWsUrl(value: string): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Resolve the relay WebSocket URL and auth token.
|
|
84
|
+
*
|
|
85
|
+
* The first non-empty source wins. A blank file counts as missing (an empty
|
|
86
|
+
* value is a configuration mistake, not a valid endpoint). A source that holds
|
|
87
|
+
* a value which was REJECTED is reported in `missing` so the operator can see
|
|
88
|
+
* why the resolver moved on.
|
|
89
|
+
*/
|
|
90
|
+
export declare function resolveRelayEndpoint(options: ResolveRelayEndpointOptions): RelayEndpointResolution;
|
|
91
|
+
/**
|
|
92
|
+
* Log-safe description of a resolution (contains no token value).
|
|
93
|
+
*/
|
|
94
|
+
export declare function describeRelayEndpoint(resolution: RelayEndpointResolution): string;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relay endpoint resolution.
|
|
3
|
+
*
|
|
4
|
+
* Resolves the relay WebSocket URL and the auth token from the SAME sources
|
|
5
|
+
* the relay server uses (see scripts/unified-webhook-server.ts), so a client
|
|
6
|
+
* and the server always agree:
|
|
7
|
+
*
|
|
8
|
+
* URL: RELAY_WS_URL env -> keys/relay-ws-url.txt -> derived from keys/telegram-webhook-url.txt
|
|
9
|
+
* Token: RELAY_AUTH_TOKEN env -> keys/relay-auth-token.txt -> keys/telegram-webhook-secret.txt
|
|
10
|
+
*
|
|
11
|
+
* The token value is never logged: only the NAME of the source it came from.
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
/** Source names, in the order they are consulted. */
|
|
16
|
+
export const RELAY_URL_SOURCES = [
|
|
17
|
+
"env RELAY_WS_URL",
|
|
18
|
+
"keys/relay-ws-url.txt",
|
|
19
|
+
"keys/telegram-webhook-url.txt (derived)",
|
|
20
|
+
];
|
|
21
|
+
export const RELAY_TOKEN_SOURCES = [
|
|
22
|
+
"env RELAY_AUTH_TOKEN",
|
|
23
|
+
"keys/relay-auth-token.txt",
|
|
24
|
+
"keys/telegram-webhook-secret.txt",
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* The token sources are the SAME chain the relay server uses, so the client
|
|
28
|
+
* and the server can never disagree about the credential.
|
|
29
|
+
*
|
|
30
|
+
* Note: `keys/telegram-webhook-secret.txt` is also the secret the Telegram
|
|
31
|
+
* webhook expects in its `x-telegram-bot-api-secret-token` header. Sharing it
|
|
32
|
+
* with the WebSocket relay widens the blast radius of that secret - put a
|
|
33
|
+
* dedicated token in `keys/relay-auth-token.txt` (it is preferred) when the
|
|
34
|
+
* relay and the webhook do not have to share one.
|
|
35
|
+
*
|
|
36
|
+
* The Telegram webhook secret itself may also come from the environment
|
|
37
|
+
* variable TELEGRAM_WEBHOOK_SECRET; that variable belongs to the HTTP
|
|
38
|
+
* webhook, not to the relay, and is therefore not part of this chain.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Derive the relay WebSocket URL from the Telegram webhook URL.
|
|
42
|
+
*
|
|
43
|
+
* The derivation keeps the host (and port) of the webhook URL and replaces the
|
|
44
|
+
* path with /ws, because the relay is exposed on the same origin as the
|
|
45
|
+
* webhook:
|
|
46
|
+
*
|
|
47
|
+
* https://webhook.bunchee.online/telegram -> wss://webhook.bunchee.online/ws
|
|
48
|
+
*
|
|
49
|
+
* That only works when the origin serves /ws, i.e. behind the reverse proxy
|
|
50
|
+
* that maps /ws to the relay port (nginx: location /ws -> 127.0.0.1:8444).
|
|
51
|
+
* When the webhook URL addresses the application DIRECTLY, the relay listens
|
|
52
|
+
* on WS_PORT (default 8444), not on the HTTP port - use RELAY_WS_URL or
|
|
53
|
+
* keys/relay-ws-url.txt for that setup.
|
|
54
|
+
*/
|
|
55
|
+
export function deriveRelayWsUrl(webhookUrl) {
|
|
56
|
+
const trimmed = webhookUrl?.trim();
|
|
57
|
+
if (!trimmed)
|
|
58
|
+
return null;
|
|
59
|
+
try {
|
|
60
|
+
const parsed = new URL(trimmed);
|
|
61
|
+
if (!parsed.host)
|
|
62
|
+
return null;
|
|
63
|
+
const secure = parsed.protocol === "https:" || parsed.protocol === "wss:";
|
|
64
|
+
const scheme = secure ? "wss:" : "ws:";
|
|
65
|
+
return `${scheme}//${parsed.host}/ws`;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* True when the value looks like a WebSocket URL.
|
|
73
|
+
*
|
|
74
|
+
* Credentials ("wss://user:pass@host") and query strings are rejected: the
|
|
75
|
+
* resolved URL is written to the log, and neither belongs there.
|
|
76
|
+
*/
|
|
77
|
+
export function isRelayWsUrl(value) {
|
|
78
|
+
const trimmed = value?.trim() ?? "";
|
|
79
|
+
if (!/^wss?:\/\//i.test(trimmed))
|
|
80
|
+
return false;
|
|
81
|
+
if (trimmed.includes("@") || trimmed.includes("?"))
|
|
82
|
+
return false;
|
|
83
|
+
try {
|
|
84
|
+
const parsed = new URL(trimmed);
|
|
85
|
+
if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:")
|
|
86
|
+
return false;
|
|
87
|
+
return parsed.host.length > 0;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function defaultReadFile(path) {
|
|
94
|
+
try {
|
|
95
|
+
return readFileSync(path, "utf-8");
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Host of a URL, or an empty string when it cannot be parsed. */
|
|
102
|
+
function hostOf(url) {
|
|
103
|
+
try {
|
|
104
|
+
return new URL(url).host;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return "";
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** True for hosts that address the machine itself. */
|
|
111
|
+
function isLoopbackHost(host) {
|
|
112
|
+
const name = host.replace(/:\d+$/, "").replace(/^\[|\]$/g, "").toLowerCase();
|
|
113
|
+
return name === "127.0.0.1" || name === "localhost" || name === "::1" || name.startsWith("127.");
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Transport warnings for a resolved URL.
|
|
117
|
+
*
|
|
118
|
+
* A plaintext ws:// URL to a remote host sends the auth token in cleartext.
|
|
119
|
+
*/
|
|
120
|
+
function transportNotes(url) {
|
|
121
|
+
if (!url.toLowerCase().startsWith("ws://"))
|
|
122
|
+
return [];
|
|
123
|
+
if (isLoopbackHost(hostOf(url)))
|
|
124
|
+
return [];
|
|
125
|
+
return [
|
|
126
|
+
"the relay url is plaintext (ws://) for a remote host - the auth token is sent unencrypted, use wss://",
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Resolve the WebSocket URL: env -> dedicated key file -> derived from the
|
|
131
|
+
* telegram webhook url. The first usable source wins; a source that holds a
|
|
132
|
+
* rejected value is reported so the operator can see why it was skipped.
|
|
133
|
+
*/
|
|
134
|
+
function resolveUrl(env, readKey) {
|
|
135
|
+
const rejected = [];
|
|
136
|
+
const notes = [];
|
|
137
|
+
const envUrl = env.RELAY_WS_URL?.trim();
|
|
138
|
+
if (envUrl) {
|
|
139
|
+
if (isRelayWsUrl(envUrl)) {
|
|
140
|
+
notes.push(...transportNotes(envUrl));
|
|
141
|
+
return { url: envUrl, source: "env RELAY_WS_URL", rejected, notes };
|
|
142
|
+
}
|
|
143
|
+
rejected.push("env RELAY_WS_URL (not a ws:// or wss:// URL)");
|
|
144
|
+
}
|
|
145
|
+
const fileUrl = readKey("relay-ws-url.txt");
|
|
146
|
+
if (fileUrl) {
|
|
147
|
+
if (isRelayWsUrl(fileUrl)) {
|
|
148
|
+
notes.push(...transportNotes(fileUrl));
|
|
149
|
+
return { url: fileUrl, source: "keys/relay-ws-url.txt", rejected, notes };
|
|
150
|
+
}
|
|
151
|
+
rejected.push("keys/relay-ws-url.txt (not a ws:// or wss:// URL)");
|
|
152
|
+
}
|
|
153
|
+
const webhookUrl = readKey("telegram-webhook-url.txt");
|
|
154
|
+
const derived = webhookUrl ? deriveRelayWsUrl(webhookUrl) : null;
|
|
155
|
+
if (derived) {
|
|
156
|
+
if (isLoopbackHost(hostOf(derived))) {
|
|
157
|
+
notes.push("derived url points at a loopback host: the relay listens on WS_PORT (default 8444), not on the webhook port - set RELAY_WS_URL or keys/relay-ws-url.txt");
|
|
158
|
+
}
|
|
159
|
+
notes.push(...transportNotes(derived));
|
|
160
|
+
return { url: derived, source: "keys/telegram-webhook-url.txt (derived)", rejected, notes };
|
|
161
|
+
}
|
|
162
|
+
if (webhookUrl) {
|
|
163
|
+
rejected.push("keys/telegram-webhook-url.txt (no ws:// or wss:// URL could be derived)");
|
|
164
|
+
}
|
|
165
|
+
return { url: null, source: null, rejected, notes };
|
|
166
|
+
}
|
|
167
|
+
/** Resolve the auth token: env -> dedicated key file -> webhook secret. */
|
|
168
|
+
function resolveToken(env, readKey) {
|
|
169
|
+
const envToken = env.RELAY_AUTH_TOKEN?.trim();
|
|
170
|
+
if (envToken) {
|
|
171
|
+
return { token: envToken, source: "env RELAY_AUTH_TOKEN" };
|
|
172
|
+
}
|
|
173
|
+
const fileToken = readKey("relay-auth-token.txt");
|
|
174
|
+
if (fileToken) {
|
|
175
|
+
return { token: fileToken, source: "keys/relay-auth-token.txt" };
|
|
176
|
+
}
|
|
177
|
+
const secret = readKey("telegram-webhook-secret.txt");
|
|
178
|
+
if (secret) {
|
|
179
|
+
return { token: secret, source: "keys/telegram-webhook-secret.txt" };
|
|
180
|
+
}
|
|
181
|
+
return { token: null, source: null };
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Resolve the relay WebSocket URL and auth token.
|
|
185
|
+
*
|
|
186
|
+
* The first non-empty source wins. A blank file counts as missing (an empty
|
|
187
|
+
* value is a configuration mistake, not a valid endpoint). A source that holds
|
|
188
|
+
* a value which was REJECTED is reported in `missing` so the operator can see
|
|
189
|
+
* why the resolver moved on.
|
|
190
|
+
*/
|
|
191
|
+
export function resolveRelayEndpoint(options) {
|
|
192
|
+
const env = options.env ?? process.env;
|
|
193
|
+
const read = options.readFile ?? defaultReadFile;
|
|
194
|
+
const readKey = (name) => {
|
|
195
|
+
try {
|
|
196
|
+
const raw = read(join(options.keysDir, name));
|
|
197
|
+
const value = raw?.trim();
|
|
198
|
+
return value ? value : undefined;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// An unreadable key file is treated as absent - the source list in
|
|
202
|
+
// `missing` tells the operator where to look.
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const urlResolution = resolveUrl(env, readKey);
|
|
207
|
+
const tokenResolution = resolveToken(env, readKey);
|
|
208
|
+
const missing = [];
|
|
209
|
+
if (!urlResolution.url) {
|
|
210
|
+
missing.push(`url not resolved (looked at ${RELAY_URL_SOURCES.join(", ")})`);
|
|
211
|
+
}
|
|
212
|
+
if (!tokenResolution.token) {
|
|
213
|
+
missing.push(`token not resolved (looked at ${RELAY_TOKEN_SOURCES.join(", ")})`);
|
|
214
|
+
}
|
|
215
|
+
for (const reason of urlResolution.rejected) {
|
|
216
|
+
missing.push(`rejected source: ${reason}`);
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
url: urlResolution.url,
|
|
220
|
+
urlSource: urlResolution.source,
|
|
221
|
+
token: tokenResolution.token,
|
|
222
|
+
tokenSource: tokenResolution.source,
|
|
223
|
+
missing,
|
|
224
|
+
notes: urlResolution.notes,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Log-safe description of a resolution (contains no token value).
|
|
229
|
+
*/
|
|
230
|
+
export function describeRelayEndpoint(resolution) {
|
|
231
|
+
const parts = [];
|
|
232
|
+
parts.push(resolution.url ? `url from ${resolution.urlSource}` : "url NOT resolved");
|
|
233
|
+
parts.push(resolution.token ? `token from ${resolution.tokenSource}` : "token NOT resolved");
|
|
234
|
+
let description = parts.join(", ");
|
|
235
|
+
if (resolution.notes.length > 0) {
|
|
236
|
+
description += ` (${resolution.notes.join("; ")})`;
|
|
237
|
+
}
|
|
238
|
+
return description;
|
|
239
|
+
}
|
|
@@ -17,6 +17,23 @@ export type { RelayServerOptions } from './server.js';
|
|
|
17
17
|
export { RelayClient } from './client.js';
|
|
18
18
|
export type { RelayClientCallbacks } from './client.js';
|
|
19
19
|
|
|
20
|
+
// Endpoint resolution (relay URL + auth token discovery)
|
|
21
|
+
export {
|
|
22
|
+
resolveRelayEndpoint,
|
|
23
|
+
deriveRelayWsUrl,
|
|
24
|
+
isRelayWsUrl,
|
|
25
|
+
describeRelayEndpoint,
|
|
26
|
+
} from './relay-endpoint.js';
|
|
27
|
+
export type {
|
|
28
|
+
RelayEndpointEnv,
|
|
29
|
+
RelayEndpointResolution,
|
|
30
|
+
ResolveRelayEndpointOptions,
|
|
31
|
+
} from './relay-endpoint.js';
|
|
32
|
+
export {
|
|
33
|
+
RELAY_URL_SOURCES,
|
|
34
|
+
RELAY_TOKEN_SOURCES,
|
|
35
|
+
} from './relay-endpoint.js';
|
|
36
|
+
|
|
20
37
|
// Types
|
|
21
38
|
export {
|
|
22
39
|
PROTOCOL_VERSION,
|