@elizaos/plugin-phone 2.0.3-beta.2 → 2.0.3-beta.3

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 (123) hide show
  1. package/dist/companion/components/Chat.d.ts +14 -0
  2. package/dist/companion/components/Chat.d.ts.map +1 -0
  3. package/dist/companion/components/Chat.js +195 -0
  4. package/dist/companion/components/Chat.js.map +1 -0
  5. package/dist/companion/components/Pairing.d.ts +9 -0
  6. package/dist/companion/components/Pairing.d.ts.map +1 -0
  7. package/dist/companion/components/Pairing.js +175 -0
  8. package/dist/companion/components/Pairing.js.map +1 -0
  9. package/dist/companion/components/PhoneCompanionApp.d.ts +9 -0
  10. package/dist/companion/components/PhoneCompanionApp.d.ts.map +1 -0
  11. package/dist/companion/components/PhoneCompanionApp.js +174 -0
  12. package/dist/companion/components/PhoneCompanionApp.js.map +1 -0
  13. package/dist/companion/components/RemoteSession.d.ts +9 -0
  14. package/dist/companion/components/RemoteSession.d.ts.map +1 -0
  15. package/dist/companion/components/RemoteSession.js +382 -0
  16. package/dist/companion/components/RemoteSession.js.map +1 -0
  17. package/dist/companion/components/index.d.ts +5 -0
  18. package/dist/companion/components/index.d.ts.map +1 -0
  19. package/dist/companion/components/index.js +11 -0
  20. package/dist/companion/components/index.js.map +1 -0
  21. package/dist/companion/index.d.ts +14 -0
  22. package/dist/companion/index.d.ts.map +1 -0
  23. package/dist/companion/index.js +12 -0
  24. package/dist/companion/index.js.map +1 -0
  25. package/dist/companion/services/eliza-intent.d.ts +63 -0
  26. package/dist/companion/services/eliza-intent.d.ts.map +1 -0
  27. package/dist/companion/services/eliza-intent.js +52 -0
  28. package/dist/companion/services/eliza-intent.js.map +1 -0
  29. package/dist/companion/services/env.d.ts +11 -0
  30. package/dist/companion/services/env.d.ts.map +1 -0
  31. package/dist/companion/services/env.js +20 -0
  32. package/dist/companion/services/env.js.map +1 -0
  33. package/dist/companion/services/index.d.ts +14 -0
  34. package/dist/companion/services/index.d.ts.map +1 -0
  35. package/dist/companion/services/index.js +29 -0
  36. package/dist/companion/services/index.js.map +1 -0
  37. package/dist/companion/services/intent-bridge.d.ts +11 -0
  38. package/dist/companion/services/intent-bridge.d.ts.map +1 -0
  39. package/dist/companion/services/intent-bridge.js +15 -0
  40. package/dist/companion/services/intent-bridge.js.map +1 -0
  41. package/dist/companion/services/logger.d.ts +14 -0
  42. package/dist/companion/services/logger.d.ts.map +1 -0
  43. package/dist/companion/services/logger.js +50 -0
  44. package/dist/companion/services/logger.js.map +1 -0
  45. package/dist/companion/services/navigation.d.ts +17 -0
  46. package/dist/companion/services/navigation.d.ts.map +1 -0
  47. package/dist/companion/services/navigation.js +104 -0
  48. package/dist/companion/services/navigation.js.map +1 -0
  49. package/dist/companion/services/push.d.ts +27 -0
  50. package/dist/companion/services/push.d.ts.map +1 -0
  51. package/dist/companion/services/push.js +101 -0
  52. package/dist/companion/services/push.js.map +1 -0
  53. package/dist/companion/services/session-client.d.ts +114 -0
  54. package/dist/companion/services/session-client.d.ts.map +1 -0
  55. package/dist/companion/services/session-client.js +197 -0
  56. package/dist/companion/services/session-client.js.map +1 -0
  57. package/dist/components/PhoneAppView.d.ts +18 -0
  58. package/dist/components/PhoneAppView.d.ts.map +1 -0
  59. package/dist/components/PhoneAppView.helpers.d.ts +12 -0
  60. package/dist/components/PhoneAppView.helpers.d.ts.map +1 -0
  61. package/dist/components/PhoneAppView.helpers.js +44 -0
  62. package/dist/components/PhoneAppView.helpers.js.map +1 -0
  63. package/dist/components/PhoneAppView.interact.d.ts +2 -0
  64. package/dist/components/PhoneAppView.interact.d.ts.map +1 -0
  65. package/dist/components/PhoneAppView.interact.js +63 -0
  66. package/dist/components/PhoneAppView.interact.js.map +1 -0
  67. package/dist/components/PhoneAppView.js +566 -0
  68. package/dist/components/PhoneAppView.js.map +1 -0
  69. package/dist/components/PhoneSpatialView.d.ts +43 -0
  70. package/dist/components/PhoneSpatialView.d.ts.map +1 -0
  71. package/dist/components/PhoneSpatialView.js +180 -0
  72. package/dist/components/PhoneSpatialView.js.map +1 -0
  73. package/dist/components/PhoneView.d.ts +13 -0
  74. package/dist/components/PhoneView.d.ts.map +1 -0
  75. package/dist/components/PhoneView.js +161 -0
  76. package/dist/components/PhoneView.js.map +1 -0
  77. package/dist/components/phone-app.d.ts +13 -0
  78. package/dist/components/phone-app.d.ts.map +1 -0
  79. package/dist/components/phone-app.js +20 -0
  80. package/dist/components/phone-app.js.map +1 -0
  81. package/dist/components/phone-view-bundle.d.ts +3 -0
  82. package/dist/components/phone-view-bundle.d.ts.map +1 -0
  83. package/dist/components/phone-view-bundle.js +7 -0
  84. package/dist/components/phone-view-bundle.js.map +1 -0
  85. package/dist/index.d.ts +25 -0
  86. package/dist/index.d.ts.map +1 -0
  87. package/dist/index.js +28 -0
  88. package/dist/index.js.map +1 -0
  89. package/dist/plugin.d.ts +17 -0
  90. package/dist/plugin.d.ts.map +1 -0
  91. package/dist/plugin.js +48 -0
  92. package/dist/plugin.js.map +1 -0
  93. package/dist/providers/call-log.d.ts +11 -0
  94. package/dist/providers/call-log.d.ts.map +1 -0
  95. package/dist/providers/call-log.js +66 -0
  96. package/dist/providers/call-log.js.map +1 -0
  97. package/dist/register-companion-page.d.ts +13 -0
  98. package/dist/register-companion-page.d.ts.map +1 -0
  99. package/dist/register-companion-page.js +12 -0
  100. package/dist/register-companion-page.js.map +1 -0
  101. package/dist/register-terminal-view.d.ts +15 -0
  102. package/dist/register-terminal-view.d.ts.map +1 -0
  103. package/dist/register-terminal-view.js +21 -0
  104. package/dist/register-terminal-view.js.map +1 -0
  105. package/dist/register.d.ts +9 -0
  106. package/dist/register.d.ts.map +1 -0
  107. package/dist/register.js +11 -0
  108. package/dist/register.js.map +1 -0
  109. package/dist/twilio.d.ts +33 -0
  110. package/dist/twilio.d.ts.map +1 -0
  111. package/dist/twilio.js +238 -0
  112. package/dist/twilio.js.map +1 -0
  113. package/dist/ui.d.ts +8 -0
  114. package/dist/ui.d.ts.map +1 -0
  115. package/dist/ui.js +23 -0
  116. package/dist/ui.js.map +1 -0
  117. package/dist/views/bundle.js +407 -0
  118. package/dist/views/bundle.js.map +1 -0
  119. package/dist/views/dist-Cd2YtKy4.js +270 -0
  120. package/dist/views/dist-Cd2YtKy4.js.map +1 -0
  121. package/dist/views/web-TGRkTsa8.js +58 -0
  122. package/dist/views/web-TGRkTsa8.js.map +1 -0
  123. package/package.json +9 -8
@@ -0,0 +1,20 @@
1
+ function readEnv() {
2
+ const meta = import.meta;
3
+ return meta.env ?? {};
4
+ }
5
+ function agentUrl() {
6
+ const raw = readEnv().VITE_ELIZA_AGENT_URL?.trim();
7
+ return raw && raw.length > 0 ? raw : null;
8
+ }
9
+ function apnsEnabled() {
10
+ return readEnv().VITE_ELIZA_APNS_ENABLED === "1";
11
+ }
12
+ function isDev() {
13
+ return readEnv().MODE !== "production";
14
+ }
15
+ export {
16
+ agentUrl,
17
+ apnsEnabled,
18
+ isDev
19
+ };
20
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/companion/services/env.ts"],"sourcesContent":["/**\n * Runtime environment for the phone-companion views.\n *\n * Values come from Vite's `import.meta.env`. Missing or empty configuration is\n * represented explicitly: `agentUrl()` returns `null` and `apnsEnabled()`\n * returns `false` rather than throwing, so callers can branch without try/catch.\n */\n\ninterface ViteEnv {\n VITE_ELIZA_AGENT_URL?: string;\n VITE_ELIZA_APNS_ENABLED?: string;\n VITE_ELIZA_LOG_LEVEL?: string;\n MODE?: string;\n}\n\nfunction readEnv(): ViteEnv {\n const meta = import.meta as { env?: ViteEnv };\n return meta.env ?? {};\n}\n\nexport function agentUrl(): string | null {\n const raw = readEnv().VITE_ELIZA_AGENT_URL?.trim();\n return raw && raw.length > 0 ? raw : null;\n}\n\nexport function apnsEnabled(): boolean {\n return readEnv().VITE_ELIZA_APNS_ENABLED === \"1\";\n}\n\nexport function isDev(): boolean {\n return readEnv().MODE !== \"production\";\n}\n"],"mappings":"AAeA,SAAS,UAAmB;AAC1B,QAAM,OAAO;AACb,SAAO,KAAK,OAAO,CAAC;AACtB;AAEO,SAAS,WAA0B;AACxC,QAAM,MAAM,QAAQ,EAAE,sBAAsB,KAAK;AACjD,SAAO,OAAO,IAAI,SAAS,IAAI,MAAM;AACvC;AAEO,SAAS,cAAuB;AACrC,SAAO,QAAQ,EAAE,4BAA4B;AAC/C;AAEO,SAAS,QAAiB;AAC/B,SAAO,QAAQ,EAAE,SAAS;AAC5B;","names":[]}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Phone-companion surface — the iOS-paired-handset experience.
3
+ *
4
+ * Originally shipped as a standalone top-level app Capacitor project;
5
+ * folded into the main Eliza iOS bundle so one binary handles both the full
6
+ * Eliza UI and the pairing / chat-mirror / remote-session flow.
7
+ */
8
+ export { ElizaIntent, type ElizaIntentPlugin, ElizaIntentWeb, type PairingStatus, type ReceiveIntentPayload, type ReceiveIntentResult, type ScheduleAlarmOptions, type ScheduleAlarmResult, type SetPairingStatusOptions, } from "./eliza-intent";
9
+ export { agentUrl, apnsEnabled, isDev } from "./env";
10
+ export { logger } from "./logger";
11
+ export { type NavState, useNavigation, type ViewName } from "./navigation";
12
+ export { type PushIntent, type RegisterPushHandle, type RegisterPushOptions, registerPush, type SessionStartIntent, } from "./push";
13
+ export { decodePairingPayload, type InputButton, type InputEvent, type PairingPayload, SessionClient, type SessionState, type TouchGesture, type TouchSample, type TouchToInputOptions, touchToInput, } from "./session-client";
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/companion/services/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,WAAW,EACX,KAAK,iBAAiB,EACtB,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,GAC7B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,KAAK,QAAQ,EAAE,aAAa,EAAE,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC3E,OAAO,EACL,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,YAAY,EACZ,KAAK,kBAAkB,GACxB,MAAM,QAAQ,CAAC;AAChB,OAAO,EACL,oBAAoB,EACpB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,aAAa,EACb,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,YAAY,GACb,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,29 @@
1
+ import {
2
+ ElizaIntent,
3
+ ElizaIntentWeb
4
+ } from "./eliza-intent.js";
5
+ import { agentUrl, apnsEnabled, isDev } from "./env.js";
6
+ import { logger } from "./logger.js";
7
+ import { useNavigation } from "./navigation.js";
8
+ import {
9
+ registerPush
10
+ } from "./push.js";
11
+ import {
12
+ decodePairingPayload,
13
+ SessionClient,
14
+ touchToInput
15
+ } from "./session-client.js";
16
+ export {
17
+ ElizaIntent,
18
+ ElizaIntentWeb,
19
+ SessionClient,
20
+ agentUrl,
21
+ apnsEnabled,
22
+ decodePairingPayload,
23
+ isDev,
24
+ logger,
25
+ registerPush,
26
+ touchToInput,
27
+ useNavigation
28
+ };
29
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/companion/services/index.ts"],"sourcesContent":["/**\n * Phone-companion surface — the iOS-paired-handset experience.\n *\n * Originally shipped as a standalone top-level app Capacitor project;\n * folded into the main Eliza iOS bundle so one binary handles both the full\n * Eliza UI and the pairing / chat-mirror / remote-session flow.\n */\n\nexport {\n ElizaIntent,\n type ElizaIntentPlugin,\n ElizaIntentWeb,\n type PairingStatus,\n type ReceiveIntentPayload,\n type ReceiveIntentResult,\n type ScheduleAlarmOptions,\n type ScheduleAlarmResult,\n type SetPairingStatusOptions,\n} from \"./eliza-intent.js\";\nexport { agentUrl, apnsEnabled, isDev } from \"./env.js\";\nexport { logger } from \"./logger.js\";\nexport { type NavState, useNavigation, type ViewName } from \"./navigation.js\";\nexport {\n type PushIntent,\n type RegisterPushHandle,\n type RegisterPushOptions,\n registerPush,\n type SessionStartIntent,\n} from \"./push.js\";\nexport {\n decodePairingPayload,\n type InputButton,\n type InputEvent,\n type PairingPayload,\n SessionClient,\n type SessionState,\n type TouchGesture,\n type TouchSample,\n type TouchToInputOptions,\n touchToInput,\n} from \"./session-client.js\";\n"],"mappings":"AAQA;AAAA,EACE;AAAA,EAEA;AAAA,OAOK;AACP,SAAS,UAAU,aAAa,aAAa;AAC7C,SAAS,cAAc;AACvB,SAAwB,qBAAoC;AAC5D;AAAA,EAIE;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EAIA;AAAA,EAKA;AAAA,OACK;","names":[]}
@@ -0,0 +1,11 @@
1
+ import { type ReceiveIntentPayload, type ReceiveIntentResult } from "./eliza-intent";
2
+ /**
3
+ * Intent bridge — single entry point used by UI layers to forward an
4
+ * agent-issued device-bus intent (see plan §6.24) to the native plugin.
5
+ *
6
+ * This is a thin wrapper, not an abstraction layer. It exists so that
7
+ * when push payloads land (T9c) there is one place to attach decoding +
8
+ * authentication rather than scattering `ElizaIntent.receiveIntent` calls.
9
+ */
10
+ export declare function forwardIntent(payload: ReceiveIntentPayload): Promise<ReceiveIntentResult>;
11
+ //# sourceMappingURL=intent-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intent-bridge.d.ts","sourceRoot":"","sources":["../../../src/companion/services/intent-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACzB,MAAM,gBAAgB,CAAC;AAGxB;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAM9B"}
@@ -0,0 +1,15 @@
1
+ import {
2
+ ElizaIntent
3
+ } from "./eliza-intent.js";
4
+ import { logger } from "./logger.js";
5
+ async function forwardIntent(payload) {
6
+ logger.debug("[IntentBridge] forward", {
7
+ kind: payload.kind,
8
+ issuedAtIso: payload.issuedAtIso
9
+ });
10
+ return ElizaIntent.receiveIntent(payload);
11
+ }
12
+ export {
13
+ forwardIntent
14
+ };
15
+ //# sourceMappingURL=intent-bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/companion/services/intent-bridge.ts"],"sourcesContent":["import {\n ElizaIntent,\n type ReceiveIntentPayload,\n type ReceiveIntentResult,\n} from \"./eliza-intent.js\";\nimport { logger } from \"./logger.js\";\n\n/**\n * Intent bridge — single entry point used by UI layers to forward an\n * agent-issued device-bus intent (see plan §6.24) to the native plugin.\n *\n * This is a thin wrapper, not an abstraction layer. It exists so that\n * when push payloads land (T9c) there is one place to attach decoding +\n * authentication rather than scattering `ElizaIntent.receiveIntent` calls.\n */\nexport async function forwardIntent(\n payload: ReceiveIntentPayload,\n): Promise<ReceiveIntentResult> {\n logger.debug(\"[IntentBridge] forward\", {\n kind: payload.kind,\n issuedAtIso: payload.issuedAtIso,\n });\n return ElizaIntent.receiveIntent(payload);\n}\n"],"mappings":"AAAA;AAAA,EACE;AAAA,OAGK;AACP,SAAS,cAAc;AAUvB,eAAsB,cACpB,SAC8B;AAC9B,SAAO,MAAM,0BAA0B;AAAA,IACrC,MAAM,QAAQ;AAAA,IACd,aAAa,QAAQ;AAAA,EACvB,CAAC;AACD,SAAO,YAAY,cAAc,OAAO;AAC1C;","names":[]}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Structured client logger for the phone-companion views.
3
+ *
4
+ * Client-side code cannot reach the server logger — this wrapper provides
5
+ * levelled, prefixed output routed through the browser console host.
6
+ * Prefix each call site with `[ClassName]` per repo convention.
7
+ */
8
+ export declare const logger: {
9
+ debug(message: string, context?: Record<string, unknown>): void;
10
+ info(message: string, context?: Record<string, unknown>): void;
11
+ warn(message: string, context?: Record<string, unknown>): void;
12
+ error(message: string, context?: Record<string, unknown>): void;
13
+ };
14
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/companion/services/logger.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgDH,eAAO,MAAM,MAAM;mBACF,MAAM,YAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;kBAGjD,MAAM,YAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;kBAGhD,MAAM,YAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;mBAG/C,MAAM,YAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;CAGhE,CAAC"}
@@ -0,0 +1,50 @@
1
+ const LEVEL_ORDER = {
2
+ debug: 10,
3
+ info: 20,
4
+ warn: 30,
5
+ error: 40
6
+ };
7
+ function envLevel() {
8
+ const raw = typeof import.meta !== "undefined" ? import.meta.env?.VITE_ELIZA_LOG_LEVEL : void 0;
9
+ if (raw === "debug" || raw === "info" || raw === "warn" || raw === "error") {
10
+ return raw;
11
+ }
12
+ return "info";
13
+ }
14
+ const currentLevel = envLevel();
15
+ function emit(level, message, context) {
16
+ if (LEVEL_ORDER[level] < LEVEL_ORDER[currentLevel]) return;
17
+ const line = `[ElizaCompanion] ${message}`;
18
+ const host = globalThis.console;
19
+ if (level === "error") {
20
+ host.error(line, context ?? {});
21
+ return;
22
+ }
23
+ if (level === "warn") {
24
+ host.warn(line, context ?? {});
25
+ return;
26
+ }
27
+ if (level === "info") {
28
+ host.info(line, context ?? {});
29
+ return;
30
+ }
31
+ host.debug(line, context ?? {});
32
+ }
33
+ const logger = {
34
+ debug(message, context) {
35
+ emit("debug", message, context);
36
+ },
37
+ info(message, context) {
38
+ emit("info", message, context);
39
+ },
40
+ warn(message, context) {
41
+ emit("warn", message, context);
42
+ },
43
+ error(message, context) {
44
+ emit("error", message, context);
45
+ }
46
+ };
47
+ export {
48
+ logger
49
+ };
50
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/companion/services/logger.ts"],"sourcesContent":["/**\n * Structured client logger for the phone-companion views.\n *\n * Client-side code cannot reach the server logger — this wrapper provides\n * levelled, prefixed output routed through the browser console host.\n * Prefix each call site with `[ClassName]` per repo convention.\n */\n\ntype LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n};\n\nfunction envLevel(): LogLevel {\n const raw =\n typeof import.meta !== \"undefined\"\n ? (import.meta as { env?: Record<string, string> }).env\n ?.VITE_ELIZA_LOG_LEVEL\n : undefined;\n if (raw === \"debug\" || raw === \"info\" || raw === \"warn\" || raw === \"error\") {\n return raw;\n }\n return \"info\";\n}\n\nconst currentLevel = envLevel();\n\nfunction emit(\n level: LogLevel,\n message: string,\n context?: Record<string, unknown>,\n): void {\n if (LEVEL_ORDER[level] < LEVEL_ORDER[currentLevel]) return;\n const line = `[ElizaCompanion] ${message}`;\n const host = globalThis.console;\n if (level === \"error\") {\n host.error(line, context ?? {});\n return;\n }\n if (level === \"warn\") {\n host.warn(line, context ?? {});\n return;\n }\n if (level === \"info\") {\n host.info(line, context ?? {});\n return;\n }\n host.debug(line, context ?? {});\n}\n\nexport const logger = {\n debug(message: string, context?: Record<string, unknown>): void {\n emit(\"debug\", message, context);\n },\n info(message: string, context?: Record<string, unknown>): void {\n emit(\"info\", message, context);\n },\n warn(message: string, context?: Record<string, unknown>): void {\n emit(\"warn\", message, context);\n },\n error(message: string, context?: Record<string, unknown>): void {\n emit(\"error\", message, context);\n },\n};\n"],"mappings":"AAUA,MAAM,cAAwC;AAAA,EAC5C,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AAEA,SAAS,WAAqB;AAC5B,QAAM,MACJ,OAAO,gBAAgB,cAClB,YAAiD,KAC9C,uBACJ;AACN,MAAI,QAAQ,WAAW,QAAQ,UAAU,QAAQ,UAAU,QAAQ,SAAS;AAC1E,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,MAAM,eAAe,SAAS;AAE9B,SAAS,KACP,OACA,SACA,SACM;AACN,MAAI,YAAY,KAAK,IAAI,YAAY,YAAY,EAAG;AACpD,QAAM,OAAO,oBAAoB,OAAO;AACxC,QAAM,OAAO,WAAW;AACxB,MAAI,UAAU,SAAS;AACrB,SAAK,MAAM,MAAM,WAAW,CAAC,CAAC;AAC9B;AAAA,EACF;AACA,MAAI,UAAU,QAAQ;AACpB,SAAK,KAAK,MAAM,WAAW,CAAC,CAAC;AAC7B;AAAA,EACF;AACA,MAAI,UAAU,QAAQ;AACpB,SAAK,KAAK,MAAM,WAAW,CAAC,CAAC;AAC7B;AAAA,EACF;AACA,OAAK,MAAM,MAAM,WAAW,CAAC,CAAC;AAChC;AAEO,MAAM,SAAS;AAAA,EACpB,MAAM,SAAiB,SAAyC;AAC9D,SAAK,SAAS,SAAS,OAAO;AAAA,EAChC;AAAA,EACA,KAAK,SAAiB,SAAyC;AAC7D,SAAK,QAAQ,SAAS,OAAO;AAAA,EAC/B;AAAA,EACA,KAAK,SAAiB,SAAyC;AAC7D,SAAK,QAAQ,SAAS,OAAO;AAAA,EAC/B;AAAA,EACA,MAAM,SAAiB,SAAyC;AAC9D,SAAK,SAAS,SAAS,OAAO;AAAA,EAChC;AACF;","names":[]}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Minimal stack navigator for the phone-companion surface.
3
+ *
4
+ * We intentionally avoid react-router / react-native-navigation: the companion
5
+ * has three screens and a linear push/pop model. This hook handles state,
6
+ * persists the current view across launches, and fires a Capacitor haptic on
7
+ * each transition when native haptics are available.
8
+ */
9
+ export type ViewName = "chat" | "pairing" | "remote-session";
10
+ export interface NavState {
11
+ view: ViewName;
12
+ ready: boolean;
13
+ push(next: ViewName): void;
14
+ pop(fallback: ViewName): void;
15
+ }
16
+ export declare function useNavigation(): NavState;
17
+ //# sourceMappingURL=navigation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../../src/companion/services/navigation.ts"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AAEH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,gBAAgB,CAAC;AAK7D,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3B,GAAG,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC/B;AAED,wBAAgB,aAAa,IAAI,QAAQ,CAmExC"}
@@ -0,0 +1,104 @@
1
+ import { Capacitor } from "@capacitor/core";
2
+ import { Haptics, ImpactStyle } from "@capacitor/haptics";
3
+ import { Preferences } from "@capacitor/preferences";
4
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
5
+ import { logger } from "./logger.js";
6
+ const STORAGE_KEY = "eliza.companion.nav.v1";
7
+ const DEFAULT_VIEW = "chat";
8
+ function useNavigation() {
9
+ const [view, setView] = useState(DEFAULT_VIEW);
10
+ const [stack, setStack] = useState([DEFAULT_VIEW]);
11
+ const [ready, setReady] = useState(false);
12
+ const stackRef = useRef(stack);
13
+ stackRef.current = stack;
14
+ useEffect(() => {
15
+ void (async () => {
16
+ try {
17
+ const result = await Preferences.get({ key: STORAGE_KEY });
18
+ if (result.value !== null) {
19
+ const parsed = parseStack(result.value);
20
+ if (parsed.length > 0) {
21
+ setStack(parsed);
22
+ setView(parsed[parsed.length - 1]);
23
+ }
24
+ }
25
+ } catch (err) {
26
+ logger.warn(
27
+ "[navigation] failed to restore navigation from preferences",
28
+ {
29
+ error: err instanceof Error ? err.message : String(err)
30
+ }
31
+ );
32
+ setStack([DEFAULT_VIEW]);
33
+ setView(DEFAULT_VIEW);
34
+ } finally {
35
+ setReady(true);
36
+ }
37
+ })();
38
+ }, []);
39
+ useEffect(() => {
40
+ if (!ready) return;
41
+ const serialized = JSON.stringify(stack);
42
+ Preferences.set({ key: STORAGE_KEY, value: serialized });
43
+ }, [stack, ready]);
44
+ const push = useCallback((next) => {
45
+ logger.info("[navigation] push", { next });
46
+ triggerHaptic();
47
+ setStack(
48
+ (current) => current[current.length - 1] === next ? current : [...current, next]
49
+ );
50
+ setView(next);
51
+ }, []);
52
+ const pop = useCallback((fallback) => {
53
+ logger.info("[navigation] pop", { fallback });
54
+ triggerHaptic();
55
+ const current = stackRef.current;
56
+ let nextStack;
57
+ let nextView;
58
+ if (current.length <= 1) {
59
+ nextStack = [fallback];
60
+ nextView = fallback;
61
+ } else {
62
+ nextStack = current.slice(0, -1);
63
+ nextView = nextStack[nextStack.length - 1];
64
+ }
65
+ stackRef.current = nextStack;
66
+ setStack(nextStack);
67
+ setView(nextView);
68
+ }, []);
69
+ return useMemo(() => ({ view, ready, push, pop }), [view, ready, push, pop]);
70
+ }
71
+ let hasLoggedInvalidStoredStack = false;
72
+ function parseStack(raw) {
73
+ try {
74
+ const parsed = JSON.parse(raw);
75
+ if (!Array.isArray(parsed)) return [];
76
+ return parsed.filter(isViewName);
77
+ } catch (err) {
78
+ if (!hasLoggedInvalidStoredStack) {
79
+ hasLoggedInvalidStoredStack = true;
80
+ logger.info(
81
+ "[navigation] invalid persisted stack; falling back to default view",
82
+ {
83
+ error: err instanceof Error ? err.message : String(err)
84
+ }
85
+ );
86
+ }
87
+ return [];
88
+ }
89
+ }
90
+ function isViewName(value) {
91
+ return value === "chat" || value === "pairing" || value === "remote-session";
92
+ }
93
+ function triggerHaptic() {
94
+ if (!Capacitor.isNativePlatform()) return;
95
+ Haptics.impact({ style: ImpactStyle.Light }).catch((err) => {
96
+ logger.debug("[navigation] haptic unavailable", {
97
+ error: err instanceof Error ? err.message : String(err)
98
+ });
99
+ });
100
+ }
101
+ export {
102
+ useNavigation
103
+ };
104
+ //# sourceMappingURL=navigation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/companion/services/navigation.ts"],"sourcesContent":["import { Capacitor } from \"@capacitor/core\";\nimport { Haptics, ImpactStyle } from \"@capacitor/haptics\";\nimport { Preferences } from \"@capacitor/preferences\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { logger } from \"./logger.js\";\n\n/**\n * Minimal stack navigator for the phone-companion surface.\n *\n * We intentionally avoid react-router / react-native-navigation: the companion\n * has three screens and a linear push/pop model. This hook handles state,\n * persists the current view across launches, and fires a Capacitor haptic on\n * each transition when native haptics are available.\n */\n\nexport type ViewName = \"chat\" | \"pairing\" | \"remote-session\";\n\nconst STORAGE_KEY = \"eliza.companion.nav.v1\";\nconst DEFAULT_VIEW: ViewName = \"chat\";\n\nexport interface NavState {\n view: ViewName;\n ready: boolean;\n push(next: ViewName): void;\n pop(fallback: ViewName): void;\n}\n\nexport function useNavigation(): NavState {\n const [view, setView] = useState<ViewName>(DEFAULT_VIEW);\n const [stack, setStack] = useState<ViewName[]>([DEFAULT_VIEW]);\n const [ready, setReady] = useState(false);\n const stackRef = useRef(stack);\n stackRef.current = stack;\n\n useEffect(() => {\n void (async () => {\n try {\n const result = await Preferences.get({ key: STORAGE_KEY });\n if (result.value !== null) {\n const parsed = parseStack(result.value);\n if (parsed.length > 0) {\n setStack(parsed);\n setView(parsed[parsed.length - 1]);\n }\n }\n } catch (err) {\n logger.warn(\n \"[navigation] failed to restore navigation from preferences\",\n {\n error: err instanceof Error ? err.message : String(err),\n },\n );\n setStack([DEFAULT_VIEW]);\n setView(DEFAULT_VIEW);\n } finally {\n setReady(true);\n }\n })();\n }, []);\n\n useEffect(() => {\n if (!ready) return;\n const serialized = JSON.stringify(stack);\n Preferences.set({ key: STORAGE_KEY, value: serialized });\n }, [stack, ready]);\n\n const push = useCallback((next: ViewName) => {\n logger.info(\"[navigation] push\", { next });\n triggerHaptic();\n setStack((current) =>\n current[current.length - 1] === next ? current : [...current, next],\n );\n setView(next);\n }, []);\n\n const pop = useCallback((fallback: ViewName) => {\n logger.info(\"[navigation] pop\", { fallback });\n triggerHaptic();\n const current = stackRef.current;\n let nextStack: ViewName[];\n let nextView: ViewName;\n if (current.length <= 1) {\n nextStack = [fallback];\n nextView = fallback;\n } else {\n nextStack = current.slice(0, -1);\n nextView = nextStack[nextStack.length - 1];\n }\n stackRef.current = nextStack;\n setStack(nextStack);\n setView(nextView);\n }, []);\n\n return useMemo(() => ({ view, ready, push, pop }), [view, ready, push, pop]);\n}\n\nlet hasLoggedInvalidStoredStack = false;\n\nfunction parseStack(raw: string): ViewName[] {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) return [];\n return parsed.filter(isViewName);\n } catch (err: unknown) {\n if (!hasLoggedInvalidStoredStack) {\n hasLoggedInvalidStoredStack = true;\n logger.info(\n \"[navigation] invalid persisted stack; falling back to default view\",\n {\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n return [];\n }\n}\n\nfunction isViewName(value: unknown): value is ViewName {\n return value === \"chat\" || value === \"pairing\" || value === \"remote-session\";\n}\n\nfunction triggerHaptic(): void {\n if (!Capacitor.isNativePlatform()) return;\n Haptics.impact({ style: ImpactStyle.Light }).catch((err: unknown) => {\n logger.debug(\"[navigation] haptic unavailable\", {\n error: err instanceof Error ? err.message : String(err),\n });\n });\n}\n"],"mappings":"AAAA,SAAS,iBAAiB;AAC1B,SAAS,SAAS,mBAAmB;AACrC,SAAS,mBAAmB;AAC5B,SAAS,aAAa,WAAW,SAAS,QAAQ,gBAAgB;AAClE,SAAS,cAAc;AAavB,MAAM,cAAc;AACpB,MAAM,eAAyB;AASxB,SAAS,gBAA0B;AACxC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAmB,YAAY;AACvD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAqB,CAAC,YAAY,CAAC;AAC7D,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,KAAK;AACxC,QAAM,WAAW,OAAO,KAAK;AAC7B,WAAS,UAAU;AAEnB,YAAU,MAAM;AACd,UAAM,YAAY;AAChB,UAAI;AACF,cAAM,SAAS,MAAM,YAAY,IAAI,EAAE,KAAK,YAAY,CAAC;AACzD,YAAI,OAAO,UAAU,MAAM;AACzB,gBAAM,SAAS,WAAW,OAAO,KAAK;AACtC,cAAI,OAAO,SAAS,GAAG;AACrB,qBAAS,MAAM;AACf,oBAAQ,OAAO,OAAO,SAAS,CAAC,CAAC;AAAA,UACnC;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,eAAO;AAAA,UACL;AAAA,UACA;AAAA,YACE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,UACxD;AAAA,QACF;AACA,iBAAS,CAAC,YAAY,CAAC;AACvB,gBAAQ,YAAY;AAAA,MACtB,UAAE;AACA,iBAAS,IAAI;AAAA,MACf;AAAA,IACF,GAAG;AAAA,EACL,GAAG,CAAC,CAAC;AAEL,YAAU,MAAM;AACd,QAAI,CAAC,MAAO;AACZ,UAAM,aAAa,KAAK,UAAU,KAAK;AACvC,gBAAY,IAAI,EAAE,KAAK,aAAa,OAAO,WAAW,CAAC;AAAA,EACzD,GAAG,CAAC,OAAO,KAAK,CAAC;AAEjB,QAAM,OAAO,YAAY,CAAC,SAAmB;AAC3C,WAAO,KAAK,qBAAqB,EAAE,KAAK,CAAC;AACzC,kBAAc;AACd;AAAA,MAAS,CAAC,YACR,QAAQ,QAAQ,SAAS,CAAC,MAAM,OAAO,UAAU,CAAC,GAAG,SAAS,IAAI;AAAA,IACpE;AACA,YAAQ,IAAI;AAAA,EACd,GAAG,CAAC,CAAC;AAEL,QAAM,MAAM,YAAY,CAAC,aAAuB;AAC9C,WAAO,KAAK,oBAAoB,EAAE,SAAS,CAAC;AAC5C,kBAAc;AACd,UAAM,UAAU,SAAS;AACzB,QAAI;AACJ,QAAI;AACJ,QAAI,QAAQ,UAAU,GAAG;AACvB,kBAAY,CAAC,QAAQ;AACrB,iBAAW;AAAA,IACb,OAAO;AACL,kBAAY,QAAQ,MAAM,GAAG,EAAE;AAC/B,iBAAW,UAAU,UAAU,SAAS,CAAC;AAAA,IAC3C;AACA,aAAS,UAAU;AACnB,aAAS,SAAS;AAClB,YAAQ,QAAQ;AAAA,EAClB,GAAG,CAAC,CAAC;AAEL,SAAO,QAAQ,OAAO,EAAE,MAAM,OAAO,MAAM,IAAI,IAAI,CAAC,MAAM,OAAO,MAAM,GAAG,CAAC;AAC7E;AAEA,IAAI,8BAA8B;AAElC,SAAS,WAAW,KAAyB;AAC3C,MAAI;AACF,UAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,QAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO,CAAC;AACpC,WAAO,OAAO,OAAO,UAAU;AAAA,EACjC,SAAS,KAAc;AACrB,QAAI,CAAC,6BAA6B;AAChC,oCAA8B;AAC9B,aAAO;AAAA,QACL;AAAA,QACA;AAAA,UACE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD;AAAA,MACF;AAAA,IACF;AACA,WAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,WAAW,OAAmC;AACrD,SAAO,UAAU,UAAU,UAAU,aAAa,UAAU;AAC9D;AAEA,SAAS,gBAAsB;AAC7B,MAAI,CAAC,UAAU,iBAAiB,EAAG;AACnC,UAAQ,OAAO,EAAE,OAAO,YAAY,MAAM,CAAC,EAAE,MAAM,CAAC,QAAiB;AACnE,WAAO,MAAM,mCAAmC;AAAA,MAC9C,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,IACxD,CAAC;AAAA,EACH,CAAC;AACH;","names":[]}
@@ -0,0 +1,27 @@
1
+ import { type PairingPayload } from "./session-client";
2
+ /**
3
+ * APNs integration.
4
+ *
5
+ * On iOS native: requests notification permission, registers for APNs, and
6
+ * listens for `pushNotificationReceived`. When a `intent.session.start` push
7
+ * arrives, we decode its pairing payload and notify the caller so the app
8
+ * can navigate to the RemoteSession view.
9
+ *
10
+ * On web: unavailable — logged once. This lets `bun run build` + `bun run dev`
11
+ * work in a browser without a simulator.
12
+ */
13
+ export interface SessionStartIntent {
14
+ kind: "session-start";
15
+ payload: PairingPayload;
16
+ }
17
+ export type PushIntent = SessionStartIntent;
18
+ export interface RegisterPushOptions {
19
+ onIntent(intent: PushIntent): void;
20
+ onToken?(deviceToken: string): void;
21
+ onError?(error: Error): void;
22
+ }
23
+ export interface RegisterPushHandle {
24
+ unregister(): Promise<void>;
25
+ }
26
+ export declare function registerPush(options: RegisterPushOptions): Promise<RegisterPushHandle>;
27
+ //# sourceMappingURL=push.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../../src/companion/services/push.ts"],"names":[],"mappings":"AAOA,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAE7E;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,cAAc,CAAC;CACzB;AAED,MAAM,MAAM,UAAU,GAAG,kBAAkB,CAAC;AAE5C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IACnC,OAAO,CAAC,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAKD,wBAAsB,YAAY,CAChC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CA8D7B"}
@@ -0,0 +1,101 @@
1
+ import { Capacitor } from "@capacitor/core";
2
+ import {
3
+ PushNotifications
4
+ } from "@capacitor/push-notifications";
5
+ import { logger } from "./logger.js";
6
+ import { decodePairingPayload } from "./session-client.js";
7
+ const INTENT_KEY = "intent";
8
+ const SESSION_START_INTENT = "session.start";
9
+ async function registerPush(options) {
10
+ if (!Capacitor.isNativePlatform()) {
11
+ logger.info("[push] skipping registration; non-native platform", {
12
+ platform: Capacitor.getPlatform()
13
+ });
14
+ return { unregister: async () => {
15
+ } };
16
+ }
17
+ const permission = await PushNotifications.requestPermissions();
18
+ if (permission.receive !== "granted") {
19
+ const error = new Error(
20
+ `[push] APNs permission not granted: ${permission.receive}`
21
+ );
22
+ logger.warn(error.message, { permission: permission.receive });
23
+ options.onError?.(error);
24
+ return { unregister: async () => {
25
+ } };
26
+ }
27
+ const tokenHandle = await PushNotifications.addListener(
28
+ "registration",
29
+ (token) => {
30
+ logger.info("[push] APNs token received", {
31
+ tokenLength: token.value.length
32
+ });
33
+ options.onToken?.(token.value);
34
+ }
35
+ );
36
+ const errorHandle = await PushNotifications.addListener(
37
+ "registrationError",
38
+ (err) => {
39
+ const error = new Error(`[push] APNs registration failed: ${err.error}`);
40
+ logger.error(error.message, {});
41
+ options.onError?.(error);
42
+ }
43
+ );
44
+ const receiveHandle = await PushNotifications.addListener(
45
+ "pushNotificationReceived",
46
+ (notification) => {
47
+ handleNotification(notification, options);
48
+ }
49
+ );
50
+ const actionHandle = await PushNotifications.addListener(
51
+ "pushNotificationActionPerformed",
52
+ (action) => {
53
+ handleNotification(action.notification, options);
54
+ }
55
+ );
56
+ await PushNotifications.register();
57
+ logger.info("[push] registered for APNs", {});
58
+ return {
59
+ unregister: async () => {
60
+ await tokenHandle.remove();
61
+ await errorHandle.remove();
62
+ await receiveHandle.remove();
63
+ await actionHandle.remove();
64
+ }
65
+ };
66
+ }
67
+ function handleNotification(notification, options) {
68
+ const data = notification.data ?? {};
69
+ const intent = data[INTENT_KEY];
70
+ if (intent !== SESSION_START_INTENT) {
71
+ logger.debug("[push] non-session intent ignored", { intent });
72
+ return;
73
+ }
74
+ const encoded = data.pairing;
75
+ if (typeof encoded !== "string") {
76
+ const error = new Error(
77
+ "[push] session.start missing `pairing` payload field"
78
+ );
79
+ logger.error(error.message, {});
80
+ options.onError?.(error);
81
+ return;
82
+ }
83
+ try {
84
+ const payload = decodePairingPayload(encoded);
85
+ logger.info("[push] session.start intent received", {
86
+ agentId: payload.agentId
87
+ });
88
+ options.onIntent({ kind: "session-start", payload });
89
+ } catch (cause) {
90
+ const error = cause instanceof Error ? cause : new Error("[push] failed to decode session.start pairing payload");
91
+ logger.error("[push] failed to decode session.start pairing payload", {
92
+ error: error.message,
93
+ encodedLength: encoded.length
94
+ });
95
+ options.onError?.(error);
96
+ }
97
+ }
98
+ export {
99
+ registerPush
100
+ };
101
+ //# sourceMappingURL=push.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/companion/services/push.ts"],"sourcesContent":["import { Capacitor } from \"@capacitor/core\";\nimport {\n type PushNotificationSchema,\n PushNotifications,\n type Token,\n} from \"@capacitor/push-notifications\";\nimport { logger } from \"./logger.js\";\nimport { decodePairingPayload, type PairingPayload } from \"./session-client.js\";\n\n/**\n * APNs integration.\n *\n * On iOS native: requests notification permission, registers for APNs, and\n * listens for `pushNotificationReceived`. When a `intent.session.start` push\n * arrives, we decode its pairing payload and notify the caller so the app\n * can navigate to the RemoteSession view.\n *\n * On web: unavailable — logged once. This lets `bun run build` + `bun run dev`\n * work in a browser without a simulator.\n */\n\nexport interface SessionStartIntent {\n kind: \"session-start\";\n payload: PairingPayload;\n}\n\nexport type PushIntent = SessionStartIntent;\n\nexport interface RegisterPushOptions {\n onIntent(intent: PushIntent): void;\n onToken?(deviceToken: string): void;\n onError?(error: Error): void;\n}\n\nexport interface RegisterPushHandle {\n unregister(): Promise<void>;\n}\n\nconst INTENT_KEY = \"intent\";\nconst SESSION_START_INTENT = \"session.start\";\n\nexport async function registerPush(\n options: RegisterPushOptions,\n): Promise<RegisterPushHandle> {\n if (!Capacitor.isNativePlatform()) {\n logger.info(\"[push] skipping registration; non-native platform\", {\n platform: Capacitor.getPlatform(),\n });\n return { unregister: async () => {} };\n }\n\n const permission = await PushNotifications.requestPermissions();\n if (permission.receive !== \"granted\") {\n const error = new Error(\n `[push] APNs permission not granted: ${permission.receive}`,\n );\n logger.warn(error.message, { permission: permission.receive });\n options.onError?.(error);\n return { unregister: async () => {} };\n }\n\n const tokenHandle = await PushNotifications.addListener(\n \"registration\",\n (token: Token) => {\n logger.info(\"[push] APNs token received\", {\n tokenLength: token.value.length,\n });\n options.onToken?.(token.value);\n },\n );\n\n const errorHandle = await PushNotifications.addListener(\n \"registrationError\",\n (err: { error: string }) => {\n const error = new Error(`[push] APNs registration failed: ${err.error}`);\n logger.error(error.message, {});\n options.onError?.(error);\n },\n );\n\n const receiveHandle = await PushNotifications.addListener(\n \"pushNotificationReceived\",\n (notification: PushNotificationSchema) => {\n handleNotification(notification, options);\n },\n );\n\n const actionHandle = await PushNotifications.addListener(\n \"pushNotificationActionPerformed\",\n (action: { notification: PushNotificationSchema }) => {\n handleNotification(action.notification, options);\n },\n );\n\n await PushNotifications.register();\n logger.info(\"[push] registered for APNs\", {});\n\n return {\n unregister: async () => {\n await tokenHandle.remove();\n await errorHandle.remove();\n await receiveHandle.remove();\n await actionHandle.remove();\n },\n };\n}\n\nfunction handleNotification(\n notification: PushNotificationSchema,\n options: RegisterPushOptions,\n): void {\n const data = notification.data ?? {};\n const intent = data[INTENT_KEY];\n if (intent !== SESSION_START_INTENT) {\n logger.debug(\"[push] non-session intent ignored\", { intent });\n return;\n }\n const encoded = data.pairing;\n if (typeof encoded !== \"string\") {\n const error = new Error(\n \"[push] session.start missing `pairing` payload field\",\n );\n logger.error(error.message, {});\n options.onError?.(error);\n return;\n }\n try {\n const payload = decodePairingPayload(encoded);\n logger.info(\"[push] session.start intent received\", {\n agentId: payload.agentId,\n });\n options.onIntent({ kind: \"session-start\", payload });\n } catch (cause) {\n const error =\n cause instanceof Error\n ? cause\n : new Error(\"[push] failed to decode session.start pairing payload\");\n logger.error(\"[push] failed to decode session.start pairing payload\", {\n error: error.message,\n encodedLength: encoded.length,\n });\n options.onError?.(error);\n }\n}\n"],"mappings":"AAAA,SAAS,iBAAiB;AAC1B;AAAA,EAEE;AAAA,OAEK;AACP,SAAS,cAAc;AACvB,SAAS,4BAAiD;AA+B1D,MAAM,aAAa;AACnB,MAAM,uBAAuB;AAE7B,eAAsB,aACpB,SAC6B;AAC7B,MAAI,CAAC,UAAU,iBAAiB,GAAG;AACjC,WAAO,KAAK,qDAAqD;AAAA,MAC/D,UAAU,UAAU,YAAY;AAAA,IAClC,CAAC;AACD,WAAO,EAAE,YAAY,YAAY;AAAA,IAAC,EAAE;AAAA,EACtC;AAEA,QAAM,aAAa,MAAM,kBAAkB,mBAAmB;AAC9D,MAAI,WAAW,YAAY,WAAW;AACpC,UAAM,QAAQ,IAAI;AAAA,MAChB,uCAAuC,WAAW,OAAO;AAAA,IAC3D;AACA,WAAO,KAAK,MAAM,SAAS,EAAE,YAAY,WAAW,QAAQ,CAAC;AAC7D,YAAQ,UAAU,KAAK;AACvB,WAAO,EAAE,YAAY,YAAY;AAAA,IAAC,EAAE;AAAA,EACtC;AAEA,QAAM,cAAc,MAAM,kBAAkB;AAAA,IAC1C;AAAA,IACA,CAAC,UAAiB;AAChB,aAAO,KAAK,8BAA8B;AAAA,QACxC,aAAa,MAAM,MAAM;AAAA,MAC3B,CAAC;AACD,cAAQ,UAAU,MAAM,KAAK;AAAA,IAC/B;AAAA,EACF;AAEA,QAAM,cAAc,MAAM,kBAAkB;AAAA,IAC1C;AAAA,IACA,CAAC,QAA2B;AAC1B,YAAM,QAAQ,IAAI,MAAM,oCAAoC,IAAI,KAAK,EAAE;AACvE,aAAO,MAAM,MAAM,SAAS,CAAC,CAAC;AAC9B,cAAQ,UAAU,KAAK;AAAA,IACzB;AAAA,EACF;AAEA,QAAM,gBAAgB,MAAM,kBAAkB;AAAA,IAC5C;AAAA,IACA,CAAC,iBAAyC;AACxC,yBAAmB,cAAc,OAAO;AAAA,IAC1C;AAAA,EACF;AAEA,QAAM,eAAe,MAAM,kBAAkB;AAAA,IAC3C;AAAA,IACA,CAAC,WAAqD;AACpD,yBAAmB,OAAO,cAAc,OAAO;AAAA,IACjD;AAAA,EACF;AAEA,QAAM,kBAAkB,SAAS;AACjC,SAAO,KAAK,8BAA8B,CAAC,CAAC;AAE5C,SAAO;AAAA,IACL,YAAY,YAAY;AACtB,YAAM,YAAY,OAAO;AACzB,YAAM,YAAY,OAAO;AACzB,YAAM,cAAc,OAAO;AAC3B,YAAM,aAAa,OAAO;AAAA,IAC5B;AAAA,EACF;AACF;AAEA,SAAS,mBACP,cACA,SACM;AACN,QAAM,OAAO,aAAa,QAAQ,CAAC;AACnC,QAAM,SAAS,KAAK,UAAU;AAC9B,MAAI,WAAW,sBAAsB;AACnC,WAAO,MAAM,qCAAqC,EAAE,OAAO,CAAC;AAC5D;AAAA,EACF;AACA,QAAM,UAAU,KAAK;AACrB,MAAI,OAAO,YAAY,UAAU;AAC/B,UAAM,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AACA,WAAO,MAAM,MAAM,SAAS,CAAC,CAAC;AAC9B,YAAQ,UAAU,KAAK;AACvB;AAAA,EACF;AACA,MAAI;AACF,UAAM,UAAU,qBAAqB,OAAO;AAC5C,WAAO,KAAK,wCAAwC;AAAA,MAClD,SAAS,QAAQ;AAAA,IACnB,CAAC;AACD,YAAQ,SAAS,EAAE,MAAM,iBAAiB,QAAQ,CAAC;AAAA,EACrD,SAAS,OAAO;AACd,UAAM,QACJ,iBAAiB,QACb,QACA,IAAI,MAAM,uDAAuD;AACvE,WAAO,MAAM,yDAAyD;AAAA,MACpE,OAAO,MAAM;AAAA,MACb,eAAe,QAAQ;AAAA,IACzB,CAAC;AACD,YAAQ,UAAU,KAAK;AAAA,EACzB;AACF;","names":[]}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Remote session WebSocket client + touch-to-input translator.
3
+ *
4
+ * The companion opens a single WebSocket to the session ingress
5
+ * (`wss://<ingress>/input`) and relays serialized input events. The host-side
6
+ * session bridge (T9a data plane) forwards those events into the noVNC /
7
+ * input-channel pipeline on the paired Mac.
8
+ *
9
+ * This module is pure — no React, no Capacitor. Makes it directly unit-testable
10
+ * under jsdom/node.
11
+ */
12
+ export type InputButton = "left" | "right" | "middle";
13
+ export type InputEvent = {
14
+ type: "mouse-move";
15
+ x: number;
16
+ y: number;
17
+ } | {
18
+ type: "mouse-down";
19
+ x: number;
20
+ y: number;
21
+ button: InputButton;
22
+ } | {
23
+ type: "mouse-up";
24
+ x: number;
25
+ y: number;
26
+ button: InputButton;
27
+ } | {
28
+ type: "mouse-click";
29
+ x: number;
30
+ y: number;
31
+ button: InputButton;
32
+ } | {
33
+ type: "mouse-drag";
34
+ fromX: number;
35
+ fromY: number;
36
+ toX: number;
37
+ toY: number;
38
+ };
39
+ export type SessionState = "idle" | "connecting" | "open" | "closed";
40
+ export interface TouchSample {
41
+ /** Pointer X in VNC-viewport pixel coordinates. */
42
+ x: number;
43
+ /** Pointer Y in VNC-viewport pixel coordinates. */
44
+ y: number;
45
+ /** Milliseconds since epoch (or any monotonic reference). */
46
+ t: number;
47
+ /** Distinct finger ID. 0 = primary touch, 1 = second finger, etc. */
48
+ pointerId: number;
49
+ }
50
+ export interface TouchGesture {
51
+ /**
52
+ * Per-pointer samples, in time order. `samples[0]` is always the primary
53
+ * pointer (`pointerId: 0`). Multi-finger taps have one sample per pointer.
54
+ */
55
+ pointers: readonly (readonly TouchSample[])[];
56
+ /** true if the gesture ended with all fingers lifted. */
57
+ ended: boolean;
58
+ }
59
+ export interface TouchToInputOptions {
60
+ /**
61
+ * Maximum movement in CSS pixels for a sample sequence to still count as a
62
+ * tap (vs a pan). Default 6px matches iOS Human Interface Guidelines for
63
+ * tap slop.
64
+ */
65
+ tapSlopPx?: number;
66
+ /**
67
+ * Duration in ms above which a single-finger hold becomes a long-press
68
+ * (right click). Default 500ms.
69
+ */
70
+ longPressMs?: number;
71
+ }
72
+ interface SessionEventMap {
73
+ state: SessionState;
74
+ error: Error;
75
+ message: unknown;
76
+ }
77
+ type Listener<T> = (value: T) => void;
78
+ export declare class SessionClient {
79
+ private readonly webSocketFactory;
80
+ private socket;
81
+ private state;
82
+ private readonly listeners;
83
+ constructor(webSocketFactory?: (url: string) => WebSocket);
84
+ getState(): SessionState;
85
+ connect(ingressUrl: string, sessionToken: string): void;
86
+ sendInput(event: InputEvent): void;
87
+ close(): void;
88
+ on<K extends keyof SessionEventMap>(event: K, handler: Listener<SessionEventMap[K]>): () => void;
89
+ private setState;
90
+ }
91
+ /**
92
+ * Translate a completed touch gesture into a sequence of input events for
93
+ * the remote host. Pure function.
94
+ *
95
+ * Rules (matches T9c spec):
96
+ * - single-finger tap (<= tapSlop, < longPress) → left click
97
+ * - single-finger long-press (>= longPressMs, <= slop) → right click
98
+ * - two-finger tap (2 simultaneous pointers, no drag) → middle click
99
+ * - single-finger pan (> tapSlop) → mouse drag
100
+ */
101
+ export declare function touchToInput(gesture: TouchGesture, options?: TouchToInputOptions): InputEvent[];
102
+ /**
103
+ * Parse a pairing QR code payload. The QR contains base64(JSON).
104
+ * Throws on malformed input — the caller (UI) shows a user-facing error.
105
+ */
106
+ export interface PairingPayload {
107
+ agentId: string;
108
+ pairingCode: string;
109
+ ingressUrl: string;
110
+ sessionToken: string;
111
+ }
112
+ export declare function decodePairingPayload(raw: string): PairingPayload;
113
+ export {};
114
+ //# sourceMappingURL=session-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-client.d.ts","sourceRoot":"","sources":["../../../src/companion/services/session-client.ts"],"names":[],"mappings":"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"}