@yefengr/pi-reach 0.0.0-stage → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +110 -2
  3. package/dist/actions/handlers.d.ts +143 -0
  4. package/dist/actions/handlers.js +200 -0
  5. package/dist/actions/handlers.js.map +1 -0
  6. package/dist/actions/registry.d.ts +39 -0
  7. package/dist/actions/registry.js +58 -0
  8. package/dist/actions/registry.js.map +1 -0
  9. package/dist/actions/session_new_bridge.d.ts +35 -0
  10. package/dist/actions/session_new_bridge.js +88 -0
  11. package/dist/actions/session_new_bridge.js.map +1 -0
  12. package/dist/commands.d.ts +29 -0
  13. package/dist/commands.js +169 -0
  14. package/dist/commands.js.map +1 -0
  15. package/dist/config.d.ts +61 -0
  16. package/dist/config.js +115 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/index.d.ts +27 -0
  19. package/dist/index.js +618 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/pairing/crypto.d.ts +15 -0
  22. package/dist/pairing/crypto.js +72 -0
  23. package/dist/pairing/crypto.js.map +1 -0
  24. package/dist/pairing/identity-keyring.d.ts +22 -0
  25. package/dist/pairing/identity-keyring.js +94 -0
  26. package/dist/pairing/identity-keyring.js.map +1 -0
  27. package/dist/pairing/identity-lock.d.ts +14 -0
  28. package/dist/pairing/identity-lock.js +116 -0
  29. package/dist/pairing/identity-lock.js.map +1 -0
  30. package/dist/pairing/owner_storage.d.ts +61 -0
  31. package/dist/pairing/owner_storage.js +263 -0
  32. package/dist/pairing/owner_storage.js.map +1 -0
  33. package/dist/pairing/qr.d.ts +39 -0
  34. package/dist/pairing/qr.js +113 -0
  35. package/dist/pairing/qr.js.map +1 -0
  36. package/dist/pairing/storage.d.ts +36 -0
  37. package/dist/pairing/storage.js +263 -0
  38. package/dist/pairing/storage.js.map +1 -0
  39. package/dist/protocol/types.d.ts +225 -0
  40. package/dist/protocol/types.js +2 -0
  41. package/dist/protocol/types.js.map +1 -0
  42. package/dist/protocol/v2/codec.d.ts +6 -0
  43. package/dist/protocol/v2/codec.js +4 -0
  44. package/dist/protocol/v2/codec.js.map +1 -0
  45. package/dist/protocol/v2/index.d.ts +4 -0
  46. package/dist/protocol/v2/index.js +5 -0
  47. package/dist/protocol/v2/index.js.map +1 -0
  48. package/dist/protocol/v2/marker.d.ts +45 -0
  49. package/dist/protocol/v2/marker.js +47 -0
  50. package/dist/protocol/v2/marker.js.map +1 -0
  51. package/dist/protocol/v2/schemas.d.ts +3 -0
  52. package/dist/protocol/v2/schemas.js +4 -0
  53. package/dist/protocol/v2/schemas.js.map +1 -0
  54. package/dist/protocol/v2/session_state.d.ts +79 -0
  55. package/dist/protocol/v2/session_state.js +166 -0
  56. package/dist/protocol/v2/session_state.js.map +1 -0
  57. package/dist/runtime/endpoint_metadata.d.ts +14 -0
  58. package/dist/runtime/endpoint_metadata.js +18 -0
  59. package/dist/runtime/endpoint_metadata.js.map +1 -0
  60. package/dist/runtime/extension_owner.d.ts +12 -0
  61. package/dist/runtime/extension_owner.js +80 -0
  62. package/dist/runtime/extension_owner.js.map +1 -0
  63. package/dist/runtime/footer.d.ts +6 -0
  64. package/dist/runtime/footer.js +21 -0
  65. package/dist/runtime/footer.js.map +1 -0
  66. package/dist/runtime/owner_router.d.ts +16 -0
  67. package/dist/runtime/owner_router.js +41 -0
  68. package/dist/runtime/owner_router.js.map +1 -0
  69. package/dist/runtime/pairing_coordinator.d.ts +42 -0
  70. package/dist/runtime/pairing_coordinator.js +136 -0
  71. package/dist/runtime/pairing_coordinator.js.map +1 -0
  72. package/dist/runtime/relay_lifecycle.d.ts +64 -0
  73. package/dist/runtime/relay_lifecycle.js +275 -0
  74. package/dist/runtime/relay_lifecycle.js.map +1 -0
  75. package/dist/session/local_config.d.ts +7 -0
  76. package/dist/session/local_config.js +52 -0
  77. package/dist/session/local_config.js.map +1 -0
  78. package/dist/timeline/history.d.ts +30 -0
  79. package/dist/timeline/history.js +170 -0
  80. package/dist/timeline/history.js.map +1 -0
  81. package/dist/timeline/partial_delta.d.ts +2 -0
  82. package/dist/timeline/partial_delta.js +16 -0
  83. package/dist/timeline/partial_delta.js.map +1 -0
  84. package/dist/timeline/run_end.d.ts +16 -0
  85. package/dist/timeline/run_end.js +23 -0
  86. package/dist/timeline/run_end.js.map +1 -0
  87. package/dist/timeline/runtime.d.ts +89 -0
  88. package/dist/timeline/runtime.js +546 -0
  89. package/dist/timeline/runtime.js.map +1 -0
  90. package/dist/timeline/sequence.d.ts +7 -0
  91. package/dist/timeline/sequence.js +16 -0
  92. package/dist/timeline/sequence.js.map +1 -0
  93. package/dist/timeline/tool_lifecycle.d.ts +36 -0
  94. package/dist/timeline/tool_lifecycle.js +171 -0
  95. package/dist/timeline/tool_lifecycle.js.map +1 -0
  96. package/dist/timeline/user_delivery.d.ts +96 -0
  97. package/dist/timeline/user_delivery.js +360 -0
  98. package/dist/timeline/user_delivery.js.map +1 -0
  99. package/dist/timeline/user_delivery_binding.d.ts +43 -0
  100. package/dist/timeline/user_delivery_binding.js +107 -0
  101. package/dist/timeline/user_delivery_binding.js.map +1 -0
  102. package/dist/timeline/v2_service.d.ts +82 -0
  103. package/dist/timeline/v2_service.js +512 -0
  104. package/dist/timeline/v2_service.js.map +1 -0
  105. package/dist/transport/peer_channel.d.ts +34 -0
  106. package/dist/transport/peer_channel.js +116 -0
  107. package/dist/transport/peer_channel.js.map +1 -0
  108. package/dist/transport/relay_client.d.ts +54 -0
  109. package/dist/transport/relay_client.js +270 -0
  110. package/dist/transport/relay_client.js.map +1 -0
  111. package/dist/vendor/protocol/constants.d.ts +4 -0
  112. package/dist/vendor/protocol/constants.js +4 -0
  113. package/dist/vendor/protocol/encoding.d.ts +9 -0
  114. package/dist/vendor/protocol/encoding.js +66 -0
  115. package/dist/vendor/protocol/outer/codec.d.ts +13 -0
  116. package/dist/vendor/protocol/outer/codec.js +105 -0
  117. package/dist/vendor/protocol/outer/index.d.ts +4 -0
  118. package/dist/vendor/protocol/outer/index.js +4 -0
  119. package/dist/vendor/protocol/outer/types.d.ts +120 -0
  120. package/dist/vendor/protocol/outer/types.js +1 -0
  121. package/dist/vendor/protocol/session/codec.d.ts +41 -0
  122. package/dist/vendor/protocol/session/codec.js +236 -0
  123. package/dist/vendor/protocol/session/frames.d.ts +5220 -0
  124. package/dist/vendor/protocol/session/frames.js +83 -0
  125. package/dist/vendor/protocol/session/index.d.ts +3 -0
  126. package/dist/vendor/protocol/session/index.js +3 -0
  127. package/dist/vendor/protocol/session/schema.d.ts +858 -0
  128. package/dist/vendor/protocol/session/schema.js +256 -0
  129. package/package.json +74 -3
@@ -0,0 +1,88 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { handleSessionNew } from "./handlers.js";
3
+ const DEFAULT_TIMEOUT_MS = 5_000;
4
+ /** Bridges a remote action to Pi's command-only, process-local newSession API. */
5
+ export class SessionNewBridge {
6
+ options;
7
+ pending = new Map();
8
+ timeoutMs = DEFAULT_TIMEOUT_MS;
9
+ running = false;
10
+ switchPermit = false;
11
+ constructor(options) {
12
+ this.options = options;
13
+ }
14
+ dispatch(sender, frame) {
15
+ const pi = this.options.getPi();
16
+ if (!pi) {
17
+ sender.send({ type: "action_error", in_reply_to: frame.id, action: "session_new", error: "session replacement is unavailable" });
18
+ return;
19
+ }
20
+ if (this.running || this.pending.size > 0) {
21
+ sender.send({ type: "action_error", in_reply_to: frame.id, action: "session_new", error: "session replacement is already in progress" });
22
+ return;
23
+ }
24
+ const token = randomUUID();
25
+ const pending = {};
26
+ pending.sender = sender;
27
+ pending.frame = frame;
28
+ pending.timer = setTimeout(() => this.fail(token, pending, "session replacement dispatch timed out"), this.timeoutMs);
29
+ pending.timer.unref();
30
+ this.pending.set(token, pending);
31
+ try {
32
+ void Promise.resolve(pi.sendUserMessage(`/pi-reach internal-session-new ${token}`, { expandPromptTemplates: true }))
33
+ .catch(() => this.fail(token, pending, "session replacement dispatch failed"));
34
+ }
35
+ catch {
36
+ this.fail(token, pending, "session replacement dispatch failed");
37
+ }
38
+ }
39
+ async run(token, ctx) {
40
+ const normalized = token.trim();
41
+ const pending = this.pending.get(normalized);
42
+ if (!pending || pending.running || this.running)
43
+ return;
44
+ clearTimeout(pending.timer);
45
+ pending.running = true;
46
+ this.running = true;
47
+ this.switchPermit = true;
48
+ this.options.onCommandContext(ctx);
49
+ const sender = { send: (message) => this.complete(normalized, pending, message) };
50
+ try {
51
+ await handleSessionNew(ctx, sender, pending.frame, this.options.onReplaced);
52
+ }
53
+ finally {
54
+ this.switchPermit = false;
55
+ this.running = false;
56
+ this.options.onFinished();
57
+ }
58
+ }
59
+ beforeSwitch(reason) {
60
+ if (!this.running)
61
+ return undefined;
62
+ if (reason === "new" && this.switchPermit) {
63
+ this.switchPermit = false;
64
+ return undefined;
65
+ }
66
+ return { cancel: true };
67
+ }
68
+ beforeFork() { return this.running ? { cancel: true } : undefined; }
69
+ isRunning() { return this.running; }
70
+ clear(error = "session replacement cancelled") {
71
+ for (const [token, pending] of [...this.pending])
72
+ this.fail(token, pending, error);
73
+ }
74
+ setTimeoutForTest(timeoutMs) {
75
+ this.timeoutMs = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_TIMEOUT_MS;
76
+ }
77
+ fail(token, pending, error) {
78
+ this.complete(token, pending, { type: "action_error", in_reply_to: pending.frame.id, action: "session_new", error });
79
+ }
80
+ complete(token, pending, message) {
81
+ if (this.pending.get(token) !== pending)
82
+ return;
83
+ this.pending.delete(token);
84
+ clearTimeout(pending.timer);
85
+ pending.sender.send(message);
86
+ }
87
+ }
88
+ //# sourceMappingURL=session_new_bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session_new_bridge.js","sourceRoot":"","sources":["../../src/actions/session_new_bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import { type Ed25519Keypair } from "./pairing/crypto.js";
3
+ import type { InitialRelayResult } from "./runtime/relay_lifecycle.js";
4
+ export type { InitialRelayResult } from "./runtime/relay_lifecycle.js";
5
+ type CommandStartContext = Pick<ExtensionContext, "ui" | "cwd">;
6
+ export interface RemoteCommandDependencies {
7
+ start(ctx: CommandStartContext): Promise<InitialRelayResult>;
8
+ waitForInitialRelay(): Promise<InitialRelayResult>;
9
+ stop(): void;
10
+ state(): "idle" | "started";
11
+ relayStatus(): string;
12
+ relayUrl(): string | null;
13
+ endpointIdentity(): Readonly<{
14
+ endpointId: string;
15
+ runtimeInstanceId: string;
16
+ }>;
17
+ activeOwnerCount(): number;
18
+ isOwnerActive(ownerId: string): boolean;
19
+ closeOwner(ownerId: string, reason: "peer_stop"): void;
20
+ updateEndpoint(): Promise<void>;
21
+ displayName(cwd?: string): string;
22
+ keypair(): Ed25519Keypair | null;
23
+ hasRelay(): boolean;
24
+ publishPairingOffer(code: string, expiresAt: number): boolean;
25
+ setCommandContext(ctx: ExtensionCommandContext): void;
26
+ runInternalSessionNew(token: string, ctx: ExtensionCommandContext): Promise<void>;
27
+ }
28
+ export declare function persistModelDefault(provider: string, modelId: string): void;
29
+ export declare function registerCommands(pi: ExtensionAPI, deps: RemoteCommandDependencies): void;
@@ -0,0 +1,169 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { canonicalizeEd25519PublicKey } from "./pairing/crypto.js";
4
+ import { clampPairTtlMs, qrSession, renderQRAscii, PAIRING_INVITE_TTL_MS } from "./pairing/qr.js";
5
+ import { listPeers, removePeer } from "./pairing/storage.js";
6
+ import { isValidRelayUrl, isWebSocketScheme, resolveRelayUrl, saveConfig } from "./config.js";
7
+ const PAIR_WIDGET_KEY = "pi-reach-pair-code";
8
+ function notify(ctx, text, kind = "info") {
9
+ try {
10
+ ctx.ui.notify(text, kind);
11
+ }
12
+ catch { /* headless or stale context */ }
13
+ }
14
+ async function pair(ctx, args, deps) {
15
+ const result = deps.state() === "idle" ? await deps.start(ctx) : await deps.waitForInitialRelay();
16
+ if (result === "cancelled") {
17
+ notify(ctx, `[pi-reach] Pair requires a Relay connection; current state: ${deps.relayStatus()}.`, "warning");
18
+ return;
19
+ }
20
+ const keypair = deps.keypair();
21
+ if (!keypair) {
22
+ notify(ctx, "[pi-reach] Pair requires an available device identity.", "warning");
23
+ return;
24
+ }
25
+ if (!deps.hasRelay()) {
26
+ notify(ctx, `[pi-reach] Pair requires a Relay connection; current state: ${deps.relayStatus()}.`, "warning");
27
+ return;
28
+ }
29
+ const ttl = /--ttl\s+(\d+)/.exec(args);
30
+ const ttlMs = ttl ? clampPairTtlMs(Number(ttl[1]) * 1_000) : PAIRING_INVITE_TTL_MS;
31
+ const invite = qrSession.issueCode(ttlMs);
32
+ if (!deps.publishPairingOffer(invite.code, invite.expiresAt)) {
33
+ qrSession.clear();
34
+ notify(ctx, `[pi-reach] Pair requires a Relay connection; current state: ${deps.relayStatus()}.`, "warning");
35
+ return;
36
+ }
37
+ try {
38
+ ctx.ui.setWidget(PAIR_WIDGET_KEY, [
39
+ "Scan to pair:",
40
+ "",
41
+ renderQRAscii(invite.code),
42
+ "",
43
+ `Pairing code: ${invite.code}`,
44
+ `Expires at: ${new Date(invite.expiresAt).toLocaleTimeString()}`,
45
+ ]);
46
+ }
47
+ catch { /* headless pairing still has a status message */ }
48
+ notify(ctx, `[pi-reach] Pairing code ready until ${new Date(invite.expiresAt).toLocaleTimeString()}.`);
49
+ }
50
+ async function listDevices(ctx, deps) {
51
+ const peers = await listPeers();
52
+ if (!peers.length) {
53
+ notify(ctx, "[pi-reach] No paired devices.");
54
+ return;
55
+ }
56
+ notify(ctx, `[pi-reach] Paired devices:\n${peers.map((peer) => `* ${peer.remote_epk.slice(0, 8)} - ${peer.name}${deps.isOwnerActive(peer.remote_epk) ? " online" : ""}`).join("\n")}`);
57
+ }
58
+ async function revoke(args, ctx, deps) {
59
+ const shortId = args.trim();
60
+ if (!shortId) {
61
+ notify(ctx, "[pi-reach] Usage: /pi-reach revoke <shortid>", "warning");
62
+ return;
63
+ }
64
+ const matches = (await listPeers()).filter((peer) => peer.remote_epk.startsWith(shortId));
65
+ if (matches.length !== 1) {
66
+ notify(ctx, matches.length ? "[pi-reach] Ambiguous owner id." : "[pi-reach] No matching paired device.", "warning");
67
+ return;
68
+ }
69
+ const peer = matches[0];
70
+ await removePeer(peer.remote_epk);
71
+ let ownerId = peer.remote_epk;
72
+ try {
73
+ ownerId = canonicalizeEd25519PublicKey(peer.remote_epk, "Owner public key");
74
+ }
75
+ catch { /* retain cleanup for corrupt legacy records */ }
76
+ deps.closeOwner(ownerId, "peer_stop");
77
+ await deps.updateEndpoint();
78
+ notify(ctx, `[pi-reach] Revoked: ${peer.name}`);
79
+ }
80
+ function status(ctx, deps) {
81
+ const url = deps.relayUrl() ?? resolveRelayUrl().url;
82
+ const identity = deps.endpointIdentity();
83
+ const detail = deps.state() === "idle"
84
+ ? "off"
85
+ : `${deps.relayStatus()}, endpoint=${identity.endpointId}, runtime=${identity.runtimeInstanceId}, owners=${deps.activeOwnerCount()}`;
86
+ notify(ctx, `[pi-reach] Relay ${detail} (${url})`);
87
+ }
88
+ function setRelay(value, ctx) {
89
+ const url = value.trim();
90
+ if (isWebSocketScheme(url)) {
91
+ notify(ctx, "[pi-reach] Use http:// or https://; it is converted internally.", "warning");
92
+ return;
93
+ }
94
+ if (!isValidRelayUrl(url)) {
95
+ notify(ctx, "[pi-reach] Invalid Relay URL.", "warning");
96
+ return;
97
+ }
98
+ saveConfig({ relay: url });
99
+ notify(ctx, `[pi-reach] Relay set to ${url}.`);
100
+ }
101
+ export function persistModelDefault(provider, modelId) {
102
+ try {
103
+ const path = join(process.cwd(), ".pi", "settings.json");
104
+ let config = {};
105
+ try {
106
+ config = JSON.parse(readFileSync(path, "utf8"));
107
+ }
108
+ catch { /* fresh file */ }
109
+ config.defaultProvider = provider;
110
+ config.defaultModel = modelId;
111
+ mkdirSync(dirname(path), { recursive: true });
112
+ writeFileSync(path, JSON.stringify(config, null, 2));
113
+ }
114
+ catch { /* a live model switch must not fail because persistence failed */ }
115
+ }
116
+ async function command(args, ctx, deps) {
117
+ deps.setCommandContext(ctx);
118
+ const [verb, ...rest] = args.trim().split(/\s+/);
119
+ const value = rest.join(" ");
120
+ try {
121
+ switch (verb || "start") {
122
+ case "start":
123
+ await deps.start(ctx);
124
+ return;
125
+ case "stop":
126
+ deps.stop();
127
+ notify(ctx, "[pi-reach] Stopped.");
128
+ return;
129
+ case "status":
130
+ status(ctx, deps);
131
+ return;
132
+ case "pair":
133
+ await pair(ctx, value, deps);
134
+ return;
135
+ case "devices":
136
+ await listDevices(ctx, deps);
137
+ return;
138
+ case "revoke":
139
+ await revoke(value, ctx, deps);
140
+ return;
141
+ case "set-relay":
142
+ setRelay(value, ctx);
143
+ return;
144
+ case "config":
145
+ notify(ctx, `[pi-reach] ${resolveRelayUrl().url}`);
146
+ return;
147
+ case "internal-session-new":
148
+ await deps.runInternalSessionNew(value, ctx);
149
+ return;
150
+ default:
151
+ await deps.start(ctx);
152
+ return;
153
+ }
154
+ }
155
+ catch (error) {
156
+ notify(ctx, `[pi-reach] ${verb} failed: ${String(error)}`, "error");
157
+ }
158
+ }
159
+ export function registerCommands(pi, deps) {
160
+ const handler = (args, ctx) => command(args, ctx, deps);
161
+ pi.registerCommand("pi-reach", { description: "Connect this Pi endpoint to Pi Reach", handler });
162
+ pi.registerCommand("pi-reach start", { description: "Connect this endpoint to Relay", handler: async (_, ctx) => handler("start", ctx) });
163
+ pi.registerCommand("pi-reach stop", { description: "Disconnect this endpoint", handler: async (_, ctx) => handler("stop", ctx) });
164
+ pi.registerCommand("pi-reach pair", { description: "Show an endpoint pairing QR", handler: async (args, ctx) => handler(`pair ${args}`, ctx) });
165
+ pi.registerCommand("pi-reach devices", { description: "List locally paired Owners", handler: async (_, ctx) => handler("devices", ctx) });
166
+ pi.registerCommand("pi-reach revoke", { description: "Revoke a locally paired Owner", handler: async (args, ctx) => handler(`revoke ${args}`, ctx) });
167
+ pi.registerCommand("pi-reach set-relay", { description: "Set Relay URL", handler: async (args, ctx) => handler(`set-relay ${args}`, ctx) });
168
+ }
169
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Default community relay. Stored in canonical http(s):// form — conversion
3
+ * to ws(s):// happens at the transport layer (see `toWebSocketUrl`). The
4
+ * community relay's reverse proxy maps `:443 → :3000` (the WS port), so the
5
+ * URL has no explicit port and the WebSocket upgrade rides on the same TLS
6
+ * connection as the HTTPS endpoint used by the Relay client.
7
+ */
8
+ export declare const kDefaultRelayUrl = "https://pi-reach-relay.yefengr.cn";
9
+ export type PiReachConfig = {
10
+ relay?: string;
11
+ };
12
+ export declare function loadConfig(): PiReachConfig;
13
+ export declare function saveConfig(patch: Partial<PiReachConfig>): void;
14
+ export type RelayResolution = {
15
+ url: string;
16
+ source: "env" | "config" | "default";
17
+ };
18
+ /**
19
+ * Resolves the effective relay URL in **canonical http(s):// form**.
20
+ *
21
+ * Precedence:
22
+ * 1. `PI_REACH_RELAY` env var (ops/CI escape hatch)
23
+ * 2. `~/.pi/pi-reach/config.json` `relay` field (set via /pi-reach set-relay)
24
+ * 3. `kDefaultRelayUrl` (community default)
25
+ *
26
+ * Any ws(s):// values found in configs or env overrides are coerced
27
+ * to http(s):// defensively — the canonical form across the codebase is
28
+ * http(s)://, and the transport layer converts to ws(s):// at WS-open time.
29
+ */
30
+ export declare function resolveRelayUrl(): RelayResolution;
31
+ /**
32
+ * Strict validator for **user-provided** relay URLs (via `/pi-reach set-relay`).
33
+ *
34
+ * Only accepts `http://` and `https://`. `ws://`/`wss://` are deliberately
35
+ * **rejected** — the canonical form stored in config is http(s):// and the
36
+ * extension converts to ws(s):// internally when opening the WebSocket.
37
+ * Forcing a single scheme at the user boundary avoids two-form drift.
38
+ */
39
+ export declare function isValidRelayUrl(url: string): boolean;
40
+ /**
41
+ * Returns true if the URL uses ws:// or wss:// scheme — for emitting a
42
+ * targeted error message when the user pastes a WebSocket URL by mistake.
43
+ */
44
+ export declare function isWebSocketScheme(url: string): boolean;
45
+ /**
46
+ * Converts an http(s):// URL to the corresponding ws(s):// form. Used by
47
+ * the transport layer right before opening the WebSocket — config storage
48
+ * and the Relay client both stay on http(s)://.
49
+ *
50
+ * https://host → wss://host
51
+ * http://host → ws://host
52
+ * ws(s)://host → pass-through (defensive — env overrides or
53
+ * configs may carry ws(s)://)
54
+ */
55
+ export declare function toWebSocketUrl(url: string): string;
56
+ /**
57
+ * Inverse of `toWebSocketUrl`. Used by `resolveRelayUrl` to coerce any
58
+ * ws(s):// values back to canonical http(s):// before returning them to
59
+ * the rest of the codebase.
60
+ */
61
+ export declare function toHttpUrl(url: string): string;
package/dist/config.js ADDED
@@ -0,0 +1,115 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ const CONFIG_DIR = path.join(os.homedir(), ".pi", "pi-reach");
5
+ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
6
+ /**
7
+ * Default community relay. Stored in canonical http(s):// form — conversion
8
+ * to ws(s):// happens at the transport layer (see `toWebSocketUrl`). The
9
+ * community relay's reverse proxy maps `:443 → :3000` (the WS port), so the
10
+ * URL has no explicit port and the WebSocket upgrade rides on the same TLS
11
+ * connection as the HTTPS endpoint used by the Relay client.
12
+ */
13
+ export const kDefaultRelayUrl = "https://pi-reach-relay.yefengr.cn";
14
+ export function loadConfig() {
15
+ try {
16
+ const raw = fs.readFileSync(CONFIG_FILE, "utf8");
17
+ const parsed = JSON.parse(raw);
18
+ if (!parsed || typeof parsed !== "object")
19
+ return {};
20
+ return parsed;
21
+ }
22
+ catch {
23
+ return {};
24
+ }
25
+ }
26
+ export function saveConfig(patch) {
27
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
28
+ const current = loadConfig();
29
+ const next = { ...current, ...patch };
30
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2));
31
+ }
32
+ /**
33
+ * Resolves the effective relay URL in **canonical http(s):// form**.
34
+ *
35
+ * Precedence:
36
+ * 1. `PI_REACH_RELAY` env var (ops/CI escape hatch)
37
+ * 2. `~/.pi/pi-reach/config.json` `relay` field (set via /pi-reach set-relay)
38
+ * 3. `kDefaultRelayUrl` (community default)
39
+ *
40
+ * Any ws(s):// values found in configs or env overrides are coerced
41
+ * to http(s):// defensively — the canonical form across the codebase is
42
+ * http(s)://, and the transport layer converts to ws(s):// at WS-open time.
43
+ */
44
+ export function resolveRelayUrl() {
45
+ const env = process.env["PI_REACH_RELAY"];
46
+ if (env && env.length > 0)
47
+ return { url: toHttpUrl(env), source: "env" };
48
+ const cfg = loadConfig();
49
+ if (cfg.relay && cfg.relay.length > 0) {
50
+ return { url: toHttpUrl(cfg.relay), source: "config" };
51
+ }
52
+ return { url: toHttpUrl(kDefaultRelayUrl), source: "default" };
53
+ }
54
+ /**
55
+ * Strict validator for **user-provided** relay URLs (via `/pi-reach set-relay`).
56
+ *
57
+ * Only accepts `http://` and `https://`. `ws://`/`wss://` are deliberately
58
+ * **rejected** — the canonical form stored in config is http(s):// and the
59
+ * extension converts to ws(s):// internally when opening the WebSocket.
60
+ * Forcing a single scheme at the user boundary avoids two-form drift.
61
+ */
62
+ export function isValidRelayUrl(url) {
63
+ if (!url)
64
+ return false;
65
+ const lower = url.toLowerCase();
66
+ if (!lower.startsWith("http://") && !lower.startsWith("https://"))
67
+ return false;
68
+ try {
69
+ new URL(url);
70
+ return true;
71
+ }
72
+ catch {
73
+ return false;
74
+ }
75
+ }
76
+ /**
77
+ * Returns true if the URL uses ws:// or wss:// scheme — for emitting a
78
+ * targeted error message when the user pastes a WebSocket URL by mistake.
79
+ */
80
+ export function isWebSocketScheme(url) {
81
+ const lower = url.toLowerCase();
82
+ return lower.startsWith("ws://") || lower.startsWith("wss://");
83
+ }
84
+ /**
85
+ * Converts an http(s):// URL to the corresponding ws(s):// form. Used by
86
+ * the transport layer right before opening the WebSocket — config storage
87
+ * and the Relay client both stay on http(s)://.
88
+ *
89
+ * https://host → wss://host
90
+ * http://host → ws://host
91
+ * ws(s)://host → pass-through (defensive — env overrides or
92
+ * configs may carry ws(s)://)
93
+ */
94
+ export function toWebSocketUrl(url) {
95
+ const lower = url.toLowerCase();
96
+ if (lower.startsWith("https://"))
97
+ return "wss://" + url.slice("https://".length);
98
+ if (lower.startsWith("http://"))
99
+ return "ws://" + url.slice("http://".length);
100
+ return url;
101
+ }
102
+ /**
103
+ * Inverse of `toWebSocketUrl`. Used by `resolveRelayUrl` to coerce any
104
+ * ws(s):// values back to canonical http(s):// before returning them to
105
+ * the rest of the codebase.
106
+ */
107
+ export function toHttpUrl(url) {
108
+ const lower = url.toLowerCase();
109
+ if (lower.startsWith("wss://"))
110
+ return "https://" + url.slice("wss://".length);
111
+ if (lower.startsWith("ws://"))
112
+ return "http://" + url.slice("ws://".length);
113
+ return url;
114
+ }
115
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;AAC9D,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEzD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,mCAAmC,CAAC;AAIpE,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAC;QAC1C,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QACrD,OAAO,MAAuB,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAA6B;IACtD,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,IAAI,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;IACtC,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC/D,CAAC;AAID;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC1C,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACzE,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,IAAI,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtC,OAAO,EAAE,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IACzD,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,SAAS,CAAC,gBAAgB,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AACjE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAChC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,KAAK,CAAC;IAChF,IAAI,CAAC;QAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,KAAK,CAAC;IAAC,CAAC;AAC5D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAChC,OAAO,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAChC,IAAI,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACjF,IAAI,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC;QAAG,OAAO,OAAO,GAAI,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAChF,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAChC,IAAI,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,UAAU,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/E,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC;QAAG,OAAO,SAAS,GAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9E,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import type { ExtensionFactory } from "@earendil-works/pi-coding-agent";
3
+ import { type CurrentModelHint } from "./actions/handlers.js";
4
+ export type { RelayConnectivity, RemoteState } from "./runtime/relay_lifecycle.js";
5
+ type ProcessIdentity = Readonly<{
6
+ endpointId: string;
7
+ runtimeInstanceId: string;
8
+ }>;
9
+ /** One Pi process owns a fresh endpoint identity; extension reloads reuse it. */
10
+ export declare function processEndpointIdentity(): ProcessIdentity;
11
+ export declare const _getState: () => "idle" | "started" | "paired";
12
+ export declare const _getCachedPublicKeyForTest: () => string | null;
13
+ export declare const _getCurrentTurnIdForTest: () => string | null;
14
+ export declare const _setPiForTest: (value: unknown) => void;
15
+ export declare const _setCurrentModelForTest: (value: CurrentModelHint | undefined) => void;
16
+ export declare const _hasPendingReconnect: () => boolean;
17
+ export declare const _getActivePeerCountForTest: () => number;
18
+ export declare const _hasActivePeerForTest: (ownerId: string) => boolean;
19
+ export declare const _getDisposedForTest: () => boolean;
20
+ export declare const _setDisposedForTest: (value: boolean) => void;
21
+ export declare const _setSessionNewBridgeTimeoutForTest: (timeoutMs: number) => void;
22
+ declare const extension: ExtensionFactory;
23
+ export default extension;
24
+ /** Compatibility seam used by focused extension tests. */
25
+ export declare function _startRelayForTest(ctx: unknown): Promise<void>;
26
+ export declare function _stopForTest(_ctx: unknown): Promise<void>;
27
+ export declare function _connectForTest(ctx: unknown): Promise<void>;