remote-pi-app 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +675 -0
  3. package/dist/actions/handlers.d.ts +138 -0
  4. package/dist/actions/handlers.js +189 -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/bin/supervisord.d.ts +2 -0
  10. package/dist/bin/supervisord.js +86 -0
  11. package/dist/bin/supervisord.js.map +1 -0
  12. package/dist/config.d.ts +62 -0
  13. package/dist/config.js +115 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/daemon/client.d.ts +20 -0
  16. package/dist/daemon/client.js +131 -0
  17. package/dist/daemon/client.js.map +1 -0
  18. package/dist/daemon/control_protocol.d.ts +176 -0
  19. package/dist/daemon/control_protocol.js +63 -0
  20. package/dist/daemon/control_protocol.js.map +1 -0
  21. package/dist/daemon/cron_log.d.ts +45 -0
  22. package/dist/daemon/cron_log.js +72 -0
  23. package/dist/daemon/cron_log.js.map +1 -0
  24. package/dist/daemon/cron_registry.d.ts +80 -0
  25. package/dist/daemon/cron_registry.js +194 -0
  26. package/dist/daemon/cron_registry.js.map +1 -0
  27. package/dist/daemon/id.d.ts +24 -0
  28. package/dist/daemon/id.js +36 -0
  29. package/dist/daemon/id.js.map +1 -0
  30. package/dist/daemon/install.d.ts +170 -0
  31. package/dist/daemon/install.js +547 -0
  32. package/dist/daemon/install.js.map +1 -0
  33. package/dist/daemon/registry.d.ts +63 -0
  34. package/dist/daemon/registry.js +153 -0
  35. package/dist/daemon/registry.js.map +1 -0
  36. package/dist/daemon/rpc_child.d.ts +194 -0
  37. package/dist/daemon/rpc_child.js +389 -0
  38. package/dist/daemon/rpc_child.js.map +1 -0
  39. package/dist/daemon/supervisor.d.ts +93 -0
  40. package/dist/daemon/supervisor.js +596 -0
  41. package/dist/daemon/supervisor.js.map +1 -0
  42. package/dist/extension_ui_bridge.d.ts +26 -0
  43. package/dist/extension_ui_bridge.js +341 -0
  44. package/dist/extension_ui_bridge.js.map +1 -0
  45. package/dist/index.d.ts +213 -0
  46. package/dist/index.js +5090 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/mcp/mesh_result.d.ts +10 -0
  49. package/dist/mcp/mesh_result.js +42 -0
  50. package/dist/mcp/mesh_result.js.map +1 -0
  51. package/dist/mcp/mesh_server.d.ts +16 -0
  52. package/dist/mcp/mesh_server.js +327 -0
  53. package/dist/mcp/mesh_server.js.map +1 -0
  54. package/dist/mesh/canonical.d.ts +30 -0
  55. package/dist/mesh/canonical.js +61 -0
  56. package/dist/mesh/canonical.js.map +1 -0
  57. package/dist/mesh/client.d.ts +18 -0
  58. package/dist/mesh/client.js +125 -0
  59. package/dist/mesh/client.js.map +1 -0
  60. package/dist/mesh/encoding.d.ts +38 -0
  61. package/dist/mesh/encoding.js +216 -0
  62. package/dist/mesh/encoding.js.map +1 -0
  63. package/dist/mesh/self_revoke.d.ts +73 -0
  64. package/dist/mesh/self_revoke.js +401 -0
  65. package/dist/mesh/self_revoke.js.map +1 -0
  66. package/dist/mesh/siblings.d.ts +49 -0
  67. package/dist/mesh/siblings.js +159 -0
  68. package/dist/mesh/siblings.js.map +1 -0
  69. package/dist/mesh/types.d.ts +34 -0
  70. package/dist/mesh/types.js +11 -0
  71. package/dist/mesh/types.js.map +1 -0
  72. package/dist/mesh/verify.d.ts +17 -0
  73. package/dist/mesh/verify.js +86 -0
  74. package/dist/mesh/verify.js.map +1 -0
  75. package/dist/pairing/crypto.d.ts +8 -0
  76. package/dist/pairing/crypto.js +22 -0
  77. package/dist/pairing/crypto.js.map +1 -0
  78. package/dist/pairing/qr.d.ts +59 -0
  79. package/dist/pairing/qr.js +125 -0
  80. package/dist/pairing/qr.js.map +1 -0
  81. package/dist/pairing/storage.d.ts +120 -0
  82. package/dist/pairing/storage.js +604 -0
  83. package/dist/pairing/storage.js.map +1 -0
  84. package/dist/protocol/codec.d.ts +7 -0
  85. package/dist/protocol/codec.js +53 -0
  86. package/dist/protocol/codec.js.map +1 -0
  87. package/dist/protocol/types.d.ts +432 -0
  88. package/dist/protocol/types.js +2 -0
  89. package/dist/protocol/types.js.map +1 -0
  90. package/dist/rooms.d.ts +29 -0
  91. package/dist/rooms.js +57 -0
  92. package/dist/rooms.js.map +1 -0
  93. package/dist/session/bridge.d.ts +37 -0
  94. package/dist/session/bridge.js +130 -0
  95. package/dist/session/bridge.js.map +1 -0
  96. package/dist/session/broker.d.ts +189 -0
  97. package/dist/session/broker.js +507 -0
  98. package/dist/session/broker.js.map +1 -0
  99. package/dist/session/broker_remote.d.ts +74 -0
  100. package/dist/session/broker_remote.js +583 -0
  101. package/dist/session/broker_remote.js.map +1 -0
  102. package/dist/session/cwd_lock.d.ts +33 -0
  103. package/dist/session/cwd_lock.js +126 -0
  104. package/dist/session/cwd_lock.js.map +1 -0
  105. package/dist/session/envelope.d.ts +33 -0
  106. package/dist/session/envelope.js +117 -0
  107. package/dist/session/envelope.js.map +1 -0
  108. package/dist/session/global_config.d.ts +33 -0
  109. package/dist/session/global_config.js +73 -0
  110. package/dist/session/global_config.js.map +1 -0
  111. package/dist/session/ipc.d.ts +27 -0
  112. package/dist/session/ipc.js +22 -0
  113. package/dist/session/ipc.js.map +1 -0
  114. package/dist/session/leader_election.d.ts +32 -0
  115. package/dist/session/leader_election.js +106 -0
  116. package/dist/session/leader_election.js.map +1 -0
  117. package/dist/session/local_config.d.ts +55 -0
  118. package/dist/session/local_config.js +157 -0
  119. package/dist/session/local_config.js.map +1 -0
  120. package/dist/session/mesh_node.d.ts +116 -0
  121. package/dist/session/mesh_node.js +573 -0
  122. package/dist/session/mesh_node.js.map +1 -0
  123. package/dist/session/peer.d.ts +139 -0
  124. package/dist/session/peer.js +451 -0
  125. package/dist/session/peer.js.map +1 -0
  126. package/dist/session/peer_inventory.d.ts +13 -0
  127. package/dist/session/peer_inventory.js +48 -0
  128. package/dist/session/peer_inventory.js.map +1 -0
  129. package/dist/session/peer_limits.d.ts +9 -0
  130. package/dist/session/peer_limits.js +21 -0
  131. package/dist/session/peer_limits.js.map +1 -0
  132. package/dist/session/setup_wizard.d.ts +39 -0
  133. package/dist/session/setup_wizard.js +63 -0
  134. package/dist/session/setup_wizard.js.map +1 -0
  135. package/dist/session/tools.d.ts +24 -0
  136. package/dist/session/tools.js +252 -0
  137. package/dist/session/tools.js.map +1 -0
  138. package/dist/session/wizard.d.ts +13 -0
  139. package/dist/session/wizard.js +20 -0
  140. package/dist/session/wizard.js.map +1 -0
  141. package/dist/transport/peer_channel.d.ts +37 -0
  142. package/dist/transport/peer_channel.js +98 -0
  143. package/dist/transport/peer_channel.js.map +1 -0
  144. package/dist/transport/pi_forward_client.d.ts +29 -0
  145. package/dist/transport/pi_forward_client.js +62 -0
  146. package/dist/transport/pi_forward_client.js.map +1 -0
  147. package/dist/transport/relay_client.d.ts +91 -0
  148. package/dist/transport/relay_client.js +206 -0
  149. package/dist/transport/relay_client.js.map +1 -0
  150. package/dist/ui/footer.d.ts +32 -0
  151. package/dist/ui/footer.js +34 -0
  152. package/dist/ui/footer.js.map +1 -0
  153. package/docs/daemon.md +289 -0
  154. package/package.json +90 -0
  155. package/service-templates/launchd.plist.template +35 -0
  156. package/service-templates/systemd.service.template +19 -0
  157. package/service-templates/task-launcher.vbs.template +10 -0
  158. package/service-templates/task-scheduler.xml.template +38 -0
  159. package/skills/agent-network/SKILL.md +310 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer_inventory.js","sourceRoot":"","sources":["../../src/session/peer_inventory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,UAAU,mBAAmB,CAAC,KAAe,EAAE,QAAiB;IACpE,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC5C,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,QAAQ,IAAI,CAAC,KAAK,QAAQ;YAAE,SAAS;QACzC,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YAC9B,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YAC9B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YACxC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClB,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAC7B,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAAa,CAAC,UAAU,CAAC,CAAC;IACrC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC3B,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,MAAM,YAAY,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAChD,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;QACjC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,YAAY,KAAK,EAAE,CAAC,CAAC;QAChC,KAAK,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { PeerInfo } from "./broker.js";
2
+ /** Shared wire limits measured in JavaScript UTF-16 code units (`.length`). */
3
+ export declare const MAX_PEERS_UPDATE_ENTRIES = 1024;
4
+ export declare const MAX_CWD_LENGTH = 4096;
5
+ export declare const MAX_NAME_LENGTH = 256;
6
+ export declare const MAX_ADDRESS_LENGTH = 4352;
7
+ export declare function isBoundedPeerInfo(value: unknown): value is PeerInfo;
8
+ export declare function isBoundedPeerRoster(infos: readonly PeerInfo[]): boolean;
9
+ export declare function isBoundedPeerAddresses(addresses: readonly unknown[]): boolean;
@@ -0,0 +1,21 @@
1
+ /** Shared wire limits measured in JavaScript UTF-16 code units (`.length`). */
2
+ export const MAX_PEERS_UPDATE_ENTRIES = 1024;
3
+ export const MAX_CWD_LENGTH = 4096;
4
+ export const MAX_NAME_LENGTH = 256;
5
+ export const MAX_ADDRESS_LENGTH = 4352;
6
+ export function isBoundedPeerInfo(value) {
7
+ if (!value || typeof value !== "object")
8
+ return false;
9
+ const { cwd, name, address } = value;
10
+ return typeof cwd === "string" && cwd.length <= MAX_CWD_LENGTH &&
11
+ typeof name === "string" && name.length <= MAX_NAME_LENGTH &&
12
+ typeof address === "string" && address.length <= MAX_ADDRESS_LENGTH;
13
+ }
14
+ export function isBoundedPeerRoster(infos) {
15
+ return infos.length <= MAX_PEERS_UPDATE_ENTRIES && infos.every(isBoundedPeerInfo);
16
+ }
17
+ export function isBoundedPeerAddresses(addresses) {
18
+ return addresses.length <= MAX_PEERS_UPDATE_ENTRIES &&
19
+ addresses.every((address) => typeof address === "string" && address.length <= MAX_ADDRESS_LENGTH);
20
+ }
21
+ //# sourceMappingURL=peer_limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer_limits.js","sourceRoot":"","sources":["../../src/session/peer_limits.ts"],"names":[],"mappings":"AAEA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC;AAC7C,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC;AACnC,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAC;AACnC,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEvC,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,KAAiB,CAAC;IACjD,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,IAAI,cAAc;QAC5D,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,IAAI,eAAe;QAC1D,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,IAAI,kBAAkB,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,KAA0B;IAE1B,OAAO,KAAK,CAAC,MAAM,IAAI,wBAAwB,IAAI,KAAK,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,SAA6B;IAClE,OAAO,SAAS,CAAC,MAAM,IAAI,wBAAwB;QACjD,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,CAC1B,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,IAAI,kBAAkB,CACpE,CAAC;AACN,CAAC"}
@@ -0,0 +1,39 @@
1
+ import type { LocalConfig } from "./local_config.js";
2
+ /**
3
+ * Pi SDK UI surface needed by the wizard. Subset of `ExtensionUIContext` —
4
+ * declared inline so tests can mock cleanly without dragging the full
5
+ * ExtensionContext shape.
6
+ */
7
+ export interface WizardUI {
8
+ /** Free-text prompt. Returns the entered string, or undefined if cancelled. */
9
+ input?: (title: string, options?: {
10
+ defaultValue?: string;
11
+ }) => Promise<string | undefined>;
12
+ /** Picker. Returns the picked option, or undefined if cancelled. */
13
+ select: (title: string, options: string[]) => Promise<string | undefined>;
14
+ /** Non-blocking notification. Used for inline validation feedback. */
15
+ notify?: (msg: string, kind: "info" | "warning" | "error") => void;
16
+ }
17
+ export interface WizardDefaults {
18
+ agent_name: string;
19
+ use_relay: boolean;
20
+ }
21
+ /**
22
+ * Runs the 2-question setup wizard. Returns the chosen config on confirm, or
23
+ * null when the user cancels any prompt.
24
+ *
25
+ * Prompts:
26
+ * 1. Agent name (default: parent/folder of cwd)
27
+ * 2. Use the relay on this terminal? (yes/no) — gates connection to the
28
+ * remote mesh (mobile devices + other PCs over the relay). "No" means
29
+ * local-only: this Pi joins the UDS mesh but doesn't open WSS.
30
+ * Final: review + confirm "Save and activate?" yes/no
31
+ *
32
+ * Daemon mode (run agents 24/7 via systemd/launchd) is intentionally NOT in
33
+ * the wizard — it's an explicit, separate opt-in via `/remote-pi install`.
34
+ *
35
+ * The local UDS mesh is always single per machine ("local" session) — no
36
+ * session question. All Pis on the same machine see each other through
37
+ * the same broker.
38
+ */
39
+ export declare function runSetupWizard(ui: WizardUI, defaults: WizardDefaults): Promise<LocalConfig | null>;
@@ -0,0 +1,63 @@
1
+ const YES = "Yes";
2
+ const NO = "No";
3
+ const CANCEL_TOKEN = "__cancel__";
4
+ /**
5
+ * Runs the 2-question setup wizard. Returns the chosen config on confirm, or
6
+ * null when the user cancels any prompt.
7
+ *
8
+ * Prompts:
9
+ * 1. Agent name (default: parent/folder of cwd)
10
+ * 2. Use the relay on this terminal? (yes/no) — gates connection to the
11
+ * remote mesh (mobile devices + other PCs over the relay). "No" means
12
+ * local-only: this Pi joins the UDS mesh but doesn't open WSS.
13
+ * Final: review + confirm "Save and activate?" yes/no
14
+ *
15
+ * Daemon mode (run agents 24/7 via systemd/launchd) is intentionally NOT in
16
+ * the wizard — it's an explicit, separate opt-in via `/remote-pi install`.
17
+ *
18
+ * The local UDS mesh is always single per machine ("local" session) — no
19
+ * session question. All Pis on the same machine see each other through
20
+ * the same broker.
21
+ */
22
+ export async function runSetupWizard(ui, defaults) {
23
+ const agent_name = await _askText(ui, "Agent name:", defaults.agent_name);
24
+ if (agent_name === null)
25
+ return null;
26
+ ui.notify?.("The relay forwards encrypted messages to the Remote Pi mobile app and other PCs in your mesh. Skip this if you only want a local-only mesh on this machine.", "info");
27
+ const useRelayChoice = await ui.select("Use the relay on this terminal to connect to the remote mesh (mobile + PCs)?", defaults.use_relay ? [YES, NO] : [NO, YES]);
28
+ if (!useRelayChoice)
29
+ return null;
30
+ const auto_start_relay = useRelayChoice === YES;
31
+ // Review + confirm
32
+ const summary = [
33
+ ` Agent name: ${agent_name}`,
34
+ ` Use relay: ${auto_start_relay ? YES : NO}`,
35
+ ].join("\n");
36
+ ui.notify?.(`Summary:\n${summary}`, "info");
37
+ const confirm = await ui.select("Save and activate?", [YES, NO]);
38
+ if (confirm !== YES)
39
+ return null;
40
+ return { agent_name, auto_start_relay };
41
+ }
42
+ /**
43
+ * Asks the user for free text. The Pi SDK's `ui.input` does not pre-fill the
44
+ * field with `defaultValue` (the SDK ignores that option), so we surface the
45
+ * default in the prompt label and treat an empty submission as "accept the
46
+ * default" — the standard CLI convention. Falls back to `select` when the
47
+ * SDK doesn't expose `input` at all.
48
+ */
49
+ async function _askText(ui, title, defaultValue) {
50
+ const titleWithHint = `${title} (default: ${defaultValue})`;
51
+ const raw = ui.input
52
+ ? await ui.input(titleWithHint, { defaultValue })
53
+ : await ui.select(titleWithHint, [defaultValue, CANCEL_TOKEN]);
54
+ if (raw === undefined)
55
+ return null;
56
+ if (raw === CANCEL_TOKEN)
57
+ return null;
58
+ const trimmed = raw.trim();
59
+ // Empty submission = accept the default. No re-prompt, no warning — the
60
+ // user explicitly asked for the default by hitting enter.
61
+ return trimmed.length > 0 ? trimmed : defaultValue;
62
+ }
63
+ //# sourceMappingURL=setup_wizard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup_wizard.js","sourceRoot":"","sources":["../../src/session/setup_wizard.ts"],"names":[],"mappings":"AAqBA,MAAM,GAAG,GAAG,KAAK,CAAC;AAClB,MAAM,EAAE,GAAG,IAAI,CAAC;AAChB,MAAM,YAAY,GAAG,YAAY,CAAC;AAElC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,EAAY,EACZ,QAAwB;IAExB,MAAM,UAAU,GAAG,MAAM,QAAQ,CAC/B,EAAE,EACF,aAAa,EACb,QAAQ,CAAC,UAAU,CACpB,CAAC;IACF,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAErC,EAAE,CAAC,MAAM,EAAE,CACT,6JAA6J,EAC7J,MAAM,CACP,CAAC;IACF,MAAM,cAAc,GAAG,MAAM,EAAE,CAAC,MAAM,CACpC,8EAA8E,EAC9E,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAC3C,CAAC;IACF,IAAI,CAAC,cAAc;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,gBAAgB,GAAG,cAAc,KAAK,GAAG,CAAC;IAEhD,mBAAmB;IACnB,MAAM,OAAO,GAAG;QACd,oBAAoB,UAAU,EAAE;QAChC,oBAAoB,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;KAClD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,EAAE,CAAC,MAAM,EAAE,CAAC,aAAa,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;IAE5C,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,oBAAoB,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;IACjE,IAAI,OAAO,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAEjC,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC;AAC1C,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,QAAQ,CACrB,EAAY,EACZ,KAAa,EACb,YAAoB;IAEpB,MAAM,aAAa,GAAG,GAAG,KAAK,cAAc,YAAY,GAAG,CAAC;IAC5D,MAAM,GAAG,GAAG,EAAE,CAAC,KAAK;QAClB,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,aAAa,EAAE,EAAE,YAAY,EAAE,CAAC;QACjD,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC;IACjE,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,GAAG,KAAK,YAAY;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,wEAAwE;IACxE,0DAA0D;IAC1D,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC;AACrD,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import type { SessionPeer } from "./peer.js";
3
+ /**
4
+ * Registers the native tools the Pi LLM uses to talk to other agents in the
5
+ * same UDS session (plano 19 transport + plan/25 Wave 0 ACK protocol):
6
+ *
7
+ * - `agent_send` — unified delivery with broker-level ACK. Returns
8
+ * a status so the LLM can decide whether to retry.
9
+ * For unicast targets the broker auto-replies with
10
+ * `received | busy | denied`. For broadcast/multicast
11
+ * the tool is fire-and-forget (status `sent`).
12
+ * - `agent_request` — **deprecated**. Still works (send + block on reply
13
+ * via `re` correlation) but the LLM should migrate
14
+ * to the event-driven send+inbox pattern. Each call
15
+ * emits a one-shot warning to stderr.
16
+ *
17
+ * Reply pattern (new world): when you receive a message you want to answer,
18
+ * send back another envelope with `re=<original-id>`. The original sender
19
+ * sees that reply in its inbox during a future turn.
20
+ *
21
+ * `getSessionPeer` is a getter (not a captured value) so changes to the
22
+ * underlying `_sessionPeer` module variable are observed live.
23
+ */
24
+ export declare function registerAgentTools(pi: ExtensionAPI, getSessionPeer: () => SessionPeer | null): void;
@@ -0,0 +1,252 @@
1
+ import { Type } from "typebox";
2
+ const NOT_IN_SESSION = "Not in a session. Run /remote-pi join first";
3
+ const ACK_TIMEOUT_MS = 5_000;
4
+ const LEGACY_REQUEST_TIMEOUT_MS = 30_000;
5
+ const LIST_PEERS_TIMEOUT_MS = 2_000;
6
+ /**
7
+ * Registers the native tools the Pi LLM uses to talk to other agents in the
8
+ * same UDS session (plano 19 transport + plan/25 Wave 0 ACK protocol):
9
+ *
10
+ * - `agent_send` — unified delivery with broker-level ACK. Returns
11
+ * a status so the LLM can decide whether to retry.
12
+ * For unicast targets the broker auto-replies with
13
+ * `received | busy | denied`. For broadcast/multicast
14
+ * the tool is fire-and-forget (status `sent`).
15
+ * - `agent_request` — **deprecated**. Still works (send + block on reply
16
+ * via `re` correlation) but the LLM should migrate
17
+ * to the event-driven send+inbox pattern. Each call
18
+ * emits a one-shot warning to stderr.
19
+ *
20
+ * Reply pattern (new world): when you receive a message you want to answer,
21
+ * send back another envelope with `re=<original-id>`. The original sender
22
+ * sees that reply in its inbox during a future turn.
23
+ *
24
+ * `getSessionPeer` is a getter (not a captured value) so changes to the
25
+ * underlying `_sessionPeer` module variable are observed live.
26
+ */
27
+ export function registerAgentTools(pi, getSessionPeer) {
28
+ const SendParams = Type.Object({
29
+ to: Type.String({
30
+ description: "Recipient agent name (e.g. 'backend'), 'broadcast', or array of names. " +
31
+ "Broadcast/multicast are fire-and-forget; unicast returns an ACK status.",
32
+ }),
33
+ body: Type.Unknown({ description: "Free-form JSON payload. String or object — your choice." }),
34
+ re: Type.Optional(Type.String({
35
+ description: "Set this to the `id` of an incoming message when you are REPLYING to it. " +
36
+ "The peer correlates your answer with their original send via this field.",
37
+ })),
38
+ });
39
+ const RequestParams = Type.Object({
40
+ to: Type.String({ description: "Recipient agent name. Must be a single peer (not broadcast)." }),
41
+ body: Type.Unknown({ description: "Free-form JSON payload to send." }),
42
+ timeout_ms: Type.Optional(Type.Number({
43
+ description: "Optional override of the default 30s reply timeout. Per-request.",
44
+ })),
45
+ });
46
+ pi.registerTool({
47
+ name: "agent_send",
48
+ label: "Agent Send",
49
+ description: "Send a message to another Pi agent in the current local session and " +
50
+ "wait for the broker's delivery ACK. Delivery is reliable: a peer that " +
51
+ "is mid-turn still receives the message (its harness enqueues it for " +
52
+ "the next turn), so you never need to retry. Returns one of: `received` " +
53
+ "(delivered; peer will process it in an upcoming turn), `denied` (peer " +
54
+ "refused), `timeout` (no ACK in 5s — peer offline / transport error), " +
55
+ "`sent` (broadcast/multicast — no ACK semantics). Use `re` to mark this " +
56
+ "message as a reply to an incoming envelope's `id` (correlation only).",
57
+ promptSnippet: "agent_send({to, body, re?}): unicast → returns {status: received|denied|timeout} (delivery is reliable; no retry-on-busy). Broadcast/multicast → fire-and-forget ({status:'sent'}).",
58
+ parameters: SendParams,
59
+ execute: async (_toolCallId, params) => {
60
+ const peer = getSessionPeer();
61
+ if (!peer) {
62
+ const details = { status: "refused", ok: false, error: NOT_IN_SESSION };
63
+ return {
64
+ content: [{ type: "text", text: NOT_IN_SESSION }],
65
+ details,
66
+ };
67
+ }
68
+ const { to, body, re } = params;
69
+ if (to === peer.address() || to === peer.name()) {
70
+ const msg = `Refused: cannot agent_send to yourself ("${to}"). Just do the work directly.`;
71
+ const details = { status: "refused", ok: false, error: msg };
72
+ return {
73
+ content: [{ type: "text", text: msg }],
74
+ details,
75
+ };
76
+ }
77
+ const isUnicast = to !== "broadcast";
78
+ // Broadcast: fire-and-forget. Broker doesn't ACK multi-target sends.
79
+ if (!isUnicast) {
80
+ try {
81
+ await peer.send(to, body, re ?? null);
82
+ const details = { status: "sent", ok: true };
83
+ return {
84
+ content: [{ type: "text", text: `Broadcast sent.` }],
85
+ details,
86
+ };
87
+ }
88
+ catch (err) {
89
+ const msg = err instanceof Error ? err.message : String(err);
90
+ const details = { status: "timeout", ok: false, error: msg };
91
+ return {
92
+ content: [{ type: "text", text: `Broadcast failed: ${msg}` }],
93
+ details,
94
+ };
95
+ }
96
+ }
97
+ // Unicast: wait for broker ACK.
98
+ try {
99
+ const ack = await peer.sendWithAck(to, body, re ?? null, ACK_TIMEOUT_MS);
100
+ const ok = ack.status === "received";
101
+ const details = {
102
+ status: ack.status,
103
+ ok,
104
+ ...(ack.target !== undefined ? { target: ack.target } : {}),
105
+ ...(ack.error !== undefined ? { error: ack.error } : {}),
106
+ ...(ack.reason !== undefined ? { reason: ack.reason } : {}),
107
+ };
108
+ const text = _formatAck(to, ack, re);
109
+ return { content: [{ type: "text", text }], details };
110
+ }
111
+ catch (err) {
112
+ const msg = err instanceof Error ? err.message : String(err);
113
+ const details = { status: "timeout", ok: false, error: msg };
114
+ return {
115
+ content: [{ type: "text", text: `Failed to send: ${msg}` }],
116
+ details,
117
+ };
118
+ }
119
+ },
120
+ });
121
+ const ListPeersParams = Type.Object({});
122
+ pi.registerTool({
123
+ name: "list_peers",
124
+ label: "List Peers",
125
+ description: "Returns the current peer inventory in this session as ADDRESSES of the " +
126
+ "form `<cwd>@<name>` (cross-PC peers prefixed `<pc>:`). An address is an " +
127
+ "opaque routing key — pass it to `agent_send`/`agent_request` VERBATIM, " +
128
+ "never build one by hand. Use BEFORE sending whenever you're unsure who's " +
129
+ "available, or after a `peer_joined` / `peer_left` notification to refresh " +
130
+ "your mental model. Resolves in milliseconds — a metadata query to the " +
131
+ "broker, not a turn of another agent.",
132
+ promptSnippet: "list_peers(): returns {peers: string[]} of addresses `<cwd>@<name>` (`<pc>:` prefix cross-PC). Echo an address verbatim to agent_send; never compose one. Cheap; call freely.",
133
+ parameters: ListPeersParams,
134
+ execute: async (_toolCallId) => {
135
+ const peer = getSessionPeer();
136
+ if (!peer) {
137
+ return {
138
+ content: [{ type: "text", text: NOT_IN_SESSION }],
139
+ details: { peers: [] },
140
+ };
141
+ }
142
+ try {
143
+ // Internal use of the request/reply primitive is fine — broker
144
+ // replies are synthesised in-process (`_handleBrokerMessage`)
145
+ // without going through `_route`, so they bypass the ACK path.
146
+ const reply = await peer.request("broker", { type: "list_peers" }, LIST_PEERS_TIMEOUT_MS);
147
+ const body = reply.body;
148
+ const peers = Array.isArray(body?.peers)
149
+ ? body.peers.filter((p) => typeof p === "string")
150
+ : [];
151
+ // Drop self from the list — the caller is the only one who can't
152
+ // address itself anyway, so listing it is noise. Peers are ADDRESSES
153
+ // (plan/38), so filter by address.
154
+ const selfAddress = peer.address();
155
+ const filtered = peers.filter((p) => p !== selfAddress);
156
+ const text = filtered.length === 0 ? "(no peers)" : filtered.join("\n");
157
+ return {
158
+ content: [{ type: "text", text }],
159
+ details: { peers: filtered },
160
+ };
161
+ }
162
+ catch (err) {
163
+ const msg = err instanceof Error ? err.message : String(err);
164
+ return {
165
+ content: [{ type: "text", text: `list_peers failed: ${msg}` }],
166
+ details: { peers: [] },
167
+ };
168
+ }
169
+ },
170
+ });
171
+ pi.registerTool({
172
+ name: "agent_request",
173
+ label: "Agent Request (deprecated)",
174
+ description: "DEPRECATED — prefer `agent_send` + observing your inbox for the " +
175
+ "reply (correlated by `re=<your-send-id>`). This tool still works: " +
176
+ "it sends a message and synchronously blocks until the peer replies " +
177
+ "or the timeout fires. Default 30s. Will be removed in a future " +
178
+ "release; migrate to the event-driven pattern in the agent-network skill.",
179
+ promptSnippet: "agent_request({to, body, timeout_ms?}): DEPRECATED synchronous request/reply (blocks current turn). Prefer agent_send + inbox observation.",
180
+ parameters: RequestParams,
181
+ execute: async (_toolCallId, params) => {
182
+ const peer = getSessionPeer();
183
+ if (!peer) {
184
+ return {
185
+ content: [{ type: "text", text: NOT_IN_SESSION }],
186
+ details: { error: NOT_IN_SESSION },
187
+ };
188
+ }
189
+ const { to, body, timeout_ms } = params;
190
+ if (to === peer.address() || to === peer.name()) {
191
+ const msg = `Refused: cannot agent_request to yourself ("${to}"). Just do the work directly.`;
192
+ return {
193
+ content: [{ type: "text", text: msg }],
194
+ details: { error: msg },
195
+ };
196
+ }
197
+ const timeout = typeof timeout_ms === "number" && timeout_ms > 0
198
+ ? timeout_ms
199
+ : LEGACY_REQUEST_TIMEOUT_MS;
200
+ try {
201
+ const reply = await peer.request(to, body, timeout);
202
+ const text = typeof reply.body === "string"
203
+ ? reply.body
204
+ : JSON.stringify(reply.body);
205
+ return {
206
+ content: [{ type: "text", text }],
207
+ details: reply.body,
208
+ };
209
+ }
210
+ catch (err) {
211
+ const msg = err instanceof Error ? err.message : String(err);
212
+ return {
213
+ content: [{ type: "text", text: `Request failed: ${msg}` }],
214
+ details: { error: msg },
215
+ };
216
+ }
217
+ },
218
+ });
219
+ }
220
+ function _formatAck(to, ack, re) {
221
+ const reSuffix = re ? ` (re=${re})` : "";
222
+ const errorSuffix = ack.error ? ` (${ack.error})` : "";
223
+ switch (ack.status) {
224
+ case "received":
225
+ return `Delivered to "${to}"${reSuffix} — peer will process it in an upcoming turn.`;
226
+ case "busy":
227
+ // plan/34 removed busy-drop: the current broker never returns `busy`. If
228
+ // we still see it, an OUT-OF-DATE broker leader dropped the message — so
229
+ // be honest that it was NOT delivered, and point at the fix.
230
+ return `NOT delivered — "${to}"${reSuffix} came back BUSY, which only ` +
231
+ `happens when an out-of-date broker leader dropped the message. ` +
232
+ `Restart the agent leading the local broker (oldest Pi/remote-pi process) ` +
233
+ `to pick up the new build, then resend.`;
234
+ case "denied":
235
+ if (ack.reason === "not_authorized") {
236
+ return `Relay did not authorize delivery to "${to}"${reSuffix}${errorSuffix}. ` +
237
+ `Do not blindly retry; verify authorization first.`;
238
+ }
239
+ if (ack.reason === "bad_envelope") {
240
+ return `Relay rejected the envelope for "${to}"${reSuffix}${errorSuffix}. ` +
241
+ `Do not blindly retry; correct the envelope first.`;
242
+ }
243
+ return `"${to}" denied the message${reSuffix}. Do not retry; report to user.`;
244
+ case "timeout":
245
+ if (ack.reason === "offline") {
246
+ return `Immediate Relay offline transport error for "${to}"${reSuffix}${errorSuffix}; ` +
247
+ `reported as timeout without waiting for the ACK deadline.`;
248
+ }
249
+ return `No ACK from "${to}" within ${ACK_TIMEOUT_MS}ms${reSuffix} — transport error. Investigate or retry later.`;
250
+ }
251
+ }
252
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/session/tools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Minimal wizard for `/remote-pi join` with no argument.
3
+ *
4
+ * The Pi SDK `ExtensionUIContext.select` accepts a title and string options
5
+ * and returns the chosen option. Wizard offers existing sessions + an "explicit
6
+ * create" sentinel. If user picks the sentinel, prompts for a fresh name via
7
+ * `select`-with-option-other (Pi's "Other" escape hatch). Returns the picked
8
+ * session name, or null if user cancelled.
9
+ */
10
+ export interface WizardUI {
11
+ select(title: string, options: string[]): Promise<string | undefined>;
12
+ }
13
+ export declare function joinWizard(ui: WizardUI, defaultName: string): Promise<string | null>;
@@ -0,0 +1,20 @@
1
+ import { listSessions, sessionHasSock } from "./global_config.js";
2
+ const CREATE_SENTINEL = "━━━ Create new session ━━━";
3
+ export async function joinWizard(ui, defaultName) {
4
+ const sessions = listSessions();
5
+ const liveSessions = sessions.filter(sessionHasSock);
6
+ const options = [...liveSessions, CREATE_SENTINEL];
7
+ const picked = await ui.select(liveSessions.length
8
+ ? "Choose a session to join, or create a new one"
9
+ : "No active sessions. Create one?", options);
10
+ if (!picked)
11
+ return null;
12
+ if (picked === CREATE_SENTINEL) {
13
+ // Caller is expected to follow up with a name prompt via ctx.ui.select
14
+ // (the Pi SDK input dialog). For non-interactive contexts we return the
15
+ // default name.
16
+ return defaultName;
17
+ }
18
+ return picked;
19
+ }
20
+ //# sourceMappingURL=wizard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wizard.js","sourceRoot":"","sources":["../../src/session/wizard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAelE,MAAM,eAAe,GAAG,4BAA4B,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,EAAY,EACZ,WAAmB;IAEnB,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,CAAC,GAAG,YAAY,EAAE,eAAe,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,MAAM,CAC5B,YAAY,CAAC,MAAM;QACjB,CAAC,CAAC,+CAA+C;QACjD,CAAC,CAAC,iCAAiC,EACrC,OAAO,CACR,CAAC;IACF,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,MAAM,KAAK,eAAe,EAAE,CAAC;QAC/B,uEAAuE;QACvE,wEAAwE;QACxE,gBAAgB;QAChB,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { ClientMessage, ServerMessage } from "../protocol/types.js";
2
+ import type { RelayClient } from "./relay_client.js";
3
+ /** Sink for ServerMessage outbound to the remote app. */
4
+ export interface PeerChannel {
5
+ send(msg: ServerMessage): void;
6
+ }
7
+ /**
8
+ * Plaintext PeerChannel backed by a RelayClient WebSocket.
9
+ *
10
+ * Usage (after pair_request handshake completes):
11
+ * const channel = new PlainPeerChannel(relay, appPeerId, myRoomId, onMsg)
12
+ * channel.send(serverMessage) // base64-encodes JSON, routes via relay
13
+ * // incoming relay messages destined for appPeerId are auto-decoded
14
+ * // and delivered via onMessage callback
15
+ *
16
+ * `myRoomId` is the *local* Pi's room id — sent on every outbound envelope
17
+ * so the app can correlate which Pi sent it (multi-pi support, plano 17).
18
+ */
19
+ export declare class PlainPeerChannel implements PeerChannel {
20
+ private readonly relay;
21
+ private readonly remotePeerId;
22
+ private readonly onMessage;
23
+ private readonly _unsubscribe;
24
+ constructor(relay: RelayClient, remotePeerId: string,
25
+ /**
26
+ * This Pi's room id. Currently NOT injected in the outer envelope
27
+ * (defensive — relay/app not yet ready). Kept in the constructor for
28
+ * forward-compat so callers don't need to change again when we re-enable.
29
+ */
30
+ myRoomId: string | undefined, onMessage: (msg: ClientMessage) => void,
31
+ /** Called when this specific peer connection is considered lost. */
32
+ _onDisconnect?: () => void);
33
+ send(msg: ServerMessage): void;
34
+ /** Detaches from relay (does not close the relay itself). */
35
+ detach(): void;
36
+ private _onLine;
37
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Plaintext PeerChannel backed by a RelayClient WebSocket.
3
+ *
4
+ * Usage (after pair_request handshake completes):
5
+ * const channel = new PlainPeerChannel(relay, appPeerId, myRoomId, onMsg)
6
+ * channel.send(serverMessage) // base64-encodes JSON, routes via relay
7
+ * // incoming relay messages destined for appPeerId are auto-decoded
8
+ * // and delivered via onMessage callback
9
+ *
10
+ * `myRoomId` is the *local* Pi's room id — sent on every outbound envelope
11
+ * so the app can correlate which Pi sent it (multi-pi support, plano 17).
12
+ */
13
+ export class PlainPeerChannel {
14
+ relay;
15
+ remotePeerId;
16
+ onMessage;
17
+ _unsubscribe;
18
+ constructor(relay, remotePeerId,
19
+ /**
20
+ * This Pi's room id. Currently NOT injected in the outer envelope
21
+ * (defensive — relay/app not yet ready). Kept in the constructor for
22
+ * forward-compat so callers don't need to change again when we re-enable.
23
+ */
24
+ myRoomId, onMessage,
25
+ /** Called when this specific peer connection is considered lost. */
26
+ _onDisconnect) {
27
+ this.relay = relay;
28
+ this.remotePeerId = remotePeerId;
29
+ this.onMessage = onMessage;
30
+ const listener = (line) => this._onLine(line);
31
+ relay.on("message", listener);
32
+ this._unsubscribe = () => relay.off("message", listener);
33
+ void _onDisconnect;
34
+ void myRoomId; // intentionally unused — see send() comment
35
+ }
36
+ // ── PeerChannel interface ──────────────────────────────────────────────────
37
+ send(msg) {
38
+ const ct = Buffer.from(JSON.stringify(msg)).toString("base64");
39
+ // NOTE: `room` removed from the outer envelope until relay (W1.A) + app
40
+ // (W1.C) accept the field. Multi-Pi multiplexing already works via
41
+ // `room_id`/`room_meta` in the WS-level `hello` — outer routing stays by
42
+ // `peer` alone. Re-add the field once downstream is ready.
43
+ const outer = { peer: this.remotePeerId, ct };
44
+ // Best-effort delivery. The relay WS can be mid-reconnect (idle/NAT drop, or
45
+ // a session_new/session-replacement teardown) when we push a server→app frame
46
+ // — notably the action_ok/action_error ack a handler emits right after
47
+ // newSession. `relay.send` throws "relay: not connected" in that window; since
48
+ // this runs inside an async SDK event callback, letting it propagate becomes an
49
+ // uncaughtException that kills the whole pi process. The relay auto-reconnects
50
+ // and the app re-syncs via session_sync, so a dropped frame is recoverable — a
51
+ // crash is not. Mirrors RelayClient.sendControl's no-op-when-closed policy.
52
+ try {
53
+ this.relay.send(JSON.stringify(outer));
54
+ }
55
+ catch {
56
+ /* relay down — drop this frame; reconnect + session_sync will recover */
57
+ }
58
+ }
59
+ /** Detaches from relay (does not close the relay itself). */
60
+ detach() {
61
+ this._unsubscribe();
62
+ }
63
+ // ── Incoming line from relay ────────────────────────────────────────────────
64
+ _onLine(line) {
65
+ let outer;
66
+ try {
67
+ outer = JSON.parse(line);
68
+ }
69
+ catch {
70
+ return; // malformed line
71
+ }
72
+ if (outer.peer !== this.remotePeerId)
73
+ return;
74
+ if (!outer.ct)
75
+ return;
76
+ let plaintext;
77
+ try {
78
+ plaintext = Buffer.from(outer.ct, "base64").toString("utf8");
79
+ }
80
+ catch {
81
+ return;
82
+ }
83
+ let msg;
84
+ try {
85
+ msg = JSON.parse(plaintext);
86
+ }
87
+ catch {
88
+ return;
89
+ }
90
+ if (!msg ||
91
+ typeof msg !== "object" ||
92
+ typeof msg.type !== "string") {
93
+ return;
94
+ }
95
+ this.onMessage(msg);
96
+ }
97
+ }
98
+ //# sourceMappingURL=peer_channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer_channel.js","sourceRoot":"","sources":["../../src/transport/peer_channel.ts"],"names":[],"mappings":"AAyBA;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,gBAAgB;IAIR;IACA;IAOA;IAXF,YAAY,CAAa;IAE1C,YACmB,KAAkB,EAClB,YAAoB;IACrC;;;;OAIG;IACH,QAA4B,EACX,SAAuC;IACxD,oEAAoE;IACpE,aAA0B;QAVT,UAAK,GAAL,KAAK,CAAa;QAClB,iBAAY,GAAZ,YAAY,CAAQ;QAOpB,cAAS,GAAT,SAAS,CAA8B;QAIxD,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACtD,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAC9B,IAAI,CAAC,YAAY,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QACzD,KAAK,aAAa,CAAC;QACnB,KAAK,QAAQ,CAAC,CAAE,4CAA4C;IAC9D,CAAC;IAED,8EAA8E;IAE9E,IAAI,CAAC,GAAkB;QACrB,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC/D,wEAAwE;QACxE,mEAAmE;QACnE,yEAAyE;QACzE,2DAA2D;QAC3D,MAAM,KAAK,GAAkB,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,EAAE,EAAE,CAAC;QAC7D,6EAA6E;QAC7E,8EAA8E;QAC9E,uEAAuE;QACvE,+EAA+E;QAC/E,gFAAgF;QAChF,+EAA+E;QAC/E,+EAA+E;QAC/E,4EAA4E;QAC5E,IAAI,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QACzC,CAAC;QAAC,MAAM,CAAC;YACP,yEAAyE;QAC3E,CAAC;IACH,CAAC;IAED,6DAA6D;IAC7D,MAAM;QACJ,IAAI,CAAC,YAAY,EAAE,CAAC;IACtB,CAAC;IAED,+EAA+E;IAEvE,OAAO,CAAC,IAAY;QAC1B,IAAI,KAAoB,CAAC;QACzB,IAAI,CAAC;YACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAkB,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,iBAAiB;QAC3B,CAAC;QAED,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,YAAY;YAAE,OAAO;QAC7C,IAAI,CAAC,KAAK,CAAC,EAAE;YAAE,OAAO;QAEtB,IAAI,SAAiB,CAAC;QACtB,IAAI,CAAC;YACH,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC/D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QAED,IAAI,GAAY,CAAC;QACjB,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QAED,IACE,CAAC,GAAG;YACJ,OAAO,GAAG,KAAK,QAAQ;YACvB,OAAQ,GAA+B,CAAC,IAAI,KAAK,QAAQ,EACzD,CAAC;YACD,OAAO;QACT,CAAC;QAED,IAAI,CAAC,SAAS,CAAC,GAAoB,CAAC,CAAC;IACvC,CAAC;CACF"}