@averyyy/pi-coding-agent 0.80.3-piclient.6 → 0.80.3-piclient.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.
@@ -67,6 +67,9 @@ const INTRA_TURN_COMPACTION_MARKER_TEXT = "Continue from the compaction summary
67
67
  const VALIDATION_HINT_MESSAGE_TYPE = "pi:validation-hint";
68
68
  const VALIDATION_HINT_FILE_LIMIT = 12;
69
69
  const VALIDATION_HINT_FAILURE_CHAR_LIMIT = 1200;
70
+ const AUTO_SESSION_NAME_INPUT_CHAR_LIMIT = 4000;
71
+ const AUTO_SESSION_NAME_MAX_CHARS = 80;
72
+ const AUTO_SESSION_NAME_PROMPT = "Name this coding session from the transcript below. Return only a concise title, in the user's language when clear. No quotes, no markdown, no explanation. Do not call tools.";
70
73
  function isPiServerMode() {
71
74
  return process.env.PI_SERVER_MODE === "true";
72
75
  }
@@ -79,6 +82,16 @@ function getStringField(input, key) {
79
82
  const value = input[key];
80
83
  return typeof value === "string" && value.trim().length > 0 ? value : undefined;
81
84
  }
85
+ function getPiServerFailurePhase(message) {
86
+ for (const diagnostic of message.diagnostics ?? []) {
87
+ if (diagnostic.type !== "pi_server_failure")
88
+ continue;
89
+ const phase = diagnostic.details?.phase;
90
+ if (typeof phase === "string")
91
+ return phase;
92
+ }
93
+ return undefined;
94
+ }
82
95
  function toolTextContent(content) {
83
96
  return content
84
97
  .filter((part) => part.type === "text")
@@ -92,6 +105,41 @@ function compactValidationSummary(text) {
92
105
  return trimmed;
93
106
  return `...${trimmed.slice(trimmed.length - VALIDATION_HINT_FAILURE_CHAR_LIMIT + 3)}`;
94
107
  }
108
+ function messageTextContent(message) {
109
+ if (!("content" in message))
110
+ return "";
111
+ const content = message.content;
112
+ if (typeof content === "string")
113
+ return content;
114
+ if (!Array.isArray(content))
115
+ return "";
116
+ return content
117
+ .filter((part) => part.type === "text")
118
+ .map((part) => part.text)
119
+ .join("\n")
120
+ .trim();
121
+ }
122
+ function truncateForSessionNameInput(text) {
123
+ if (text.length <= AUTO_SESSION_NAME_INPUT_CHAR_LIMIT)
124
+ return text;
125
+ return text.slice(0, AUTO_SESSION_NAME_INPUT_CHAR_LIMIT).trim();
126
+ }
127
+ function sanitizeGeneratedSessionName(text) {
128
+ const firstLine = text
129
+ .split(/\r?\n/u)
130
+ .map((line) => line.trim())
131
+ .find((line) => line.length > 0);
132
+ if (!firstLine)
133
+ return undefined;
134
+ const title = firstLine
135
+ .replace(/^title:\s*/iu, "")
136
+ .replace(/^["'`]+|["'`]+$/g, "")
137
+ .replace(/\s+/g, " ")
138
+ .trim();
139
+ if (!title)
140
+ return undefined;
141
+ return title.length > AUTO_SESSION_NAME_MAX_CHARS ? title.slice(0, AUTO_SESSION_NAME_MAX_CHARS).trim() : title;
142
+ }
95
143
  // ============================================================================
96
144
  // AgentSession Class
97
145
  // ============================================================================
@@ -144,6 +192,7 @@ export class AgentSession {
144
192
  _extensionShutdownHandler;
145
193
  _extensionErrorListener;
146
194
  _extensionErrorUnsubscriber;
195
+ _autoSessionNameAttempted = false;
147
196
  // Model registry for API key resolution
148
197
  _modelRegistry;
149
198
  // Tool registry for extension getTools/setTools
@@ -883,12 +932,79 @@ export class AgentSession {
883
932
  throw error;
884
933
  }
885
934
  }
935
+ await this._maybeGenerateSessionName();
886
936
  }
887
937
  finally {
888
938
  this._systemPromptOverride = undefined;
889
939
  this._flushPendingBashMessages();
890
940
  }
891
941
  }
942
+ _sessionHasInfoEntry() {
943
+ return this.sessionManager.getEntries().some((entry) => entry.type === "session_info");
944
+ }
945
+ _buildSessionNameTranscript() {
946
+ const lines = [];
947
+ for (const message of this.agent.state.messages) {
948
+ if (message.role !== "user" && message.role !== "assistant")
949
+ continue;
950
+ const text = messageTextContent(message);
951
+ if (!text)
952
+ continue;
953
+ lines.push(`${message.role === "user" ? "User" : "Assistant"}: ${text}`);
954
+ }
955
+ const transcript = lines.join("\n\n").trim();
956
+ return transcript ? truncateForSessionNameInput(transcript) : undefined;
957
+ }
958
+ _shouldGenerateSessionName() {
959
+ if (this._autoSessionNameAttempted || this._sessionHasInfoEntry())
960
+ return false;
961
+ const userMessages = this.agent.state.messages.filter((message) => message.role === "user");
962
+ if (userMessages.length !== 1)
963
+ return false;
964
+ const lastAssistant = this._findLastAssistantMessage();
965
+ return lastAssistant !== undefined && !this._isTerminalAssistantFailure(lastAssistant);
966
+ }
967
+ async _maybeGenerateSessionName() {
968
+ if (!this._shouldGenerateSessionName())
969
+ return;
970
+ this._autoSessionNameAttempted = true;
971
+ const model = this.model;
972
+ const transcript = this._buildSessionNameTranscript();
973
+ if (!model || !transcript)
974
+ return;
975
+ try {
976
+ const stream = await this.agent.streamFn(model, {
977
+ systemPrompt: this.systemPrompt,
978
+ messages: [
979
+ {
980
+ role: "user",
981
+ content: [{ type: "text", text: `${AUTO_SESSION_NAME_PROMPT}\n\n${transcript}` }],
982
+ timestamp: Date.now(),
983
+ },
984
+ ],
985
+ tools: this.agent.state.tools,
986
+ }, {
987
+ maxTokens: 32,
988
+ reasoning: undefined,
989
+ sessionId: this.sessionId,
990
+ transport: this.agent.transport,
991
+ thinkingBudgets: this.agent.thinkingBudgets,
992
+ maxRetryDelayMs: this.agent.maxRetryDelayMs,
993
+ });
994
+ for await (const _event of stream) {
995
+ // Drain the title stream without surfacing it as a chat message.
996
+ }
997
+ const response = await stream.result();
998
+ if (response.stopReason === "error" || response.stopReason === "aborted")
999
+ return;
1000
+ const title = sanitizeGeneratedSessionName(messageTextContent(response));
1001
+ if (title)
1002
+ this.setSessionName(title);
1003
+ }
1004
+ catch {
1005
+ // Session naming is a post-turn convenience; the completed assistant turn stays authoritative.
1006
+ }
1007
+ }
892
1008
  async _handlePostAgentRun() {
893
1009
  const msg = this._lastAssistantMessage;
894
1010
  this._lastAssistantMessage = undefined;
@@ -2345,6 +2461,9 @@ export class AgentSession {
2345
2461
  return false;
2346
2462
  if (this._isPostAssistantPiServerSyncFailure(message))
2347
2463
  return false;
2464
+ const piServerPhase = getPiServerFailurePhase(message);
2465
+ if (piServerPhase && piServerPhase !== "provider_stream")
2466
+ return false;
2348
2467
  if (message.errorMessage && this._isNonRetryableProviderLimitError(message.errorMessage))
2349
2468
  return false;
2350
2469
  return isRetryableAssistantError(message);