@ateam-ai-mobile/core 0.1.5 → 0.1.7
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 +386 -2
- package/dist/index.d.ts +386 -2
- package/dist/index.js +1819 -1333
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1792 -1332
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/acs/ws.ts +46 -2
- package/src/api/tenants.ts +17 -1
- package/src/authSession.ts +241 -0
- package/src/chat/store.ts +35 -0
- package/src/consumerAuth.ts +216 -0
- package/src/deepLinks.ts +38 -0
- package/src/gate.ts +67 -0
- package/src/index.ts +68 -0
- package/src/lifecycle.ts +43 -0
- package/src/pushRegistration.ts +71 -0
- package/src/session.ts +62 -0
- package/src/workspace.ts +127 -0
package/dist/index.mjs
CHANGED
|
@@ -170,1476 +170,1910 @@ function unwrapMcpResult(result) {
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
// src/
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
constructor() {
|
|
181
|
-
this.listeners = /* @__PURE__ */ new Map();
|
|
182
|
-
// Ring buffer of plugin commands seen recently. Lets a plugin that mounts
|
|
183
|
-
// AFTER its first command was emitted (e.g. a surface mounted by the
|
|
184
|
-
// dispatcher in response to focus-plugin) still pick that command up via
|
|
185
|
-
// `drainPluginCommands(pluginId)`. Self-cleans on TTL + size cap.
|
|
186
|
-
this.recent = [];
|
|
173
|
+
// src/session.ts
|
|
174
|
+
function hostOf(u) {
|
|
175
|
+
if (!u) return "";
|
|
176
|
+
try {
|
|
177
|
+
return new URL(u).host;
|
|
178
|
+
} catch {
|
|
179
|
+
return u;
|
|
187
180
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
181
|
+
}
|
|
182
|
+
function isDevHost(u) {
|
|
183
|
+
const h = hostOf(u);
|
|
184
|
+
return !!h && (h.includes("dev-") || /(^|\.)dev\./.test(h));
|
|
185
|
+
}
|
|
186
|
+
function getSession() {
|
|
187
|
+
let consumerUrl = "";
|
|
188
|
+
try {
|
|
189
|
+
consumerUrl = getConsumerAuthApiUrl();
|
|
190
|
+
} catch {
|
|
192
191
|
}
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
let coreUrl = "";
|
|
193
|
+
try {
|
|
194
|
+
coreUrl = getCoreApiUrl();
|
|
195
|
+
} catch {
|
|
195
196
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
197
|
+
const tenant = getTenant() || null;
|
|
198
|
+
const user = getUser();
|
|
199
|
+
return {
|
|
200
|
+
tenant,
|
|
201
|
+
tenantName: tenant || "\u2014",
|
|
202
|
+
env: isDevHost(consumerUrl) || isDevHost(coreUrl) ? "dev" : "prod",
|
|
203
|
+
host: hostOf(consumerUrl || coreUrl),
|
|
204
|
+
actorId: user?.actorId ?? null,
|
|
205
|
+
email: user?.email ?? null
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/authSession.ts
|
|
210
|
+
function decodeJwtPayload(token) {
|
|
211
|
+
try {
|
|
212
|
+
const segs = token.split(".");
|
|
213
|
+
if (segs.length !== 3) return null;
|
|
214
|
+
const b64 = segs[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
215
|
+
const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
|
|
216
|
+
const json = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf8");
|
|
217
|
+
return JSON.parse(json);
|
|
218
|
+
} catch {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function userFromJwt(token) {
|
|
223
|
+
const claims = decodeJwtPayload(token);
|
|
224
|
+
if (!claims?.sub) return null;
|
|
225
|
+
return {
|
|
226
|
+
actorId: String(claims.sub),
|
|
227
|
+
email: claims.email,
|
|
228
|
+
displayName: claims.name || claims.email,
|
|
229
|
+
tenantRole: claims.tenantRole ?? null,
|
|
230
|
+
serviceRole: claims.serviceRole ?? null,
|
|
231
|
+
platformRoles: Array.isArray(claims.platformRoles) ? claims.platformRoles : [],
|
|
232
|
+
roles: Array.isArray(claims.roles) ? claims.roles : []
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
async function attemptRefresh(expiredToken) {
|
|
236
|
+
let res;
|
|
237
|
+
try {
|
|
238
|
+
res = await fetch(`${getCoreApiUrl()}/api/auth/refresh`, {
|
|
239
|
+
method: "POST",
|
|
240
|
+
headers: {
|
|
241
|
+
"Authorization": `Bearer ${expiredToken}`,
|
|
242
|
+
"Content-Type": "application/json"
|
|
211
243
|
}
|
|
212
244
|
});
|
|
245
|
+
} catch (e) {
|
|
246
|
+
console.warn("[adas/session] refresh fetch threw (network):", e?.message || e);
|
|
247
|
+
return { ok: "transient", reason: `network: ${e?.message || "unknown"}` };
|
|
213
248
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
249
|
+
if (res.status === 401 || res.status === 403) {
|
|
250
|
+
console.warn(`[adas/session] refresh rejected with ${res.status} \u2014 auth genuinely broken`);
|
|
251
|
+
return { ok: "auth_failed", status: res.status, reason: `core ${res.status}` };
|
|
252
|
+
}
|
|
253
|
+
if (!res.ok) {
|
|
254
|
+
console.warn(`[adas/session] refresh server error ${res.status} \u2014 treating as transient`);
|
|
255
|
+
return { ok: "transient", status: res.status, reason: `core ${res.status}` };
|
|
256
|
+
}
|
|
257
|
+
const body = await res.json().catch(() => null);
|
|
258
|
+
if (typeof body?.token === "string") return { ok: "ok", token: body.token };
|
|
259
|
+
console.warn("[adas/session] refresh returned 2xx but no token \u2014 treating as auth_failed");
|
|
260
|
+
return { ok: "auth_failed", status: res.status, reason: "malformed refresh response" };
|
|
261
|
+
}
|
|
262
|
+
function createAuthSession(deps) {
|
|
263
|
+
let state = { token: null, user: null, status: "loading" };
|
|
264
|
+
const subs = /* @__PURE__ */ new Set();
|
|
265
|
+
const set = (patch) => {
|
|
266
|
+
state = { ...state, ...patch };
|
|
267
|
+
for (const cb of subs) cb(state);
|
|
268
|
+
};
|
|
269
|
+
async function adopt(token) {
|
|
270
|
+
await deps.persistence.saveToken(token);
|
|
271
|
+
const user = userFromJwt(token);
|
|
272
|
+
set({ token, user, status: "authenticated" });
|
|
273
|
+
await deps.onToken?.(token, user);
|
|
274
|
+
}
|
|
275
|
+
async function refresh() {
|
|
276
|
+
const current = state.token;
|
|
277
|
+
if (!current) return { ok: "auth_failed", status: 0, reason: "no token in store" };
|
|
278
|
+
const outcome = await attemptRefresh(current);
|
|
279
|
+
if (outcome.ok === "ok") {
|
|
280
|
+
await adopt(outcome.token);
|
|
281
|
+
return { ok: "ok", status: 200 };
|
|
225
282
|
}
|
|
226
|
-
|
|
227
|
-
return
|
|
283
|
+
if (outcome.ok === "auth_failed") return { ok: "auth_failed", status: outcome.status, reason: outcome.reason };
|
|
284
|
+
return { ok: "transient", status: outcome.status ?? 0, reason: outcome.reason };
|
|
228
285
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
286
|
+
async function logout() {
|
|
287
|
+
await deps.onLogout?.();
|
|
288
|
+
await deps.persistence.clearToken();
|
|
289
|
+
set({ token: null, user: null, status: "signed_out" });
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
getToken: () => state.token,
|
|
293
|
+
getUser: () => state.user,
|
|
294
|
+
getState: () => state,
|
|
295
|
+
subscribe(cb) {
|
|
296
|
+
subs.add(cb);
|
|
297
|
+
return () => {
|
|
298
|
+
subs.delete(cb);
|
|
299
|
+
};
|
|
300
|
+
},
|
|
301
|
+
async restore() {
|
|
302
|
+
const token = await deps.persistence.loadToken();
|
|
303
|
+
if (!token) {
|
|
304
|
+
set({ status: "signed_out" });
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const user = userFromJwt(token);
|
|
308
|
+
if (!user) {
|
|
309
|
+
await deps.persistence.clearToken();
|
|
310
|
+
set({ status: "signed_out" });
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const claims = decodeJwtPayload(token);
|
|
314
|
+
const exp = typeof claims?.exp === "number" ? claims.exp * 1e3 : 0;
|
|
315
|
+
if (exp && Date.now() >= exp) {
|
|
316
|
+
const outcome = await attemptRefresh(token);
|
|
317
|
+
if (outcome.ok === "ok") {
|
|
318
|
+
await adopt(outcome.token);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (outcome.ok === "auth_failed") {
|
|
322
|
+
await deps.persistence.clearToken();
|
|
323
|
+
set({ token: null, user: null, status: "signed_out" });
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
set({ token, user, status: "authenticated" });
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
set({ token, user, status: "authenticated" });
|
|
330
|
+
},
|
|
331
|
+
async login(token) {
|
|
332
|
+
const user = userFromJwt(token);
|
|
333
|
+
if (!user) throw new Error("Invalid JWT \u2014 could not decode subject");
|
|
334
|
+
await adopt(token);
|
|
335
|
+
},
|
|
336
|
+
logout,
|
|
337
|
+
refresh
|
|
237
338
|
};
|
|
238
|
-
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
239
|
-
return headers;
|
|
240
339
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
340
|
+
|
|
341
|
+
// src/pushRegistration.ts
|
|
342
|
+
function createPushRegistration(backend) {
|
|
343
|
+
let token = null;
|
|
344
|
+
return {
|
|
345
|
+
getToken: () => token,
|
|
346
|
+
async setToken(next) {
|
|
347
|
+
if (!next) {
|
|
348
|
+
token = null;
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (next === token) return;
|
|
352
|
+
token = next;
|
|
353
|
+
try {
|
|
354
|
+
await backend.registerDevice(next);
|
|
355
|
+
} catch (e) {
|
|
356
|
+
console.warn("[adas/push] registerDevice failed:", e?.message || e);
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
async unregister() {
|
|
360
|
+
const t = token;
|
|
361
|
+
token = null;
|
|
362
|
+
if (t) {
|
|
363
|
+
backend.unregisterDevice(t).catch(
|
|
364
|
+
(e) => console.warn("[adas/push] unregisterDevice failed:", e?.message || e)
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
};
|
|
244
369
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
370
|
+
|
|
371
|
+
// src/acs/widgetsStore.ts
|
|
372
|
+
import { create } from "zustand";
|
|
373
|
+
|
|
374
|
+
// src/acs/providers.ts
|
|
375
|
+
var _reachability = null;
|
|
376
|
+
function setAcsReachabilityReporter(r) {
|
|
377
|
+
_reachability = r;
|
|
252
378
|
}
|
|
253
|
-
function
|
|
254
|
-
|
|
379
|
+
function reportAcsServerUp() {
|
|
380
|
+
try {
|
|
381
|
+
_reachability?.up();
|
|
382
|
+
} catch {
|
|
383
|
+
}
|
|
255
384
|
}
|
|
256
|
-
function
|
|
257
|
-
|
|
258
|
-
|
|
385
|
+
function reportAcsServerDown() {
|
|
386
|
+
try {
|
|
387
|
+
_reachability?.down();
|
|
388
|
+
} catch {
|
|
389
|
+
}
|
|
259
390
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
if (!latest?.message) return void 0;
|
|
264
|
-
return {
|
|
265
|
-
message: String(latest.message || ""),
|
|
266
|
-
...latest.step != null ? { step: latest.step } : {},
|
|
267
|
-
...latest.total != null ? { total: latest.total } : {}
|
|
268
|
-
};
|
|
391
|
+
var _kv = null;
|
|
392
|
+
function setAcsCacheStorage(kv) {
|
|
393
|
+
_kv = kv;
|
|
269
394
|
}
|
|
270
|
-
function
|
|
271
|
-
|
|
272
|
-
if (!exec || typeof exec !== "object") return void 0;
|
|
273
|
-
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];
|
|
274
|
-
if (!latest) return void 0;
|
|
275
|
-
const steps = exec[latest.k];
|
|
276
|
-
if (!Array.isArray(steps) || !steps.length) return void 0;
|
|
277
|
-
const last = steps[steps.length - 1];
|
|
278
|
-
return last?.tool || last?.phase;
|
|
395
|
+
function getAcsCacheStorage() {
|
|
396
|
+
return _kv;
|
|
279
397
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
inputId: pq.correlation_id || pq.correlationId || pq.inputId || "",
|
|
299
|
-
question: pq.prompt_message || pq.question || "The assistant needs your input",
|
|
300
|
-
inputType: pq.inputType || "text",
|
|
301
|
-
options: pq.options || pq.requested_fields,
|
|
302
|
-
placeholder: pq.placeholder
|
|
303
|
-
};
|
|
398
|
+
|
|
399
|
+
// src/acs/api.ts
|
|
400
|
+
async function authedFetch(path, init) {
|
|
401
|
+
const token = getToken();
|
|
402
|
+
try {
|
|
403
|
+
const res = await fetch(`${getCoreApiUrl()}${path}`, {
|
|
404
|
+
...init,
|
|
405
|
+
headers: {
|
|
406
|
+
"Content-Type": "application/json",
|
|
407
|
+
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
408
|
+
...init?.headers || {}
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
reportAcsServerUp();
|
|
412
|
+
return res;
|
|
413
|
+
} catch (e) {
|
|
414
|
+
reportAcsServerDown();
|
|
415
|
+
throw e;
|
|
304
416
|
}
|
|
305
|
-
return progress;
|
|
306
|
-
}
|
|
307
|
-
function isTerminalSnapshot(snap) {
|
|
308
|
-
if (extractPendingQuestion(snap)) return false;
|
|
309
|
-
return snap?.done === true || isTerminalStatus(snap?.status);
|
|
310
417
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
418
|
+
async function listSurface(surface, context) {
|
|
419
|
+
try {
|
|
420
|
+
const res = await authedFetch("/api/acs/surface", {
|
|
421
|
+
method: "POST",
|
|
422
|
+
body: JSON.stringify({ surface, ...context ? { context } : {} })
|
|
423
|
+
});
|
|
424
|
+
if (!res.ok) {
|
|
425
|
+
console.warn(`[acs] /surface ${surface} returned ${res.status}`);
|
|
426
|
+
return [];
|
|
427
|
+
}
|
|
428
|
+
const data = await res.json();
|
|
429
|
+
return Array.isArray(data?.items) ? data.items : [];
|
|
430
|
+
} catch (e) {
|
|
431
|
+
console.warn("[acs] /surface failed:", e?.message || e);
|
|
432
|
+
return [];
|
|
433
|
+
}
|
|
320
434
|
}
|
|
321
|
-
function
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (
|
|
325
|
-
|
|
435
|
+
async function listMadeForYou() {
|
|
436
|
+
try {
|
|
437
|
+
const res = await authedFetch("/api/acs/made-for-you", { method: "GET" });
|
|
438
|
+
if (!res.ok) {
|
|
439
|
+
console.warn(`[acs] /made-for-you returned ${res.status}`);
|
|
440
|
+
return [];
|
|
326
441
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
442
|
+
const data = await res.json();
|
|
443
|
+
return Array.isArray(data?.artifacts) ? data.artifacts : [];
|
|
444
|
+
} catch (e) {
|
|
445
|
+
console.warn("[acs] /made-for-you failed:", e?.message || e);
|
|
446
|
+
return [];
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
async function deleteArtifact(artifactId, reason = "user_long_press_delete") {
|
|
450
|
+
try {
|
|
451
|
+
const res = await authedFetch(
|
|
452
|
+
`/api/acs/artifacts/${encodeURIComponent(artifactId)}`,
|
|
453
|
+
{ method: "DELETE", body: JSON.stringify({ reason }) }
|
|
454
|
+
);
|
|
455
|
+
const data = await res.json().catch(() => ({}));
|
|
456
|
+
if (res.ok) {
|
|
457
|
+
return { ok: true, already: !!data?.already, status: res.status };
|
|
332
458
|
}
|
|
333
|
-
if (
|
|
334
|
-
|
|
335
|
-
if (md) return md;
|
|
459
|
+
if (res.status === 404) {
|
|
460
|
+
return { ok: true, notFound: true, status: 404 };
|
|
336
461
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
462
|
+
return { ok: false, status: res.status, error: data?.error || `HTTP ${res.status}` };
|
|
463
|
+
} catch (e) {
|
|
464
|
+
return { ok: false, status: 0, error: e?.message || "network error" };
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
async function patchArtifact(artifactId, body, reason) {
|
|
468
|
+
try {
|
|
469
|
+
const res = await authedFetch(
|
|
470
|
+
`/api/acs/artifacts/${encodeURIComponent(artifactId)}`,
|
|
471
|
+
{ method: "PATCH", body: JSON.stringify({ ...body, reason }) }
|
|
472
|
+
);
|
|
473
|
+
const data = await res.json().catch(() => ({}));
|
|
474
|
+
if (res.ok) {
|
|
475
|
+
return {
|
|
476
|
+
ok: true,
|
|
477
|
+
already: !!data?.already,
|
|
478
|
+
artifact: data?.artifact || void 0,
|
|
479
|
+
status: res.status
|
|
480
|
+
};
|
|
342
481
|
}
|
|
343
|
-
if (
|
|
344
|
-
return
|
|
482
|
+
if (res.status === 404) {
|
|
483
|
+
return { ok: true, notFound: true, status: 404 };
|
|
345
484
|
}
|
|
346
|
-
return
|
|
347
|
-
|
|
485
|
+
return {
|
|
486
|
+
ok: false,
|
|
487
|
+
status: res.status,
|
|
488
|
+
error: data?.error || `HTTP ${res.status}`
|
|
489
|
+
};
|
|
490
|
+
} catch (e) {
|
|
491
|
+
return { ok: false, status: 0, error: e?.message || "network error" };
|
|
492
|
+
}
|
|
348
493
|
}
|
|
349
|
-
function
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
494
|
+
async function setOrder(pinned) {
|
|
495
|
+
try {
|
|
496
|
+
const res = await authedFetch("/api/acs/order", {
|
|
497
|
+
method: "PUT",
|
|
498
|
+
body: JSON.stringify({ pinned })
|
|
499
|
+
});
|
|
500
|
+
if (res.ok) return { ok: true, status: res.status };
|
|
501
|
+
const data = await res.json().catch(() => ({}));
|
|
502
|
+
return { ok: false, status: res.status, error: data?.error || `HTTP ${res.status}` };
|
|
503
|
+
} catch (e) {
|
|
504
|
+
return { ok: false, status: 0, error: e?.message || "network error" };
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
async function recordEvent(artifactId, event, surface) {
|
|
508
|
+
try {
|
|
509
|
+
await authedFetch("/api/acs/event", {
|
|
510
|
+
method: "POST",
|
|
511
|
+
body: JSON.stringify({ artifactId, event, ...surface ? { surface } : {} })
|
|
512
|
+
});
|
|
513
|
+
} catch {
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
async function listHomeActions() {
|
|
517
|
+
try {
|
|
518
|
+
const res = await authedFetch("/api/acs/home-actions", { method: "GET" });
|
|
519
|
+
if (!res.ok) {
|
|
520
|
+
console.warn(`[acs] /home-actions returned ${res.status}`);
|
|
521
|
+
return [];
|
|
359
522
|
}
|
|
523
|
+
const data = await res.json();
|
|
524
|
+
return Array.isArray(data?.actions) ? data.actions : [];
|
|
525
|
+
} catch (e) {
|
|
526
|
+
console.warn("[acs] /home-actions failed:", e?.message || e);
|
|
527
|
+
return [];
|
|
360
528
|
}
|
|
361
|
-
return atts.length > 0 ? atts : void 0;
|
|
362
529
|
}
|
|
363
|
-
|
|
364
|
-
|
|
530
|
+
async function patchHomeAction(actionId, body, reason) {
|
|
531
|
+
try {
|
|
532
|
+
const res = await authedFetch(
|
|
533
|
+
`/api/acs/home-actions/${encodeURIComponent(actionId)}`,
|
|
534
|
+
{ method: "PATCH", body: JSON.stringify({ ...body, reason }) }
|
|
535
|
+
);
|
|
536
|
+
const data = await res.json().catch(() => ({}));
|
|
537
|
+
if (res.ok) return { ok: true, action: data?.action || void 0, status: res.status };
|
|
538
|
+
if (res.status === 404) return { ok: true, status: 404 };
|
|
539
|
+
return { ok: false, status: res.status, error: data?.error || `HTTP ${res.status}` };
|
|
540
|
+
} catch (e) {
|
|
541
|
+
return { ok: false, status: 0, error: e?.message || "network error" };
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
async function setHomeActionOrder(order) {
|
|
545
|
+
try {
|
|
546
|
+
const res = await authedFetch("/api/acs/home-actions/order", {
|
|
547
|
+
method: "PUT",
|
|
548
|
+
body: JSON.stringify({ order })
|
|
549
|
+
});
|
|
550
|
+
if (res.ok) return { ok: true, status: res.status };
|
|
551
|
+
const data = await res.json().catch(() => ({}));
|
|
552
|
+
return { ok: false, status: res.status, error: data?.error || `HTTP ${res.status}` };
|
|
553
|
+
} catch (e) {
|
|
554
|
+
return { ok: false, status: 0, error: e?.message || "network error" };
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// src/acs/widgetsStore.ts
|
|
559
|
+
var useWidgetsStore = create((set, get) => ({
|
|
560
|
+
tiles: [],
|
|
561
|
+
hydrated: false,
|
|
365
562
|
loading: false,
|
|
366
563
|
error: null,
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
564
|
+
hydrate: async () => {
|
|
565
|
+
if (get().hydrated || get().loading) return;
|
|
566
|
+
await get().refresh();
|
|
567
|
+
},
|
|
568
|
+
refresh: async () => {
|
|
569
|
+
if (get().loading) return;
|
|
570
|
+
set({ loading: true, error: null });
|
|
374
571
|
try {
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
572
|
+
const tiles = await listSurface("home");
|
|
573
|
+
set({ tiles, loading: false, hydrated: true });
|
|
574
|
+
} catch (e) {
|
|
575
|
+
set({ loading: false, hydrated: true, error: e?.message || "Failed to load widgets" });
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
clear: () => set({ tiles: [], hydrated: false, loading: false, error: null }),
|
|
579
|
+
remove: async (artifactId, reason = "user_long_press_delete") => {
|
|
580
|
+
const before = get().tiles;
|
|
581
|
+
set({ tiles: before.filter((t) => t.id !== artifactId) });
|
|
582
|
+
const result = await deleteArtifact(artifactId, reason);
|
|
583
|
+
if (result.ok) {
|
|
584
|
+
return { ok: true };
|
|
585
|
+
}
|
|
586
|
+
set({ tiles: before });
|
|
587
|
+
return { ok: false, error: result.error || `HTTP ${result.status}` };
|
|
588
|
+
},
|
|
589
|
+
hide: async (artifactId) => {
|
|
590
|
+
const before = get().tiles;
|
|
591
|
+
set({
|
|
592
|
+
tiles: before.map(
|
|
593
|
+
(t) => t.id === artifactId ? { ...t, visibility: "hidden" } : t
|
|
594
|
+
)
|
|
595
|
+
});
|
|
596
|
+
const r = await patchArtifact(artifactId, { visibility: "hidden" }, "user_long_press_hide");
|
|
597
|
+
if (r.ok) {
|
|
598
|
+
if (r.artifact) {
|
|
599
|
+
set({
|
|
600
|
+
tiles: get().tiles.map((t) => t.id === artifactId ? { ...t, ...r.artifact } : t)
|
|
601
|
+
});
|
|
389
602
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
skillSlug: turn.skillSlug,
|
|
409
|
-
...userAtts ? { attachments: userAtts } : {},
|
|
410
|
-
...turnChainId ? { chainId: turnChainId } : {}
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
const assistantText = blocksToText(turn.assistant?.blocks);
|
|
414
|
-
const assistantAtts = blocksToAttachments(turn.assistant?.blocks);
|
|
415
|
-
const assistantActions = Array.isArray(turn.assistant?.actions) ? turn.assistant.actions : void 0;
|
|
416
|
-
const turnKind = typeof turn.kind === "string" ? turn.kind : void 0;
|
|
417
|
-
if (assistantText || assistantAtts || assistantActions?.length) {
|
|
418
|
-
persistedMessages.push({
|
|
419
|
-
id: `bot-${turn.jobId}`,
|
|
420
|
-
role: "assistant",
|
|
421
|
-
content: assistantText || "",
|
|
422
|
-
timestamp: new Date(turn.finishedAt || turn.startedAt || Date.now()),
|
|
423
|
-
skillSlug: turn.skillSlug,
|
|
424
|
-
...assistantAtts ? { attachments: assistantAtts } : {},
|
|
425
|
-
...assistantActions?.length ? { actions: assistantActions } : {},
|
|
426
|
-
...turnChainId ? { chainId: turnChainId } : {},
|
|
427
|
-
...turnKind ? { kind: turnKind } : {}
|
|
428
|
-
});
|
|
429
|
-
}
|
|
603
|
+
return { ok: true };
|
|
604
|
+
}
|
|
605
|
+
set({ tiles: before });
|
|
606
|
+
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
607
|
+
},
|
|
608
|
+
unhide: async (artifactId) => {
|
|
609
|
+
const before = get().tiles;
|
|
610
|
+
set({
|
|
611
|
+
tiles: before.map(
|
|
612
|
+
(t) => t.id === artifactId ? { ...t, visibility: "pinned" } : t
|
|
613
|
+
)
|
|
614
|
+
});
|
|
615
|
+
const r = await patchArtifact(artifactId, { visibility: "pinned" }, "user_long_press_unhide");
|
|
616
|
+
if (r.ok) {
|
|
617
|
+
if (r.artifact) {
|
|
618
|
+
set({
|
|
619
|
+
tiles: get().tiles.map((t) => t.id === artifactId ? { ...t, ...r.artifact } : t)
|
|
620
|
+
});
|
|
430
621
|
}
|
|
431
|
-
|
|
432
|
-
const keptPlaceholders = [];
|
|
433
|
-
for (const m of current) {
|
|
434
|
-
if (!m.id.startsWith("job-") || !m.jobProgress || !m.chainId) continue;
|
|
435
|
-
const matchIdx = persistedMessages.findIndex(
|
|
436
|
-
(pm) => pm.role === "assistant" && pm.chainId === m.chainId
|
|
437
|
-
);
|
|
438
|
-
if (matchIdx >= 0) {
|
|
439
|
-
const st = m.jobProgress.status;
|
|
440
|
-
if (st === "running" || st === "paused") {
|
|
441
|
-
persistedMessages[matchIdx] = { ...persistedMessages[matchIdx], jobProgress: m.jobProgress };
|
|
442
|
-
}
|
|
443
|
-
} else {
|
|
444
|
-
keptPlaceholders.push(m);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
const isLocalUserId = (id) => /^user-\d+$/.test(id);
|
|
448
|
-
const keptUserBubbles = current.filter((m) => {
|
|
449
|
-
if (m.role !== "user" || !isLocalUserId(m.id)) return false;
|
|
450
|
-
if (!m.chainId) return true;
|
|
451
|
-
return !persistedMessages.some(
|
|
452
|
-
(pm) => pm.role === "user" && pm.chainId === m.chainId
|
|
453
|
-
);
|
|
454
|
-
});
|
|
455
|
-
const finalMessages = [...persistedMessages, ...keptUserBubbles, ...keptPlaceholders];
|
|
456
|
-
finalMessages.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
457
|
-
let firstRunning = keptPlaceholders.find((m) => m.jobProgress?.status === "running");
|
|
458
|
-
try {
|
|
459
|
-
let activeChains = [];
|
|
460
|
-
const chainsRes = await fetch(`${getCoreApiUrl()}/api/chains/active`, {
|
|
461
|
-
headers: coreHeaders()
|
|
462
|
-
});
|
|
463
|
-
if (chainsRes.ok) {
|
|
464
|
-
const body = await chainsRes.json();
|
|
465
|
-
activeChains = Array.isArray(body?.chains) ? body.chains : [];
|
|
466
|
-
} else {
|
|
467
|
-
const legacy = await fetch(`${getCoreApiUrl()}/api/chain/current`, {
|
|
468
|
-
headers: coreHeaders()
|
|
469
|
-
});
|
|
470
|
-
if (legacy.ok) {
|
|
471
|
-
const lb = await legacy.json();
|
|
472
|
-
if (lb?.data?.id) activeChains = [lb.data];
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
for (const slim of activeChains) {
|
|
476
|
-
if (!slim?.id) continue;
|
|
477
|
-
const chainId = String(slim.id);
|
|
478
|
-
const alreadyTracked = finalMessages.some(
|
|
479
|
-
(m) => m.role === "assistant" && m.jobProgress?.jobId === chainId
|
|
480
|
-
);
|
|
481
|
-
if (alreadyTracked) continue;
|
|
482
|
-
const progress = snapshotToProgress(
|
|
483
|
-
{ ...slim, status: slim.chainStatus || slim.status },
|
|
484
|
-
chainId
|
|
485
|
-
);
|
|
486
|
-
const chainCreatedAt = typeof slim.createdAt === "number" ? slim.createdAt : typeof slim.lastUpdate === "number" ? slim.lastUpdate : Date.now();
|
|
487
|
-
const chainLastUpdate = typeof slim.lastUpdate === "number" ? slim.lastUpdate : chainCreatedAt;
|
|
488
|
-
const jobProgress = {
|
|
489
|
-
jobId: chainId,
|
|
490
|
-
actorId: extractActorId(slim),
|
|
491
|
-
status: "running",
|
|
492
|
-
chainStartedAt: chainCreatedAt,
|
|
493
|
-
...progress
|
|
494
|
-
};
|
|
495
|
-
const goal = typeof slim.goal === "string" ? slim.goal : "";
|
|
496
|
-
const userExists = finalMessages.some(
|
|
497
|
-
(m) => m.role === "user" && (m.chainId === chainId || goal && m.content === goal)
|
|
498
|
-
);
|
|
499
|
-
if (goal && !userExists) {
|
|
500
|
-
finalMessages.push({
|
|
501
|
-
id: `user-${chainId}`,
|
|
502
|
-
role: "user",
|
|
503
|
-
content: goal,
|
|
504
|
-
timestamp: new Date(chainCreatedAt),
|
|
505
|
-
chainId
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
let matchIdx = -1;
|
|
509
|
-
for (let i = finalMessages.length - 1; i >= 0; i--) {
|
|
510
|
-
const m = finalMessages[i];
|
|
511
|
-
if (m.role === "assistant" && m.chainId === chainId) {
|
|
512
|
-
matchIdx = i;
|
|
513
|
-
break;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
if (matchIdx >= 0) {
|
|
517
|
-
finalMessages[matchIdx] = { ...finalMessages[matchIdx], jobProgress };
|
|
518
|
-
if (jobProgress.status === "running" && !firstRunning) firstRunning = finalMessages[matchIdx];
|
|
519
|
-
} else {
|
|
520
|
-
const reattachBubble = {
|
|
521
|
-
id: `job-${chainId}`,
|
|
522
|
-
role: "assistant",
|
|
523
|
-
content: "Working on it...",
|
|
524
|
-
timestamp: new Date(chainLastUpdate),
|
|
525
|
-
jobProgress,
|
|
526
|
-
chainId
|
|
527
|
-
};
|
|
528
|
-
finalMessages.push(reattachBubble);
|
|
529
|
-
if (jobProgress.status === "running" && !firstRunning) firstRunning = reattachBubble;
|
|
530
|
-
}
|
|
531
|
-
streamJob(chainId, getTenant(), getToken());
|
|
532
|
-
}
|
|
533
|
-
finalMessages.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
534
|
-
} catch {
|
|
535
|
-
}
|
|
536
|
-
set({
|
|
537
|
-
messages: finalMessages,
|
|
538
|
-
conversationLoaded: true,
|
|
539
|
-
error: null,
|
|
540
|
-
activeJobId: firstRunning?.jobProgress?.jobId || null,
|
|
541
|
-
loading: !!firstRunning
|
|
542
|
-
});
|
|
543
|
-
} catch (err) {
|
|
544
|
-
console.warn("[adas-chat] Error loading conversation:", err.message);
|
|
545
|
-
set({ conversationLoaded: true });
|
|
546
|
-
}
|
|
547
|
-
},
|
|
548
|
-
// -----------------------------------------------------------------------
|
|
549
|
-
// Send message via Core API
|
|
550
|
-
// POST /api/chat { goal } → { jobId } → stream → fetch turn result
|
|
551
|
-
// -----------------------------------------------------------------------
|
|
552
|
-
sendMessage: async (text, skillSlug, attachments) => {
|
|
553
|
-
const userMsg = {
|
|
554
|
-
id: `user-${Date.now()}`,
|
|
555
|
-
role: "user",
|
|
556
|
-
content: text,
|
|
557
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
558
|
-
...attachments?.length ? {
|
|
559
|
-
attachments: attachments.map((a) => ({
|
|
560
|
-
mimeType: a.mimeType,
|
|
561
|
-
name: a.name,
|
|
562
|
-
previewUri: a.mimeType.startsWith("image/") ? `data:${a.mimeType};base64,${a.data}` : void 0
|
|
563
|
-
}))
|
|
564
|
-
} : {}
|
|
565
|
-
};
|
|
566
|
-
set((s) => ({ messages: [...s.messages, userMsg], loading: true, error: null }));
|
|
567
|
-
try {
|
|
568
|
-
const actor = getUser();
|
|
569
|
-
const body = {
|
|
570
|
-
goal: text,
|
|
571
|
-
// Opt in to the Chain Additions router (attach/cancel/new while a
|
|
572
|
-
// chain runs). Backend ignores it where not applicable — safe to send.
|
|
573
|
-
chainRouting: true,
|
|
574
|
-
triggerContext: {
|
|
575
|
-
channel: "mobile",
|
|
576
|
-
replyContext: { chatId: actor?.actorId || "unknown" }
|
|
577
|
-
}
|
|
578
|
-
};
|
|
579
|
-
if (skillSlug) body.skillSlug = skillSlug;
|
|
580
|
-
if (attachments?.length) body.attachments = attachments;
|
|
581
|
-
const res = await fetch(`${getCoreApiUrl()}/api/chat`, {
|
|
582
|
-
method: "POST",
|
|
583
|
-
headers: coreHeaders(),
|
|
584
|
-
body: JSON.stringify(body)
|
|
585
|
-
});
|
|
586
|
-
if (!res.ok) {
|
|
587
|
-
let errMsg = `Server error (${res.status})`;
|
|
588
|
-
try {
|
|
589
|
-
const text2 = await res.text();
|
|
590
|
-
if (text2.startsWith("<") || text2.startsWith("<!")) {
|
|
591
|
-
errMsg = res.status === 502 ? "Server is temporarily unavailable. Please try again." : `Server error (${res.status})`;
|
|
592
|
-
} else {
|
|
593
|
-
const parsed = JSON.parse(text2);
|
|
594
|
-
errMsg = parsed.error || errMsg;
|
|
595
|
-
}
|
|
596
|
-
} catch {
|
|
597
|
-
}
|
|
598
|
-
throw new Error(errMsg);
|
|
599
|
-
}
|
|
600
|
-
const data = await res.json();
|
|
601
|
-
if (!data.ok) {
|
|
602
|
-
throw new Error(data.error || "Failed to send message");
|
|
603
|
-
}
|
|
604
|
-
if (data.mode === "chain_addition" || data.mode === "chain_cancel") {
|
|
605
|
-
const ack = data.mode === "chain_addition" ? data.ack || "Got it \u2014 added to the running task." : "Stopping the task\u2026";
|
|
606
|
-
set((s) => ({
|
|
607
|
-
messages: s.messages.map((m) => m.id === userMsg.id && data.chainId ? { ...m, chainId: data.chainId } : m).concat({
|
|
608
|
-
id: `ack-${Date.now()}`,
|
|
609
|
-
role: "assistant",
|
|
610
|
-
content: ack,
|
|
611
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
612
|
-
...data.chainId ? { chainId: data.chainId } : {}
|
|
613
|
-
})
|
|
614
|
-
}));
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
const jobId = data.id || data.jobId;
|
|
618
|
-
if (!jobId) {
|
|
619
|
-
throw new Error("No job ID returned");
|
|
620
|
-
}
|
|
621
|
-
if (attachments?.length) {
|
|
622
|
-
await getConfig().attachmentCache?.save(jobId, attachments);
|
|
623
|
-
}
|
|
624
|
-
const progressMsg = {
|
|
625
|
-
id: `job-${jobId}`,
|
|
626
|
-
role: "assistant",
|
|
627
|
-
content: "Working on it...",
|
|
628
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
629
|
-
jobProgress: {
|
|
630
|
-
jobId,
|
|
631
|
-
actorId: actor?.actorId,
|
|
632
|
-
status: "running",
|
|
633
|
-
subStatus: "Starting...",
|
|
634
|
-
chainStartedAt: Date.now()
|
|
635
|
-
},
|
|
636
|
-
chainId: jobId
|
|
637
|
-
};
|
|
638
|
-
set((s) => ({
|
|
639
|
-
messages: s.messages.map(
|
|
640
|
-
(m) => m.id === userMsg.id ? { ...m, chainId: jobId } : m
|
|
641
|
-
).concat(progressMsg),
|
|
642
|
-
activeJobId: jobId
|
|
643
|
-
}));
|
|
644
|
-
streamJob(jobId, getTenant(), getToken());
|
|
645
|
-
} catch (err) {
|
|
646
|
-
set((s) => ({
|
|
647
|
-
messages: [...s.messages, {
|
|
648
|
-
id: `error-${Date.now()}`,
|
|
649
|
-
role: "system",
|
|
650
|
-
content: `Error: ${err.message}`,
|
|
651
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
652
|
-
}],
|
|
653
|
-
loading: false
|
|
654
|
-
}));
|
|
622
|
+
return { ok: true };
|
|
655
623
|
}
|
|
624
|
+
set({ tiles: before });
|
|
625
|
+
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
656
626
|
},
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
627
|
+
rename: async (artifactId, name) => {
|
|
628
|
+
const trimmed = name.trim();
|
|
629
|
+
if (!trimmed) return { ok: false, error: "Name cannot be empty" };
|
|
630
|
+
if (trimmed.length > 60) return { ok: false, error: "Name must be 60 characters or less" };
|
|
631
|
+
const before = get().tiles;
|
|
632
|
+
set({
|
|
633
|
+
tiles: before.map((t) => t.id === artifactId ? { ...t, name: trimmed } : t)
|
|
634
|
+
});
|
|
635
|
+
const r = await patchArtifact(artifactId, { name: trimmed }, "user_long_press_rename");
|
|
636
|
+
if (r.ok) {
|
|
637
|
+
if (r.artifact) {
|
|
638
|
+
set({
|
|
639
|
+
tiles: get().tiles.map((t) => t.id === artifactId ? { ...t, ...r.artifact } : t)
|
|
640
|
+
});
|
|
670
641
|
}
|
|
671
|
-
|
|
672
|
-
console.warn("[adas-chat] Error responding to question:", err.message);
|
|
642
|
+
return { ok: true };
|
|
673
643
|
}
|
|
644
|
+
set({ tiles: before });
|
|
645
|
+
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
674
646
|
},
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
updateJobProgress: (jobId, progress) => {
|
|
679
|
-
set((s) => ({
|
|
680
|
-
messages: s.messages.map((m) => {
|
|
681
|
-
if (m.jobProgress?.jobId === jobId) {
|
|
682
|
-
const updated = { ...m.jobProgress, ...progress };
|
|
683
|
-
if (progress.status && progress.status !== "paused") {
|
|
684
|
-
updated.pendingQuestion = void 0;
|
|
685
|
-
}
|
|
686
|
-
const isPersisted = m.id.startsWith("bot-");
|
|
687
|
-
const content = isPersisted ? m.content : progress.resultMessage || progress.pendingQuestion?.question || m.content;
|
|
688
|
-
const actions = isPersisted ? m.actions : progress.resultActions?.length ? progress.resultActions : m.actions;
|
|
689
|
-
return {
|
|
690
|
-
...m,
|
|
691
|
-
jobProgress: updated,
|
|
692
|
-
content,
|
|
693
|
-
...actions ? { actions } : {}
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
return m;
|
|
697
|
-
}),
|
|
698
|
-
loading: progress.status === "done" || progress.status === "error" || progress.status === "paused" ? false : s.loading,
|
|
699
|
-
activeJobId: progress.status === "done" || progress.status === "error" || progress.status === "paused" ? null : s.activeJobId
|
|
700
|
-
}));
|
|
701
|
-
},
|
|
702
|
-
clearChat: () => {
|
|
647
|
+
reorder: async (pinnedIds) => {
|
|
648
|
+
const before = get().tiles;
|
|
649
|
+
const idToOrder = new Map(pinnedIds.map((id, i) => [id, i]));
|
|
703
650
|
set({
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
conversationLoaded: false
|
|
651
|
+
tiles: before.map((t) => {
|
|
652
|
+
const o = idToOrder.get(t.id);
|
|
653
|
+
return o == null ? t : { ...t, pinOrder: o };
|
|
654
|
+
})
|
|
709
655
|
});
|
|
656
|
+
const r = await setOrder(pinnedIds);
|
|
657
|
+
if (r.ok) return { ok: true };
|
|
658
|
+
set({ tiles: before });
|
|
659
|
+
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
710
660
|
},
|
|
711
|
-
|
|
661
|
+
applyServerUpdate: (artifact) => {
|
|
712
662
|
set({
|
|
713
|
-
|
|
714
|
-
loading: false,
|
|
715
|
-
error: null,
|
|
716
|
-
activeJobId: null,
|
|
717
|
-
conversationLoaded: true
|
|
663
|
+
tiles: get().tiles.map((t) => t.id === artifact.id ? { ...t, ...artifact } : t)
|
|
718
664
|
});
|
|
665
|
+
},
|
|
666
|
+
applyServerRetire: (artifactId) => {
|
|
667
|
+
set({ tiles: get().tiles.filter((t) => t.id !== artifactId) });
|
|
668
|
+
},
|
|
669
|
+
applyServerOrder: (pinnedIds) => {
|
|
670
|
+
const idToOrder = new Map(pinnedIds.map((id, i) => [id, i]));
|
|
671
|
+
set({
|
|
672
|
+
tiles: get().tiles.map((t) => {
|
|
673
|
+
const o = idToOrder.get(t.id);
|
|
674
|
+
return o == null ? t : { ...t, pinOrder: o };
|
|
675
|
+
})
|
|
676
|
+
});
|
|
677
|
+
},
|
|
678
|
+
track: (artifactId, event) => {
|
|
679
|
+
recordEvent(artifactId, event, "home");
|
|
719
680
|
}
|
|
720
|
-
}));
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
const pluginId = cmd?.pluginId || cmd?.plugin_id || cmd?.args?.plugin_id;
|
|
729
|
-
if (pluginId) {
|
|
730
|
-
const props = cmd?.props ?? cmd?.args?.props ?? void 0;
|
|
731
|
-
pluginCommandEmitter.emit("focus-plugin", { pluginId, props });
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
var _ws = null;
|
|
736
|
-
var _wsReady = false;
|
|
737
|
-
var _currentJobId = null;
|
|
738
|
-
function ensureWs(tenant, token) {
|
|
739
|
-
if (_ws && _wsReady) return _ws;
|
|
740
|
-
const wsUrl = `${getCoreWsUrl()}/ws?token=${encodeURIComponent(token || "")}&tenant=${encodeURIComponent(tenant)}`;
|
|
741
|
-
const ws = new WebSocket(wsUrl);
|
|
742
|
-
_ws = ws;
|
|
743
|
-
_wsReady = false;
|
|
744
|
-
ws.onopen = () => {
|
|
745
|
-
_wsReady = true;
|
|
746
|
-
if (_currentJobId) {
|
|
747
|
-
ws.send(JSON.stringify({ type: "subscribe", jobId: _currentJobId }));
|
|
748
|
-
}
|
|
749
|
-
};
|
|
750
|
-
ws.onmessage = (ev) => {
|
|
751
|
-
try {
|
|
752
|
-
const msg = JSON.parse(typeof ev.data === "string" ? ev.data : "");
|
|
753
|
-
const { event, data } = msg;
|
|
754
|
-
const { updateJobProgress } = useChatStore.getState();
|
|
755
|
-
const jobId = data && (data.id || data.chainId) || _currentJobId;
|
|
756
|
-
if (event === "plugin_command") {
|
|
757
|
-
dispatchPluginCommands([data]);
|
|
758
|
-
return;
|
|
759
|
-
}
|
|
760
|
-
if (event === "plugin-command-ack" || event === "pong") return;
|
|
761
|
-
if (!jobId) return;
|
|
762
|
-
switch (event) {
|
|
763
|
-
case "init":
|
|
764
|
-
case "job":
|
|
765
|
-
case "update": {
|
|
766
|
-
const snap = data;
|
|
767
|
-
const progress = snapshotToProgress(snap, jobId);
|
|
768
|
-
if (isTerminalSnapshot(snap)) {
|
|
769
|
-
if (snap.status === "done" && (snap.isHandoff || snap.state?.handoffTransfer)) {
|
|
770
|
-
updateJobProgress(jobId, { status: "running" });
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
773
|
-
_currentJobId = null;
|
|
774
|
-
if (progress.status === "paused" && progress.pendingQuestion) {
|
|
775
|
-
updateJobProgress(jobId, progress);
|
|
776
|
-
} else {
|
|
777
|
-
fetchTurnResult(jobId, snap);
|
|
778
|
-
}
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
updateJobProgress(jobId, progress);
|
|
782
|
-
break;
|
|
783
|
-
}
|
|
784
|
-
case "chain_final":
|
|
785
|
-
_currentJobId = null;
|
|
786
|
-
fetchTurnResult(jobId, data);
|
|
787
|
-
break;
|
|
788
|
-
case "final":
|
|
789
|
-
_currentJobId = null;
|
|
790
|
-
fetchTurnResult(jobId, data);
|
|
791
|
-
break;
|
|
792
|
-
}
|
|
793
|
-
} catch (err) {
|
|
794
|
-
console.warn("[adas-chat] WS parse error:", err);
|
|
795
|
-
}
|
|
796
|
-
};
|
|
797
|
-
ws.onerror = () => {
|
|
798
|
-
_ws = null;
|
|
799
|
-
_wsReady = false;
|
|
800
|
-
if (_currentJobId) {
|
|
801
|
-
const jid = _currentJobId;
|
|
802
|
-
_currentJobId = null;
|
|
803
|
-
pollJob(jid, token);
|
|
804
|
-
}
|
|
805
|
-
};
|
|
806
|
-
ws.onclose = () => {
|
|
807
|
-
_ws = null;
|
|
808
|
-
_wsReady = false;
|
|
809
|
-
if (_currentJobId) {
|
|
810
|
-
const jid = _currentJobId;
|
|
811
|
-
_currentJobId = null;
|
|
812
|
-
pollJob(jid, token);
|
|
813
|
-
}
|
|
814
|
-
};
|
|
815
|
-
return ws;
|
|
816
|
-
}
|
|
817
|
-
function streamJob(jobId, tenant, token) {
|
|
818
|
-
_currentJobId = jobId;
|
|
819
|
-
const ws = ensureWs(tenant, token);
|
|
820
|
-
if (_wsReady) {
|
|
821
|
-
ws.send(JSON.stringify({ type: "subscribe", jobId }));
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
function closeChatStream() {
|
|
825
|
-
_currentJobId = null;
|
|
826
|
-
if (_ws) {
|
|
827
|
-
try {
|
|
828
|
-
_ws.close();
|
|
829
|
-
} catch {
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
_ws = null;
|
|
833
|
-
_wsReady = false;
|
|
834
|
-
}
|
|
835
|
-
async function fetchTurnResult(jobId, jobSnap) {
|
|
836
|
-
if (_processedPluginCmds.size > 200) _processedPluginCmds.clear();
|
|
837
|
-
const { updateJobProgress } = useChatStore.getState();
|
|
838
|
-
const status = normalizeJobStatus(jobSnap) === "error" ? "error" : "done";
|
|
839
|
-
if (jobSnap.isHandoff || jobSnap.state?.handoffTransfer || jobSnap.result?.contentType === "handoff") {
|
|
840
|
-
try {
|
|
841
|
-
for (let i = 0; i < 40; i++) {
|
|
842
|
-
const statusRes = await fetch(`${getCoreApiUrl()}/api/job/${jobId}/status`, {
|
|
843
|
-
headers: coreHeaders()
|
|
844
|
-
});
|
|
845
|
-
if (statusRes.ok) {
|
|
846
|
-
const statusData = await statusRes.json();
|
|
847
|
-
if (statusData.done && !statusData.isHandoff) {
|
|
848
|
-
break;
|
|
849
|
-
}
|
|
850
|
-
updateJobProgress(jobId, { status: "running" });
|
|
851
|
-
}
|
|
852
|
-
await new Promise((r) => setTimeout(r, 1500));
|
|
853
|
-
}
|
|
854
|
-
} catch {
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
for (let attempt = 0; attempt < 5; attempt++) {
|
|
858
|
-
try {
|
|
859
|
-
const res = await fetch(`${getCoreApiUrl()}/api/conversation?limit=1`, {
|
|
860
|
-
headers: coreHeaders()
|
|
861
|
-
});
|
|
862
|
-
if (res.ok) {
|
|
863
|
-
const data = await res.json();
|
|
864
|
-
const items = Array.isArray(data.items) ? data.items : [];
|
|
865
|
-
const turn = items[0];
|
|
866
|
-
if (turn?.assistant || turn?.turn?.assistant) {
|
|
867
|
-
const assistant = turn.assistant || turn.turn?.assistant;
|
|
868
|
-
const text = blocksToText(assistant.blocks);
|
|
869
|
-
const actions = Array.isArray(assistant.actions) && assistant.actions.length ? assistant.actions : void 0;
|
|
870
|
-
if (text) {
|
|
871
|
-
updateJobProgress(jobId, {
|
|
872
|
-
status,
|
|
873
|
-
resultMessage: text,
|
|
874
|
-
...actions ? { resultActions: actions } : {}
|
|
875
|
-
});
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
} catch {
|
|
881
|
-
}
|
|
882
|
-
await new Promise((r) => setTimeout(r, 800));
|
|
883
|
-
}
|
|
884
|
-
const fallback = jobSnap.result?.message || jobSnap.state?.finalReply || (status === "error" ? "Something went wrong. Please try again." : "Done.");
|
|
885
|
-
updateJobProgress(jobId, { status, resultMessage: fallback });
|
|
681
|
+
}));
|
|
682
|
+
|
|
683
|
+
// src/acs/homeActionsStore.ts
|
|
684
|
+
import { create as create2 } from "zustand";
|
|
685
|
+
var CACHE_KEY = "ateam.homeActions.v1";
|
|
686
|
+
function persist(actions) {
|
|
687
|
+
getAcsCacheStorage()?.setItem(CACHE_KEY, JSON.stringify(actions)).catch(() => {
|
|
688
|
+
});
|
|
886
689
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
690
|
+
function sortByPin(a, b) {
|
|
691
|
+
if (!!a.pinned !== !!b.pinned) return a.pinned ? -1 : 1;
|
|
692
|
+
const ao = typeof a.pinOrder === "number" ? a.pinOrder : Number.MAX_SAFE_INTEGER;
|
|
693
|
+
const bo = typeof b.pinOrder === "number" ? b.pinOrder : Number.MAX_SAFE_INTEGER;
|
|
694
|
+
return ao - bo;
|
|
695
|
+
}
|
|
696
|
+
var useHomeActionsStore = create2((set, get) => ({
|
|
697
|
+
actions: [],
|
|
698
|
+
hydrated: false,
|
|
699
|
+
loading: false,
|
|
700
|
+
hydrate: async () => {
|
|
701
|
+
if (get().hydrated || get().loading) return;
|
|
890
702
|
try {
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
|
|
703
|
+
const cached = await getAcsCacheStorage()?.getItem(CACHE_KEY);
|
|
704
|
+
if (cached) {
|
|
705
|
+
const parsed = JSON.parse(cached);
|
|
706
|
+
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
707
|
+
set({ actions: parsed, hydrated: true });
|
|
894
708
|
}
|
|
895
|
-
});
|
|
896
|
-
if (!res.ok) {
|
|
897
|
-
if (res.status === 404) return;
|
|
898
|
-
continue;
|
|
899
|
-
}
|
|
900
|
-
const snap = await res.json();
|
|
901
|
-
const progress = snapshotToProgress(snap, jobId);
|
|
902
|
-
if (progress.status === "paused" && progress.pendingQuestion) {
|
|
903
|
-
updateJobProgress(jobId, progress);
|
|
904
|
-
return;
|
|
905
|
-
}
|
|
906
|
-
updateJobProgress(jobId, progress);
|
|
907
|
-
if (isTerminalSnapshot(snap)) {
|
|
908
|
-
fetchTurnResult(jobId, snap);
|
|
909
|
-
return;
|
|
910
709
|
}
|
|
911
710
|
} catch {
|
|
912
711
|
}
|
|
913
|
-
await
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
712
|
+
await get().refresh();
|
|
713
|
+
},
|
|
714
|
+
refresh: async () => {
|
|
715
|
+
if (get().loading) return;
|
|
716
|
+
set({ loading: true });
|
|
717
|
+
const actions = await listHomeActions();
|
|
718
|
+
if (actions.length > 0) {
|
|
719
|
+
set({ actions, loading: false, hydrated: true });
|
|
720
|
+
persist(actions);
|
|
721
|
+
} else {
|
|
722
|
+
set({ loading: false, hydrated: true });
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
// clear() wipes in-memory state on sign-out AND drops the disk cache — a
|
|
726
|
+
// different user must not see the prior user's cards.
|
|
727
|
+
clear: () => {
|
|
728
|
+
set({ actions: [], hydrated: false, loading: false });
|
|
729
|
+
getAcsCacheStorage()?.setItem(CACHE_KEY, "[]").catch(() => {
|
|
928
730
|
});
|
|
929
|
-
const data = await res.json().catch(() => null);
|
|
930
|
-
if (!res.ok || !data?.ok || !Array.isArray(data.tenants)) return [];
|
|
931
|
-
return data.tenants.map((t) => ({
|
|
932
|
-
id: t.id,
|
|
933
|
-
name: t.name || t.id,
|
|
934
|
-
role: t.role ?? null
|
|
935
|
-
}));
|
|
936
|
-
} catch {
|
|
937
|
-
return [];
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
async function switchTenant(targetTenantId) {
|
|
941
|
-
const token = getToken();
|
|
942
|
-
if (!token) throw new Error("[adas] switchTenant: not signed in");
|
|
943
|
-
const res = await fetch(`${getConsumerAuthApiUrl()}/platform/consumer/switch-tenant`, {
|
|
944
|
-
method: "POST",
|
|
945
|
-
headers: {
|
|
946
|
-
"Content-Type": "application/json",
|
|
947
|
-
Authorization: `Bearer ${token}`
|
|
948
|
-
},
|
|
949
|
-
body: JSON.stringify({ target_tenant: targetTenantId })
|
|
950
|
-
});
|
|
951
|
-
const data = await res.json().catch(() => null);
|
|
952
|
-
if (!res.ok || !data?.ok || !data.jwt) {
|
|
953
|
-
throw new Error(data?.error || `switchTenant failed (${res.status})`);
|
|
954
|
-
}
|
|
955
|
-
const session = {
|
|
956
|
-
jwt: data.jwt,
|
|
957
|
-
tenant: data.tenant,
|
|
958
|
-
actor: data.actor
|
|
959
|
-
};
|
|
960
|
-
const onSession = getConfig().onSession;
|
|
961
|
-
if (!onSession) {
|
|
962
|
-
throw new Error("[adas] switchTenant: config.onSession is required to persist the new session");
|
|
963
|
-
}
|
|
964
|
-
await onSession(session);
|
|
965
|
-
closeChatStream();
|
|
966
|
-
useChatStore.getState().clearChat();
|
|
967
|
-
return session;
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
// src/theme/tokens.ts
|
|
971
|
-
var colors = {
|
|
972
|
-
dark: {
|
|
973
|
-
bgPrimary: "#0a0e14",
|
|
974
|
-
bgSecondary: "#111820",
|
|
975
|
-
bgTertiary: "#1a2230",
|
|
976
|
-
bgInput: "#0d1219",
|
|
977
|
-
bgHover: "#1e2a3a",
|
|
978
|
-
border: "rgba(255, 255, 255, 0.08)",
|
|
979
|
-
borderHover: "rgba(255, 255, 255, 0.15)",
|
|
980
|
-
textPrimary: "#e8ecf1",
|
|
981
|
-
textSecondary: "#8b95a5",
|
|
982
|
-
textMuted: "#5a6577",
|
|
983
|
-
accent: "#2563eb",
|
|
984
|
-
accentHover: "#3b82f6",
|
|
985
|
-
accentSoft: "rgba(37, 99, 235, 0.15)",
|
|
986
|
-
green: "#16a34a",
|
|
987
|
-
greenSoft: "rgba(22, 163, 74, 0.15)",
|
|
988
|
-
red: "#dc2626",
|
|
989
|
-
redSoft: "rgba(220, 38, 38, 0.15)",
|
|
990
|
-
amber: "#f59e0b",
|
|
991
|
-
amberSoft: "rgba(245, 158, 11, 0.15)",
|
|
992
|
-
purple: "#8b5cf6",
|
|
993
|
-
purpleSoft: "rgba(139, 92, 246, 0.15)",
|
|
994
|
-
// Message-specific
|
|
995
|
-
userBubble: "#2563eb",
|
|
996
|
-
botBubble: "#1a2230",
|
|
997
|
-
botBubbleBorder: "rgba(255, 255, 255, 0.08)"
|
|
998
731
|
},
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
md: 12,
|
|
1065
|
-
lg: 16,
|
|
1066
|
-
xl: 20,
|
|
1067
|
-
full: 9999
|
|
1068
|
-
};
|
|
732
|
+
dismiss: async (actionId) => {
|
|
733
|
+
const before = get().actions;
|
|
734
|
+
const next = before.filter((a) => a.actionId !== actionId);
|
|
735
|
+
set({ actions: next });
|
|
736
|
+
persist(next);
|
|
737
|
+
const r = await patchHomeAction(actionId, { dismissed: true }, "user_dismiss");
|
|
738
|
+
if (r.ok) return { ok: true };
|
|
739
|
+
set({ actions: before });
|
|
740
|
+
persist(before);
|
|
741
|
+
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
742
|
+
},
|
|
743
|
+
setPinned: async (actionId, pinned) => {
|
|
744
|
+
const before = get().actions;
|
|
745
|
+
const next = before.map((a) => a.actionId === actionId ? { ...a, pinned } : a).sort(sortByPin);
|
|
746
|
+
set({ actions: next });
|
|
747
|
+
persist(next);
|
|
748
|
+
const r = await patchHomeAction(actionId, { pinned }, pinned ? "user_pin" : "user_unpin");
|
|
749
|
+
if (r.ok) {
|
|
750
|
+
if (r.action) {
|
|
751
|
+
const merged = get().actions.map((a) => a.actionId === actionId ? { ...a, ...r.action } : a).sort(sortByPin);
|
|
752
|
+
set({ actions: merged });
|
|
753
|
+
persist(merged);
|
|
754
|
+
}
|
|
755
|
+
return { ok: true };
|
|
756
|
+
}
|
|
757
|
+
set({ actions: before });
|
|
758
|
+
persist(before);
|
|
759
|
+
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
760
|
+
},
|
|
761
|
+
reorder: async (orderedIds) => {
|
|
762
|
+
const before = get().actions;
|
|
763
|
+
const idx = new Map(orderedIds.map((id, i) => [id, i]));
|
|
764
|
+
const next = before.map((a) => {
|
|
765
|
+
const o = idx.get(a.actionId);
|
|
766
|
+
return o == null ? a : { ...a, pinOrder: o };
|
|
767
|
+
}).sort(sortByPin);
|
|
768
|
+
set({ actions: next });
|
|
769
|
+
persist(next);
|
|
770
|
+
const r = await setHomeActionOrder(orderedIds);
|
|
771
|
+
if (r.ok) return { ok: true };
|
|
772
|
+
set({ actions: before });
|
|
773
|
+
persist(before);
|
|
774
|
+
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
775
|
+
},
|
|
776
|
+
applyServerUpsert: (action) => {
|
|
777
|
+
const rest = get().actions.filter((a) => a.actionId !== action.actionId);
|
|
778
|
+
const next = [...rest, action].sort(sortByPin);
|
|
779
|
+
set({ actions: next });
|
|
780
|
+
persist(next);
|
|
781
|
+
},
|
|
782
|
+
applyServerRemove: (actionId) => {
|
|
783
|
+
const next = get().actions.filter((a) => a.actionId !== actionId);
|
|
784
|
+
set({ actions: next });
|
|
785
|
+
persist(next);
|
|
786
|
+
},
|
|
787
|
+
applyServerOrder: (orderedIds) => {
|
|
788
|
+
const idx = new Map(orderedIds.map((id, i) => [id, i]));
|
|
789
|
+
const next = get().actions.map((a) => {
|
|
790
|
+
const o = idx.get(a.actionId);
|
|
791
|
+
return o == null ? a : { ...a, pinOrder: o };
|
|
792
|
+
}).sort(sortByPin);
|
|
793
|
+
set({ actions: next });
|
|
794
|
+
persist(next);
|
|
795
|
+
}
|
|
796
|
+
}));
|
|
1069
797
|
|
|
1070
|
-
// src/acs/
|
|
1071
|
-
var
|
|
1072
|
-
|
|
1073
|
-
|
|
798
|
+
// src/acs/ws.ts
|
|
799
|
+
var _ws = null;
|
|
800
|
+
var _wantOpen = false;
|
|
801
|
+
var _retryAttempt = 0;
|
|
802
|
+
var _retryTimer = null;
|
|
803
|
+
var _token = null;
|
|
804
|
+
var _tenant = null;
|
|
805
|
+
var TOPIC = "acs.artifacts";
|
|
806
|
+
var TOPIC_HOME = "acs.home-actions";
|
|
807
|
+
var MAX_BACKOFF_MS = 3e4;
|
|
808
|
+
var TERMINAL_CLOSE_CODES = /* @__PURE__ */ new Set([4003]);
|
|
809
|
+
var _terminated = false;
|
|
810
|
+
function backoffMs(attempt) {
|
|
811
|
+
const base = Math.min(MAX_BACKOFF_MS, 500 * Math.pow(2, attempt));
|
|
812
|
+
const jitter = Math.random() * 250;
|
|
813
|
+
return base + jitter;
|
|
1074
814
|
}
|
|
1075
|
-
function
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
815
|
+
function send(obj) {
|
|
816
|
+
if (_ws && _ws.readyState === 1) {
|
|
817
|
+
try {
|
|
818
|
+
_ws.send(JSON.stringify(obj));
|
|
819
|
+
} catch (e) {
|
|
820
|
+
console.warn("[acsWs] send failed", e);
|
|
821
|
+
}
|
|
1079
822
|
}
|
|
1080
823
|
}
|
|
1081
|
-
function
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
824
|
+
function handleEvent(msg) {
|
|
825
|
+
if (msg?.event === "subscribed") {
|
|
826
|
+
console.log("[acsWs] subscribed to", msg?.data?.topic);
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
if (msg?.topic === TOPIC_HOME) {
|
|
830
|
+
const home = useHomeActionsStore.getState();
|
|
831
|
+
switch (msg.event) {
|
|
832
|
+
case "updated":
|
|
833
|
+
if (msg.action && typeof msg.action.actionId === "string") home.applyServerUpsert(msg.action);
|
|
834
|
+
break;
|
|
835
|
+
case "removed":
|
|
836
|
+
if (typeof msg.actionId === "string") home.applyServerRemove(msg.actionId);
|
|
837
|
+
break;
|
|
838
|
+
case "order":
|
|
839
|
+
if (Array.isArray(msg.order)) home.applyServerOrder(msg.order.filter((id) => typeof id === "string"));
|
|
840
|
+
break;
|
|
841
|
+
case "refreshed":
|
|
842
|
+
home.refresh();
|
|
843
|
+
break;
|
|
844
|
+
default:
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (msg?.topic !== TOPIC) return;
|
|
850
|
+
const store = useWidgetsStore.getState();
|
|
851
|
+
switch (msg.event) {
|
|
852
|
+
case "updated":
|
|
853
|
+
if (msg.artifact && typeof msg.artifact.id === "string") {
|
|
854
|
+
store.applyServerUpdate(msg.artifact);
|
|
855
|
+
}
|
|
856
|
+
break;
|
|
857
|
+
case "retired":
|
|
858
|
+
if (typeof msg.artifactId === "string") {
|
|
859
|
+
store.applyServerRetire(msg.artifactId);
|
|
860
|
+
}
|
|
861
|
+
break;
|
|
862
|
+
case "order":
|
|
863
|
+
if (Array.isArray(msg.pinned)) {
|
|
864
|
+
store.applyServerOrder(msg.pinned.filter((id) => typeof id === "string"));
|
|
865
|
+
}
|
|
866
|
+
break;
|
|
867
|
+
case "created":
|
|
868
|
+
if (msg.artifact && typeof msg.artifact.id === "string") {
|
|
869
|
+
store.applyServerUpdate(msg.artifact);
|
|
870
|
+
}
|
|
871
|
+
break;
|
|
872
|
+
default:
|
|
873
|
+
break;
|
|
1085
874
|
}
|
|
1086
875
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
876
|
+
function openSocket() {
|
|
877
|
+
if (_ws) return;
|
|
878
|
+
if (!_token || !_tenant) return;
|
|
879
|
+
const url = `${getCoreWsUrl()}/ws?token=${encodeURIComponent(_token)}&tenant=${encodeURIComponent(_tenant)}`;
|
|
880
|
+
console.log(`[acsWs] opening`);
|
|
881
|
+
const ws = new WebSocket(url);
|
|
882
|
+
_ws = ws;
|
|
883
|
+
const isCurrent = () => ws === _ws;
|
|
884
|
+
ws.onopen = () => {
|
|
885
|
+
if (!isCurrent()) return;
|
|
886
|
+
_retryAttempt = 0;
|
|
887
|
+
send({ type: "subscribe", topic: TOPIC });
|
|
888
|
+
send({ type: "subscribe", topic: TOPIC_HOME });
|
|
889
|
+
};
|
|
890
|
+
ws.onmessage = (e) => {
|
|
891
|
+
if (!isCurrent()) return;
|
|
892
|
+
try {
|
|
893
|
+
const msg = JSON.parse(typeof e.data === "string" ? e.data : "");
|
|
894
|
+
handleEvent(msg);
|
|
895
|
+
} catch (err) {
|
|
896
|
+
console.warn("[acsWs] parse failed", err);
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
ws.onerror = (e) => {
|
|
900
|
+
if (!isCurrent()) return;
|
|
901
|
+
console.warn("[acsWs] error", e?.message || e);
|
|
902
|
+
};
|
|
903
|
+
ws.onclose = (ev) => {
|
|
904
|
+
if (!isCurrent()) return;
|
|
905
|
+
_ws = null;
|
|
906
|
+
if (!_wantOpen) return;
|
|
907
|
+
const code = typeof ev?.code === "number" ? ev.code : 0;
|
|
908
|
+
if (TERMINAL_CLOSE_CODES.has(code)) {
|
|
909
|
+
_terminated = true;
|
|
910
|
+
if (_retryTimer) {
|
|
911
|
+
clearTimeout(_retryTimer);
|
|
912
|
+
_retryTimer = null;
|
|
913
|
+
}
|
|
914
|
+
console.warn(`[acsWs] server refused permanently (close ${code}); stopping reconnects until re-auth/tenant change`);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
const delay = backoffMs(_retryAttempt++);
|
|
918
|
+
console.log(`[acsWs] closed (${code}); reconnecting in ${Math.round(delay)}ms (attempt ${_retryAttempt})`);
|
|
919
|
+
if (_retryTimer) clearTimeout(_retryTimer);
|
|
920
|
+
_retryTimer = setTimeout(openSocket, delay);
|
|
921
|
+
};
|
|
1090
922
|
}
|
|
1091
|
-
function
|
|
1092
|
-
return
|
|
923
|
+
function notifyAcsWsForeground() {
|
|
924
|
+
if (_terminated) return;
|
|
925
|
+
if (_wantOpen && !_ws) {
|
|
926
|
+
if (_retryTimer) {
|
|
927
|
+
clearTimeout(_retryTimer);
|
|
928
|
+
_retryTimer = null;
|
|
929
|
+
}
|
|
930
|
+
_retryAttempt = 0;
|
|
931
|
+
openSocket();
|
|
932
|
+
}
|
|
1093
933
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
1104
|
-
...init?.headers || {}
|
|
1105
|
-
}
|
|
1106
|
-
});
|
|
1107
|
-
reportAcsServerUp();
|
|
1108
|
-
return res;
|
|
1109
|
-
} catch (e) {
|
|
1110
|
-
reportAcsServerDown();
|
|
1111
|
-
throw e;
|
|
934
|
+
function connectAcsWs(token, tenant) {
|
|
935
|
+
_terminated = false;
|
|
936
|
+
if (_ws && _wantOpen && _token === token && _tenant === tenant) return;
|
|
937
|
+
if (_ws) {
|
|
938
|
+
try {
|
|
939
|
+
_ws.close();
|
|
940
|
+
} catch {
|
|
941
|
+
}
|
|
942
|
+
_ws = null;
|
|
1112
943
|
}
|
|
944
|
+
_token = token;
|
|
945
|
+
_tenant = tenant;
|
|
946
|
+
_wantOpen = true;
|
|
947
|
+
openSocket();
|
|
1113
948
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
949
|
+
function disconnectAcsWs() {
|
|
950
|
+
_wantOpen = false;
|
|
951
|
+
_terminated = false;
|
|
952
|
+
_token = null;
|
|
953
|
+
_tenant = null;
|
|
954
|
+
if (_retryTimer) {
|
|
955
|
+
clearTimeout(_retryTimer);
|
|
956
|
+
_retryTimer = null;
|
|
957
|
+
}
|
|
958
|
+
if (_ws) {
|
|
959
|
+
try {
|
|
960
|
+
_ws.close();
|
|
961
|
+
} catch {
|
|
1123
962
|
}
|
|
1124
|
-
|
|
1125
|
-
return Array.isArray(data?.items) ? data.items : [];
|
|
1126
|
-
} catch (e) {
|
|
1127
|
-
console.warn("[acs] /surface failed:", e?.message || e);
|
|
1128
|
-
return [];
|
|
963
|
+
_ws = null;
|
|
1129
964
|
}
|
|
1130
965
|
}
|
|
1131
|
-
|
|
966
|
+
|
|
967
|
+
// src/lifecycle.ts
|
|
968
|
+
var foregroundCbs = /* @__PURE__ */ new Set();
|
|
969
|
+
function onAppForeground(cb) {
|
|
970
|
+
foregroundCbs.add(cb);
|
|
971
|
+
return () => {
|
|
972
|
+
foregroundCbs.delete(cb);
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
function notifyAppForeground() {
|
|
1132
976
|
try {
|
|
1133
|
-
|
|
1134
|
-
if (!res.ok) {
|
|
1135
|
-
console.warn(`[acs] /made-for-you returned ${res.status}`);
|
|
1136
|
-
return [];
|
|
1137
|
-
}
|
|
1138
|
-
const data = await res.json();
|
|
1139
|
-
return Array.isArray(data?.artifacts) ? data.artifacts : [];
|
|
977
|
+
notifyAcsWsForeground();
|
|
1140
978
|
} catch (e) {
|
|
1141
|
-
console.warn("[
|
|
1142
|
-
|
|
979
|
+
console.warn("[adas/lifecycle] acs foreground reconnect failed:", e?.message || e);
|
|
980
|
+
}
|
|
981
|
+
for (const cb of foregroundCbs) {
|
|
982
|
+
try {
|
|
983
|
+
cb();
|
|
984
|
+
} catch (e) {
|
|
985
|
+
console.warn("[adas/lifecycle] foreground callback failed:", e?.message || e);
|
|
986
|
+
}
|
|
1143
987
|
}
|
|
1144
988
|
}
|
|
1145
|
-
|
|
989
|
+
|
|
990
|
+
// src/gate.ts
|
|
991
|
+
function resolveAdasPhase(f) {
|
|
992
|
+
if (!f.configHydrated || f.authLoading || f.capabilitiesLoading || f.onboarded === void 0) {
|
|
993
|
+
return "loading";
|
|
994
|
+
}
|
|
995
|
+
if (!f.onboarded) return "onboarding";
|
|
996
|
+
if (!f.isAuthenticated) {
|
|
997
|
+
return f.handoffPending ? "handoff" : f.invitePending ? "invite" : "login";
|
|
998
|
+
}
|
|
999
|
+
if (f.requireTenantPick && !f.tenantConfirmed) return "tenant";
|
|
1000
|
+
if (!f.hasCompletedSetup) return "capabilities";
|
|
1001
|
+
return "app";
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// src/consumerAuth.ts
|
|
1005
|
+
function cleanErrorMessage(status, rawText, fallback) {
|
|
1006
|
+
const trimmed = (rawText || "").trim();
|
|
1007
|
+
if (trimmed.startsWith("<")) {
|
|
1008
|
+
return `${fallback} (${status}) \u2014 backend unreachable`;
|
|
1009
|
+
}
|
|
1010
|
+
if (!trimmed) return `${fallback} (${status})`;
|
|
1011
|
+
const short = trimmed.length > 200 ? `${trimmed.slice(0, 200)}\u2026` : trimmed;
|
|
1012
|
+
return `${fallback} (${status}): ${short}`;
|
|
1013
|
+
}
|
|
1014
|
+
async function postJson(path, body, jwt) {
|
|
1015
|
+
const res = await fetch(`${getConsumerAuthApiUrl()}${path}`, {
|
|
1016
|
+
method: "POST",
|
|
1017
|
+
headers: {
|
|
1018
|
+
"Content-Type": "application/json",
|
|
1019
|
+
...jwt ? { Authorization: `Bearer ${jwt}` } : {}
|
|
1020
|
+
},
|
|
1021
|
+
body: JSON.stringify(body)
|
|
1022
|
+
});
|
|
1023
|
+
const text = await res.text();
|
|
1024
|
+
let parsed = null;
|
|
1146
1025
|
try {
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
return { ok: true, already: !!data?.already, status: res.status };
|
|
1154
|
-
}
|
|
1155
|
-
if (res.status === 404) {
|
|
1156
|
-
return { ok: true, notFound: true, status: 404 };
|
|
1157
|
-
}
|
|
1158
|
-
return { ok: false, status: res.status, error: data?.error || `HTTP ${res.status}` };
|
|
1159
|
-
} catch (e) {
|
|
1160
|
-
return { ok: false, status: 0, error: e?.message || "network error" };
|
|
1026
|
+
parsed = JSON.parse(text);
|
|
1027
|
+
} catch {
|
|
1028
|
+
}
|
|
1029
|
+
if (!res.ok || parsed?.ok === false) {
|
|
1030
|
+
if (parsed?.error) throw new Error(parsed.error);
|
|
1031
|
+
throw new Error(cleanErrorMessage(res.status, text, `consumer-auth ${path} failed`));
|
|
1161
1032
|
}
|
|
1033
|
+
return parsed;
|
|
1162
1034
|
}
|
|
1163
|
-
async function
|
|
1035
|
+
async function fetchConsumerConfig() {
|
|
1036
|
+
const url = `${getConsumerAuthApiUrl()}/platform/consumer/config?tenant=${encodeURIComponent(getTenant())}`;
|
|
1037
|
+
const res = await fetch(url);
|
|
1038
|
+
const text = await res.text();
|
|
1039
|
+
let data = null;
|
|
1164
1040
|
try {
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
return {
|
|
1172
|
-
ok: true,
|
|
1173
|
-
already: !!data?.already,
|
|
1174
|
-
artifact: data?.artifact || void 0,
|
|
1175
|
-
status: res.status
|
|
1176
|
-
};
|
|
1177
|
-
}
|
|
1178
|
-
if (res.status === 404) {
|
|
1179
|
-
return { ok: true, notFound: true, status: 404 };
|
|
1180
|
-
}
|
|
1181
|
-
return {
|
|
1182
|
-
ok: false,
|
|
1183
|
-
status: res.status,
|
|
1184
|
-
error: data?.error || `HTTP ${res.status}`
|
|
1185
|
-
};
|
|
1186
|
-
} catch (e) {
|
|
1187
|
-
return { ok: false, status: 0, error: e?.message || "network error" };
|
|
1041
|
+
data = JSON.parse(text);
|
|
1042
|
+
} catch {
|
|
1043
|
+
}
|
|
1044
|
+
if (!res.ok || !data?.ok) {
|
|
1045
|
+
if (data?.error) throw new Error(data.error);
|
|
1046
|
+
throw new Error(cleanErrorMessage(res.status, text, "Failed to load sign-in config"));
|
|
1188
1047
|
}
|
|
1048
|
+
return data;
|
|
1189
1049
|
}
|
|
1190
|
-
async function
|
|
1050
|
+
async function signInWithGoogle(idToken) {
|
|
1051
|
+
return postJson("/platform/consumer/oauth/google", {
|
|
1052
|
+
tenant: getTenant(),
|
|
1053
|
+
id_token: idToken
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
async function signInWithApple(idToken) {
|
|
1057
|
+
return postJson("/platform/consumer/oauth/apple", {
|
|
1058
|
+
tenant: getTenant(),
|
|
1059
|
+
id_token: idToken
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
async function exchangeHandoff(code) {
|
|
1063
|
+
return postJson("/platform/consumer/handoff/exchange", { code });
|
|
1064
|
+
}
|
|
1065
|
+
async function previewInvite(code) {
|
|
1066
|
+
const url = `${getConsumerAuthApiUrl()}/platform/consumer/invite/${encodeURIComponent(code)}`;
|
|
1067
|
+
const res = await fetch(url);
|
|
1068
|
+
const text = await res.text();
|
|
1069
|
+
let data = null;
|
|
1191
1070
|
try {
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
body: JSON.stringify({ pinned })
|
|
1195
|
-
});
|
|
1196
|
-
if (res.ok) return { ok: true, status: res.status };
|
|
1197
|
-
const data = await res.json().catch(() => ({}));
|
|
1198
|
-
return { ok: false, status: res.status, error: data?.error || `HTTP ${res.status}` };
|
|
1199
|
-
} catch (e) {
|
|
1200
|
-
return { ok: false, status: 0, error: e?.message || "network error" };
|
|
1071
|
+
data = JSON.parse(text);
|
|
1072
|
+
} catch {
|
|
1201
1073
|
}
|
|
1074
|
+
if (res.status === 404) throw new Error("This invite is invalid or has expired.");
|
|
1075
|
+
if (!res.ok || !data?.ok) {
|
|
1076
|
+
if (data?.error) throw new Error(data.error);
|
|
1077
|
+
throw new Error(cleanErrorMessage(res.status, text, "Could not load invite"));
|
|
1078
|
+
}
|
|
1079
|
+
return data;
|
|
1202
1080
|
}
|
|
1203
|
-
async function
|
|
1081
|
+
async function joinWithGoogle(invite, idToken) {
|
|
1082
|
+
return postJson("/platform/consumer/oauth/google", { invite, id_token: idToken });
|
|
1083
|
+
}
|
|
1084
|
+
async function joinWithApple(invite, idToken) {
|
|
1085
|
+
return postJson("/platform/consumer/oauth/apple", { invite, id_token: idToken });
|
|
1086
|
+
}
|
|
1087
|
+
async function openJoinWithGoogle(tenant, idToken) {
|
|
1088
|
+
return postJson("/platform/consumer/oauth/google", { tenant, id_token: idToken });
|
|
1089
|
+
}
|
|
1090
|
+
async function openJoinWithApple(tenant, idToken) {
|
|
1091
|
+
return postJson("/platform/consumer/oauth/apple", { tenant, id_token: idToken });
|
|
1092
|
+
}
|
|
1093
|
+
async function linkChannelStart(jwt, channel, handle) {
|
|
1094
|
+
return postJson("/platform/consumer/link-channel/start", { channel, handle }, jwt);
|
|
1095
|
+
}
|
|
1096
|
+
async function linkChannelVerify(jwt, challengeId, code) {
|
|
1097
|
+
return postJson("/platform/consumer/link-channel/verify", { challenge_id: challengeId, code }, jwt);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// src/deepLinks.ts
|
|
1101
|
+
function parseDeepLinkParam(url, param) {
|
|
1102
|
+
if (!url) return null;
|
|
1103
|
+
const re = new RegExp(`[?&]${param}=([^&#\\s]+)`, "i");
|
|
1104
|
+
const m = url.match(re);
|
|
1105
|
+
if (!m) return null;
|
|
1204
1106
|
try {
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
body: JSON.stringify({ artifactId, event, ...surface ? { surface } : {} })
|
|
1208
|
-
});
|
|
1107
|
+
const v = decodeURIComponent(m[1]);
|
|
1108
|
+
return v.length > 0 ? v : null;
|
|
1209
1109
|
} catch {
|
|
1110
|
+
return m[1].length > 0 ? m[1] : null;
|
|
1210
1111
|
}
|
|
1211
1112
|
}
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1113
|
+
function parseInviteCode(url) {
|
|
1114
|
+
return parseDeepLinkParam(url, "invite");
|
|
1115
|
+
}
|
|
1116
|
+
function parseHandoffCode(url) {
|
|
1117
|
+
return parseDeepLinkParam(url, "handoff");
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// src/workspace.ts
|
|
1121
|
+
import { create as create3 } from "zustand";
|
|
1122
|
+
function normalize(w) {
|
|
1123
|
+
if (!w || typeof w !== "object") return null;
|
|
1124
|
+
const state = w.state === "active" || w.state === "expired" || w.state === "suspended" ? w.state : "unknown";
|
|
1125
|
+
return {
|
|
1126
|
+
state,
|
|
1127
|
+
reason: typeof w.reason === "string" ? w.reason : null,
|
|
1128
|
+
trialEndsAt: typeof w.trialEndsAt === "string" ? w.trialEndsAt : null,
|
|
1129
|
+
expiresInDays: typeof w.expiresInDays === "number" ? w.expiresInDays : null,
|
|
1130
|
+
capabilities: w.capabilities && typeof w.capabilities === "object" ? w.capabilities : {},
|
|
1131
|
+
message: typeof w.message === "string" && w.message.trim() ? w.message : null
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
var _gen = 0;
|
|
1135
|
+
var useWorkspaceStore = create3((set) => ({
|
|
1136
|
+
snapshot: null,
|
|
1137
|
+
loaded: false,
|
|
1138
|
+
refresh: async () => {
|
|
1139
|
+
const gen = _gen;
|
|
1140
|
+
try {
|
|
1141
|
+
const res = await apiFetch("/api/session");
|
|
1142
|
+
if (gen !== _gen) return;
|
|
1143
|
+
set({ snapshot: normalize(res?.workspace), loaded: true });
|
|
1144
|
+
} catch {
|
|
1145
|
+
if (gen !== _gen) return;
|
|
1146
|
+
set({ loaded: true });
|
|
1218
1147
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
return [];
|
|
1148
|
+
},
|
|
1149
|
+
clear: () => {
|
|
1150
|
+
_gen++;
|
|
1151
|
+
set({ snapshot: null, loaded: false });
|
|
1224
1152
|
}
|
|
1153
|
+
}));
|
|
1154
|
+
function capabilityEnabled(name) {
|
|
1155
|
+
const { snapshot } = useWorkspaceStore.getState();
|
|
1156
|
+
if (!snapshot) return true;
|
|
1157
|
+
return snapshot.capabilities?.[name] === true;
|
|
1158
|
+
}
|
|
1159
|
+
function useCapability(name) {
|
|
1160
|
+
return useWorkspaceStore((s) => {
|
|
1161
|
+
if (!s.snapshot) return true;
|
|
1162
|
+
return s.snapshot.capabilities?.[name] === true;
|
|
1163
|
+
});
|
|
1225
1164
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1165
|
+
|
|
1166
|
+
// src/chat/store.ts
|
|
1167
|
+
import { create as create4 } from "zustand";
|
|
1168
|
+
|
|
1169
|
+
// src/utils/emitter.ts
|
|
1170
|
+
var RECENT_TTL_MS = 1e4;
|
|
1171
|
+
var RECENT_MAX = 20;
|
|
1172
|
+
var PluginCommandEmitter = class {
|
|
1173
|
+
constructor() {
|
|
1174
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
1175
|
+
// Ring buffer of plugin commands seen recently. Lets a plugin that mounts
|
|
1176
|
+
// AFTER its first command was emitted (e.g. a surface mounted by the
|
|
1177
|
+
// dispatcher in response to focus-plugin) still pick that command up via
|
|
1178
|
+
// `drainPluginCommands(pluginId)`. Self-cleans on TTL + size cap.
|
|
1179
|
+
this.recent = [];
|
|
1180
|
+
}
|
|
1181
|
+
on(event, fn) {
|
|
1182
|
+
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
1183
|
+
this.listeners.get(event).add(fn);
|
|
1184
|
+
return () => this.off(event, fn);
|
|
1185
|
+
}
|
|
1186
|
+
off(event, fn) {
|
|
1187
|
+
this.listeners.get(event)?.delete(fn);
|
|
1188
|
+
}
|
|
1189
|
+
emit(event, data) {
|
|
1190
|
+
if (event === "plugin-command" && data && data.pluginId && data.command && data.command !== "__focus") {
|
|
1191
|
+
this.recent.push({
|
|
1192
|
+
pluginId: data.pluginId,
|
|
1193
|
+
command: data.command,
|
|
1194
|
+
args: data.args,
|
|
1195
|
+
correlationId: data.correlationId,
|
|
1196
|
+
_bufferedAt: Date.now()
|
|
1197
|
+
});
|
|
1198
|
+
if (this.recent.length > RECENT_MAX) this.recent.shift();
|
|
1199
|
+
}
|
|
1200
|
+
this.listeners.get(event)?.forEach((fn) => {
|
|
1201
|
+
try {
|
|
1202
|
+
fn(data);
|
|
1203
|
+
} catch {
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
/** Pop and return any plugin-commands targeted at `pluginId` that were
|
|
1208
|
+
* emitted within the last RECENT_TTL_MS. Used by the native bridge on
|
|
1209
|
+
* mount so the surface-dispatcher path doesn't drop the first command. */
|
|
1210
|
+
drainPluginCommands(pluginId) {
|
|
1211
|
+
const now = Date.now();
|
|
1212
|
+
const out = [];
|
|
1213
|
+
const remaining = [];
|
|
1214
|
+
for (const cmd of this.recent) {
|
|
1215
|
+
if (now - cmd._bufferedAt > RECENT_TTL_MS) continue;
|
|
1216
|
+
if (cmd.pluginId === pluginId) out.push(cmd);
|
|
1217
|
+
else remaining.push(cmd);
|
|
1218
|
+
}
|
|
1219
|
+
this.recent = remaining;
|
|
1220
|
+
return out;
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
var pluginCommandEmitter = new PluginCommandEmitter();
|
|
1224
|
+
|
|
1225
|
+
// src/chat/store.ts
|
|
1226
|
+
function coreHeaders() {
|
|
1227
|
+
const token = getToken();
|
|
1228
|
+
const headers = {
|
|
1229
|
+
"Content-Type": "application/json"
|
|
1230
|
+
};
|
|
1231
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
1232
|
+
return headers;
|
|
1233
|
+
}
|
|
1234
|
+
function isTerminalStatus(status) {
|
|
1235
|
+
const s = typeof status === "string" ? status.toLowerCase() : "";
|
|
1236
|
+
return s === "done" || s === "error" || s === "failed" || s === "completed";
|
|
1237
|
+
}
|
|
1238
|
+
function normalizeJobStatus(snap) {
|
|
1239
|
+
const status = typeof snap?.status === "string" ? snap.status.toLowerCase() : "";
|
|
1240
|
+
if (status === "done" || status === "completed") return "done";
|
|
1241
|
+
if (status === "error" || status === "failed") return "error";
|
|
1242
|
+
if (status === "paused" || status === "waiting") return "paused";
|
|
1243
|
+
if (status === "unknown") return "unknown";
|
|
1244
|
+
return "running";
|
|
1245
|
+
}
|
|
1246
|
+
function extractPendingQuestion(snap) {
|
|
1247
|
+
return snap?.pendingQuestion || snap?.state?.pendingQuestion || snap?.state?.waiting || null;
|
|
1248
|
+
}
|
|
1249
|
+
function extractActorId(snap) {
|
|
1250
|
+
const actorId = snap?.actorId || snap?.actor?.actorId || snap?.actor?.id || snap?.state?.actorId || snap?.state?.actor?.actorId || snap?.state?.actor?.id;
|
|
1251
|
+
return typeof actorId === "string" && actorId.length > 0 ? actorId : void 0;
|
|
1252
|
+
}
|
|
1253
|
+
function extractLiveProgress(snap) {
|
|
1254
|
+
const lp = snap?.liveProgress || snap?.state?.__live_progress;
|
|
1255
|
+
const latest = Array.isArray(lp) ? lp[lp.length - 1] : lp;
|
|
1256
|
+
if (!latest?.message) return void 0;
|
|
1257
|
+
return {
|
|
1258
|
+
message: String(latest.message || ""),
|
|
1259
|
+
...latest.step != null ? { step: latest.step } : {},
|
|
1260
|
+
...latest.total != null ? { total: latest.total } : {}
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
function extractCurrentTool(state) {
|
|
1264
|
+
const exec = state?.steps?.__exec;
|
|
1265
|
+
if (!exec || typeof exec !== "object") return void 0;
|
|
1266
|
+
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];
|
|
1267
|
+
if (!latest) return void 0;
|
|
1268
|
+
const steps = exec[latest.k];
|
|
1269
|
+
if (!Array.isArray(steps) || !steps.length) return void 0;
|
|
1270
|
+
const last = steps[steps.length - 1];
|
|
1271
|
+
return last?.tool || last?.phase;
|
|
1272
|
+
}
|
|
1273
|
+
function snapshotToProgress(snap, rootJobId) {
|
|
1274
|
+
const pq = extractPendingQuestion(snap);
|
|
1275
|
+
const progress = {
|
|
1276
|
+
jobId: rootJobId,
|
|
1277
|
+
actorId: extractActorId(snap),
|
|
1278
|
+
status: pq ? "paused" : normalizeJobStatus(snap),
|
|
1279
|
+
subStatus: snap?.subStatus || snap?.phase || snap?.state?.visual?.phase,
|
|
1280
|
+
phase: snap?.phase || snap?.state?.visual?.phase,
|
|
1281
|
+
iteration: snap?.iteration || snap?.state?.plannerIter,
|
|
1282
|
+
skillSlug: snap?.skillSlug || snap?.activeSkill,
|
|
1283
|
+
currentTool: snap?.currentTool || snap?.lastTool || extractCurrentTool(snap?.state)
|
|
1284
|
+
};
|
|
1285
|
+
const liveProgress = extractLiveProgress(snap);
|
|
1286
|
+
if (liveProgress) progress.liveProgress = liveProgress;
|
|
1287
|
+
if (typeof snap?.humanStatus === "string" && snap.humanStatus) progress.humanStatus = snap.humanStatus;
|
|
1288
|
+
if (snap?.humanLabels && typeof snap.humanLabels === "object") progress.humanLabels = snap.humanLabels;
|
|
1289
|
+
if (pq) {
|
|
1290
|
+
progress.pendingQuestion = {
|
|
1291
|
+
inputId: pq.correlation_id || pq.correlationId || pq.inputId || "",
|
|
1292
|
+
question: pq.prompt_message || pq.question || "The assistant needs your input",
|
|
1293
|
+
inputType: pq.inputType || "text",
|
|
1294
|
+
options: pq.options || pq.requested_fields,
|
|
1295
|
+
placeholder: pq.placeholder
|
|
1296
|
+
};
|
|
1238
1297
|
}
|
|
1298
|
+
return progress;
|
|
1299
|
+
}
|
|
1300
|
+
function isTerminalSnapshot(snap) {
|
|
1301
|
+
if (extractPendingQuestion(snap)) return false;
|
|
1302
|
+
return snap?.done === true || isTerminalStatus(snap?.status);
|
|
1303
|
+
}
|
|
1304
|
+
var TABLE_BLOCK_OPEN = "\uE100";
|
|
1305
|
+
var TABLE_BLOCK_CLOSE = "\uE101";
|
|
1306
|
+
function renderTableShape(content) {
|
|
1307
|
+
if (!content || typeof content !== "object") return null;
|
|
1308
|
+
const cols = Array.isArray(content.columns) ? content.columns : null;
|
|
1309
|
+
const rows = Array.isArray(content.rows) ? content.rows : null;
|
|
1310
|
+
if (!cols || !rows) return null;
|
|
1311
|
+
const payload = encodeURIComponent(JSON.stringify({ columns: cols, rows }));
|
|
1312
|
+
return `${TABLE_BLOCK_OPEN}${payload}${TABLE_BLOCK_CLOSE}`;
|
|
1313
|
+
}
|
|
1314
|
+
function blocksToText(blocks) {
|
|
1315
|
+
if (!blocks || blocks.length === 0) return "";
|
|
1316
|
+
const hasTextBlock = blocks.some(
|
|
1317
|
+
(b) => b.kind === "text" || b.kind === "markdown" || b.kind === "description"
|
|
1318
|
+
);
|
|
1319
|
+
return blocks.map((b) => {
|
|
1320
|
+
if ((b.kind === "json" || b.kind === "code") && hasTextBlock) return "";
|
|
1321
|
+
if (b.kind === "text" || b.kind === "markdown" || b.kind === "description") {
|
|
1322
|
+
return typeof b.content === "string" ? b.content : "";
|
|
1323
|
+
}
|
|
1324
|
+
if (b.kind === "code") {
|
|
1325
|
+
const code = typeof b.content === "object" ? b.content.content : b.content;
|
|
1326
|
+
return `\`\`\`
|
|
1327
|
+
${code}
|
|
1328
|
+
\`\`\``;
|
|
1329
|
+
}
|
|
1330
|
+
if (b.kind === "table" && b.content && typeof b.content === "object") {
|
|
1331
|
+
const md = renderTableShape(b.content);
|
|
1332
|
+
if (md) return md;
|
|
1333
|
+
}
|
|
1334
|
+
if (b.kind === "json") {
|
|
1335
|
+
if (b.content && typeof b.content === "object" && "pendingQuestion" in b.content) return "";
|
|
1336
|
+
const md = renderTableShape(b.content);
|
|
1337
|
+
if (md) return md;
|
|
1338
|
+
return "```json\n" + JSON.stringify(b.content, null, 2) + "\n```";
|
|
1339
|
+
}
|
|
1340
|
+
if (b.kind === "html") {
|
|
1341
|
+
return typeof b.content === "string" ? b.content : "";
|
|
1342
|
+
}
|
|
1343
|
+
return typeof b.content === "string" ? b.content : JSON.stringify(b.content);
|
|
1344
|
+
}).filter(Boolean).join("\n\n");
|
|
1239
1345
|
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1346
|
+
function blocksToAttachments(blocks) {
|
|
1347
|
+
if (!blocks || blocks.length === 0) return void 0;
|
|
1348
|
+
const atts = [];
|
|
1349
|
+
for (const b of blocks) {
|
|
1350
|
+
if ((b.kind === "image" || b.kind === "file") && (b.url || b.hash)) {
|
|
1351
|
+
atts.push({
|
|
1352
|
+
mimeType: b.mimeType || (b.kind === "image" ? "image/jpeg" : "application/octet-stream"),
|
|
1353
|
+
name: b.name || "attachment",
|
|
1354
|
+
previewUri: b.url ? `${getCoreApiUrl()}${b.url}` : void 0
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1251
1357
|
}
|
|
1358
|
+
return atts.length > 0 ? atts : void 0;
|
|
1252
1359
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
import { create as create2 } from "zustand";
|
|
1256
|
-
var useWidgetsStore = create2((set, get) => ({
|
|
1257
|
-
tiles: [],
|
|
1258
|
-
hydrated: false,
|
|
1360
|
+
var useChatStore = create4((set, get) => ({
|
|
1361
|
+
messages: [],
|
|
1259
1362
|
loading: false,
|
|
1260
1363
|
error: null,
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1364
|
+
activeJobId: null,
|
|
1365
|
+
conversationLoaded: false,
|
|
1366
|
+
// -----------------------------------------------------------------------
|
|
1367
|
+
// Load conversation history from Core
|
|
1368
|
+
// GET /api/conversation → array of turns [{user, assistant, jobId, ...}]
|
|
1369
|
+
// -----------------------------------------------------------------------
|
|
1370
|
+
loadConversation: async () => {
|
|
1268
1371
|
try {
|
|
1269
|
-
const
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
set({ tiles: before.filter((t) => t.id !== artifactId) });
|
|
1279
|
-
const result = await deleteArtifact(artifactId, reason);
|
|
1280
|
-
if (result.ok) {
|
|
1281
|
-
return { ok: true };
|
|
1282
|
-
}
|
|
1283
|
-
set({ tiles: before });
|
|
1284
|
-
return { ok: false, error: result.error || `HTTP ${result.status}` };
|
|
1285
|
-
},
|
|
1286
|
-
hide: async (artifactId) => {
|
|
1287
|
-
const before = get().tiles;
|
|
1288
|
-
set({
|
|
1289
|
-
tiles: before.map(
|
|
1290
|
-
(t) => t.id === artifactId ? { ...t, visibility: "hidden" } : t
|
|
1291
|
-
)
|
|
1292
|
-
});
|
|
1293
|
-
const r = await patchArtifact(artifactId, { visibility: "hidden" }, "user_long_press_hide");
|
|
1294
|
-
if (r.ok) {
|
|
1295
|
-
if (r.artifact) {
|
|
1296
|
-
set({
|
|
1297
|
-
tiles: get().tiles.map((t) => t.id === artifactId ? { ...t, ...r.artifact } : t)
|
|
1298
|
-
});
|
|
1372
|
+
const attachmentCache = getConfig().attachmentCache;
|
|
1373
|
+
await attachmentCache?.ready();
|
|
1374
|
+
const res = await fetch(`${getCoreApiUrl()}/api/conversation?limit=20`, {
|
|
1375
|
+
headers: coreHeaders()
|
|
1376
|
+
});
|
|
1377
|
+
if (!res.ok) {
|
|
1378
|
+
console.warn("[adas-chat] Failed to load conversation:", res.status);
|
|
1379
|
+
set({ conversationLoaded: true });
|
|
1380
|
+
return;
|
|
1299
1381
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
},
|
|
1305
|
-
unhide: async (artifactId) => {
|
|
1306
|
-
const before = get().tiles;
|
|
1307
|
-
set({
|
|
1308
|
-
tiles: before.map(
|
|
1309
|
-
(t) => t.id === artifactId ? { ...t, visibility: "pinned" } : t
|
|
1310
|
-
)
|
|
1311
|
-
});
|
|
1312
|
-
const r = await patchArtifact(artifactId, { visibility: "pinned" }, "user_long_press_unhide");
|
|
1313
|
-
if (r.ok) {
|
|
1314
|
-
if (r.artifact) {
|
|
1315
|
-
set({
|
|
1316
|
-
tiles: get().tiles.map((t) => t.id === artifactId ? { ...t, ...r.artifact } : t)
|
|
1317
|
-
});
|
|
1382
|
+
const data = await res.json();
|
|
1383
|
+
if (!data.ok || !data.items) {
|
|
1384
|
+
set({ conversationLoaded: true });
|
|
1385
|
+
return;
|
|
1318
1386
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1387
|
+
const items = [...data.items].reverse();
|
|
1388
|
+
const persistedMessages = [];
|
|
1389
|
+
const seenJobIds = /* @__PURE__ */ new Set();
|
|
1390
|
+
for (const turn of items) {
|
|
1391
|
+
if (turn.jobId && seenJobIds.has(turn.jobId)) continue;
|
|
1392
|
+
if (turn.jobId) seenJobIds.add(turn.jobId);
|
|
1393
|
+
const userText = blocksToText(turn.user?.blocks);
|
|
1394
|
+
let userAtts = blocksToAttachments(turn.user?.blocks);
|
|
1395
|
+
if (!userAtts && turn.jobId) {
|
|
1396
|
+
userAtts = attachmentCache?.get(turn.jobId);
|
|
1397
|
+
}
|
|
1398
|
+
const turnChainId = typeof turn.chainId === "string" ? turn.chainId : void 0;
|
|
1399
|
+
if (userText || userAtts) {
|
|
1400
|
+
persistedMessages.push({
|
|
1401
|
+
id: `user-${turn.jobId}`,
|
|
1402
|
+
role: "user",
|
|
1403
|
+
content: userText || "",
|
|
1404
|
+
timestamp: new Date(turn.startedAt || Date.now()),
|
|
1405
|
+
skillSlug: turn.skillSlug,
|
|
1406
|
+
...userAtts ? { attachments: userAtts } : {},
|
|
1407
|
+
...turnChainId ? { chainId: turnChainId } : {}
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
const assistantText = blocksToText(turn.assistant?.blocks);
|
|
1411
|
+
const assistantAtts = blocksToAttachments(turn.assistant?.blocks);
|
|
1412
|
+
const assistantActions = Array.isArray(turn.assistant?.actions) ? turn.assistant.actions : void 0;
|
|
1413
|
+
const turnKind = typeof turn.kind === "string" ? turn.kind : void 0;
|
|
1414
|
+
if (assistantText || assistantAtts || assistantActions?.length) {
|
|
1415
|
+
persistedMessages.push({
|
|
1416
|
+
id: `bot-${turn.jobId}`,
|
|
1417
|
+
role: "assistant",
|
|
1418
|
+
content: assistantText || "",
|
|
1419
|
+
timestamp: new Date(turn.finishedAt || turn.startedAt || Date.now()),
|
|
1420
|
+
skillSlug: turn.skillSlug,
|
|
1421
|
+
...assistantAtts ? { attachments: assistantAtts } : {},
|
|
1422
|
+
...assistantActions?.length ? { actions: assistantActions } : {},
|
|
1423
|
+
...turnChainId ? { chainId: turnChainId } : {},
|
|
1424
|
+
...turnKind ? { kind: turnKind } : {}
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
const current = get().messages;
|
|
1429
|
+
const keptPlaceholders = [];
|
|
1430
|
+
for (const m of current) {
|
|
1431
|
+
if (!m.id.startsWith("job-") || !m.jobProgress || !m.chainId) continue;
|
|
1432
|
+
const matchIdx = persistedMessages.findIndex(
|
|
1433
|
+
(pm) => pm.role === "assistant" && pm.chainId === m.chainId
|
|
1434
|
+
);
|
|
1435
|
+
if (matchIdx >= 0) {
|
|
1436
|
+
const st = m.jobProgress.status;
|
|
1437
|
+
if (st === "running" || st === "paused") {
|
|
1438
|
+
persistedMessages[matchIdx] = { ...persistedMessages[matchIdx], jobProgress: m.jobProgress };
|
|
1439
|
+
}
|
|
1440
|
+
} else {
|
|
1441
|
+
keptPlaceholders.push(m);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
const isLocalUserId = (id) => /^user-\d+$/.test(id);
|
|
1445
|
+
const keptUserBubbles = current.filter((m) => {
|
|
1446
|
+
if (m.role !== "user" || !isLocalUserId(m.id)) return false;
|
|
1447
|
+
if (!m.chainId) return true;
|
|
1448
|
+
return !persistedMessages.some(
|
|
1449
|
+
(pm) => pm.role === "user" && pm.chainId === m.chainId
|
|
1450
|
+
);
|
|
1451
|
+
});
|
|
1452
|
+
const finalMessages = [...persistedMessages, ...keptUserBubbles, ...keptPlaceholders];
|
|
1453
|
+
finalMessages.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
1454
|
+
let firstRunning = keptPlaceholders.find((m) => m.jobProgress?.status === "running");
|
|
1455
|
+
try {
|
|
1456
|
+
let activeChains = [];
|
|
1457
|
+
const chainsRes = await fetch(`${getCoreApiUrl()}/api/chains/active`, {
|
|
1458
|
+
headers: coreHeaders()
|
|
1337
1459
|
});
|
|
1460
|
+
if (chainsRes.ok) {
|
|
1461
|
+
const body = await chainsRes.json();
|
|
1462
|
+
activeChains = Array.isArray(body?.chains) ? body.chains : [];
|
|
1463
|
+
} else {
|
|
1464
|
+
const legacy = await fetch(`${getCoreApiUrl()}/api/chain/current`, {
|
|
1465
|
+
headers: coreHeaders()
|
|
1466
|
+
});
|
|
1467
|
+
if (legacy.ok) {
|
|
1468
|
+
const lb = await legacy.json();
|
|
1469
|
+
if (lb?.data?.id) activeChains = [lb.data];
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
for (const slim of activeChains) {
|
|
1473
|
+
if (!slim?.id) continue;
|
|
1474
|
+
const chainId = String(slim.id);
|
|
1475
|
+
const alreadyTracked = finalMessages.some(
|
|
1476
|
+
(m) => m.role === "assistant" && m.jobProgress?.jobId === chainId
|
|
1477
|
+
);
|
|
1478
|
+
if (alreadyTracked) continue;
|
|
1479
|
+
const progress = snapshotToProgress(
|
|
1480
|
+
{ ...slim, status: slim.chainStatus || slim.status },
|
|
1481
|
+
chainId
|
|
1482
|
+
);
|
|
1483
|
+
const chainCreatedAt = typeof slim.createdAt === "number" ? slim.createdAt : typeof slim.lastUpdate === "number" ? slim.lastUpdate : Date.now();
|
|
1484
|
+
const chainLastUpdate = typeof slim.lastUpdate === "number" ? slim.lastUpdate : chainCreatedAt;
|
|
1485
|
+
const jobProgress = {
|
|
1486
|
+
jobId: chainId,
|
|
1487
|
+
actorId: extractActorId(slim),
|
|
1488
|
+
status: "running",
|
|
1489
|
+
chainStartedAt: chainCreatedAt,
|
|
1490
|
+
...progress
|
|
1491
|
+
};
|
|
1492
|
+
const goal = typeof slim.goal === "string" ? slim.goal : "";
|
|
1493
|
+
const userExists = finalMessages.some(
|
|
1494
|
+
(m) => m.role === "user" && (m.chainId === chainId || goal && m.content === goal)
|
|
1495
|
+
);
|
|
1496
|
+
if (goal && !userExists) {
|
|
1497
|
+
finalMessages.push({
|
|
1498
|
+
id: `user-${chainId}`,
|
|
1499
|
+
role: "user",
|
|
1500
|
+
content: goal,
|
|
1501
|
+
timestamp: new Date(chainCreatedAt),
|
|
1502
|
+
chainId
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
let matchIdx = -1;
|
|
1506
|
+
for (let i = finalMessages.length - 1; i >= 0; i--) {
|
|
1507
|
+
const m = finalMessages[i];
|
|
1508
|
+
if (m.role === "assistant" && m.chainId === chainId) {
|
|
1509
|
+
matchIdx = i;
|
|
1510
|
+
break;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
if (matchIdx >= 0) {
|
|
1514
|
+
finalMessages[matchIdx] = { ...finalMessages[matchIdx], jobProgress };
|
|
1515
|
+
if (jobProgress.status === "running" && !firstRunning) firstRunning = finalMessages[matchIdx];
|
|
1516
|
+
} else {
|
|
1517
|
+
const reattachBubble = {
|
|
1518
|
+
id: `job-${chainId}`,
|
|
1519
|
+
role: "assistant",
|
|
1520
|
+
content: "Working on it...",
|
|
1521
|
+
timestamp: new Date(chainLastUpdate),
|
|
1522
|
+
jobProgress,
|
|
1523
|
+
chainId
|
|
1524
|
+
};
|
|
1525
|
+
finalMessages.push(reattachBubble);
|
|
1526
|
+
if (jobProgress.status === "running" && !firstRunning) firstRunning = reattachBubble;
|
|
1527
|
+
}
|
|
1528
|
+
streamJob(chainId, getTenant(), getToken());
|
|
1529
|
+
}
|
|
1530
|
+
finalMessages.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
1531
|
+
} catch {
|
|
1338
1532
|
}
|
|
1339
|
-
|
|
1533
|
+
set({
|
|
1534
|
+
messages: finalMessages,
|
|
1535
|
+
conversationLoaded: true,
|
|
1536
|
+
error: null,
|
|
1537
|
+
activeJobId: firstRunning?.jobProgress?.jobId || null,
|
|
1538
|
+
loading: !!firstRunning
|
|
1539
|
+
});
|
|
1540
|
+
} catch (err) {
|
|
1541
|
+
console.warn("[adas-chat] Error loading conversation:", err.message);
|
|
1542
|
+
set({ conversationLoaded: true });
|
|
1340
1543
|
}
|
|
1341
|
-
set({ tiles: before });
|
|
1342
|
-
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
1343
|
-
},
|
|
1344
|
-
reorder: async (pinnedIds) => {
|
|
1345
|
-
const before = get().tiles;
|
|
1346
|
-
const idToOrder = new Map(pinnedIds.map((id, i) => [id, i]));
|
|
1347
|
-
set({
|
|
1348
|
-
tiles: before.map((t) => {
|
|
1349
|
-
const o = idToOrder.get(t.id);
|
|
1350
|
-
return o == null ? t : { ...t, pinOrder: o };
|
|
1351
|
-
})
|
|
1352
|
-
});
|
|
1353
|
-
const r = await setOrder(pinnedIds);
|
|
1354
|
-
if (r.ok) return { ok: true };
|
|
1355
|
-
set({ tiles: before });
|
|
1356
|
-
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
1357
|
-
},
|
|
1358
|
-
applyServerUpdate: (artifact) => {
|
|
1359
|
-
set({
|
|
1360
|
-
tiles: get().tiles.map((t) => t.id === artifact.id ? { ...t, ...artifact } : t)
|
|
1361
|
-
});
|
|
1362
|
-
},
|
|
1363
|
-
applyServerRetire: (artifactId) => {
|
|
1364
|
-
set({ tiles: get().tiles.filter((t) => t.id !== artifactId) });
|
|
1365
|
-
},
|
|
1366
|
-
applyServerOrder: (pinnedIds) => {
|
|
1367
|
-
const idToOrder = new Map(pinnedIds.map((id, i) => [id, i]));
|
|
1368
|
-
set({
|
|
1369
|
-
tiles: get().tiles.map((t) => {
|
|
1370
|
-
const o = idToOrder.get(t.id);
|
|
1371
|
-
return o == null ? t : { ...t, pinOrder: o };
|
|
1372
|
-
})
|
|
1373
|
-
});
|
|
1374
1544
|
},
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
actions: [],
|
|
1395
|
-
hydrated: false,
|
|
1396
|
-
loading: false,
|
|
1397
|
-
hydrate: async () => {
|
|
1398
|
-
if (get().hydrated || get().loading) return;
|
|
1545
|
+
// -----------------------------------------------------------------------
|
|
1546
|
+
// Send message via Core API
|
|
1547
|
+
// POST /api/chat { goal } → { jobId } → stream → fetch turn result
|
|
1548
|
+
// -----------------------------------------------------------------------
|
|
1549
|
+
sendMessage: async (text, skillSlug, attachments) => {
|
|
1550
|
+
const userMsg = {
|
|
1551
|
+
id: `user-${Date.now()}`,
|
|
1552
|
+
role: "user",
|
|
1553
|
+
content: text,
|
|
1554
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1555
|
+
...attachments?.length ? {
|
|
1556
|
+
attachments: attachments.map((a) => ({
|
|
1557
|
+
mimeType: a.mimeType,
|
|
1558
|
+
name: a.name,
|
|
1559
|
+
previewUri: a.mimeType.startsWith("image/") ? `data:${a.mimeType};base64,${a.data}` : void 0
|
|
1560
|
+
}))
|
|
1561
|
+
} : {}
|
|
1562
|
+
};
|
|
1563
|
+
set((s) => ({ messages: [...s.messages, userMsg], loading: true, error: null }));
|
|
1399
1564
|
try {
|
|
1400
|
-
const
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1565
|
+
const actor = getUser();
|
|
1566
|
+
const body = {
|
|
1567
|
+
goal: text,
|
|
1568
|
+
// Opt in to the Chain Additions router (attach/cancel/new while a
|
|
1569
|
+
// chain runs). Backend ignores it where not applicable — safe to send.
|
|
1570
|
+
chainRouting: true,
|
|
1571
|
+
triggerContext: {
|
|
1572
|
+
channel: "mobile",
|
|
1573
|
+
replyContext: { chatId: actor?.actorId || "unknown" }
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
1576
|
+
if (skillSlug) body.skillSlug = skillSlug;
|
|
1577
|
+
if (attachments?.length) body.attachments = attachments;
|
|
1578
|
+
const res = await fetch(`${getCoreApiUrl()}/api/chat`, {
|
|
1579
|
+
method: "POST",
|
|
1580
|
+
headers: coreHeaders(),
|
|
1581
|
+
body: JSON.stringify(body)
|
|
1582
|
+
});
|
|
1583
|
+
if (!res.ok) {
|
|
1584
|
+
let errMsg = `Server error (${res.status})`;
|
|
1585
|
+
try {
|
|
1586
|
+
const text2 = await res.text();
|
|
1587
|
+
if (text2.startsWith("<") || text2.startsWith("<!")) {
|
|
1588
|
+
errMsg = res.status === 502 ? "Server is temporarily unavailable. Please try again." : `Server error (${res.status})`;
|
|
1589
|
+
} else {
|
|
1590
|
+
const parsed = JSON.parse(text2);
|
|
1591
|
+
errMsg = parsed.error || errMsg;
|
|
1592
|
+
}
|
|
1593
|
+
} catch {
|
|
1405
1594
|
}
|
|
1595
|
+
throw new Error(errMsg);
|
|
1406
1596
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1597
|
+
const data = await res.json();
|
|
1598
|
+
if (!data.ok) {
|
|
1599
|
+
throw new Error(data.error || "Failed to send message");
|
|
1600
|
+
}
|
|
1601
|
+
if (data.mode === "chain_addition" || data.mode === "chain_cancel") {
|
|
1602
|
+
const ack = data.mode === "chain_addition" ? data.ack || "Got it \u2014 added to the running task." : "Stopping the task\u2026";
|
|
1603
|
+
set((s) => ({
|
|
1604
|
+
messages: s.messages.map((m) => m.id === userMsg.id && data.chainId ? { ...m, chainId: data.chainId } : m).concat({
|
|
1605
|
+
id: `ack-${Date.now()}`,
|
|
1606
|
+
role: "assistant",
|
|
1607
|
+
content: ack,
|
|
1608
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1609
|
+
...data.chainId ? { chainId: data.chainId } : {}
|
|
1610
|
+
})
|
|
1611
|
+
}));
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
const jobId = data.id || data.jobId;
|
|
1615
|
+
if (!jobId) {
|
|
1616
|
+
throw new Error("No job ID returned");
|
|
1617
|
+
}
|
|
1618
|
+
if (attachments?.length) {
|
|
1619
|
+
await getConfig().attachmentCache?.save(jobId, attachments);
|
|
1620
|
+
}
|
|
1621
|
+
const progressMsg = {
|
|
1622
|
+
id: `job-${jobId}`,
|
|
1623
|
+
role: "assistant",
|
|
1624
|
+
content: "Working on it...",
|
|
1625
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1626
|
+
jobProgress: {
|
|
1627
|
+
jobId,
|
|
1628
|
+
actorId: actor?.actorId,
|
|
1629
|
+
status: "running",
|
|
1630
|
+
subStatus: "Starting...",
|
|
1631
|
+
chainStartedAt: Date.now()
|
|
1632
|
+
},
|
|
1633
|
+
chainId: jobId
|
|
1634
|
+
};
|
|
1635
|
+
set((s) => ({
|
|
1636
|
+
messages: s.messages.map(
|
|
1637
|
+
(m) => m.id === userMsg.id ? { ...m, chainId: jobId } : m
|
|
1638
|
+
).concat(progressMsg),
|
|
1639
|
+
activeJobId: jobId
|
|
1640
|
+
}));
|
|
1641
|
+
streamJob(jobId, getTenant(), getToken());
|
|
1642
|
+
} catch (err) {
|
|
1643
|
+
set((s) => ({
|
|
1644
|
+
messages: [...s.messages, {
|
|
1645
|
+
id: `error-${Date.now()}`,
|
|
1646
|
+
role: "system",
|
|
1647
|
+
content: `Error: ${err.message}`,
|
|
1648
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1649
|
+
}],
|
|
1650
|
+
loading: false
|
|
1651
|
+
}));
|
|
1420
1652
|
}
|
|
1421
1653
|
},
|
|
1422
|
-
//
|
|
1423
|
-
//
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
if (r.ok) return { ok: true };
|
|
1436
|
-
set({ actions: before });
|
|
1437
|
-
persist(before);
|
|
1438
|
-
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
1439
|
-
},
|
|
1440
|
-
setPinned: async (actionId, pinned) => {
|
|
1441
|
-
const before = get().actions;
|
|
1442
|
-
const next = before.map((a) => a.actionId === actionId ? { ...a, pinned } : a).sort(sortByPin);
|
|
1443
|
-
set({ actions: next });
|
|
1444
|
-
persist(next);
|
|
1445
|
-
const r = await patchHomeAction(actionId, { pinned }, pinned ? "user_pin" : "user_unpin");
|
|
1446
|
-
if (r.ok) {
|
|
1447
|
-
if (r.action) {
|
|
1448
|
-
const merged = get().actions.map((a) => a.actionId === actionId ? { ...a, ...r.action } : a).sort(sortByPin);
|
|
1449
|
-
set({ actions: merged });
|
|
1450
|
-
persist(merged);
|
|
1654
|
+
// -----------------------------------------------------------------------
|
|
1655
|
+
// Respond to sys.askUser paused question
|
|
1656
|
+
// -----------------------------------------------------------------------
|
|
1657
|
+
respondToQuestion: async (jobId, inputId, answer) => {
|
|
1658
|
+
try {
|
|
1659
|
+
const res = await fetch(`${getCoreApiUrl()}/api/job/${jobId}/respond`, {
|
|
1660
|
+
method: "POST",
|
|
1661
|
+
headers: coreHeaders(),
|
|
1662
|
+
body: JSON.stringify({ inputId, answer })
|
|
1663
|
+
});
|
|
1664
|
+
const data = await res.json();
|
|
1665
|
+
if (!data.ok) {
|
|
1666
|
+
console.warn("[adas-chat] Failed to respond to question:", data.error);
|
|
1451
1667
|
}
|
|
1452
|
-
|
|
1668
|
+
} catch (err) {
|
|
1669
|
+
console.warn("[adas-chat] Error responding to question:", err.message);
|
|
1453
1670
|
}
|
|
1454
|
-
set({ actions: before });
|
|
1455
|
-
persist(before);
|
|
1456
|
-
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
1457
|
-
},
|
|
1458
|
-
reorder: async (orderedIds) => {
|
|
1459
|
-
const before = get().actions;
|
|
1460
|
-
const idx = new Map(orderedIds.map((id, i) => [id, i]));
|
|
1461
|
-
const next = before.map((a) => {
|
|
1462
|
-
const o = idx.get(a.actionId);
|
|
1463
|
-
return o == null ? a : { ...a, pinOrder: o };
|
|
1464
|
-
}).sort(sortByPin);
|
|
1465
|
-
set({ actions: next });
|
|
1466
|
-
persist(next);
|
|
1467
|
-
const r = await setHomeActionOrder(orderedIds);
|
|
1468
|
-
if (r.ok) return { ok: true };
|
|
1469
|
-
set({ actions: before });
|
|
1470
|
-
persist(before);
|
|
1471
|
-
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
1472
1671
|
},
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1672
|
+
// -----------------------------------------------------------------------
|
|
1673
|
+
// Update job progress (called by the stream handler)
|
|
1674
|
+
// -----------------------------------------------------------------------
|
|
1675
|
+
updateJobProgress: (jobId, progress) => {
|
|
1676
|
+
set((s) => ({
|
|
1677
|
+
messages: s.messages.map((m) => {
|
|
1678
|
+
if (m.jobProgress?.jobId === jobId) {
|
|
1679
|
+
const updated = { ...m.jobProgress, ...progress };
|
|
1680
|
+
if (progress.status && progress.status !== "paused") {
|
|
1681
|
+
updated.pendingQuestion = void 0;
|
|
1682
|
+
}
|
|
1683
|
+
const isPersisted = m.id.startsWith("bot-");
|
|
1684
|
+
const content = isPersisted ? m.content : progress.resultMessage || progress.pendingQuestion?.question || m.content;
|
|
1685
|
+
const actions = isPersisted ? m.actions : progress.resultActions?.length ? progress.resultActions : m.actions;
|
|
1686
|
+
return {
|
|
1687
|
+
...m,
|
|
1688
|
+
jobProgress: updated,
|
|
1689
|
+
content,
|
|
1690
|
+
...actions ? { actions } : {}
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
return m;
|
|
1694
|
+
}),
|
|
1695
|
+
loading: progress.status === "done" || progress.status === "error" || progress.status === "paused" ? false : s.loading,
|
|
1696
|
+
activeJobId: progress.status === "done" || progress.status === "error" || progress.status === "paused" ? null : s.activeJobId
|
|
1697
|
+
}));
|
|
1478
1698
|
},
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1699
|
+
clearChat: () => {
|
|
1700
|
+
set({
|
|
1701
|
+
messages: [],
|
|
1702
|
+
loading: false,
|
|
1703
|
+
error: null,
|
|
1704
|
+
activeJobId: null,
|
|
1705
|
+
conversationLoaded: false
|
|
1706
|
+
});
|
|
1483
1707
|
},
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1708
|
+
newChat: () => {
|
|
1709
|
+
set({
|
|
1710
|
+
messages: [],
|
|
1711
|
+
loading: false,
|
|
1712
|
+
error: null,
|
|
1713
|
+
activeJobId: null,
|
|
1714
|
+
conversationLoaded: true
|
|
1715
|
+
});
|
|
1492
1716
|
}
|
|
1493
1717
|
}));
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
function backoffMs(attempt) {
|
|
1506
|
-
const base = Math.min(MAX_BACKOFF_MS, 500 * Math.pow(2, attempt));
|
|
1507
|
-
const jitter = Math.random() * 250;
|
|
1508
|
-
return base + jitter;
|
|
1509
|
-
}
|
|
1510
|
-
function send(obj) {
|
|
1511
|
-
if (_ws2 && _ws2.readyState === 1) {
|
|
1512
|
-
try {
|
|
1513
|
-
_ws2.send(JSON.stringify(obj));
|
|
1514
|
-
} catch (e) {
|
|
1515
|
-
console.warn("[acsWs] send failed", e);
|
|
1718
|
+
var _processedPluginCmds = /* @__PURE__ */ new Set();
|
|
1719
|
+
function dispatchPluginCommands(cmds) {
|
|
1720
|
+
for (const cmd of cmds) {
|
|
1721
|
+
const corrId = cmd?.correlationId || cmd?.correlation_id;
|
|
1722
|
+
if (corrId && _processedPluginCmds.has(corrId)) continue;
|
|
1723
|
+
if (corrId) _processedPluginCmds.add(corrId);
|
|
1724
|
+
pluginCommandEmitter.emit("plugin-command", cmd);
|
|
1725
|
+
const pluginId = cmd?.pluginId || cmd?.plugin_id || cmd?.args?.plugin_id;
|
|
1726
|
+
if (pluginId) {
|
|
1727
|
+
const props = cmd?.props ?? cmd?.args?.props ?? void 0;
|
|
1728
|
+
pluginCommandEmitter.emit("focus-plugin", { pluginId, props });
|
|
1516
1729
|
}
|
|
1517
1730
|
}
|
|
1518
1731
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
if (msg?.topic === TOPIC_HOME) {
|
|
1525
|
-
const home = useHomeActionsStore.getState();
|
|
1526
|
-
switch (msg.event) {
|
|
1527
|
-
case "updated":
|
|
1528
|
-
if (msg.action && typeof msg.action.actionId === "string") home.applyServerUpsert(msg.action);
|
|
1529
|
-
break;
|
|
1530
|
-
case "removed":
|
|
1531
|
-
if (typeof msg.actionId === "string") home.applyServerRemove(msg.actionId);
|
|
1532
|
-
break;
|
|
1533
|
-
case "order":
|
|
1534
|
-
if (Array.isArray(msg.order)) home.applyServerOrder(msg.order.filter((id) => typeof id === "string"));
|
|
1535
|
-
break;
|
|
1536
|
-
case "refreshed":
|
|
1537
|
-
home.refresh();
|
|
1538
|
-
break;
|
|
1539
|
-
default:
|
|
1540
|
-
break;
|
|
1541
|
-
}
|
|
1542
|
-
return;
|
|
1543
|
-
}
|
|
1544
|
-
if (msg?.topic !== TOPIC) return;
|
|
1545
|
-
const store = useWidgetsStore.getState();
|
|
1546
|
-
switch (msg.event) {
|
|
1547
|
-
case "updated":
|
|
1548
|
-
if (msg.artifact && typeof msg.artifact.id === "string") {
|
|
1549
|
-
store.applyServerUpdate(msg.artifact);
|
|
1550
|
-
}
|
|
1551
|
-
break;
|
|
1552
|
-
case "retired":
|
|
1553
|
-
if (typeof msg.artifactId === "string") {
|
|
1554
|
-
store.applyServerRetire(msg.artifactId);
|
|
1555
|
-
}
|
|
1556
|
-
break;
|
|
1557
|
-
case "order":
|
|
1558
|
-
if (Array.isArray(msg.pinned)) {
|
|
1559
|
-
store.applyServerOrder(msg.pinned.filter((id) => typeof id === "string"));
|
|
1560
|
-
}
|
|
1561
|
-
break;
|
|
1562
|
-
case "created":
|
|
1563
|
-
if (msg.artifact && typeof msg.artifact.id === "string") {
|
|
1564
|
-
store.applyServerUpdate(msg.artifact);
|
|
1565
|
-
}
|
|
1566
|
-
break;
|
|
1567
|
-
default:
|
|
1568
|
-
break;
|
|
1569
|
-
}
|
|
1732
|
+
var _ws2 = null;
|
|
1733
|
+
var _wsReady = false;
|
|
1734
|
+
var _clientDeclarationProvider = null;
|
|
1735
|
+
function setClientDeclarationProvider(fn) {
|
|
1736
|
+
_clientDeclarationProvider = fn;
|
|
1570
1737
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
if (
|
|
1574
|
-
const
|
|
1575
|
-
|
|
1576
|
-
const ws = new WebSocket(url);
|
|
1738
|
+
var _currentJobId = null;
|
|
1739
|
+
function ensureWs(tenant, token) {
|
|
1740
|
+
if (_ws2 && _wsReady) return _ws2;
|
|
1741
|
+
const wsUrl = `${getCoreWsUrl()}/ws?token=${encodeURIComponent(token || "")}&tenant=${encodeURIComponent(tenant)}`;
|
|
1742
|
+
const ws = new WebSocket(wsUrl);
|
|
1577
1743
|
_ws2 = ws;
|
|
1744
|
+
_wsReady = false;
|
|
1578
1745
|
ws.onopen = () => {
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1746
|
+
_wsReady = true;
|
|
1747
|
+
try {
|
|
1748
|
+
const declared = _clientDeclarationProvider?.();
|
|
1749
|
+
if (declared) ws.send(JSON.stringify({ type: "declare_client", ...declared }));
|
|
1750
|
+
} catch {
|
|
1751
|
+
}
|
|
1752
|
+
if (_currentJobId) {
|
|
1753
|
+
ws.send(JSON.stringify({ type: "subscribe", jobId: _currentJobId }));
|
|
1754
|
+
}
|
|
1582
1755
|
};
|
|
1583
|
-
ws.onmessage = (
|
|
1756
|
+
ws.onmessage = (ev) => {
|
|
1584
1757
|
try {
|
|
1585
|
-
const msg = JSON.parse(typeof
|
|
1586
|
-
|
|
1758
|
+
const msg = JSON.parse(typeof ev.data === "string" ? ev.data : "");
|
|
1759
|
+
const { event, data } = msg;
|
|
1760
|
+
const { updateJobProgress } = useChatStore.getState();
|
|
1761
|
+
const jobId = data && (data.id || data.chainId) || _currentJobId;
|
|
1762
|
+
if (event === "plugin_command") {
|
|
1763
|
+
dispatchPluginCommands([data]);
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
if (event === "plugin-command-ack" || event === "pong") return;
|
|
1767
|
+
if (!jobId) return;
|
|
1768
|
+
switch (event) {
|
|
1769
|
+
case "init":
|
|
1770
|
+
case "job":
|
|
1771
|
+
case "update": {
|
|
1772
|
+
const snap = data;
|
|
1773
|
+
const progress = snapshotToProgress(snap, jobId);
|
|
1774
|
+
if (isTerminalSnapshot(snap)) {
|
|
1775
|
+
if (snap.status === "done" && (snap.isHandoff || snap.state?.handoffTransfer)) {
|
|
1776
|
+
updateJobProgress(jobId, { status: "running" });
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
_currentJobId = null;
|
|
1780
|
+
if (progress.status === "paused" && progress.pendingQuestion) {
|
|
1781
|
+
updateJobProgress(jobId, progress);
|
|
1782
|
+
} else {
|
|
1783
|
+
fetchTurnResult(jobId, snap);
|
|
1784
|
+
}
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
updateJobProgress(jobId, progress);
|
|
1788
|
+
break;
|
|
1789
|
+
}
|
|
1790
|
+
case "chain_final":
|
|
1791
|
+
_currentJobId = null;
|
|
1792
|
+
fetchTurnResult(jobId, data);
|
|
1793
|
+
break;
|
|
1794
|
+
case "final":
|
|
1795
|
+
_currentJobId = null;
|
|
1796
|
+
fetchTurnResult(jobId, data);
|
|
1797
|
+
break;
|
|
1798
|
+
}
|
|
1587
1799
|
} catch (err) {
|
|
1588
|
-
console.warn("[
|
|
1800
|
+
console.warn("[adas-chat] WS parse error:", err);
|
|
1589
1801
|
}
|
|
1590
1802
|
};
|
|
1591
|
-
ws.onerror = (
|
|
1592
|
-
|
|
1803
|
+
ws.onerror = () => {
|
|
1804
|
+
_ws2 = null;
|
|
1805
|
+
_wsReady = false;
|
|
1806
|
+
if (_currentJobId) {
|
|
1807
|
+
const jid = _currentJobId;
|
|
1808
|
+
_currentJobId = null;
|
|
1809
|
+
pollJob(jid, token);
|
|
1810
|
+
}
|
|
1593
1811
|
};
|
|
1594
1812
|
ws.onclose = () => {
|
|
1595
1813
|
_ws2 = null;
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
openSocket();
|
|
1814
|
+
_wsReady = false;
|
|
1815
|
+
if (_currentJobId) {
|
|
1816
|
+
const jid = _currentJobId;
|
|
1817
|
+
_currentJobId = null;
|
|
1818
|
+
pollJob(jid, token);
|
|
1819
|
+
}
|
|
1820
|
+
};
|
|
1821
|
+
return ws;
|
|
1822
|
+
}
|
|
1823
|
+
function streamJob(jobId, tenant, token) {
|
|
1824
|
+
_currentJobId = jobId;
|
|
1825
|
+
const ws = ensureWs(tenant, token);
|
|
1826
|
+
if (_wsReady) {
|
|
1827
|
+
ws.send(JSON.stringify({ type: "subscribe", jobId }));
|
|
1611
1828
|
}
|
|
1612
1829
|
}
|
|
1613
|
-
function
|
|
1614
|
-
|
|
1830
|
+
function closeChatStream() {
|
|
1831
|
+
_currentJobId = null;
|
|
1615
1832
|
if (_ws2) {
|
|
1616
1833
|
try {
|
|
1617
1834
|
_ws2.close();
|
|
1618
1835
|
} catch {
|
|
1619
1836
|
}
|
|
1620
|
-
_ws2 = null;
|
|
1621
1837
|
}
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
_wantOpen = true;
|
|
1625
|
-
openSocket();
|
|
1838
|
+
_ws2 = null;
|
|
1839
|
+
_wsReady = false;
|
|
1626
1840
|
}
|
|
1627
|
-
function
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
if (
|
|
1632
|
-
|
|
1633
|
-
|
|
1841
|
+
async function fetchTurnResult(jobId, jobSnap) {
|
|
1842
|
+
if (_processedPluginCmds.size > 200) _processedPluginCmds.clear();
|
|
1843
|
+
const { updateJobProgress } = useChatStore.getState();
|
|
1844
|
+
const status = normalizeJobStatus(jobSnap) === "error" ? "error" : "done";
|
|
1845
|
+
if (jobSnap.isHandoff || jobSnap.state?.handoffTransfer || jobSnap.result?.contentType === "handoff") {
|
|
1846
|
+
try {
|
|
1847
|
+
for (let i = 0; i < 40; i++) {
|
|
1848
|
+
const statusRes = await fetch(`${getCoreApiUrl()}/api/job/${jobId}/status`, {
|
|
1849
|
+
headers: coreHeaders()
|
|
1850
|
+
});
|
|
1851
|
+
if (statusRes.ok) {
|
|
1852
|
+
const statusData = await statusRes.json();
|
|
1853
|
+
if (statusData.done && !statusData.isHandoff) {
|
|
1854
|
+
break;
|
|
1855
|
+
}
|
|
1856
|
+
updateJobProgress(jobId, { status: "running" });
|
|
1857
|
+
}
|
|
1858
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
1859
|
+
}
|
|
1860
|
+
} catch {
|
|
1861
|
+
}
|
|
1634
1862
|
}
|
|
1635
|
-
|
|
1863
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
1636
1864
|
try {
|
|
1637
|
-
|
|
1865
|
+
const res = await fetch(`${getCoreApiUrl()}/api/conversation?limit=1`, {
|
|
1866
|
+
headers: coreHeaders()
|
|
1867
|
+
});
|
|
1868
|
+
if (res.ok) {
|
|
1869
|
+
const data = await res.json();
|
|
1870
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
1871
|
+
const turn = items[0];
|
|
1872
|
+
if (turn?.assistant || turn?.turn?.assistant) {
|
|
1873
|
+
const assistant = turn.assistant || turn.turn?.assistant;
|
|
1874
|
+
const text = blocksToText(assistant.blocks);
|
|
1875
|
+
const actions = Array.isArray(assistant.actions) && assistant.actions.length ? assistant.actions : void 0;
|
|
1876
|
+
if (text) {
|
|
1877
|
+
updateJobProgress(jobId, {
|
|
1878
|
+
status,
|
|
1879
|
+
resultMessage: text,
|
|
1880
|
+
...actions ? { resultActions: actions } : {}
|
|
1881
|
+
});
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1638
1886
|
} catch {
|
|
1639
1887
|
}
|
|
1640
|
-
|
|
1888
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
1889
|
+
}
|
|
1890
|
+
const fallback = jobSnap.result?.message || jobSnap.state?.finalReply || (status === "error" ? "Something went wrong. Please try again." : "Done.");
|
|
1891
|
+
updateJobProgress(jobId, { status, resultMessage: fallback });
|
|
1892
|
+
}
|
|
1893
|
+
async function pollJob(jobId, token) {
|
|
1894
|
+
const { updateJobProgress } = useChatStore.getState();
|
|
1895
|
+
for (let i = 0; i < 120; i++) {
|
|
1896
|
+
try {
|
|
1897
|
+
const res = await fetch(`${getCoreApiUrl()}/api/job/${jobId}/status`, {
|
|
1898
|
+
headers: {
|
|
1899
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
1900
|
+
}
|
|
1901
|
+
});
|
|
1902
|
+
if (!res.ok) {
|
|
1903
|
+
if (res.status === 404) return;
|
|
1904
|
+
continue;
|
|
1905
|
+
}
|
|
1906
|
+
const snap = await res.json();
|
|
1907
|
+
const progress = snapshotToProgress(snap, jobId);
|
|
1908
|
+
if (progress.status === "paused" && progress.pendingQuestion) {
|
|
1909
|
+
updateJobProgress(jobId, progress);
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
updateJobProgress(jobId, progress);
|
|
1913
|
+
if (isTerminalSnapshot(snap)) {
|
|
1914
|
+
fetchTurnResult(jobId, snap);
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
} catch {
|
|
1918
|
+
}
|
|
1919
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
1920
|
+
}
|
|
1921
|
+
updateJobProgress(jobId, {
|
|
1922
|
+
status: "error",
|
|
1923
|
+
resultMessage: "Job timed out. Please try again."
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
// src/api/tenants.ts
|
|
1928
|
+
async function listMyTenants() {
|
|
1929
|
+
const token = getToken();
|
|
1930
|
+
if (!token) return [];
|
|
1931
|
+
try {
|
|
1932
|
+
const res = await fetch(`${getConsumerAuthApiUrl()}/platform/consumer/my-tenants`, {
|
|
1933
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1934
|
+
});
|
|
1935
|
+
const data = await res.json().catch(() => null);
|
|
1936
|
+
if (!res.ok || !data?.ok || !Array.isArray(data.tenants)) return [];
|
|
1937
|
+
return data.tenants.map((t) => ({
|
|
1938
|
+
id: t.id,
|
|
1939
|
+
// Fall back to the id rather than to an empty string: a nameless row in
|
|
1940
|
+
// a team switcher is unpickable.
|
|
1941
|
+
name: t.name || t.id,
|
|
1942
|
+
role: t.role ?? null
|
|
1943
|
+
}));
|
|
1944
|
+
} catch {
|
|
1945
|
+
return [];
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
async function switchTenant(targetTenantId) {
|
|
1949
|
+
const token = getToken();
|
|
1950
|
+
if (!token) throw new Error("[adas] switchTenant: not signed in");
|
|
1951
|
+
const res = await fetch(`${getConsumerAuthApiUrl()}/platform/consumer/switch-tenant`, {
|
|
1952
|
+
method: "POST",
|
|
1953
|
+
headers: {
|
|
1954
|
+
"Content-Type": "application/json",
|
|
1955
|
+
Authorization: `Bearer ${token}`
|
|
1956
|
+
},
|
|
1957
|
+
body: JSON.stringify({ target_tenant: targetTenantId })
|
|
1958
|
+
});
|
|
1959
|
+
const data = await res.json().catch(() => null);
|
|
1960
|
+
if (!res.ok || !data?.ok || !data.jwt) {
|
|
1961
|
+
throw new Error(data?.error || `switchTenant failed (${res.status})`);
|
|
1962
|
+
}
|
|
1963
|
+
const session = {
|
|
1964
|
+
jwt: data.jwt,
|
|
1965
|
+
tenant: data.tenant,
|
|
1966
|
+
actor: data.actor
|
|
1967
|
+
};
|
|
1968
|
+
const onSession = getConfig().onSession;
|
|
1969
|
+
if (!onSession) {
|
|
1970
|
+
throw new Error("[adas] switchTenant: config.onSession is required to persist the new session");
|
|
1641
1971
|
}
|
|
1972
|
+
await onSession(session);
|
|
1973
|
+
closeChatStream();
|
|
1974
|
+
useChatStore.getState().clearChat();
|
|
1975
|
+
return session;
|
|
1642
1976
|
}
|
|
1977
|
+
|
|
1978
|
+
// src/theme/tokens.ts
|
|
1979
|
+
var colors = {
|
|
1980
|
+
dark: {
|
|
1981
|
+
bgPrimary: "#0a0e14",
|
|
1982
|
+
bgSecondary: "#111820",
|
|
1983
|
+
bgTertiary: "#1a2230",
|
|
1984
|
+
bgInput: "#0d1219",
|
|
1985
|
+
bgHover: "#1e2a3a",
|
|
1986
|
+
border: "rgba(255, 255, 255, 0.08)",
|
|
1987
|
+
borderHover: "rgba(255, 255, 255, 0.15)",
|
|
1988
|
+
textPrimary: "#e8ecf1",
|
|
1989
|
+
textSecondary: "#8b95a5",
|
|
1990
|
+
textMuted: "#5a6577",
|
|
1991
|
+
accent: "#2563eb",
|
|
1992
|
+
accentHover: "#3b82f6",
|
|
1993
|
+
accentSoft: "rgba(37, 99, 235, 0.15)",
|
|
1994
|
+
green: "#16a34a",
|
|
1995
|
+
greenSoft: "rgba(22, 163, 74, 0.15)",
|
|
1996
|
+
red: "#dc2626",
|
|
1997
|
+
redSoft: "rgba(220, 38, 38, 0.15)",
|
|
1998
|
+
amber: "#f59e0b",
|
|
1999
|
+
amberSoft: "rgba(245, 158, 11, 0.15)",
|
|
2000
|
+
purple: "#8b5cf6",
|
|
2001
|
+
purpleSoft: "rgba(139, 92, 246, 0.15)",
|
|
2002
|
+
// Message-specific
|
|
2003
|
+
userBubble: "#2563eb",
|
|
2004
|
+
botBubble: "#1a2230",
|
|
2005
|
+
botBubbleBorder: "rgba(255, 255, 255, 0.08)"
|
|
2006
|
+
},
|
|
2007
|
+
light: {
|
|
2008
|
+
bgPrimary: "#ffffff",
|
|
2009
|
+
bgSecondary: "#f8f9fa",
|
|
2010
|
+
bgTertiary: "#f1f3f5",
|
|
2011
|
+
bgInput: "#ffffff",
|
|
2012
|
+
bgHover: "#e9ecef",
|
|
2013
|
+
border: "rgba(0, 0, 0, 0.08)",
|
|
2014
|
+
borderHover: "rgba(0, 0, 0, 0.15)",
|
|
2015
|
+
textPrimary: "#1a1a2e",
|
|
2016
|
+
textSecondary: "#6b7280",
|
|
2017
|
+
textMuted: "#9ca3af",
|
|
2018
|
+
accent: "#2563eb",
|
|
2019
|
+
accentHover: "#1d4ed8",
|
|
2020
|
+
accentSoft: "rgba(37, 99, 235, 0.1)",
|
|
2021
|
+
green: "#16a34a",
|
|
2022
|
+
greenSoft: "rgba(22, 163, 74, 0.1)",
|
|
2023
|
+
red: "#dc2626",
|
|
2024
|
+
redSoft: "rgba(220, 38, 38, 0.1)",
|
|
2025
|
+
amber: "#f59e0b",
|
|
2026
|
+
amberSoft: "rgba(245, 158, 11, 0.1)",
|
|
2027
|
+
purple: "#8b5cf6",
|
|
2028
|
+
purpleSoft: "rgba(139, 92, 246, 0.1)",
|
|
2029
|
+
userBubble: "#2563eb",
|
|
2030
|
+
botBubble: "#f1f3f5",
|
|
2031
|
+
botBubbleBorder: "rgba(0, 0, 0, 0.08)"
|
|
2032
|
+
}
|
|
2033
|
+
};
|
|
2034
|
+
var ada = {
|
|
2035
|
+
paper: "#FAF6EE",
|
|
2036
|
+
paperSoft: "#F2EADC",
|
|
2037
|
+
paperDeep: "#EBE0CC",
|
|
2038
|
+
ink: "#1a1410",
|
|
2039
|
+
inkSoft: "#5a4d3f",
|
|
2040
|
+
inkMuted: "#9a8870",
|
|
2041
|
+
border: "rgba(26, 20, 16, 0.08)",
|
|
2042
|
+
borderStrong: "rgba(26, 20, 16, 0.16)",
|
|
2043
|
+
orange: "#FF7A28",
|
|
2044
|
+
orangeDeep: "#B8410A",
|
|
2045
|
+
orangeSoft: "rgba(255, 122, 40, 0.14)",
|
|
2046
|
+
userBubble: "#1a1410",
|
|
2047
|
+
userBubbleText: "#FAF6EE",
|
|
2048
|
+
botBubble: "#F2EADC",
|
|
2049
|
+
botBubbleBorder: "rgba(26, 20, 16, 0.06)",
|
|
2050
|
+
botBubbleText: "#1a1410"
|
|
2051
|
+
};
|
|
2052
|
+
var spacing = {
|
|
2053
|
+
xs: 4,
|
|
2054
|
+
sm: 8,
|
|
2055
|
+
md: 12,
|
|
2056
|
+
lg: 16,
|
|
2057
|
+
xl: 20,
|
|
2058
|
+
xxl: 24,
|
|
2059
|
+
xxxl: 32
|
|
2060
|
+
};
|
|
2061
|
+
var fontSize = {
|
|
2062
|
+
xs: 11,
|
|
2063
|
+
sm: 13,
|
|
2064
|
+
md: 15,
|
|
2065
|
+
lg: 17,
|
|
2066
|
+
xl: 20,
|
|
2067
|
+
xxl: 24,
|
|
2068
|
+
title: 28
|
|
2069
|
+
};
|
|
2070
|
+
var borderRadius = {
|
|
2071
|
+
sm: 8,
|
|
2072
|
+
md: 12,
|
|
2073
|
+
lg: 16,
|
|
2074
|
+
xl: 20,
|
|
2075
|
+
full: 9999
|
|
2076
|
+
};
|
|
1643
2077
|
export {
|
|
1644
2078
|
PluginCommandEmitter,
|
|
1645
2079
|
TABLE_BLOCK_CLOSE,
|
|
@@ -1649,23 +2083,34 @@ export {
|
|
|
1649
2083
|
apiFetchRaw,
|
|
1650
2084
|
blocksToText,
|
|
1651
2085
|
borderRadius,
|
|
2086
|
+
capabilityEnabled,
|
|
1652
2087
|
closeChatStream,
|
|
1653
2088
|
colors,
|
|
1654
2089
|
connectAcsWs,
|
|
1655
2090
|
connectorCall,
|
|
2091
|
+
createAuthSession,
|
|
2092
|
+
createPushRegistration,
|
|
1656
2093
|
deleteArtifact,
|
|
1657
2094
|
disconnectAcsWs,
|
|
2095
|
+
exchangeHandoff,
|
|
2096
|
+
fetchConsumerConfig,
|
|
1658
2097
|
fontSize,
|
|
1659
2098
|
getConfig,
|
|
1660
2099
|
getConsumerAuthApiUrl,
|
|
1661
2100
|
getCoreApiUrl,
|
|
1662
2101
|
getCoreWsUrl,
|
|
2102
|
+
getSession,
|
|
1663
2103
|
getTenant,
|
|
1664
2104
|
getToken,
|
|
1665
2105
|
getUser,
|
|
1666
2106
|
getVoiceApiUrl,
|
|
1667
2107
|
initAdas,
|
|
1668
2108
|
isAdasInitialized,
|
|
2109
|
+
isDevHost,
|
|
2110
|
+
joinWithApple,
|
|
2111
|
+
joinWithGoogle,
|
|
2112
|
+
linkChannelStart,
|
|
2113
|
+
linkChannelVerify,
|
|
1669
2114
|
listHomeActions,
|
|
1670
2115
|
listMadeForYou,
|
|
1671
2116
|
listMyTenants,
|
|
@@ -1673,21 +2118,36 @@ export {
|
|
|
1673
2118
|
mcpFetch,
|
|
1674
2119
|
mcpToolCall,
|
|
1675
2120
|
notifyAcsWsForeground,
|
|
2121
|
+
notifyAppForeground,
|
|
2122
|
+
onAppForeground,
|
|
2123
|
+
openJoinWithApple,
|
|
2124
|
+
openJoinWithGoogle,
|
|
2125
|
+
parseDeepLinkParam,
|
|
2126
|
+
parseHandoffCode,
|
|
2127
|
+
parseInviteCode,
|
|
1676
2128
|
patchArtifact,
|
|
1677
2129
|
patchHomeAction,
|
|
1678
2130
|
pluginCommandEmitter,
|
|
1679
2131
|
postCommandResult,
|
|
2132
|
+
previewInvite,
|
|
1680
2133
|
recordEvent,
|
|
1681
2134
|
refreshToken,
|
|
2135
|
+
resolveAdasPhase,
|
|
1682
2136
|
setAcsCacheStorage,
|
|
1683
2137
|
setAcsReachabilityReporter,
|
|
2138
|
+
setClientDeclarationProvider,
|
|
1684
2139
|
setHomeActionOrder,
|
|
1685
2140
|
setOrder,
|
|
2141
|
+
signInWithApple,
|
|
2142
|
+
signInWithGoogle,
|
|
1686
2143
|
spacing,
|
|
1687
2144
|
switchTenant,
|
|
1688
2145
|
unwrapMcpResult,
|
|
2146
|
+
useCapability,
|
|
1689
2147
|
useChatStore,
|
|
1690
2148
|
useHomeActionsStore,
|
|
1691
|
-
useWidgetsStore
|
|
2149
|
+
useWidgetsStore,
|
|
2150
|
+
useWorkspaceStore,
|
|
2151
|
+
userFromJwt
|
|
1692
2152
|
};
|
|
1693
2153
|
//# sourceMappingURL=index.mjs.map
|