@firstpick/pi-package-webui 0.2.6 → 0.2.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/bin/pi-webui.mjs CHANGED
@@ -2147,6 +2147,22 @@ function responseWithPendingThinking(tab, response) {
2147
2147
  return { ...response, data: stateWithPendingThinking(tab, response.data) };
2148
2148
  }
2149
2149
 
2150
+ function eventForTabClients(tab, event) {
2151
+ return {
2152
+ ...responseWithPendingThinking(tab, event),
2153
+ tabId: tab.id,
2154
+ tabTitle: tab.title,
2155
+ tabActivity: tabActivitySnapshot(tab),
2156
+ };
2157
+ }
2158
+
2159
+ function broadcastPendingThinkingState(tab, state) {
2160
+ broadcastTabEvent(tab, {
2161
+ ...eventForTabClients(tab, { type: "response", command: "get_state", success: true, data: stateWithPendingThinking(tab, state) }),
2162
+ pendingExtensionUiRequestCount: pendingExtensionUiRequests(tab).length,
2163
+ });
2164
+ }
2165
+
2150
2166
  function forgetTabState(tab) {
2151
2167
  if (!tab) return;
2152
2168
  tab.lastState = null;
@@ -2508,7 +2524,7 @@ function attachRpcToTab(tab, rpc) {
2508
2524
  tab.rpcUnsubscribe = rpc.onEvent((event) => {
2509
2525
  if (resolveWebuiHelperResponse(tab, event) || resolveWebuiHelperRpcResponse(tab, event)) return;
2510
2526
  updateTabActivityFromEvent(tab, event);
2511
- let scopedEvent = { ...event, tabId: tab.id, tabTitle: tab.title, tabActivity: tabActivitySnapshot(tab) };
2527
+ let scopedEvent = eventForTabClients(tab, event);
2512
2528
  if (event?.type === "pi_process_exit" || event?.type === "pi_process_error") {
2513
2529
  clearPendingExtensionUiRequests(tab);
2514
2530
  clearExtensionStatuses(tab);
@@ -3817,10 +3833,16 @@ async function setThinkingLevelForTab(tab, level, { allowPending = true } = {})
3817
3833
  const stateResult = allowPending ? await safeRpcData(tab, { type: "get_state" }, STATUS_RPC_TIMEOUT_MS) : { ok: false };
3818
3834
  if (allowPending && stateResult.ok && stateIsBusyForSettings(stateResult.data)) {
3819
3835
  tab.pendingThinkingLevel = level;
3836
+ broadcastPendingThinkingState(tab, stateResult.data);
3820
3837
  return rpcSuccess("set_thinking_level", { level, pending: true, message: `Thinking level ${level} will apply to the next prompt.` });
3821
3838
  }
3822
3839
  const response = await tab.rpc.send({ type: "set_thinking_level", level });
3823
- if (response.success !== false) tab.pendingThinkingLevel = undefined;
3840
+ if (response.success !== false) {
3841
+ tab.pendingThinkingLevel = undefined;
3842
+ const updatedState = await safeRpcData(tab, { type: "get_state" }, STATUS_RPC_TIMEOUT_MS);
3843
+ const effectiveLevel = updatedState.ok ? updatedState.data?.thinkingLevel : level;
3844
+ return { ...response, data: { ...(response.data && typeof response.data === "object" ? response.data : {}), level: effectiveLevel || level, requestedLevel: level } };
3845
+ }
3824
3846
  return response;
3825
3847
  }
3826
3848
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firstpick/pi-package-webui",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Pi Web UI companion package with a local browser UI CLI plus /webui-start and /webui-status commands.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/public/app.js CHANGED
@@ -7288,10 +7288,10 @@ function resetOptionalFeatureAvailability() {
7288
7288
  renderOptionalFeatureControls();
7289
7289
  }
7290
7290
 
7291
- function requestGitFooterWebuiPayload(tabContext = activeTabContext()) {
7291
+ function requestGitFooterWebuiPayload(tabContext = activeTabContext(), { force = false } = {}) {
7292
7292
  if (!tabContext.tabId || isOptionalFeatureDisabled("gitFooterStatus")) return;
7293
7293
  if (currentState?.isStreaming || currentState?.isCompacting) return;
7294
- if (!hasAvailableCommand("git-footer-refresh") || statusEntries.has(GIT_FOOTER_WEBUI_STATUS_KEY)) return;
7294
+ if (!hasAvailableCommand("git-footer-refresh") || (!force && statusEntries.has(GIT_FOOTER_WEBUI_STATUS_KEY))) return;
7295
7295
  if (gitFooterPayloadRefreshInFlightByTab.has(tabContext.tabId)) return;
7296
7296
 
7297
7297
  gitFooterPayloadRefreshInFlightByTab.add(tabContext.tabId);
@@ -7730,7 +7730,8 @@ function openNativeSettingsDialog() {
7730
7730
  setNativeCommandError("");
7731
7731
  try {
7732
7732
  const requests = [];
7733
- if (thinking.select.value !== state.thinkingLevel) requests.push(nativeCommandApi("/api/thinking", { method: "POST", body: { level: thinking.select.value } }));
7733
+ const thinkingLevelChanged = thinking.select.value !== state.thinkingLevel;
7734
+ if (thinkingLevelChanged) requests.push(nativeCommandApi("/api/thinking", { method: "POST", body: { level: thinking.select.value } }));
7734
7735
  if (steering.select.value !== state.steeringMode) requests.push(nativeCommandApi("/api/steering-mode", { method: "POST", body: { mode: steering.select.value } }));
7735
7736
  if (followUp.select.value !== state.followUpMode) requests.push(nativeCommandApi("/api/follow-up-mode", { method: "POST", body: { mode: followUp.select.value } }));
7736
7737
  if (autoCompact.input.checked !== state.autoCompactionEnabled) requests.push(nativeCommandApi("/api/auto-compaction", { method: "POST", body: { enabled: autoCompact.input.checked } }));
@@ -7738,6 +7739,7 @@ function openNativeSettingsDialog() {
7738
7739
  if (thinkingOutput.input.checked !== thinkingOutputVisible) setThinkingOutputVisible(thinkingOutput.input.checked);
7739
7740
  if (doneNotifications.input.checked !== agentDoneNotificationsEnabled) await setAgentDoneNotificationsEnabled(doneNotifications.input.checked);
7740
7741
  await Promise.all(requests);
7742
+ if (thinkingLevelChanged) requestGitFooterWebuiPayload(activeTabContext(), { force: true });
7741
7743
  addTransientMessage({ role: "native", title: "/settings", content: "Settings updated.", level: "info" });
7742
7744
  closeNativeCommandDialog();
7743
7745
  await refreshState();
@@ -8335,15 +8337,26 @@ function handleMessageUpdate(event) {
8335
8337
  }
8336
8338
  }
8337
8339
 
8340
+ function modelStateKey(model) {
8341
+ return model ? `${model.provider || ""}/${model.id || ""}` : "";
8342
+ }
8343
+
8344
+ function gitFooterRelevantStateChanged(previousState, nextState) {
8345
+ if (!previousState || !nextState) return false;
8346
+ return previousState.thinkingLevel !== nextState.thinkingLevel || modelStateKey(previousState.model) !== modelStateKey(nextState.model);
8347
+ }
8348
+
8338
8349
  async function refreshState(tabContext = activeTabContext()) {
8339
8350
  if (!tabContext.tabId) return;
8340
8351
  const response = await api("/api/state", { tabId: tabContext.tabId });
8341
8352
  if (!isCurrentTabContext(tabContext)) return;
8353
+ const previousState = currentState;
8342
8354
  currentState = response.data || null;
8355
+ const shouldRefreshGitFooter = gitFooterRelevantStateChanged(previousState, currentState);
8343
8356
  syncActiveTabActivityFromState(currentState);
8344
8357
  syncRunIndicatorFromState(currentState);
8345
8358
  renderStatus();
8346
- requestGitFooterWebuiPayload(tabContext);
8359
+ requestGitFooterWebuiPayload(tabContext, { force: shouldRefreshGitFooter });
8347
8360
  }
8348
8361
 
8349
8362
  async function refreshStats(tabContext = activeTabContext()) {
@@ -9181,6 +9194,7 @@ async function cycleThinkingFromShortcut() {
9181
9194
  if (response.data?.level && currentState) currentState = { ...currentState, thinkingLevel: response.data.level };
9182
9195
  if (isCurrentTabContext(tabContext)) {
9183
9196
  addTransientMessage({ role: "native", title: "thinking", content: response.data?.level ? `Thinking level: ${response.data.level}` : "Thinking level did not change.", level: "info" });
9197
+ if (response.data?.level) requestGitFooterWebuiPayload(tabContext, { force: true });
9184
9198
  await Promise.allSettled([refreshState(tabContext), refreshStats(tabContext)]);
9185
9199
  }
9186
9200
  } catch (error) {
@@ -10031,7 +10045,14 @@ elements.setThinkingButton.addEventListener("click", async () => {
10031
10045
  try {
10032
10046
  const response = await api("/api/thinking", { method: "POST", body: { level: elements.thinkingSelect.value }, tabId: tabContext.tabId });
10033
10047
  if (isCurrentTabContext(tabContext)) {
10034
- if (response.data?.pending) addEvent(response.data.message || `Thinking level ${response.data.level} will apply to the next prompt.`, "info");
10048
+ if (response.data?.pending) {
10049
+ addEvent(response.data.message || `Thinking level ${response.data.level} will apply to the next prompt.`, "info");
10050
+ } else if (response.data?.level) {
10051
+ const requested = response.data.requestedLevel;
10052
+ const effective = response.data.level;
10053
+ addEvent(requested && requested !== effective ? `Thinking level set to ${effective} (requested ${requested}).` : `Thinking level set to ${effective}.`, "info");
10054
+ requestGitFooterWebuiPayload(tabContext, { force: true });
10055
+ }
10035
10056
  await refreshState(tabContext);
10036
10057
  }
10037
10058
  } catch (error) {
@@ -368,6 +368,10 @@ assert.doesNotMatch(app, /Git footer status disabled/, "disabled git footer shou
368
368
  assert.doesNotMatch(app, /footerMeta\("runtime"/, "minimal Web UI footer should not render runtime metadata");
369
369
  assert.match(app, /statusEntries\.has\(GIT_FOOTER_WEBUI_STATUS_KEY\)/, "optional feature detection should recognize the git-footer-status Web UI payload");
370
370
  assert.match(app, /\/git-footer-refresh --webui-silent/, "Web UI should quietly request the extension-owned footer payload when idle and missing");
371
+ assert.match(app, /function requestGitFooterWebuiPayload\(tabContext = activeTabContext\(\), \{ force = false \} = \{\}\)[\s\S]*?!force && statusEntries\.has\(GIT_FOOTER_WEBUI_STATUS_KEY\)/, "git footer payload refresh should support forced refresh even when a live payload already exists");
372
+ assert.match(app, /function gitFooterRelevantStateChanged\(previousState, nextState\)[\s\S]*?previousState\.thinkingLevel !== nextState\.thinkingLevel[\s\S]*?modelStateKey\(previousState\.model\) !== modelStateKey\(nextState\.model\)/, "state refresh should detect model and thinking changes that make the git footer payload stale");
373
+ assert.match(app, /requestGitFooterWebuiPayload\(tabContext, \{ force: shouldRefreshGitFooter \}\)/, "state refresh should force-refresh the git footer when model or thinking state changes");
374
+ assert.match(app, /if \(response\.data\?\.level\) requestGitFooterWebuiPayload\(tabContext, \{ force: true \}\)/, "thinking shortcut should immediately force-refresh the git footer payload");
371
375
  assert.match(app, /Loading git footer status…/, "missing git footer payload should show a loading state before declaring the extension unavailable");
372
376
  assert.match(app, /GIT_FOOTER_WEBUI_PAYLOAD_CACHE_KEY/, "git footer payloads should be cached across Web UI reloads");
373
377
  assert.match(app, /function setOptionalFeatureDisabled\(featureId, disabled\)[\s\S]*clearGitFooterWebuiPayloadCache\(\)/, "changing the git footer feature toggle should invalidate the cached footer payload");
@@ -147,9 +147,12 @@ assert.match(server, /async function cycleTabModel\(tab, direction = "forward"\)
147
147
  assert.match(server, /url\.pathname === "\/api\/model-cycle" && req\.method === "POST"/, "server should expose model-cycle endpoint for shortcuts");
148
148
  assert.match(server, /case "\/api\/thinking-cycle":[\s\S]*?type: "cycle_thinking_level"/, "server should expose thinking-cycle endpoint for shortcuts");
149
149
  assert.match(server, /async function setThinkingLevelForTab\(tab, level, \{ allowPending = true \} = \{\}\)[\s\S]*?stateIsBusyForSettings\(stateResult\.data\)[\s\S]*?tab\.pendingThinkingLevel = level/, "server should queue side-panel thinking changes while a tab is running");
150
+ assert.match(server, /function eventForTabClients\(tab, event\)[\s\S]*?responseWithPendingThinking\(tab, event\)[\s\S]*?tabId: tab\.id/, "server should decorate SSE state responses with pending thinking before broadcasting to clients");
151
+ assert.match(server, /tab\.pendingThinkingLevel = level;\n\s+broadcastPendingThinkingState\(tab, stateResult\.data\)/, "server should broadcast queued thinking state after assigning the pending level");
150
152
  assert.match(server, /const pendingThinkingResponse = await applyPendingThinkingBeforePrompt\(tab\)/, "server should apply queued thinking level before the next prompt");
151
153
  assert.match(app, /pendingThinkingLevel[\s\S]*?next prompt/, "frontend should show queued thinking changes as applying on the next prompt");
152
154
  assert.match(app, /response\.data\?\.pending[\s\S]*?will apply to the next prompt/, "frontend should announce queued side-panel thinking changes");
155
+ assert.match(app, /response\.data\?\.level[\s\S]*?Thinking level set to/, "frontend should announce effective side-panel thinking changes");
153
156
  assert.match(app, /function handleNativeAppShortcut\(event\)/, "frontend should centralize native app shortcut handling");
154
157
  assert.match(app, /openNativeModelSelector\(\)/, "Ctrl+L shortcut should open the native model selector");
155
158
  assert.match(app, /cycleModelFromShortcut\(event\.shiftKey \? "backward" : "forward"\)/, "Ctrl+P shortcuts should cycle models forward and backward");