pi-ui-extend 0.1.24 → 0.1.26

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.
@@ -177,7 +177,6 @@ export async function completeInputWithPi(runtime, draft, config, signal) {
177
177
  maxTokens: requestModel.maxTokens,
178
178
  ...(parsedModel.thinkingLevel && parsedModel.thinkingLevel !== "off" ? { reasoning: parsedModel.thinkingLevel } : {}),
179
179
  signal: requestSignal.signal,
180
- temperature: 0.1,
181
180
  timeoutMs,
182
181
  });
183
182
  for await (const event of stream) {
@@ -363,6 +363,9 @@ export class AppSessionEventController {
363
363
  }
364
364
  if (!this.assistantTextBuffer)
365
365
  return visibleText;
366
+ if (!final && shouldHoldAssistantStreamWhitespaceTail(this.assistantTextBuffer, this.hasVisibleAssistantText(visibleText))) {
367
+ return visibleText;
368
+ }
366
369
  if (shouldHoldAssistantStreamTail(this.assistantTextBuffer, this.hasVisibleAssistantText(visibleText))) {
367
370
  if (final)
368
371
  this.assistantTextBuffer = "";
@@ -445,6 +448,9 @@ function shouldHoldAssistantStreamTail(text, hasVisibleText) {
445
448
  return !hasVisibleText;
446
449
  return isPotentialDcpMetadataLine(text);
447
450
  }
451
+ function shouldHoldAssistantStreamWhitespaceTail(text, hasVisibleText) {
452
+ return hasVisibleText && text.trim().length === 0;
453
+ }
448
454
  function isHiddenMarkdownMetadataLine(line) {
449
455
  return isMarkdownReferenceDefinition(line) || isPotentialDcpMetadataLine(line);
450
456
  }
@@ -41,8 +41,10 @@ export declare class AppTabsController {
41
41
  private readonly runtimeLoadsByTabId;
42
42
  private readonly runtimeSubscriptionsByTabId;
43
43
  private readonly runtimeRefreshTimersByTabId;
44
+ private readonly historyReloadTimersByTabId;
44
45
  private readonly inputStatesByTabId;
45
46
  private readonly deferredUserMessagesByTabId;
47
+ private readonly tabIdsNeedingHistoryReload;
46
48
  private activeTabId;
47
49
  private pendingActiveTabId;
48
50
  private historyLoadGeneration;
@@ -89,6 +91,9 @@ export declare class AppTabsController {
89
91
  private shouldScheduleDelayedSyncForRuntimeEvent;
90
92
  private scheduleDelayedRuntimeSync;
91
93
  private clearRuntimeRefreshTimers;
94
+ private clearHistoryReloadTimers;
95
+ private scheduleDelayedHistoryReload;
96
+ private reloadActiveTabHistoryIfNeeded;
92
97
  private syncTabFromObservedRuntime;
93
98
  private storeActiveInputState;
94
99
  private storeActiveDeferredUserMessages;
@@ -20,8 +20,10 @@ export class AppTabsController {
20
20
  runtimeLoadsByTabId = new Map();
21
21
  runtimeSubscriptionsByTabId = new Map();
22
22
  runtimeRefreshTimersByTabId = new Map();
23
+ historyReloadTimersByTabId = new Map();
23
24
  inputStatesByTabId = new Map();
24
25
  deferredUserMessagesByTabId = new Map();
26
+ tabIdsNeedingHistoryReload = new Set();
25
27
  activeTabId;
26
28
  pendingActiveTabId;
27
29
  historyLoadGeneration = 0;
@@ -632,6 +634,7 @@ export class AppTabsController {
632
634
  void this.saveTabs();
633
635
  this.scheduleTabPrewarm();
634
636
  await this.loadActiveSessionHistory(targetRuntime);
637
+ this.scheduleDelayedHistoryReload(target.id, targetRuntime);
635
638
  }
636
639
  async closeTab(tabId) {
637
640
  if (this.pendingActiveTabId) {
@@ -814,6 +817,8 @@ export class AppTabsController {
814
817
  this.runtimesByTabId.delete(tabId);
815
818
  this.runtimeLoadsByTabId.delete(tabId);
816
819
  this.clearRuntimeRefreshTimers(tabId);
820
+ this.clearHistoryReloadTimers(tabId);
821
+ this.tabIdsNeedingHistoryReload.delete(tabId);
817
822
  const subscription = this.runtimeSubscriptionsByTabId.get(tabId);
818
823
  subscription?.unsubscribe();
819
824
  this.runtimeSubscriptionsByTabId.delete(tabId);
@@ -822,6 +827,9 @@ export class AppTabsController {
822
827
  for (const tabId of this.runtimeRefreshTimersByTabId.keys()) {
823
828
  this.clearRuntimeRefreshTimers(tabId);
824
829
  }
830
+ for (const tabId of this.historyReloadTimersByTabId.keys()) {
831
+ this.clearHistoryReloadTimers(tabId);
832
+ }
825
833
  for (const subscription of this.runtimeSubscriptionsByTabId.values()) {
826
834
  subscription.unsubscribe();
827
835
  }
@@ -835,6 +843,7 @@ export class AppTabsController {
835
843
  const unsubscribe = runtime.session.subscribe((event) => {
836
844
  if (this.shouldScheduleDelayedSyncForRuntimeEvent(event)) {
837
845
  this.scheduleDelayedRuntimeSync(tabId, runtime);
846
+ this.tabIdsNeedingHistoryReload.add(tabId);
838
847
  }
839
848
  if (!this.shouldSyncTabFromRuntimeEvent(event))
840
849
  return;
@@ -878,6 +887,44 @@ export class AppTabsController {
878
887
  clearTimeout(timer);
879
888
  this.runtimeRefreshTimersByTabId.delete(tabId);
880
889
  }
890
+ clearHistoryReloadTimers(tabId) {
891
+ const timers = this.historyReloadTimersByTabId.get(tabId);
892
+ if (!timers)
893
+ return;
894
+ for (const timer of timers)
895
+ clearTimeout(timer);
896
+ this.historyReloadTimersByTabId.delete(tabId);
897
+ }
898
+ scheduleDelayedHistoryReload(tabId, runtime) {
899
+ if (!this.tabIdsNeedingHistoryReload.has(tabId))
900
+ return;
901
+ if (tabId !== this.activeTabId || this.pendingActiveTabId !== undefined)
902
+ return;
903
+ this.clearHistoryReloadTimers(tabId);
904
+ for (const delayMs of [150, 1000, 3000]) {
905
+ const timer = setTimeout(() => {
906
+ this.historyReloadTimersByTabId.get(tabId)?.delete(timer);
907
+ void this.reloadActiveTabHistoryIfNeeded(tabId, runtime, delayMs === 3000);
908
+ }, delayMs);
909
+ timer.unref?.();
910
+ let timers = this.historyReloadTimersByTabId.get(tabId);
911
+ if (!timers) {
912
+ timers = new Set();
913
+ this.historyReloadTimersByTabId.set(tabId, timers);
914
+ }
915
+ timers.add(timer);
916
+ }
917
+ }
918
+ async reloadActiveTabHistoryIfNeeded(tabId, runtime, finalAttempt) {
919
+ if (tabId !== this.activeTabId || this.pendingActiveTabId !== undefined || this.host.runtime() !== runtime)
920
+ return;
921
+ if (!this.tabIdsNeedingHistoryReload.has(tabId))
922
+ return;
923
+ await this.loadActiveSessionHistory(runtime);
924
+ if (finalAttempt && tabId === this.activeTabId && this.host.runtime() === runtime) {
925
+ this.tabIdsNeedingHistoryReload.delete(tabId);
926
+ }
927
+ }
881
928
  syncTabFromObservedRuntime(tabId, runtime) {
882
929
  const tab = this.tabItems.find((item) => item.id === tabId);
883
930
  if (!tab) {
@@ -83,7 +83,6 @@ export declare const PiToolsSuiteConfigSchema: Type.TObject<{
83
83
  maxTaskChars: Type.TOptional<Type.TNumber>;
84
84
  maxTokens: Type.TOptional<Type.TNumber>;
85
85
  maxRetries: Type.TOptional<Type.TNumber>;
86
- temperature: Type.TOptional<Type.TNumber>;
87
86
  timeoutMs: Type.TOptional<Type.TNumber>;
88
87
  debug: Type.TOptional<Type.TBoolean>;
89
88
  }>>;
@@ -115,7 +115,6 @@ const SubagentRoutingConfig = Type.Object({
115
115
  maxTaskChars: Type.Optional(Type.Number({ description: "Max task/scope characters sent to router.", minimum: 100 })),
116
116
  maxTokens: Type.Optional(Type.Number({ description: "Max router response tokens.", minimum: 8 })),
117
117
  maxRetries: Type.Optional(Type.Number({ description: "Router request retries.", minimum: 0 })),
118
- temperature: Type.Optional(Type.Number({ description: "Router sampling temperature.", minimum: 0, maximum: 2 })),
119
118
  timeoutMs: Type.Optional(Type.Number({ description: "Router request timeout in ms.", minimum: 1000 })),
120
119
  debug: Type.Optional(Type.Boolean({ description: "Show routing debug warnings." })),
121
120
  }, { description: "LLM-based role routing configuration." });
@@ -12,7 +12,6 @@ export interface SessionTitleConfig {
12
12
  maxRetries: number;
13
13
  generationAttempts: number;
14
14
  retryDelayMs: number;
15
- temperature: number;
16
15
  timeoutMs: number;
17
16
  terminalTitle: boolean;
18
17
  terminalTitlePrefix: string;
@@ -29,7 +28,6 @@ const DEFAULT_CONFIG: SessionTitleConfig = {
29
28
  maxRetries: 2,
30
29
  generationAttempts: 3,
31
30
  retryDelayMs: 3000,
32
- temperature: 0.2,
33
31
  timeoutMs: 12_000,
34
32
  terminalTitle: true,
35
33
  terminalTitlePrefix: "pi — ",
@@ -83,9 +81,6 @@ function mergeConfig(base: SessionTitleConfig, raw: Record<string, unknown>): Se
83
81
  if (typeof raw.retryDelayMs === "number" && Number.isFinite(raw.retryDelayMs)) {
84
82
  next.retryDelayMs = Math.max(250, Math.floor(raw.retryDelayMs));
85
83
  }
86
- if (typeof raw.temperature === "number" && Number.isFinite(raw.temperature)) {
87
- next.temperature = Math.min(2, Math.max(0, raw.temperature));
88
- }
89
84
  if (typeof raw.timeoutMs === "number" && Number.isFinite(raw.timeoutMs)) {
90
85
  next.timeoutMs = Math.max(1000, Math.floor(raw.timeoutMs));
91
86
  }
@@ -221,7 +221,6 @@ async function generateSessionTitle(
221
221
  maxRetries: config.maxRetries,
222
222
  maxTokens: config.maxTokens,
223
223
  signal,
224
- temperature: config.temperature,
225
224
  timeoutMs: config.timeoutMs,
226
225
  },
227
226
  );
@@ -163,7 +163,7 @@ Async-subagents also injects a lightweight oh-my-openagent-style system-prompt s
163
163
 
164
164
  When the parent model cannot inspect images, async-subagents adds vision-delegation guidance and can save current-turn image attachments under `.pi/subagents/attachments/` so a `vision` sub-agent can receive them as `imagePaths`. Dynamic provider capabilities can be missing or stale after switching models, so blind parent models can still be configured explicitly with case-insensitive `*` masks under `asyncSubagents.vision.blindModelPatterns` in `~/.config/pi/pi-tools-suite.jsonc`. GLM is no longer treated as blind by async-subagents by default; the main-session `glm-coding-discipline` lookup tool is the preferred path for GLM visual lookups.
165
165
 
166
- When a task omits `subagentType`, async-subagents asks a lightweight router model to choose one configured type for each task from the task text/scope and the `types.<name>.description` metadata. Explicit task `subagentType` still wins. Keep type descriptions short, literal, and distinct because they are inserted into the router prompt for a small model. Router settings live under `asyncSubagents.routing` (`enabled`, `model`, `maxTaskChars`, `maxTokens`, `maxRetries`, `temperature`, `timeoutMs`, `debug`); the default router model is `zai/glm-4.5-air`. If the router is disabled, unavailable, aborted, or returns invalid JSON, omitted types fall back to `defaultType`.
166
+ When a task omits `subagentType`, async-subagents asks a lightweight router model to choose one configured type for each task from the task text/scope and the `types.<name>.description` metadata. Explicit task `subagentType` still wins. Keep type descriptions short, literal, and distinct because they are inserted into the router prompt for a small model. Router settings live under `asyncSubagents.routing` (`enabled`, `model`, `maxTaskChars`, `maxTokens`, `maxRetries`, `timeoutMs`, `debug`); the default router model is `zai/glm-4.5-air`. If the router is disabled, unavailable, aborted, or returns invalid JSON, omitted types fall back to `defaultType`.
167
167
 
168
168
  Define optional `presets` under `asyncSubagents` in `~/.config/pi/pi-tools-suite.jsonc`, `$PI_CONFIG_DIR/pi-tools-suite.jsonc`, or project `.pi/pi-tools-suite.jsonc`, then use `/subagent-preset` or `/subagent-preset-config` to pick one persistent active preset for future spawns across all sessions. Set `AGENTS_PRESET=<name>` before launching Pi to override the saved preset for only the current process/session without changing the saved selection. If Pi is already running, use `/subagent-preset session <name>` for the same process-only override, and `/subagent-preset session-clear` to remove that runtime override. The TUI only selects presets already present in config; it does not edit JSON. If no `asyncSubagents` section exists, run `/subagent-preset init` to insert the bundled sample from `src/async-subagents/async-subagents.sample.jsonc` into the shared config (or to copy a standalone override file when `ASYNC_SUBAGENTS_CONFIG` / `PI_SUBAGENTS_CONFIG` is set). Existing config sections/files are never overwritten. Presets select an agent/model configuration: they can provide global fallback `model`/`thinking`/`extraArgs` and per-role overrides under `asyncSubagents.presets.<name>.types.<subagentType>`. They can also provide ordered `fallbackModels` globally or per-role; when a sub-agent fails with quota/rate-limit errors such as 429, async-subagents immediately tries the next fallback model and remembers the exhausted provider for the current Pi process/session, so later spawns skip that provider until Pi exits. This is intended for provider-level fallback chains such as `antigravity/* → openai-codex/* → zai/*` or `openai-codex/* → zai/*`; omit fallbacks for effectively unlimited providers. Antigravity account rotation has priority over preset fallback: async-subagents only falls back after Antigravity reports that all configured accounts are exhausted for that model. Explicit task model overrides and force-current-model disable preset fallback for that task. The active preset name is stored separately in `~/.pi/agent/subagent-preset-selection.json`.
169
169
 
@@ -58,7 +58,6 @@
58
58
  "maxTaskChars": 1200,
59
59
  "maxTokens": 512,
60
60
  "maxRetries": 1,
61
- "temperature": 0,
62
61
  "timeoutMs": 12000,
63
62
  "debug": false
64
63
  },
@@ -37,8 +37,6 @@ export interface SubagentRoutingConfig {
37
37
  maxTokens?: number;
38
38
  /** Router complete() retries. */
39
39
  maxRetries?: number;
40
- /** Router sampling temperature. */
41
- temperature?: number;
42
40
  /** Router request timeout. */
43
41
  timeoutMs?: number;
44
42
  /** Show best-effort UI warnings when routing falls back. */
@@ -150,7 +148,6 @@ export const DEFAULT_ROUTING_CONFIG: ResolvedSubagentRoutingConfig = {
150
148
  maxTaskChars: 1200,
151
149
  maxTokens: 512,
152
150
  maxRetries: 1,
153
- temperature: 0,
154
151
  timeoutMs: 12_000,
155
152
  debug: false,
156
153
  };
@@ -522,8 +519,6 @@ function normalizeRoutingConfig(value: Record<string, unknown>): SubagentRouting
522
519
  if (maxTokens !== undefined) routing.maxTokens = Math.max(8, Math.round(maxTokens));
523
520
  const maxRetries = finiteNumber(value.maxRetries);
524
521
  if (maxRetries !== undefined) routing.maxRetries = Math.max(0, Math.round(maxRetries));
525
- const temperature = finiteNumber(value.temperature);
526
- if (temperature !== undefined) routing.temperature = Math.min(2, Math.max(0, temperature));
527
522
  const timeoutMs = finiteNumber(value.timeoutMs);
528
523
  if (timeoutMs !== undefined) routing.timeoutMs = Math.max(1000, Math.round(timeoutMs));
529
524
  return routing;
@@ -80,7 +80,6 @@ export async function routeSubagentTasks(
80
80
  maxRetries: routing.maxRetries,
81
81
  maxTokens: routing.maxTokens,
82
82
  signal,
83
- temperature: routing.temperature,
84
83
  timeoutMs: routing.timeoutMs,
85
84
  },
86
85
  );
@@ -82,7 +82,6 @@ export async function decideUltraworkAuto(
82
82
  maxRetries: routing.maxRetries,
83
83
  maxTokens: Math.min(routing.maxTokens, 32),
84
84
  signal,
85
- temperature: 0,
86
85
  timeoutMs: routing.timeoutMs,
87
86
  },
88
87
  );
@@ -23,7 +23,7 @@ export const DEFAULT_PI_TOOLS_SUITE_CONFIG_JSONC = String.raw`{
23
23
  },
24
24
  "asyncSubagents": {
25
25
  "defaultType": "quick",
26
- "routing": { "enabled": true, "model": "zai/glm-4.5-air", "maxTaskChars": 1200, "maxTokens": 512, "maxRetries": 1, "temperature": 0, "timeoutMs": 12000, "debug": false },
26
+ "routing": { "enabled": true, "model": "zai/glm-4.5-air", "maxTaskChars": 1200, "maxTokens": 512, "maxRetries": 1, "timeoutMs": 12000, "debug": false },
27
27
  "presets": {
28
28
  "cheap": {
29
29
  "description": "Use cheap GLM/Gemini Flash models for text/code roles; keep vision on the enabled GPT vision model.",
@@ -40,6 +40,7 @@ const DEFAULT_LOOKUP_TIMEOUT_MS = 120_000;
40
40
  const MAX_IMAGE_BYTES = 16 * 1024 * 1024;
41
41
  const SILENCE_REMINDER_MIN_VIOLATION_GAP = 3;
42
42
  const SILENCE_REMINDER_MIN_MESSAGE_GAP = 12;
43
+ const LOOKUP_TOOL_NAME = "lookup";
43
44
 
44
45
  const LOOKUP_TOOL_PARAMS = Type.Object(
45
46
  {
@@ -187,16 +188,35 @@ export default function glmCodingDiscipline(pi: ExtensionAPI) {
187
188
  pi.registerTool(createLookupTool());
188
189
  }
189
190
 
191
+ function syncLookupToolAvailability(modelRef: string | undefined, cwd?: string): void {
192
+ const activeTools = typeof pi.getActiveTools === "function" ? pi.getActiveTools() : undefined;
193
+ if (!Array.isArray(activeTools)) return;
194
+
195
+ const lookupEnabled = Boolean(lookupModelFromConfig(cwd));
196
+ const shouldExposeLookup = lookupEnabled && isGlmModel(modelRef);
197
+ const hasLookup = activeTools.includes(LOOKUP_TOOL_NAME);
198
+
199
+ if (shouldExposeLookup === hasLookup) return;
200
+ if (typeof pi.setActiveTools !== "function") return;
201
+
202
+ const nextTools = shouldExposeLookup
203
+ ? [...activeTools, LOOKUP_TOOL_NAME]
204
+ : activeTools.filter((tool: unknown) => tool !== LOOKUP_TOOL_NAME);
205
+ pi.setActiveTools([...new Set(nextTools)]);
206
+ }
207
+
190
208
  maybeRegisterLookupTool(process.cwd());
191
209
 
192
210
  pi.on("session_start", async (_event: unknown, ctx: unknown) => {
193
211
  selectedModelRef = modelRefFromContext(ctx);
194
212
  maybeRegisterLookupTool(contextCwd(ctx));
213
+ syncLookupToolAvailability(selectedModelRef, contextCwd(ctx));
195
214
  });
196
215
 
197
216
  pi.on("model_select", async (event: { model?: unknown }, ctx: unknown) => {
198
217
  selectedModelRef = modelRefFromModel(event.model) ?? modelRefFromContext(ctx);
199
218
  maybeRegisterLookupTool(contextCwd(ctx));
219
+ syncLookupToolAvailability(selectedModelRef, contextCwd(ctx));
200
220
  });
201
221
 
202
222
  pi.on("before_provider_request", async (event: { payload?: unknown }, ctx: unknown) => {
@@ -273,7 +293,7 @@ export function injectCodingDisciplineIntoPayload(payload: unknown, options: { l
273
293
 
274
294
  function createLookupTool() {
275
295
  return {
276
- name: "lookup",
296
+ name: LOOKUP_TOOL_NAME,
277
297
  label: "Lookup",
278
298
  description: [
279
299
  "Ask the configured vision-capable lookup model to inspect recent image/screenshot context and answer a focused visual question.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ui-extend",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -419,12 +419,6 @@
419
419
  "description": "Router request retries.",
420
420
  "minimum": 0
421
421
  },
422
- "temperature": {
423
- "type": "number",
424
- "description": "Router sampling temperature.",
425
- "minimum": 0,
426
- "maximum": 2
427
- },
428
422
  "timeoutMs": {
429
423
  "type": "number",
430
424
  "description": "Router request timeout in ms.",