@oh-my-pi/pi-coding-agent 3.5.1337 → 3.8.1337

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.
@@ -503,19 +503,19 @@ export class ToolExecutionComponent extends Container {
503
503
  }
504
504
 
505
505
  // Show LSP diagnostics if available
506
- if (this.result?.details?.diagnostics?.available) {
506
+ if (this.result?.details?.diagnostics) {
507
507
  const diag = this.result.details.diagnostics;
508
- if (diag.diagnostics.length > 0) {
509
- const icon = diag.hasErrors ? theme.fg("error", "●") : theme.fg("warning", "●");
508
+ if (diag.messages.length > 0) {
509
+ const icon = diag.errored ? theme.fg("error", "●") : theme.fg("warning", "●");
510
510
  text += `\n\n${icon} ${theme.fg("toolTitle", "LSP Diagnostics")} ${theme.fg("dim", `(${diag.summary})`)}`;
511
- const maxDiags = this.expanded ? diag.diagnostics.length : 5;
512
- const displayDiags = diag.diagnostics.slice(0, maxDiags);
511
+ const maxDiags = this.expanded ? diag.messages.length : 5;
512
+ const displayDiags = diag.messages.slice(0, maxDiags);
513
513
  for (const d of displayDiags) {
514
514
  const color = d.includes("[error]") ? "error" : d.includes("[warning]") ? "warning" : "dim";
515
515
  text += `\n ${theme.fg(color, d)}`;
516
516
  }
517
- if (diag.diagnostics.length > maxDiags) {
518
- text += theme.fg("dim", `\n ... (${diag.diagnostics.length - maxDiags} more)`);
517
+ if (diag.messages.length > maxDiags) {
518
+ text += theme.fg("dim", `\n ... (${diag.messages.length - maxDiags} more)`);
519
519
  }
520
520
  }
521
521
  }
@@ -552,19 +552,19 @@ export class ToolExecutionComponent extends Container {
552
552
  }
553
553
 
554
554
  // Show LSP diagnostics if available
555
- if (this.result?.details?.diagnostics?.available) {
555
+ if (this.result?.details?.diagnostics) {
556
556
  const diag = this.result.details.diagnostics;
557
- if (diag.diagnostics.length > 0) {
558
- const icon = diag.hasErrors ? theme.fg("error", "●") : theme.fg("warning", "●");
557
+ if (diag.messages.length > 0) {
558
+ const icon = diag.errored ? theme.fg("error", "●") : theme.fg("warning", "●");
559
559
  text += `\n\n${icon} ${theme.fg("toolTitle", "LSP Diagnostics")} ${theme.fg("dim", `(${diag.summary})`)}`;
560
- const maxDiags = this.expanded ? diag.diagnostics.length : 5;
561
- const displayDiags = diag.diagnostics.slice(0, maxDiags);
560
+ const maxDiags = this.expanded ? diag.messages.length : 5;
561
+ const displayDiags = diag.messages.slice(0, maxDiags);
562
562
  for (const d of displayDiags) {
563
563
  const color = d.includes("[error]") ? "error" : d.includes("[warning]") ? "warning" : "dim";
564
564
  text += `\n ${theme.fg(color, d)}`;
565
565
  }
566
- if (diag.diagnostics.length > maxDiags) {
567
- text += theme.fg("dim", `\n ... (${diag.diagnostics.length - maxDiags} more)`);
566
+ if (diag.messages.length > maxDiags) {
567
+ text += theme.fg("dim", `\n ... (${diag.messages.length - maxDiags} more)`);
568
568
  }
569
569
  }
570
570
  }
@@ -143,9 +143,6 @@ export class InteractiveMode {
143
143
  // Custom tools for custom rendering
144
144
  private customTools: Map<string, LoadedCustomTool>;
145
145
 
146
- // Title generation state
147
- private titleGenerationAttempted = false;
148
-
149
146
  // Convenience accessors
150
147
  private get agent() {
151
148
  return this.session.agent;
@@ -261,7 +258,6 @@ export class InteractiveMode {
261
258
  const existingTitle = this.sessionManager.getSessionTitle();
262
259
  if (existingTitle) {
263
260
  setTerminalTitle(`pi: ${existingTitle}`);
264
- this.titleGenerationAttempted = true; // Don't try to generate again
265
261
  }
266
262
 
267
263
  // Setup UI layout
@@ -401,7 +397,6 @@ export class InteractiveMode {
401
397
  this.streamingComponent = undefined;
402
398
  this.streamingMessage = undefined;
403
399
  this.pendingTools.clear();
404
- this.titleGenerationAttempted = false;
405
400
 
406
401
  this.chatContainer.addChild(new Spacer(1));
407
402
  this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ New session started")}`, 1, 1));
@@ -903,6 +898,21 @@ export class InteractiveMode {
903
898
  // First, move any pending bash components to chat
904
899
  this.flushPendingBashComponents();
905
900
 
901
+ // Generate session title on first message
902
+ const hasUserMessages = this.agent.state.messages.some((m) => m.role === "user");
903
+ if (!hasUserMessages && !this.sessionManager.getSessionTitle()) {
904
+ const registry = this.session.modelRegistry;
905
+ const smolModel = this.settingsManager.getModelRole("smol");
906
+ generateSessionTitle(text, registry, smolModel)
907
+ .then((title) => {
908
+ if (title) {
909
+ this.sessionManager.setSessionTitle(title);
910
+ setTerminalTitle(`omp: ${title}`);
911
+ }
912
+ })
913
+ .catch(() => {});
914
+ }
915
+
906
916
  if (this.onInputCallback) {
907
917
  // Include any pending images from clipboard paste
908
918
  const images = this.pendingImages.length > 0 ? [...this.pendingImages] : undefined;
@@ -1085,12 +1095,6 @@ export class InteractiveMode {
1085
1095
  }
1086
1096
  this.pendingTools.clear();
1087
1097
  this.ui.requestRender();
1088
-
1089
- // Generate session title after first turn (if not already titled)
1090
- if (!this.titleGenerationAttempted && !this.sessionManager.getSessionTitle()) {
1091
- this.titleGenerationAttempted = true;
1092
- this.maybeGenerateTitle();
1093
- }
1094
1098
  break;
1095
1099
 
1096
1100
  case "auto_compaction_start": {
@@ -1635,42 +1639,6 @@ export class InteractiveMode {
1635
1639
  this.ui.requestRender();
1636
1640
  }
1637
1641
 
1638
- /**
1639
- * Generate a title for the session based on the first user message.
1640
- * Runs in background, doesn't block UI.
1641
- */
1642
- private maybeGenerateTitle(): void {
1643
- // Find the first user message
1644
- const messages = this.agent.state.messages;
1645
- const firstUserMessage = messages.find((m) => m.role === "user");
1646
- if (!firstUserMessage) return;
1647
-
1648
- // Extract text content
1649
- let messageText = "";
1650
- for (const content of firstUserMessage.content) {
1651
- if (typeof content === "string") {
1652
- messageText += content;
1653
- } else if (content.type === "text") {
1654
- messageText += content.text;
1655
- }
1656
- }
1657
- if (!messageText.trim()) return;
1658
-
1659
- // Generate title in background
1660
- const registry = this.session.modelRegistry;
1661
- const smolModel = this.settingsManager.getModelRole("smol");
1662
- generateSessionTitle(messageText, registry, smolModel)
1663
- .then((title) => {
1664
- if (title) {
1665
- this.sessionManager.setSessionTitle(title);
1666
- setTerminalTitle(`omp: ${title}`);
1667
- }
1668
- })
1669
- .catch(() => {
1670
- // Errors logged via logger in title-generator
1671
- });
1672
- }
1673
-
1674
1642
  private updatePendingMessagesDisplay(): void {
1675
1643
  this.pendingMessagesContainer.clear();
1676
1644
  const queuedMessages = this.session.getQueuedMessages();
@@ -2076,13 +2044,7 @@ export class InteractiveMode {
2076
2044
  }
2077
2045
  this.ui.requestRender();
2078
2046
 
2079
- const openCmd =
2080
- process.platform === "darwin"
2081
- ? "open"
2082
- : process.platform === "win32"
2083
- ? "start"
2084
- : "xdg-open";
2085
- Bun.spawn([openCmd, info.url], { stdin: "ignore", stdout: "ignore", stderr: "ignore" });
2047
+ this.openInBrowser(info.url);
2086
2048
  },
2087
2049
  onPrompt: async (prompt: { message: string; placeholder?: string }) => {
2088
2050
  this.chatContainer.addChild(new Spacer(1));
@@ -2156,6 +2118,11 @@ export class InteractiveMode {
2156
2118
  // Command handlers
2157
2119
  // =========================================================================
2158
2120
 
2121
+ private openInBrowser(urlOrPath: string): void {
2122
+ const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
2123
+ Bun.spawn([openCmd, urlOrPath], { stdin: "ignore", stdout: "ignore", stderr: "ignore" });
2124
+ }
2125
+
2159
2126
  private async handleExportCommand(text: string): Promise<void> {
2160
2127
  const parts = text.split(/\s+/);
2161
2128
  const arg = parts.length > 1 ? parts[1] : undefined;
@@ -2180,6 +2147,7 @@ export class InteractiveMode {
2180
2147
  try {
2181
2148
  const filePath = await this.session.exportToHtml(arg);
2182
2149
  this.showStatus(`Session exported to: ${filePath}`);
2150
+ this.openInBrowser(filePath);
2183
2151
  } catch (error: unknown) {
2184
2152
  this.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
2185
2153
  }
@@ -2293,6 +2261,7 @@ export class InteractiveMode {
2293
2261
  // Create the preview URL
2294
2262
  const previewUrl = `https://gistpreview.github.io/?${gistId}`;
2295
2263
  this.showStatus(`Share URL: ${previewUrl}\nGist: ${gistUrl}`);
2264
+ this.openInBrowser(previewUrl);
2296
2265
  } catch (error: unknown) {
2297
2266
  if (!loader.signal.aborted) {
2298
2267
  restoreEditor();
@@ -11,6 +11,7 @@
11
11
  * - Hook UI: Hook UI requests are emitted, client responds with hook_ui_response
12
12
  */
13
13
 
14
+ import { nanoid } from "nanoid";
14
15
  import type { AgentSession } from "../../core/agent-session";
15
16
  import type { HookUIContext } from "../../core/hooks/index";
16
17
  import { logger } from "../../core/logger";
@@ -52,7 +53,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
52
53
  */
53
54
  const createHookUIContext = (): HookUIContext => ({
54
55
  async select(title: string, options: string[]): Promise<string | undefined> {
55
- const id = crypto.randomUUID();
56
+ const id = nanoid();
56
57
  return new Promise((resolve, reject) => {
57
58
  pendingHookRequests.set(id, {
58
59
  resolve: (response: RpcHookUIResponse) => {
@@ -71,7 +72,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
71
72
  },
72
73
 
73
74
  async confirm(title: string, message: string): Promise<boolean> {
74
- const id = crypto.randomUUID();
75
+ const id = nanoid();
75
76
  return new Promise((resolve, reject) => {
76
77
  pendingHookRequests.set(id, {
77
78
  resolve: (response: RpcHookUIResponse) => {
@@ -90,7 +91,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
90
91
  },
91
92
 
92
93
  async input(title: string, placeholder?: string): Promise<string | undefined> {
93
- const id = crypto.randomUUID();
94
+ const id = nanoid();
94
95
  return new Promise((resolve, reject) => {
95
96
  pendingHookRequests.set(id, {
96
97
  resolve: (response: RpcHookUIResponse) => {
@@ -112,7 +113,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
112
113
  // Fire and forget - no response needed
113
114
  output({
114
115
  type: "hook_ui_request",
115
- id: crypto.randomUUID(),
116
+ id: nanoid(),
116
117
  method: "notify",
117
118
  message,
118
119
  notifyType: type,
@@ -123,7 +124,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
123
124
  // Fire and forget - no response needed
124
125
  output({
125
126
  type: "hook_ui_request",
126
- id: crypto.randomUUID(),
127
+ id: nanoid(),
127
128
  method: "setStatus",
128
129
  statusKey: key,
129
130
  statusText: text,
@@ -139,7 +140,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
139
140
  // Fire and forget - host can implement editor control
140
141
  output({
141
142
  type: "hook_ui_request",
142
- id: crypto.randomUUID(),
143
+ id: nanoid(),
143
144
  method: "set_editor_text",
144
145
  text,
145
146
  } as RpcHookUIRequest);
@@ -152,7 +153,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
152
153
  },
153
154
 
154
155
  async editor(title: string, prefill?: string): Promise<string | undefined> {
155
- const id = crypto.randomUUID();
156
+ const id = nanoid();
156
157
  return new Promise((resolve, reject) => {
157
158
  pendingHookRequests.set(id, {
158
159
  resolve: (response: RpcHookUIResponse) => {