@ateam-ai-mobile/core 0.1.1 → 0.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ateam-ai-mobile/core",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Headless ADAS SDK core \u2014 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",
@@ -1,4 +1,6 @@
1
1
  import { apiFetch } from './client';
2
+ import { getConsumerAuthApiUrl, getToken, getConfig, type AdasSession } from '../config';
3
+ import { useChatStore, closeChatStream } from '../chat/store';
2
4
 
3
5
  export interface TenantMembership {
4
6
  id: string;
@@ -9,14 +11,65 @@ export interface TenantMembership {
9
11
  /**
10
12
  * List the tenants the CURRENT user belongs to (GET /api/tenants/list).
11
13
  *
12
- * For consumer JWTs the backend resolves memberships by login identity
13
- * (google/apple oauth or embed id) across active tenants; builder JWTs get
14
- * their user-doc memberships. Returns [] when signed out or unknown.
15
- *
16
- * Note: switching tenant still requires a NEW JWT for that tenant (the
17
- * token is tenant-scoped) — the host owns that re-auth flow.
14
+ * The list is membership-scoped: for consumer JWTs the backend returns ONLY
15
+ * tenants where the user's login identity (google/apple oauth or embed id)
16
+ * has an active actor; builder JWTs get their user-doc memberships. It never
17
+ * leaks the full tenant list. Returns [] when signed out or unknown.
18
18
  */
19
19
  export async function listMyTenants(): Promise<TenantMembership[]> {
20
20
  const data = await apiFetch<{ ok: boolean; tenants?: TenantMembership[] }>('/api/tenants/list');
21
21
  return data.ok && Array.isArray(data.tenants) ? data.tenants : [];
22
22
  }
23
+
24
+ /**
25
+ * Re-scope the whole session to another tenant the user already belongs to.
26
+ *
27
+ * The backend treats the JWT tenant claim as the sole source of truth, so
28
+ * switching = minting a fresh JWT for the target tenant's actor. The SDK
29
+ * owns the call (POST consumer-auth /platform/consumer/switch-tenant, authed
30
+ * by the current token); the HOST persists the new session via
31
+ * `config.onSession` (the SDK never stores credentials). The chat stream is
32
+ * then torn down and cleared so it reconnects against the new tenant.
33
+ *
34
+ * Requires `consumerAuthUrl` and `onSession` in the initAdas() config.
35
+ * Throws on any non-OK response (e.g. not a member of the target tenant).
36
+ */
37
+ export async function switchTenant(targetTenantId: string): Promise<AdasSession> {
38
+ const token = getToken();
39
+ if (!token) throw new Error('[adas] switchTenant: not signed in');
40
+
41
+ const res = await fetch(`${getConsumerAuthApiUrl()}/platform/consumer/switch-tenant`, {
42
+ method: 'POST',
43
+ headers: {
44
+ 'Content-Type': 'application/json',
45
+ Authorization: `Bearer ${token}`,
46
+ },
47
+ body: JSON.stringify({ target_tenant: targetTenantId }),
48
+ });
49
+
50
+ const data = await res.json().catch(() => null);
51
+ if (!res.ok || !data?.ok || !data.jwt) {
52
+ throw new Error(data?.error || `switchTenant failed (${res.status})`);
53
+ }
54
+
55
+ const session: AdasSession = {
56
+ jwt: data.jwt,
57
+ tenant: data.tenant,
58
+ actor: data.actor,
59
+ };
60
+
61
+ // Host persists the new token + active tenant BEFORE we reconnect, so the
62
+ // fresh stream/requests pick up the target-tenant credentials.
63
+ const onSession = getConfig().onSession;
64
+ if (!onSession) {
65
+ throw new Error('[adas] switchTenant: config.onSession is required to persist the new session');
66
+ }
67
+ await onSession(session);
68
+
69
+ // Tear down the old-tenant stream and history; the chat screen reloads
70
+ // against the new tenant on its next mount / loadConversation().
71
+ closeChatStream();
72
+ useChatStore.getState().clearChat();
73
+
74
+ return session;
75
+ }
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 || '',
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;
package/src/config.ts CHANGED
@@ -57,6 +57,10 @@ export interface AdasConfig {
57
57
  coreUrl: UrlSource;
58
58
  /** Voice service base URL (optional — only voice features need it). */
59
59
  voiceUrl?: UrlSource;
60
+ /** Consumer-auth base URL, e.g. https://consumer-auth.ateam-ai.com. Only
61
+ * needed for `switchTenant()` (re-scoping the session to another tenant
62
+ * the user belongs to). */
63
+ consumerAuthUrl?: UrlSource;
60
64
  tokenProvider: TokenProvider;
61
65
  /** Tenant slug — used for plugin URL resolution and voice, never sent as a
62
66
  * header (the backend derives the tenant from the JWT). Pass a getter when
@@ -70,6 +74,18 @@ export interface AdasConfig {
70
74
  * survive app restarts. No-op when omitted (previews live in memory for
71
75
  * the session only). @ateam-ai-mobile/ui ships an Expo/RN implementation. */
72
76
  attachmentCache?: AttachmentCache;
77
+ /** Called by `switchTenant()` with the freshly-minted session for the
78
+ * target tenant. The host MUST persist it here (store the new JWT so
79
+ * tokenProvider.getToken() returns it, and update the active tenant) —
80
+ * the SDK never stores credentials. Awaited before the chat reconnects. */
81
+ onSession?: (session: AdasSession) => void | Promise<void>;
82
+ }
83
+
84
+ /** A tenant-scoped session minted by the platform (e.g. via switchTenant). */
85
+ export interface AdasSession {
86
+ jwt: string;
87
+ tenant: { id: string; name: string };
88
+ actor: { id: string; email: string | null; roles: string[] };
73
89
  }
74
90
 
75
91
  let _config: AdasConfig | null = null;
@@ -110,6 +126,12 @@ export function getVoiceApiUrl(): string {
110
126
  return resolveUrl(url);
111
127
  }
112
128
 
129
+ export function getConsumerAuthApiUrl(): string {
130
+ const url = getConfig().consumerAuthUrl;
131
+ if (!url) throw new Error('[adas] consumerAuthUrl not configured — required for switchTenant()');
132
+ return resolveUrl(url);
133
+ }
134
+
113
135
  export function getTenant(): string {
114
136
  const t = getConfig().tenant;
115
137
  return (typeof t === 'function' ? t() : t) || '';
package/src/index.ts CHANGED
@@ -23,11 +23,12 @@ export {
23
23
  getCoreApiUrl,
24
24
  getCoreWsUrl,
25
25
  getVoiceApiUrl,
26
+ getConsumerAuthApiUrl,
26
27
  getTenant,
27
28
  getUser,
28
29
  getToken,
29
30
  } from './config';
30
- export type { AdasConfig, TokenProvider, RefreshOutcome, AdasUser } from './config';
31
+ export type { AdasConfig, TokenProvider, RefreshOutcome, AdasUser, AdasSession } from './config';
31
32
 
32
33
  // API client
33
34
  export { apiFetch, apiFetchRaw } from './api/client';
@@ -42,7 +43,7 @@ export {
42
43
  } from './api/mcp';
43
44
 
44
45
  // Tenant memberships
45
- export { listMyTenants } from './api/tenants';
46
+ export { listMyTenants, switchTenant } from './api/tenants';
46
47
  export type { TenantMembership } from './api/tenants';
47
48
 
48
49
  // Chat