@jini-ai/chat 0.3.10 → 0.3.11

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.
@@ -32,6 +32,9 @@ export interface UseChatPaneRuntimeInventoryResult {
32
32
  * the daemon goes from offline to online, the window regains focus, or the tab becomes visible.
33
33
  * A RESOLVED list — even an empty one — is a finished answer; hosts reject when theirs is not one.
34
34
  *
35
+ * Health polling runs only while the tab is visible (hidden tabs made no-op status calls every
36
+ * `pollIntervalMs` forever), and never overlaps a status call still in flight.
37
+ *
35
38
  * @complexity Time: O(n) per inventory response; space: O(n) for the snapshot.
36
39
  * @overallScore 100/100
37
40
  */
@@ -17,6 +17,9 @@ const DEFAULT_RETRY_DELAYS_MS = [1_000, 2_000, 4_000, 8_000, 15_000, 30_000];
17
17
  * the daemon goes from offline to online, the window regains focus, or the tab becomes visible.
18
18
  * A RESOLVED list — even an empty one — is a finished answer; hosts reject when theirs is not one.
19
19
  *
20
+ * Health polling runs only while the tab is visible (hidden tabs made no-op status calls every
21
+ * `pollIntervalMs` forever), and never overlaps a status call still in flight.
22
+ *
20
23
  * @complexity Time: O(n) per inventory response; space: O(n) for the snapshot.
21
24
  * @overallScore 100/100
22
25
  */
@@ -99,10 +102,42 @@ export function useChatPaneRuntimeInventory({ access, initialAgents = EMPTY_AGEN
99
102
  return;
100
103
  retry.reset();
101
104
  void loadAgents(false);
102
- void refreshStatus();
103
- const timer = window.setInterval(() => void refreshStatus(), pollIntervalMs);
104
- return () => {
105
+ // Health polls only while the tab is visible: a hidden tab stops its timer, and showing it
106
+ // again checks once at once and restarts the interval. A tick is skipped while the previous
107
+ // status call is still out, so a slow host never piles up requests.
108
+ let timer;
109
+ let statusInFlight = false;
110
+ const pollStatus = () => {
111
+ if (statusInFlight)
112
+ return;
113
+ statusInFlight = true;
114
+ void refreshStatus().finally(() => {
115
+ statusInFlight = false;
116
+ });
117
+ };
118
+ const startPolling = () => {
119
+ if (timer === undefined)
120
+ timer = window.setInterval(pollStatus, pollIntervalMs);
121
+ };
122
+ const stopPolling = () => {
105
123
  window.clearInterval(timer);
124
+ timer = undefined;
125
+ };
126
+ const onVisibilityChange = () => {
127
+ if (document.visibilityState !== 'visible') {
128
+ stopPolling();
129
+ return;
130
+ }
131
+ pollStatus();
132
+ startPolling();
133
+ };
134
+ pollStatus();
135
+ if (document.visibilityState === 'visible')
136
+ startPolling();
137
+ document.addEventListener('visibilitychange', onVisibilityChange);
138
+ return () => {
139
+ document.removeEventListener('visibilitychange', onVisibilityChange);
140
+ stopPolling();
106
141
  retry.cancel();
107
142
  inventory.supersede();
108
143
  health.supersede();
@@ -111,17 +146,24 @@ export function useChatPaneRuntimeInventory({ access, initialAgents = EMPTY_AGEN
111
146
  useEffect(() => {
112
147
  if (!access)
113
148
  return;
114
- const onWake = () => {
149
+ // The status check on becoming visible belongs to the polling effect above; this one only
150
+ // retries an unanswered inventory load (and, on focus, checks status too).
151
+ const onFocus = () => {
115
152
  if (document.visibilityState === 'hidden')
116
153
  return;
117
154
  void refreshStatus();
118
155
  reloadIfUnanswered();
119
156
  };
120
- window.addEventListener('focus', onWake);
121
- document.addEventListener('visibilitychange', onWake);
157
+ const onVisible = () => {
158
+ if (document.visibilityState === 'hidden')
159
+ return;
160
+ reloadIfUnanswered();
161
+ };
162
+ window.addEventListener('focus', onFocus);
163
+ document.addEventListener('visibilitychange', onVisible);
122
164
  return () => {
123
- window.removeEventListener('focus', onWake);
124
- document.removeEventListener('visibilitychange', onWake);
165
+ window.removeEventListener('focus', onFocus);
166
+ document.removeEventListener('visibilitychange', onVisible);
125
167
  };
126
168
  }, [access, refreshStatus, reloadIfUnanswered]);
127
169
  const rescanAgents = useCallback(() => loadAgents(true), [loadAgents]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/chat",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "description": "Jini's chat/artifact frontend, consolidated into one package with two subpaths: framework-free chat vocabulary and pure parsers in `/core`, headless hooks + presentational components + slots in `/react`. Import only the layer you need.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -64,8 +64,8 @@
64
64
  "@mcp-ui/client": "7.1.1",
65
65
  "@mcp-ui/server": "6.1.0",
66
66
  "@jini-ai/agentic": "0.3.7",
67
- "@jini-ai/protocol": "0.3.1",
68
67
  "@jini-ai/platform": "0.3.0",
68
+ "@jini-ai/protocol": "0.3.1",
69
69
  "@jini-ai/ui": "0.3.14"
70
70
  },
71
71
  "peerDependencies": {