@ateam-ai-mobile/core 0.1.2 → 0.1.4
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.d.mts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +90 -60
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +89 -60
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/api/tenants.ts +16 -3
- package/src/chat/store.ts +97 -73
- package/src/chat/types.ts +6 -0
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ateam-ai-mobile/core",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Headless ADAS SDK core
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Headless ADAS SDK core — auth bridge, API client, chat/job streaming, MCP calls, theme tokens. No UI, no Expo dependencies.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
@@ -24,4 +24,4 @@
|
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|
|
26
26
|
}
|
|
27
|
-
}
|
|
27
|
+
}
|
package/src/api/tenants.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { apiFetch } from './client';
|
|
2
1
|
import { getConsumerAuthApiUrl, getToken, getConfig, type AdasSession } from '../config';
|
|
3
2
|
import { useChatStore, closeChatStream } from '../chat/store';
|
|
4
3
|
|
|
@@ -17,8 +16,22 @@ export interface TenantMembership {
|
|
|
17
16
|
* leaks the full tenant list. Returns [] when signed out or unknown.
|
|
18
17
|
*/
|
|
19
18
|
export async function listMyTenants(): Promise<TenantMembership[]> {
|
|
20
|
-
const
|
|
21
|
-
|
|
19
|
+
const token = getToken();
|
|
20
|
+
if (!token) return [];
|
|
21
|
+
try {
|
|
22
|
+
const res = await fetch(`${getConsumerAuthApiUrl()}/platform/consumer/my-tenants`, {
|
|
23
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
24
|
+
});
|
|
25
|
+
const data = await res.json().catch(() => null);
|
|
26
|
+
if (!res.ok || !data?.ok || !Array.isArray(data.tenants)) return [];
|
|
27
|
+
return data.tenants.map((t: any) => ({
|
|
28
|
+
id: t.id,
|
|
29
|
+
name: t.name || t.id,
|
|
30
|
+
role: t.role ?? null,
|
|
31
|
+
}));
|
|
32
|
+
} catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
22
35
|
}
|
|
23
36
|
|
|
24
37
|
/**
|
package/src/chat/store.ts
CHANGED
|
@@ -128,6 +128,10 @@ function snapshotToProgress(snap: any, rootJobId: string): Partial<JobProgress>
|
|
|
128
128
|
const liveProgress = extractLiveProgress(snap);
|
|
129
129
|
if (liveProgress) progress.liveProgress = liveProgress;
|
|
130
130
|
|
|
131
|
+
// Human-language status (additive; absent when disabled/uncached).
|
|
132
|
+
if (typeof snap?.humanStatus === 'string' && snap.humanStatus) progress.humanStatus = snap.humanStatus;
|
|
133
|
+
if (snap?.humanLabels && typeof snap.humanLabels === 'object') progress.humanLabels = snap.humanLabels;
|
|
134
|
+
|
|
131
135
|
if (pq) {
|
|
132
136
|
progress.pendingQuestion = {
|
|
133
137
|
inputId: pq.correlation_id || pq.correlationId || pq.inputId || '',
|
|
@@ -349,85 +353,105 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
349
353
|
|
|
350
354
|
let firstRunning: Message | undefined = keptPlaceholders.find(m => m.jobProgress?.status === 'running');
|
|
351
355
|
|
|
352
|
-
// Cold-start / post-refresh reattach:
|
|
353
|
-
// restart.
|
|
354
|
-
//
|
|
355
|
-
//
|
|
356
|
+
// Cold-start / post-refresh reattach: WS subscriptions die on app
|
|
357
|
+
// restart. Fetch ALL active chains for this actor (chain-native SlimChain
|
|
358
|
+
// API) and reattach EACH — multiple concurrent chains must all come back
|
|
359
|
+
// in progress, not just the most-recently-updated one. Falls back to the
|
|
360
|
+
// legacy single-chain /api/chain/current on older backends.
|
|
356
361
|
try {
|
|
357
|
-
|
|
362
|
+
let activeChains: any[] = [];
|
|
363
|
+
const chainsRes = await fetch(`${getCoreApiUrl()}/api/chains/active`, {
|
|
358
364
|
headers: coreHeaders(),
|
|
359
365
|
});
|
|
360
|
-
if (
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
// Re-open the live subscription.
|
|
427
|
-
streamJob(chainId, getTenant(), getToken());
|
|
366
|
+
if (chainsRes.ok) {
|
|
367
|
+
const body = await chainsRes.json();
|
|
368
|
+
activeChains = Array.isArray(body?.chains) ? body.chains : [];
|
|
369
|
+
} else {
|
|
370
|
+
// Legacy backend without the plural endpoint — single most-recent chain.
|
|
371
|
+
const legacy = await fetch(`${getCoreApiUrl()}/api/chain/current`, {
|
|
372
|
+
headers: coreHeaders(),
|
|
373
|
+
});
|
|
374
|
+
if (legacy.ok) {
|
|
375
|
+
const lb = await legacy.json();
|
|
376
|
+
if (lb?.data?.id) activeChains = [lb.data];
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
for (const slim of activeChains) {
|
|
381
|
+
if (!slim?.id) continue;
|
|
382
|
+
const chainId = String(slim.id);
|
|
383
|
+
const alreadyTracked = finalMessages.some(m =>
|
|
384
|
+
m.role === 'assistant' && m.jobProgress?.jobId === chainId
|
|
385
|
+
);
|
|
386
|
+
if (alreadyTracked) continue;
|
|
387
|
+
|
|
388
|
+
// Chain-native status: prefer the whole-chain aggregate (chainStatus)
|
|
389
|
+
// over the active job's per-job status, so e.g. a chain still running
|
|
390
|
+
// across a handoff isn't shown as "done". A pending question still
|
|
391
|
+
// overrides to "paused" inside snapshotToProgress.
|
|
392
|
+
const progress = snapshotToProgress(
|
|
393
|
+
{ ...slim, status: slim.chainStatus || slim.status },
|
|
394
|
+
chainId,
|
|
395
|
+
);
|
|
396
|
+
const chainCreatedAt = typeof slim.createdAt === 'number'
|
|
397
|
+
? slim.createdAt
|
|
398
|
+
: (typeof slim.lastUpdate === 'number' ? slim.lastUpdate : Date.now());
|
|
399
|
+
const chainLastUpdate = typeof slim.lastUpdate === 'number' ? slim.lastUpdate : chainCreatedAt;
|
|
400
|
+
const jobProgress = {
|
|
401
|
+
jobId: chainId,
|
|
402
|
+
actorId: extractActorId(slim),
|
|
403
|
+
status: 'running',
|
|
404
|
+
chainStartedAt: chainCreatedAt,
|
|
405
|
+
...progress,
|
|
406
|
+
} as JobProgress;
|
|
407
|
+
|
|
408
|
+
// Synthesize the user bubble if a hard refresh wiped the optimistic
|
|
409
|
+
// one and the chain hasn't persisted its turn yet.
|
|
410
|
+
const goal = typeof slim.goal === 'string' ? slim.goal : '';
|
|
411
|
+
const userExists = finalMessages.some(m =>
|
|
412
|
+
m.role === 'user' && (m.chainId === chainId || (goal && m.content === goal))
|
|
413
|
+
);
|
|
414
|
+
if (goal && !userExists) {
|
|
415
|
+
finalMessages.push({
|
|
416
|
+
id: `user-${chainId}`,
|
|
417
|
+
role: 'user',
|
|
418
|
+
content: goal,
|
|
419
|
+
timestamp: new Date(chainCreatedAt),
|
|
420
|
+
chainId,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Assistant side: attach to matching persisted turn if one exists;
|
|
425
|
+
// otherwise push a placeholder bubble.
|
|
426
|
+
let matchIdx = -1;
|
|
427
|
+
for (let i = finalMessages.length - 1; i >= 0; i--) {
|
|
428
|
+
const m = finalMessages[i];
|
|
429
|
+
if (m.role === 'assistant' && m.chainId === chainId) {
|
|
430
|
+
matchIdx = i;
|
|
431
|
+
break;
|
|
428
432
|
}
|
|
429
433
|
}
|
|
434
|
+
if (matchIdx >= 0) {
|
|
435
|
+
finalMessages[matchIdx] = { ...finalMessages[matchIdx], jobProgress };
|
|
436
|
+
if (jobProgress.status === 'running' && !firstRunning) firstRunning = finalMessages[matchIdx];
|
|
437
|
+
} else {
|
|
438
|
+
const reattachBubble: Message = {
|
|
439
|
+
id: `job-${chainId}`,
|
|
440
|
+
role: 'assistant',
|
|
441
|
+
content: 'Working on it...',
|
|
442
|
+
timestamp: new Date(chainLastUpdate),
|
|
443
|
+
jobProgress,
|
|
444
|
+
chainId,
|
|
445
|
+
};
|
|
446
|
+
finalMessages.push(reattachBubble);
|
|
447
|
+
if (jobProgress.status === 'running' && !firstRunning) firstRunning = reattachBubble;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Re-open the live subscription for this chain.
|
|
451
|
+
streamJob(chainId, getTenant(), getToken());
|
|
430
452
|
}
|
|
453
|
+
|
|
454
|
+
finalMessages.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
431
455
|
} catch {}
|
|
432
456
|
|
|
433
457
|
set({
|
package/src/chat/types.ts
CHANGED
|
@@ -26,6 +26,12 @@ export interface JobProgress {
|
|
|
26
26
|
resultActions?: UIAction[];
|
|
27
27
|
/** Live progress from adas_emit_progress() — latest event from a running fat tool */
|
|
28
28
|
liveProgress?: { message: string; step?: number; total?: number };
|
|
29
|
+
/** One warm, present-tense, localized line describing the current action
|
|
30
|
+
* (backend statusI18n). Absent when disabled / not yet cached → fall back
|
|
31
|
+
* to raw tokens. */
|
|
32
|
+
humanStatus?: string;
|
|
33
|
+
/** Lookup { rawToken → friendly phrase } for tokens in this snapshot. */
|
|
34
|
+
humanLabels?: Record<string, string>;
|
|
29
35
|
pendingQuestion?: {
|
|
30
36
|
inputId: string;
|
|
31
37
|
question: string;
|