@ateam-ai-mobile/core 0.1.6 → 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 +365 -2
- package/dist/index.d.ts +365 -2
- package/dist/index.js +1772 -1330
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1747 -1329
- 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 +26 -0
- package/src/consumerAuth.ts +216 -0
- package/src/deepLinks.ts +38 -0
- package/src/gate.ts +67 -0
- package/src/index.ts +66 -0
- package/src/lifecycle.ts +43 -0
- package/src/pushRegistration.ts +71 -0
- package/src/workspace.ts +127 -0
package/dist/index.mjs
CHANGED
|
@@ -206,1480 +206,1874 @@ function getSession() {
|
|
|
206
206
|
};
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
// src/
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
// dispatcher in response to focus-plugin) still pick that command up via
|
|
221
|
-
// `drainPluginCommands(pluginId)`. Self-cleans on TTL + size cap.
|
|
222
|
-
this.recent = [];
|
|
223
|
-
}
|
|
224
|
-
on(event, fn) {
|
|
225
|
-
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
226
|
-
this.listeners.get(event).add(fn);
|
|
227
|
-
return () => this.off(event, fn);
|
|
228
|
-
}
|
|
229
|
-
off(event, fn) {
|
|
230
|
-
this.listeners.get(event)?.delete(fn);
|
|
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;
|
|
231
220
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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"
|
|
247
243
|
}
|
|
248
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"}` };
|
|
249
248
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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 };
|
|
261
282
|
}
|
|
262
|
-
|
|
263
|
-
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 };
|
|
264
285
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
|
273
338
|
};
|
|
274
|
-
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
275
|
-
return headers;
|
|
276
|
-
}
|
|
277
|
-
function isTerminalStatus(status) {
|
|
278
|
-
const s = typeof status === "string" ? status.toLowerCase() : "";
|
|
279
|
-
return s === "done" || s === "error" || s === "failed" || s === "completed";
|
|
280
|
-
}
|
|
281
|
-
function normalizeJobStatus(snap) {
|
|
282
|
-
const status = typeof snap?.status === "string" ? snap.status.toLowerCase() : "";
|
|
283
|
-
if (status === "done" || status === "completed") return "done";
|
|
284
|
-
if (status === "error" || status === "failed") return "error";
|
|
285
|
-
if (status === "paused" || status === "waiting") return "paused";
|
|
286
|
-
if (status === "unknown") return "unknown";
|
|
287
|
-
return "running";
|
|
288
|
-
}
|
|
289
|
-
function extractPendingQuestion(snap) {
|
|
290
|
-
return snap?.pendingQuestion || snap?.state?.pendingQuestion || snap?.state?.waiting || null;
|
|
291
|
-
}
|
|
292
|
-
function extractActorId(snap) {
|
|
293
|
-
const actorId = snap?.actorId || snap?.actor?.actorId || snap?.actor?.id || snap?.state?.actorId || snap?.state?.actor?.actorId || snap?.state?.actor?.id;
|
|
294
|
-
return typeof actorId === "string" && actorId.length > 0 ? actorId : void 0;
|
|
295
339
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
340
|
+
|
|
341
|
+
// src/pushRegistration.ts
|
|
342
|
+
function createPushRegistration(backend) {
|
|
343
|
+
let token = null;
|
|
300
344
|
return {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
+
}
|
|
304
368
|
};
|
|
305
369
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return last?.tool || last?.phase;
|
|
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;
|
|
315
378
|
}
|
|
316
|
-
function
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
actorId: extractActorId(snap),
|
|
321
|
-
status: pq ? "paused" : normalizeJobStatus(snap),
|
|
322
|
-
subStatus: snap?.subStatus || snap?.phase || snap?.state?.visual?.phase,
|
|
323
|
-
phase: snap?.phase || snap?.state?.visual?.phase,
|
|
324
|
-
iteration: snap?.iteration || snap?.state?.plannerIter,
|
|
325
|
-
skillSlug: snap?.skillSlug || snap?.activeSkill,
|
|
326
|
-
currentTool: snap?.currentTool || snap?.lastTool || extractCurrentTool(snap?.state)
|
|
327
|
-
};
|
|
328
|
-
const liveProgress = extractLiveProgress(snap);
|
|
329
|
-
if (liveProgress) progress.liveProgress = liveProgress;
|
|
330
|
-
if (typeof snap?.humanStatus === "string" && snap.humanStatus) progress.humanStatus = snap.humanStatus;
|
|
331
|
-
if (snap?.humanLabels && typeof snap.humanLabels === "object") progress.humanLabels = snap.humanLabels;
|
|
332
|
-
if (pq) {
|
|
333
|
-
progress.pendingQuestion = {
|
|
334
|
-
inputId: pq.correlation_id || pq.correlationId || pq.inputId || "",
|
|
335
|
-
question: pq.prompt_message || pq.question || "The assistant needs your input",
|
|
336
|
-
inputType: pq.inputType || "text",
|
|
337
|
-
options: pq.options || pq.requested_fields,
|
|
338
|
-
placeholder: pq.placeholder
|
|
339
|
-
};
|
|
379
|
+
function reportAcsServerUp() {
|
|
380
|
+
try {
|
|
381
|
+
_reachability?.up();
|
|
382
|
+
} catch {
|
|
340
383
|
}
|
|
341
|
-
return progress;
|
|
342
384
|
}
|
|
343
|
-
function
|
|
344
|
-
|
|
345
|
-
|
|
385
|
+
function reportAcsServerDown() {
|
|
386
|
+
try {
|
|
387
|
+
_reachability?.down();
|
|
388
|
+
} catch {
|
|
389
|
+
}
|
|
346
390
|
}
|
|
347
|
-
var
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if (!content || typeof content !== "object") return null;
|
|
351
|
-
const cols = Array.isArray(content.columns) ? content.columns : null;
|
|
352
|
-
const rows = Array.isArray(content.rows) ? content.rows : null;
|
|
353
|
-
if (!cols || !rows) return null;
|
|
354
|
-
const payload = encodeURIComponent(JSON.stringify({ columns: cols, rows }));
|
|
355
|
-
return `${TABLE_BLOCK_OPEN}${payload}${TABLE_BLOCK_CLOSE}`;
|
|
391
|
+
var _kv = null;
|
|
392
|
+
function setAcsCacheStorage(kv) {
|
|
393
|
+
_kv = kv;
|
|
356
394
|
}
|
|
357
|
-
function
|
|
358
|
-
|
|
359
|
-
const hasTextBlock = blocks.some(
|
|
360
|
-
(b) => b.kind === "text" || b.kind === "markdown" || b.kind === "description"
|
|
361
|
-
);
|
|
362
|
-
return blocks.map((b) => {
|
|
363
|
-
if ((b.kind === "json" || b.kind === "code") && hasTextBlock) return "";
|
|
364
|
-
if (b.kind === "text" || b.kind === "markdown" || b.kind === "description") {
|
|
365
|
-
return typeof b.content === "string" ? b.content : "";
|
|
366
|
-
}
|
|
367
|
-
if (b.kind === "code") {
|
|
368
|
-
const code = typeof b.content === "object" ? b.content.content : b.content;
|
|
369
|
-
return `\`\`\`
|
|
370
|
-
${code}
|
|
371
|
-
\`\`\``;
|
|
372
|
-
}
|
|
373
|
-
if (b.kind === "table" && b.content && typeof b.content === "object") {
|
|
374
|
-
const md = renderTableShape(b.content);
|
|
375
|
-
if (md) return md;
|
|
376
|
-
}
|
|
377
|
-
if (b.kind === "json") {
|
|
378
|
-
if (b.content && typeof b.content === "object" && "pendingQuestion" in b.content) return "";
|
|
379
|
-
const md = renderTableShape(b.content);
|
|
380
|
-
if (md) return md;
|
|
381
|
-
return "```json\n" + JSON.stringify(b.content, null, 2) + "\n```";
|
|
382
|
-
}
|
|
383
|
-
if (b.kind === "html") {
|
|
384
|
-
return typeof b.content === "string" ? b.content : "";
|
|
385
|
-
}
|
|
386
|
-
return typeof b.content === "string" ? b.content : JSON.stringify(b.content);
|
|
387
|
-
}).filter(Boolean).join("\n\n");
|
|
395
|
+
function getAcsCacheStorage() {
|
|
396
|
+
return _kv;
|
|
388
397
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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;
|
|
400
416
|
}
|
|
401
|
-
return atts.length > 0 ? atts : void 0;
|
|
402
417
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
+
}
|
|
434
|
+
}
|
|
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 [];
|
|
441
|
+
}
|
|
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 };
|
|
458
|
+
}
|
|
459
|
+
if (res.status === 404) {
|
|
460
|
+
return { ok: true, notFound: true, status: 404 };
|
|
461
|
+
}
|
|
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
|
+
};
|
|
481
|
+
}
|
|
482
|
+
if (res.status === 404) {
|
|
483
|
+
return { ok: true, notFound: true, status: 404 };
|
|
484
|
+
}
|
|
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
|
+
}
|
|
493
|
+
}
|
|
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 [];
|
|
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 [];
|
|
528
|
+
}
|
|
529
|
+
}
|
|
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,
|
|
562
|
+
loading: false,
|
|
406
563
|
error: null,
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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 });
|
|
414
571
|
try {
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
});
|
|
420
|
-
if (!res.ok) {
|
|
421
|
-
console.warn("[adas-chat] Failed to load conversation:", res.status);
|
|
422
|
-
set({ conversationLoaded: true });
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
const data = await res.json();
|
|
426
|
-
if (!data.ok || !data.items) {
|
|
427
|
-
set({ conversationLoaded: true });
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
const items = [...data.items].reverse();
|
|
431
|
-
const persistedMessages = [];
|
|
432
|
-
const seenJobIds = /* @__PURE__ */ new Set();
|
|
433
|
-
for (const turn of items) {
|
|
434
|
-
if (turn.jobId && seenJobIds.has(turn.jobId)) continue;
|
|
435
|
-
if (turn.jobId) seenJobIds.add(turn.jobId);
|
|
436
|
-
const userText = blocksToText(turn.user?.blocks);
|
|
437
|
-
let userAtts = blocksToAttachments(turn.user?.blocks);
|
|
438
|
-
if (!userAtts && turn.jobId) {
|
|
439
|
-
userAtts = attachmentCache?.get(turn.jobId);
|
|
440
|
-
}
|
|
441
|
-
const turnChainId = typeof turn.chainId === "string" ? turn.chainId : void 0;
|
|
442
|
-
if (userText || userAtts) {
|
|
443
|
-
persistedMessages.push({
|
|
444
|
-
id: `user-${turn.jobId}`,
|
|
445
|
-
role: "user",
|
|
446
|
-
content: userText || "",
|
|
447
|
-
timestamp: new Date(turn.startedAt || Date.now()),
|
|
448
|
-
skillSlug: turn.skillSlug,
|
|
449
|
-
...userAtts ? { attachments: userAtts } : {},
|
|
450
|
-
...turnChainId ? { chainId: turnChainId } : {}
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
const assistantText = blocksToText(turn.assistant?.blocks);
|
|
454
|
-
const assistantAtts = blocksToAttachments(turn.assistant?.blocks);
|
|
455
|
-
const assistantActions = Array.isArray(turn.assistant?.actions) ? turn.assistant.actions : void 0;
|
|
456
|
-
const turnKind = typeof turn.kind === "string" ? turn.kind : void 0;
|
|
457
|
-
if (assistantText || assistantAtts || assistantActions?.length) {
|
|
458
|
-
persistedMessages.push({
|
|
459
|
-
id: `bot-${turn.jobId}`,
|
|
460
|
-
role: "assistant",
|
|
461
|
-
content: assistantText || "",
|
|
462
|
-
timestamp: new Date(turn.finishedAt || turn.startedAt || Date.now()),
|
|
463
|
-
skillSlug: turn.skillSlug,
|
|
464
|
-
...assistantAtts ? { attachments: assistantAtts } : {},
|
|
465
|
-
...assistantActions?.length ? { actions: assistantActions } : {},
|
|
466
|
-
...turnChainId ? { chainId: turnChainId } : {},
|
|
467
|
-
...turnKind ? { kind: turnKind } : {}
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
const current = get().messages;
|
|
472
|
-
const keptPlaceholders = [];
|
|
473
|
-
for (const m of current) {
|
|
474
|
-
if (!m.id.startsWith("job-") || !m.jobProgress || !m.chainId) continue;
|
|
475
|
-
const matchIdx = persistedMessages.findIndex(
|
|
476
|
-
(pm) => pm.role === "assistant" && pm.chainId === m.chainId
|
|
477
|
-
);
|
|
478
|
-
if (matchIdx >= 0) {
|
|
479
|
-
const st = m.jobProgress.status;
|
|
480
|
-
if (st === "running" || st === "paused") {
|
|
481
|
-
persistedMessages[matchIdx] = { ...persistedMessages[matchIdx], jobProgress: m.jobProgress };
|
|
482
|
-
}
|
|
483
|
-
} else {
|
|
484
|
-
keptPlaceholders.push(m);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
const isLocalUserId = (id) => /^user-\d+$/.test(id);
|
|
488
|
-
const keptUserBubbles = current.filter((m) => {
|
|
489
|
-
if (m.role !== "user" || !isLocalUserId(m.id)) return false;
|
|
490
|
-
if (!m.chainId) return true;
|
|
491
|
-
return !persistedMessages.some(
|
|
492
|
-
(pm) => pm.role === "user" && pm.chainId === m.chainId
|
|
493
|
-
);
|
|
494
|
-
});
|
|
495
|
-
const finalMessages = [...persistedMessages, ...keptUserBubbles, ...keptPlaceholders];
|
|
496
|
-
finalMessages.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
497
|
-
let firstRunning = keptPlaceholders.find((m) => m.jobProgress?.status === "running");
|
|
498
|
-
try {
|
|
499
|
-
let activeChains = [];
|
|
500
|
-
const chainsRes = await fetch(`${getCoreApiUrl()}/api/chains/active`, {
|
|
501
|
-
headers: coreHeaders()
|
|
502
|
-
});
|
|
503
|
-
if (chainsRes.ok) {
|
|
504
|
-
const body = await chainsRes.json();
|
|
505
|
-
activeChains = Array.isArray(body?.chains) ? body.chains : [];
|
|
506
|
-
} else {
|
|
507
|
-
const legacy = await fetch(`${getCoreApiUrl()}/api/chain/current`, {
|
|
508
|
-
headers: coreHeaders()
|
|
509
|
-
});
|
|
510
|
-
if (legacy.ok) {
|
|
511
|
-
const lb = await legacy.json();
|
|
512
|
-
if (lb?.data?.id) activeChains = [lb.data];
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
for (const slim of activeChains) {
|
|
516
|
-
if (!slim?.id) continue;
|
|
517
|
-
const chainId = String(slim.id);
|
|
518
|
-
const alreadyTracked = finalMessages.some(
|
|
519
|
-
(m) => m.role === "assistant" && m.jobProgress?.jobId === chainId
|
|
520
|
-
);
|
|
521
|
-
if (alreadyTracked) continue;
|
|
522
|
-
const progress = snapshotToProgress(
|
|
523
|
-
{ ...slim, status: slim.chainStatus || slim.status },
|
|
524
|
-
chainId
|
|
525
|
-
);
|
|
526
|
-
const chainCreatedAt = typeof slim.createdAt === "number" ? slim.createdAt : typeof slim.lastUpdate === "number" ? slim.lastUpdate : Date.now();
|
|
527
|
-
const chainLastUpdate = typeof slim.lastUpdate === "number" ? slim.lastUpdate : chainCreatedAt;
|
|
528
|
-
const jobProgress = {
|
|
529
|
-
jobId: chainId,
|
|
530
|
-
actorId: extractActorId(slim),
|
|
531
|
-
status: "running",
|
|
532
|
-
chainStartedAt: chainCreatedAt,
|
|
533
|
-
...progress
|
|
534
|
-
};
|
|
535
|
-
const goal = typeof slim.goal === "string" ? slim.goal : "";
|
|
536
|
-
const userExists = finalMessages.some(
|
|
537
|
-
(m) => m.role === "user" && (m.chainId === chainId || goal && m.content === goal)
|
|
538
|
-
);
|
|
539
|
-
if (goal && !userExists) {
|
|
540
|
-
finalMessages.push({
|
|
541
|
-
id: `user-${chainId}`,
|
|
542
|
-
role: "user",
|
|
543
|
-
content: goal,
|
|
544
|
-
timestamp: new Date(chainCreatedAt),
|
|
545
|
-
chainId
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
let matchIdx = -1;
|
|
549
|
-
for (let i = finalMessages.length - 1; i >= 0; i--) {
|
|
550
|
-
const m = finalMessages[i];
|
|
551
|
-
if (m.role === "assistant" && m.chainId === chainId) {
|
|
552
|
-
matchIdx = i;
|
|
553
|
-
break;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
if (matchIdx >= 0) {
|
|
557
|
-
finalMessages[matchIdx] = { ...finalMessages[matchIdx], jobProgress };
|
|
558
|
-
if (jobProgress.status === "running" && !firstRunning) firstRunning = finalMessages[matchIdx];
|
|
559
|
-
} else {
|
|
560
|
-
const reattachBubble = {
|
|
561
|
-
id: `job-${chainId}`,
|
|
562
|
-
role: "assistant",
|
|
563
|
-
content: "Working on it...",
|
|
564
|
-
timestamp: new Date(chainLastUpdate),
|
|
565
|
-
jobProgress,
|
|
566
|
-
chainId
|
|
567
|
-
};
|
|
568
|
-
finalMessages.push(reattachBubble);
|
|
569
|
-
if (jobProgress.status === "running" && !firstRunning) firstRunning = reattachBubble;
|
|
570
|
-
}
|
|
571
|
-
streamJob(chainId, getTenant(), getToken());
|
|
572
|
-
}
|
|
573
|
-
finalMessages.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
574
|
-
} catch {
|
|
575
|
-
}
|
|
576
|
-
set({
|
|
577
|
-
messages: finalMessages,
|
|
578
|
-
conversationLoaded: true,
|
|
579
|
-
error: null,
|
|
580
|
-
activeJobId: firstRunning?.jobProgress?.jobId || null,
|
|
581
|
-
loading: !!firstRunning
|
|
582
|
-
});
|
|
583
|
-
} catch (err) {
|
|
584
|
-
console.warn("[adas-chat] Error loading conversation:", err.message);
|
|
585
|
-
set({ conversationLoaded: true });
|
|
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" });
|
|
586
576
|
}
|
|
587
577
|
},
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
};
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
triggerContext: {
|
|
615
|
-
channel: "mobile",
|
|
616
|
-
replyContext: { chatId: actor?.actorId || "unknown" }
|
|
617
|
-
}
|
|
618
|
-
};
|
|
619
|
-
if (skillSlug) body.skillSlug = skillSlug;
|
|
620
|
-
if (attachments?.length) body.attachments = attachments;
|
|
621
|
-
const res = await fetch(`${getCoreApiUrl()}/api/chat`, {
|
|
622
|
-
method: "POST",
|
|
623
|
-
headers: coreHeaders(),
|
|
624
|
-
body: JSON.stringify(body)
|
|
625
|
-
});
|
|
626
|
-
if (!res.ok) {
|
|
627
|
-
let errMsg = `Server error (${res.status})`;
|
|
628
|
-
try {
|
|
629
|
-
const text2 = await res.text();
|
|
630
|
-
if (text2.startsWith("<") || text2.startsWith("<!")) {
|
|
631
|
-
errMsg = res.status === 502 ? "Server is temporarily unavailable. Please try again." : `Server error (${res.status})`;
|
|
632
|
-
} else {
|
|
633
|
-
const parsed = JSON.parse(text2);
|
|
634
|
-
errMsg = parsed.error || errMsg;
|
|
635
|
-
}
|
|
636
|
-
} catch {
|
|
637
|
-
}
|
|
638
|
-
throw new Error(errMsg);
|
|
639
|
-
}
|
|
640
|
-
const data = await res.json();
|
|
641
|
-
if (!data.ok) {
|
|
642
|
-
throw new Error(data.error || "Failed to send message");
|
|
643
|
-
}
|
|
644
|
-
if (data.mode === "chain_addition" || data.mode === "chain_cancel") {
|
|
645
|
-
const ack = data.mode === "chain_addition" ? data.ack || "Got it \u2014 added to the running task." : "Stopping the task\u2026";
|
|
646
|
-
set((s) => ({
|
|
647
|
-
messages: s.messages.map((m) => m.id === userMsg.id && data.chainId ? { ...m, chainId: data.chainId } : m).concat({
|
|
648
|
-
id: `ack-${Date.now()}`,
|
|
649
|
-
role: "assistant",
|
|
650
|
-
content: ack,
|
|
651
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
652
|
-
...data.chainId ? { chainId: data.chainId } : {}
|
|
653
|
-
})
|
|
654
|
-
}));
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
const jobId = data.id || data.jobId;
|
|
658
|
-
if (!jobId) {
|
|
659
|
-
throw new Error("No job ID returned");
|
|
660
|
-
}
|
|
661
|
-
if (attachments?.length) {
|
|
662
|
-
await getConfig().attachmentCache?.save(jobId, attachments);
|
|
663
|
-
}
|
|
664
|
-
const progressMsg = {
|
|
665
|
-
id: `job-${jobId}`,
|
|
666
|
-
role: "assistant",
|
|
667
|
-
content: "Working on it...",
|
|
668
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
669
|
-
jobProgress: {
|
|
670
|
-
jobId,
|
|
671
|
-
actorId: actor?.actorId,
|
|
672
|
-
status: "running",
|
|
673
|
-
subStatus: "Starting...",
|
|
674
|
-
chainStartedAt: Date.now()
|
|
675
|
-
},
|
|
676
|
-
chainId: jobId
|
|
677
|
-
};
|
|
678
|
-
set((s) => ({
|
|
679
|
-
messages: s.messages.map(
|
|
680
|
-
(m) => m.id === userMsg.id ? { ...m, chainId: jobId } : m
|
|
681
|
-
).concat(progressMsg),
|
|
682
|
-
activeJobId: jobId
|
|
683
|
-
}));
|
|
684
|
-
streamJob(jobId, getTenant(), getToken());
|
|
685
|
-
} catch (err) {
|
|
686
|
-
set((s) => ({
|
|
687
|
-
messages: [...s.messages, {
|
|
688
|
-
id: `error-${Date.now()}`,
|
|
689
|
-
role: "system",
|
|
690
|
-
content: `Error: ${err.message}`,
|
|
691
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
692
|
-
}],
|
|
693
|
-
loading: false
|
|
694
|
-
}));
|
|
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
|
+
});
|
|
602
|
+
}
|
|
603
|
+
return { ok: true };
|
|
695
604
|
}
|
|
605
|
+
set({ tiles: before });
|
|
606
|
+
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
696
607
|
},
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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
|
+
});
|
|
710
621
|
}
|
|
711
|
-
|
|
712
|
-
console.warn("[adas-chat] Error responding to question:", err.message);
|
|
622
|
+
return { ok: true };
|
|
713
623
|
}
|
|
624
|
+
set({ tiles: before });
|
|
625
|
+
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
714
626
|
},
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
};
|
|
735
|
-
}
|
|
736
|
-
return m;
|
|
737
|
-
}),
|
|
738
|
-
loading: progress.status === "done" || progress.status === "error" || progress.status === "paused" ? false : s.loading,
|
|
739
|
-
activeJobId: progress.status === "done" || progress.status === "error" || progress.status === "paused" ? null : s.activeJobId
|
|
740
|
-
}));
|
|
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
|
+
});
|
|
641
|
+
}
|
|
642
|
+
return { ok: true };
|
|
643
|
+
}
|
|
644
|
+
set({ tiles: before });
|
|
645
|
+
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
741
646
|
},
|
|
742
|
-
|
|
647
|
+
reorder: async (pinnedIds) => {
|
|
648
|
+
const before = get().tiles;
|
|
649
|
+
const idToOrder = new Map(pinnedIds.map((id, i) => [id, i]));
|
|
743
650
|
set({
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
conversationLoaded: false
|
|
651
|
+
tiles: before.map((t) => {
|
|
652
|
+
const o = idToOrder.get(t.id);
|
|
653
|
+
return o == null ? t : { ...t, pinOrder: o };
|
|
654
|
+
})
|
|
749
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}` };
|
|
750
660
|
},
|
|
751
|
-
|
|
661
|
+
applyServerUpdate: (artifact) => {
|
|
752
662
|
set({
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
663
|
+
tiles: get().tiles.map((t) => t.id === artifact.id ? { ...t, ...artifact } : t)
|
|
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");
|
|
680
|
+
}
|
|
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
|
+
});
|
|
689
|
+
}
|
|
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;
|
|
702
|
+
try {
|
|
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 });
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
} catch {
|
|
711
|
+
}
|
|
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(() => {
|
|
758
730
|
});
|
|
731
|
+
},
|
|
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
|
+
}));
|
|
797
|
+
|
|
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;
|
|
814
|
+
}
|
|
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
|
+
}
|
|
759
822
|
}
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
823
|
+
}
|
|
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;
|
|
772
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;
|
|
773
874
|
}
|
|
774
875
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
const
|
|
781
|
-
const ws = new WebSocket(wsUrl);
|
|
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);
|
|
782
882
|
_ws = ws;
|
|
783
|
-
|
|
883
|
+
const isCurrent = () => ws === _ws;
|
|
784
884
|
ws.onopen = () => {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
}
|
|
885
|
+
if (!isCurrent()) return;
|
|
886
|
+
_retryAttempt = 0;
|
|
887
|
+
send({ type: "subscribe", topic: TOPIC });
|
|
888
|
+
send({ type: "subscribe", topic: TOPIC_HOME });
|
|
789
889
|
};
|
|
790
|
-
ws.onmessage = (
|
|
890
|
+
ws.onmessage = (e) => {
|
|
891
|
+
if (!isCurrent()) return;
|
|
791
892
|
try {
|
|
792
|
-
const msg = JSON.parse(typeof
|
|
793
|
-
|
|
794
|
-
const { updateJobProgress } = useChatStore.getState();
|
|
795
|
-
const jobId = data && (data.id || data.chainId) || _currentJobId;
|
|
796
|
-
if (event === "plugin_command") {
|
|
797
|
-
dispatchPluginCommands([data]);
|
|
798
|
-
return;
|
|
799
|
-
}
|
|
800
|
-
if (event === "plugin-command-ack" || event === "pong") return;
|
|
801
|
-
if (!jobId) return;
|
|
802
|
-
switch (event) {
|
|
803
|
-
case "init":
|
|
804
|
-
case "job":
|
|
805
|
-
case "update": {
|
|
806
|
-
const snap = data;
|
|
807
|
-
const progress = snapshotToProgress(snap, jobId);
|
|
808
|
-
if (isTerminalSnapshot(snap)) {
|
|
809
|
-
if (snap.status === "done" && (snap.isHandoff || snap.state?.handoffTransfer)) {
|
|
810
|
-
updateJobProgress(jobId, { status: "running" });
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
_currentJobId = null;
|
|
814
|
-
if (progress.status === "paused" && progress.pendingQuestion) {
|
|
815
|
-
updateJobProgress(jobId, progress);
|
|
816
|
-
} else {
|
|
817
|
-
fetchTurnResult(jobId, snap);
|
|
818
|
-
}
|
|
819
|
-
return;
|
|
820
|
-
}
|
|
821
|
-
updateJobProgress(jobId, progress);
|
|
822
|
-
break;
|
|
823
|
-
}
|
|
824
|
-
case "chain_final":
|
|
825
|
-
_currentJobId = null;
|
|
826
|
-
fetchTurnResult(jobId, data);
|
|
827
|
-
break;
|
|
828
|
-
case "final":
|
|
829
|
-
_currentJobId = null;
|
|
830
|
-
fetchTurnResult(jobId, data);
|
|
831
|
-
break;
|
|
832
|
-
}
|
|
893
|
+
const msg = JSON.parse(typeof e.data === "string" ? e.data : "");
|
|
894
|
+
handleEvent(msg);
|
|
833
895
|
} catch (err) {
|
|
834
|
-
console.warn("[
|
|
896
|
+
console.warn("[acsWs] parse failed", err);
|
|
835
897
|
}
|
|
836
898
|
};
|
|
837
|
-
ws.onerror = () => {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
if (_currentJobId) {
|
|
841
|
-
const jid = _currentJobId;
|
|
842
|
-
_currentJobId = null;
|
|
843
|
-
pollJob(jid, token);
|
|
844
|
-
}
|
|
899
|
+
ws.onerror = (e) => {
|
|
900
|
+
if (!isCurrent()) return;
|
|
901
|
+
console.warn("[acsWs] error", e?.message || e);
|
|
845
902
|
};
|
|
846
|
-
ws.onclose = () => {
|
|
903
|
+
ws.onclose = (ev) => {
|
|
904
|
+
if (!isCurrent()) return;
|
|
847
905
|
_ws = null;
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
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;
|
|
853
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);
|
|
854
921
|
};
|
|
855
|
-
return ws;
|
|
856
922
|
}
|
|
857
|
-
function
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
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();
|
|
862
932
|
}
|
|
863
933
|
}
|
|
864
|
-
function
|
|
865
|
-
|
|
934
|
+
function connectAcsWs(token, tenant) {
|
|
935
|
+
_terminated = false;
|
|
936
|
+
if (_ws && _wantOpen && _token === token && _tenant === tenant) return;
|
|
866
937
|
if (_ws) {
|
|
867
938
|
try {
|
|
868
939
|
_ws.close();
|
|
869
940
|
} catch {
|
|
870
941
|
}
|
|
942
|
+
_ws = null;
|
|
871
943
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
if (_processedPluginCmds.size > 200) _processedPluginCmds.clear();
|
|
877
|
-
const { updateJobProgress } = useChatStore.getState();
|
|
878
|
-
const status = normalizeJobStatus(jobSnap) === "error" ? "error" : "done";
|
|
879
|
-
if (jobSnap.isHandoff || jobSnap.state?.handoffTransfer || jobSnap.result?.contentType === "handoff") {
|
|
880
|
-
try {
|
|
881
|
-
for (let i = 0; i < 40; i++) {
|
|
882
|
-
const statusRes = await fetch(`${getCoreApiUrl()}/api/job/${jobId}/status`, {
|
|
883
|
-
headers: coreHeaders()
|
|
884
|
-
});
|
|
885
|
-
if (statusRes.ok) {
|
|
886
|
-
const statusData = await statusRes.json();
|
|
887
|
-
if (statusData.done && !statusData.isHandoff) {
|
|
888
|
-
break;
|
|
889
|
-
}
|
|
890
|
-
updateJobProgress(jobId, { status: "running" });
|
|
891
|
-
}
|
|
892
|
-
await new Promise((r) => setTimeout(r, 1500));
|
|
893
|
-
}
|
|
894
|
-
} catch {
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
for (let attempt = 0; attempt < 5; attempt++) {
|
|
898
|
-
try {
|
|
899
|
-
const res = await fetch(`${getCoreApiUrl()}/api/conversation?limit=1`, {
|
|
900
|
-
headers: coreHeaders()
|
|
901
|
-
});
|
|
902
|
-
if (res.ok) {
|
|
903
|
-
const data = await res.json();
|
|
904
|
-
const items = Array.isArray(data.items) ? data.items : [];
|
|
905
|
-
const turn = items[0];
|
|
906
|
-
if (turn?.assistant || turn?.turn?.assistant) {
|
|
907
|
-
const assistant = turn.assistant || turn.turn?.assistant;
|
|
908
|
-
const text = blocksToText(assistant.blocks);
|
|
909
|
-
const actions = Array.isArray(assistant.actions) && assistant.actions.length ? assistant.actions : void 0;
|
|
910
|
-
if (text) {
|
|
911
|
-
updateJobProgress(jobId, {
|
|
912
|
-
status,
|
|
913
|
-
resultMessage: text,
|
|
914
|
-
...actions ? { resultActions: actions } : {}
|
|
915
|
-
});
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
} catch {
|
|
921
|
-
}
|
|
922
|
-
await new Promise((r) => setTimeout(r, 800));
|
|
923
|
-
}
|
|
924
|
-
const fallback = jobSnap.result?.message || jobSnap.state?.finalReply || (status === "error" ? "Something went wrong. Please try again." : "Done.");
|
|
925
|
-
updateJobProgress(jobId, { status, resultMessage: fallback });
|
|
944
|
+
_token = token;
|
|
945
|
+
_tenant = tenant;
|
|
946
|
+
_wantOpen = true;
|
|
947
|
+
openSocket();
|
|
926
948
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
}
|
|
940
|
-
const snap = await res.json();
|
|
941
|
-
const progress = snapshotToProgress(snap, jobId);
|
|
942
|
-
if (progress.status === "paused" && progress.pendingQuestion) {
|
|
943
|
-
updateJobProgress(jobId, progress);
|
|
944
|
-
return;
|
|
945
|
-
}
|
|
946
|
-
updateJobProgress(jobId, progress);
|
|
947
|
-
if (isTerminalSnapshot(snap)) {
|
|
948
|
-
fetchTurnResult(jobId, snap);
|
|
949
|
-
return;
|
|
950
|
-
}
|
|
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();
|
|
951
961
|
} catch {
|
|
952
962
|
}
|
|
953
|
-
|
|
963
|
+
_ws = null;
|
|
954
964
|
}
|
|
955
|
-
updateJobProgress(jobId, {
|
|
956
|
-
status: "error",
|
|
957
|
-
resultMessage: "Job timed out. Please try again."
|
|
958
|
-
});
|
|
959
965
|
}
|
|
960
966
|
|
|
961
|
-
// src/
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
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() {
|
|
965
976
|
try {
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
}
|
|
976
|
-
} catch {
|
|
977
|
-
return [];
|
|
977
|
+
notifyAcsWsForeground();
|
|
978
|
+
} catch (e) {
|
|
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
|
+
}
|
|
978
987
|
}
|
|
979
988
|
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
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}`, {
|
|
984
1016
|
method: "POST",
|
|
985
1017
|
headers: {
|
|
986
1018
|
"Content-Type": "application/json",
|
|
987
|
-
Authorization: `Bearer ${
|
|
1019
|
+
...jwt ? { Authorization: `Bearer ${jwt}` } : {}
|
|
988
1020
|
},
|
|
989
|
-
body: JSON.stringify(
|
|
1021
|
+
body: JSON.stringify(body)
|
|
990
1022
|
});
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
1023
|
+
const text = await res.text();
|
|
1024
|
+
let parsed = null;
|
|
1025
|
+
try {
|
|
1026
|
+
parsed = JSON.parse(text);
|
|
1027
|
+
} catch {
|
|
994
1028
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
actor: data.actor
|
|
999
|
-
};
|
|
1000
|
-
const onSession = getConfig().onSession;
|
|
1001
|
-
if (!onSession) {
|
|
1002
|
-
throw new Error("[adas] switchTenant: config.onSession is required to persist the new session");
|
|
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`));
|
|
1003
1032
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1033
|
+
return parsed;
|
|
1034
|
+
}
|
|
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;
|
|
1040
|
+
try {
|
|
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"));
|
|
1047
|
+
}
|
|
1048
|
+
return data;
|
|
1049
|
+
}
|
|
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;
|
|
1070
|
+
try {
|
|
1071
|
+
data = JSON.parse(text);
|
|
1072
|
+
} catch {
|
|
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;
|
|
1080
|
+
}
|
|
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);
|
|
1008
1098
|
}
|
|
1009
1099
|
|
|
1010
|
-
// src/
|
|
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
|
-
|
|
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;
|
|
1106
|
+
try {
|
|
1107
|
+
const v = decodeURIComponent(m[1]);
|
|
1108
|
+
return v.length > 0 ? v : null;
|
|
1109
|
+
} catch {
|
|
1110
|
+
return m[1].length > 0 ? m[1] : null;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
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 });
|
|
1147
|
+
}
|
|
1038
1148
|
},
|
|
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
|
-
|
|
1149
|
+
clear: () => {
|
|
1150
|
+
_gen++;
|
|
1151
|
+
set({ snapshot: null, loaded: false });
|
|
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
|
+
});
|
|
1164
|
+
}
|
|
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;
|
|
1064
1221
|
}
|
|
1065
1222
|
};
|
|
1066
|
-
var
|
|
1067
|
-
paper: "#FAF6EE",
|
|
1068
|
-
paperSoft: "#F2EADC",
|
|
1069
|
-
paperDeep: "#EBE0CC",
|
|
1070
|
-
ink: "#1a1410",
|
|
1071
|
-
inkSoft: "#5a4d3f",
|
|
1072
|
-
inkMuted: "#9a8870",
|
|
1073
|
-
border: "rgba(26, 20, 16, 0.08)",
|
|
1074
|
-
borderStrong: "rgba(26, 20, 16, 0.16)",
|
|
1075
|
-
orange: "#FF7A28",
|
|
1076
|
-
orangeDeep: "#B8410A",
|
|
1077
|
-
orangeSoft: "rgba(255, 122, 40, 0.14)",
|
|
1078
|
-
userBubble: "#1a1410",
|
|
1079
|
-
userBubbleText: "#FAF6EE",
|
|
1080
|
-
botBubble: "#F2EADC",
|
|
1081
|
-
botBubbleBorder: "rgba(26, 20, 16, 0.06)",
|
|
1082
|
-
botBubbleText: "#1a1410"
|
|
1083
|
-
};
|
|
1084
|
-
var spacing = {
|
|
1085
|
-
xs: 4,
|
|
1086
|
-
sm: 8,
|
|
1087
|
-
md: 12,
|
|
1088
|
-
lg: 16,
|
|
1089
|
-
xl: 20,
|
|
1090
|
-
xxl: 24,
|
|
1091
|
-
xxxl: 32
|
|
1092
|
-
};
|
|
1093
|
-
var fontSize = {
|
|
1094
|
-
xs: 11,
|
|
1095
|
-
sm: 13,
|
|
1096
|
-
md: 15,
|
|
1097
|
-
lg: 17,
|
|
1098
|
-
xl: 20,
|
|
1099
|
-
xxl: 24,
|
|
1100
|
-
title: 28
|
|
1101
|
-
};
|
|
1102
|
-
var borderRadius = {
|
|
1103
|
-
sm: 8,
|
|
1104
|
-
md: 12,
|
|
1105
|
-
lg: 16,
|
|
1106
|
-
xl: 20,
|
|
1107
|
-
full: 9999
|
|
1108
|
-
};
|
|
1223
|
+
var pluginCommandEmitter = new PluginCommandEmitter();
|
|
1109
1224
|
|
|
1110
|
-
// src/
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
} catch {
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
function reportAcsServerDown() {
|
|
1122
|
-
try {
|
|
1123
|
-
_reachability?.down();
|
|
1124
|
-
} catch {
|
|
1125
|
-
}
|
|
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;
|
|
1126
1233
|
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1234
|
+
function isTerminalStatus(status) {
|
|
1235
|
+
const s = typeof status === "string" ? status.toLowerCase() : "";
|
|
1236
|
+
return s === "done" || s === "error" || s === "failed" || s === "completed";
|
|
1130
1237
|
}
|
|
1131
|
-
function
|
|
1132
|
-
|
|
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";
|
|
1133
1245
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
async function authedFetch(path, init) {
|
|
1137
|
-
const token = getToken();
|
|
1138
|
-
try {
|
|
1139
|
-
const res = await fetch(`${getCoreApiUrl()}${path}`, {
|
|
1140
|
-
...init,
|
|
1141
|
-
headers: {
|
|
1142
|
-
"Content-Type": "application/json",
|
|
1143
|
-
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
1144
|
-
...init?.headers || {}
|
|
1145
|
-
}
|
|
1146
|
-
});
|
|
1147
|
-
reportAcsServerUp();
|
|
1148
|
-
return res;
|
|
1149
|
-
} catch (e) {
|
|
1150
|
-
reportAcsServerDown();
|
|
1151
|
-
throw e;
|
|
1152
|
-
}
|
|
1246
|
+
function extractPendingQuestion(snap) {
|
|
1247
|
+
return snap?.pendingQuestion || snap?.state?.pendingQuestion || snap?.state?.waiting || null;
|
|
1153
1248
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
method: "POST",
|
|
1158
|
-
body: JSON.stringify({ surface, ...context ? { context } : {} })
|
|
1159
|
-
});
|
|
1160
|
-
if (!res.ok) {
|
|
1161
|
-
console.warn(`[acs] /surface ${surface} returned ${res.status}`);
|
|
1162
|
-
return [];
|
|
1163
|
-
}
|
|
1164
|
-
const data = await res.json();
|
|
1165
|
-
return Array.isArray(data?.items) ? data.items : [];
|
|
1166
|
-
} catch (e) {
|
|
1167
|
-
console.warn("[acs] /surface failed:", e?.message || e);
|
|
1168
|
-
return [];
|
|
1169
|
-
}
|
|
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;
|
|
1170
1252
|
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
} catch (e) {
|
|
1181
|
-
console.warn("[acs] /made-for-you failed:", e?.message || e);
|
|
1182
|
-
return [];
|
|
1183
|
-
}
|
|
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
|
+
};
|
|
1184
1262
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
}
|
|
1195
|
-
if (res.status === 404) {
|
|
1196
|
-
return { ok: true, notFound: true, status: 404 };
|
|
1197
|
-
}
|
|
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" };
|
|
1201
|
-
}
|
|
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;
|
|
1202
1272
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
)
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
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
|
|
1225
1296
|
};
|
|
1226
|
-
} catch (e) {
|
|
1227
|
-
return { ok: false, status: 0, error: e?.message || "network error" };
|
|
1228
1297
|
}
|
|
1298
|
+
return progress;
|
|
1229
1299
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
method: "PUT",
|
|
1234
|
-
body: JSON.stringify({ pinned })
|
|
1235
|
-
});
|
|
1236
|
-
if (res.ok) return { ok: true, status: res.status };
|
|
1237
|
-
const data = await res.json().catch(() => ({}));
|
|
1238
|
-
return { ok: false, status: res.status, error: data?.error || `HTTP ${res.status}` };
|
|
1239
|
-
} catch (e) {
|
|
1240
|
-
return { ok: false, status: 0, error: e?.message || "network error" };
|
|
1241
|
-
}
|
|
1300
|
+
function isTerminalSnapshot(snap) {
|
|
1301
|
+
if (extractPendingQuestion(snap)) return false;
|
|
1302
|
+
return snap?.done === true || isTerminalStatus(snap?.status);
|
|
1242
1303
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
}
|
|
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}`;
|
|
1251
1313
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
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 : "";
|
|
1258
1323
|
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
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");
|
|
1279
1345
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
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
|
+
}
|
|
1291
1357
|
}
|
|
1358
|
+
return atts.length > 0 ? atts : void 0;
|
|
1292
1359
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
import { create as create2 } from "zustand";
|
|
1296
|
-
var useWidgetsStore = create2((set, get) => ({
|
|
1297
|
-
tiles: [],
|
|
1298
|
-
hydrated: false,
|
|
1360
|
+
var useChatStore = create4((set, get) => ({
|
|
1361
|
+
messages: [],
|
|
1299
1362
|
loading: false,
|
|
1300
1363
|
error: null,
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
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 () => {
|
|
1308
1371
|
try {
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
set({ tiles: before.filter((t) => t.id !== artifactId) });
|
|
1319
|
-
const result = await deleteArtifact(artifactId, reason);
|
|
1320
|
-
if (result.ok) {
|
|
1321
|
-
return { ok: true };
|
|
1322
|
-
}
|
|
1323
|
-
set({ tiles: before });
|
|
1324
|
-
return { ok: false, error: result.error || `HTTP ${result.status}` };
|
|
1325
|
-
},
|
|
1326
|
-
hide: async (artifactId) => {
|
|
1327
|
-
const before = get().tiles;
|
|
1328
|
-
set({
|
|
1329
|
-
tiles: before.map(
|
|
1330
|
-
(t) => t.id === artifactId ? { ...t, visibility: "hidden" } : t
|
|
1331
|
-
)
|
|
1332
|
-
});
|
|
1333
|
-
const r = await patchArtifact(artifactId, { visibility: "hidden" }, "user_long_press_hide");
|
|
1334
|
-
if (r.ok) {
|
|
1335
|
-
if (r.artifact) {
|
|
1336
|
-
set({
|
|
1337
|
-
tiles: get().tiles.map((t) => t.id === artifactId ? { ...t, ...r.artifact } : t)
|
|
1338
|
-
});
|
|
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;
|
|
1339
1381
|
}
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
},
|
|
1345
|
-
unhide: async (artifactId) => {
|
|
1346
|
-
const before = get().tiles;
|
|
1347
|
-
set({
|
|
1348
|
-
tiles: before.map(
|
|
1349
|
-
(t) => t.id === artifactId ? { ...t, visibility: "pinned" } : t
|
|
1350
|
-
)
|
|
1351
|
-
});
|
|
1352
|
-
const r = await patchArtifact(artifactId, { visibility: "pinned" }, "user_long_press_unhide");
|
|
1353
|
-
if (r.ok) {
|
|
1354
|
-
if (r.artifact) {
|
|
1355
|
-
set({
|
|
1356
|
-
tiles: get().tiles.map((t) => t.id === artifactId ? { ...t, ...r.artifact } : t)
|
|
1357
|
-
});
|
|
1382
|
+
const data = await res.json();
|
|
1383
|
+
if (!data.ok || !data.items) {
|
|
1384
|
+
set({ conversationLoaded: true });
|
|
1385
|
+
return;
|
|
1358
1386
|
}
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
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()
|
|
1377
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 {
|
|
1378
1532
|
}
|
|
1379
|
-
|
|
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 });
|
|
1380
1543
|
}
|
|
1381
|
-
set({ tiles: before });
|
|
1382
|
-
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
1383
|
-
},
|
|
1384
|
-
reorder: async (pinnedIds) => {
|
|
1385
|
-
const before = get().tiles;
|
|
1386
|
-
const idToOrder = new Map(pinnedIds.map((id, i) => [id, i]));
|
|
1387
|
-
set({
|
|
1388
|
-
tiles: before.map((t) => {
|
|
1389
|
-
const o = idToOrder.get(t.id);
|
|
1390
|
-
return o == null ? t : { ...t, pinOrder: o };
|
|
1391
|
-
})
|
|
1392
|
-
});
|
|
1393
|
-
const r = await setOrder(pinnedIds);
|
|
1394
|
-
if (r.ok) return { ok: true };
|
|
1395
|
-
set({ tiles: before });
|
|
1396
|
-
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
1397
|
-
},
|
|
1398
|
-
applyServerUpdate: (artifact) => {
|
|
1399
|
-
set({
|
|
1400
|
-
tiles: get().tiles.map((t) => t.id === artifact.id ? { ...t, ...artifact } : t)
|
|
1401
|
-
});
|
|
1402
|
-
},
|
|
1403
|
-
applyServerRetire: (artifactId) => {
|
|
1404
|
-
set({ tiles: get().tiles.filter((t) => t.id !== artifactId) });
|
|
1405
|
-
},
|
|
1406
|
-
applyServerOrder: (pinnedIds) => {
|
|
1407
|
-
const idToOrder = new Map(pinnedIds.map((id, i) => [id, i]));
|
|
1408
|
-
set({
|
|
1409
|
-
tiles: get().tiles.map((t) => {
|
|
1410
|
-
const o = idToOrder.get(t.id);
|
|
1411
|
-
return o == null ? t : { ...t, pinOrder: o };
|
|
1412
|
-
})
|
|
1413
|
-
});
|
|
1414
1544
|
},
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
actions: [],
|
|
1435
|
-
hydrated: false,
|
|
1436
|
-
loading: false,
|
|
1437
|
-
hydrate: async () => {
|
|
1438
|
-
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 }));
|
|
1439
1564
|
try {
|
|
1440
|
-
const
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
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 {
|
|
1445
1594
|
}
|
|
1595
|
+
throw new Error(errMsg);
|
|
1446
1596
|
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
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
|
+
}));
|
|
1460
1652
|
}
|
|
1461
1653
|
},
|
|
1462
|
-
//
|
|
1463
|
-
//
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
if (r.ok) return { ok: true };
|
|
1476
|
-
set({ actions: before });
|
|
1477
|
-
persist(before);
|
|
1478
|
-
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
1479
|
-
},
|
|
1480
|
-
setPinned: async (actionId, pinned) => {
|
|
1481
|
-
const before = get().actions;
|
|
1482
|
-
const next = before.map((a) => a.actionId === actionId ? { ...a, pinned } : a).sort(sortByPin);
|
|
1483
|
-
set({ actions: next });
|
|
1484
|
-
persist(next);
|
|
1485
|
-
const r = await patchHomeAction(actionId, { pinned }, pinned ? "user_pin" : "user_unpin");
|
|
1486
|
-
if (r.ok) {
|
|
1487
|
-
if (r.action) {
|
|
1488
|
-
const merged = get().actions.map((a) => a.actionId === actionId ? { ...a, ...r.action } : a).sort(sortByPin);
|
|
1489
|
-
set({ actions: merged });
|
|
1490
|
-
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);
|
|
1491
1667
|
}
|
|
1492
|
-
|
|
1668
|
+
} catch (err) {
|
|
1669
|
+
console.warn("[adas-chat] Error responding to question:", err.message);
|
|
1493
1670
|
}
|
|
1494
|
-
set({ actions: before });
|
|
1495
|
-
persist(before);
|
|
1496
|
-
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
1497
|
-
},
|
|
1498
|
-
reorder: async (orderedIds) => {
|
|
1499
|
-
const before = get().actions;
|
|
1500
|
-
const idx = new Map(orderedIds.map((id, i) => [id, i]));
|
|
1501
|
-
const next = before.map((a) => {
|
|
1502
|
-
const o = idx.get(a.actionId);
|
|
1503
|
-
return o == null ? a : { ...a, pinOrder: o };
|
|
1504
|
-
}).sort(sortByPin);
|
|
1505
|
-
set({ actions: next });
|
|
1506
|
-
persist(next);
|
|
1507
|
-
const r = await setHomeActionOrder(orderedIds);
|
|
1508
|
-
if (r.ok) return { ok: true };
|
|
1509
|
-
set({ actions: before });
|
|
1510
|
-
persist(before);
|
|
1511
|
-
return { ok: false, error: r.error || `HTTP ${r.status}` };
|
|
1512
1671
|
},
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
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
|
+
}));
|
|
1518
1698
|
},
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1699
|
+
clearChat: () => {
|
|
1700
|
+
set({
|
|
1701
|
+
messages: [],
|
|
1702
|
+
loading: false,
|
|
1703
|
+
error: null,
|
|
1704
|
+
activeJobId: null,
|
|
1705
|
+
conversationLoaded: false
|
|
1706
|
+
});
|
|
1523
1707
|
},
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1708
|
+
newChat: () => {
|
|
1709
|
+
set({
|
|
1710
|
+
messages: [],
|
|
1711
|
+
loading: false,
|
|
1712
|
+
error: null,
|
|
1713
|
+
activeJobId: null,
|
|
1714
|
+
conversationLoaded: true
|
|
1715
|
+
});
|
|
1532
1716
|
}
|
|
1533
1717
|
}));
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
function backoffMs(attempt) {
|
|
1546
|
-
const base = Math.min(MAX_BACKOFF_MS, 500 * Math.pow(2, attempt));
|
|
1547
|
-
const jitter = Math.random() * 250;
|
|
1548
|
-
return base + jitter;
|
|
1549
|
-
}
|
|
1550
|
-
function send(obj) {
|
|
1551
|
-
if (_ws2 && _ws2.readyState === 1) {
|
|
1552
|
-
try {
|
|
1553
|
-
_ws2.send(JSON.stringify(obj));
|
|
1554
|
-
} catch (e) {
|
|
1555
|
-
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 });
|
|
1556
1729
|
}
|
|
1557
1730
|
}
|
|
1558
1731
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
if (msg?.topic === TOPIC_HOME) {
|
|
1565
|
-
const home = useHomeActionsStore.getState();
|
|
1566
|
-
switch (msg.event) {
|
|
1567
|
-
case "updated":
|
|
1568
|
-
if (msg.action && typeof msg.action.actionId === "string") home.applyServerUpsert(msg.action);
|
|
1569
|
-
break;
|
|
1570
|
-
case "removed":
|
|
1571
|
-
if (typeof msg.actionId === "string") home.applyServerRemove(msg.actionId);
|
|
1572
|
-
break;
|
|
1573
|
-
case "order":
|
|
1574
|
-
if (Array.isArray(msg.order)) home.applyServerOrder(msg.order.filter((id) => typeof id === "string"));
|
|
1575
|
-
break;
|
|
1576
|
-
case "refreshed":
|
|
1577
|
-
home.refresh();
|
|
1578
|
-
break;
|
|
1579
|
-
default:
|
|
1580
|
-
break;
|
|
1581
|
-
}
|
|
1582
|
-
return;
|
|
1583
|
-
}
|
|
1584
|
-
if (msg?.topic !== TOPIC) return;
|
|
1585
|
-
const store = useWidgetsStore.getState();
|
|
1586
|
-
switch (msg.event) {
|
|
1587
|
-
case "updated":
|
|
1588
|
-
if (msg.artifact && typeof msg.artifact.id === "string") {
|
|
1589
|
-
store.applyServerUpdate(msg.artifact);
|
|
1590
|
-
}
|
|
1591
|
-
break;
|
|
1592
|
-
case "retired":
|
|
1593
|
-
if (typeof msg.artifactId === "string") {
|
|
1594
|
-
store.applyServerRetire(msg.artifactId);
|
|
1595
|
-
}
|
|
1596
|
-
break;
|
|
1597
|
-
case "order":
|
|
1598
|
-
if (Array.isArray(msg.pinned)) {
|
|
1599
|
-
store.applyServerOrder(msg.pinned.filter((id) => typeof id === "string"));
|
|
1600
|
-
}
|
|
1601
|
-
break;
|
|
1602
|
-
case "created":
|
|
1603
|
-
if (msg.artifact && typeof msg.artifact.id === "string") {
|
|
1604
|
-
store.applyServerUpdate(msg.artifact);
|
|
1605
|
-
}
|
|
1606
|
-
break;
|
|
1607
|
-
default:
|
|
1608
|
-
break;
|
|
1609
|
-
}
|
|
1732
|
+
var _ws2 = null;
|
|
1733
|
+
var _wsReady = false;
|
|
1734
|
+
var _clientDeclarationProvider = null;
|
|
1735
|
+
function setClientDeclarationProvider(fn) {
|
|
1736
|
+
_clientDeclarationProvider = fn;
|
|
1610
1737
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
if (
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
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);
|
|
1617
1743
|
_ws2 = ws;
|
|
1744
|
+
_wsReady = false;
|
|
1618
1745
|
ws.onopen = () => {
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
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
|
+
}
|
|
1622
1755
|
};
|
|
1623
|
-
ws.onmessage = (
|
|
1756
|
+
ws.onmessage = (ev) => {
|
|
1624
1757
|
try {
|
|
1625
|
-
const msg = JSON.parse(typeof
|
|
1626
|
-
|
|
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
|
+
}
|
|
1627
1799
|
} catch (err) {
|
|
1628
|
-
console.warn("[
|
|
1800
|
+
console.warn("[adas-chat] WS parse error:", err);
|
|
1629
1801
|
}
|
|
1630
1802
|
};
|
|
1631
|
-
ws.onerror = (
|
|
1632
|
-
|
|
1803
|
+
ws.onerror = () => {
|
|
1804
|
+
_ws2 = null;
|
|
1805
|
+
_wsReady = false;
|
|
1806
|
+
if (_currentJobId) {
|
|
1807
|
+
const jid = _currentJobId;
|
|
1808
|
+
_currentJobId = null;
|
|
1809
|
+
pollJob(jid, token);
|
|
1810
|
+
}
|
|
1633
1811
|
};
|
|
1634
1812
|
ws.onclose = () => {
|
|
1635
1813
|
_ws2 = null;
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
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 }));
|
|
1651
1828
|
}
|
|
1652
1829
|
}
|
|
1653
|
-
function
|
|
1654
|
-
|
|
1830
|
+
function closeChatStream() {
|
|
1831
|
+
_currentJobId = null;
|
|
1655
1832
|
if (_ws2) {
|
|
1656
1833
|
try {
|
|
1657
1834
|
_ws2.close();
|
|
1658
1835
|
} catch {
|
|
1659
1836
|
}
|
|
1660
|
-
_ws2 = null;
|
|
1661
1837
|
}
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
_wantOpen = true;
|
|
1665
|
-
openSocket();
|
|
1838
|
+
_ws2 = null;
|
|
1839
|
+
_wsReady = false;
|
|
1666
1840
|
}
|
|
1667
|
-
function
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
if (
|
|
1672
|
-
|
|
1673
|
-
|
|
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
|
+
}
|
|
1674
1862
|
}
|
|
1675
|
-
|
|
1863
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
1676
1864
|
try {
|
|
1677
|
-
|
|
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
|
+
}
|
|
1678
1886
|
} catch {
|
|
1679
1887
|
}
|
|
1680
|
-
|
|
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");
|
|
1681
1971
|
}
|
|
1972
|
+
await onSession(session);
|
|
1973
|
+
closeChatStream();
|
|
1974
|
+
useChatStore.getState().clearChat();
|
|
1975
|
+
return session;
|
|
1682
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
|
+
};
|
|
1683
2077
|
export {
|
|
1684
2078
|
PluginCommandEmitter,
|
|
1685
2079
|
TABLE_BLOCK_CLOSE,
|
|
@@ -1689,12 +2083,17 @@ export {
|
|
|
1689
2083
|
apiFetchRaw,
|
|
1690
2084
|
blocksToText,
|
|
1691
2085
|
borderRadius,
|
|
2086
|
+
capabilityEnabled,
|
|
1692
2087
|
closeChatStream,
|
|
1693
2088
|
colors,
|
|
1694
2089
|
connectAcsWs,
|
|
1695
2090
|
connectorCall,
|
|
2091
|
+
createAuthSession,
|
|
2092
|
+
createPushRegistration,
|
|
1696
2093
|
deleteArtifact,
|
|
1697
2094
|
disconnectAcsWs,
|
|
2095
|
+
exchangeHandoff,
|
|
2096
|
+
fetchConsumerConfig,
|
|
1698
2097
|
fontSize,
|
|
1699
2098
|
getConfig,
|
|
1700
2099
|
getConsumerAuthApiUrl,
|
|
@@ -1708,6 +2107,10 @@ export {
|
|
|
1708
2107
|
initAdas,
|
|
1709
2108
|
isAdasInitialized,
|
|
1710
2109
|
isDevHost,
|
|
2110
|
+
joinWithApple,
|
|
2111
|
+
joinWithGoogle,
|
|
2112
|
+
linkChannelStart,
|
|
2113
|
+
linkChannelVerify,
|
|
1711
2114
|
listHomeActions,
|
|
1712
2115
|
listMadeForYou,
|
|
1713
2116
|
listMyTenants,
|
|
@@ -1715,21 +2118,36 @@ export {
|
|
|
1715
2118
|
mcpFetch,
|
|
1716
2119
|
mcpToolCall,
|
|
1717
2120
|
notifyAcsWsForeground,
|
|
2121
|
+
notifyAppForeground,
|
|
2122
|
+
onAppForeground,
|
|
2123
|
+
openJoinWithApple,
|
|
2124
|
+
openJoinWithGoogle,
|
|
2125
|
+
parseDeepLinkParam,
|
|
2126
|
+
parseHandoffCode,
|
|
2127
|
+
parseInviteCode,
|
|
1718
2128
|
patchArtifact,
|
|
1719
2129
|
patchHomeAction,
|
|
1720
2130
|
pluginCommandEmitter,
|
|
1721
2131
|
postCommandResult,
|
|
2132
|
+
previewInvite,
|
|
1722
2133
|
recordEvent,
|
|
1723
2134
|
refreshToken,
|
|
2135
|
+
resolveAdasPhase,
|
|
1724
2136
|
setAcsCacheStorage,
|
|
1725
2137
|
setAcsReachabilityReporter,
|
|
2138
|
+
setClientDeclarationProvider,
|
|
1726
2139
|
setHomeActionOrder,
|
|
1727
2140
|
setOrder,
|
|
2141
|
+
signInWithApple,
|
|
2142
|
+
signInWithGoogle,
|
|
1728
2143
|
spacing,
|
|
1729
2144
|
switchTenant,
|
|
1730
2145
|
unwrapMcpResult,
|
|
2146
|
+
useCapability,
|
|
1731
2147
|
useChatStore,
|
|
1732
2148
|
useHomeActionsStore,
|
|
1733
|
-
useWidgetsStore
|
|
2149
|
+
useWidgetsStore,
|
|
2150
|
+
useWorkspaceStore,
|
|
2151
|
+
userFromJwt
|
|
1734
2152
|
};
|
|
1735
2153
|
//# sourceMappingURL=index.mjs.map
|