@ateam-ai-mobile/core 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,1081 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ PluginCommandEmitter: () => PluginCommandEmitter,
24
+ TABLE_BLOCK_CLOSE: () => TABLE_BLOCK_CLOSE,
25
+ TABLE_BLOCK_OPEN: () => TABLE_BLOCK_OPEN,
26
+ ada: () => ada,
27
+ apiFetch: () => apiFetch,
28
+ apiFetchRaw: () => apiFetchRaw,
29
+ blocksToText: () => blocksToText,
30
+ borderRadius: () => borderRadius,
31
+ closeChatStream: () => closeChatStream,
32
+ colors: () => colors,
33
+ connectorCall: () => connectorCall,
34
+ fontSize: () => fontSize,
35
+ getConfig: () => getConfig,
36
+ getCoreApiUrl: () => getCoreApiUrl,
37
+ getCoreWsUrl: () => getCoreWsUrl,
38
+ getTenant: () => getTenant,
39
+ getToken: () => getToken,
40
+ getUser: () => getUser,
41
+ getVoiceApiUrl: () => getVoiceApiUrl,
42
+ initAdas: () => initAdas,
43
+ isAdasInitialized: () => isAdasInitialized,
44
+ mcpFetch: () => mcpFetch,
45
+ mcpToolCall: () => mcpToolCall,
46
+ pluginCommandEmitter: () => pluginCommandEmitter,
47
+ postCommandResult: () => postCommandResult,
48
+ spacing: () => spacing,
49
+ unwrapMcpResult: () => unwrapMcpResult,
50
+ useChatStore: () => useChatStore
51
+ });
52
+ module.exports = __toCommonJS(index_exports);
53
+
54
+ // src/config.ts
55
+ var _config = null;
56
+ function initAdas(config) {
57
+ _config = config;
58
+ }
59
+ function isAdasInitialized() {
60
+ return _config !== null;
61
+ }
62
+ function getConfig() {
63
+ if (!_config) {
64
+ throw new Error("[adas] SDK not initialized \u2014 call initAdas(config) before using any ADAS API");
65
+ }
66
+ return _config;
67
+ }
68
+ function getCoreApiUrl() {
69
+ return getConfig().coreUrl.replace(/\/$/, "");
70
+ }
71
+ function getCoreWsUrl() {
72
+ return getCoreApiUrl().replace(/^http/, "ws");
73
+ }
74
+ function getVoiceApiUrl() {
75
+ const url = getConfig().voiceUrl;
76
+ if (!url) throw new Error("[adas] voiceUrl not configured");
77
+ return url.replace(/\/$/, "");
78
+ }
79
+ function getTenant() {
80
+ return getConfig().tenant || "";
81
+ }
82
+ function getUser() {
83
+ return getConfig().getUser?.() || null;
84
+ }
85
+ function getToken() {
86
+ return getConfig().tokenProvider.getToken();
87
+ }
88
+ async function refreshToken() {
89
+ return getConfig().tokenProvider.refresh();
90
+ }
91
+ function notifyAuthFailure() {
92
+ getConfig().tokenProvider.onAuthFailure?.();
93
+ }
94
+
95
+ // src/api/client.ts
96
+ async function apiFetch(path, options = {}) {
97
+ const { baseUrl = "core", ...fetchOptions } = options;
98
+ const base = baseUrl === "voice" ? getVoiceApiUrl() : getCoreApiUrl();
99
+ const url = `${base}${path}`;
100
+ const token = getToken();
101
+ const headers = {
102
+ ...fetchOptions.headers
103
+ };
104
+ if (token && !headers["Authorization"]) {
105
+ headers["Authorization"] = `Bearer ${token}`;
106
+ }
107
+ if (!headers["Content-Type"] && fetchOptions.body && typeof fetchOptions.body === "string") {
108
+ headers["Content-Type"] = "application/json";
109
+ }
110
+ const res = await fetch(url, { ...fetchOptions, headers });
111
+ if (res.status === 401 && token) {
112
+ console.warn(`[adas] 401 on ${path} \u2014 attempting refresh`);
113
+ const outcome = await refreshToken();
114
+ if (outcome.ok === "ok") {
115
+ const newToken = getToken();
116
+ headers["Authorization"] = `Bearer ${newToken}`;
117
+ const retryRes = await fetch(url, { ...fetchOptions, headers });
118
+ if (retryRes.ok) {
119
+ return retryRes.json();
120
+ }
121
+ if (retryRes.status === 401 || retryRes.status === 403) {
122
+ console.warn(`[adas] retry after refresh still ${retryRes.status} on ${path} \u2014 auth failure`);
123
+ notifyAuthFailure();
124
+ throw new Error("Session expired");
125
+ }
126
+ const text = await retryRes.text().catch(() => "");
127
+ throw new Error(`API ${retryRes.status}: ${text}`);
128
+ }
129
+ if (outcome.ok === "auth_failed") {
130
+ console.warn(`[adas] refresh auth_failed on ${path} (${outcome.reason}) \u2014 auth failure`);
131
+ notifyAuthFailure();
132
+ throw new Error("Session expired");
133
+ }
134
+ throw new Error(`API 401: refresh transient (${outcome.reason})`);
135
+ }
136
+ if (!res.ok) {
137
+ const text = await res.text();
138
+ throw new Error(`API ${res.status}: ${text}`);
139
+ }
140
+ return res.json();
141
+ }
142
+ async function apiFetchRaw(path, options = {}) {
143
+ const { baseUrl = "core", ...fetchOptions } = options;
144
+ const base = baseUrl === "voice" ? getVoiceApiUrl() : getCoreApiUrl();
145
+ const url = `${base}${path}`;
146
+ const token = getToken();
147
+ const headers = {
148
+ ...fetchOptions.headers
149
+ };
150
+ if (token && !headers["Authorization"]) {
151
+ headers["Authorization"] = `Bearer ${token}`;
152
+ }
153
+ return fetch(url, { ...fetchOptions, headers });
154
+ }
155
+
156
+ // src/api/mcp.ts
157
+ async function connectorCall(connectorId, tool, args = {}) {
158
+ try {
159
+ const res = await apiFetchRaw(
160
+ `/api/connectors/${encodeURIComponent(connectorId)}/call`,
161
+ {
162
+ method: "POST",
163
+ headers: { "Content-Type": "application/json" },
164
+ body: JSON.stringify({ tool, args })
165
+ }
166
+ );
167
+ if (!res.ok) {
168
+ const text = await res.text();
169
+ return { ok: false, result: null, error: `HTTP ${res.status}: ${text}` };
170
+ }
171
+ const data = await res.json();
172
+ if (data.ok !== void 0) return data;
173
+ return { ok: true, result: data };
174
+ } catch (err) {
175
+ return { ok: false, result: null, error: err instanceof Error ? err.message : String(err) };
176
+ }
177
+ }
178
+ async function mcpFetch(body) {
179
+ const res = await apiFetchRaw("/mcp", {
180
+ method: "POST",
181
+ headers: { "Content-Type": "application/json" },
182
+ body: typeof body === "string" ? body : JSON.stringify(body)
183
+ });
184
+ if (!res.ok) {
185
+ const text = await res.text();
186
+ throw new Error(`MCP fetch failed: HTTP ${res.status}: ${text}`);
187
+ }
188
+ return res.json();
189
+ }
190
+ async function mcpToolCall(toolName, args = {}) {
191
+ return mcpFetch({
192
+ jsonrpc: "2.0",
193
+ id: Date.now(),
194
+ method: "tools/call",
195
+ params: { name: toolName, arguments: args }
196
+ });
197
+ }
198
+ async function postCommandResult(correlationId, result, error) {
199
+ await apiFetchRaw("/api/plugin-command-result", {
200
+ method: "POST",
201
+ headers: { "Content-Type": "application/json" },
202
+ body: JSON.stringify({ correlationId, result, error })
203
+ });
204
+ }
205
+ function unwrapMcpResult(result) {
206
+ if (!result) return result;
207
+ if (!result.content) return result;
208
+ const textItem = Array.isArray(result.content) ? result.content.find((c) => c.type === "text") : null;
209
+ if (!textItem?.text) return result;
210
+ try {
211
+ return JSON.parse(textItem.text);
212
+ } catch {
213
+ return textItem.text;
214
+ }
215
+ }
216
+
217
+ // src/chat/store.ts
218
+ var import_zustand = require("zustand");
219
+
220
+ // src/utils/emitter.ts
221
+ var RECENT_TTL_MS = 1e4;
222
+ var RECENT_MAX = 20;
223
+ var PluginCommandEmitter = class {
224
+ constructor() {
225
+ this.listeners = /* @__PURE__ */ new Map();
226
+ // Ring buffer of plugin commands seen recently. Lets a plugin that mounts
227
+ // AFTER its first command was emitted (e.g. a surface mounted by the
228
+ // dispatcher in response to focus-plugin) still pick that command up via
229
+ // `drainPluginCommands(pluginId)`. Self-cleans on TTL + size cap.
230
+ this.recent = [];
231
+ }
232
+ on(event, fn) {
233
+ if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
234
+ this.listeners.get(event).add(fn);
235
+ return () => this.off(event, fn);
236
+ }
237
+ off(event, fn) {
238
+ this.listeners.get(event)?.delete(fn);
239
+ }
240
+ emit(event, data) {
241
+ if (event === "plugin-command" && data && data.pluginId && data.command && data.command !== "__focus") {
242
+ this.recent.push({
243
+ pluginId: data.pluginId,
244
+ command: data.command,
245
+ args: data.args,
246
+ correlationId: data.correlationId,
247
+ _bufferedAt: Date.now()
248
+ });
249
+ if (this.recent.length > RECENT_MAX) this.recent.shift();
250
+ }
251
+ this.listeners.get(event)?.forEach((fn) => {
252
+ try {
253
+ fn(data);
254
+ } catch {
255
+ }
256
+ });
257
+ }
258
+ /** Pop and return any plugin-commands targeted at `pluginId` that were
259
+ * emitted within the last RECENT_TTL_MS. Used by the native bridge on
260
+ * mount so the surface-dispatcher path doesn't drop the first command. */
261
+ drainPluginCommands(pluginId) {
262
+ const now = Date.now();
263
+ const out = [];
264
+ const remaining = [];
265
+ for (const cmd of this.recent) {
266
+ if (now - cmd._bufferedAt > RECENT_TTL_MS) continue;
267
+ if (cmd.pluginId === pluginId) out.push(cmd);
268
+ else remaining.push(cmd);
269
+ }
270
+ this.recent = remaining;
271
+ return out;
272
+ }
273
+ };
274
+ var pluginCommandEmitter = new PluginCommandEmitter();
275
+
276
+ // src/chat/store.ts
277
+ function coreHeaders() {
278
+ const token = getToken();
279
+ const headers = {
280
+ "Content-Type": "application/json"
281
+ };
282
+ if (token) headers["Authorization"] = `Bearer ${token}`;
283
+ return headers;
284
+ }
285
+ function isTerminalStatus(status) {
286
+ const s = typeof status === "string" ? status.toLowerCase() : "";
287
+ return s === "done" || s === "error" || s === "failed" || s === "completed";
288
+ }
289
+ function normalizeJobStatus(snap) {
290
+ const status = typeof snap?.status === "string" ? snap.status.toLowerCase() : "";
291
+ if (status === "done" || status === "completed") return "done";
292
+ if (status === "error" || status === "failed") return "error";
293
+ if (status === "paused" || status === "waiting") return "paused";
294
+ if (status === "unknown") return "unknown";
295
+ return "running";
296
+ }
297
+ function extractPendingQuestion(snap) {
298
+ return snap?.pendingQuestion || snap?.state?.pendingQuestion || snap?.state?.waiting || null;
299
+ }
300
+ function extractActorId(snap) {
301
+ const actorId = snap?.actorId || snap?.actor?.actorId || snap?.actor?.id || snap?.state?.actorId || snap?.state?.actor?.actorId || snap?.state?.actor?.id;
302
+ return typeof actorId === "string" && actorId.length > 0 ? actorId : void 0;
303
+ }
304
+ function extractLiveProgress(snap) {
305
+ const lp = snap?.liveProgress || snap?.state?.__live_progress;
306
+ const latest = Array.isArray(lp) ? lp[lp.length - 1] : lp;
307
+ if (!latest?.message) return void 0;
308
+ return {
309
+ message: String(latest.message || ""),
310
+ ...latest.step != null ? { step: latest.step } : {},
311
+ ...latest.total != null ? { total: latest.total } : {}
312
+ };
313
+ }
314
+ function extractCurrentTool(state) {
315
+ const exec = state?.steps?.__exec;
316
+ if (!exec || typeof exec !== "object") return void 0;
317
+ const latest = Object.keys(exec).map((k) => ({ k, n: parseInt(k.replace("iter_", ""), 10) })).filter(({ n }) => !isNaN(n)).sort((a, b) => b.n - a.n)[0];
318
+ if (!latest) return void 0;
319
+ const steps = exec[latest.k];
320
+ if (!Array.isArray(steps) || !steps.length) return void 0;
321
+ const last = steps[steps.length - 1];
322
+ return last?.tool || last?.phase;
323
+ }
324
+ function snapshotToProgress(snap, rootJobId) {
325
+ const pq = extractPendingQuestion(snap);
326
+ const progress = {
327
+ jobId: rootJobId,
328
+ actorId: extractActorId(snap),
329
+ status: pq ? "paused" : normalizeJobStatus(snap),
330
+ subStatus: snap?.subStatus || snap?.phase || snap?.state?.visual?.phase,
331
+ phase: snap?.phase || snap?.state?.visual?.phase,
332
+ iteration: snap?.iteration || snap?.state?.plannerIter,
333
+ skillSlug: snap?.skillSlug || snap?.activeSkill,
334
+ currentTool: snap?.currentTool || snap?.lastTool || extractCurrentTool(snap?.state)
335
+ };
336
+ const liveProgress = extractLiveProgress(snap);
337
+ if (liveProgress) progress.liveProgress = liveProgress;
338
+ if (pq) {
339
+ progress.pendingQuestion = {
340
+ inputId: pq.correlation_id || pq.correlationId || pq.inputId || "",
341
+ question: pq.prompt_message || pq.question || "The assistant needs your input",
342
+ inputType: pq.inputType || "text",
343
+ options: pq.options || pq.requested_fields,
344
+ placeholder: pq.placeholder
345
+ };
346
+ }
347
+ return progress;
348
+ }
349
+ function isTerminalSnapshot(snap) {
350
+ if (extractPendingQuestion(snap)) return false;
351
+ return snap?.done === true || isTerminalStatus(snap?.status);
352
+ }
353
+ var TABLE_BLOCK_OPEN = "\uE100";
354
+ var TABLE_BLOCK_CLOSE = "\uE101";
355
+ function renderTableShape(content) {
356
+ if (!content || typeof content !== "object") return null;
357
+ const cols = Array.isArray(content.columns) ? content.columns : null;
358
+ const rows = Array.isArray(content.rows) ? content.rows : null;
359
+ if (!cols || !rows) return null;
360
+ const payload = encodeURIComponent(JSON.stringify({ columns: cols, rows }));
361
+ return `${TABLE_BLOCK_OPEN}${payload}${TABLE_BLOCK_CLOSE}`;
362
+ }
363
+ function blocksToText(blocks) {
364
+ if (!blocks || blocks.length === 0) return "";
365
+ return blocks.map((b) => {
366
+ if (b.kind === "text" || b.kind === "markdown" || b.kind === "description") {
367
+ return typeof b.content === "string" ? b.content : "";
368
+ }
369
+ if (b.kind === "code") {
370
+ const code = typeof b.content === "object" ? b.content.content : b.content;
371
+ return `\`\`\`
372
+ ${code}
373
+ \`\`\``;
374
+ }
375
+ if (b.kind === "table" && b.content && typeof b.content === "object") {
376
+ const md = renderTableShape(b.content);
377
+ if (md) return md;
378
+ }
379
+ if (b.kind === "json") {
380
+ if (b.content && typeof b.content === "object" && "pendingQuestion" in b.content) return "";
381
+ const md = renderTableShape(b.content);
382
+ if (md) return md;
383
+ return "```json\n" + JSON.stringify(b.content, null, 2) + "\n```";
384
+ }
385
+ if (b.kind === "html") {
386
+ return typeof b.content === "string" ? b.content : "";
387
+ }
388
+ return typeof b.content === "string" ? b.content : JSON.stringify(b.content);
389
+ }).filter(Boolean).join("\n\n");
390
+ }
391
+ function blocksToAttachments(blocks) {
392
+ if (!blocks || blocks.length === 0) return void 0;
393
+ const atts = [];
394
+ for (const b of blocks) {
395
+ if ((b.kind === "image" || b.kind === "file") && (b.url || b.hash)) {
396
+ atts.push({
397
+ mimeType: b.mimeType || (b.kind === "image" ? "image/jpeg" : "application/octet-stream"),
398
+ name: b.name || "attachment",
399
+ previewUri: b.url ? `${getCoreApiUrl()}${b.url}` : void 0
400
+ });
401
+ }
402
+ }
403
+ return atts.length > 0 ? atts : void 0;
404
+ }
405
+ var useChatStore = (0, import_zustand.create)((set, get) => ({
406
+ messages: [],
407
+ loading: false,
408
+ error: null,
409
+ activeJobId: null,
410
+ conversationLoaded: false,
411
+ // -----------------------------------------------------------------------
412
+ // Load conversation history from Core
413
+ // GET /api/conversation → array of turns [{user, assistant, jobId, ...}]
414
+ // -----------------------------------------------------------------------
415
+ loadConversation: async () => {
416
+ try {
417
+ const attachmentCache = getConfig().attachmentCache;
418
+ await attachmentCache?.ready();
419
+ const res = await fetch(`${getCoreApiUrl()}/api/conversation?limit=20`, {
420
+ headers: coreHeaders()
421
+ });
422
+ if (!res.ok) {
423
+ console.warn("[adas-chat] Failed to load conversation:", res.status);
424
+ set({ conversationLoaded: true });
425
+ return;
426
+ }
427
+ const data = await res.json();
428
+ if (!data.ok || !data.items) {
429
+ set({ conversationLoaded: true });
430
+ return;
431
+ }
432
+ const items = [...data.items].reverse();
433
+ const persistedMessages = [];
434
+ const seenJobIds = /* @__PURE__ */ new Set();
435
+ for (const turn of items) {
436
+ if (turn.jobId && seenJobIds.has(turn.jobId)) continue;
437
+ if (turn.jobId) seenJobIds.add(turn.jobId);
438
+ const userText = blocksToText(turn.user?.blocks);
439
+ let userAtts = blocksToAttachments(turn.user?.blocks);
440
+ if (!userAtts && turn.jobId) {
441
+ userAtts = attachmentCache?.get(turn.jobId);
442
+ }
443
+ const turnChainId = typeof turn.chainId === "string" ? turn.chainId : void 0;
444
+ if (userText || userAtts) {
445
+ persistedMessages.push({
446
+ id: `user-${turn.jobId}`,
447
+ role: "user",
448
+ content: userText || "",
449
+ timestamp: new Date(turn.startedAt || Date.now()),
450
+ skillSlug: turn.skillSlug,
451
+ ...userAtts ? { attachments: userAtts } : {},
452
+ ...turnChainId ? { chainId: turnChainId } : {}
453
+ });
454
+ }
455
+ const assistantText = blocksToText(turn.assistant?.blocks);
456
+ const assistantAtts = blocksToAttachments(turn.assistant?.blocks);
457
+ const assistantActions = Array.isArray(turn.assistant?.actions) ? turn.assistant.actions : void 0;
458
+ const turnKind = typeof turn.kind === "string" ? turn.kind : void 0;
459
+ if (assistantText || assistantAtts || assistantActions?.length) {
460
+ persistedMessages.push({
461
+ id: `bot-${turn.jobId}`,
462
+ role: "assistant",
463
+ content: assistantText || "",
464
+ timestamp: new Date(turn.finishedAt || turn.startedAt || Date.now()),
465
+ skillSlug: turn.skillSlug,
466
+ ...assistantAtts ? { attachments: assistantAtts } : {},
467
+ ...assistantActions?.length ? { actions: assistantActions } : {},
468
+ ...turnChainId ? { chainId: turnChainId } : {},
469
+ ...turnKind ? { kind: turnKind } : {}
470
+ });
471
+ }
472
+ }
473
+ const current = get().messages;
474
+ const keptPlaceholders = [];
475
+ for (const m of current) {
476
+ if (!m.id.startsWith("job-") || !m.jobProgress || !m.chainId) continue;
477
+ const matchIdx = persistedMessages.findIndex(
478
+ (pm) => pm.role === "assistant" && pm.chainId === m.chainId
479
+ );
480
+ if (matchIdx >= 0) {
481
+ const st = m.jobProgress.status;
482
+ if (st === "running" || st === "paused") {
483
+ persistedMessages[matchIdx] = { ...persistedMessages[matchIdx], jobProgress: m.jobProgress };
484
+ }
485
+ } else {
486
+ keptPlaceholders.push(m);
487
+ }
488
+ }
489
+ const isLocalUserId = (id) => /^user-\d+$/.test(id);
490
+ const keptUserBubbles = current.filter((m) => {
491
+ if (m.role !== "user" || !isLocalUserId(m.id)) return false;
492
+ if (!m.chainId) return true;
493
+ return !persistedMessages.some(
494
+ (pm) => pm.role === "user" && pm.chainId === m.chainId
495
+ );
496
+ });
497
+ const finalMessages = [...persistedMessages, ...keptUserBubbles, ...keptPlaceholders];
498
+ finalMessages.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
499
+ let firstRunning = keptPlaceholders.find((m) => m.jobProgress?.status === "running");
500
+ try {
501
+ const chainRes = await fetch(`${getCoreApiUrl()}/api/chain/current`, {
502
+ headers: coreHeaders()
503
+ });
504
+ if (chainRes.ok) {
505
+ const chainBody = await chainRes.json();
506
+ const slim = chainBody?.data;
507
+ if (slim?.id) {
508
+ const chainId = String(slim.id);
509
+ const alreadyTracked = finalMessages.some(
510
+ (m) => m.role === "assistant" && m.jobProgress?.jobId === chainId
511
+ );
512
+ if (!alreadyTracked) {
513
+ const progress = snapshotToProgress(slim, chainId);
514
+ const chainCreatedAt = typeof slim.createdAt === "number" ? slim.createdAt : typeof slim.lastUpdate === "number" ? slim.lastUpdate : Date.now();
515
+ const chainLastUpdate = typeof slim.lastUpdate === "number" ? slim.lastUpdate : chainCreatedAt;
516
+ const jobProgress = {
517
+ jobId: chainId,
518
+ actorId: extractActorId(slim),
519
+ status: "running",
520
+ chainStartedAt: chainCreatedAt,
521
+ ...progress
522
+ };
523
+ const goal = typeof slim.goal === "string" ? slim.goal : "";
524
+ const userExists = finalMessages.some(
525
+ (m) => m.role === "user" && (m.chainId === chainId || goal && m.content === goal)
526
+ );
527
+ if (goal && !userExists) {
528
+ finalMessages.push({
529
+ id: `user-${chainId}`,
530
+ role: "user",
531
+ content: goal,
532
+ timestamp: new Date(chainCreatedAt),
533
+ chainId
534
+ });
535
+ }
536
+ let matchIdx = -1;
537
+ for (let i = finalMessages.length - 1; i >= 0; i--) {
538
+ const m = finalMessages[i];
539
+ if (m.role === "assistant" && m.chainId === chainId) {
540
+ matchIdx = i;
541
+ break;
542
+ }
543
+ }
544
+ if (matchIdx >= 0) {
545
+ finalMessages[matchIdx] = { ...finalMessages[matchIdx], jobProgress };
546
+ if (jobProgress.status === "running") firstRunning = finalMessages[matchIdx];
547
+ } else {
548
+ const reattachBubble = {
549
+ id: `job-${chainId}`,
550
+ role: "assistant",
551
+ content: "Working on it...",
552
+ timestamp: new Date(chainLastUpdate),
553
+ jobProgress,
554
+ chainId
555
+ };
556
+ finalMessages.push(reattachBubble);
557
+ if (jobProgress.status === "running") firstRunning = reattachBubble;
558
+ }
559
+ finalMessages.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
560
+ streamJob(chainId, getTenant(), getToken());
561
+ }
562
+ }
563
+ }
564
+ } catch {
565
+ }
566
+ set({
567
+ messages: finalMessages,
568
+ conversationLoaded: true,
569
+ error: null,
570
+ activeJobId: firstRunning?.jobProgress?.jobId || null,
571
+ loading: !!firstRunning
572
+ });
573
+ } catch (err) {
574
+ console.warn("[adas-chat] Error loading conversation:", err.message);
575
+ set({ conversationLoaded: true });
576
+ }
577
+ },
578
+ // -----------------------------------------------------------------------
579
+ // Send message via Core API
580
+ // POST /api/chat { goal } → { jobId } → stream → fetch turn result
581
+ // -----------------------------------------------------------------------
582
+ sendMessage: async (text, skillSlug, attachments) => {
583
+ const userMsg = {
584
+ id: `user-${Date.now()}`,
585
+ role: "user",
586
+ content: text,
587
+ timestamp: /* @__PURE__ */ new Date(),
588
+ ...attachments?.length ? {
589
+ attachments: attachments.map((a) => ({
590
+ mimeType: a.mimeType,
591
+ name: a.name,
592
+ previewUri: a.mimeType.startsWith("image/") ? `data:${a.mimeType};base64,${a.data}` : void 0
593
+ }))
594
+ } : {}
595
+ };
596
+ set((s) => ({ messages: [...s.messages, userMsg], loading: true, error: null }));
597
+ try {
598
+ const actor = getUser();
599
+ const body = {
600
+ goal: text,
601
+ // Opt in to the Chain Additions router (attach/cancel/new while a
602
+ // chain runs). Backend ignores it where not applicable — safe to send.
603
+ chainRouting: true,
604
+ triggerContext: {
605
+ channel: "mobile",
606
+ replyContext: { chatId: actor?.actorId || "unknown" }
607
+ }
608
+ };
609
+ if (skillSlug) body.skillSlug = skillSlug;
610
+ if (attachments?.length) body.attachments = attachments;
611
+ const res = await fetch(`${getCoreApiUrl()}/api/chat`, {
612
+ method: "POST",
613
+ headers: coreHeaders(),
614
+ body: JSON.stringify(body)
615
+ });
616
+ if (!res.ok) {
617
+ let errMsg = `Server error (${res.status})`;
618
+ try {
619
+ const text2 = await res.text();
620
+ if (text2.startsWith("<") || text2.startsWith("<!")) {
621
+ errMsg = res.status === 502 ? "Server is temporarily unavailable. Please try again." : `Server error (${res.status})`;
622
+ } else {
623
+ const parsed = JSON.parse(text2);
624
+ errMsg = parsed.error || errMsg;
625
+ }
626
+ } catch {
627
+ }
628
+ throw new Error(errMsg);
629
+ }
630
+ const data = await res.json();
631
+ if (!data.ok) {
632
+ throw new Error(data.error || "Failed to send message");
633
+ }
634
+ if (data.mode === "chain_addition" || data.mode === "chain_cancel") {
635
+ const ack = data.mode === "chain_addition" ? data.ack || "Got it \u2014 added to the running task." : "Stopping the task\u2026";
636
+ set((s) => ({
637
+ messages: s.messages.map((m) => m.id === userMsg.id && data.chainId ? { ...m, chainId: data.chainId } : m).concat({
638
+ id: `ack-${Date.now()}`,
639
+ role: "assistant",
640
+ content: ack,
641
+ timestamp: /* @__PURE__ */ new Date(),
642
+ ...data.chainId ? { chainId: data.chainId } : {}
643
+ })
644
+ }));
645
+ return;
646
+ }
647
+ const jobId = data.id || data.jobId;
648
+ if (!jobId) {
649
+ throw new Error("No job ID returned");
650
+ }
651
+ if (attachments?.length) {
652
+ await getConfig().attachmentCache?.save(jobId, attachments);
653
+ }
654
+ const progressMsg = {
655
+ id: `job-${jobId}`,
656
+ role: "assistant",
657
+ content: "Working on it...",
658
+ timestamp: /* @__PURE__ */ new Date(),
659
+ jobProgress: {
660
+ jobId,
661
+ actorId: actor?.actorId,
662
+ status: "running",
663
+ subStatus: "Starting...",
664
+ chainStartedAt: Date.now()
665
+ },
666
+ chainId: jobId
667
+ };
668
+ set((s) => ({
669
+ messages: s.messages.map(
670
+ (m) => m.id === userMsg.id ? { ...m, chainId: jobId } : m
671
+ ).concat(progressMsg),
672
+ activeJobId: jobId
673
+ }));
674
+ streamJob(jobId, getTenant(), getToken());
675
+ } catch (err) {
676
+ set((s) => ({
677
+ messages: [...s.messages, {
678
+ id: `error-${Date.now()}`,
679
+ role: "system",
680
+ content: `Error: ${err.message}`,
681
+ timestamp: /* @__PURE__ */ new Date()
682
+ }],
683
+ loading: false
684
+ }));
685
+ }
686
+ },
687
+ // -----------------------------------------------------------------------
688
+ // Respond to sys.askUser paused question
689
+ // -----------------------------------------------------------------------
690
+ respondToQuestion: async (jobId, inputId, answer) => {
691
+ try {
692
+ const res = await fetch(`${getCoreApiUrl()}/api/job/${jobId}/respond`, {
693
+ method: "POST",
694
+ headers: coreHeaders(),
695
+ body: JSON.stringify({ inputId, answer })
696
+ });
697
+ const data = await res.json();
698
+ if (!data.ok) {
699
+ console.warn("[adas-chat] Failed to respond to question:", data.error);
700
+ }
701
+ } catch (err) {
702
+ console.warn("[adas-chat] Error responding to question:", err.message);
703
+ }
704
+ },
705
+ // -----------------------------------------------------------------------
706
+ // Update job progress (called by the stream handler)
707
+ // -----------------------------------------------------------------------
708
+ updateJobProgress: (jobId, progress) => {
709
+ set((s) => ({
710
+ messages: s.messages.map((m) => {
711
+ if (m.jobProgress?.jobId === jobId) {
712
+ const updated = { ...m.jobProgress, ...progress };
713
+ if (progress.status && progress.status !== "paused") {
714
+ updated.pendingQuestion = void 0;
715
+ }
716
+ const isPersisted = m.id.startsWith("bot-");
717
+ const content = isPersisted ? m.content : progress.resultMessage || progress.pendingQuestion?.question || m.content;
718
+ const actions = isPersisted ? m.actions : progress.resultActions?.length ? progress.resultActions : m.actions;
719
+ return {
720
+ ...m,
721
+ jobProgress: updated,
722
+ content,
723
+ ...actions ? { actions } : {}
724
+ };
725
+ }
726
+ return m;
727
+ }),
728
+ loading: progress.status === "done" || progress.status === "error" || progress.status === "paused" ? false : s.loading,
729
+ activeJobId: progress.status === "done" || progress.status === "error" || progress.status === "paused" ? null : s.activeJobId
730
+ }));
731
+ },
732
+ clearChat: () => {
733
+ set({
734
+ messages: [],
735
+ loading: false,
736
+ error: null,
737
+ activeJobId: null,
738
+ conversationLoaded: false
739
+ });
740
+ },
741
+ newChat: () => {
742
+ set({
743
+ messages: [],
744
+ loading: false,
745
+ error: null,
746
+ activeJobId: null,
747
+ conversationLoaded: true
748
+ });
749
+ }
750
+ }));
751
+ var _processedPluginCmds = /* @__PURE__ */ new Set();
752
+ function dispatchPluginCommands(cmds) {
753
+ for (const cmd of cmds) {
754
+ const corrId = cmd?.correlationId || cmd?.correlation_id;
755
+ if (corrId && _processedPluginCmds.has(corrId)) continue;
756
+ if (corrId) _processedPluginCmds.add(corrId);
757
+ pluginCommandEmitter.emit("plugin-command", cmd);
758
+ const pluginId = cmd?.pluginId || cmd?.plugin_id || cmd?.args?.plugin_id;
759
+ if (pluginId) {
760
+ const props = cmd?.props ?? cmd?.args?.props ?? void 0;
761
+ pluginCommandEmitter.emit("focus-plugin", { pluginId, props });
762
+ }
763
+ }
764
+ }
765
+ var _ws = null;
766
+ var _wsReady = false;
767
+ var _currentJobId = null;
768
+ function ensureWs(tenant, token) {
769
+ if (_ws && _wsReady) return _ws;
770
+ const wsUrl = `${getCoreWsUrl()}/ws?token=${encodeURIComponent(token || "")}&tenant=${encodeURIComponent(tenant)}`;
771
+ const ws = new WebSocket(wsUrl);
772
+ _ws = ws;
773
+ _wsReady = false;
774
+ ws.onopen = () => {
775
+ _wsReady = true;
776
+ if (_currentJobId) {
777
+ ws.send(JSON.stringify({ type: "subscribe", jobId: _currentJobId }));
778
+ }
779
+ };
780
+ ws.onmessage = (ev) => {
781
+ try {
782
+ const msg = JSON.parse(typeof ev.data === "string" ? ev.data : "");
783
+ const { event, data } = msg;
784
+ const { updateJobProgress } = useChatStore.getState();
785
+ const jobId = data && (data.id || data.chainId) || _currentJobId;
786
+ if (event === "plugin_command") {
787
+ dispatchPluginCommands([data]);
788
+ return;
789
+ }
790
+ if (event === "plugin-command-ack" || event === "pong") return;
791
+ if (!jobId) return;
792
+ switch (event) {
793
+ case "init":
794
+ case "job":
795
+ case "update": {
796
+ const snap = data;
797
+ const progress = snapshotToProgress(snap, jobId);
798
+ if (isTerminalSnapshot(snap)) {
799
+ if (snap.status === "done" && (snap.isHandoff || snap.state?.handoffTransfer)) {
800
+ updateJobProgress(jobId, { status: "running" });
801
+ return;
802
+ }
803
+ _currentJobId = null;
804
+ if (progress.status === "paused" && progress.pendingQuestion) {
805
+ updateJobProgress(jobId, progress);
806
+ } else {
807
+ fetchTurnResult(jobId, snap);
808
+ }
809
+ return;
810
+ }
811
+ updateJobProgress(jobId, progress);
812
+ break;
813
+ }
814
+ case "chain_final":
815
+ _currentJobId = null;
816
+ fetchTurnResult(jobId, data);
817
+ break;
818
+ case "final":
819
+ _currentJobId = null;
820
+ fetchTurnResult(jobId, data);
821
+ break;
822
+ }
823
+ } catch (err) {
824
+ console.warn("[adas-chat] WS parse error:", err);
825
+ }
826
+ };
827
+ ws.onerror = () => {
828
+ _ws = null;
829
+ _wsReady = false;
830
+ if (_currentJobId) {
831
+ const jid = _currentJobId;
832
+ _currentJobId = null;
833
+ pollJob(jid, token);
834
+ }
835
+ };
836
+ ws.onclose = () => {
837
+ _ws = null;
838
+ _wsReady = false;
839
+ if (_currentJobId) {
840
+ const jid = _currentJobId;
841
+ _currentJobId = null;
842
+ pollJob(jid, token);
843
+ }
844
+ };
845
+ return ws;
846
+ }
847
+ function streamJob(jobId, tenant, token) {
848
+ _currentJobId = jobId;
849
+ const ws = ensureWs(tenant, token);
850
+ if (_wsReady) {
851
+ ws.send(JSON.stringify({ type: "subscribe", jobId }));
852
+ }
853
+ }
854
+ function closeChatStream() {
855
+ _currentJobId = null;
856
+ if (_ws) {
857
+ try {
858
+ _ws.close();
859
+ } catch {
860
+ }
861
+ }
862
+ _ws = null;
863
+ _wsReady = false;
864
+ }
865
+ async function fetchTurnResult(jobId, jobSnap) {
866
+ if (_processedPluginCmds.size > 200) _processedPluginCmds.clear();
867
+ const { updateJobProgress } = useChatStore.getState();
868
+ const status = normalizeJobStatus(jobSnap) === "error" ? "error" : "done";
869
+ if (jobSnap.isHandoff || jobSnap.state?.handoffTransfer || jobSnap.result?.contentType === "handoff") {
870
+ try {
871
+ for (let i = 0; i < 40; i++) {
872
+ const statusRes = await fetch(`${getCoreApiUrl()}/api/job/${jobId}/status`, {
873
+ headers: coreHeaders()
874
+ });
875
+ if (statusRes.ok) {
876
+ const statusData = await statusRes.json();
877
+ if (statusData.done && !statusData.isHandoff) {
878
+ break;
879
+ }
880
+ updateJobProgress(jobId, { status: "running" });
881
+ }
882
+ await new Promise((r) => setTimeout(r, 1500));
883
+ }
884
+ } catch {
885
+ }
886
+ }
887
+ for (let attempt = 0; attempt < 5; attempt++) {
888
+ try {
889
+ const res = await fetch(`${getCoreApiUrl()}/api/conversation?limit=1`, {
890
+ headers: coreHeaders()
891
+ });
892
+ if (res.ok) {
893
+ const data = await res.json();
894
+ const items = Array.isArray(data.items) ? data.items : [];
895
+ const turn = items[0];
896
+ if (turn?.assistant || turn?.turn?.assistant) {
897
+ const assistant = turn.assistant || turn.turn?.assistant;
898
+ const text = blocksToText(assistant.blocks);
899
+ const actions = Array.isArray(assistant.actions) && assistant.actions.length ? assistant.actions : void 0;
900
+ if (text) {
901
+ updateJobProgress(jobId, {
902
+ status,
903
+ resultMessage: text,
904
+ ...actions ? { resultActions: actions } : {}
905
+ });
906
+ return;
907
+ }
908
+ }
909
+ }
910
+ } catch {
911
+ }
912
+ await new Promise((r) => setTimeout(r, 800));
913
+ }
914
+ const fallback = jobSnap.result?.message || jobSnap.state?.finalReply || (status === "error" ? "Something went wrong. Please try again." : "Done.");
915
+ updateJobProgress(jobId, { status, resultMessage: fallback });
916
+ }
917
+ async function pollJob(jobId, token) {
918
+ const { updateJobProgress } = useChatStore.getState();
919
+ for (let i = 0; i < 120; i++) {
920
+ try {
921
+ const res = await fetch(`${getCoreApiUrl()}/api/job/${jobId}/status`, {
922
+ headers: {
923
+ ...token ? { Authorization: `Bearer ${token}` } : {}
924
+ }
925
+ });
926
+ if (!res.ok) {
927
+ if (res.status === 404) return;
928
+ continue;
929
+ }
930
+ const snap = await res.json();
931
+ const progress = snapshotToProgress(snap, jobId);
932
+ if (progress.status === "paused" && progress.pendingQuestion) {
933
+ updateJobProgress(jobId, progress);
934
+ return;
935
+ }
936
+ updateJobProgress(jobId, progress);
937
+ if (isTerminalSnapshot(snap)) {
938
+ fetchTurnResult(jobId, snap);
939
+ return;
940
+ }
941
+ } catch {
942
+ }
943
+ await new Promise((r) => setTimeout(r, 1500));
944
+ }
945
+ updateJobProgress(jobId, {
946
+ status: "error",
947
+ resultMessage: "Job timed out. Please try again."
948
+ });
949
+ }
950
+
951
+ // src/theme/tokens.ts
952
+ var colors = {
953
+ dark: {
954
+ bgPrimary: "#0a0e14",
955
+ bgSecondary: "#111820",
956
+ bgTertiary: "#1a2230",
957
+ bgInput: "#0d1219",
958
+ bgHover: "#1e2a3a",
959
+ border: "rgba(255, 255, 255, 0.08)",
960
+ borderHover: "rgba(255, 255, 255, 0.15)",
961
+ textPrimary: "#e8ecf1",
962
+ textSecondary: "#8b95a5",
963
+ textMuted: "#5a6577",
964
+ accent: "#2563eb",
965
+ accentHover: "#3b82f6",
966
+ accentSoft: "rgba(37, 99, 235, 0.15)",
967
+ green: "#16a34a",
968
+ greenSoft: "rgba(22, 163, 74, 0.15)",
969
+ red: "#dc2626",
970
+ redSoft: "rgba(220, 38, 38, 0.15)",
971
+ amber: "#f59e0b",
972
+ amberSoft: "rgba(245, 158, 11, 0.15)",
973
+ purple: "#8b5cf6",
974
+ purpleSoft: "rgba(139, 92, 246, 0.15)",
975
+ // Message-specific
976
+ userBubble: "#2563eb",
977
+ botBubble: "#1a2230",
978
+ botBubbleBorder: "rgba(255, 255, 255, 0.08)"
979
+ },
980
+ light: {
981
+ bgPrimary: "#ffffff",
982
+ bgSecondary: "#f8f9fa",
983
+ bgTertiary: "#f1f3f5",
984
+ bgInput: "#ffffff",
985
+ bgHover: "#e9ecef",
986
+ border: "rgba(0, 0, 0, 0.08)",
987
+ borderHover: "rgba(0, 0, 0, 0.15)",
988
+ textPrimary: "#1a1a2e",
989
+ textSecondary: "#6b7280",
990
+ textMuted: "#9ca3af",
991
+ accent: "#2563eb",
992
+ accentHover: "#1d4ed8",
993
+ accentSoft: "rgba(37, 99, 235, 0.1)",
994
+ green: "#16a34a",
995
+ greenSoft: "rgba(22, 163, 74, 0.1)",
996
+ red: "#dc2626",
997
+ redSoft: "rgba(220, 38, 38, 0.1)",
998
+ amber: "#f59e0b",
999
+ amberSoft: "rgba(245, 158, 11, 0.1)",
1000
+ purple: "#8b5cf6",
1001
+ purpleSoft: "rgba(139, 92, 246, 0.1)",
1002
+ userBubble: "#2563eb",
1003
+ botBubble: "#f1f3f5",
1004
+ botBubbleBorder: "rgba(0, 0, 0, 0.08)"
1005
+ }
1006
+ };
1007
+ var ada = {
1008
+ paper: "#FAF6EE",
1009
+ paperSoft: "#F2EADC",
1010
+ paperDeep: "#EBE0CC",
1011
+ ink: "#1a1410",
1012
+ inkSoft: "#5a4d3f",
1013
+ inkMuted: "#9a8870",
1014
+ border: "rgba(26, 20, 16, 0.08)",
1015
+ borderStrong: "rgba(26, 20, 16, 0.16)",
1016
+ orange: "#FF7A28",
1017
+ orangeDeep: "#B8410A",
1018
+ orangeSoft: "rgba(255, 122, 40, 0.14)",
1019
+ userBubble: "#1a1410",
1020
+ userBubbleText: "#FAF6EE",
1021
+ botBubble: "#F2EADC",
1022
+ botBubbleBorder: "rgba(26, 20, 16, 0.06)",
1023
+ botBubbleText: "#1a1410"
1024
+ };
1025
+ var spacing = {
1026
+ xs: 4,
1027
+ sm: 8,
1028
+ md: 12,
1029
+ lg: 16,
1030
+ xl: 20,
1031
+ xxl: 24,
1032
+ xxxl: 32
1033
+ };
1034
+ var fontSize = {
1035
+ xs: 11,
1036
+ sm: 13,
1037
+ md: 15,
1038
+ lg: 17,
1039
+ xl: 20,
1040
+ xxl: 24,
1041
+ title: 28
1042
+ };
1043
+ var borderRadius = {
1044
+ sm: 8,
1045
+ md: 12,
1046
+ lg: 16,
1047
+ xl: 20,
1048
+ full: 9999
1049
+ };
1050
+ // Annotate the CommonJS export names for ESM import in node:
1051
+ 0 && (module.exports = {
1052
+ PluginCommandEmitter,
1053
+ TABLE_BLOCK_CLOSE,
1054
+ TABLE_BLOCK_OPEN,
1055
+ ada,
1056
+ apiFetch,
1057
+ apiFetchRaw,
1058
+ blocksToText,
1059
+ borderRadius,
1060
+ closeChatStream,
1061
+ colors,
1062
+ connectorCall,
1063
+ fontSize,
1064
+ getConfig,
1065
+ getCoreApiUrl,
1066
+ getCoreWsUrl,
1067
+ getTenant,
1068
+ getToken,
1069
+ getUser,
1070
+ getVoiceApiUrl,
1071
+ initAdas,
1072
+ isAdasInitialized,
1073
+ mcpFetch,
1074
+ mcpToolCall,
1075
+ pluginCommandEmitter,
1076
+ postCommandResult,
1077
+ spacing,
1078
+ unwrapMcpResult,
1079
+ useChatStore
1080
+ });
1081
+ //# sourceMappingURL=index.js.map