@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.
- package/dist/companion/components/Chat.d.ts +14 -0
- package/dist/companion/components/Chat.d.ts.map +1 -0
- package/dist/companion/components/Chat.js +195 -0
- package/dist/companion/components/Chat.js.map +1 -0
- package/dist/companion/components/Pairing.d.ts +9 -0
- package/dist/companion/components/Pairing.d.ts.map +1 -0
- package/dist/companion/components/Pairing.js +175 -0
- package/dist/companion/components/Pairing.js.map +1 -0
- package/dist/companion/components/PhoneCompanionApp.d.ts +9 -0
- package/dist/companion/components/PhoneCompanionApp.d.ts.map +1 -0
- package/dist/companion/components/PhoneCompanionApp.js +174 -0
- package/dist/companion/components/PhoneCompanionApp.js.map +1 -0
- package/dist/companion/components/RemoteSession.d.ts +9 -0
- package/dist/companion/components/RemoteSession.d.ts.map +1 -0
- package/dist/companion/components/RemoteSession.js +382 -0
- package/dist/companion/components/RemoteSession.js.map +1 -0
- package/dist/companion/components/index.d.ts +5 -0
- package/dist/companion/components/index.d.ts.map +1 -0
- package/dist/companion/components/index.js +11 -0
- package/dist/companion/components/index.js.map +1 -0
- package/dist/companion/index.d.ts +14 -0
- package/dist/companion/index.d.ts.map +1 -0
- package/dist/companion/index.js +12 -0
- package/dist/companion/index.js.map +1 -0
- package/dist/companion/services/eliza-intent.d.ts +63 -0
- package/dist/companion/services/eliza-intent.d.ts.map +1 -0
- package/dist/companion/services/eliza-intent.js +52 -0
- package/dist/companion/services/eliza-intent.js.map +1 -0
- package/dist/companion/services/env.d.ts +11 -0
- package/dist/companion/services/env.d.ts.map +1 -0
- package/dist/companion/services/env.js +20 -0
- package/dist/companion/services/env.js.map +1 -0
- package/dist/companion/services/index.d.ts +14 -0
- package/dist/companion/services/index.d.ts.map +1 -0
- package/dist/companion/services/index.js +29 -0
- package/dist/companion/services/index.js.map +1 -0
- package/dist/companion/services/intent-bridge.d.ts +11 -0
- package/dist/companion/services/intent-bridge.d.ts.map +1 -0
- package/dist/companion/services/intent-bridge.js +15 -0
- package/dist/companion/services/intent-bridge.js.map +1 -0
- package/dist/companion/services/logger.d.ts +14 -0
- package/dist/companion/services/logger.d.ts.map +1 -0
- package/dist/companion/services/logger.js +50 -0
- package/dist/companion/services/logger.js.map +1 -0
- package/dist/companion/services/navigation.d.ts +17 -0
- package/dist/companion/services/navigation.d.ts.map +1 -0
- package/dist/companion/services/navigation.js +104 -0
- package/dist/companion/services/navigation.js.map +1 -0
- package/dist/companion/services/push.d.ts +27 -0
- package/dist/companion/services/push.d.ts.map +1 -0
- package/dist/companion/services/push.js +101 -0
- package/dist/companion/services/push.js.map +1 -0
- package/dist/companion/services/session-client.d.ts +114 -0
- package/dist/companion/services/session-client.d.ts.map +1 -0
- package/dist/companion/services/session-client.js +197 -0
- package/dist/companion/services/session-client.js.map +1 -0
- package/dist/components/PhoneAppView.d.ts +18 -0
- package/dist/components/PhoneAppView.d.ts.map +1 -0
- package/dist/components/PhoneAppView.helpers.d.ts +12 -0
- package/dist/components/PhoneAppView.helpers.d.ts.map +1 -0
- package/dist/components/PhoneAppView.helpers.js +44 -0
- package/dist/components/PhoneAppView.helpers.js.map +1 -0
- package/dist/components/PhoneAppView.interact.d.ts +2 -0
- package/dist/components/PhoneAppView.interact.d.ts.map +1 -0
- package/dist/components/PhoneAppView.interact.js +63 -0
- package/dist/components/PhoneAppView.interact.js.map +1 -0
- package/dist/components/PhoneAppView.js +566 -0
- package/dist/components/PhoneAppView.js.map +1 -0
- package/dist/components/PhoneSpatialView.d.ts +43 -0
- package/dist/components/PhoneSpatialView.d.ts.map +1 -0
- package/dist/components/PhoneSpatialView.js +180 -0
- package/dist/components/PhoneSpatialView.js.map +1 -0
- package/dist/components/PhoneView.d.ts +13 -0
- package/dist/components/PhoneView.d.ts.map +1 -0
- package/dist/components/PhoneView.js +161 -0
- package/dist/components/PhoneView.js.map +1 -0
- package/dist/components/phone-app.d.ts +13 -0
- package/dist/components/phone-app.d.ts.map +1 -0
- package/dist/components/phone-app.js +20 -0
- package/dist/components/phone-app.js.map +1 -0
- package/dist/components/phone-view-bundle.d.ts +3 -0
- package/dist/components/phone-view-bundle.d.ts.map +1 -0
- package/dist/components/phone-view-bundle.js +7 -0
- package/dist/components/phone-view-bundle.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +17 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +48 -0
- package/dist/plugin.js.map +1 -0
- package/dist/providers/call-log.d.ts +11 -0
- package/dist/providers/call-log.d.ts.map +1 -0
- package/dist/providers/call-log.js +66 -0
- package/dist/providers/call-log.js.map +1 -0
- package/dist/register-companion-page.d.ts +13 -0
- package/dist/register-companion-page.d.ts.map +1 -0
- package/dist/register-companion-page.js +12 -0
- package/dist/register-companion-page.js.map +1 -0
- package/dist/register-terminal-view.d.ts +15 -0
- package/dist/register-terminal-view.d.ts.map +1 -0
- package/dist/register-terminal-view.js +21 -0
- package/dist/register-terminal-view.js.map +1 -0
- package/dist/register.d.ts +9 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +11 -0
- package/dist/register.js.map +1 -0
- package/dist/twilio.d.ts +33 -0
- package/dist/twilio.d.ts.map +1 -0
- package/dist/twilio.js +238 -0
- package/dist/twilio.js.map +1 -0
- package/dist/ui.d.ts +8 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +23 -0
- package/dist/ui.js.map +1 -0
- package/dist/views/bundle.js +407 -0
- package/dist/views/bundle.js.map +1 -0
- package/dist/views/dist-Cd2YtKy4.js +270 -0
- package/dist/views/dist-Cd2YtKy4.js.map +1 -0
- package/dist/views/web-TGRkTsa8.js +58 -0
- package/dist/views/web-TGRkTsa8.js.map +1 -0
- package/package.json +9 -8
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { logger } from "./logger.js";
|
|
2
|
+
const DEFAULT_TAP_SLOP_PX = 6;
|
|
3
|
+
const DEFAULT_LONG_PRESS_MS = 500;
|
|
4
|
+
class SessionClient {
|
|
5
|
+
constructor(webSocketFactory = (url) => new WebSocket(url)) {
|
|
6
|
+
this.webSocketFactory = webSocketFactory;
|
|
7
|
+
}
|
|
8
|
+
webSocketFactory;
|
|
9
|
+
socket = null;
|
|
10
|
+
state = "idle";
|
|
11
|
+
listeners = {
|
|
12
|
+
state: /* @__PURE__ */ new Set(),
|
|
13
|
+
error: /* @__PURE__ */ new Set(),
|
|
14
|
+
message: /* @__PURE__ */ new Set()
|
|
15
|
+
};
|
|
16
|
+
getState() {
|
|
17
|
+
return this.state;
|
|
18
|
+
}
|
|
19
|
+
connect(ingressUrl, sessionToken) {
|
|
20
|
+
if (this.state !== "idle" && this.state !== "closed") {
|
|
21
|
+
logger.warn("[SessionClient] connect ignored; already connected", {
|
|
22
|
+
state: this.state
|
|
23
|
+
});
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const url = appendToken(ingressUrl, sessionToken);
|
|
27
|
+
logger.info("[SessionClient] connect", {
|
|
28
|
+
urlHost: safeHost(url)
|
|
29
|
+
});
|
|
30
|
+
this.setState("connecting");
|
|
31
|
+
const socket = this.webSocketFactory(url);
|
|
32
|
+
this.socket = socket;
|
|
33
|
+
socket.addEventListener("open", () => {
|
|
34
|
+
if (this.socket !== socket) return;
|
|
35
|
+
logger.info("[SessionClient] open", {});
|
|
36
|
+
this.setState("open");
|
|
37
|
+
});
|
|
38
|
+
socket.addEventListener("close", (event) => {
|
|
39
|
+
if (this.socket !== socket) return;
|
|
40
|
+
logger.info("[SessionClient] close", {
|
|
41
|
+
code: event.code,
|
|
42
|
+
clean: event.wasClean
|
|
43
|
+
});
|
|
44
|
+
this.setState("closed");
|
|
45
|
+
});
|
|
46
|
+
socket.addEventListener("error", () => {
|
|
47
|
+
if (this.socket !== socket) return;
|
|
48
|
+
const err = new Error("SessionClient WebSocket error");
|
|
49
|
+
logger.error("[SessionClient] error", { message: err.message });
|
|
50
|
+
for (const listener of this.listeners.error) listener(err);
|
|
51
|
+
});
|
|
52
|
+
socket.addEventListener("message", (event) => {
|
|
53
|
+
if (this.socket !== socket) return;
|
|
54
|
+
for (const listener of this.listeners.message) listener(event.data);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
sendInput(event) {
|
|
58
|
+
const socket = this.socket;
|
|
59
|
+
if (socket === null || this.state !== "open") {
|
|
60
|
+
logger.warn("[SessionClient] sendInput dropped; socket not open", {
|
|
61
|
+
state: this.state,
|
|
62
|
+
type: event.type
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
socket.send(JSON.stringify(event));
|
|
67
|
+
}
|
|
68
|
+
close() {
|
|
69
|
+
if (this.socket !== null) {
|
|
70
|
+
logger.info("[SessionClient] close requested", {});
|
|
71
|
+
this.socket.close();
|
|
72
|
+
this.socket = null;
|
|
73
|
+
}
|
|
74
|
+
this.setState("closed");
|
|
75
|
+
}
|
|
76
|
+
on(event, handler) {
|
|
77
|
+
this.listeners[event].add(handler);
|
|
78
|
+
return () => {
|
|
79
|
+
this.listeners[event].delete(handler);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
setState(next) {
|
|
83
|
+
if (this.state === next) return;
|
|
84
|
+
this.state = next;
|
|
85
|
+
for (const listener of this.listeners.state) listener(next);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function appendToken(ingressUrl, token) {
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = new URL(ingressUrl);
|
|
92
|
+
} catch {
|
|
93
|
+
throw new Error("SessionClient: ingress URL is not a valid absolute URL");
|
|
94
|
+
}
|
|
95
|
+
parsed.searchParams.set("token", token);
|
|
96
|
+
return parsed.toString();
|
|
97
|
+
}
|
|
98
|
+
function safeHost(url) {
|
|
99
|
+
const match = /^wss?:\/\/([^/]+)/.exec(url);
|
|
100
|
+
return match !== null ? match[1] : "unknown";
|
|
101
|
+
}
|
|
102
|
+
function touchToInput(gesture, options = {}) {
|
|
103
|
+
if (!gesture.ended) return [];
|
|
104
|
+
const tapSlop = options.tapSlopPx ?? DEFAULT_TAP_SLOP_PX;
|
|
105
|
+
const longPress = options.longPressMs ?? DEFAULT_LONG_PRESS_MS;
|
|
106
|
+
const pointers = gesture.pointers.filter((samples2) => samples2.length > 0);
|
|
107
|
+
if (pointers.length === 0) return [];
|
|
108
|
+
if (pointers.length === 2) {
|
|
109
|
+
const primary = pointers[0];
|
|
110
|
+
const secondary = pointers[1];
|
|
111
|
+
if (isTap(primary, tapSlop) && isTap(secondary, tapSlop)) {
|
|
112
|
+
const pos = lastSample(primary);
|
|
113
|
+
return [{ type: "mouse-click", x: pos.x, y: pos.y, button: "middle" }];
|
|
114
|
+
}
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
if (pointers.length !== 1) return [];
|
|
118
|
+
const samples = pointers[0];
|
|
119
|
+
const first = samples[0];
|
|
120
|
+
const last = lastSample(samples);
|
|
121
|
+
const displacement = distance(first.x, first.y, last.x, last.y);
|
|
122
|
+
const duration = last.t - first.t;
|
|
123
|
+
if (displacement <= tapSlop) {
|
|
124
|
+
if (duration >= longPress) {
|
|
125
|
+
return [{ type: "mouse-click", x: last.x, y: last.y, button: "right" }];
|
|
126
|
+
}
|
|
127
|
+
return [{ type: "mouse-click", x: last.x, y: last.y, button: "left" }];
|
|
128
|
+
}
|
|
129
|
+
return [
|
|
130
|
+
{
|
|
131
|
+
type: "mouse-drag",
|
|
132
|
+
fromX: first.x,
|
|
133
|
+
fromY: first.y,
|
|
134
|
+
toX: last.x,
|
|
135
|
+
toY: last.y
|
|
136
|
+
}
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
function isTap(samples, tapSlopPx) {
|
|
140
|
+
if (samples.length === 0) return false;
|
|
141
|
+
const first = samples[0];
|
|
142
|
+
let maxDist = 0;
|
|
143
|
+
for (const s of samples) {
|
|
144
|
+
const d = distance(first.x, first.y, s.x, s.y);
|
|
145
|
+
if (d > maxDist) maxDist = d;
|
|
146
|
+
}
|
|
147
|
+
return maxDist <= tapSlopPx;
|
|
148
|
+
}
|
|
149
|
+
function lastSample(samples) {
|
|
150
|
+
const s = samples[samples.length - 1];
|
|
151
|
+
if (!s) throw new Error("lastSample called with empty array");
|
|
152
|
+
return s;
|
|
153
|
+
}
|
|
154
|
+
function distance(x1, y1, x2, y2) {
|
|
155
|
+
const dx = x2 - x1;
|
|
156
|
+
const dy = y2 - y1;
|
|
157
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
158
|
+
}
|
|
159
|
+
function decodePairingPayload(raw) {
|
|
160
|
+
const decoded = decodeBase64(raw.trim());
|
|
161
|
+
const parsed = JSON.parse(decoded);
|
|
162
|
+
if (!isRecord(parsed)) {
|
|
163
|
+
throw new Error("PairingPayload decode: not an object");
|
|
164
|
+
}
|
|
165
|
+
const { agentId, pairingCode, ingressUrl, sessionToken } = parsed;
|
|
166
|
+
return {
|
|
167
|
+
agentId: requireNonEmptyPairingString(agentId, "agentId"),
|
|
168
|
+
pairingCode: requireNonEmptyPairingString(pairingCode, "pairingCode"),
|
|
169
|
+
ingressUrl: requireNonEmptyPairingString(ingressUrl, "ingressUrl"),
|
|
170
|
+
sessionToken: requireNonEmptyPairingString(sessionToken, "sessionToken")
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function requireNonEmptyPairingString(value, field) {
|
|
174
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`PairingPayload decode: ${field} must be a non-empty string`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
return value.trim();
|
|
180
|
+
}
|
|
181
|
+
function decodeBase64(input) {
|
|
182
|
+
if (typeof atob === "function") return atob(input);
|
|
183
|
+
const nodeBuffer = globalThis.Buffer;
|
|
184
|
+
if (nodeBuffer !== void 0) {
|
|
185
|
+
return nodeBuffer.from(input, "base64").toString("utf8");
|
|
186
|
+
}
|
|
187
|
+
throw new Error("decodeBase64: no atob or Buffer available");
|
|
188
|
+
}
|
|
189
|
+
function isRecord(value) {
|
|
190
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
191
|
+
}
|
|
192
|
+
export {
|
|
193
|
+
SessionClient,
|
|
194
|
+
decodePairingPayload,
|
|
195
|
+
touchToInput
|
|
196
|
+
};
|
|
197
|
+
//# sourceMappingURL=session-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/companion/services/session-client.ts"],"sourcesContent":["import { logger } from \"./logger.js\";\n\n/**\n * Remote session WebSocket client + touch-to-input translator.\n *\n * The companion opens a single WebSocket to the session ingress\n * (`wss://<ingress>/input`) and relays serialized input events. The host-side\n * session bridge (T9a data plane) forwards those events into the noVNC /\n * input-channel pipeline on the paired Mac.\n *\n * This module is pure — no React, no Capacitor. Makes it directly unit-testable\n * under jsdom/node.\n */\n\nexport type InputButton = \"left\" | \"right\" | \"middle\";\n\nexport type InputEvent =\n | { type: \"mouse-move\"; x: number; y: number }\n | { type: \"mouse-down\"; x: number; y: number; button: InputButton }\n | { type: \"mouse-up\"; x: number; y: number; button: InputButton }\n | { type: \"mouse-click\"; x: number; y: number; button: InputButton }\n | {\n type: \"mouse-drag\";\n fromX: number;\n fromY: number;\n toX: number;\n toY: number;\n };\n\nexport type SessionState = \"idle\" | \"connecting\" | \"open\" | \"closed\";\n\nexport interface TouchSample {\n /** Pointer X in VNC-viewport pixel coordinates. */\n x: number;\n /** Pointer Y in VNC-viewport pixel coordinates. */\n y: number;\n /** Milliseconds since epoch (or any monotonic reference). */\n t: number;\n /** Distinct finger ID. 0 = primary touch, 1 = second finger, etc. */\n pointerId: number;\n}\n\nexport interface TouchGesture {\n /**\n * Per-pointer samples, in time order. `samples[0]` is always the primary\n * pointer (`pointerId: 0`). Multi-finger taps have one sample per pointer.\n */\n pointers: readonly (readonly TouchSample[])[];\n /** true if the gesture ended with all fingers lifted. */\n ended: boolean;\n}\n\nexport interface TouchToInputOptions {\n /**\n * Maximum movement in CSS pixels for a sample sequence to still count as a\n * tap (vs a pan). Default 6px matches iOS Human Interface Guidelines for\n * tap slop.\n */\n tapSlopPx?: number;\n /**\n * Duration in ms above which a single-finger hold becomes a long-press\n * (right click). Default 500ms.\n */\n longPressMs?: number;\n}\n\nconst DEFAULT_TAP_SLOP_PX = 6;\nconst DEFAULT_LONG_PRESS_MS = 500;\n\ninterface SessionEventMap {\n state: SessionState;\n error: Error;\n message: unknown;\n}\n\ntype Listener<T> = (value: T) => void;\n\nexport class SessionClient {\n private socket: WebSocket | null = null;\n private state: SessionState = \"idle\";\n private readonly listeners: {\n [K in keyof SessionEventMap]: Set<Listener<SessionEventMap[K]>>;\n } = {\n state: new Set(),\n error: new Set(),\n message: new Set(),\n };\n\n constructor(\n private readonly webSocketFactory: (url: string) => WebSocket = (url) =>\n new WebSocket(url),\n ) {}\n\n getState(): SessionState {\n return this.state;\n }\n\n connect(ingressUrl: string, sessionToken: string): void {\n if (this.state !== \"idle\" && this.state !== \"closed\") {\n logger.warn(\"[SessionClient] connect ignored; already connected\", {\n state: this.state,\n });\n return;\n }\n const url = appendToken(ingressUrl, sessionToken);\n logger.info(\"[SessionClient] connect\", {\n urlHost: safeHost(url),\n });\n this.setState(\"connecting\");\n const socket = this.webSocketFactory(url);\n this.socket = socket;\n\n socket.addEventListener(\"open\", () => {\n if (this.socket !== socket) return;\n logger.info(\"[SessionClient] open\", {});\n this.setState(\"open\");\n });\n socket.addEventListener(\"close\", (event: CloseEvent) => {\n if (this.socket !== socket) return;\n logger.info(\"[SessionClient] close\", {\n code: event.code,\n clean: event.wasClean,\n });\n this.setState(\"closed\");\n });\n socket.addEventListener(\"error\", () => {\n if (this.socket !== socket) return;\n const err = new Error(\"SessionClient WebSocket error\");\n logger.error(\"[SessionClient] error\", { message: err.message });\n for (const listener of this.listeners.error) listener(err);\n });\n socket.addEventListener(\"message\", (event: MessageEvent) => {\n if (this.socket !== socket) return;\n for (const listener of this.listeners.message) listener(event.data);\n });\n }\n\n sendInput(event: InputEvent): void {\n const socket = this.socket;\n if (socket === null || this.state !== \"open\") {\n logger.warn(\"[SessionClient] sendInput dropped; socket not open\", {\n state: this.state,\n type: event.type,\n });\n return;\n }\n socket.send(JSON.stringify(event));\n }\n\n close(): void {\n if (this.socket !== null) {\n logger.info(\"[SessionClient] close requested\", {});\n this.socket.close();\n this.socket = null;\n }\n this.setState(\"closed\");\n }\n\n on<K extends keyof SessionEventMap>(\n event: K,\n handler: Listener<SessionEventMap[K]>,\n ): () => void {\n this.listeners[event].add(handler);\n return () => {\n this.listeners[event].delete(handler);\n };\n }\n\n private setState(next: SessionState): void {\n if (this.state === next) return;\n this.state = next;\n for (const listener of this.listeners.state) listener(next);\n }\n}\n\nfunction appendToken(ingressUrl: string, token: string): string {\n let parsed: URL;\n try {\n parsed = new URL(ingressUrl);\n } catch {\n throw new Error(\"SessionClient: ingress URL is not a valid absolute URL\");\n }\n parsed.searchParams.set(\"token\", token);\n return parsed.toString();\n}\n\nfunction safeHost(url: string): string {\n const match = /^wss?:\\/\\/([^/]+)/.exec(url);\n return match !== null ? match[1] : \"unknown\";\n}\n\n/**\n * Translate a completed touch gesture into a sequence of input events for\n * the remote host. Pure function.\n *\n * Rules (matches T9c spec):\n * - single-finger tap (<= tapSlop, < longPress) → left click\n * - single-finger long-press (>= longPressMs, <= slop) → right click\n * - two-finger tap (2 simultaneous pointers, no drag) → middle click\n * - single-finger pan (> tapSlop) → mouse drag\n */\nexport function touchToInput(\n gesture: TouchGesture,\n options: TouchToInputOptions = {},\n): InputEvent[] {\n if (!gesture.ended) return [];\n const tapSlop = options.tapSlopPx ?? DEFAULT_TAP_SLOP_PX;\n const longPress = options.longPressMs ?? DEFAULT_LONG_PRESS_MS;\n\n const pointers = gesture.pointers.filter((samples) => samples.length > 0);\n if (pointers.length === 0) return [];\n\n if (pointers.length === 2) {\n const primary = pointers[0];\n const secondary = pointers[1];\n if (isTap(primary, tapSlop) && isTap(secondary, tapSlop)) {\n const pos = lastSample(primary);\n return [{ type: \"mouse-click\", x: pos.x, y: pos.y, button: \"middle\" }];\n }\n return [];\n }\n\n if (pointers.length !== 1) return [];\n const samples = pointers[0];\n const first = samples[0];\n const last = lastSample(samples);\n const displacement = distance(first.x, first.y, last.x, last.y);\n const duration = last.t - first.t;\n\n if (displacement <= tapSlop) {\n if (duration >= longPress) {\n return [{ type: \"mouse-click\", x: last.x, y: last.y, button: \"right\" }];\n }\n return [{ type: \"mouse-click\", x: last.x, y: last.y, button: \"left\" }];\n }\n\n return [\n {\n type: \"mouse-drag\",\n fromX: first.x,\n fromY: first.y,\n toX: last.x,\n toY: last.y,\n },\n ];\n}\n\nfunction isTap(samples: readonly TouchSample[], tapSlopPx: number): boolean {\n if (samples.length === 0) return false;\n const first = samples[0];\n let maxDist = 0;\n for (const s of samples) {\n const d = distance(first.x, first.y, s.x, s.y);\n if (d > maxDist) maxDist = d;\n }\n return maxDist <= tapSlopPx;\n}\n\nfunction lastSample(samples: readonly TouchSample[]): TouchSample {\n const s = samples[samples.length - 1];\n if (!s) throw new Error(\"lastSample called with empty array\");\n return s;\n}\n\nfunction distance(x1: number, y1: number, x2: number, y2: number): number {\n const dx = x2 - x1;\n const dy = y2 - y1;\n return Math.sqrt(dx * dx + dy * dy);\n}\n\n/**\n * Parse a pairing QR code payload. The QR contains base64(JSON).\n * Throws on malformed input — the caller (UI) shows a user-facing error.\n */\nexport interface PairingPayload {\n agentId: string;\n pairingCode: string;\n ingressUrl: string;\n sessionToken: string;\n}\n\nexport function decodePairingPayload(raw: string): PairingPayload {\n const decoded = decodeBase64(raw.trim());\n const parsed: unknown = JSON.parse(decoded);\n if (!isRecord(parsed)) {\n throw new Error(\"PairingPayload decode: not an object\");\n }\n const { agentId, pairingCode, ingressUrl, sessionToken } = parsed;\n return {\n agentId: requireNonEmptyPairingString(agentId, \"agentId\"),\n pairingCode: requireNonEmptyPairingString(pairingCode, \"pairingCode\"),\n ingressUrl: requireNonEmptyPairingString(ingressUrl, \"ingressUrl\"),\n sessionToken: requireNonEmptyPairingString(sessionToken, \"sessionToken\"),\n };\n}\n\nfunction requireNonEmptyPairingString(value: unknown, field: string): string {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new Error(\n `PairingPayload decode: ${field} must be a non-empty string`,\n );\n }\n return value.trim();\n}\n\nfunction decodeBase64(input: string): string {\n if (typeof atob === \"function\") return atob(input);\n // Node fallback for tests running without jsdom shims.\n const nodeBuffer = (\n globalThis as {\n Buffer?: {\n from(input: string, enc: string): { toString(enc: string): string };\n };\n }\n ).Buffer;\n if (nodeBuffer !== undefined) {\n return nodeBuffer.from(input, \"base64\").toString(\"utf8\");\n }\n throw new Error(\"decodeBase64: no atob or Buffer available\");\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"],"mappings":"AAAA,SAAS,cAAc;AAkEvB,MAAM,sBAAsB;AAC5B,MAAM,wBAAwB;AAUvB,MAAM,cAAc;AAAA,EAWzB,YACmB,mBAA+C,CAAC,QAC/D,IAAI,UAAU,GAAG,GACnB;AAFiB;AAAA,EAEhB;AAAA,EAFgB;AAAA,EAXX,SAA2B;AAAA,EAC3B,QAAsB;AAAA,EACb,YAEb;AAAA,IACF,OAAO,oBAAI,IAAI;AAAA,IACf,OAAO,oBAAI,IAAI;AAAA,IACf,SAAS,oBAAI,IAAI;AAAA,EACnB;AAAA,EAOA,WAAyB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,QAAQ,YAAoB,cAA4B;AACtD,QAAI,KAAK,UAAU,UAAU,KAAK,UAAU,UAAU;AACpD,aAAO,KAAK,sDAAsD;AAAA,QAChE,OAAO,KAAK;AAAA,MACd,CAAC;AACD;AAAA,IACF;AACA,UAAM,MAAM,YAAY,YAAY,YAAY;AAChD,WAAO,KAAK,2BAA2B;AAAA,MACrC,SAAS,SAAS,GAAG;AAAA,IACvB,CAAC;AACD,SAAK,SAAS,YAAY;AAC1B,UAAM,SAAS,KAAK,iBAAiB,GAAG;AACxC,SAAK,SAAS;AAEd,WAAO,iBAAiB,QAAQ,MAAM;AACpC,UAAI,KAAK,WAAW,OAAQ;AAC5B,aAAO,KAAK,wBAAwB,CAAC,CAAC;AACtC,WAAK,SAAS,MAAM;AAAA,IACtB,CAAC;AACD,WAAO,iBAAiB,SAAS,CAAC,UAAsB;AACtD,UAAI,KAAK,WAAW,OAAQ;AAC5B,aAAO,KAAK,yBAAyB;AAAA,QACnC,MAAM,MAAM;AAAA,QACZ,OAAO,MAAM;AAAA,MACf,CAAC;AACD,WAAK,SAAS,QAAQ;AAAA,IACxB,CAAC;AACD,WAAO,iBAAiB,SAAS,MAAM;AACrC,UAAI,KAAK,WAAW,OAAQ;AAC5B,YAAM,MAAM,IAAI,MAAM,+BAA+B;AACrD,aAAO,MAAM,yBAAyB,EAAE,SAAS,IAAI,QAAQ,CAAC;AAC9D,iBAAW,YAAY,KAAK,UAAU,MAAO,UAAS,GAAG;AAAA,IAC3D,CAAC;AACD,WAAO,iBAAiB,WAAW,CAAC,UAAwB;AAC1D,UAAI,KAAK,WAAW,OAAQ;AAC5B,iBAAW,YAAY,KAAK,UAAU,QAAS,UAAS,MAAM,IAAI;AAAA,IACpE,CAAC;AAAA,EACH;AAAA,EAEA,UAAU,OAAyB;AACjC,UAAM,SAAS,KAAK;AACpB,QAAI,WAAW,QAAQ,KAAK,UAAU,QAAQ;AAC5C,aAAO,KAAK,sDAAsD;AAAA,QAChE,OAAO,KAAK;AAAA,QACZ,MAAM,MAAM;AAAA,MACd,CAAC;AACD;AAAA,IACF;AACA,WAAO,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,EACnC;AAAA,EAEA,QAAc;AACZ,QAAI,KAAK,WAAW,MAAM;AACxB,aAAO,KAAK,mCAAmC,CAAC,CAAC;AACjD,WAAK,OAAO,MAAM;AAClB,WAAK,SAAS;AAAA,IAChB;AACA,SAAK,SAAS,QAAQ;AAAA,EACxB;AAAA,EAEA,GACE,OACA,SACY;AACZ,SAAK,UAAU,KAAK,EAAE,IAAI,OAAO;AACjC,WAAO,MAAM;AACX,WAAK,UAAU,KAAK,EAAE,OAAO,OAAO;AAAA,IACtC;AAAA,EACF;AAAA,EAEQ,SAAS,MAA0B;AACzC,QAAI,KAAK,UAAU,KAAM;AACzB,SAAK,QAAQ;AACb,eAAW,YAAY,KAAK,UAAU,MAAO,UAAS,IAAI;AAAA,EAC5D;AACF;AAEA,SAAS,YAAY,YAAoB,OAAuB;AAC9D,MAAI;AACJ,MAAI;AACF,aAAS,IAAI,IAAI,UAAU;AAAA,EAC7B,QAAQ;AACN,UAAM,IAAI,MAAM,wDAAwD;AAAA,EAC1E;AACA,SAAO,aAAa,IAAI,SAAS,KAAK;AACtC,SAAO,OAAO,SAAS;AACzB;AAEA,SAAS,SAAS,KAAqB;AACrC,QAAM,QAAQ,oBAAoB,KAAK,GAAG;AAC1C,SAAO,UAAU,OAAO,MAAM,CAAC,IAAI;AACrC;AAYO,SAAS,aACd,SACA,UAA+B,CAAC,GAClB;AACd,MAAI,CAAC,QAAQ,MAAO,QAAO,CAAC;AAC5B,QAAM,UAAU,QAAQ,aAAa;AACrC,QAAM,YAAY,QAAQ,eAAe;AAEzC,QAAM,WAAW,QAAQ,SAAS,OAAO,CAACA,aAAYA,SAAQ,SAAS,CAAC;AACxE,MAAI,SAAS,WAAW,EAAG,QAAO,CAAC;AAEnC,MAAI,SAAS,WAAW,GAAG;AACzB,UAAM,UAAU,SAAS,CAAC;AAC1B,UAAM,YAAY,SAAS,CAAC;AAC5B,QAAI,MAAM,SAAS,OAAO,KAAK,MAAM,WAAW,OAAO,GAAG;AACxD,YAAM,MAAM,WAAW,OAAO;AAC9B,aAAO,CAAC,EAAE,MAAM,eAAe,GAAG,IAAI,GAAG,GAAG,IAAI,GAAG,QAAQ,SAAS,CAAC;AAAA,IACvE;AACA,WAAO,CAAC;AAAA,EACV;AAEA,MAAI,SAAS,WAAW,EAAG,QAAO,CAAC;AACnC,QAAM,UAAU,SAAS,CAAC;AAC1B,QAAM,QAAQ,QAAQ,CAAC;AACvB,QAAM,OAAO,WAAW,OAAO;AAC/B,QAAM,eAAe,SAAS,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;AAC9D,QAAM,WAAW,KAAK,IAAI,MAAM;AAEhC,MAAI,gBAAgB,SAAS;AAC3B,QAAI,YAAY,WAAW;AACzB,aAAO,CAAC,EAAE,MAAM,eAAe,GAAG,KAAK,GAAG,GAAG,KAAK,GAAG,QAAQ,QAAQ,CAAC;AAAA,IACxE;AACA,WAAO,CAAC,EAAE,MAAM,eAAe,GAAG,KAAK,GAAG,GAAG,KAAK,GAAG,QAAQ,OAAO,CAAC;AAAA,EACvE;AAEA,SAAO;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,OAAO,MAAM;AAAA,MACb,OAAO,MAAM;AAAA,MACb,KAAK,KAAK;AAAA,MACV,KAAK,KAAK;AAAA,IACZ;AAAA,EACF;AACF;AAEA,SAAS,MAAM,SAAiC,WAA4B;AAC1E,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAM,QAAQ,QAAQ,CAAC;AACvB,MAAI,UAAU;AACd,aAAW,KAAK,SAAS;AACvB,UAAM,IAAI,SAAS,MAAM,GAAG,MAAM,GAAG,EAAE,GAAG,EAAE,CAAC;AAC7C,QAAI,IAAI,QAAS,WAAU;AAAA,EAC7B;AACA,SAAO,WAAW;AACpB;AAEA,SAAS,WAAW,SAA8C;AAChE,QAAM,IAAI,QAAQ,QAAQ,SAAS,CAAC;AACpC,MAAI,CAAC,EAAG,OAAM,IAAI,MAAM,oCAAoC;AAC5D,SAAO;AACT;AAEA,SAAS,SAAS,IAAY,IAAY,IAAY,IAAoB;AACxE,QAAM,KAAK,KAAK;AAChB,QAAM,KAAK,KAAK;AAChB,SAAO,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE;AACpC;AAaO,SAAS,qBAAqB,KAA6B;AAChE,QAAM,UAAU,aAAa,IAAI,KAAK,CAAC;AACvC,QAAM,SAAkB,KAAK,MAAM,OAAO;AAC1C,MAAI,CAAC,SAAS,MAAM,GAAG;AACrB,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACxD;AACA,QAAM,EAAE,SAAS,aAAa,YAAY,aAAa,IAAI;AAC3D,SAAO;AAAA,IACL,SAAS,6BAA6B,SAAS,SAAS;AAAA,IACxD,aAAa,6BAA6B,aAAa,aAAa;AAAA,IACpE,YAAY,6BAA6B,YAAY,YAAY;AAAA,IACjE,cAAc,6BAA6B,cAAc,cAAc;AAAA,EACzE;AACF;AAEA,SAAS,6BAA6B,OAAgB,OAAuB;AAC3E,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,WAAW,GAAG;AAC1D,UAAM,IAAI;AAAA,MACR,0BAA0B,KAAK;AAAA,IACjC;AAAA,EACF;AACA,SAAO,MAAM,KAAK;AACpB;AAEA,SAAS,aAAa,OAAuB;AAC3C,MAAI,OAAO,SAAS,WAAY,QAAO,KAAK,KAAK;AAEjD,QAAM,aACJ,WAKA;AACF,MAAI,eAAe,QAAW;AAC5B,WAAO,WAAW,KAAK,OAAO,QAAQ,EAAE,SAAS,MAAM;AAAA,EACzD;AACA,QAAM,IAAI,MAAM,2CAA2C;AAC7D;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;","names":["samples"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhoneAppView — full-screen overlay for the Phone app.
|
|
3
|
+
*
|
|
4
|
+
* Two tabs:
|
|
5
|
+
* - Dialer: number pad, in-progress display, place-call button.
|
|
6
|
+
* - Recent: scrollable call log with type icon, name/number, timestamp.
|
|
7
|
+
*
|
|
8
|
+
* The address book lives in the separate Contacts view; the header "Contacts"
|
|
9
|
+
* affordance navigates there via the `eliza:navigate:view` bus rather than
|
|
10
|
+
* embedding a duplicate contacts pane here.
|
|
11
|
+
*
|
|
12
|
+
* The native dependency (`@elizaos/capacitor-phone`) is imported eagerly —
|
|
13
|
+
* Capacitor's `registerPlugin` returns a proxy that resolves the web fallback
|
|
14
|
+
* on web/iOS, so the import is safe even on non-Android platforms.
|
|
15
|
+
*/
|
|
16
|
+
import type { OverlayAppContext } from "@elizaos/ui";
|
|
17
|
+
export declare function PhoneAppView({ exitToApps, t }: OverlayAppContext): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
//# sourceMappingURL=PhoneAppView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PhoneAppView.d.ts","sourceRoot":"","sources":["../../src/components/PhoneAppView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAuNrD,wBAAgB,YAAY,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,EAAE,iBAAiB,2CA6ahE"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CallLogEntry } from "@elizaos/capacitor-phone";
|
|
2
|
+
export declare function callLabelFor(entry: CallLogEntry): string;
|
|
3
|
+
/** Strip whitespace and visual separators while keeping leading + and digits. */
|
|
4
|
+
export declare function normalizeNumber(input: string): string;
|
|
5
|
+
export declare function loadPhoneState(options?: {
|
|
6
|
+
limit?: unknown;
|
|
7
|
+
number?: string;
|
|
8
|
+
}): Promise<{
|
|
9
|
+
status: import("@elizaos/capacitor-phone").PhoneStatus | null;
|
|
10
|
+
calls: CallLogEntry[];
|
|
11
|
+
}>;
|
|
12
|
+
//# sourceMappingURL=PhoneAppView.helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PhoneAppView.helpers.d.ts","sourceRoot":"","sources":["../../src/components/PhoneAppView.helpers.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAM7D,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAQxD;AAED,iFAAiF;AACjF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKrD;AASD,wBAAsB,cAAc,CAAC,OAAO,CAAC,EAAE;IAC7C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;;;GAcA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Phone } from "@elizaos/capacitor-phone";
|
|
2
|
+
const DEFAULT_CALL_LOG_LIMIT = 50;
|
|
3
|
+
const MAX_CALL_LOG_LIMIT = 200;
|
|
4
|
+
function callLabelFor(entry) {
|
|
5
|
+
if (entry.cachedName && entry.cachedName.trim().length > 0) {
|
|
6
|
+
return entry.cachedName.trim();
|
|
7
|
+
}
|
|
8
|
+
if (entry.number && entry.number.trim().length > 0) {
|
|
9
|
+
return entry.number.trim();
|
|
10
|
+
}
|
|
11
|
+
return "Unknown";
|
|
12
|
+
}
|
|
13
|
+
function normalizeNumber(input) {
|
|
14
|
+
const trimmed = input.trim();
|
|
15
|
+
if (!trimmed) return "";
|
|
16
|
+
const leadingPlus = trimmed.startsWith("+") ? "+" : "";
|
|
17
|
+
return `${leadingPlus}${trimmed.replace(/[^0-9]/g, "")}`;
|
|
18
|
+
}
|
|
19
|
+
function normalizeCallLogLimit(limit) {
|
|
20
|
+
if (!Number.isFinite(limit) || typeof limit !== "number") {
|
|
21
|
+
return DEFAULT_CALL_LOG_LIMIT;
|
|
22
|
+
}
|
|
23
|
+
return Math.min(MAX_CALL_LOG_LIMIT, Math.max(1, Math.trunc(limit)));
|
|
24
|
+
}
|
|
25
|
+
async function loadPhoneState(options) {
|
|
26
|
+
const normalizedNumber = typeof options?.number === "string" ? normalizeNumber(options.number) : "";
|
|
27
|
+
const [status, recent] = await Promise.all([
|
|
28
|
+
Phone.getStatus().catch(() => null),
|
|
29
|
+
Phone.listRecentCalls({
|
|
30
|
+
limit: normalizeCallLogLimit(options?.limit),
|
|
31
|
+
...normalizedNumber ? { number: normalizedNumber } : {}
|
|
32
|
+
})
|
|
33
|
+
]);
|
|
34
|
+
return {
|
|
35
|
+
status,
|
|
36
|
+
calls: recent.calls
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
callLabelFor,
|
|
41
|
+
loadPhoneState,
|
|
42
|
+
normalizeNumber
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=PhoneAppView.helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/PhoneAppView.helpers.ts"],"sourcesContent":["// Pure phone data helpers shared between PhoneAppView.tsx (the React views) and\n// PhoneAppView.interact.ts (the terminal capability handler). Split out so the\n// .tsx file exports only React components and stays Fast-Refresh-compatible\n// (Vite full-reloads a component file that also exports plain functions).\n\nimport type { CallLogEntry } from \"@elizaos/capacitor-phone\";\nimport { Phone } from \"@elizaos/capacitor-phone\";\n\nconst DEFAULT_CALL_LOG_LIMIT = 50;\nconst MAX_CALL_LOG_LIMIT = 200;\n\nexport function callLabelFor(entry: CallLogEntry): string {\n if (entry.cachedName && entry.cachedName.trim().length > 0) {\n return entry.cachedName.trim();\n }\n if (entry.number && entry.number.trim().length > 0) {\n return entry.number.trim();\n }\n return \"Unknown\";\n}\n\n/** Strip whitespace and visual separators while keeping leading + and digits. */\nexport function normalizeNumber(input: string): string {\n const trimmed = input.trim();\n if (!trimmed) return \"\";\n const leadingPlus = trimmed.startsWith(\"+\") ? \"+\" : \"\";\n return `${leadingPlus}${trimmed.replace(/[^0-9]/g, \"\")}`;\n}\n\nfunction normalizeCallLogLimit(limit: unknown): number {\n if (!Number.isFinite(limit) || typeof limit !== \"number\") {\n return DEFAULT_CALL_LOG_LIMIT;\n }\n return Math.min(MAX_CALL_LOG_LIMIT, Math.max(1, Math.trunc(limit)));\n}\n\nexport async function loadPhoneState(options?: {\n limit?: unknown;\n number?: string;\n}) {\n const normalizedNumber =\n typeof options?.number === \"string\" ? normalizeNumber(options.number) : \"\";\n const [status, recent] = await Promise.all([\n Phone.getStatus().catch(() => null),\n Phone.listRecentCalls({\n limit: normalizeCallLogLimit(options?.limit),\n ...(normalizedNumber ? { number: normalizedNumber } : {}),\n }),\n ]);\n return {\n status,\n calls: recent.calls,\n };\n}\n"],"mappings":"AAMA,SAAS,aAAa;AAEtB,MAAM,yBAAyB;AAC/B,MAAM,qBAAqB;AAEpB,SAAS,aAAa,OAA6B;AACxD,MAAI,MAAM,cAAc,MAAM,WAAW,KAAK,EAAE,SAAS,GAAG;AAC1D,WAAO,MAAM,WAAW,KAAK;AAAA,EAC/B;AACA,MAAI,MAAM,UAAU,MAAM,OAAO,KAAK,EAAE,SAAS,GAAG;AAClD,WAAO,MAAM,OAAO,KAAK;AAAA,EAC3B;AACA,SAAO;AACT;AAGO,SAAS,gBAAgB,OAAuB;AACrD,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,cAAc,QAAQ,WAAW,GAAG,IAAI,MAAM;AACpD,SAAO,GAAG,WAAW,GAAG,QAAQ,QAAQ,WAAW,EAAE,CAAC;AACxD;AAEA,SAAS,sBAAsB,OAAwB;AACrD,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,OAAO,UAAU,UAAU;AACxD,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI,oBAAoB,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,CAAC,CAAC;AACpE;AAEA,eAAsB,eAAe,SAGlC;AACD,QAAM,mBACJ,OAAO,SAAS,WAAW,WAAW,gBAAgB,QAAQ,MAAM,IAAI;AAC1E,QAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,QAAQ,IAAI;AAAA,IACzC,MAAM,UAAU,EAAE,MAAM,MAAM,IAAI;AAAA,IAClC,MAAM,gBAAgB;AAAA,MACpB,OAAO,sBAAsB,SAAS,KAAK;AAAA,MAC3C,GAAI,mBAAmB,EAAE,QAAQ,iBAAiB,IAAI,CAAC;AAAA,IACzD,CAAC;AAAA,EACH,CAAC;AACD,SAAO;AAAA,IACL;AAAA,IACA,OAAO,OAAO;AAAA,EAChB;AACF;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PhoneAppView.interact.d.ts","sourceRoot":"","sources":["../../src/components/PhoneAppView.interact.ts"],"names":[],"mappings":"AAYA,wBAAsB,QAAQ,CAC5B,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAAC,OAAO,CAAC,CA2DlB"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Phone } from "@elizaos/capacitor-phone";
|
|
2
|
+
import {
|
|
3
|
+
callLabelFor,
|
|
4
|
+
loadPhoneState,
|
|
5
|
+
normalizeNumber
|
|
6
|
+
} from "./PhoneAppView.helpers.js";
|
|
7
|
+
async function interact(capability, params) {
|
|
8
|
+
if (capability === "terminal-phone-state") {
|
|
9
|
+
const state = await loadPhoneState({
|
|
10
|
+
limit: params?.limit,
|
|
11
|
+
number: typeof params?.number === "string" ? params.number : void 0
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
viewType: "tui",
|
|
15
|
+
status: state.status,
|
|
16
|
+
calls: state.calls.map((call) => ({
|
|
17
|
+
id: call.id,
|
|
18
|
+
number: call.number,
|
|
19
|
+
cachedName: call.cachedName,
|
|
20
|
+
label: callLabelFor(call),
|
|
21
|
+
date: call.date,
|
|
22
|
+
durationSeconds: call.durationSeconds,
|
|
23
|
+
type: call.type,
|
|
24
|
+
isNew: call.isNew,
|
|
25
|
+
agentSummary: call.agentSummary,
|
|
26
|
+
agentTranscript: call.agentTranscript
|
|
27
|
+
}))
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (capability === "terminal-place-call") {
|
|
31
|
+
const number = normalizeNumber(
|
|
32
|
+
typeof params?.number === "string" ? params.number : ""
|
|
33
|
+
);
|
|
34
|
+
if (!number) throw new Error("number is required");
|
|
35
|
+
await Phone.placeCall({ number });
|
|
36
|
+
return { placed: true, number, viewType: "tui" };
|
|
37
|
+
}
|
|
38
|
+
if (capability === "terminal-open-dialer") {
|
|
39
|
+
const number = normalizeNumber(
|
|
40
|
+
typeof params?.number === "string" ? params.number : ""
|
|
41
|
+
);
|
|
42
|
+
await Phone.openDialer(number ? { number } : void 0);
|
|
43
|
+
return { opened: true, number: number || null, viewType: "tui" };
|
|
44
|
+
}
|
|
45
|
+
if (capability === "terminal-save-call-transcript") {
|
|
46
|
+
const callId = typeof params?.callId === "string" ? params.callId.trim() : "";
|
|
47
|
+
const transcript = typeof params?.transcript === "string" ? params.transcript.trim() : "";
|
|
48
|
+
const summary = typeof params?.summary === "string" ? params.summary.trim() : "";
|
|
49
|
+
if (!callId) throw new Error("callId is required");
|
|
50
|
+
if (!transcript) throw new Error("transcript is required");
|
|
51
|
+
const result = await Phone.saveCallTranscript({
|
|
52
|
+
callId,
|
|
53
|
+
transcript,
|
|
54
|
+
...summary ? { summary } : {}
|
|
55
|
+
});
|
|
56
|
+
return { saved: true, updatedAt: result.updatedAt, viewType: "tui" };
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`Unsupported capability "${capability}"`);
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
interact
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=PhoneAppView.interact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/PhoneAppView.interact.ts"],"sourcesContent":["// View-bundle `interact` capability handler, split out of PhoneAppView.tsx so\n// that file exports only React components and stays Fast-Refresh-compatible\n// (Vite would full-reload a component file that also exports a plain function).\n// The view bundle re-exports `interact` via ./phone-view-bundle.ts.\n\nimport { Phone } from \"@elizaos/capacitor-phone\";\nimport {\n callLabelFor,\n loadPhoneState,\n normalizeNumber,\n} from \"./PhoneAppView.helpers.js\";\n\nexport async function interact(\n capability: string,\n params?: Record<string, unknown>,\n): Promise<unknown> {\n if (capability === \"terminal-phone-state\") {\n const state = await loadPhoneState({\n limit: params?.limit,\n number: typeof params?.number === \"string\" ? params.number : undefined,\n });\n return {\n viewType: \"tui\",\n status: state.status,\n calls: state.calls.map((call) => ({\n id: call.id,\n number: call.number,\n cachedName: call.cachedName,\n label: callLabelFor(call),\n date: call.date,\n durationSeconds: call.durationSeconds,\n type: call.type,\n isNew: call.isNew,\n agentSummary: call.agentSummary,\n agentTranscript: call.agentTranscript,\n })),\n };\n }\n\n if (capability === \"terminal-place-call\") {\n const number = normalizeNumber(\n typeof params?.number === \"string\" ? params.number : \"\",\n );\n if (!number) throw new Error(\"number is required\");\n await Phone.placeCall({ number });\n return { placed: true, number, viewType: \"tui\" };\n }\n\n if (capability === \"terminal-open-dialer\") {\n const number = normalizeNumber(\n typeof params?.number === \"string\" ? params.number : \"\",\n );\n await Phone.openDialer(number ? { number } : undefined);\n return { opened: true, number: number || null, viewType: \"tui\" };\n }\n\n if (capability === \"terminal-save-call-transcript\") {\n const callId =\n typeof params?.callId === \"string\" ? params.callId.trim() : \"\";\n const transcript =\n typeof params?.transcript === \"string\" ? params.transcript.trim() : \"\";\n const summary =\n typeof params?.summary === \"string\" ? params.summary.trim() : \"\";\n if (!callId) throw new Error(\"callId is required\");\n if (!transcript) throw new Error(\"transcript is required\");\n const result = await Phone.saveCallTranscript({\n callId,\n transcript,\n ...(summary ? { summary } : {}),\n });\n return { saved: true, updatedAt: result.updatedAt, viewType: \"tui\" };\n }\n\n throw new Error(`Unsupported capability \"${capability}\"`);\n}\n"],"mappings":"AAKA,SAAS,aAAa;AACtB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,eAAsB,SACpB,YACA,QACkB;AAClB,MAAI,eAAe,wBAAwB;AACzC,UAAM,QAAQ,MAAM,eAAe;AAAA,MACjC,OAAO,QAAQ;AAAA,MACf,QAAQ,OAAO,QAAQ,WAAW,WAAW,OAAO,SAAS;AAAA,IAC/D,CAAC;AACD,WAAO;AAAA,MACL,UAAU;AAAA,MACV,QAAQ,MAAM;AAAA,MACd,OAAO,MAAM,MAAM,IAAI,CAAC,UAAU;AAAA,QAChC,IAAI,KAAK;AAAA,QACT,QAAQ,KAAK;AAAA,QACb,YAAY,KAAK;AAAA,QACjB,OAAO,aAAa,IAAI;AAAA,QACxB,MAAM,KAAK;AAAA,QACX,iBAAiB,KAAK;AAAA,QACtB,MAAM,KAAK;AAAA,QACX,OAAO,KAAK;AAAA,QACZ,cAAc,KAAK;AAAA,QACnB,iBAAiB,KAAK;AAAA,MACxB,EAAE;AAAA,IACJ;AAAA,EACF;AAEA,MAAI,eAAe,uBAAuB;AACxC,UAAM,SAAS;AAAA,MACb,OAAO,QAAQ,WAAW,WAAW,OAAO,SAAS;AAAA,IACvD;AACA,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,oBAAoB;AACjD,UAAM,MAAM,UAAU,EAAE,OAAO,CAAC;AAChC,WAAO,EAAE,QAAQ,MAAM,QAAQ,UAAU,MAAM;AAAA,EACjD;AAEA,MAAI,eAAe,wBAAwB;AACzC,UAAM,SAAS;AAAA,MACb,OAAO,QAAQ,WAAW,WAAW,OAAO,SAAS;AAAA,IACvD;AACA,UAAM,MAAM,WAAW,SAAS,EAAE,OAAO,IAAI,MAAS;AACtD,WAAO,EAAE,QAAQ,MAAM,QAAQ,UAAU,MAAM,UAAU,MAAM;AAAA,EACjE;AAEA,MAAI,eAAe,iCAAiC;AAClD,UAAM,SACJ,OAAO,QAAQ,WAAW,WAAW,OAAO,OAAO,KAAK,IAAI;AAC9D,UAAM,aACJ,OAAO,QAAQ,eAAe,WAAW,OAAO,WAAW,KAAK,IAAI;AACtE,UAAM,UACJ,OAAO,QAAQ,YAAY,WAAW,OAAO,QAAQ,KAAK,IAAI;AAChE,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,oBAAoB;AACjD,QAAI,CAAC,WAAY,OAAM,IAAI,MAAM,wBAAwB;AACzD,UAAM,SAAS,MAAM,MAAM,mBAAmB;AAAA,MAC5C;AAAA,MACA;AAAA,MACA,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC/B,CAAC;AACD,WAAO,EAAE,OAAO,MAAM,WAAW,OAAO,WAAW,UAAU,MAAM;AAAA,EACrE;AAEA,QAAM,IAAI,MAAM,2BAA2B,UAAU,GAAG;AAC1D;","names":[]}
|