@hainiva/agent-sdk 0.0.0-stage → 0.3.0

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 (119) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/LICENSE +21 -0
  3. package/README.md +278 -2
  4. package/SECURITY.md +33 -0
  5. package/dist/cjs/agent.cjs +320 -0
  6. package/dist/cjs/agent.d.cts +239 -0
  7. package/dist/cjs/calls.cjs +277 -0
  8. package/dist/cjs/calls.d.cts +232 -0
  9. package/dist/cjs/contract.cjs +14 -0
  10. package/dist/cjs/contract.d.cts +225 -0
  11. package/dist/cjs/embed.cjs +253 -0
  12. package/dist/cjs/embed.d.cts +187 -0
  13. package/dist/cjs/emitter.cjs +50 -0
  14. package/dist/cjs/emitter.d.cts +22 -0
  15. package/dist/cjs/errors.cjs +28 -0
  16. package/dist/cjs/errors.d.cts +15 -0
  17. package/dist/cjs/events.cjs +97 -0
  18. package/dist/cjs/events.d.cts +97 -0
  19. package/dist/cjs/index.cjs +72 -0
  20. package/dist/cjs/index.d.cts +28 -0
  21. package/dist/cjs/livekit.cjs +56 -0
  22. package/dist/cjs/livekit.d.cts +35 -0
  23. package/dist/cjs/log.cjs +15 -0
  24. package/dist/cjs/log.d.cts +5 -0
  25. package/dist/cjs/react-native.cjs +32 -0
  26. package/dist/cjs/react-native.d.cts +39 -0
  27. package/dist/cjs/reactNativeDesk.cjs +107 -0
  28. package/dist/cjs/reactNativeDesk.d.cts +53 -0
  29. package/dist/cjs/redact.cjs +71 -0
  30. package/dist/cjs/redact.d.cts +21 -0
  31. package/dist/cjs/rest.cjs +286 -0
  32. package/dist/cjs/rest.d.cts +172 -0
  33. package/dist/cjs/session.cjs +159 -0
  34. package/dist/cjs/session.d.cts +101 -0
  35. package/dist/cjs/singleFlight.cjs +49 -0
  36. package/dist/cjs/singleFlight.d.cts +32 -0
  37. package/dist/cjs/url.cjs +39 -0
  38. package/dist/cjs/url.d.cts +13 -0
  39. package/dist/cjs/web.cjs +28 -0
  40. package/dist/cjs/web.d.cts +23 -0
  41. package/dist/cjs/ws.cjs +246 -0
  42. package/dist/cjs/ws.d.cts +134 -0
  43. package/dist/esm/agent.d.ts +240 -0
  44. package/dist/esm/agent.d.ts.map +1 -0
  45. package/dist/esm/agent.js +317 -0
  46. package/dist/esm/agent.js.map +1 -0
  47. package/dist/esm/calls.d.ts +233 -0
  48. package/dist/esm/calls.d.ts.map +1 -0
  49. package/dist/esm/calls.js +271 -0
  50. package/dist/esm/calls.js.map +1 -0
  51. package/dist/esm/contract.d.ts +226 -0
  52. package/dist/esm/contract.d.ts.map +1 -0
  53. package/dist/esm/contract.js +12 -0
  54. package/dist/esm/contract.js.map +1 -0
  55. package/dist/esm/embed.d.ts +188 -0
  56. package/dist/esm/embed.d.ts.map +1 -0
  57. package/dist/esm/embed.js +247 -0
  58. package/dist/esm/embed.js.map +1 -0
  59. package/dist/esm/emitter.d.ts +23 -0
  60. package/dist/esm/emitter.d.ts.map +1 -0
  61. package/dist/esm/emitter.js +47 -0
  62. package/dist/esm/emitter.js.map +1 -0
  63. package/dist/esm/errors.d.ts +16 -0
  64. package/dist/esm/errors.d.ts.map +1 -0
  65. package/dist/esm/errors.js +25 -0
  66. package/dist/esm/errors.js.map +1 -0
  67. package/dist/esm/events.d.ts +98 -0
  68. package/dist/esm/events.d.ts.map +1 -0
  69. package/dist/esm/events.js +90 -0
  70. package/dist/esm/events.js.map +1 -0
  71. package/dist/esm/index.d.ts +29 -0
  72. package/dist/esm/index.d.ts.map +1 -0
  73. package/dist/esm/index.js +29 -0
  74. package/dist/esm/index.js.map +1 -0
  75. package/dist/esm/livekit.d.ts +36 -0
  76. package/dist/esm/livekit.d.ts.map +1 -0
  77. package/dist/esm/livekit.js +54 -0
  78. package/dist/esm/livekit.js.map +1 -0
  79. package/dist/esm/log.d.ts +6 -0
  80. package/dist/esm/log.d.ts.map +1 -0
  81. package/dist/esm/log.js +13 -0
  82. package/dist/esm/log.js.map +1 -0
  83. package/dist/esm/react-native.d.ts +40 -0
  84. package/dist/esm/react-native.d.ts.map +1 -0
  85. package/dist/esm/react-native.js +26 -0
  86. package/dist/esm/react-native.js.map +1 -0
  87. package/dist/esm/reactNativeDesk.d.ts +54 -0
  88. package/dist/esm/reactNativeDesk.d.ts.map +1 -0
  89. package/dist/esm/reactNativeDesk.js +103 -0
  90. package/dist/esm/reactNativeDesk.js.map +1 -0
  91. package/dist/esm/redact.d.ts +22 -0
  92. package/dist/esm/redact.d.ts.map +1 -0
  93. package/dist/esm/redact.js +66 -0
  94. package/dist/esm/redact.js.map +1 -0
  95. package/dist/esm/rest.d.ts +173 -0
  96. package/dist/esm/rest.d.ts.map +1 -0
  97. package/dist/esm/rest.js +281 -0
  98. package/dist/esm/rest.js.map +1 -0
  99. package/dist/esm/session.d.ts +102 -0
  100. package/dist/esm/session.d.ts.map +1 -0
  101. package/dist/esm/session.js +155 -0
  102. package/dist/esm/session.js.map +1 -0
  103. package/dist/esm/singleFlight.d.ts +33 -0
  104. package/dist/esm/singleFlight.d.ts.map +1 -0
  105. package/dist/esm/singleFlight.js +46 -0
  106. package/dist/esm/singleFlight.js.map +1 -0
  107. package/dist/esm/url.d.ts +14 -0
  108. package/dist/esm/url.d.ts.map +1 -0
  109. package/dist/esm/url.js +37 -0
  110. package/dist/esm/url.js.map +1 -0
  111. package/dist/esm/web.d.ts +24 -0
  112. package/dist/esm/web.d.ts.map +1 -0
  113. package/dist/esm/web.js +23 -0
  114. package/dist/esm/web.js.map +1 -0
  115. package/dist/esm/ws.d.ts +135 -0
  116. package/dist/esm/ws.d.ts.map +1 -0
  117. package/dist/esm/ws.js +242 -0
  118. package/dist/esm/ws.js.map +1 -0
  119. package/package.json +117 -4
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CallStore = exports.initialCallsState = void 0;
4
+ exports.transition = transition;
5
+ exports.activeCall = activeCall;
6
+ exports.ringingCalls = ringingCalls;
7
+ const emitter_js_1 = require("./emitter.cjs");
8
+ exports.initialCallsState = Object.freeze({ calls: Object.freeze({}), activeId: null });
9
+ const LIVE = new Set(["connecting", "active", "held", "transferring"]);
10
+ const IN_CALL = new Set(["active", "held", "transferring"]);
11
+ function newCall(info, phase, at) {
12
+ return { info, phase, muted: false, since: null, ringingSince: phase === "ringing" ? at : null, transfer: null, notice: null, endReason: null, endedAt: null, error: null };
13
+ }
14
+ function activeOf(calls) {
15
+ for (const c of Object.values(calls))
16
+ if (LIVE.has(c.phase))
17
+ return c.info.id;
18
+ return null;
19
+ }
20
+ /** Apply one event. Pure: never mutates `state`. */
21
+ function transition(state, ev) {
22
+ const calls = { ...state.calls };
23
+ const effects = [];
24
+ const same = { state, effects: [], changed: false };
25
+ const id = ev.type === "ring" || ev.type === "dial" ? ev.call.id : ev.id;
26
+ const c = calls[id];
27
+ const put = (next) => { calls[next.info.id] = next; };
28
+ const end = (call, reason, at, extra = {}) => {
29
+ if (call.phase === "ringing")
30
+ effects.push({ type: "ring.cancel", id: call.info.id });
31
+ else if (LIVE.has(call.phase))
32
+ effects.push({ type: "media.leave", id: call.info.id });
33
+ put({ ...call, ...extra, phase: "ended", endReason: reason, endedAt: at, transfer: null });
34
+ effects.push({ type: "ended", id: call.info.id, reason });
35
+ };
36
+ const done = () => {
37
+ const next = { calls, activeId: activeOf(calls) };
38
+ return { state: next, effects, changed: true };
39
+ };
40
+ switch (ev.type) {
41
+ case "ring": {
42
+ if (!ev.call.id)
43
+ return same;
44
+ if (c && c.phase !== "ended")
45
+ return same; // the same ring again (push + socket)
46
+ if (ev.call.expiresAt !== undefined && ev.call.expiresAt <= ev.at)
47
+ return same; // arrived too late
48
+ if (state.activeId) {
49
+ // On a call: this device turns it away (iPhone: CallKit already shows it).
50
+ return { state, effects: [{ type: "ring.cancel", id }], changed: false };
51
+ }
52
+ put(newCall({ ...ev.call, direction: "inbound" }, "ringing", ev.at));
53
+ effects.push({ type: "ring.show", call: ev.call });
54
+ return done();
55
+ }
56
+ case "dial": {
57
+ if (!ev.call.id || state.activeId)
58
+ return same;
59
+ if (Object.values(state.calls).some((x) => x.phase === "ringing"))
60
+ return same; // answer or decline first
61
+ if (c && c.phase !== "ended")
62
+ return same;
63
+ put(newCall({ ...ev.call, direction: "outbound", path: "out" }, "connecting", ev.at));
64
+ return done();
65
+ }
66
+ case "accept": {
67
+ if (!c || c.phase !== "ringing" || state.activeId)
68
+ return same;
69
+ if (c.info.expiresAt !== undefined && c.info.expiresAt <= ev.at) {
70
+ end(c, "missed", ev.at);
71
+ return done();
72
+ }
73
+ effects.push({ type: "ring.cancel", id });
74
+ put({ ...c, phase: "connecting" });
75
+ for (const other of Object.values(calls)) {
76
+ if (other.info.id !== id && other.phase === "ringing")
77
+ end(other, "busy", ev.at);
78
+ }
79
+ return done();
80
+ }
81
+ case "decline": {
82
+ if (!c || c.phase !== "ringing")
83
+ return same;
84
+ end(c, "declined", ev.at);
85
+ return done();
86
+ }
87
+ case "expire": {
88
+ if (!c || c.phase !== "ringing")
89
+ return same;
90
+ if (c.info.expiresAt !== undefined && c.info.expiresAt > ev.at)
91
+ return same;
92
+ end(c, "missed", ev.at);
93
+ return done();
94
+ }
95
+ case "ring_cancelled": {
96
+ if (!c)
97
+ return same;
98
+ if (c.phase === "ringing") {
99
+ end(c, ev.reason, ev.at);
100
+ return done();
101
+ }
102
+ // Answering here: "taken elsewhere" is about the race the server decides
103
+ // (accept_failed tells); the caller hanging up ends it.
104
+ if (c.phase === "connecting" && ev.reason === "remote_ended") {
105
+ end(c, "remote_ended", ev.at);
106
+ return done();
107
+ }
108
+ return same;
109
+ }
110
+ case "accept_failed": {
111
+ if (!c || c.phase !== "connecting")
112
+ return same;
113
+ const reason = ev.reason === "answered_elsewhere" ? "answered_elsewhere" : ev.reason === "gone" ? "remote_ended" : "failed";
114
+ end(c, reason, ev.at, { error: ev.error ?? null });
115
+ return done();
116
+ }
117
+ case "connected": {
118
+ if (!c || c.phase === "ended") {
119
+ // Media came up after the call ended (caller hung up during the answer): leave it.
120
+ return { state, effects: [{ type: "media.leave", id }], changed: false };
121
+ }
122
+ if (c.phase !== "connecting")
123
+ return same;
124
+ put({ ...c, phase: "active", since: ev.answered ? ev.at : null, muted: false });
125
+ return done();
126
+ }
127
+ case "answered": {
128
+ if (!c || !IN_CALL.has(c.phase) || c.since !== null)
129
+ return same;
130
+ put({ ...c, since: ev.at });
131
+ return done();
132
+ }
133
+ case "mute": {
134
+ if (!c || !IN_CALL.has(c.phase) || c.muted === ev.muted)
135
+ return same;
136
+ put({ ...c, muted: ev.muted });
137
+ return done();
138
+ }
139
+ case "hold": {
140
+ if (!c || c.phase !== "active")
141
+ return same;
142
+ put({ ...c, phase: "held" });
143
+ return done();
144
+ }
145
+ case "resume": {
146
+ if (!c || c.phase !== "held")
147
+ return same;
148
+ put({ ...c, phase: "active" });
149
+ return done();
150
+ }
151
+ case "transfer_start": {
152
+ if (!c || (c.phase !== "active" && c.phase !== "held"))
153
+ return same;
154
+ const mode = ev.target.kind === "queue" ? "blind" : ev.mode;
155
+ put({ ...c, phase: "transferring", notice: null, transfer: { mode, target: ev.target, joined: false, startedAt: ev.at, resumeTo: c.phase } });
156
+ return done();
157
+ }
158
+ case "transfer_joined": {
159
+ if (!c || c.phase !== "transferring" || !c.transfer || c.transfer.mode !== "warm" || c.transfer.joined)
160
+ return same;
161
+ put({ ...c, transfer: { ...c.transfer, joined: true } });
162
+ return done();
163
+ }
164
+ case "transfer_cancelled": {
165
+ if (!c || c.phase !== "transferring" || !c.transfer)
166
+ return same;
167
+ put({ ...c, phase: c.transfer.resumeTo, transfer: null, notice: ev.notice ?? null });
168
+ return done();
169
+ }
170
+ case "transfer_completed": {
171
+ if (!c || c.phase !== "transferring")
172
+ return same;
173
+ end(c, "transferred", ev.at);
174
+ return done();
175
+ }
176
+ case "remote_end": {
177
+ if (!c || c.phase === "ended")
178
+ return same;
179
+ // A blind transfer ends here when the server hands the call over.
180
+ const reason = c.phase === "transferring" && c.transfer?.mode === "blind" ? "transferred" : "remote_ended";
181
+ end(c, reason, ev.at);
182
+ return done();
183
+ }
184
+ case "hang_up": {
185
+ if (!c || !LIVE.has(c.phase))
186
+ return same;
187
+ end(c, "hung_up", ev.at);
188
+ return done();
189
+ }
190
+ case "rekey": {
191
+ // An outgoing call learns its server id.
192
+ if (!c || c.phase === "ended" || !ev.newId || ev.newId === id || calls[ev.newId])
193
+ return same;
194
+ delete calls[id];
195
+ put({ ...c, info: { ...c.info, id: ev.newId, ...(ev.conversationId ? { conversationId: ev.conversationId } : {}) } });
196
+ return done();
197
+ }
198
+ case "dismiss": {
199
+ if (!c || c.phase !== "ended")
200
+ return same;
201
+ delete calls[id];
202
+ return done();
203
+ }
204
+ }
205
+ }
206
+ /** The call holding the line, if any. */
207
+ function activeCall(state) {
208
+ return state.activeId ? state.calls[state.activeId] ?? null : null;
209
+ }
210
+ /** Calls ringing now, oldest first. */
211
+ function ringingCalls(state) {
212
+ return Object.values(state.calls).filter((c) => c.phase === "ringing").sort((a, b) => (a.ringingSince ?? 0) - (b.ringingSince ?? 0));
213
+ }
214
+ /**
215
+ * The state machine with a clock: rings expire on their own, and listeners hear
216
+ * every change and effect. Still free of React Native, LiveKit and HTTP.
217
+ */
218
+ class CallStore {
219
+ constructor(o = {}) {
220
+ this.o = o;
221
+ this.events = new emitter_js_1.TypedEmitter();
222
+ this.st = exports.initialCallsState;
223
+ this.timers = new Map();
224
+ this.now = o.now ?? Date.now;
225
+ }
226
+ get state() { return this.st; }
227
+ /** Apply an event. `at` is filled from the clock when omitted. */
228
+ dispatch(ev) {
229
+ const full = ("at" in ev && typeof ev.at === "number" ? ev : { ...ev, at: this.now() });
230
+ const t = transition(this.st, full);
231
+ if (t.changed) {
232
+ this.st = t.state;
233
+ this.syncTimers();
234
+ this.events.emit("change", this.st);
235
+ }
236
+ for (const e of t.effects)
237
+ this.events.emit("effect", e);
238
+ return t;
239
+ }
240
+ /** Stop every timer (e.g. on sign-out). */
241
+ dispose() {
242
+ for (const t of this.timers.values())
243
+ clearTimeout(t);
244
+ this.timers.clear();
245
+ this.events.removeAll();
246
+ }
247
+ syncTimers() {
248
+ for (const [key, t] of this.timers) {
249
+ const cut = key.indexOf(":");
250
+ const kind = key.slice(0, cut);
251
+ const id = key.slice(cut + 1);
252
+ const c = this.st.calls[id];
253
+ if (!c || (kind === "expire" && c.phase !== "ringing") || (kind === "forget" && c.phase !== "ended")) {
254
+ clearTimeout(t);
255
+ this.timers.delete(key);
256
+ }
257
+ }
258
+ for (const c of Object.values(this.st.calls)) {
259
+ const id = c.info.id;
260
+ if (c.phase === "ringing" && c.info.expiresAt !== undefined && !this.timers.has(`expire:${id}`)) {
261
+ const left = Math.max(0, c.info.expiresAt - this.now());
262
+ this.timers.set(`expire:${id}`, setTimeout(() => {
263
+ this.timers.delete(`expire:${id}`);
264
+ this.dispatch({ type: "expire", id, at: Math.max(this.now(), c.info.expiresAt ?? 0) });
265
+ }, left));
266
+ }
267
+ const forget = this.o.forgetEndedAfterMs ?? 0;
268
+ if (c.phase === "ended" && forget > 0 && !this.timers.has(`forget:${id}`)) {
269
+ this.timers.set(`forget:${id}`, setTimeout(() => {
270
+ this.timers.delete(`forget:${id}`);
271
+ this.dispatch({ type: "dismiss", id });
272
+ }, forget));
273
+ }
274
+ }
275
+ }
276
+ }
277
+ exports.CallStore = CallStore;
@@ -0,0 +1,232 @@
1
+ import { TypedEmitter } from "./emitter.cjs";
2
+ /**
3
+ * The agent's calls as a pure state machine — ported from the HAINIVA Agent app
4
+ * (apps/agent-mobile/src/calls.ts), without React Native, LiveKit or HTTP: the
5
+ * host feeds it events (a ring push, the user's answer, "media connected", the
6
+ * server's cancel…) and acts on the effects it returns (stop the native ring,
7
+ * leave the room). Same rules as the app:
8
+ *
9
+ * - one call at a time: a ring while on a call is turned away ("busy"), and
10
+ * answering one ring turns away the others;
11
+ * - a ring ends at its expiresAt ("missed"), when declined, or when the server
12
+ * cancels it (taken elsewhere, caller gone);
13
+ * - while answering, "taken elsewhere" is ignored (the server's answer race
14
+ * decides, via accept_failed) but the caller hanging up ends it, and media
15
+ * that connects after that is left at once;
16
+ * - hold, mute and warm/blind transfer as on the desk.
17
+ *
18
+ * ringing ──accept──▶ connecting ──connected──▶ active ⇄ held
19
+ * │ │ │ └─transfer_start─▶ transferring
20
+ * ├─decline/expire/ └─accept_failed/ │ │
21
+ * │ ring_cancelled remote_end/hang_up └─remote_end/hang_up ├─transfer_completed
22
+ * ▼ ▼ ▼ ▼
23
+ * ended ◀──────────────────────────────────────────────────────────── ended
24
+ */
25
+ export type CallDirection = "inbound" | "outbound";
26
+ /** direct / queue: a ring; transfer: a colleague's transfer invite; out: placed here. */
27
+ export type CallPath = "direct" | "queue" | "transfer" | "out";
28
+ export interface CallInfo {
29
+ /** The interaction id (or a client id for an outgoing call until the server's arrives). */
30
+ id: string;
31
+ conversationId?: string;
32
+ direction: CallDirection;
33
+ path: CallPath;
34
+ /** Caller (inbound) or callee (outbound) as shown. */
35
+ from: string;
36
+ line?: string;
37
+ /** Epoch ms when an unanswered ring ends. */
38
+ expiresAt?: number;
39
+ /** A transfer invite: how the colleague is handing it over. */
40
+ transfer?: {
41
+ mode: "warm" | "blind";
42
+ };
43
+ }
44
+ export type CallPhase = "ringing" | "connecting" | "active" | "held" | "transferring" | "ended";
45
+ export type EndReason = "declined" | "missed" | "answered_elsewhere" | "remote_ended" | "hung_up" | "transferred" | "busy" | "failed" | "cancelled";
46
+ export type TransferTarget = {
47
+ kind: "agent";
48
+ agentId: string;
49
+ name: string;
50
+ } | {
51
+ kind: "queue";
52
+ queueId: string;
53
+ name: string;
54
+ } | {
55
+ kind: "external";
56
+ number: string;
57
+ };
58
+ export interface TransferState {
59
+ mode: "warm" | "blind";
60
+ target: TransferTarget;
61
+ /** Warm: the colleague / number is in the call. */
62
+ joined: boolean;
63
+ startedAt: number;
64
+ /** Where a cancelled transfer returns to. */
65
+ resumeTo: "active" | "held";
66
+ }
67
+ export interface Call {
68
+ readonly info: CallInfo;
69
+ readonly phase: CallPhase;
70
+ readonly muted: boolean;
71
+ /** When talking started; null while an outgoing call still rings at the far end. */
72
+ readonly since: number | null;
73
+ readonly ringingSince: number | null;
74
+ readonly transfer: TransferState | null;
75
+ /** A short note for the UI (e.g. "colleague left"). */
76
+ readonly notice: string | null;
77
+ readonly endReason: EndReason | null;
78
+ readonly endedAt: number | null;
79
+ readonly error: string | null;
80
+ }
81
+ export interface CallsState {
82
+ readonly calls: Readonly<Record<string, Call>>;
83
+ /** The call that holds the line (connecting, active, held, transferring), if any. */
84
+ readonly activeId: string | null;
85
+ }
86
+ export declare const initialCallsState: CallsState;
87
+ export type CallEvent = {
88
+ type: "ring";
89
+ call: CallInfo;
90
+ at: number;
91
+ } | {
92
+ type: "dial";
93
+ call: CallInfo;
94
+ at: number;
95
+ } | {
96
+ type: "accept";
97
+ id: string;
98
+ at: number;
99
+ } | {
100
+ type: "decline";
101
+ id: string;
102
+ at: number;
103
+ } | {
104
+ type: "expire";
105
+ id: string;
106
+ at: number;
107
+ } | {
108
+ type: "ring_cancelled";
109
+ id: string;
110
+ reason: "answered_elsewhere" | "remote_ended" | "cancelled";
111
+ at: number;
112
+ } | {
113
+ type: "accept_failed";
114
+ id: string;
115
+ reason: "answered_elsewhere" | "gone" | "failed";
116
+ error?: string;
117
+ at: number;
118
+ } | {
119
+ type: "connected";
120
+ id: string;
121
+ answered: boolean;
122
+ at: number;
123
+ } | {
124
+ type: "answered";
125
+ id: string;
126
+ at: number;
127
+ } | {
128
+ type: "mute";
129
+ id: string;
130
+ muted: boolean;
131
+ } | {
132
+ type: "hold";
133
+ id: string;
134
+ } | {
135
+ type: "resume";
136
+ id: string;
137
+ } | {
138
+ type: "transfer_start";
139
+ id: string;
140
+ mode: "warm" | "blind";
141
+ target: TransferTarget;
142
+ at: number;
143
+ } | {
144
+ type: "transfer_joined";
145
+ id: string;
146
+ } | {
147
+ type: "transfer_cancelled";
148
+ id: string;
149
+ notice?: string;
150
+ } | {
151
+ type: "transfer_completed";
152
+ id: string;
153
+ at: number;
154
+ } | {
155
+ type: "remote_end";
156
+ id: string;
157
+ at: number;
158
+ } | {
159
+ type: "hang_up";
160
+ id: string;
161
+ at: number;
162
+ } | {
163
+ type: "rekey";
164
+ id: string;
165
+ newId: string;
166
+ conversationId?: string;
167
+ } | {
168
+ type: "dismiss";
169
+ id: string;
170
+ };
171
+ /** What the host must do after a transition. */
172
+ export type CallEffect = {
173
+ type: "ring.show";
174
+ call: CallInfo;
175
+ }
176
+ /** Stop this device ringing for the call (native ring / CallKit). */
177
+ | {
178
+ type: "ring.cancel";
179
+ id: string;
180
+ }
181
+ /** Leave the call's media room (or never join it). */
182
+ | {
183
+ type: "media.leave";
184
+ id: string;
185
+ } | {
186
+ type: "ended";
187
+ id: string;
188
+ reason: EndReason;
189
+ };
190
+ export interface Transition {
191
+ state: CallsState;
192
+ effects: CallEffect[];
193
+ /** False when the event did not apply (state is the same object). */
194
+ changed: boolean;
195
+ }
196
+ /** Apply one event. Pure: never mutates `state`. */
197
+ export declare function transition(state: CallsState, ev: CallEvent): Transition;
198
+ /** The call holding the line, if any. */
199
+ export declare function activeCall(state: CallsState): Call | null;
200
+ /** Calls ringing now, oldest first. */
201
+ export declare function ringingCalls(state: CallsState): Call[];
202
+ export interface CallStoreEvents {
203
+ change: CallsState;
204
+ effect: CallEffect;
205
+ }
206
+ export interface CallStoreOptions {
207
+ now?: () => number;
208
+ /** Remove ended calls this long after they end (0 = keep until dismissed). Default 0. */
209
+ forgetEndedAfterMs?: number;
210
+ }
211
+ /**
212
+ * The state machine with a clock: rings expire on their own, and listeners hear
213
+ * every change and effect. Still free of React Native, LiveKit and HTTP.
214
+ */
215
+ export declare class CallStore {
216
+ private readonly o;
217
+ readonly events: TypedEmitter<CallStoreEvents>;
218
+ private st;
219
+ private readonly timers;
220
+ private readonly now;
221
+ constructor(o?: CallStoreOptions);
222
+ get state(): CallsState;
223
+ /** Apply an event. `at` is filled from the clock when omitted. */
224
+ dispatch(ev: DistributiveOmit<CallEvent, "at"> & {
225
+ at?: number;
226
+ }): Transition;
227
+ /** Stop every timer (e.g. on sign-out). */
228
+ dispose(): void;
229
+ private syncTimers;
230
+ }
231
+ type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
232
+ export {};
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ /**
3
+ * GENERATED from the HAINIVA monorepo (@aicc/types agentApi.ts and
4
+ * agentEvents.ts) — do not edit by hand. The Agent API's response shapes and
5
+ * event names: additive changes only within a major version.
6
+ */
7
+ /* eslint-disable */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.AGENT_EVENT_NAMES = void 0;
10
+ // ── The event stream (/agent/v1/ws) ──────────────────────────────────────────
11
+ exports.AGENT_EVENT_NAMES = [
12
+ "call.ringing", "call.ended", "conversation.assigned", "message.created",
13
+ "wrapup.required", "presence.changed", "supervisor.alert",
14
+ ];