pilotswarm-cli 0.1.7 → 0.1.8

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.
Files changed (2) hide show
  1. package/cli/tui.js +84 -0
  2. package/package.json +1 -1
package/cli/tui.js CHANGED
@@ -2683,6 +2683,7 @@ function recolorWorkerPanes() {
2683
2683
 
2684
2684
  function showCopilotMessage(raw, orchId) {
2685
2685
  const _ph = perfStart("showCopilotMessage");
2686
+ stopChatSpinner(orchId);
2686
2687
 
2687
2688
  appendActivity(`{green-fg}[obs] showCopilotMessage called for ${orchId === activeOrchId ? "ACTIVE" : "background"} session, len=${raw?.length || 0}{/green-fg}`, orchId);
2688
2689
 
@@ -3543,6 +3544,60 @@ const sessionRecoveredTurnResult = new Map(); // orchId → normalized completed
3543
3544
  const sessionObservers = new Map(); // orchId → AbortController
3544
3545
  const sessionLiveStatus = new Map(); // orchId → "idle"|"running"|"waiting"|"input_required"
3545
3546
  const sessionPendingTurns = new Set(); // orchIds with a locally-sent turn awaiting first live status
3547
+
3548
+ // ─── Inline chat spinner ─────────────────────────────────────────
3549
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
3550
+ const sessionSpinnerIndex = new Map(); // orchId → buffer line index where spinner lives
3551
+ let _spinnerFrame = 0;
3552
+ let _spinnerTimer = null;
3553
+
3554
+ function startChatSpinner(orchId) {
3555
+ const buf = sessionChatBuffers.get(orchId);
3556
+ if (!buf) return;
3557
+ // Remove existing spinner if any
3558
+ stopChatSpinner(orchId);
3559
+ const line = `{gray-fg}${SPINNER_FRAMES[0]} Thinking…{/gray-fg}`;
3560
+ buf.push("");
3561
+ buf.push(line);
3562
+ sessionSpinnerIndex.set(orchId, buf.length - 1);
3563
+ if (orchId === activeOrchId) invalidateChat("bottom");
3564
+ // Start global animation timer if not running
3565
+ if (!_spinnerTimer) {
3566
+ _spinnerTimer = setInterval(() => {
3567
+ _spinnerFrame = (_spinnerFrame + 1) % SPINNER_FRAMES.length;
3568
+ let anyActive = false;
3569
+ for (const [sid, idx] of sessionSpinnerIndex) {
3570
+ const b = sessionChatBuffers.get(sid);
3571
+ if (b && idx < b.length) {
3572
+ b[idx] = `{gray-fg}${SPINNER_FRAMES[_spinnerFrame]} Thinking…{/gray-fg}`;
3573
+ if (sid === activeOrchId) { invalidateChat(); anyActive = true; }
3574
+ }
3575
+ }
3576
+ if (!anyActive && sessionSpinnerIndex.size === 0) {
3577
+ clearInterval(_spinnerTimer);
3578
+ _spinnerTimer = null;
3579
+ }
3580
+ }, 80);
3581
+ }
3582
+ }
3583
+
3584
+ function stopChatSpinner(orchId) {
3585
+ const idx = sessionSpinnerIndex.get(orchId);
3586
+ if (idx == null) return;
3587
+ const buf = sessionChatBuffers.get(orchId);
3588
+ if (buf && idx < buf.length) {
3589
+ // Remove spinner line and the blank line before it
3590
+ const startIdx = (idx > 0 && buf[idx - 1] === "") ? idx - 1 : idx;
3591
+ buf.splice(startIdx, idx - startIdx + 1);
3592
+ }
3593
+ sessionSpinnerIndex.delete(orchId);
3594
+ if (orchId === activeOrchId) invalidateChat();
3595
+ // Stop global timer if no spinners remain
3596
+ if (sessionSpinnerIndex.size === 0 && _spinnerTimer) {
3597
+ clearInterval(_spinnerTimer);
3598
+ _spinnerTimer = null;
3599
+ }
3600
+ }
3546
3601
  const sessionPendingQuestions = new Map(); // orchId → latest input-required question awaiting a user answer
3547
3602
  const sessionLastSeenResponseVersion = new Map(); // orchId → latest KV-backed response version rendered
3548
3603
  const sessionLastSeenCommandVersion = new Map(); // orchId → latest KV-backed command response version rendered
@@ -3756,6 +3811,33 @@ function handleDbRecovered() {
3756
3811
  if (_dbOffline) {
3757
3812
  appendLog(`{green-fg}Database connection restored.{/green-fg}`);
3758
3813
  setStatus("Database connection restored.");
3814
+
3815
+ // The orchestration list poll uses its own management client pool, but
3816
+ // the active session's CMS event stream and reconstructed history may
3817
+ // still be stale after a DB outage. Re-prime the active session view so
3818
+ // chat/activity panes resume updating without requiring a manual switch.
3819
+ const recoveredOrchId = activeOrchId;
3820
+ if (recoveredOrchId) {
3821
+ stopCmsPoller();
3822
+ loadCmsHistory(recoveredOrchId, { force: true })
3823
+ .then(() => {
3824
+ if (recoveredOrchId === activeOrchId) {
3825
+ startCmsPoller(recoveredOrchId);
3826
+ invalidateChat();
3827
+ invalidateActivity();
3828
+ redrawActiveViews();
3829
+ scheduleLightRefresh("dbRecovered", recoveredOrchId);
3830
+ }
3831
+ })
3832
+ .catch(() => {
3833
+ if (recoveredOrchId === activeOrchId) {
3834
+ startCmsPoller(recoveredOrchId);
3835
+ invalidateChat();
3836
+ invalidateActivity();
3837
+ redrawActiveViews();
3838
+ }
3839
+ });
3840
+ }
3759
3841
  }
3760
3842
  _dbOffline = false;
3761
3843
  _dbNextRetryAt = 0;
@@ -5205,6 +5287,7 @@ function startObserver(orchId) {
5205
5287
 
5206
5288
  function renderResponsePayload(response, cs, source) {
5207
5289
  if (!response) return;
5290
+ stopChatSpinner(orchId);
5208
5291
  if (response.type === "completed" && response.content) {
5209
5292
  appendActivity(`{green-fg}[obs] ✓ SHOWING ${source}: version=${response.version} type=completed content=${response.content.slice(0, 80)}{/green-fg}`, orchId);
5210
5293
  renderCompletedContent(response.content);
@@ -6121,6 +6204,7 @@ async function handleInput(text) {
6121
6204
  }
6122
6205
 
6123
6206
  appendChatRaw(`{white-fg}[${ts()}]{/white-fg} {white-fg}{bold}You:{/bold} ${trimmed}{/white-fg}`, targetOrchId);
6207
+ startChatSpinner(targetOrchId);
6124
6208
  inputBar.clearValue();
6125
6209
  focusInput();
6126
6210
  setSessionPendingTurn(targetOrchId, true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pilotswarm-cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Terminal UI for pilotswarm — interactive durable agent orchestration.",
5
5
  "type": "module",
6
6
  "bin": {