@astralform/js 7.1.1 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +74 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +74 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2371,6 +2371,13 @@ var StreamManager = class {
|
|
|
2371
2371
|
* its awaits. This can.
|
|
2372
2372
|
*/
|
|
2373
2373
|
this.turnCounter = 0;
|
|
2374
|
+
/**
|
|
2375
|
+
* True while a `resync` is between its probe and its restore. Visibility and
|
|
2376
|
+
* focus listeners can both fire for one return, and two overlapping resyncs
|
|
2377
|
+
* would each detach the other's stream mid-flight — the second call must
|
|
2378
|
+
* find the flag set and leave the first to converge.
|
|
2379
|
+
*/
|
|
2380
|
+
this._resyncing = false;
|
|
2374
2381
|
this.session = session;
|
|
2375
2382
|
this.attach();
|
|
2376
2383
|
}
|
|
@@ -2555,6 +2562,73 @@ var StreamManager = class {
|
|
|
2555
2562
|
}
|
|
2556
2563
|
}
|
|
2557
2564
|
}
|
|
2565
|
+
// ── Resync after the page sat in the background ───────────────
|
|
2566
|
+
/**
|
|
2567
|
+
* Re-attach to whatever the server says is live for the ACTIVE conversation.
|
|
2568
|
+
*
|
|
2569
|
+
* The reconnect machinery inside ``consumeEventStream`` only runs while a
|
|
2570
|
+
* stream is being consumed — and a page suspended in the background (locked
|
|
2571
|
+
* phone, app switch, hidden tab) can outlive it: timers are throttled or
|
|
2572
|
+
* suspended, so the stall watchdog may never fire while hidden; the
|
|
2573
|
+
* reconnect budget (``SSE_MAX_RECONNECTS``) can burn out in fail-fast
|
|
2574
|
+
* attempts; and a 401 from a rotated access token ends the loop outright as
|
|
2575
|
+
* non-retryable. What is left is a manager that believes a turn is streaming
|
|
2576
|
+
* (or has given up on one that is still running) with nothing attached — and
|
|
2577
|
+
* no navigation will ever fix it, because ``switchTo`` early-returns on the
|
|
2578
|
+
* conversation it is already on.
|
|
2579
|
+
*
|
|
2580
|
+
* So the consumer calls this when the user COMES BACK
|
|
2581
|
+
* (``visibilitychange → visible``, window ``focus``). One ``getActiveJob``
|
|
2582
|
+
* probe, then:
|
|
2583
|
+
*
|
|
2584
|
+
* - attached to exactly the job the server calls live → healthy. The stall
|
|
2585
|
+
* watchdog owns zombie recovery from here, now that timers run again.
|
|
2586
|
+
* No-op.
|
|
2587
|
+
* - anything else — attached to a job the server no longer calls live,
|
|
2588
|
+
* attached to nothing while a job runs, or idle with a live job another
|
|
2589
|
+
* tab/device started — → detach and re-run ``restore``, the same path a
|
|
2590
|
+
* conversation reopen takes, with all of its supersession and takeover
|
|
2591
|
+
* guards inherited.
|
|
2592
|
+
*
|
|
2593
|
+
* Skipped while a restore is already in flight (it is converging on server
|
|
2594
|
+
* truth by itself) and while another resync holds the flag — see
|
|
2595
|
+
* ``_resyncing``.
|
|
2596
|
+
*/
|
|
2597
|
+
async resync() {
|
|
2598
|
+
const conversationId = this._activeConversationId;
|
|
2599
|
+
if (!conversationId) return;
|
|
2600
|
+
if (this._state === "restoring" || this._resyncing) return;
|
|
2601
|
+
this._resyncing = true;
|
|
2602
|
+
const gen = this.generation;
|
|
2603
|
+
const turn = this.turnCounter;
|
|
2604
|
+
try {
|
|
2605
|
+
let activeJobId = null;
|
|
2606
|
+
try {
|
|
2607
|
+
activeJobId = (await this.session.client.getActiveJob(conversationId)).jobId;
|
|
2608
|
+
} catch {
|
|
2609
|
+
return;
|
|
2610
|
+
}
|
|
2611
|
+
if (gen !== this.generation || this.turnStarted(turn)) return;
|
|
2612
|
+
const attached = this._state === "streaming" || this.session.isStreaming;
|
|
2613
|
+
if (activeJobId === null && !attached) return;
|
|
2614
|
+
if (activeJobId !== null && this.session.isStreaming && this.session.currentJobId === activeJobId) {
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
this.session.detach();
|
|
2618
|
+
this.session.currentJobId = null;
|
|
2619
|
+
this._state = "idle";
|
|
2620
|
+
try {
|
|
2621
|
+
await this.restore(conversationId, gen);
|
|
2622
|
+
} catch {
|
|
2623
|
+
}
|
|
2624
|
+
} finally {
|
|
2625
|
+
this._resyncing = false;
|
|
2626
|
+
const restoring = "restoring";
|
|
2627
|
+
if (gen === this.generation && this._state === restoring) {
|
|
2628
|
+
this.settleIdle();
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2558
2632
|
// ── Create / rename / delete conversation ─────────────────────
|
|
2559
2633
|
/**
|
|
2560
2634
|
* Create a conversation and make it active.
|