@mariozechner/pi-coding-agent 0.6.2 → 0.7.1

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/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  A radically simple and opinionated coding agent with multi-model support (including mid-session switching), a simple yet powerful CLI for headless coding tasks, and many creature comforts you might be used to from other coding agents.
4
4
 
5
+ Works on Linux, macOS, and Windows (barely tested, needs Git Bash running in the "modern" Windows Terminal).
6
+
5
7
  ## Installation
6
8
 
7
9
  ```bash
@@ -320,7 +322,7 @@ Write content to a file. Creates the file if it doesn't exist, overwrites if it
320
322
  Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits. Returns an error if the text appears multiple times or isn't found.
321
323
 
322
324
  **bash**
323
- Execute a bash command in the current working directory. Returns stdout and stderr. Commands run with a 30 second timeout.
325
+ Execute a bash command in the current working directory. Returns stdout and stderr. Optionally accepts a `timeout` parameter (in seconds) - no default timeout.
324
326
 
325
327
  ### MCP & Adding Your Own Tools
326
328
 
@@ -1 +1 @@
1
- {"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAczD,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,SAAS,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,GAAG,CAAC;CACb;AAED,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,uBAAuB,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,cAAc;IAC1B,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,WAAW,CAAU;IAC7B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,kBAAkB,CAAkB;IAC5C,OAAO,CAAC,eAAe,CAAa;IAEpC,YAAY,eAAe,GAAE,OAAe,EAAE,iBAAiB,CAAC,EAAE,MAAM,EAsBvE;IAED,qDAAqD;IACrD,OAAO,SAEN;IAED,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,+BAA+B;IAiBvC,OAAO,CAAC,aAAa;IAkBrB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAmBpC;IAED,WAAW,CAAC,OAAO,EAAE,GAAG,GAAG,IAAI,CAa9B;IAED,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAanD;IAED,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAanC;IAED,YAAY,IAAI,GAAG,EAAE,CAkBpB;IAED,iBAAiB,IAAI,MAAM,CAqB1B;IAED,SAAS,IAAI,MAAM,GAAG,IAAI,CAqBzB;IAED,YAAY,IAAI,MAAM,CAErB;IAED,cAAc,IAAI,MAAM,CAEvB;IAED;;OAEG;IACH,eAAe,IAAI,KAAK,CAAC;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,IAAI,CAAC;QACd,QAAQ,EAAE,IAAI,CAAC;QACf,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,MAAM,CAAC;KACxB,CAAC,CAsFD;IAED;;OAEG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAKjC;IAED;;;OAGG;IACH,uBAAuB,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,OAAO,CAOhD;CACD","sourcesContent":["import type { AgentState } from \"@mariozechner/pi-agent\";\nimport { randomBytes } from \"crypto\";\nimport { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join, resolve } from \"path\";\n\nfunction uuidv4(): string {\n\tconst bytes = randomBytes(16);\n\tbytes[6] = (bytes[6] & 0x0f) | 0x40;\n\tbytes[8] = (bytes[8] & 0x3f) | 0x80;\n\tconst hex = bytes.toString(\"hex\");\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;\n}\n\nexport interface SessionHeader {\n\ttype: \"session\";\n\tid: string;\n\ttimestamp: string;\n\tcwd: string;\n\tmodel: string;\n\tthinkingLevel: string;\n}\n\nexport interface SessionMessageEntry {\n\ttype: \"message\";\n\ttimestamp: string;\n\tmessage: any; // AppMessage from agent state\n}\n\nexport interface ThinkingLevelChangeEntry {\n\ttype: \"thinking_level_change\";\n\ttimestamp: string;\n\tthinkingLevel: string;\n}\n\nexport interface ModelChangeEntry {\n\ttype: \"model_change\";\n\ttimestamp: string;\n\tmodel: string;\n}\n\nexport class SessionManager {\n\tprivate sessionId!: string;\n\tprivate sessionFile!: string;\n\tprivate sessionDir: string;\n\tprivate enabled: boolean = true;\n\tprivate sessionInitialized: boolean = false;\n\tprivate pendingMessages: any[] = [];\n\n\tconstructor(continueSession: boolean = false, customSessionPath?: string) {\n\t\tthis.sessionDir = this.getSessionDirectory();\n\n\t\tif (customSessionPath) {\n\t\t\t// Use custom session file path\n\t\t\tthis.sessionFile = resolve(customSessionPath);\n\t\t\tthis.loadSessionId();\n\t\t\t// Mark as initialized since we're loading an existing session\n\t\t\tthis.sessionInitialized = existsSync(this.sessionFile);\n\t\t} else if (continueSession) {\n\t\t\tconst mostRecent = this.findMostRecentlyModifiedSession();\n\t\t\tif (mostRecent) {\n\t\t\t\tthis.sessionFile = mostRecent;\n\t\t\t\tthis.loadSessionId();\n\t\t\t\t// Mark as initialized since we're loading an existing session\n\t\t\t\tthis.sessionInitialized = true;\n\t\t\t} else {\n\t\t\t\tthis.initNewSession();\n\t\t\t}\n\t\t} else {\n\t\t\tthis.initNewSession();\n\t\t}\n\t}\n\n\t/** Disable session saving (for --no-session mode) */\n\tdisable() {\n\t\tthis.enabled = false;\n\t}\n\n\tprivate getSessionDirectory(): string {\n\t\tconst cwd = process.cwd();\n\t\tconst safePath = \"--\" + cwd.replace(/^\\//, \"\").replace(/\\//g, \"-\") + \"--\";\n\n\t\tconst configDir = resolve(process.env.CODING_AGENT_DIR || join(homedir(), \".pi/agent/\"));\n\t\tconst sessionDir = join(configDir, \"sessions\", safePath);\n\t\tif (!existsSync(sessionDir)) {\n\t\t\tmkdirSync(sessionDir, { recursive: true });\n\t\t}\n\t\treturn sessionDir;\n\t}\n\n\tprivate initNewSession(): void {\n\t\tthis.sessionId = uuidv4();\n\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\tthis.sessionFile = join(this.sessionDir, `${timestamp}_${this.sessionId}.jsonl`);\n\t}\n\n\tprivate findMostRecentlyModifiedSession(): string | null {\n\t\ttry {\n\t\t\tconst files = readdirSync(this.sessionDir)\n\t\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t\t.map((f) => ({\n\t\t\t\t\tname: f,\n\t\t\t\t\tpath: join(this.sessionDir, f),\n\t\t\t\t\tmtime: statSync(join(this.sessionDir, f)).mtime,\n\t\t\t\t}))\n\t\t\t\t.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());\n\n\t\t\treturn files[0]?.path || null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate loadSessionId(): void {\n\t\tif (!existsSync(this.sessionFile)) return;\n\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"session\") {\n\t\t\t\t\tthis.sessionId = entry.id;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\t\tthis.sessionId = uuidv4();\n\t}\n\n\tstartSession(state: AgentState): void {\n\t\tif (!this.enabled || this.sessionInitialized) return;\n\t\tthis.sessionInitialized = true;\n\n\t\tconst entry: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tid: this.sessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: process.cwd(),\n\t\t\tmodel: `${state.model.provider}/${state.model.id}`,\n\t\t\tthinkingLevel: state.thinkingLevel,\n\t\t};\n\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\n\t\t// Write any queued messages\n\t\tfor (const msg of this.pendingMessages) {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(msg) + \"\\n\");\n\t\t}\n\t\tthis.pendingMessages = [];\n\t}\n\n\tsaveMessage(message: any): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: SessionMessageEntry = {\n\t\t\ttype: \"message\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmessage,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveThinkingLevelChange(thinkingLevel: string): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: ThinkingLevelChangeEntry = {\n\t\t\ttype: \"thinking_level_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tthinkingLevel,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveModelChange(model: string): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: ModelChangeEntry = {\n\t\t\ttype: \"model_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmodel,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tloadMessages(): any[] {\n\t\tif (!existsSync(this.sessionFile)) return [];\n\n\t\tconst messages: any[] = [];\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"message\") {\n\t\t\t\t\tmessages.push(entry.message);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn messages;\n\t}\n\n\tloadThinkingLevel(): string {\n\t\tif (!existsSync(this.sessionFile)) return \"off\";\n\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\n\t\t// Find the most recent thinking level (from session header or change event)\n\t\tlet lastThinkingLevel = \"off\";\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"session\" && entry.thinkingLevel) {\n\t\t\t\t\tlastThinkingLevel = entry.thinkingLevel;\n\t\t\t\t} else if (entry.type === \"thinking_level_change\" && entry.thinkingLevel) {\n\t\t\t\t\tlastThinkingLevel = entry.thinkingLevel;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn lastThinkingLevel;\n\t}\n\n\tloadModel(): string | null {\n\t\tif (!existsSync(this.sessionFile)) return null;\n\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\n\t\t// Find the most recent model (from session header or change event)\n\t\tlet lastModel: string | null = null;\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"session\" && entry.model) {\n\t\t\t\t\tlastModel = entry.model;\n\t\t\t\t} else if (entry.type === \"model_change\" && entry.model) {\n\t\t\t\t\tlastModel = entry.model;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn lastModel;\n\t}\n\n\tgetSessionId(): string {\n\t\treturn this.sessionId;\n\t}\n\n\tgetSessionFile(): string {\n\t\treturn this.sessionFile;\n\t}\n\n\t/**\n\t * Load all sessions for the current directory with metadata\n\t */\n\tloadAllSessions(): Array<{\n\t\tpath: string;\n\t\tid: string;\n\t\tcreated: Date;\n\t\tmodified: Date;\n\t\tmessageCount: number;\n\t\tfirstMessage: string;\n\t\tallMessagesText: string;\n\t}> {\n\t\tconst sessions: Array<{\n\t\t\tpath: string;\n\t\t\tid: string;\n\t\t\tcreated: Date;\n\t\t\tmodified: Date;\n\t\t\tmessageCount: number;\n\t\t\tfirstMessage: string;\n\t\t\tallMessagesText: string;\n\t\t}> = [];\n\n\t\ttry {\n\t\t\tconst files = readdirSync(this.sessionDir)\n\t\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t\t.map((f) => join(this.sessionDir, f));\n\n\t\t\tfor (const file of files) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(file);\n\t\t\t\t\tconst content = readFileSync(file, \"utf8\");\n\t\t\t\t\tconst lines = content.trim().split(\"\\n\");\n\n\t\t\t\t\tlet sessionId = \"\";\n\t\t\t\t\tlet created = stats.birthtime;\n\t\t\t\t\tlet messageCount = 0;\n\t\t\t\t\tlet firstMessage = \"\";\n\t\t\t\t\tconst allMessages: string[] = [];\n\n\t\t\t\t\tfor (const line of lines) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst entry = JSON.parse(line);\n\n\t\t\t\t\t\t\t// Extract session ID from first session entry\n\t\t\t\t\t\t\tif (entry.type === \"session\" && !sessionId) {\n\t\t\t\t\t\t\t\tsessionId = entry.id;\n\t\t\t\t\t\t\t\tcreated = new Date(entry.timestamp);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Count messages and collect all text\n\t\t\t\t\t\t\tif (entry.type === \"message\") {\n\t\t\t\t\t\t\t\tmessageCount++;\n\n\t\t\t\t\t\t\t\t// Extract text from user and assistant messages\n\t\t\t\t\t\t\t\tif (entry.message.role === \"user\" || entry.message.role === \"assistant\") {\n\t\t\t\t\t\t\t\t\tconst textContent = entry.message.content\n\t\t\t\t\t\t\t\t\t\t.filter((c: any) => c.type === \"text\")\n\t\t\t\t\t\t\t\t\t\t.map((c: any) => c.text)\n\t\t\t\t\t\t\t\t\t\t.join(\" \");\n\n\t\t\t\t\t\t\t\t\tif (textContent) {\n\t\t\t\t\t\t\t\t\t\tallMessages.push(textContent);\n\n\t\t\t\t\t\t\t\t\t\t// Get first user message for display\n\t\t\t\t\t\t\t\t\t\tif (!firstMessage && entry.message.role === \"user\") {\n\t\t\t\t\t\t\t\t\t\t\tfirstMessage = textContent;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// Skip malformed lines\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tsessions.push({\n\t\t\t\t\t\tpath: file,\n\t\t\t\t\t\tid: sessionId || \"unknown\",\n\t\t\t\t\t\tcreated,\n\t\t\t\t\t\tmodified: stats.mtime,\n\t\t\t\t\t\tmessageCount,\n\t\t\t\t\t\tfirstMessage: firstMessage || \"(no messages)\",\n\t\t\t\t\t\tallMessagesText: allMessages.join(\" \"),\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Skip files that can't be read\n\t\t\t\t\tconsole.error(`Failed to read session file ${file}:`, error);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Sort by modified date (most recent first)\n\t\t\tsessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to load sessions:\", error);\n\t\t}\n\n\t\treturn sessions;\n\t}\n\n\t/**\n\t * Set the session file to an existing session\n\t */\n\tsetSessionFile(path: string): void {\n\t\tthis.sessionFile = path;\n\t\tthis.loadSessionId();\n\t\t// Mark as initialized since we're loading an existing session\n\t\tthis.sessionInitialized = existsSync(path);\n\t}\n\n\t/**\n\t * Check if we should initialize the session based on message history.\n\t * Session is initialized when we have at least 1 user message and 1 assistant message.\n\t */\n\tshouldInitializeSession(messages: any[]): boolean {\n\t\tif (this.sessionInitialized) return false;\n\n\t\tconst userMessages = messages.filter((m) => m.role === \"user\");\n\t\tconst assistantMessages = messages.filter((m) => m.role === \"assistant\");\n\n\t\treturn userMessages.length >= 1 && assistantMessages.length >= 1;\n\t}\n}\n"]}
1
+ {"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAczD,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,SAAS,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,GAAG,CAAC;CACb;AAED,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,uBAAuB,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,cAAc;IAC1B,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,WAAW,CAAU;IAC7B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,kBAAkB,CAAkB;IAC5C,OAAO,CAAC,eAAe,CAAa;IAEpC,YAAY,eAAe,GAAE,OAAe,EAAE,iBAAiB,CAAC,EAAE,MAAM,EAsBvE;IAED,qDAAqD;IACrD,OAAO,SAEN;IAED,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,+BAA+B;IAiBvC,OAAO,CAAC,aAAa;IAkBrB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAmBpC;IAED,WAAW,CAAC,OAAO,EAAE,GAAG,GAAG,IAAI,CAa9B;IAED,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAanD;IAED,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAanC;IAED,YAAY,IAAI,GAAG,EAAE,CAkBpB;IAED,iBAAiB,IAAI,MAAM,CAqB1B;IAED,SAAS,IAAI,MAAM,GAAG,IAAI,CAqBzB;IAED,YAAY,IAAI,MAAM,CAErB;IAED,cAAc,IAAI,MAAM,CAEvB;IAED;;OAEG;IACH,eAAe,IAAI,KAAK,CAAC;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,IAAI,CAAC;QACd,QAAQ,EAAE,IAAI,CAAC;QACf,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,MAAM,CAAC;KACxB,CAAC,CAsFD;IAED;;OAEG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAKjC;IAED;;;OAGG;IACH,uBAAuB,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,OAAO,CAOhD;CACD","sourcesContent":["import type { AgentState } from \"@mariozechner/pi-agent\";\nimport { randomBytes } from \"crypto\";\nimport { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join, resolve } from \"path\";\n\nfunction uuidv4(): string {\n\tconst bytes = randomBytes(16);\n\tbytes[6] = (bytes[6] & 0x0f) | 0x40;\n\tbytes[8] = (bytes[8] & 0x3f) | 0x80;\n\tconst hex = bytes.toString(\"hex\");\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;\n}\n\nexport interface SessionHeader {\n\ttype: \"session\";\n\tid: string;\n\ttimestamp: string;\n\tcwd: string;\n\tmodel: string;\n\tthinkingLevel: string;\n}\n\nexport interface SessionMessageEntry {\n\ttype: \"message\";\n\ttimestamp: string;\n\tmessage: any; // AppMessage from agent state\n}\n\nexport interface ThinkingLevelChangeEntry {\n\ttype: \"thinking_level_change\";\n\ttimestamp: string;\n\tthinkingLevel: string;\n}\n\nexport interface ModelChangeEntry {\n\ttype: \"model_change\";\n\ttimestamp: string;\n\tmodel: string;\n}\n\nexport class SessionManager {\n\tprivate sessionId!: string;\n\tprivate sessionFile!: string;\n\tprivate sessionDir: string;\n\tprivate enabled: boolean = true;\n\tprivate sessionInitialized: boolean = false;\n\tprivate pendingMessages: any[] = [];\n\n\tconstructor(continueSession: boolean = false, customSessionPath?: string) {\n\t\tthis.sessionDir = this.getSessionDirectory();\n\n\t\tif (customSessionPath) {\n\t\t\t// Use custom session file path\n\t\t\tthis.sessionFile = resolve(customSessionPath);\n\t\t\tthis.loadSessionId();\n\t\t\t// Mark as initialized since we're loading an existing session\n\t\t\tthis.sessionInitialized = existsSync(this.sessionFile);\n\t\t} else if (continueSession) {\n\t\t\tconst mostRecent = this.findMostRecentlyModifiedSession();\n\t\t\tif (mostRecent) {\n\t\t\t\tthis.sessionFile = mostRecent;\n\t\t\t\tthis.loadSessionId();\n\t\t\t\t// Mark as initialized since we're loading an existing session\n\t\t\t\tthis.sessionInitialized = true;\n\t\t\t} else {\n\t\t\t\tthis.initNewSession();\n\t\t\t}\n\t\t} else {\n\t\t\tthis.initNewSession();\n\t\t}\n\t}\n\n\t/** Disable session saving (for --no-session mode) */\n\tdisable() {\n\t\tthis.enabled = false;\n\t}\n\n\tprivate getSessionDirectory(): string {\n\t\tconst cwd = process.cwd();\n\t\t// Replace all path separators and colons (for Windows drive letters) with dashes\n\t\tconst safePath = \"--\" + cwd.replace(/^[/\\\\]/, \"\").replace(/[/\\\\:]/g, \"-\") + \"--\";\n\n\t\tconst configDir = resolve(process.env.CODING_AGENT_DIR || join(homedir(), \".pi/agent/\"));\n\t\tconst sessionDir = join(configDir, \"sessions\", safePath);\n\t\tif (!existsSync(sessionDir)) {\n\t\t\tmkdirSync(sessionDir, { recursive: true });\n\t\t}\n\t\treturn sessionDir;\n\t}\n\n\tprivate initNewSession(): void {\n\t\tthis.sessionId = uuidv4();\n\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\tthis.sessionFile = join(this.sessionDir, `${timestamp}_${this.sessionId}.jsonl`);\n\t}\n\n\tprivate findMostRecentlyModifiedSession(): string | null {\n\t\ttry {\n\t\t\tconst files = readdirSync(this.sessionDir)\n\t\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t\t.map((f) => ({\n\t\t\t\t\tname: f,\n\t\t\t\t\tpath: join(this.sessionDir, f),\n\t\t\t\t\tmtime: statSync(join(this.sessionDir, f)).mtime,\n\t\t\t\t}))\n\t\t\t\t.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());\n\n\t\t\treturn files[0]?.path || null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate loadSessionId(): void {\n\t\tif (!existsSync(this.sessionFile)) return;\n\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"session\") {\n\t\t\t\t\tthis.sessionId = entry.id;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\t\tthis.sessionId = uuidv4();\n\t}\n\n\tstartSession(state: AgentState): void {\n\t\tif (!this.enabled || this.sessionInitialized) return;\n\t\tthis.sessionInitialized = true;\n\n\t\tconst entry: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tid: this.sessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: process.cwd(),\n\t\t\tmodel: `${state.model.provider}/${state.model.id}`,\n\t\t\tthinkingLevel: state.thinkingLevel,\n\t\t};\n\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\n\t\t// Write any queued messages\n\t\tfor (const msg of this.pendingMessages) {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(msg) + \"\\n\");\n\t\t}\n\t\tthis.pendingMessages = [];\n\t}\n\n\tsaveMessage(message: any): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: SessionMessageEntry = {\n\t\t\ttype: \"message\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmessage,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveThinkingLevelChange(thinkingLevel: string): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: ThinkingLevelChangeEntry = {\n\t\t\ttype: \"thinking_level_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tthinkingLevel,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveModelChange(model: string): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: ModelChangeEntry = {\n\t\t\ttype: \"model_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmodel,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tloadMessages(): any[] {\n\t\tif (!existsSync(this.sessionFile)) return [];\n\n\t\tconst messages: any[] = [];\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"message\") {\n\t\t\t\t\tmessages.push(entry.message);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn messages;\n\t}\n\n\tloadThinkingLevel(): string {\n\t\tif (!existsSync(this.sessionFile)) return \"off\";\n\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\n\t\t// Find the most recent thinking level (from session header or change event)\n\t\tlet lastThinkingLevel = \"off\";\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"session\" && entry.thinkingLevel) {\n\t\t\t\t\tlastThinkingLevel = entry.thinkingLevel;\n\t\t\t\t} else if (entry.type === \"thinking_level_change\" && entry.thinkingLevel) {\n\t\t\t\t\tlastThinkingLevel = entry.thinkingLevel;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn lastThinkingLevel;\n\t}\n\n\tloadModel(): string | null {\n\t\tif (!existsSync(this.sessionFile)) return null;\n\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\n\t\t// Find the most recent model (from session header or change event)\n\t\tlet lastModel: string | null = null;\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"session\" && entry.model) {\n\t\t\t\t\tlastModel = entry.model;\n\t\t\t\t} else if (entry.type === \"model_change\" && entry.model) {\n\t\t\t\t\tlastModel = entry.model;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn lastModel;\n\t}\n\n\tgetSessionId(): string {\n\t\treturn this.sessionId;\n\t}\n\n\tgetSessionFile(): string {\n\t\treturn this.sessionFile;\n\t}\n\n\t/**\n\t * Load all sessions for the current directory with metadata\n\t */\n\tloadAllSessions(): Array<{\n\t\tpath: string;\n\t\tid: string;\n\t\tcreated: Date;\n\t\tmodified: Date;\n\t\tmessageCount: number;\n\t\tfirstMessage: string;\n\t\tallMessagesText: string;\n\t}> {\n\t\tconst sessions: Array<{\n\t\t\tpath: string;\n\t\t\tid: string;\n\t\t\tcreated: Date;\n\t\t\tmodified: Date;\n\t\t\tmessageCount: number;\n\t\t\tfirstMessage: string;\n\t\t\tallMessagesText: string;\n\t\t}> = [];\n\n\t\ttry {\n\t\t\tconst files = readdirSync(this.sessionDir)\n\t\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t\t.map((f) => join(this.sessionDir, f));\n\n\t\t\tfor (const file of files) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(file);\n\t\t\t\t\tconst content = readFileSync(file, \"utf8\");\n\t\t\t\t\tconst lines = content.trim().split(\"\\n\");\n\n\t\t\t\t\tlet sessionId = \"\";\n\t\t\t\t\tlet created = stats.birthtime;\n\t\t\t\t\tlet messageCount = 0;\n\t\t\t\t\tlet firstMessage = \"\";\n\t\t\t\t\tconst allMessages: string[] = [];\n\n\t\t\t\t\tfor (const line of lines) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst entry = JSON.parse(line);\n\n\t\t\t\t\t\t\t// Extract session ID from first session entry\n\t\t\t\t\t\t\tif (entry.type === \"session\" && !sessionId) {\n\t\t\t\t\t\t\t\tsessionId = entry.id;\n\t\t\t\t\t\t\t\tcreated = new Date(entry.timestamp);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Count messages and collect all text\n\t\t\t\t\t\t\tif (entry.type === \"message\") {\n\t\t\t\t\t\t\t\tmessageCount++;\n\n\t\t\t\t\t\t\t\t// Extract text from user and assistant messages\n\t\t\t\t\t\t\t\tif (entry.message.role === \"user\" || entry.message.role === \"assistant\") {\n\t\t\t\t\t\t\t\t\tconst textContent = entry.message.content\n\t\t\t\t\t\t\t\t\t\t.filter((c: any) => c.type === \"text\")\n\t\t\t\t\t\t\t\t\t\t.map((c: any) => c.text)\n\t\t\t\t\t\t\t\t\t\t.join(\" \");\n\n\t\t\t\t\t\t\t\t\tif (textContent) {\n\t\t\t\t\t\t\t\t\t\tallMessages.push(textContent);\n\n\t\t\t\t\t\t\t\t\t\t// Get first user message for display\n\t\t\t\t\t\t\t\t\t\tif (!firstMessage && entry.message.role === \"user\") {\n\t\t\t\t\t\t\t\t\t\t\tfirstMessage = textContent;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// Skip malformed lines\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tsessions.push({\n\t\t\t\t\t\tpath: file,\n\t\t\t\t\t\tid: sessionId || \"unknown\",\n\t\t\t\t\t\tcreated,\n\t\t\t\t\t\tmodified: stats.mtime,\n\t\t\t\t\t\tmessageCount,\n\t\t\t\t\t\tfirstMessage: firstMessage || \"(no messages)\",\n\t\t\t\t\t\tallMessagesText: allMessages.join(\" \"),\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Skip files that can't be read\n\t\t\t\t\tconsole.error(`Failed to read session file ${file}:`, error);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Sort by modified date (most recent first)\n\t\t\tsessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to load sessions:\", error);\n\t\t}\n\n\t\treturn sessions;\n\t}\n\n\t/**\n\t * Set the session file to an existing session\n\t */\n\tsetSessionFile(path: string): void {\n\t\tthis.sessionFile = path;\n\t\tthis.loadSessionId();\n\t\t// Mark as initialized since we're loading an existing session\n\t\tthis.sessionInitialized = existsSync(path);\n\t}\n\n\t/**\n\t * Check if we should initialize the session based on message history.\n\t * Session is initialized when we have at least 1 user message and 1 assistant message.\n\t */\n\tshouldInitializeSession(messages: any[]): boolean {\n\t\tif (this.sessionInitialized) return false;\n\n\t\tconst userMessages = messages.filter((m) => m.role === \"user\");\n\t\tconst assistantMessages = messages.filter((m) => m.role === \"assistant\");\n\n\t\treturn userMessages.length >= 1 && assistantMessages.length >= 1;\n\t}\n}\n"]}
@@ -47,7 +47,8 @@ export class SessionManager {
47
47
  }
48
48
  getSessionDirectory() {
49
49
  const cwd = process.cwd();
50
- const safePath = "--" + cwd.replace(/^\//, "").replace(/\//g, "-") + "--";
50
+ // Replace all path separators and colons (for Windows drive letters) with dashes
51
+ const safePath = "--" + cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-") + "--";
51
52
  const configDir = resolve(process.env.CODING_AGENT_DIR || join(homedir(), ".pi/agent/"));
52
53
  const sessionDir = join(configDir, "sessions", safePath);
53
54
  if (!existsSync(sessionDir)) {
@@ -1 +1 @@
1
- {"version":3,"file":"session-manager.js","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentState } from \"@mariozechner/pi-agent\";\nimport { randomBytes } from \"crypto\";\nimport { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join, resolve } from \"path\";\n\nfunction uuidv4(): string {\n\tconst bytes = randomBytes(16);\n\tbytes[6] = (bytes[6] & 0x0f) | 0x40;\n\tbytes[8] = (bytes[8] & 0x3f) | 0x80;\n\tconst hex = bytes.toString(\"hex\");\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;\n}\n\nexport interface SessionHeader {\n\ttype: \"session\";\n\tid: string;\n\ttimestamp: string;\n\tcwd: string;\n\tmodel: string;\n\tthinkingLevel: string;\n}\n\nexport interface SessionMessageEntry {\n\ttype: \"message\";\n\ttimestamp: string;\n\tmessage: any; // AppMessage from agent state\n}\n\nexport interface ThinkingLevelChangeEntry {\n\ttype: \"thinking_level_change\";\n\ttimestamp: string;\n\tthinkingLevel: string;\n}\n\nexport interface ModelChangeEntry {\n\ttype: \"model_change\";\n\ttimestamp: string;\n\tmodel: string;\n}\n\nexport class SessionManager {\n\tprivate sessionId!: string;\n\tprivate sessionFile!: string;\n\tprivate sessionDir: string;\n\tprivate enabled: boolean = true;\n\tprivate sessionInitialized: boolean = false;\n\tprivate pendingMessages: any[] = [];\n\n\tconstructor(continueSession: boolean = false, customSessionPath?: string) {\n\t\tthis.sessionDir = this.getSessionDirectory();\n\n\t\tif (customSessionPath) {\n\t\t\t// Use custom session file path\n\t\t\tthis.sessionFile = resolve(customSessionPath);\n\t\t\tthis.loadSessionId();\n\t\t\t// Mark as initialized since we're loading an existing session\n\t\t\tthis.sessionInitialized = existsSync(this.sessionFile);\n\t\t} else if (continueSession) {\n\t\t\tconst mostRecent = this.findMostRecentlyModifiedSession();\n\t\t\tif (mostRecent) {\n\t\t\t\tthis.sessionFile = mostRecent;\n\t\t\t\tthis.loadSessionId();\n\t\t\t\t// Mark as initialized since we're loading an existing session\n\t\t\t\tthis.sessionInitialized = true;\n\t\t\t} else {\n\t\t\t\tthis.initNewSession();\n\t\t\t}\n\t\t} else {\n\t\t\tthis.initNewSession();\n\t\t}\n\t}\n\n\t/** Disable session saving (for --no-session mode) */\n\tdisable() {\n\t\tthis.enabled = false;\n\t}\n\n\tprivate getSessionDirectory(): string {\n\t\tconst cwd = process.cwd();\n\t\tconst safePath = \"--\" + cwd.replace(/^\\//, \"\").replace(/\\//g, \"-\") + \"--\";\n\n\t\tconst configDir = resolve(process.env.CODING_AGENT_DIR || join(homedir(), \".pi/agent/\"));\n\t\tconst sessionDir = join(configDir, \"sessions\", safePath);\n\t\tif (!existsSync(sessionDir)) {\n\t\t\tmkdirSync(sessionDir, { recursive: true });\n\t\t}\n\t\treturn sessionDir;\n\t}\n\n\tprivate initNewSession(): void {\n\t\tthis.sessionId = uuidv4();\n\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\tthis.sessionFile = join(this.sessionDir, `${timestamp}_${this.sessionId}.jsonl`);\n\t}\n\n\tprivate findMostRecentlyModifiedSession(): string | null {\n\t\ttry {\n\t\t\tconst files = readdirSync(this.sessionDir)\n\t\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t\t.map((f) => ({\n\t\t\t\t\tname: f,\n\t\t\t\t\tpath: join(this.sessionDir, f),\n\t\t\t\t\tmtime: statSync(join(this.sessionDir, f)).mtime,\n\t\t\t\t}))\n\t\t\t\t.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());\n\n\t\t\treturn files[0]?.path || null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate loadSessionId(): void {\n\t\tif (!existsSync(this.sessionFile)) return;\n\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"session\") {\n\t\t\t\t\tthis.sessionId = entry.id;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\t\tthis.sessionId = uuidv4();\n\t}\n\n\tstartSession(state: AgentState): void {\n\t\tif (!this.enabled || this.sessionInitialized) return;\n\t\tthis.sessionInitialized = true;\n\n\t\tconst entry: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tid: this.sessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: process.cwd(),\n\t\t\tmodel: `${state.model.provider}/${state.model.id}`,\n\t\t\tthinkingLevel: state.thinkingLevel,\n\t\t};\n\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\n\t\t// Write any queued messages\n\t\tfor (const msg of this.pendingMessages) {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(msg) + \"\\n\");\n\t\t}\n\t\tthis.pendingMessages = [];\n\t}\n\n\tsaveMessage(message: any): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: SessionMessageEntry = {\n\t\t\ttype: \"message\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmessage,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveThinkingLevelChange(thinkingLevel: string): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: ThinkingLevelChangeEntry = {\n\t\t\ttype: \"thinking_level_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tthinkingLevel,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveModelChange(model: string): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: ModelChangeEntry = {\n\t\t\ttype: \"model_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmodel,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tloadMessages(): any[] {\n\t\tif (!existsSync(this.sessionFile)) return [];\n\n\t\tconst messages: any[] = [];\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"message\") {\n\t\t\t\t\tmessages.push(entry.message);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn messages;\n\t}\n\n\tloadThinkingLevel(): string {\n\t\tif (!existsSync(this.sessionFile)) return \"off\";\n\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\n\t\t// Find the most recent thinking level (from session header or change event)\n\t\tlet lastThinkingLevel = \"off\";\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"session\" && entry.thinkingLevel) {\n\t\t\t\t\tlastThinkingLevel = entry.thinkingLevel;\n\t\t\t\t} else if (entry.type === \"thinking_level_change\" && entry.thinkingLevel) {\n\t\t\t\t\tlastThinkingLevel = entry.thinkingLevel;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn lastThinkingLevel;\n\t}\n\n\tloadModel(): string | null {\n\t\tif (!existsSync(this.sessionFile)) return null;\n\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\n\t\t// Find the most recent model (from session header or change event)\n\t\tlet lastModel: string | null = null;\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"session\" && entry.model) {\n\t\t\t\t\tlastModel = entry.model;\n\t\t\t\t} else if (entry.type === \"model_change\" && entry.model) {\n\t\t\t\t\tlastModel = entry.model;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn lastModel;\n\t}\n\n\tgetSessionId(): string {\n\t\treturn this.sessionId;\n\t}\n\n\tgetSessionFile(): string {\n\t\treturn this.sessionFile;\n\t}\n\n\t/**\n\t * Load all sessions for the current directory with metadata\n\t */\n\tloadAllSessions(): Array<{\n\t\tpath: string;\n\t\tid: string;\n\t\tcreated: Date;\n\t\tmodified: Date;\n\t\tmessageCount: number;\n\t\tfirstMessage: string;\n\t\tallMessagesText: string;\n\t}> {\n\t\tconst sessions: Array<{\n\t\t\tpath: string;\n\t\t\tid: string;\n\t\t\tcreated: Date;\n\t\t\tmodified: Date;\n\t\t\tmessageCount: number;\n\t\t\tfirstMessage: string;\n\t\t\tallMessagesText: string;\n\t\t}> = [];\n\n\t\ttry {\n\t\t\tconst files = readdirSync(this.sessionDir)\n\t\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t\t.map((f) => join(this.sessionDir, f));\n\n\t\t\tfor (const file of files) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(file);\n\t\t\t\t\tconst content = readFileSync(file, \"utf8\");\n\t\t\t\t\tconst lines = content.trim().split(\"\\n\");\n\n\t\t\t\t\tlet sessionId = \"\";\n\t\t\t\t\tlet created = stats.birthtime;\n\t\t\t\t\tlet messageCount = 0;\n\t\t\t\t\tlet firstMessage = \"\";\n\t\t\t\t\tconst allMessages: string[] = [];\n\n\t\t\t\t\tfor (const line of lines) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst entry = JSON.parse(line);\n\n\t\t\t\t\t\t\t// Extract session ID from first session entry\n\t\t\t\t\t\t\tif (entry.type === \"session\" && !sessionId) {\n\t\t\t\t\t\t\t\tsessionId = entry.id;\n\t\t\t\t\t\t\t\tcreated = new Date(entry.timestamp);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Count messages and collect all text\n\t\t\t\t\t\t\tif (entry.type === \"message\") {\n\t\t\t\t\t\t\t\tmessageCount++;\n\n\t\t\t\t\t\t\t\t// Extract text from user and assistant messages\n\t\t\t\t\t\t\t\tif (entry.message.role === \"user\" || entry.message.role === \"assistant\") {\n\t\t\t\t\t\t\t\t\tconst textContent = entry.message.content\n\t\t\t\t\t\t\t\t\t\t.filter((c: any) => c.type === \"text\")\n\t\t\t\t\t\t\t\t\t\t.map((c: any) => c.text)\n\t\t\t\t\t\t\t\t\t\t.join(\" \");\n\n\t\t\t\t\t\t\t\t\tif (textContent) {\n\t\t\t\t\t\t\t\t\t\tallMessages.push(textContent);\n\n\t\t\t\t\t\t\t\t\t\t// Get first user message for display\n\t\t\t\t\t\t\t\t\t\tif (!firstMessage && entry.message.role === \"user\") {\n\t\t\t\t\t\t\t\t\t\t\tfirstMessage = textContent;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// Skip malformed lines\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tsessions.push({\n\t\t\t\t\t\tpath: file,\n\t\t\t\t\t\tid: sessionId || \"unknown\",\n\t\t\t\t\t\tcreated,\n\t\t\t\t\t\tmodified: stats.mtime,\n\t\t\t\t\t\tmessageCount,\n\t\t\t\t\t\tfirstMessage: firstMessage || \"(no messages)\",\n\t\t\t\t\t\tallMessagesText: allMessages.join(\" \"),\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Skip files that can't be read\n\t\t\t\t\tconsole.error(`Failed to read session file ${file}:`, error);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Sort by modified date (most recent first)\n\t\t\tsessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to load sessions:\", error);\n\t\t}\n\n\t\treturn sessions;\n\t}\n\n\t/**\n\t * Set the session file to an existing session\n\t */\n\tsetSessionFile(path: string): void {\n\t\tthis.sessionFile = path;\n\t\tthis.loadSessionId();\n\t\t// Mark as initialized since we're loading an existing session\n\t\tthis.sessionInitialized = existsSync(path);\n\t}\n\n\t/**\n\t * Check if we should initialize the session based on message history.\n\t * Session is initialized when we have at least 1 user message and 1 assistant message.\n\t */\n\tshouldInitializeSession(messages: any[]): boolean {\n\t\tif (this.sessionInitialized) return false;\n\n\t\tconst userMessages = messages.filter((m) => m.role === \"user\");\n\t\tconst assistantMessages = messages.filter((m) => m.role === \"assistant\");\n\n\t\treturn userMessages.length >= 1 && assistantMessages.length >= 1;\n\t}\n}\n"]}
1
+ {"version":3,"file":"session-manager.js","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentState } from \"@mariozechner/pi-agent\";\nimport { randomBytes } from \"crypto\";\nimport { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join, resolve } from \"path\";\n\nfunction uuidv4(): string {\n\tconst bytes = randomBytes(16);\n\tbytes[6] = (bytes[6] & 0x0f) | 0x40;\n\tbytes[8] = (bytes[8] & 0x3f) | 0x80;\n\tconst hex = bytes.toString(\"hex\");\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;\n}\n\nexport interface SessionHeader {\n\ttype: \"session\";\n\tid: string;\n\ttimestamp: string;\n\tcwd: string;\n\tmodel: string;\n\tthinkingLevel: string;\n}\n\nexport interface SessionMessageEntry {\n\ttype: \"message\";\n\ttimestamp: string;\n\tmessage: any; // AppMessage from agent state\n}\n\nexport interface ThinkingLevelChangeEntry {\n\ttype: \"thinking_level_change\";\n\ttimestamp: string;\n\tthinkingLevel: string;\n}\n\nexport interface ModelChangeEntry {\n\ttype: \"model_change\";\n\ttimestamp: string;\n\tmodel: string;\n}\n\nexport class SessionManager {\n\tprivate sessionId!: string;\n\tprivate sessionFile!: string;\n\tprivate sessionDir: string;\n\tprivate enabled: boolean = true;\n\tprivate sessionInitialized: boolean = false;\n\tprivate pendingMessages: any[] = [];\n\n\tconstructor(continueSession: boolean = false, customSessionPath?: string) {\n\t\tthis.sessionDir = this.getSessionDirectory();\n\n\t\tif (customSessionPath) {\n\t\t\t// Use custom session file path\n\t\t\tthis.sessionFile = resolve(customSessionPath);\n\t\t\tthis.loadSessionId();\n\t\t\t// Mark as initialized since we're loading an existing session\n\t\t\tthis.sessionInitialized = existsSync(this.sessionFile);\n\t\t} else if (continueSession) {\n\t\t\tconst mostRecent = this.findMostRecentlyModifiedSession();\n\t\t\tif (mostRecent) {\n\t\t\t\tthis.sessionFile = mostRecent;\n\t\t\t\tthis.loadSessionId();\n\t\t\t\t// Mark as initialized since we're loading an existing session\n\t\t\t\tthis.sessionInitialized = true;\n\t\t\t} else {\n\t\t\t\tthis.initNewSession();\n\t\t\t}\n\t\t} else {\n\t\t\tthis.initNewSession();\n\t\t}\n\t}\n\n\t/** Disable session saving (for --no-session mode) */\n\tdisable() {\n\t\tthis.enabled = false;\n\t}\n\n\tprivate getSessionDirectory(): string {\n\t\tconst cwd = process.cwd();\n\t\t// Replace all path separators and colons (for Windows drive letters) with dashes\n\t\tconst safePath = \"--\" + cwd.replace(/^[/\\\\]/, \"\").replace(/[/\\\\:]/g, \"-\") + \"--\";\n\n\t\tconst configDir = resolve(process.env.CODING_AGENT_DIR || join(homedir(), \".pi/agent/\"));\n\t\tconst sessionDir = join(configDir, \"sessions\", safePath);\n\t\tif (!existsSync(sessionDir)) {\n\t\t\tmkdirSync(sessionDir, { recursive: true });\n\t\t}\n\t\treturn sessionDir;\n\t}\n\n\tprivate initNewSession(): void {\n\t\tthis.sessionId = uuidv4();\n\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\tthis.sessionFile = join(this.sessionDir, `${timestamp}_${this.sessionId}.jsonl`);\n\t}\n\n\tprivate findMostRecentlyModifiedSession(): string | null {\n\t\ttry {\n\t\t\tconst files = readdirSync(this.sessionDir)\n\t\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t\t.map((f) => ({\n\t\t\t\t\tname: f,\n\t\t\t\t\tpath: join(this.sessionDir, f),\n\t\t\t\t\tmtime: statSync(join(this.sessionDir, f)).mtime,\n\t\t\t\t}))\n\t\t\t\t.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());\n\n\t\t\treturn files[0]?.path || null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate loadSessionId(): void {\n\t\tif (!existsSync(this.sessionFile)) return;\n\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"session\") {\n\t\t\t\t\tthis.sessionId = entry.id;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\t\tthis.sessionId = uuidv4();\n\t}\n\n\tstartSession(state: AgentState): void {\n\t\tif (!this.enabled || this.sessionInitialized) return;\n\t\tthis.sessionInitialized = true;\n\n\t\tconst entry: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tid: this.sessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: process.cwd(),\n\t\t\tmodel: `${state.model.provider}/${state.model.id}`,\n\t\t\tthinkingLevel: state.thinkingLevel,\n\t\t};\n\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\n\t\t// Write any queued messages\n\t\tfor (const msg of this.pendingMessages) {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(msg) + \"\\n\");\n\t\t}\n\t\tthis.pendingMessages = [];\n\t}\n\n\tsaveMessage(message: any): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: SessionMessageEntry = {\n\t\t\ttype: \"message\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmessage,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveThinkingLevelChange(thinkingLevel: string): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: ThinkingLevelChangeEntry = {\n\t\t\ttype: \"thinking_level_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tthinkingLevel,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveModelChange(model: string): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: ModelChangeEntry = {\n\t\t\ttype: \"model_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmodel,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tloadMessages(): any[] {\n\t\tif (!existsSync(this.sessionFile)) return [];\n\n\t\tconst messages: any[] = [];\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"message\") {\n\t\t\t\t\tmessages.push(entry.message);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn messages;\n\t}\n\n\tloadThinkingLevel(): string {\n\t\tif (!existsSync(this.sessionFile)) return \"off\";\n\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\n\t\t// Find the most recent thinking level (from session header or change event)\n\t\tlet lastThinkingLevel = \"off\";\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"session\" && entry.thinkingLevel) {\n\t\t\t\t\tlastThinkingLevel = entry.thinkingLevel;\n\t\t\t\t} else if (entry.type === \"thinking_level_change\" && entry.thinkingLevel) {\n\t\t\t\t\tlastThinkingLevel = entry.thinkingLevel;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn lastThinkingLevel;\n\t}\n\n\tloadModel(): string | null {\n\t\tif (!existsSync(this.sessionFile)) return null;\n\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\n\t\t// Find the most recent model (from session header or change event)\n\t\tlet lastModel: string | null = null;\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"session\" && entry.model) {\n\t\t\t\t\tlastModel = entry.model;\n\t\t\t\t} else if (entry.type === \"model_change\" && entry.model) {\n\t\t\t\t\tlastModel = entry.model;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn lastModel;\n\t}\n\n\tgetSessionId(): string {\n\t\treturn this.sessionId;\n\t}\n\n\tgetSessionFile(): string {\n\t\treturn this.sessionFile;\n\t}\n\n\t/**\n\t * Load all sessions for the current directory with metadata\n\t */\n\tloadAllSessions(): Array<{\n\t\tpath: string;\n\t\tid: string;\n\t\tcreated: Date;\n\t\tmodified: Date;\n\t\tmessageCount: number;\n\t\tfirstMessage: string;\n\t\tallMessagesText: string;\n\t}> {\n\t\tconst sessions: Array<{\n\t\t\tpath: string;\n\t\t\tid: string;\n\t\t\tcreated: Date;\n\t\t\tmodified: Date;\n\t\t\tmessageCount: number;\n\t\t\tfirstMessage: string;\n\t\t\tallMessagesText: string;\n\t\t}> = [];\n\n\t\ttry {\n\t\t\tconst files = readdirSync(this.sessionDir)\n\t\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t\t.map((f) => join(this.sessionDir, f));\n\n\t\t\tfor (const file of files) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(file);\n\t\t\t\t\tconst content = readFileSync(file, \"utf8\");\n\t\t\t\t\tconst lines = content.trim().split(\"\\n\");\n\n\t\t\t\t\tlet sessionId = \"\";\n\t\t\t\t\tlet created = stats.birthtime;\n\t\t\t\t\tlet messageCount = 0;\n\t\t\t\t\tlet firstMessage = \"\";\n\t\t\t\t\tconst allMessages: string[] = [];\n\n\t\t\t\t\tfor (const line of lines) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst entry = JSON.parse(line);\n\n\t\t\t\t\t\t\t// Extract session ID from first session entry\n\t\t\t\t\t\t\tif (entry.type === \"session\" && !sessionId) {\n\t\t\t\t\t\t\t\tsessionId = entry.id;\n\t\t\t\t\t\t\t\tcreated = new Date(entry.timestamp);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Count messages and collect all text\n\t\t\t\t\t\t\tif (entry.type === \"message\") {\n\t\t\t\t\t\t\t\tmessageCount++;\n\n\t\t\t\t\t\t\t\t// Extract text from user and assistant messages\n\t\t\t\t\t\t\t\tif (entry.message.role === \"user\" || entry.message.role === \"assistant\") {\n\t\t\t\t\t\t\t\t\tconst textContent = entry.message.content\n\t\t\t\t\t\t\t\t\t\t.filter((c: any) => c.type === \"text\")\n\t\t\t\t\t\t\t\t\t\t.map((c: any) => c.text)\n\t\t\t\t\t\t\t\t\t\t.join(\" \");\n\n\t\t\t\t\t\t\t\t\tif (textContent) {\n\t\t\t\t\t\t\t\t\t\tallMessages.push(textContent);\n\n\t\t\t\t\t\t\t\t\t\t// Get first user message for display\n\t\t\t\t\t\t\t\t\t\tif (!firstMessage && entry.message.role === \"user\") {\n\t\t\t\t\t\t\t\t\t\t\tfirstMessage = textContent;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// Skip malformed lines\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tsessions.push({\n\t\t\t\t\t\tpath: file,\n\t\t\t\t\t\tid: sessionId || \"unknown\",\n\t\t\t\t\t\tcreated,\n\t\t\t\t\t\tmodified: stats.mtime,\n\t\t\t\t\t\tmessageCount,\n\t\t\t\t\t\tfirstMessage: firstMessage || \"(no messages)\",\n\t\t\t\t\t\tallMessagesText: allMessages.join(\" \"),\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Skip files that can't be read\n\t\t\t\t\tconsole.error(`Failed to read session file ${file}:`, error);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Sort by modified date (most recent first)\n\t\t\tsessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to load sessions:\", error);\n\t\t}\n\n\t\treturn sessions;\n\t}\n\n\t/**\n\t * Set the session file to an existing session\n\t */\n\tsetSessionFile(path: string): void {\n\t\tthis.sessionFile = path;\n\t\tthis.loadSessionId();\n\t\t// Mark as initialized since we're loading an existing session\n\t\tthis.sessionInitialized = existsSync(path);\n\t}\n\n\t/**\n\t * Check if we should initialize the session based on message history.\n\t * Session is initialized when we have at least 1 user message and 1 assistant message.\n\t */\n\tshouldInitializeSession(messages: any[]): boolean {\n\t\tif (this.sessionInitialized) return false;\n\n\t\tconst userMessages = messages.filter((m) => m.role === \"user\");\n\t\tconst assistantMessages = messages.filter((m) => m.role === \"assistant\");\n\n\t\treturn userMessages.length >= 1 && assistantMessages.length >= 1;\n\t}\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import type { AgentTool } from "@mariozechner/pi-ai";
2
2
  declare const bashSchema: import("@sinclair/typebox").TObject<{
3
3
  command: import("@sinclair/typebox").TString;
4
+ timeout: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
4
5
  }>;
5
6
  export declare const bashTool: AgentTool<typeof bashSchema>;
6
7
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAIrD,QAAA,MAAM,UAAU;;EAEd,CAAC;AAEH,eAAO,MAAM,QAAQ,EAAE,SAAS,CAAC,OAAO,UAAU,CA0HjD,CAAC","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { spawn } from \"child_process\";\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n});\n\nexport const bashTool: AgentTool<typeof bashSchema> = {\n\tname: \"bash\",\n\tlabel: \"bash\",\n\tdescription:\n\t\t\"Execute a bash command in the current working directory. Returns stdout and stderr. Commands run with a 30 second timeout.\",\n\tparameters: bashSchema,\n\texecute: async (_toolCallId: string, { command }: { command: string }, signal?: AbortSignal) => {\n\t\treturn new Promise((resolve, _reject) => {\n\t\t\tconst child = spawn(\"sh\", [\"-c\", command], {\n\t\t\t\tdetached: true,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t});\n\n\t\t\tlet stdout = \"\";\n\t\t\tlet stderr = \"\";\n\t\t\tlet timedOut = false;\n\n\t\t\t// Set timeout\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\ttimedOut = true;\n\t\t\t\tonAbort();\n\t\t\t}, 30000);\n\n\t\t\t// Collect stdout\n\t\t\tif (child.stdout) {\n\t\t\t\tchild.stdout.on(\"data\", (data) => {\n\t\t\t\t\tstdout += data.toString();\n\t\t\t\t\t// Limit buffer size\n\t\t\t\t\tif (stdout.length > 10 * 1024 * 1024) {\n\t\t\t\t\t\tstdout = stdout.slice(0, 10 * 1024 * 1024);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Collect stderr\n\t\t\tif (child.stderr) {\n\t\t\t\tchild.stderr.on(\"data\", (data) => {\n\t\t\t\t\tstderr += data.toString();\n\t\t\t\t\t// Limit buffer size\n\t\t\t\t\tif (stderr.length > 10 * 1024 * 1024) {\n\t\t\t\t\t\tstderr = stderr.slice(0, 10 * 1024 * 1024);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Handle process exit\n\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tif (signal) {\n\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t}\n\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\tlet output = \"\";\n\t\t\t\t\tif (stdout) output += stdout;\n\t\t\t\t\tif (stderr) {\n\t\t\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\t\t\toutput += stderr;\n\t\t\t\t\t}\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\toutput += \"Command aborted\";\n\t\t\t\t\tresolve({ content: [{ type: \"text\", text: `Command failed\\n\\n${output}` }], details: undefined });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (timedOut) {\n\t\t\t\t\tlet output = \"\";\n\t\t\t\t\tif (stdout) output += stdout;\n\t\t\t\t\tif (stderr) {\n\t\t\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\t\t\toutput += stderr;\n\t\t\t\t\t}\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\toutput += \"Command timed out after 30 seconds\";\n\t\t\t\t\tresolve({ content: [{ type: \"text\", text: `Command failed\\n\\n${output}` }], details: undefined });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet output = \"\";\n\t\t\t\tif (stdout) output += stdout;\n\t\t\t\tif (stderr) {\n\t\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\t\toutput += stderr;\n\t\t\t\t}\n\n\t\t\t\tif (code !== 0 && code !== null) {\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\tresolve({\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Command failed\\n\\n${output}Command exited with code ${code}` }],\n\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tresolve({ content: [{ type: \"text\", text: output || \"(no output)\" }], details: undefined });\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Handle abort signal - kill entire process tree\n\t\t\tconst onAbort = () => {\n\t\t\t\tif (child.pid) {\n\t\t\t\t\t// Kill the entire process group (negative PID kills all processes in the group)\n\t\t\t\t\ttry {\n\t\t\t\t\t\tprocess.kill(-child.pid, \"SIGKILL\");\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t// Fallback to killing just the child if process group kill fails\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\t\t\t} catch (e2) {\n\t\t\t\t\t\t\t// Process already dead\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tif (signal) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\tonAbort();\n\t\t\t\t} else {\n\t\t\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n};\n"]}
1
+ {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAIrD,QAAA,MAAM,UAAU;;;EAGd,CAAC;AAEH,eAAO,MAAM,QAAQ,EAAE,SAAS,CAAC,OAAO,UAAU,CAmIjD,CAAC","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { spawn } from \"child_process\";\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\nexport const bashTool: AgentTool<typeof bashSchema> = {\n\tname: \"bash\",\n\tlabel: \"bash\",\n\tdescription:\n\t\t\"Execute a bash command in the current working directory. Returns stdout and stderr. Optionally provide a timeout in seconds.\",\n\tparameters: bashSchema,\n\texecute: async (\n\t\t_toolCallId: string,\n\t\t{ command, timeout }: { command: string; timeout?: number },\n\t\tsignal?: AbortSignal,\n\t) => {\n\t\treturn new Promise((resolve, _reject) => {\n\t\t\tconst child = spawn(\"sh\", [\"-c\", command], {\n\t\t\t\tdetached: true,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t});\n\n\t\t\tlet stdout = \"\";\n\t\t\tlet stderr = \"\";\n\t\t\tlet timedOut = false;\n\n\t\t\t// Set timeout if provided\n\t\t\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\t\t\tif (timeout !== undefined && timeout > 0) {\n\t\t\t\ttimeoutHandle = setTimeout(() => {\n\t\t\t\t\ttimedOut = true;\n\t\t\t\t\tonAbort();\n\t\t\t\t}, timeout * 1000);\n\t\t\t}\n\n\t\t\t// Collect stdout\n\t\t\tif (child.stdout) {\n\t\t\t\tchild.stdout.on(\"data\", (data) => {\n\t\t\t\t\tstdout += data.toString();\n\t\t\t\t\t// Limit buffer size\n\t\t\t\t\tif (stdout.length > 10 * 1024 * 1024) {\n\t\t\t\t\t\tstdout = stdout.slice(0, 10 * 1024 * 1024);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Collect stderr\n\t\t\tif (child.stderr) {\n\t\t\t\tchild.stderr.on(\"data\", (data) => {\n\t\t\t\t\tstderr += data.toString();\n\t\t\t\t\t// Limit buffer size\n\t\t\t\t\tif (stderr.length > 10 * 1024 * 1024) {\n\t\t\t\t\t\tstderr = stderr.slice(0, 10 * 1024 * 1024);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Handle process exit\n\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\tif (timeoutHandle) {\n\t\t\t\t\tclearTimeout(timeoutHandle);\n\t\t\t\t}\n\t\t\t\tif (signal) {\n\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t}\n\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\tlet output = \"\";\n\t\t\t\t\tif (stdout) output += stdout;\n\t\t\t\t\tif (stderr) {\n\t\t\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\t\t\toutput += stderr;\n\t\t\t\t\t}\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\toutput += \"Command aborted\";\n\t\t\t\t\tresolve({ content: [{ type: \"text\", text: `Command failed\\n\\n${output}` }], details: undefined });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (timedOut) {\n\t\t\t\t\tlet output = \"\";\n\t\t\t\t\tif (stdout) output += stdout;\n\t\t\t\t\tif (stderr) {\n\t\t\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\t\t\toutput += stderr;\n\t\t\t\t\t}\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\toutput += `Command timed out after ${timeout} seconds`;\n\t\t\t\t\tresolve({ content: [{ type: \"text\", text: `Command failed\\n\\n${output}` }], details: undefined });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet output = \"\";\n\t\t\t\tif (stdout) output += stdout;\n\t\t\t\tif (stderr) {\n\t\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\t\toutput += stderr;\n\t\t\t\t}\n\n\t\t\t\tif (code !== 0 && code !== null) {\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\tresolve({\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Command failed\\n\\n${output}Command exited with code ${code}` }],\n\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tresolve({ content: [{ type: \"text\", text: output || \"(no output)\" }], details: undefined });\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Handle abort signal - kill entire process tree\n\t\t\tconst onAbort = () => {\n\t\t\t\tif (child.pid) {\n\t\t\t\t\t// Kill the entire process group (negative PID kills all processes in the group)\n\t\t\t\t\ttry {\n\t\t\t\t\t\tprocess.kill(-child.pid, \"SIGKILL\");\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t// Fallback to killing just the child if process group kill fails\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\t\t\t} catch (e2) {\n\t\t\t\t\t\t\t// Process already dead\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tif (signal) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\tonAbort();\n\t\t\t\t} else {\n\t\t\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n};\n"]}
@@ -2,13 +2,14 @@ import { Type } from "@sinclair/typebox";
2
2
  import { spawn } from "child_process";
3
3
  const bashSchema = Type.Object({
4
4
  command: Type.String({ description: "Bash command to execute" }),
5
+ timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
5
6
  });
6
7
  export const bashTool = {
7
8
  name: "bash",
8
9
  label: "bash",
9
- description: "Execute a bash command in the current working directory. Returns stdout and stderr. Commands run with a 30 second timeout.",
10
+ description: "Execute a bash command in the current working directory. Returns stdout and stderr. Optionally provide a timeout in seconds.",
10
11
  parameters: bashSchema,
11
- execute: async (_toolCallId, { command }, signal) => {
12
+ execute: async (_toolCallId, { command, timeout }, signal) => {
12
13
  return new Promise((resolve, _reject) => {
13
14
  const child = spawn("sh", ["-c", command], {
14
15
  detached: true,
@@ -17,11 +18,14 @@ export const bashTool = {
17
18
  let stdout = "";
18
19
  let stderr = "";
19
20
  let timedOut = false;
20
- // Set timeout
21
- const timeout = setTimeout(() => {
22
- timedOut = true;
23
- onAbort();
24
- }, 30000);
21
+ // Set timeout if provided
22
+ let timeoutHandle;
23
+ if (timeout !== undefined && timeout > 0) {
24
+ timeoutHandle = setTimeout(() => {
25
+ timedOut = true;
26
+ onAbort();
27
+ }, timeout * 1000);
28
+ }
25
29
  // Collect stdout
26
30
  if (child.stdout) {
27
31
  child.stdout.on("data", (data) => {
@@ -44,7 +48,9 @@ export const bashTool = {
44
48
  }
45
49
  // Handle process exit
46
50
  child.on("close", (code) => {
47
- clearTimeout(timeout);
51
+ if (timeoutHandle) {
52
+ clearTimeout(timeoutHandle);
53
+ }
48
54
  if (signal) {
49
55
  signal.removeEventListener("abort", onAbort);
50
56
  }
@@ -74,7 +80,7 @@ export const bashTool = {
74
80
  }
75
81
  if (output)
76
82
  output += "\n\n";
77
- output += "Command timed out after 30 seconds";
83
+ output += `Command timed out after ${timeout} seconds`;
78
84
  resolve({ content: [{ type: "text", text: `Command failed\n\n${output}` }], details: undefined });
79
85
  return;
80
86
  }
@@ -1 +1 @@
1
- {"version":3,"file":"bash.js","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { spawn } from \"child_process\";\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n});\n\nexport const bashTool: AgentTool<typeof bashSchema> = {\n\tname: \"bash\",\n\tlabel: \"bash\",\n\tdescription:\n\t\t\"Execute a bash command in the current working directory. Returns stdout and stderr. Commands run with a 30 second timeout.\",\n\tparameters: bashSchema,\n\texecute: async (_toolCallId: string, { command }: { command: string }, signal?: AbortSignal) => {\n\t\treturn new Promise((resolve, _reject) => {\n\t\t\tconst child = spawn(\"sh\", [\"-c\", command], {\n\t\t\t\tdetached: true,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t});\n\n\t\t\tlet stdout = \"\";\n\t\t\tlet stderr = \"\";\n\t\t\tlet timedOut = false;\n\n\t\t\t// Set timeout\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\ttimedOut = true;\n\t\t\t\tonAbort();\n\t\t\t}, 30000);\n\n\t\t\t// Collect stdout\n\t\t\tif (child.stdout) {\n\t\t\t\tchild.stdout.on(\"data\", (data) => {\n\t\t\t\t\tstdout += data.toString();\n\t\t\t\t\t// Limit buffer size\n\t\t\t\t\tif (stdout.length > 10 * 1024 * 1024) {\n\t\t\t\t\t\tstdout = stdout.slice(0, 10 * 1024 * 1024);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Collect stderr\n\t\t\tif (child.stderr) {\n\t\t\t\tchild.stderr.on(\"data\", (data) => {\n\t\t\t\t\tstderr += data.toString();\n\t\t\t\t\t// Limit buffer size\n\t\t\t\t\tif (stderr.length > 10 * 1024 * 1024) {\n\t\t\t\t\t\tstderr = stderr.slice(0, 10 * 1024 * 1024);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Handle process exit\n\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tif (signal) {\n\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t}\n\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\tlet output = \"\";\n\t\t\t\t\tif (stdout) output += stdout;\n\t\t\t\t\tif (stderr) {\n\t\t\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\t\t\toutput += stderr;\n\t\t\t\t\t}\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\toutput += \"Command aborted\";\n\t\t\t\t\tresolve({ content: [{ type: \"text\", text: `Command failed\\n\\n${output}` }], details: undefined });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (timedOut) {\n\t\t\t\t\tlet output = \"\";\n\t\t\t\t\tif (stdout) output += stdout;\n\t\t\t\t\tif (stderr) {\n\t\t\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\t\t\toutput += stderr;\n\t\t\t\t\t}\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\toutput += \"Command timed out after 30 seconds\";\n\t\t\t\t\tresolve({ content: [{ type: \"text\", text: `Command failed\\n\\n${output}` }], details: undefined });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet output = \"\";\n\t\t\t\tif (stdout) output += stdout;\n\t\t\t\tif (stderr) {\n\t\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\t\toutput += stderr;\n\t\t\t\t}\n\n\t\t\t\tif (code !== 0 && code !== null) {\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\tresolve({\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Command failed\\n\\n${output}Command exited with code ${code}` }],\n\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tresolve({ content: [{ type: \"text\", text: output || \"(no output)\" }], details: undefined });\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Handle abort signal - kill entire process tree\n\t\t\tconst onAbort = () => {\n\t\t\t\tif (child.pid) {\n\t\t\t\t\t// Kill the entire process group (negative PID kills all processes in the group)\n\t\t\t\t\ttry {\n\t\t\t\t\t\tprocess.kill(-child.pid, \"SIGKILL\");\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t// Fallback to killing just the child if process group kill fails\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\t\t\t} catch (e2) {\n\t\t\t\t\t\t\t// Process already dead\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tif (signal) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\tonAbort();\n\t\t\t\t} else {\n\t\t\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n};\n"]}
1
+ {"version":3,"file":"bash.js","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { spawn } from \"child_process\";\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\nexport const bashTool: AgentTool<typeof bashSchema> = {\n\tname: \"bash\",\n\tlabel: \"bash\",\n\tdescription:\n\t\t\"Execute a bash command in the current working directory. Returns stdout and stderr. Optionally provide a timeout in seconds.\",\n\tparameters: bashSchema,\n\texecute: async (\n\t\t_toolCallId: string,\n\t\t{ command, timeout }: { command: string; timeout?: number },\n\t\tsignal?: AbortSignal,\n\t) => {\n\t\treturn new Promise((resolve, _reject) => {\n\t\t\tconst child = spawn(\"sh\", [\"-c\", command], {\n\t\t\t\tdetached: true,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t});\n\n\t\t\tlet stdout = \"\";\n\t\t\tlet stderr = \"\";\n\t\t\tlet timedOut = false;\n\n\t\t\t// Set timeout if provided\n\t\t\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\t\t\tif (timeout !== undefined && timeout > 0) {\n\t\t\t\ttimeoutHandle = setTimeout(() => {\n\t\t\t\t\ttimedOut = true;\n\t\t\t\t\tonAbort();\n\t\t\t\t}, timeout * 1000);\n\t\t\t}\n\n\t\t\t// Collect stdout\n\t\t\tif (child.stdout) {\n\t\t\t\tchild.stdout.on(\"data\", (data) => {\n\t\t\t\t\tstdout += data.toString();\n\t\t\t\t\t// Limit buffer size\n\t\t\t\t\tif (stdout.length > 10 * 1024 * 1024) {\n\t\t\t\t\t\tstdout = stdout.slice(0, 10 * 1024 * 1024);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Collect stderr\n\t\t\tif (child.stderr) {\n\t\t\t\tchild.stderr.on(\"data\", (data) => {\n\t\t\t\t\tstderr += data.toString();\n\t\t\t\t\t// Limit buffer size\n\t\t\t\t\tif (stderr.length > 10 * 1024 * 1024) {\n\t\t\t\t\t\tstderr = stderr.slice(0, 10 * 1024 * 1024);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Handle process exit\n\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\tif (timeoutHandle) {\n\t\t\t\t\tclearTimeout(timeoutHandle);\n\t\t\t\t}\n\t\t\t\tif (signal) {\n\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t}\n\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\tlet output = \"\";\n\t\t\t\t\tif (stdout) output += stdout;\n\t\t\t\t\tif (stderr) {\n\t\t\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\t\t\toutput += stderr;\n\t\t\t\t\t}\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\toutput += \"Command aborted\";\n\t\t\t\t\tresolve({ content: [{ type: \"text\", text: `Command failed\\n\\n${output}` }], details: undefined });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (timedOut) {\n\t\t\t\t\tlet output = \"\";\n\t\t\t\t\tif (stdout) output += stdout;\n\t\t\t\t\tif (stderr) {\n\t\t\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\t\t\toutput += stderr;\n\t\t\t\t\t}\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\toutput += `Command timed out after ${timeout} seconds`;\n\t\t\t\t\tresolve({ content: [{ type: \"text\", text: `Command failed\\n\\n${output}` }], details: undefined });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet output = \"\";\n\t\t\t\tif (stdout) output += stdout;\n\t\t\t\tif (stderr) {\n\t\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\t\toutput += stderr;\n\t\t\t\t}\n\n\t\t\t\tif (code !== 0 && code !== null) {\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\tresolve({\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Command failed\\n\\n${output}Command exited with code ${code}` }],\n\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tresolve({ content: [{ type: \"text\", text: output || \"(no output)\" }], details: undefined });\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Handle abort signal - kill entire process tree\n\t\t\tconst onAbort = () => {\n\t\t\t\tif (child.pid) {\n\t\t\t\t\t// Kill the entire process group (negative PID kills all processes in the group)\n\t\t\t\t\ttry {\n\t\t\t\t\t\tprocess.kill(-child.pid, \"SIGKILL\");\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t// Fallback to killing just the child if process group kill fails\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\t\t\t} catch (e2) {\n\t\t\t\t\t\t\t// Process already dead\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tif (signal) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\tonAbort();\n\t\t\t\t} else {\n\t\t\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n};\n"]}
@@ -4,6 +4,7 @@ export { readTool } from "./read.js";
4
4
  export { writeTool } from "./write.js";
5
5
  export declare const codingTools: (import("../../../ai/dist/index.js").AgentTool<import("@sinclair/typebox").TObject<{
6
6
  command: import("@sinclair/typebox").TString;
7
+ timeout: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
7
8
  }>, any> | import("../../../ai/dist/index.js").AgentTool<import("@sinclair/typebox").TObject<{
8
9
  path: import("@sinclair/typebox").TString;
9
10
  oldText: import("@sinclair/typebox").TString;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAOvC,eAAO,MAAM,WAAW;;;;;;;;;;;;;WAA4C,CAAC","sourcesContent":["export { bashTool } from \"./bash.js\";\nexport { editTool } from \"./edit.js\";\nexport { readTool } from \"./read.js\";\nexport { writeTool } from \"./write.js\";\n\nimport { bashTool } from \"./bash.js\";\nimport { editTool } from \"./edit.js\";\nimport { readTool } from \"./read.js\";\nimport { writeTool } from \"./write.js\";\n\nexport const codingTools = [readTool, bashTool, editTool, writeTool];\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAOvC,eAAO,MAAM,WAAW;;;;;;;;;;;;;;WAA4C,CAAC","sourcesContent":["export { bashTool } from \"./bash.js\";\nexport { editTool } from \"./edit.js\";\nexport { readTool } from \"./read.js\";\nexport { writeTool } from \"./write.js\";\n\nimport { bashTool } from \"./bash.js\";\nimport { editTool } from \"./edit.js\";\nimport { readTool } from \"./read.js\";\nimport { writeTool } from \"./write.js\";\n\nexport const codingTools = [readTool, bashTool, editTool, writeTool];\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"tui-renderer.d.ts","sourceRoot":"","sources":["../../src/tui/tui-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAc5E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAS5D;;GAEG;AACH,qBAAa,WAAW;IACvB,OAAO,CAAC,EAAE,CAAM;IAChB,OAAO,CAAC,aAAa,CAAY;IACjC,OAAO,CAAC,eAAe,CAAY;IACnC,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,eAAe,CAAY;IACnC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,eAAe,CAAC,CAAyB;IACjD,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,mBAAmB,CAAC,CAAa;IACzC,OAAO,CAAC,cAAc,CAAK;IAG3B,OAAO,CAAC,kBAAkB,CAA0C;IAGpE,OAAO,CAAC,YAAY,CAA6C;IAGjE,OAAO,CAAC,gBAAgB,CAA0C;IAGlE,OAAO,CAAC,aAAa,CAAuC;IAG5D,OAAO,CAAC,kBAAkB,CAAQ;IAElC,YAAY,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAuCxE;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CA2F1B;IAEK,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAsIrE;IAED,OAAO,CAAC,gBAAgB;IAqBxB,qBAAqB,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CA8D7C;IAEK,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAOpC;IAED,oBAAoB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAE/C;IAED,OAAO,CAAC,WAAW;IAgBnB,WAAW,IAAI,IAAI,CAGlB;IAED,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAKpC;IAED,OAAO,CAAC,oBAAoB;IAkC5B,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,iBAAiB;IAkCzB,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,mBAAmB;IAuB3B,OAAO,CAAC,oBAAoB;IA4D5B,IAAI,IAAI,IAAI,CASX;CACD","sourcesContent":["import type { Agent, AgentEvent, AgentState } from \"@mariozechner/pi-agent\";\nimport type { AssistantMessage, Message } from \"@mariozechner/pi-ai\";\nimport type { SlashCommand } from \"@mariozechner/pi-tui\";\nimport {\n\tCombinedAutocompleteProvider,\n\tContainer,\n\tLoader,\n\tProcessTerminal,\n\tSpacer,\n\tText,\n\tTUI,\n} from \"@mariozechner/pi-tui\";\nimport chalk from \"chalk\";\nimport { exportSessionToHtml } from \"../export-html.js\";\nimport type { SessionManager } from \"../session-manager.js\";\nimport { AssistantMessageComponent } from \"./assistant-message.js\";\nimport { CustomEditor } from \"./custom-editor.js\";\nimport { FooterComponent } from \"./footer.js\";\nimport { ModelSelectorComponent } from \"./model-selector.js\";\nimport { ThinkingSelectorComponent } from \"./thinking-selector.js\";\nimport { ToolExecutionComponent } from \"./tool-execution.js\";\nimport { UserMessageComponent } from \"./user-message.js\";\n\n/**\n * TUI renderer for the coding agent\n */\nexport class TuiRenderer {\n\tprivate ui: TUI;\n\tprivate chatContainer: Container;\n\tprivate statusContainer: Container;\n\tprivate editor: CustomEditor;\n\tprivate editorContainer: Container; // Container to swap between editor and selector\n\tprivate footer: FooterComponent;\n\tprivate agent: Agent;\n\tprivate sessionManager: SessionManager;\n\tprivate version: string;\n\tprivate isInitialized = false;\n\tprivate onInputCallback?: (text: string) => void;\n\tprivate loadingAnimation: Loader | null = null;\n\tprivate onInterruptCallback?: () => void;\n\tprivate lastSigintTime = 0;\n\n\t// Streaming message tracking\n\tprivate streamingComponent: AssistantMessageComponent | null = null;\n\n\t// Tool execution tracking: toolCallId -> component\n\tprivate pendingTools = new Map<string, ToolExecutionComponent>();\n\n\t// Thinking level selector\n\tprivate thinkingSelector: ThinkingSelectorComponent | null = null;\n\n\t// Model selector\n\tprivate modelSelector: ModelSelectorComponent | null = null;\n\n\t// Track if this is the first user message (to skip spacer)\n\tprivate isFirstUserMessage = true;\n\n\tconstructor(agent: Agent, sessionManager: SessionManager, version: string) {\n\t\tthis.agent = agent;\n\t\tthis.sessionManager = sessionManager;\n\t\tthis.version = version;\n\t\tthis.ui = new TUI(new ProcessTerminal());\n\t\tthis.chatContainer = new Container();\n\t\tthis.statusContainer = new Container();\n\t\tthis.editor = new CustomEditor();\n\t\tthis.editorContainer = new Container(); // Container to hold editor or selector\n\t\tthis.editorContainer.addChild(this.editor); // Start with editor\n\t\tthis.footer = new FooterComponent(agent.state);\n\n\t\t// Define slash commands\n\t\tconst thinkingCommand: SlashCommand = {\n\t\t\tname: \"thinking\",\n\t\t\tdescription: \"Select reasoning level (opens selector UI)\",\n\t\t};\n\n\t\tconst modelCommand: SlashCommand = {\n\t\t\tname: \"model\",\n\t\t\tdescription: \"Select model (opens selector UI)\",\n\t\t};\n\n\t\tconst exportCommand: SlashCommand = {\n\t\t\tname: \"export\",\n\t\t\tdescription: \"Export session to HTML file\",\n\t\t};\n\n\t\tconst sessionCommand: SlashCommand = {\n\t\t\tname: \"session\",\n\t\t\tdescription: \"Show session info and stats\",\n\t\t};\n\n\t\t// Setup autocomplete for file paths and slash commands\n\t\tconst autocompleteProvider = new CombinedAutocompleteProvider(\n\t\t\t[thinkingCommand, modelCommand, exportCommand, sessionCommand],\n\t\t\tprocess.cwd(),\n\t\t);\n\t\tthis.editor.setAutocompleteProvider(autocompleteProvider);\n\t}\n\n\tasync init(): Promise<void> {\n\t\tif (this.isInitialized) return;\n\n\t\t// Add header with logo and instructions\n\t\tconst logo = chalk.bold.cyan(\"pi\") + chalk.dim(` v${this.version}`);\n\t\tconst instructions =\n\t\t\tchalk.dim(\"esc\") +\n\t\t\tchalk.gray(\" to interrupt\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"ctrl+c\") +\n\t\t\tchalk.gray(\" to clear\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"ctrl+c twice\") +\n\t\t\tchalk.gray(\" to exit\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"ctrl+k\") +\n\t\t\tchalk.gray(\" to delete line\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"/\") +\n\t\t\tchalk.gray(\" for commands\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"drop files\") +\n\t\t\tchalk.gray(\" to attach\");\n\t\tconst header = new Text(logo + \"\\n\" + instructions, 1, 0);\n\n\t\t// Setup UI layout\n\t\tthis.ui.addChild(new Spacer(1));\n\t\tthis.ui.addChild(header);\n\t\tthis.ui.addChild(new Spacer(1));\n\t\tthis.ui.addChild(this.chatContainer);\n\t\tthis.ui.addChild(this.statusContainer);\n\t\tthis.ui.addChild(new Spacer(1));\n\t\tthis.ui.addChild(this.editorContainer); // Use container that can hold editor or selector\n\t\tthis.ui.addChild(this.footer);\n\t\tthis.ui.setFocus(this.editor);\n\n\t\t// Set up custom key handlers on the editor\n\t\tthis.editor.onEscape = () => {\n\t\t\t// Intercept Escape key when processing\n\t\t\tif (this.loadingAnimation && this.onInterruptCallback) {\n\t\t\t\tthis.onInterruptCallback();\n\t\t\t}\n\t\t};\n\n\t\tthis.editor.onCtrlC = () => {\n\t\t\tthis.handleCtrlC();\n\t\t};\n\n\t\t// Handle editor submission\n\t\tthis.editor.onSubmit = (text: string) => {\n\t\t\ttext = text.trim();\n\t\t\tif (!text) return;\n\n\t\t\t// Check for /thinking command\n\t\t\tif (text === \"/thinking\") {\n\t\t\t\t// Show thinking level selector\n\t\t\t\tthis.showThinkingSelector();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check for /model command\n\t\t\tif (text === \"/model\") {\n\t\t\t\t// Show model selector\n\t\t\t\tthis.showModelSelector();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check for /export command\n\t\t\tif (text.startsWith(\"/export\")) {\n\t\t\t\tthis.handleExportCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check for /session command\n\t\t\tif (text === \"/session\") {\n\t\t\t\tthis.handleSessionCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this.onInputCallback) {\n\t\t\t\tthis.onInputCallback(text);\n\t\t\t}\n\t\t};\n\n\t\t// Start the UI\n\t\tthis.ui.start();\n\t\tthis.isInitialized = true;\n\t}\n\n\tasync handleEvent(event: AgentEvent, state: AgentState): Promise<void> {\n\t\tif (!this.isInitialized) {\n\t\t\tawait this.init();\n\t\t}\n\n\t\t// Update footer with current stats\n\t\tthis.footer.updateState(state);\n\n\t\tswitch (event.type) {\n\t\t\tcase \"agent_start\":\n\t\t\t\t// Show loading animation\n\t\t\t\tthis.editor.disableSubmit = true;\n\t\t\t\t// Stop old loader before clearing\n\t\t\t\tif (this.loadingAnimation) {\n\t\t\t\t\tthis.loadingAnimation.stop();\n\t\t\t\t}\n\t\t\t\tthis.statusContainer.clear();\n\t\t\t\tthis.loadingAnimation = new Loader(this.ui, \"Working... (esc to interrupt)\");\n\t\t\t\tthis.statusContainer.addChild(this.loadingAnimation);\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"message_start\":\n\t\t\t\tif (event.message.role === \"user\") {\n\t\t\t\t\t// Show user message immediately and clear editor\n\t\t\t\t\tthis.addMessageToChat(event.message);\n\t\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t} else if (event.message.role === \"assistant\") {\n\t\t\t\t\t// Create assistant component for streaming\n\t\t\t\t\tthis.streamingComponent = new AssistantMessageComponent();\n\t\t\t\t\tthis.chatContainer.addChild(this.streamingComponent);\n\t\t\t\t\tthis.streamingComponent.updateContent(event.message as AssistantMessage);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"message_update\":\n\t\t\t\t// Update streaming component\n\t\t\t\tif (this.streamingComponent && event.message.role === \"assistant\") {\n\t\t\t\t\tconst assistantMsg = event.message as AssistantMessage;\n\t\t\t\t\tthis.streamingComponent.updateContent(assistantMsg);\n\n\t\t\t\t\t// Create tool execution components as soon as we see tool calls\n\t\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\t\tif (content.type === \"toolCall\") {\n\t\t\t\t\t\t\t// Only create if we haven't created it yet\n\t\t\t\t\t\t\tif (!this.pendingTools.has(content.id)) {\n\t\t\t\t\t\t\t\tthis.chatContainer.addChild(new Text(\"\", 0, 0));\n\t\t\t\t\t\t\t\tconst component = new ToolExecutionComponent(content.name, content.arguments);\n\t\t\t\t\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\t\t\t\t\tthis.pendingTools.set(content.id, component);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Update existing component with latest arguments as they stream\n\t\t\t\t\t\t\t\tconst component = this.pendingTools.get(content.id);\n\t\t\t\t\t\t\t\tif (component) {\n\t\t\t\t\t\t\t\t\tcomponent.updateArgs(content.arguments);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"message_end\":\n\t\t\t\t// Skip user messages (already shown in message_start)\n\t\t\t\tif (event.message.role === \"user\") {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (this.streamingComponent && event.message.role === \"assistant\") {\n\t\t\t\t\tconst assistantMsg = event.message as AssistantMessage;\n\n\t\t\t\t\t// Update streaming component with final message (includes stopReason)\n\t\t\t\t\tthis.streamingComponent.updateContent(assistantMsg);\n\n\t\t\t\t\t// If message was aborted or errored, mark all pending tool components as failed\n\t\t\t\t\tif (assistantMsg.stopReason === \"aborted\" || assistantMsg.stopReason === \"error\") {\n\t\t\t\t\t\tconst errorMessage =\n\t\t\t\t\t\t\tassistantMsg.stopReason === \"aborted\" ? \"Operation aborted\" : assistantMsg.errorMessage || \"Error\";\n\t\t\t\t\t\tfor (const [toolCallId, component] of this.pendingTools.entries()) {\n\t\t\t\t\t\t\tcomponent.updateResult({\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: errorMessage }],\n\t\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.pendingTools.clear();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Keep the streaming component - it's now the final assistant message\n\t\t\t\t\tthis.streamingComponent = null;\n\t\t\t\t}\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"tool_execution_start\": {\n\t\t\t\t// Component should already exist from message_update, but create if missing\n\t\t\t\tif (!this.pendingTools.has(event.toolCallId)) {\n\t\t\t\t\tconst component = new ToolExecutionComponent(event.toolName, event.args);\n\t\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\t\tthis.pendingTools.set(event.toolCallId, component);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"tool_execution_end\": {\n\t\t\t\t// Update the existing tool component with the result\n\t\t\t\tconst component = this.pendingTools.get(event.toolCallId);\n\t\t\t\tif (component) {\n\t\t\t\t\tcomponent.updateResult(event.result);\n\t\t\t\t\tthis.pendingTools.delete(event.toolCallId);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"agent_end\":\n\t\t\t\t// Stop loading animation\n\t\t\t\tif (this.loadingAnimation) {\n\t\t\t\t\tthis.loadingAnimation.stop();\n\t\t\t\t\tthis.loadingAnimation = null;\n\t\t\t\t\tthis.statusContainer.clear();\n\t\t\t\t}\n\t\t\t\tif (this.streamingComponent) {\n\t\t\t\t\tthis.chatContainer.removeChild(this.streamingComponent);\n\t\t\t\t\tthis.streamingComponent = null;\n\t\t\t\t}\n\t\t\t\tthis.pendingTools.clear();\n\t\t\t\tthis.editor.disableSubmit = false;\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprivate addMessageToChat(message: Message): void {\n\t\tif (message.role === \"user\") {\n\t\t\tconst userMsg = message as any;\n\t\t\t// Extract text content from content blocks\n\t\t\tconst textBlocks = userMsg.content.filter((c: any) => c.type === \"text\");\n\t\t\tconst textContent = textBlocks.map((c: any) => c.text).join(\"\");\n\t\t\tif (textContent) {\n\t\t\t\tconst userComponent = new UserMessageComponent(textContent, this.isFirstUserMessage);\n\t\t\t\tthis.chatContainer.addChild(userComponent);\n\t\t\t\tthis.isFirstUserMessage = false;\n\t\t\t}\n\t\t} else if (message.role === \"assistant\") {\n\t\t\tconst assistantMsg = message as AssistantMessage;\n\n\t\t\t// Add assistant message component\n\t\t\tconst assistantComponent = new AssistantMessageComponent(assistantMsg);\n\t\t\tthis.chatContainer.addChild(assistantComponent);\n\t\t}\n\t\t// Note: tool calls and results are now handled via tool_execution_start/end events\n\t}\n\n\trenderInitialMessages(state: AgentState): void {\n\t\t// Render all existing messages (for --continue mode)\n\t\t// Reset first user message flag for initial render\n\t\tthis.isFirstUserMessage = true;\n\n\t\t// Render messages\n\t\tfor (let i = 0; i < state.messages.length; i++) {\n\t\t\tconst message = state.messages[i];\n\n\t\t\tif (message.role === \"user\") {\n\t\t\t\tconst userMsg = message as any;\n\t\t\t\tconst textBlocks = userMsg.content.filter((c: any) => c.type === \"text\");\n\t\t\t\tconst textContent = textBlocks.map((c: any) => c.text).join(\"\");\n\t\t\t\tif (textContent) {\n\t\t\t\t\tconst userComponent = new UserMessageComponent(textContent, this.isFirstUserMessage);\n\t\t\t\t\tthis.chatContainer.addChild(userComponent);\n\t\t\t\t\tthis.isFirstUserMessage = false;\n\t\t\t\t}\n\t\t\t} else if (message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\tconst assistantComponent = new AssistantMessageComponent(assistantMsg);\n\t\t\t\tthis.chatContainer.addChild(assistantComponent);\n\n\t\t\t\t// Create tool execution components for any tool calls\n\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\tif (content.type === \"toolCall\") {\n\t\t\t\t\t\tconst component = new ToolExecutionComponent(content.name, content.arguments);\n\t\t\t\t\t\tthis.chatContainer.addChild(component);\n\n\t\t\t\t\t\t// If message was aborted/errored, immediately mark tool as failed\n\t\t\t\t\t\tif (assistantMsg.stopReason === \"aborted\" || assistantMsg.stopReason === \"error\") {\n\t\t\t\t\t\t\tconst errorMessage =\n\t\t\t\t\t\t\t\tassistantMsg.stopReason === \"aborted\"\n\t\t\t\t\t\t\t\t\t? \"Operation aborted\"\n\t\t\t\t\t\t\t\t\t: assistantMsg.errorMessage || \"Error\";\n\t\t\t\t\t\t\tcomponent.updateResult({\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: errorMessage }],\n\t\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Store in map so we can update with results later\n\t\t\t\t\t\t\tthis.pendingTools.set(content.id, component);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (message.role === \"toolResult\") {\n\t\t\t\t// Update existing tool execution component with results\t\t\t\t;\n\t\t\t\tconst component = this.pendingTools.get(message.toolCallId);\n\t\t\t\tif (component) {\n\t\t\t\t\tcomponent.updateResult({\n\t\t\t\t\t\tcontent: message.content,\n\t\t\t\t\t\tdetails: message.details,\n\t\t\t\t\t\tisError: message.isError,\n\t\t\t\t\t});\n\t\t\t\t\t// Remove from pending map since it's complete\n\t\t\t\t\tthis.pendingTools.delete(message.toolCallId);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Clear pending tools after rendering initial messages\n\t\tthis.pendingTools.clear();\n\t\tthis.ui.requestRender();\n\t}\n\n\tasync getUserInput(): Promise<string> {\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.onInputCallback = (text: string) => {\n\t\t\t\tthis.onInputCallback = undefined;\n\t\t\t\tresolve(text);\n\t\t\t};\n\t\t});\n\t}\n\n\tsetInterruptCallback(callback: () => void): void {\n\t\tthis.onInterruptCallback = callback;\n\t}\n\n\tprivate handleCtrlC(): void {\n\t\t// Handle Ctrl+C double-press logic\n\t\tconst now = Date.now();\n\t\tconst timeSinceLastCtrlC = now - this.lastSigintTime;\n\n\t\tif (timeSinceLastCtrlC < 500) {\n\t\t\t// Second Ctrl+C within 500ms - exit\n\t\t\tthis.stop();\n\t\t\tprocess.exit(0);\n\t\t} else {\n\t\t\t// First Ctrl+C - clear the editor\n\t\t\tthis.clearEditor();\n\t\t\tthis.lastSigintTime = now;\n\t\t}\n\t}\n\n\tclearEditor(): void {\n\t\tthis.editor.setText(\"\");\n\t\tthis.ui.requestRender();\n\t}\n\n\tshowError(errorMessage: string): void {\n\t\t// Show error message in the chat\n\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\tthis.chatContainer.addChild(new Text(chalk.red(`Error: ${errorMessage}`), 1, 0));\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate showThinkingSelector(): void {\n\t\t// Create thinking selector with current level\n\t\tthis.thinkingSelector = new ThinkingSelectorComponent(\n\t\t\tthis.agent.state.thinkingLevel,\n\t\t\t(level) => {\n\t\t\t\t// Apply the selected thinking level\n\t\t\t\tthis.agent.setThinkingLevel(level);\n\n\t\t\t\t// Save thinking level change to session\n\t\t\t\tthis.sessionManager.saveThinkingLevelChange(level);\n\n\t\t\t\t// Show confirmation message with proper spacing\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\tconst confirmText = new Text(chalk.dim(`Thinking level: ${level}`), 1, 0);\n\t\t\t\tthis.chatContainer.addChild(confirmText);\n\n\t\t\t\t// Hide selector and show editor again\n\t\t\t\tthis.hideThinkingSelector();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t},\n\t\t\t() => {\n\t\t\t\t// Just hide the selector\n\t\t\t\tthis.hideThinkingSelector();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t},\n\t\t);\n\n\t\t// Replace editor with selector\n\t\tthis.editorContainer.clear();\n\t\tthis.editorContainer.addChild(this.thinkingSelector);\n\t\tthis.ui.setFocus(this.thinkingSelector.getSelectList());\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate hideThinkingSelector(): void {\n\t\t// Replace selector with editor in the container\n\t\tthis.editorContainer.clear();\n\t\tthis.editorContainer.addChild(this.editor);\n\t\tthis.thinkingSelector = null;\n\t\tthis.ui.setFocus(this.editor);\n\t}\n\n\tprivate showModelSelector(): void {\n\t\t// Create model selector with current model\n\t\tthis.modelSelector = new ModelSelectorComponent(\n\t\t\tthis.agent.state.model,\n\t\t\t(model) => {\n\t\t\t\t// Apply the selected model\n\t\t\t\tthis.agent.setModel(model);\n\n\t\t\t\t// Save model change to session\n\t\t\t\tthis.sessionManager.saveModelChange(`${model.provider}/${model.id}`);\n\n\t\t\t\t// Show confirmation message with proper spacing\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\tconst confirmText = new Text(chalk.dim(`Model: ${model.id}`), 1, 0);\n\t\t\t\tthis.chatContainer.addChild(confirmText);\n\n\t\t\t\t// Hide selector and show editor again\n\t\t\t\tthis.hideModelSelector();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t},\n\t\t\t() => {\n\t\t\t\t// Just hide the selector\n\t\t\t\tthis.hideModelSelector();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t},\n\t\t);\n\n\t\t// Replace editor with selector\n\t\tthis.editorContainer.clear();\n\t\tthis.editorContainer.addChild(this.modelSelector);\n\t\tthis.ui.setFocus(this.modelSelector);\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate hideModelSelector(): void {\n\t\t// Replace selector with editor in the container\n\t\tthis.editorContainer.clear();\n\t\tthis.editorContainer.addChild(this.editor);\n\t\tthis.modelSelector = null;\n\t\tthis.ui.setFocus(this.editor);\n\t}\n\n\tprivate handleExportCommand(text: string): void {\n\t\t// Parse optional filename from command: /export [filename]\n\t\tconst parts = text.split(/\\s+/);\n\t\tconst outputPath = parts.length > 1 ? parts[1] : undefined;\n\n\t\ttry {\n\t\t\t// Export session to HTML\n\t\t\tconst filePath = exportSessionToHtml(this.sessionManager, this.agent.state, outputPath);\n\n\t\t\t// Show success message in chat - matching thinking level style\n\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\tthis.chatContainer.addChild(new Text(chalk.dim(`Session exported to: ${filePath}`), 1, 0));\n\t\t\tthis.ui.requestRender();\n\t\t} catch (error: any) {\n\t\t\t// Show error message in chat\n\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\tthis.chatContainer.addChild(\n\t\t\t\tnew Text(chalk.red(`Failed to export session: ${error.message || \"Unknown error\"}`), 1, 0),\n\t\t\t);\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n\n\tprivate handleSessionCommand(): void {\n\t\t// Get session info\n\t\tconst sessionFile = this.sessionManager.getSessionFile();\n\t\tconst state = this.agent.state;\n\n\t\t// Count messages\n\t\tconst userMessages = state.messages.filter((m) => m.role === \"user\").length;\n\t\tconst assistantMessages = state.messages.filter((m) => m.role === \"assistant\").length;\n\t\tconst totalMessages = state.messages.length;\n\n\t\t// Calculate cumulative usage from all assistant messages (same as footer)\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const message of state.messages) {\n\t\t\tif (message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\ttotalInput += assistantMsg.usage.input;\n\t\t\t\ttotalOutput += assistantMsg.usage.output;\n\t\t\t\ttotalCacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\ttotalCost += assistantMsg.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\tconst totalTokens = totalInput + totalOutput + totalCacheRead + totalCacheWrite;\n\n\t\t// Build info text\n\t\tlet info = `${chalk.bold(\"Session Info\")}\\n\\n`;\n\t\tinfo += `${chalk.dim(\"File:\")} ${sessionFile}\\n`;\n\t\tinfo += `${chalk.dim(\"ID:\")} ${this.sessionManager.getSessionId()}\\n\\n`;\n\t\tinfo += `${chalk.bold(\"Messages\")}\\n`;\n\t\tinfo += `${chalk.dim(\"User:\")} ${userMessages}\\n`;\n\t\tinfo += `${chalk.dim(\"Assistant:\")} ${assistantMessages}\\n`;\n\t\tinfo += `${chalk.dim(\"Total:\")} ${totalMessages}\\n\\n`;\n\t\tinfo += `${chalk.bold(\"Tokens\")}\\n`;\n\t\tinfo += `${chalk.dim(\"Input:\")} ${totalInput.toLocaleString()}\\n`;\n\t\tinfo += `${chalk.dim(\"Output:\")} ${totalOutput.toLocaleString()}\\n`;\n\t\tif (totalCacheRead > 0) {\n\t\t\tinfo += `${chalk.dim(\"Cache Read:\")} ${totalCacheRead.toLocaleString()}\\n`;\n\t\t}\n\t\tif (totalCacheWrite > 0) {\n\t\t\tinfo += `${chalk.dim(\"Cache Write:\")} ${totalCacheWrite.toLocaleString()}\\n`;\n\t\t}\n\t\tinfo += `${chalk.dim(\"Total:\")} ${totalTokens.toLocaleString()}\\n`;\n\n\t\tif (totalCost > 0) {\n\t\t\tinfo += `\\n${chalk.bold(\"Cost\")}\\n`;\n\t\t\tinfo += `${chalk.dim(\"Total:\")} ${totalCost.toFixed(4)}`;\n\t\t}\n\n\t\t// Show info in chat\n\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\tthis.chatContainer.addChild(new Text(info, 1, 0));\n\t\tthis.ui.requestRender();\n\t}\n\n\tstop(): void {\n\t\tif (this.loadingAnimation) {\n\t\t\tthis.loadingAnimation.stop();\n\t\t\tthis.loadingAnimation = null;\n\t\t}\n\t\tif (this.isInitialized) {\n\t\t\tthis.ui.stop();\n\t\t\tthis.isInitialized = false;\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"tui-renderer.d.ts","sourceRoot":"","sources":["../../src/tui/tui-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAc5E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAS5D;;GAEG;AACH,qBAAa,WAAW;IACvB,OAAO,CAAC,EAAE,CAAM;IAChB,OAAO,CAAC,aAAa,CAAY;IACjC,OAAO,CAAC,eAAe,CAAY;IACnC,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,eAAe,CAAY;IACnC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,eAAe,CAAC,CAAyB;IACjD,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,mBAAmB,CAAC,CAAa;IACzC,OAAO,CAAC,cAAc,CAAK;IAG3B,OAAO,CAAC,kBAAkB,CAA0C;IAGpE,OAAO,CAAC,YAAY,CAA6C;IAGjE,OAAO,CAAC,gBAAgB,CAA0C;IAGlE,OAAO,CAAC,aAAa,CAAuC;IAG5D,OAAO,CAAC,kBAAkB,CAAQ;IAElC,YAAY,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAuCxE;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CA2F1B;IAEK,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAsIrE;IAED,OAAO,CAAC,gBAAgB;IAqBxB,qBAAqB,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CA8D7C;IAEK,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAOpC;IAED,oBAAoB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAE/C;IAED,OAAO,CAAC,WAAW;IAgBnB,WAAW,IAAI,IAAI,CAGlB;IAED,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAKpC;IAED,OAAO,CAAC,oBAAoB;IAkC5B,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,iBAAiB;IAkCzB,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,mBAAmB;IAuB3B,OAAO,CAAC,oBAAoB;IAwE5B,IAAI,IAAI,IAAI,CASX;CACD","sourcesContent":["import type { Agent, AgentEvent, AgentState } from \"@mariozechner/pi-agent\";\nimport type { AssistantMessage, Message } from \"@mariozechner/pi-ai\";\nimport type { SlashCommand } from \"@mariozechner/pi-tui\";\nimport {\n\tCombinedAutocompleteProvider,\n\tContainer,\n\tLoader,\n\tProcessTerminal,\n\tSpacer,\n\tText,\n\tTUI,\n} from \"@mariozechner/pi-tui\";\nimport chalk from \"chalk\";\nimport { exportSessionToHtml } from \"../export-html.js\";\nimport type { SessionManager } from \"../session-manager.js\";\nimport { AssistantMessageComponent } from \"./assistant-message.js\";\nimport { CustomEditor } from \"./custom-editor.js\";\nimport { FooterComponent } from \"./footer.js\";\nimport { ModelSelectorComponent } from \"./model-selector.js\";\nimport { ThinkingSelectorComponent } from \"./thinking-selector.js\";\nimport { ToolExecutionComponent } from \"./tool-execution.js\";\nimport { UserMessageComponent } from \"./user-message.js\";\n\n/**\n * TUI renderer for the coding agent\n */\nexport class TuiRenderer {\n\tprivate ui: TUI;\n\tprivate chatContainer: Container;\n\tprivate statusContainer: Container;\n\tprivate editor: CustomEditor;\n\tprivate editorContainer: Container; // Container to swap between editor and selector\n\tprivate footer: FooterComponent;\n\tprivate agent: Agent;\n\tprivate sessionManager: SessionManager;\n\tprivate version: string;\n\tprivate isInitialized = false;\n\tprivate onInputCallback?: (text: string) => void;\n\tprivate loadingAnimation: Loader | null = null;\n\tprivate onInterruptCallback?: () => void;\n\tprivate lastSigintTime = 0;\n\n\t// Streaming message tracking\n\tprivate streamingComponent: AssistantMessageComponent | null = null;\n\n\t// Tool execution tracking: toolCallId -> component\n\tprivate pendingTools = new Map<string, ToolExecutionComponent>();\n\n\t// Thinking level selector\n\tprivate thinkingSelector: ThinkingSelectorComponent | null = null;\n\n\t// Model selector\n\tprivate modelSelector: ModelSelectorComponent | null = null;\n\n\t// Track if this is the first user message (to skip spacer)\n\tprivate isFirstUserMessage = true;\n\n\tconstructor(agent: Agent, sessionManager: SessionManager, version: string) {\n\t\tthis.agent = agent;\n\t\tthis.sessionManager = sessionManager;\n\t\tthis.version = version;\n\t\tthis.ui = new TUI(new ProcessTerminal());\n\t\tthis.chatContainer = new Container();\n\t\tthis.statusContainer = new Container();\n\t\tthis.editor = new CustomEditor();\n\t\tthis.editorContainer = new Container(); // Container to hold editor or selector\n\t\tthis.editorContainer.addChild(this.editor); // Start with editor\n\t\tthis.footer = new FooterComponent(agent.state);\n\n\t\t// Define slash commands\n\t\tconst thinkingCommand: SlashCommand = {\n\t\t\tname: \"thinking\",\n\t\t\tdescription: \"Select reasoning level (opens selector UI)\",\n\t\t};\n\n\t\tconst modelCommand: SlashCommand = {\n\t\t\tname: \"model\",\n\t\t\tdescription: \"Select model (opens selector UI)\",\n\t\t};\n\n\t\tconst exportCommand: SlashCommand = {\n\t\t\tname: \"export\",\n\t\t\tdescription: \"Export session to HTML file\",\n\t\t};\n\n\t\tconst sessionCommand: SlashCommand = {\n\t\t\tname: \"session\",\n\t\t\tdescription: \"Show session info and stats\",\n\t\t};\n\n\t\t// Setup autocomplete for file paths and slash commands\n\t\tconst autocompleteProvider = new CombinedAutocompleteProvider(\n\t\t\t[thinkingCommand, modelCommand, exportCommand, sessionCommand],\n\t\t\tprocess.cwd(),\n\t\t);\n\t\tthis.editor.setAutocompleteProvider(autocompleteProvider);\n\t}\n\n\tasync init(): Promise<void> {\n\t\tif (this.isInitialized) return;\n\n\t\t// Add header with logo and instructions\n\t\tconst logo = chalk.bold.cyan(\"pi\") + chalk.dim(` v${this.version}`);\n\t\tconst instructions =\n\t\t\tchalk.dim(\"esc\") +\n\t\t\tchalk.gray(\" to interrupt\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"ctrl+c\") +\n\t\t\tchalk.gray(\" to clear\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"ctrl+c twice\") +\n\t\t\tchalk.gray(\" to exit\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"ctrl+k\") +\n\t\t\tchalk.gray(\" to delete line\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"/\") +\n\t\t\tchalk.gray(\" for commands\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"drop files\") +\n\t\t\tchalk.gray(\" to attach\");\n\t\tconst header = new Text(logo + \"\\n\" + instructions, 1, 0);\n\n\t\t// Setup UI layout\n\t\tthis.ui.addChild(new Spacer(1));\n\t\tthis.ui.addChild(header);\n\t\tthis.ui.addChild(new Spacer(1));\n\t\tthis.ui.addChild(this.chatContainer);\n\t\tthis.ui.addChild(this.statusContainer);\n\t\tthis.ui.addChild(new Spacer(1));\n\t\tthis.ui.addChild(this.editorContainer); // Use container that can hold editor or selector\n\t\tthis.ui.addChild(this.footer);\n\t\tthis.ui.setFocus(this.editor);\n\n\t\t// Set up custom key handlers on the editor\n\t\tthis.editor.onEscape = () => {\n\t\t\t// Intercept Escape key when processing\n\t\t\tif (this.loadingAnimation && this.onInterruptCallback) {\n\t\t\t\tthis.onInterruptCallback();\n\t\t\t}\n\t\t};\n\n\t\tthis.editor.onCtrlC = () => {\n\t\t\tthis.handleCtrlC();\n\t\t};\n\n\t\t// Handle editor submission\n\t\tthis.editor.onSubmit = (text: string) => {\n\t\t\ttext = text.trim();\n\t\t\tif (!text) return;\n\n\t\t\t// Check for /thinking command\n\t\t\tif (text === \"/thinking\") {\n\t\t\t\t// Show thinking level selector\n\t\t\t\tthis.showThinkingSelector();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check for /model command\n\t\t\tif (text === \"/model\") {\n\t\t\t\t// Show model selector\n\t\t\t\tthis.showModelSelector();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check for /export command\n\t\t\tif (text.startsWith(\"/export\")) {\n\t\t\t\tthis.handleExportCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check for /session command\n\t\t\tif (text === \"/session\") {\n\t\t\t\tthis.handleSessionCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this.onInputCallback) {\n\t\t\t\tthis.onInputCallback(text);\n\t\t\t}\n\t\t};\n\n\t\t// Start the UI\n\t\tthis.ui.start();\n\t\tthis.isInitialized = true;\n\t}\n\n\tasync handleEvent(event: AgentEvent, state: AgentState): Promise<void> {\n\t\tif (!this.isInitialized) {\n\t\t\tawait this.init();\n\t\t}\n\n\t\t// Update footer with current stats\n\t\tthis.footer.updateState(state);\n\n\t\tswitch (event.type) {\n\t\t\tcase \"agent_start\":\n\t\t\t\t// Show loading animation\n\t\t\t\tthis.editor.disableSubmit = true;\n\t\t\t\t// Stop old loader before clearing\n\t\t\t\tif (this.loadingAnimation) {\n\t\t\t\t\tthis.loadingAnimation.stop();\n\t\t\t\t}\n\t\t\t\tthis.statusContainer.clear();\n\t\t\t\tthis.loadingAnimation = new Loader(this.ui, \"Working... (esc to interrupt)\");\n\t\t\t\tthis.statusContainer.addChild(this.loadingAnimation);\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"message_start\":\n\t\t\t\tif (event.message.role === \"user\") {\n\t\t\t\t\t// Show user message immediately and clear editor\n\t\t\t\t\tthis.addMessageToChat(event.message);\n\t\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t} else if (event.message.role === \"assistant\") {\n\t\t\t\t\t// Create assistant component for streaming\n\t\t\t\t\tthis.streamingComponent = new AssistantMessageComponent();\n\t\t\t\t\tthis.chatContainer.addChild(this.streamingComponent);\n\t\t\t\t\tthis.streamingComponent.updateContent(event.message as AssistantMessage);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"message_update\":\n\t\t\t\t// Update streaming component\n\t\t\t\tif (this.streamingComponent && event.message.role === \"assistant\") {\n\t\t\t\t\tconst assistantMsg = event.message as AssistantMessage;\n\t\t\t\t\tthis.streamingComponent.updateContent(assistantMsg);\n\n\t\t\t\t\t// Create tool execution components as soon as we see tool calls\n\t\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\t\tif (content.type === \"toolCall\") {\n\t\t\t\t\t\t\t// Only create if we haven't created it yet\n\t\t\t\t\t\t\tif (!this.pendingTools.has(content.id)) {\n\t\t\t\t\t\t\t\tthis.chatContainer.addChild(new Text(\"\", 0, 0));\n\t\t\t\t\t\t\t\tconst component = new ToolExecutionComponent(content.name, content.arguments);\n\t\t\t\t\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\t\t\t\t\tthis.pendingTools.set(content.id, component);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Update existing component with latest arguments as they stream\n\t\t\t\t\t\t\t\tconst component = this.pendingTools.get(content.id);\n\t\t\t\t\t\t\t\tif (component) {\n\t\t\t\t\t\t\t\t\tcomponent.updateArgs(content.arguments);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"message_end\":\n\t\t\t\t// Skip user messages (already shown in message_start)\n\t\t\t\tif (event.message.role === \"user\") {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (this.streamingComponent && event.message.role === \"assistant\") {\n\t\t\t\t\tconst assistantMsg = event.message as AssistantMessage;\n\n\t\t\t\t\t// Update streaming component with final message (includes stopReason)\n\t\t\t\t\tthis.streamingComponent.updateContent(assistantMsg);\n\n\t\t\t\t\t// If message was aborted or errored, mark all pending tool components as failed\n\t\t\t\t\tif (assistantMsg.stopReason === \"aborted\" || assistantMsg.stopReason === \"error\") {\n\t\t\t\t\t\tconst errorMessage =\n\t\t\t\t\t\t\tassistantMsg.stopReason === \"aborted\" ? \"Operation aborted\" : assistantMsg.errorMessage || \"Error\";\n\t\t\t\t\t\tfor (const [toolCallId, component] of this.pendingTools.entries()) {\n\t\t\t\t\t\t\tcomponent.updateResult({\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: errorMessage }],\n\t\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.pendingTools.clear();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Keep the streaming component - it's now the final assistant message\n\t\t\t\t\tthis.streamingComponent = null;\n\t\t\t\t}\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"tool_execution_start\": {\n\t\t\t\t// Component should already exist from message_update, but create if missing\n\t\t\t\tif (!this.pendingTools.has(event.toolCallId)) {\n\t\t\t\t\tconst component = new ToolExecutionComponent(event.toolName, event.args);\n\t\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\t\tthis.pendingTools.set(event.toolCallId, component);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"tool_execution_end\": {\n\t\t\t\t// Update the existing tool component with the result\n\t\t\t\tconst component = this.pendingTools.get(event.toolCallId);\n\t\t\t\tif (component) {\n\t\t\t\t\tcomponent.updateResult(event.result);\n\t\t\t\t\tthis.pendingTools.delete(event.toolCallId);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"agent_end\":\n\t\t\t\t// Stop loading animation\n\t\t\t\tif (this.loadingAnimation) {\n\t\t\t\t\tthis.loadingAnimation.stop();\n\t\t\t\t\tthis.loadingAnimation = null;\n\t\t\t\t\tthis.statusContainer.clear();\n\t\t\t\t}\n\t\t\t\tif (this.streamingComponent) {\n\t\t\t\t\tthis.chatContainer.removeChild(this.streamingComponent);\n\t\t\t\t\tthis.streamingComponent = null;\n\t\t\t\t}\n\t\t\t\tthis.pendingTools.clear();\n\t\t\t\tthis.editor.disableSubmit = false;\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprivate addMessageToChat(message: Message): void {\n\t\tif (message.role === \"user\") {\n\t\t\tconst userMsg = message as any;\n\t\t\t// Extract text content from content blocks\n\t\t\tconst textBlocks = userMsg.content.filter((c: any) => c.type === \"text\");\n\t\t\tconst textContent = textBlocks.map((c: any) => c.text).join(\"\");\n\t\t\tif (textContent) {\n\t\t\t\tconst userComponent = new UserMessageComponent(textContent, this.isFirstUserMessage);\n\t\t\t\tthis.chatContainer.addChild(userComponent);\n\t\t\t\tthis.isFirstUserMessage = false;\n\t\t\t}\n\t\t} else if (message.role === \"assistant\") {\n\t\t\tconst assistantMsg = message as AssistantMessage;\n\n\t\t\t// Add assistant message component\n\t\t\tconst assistantComponent = new AssistantMessageComponent(assistantMsg);\n\t\t\tthis.chatContainer.addChild(assistantComponent);\n\t\t}\n\t\t// Note: tool calls and results are now handled via tool_execution_start/end events\n\t}\n\n\trenderInitialMessages(state: AgentState): void {\n\t\t// Render all existing messages (for --continue mode)\n\t\t// Reset first user message flag for initial render\n\t\tthis.isFirstUserMessage = true;\n\n\t\t// Render messages\n\t\tfor (let i = 0; i < state.messages.length; i++) {\n\t\t\tconst message = state.messages[i];\n\n\t\t\tif (message.role === \"user\") {\n\t\t\t\tconst userMsg = message as any;\n\t\t\t\tconst textBlocks = userMsg.content.filter((c: any) => c.type === \"text\");\n\t\t\t\tconst textContent = textBlocks.map((c: any) => c.text).join(\"\");\n\t\t\t\tif (textContent) {\n\t\t\t\t\tconst userComponent = new UserMessageComponent(textContent, this.isFirstUserMessage);\n\t\t\t\t\tthis.chatContainer.addChild(userComponent);\n\t\t\t\t\tthis.isFirstUserMessage = false;\n\t\t\t\t}\n\t\t\t} else if (message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\tconst assistantComponent = new AssistantMessageComponent(assistantMsg);\n\t\t\t\tthis.chatContainer.addChild(assistantComponent);\n\n\t\t\t\t// Create tool execution components for any tool calls\n\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\tif (content.type === \"toolCall\") {\n\t\t\t\t\t\tconst component = new ToolExecutionComponent(content.name, content.arguments);\n\t\t\t\t\t\tthis.chatContainer.addChild(component);\n\n\t\t\t\t\t\t// If message was aborted/errored, immediately mark tool as failed\n\t\t\t\t\t\tif (assistantMsg.stopReason === \"aborted\" || assistantMsg.stopReason === \"error\") {\n\t\t\t\t\t\t\tconst errorMessage =\n\t\t\t\t\t\t\t\tassistantMsg.stopReason === \"aborted\"\n\t\t\t\t\t\t\t\t\t? \"Operation aborted\"\n\t\t\t\t\t\t\t\t\t: assistantMsg.errorMessage || \"Error\";\n\t\t\t\t\t\t\tcomponent.updateResult({\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: errorMessage }],\n\t\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Store in map so we can update with results later\n\t\t\t\t\t\t\tthis.pendingTools.set(content.id, component);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (message.role === \"toolResult\") {\n\t\t\t\t// Update existing tool execution component with results\t\t\t\t;\n\t\t\t\tconst component = this.pendingTools.get(message.toolCallId);\n\t\t\t\tif (component) {\n\t\t\t\t\tcomponent.updateResult({\n\t\t\t\t\t\tcontent: message.content,\n\t\t\t\t\t\tdetails: message.details,\n\t\t\t\t\t\tisError: message.isError,\n\t\t\t\t\t});\n\t\t\t\t\t// Remove from pending map since it's complete\n\t\t\t\t\tthis.pendingTools.delete(message.toolCallId);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Clear pending tools after rendering initial messages\n\t\tthis.pendingTools.clear();\n\t\tthis.ui.requestRender();\n\t}\n\n\tasync getUserInput(): Promise<string> {\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.onInputCallback = (text: string) => {\n\t\t\t\tthis.onInputCallback = undefined;\n\t\t\t\tresolve(text);\n\t\t\t};\n\t\t});\n\t}\n\n\tsetInterruptCallback(callback: () => void): void {\n\t\tthis.onInterruptCallback = callback;\n\t}\n\n\tprivate handleCtrlC(): void {\n\t\t// Handle Ctrl+C double-press logic\n\t\tconst now = Date.now();\n\t\tconst timeSinceLastCtrlC = now - this.lastSigintTime;\n\n\t\tif (timeSinceLastCtrlC < 500) {\n\t\t\t// Second Ctrl+C within 500ms - exit\n\t\t\tthis.stop();\n\t\t\tprocess.exit(0);\n\t\t} else {\n\t\t\t// First Ctrl+C - clear the editor\n\t\t\tthis.clearEditor();\n\t\t\tthis.lastSigintTime = now;\n\t\t}\n\t}\n\n\tclearEditor(): void {\n\t\tthis.editor.setText(\"\");\n\t\tthis.ui.requestRender();\n\t}\n\n\tshowError(errorMessage: string): void {\n\t\t// Show error message in the chat\n\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\tthis.chatContainer.addChild(new Text(chalk.red(`Error: ${errorMessage}`), 1, 0));\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate showThinkingSelector(): void {\n\t\t// Create thinking selector with current level\n\t\tthis.thinkingSelector = new ThinkingSelectorComponent(\n\t\t\tthis.agent.state.thinkingLevel,\n\t\t\t(level) => {\n\t\t\t\t// Apply the selected thinking level\n\t\t\t\tthis.agent.setThinkingLevel(level);\n\n\t\t\t\t// Save thinking level change to session\n\t\t\t\tthis.sessionManager.saveThinkingLevelChange(level);\n\n\t\t\t\t// Show confirmation message with proper spacing\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\tconst confirmText = new Text(chalk.dim(`Thinking level: ${level}`), 1, 0);\n\t\t\t\tthis.chatContainer.addChild(confirmText);\n\n\t\t\t\t// Hide selector and show editor again\n\t\t\t\tthis.hideThinkingSelector();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t},\n\t\t\t() => {\n\t\t\t\t// Just hide the selector\n\t\t\t\tthis.hideThinkingSelector();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t},\n\t\t);\n\n\t\t// Replace editor with selector\n\t\tthis.editorContainer.clear();\n\t\tthis.editorContainer.addChild(this.thinkingSelector);\n\t\tthis.ui.setFocus(this.thinkingSelector.getSelectList());\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate hideThinkingSelector(): void {\n\t\t// Replace selector with editor in the container\n\t\tthis.editorContainer.clear();\n\t\tthis.editorContainer.addChild(this.editor);\n\t\tthis.thinkingSelector = null;\n\t\tthis.ui.setFocus(this.editor);\n\t}\n\n\tprivate showModelSelector(): void {\n\t\t// Create model selector with current model\n\t\tthis.modelSelector = new ModelSelectorComponent(\n\t\t\tthis.agent.state.model,\n\t\t\t(model) => {\n\t\t\t\t// Apply the selected model\n\t\t\t\tthis.agent.setModel(model);\n\n\t\t\t\t// Save model change to session\n\t\t\t\tthis.sessionManager.saveModelChange(`${model.provider}/${model.id}`);\n\n\t\t\t\t// Show confirmation message with proper spacing\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\tconst confirmText = new Text(chalk.dim(`Model: ${model.id}`), 1, 0);\n\t\t\t\tthis.chatContainer.addChild(confirmText);\n\n\t\t\t\t// Hide selector and show editor again\n\t\t\t\tthis.hideModelSelector();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t},\n\t\t\t() => {\n\t\t\t\t// Just hide the selector\n\t\t\t\tthis.hideModelSelector();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t},\n\t\t);\n\n\t\t// Replace editor with selector\n\t\tthis.editorContainer.clear();\n\t\tthis.editorContainer.addChild(this.modelSelector);\n\t\tthis.ui.setFocus(this.modelSelector);\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate hideModelSelector(): void {\n\t\t// Replace selector with editor in the container\n\t\tthis.editorContainer.clear();\n\t\tthis.editorContainer.addChild(this.editor);\n\t\tthis.modelSelector = null;\n\t\tthis.ui.setFocus(this.editor);\n\t}\n\n\tprivate handleExportCommand(text: string): void {\n\t\t// Parse optional filename from command: /export [filename]\n\t\tconst parts = text.split(/\\s+/);\n\t\tconst outputPath = parts.length > 1 ? parts[1] : undefined;\n\n\t\ttry {\n\t\t\t// Export session to HTML\n\t\t\tconst filePath = exportSessionToHtml(this.sessionManager, this.agent.state, outputPath);\n\n\t\t\t// Show success message in chat - matching thinking level style\n\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\tthis.chatContainer.addChild(new Text(chalk.dim(`Session exported to: ${filePath}`), 1, 0));\n\t\t\tthis.ui.requestRender();\n\t\t} catch (error: any) {\n\t\t\t// Show error message in chat\n\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\tthis.chatContainer.addChild(\n\t\t\t\tnew Text(chalk.red(`Failed to export session: ${error.message || \"Unknown error\"}`), 1, 0),\n\t\t\t);\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n\n\tprivate handleSessionCommand(): void {\n\t\t// Get session info\n\t\tconst sessionFile = this.sessionManager.getSessionFile();\n\t\tconst state = this.agent.state;\n\n\t\t// Count messages\n\t\tconst userMessages = state.messages.filter((m) => m.role === \"user\").length;\n\t\tconst assistantMessages = state.messages.filter((m) => m.role === \"assistant\").length;\n\t\tconst toolResults = state.messages.filter((m) => m.role === \"toolResult\").length;\n\t\tconst totalMessages = state.messages.length;\n\n\t\t// Count tool calls from assistant messages\n\t\tlet toolCalls = 0;\n\t\tfor (const message of state.messages) {\n\t\t\tif (message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\ttoolCalls += assistantMsg.content.filter((c) => c.type === \"toolCall\").length;\n\t\t\t}\n\t\t}\n\n\t\t// Calculate cumulative usage from all assistant messages (same as footer)\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const message of state.messages) {\n\t\t\tif (message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\ttotalInput += assistantMsg.usage.input;\n\t\t\t\ttotalOutput += assistantMsg.usage.output;\n\t\t\t\ttotalCacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\ttotalCost += assistantMsg.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\tconst totalTokens = totalInput + totalOutput + totalCacheRead + totalCacheWrite;\n\n\t\t// Build info text\n\t\tlet info = `${chalk.bold(\"Session Info\")}\\n\\n`;\n\t\tinfo += `${chalk.dim(\"File:\")} ${sessionFile}\\n`;\n\t\tinfo += `${chalk.dim(\"ID:\")} ${this.sessionManager.getSessionId()}\\n\\n`;\n\t\tinfo += `${chalk.bold(\"Messages\")}\\n`;\n\t\tinfo += `${chalk.dim(\"User:\")} ${userMessages}\\n`;\n\t\tinfo += `${chalk.dim(\"Assistant:\")} ${assistantMessages}\\n`;\n\t\tinfo += `${chalk.dim(\"Tool Calls:\")} ${toolCalls}\\n`;\n\t\tinfo += `${chalk.dim(\"Tool Results:\")} ${toolResults}\\n`;\n\t\tinfo += `${chalk.dim(\"Total:\")} ${totalMessages}\\n\\n`;\n\t\tinfo += `${chalk.bold(\"Tokens\")}\\n`;\n\t\tinfo += `${chalk.dim(\"Input:\")} ${totalInput.toLocaleString()}\\n`;\n\t\tinfo += `${chalk.dim(\"Output:\")} ${totalOutput.toLocaleString()}\\n`;\n\t\tif (totalCacheRead > 0) {\n\t\t\tinfo += `${chalk.dim(\"Cache Read:\")} ${totalCacheRead.toLocaleString()}\\n`;\n\t\t}\n\t\tif (totalCacheWrite > 0) {\n\t\t\tinfo += `${chalk.dim(\"Cache Write:\")} ${totalCacheWrite.toLocaleString()}\\n`;\n\t\t}\n\t\tinfo += `${chalk.dim(\"Total:\")} ${totalTokens.toLocaleString()}\\n`;\n\n\t\tif (totalCost > 0) {\n\t\t\tinfo += `\\n${chalk.bold(\"Cost\")}\\n`;\n\t\t\tinfo += `${chalk.dim(\"Total:\")} ${totalCost.toFixed(4)}`;\n\t\t}\n\n\t\t// Show info in chat\n\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\tthis.chatContainer.addChild(new Text(info, 1, 0));\n\t\tthis.ui.requestRender();\n\t}\n\n\tstop(): void {\n\t\tif (this.loadingAnimation) {\n\t\t\tthis.loadingAnimation.stop();\n\t\t\tthis.loadingAnimation = null;\n\t\t}\n\t\tif (this.isInitialized) {\n\t\t\tthis.ui.stop();\n\t\t\tthis.isInitialized = false;\n\t\t}\n\t}\n}\n"]}
@@ -480,7 +480,16 @@ export class TuiRenderer {
480
480
  // Count messages
481
481
  const userMessages = state.messages.filter((m) => m.role === "user").length;
482
482
  const assistantMessages = state.messages.filter((m) => m.role === "assistant").length;
483
+ const toolResults = state.messages.filter((m) => m.role === "toolResult").length;
483
484
  const totalMessages = state.messages.length;
485
+ // Count tool calls from assistant messages
486
+ let toolCalls = 0;
487
+ for (const message of state.messages) {
488
+ if (message.role === "assistant") {
489
+ const assistantMsg = message;
490
+ toolCalls += assistantMsg.content.filter((c) => c.type === "toolCall").length;
491
+ }
492
+ }
484
493
  // Calculate cumulative usage from all assistant messages (same as footer)
485
494
  let totalInput = 0;
486
495
  let totalOutput = 0;
@@ -505,6 +514,8 @@ export class TuiRenderer {
505
514
  info += `${chalk.bold("Messages")}\n`;
506
515
  info += `${chalk.dim("User:")} ${userMessages}\n`;
507
516
  info += `${chalk.dim("Assistant:")} ${assistantMessages}\n`;
517
+ info += `${chalk.dim("Tool Calls:")} ${toolCalls}\n`;
518
+ info += `${chalk.dim("Tool Results:")} ${toolResults}\n`;
508
519
  info += `${chalk.dim("Total:")} ${totalMessages}\n\n`;
509
520
  info += `${chalk.bold("Tokens")}\n`;
510
521
  info += `${chalk.dim("Input:")} ${totalInput.toLocaleString()}\n`;
@@ -1 +1 @@
1
- {"version":3,"file":"tui-renderer.js","sourceRoot":"","sources":["../../src/tui/tui-renderer.ts"],"names":[],"mappings":"AAGA,OAAO,EACN,4BAA4B,EAC5B,SAAS,EACT,MAAM,EACN,eAAe,EACf,MAAM,EACN,IAAI,EACJ,GAAG,GACH,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEzD;;GAEG;AACH,MAAM,OAAO,WAAW;IACf,EAAE,CAAM;IACR,aAAa,CAAY;IACzB,eAAe,CAAY;IAC3B,MAAM,CAAe;IACrB,eAAe,CAAY,CAAC,gDAAgD;IAC5E,MAAM,CAAkB;IACxB,KAAK,CAAQ;IACb,cAAc,CAAiB;IAC/B,OAAO,CAAS;IAChB,aAAa,GAAG,KAAK,CAAC;IACtB,eAAe,CAA0B;IACzC,gBAAgB,GAAkB,IAAI,CAAC;IACvC,mBAAmB,CAAc;IACjC,cAAc,GAAG,CAAC,CAAC;IAE3B,6BAA6B;IACrB,kBAAkB,GAAqC,IAAI,CAAC;IAEpE,mDAAmD;IAC3C,YAAY,GAAG,IAAI,GAAG,EAAkC,CAAC;IAEjE,0BAA0B;IAClB,gBAAgB,GAAqC,IAAI,CAAC;IAElE,iBAAiB;IACT,aAAa,GAAkC,IAAI,CAAC;IAE5D,2DAA2D;IACnD,kBAAkB,GAAG,IAAI,CAAC;IAElC,YAAY,KAAY,EAAE,cAA8B,EAAE,OAAe,EAAE;QAC1E,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACrC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,EAAE,GAAG,IAAI,GAAG,CAAC,IAAI,eAAe,EAAE,CAAC,CAAC;QACzC,IAAI,CAAC,aAAa,GAAG,IAAI,SAAS,EAAE,CAAC;QACrC,IAAI,CAAC,eAAe,GAAG,IAAI,SAAS,EAAE,CAAC;QACvC,IAAI,CAAC,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QACjC,IAAI,CAAC,eAAe,GAAG,IAAI,SAAS,EAAE,CAAC,CAAC,uCAAuC;QAC/E,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,oBAAoB;QAChE,IAAI,CAAC,MAAM,GAAG,IAAI,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAE/C,wBAAwB;QACxB,MAAM,eAAe,GAAiB;YACrC,IAAI,EAAE,UAAU;YAChB,WAAW,EAAE,4CAA4C;SACzD,CAAC;QAEF,MAAM,YAAY,GAAiB;YAClC,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,kCAAkC;SAC/C,CAAC;QAEF,MAAM,aAAa,GAAiB;YACnC,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,6BAA6B;SAC1C,CAAC;QAEF,MAAM,cAAc,GAAiB;YACpC,IAAI,EAAE,SAAS;YACf,WAAW,EAAE,6BAA6B;SAC1C,CAAC;QAEF,uDAAuD;QACvD,MAAM,oBAAoB,GAAG,IAAI,4BAA4B,CAC5D,CAAC,eAAe,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,CAAC,EAC9D,OAAO,CAAC,GAAG,EAAE,CACb,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,uBAAuB,CAAC,oBAAoB,CAAC,CAAC;IAAA,CAC1D;IAED,KAAK,CAAC,IAAI,GAAkB;QAC3B,IAAI,IAAI,CAAC,aAAa;YAAE,OAAO;QAE/B,wCAAwC;QACxC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QACpE,MAAM,YAAY,GACjB,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;YAChB,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC;YAC3B,IAAI;YACJ,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC;YACnB,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC;YACvB,IAAI;YACJ,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC;YACzB,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC;YACtB,IAAI;YACJ,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC;YACnB,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC;YAC7B,IAAI;YACJ,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;YACd,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC;YAC3B,IAAI;YACJ,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC1B,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAE1D,kBAAkB;QAClB,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzB,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACrC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACvC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,iDAAiD;QACzF,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAE9B,2CAA2C;QAC3C,IAAI,CAAC,MAAM,CAAC,QAAQ,GAAG,GAAG,EAAE,CAAC;YAC5B,uCAAuC;YACvC,IAAI,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;gBACvD,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC5B,CAAC;QAAA,CACD,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC;YAC3B,IAAI,CAAC,WAAW,EAAE,CAAC;QAAA,CACnB,CAAC;QAEF,2BAA2B;QAC3B,IAAI,CAAC,MAAM,CAAC,QAAQ,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC;YACxC,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YACnB,IAAI,CAAC,IAAI;gBAAE,OAAO;YAElB,8BAA8B;YAC9B,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;gBAC1B,+BAA+B;gBAC/B,IAAI,CAAC,oBAAoB,EAAE,CAAC;gBAC5B,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBACxB,OAAO;YACR,CAAC;YAED,2BAA2B;YAC3B,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACvB,sBAAsB;gBACtB,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBACzB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBACxB,OAAO;YACR,CAAC;YAED,4BAA4B;YAC5B,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBAChC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;gBAC/B,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBACxB,OAAO;YACR,CAAC;YAED,6BAA6B;YAC7B,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;gBACzB,IAAI,CAAC,oBAAoB,EAAE,CAAC;gBAC5B,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBACxB,OAAO;YACR,CAAC;YAED,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;gBAC1B,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC;QAAA,CACD,CAAC;QAEF,eAAe;QACf,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;QAChB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;IAAA,CAC1B;IAED,KAAK,CAAC,WAAW,CAAC,KAAiB,EAAE,KAAiB,EAAiB;QACtE,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACzB,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACnB,CAAC;QAED,mCAAmC;QACnC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAE/B,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACpB,KAAK,aAAa;gBACjB,yBAAyB;gBACzB,IAAI,CAAC,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC;gBACjC,kCAAkC;gBAClC,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;oBAC3B,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC;gBAC9B,CAAC;gBACD,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;gBAC7B,IAAI,CAAC,gBAAgB,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,+BAA+B,CAAC,CAAC;gBAC7E,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;gBACrD,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;gBACxB,MAAM;YAEP,KAAK,eAAe;gBACnB,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;oBACnC,iDAAiD;oBACjD,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;oBACrC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;oBACxB,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;gBACzB,CAAC;qBAAM,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;oBAC/C,2CAA2C;oBAC3C,IAAI,CAAC,kBAAkB,GAAG,IAAI,yBAAyB,EAAE,CAAC;oBAC1D,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;oBACrD,IAAI,CAAC,kBAAkB,CAAC,aAAa,CAAC,KAAK,CAAC,OAA2B,CAAC,CAAC;oBACzE,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;gBACzB,CAAC;gBACD,MAAM;YAEP,KAAK,gBAAgB;gBACpB,6BAA6B;gBAC7B,IAAI,IAAI,CAAC,kBAAkB,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;oBACnE,MAAM,YAAY,GAAG,KAAK,CAAC,OAA2B,CAAC;oBACvD,IAAI,CAAC,kBAAkB,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;oBAEpD,gEAAgE;oBAChE,KAAK,MAAM,OAAO,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;wBAC5C,IAAI,OAAO,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;4BACjC,2CAA2C;4BAC3C,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;gCACxC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gCAChD,MAAM,SAAS,GAAG,IAAI,sBAAsB,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;gCAC9E,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;gCACvC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;4BAC9C,CAAC;iCAAM,CAAC;gCACP,iEAAiE;gCACjE,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gCACpD,IAAI,SAAS,EAAE,CAAC;oCACf,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;gCACzC,CAAC;4BACF,CAAC;wBACF,CAAC;oBACF,CAAC;oBAED,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;gBACzB,CAAC;gBACD,MAAM;YAEP,KAAK,aAAa;gBACjB,sDAAsD;gBACtD,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;oBACnC,MAAM;gBACP,CAAC;gBACD,IAAI,IAAI,CAAC,kBAAkB,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;oBACnE,MAAM,YAAY,GAAG,KAAK,CAAC,OAA2B,CAAC;oBAEvD,sEAAsE;oBACtE,IAAI,CAAC,kBAAkB,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;oBAEpD,gFAAgF;oBAChF,IAAI,YAAY,CAAC,UAAU,KAAK,SAAS,IAAI,YAAY,CAAC,UAAU,KAAK,OAAO,EAAE,CAAC;wBAClF,MAAM,YAAY,GACjB,YAAY,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,YAAY,CAAC,YAAY,IAAI,OAAO,CAAC;wBACpG,KAAK,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC;4BACnE,SAAS,CAAC,YAAY,CAAC;gCACtB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;gCAC/C,OAAO,EAAE,IAAI;6BACb,CAAC,CAAC;wBACJ,CAAC;wBACD,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;oBAC3B,CAAC;oBAED,sEAAsE;oBACtE,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;gBAChC,CAAC;gBACD,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;gBACxB,MAAM;YAEP,KAAK,sBAAsB,EAAE,CAAC;gBAC7B,4EAA4E;gBAC5E,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;oBAC9C,MAAM,SAAS,GAAG,IAAI,sBAAsB,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;oBACzE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;oBACvC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;oBACnD,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;gBACzB,CAAC;gBACD,MAAM;YACP,CAAC;YAED,KAAK,oBAAoB,EAAE,CAAC;gBAC3B,qDAAqD;gBACrD,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC1D,IAAI,SAAS,EAAE,CAAC;oBACf,SAAS,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;oBACrC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;oBAC3C,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;gBACzB,CAAC;gBACD,MAAM;YACP,CAAC;YAED,KAAK,WAAW;gBACf,yBAAyB;gBACzB,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;oBAC3B,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC;oBAC7B,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;oBAC7B,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;gBAC9B,CAAC;gBACD,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;oBAC7B,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;oBACxD,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;gBAChC,CAAC;gBACD,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;gBAC1B,IAAI,CAAC,MAAM,CAAC,aAAa,GAAG,KAAK,CAAC;gBAClC,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;gBACxB,MAAM;QACR,CAAC;IAAA,CACD;IAEO,gBAAgB,CAAC,OAAgB,EAAQ;QAChD,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC7B,MAAM,OAAO,GAAG,OAAc,CAAC;YAC/B,2CAA2C;YAC3C,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;YACzE,MAAM,WAAW,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAChE,IAAI,WAAW,EAAE,CAAC;gBACjB,MAAM,aAAa,GAAG,IAAI,oBAAoB,CAAC,WAAW,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC;gBACrF,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;gBAC3C,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC;YACjC,CAAC;QACF,CAAC;aAAM,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACzC,MAAM,YAAY,GAAG,OAA2B,CAAC;YAEjD,kCAAkC;YAClC,MAAM,kBAAkB,GAAG,IAAI,yBAAyB,CAAC,YAAY,CAAC,CAAC;YACvE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;QACjD,CAAC;QACD,mFAAmF;IADlF,CAED;IAED,qBAAqB,CAAC,KAAiB,EAAQ;QAC9C,qDAAqD;QACrD,mDAAmD;QACnD,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QAE/B,kBAAkB;QAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAChD,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAElC,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAC7B,MAAM,OAAO,GAAG,OAAc,CAAC;gBAC/B,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;gBACzE,MAAM,WAAW,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBAChE,IAAI,WAAW,EAAE,CAAC;oBACjB,MAAM,aAAa,GAAG,IAAI,oBAAoB,CAAC,WAAW,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC;oBACrF,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;oBAC3C,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC;gBACjC,CAAC;YACF,CAAC;iBAAM,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBACzC,MAAM,YAAY,GAAG,OAA2B,CAAC;gBACjD,MAAM,kBAAkB,GAAG,IAAI,yBAAyB,CAAC,YAAY,CAAC,CAAC;gBACvE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;gBAEhD,sDAAsD;gBACtD,KAAK,MAAM,OAAO,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;oBAC5C,IAAI,OAAO,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;wBACjC,MAAM,SAAS,GAAG,IAAI,sBAAsB,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;wBAC9E,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;wBAEvC,kEAAkE;wBAClE,IAAI,YAAY,CAAC,UAAU,KAAK,SAAS,IAAI,YAAY,CAAC,UAAU,KAAK,OAAO,EAAE,CAAC;4BAClF,MAAM,YAAY,GACjB,YAAY,CAAC,UAAU,KAAK,SAAS;gCACpC,CAAC,CAAC,mBAAmB;gCACrB,CAAC,CAAC,YAAY,CAAC,YAAY,IAAI,OAAO,CAAC;4BACzC,SAAS,CAAC,YAAY,CAAC;gCACtB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;gCAC/C,OAAO,EAAE,IAAI;6BACb,CAAC,CAAC;wBACJ,CAAC;6BAAM,CAAC;4BACP,mDAAmD;4BACnD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;wBAC9C,CAAC;oBACF,CAAC;gBACF,CAAC;YACF,CAAC;iBAAM,IAAI,OAAO,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBAC1C,6DAA6D;gBAC7D,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;gBAC5D,IAAI,SAAS,EAAE,CAAC;oBACf,SAAS,CAAC,YAAY,CAAC;wBACtB,OAAO,EAAE,OAAO,CAAC,OAAO;wBACxB,OAAO,EAAE,OAAO,CAAC,OAAO;wBACxB,OAAO,EAAE,OAAO,CAAC,OAAO;qBACxB,CAAC,CAAC;oBACH,8CAA8C;oBAC9C,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;gBAC9C,CAAC;YACF,CAAC;QACF,CAAC;QACD,uDAAuD;QACvD,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;IAAA,CACxB;IAED,KAAK,CAAC,YAAY,GAAoB;QACrC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;YAC/B,IAAI,CAAC,eAAe,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC;gBACxC,IAAI,CAAC,eAAe,GAAG,SAAS,CAAC;gBACjC,OAAO,CAAC,IAAI,CAAC,CAAC;YAAA,CACd,CAAC;QAAA,CACF,CAAC,CAAC;IAAA,CACH;IAED,oBAAoB,CAAC,QAAoB,EAAQ;QAChD,IAAI,CAAC,mBAAmB,GAAG,QAAQ,CAAC;IAAA,CACpC;IAEO,WAAW,GAAS;QAC3B,mCAAmC;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,kBAAkB,GAAG,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC;QAErD,IAAI,kBAAkB,GAAG,GAAG,EAAE,CAAC;YAC9B,oCAAoC;YACpC,IAAI,CAAC,IAAI,EAAE,CAAC;YACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;aAAM,CAAC;YACP,kCAAkC;YAClC,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,IAAI,CAAC,cAAc,GAAG,GAAG,CAAC;QAC3B,CAAC;IAAA,CACD;IAED,WAAW,GAAS;QACnB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACxB,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;IAAA,CACxB;IAED,SAAS,CAAC,YAAoB,EAAQ;QACrC,iCAAiC;QACjC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3C,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,YAAY,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACjF,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;IAAA,CACxB;IAEO,oBAAoB,GAAS;QACpC,8CAA8C;QAC9C,IAAI,CAAC,gBAAgB,GAAG,IAAI,yBAAyB,CACpD,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,EAC9B,CAAC,KAAK,EAAE,EAAE,CAAC;YACV,oCAAoC;YACpC,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;YAEnC,wCAAwC;YACxC,IAAI,CAAC,cAAc,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC;YAEnD,gDAAgD;YAChD,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3C,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,mBAAmB,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC1E,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;YAEzC,sCAAsC;YACtC,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC5B,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;QAAA,CACxB,EACD,GAAG,EAAE,CAAC;YACL,yBAAyB;YACzB,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC5B,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;QAAA,CACxB,CACD,CAAC;QAEF,+BAA+B;QAC/B,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAC7B,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACrD,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,aAAa,EAAE,CAAC,CAAC;QACxD,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;IAAA,CACxB;IAEO,oBAAoB,GAAS;QACpC,gDAAgD;QAChD,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAC7B,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAC7B,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAAA,CAC9B;IAEO,iBAAiB,GAAS;QACjC,2CAA2C;QAC3C,IAAI,CAAC,aAAa,GAAG,IAAI,sBAAsB,CAC9C,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EACtB,CAAC,KAAK,EAAE,EAAE,CAAC;YACV,2BAA2B;YAC3B,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAE3B,+BAA+B;YAC/B,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;YAErE,gDAAgD;YAChD,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3C,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YACpE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;YAEzC,sCAAsC;YACtC,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACzB,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;QAAA,CACxB,EACD,GAAG,EAAE,CAAC;YACL,yBAAyB;YACzB,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACzB,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;QAAA,CACxB,CACD,CAAC;QAEF,+BAA+B;QAC/B,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAC7B,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAClD,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACrC,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;IAAA,CACxB;IAEO,iBAAiB,GAAS;QACjC,gDAAgD;QAChD,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAC7B,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAAA,CAC9B;IAEO,mBAAmB,CAAC,IAAY,EAAQ;QAC/C,2DAA2D;QAC3D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAE3D,IAAI,CAAC;YACJ,yBAAyB;YACzB,MAAM,QAAQ,GAAG,mBAAmB,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;YAExF,+DAA+D;YAC/D,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3C,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,wBAAwB,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAC3F,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;QACzB,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACrB,6BAA6B;YAC7B,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3C,IAAI,CAAC,aAAa,CAAC,QAAQ,CAC1B,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,6BAA6B,KAAK,CAAC,OAAO,IAAI,eAAe,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAC1F,CAAC;YACF,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;QACzB,CAAC;IAAA,CACD;IAEO,oBAAoB,GAAS;QACpC,mBAAmB;QACnB,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,EAAE,CAAC;QACzD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;QAE/B,iBAAiB;QACjB,MAAM,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;QAC5E,MAAM,iBAAiB,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,MAAM,CAAC;QACtF,MAAM,aAAa,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;QAE5C,0EAA0E;QAC1E,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,cAAc,GAAG,CAAC,CAAC;QACvB,IAAI,eAAe,GAAG,CAAC,CAAC;QACxB,IAAI,SAAS,GAAG,CAAC,CAAC;QAElB,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACtC,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBAClC,MAAM,YAAY,GAAG,OAA2B,CAAC;gBACjD,UAAU,IAAI,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC;gBACvC,WAAW,IAAI,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC;gBACzC,cAAc,IAAI,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC;gBAC/C,eAAe,IAAI,YAAY,CAAC,KAAK,CAAC,UAAU,CAAC;gBACjD,SAAS,IAAI,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;YAC5C,CAAC;QACF,CAAC;QAED,MAAM,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,cAAc,GAAG,eAAe,CAAC;QAEhF,kBAAkB;QAClB,IAAI,IAAI,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC;QAC/C,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,WAAW,IAAI,CAAC;QACjD,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,YAAY,EAAE,MAAM,CAAC;QACxE,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QACtC,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,YAAY,IAAI,CAAC;QAClD,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,iBAAiB,IAAI,CAAC;QAC5D,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,aAAa,MAAM,CAAC;QACtD,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QACpC,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,UAAU,CAAC,cAAc,EAAE,IAAI,CAAC;QAClE,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,WAAW,CAAC,cAAc,EAAE,IAAI,CAAC;QACpE,IAAI,cAAc,GAAG,CAAC,EAAE,CAAC;YACxB,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,cAAc,CAAC,cAAc,EAAE,IAAI,CAAC;QAC5E,CAAC;QACD,IAAI,eAAe,GAAG,CAAC,EAAE,CAAC;YACzB,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,eAAe,CAAC,cAAc,EAAE,IAAI,CAAC;QAC9E,CAAC;QACD,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,WAAW,CAAC,cAAc,EAAE,IAAI,CAAC;QAEnE,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YACnB,IAAI,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YACpC,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,CAAC;QAED,oBAAoB;QACpB,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3C,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAClD,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;IAAA,CACxB;IAED,IAAI,GAAS;QACZ,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC3B,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC;YAC7B,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAC9B,CAAC;QACD,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;YACf,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;QAC5B,CAAC;IAAA,CACD;CACD","sourcesContent":["import type { Agent, AgentEvent, AgentState } from \"@mariozechner/pi-agent\";\nimport type { AssistantMessage, Message } from \"@mariozechner/pi-ai\";\nimport type { SlashCommand } from \"@mariozechner/pi-tui\";\nimport {\n\tCombinedAutocompleteProvider,\n\tContainer,\n\tLoader,\n\tProcessTerminal,\n\tSpacer,\n\tText,\n\tTUI,\n} from \"@mariozechner/pi-tui\";\nimport chalk from \"chalk\";\nimport { exportSessionToHtml } from \"../export-html.js\";\nimport type { SessionManager } from \"../session-manager.js\";\nimport { AssistantMessageComponent } from \"./assistant-message.js\";\nimport { CustomEditor } from \"./custom-editor.js\";\nimport { FooterComponent } from \"./footer.js\";\nimport { ModelSelectorComponent } from \"./model-selector.js\";\nimport { ThinkingSelectorComponent } from \"./thinking-selector.js\";\nimport { ToolExecutionComponent } from \"./tool-execution.js\";\nimport { UserMessageComponent } from \"./user-message.js\";\n\n/**\n * TUI renderer for the coding agent\n */\nexport class TuiRenderer {\n\tprivate ui: TUI;\n\tprivate chatContainer: Container;\n\tprivate statusContainer: Container;\n\tprivate editor: CustomEditor;\n\tprivate editorContainer: Container; // Container to swap between editor and selector\n\tprivate footer: FooterComponent;\n\tprivate agent: Agent;\n\tprivate sessionManager: SessionManager;\n\tprivate version: string;\n\tprivate isInitialized = false;\n\tprivate onInputCallback?: (text: string) => void;\n\tprivate loadingAnimation: Loader | null = null;\n\tprivate onInterruptCallback?: () => void;\n\tprivate lastSigintTime = 0;\n\n\t// Streaming message tracking\n\tprivate streamingComponent: AssistantMessageComponent | null = null;\n\n\t// Tool execution tracking: toolCallId -> component\n\tprivate pendingTools = new Map<string, ToolExecutionComponent>();\n\n\t// Thinking level selector\n\tprivate thinkingSelector: ThinkingSelectorComponent | null = null;\n\n\t// Model selector\n\tprivate modelSelector: ModelSelectorComponent | null = null;\n\n\t// Track if this is the first user message (to skip spacer)\n\tprivate isFirstUserMessage = true;\n\n\tconstructor(agent: Agent, sessionManager: SessionManager, version: string) {\n\t\tthis.agent = agent;\n\t\tthis.sessionManager = sessionManager;\n\t\tthis.version = version;\n\t\tthis.ui = new TUI(new ProcessTerminal());\n\t\tthis.chatContainer = new Container();\n\t\tthis.statusContainer = new Container();\n\t\tthis.editor = new CustomEditor();\n\t\tthis.editorContainer = new Container(); // Container to hold editor or selector\n\t\tthis.editorContainer.addChild(this.editor); // Start with editor\n\t\tthis.footer = new FooterComponent(agent.state);\n\n\t\t// Define slash commands\n\t\tconst thinkingCommand: SlashCommand = {\n\t\t\tname: \"thinking\",\n\t\t\tdescription: \"Select reasoning level (opens selector UI)\",\n\t\t};\n\n\t\tconst modelCommand: SlashCommand = {\n\t\t\tname: \"model\",\n\t\t\tdescription: \"Select model (opens selector UI)\",\n\t\t};\n\n\t\tconst exportCommand: SlashCommand = {\n\t\t\tname: \"export\",\n\t\t\tdescription: \"Export session to HTML file\",\n\t\t};\n\n\t\tconst sessionCommand: SlashCommand = {\n\t\t\tname: \"session\",\n\t\t\tdescription: \"Show session info and stats\",\n\t\t};\n\n\t\t// Setup autocomplete for file paths and slash commands\n\t\tconst autocompleteProvider = new CombinedAutocompleteProvider(\n\t\t\t[thinkingCommand, modelCommand, exportCommand, sessionCommand],\n\t\t\tprocess.cwd(),\n\t\t);\n\t\tthis.editor.setAutocompleteProvider(autocompleteProvider);\n\t}\n\n\tasync init(): Promise<void> {\n\t\tif (this.isInitialized) return;\n\n\t\t// Add header with logo and instructions\n\t\tconst logo = chalk.bold.cyan(\"pi\") + chalk.dim(` v${this.version}`);\n\t\tconst instructions =\n\t\t\tchalk.dim(\"esc\") +\n\t\t\tchalk.gray(\" to interrupt\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"ctrl+c\") +\n\t\t\tchalk.gray(\" to clear\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"ctrl+c twice\") +\n\t\t\tchalk.gray(\" to exit\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"ctrl+k\") +\n\t\t\tchalk.gray(\" to delete line\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"/\") +\n\t\t\tchalk.gray(\" for commands\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"drop files\") +\n\t\t\tchalk.gray(\" to attach\");\n\t\tconst header = new Text(logo + \"\\n\" + instructions, 1, 0);\n\n\t\t// Setup UI layout\n\t\tthis.ui.addChild(new Spacer(1));\n\t\tthis.ui.addChild(header);\n\t\tthis.ui.addChild(new Spacer(1));\n\t\tthis.ui.addChild(this.chatContainer);\n\t\tthis.ui.addChild(this.statusContainer);\n\t\tthis.ui.addChild(new Spacer(1));\n\t\tthis.ui.addChild(this.editorContainer); // Use container that can hold editor or selector\n\t\tthis.ui.addChild(this.footer);\n\t\tthis.ui.setFocus(this.editor);\n\n\t\t// Set up custom key handlers on the editor\n\t\tthis.editor.onEscape = () => {\n\t\t\t// Intercept Escape key when processing\n\t\t\tif (this.loadingAnimation && this.onInterruptCallback) {\n\t\t\t\tthis.onInterruptCallback();\n\t\t\t}\n\t\t};\n\n\t\tthis.editor.onCtrlC = () => {\n\t\t\tthis.handleCtrlC();\n\t\t};\n\n\t\t// Handle editor submission\n\t\tthis.editor.onSubmit = (text: string) => {\n\t\t\ttext = text.trim();\n\t\t\tif (!text) return;\n\n\t\t\t// Check for /thinking command\n\t\t\tif (text === \"/thinking\") {\n\t\t\t\t// Show thinking level selector\n\t\t\t\tthis.showThinkingSelector();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check for /model command\n\t\t\tif (text === \"/model\") {\n\t\t\t\t// Show model selector\n\t\t\t\tthis.showModelSelector();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check for /export command\n\t\t\tif (text.startsWith(\"/export\")) {\n\t\t\t\tthis.handleExportCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check for /session command\n\t\t\tif (text === \"/session\") {\n\t\t\t\tthis.handleSessionCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this.onInputCallback) {\n\t\t\t\tthis.onInputCallback(text);\n\t\t\t}\n\t\t};\n\n\t\t// Start the UI\n\t\tthis.ui.start();\n\t\tthis.isInitialized = true;\n\t}\n\n\tasync handleEvent(event: AgentEvent, state: AgentState): Promise<void> {\n\t\tif (!this.isInitialized) {\n\t\t\tawait this.init();\n\t\t}\n\n\t\t// Update footer with current stats\n\t\tthis.footer.updateState(state);\n\n\t\tswitch (event.type) {\n\t\t\tcase \"agent_start\":\n\t\t\t\t// Show loading animation\n\t\t\t\tthis.editor.disableSubmit = true;\n\t\t\t\t// Stop old loader before clearing\n\t\t\t\tif (this.loadingAnimation) {\n\t\t\t\t\tthis.loadingAnimation.stop();\n\t\t\t\t}\n\t\t\t\tthis.statusContainer.clear();\n\t\t\t\tthis.loadingAnimation = new Loader(this.ui, \"Working... (esc to interrupt)\");\n\t\t\t\tthis.statusContainer.addChild(this.loadingAnimation);\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"message_start\":\n\t\t\t\tif (event.message.role === \"user\") {\n\t\t\t\t\t// Show user message immediately and clear editor\n\t\t\t\t\tthis.addMessageToChat(event.message);\n\t\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t} else if (event.message.role === \"assistant\") {\n\t\t\t\t\t// Create assistant component for streaming\n\t\t\t\t\tthis.streamingComponent = new AssistantMessageComponent();\n\t\t\t\t\tthis.chatContainer.addChild(this.streamingComponent);\n\t\t\t\t\tthis.streamingComponent.updateContent(event.message as AssistantMessage);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"message_update\":\n\t\t\t\t// Update streaming component\n\t\t\t\tif (this.streamingComponent && event.message.role === \"assistant\") {\n\t\t\t\t\tconst assistantMsg = event.message as AssistantMessage;\n\t\t\t\t\tthis.streamingComponent.updateContent(assistantMsg);\n\n\t\t\t\t\t// Create tool execution components as soon as we see tool calls\n\t\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\t\tif (content.type === \"toolCall\") {\n\t\t\t\t\t\t\t// Only create if we haven't created it yet\n\t\t\t\t\t\t\tif (!this.pendingTools.has(content.id)) {\n\t\t\t\t\t\t\t\tthis.chatContainer.addChild(new Text(\"\", 0, 0));\n\t\t\t\t\t\t\t\tconst component = new ToolExecutionComponent(content.name, content.arguments);\n\t\t\t\t\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\t\t\t\t\tthis.pendingTools.set(content.id, component);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Update existing component with latest arguments as they stream\n\t\t\t\t\t\t\t\tconst component = this.pendingTools.get(content.id);\n\t\t\t\t\t\t\t\tif (component) {\n\t\t\t\t\t\t\t\t\tcomponent.updateArgs(content.arguments);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"message_end\":\n\t\t\t\t// Skip user messages (already shown in message_start)\n\t\t\t\tif (event.message.role === \"user\") {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (this.streamingComponent && event.message.role === \"assistant\") {\n\t\t\t\t\tconst assistantMsg = event.message as AssistantMessage;\n\n\t\t\t\t\t// Update streaming component with final message (includes stopReason)\n\t\t\t\t\tthis.streamingComponent.updateContent(assistantMsg);\n\n\t\t\t\t\t// If message was aborted or errored, mark all pending tool components as failed\n\t\t\t\t\tif (assistantMsg.stopReason === \"aborted\" || assistantMsg.stopReason === \"error\") {\n\t\t\t\t\t\tconst errorMessage =\n\t\t\t\t\t\t\tassistantMsg.stopReason === \"aborted\" ? \"Operation aborted\" : assistantMsg.errorMessage || \"Error\";\n\t\t\t\t\t\tfor (const [toolCallId, component] of this.pendingTools.entries()) {\n\t\t\t\t\t\t\tcomponent.updateResult({\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: errorMessage }],\n\t\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.pendingTools.clear();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Keep the streaming component - it's now the final assistant message\n\t\t\t\t\tthis.streamingComponent = null;\n\t\t\t\t}\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"tool_execution_start\": {\n\t\t\t\t// Component should already exist from message_update, but create if missing\n\t\t\t\tif (!this.pendingTools.has(event.toolCallId)) {\n\t\t\t\t\tconst component = new ToolExecutionComponent(event.toolName, event.args);\n\t\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\t\tthis.pendingTools.set(event.toolCallId, component);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"tool_execution_end\": {\n\t\t\t\t// Update the existing tool component with the result\n\t\t\t\tconst component = this.pendingTools.get(event.toolCallId);\n\t\t\t\tif (component) {\n\t\t\t\t\tcomponent.updateResult(event.result);\n\t\t\t\t\tthis.pendingTools.delete(event.toolCallId);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"agent_end\":\n\t\t\t\t// Stop loading animation\n\t\t\t\tif (this.loadingAnimation) {\n\t\t\t\t\tthis.loadingAnimation.stop();\n\t\t\t\t\tthis.loadingAnimation = null;\n\t\t\t\t\tthis.statusContainer.clear();\n\t\t\t\t}\n\t\t\t\tif (this.streamingComponent) {\n\t\t\t\t\tthis.chatContainer.removeChild(this.streamingComponent);\n\t\t\t\t\tthis.streamingComponent = null;\n\t\t\t\t}\n\t\t\t\tthis.pendingTools.clear();\n\t\t\t\tthis.editor.disableSubmit = false;\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprivate addMessageToChat(message: Message): void {\n\t\tif (message.role === \"user\") {\n\t\t\tconst userMsg = message as any;\n\t\t\t// Extract text content from content blocks\n\t\t\tconst textBlocks = userMsg.content.filter((c: any) => c.type === \"text\");\n\t\t\tconst textContent = textBlocks.map((c: any) => c.text).join(\"\");\n\t\t\tif (textContent) {\n\t\t\t\tconst userComponent = new UserMessageComponent(textContent, this.isFirstUserMessage);\n\t\t\t\tthis.chatContainer.addChild(userComponent);\n\t\t\t\tthis.isFirstUserMessage = false;\n\t\t\t}\n\t\t} else if (message.role === \"assistant\") {\n\t\t\tconst assistantMsg = message as AssistantMessage;\n\n\t\t\t// Add assistant message component\n\t\t\tconst assistantComponent = new AssistantMessageComponent(assistantMsg);\n\t\t\tthis.chatContainer.addChild(assistantComponent);\n\t\t}\n\t\t// Note: tool calls and results are now handled via tool_execution_start/end events\n\t}\n\n\trenderInitialMessages(state: AgentState): void {\n\t\t// Render all existing messages (for --continue mode)\n\t\t// Reset first user message flag for initial render\n\t\tthis.isFirstUserMessage = true;\n\n\t\t// Render messages\n\t\tfor (let i = 0; i < state.messages.length; i++) {\n\t\t\tconst message = state.messages[i];\n\n\t\t\tif (message.role === \"user\") {\n\t\t\t\tconst userMsg = message as any;\n\t\t\t\tconst textBlocks = userMsg.content.filter((c: any) => c.type === \"text\");\n\t\t\t\tconst textContent = textBlocks.map((c: any) => c.text).join(\"\");\n\t\t\t\tif (textContent) {\n\t\t\t\t\tconst userComponent = new UserMessageComponent(textContent, this.isFirstUserMessage);\n\t\t\t\t\tthis.chatContainer.addChild(userComponent);\n\t\t\t\t\tthis.isFirstUserMessage = false;\n\t\t\t\t}\n\t\t\t} else if (message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\tconst assistantComponent = new AssistantMessageComponent(assistantMsg);\n\t\t\t\tthis.chatContainer.addChild(assistantComponent);\n\n\t\t\t\t// Create tool execution components for any tool calls\n\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\tif (content.type === \"toolCall\") {\n\t\t\t\t\t\tconst component = new ToolExecutionComponent(content.name, content.arguments);\n\t\t\t\t\t\tthis.chatContainer.addChild(component);\n\n\t\t\t\t\t\t// If message was aborted/errored, immediately mark tool as failed\n\t\t\t\t\t\tif (assistantMsg.stopReason === \"aborted\" || assistantMsg.stopReason === \"error\") {\n\t\t\t\t\t\t\tconst errorMessage =\n\t\t\t\t\t\t\t\tassistantMsg.stopReason === \"aborted\"\n\t\t\t\t\t\t\t\t\t? \"Operation aborted\"\n\t\t\t\t\t\t\t\t\t: assistantMsg.errorMessage || \"Error\";\n\t\t\t\t\t\t\tcomponent.updateResult({\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: errorMessage }],\n\t\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Store in map so we can update with results later\n\t\t\t\t\t\t\tthis.pendingTools.set(content.id, component);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (message.role === \"toolResult\") {\n\t\t\t\t// Update existing tool execution component with results\t\t\t\t;\n\t\t\t\tconst component = this.pendingTools.get(message.toolCallId);\n\t\t\t\tif (component) {\n\t\t\t\t\tcomponent.updateResult({\n\t\t\t\t\t\tcontent: message.content,\n\t\t\t\t\t\tdetails: message.details,\n\t\t\t\t\t\tisError: message.isError,\n\t\t\t\t\t});\n\t\t\t\t\t// Remove from pending map since it's complete\n\t\t\t\t\tthis.pendingTools.delete(message.toolCallId);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Clear pending tools after rendering initial messages\n\t\tthis.pendingTools.clear();\n\t\tthis.ui.requestRender();\n\t}\n\n\tasync getUserInput(): Promise<string> {\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.onInputCallback = (text: string) => {\n\t\t\t\tthis.onInputCallback = undefined;\n\t\t\t\tresolve(text);\n\t\t\t};\n\t\t});\n\t}\n\n\tsetInterruptCallback(callback: () => void): void {\n\t\tthis.onInterruptCallback = callback;\n\t}\n\n\tprivate handleCtrlC(): void {\n\t\t// Handle Ctrl+C double-press logic\n\t\tconst now = Date.now();\n\t\tconst timeSinceLastCtrlC = now - this.lastSigintTime;\n\n\t\tif (timeSinceLastCtrlC < 500) {\n\t\t\t// Second Ctrl+C within 500ms - exit\n\t\t\tthis.stop();\n\t\t\tprocess.exit(0);\n\t\t} else {\n\t\t\t// First Ctrl+C - clear the editor\n\t\t\tthis.clearEditor();\n\t\t\tthis.lastSigintTime = now;\n\t\t}\n\t}\n\n\tclearEditor(): void {\n\t\tthis.editor.setText(\"\");\n\t\tthis.ui.requestRender();\n\t}\n\n\tshowError(errorMessage: string): void {\n\t\t// Show error message in the chat\n\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\tthis.chatContainer.addChild(new Text(chalk.red(`Error: ${errorMessage}`), 1, 0));\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate showThinkingSelector(): void {\n\t\t// Create thinking selector with current level\n\t\tthis.thinkingSelector = new ThinkingSelectorComponent(\n\t\t\tthis.agent.state.thinkingLevel,\n\t\t\t(level) => {\n\t\t\t\t// Apply the selected thinking level\n\t\t\t\tthis.agent.setThinkingLevel(level);\n\n\t\t\t\t// Save thinking level change to session\n\t\t\t\tthis.sessionManager.saveThinkingLevelChange(level);\n\n\t\t\t\t// Show confirmation message with proper spacing\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\tconst confirmText = new Text(chalk.dim(`Thinking level: ${level}`), 1, 0);\n\t\t\t\tthis.chatContainer.addChild(confirmText);\n\n\t\t\t\t// Hide selector and show editor again\n\t\t\t\tthis.hideThinkingSelector();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t},\n\t\t\t() => {\n\t\t\t\t// Just hide the selector\n\t\t\t\tthis.hideThinkingSelector();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t},\n\t\t);\n\n\t\t// Replace editor with selector\n\t\tthis.editorContainer.clear();\n\t\tthis.editorContainer.addChild(this.thinkingSelector);\n\t\tthis.ui.setFocus(this.thinkingSelector.getSelectList());\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate hideThinkingSelector(): void {\n\t\t// Replace selector with editor in the container\n\t\tthis.editorContainer.clear();\n\t\tthis.editorContainer.addChild(this.editor);\n\t\tthis.thinkingSelector = null;\n\t\tthis.ui.setFocus(this.editor);\n\t}\n\n\tprivate showModelSelector(): void {\n\t\t// Create model selector with current model\n\t\tthis.modelSelector = new ModelSelectorComponent(\n\t\t\tthis.agent.state.model,\n\t\t\t(model) => {\n\t\t\t\t// Apply the selected model\n\t\t\t\tthis.agent.setModel(model);\n\n\t\t\t\t// Save model change to session\n\t\t\t\tthis.sessionManager.saveModelChange(`${model.provider}/${model.id}`);\n\n\t\t\t\t// Show confirmation message with proper spacing\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\tconst confirmText = new Text(chalk.dim(`Model: ${model.id}`), 1, 0);\n\t\t\t\tthis.chatContainer.addChild(confirmText);\n\n\t\t\t\t// Hide selector and show editor again\n\t\t\t\tthis.hideModelSelector();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t},\n\t\t\t() => {\n\t\t\t\t// Just hide the selector\n\t\t\t\tthis.hideModelSelector();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t},\n\t\t);\n\n\t\t// Replace editor with selector\n\t\tthis.editorContainer.clear();\n\t\tthis.editorContainer.addChild(this.modelSelector);\n\t\tthis.ui.setFocus(this.modelSelector);\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate hideModelSelector(): void {\n\t\t// Replace selector with editor in the container\n\t\tthis.editorContainer.clear();\n\t\tthis.editorContainer.addChild(this.editor);\n\t\tthis.modelSelector = null;\n\t\tthis.ui.setFocus(this.editor);\n\t}\n\n\tprivate handleExportCommand(text: string): void {\n\t\t// Parse optional filename from command: /export [filename]\n\t\tconst parts = text.split(/\\s+/);\n\t\tconst outputPath = parts.length > 1 ? parts[1] : undefined;\n\n\t\ttry {\n\t\t\t// Export session to HTML\n\t\t\tconst filePath = exportSessionToHtml(this.sessionManager, this.agent.state, outputPath);\n\n\t\t\t// Show success message in chat - matching thinking level style\n\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\tthis.chatContainer.addChild(new Text(chalk.dim(`Session exported to: ${filePath}`), 1, 0));\n\t\t\tthis.ui.requestRender();\n\t\t} catch (error: any) {\n\t\t\t// Show error message in chat\n\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\tthis.chatContainer.addChild(\n\t\t\t\tnew Text(chalk.red(`Failed to export session: ${error.message || \"Unknown error\"}`), 1, 0),\n\t\t\t);\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n\n\tprivate handleSessionCommand(): void {\n\t\t// Get session info\n\t\tconst sessionFile = this.sessionManager.getSessionFile();\n\t\tconst state = this.agent.state;\n\n\t\t// Count messages\n\t\tconst userMessages = state.messages.filter((m) => m.role === \"user\").length;\n\t\tconst assistantMessages = state.messages.filter((m) => m.role === \"assistant\").length;\n\t\tconst totalMessages = state.messages.length;\n\n\t\t// Calculate cumulative usage from all assistant messages (same as footer)\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const message of state.messages) {\n\t\t\tif (message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\ttotalInput += assistantMsg.usage.input;\n\t\t\t\ttotalOutput += assistantMsg.usage.output;\n\t\t\t\ttotalCacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\ttotalCost += assistantMsg.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\tconst totalTokens = totalInput + totalOutput + totalCacheRead + totalCacheWrite;\n\n\t\t// Build info text\n\t\tlet info = `${chalk.bold(\"Session Info\")}\\n\\n`;\n\t\tinfo += `${chalk.dim(\"File:\")} ${sessionFile}\\n`;\n\t\tinfo += `${chalk.dim(\"ID:\")} ${this.sessionManager.getSessionId()}\\n\\n`;\n\t\tinfo += `${chalk.bold(\"Messages\")}\\n`;\n\t\tinfo += `${chalk.dim(\"User:\")} ${userMessages}\\n`;\n\t\tinfo += `${chalk.dim(\"Assistant:\")} ${assistantMessages}\\n`;\n\t\tinfo += `${chalk.dim(\"Total:\")} ${totalMessages}\\n\\n`;\n\t\tinfo += `${chalk.bold(\"Tokens\")}\\n`;\n\t\tinfo += `${chalk.dim(\"Input:\")} ${totalInput.toLocaleString()}\\n`;\n\t\tinfo += `${chalk.dim(\"Output:\")} ${totalOutput.toLocaleString()}\\n`;\n\t\tif (totalCacheRead > 0) {\n\t\t\tinfo += `${chalk.dim(\"Cache Read:\")} ${totalCacheRead.toLocaleString()}\\n`;\n\t\t}\n\t\tif (totalCacheWrite > 0) {\n\t\t\tinfo += `${chalk.dim(\"Cache Write:\")} ${totalCacheWrite.toLocaleString()}\\n`;\n\t\t}\n\t\tinfo += `${chalk.dim(\"Total:\")} ${totalTokens.toLocaleString()}\\n`;\n\n\t\tif (totalCost > 0) {\n\t\t\tinfo += `\\n${chalk.bold(\"Cost\")}\\n`;\n\t\t\tinfo += `${chalk.dim(\"Total:\")} ${totalCost.toFixed(4)}`;\n\t\t}\n\n\t\t// Show info in chat\n\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\tthis.chatContainer.addChild(new Text(info, 1, 0));\n\t\tthis.ui.requestRender();\n\t}\n\n\tstop(): void {\n\t\tif (this.loadingAnimation) {\n\t\t\tthis.loadingAnimation.stop();\n\t\t\tthis.loadingAnimation = null;\n\t\t}\n\t\tif (this.isInitialized) {\n\t\t\tthis.ui.stop();\n\t\t\tthis.isInitialized = false;\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"tui-renderer.js","sourceRoot":"","sources":["../../src/tui/tui-renderer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Agent, AgentEvent, AgentState } from \"@mariozechner/pi-agent\";\nimport type { AssistantMessage, Message } from \"@mariozechner/pi-ai\";\nimport type { SlashCommand } from \"@mariozechner/pi-tui\";\nimport {\n\tCombinedAutocompleteProvider,\n\tContainer,\n\tLoader,\n\tProcessTerminal,\n\tSpacer,\n\tText,\n\tTUI,\n} from \"@mariozechner/pi-tui\";\nimport chalk from \"chalk\";\nimport { exportSessionToHtml } from \"../export-html.js\";\nimport type { SessionManager } from \"../session-manager.js\";\nimport { AssistantMessageComponent } from \"./assistant-message.js\";\nimport { CustomEditor } from \"./custom-editor.js\";\nimport { FooterComponent } from \"./footer.js\";\nimport { ModelSelectorComponent } from \"./model-selector.js\";\nimport { ThinkingSelectorComponent } from \"./thinking-selector.js\";\nimport { ToolExecutionComponent } from \"./tool-execution.js\";\nimport { UserMessageComponent } from \"./user-message.js\";\n\n/**\n * TUI renderer for the coding agent\n */\nexport class TuiRenderer {\n\tprivate ui: TUI;\n\tprivate chatContainer: Container;\n\tprivate statusContainer: Container;\n\tprivate editor: CustomEditor;\n\tprivate editorContainer: Container; // Container to swap between editor and selector\n\tprivate footer: FooterComponent;\n\tprivate agent: Agent;\n\tprivate sessionManager: SessionManager;\n\tprivate version: string;\n\tprivate isInitialized = false;\n\tprivate onInputCallback?: (text: string) => void;\n\tprivate loadingAnimation: Loader | null = null;\n\tprivate onInterruptCallback?: () => void;\n\tprivate lastSigintTime = 0;\n\n\t// Streaming message tracking\n\tprivate streamingComponent: AssistantMessageComponent | null = null;\n\n\t// Tool execution tracking: toolCallId -> component\n\tprivate pendingTools = new Map<string, ToolExecutionComponent>();\n\n\t// Thinking level selector\n\tprivate thinkingSelector: ThinkingSelectorComponent | null = null;\n\n\t// Model selector\n\tprivate modelSelector: ModelSelectorComponent | null = null;\n\n\t// Track if this is the first user message (to skip spacer)\n\tprivate isFirstUserMessage = true;\n\n\tconstructor(agent: Agent, sessionManager: SessionManager, version: string) {\n\t\tthis.agent = agent;\n\t\tthis.sessionManager = sessionManager;\n\t\tthis.version = version;\n\t\tthis.ui = new TUI(new ProcessTerminal());\n\t\tthis.chatContainer = new Container();\n\t\tthis.statusContainer = new Container();\n\t\tthis.editor = new CustomEditor();\n\t\tthis.editorContainer = new Container(); // Container to hold editor or selector\n\t\tthis.editorContainer.addChild(this.editor); // Start with editor\n\t\tthis.footer = new FooterComponent(agent.state);\n\n\t\t// Define slash commands\n\t\tconst thinkingCommand: SlashCommand = {\n\t\t\tname: \"thinking\",\n\t\t\tdescription: \"Select reasoning level (opens selector UI)\",\n\t\t};\n\n\t\tconst modelCommand: SlashCommand = {\n\t\t\tname: \"model\",\n\t\t\tdescription: \"Select model (opens selector UI)\",\n\t\t};\n\n\t\tconst exportCommand: SlashCommand = {\n\t\t\tname: \"export\",\n\t\t\tdescription: \"Export session to HTML file\",\n\t\t};\n\n\t\tconst sessionCommand: SlashCommand = {\n\t\t\tname: \"session\",\n\t\t\tdescription: \"Show session info and stats\",\n\t\t};\n\n\t\t// Setup autocomplete for file paths and slash commands\n\t\tconst autocompleteProvider = new CombinedAutocompleteProvider(\n\t\t\t[thinkingCommand, modelCommand, exportCommand, sessionCommand],\n\t\t\tprocess.cwd(),\n\t\t);\n\t\tthis.editor.setAutocompleteProvider(autocompleteProvider);\n\t}\n\n\tasync init(): Promise<void> {\n\t\tif (this.isInitialized) return;\n\n\t\t// Add header with logo and instructions\n\t\tconst logo = chalk.bold.cyan(\"pi\") + chalk.dim(` v${this.version}`);\n\t\tconst instructions =\n\t\t\tchalk.dim(\"esc\") +\n\t\t\tchalk.gray(\" to interrupt\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"ctrl+c\") +\n\t\t\tchalk.gray(\" to clear\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"ctrl+c twice\") +\n\t\t\tchalk.gray(\" to exit\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"ctrl+k\") +\n\t\t\tchalk.gray(\" to delete line\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"/\") +\n\t\t\tchalk.gray(\" for commands\") +\n\t\t\t\"\\n\" +\n\t\t\tchalk.dim(\"drop files\") +\n\t\t\tchalk.gray(\" to attach\");\n\t\tconst header = new Text(logo + \"\\n\" + instructions, 1, 0);\n\n\t\t// Setup UI layout\n\t\tthis.ui.addChild(new Spacer(1));\n\t\tthis.ui.addChild(header);\n\t\tthis.ui.addChild(new Spacer(1));\n\t\tthis.ui.addChild(this.chatContainer);\n\t\tthis.ui.addChild(this.statusContainer);\n\t\tthis.ui.addChild(new Spacer(1));\n\t\tthis.ui.addChild(this.editorContainer); // Use container that can hold editor or selector\n\t\tthis.ui.addChild(this.footer);\n\t\tthis.ui.setFocus(this.editor);\n\n\t\t// Set up custom key handlers on the editor\n\t\tthis.editor.onEscape = () => {\n\t\t\t// Intercept Escape key when processing\n\t\t\tif (this.loadingAnimation && this.onInterruptCallback) {\n\t\t\t\tthis.onInterruptCallback();\n\t\t\t}\n\t\t};\n\n\t\tthis.editor.onCtrlC = () => {\n\t\t\tthis.handleCtrlC();\n\t\t};\n\n\t\t// Handle editor submission\n\t\tthis.editor.onSubmit = (text: string) => {\n\t\t\ttext = text.trim();\n\t\t\tif (!text) return;\n\n\t\t\t// Check for /thinking command\n\t\t\tif (text === \"/thinking\") {\n\t\t\t\t// Show thinking level selector\n\t\t\t\tthis.showThinkingSelector();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check for /model command\n\t\t\tif (text === \"/model\") {\n\t\t\t\t// Show model selector\n\t\t\t\tthis.showModelSelector();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check for /export command\n\t\t\tif (text.startsWith(\"/export\")) {\n\t\t\t\tthis.handleExportCommand(text);\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check for /session command\n\t\t\tif (text === \"/session\") {\n\t\t\t\tthis.handleSessionCommand();\n\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this.onInputCallback) {\n\t\t\t\tthis.onInputCallback(text);\n\t\t\t}\n\t\t};\n\n\t\t// Start the UI\n\t\tthis.ui.start();\n\t\tthis.isInitialized = true;\n\t}\n\n\tasync handleEvent(event: AgentEvent, state: AgentState): Promise<void> {\n\t\tif (!this.isInitialized) {\n\t\t\tawait this.init();\n\t\t}\n\n\t\t// Update footer with current stats\n\t\tthis.footer.updateState(state);\n\n\t\tswitch (event.type) {\n\t\t\tcase \"agent_start\":\n\t\t\t\t// Show loading animation\n\t\t\t\tthis.editor.disableSubmit = true;\n\t\t\t\t// Stop old loader before clearing\n\t\t\t\tif (this.loadingAnimation) {\n\t\t\t\t\tthis.loadingAnimation.stop();\n\t\t\t\t}\n\t\t\t\tthis.statusContainer.clear();\n\t\t\t\tthis.loadingAnimation = new Loader(this.ui, \"Working... (esc to interrupt)\");\n\t\t\t\tthis.statusContainer.addChild(this.loadingAnimation);\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"message_start\":\n\t\t\t\tif (event.message.role === \"user\") {\n\t\t\t\t\t// Show user message immediately and clear editor\n\t\t\t\t\tthis.addMessageToChat(event.message);\n\t\t\t\t\tthis.editor.setText(\"\");\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t} else if (event.message.role === \"assistant\") {\n\t\t\t\t\t// Create assistant component for streaming\n\t\t\t\t\tthis.streamingComponent = new AssistantMessageComponent();\n\t\t\t\t\tthis.chatContainer.addChild(this.streamingComponent);\n\t\t\t\t\tthis.streamingComponent.updateContent(event.message as AssistantMessage);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"message_update\":\n\t\t\t\t// Update streaming component\n\t\t\t\tif (this.streamingComponent && event.message.role === \"assistant\") {\n\t\t\t\t\tconst assistantMsg = event.message as AssistantMessage;\n\t\t\t\t\tthis.streamingComponent.updateContent(assistantMsg);\n\n\t\t\t\t\t// Create tool execution components as soon as we see tool calls\n\t\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\t\tif (content.type === \"toolCall\") {\n\t\t\t\t\t\t\t// Only create if we haven't created it yet\n\t\t\t\t\t\t\tif (!this.pendingTools.has(content.id)) {\n\t\t\t\t\t\t\t\tthis.chatContainer.addChild(new Text(\"\", 0, 0));\n\t\t\t\t\t\t\t\tconst component = new ToolExecutionComponent(content.name, content.arguments);\n\t\t\t\t\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\t\t\t\t\tthis.pendingTools.set(content.id, component);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Update existing component with latest arguments as they stream\n\t\t\t\t\t\t\t\tconst component = this.pendingTools.get(content.id);\n\t\t\t\t\t\t\t\tif (component) {\n\t\t\t\t\t\t\t\t\tcomponent.updateArgs(content.arguments);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase \"message_end\":\n\t\t\t\t// Skip user messages (already shown in message_start)\n\t\t\t\tif (event.message.role === \"user\") {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (this.streamingComponent && event.message.role === \"assistant\") {\n\t\t\t\t\tconst assistantMsg = event.message as AssistantMessage;\n\n\t\t\t\t\t// Update streaming component with final message (includes stopReason)\n\t\t\t\t\tthis.streamingComponent.updateContent(assistantMsg);\n\n\t\t\t\t\t// If message was aborted or errored, mark all pending tool components as failed\n\t\t\t\t\tif (assistantMsg.stopReason === \"aborted\" || assistantMsg.stopReason === \"error\") {\n\t\t\t\t\t\tconst errorMessage =\n\t\t\t\t\t\t\tassistantMsg.stopReason === \"aborted\" ? \"Operation aborted\" : assistantMsg.errorMessage || \"Error\";\n\t\t\t\t\t\tfor (const [toolCallId, component] of this.pendingTools.entries()) {\n\t\t\t\t\t\t\tcomponent.updateResult({\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: errorMessage }],\n\t\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.pendingTools.clear();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Keep the streaming component - it's now the final assistant message\n\t\t\t\t\tthis.streamingComponent = null;\n\t\t\t\t}\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\n\t\t\tcase \"tool_execution_start\": {\n\t\t\t\t// Component should already exist from message_update, but create if missing\n\t\t\t\tif (!this.pendingTools.has(event.toolCallId)) {\n\t\t\t\t\tconst component = new ToolExecutionComponent(event.toolName, event.args);\n\t\t\t\t\tthis.chatContainer.addChild(component);\n\t\t\t\t\tthis.pendingTools.set(event.toolCallId, component);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"tool_execution_end\": {\n\t\t\t\t// Update the existing tool component with the result\n\t\t\t\tconst component = this.pendingTools.get(event.toolCallId);\n\t\t\t\tif (component) {\n\t\t\t\t\tcomponent.updateResult(event.result);\n\t\t\t\t\tthis.pendingTools.delete(event.toolCallId);\n\t\t\t\t\tthis.ui.requestRender();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"agent_end\":\n\t\t\t\t// Stop loading animation\n\t\t\t\tif (this.loadingAnimation) {\n\t\t\t\t\tthis.loadingAnimation.stop();\n\t\t\t\t\tthis.loadingAnimation = null;\n\t\t\t\t\tthis.statusContainer.clear();\n\t\t\t\t}\n\t\t\t\tif (this.streamingComponent) {\n\t\t\t\t\tthis.chatContainer.removeChild(this.streamingComponent);\n\t\t\t\t\tthis.streamingComponent = null;\n\t\t\t\t}\n\t\t\t\tthis.pendingTools.clear();\n\t\t\t\tthis.editor.disableSubmit = false;\n\t\t\t\tthis.ui.requestRender();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprivate addMessageToChat(message: Message): void {\n\t\tif (message.role === \"user\") {\n\t\t\tconst userMsg = message as any;\n\t\t\t// Extract text content from content blocks\n\t\t\tconst textBlocks = userMsg.content.filter((c: any) => c.type === \"text\");\n\t\t\tconst textContent = textBlocks.map((c: any) => c.text).join(\"\");\n\t\t\tif (textContent) {\n\t\t\t\tconst userComponent = new UserMessageComponent(textContent, this.isFirstUserMessage);\n\t\t\t\tthis.chatContainer.addChild(userComponent);\n\t\t\t\tthis.isFirstUserMessage = false;\n\t\t\t}\n\t\t} else if (message.role === \"assistant\") {\n\t\t\tconst assistantMsg = message as AssistantMessage;\n\n\t\t\t// Add assistant message component\n\t\t\tconst assistantComponent = new AssistantMessageComponent(assistantMsg);\n\t\t\tthis.chatContainer.addChild(assistantComponent);\n\t\t}\n\t\t// Note: tool calls and results are now handled via tool_execution_start/end events\n\t}\n\n\trenderInitialMessages(state: AgentState): void {\n\t\t// Render all existing messages (for --continue mode)\n\t\t// Reset first user message flag for initial render\n\t\tthis.isFirstUserMessage = true;\n\n\t\t// Render messages\n\t\tfor (let i = 0; i < state.messages.length; i++) {\n\t\t\tconst message = state.messages[i];\n\n\t\t\tif (message.role === \"user\") {\n\t\t\t\tconst userMsg = message as any;\n\t\t\t\tconst textBlocks = userMsg.content.filter((c: any) => c.type === \"text\");\n\t\t\t\tconst textContent = textBlocks.map((c: any) => c.text).join(\"\");\n\t\t\t\tif (textContent) {\n\t\t\t\t\tconst userComponent = new UserMessageComponent(textContent, this.isFirstUserMessage);\n\t\t\t\t\tthis.chatContainer.addChild(userComponent);\n\t\t\t\t\tthis.isFirstUserMessage = false;\n\t\t\t\t}\n\t\t\t} else if (message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\tconst assistantComponent = new AssistantMessageComponent(assistantMsg);\n\t\t\t\tthis.chatContainer.addChild(assistantComponent);\n\n\t\t\t\t// Create tool execution components for any tool calls\n\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\tif (content.type === \"toolCall\") {\n\t\t\t\t\t\tconst component = new ToolExecutionComponent(content.name, content.arguments);\n\t\t\t\t\t\tthis.chatContainer.addChild(component);\n\n\t\t\t\t\t\t// If message was aborted/errored, immediately mark tool as failed\n\t\t\t\t\t\tif (assistantMsg.stopReason === \"aborted\" || assistantMsg.stopReason === \"error\") {\n\t\t\t\t\t\t\tconst errorMessage =\n\t\t\t\t\t\t\t\tassistantMsg.stopReason === \"aborted\"\n\t\t\t\t\t\t\t\t\t? \"Operation aborted\"\n\t\t\t\t\t\t\t\t\t: assistantMsg.errorMessage || \"Error\";\n\t\t\t\t\t\t\tcomponent.updateResult({\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: errorMessage }],\n\t\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Store in map so we can update with results later\n\t\t\t\t\t\t\tthis.pendingTools.set(content.id, component);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (message.role === \"toolResult\") {\n\t\t\t\t// Update existing tool execution component with results\t\t\t\t;\n\t\t\t\tconst component = this.pendingTools.get(message.toolCallId);\n\t\t\t\tif (component) {\n\t\t\t\t\tcomponent.updateResult({\n\t\t\t\t\t\tcontent: message.content,\n\t\t\t\t\t\tdetails: message.details,\n\t\t\t\t\t\tisError: message.isError,\n\t\t\t\t\t});\n\t\t\t\t\t// Remove from pending map since it's complete\n\t\t\t\t\tthis.pendingTools.delete(message.toolCallId);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Clear pending tools after rendering initial messages\n\t\tthis.pendingTools.clear();\n\t\tthis.ui.requestRender();\n\t}\n\n\tasync getUserInput(): Promise<string> {\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.onInputCallback = (text: string) => {\n\t\t\t\tthis.onInputCallback = undefined;\n\t\t\t\tresolve(text);\n\t\t\t};\n\t\t});\n\t}\n\n\tsetInterruptCallback(callback: () => void): void {\n\t\tthis.onInterruptCallback = callback;\n\t}\n\n\tprivate handleCtrlC(): void {\n\t\t// Handle Ctrl+C double-press logic\n\t\tconst now = Date.now();\n\t\tconst timeSinceLastCtrlC = now - this.lastSigintTime;\n\n\t\tif (timeSinceLastCtrlC < 500) {\n\t\t\t// Second Ctrl+C within 500ms - exit\n\t\t\tthis.stop();\n\t\t\tprocess.exit(0);\n\t\t} else {\n\t\t\t// First Ctrl+C - clear the editor\n\t\t\tthis.clearEditor();\n\t\t\tthis.lastSigintTime = now;\n\t\t}\n\t}\n\n\tclearEditor(): void {\n\t\tthis.editor.setText(\"\");\n\t\tthis.ui.requestRender();\n\t}\n\n\tshowError(errorMessage: string): void {\n\t\t// Show error message in the chat\n\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\tthis.chatContainer.addChild(new Text(chalk.red(`Error: ${errorMessage}`), 1, 0));\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate showThinkingSelector(): void {\n\t\t// Create thinking selector with current level\n\t\tthis.thinkingSelector = new ThinkingSelectorComponent(\n\t\t\tthis.agent.state.thinkingLevel,\n\t\t\t(level) => {\n\t\t\t\t// Apply the selected thinking level\n\t\t\t\tthis.agent.setThinkingLevel(level);\n\n\t\t\t\t// Save thinking level change to session\n\t\t\t\tthis.sessionManager.saveThinkingLevelChange(level);\n\n\t\t\t\t// Show confirmation message with proper spacing\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\tconst confirmText = new Text(chalk.dim(`Thinking level: ${level}`), 1, 0);\n\t\t\t\tthis.chatContainer.addChild(confirmText);\n\n\t\t\t\t// Hide selector and show editor again\n\t\t\t\tthis.hideThinkingSelector();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t},\n\t\t\t() => {\n\t\t\t\t// Just hide the selector\n\t\t\t\tthis.hideThinkingSelector();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t},\n\t\t);\n\n\t\t// Replace editor with selector\n\t\tthis.editorContainer.clear();\n\t\tthis.editorContainer.addChild(this.thinkingSelector);\n\t\tthis.ui.setFocus(this.thinkingSelector.getSelectList());\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate hideThinkingSelector(): void {\n\t\t// Replace selector with editor in the container\n\t\tthis.editorContainer.clear();\n\t\tthis.editorContainer.addChild(this.editor);\n\t\tthis.thinkingSelector = null;\n\t\tthis.ui.setFocus(this.editor);\n\t}\n\n\tprivate showModelSelector(): void {\n\t\t// Create model selector with current model\n\t\tthis.modelSelector = new ModelSelectorComponent(\n\t\t\tthis.agent.state.model,\n\t\t\t(model) => {\n\t\t\t\t// Apply the selected model\n\t\t\t\tthis.agent.setModel(model);\n\n\t\t\t\t// Save model change to session\n\t\t\t\tthis.sessionManager.saveModelChange(`${model.provider}/${model.id}`);\n\n\t\t\t\t// Show confirmation message with proper spacing\n\t\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\t\tconst confirmText = new Text(chalk.dim(`Model: ${model.id}`), 1, 0);\n\t\t\t\tthis.chatContainer.addChild(confirmText);\n\n\t\t\t\t// Hide selector and show editor again\n\t\t\t\tthis.hideModelSelector();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t},\n\t\t\t() => {\n\t\t\t\t// Just hide the selector\n\t\t\t\tthis.hideModelSelector();\n\t\t\t\tthis.ui.requestRender();\n\t\t\t},\n\t\t);\n\n\t\t// Replace editor with selector\n\t\tthis.editorContainer.clear();\n\t\tthis.editorContainer.addChild(this.modelSelector);\n\t\tthis.ui.setFocus(this.modelSelector);\n\t\tthis.ui.requestRender();\n\t}\n\n\tprivate hideModelSelector(): void {\n\t\t// Replace selector with editor in the container\n\t\tthis.editorContainer.clear();\n\t\tthis.editorContainer.addChild(this.editor);\n\t\tthis.modelSelector = null;\n\t\tthis.ui.setFocus(this.editor);\n\t}\n\n\tprivate handleExportCommand(text: string): void {\n\t\t// Parse optional filename from command: /export [filename]\n\t\tconst parts = text.split(/\\s+/);\n\t\tconst outputPath = parts.length > 1 ? parts[1] : undefined;\n\n\t\ttry {\n\t\t\t// Export session to HTML\n\t\t\tconst filePath = exportSessionToHtml(this.sessionManager, this.agent.state, outputPath);\n\n\t\t\t// Show success message in chat - matching thinking level style\n\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\tthis.chatContainer.addChild(new Text(chalk.dim(`Session exported to: ${filePath}`), 1, 0));\n\t\t\tthis.ui.requestRender();\n\t\t} catch (error: any) {\n\t\t\t// Show error message in chat\n\t\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\t\tthis.chatContainer.addChild(\n\t\t\t\tnew Text(chalk.red(`Failed to export session: ${error.message || \"Unknown error\"}`), 1, 0),\n\t\t\t);\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n\n\tprivate handleSessionCommand(): void {\n\t\t// Get session info\n\t\tconst sessionFile = this.sessionManager.getSessionFile();\n\t\tconst state = this.agent.state;\n\n\t\t// Count messages\n\t\tconst userMessages = state.messages.filter((m) => m.role === \"user\").length;\n\t\tconst assistantMessages = state.messages.filter((m) => m.role === \"assistant\").length;\n\t\tconst toolResults = state.messages.filter((m) => m.role === \"toolResult\").length;\n\t\tconst totalMessages = state.messages.length;\n\n\t\t// Count tool calls from assistant messages\n\t\tlet toolCalls = 0;\n\t\tfor (const message of state.messages) {\n\t\t\tif (message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\ttoolCalls += assistantMsg.content.filter((c) => c.type === \"toolCall\").length;\n\t\t\t}\n\t\t}\n\n\t\t// Calculate cumulative usage from all assistant messages (same as footer)\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const message of state.messages) {\n\t\t\tif (message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\ttotalInput += assistantMsg.usage.input;\n\t\t\t\ttotalOutput += assistantMsg.usage.output;\n\t\t\t\ttotalCacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\ttotalCost += assistantMsg.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\tconst totalTokens = totalInput + totalOutput + totalCacheRead + totalCacheWrite;\n\n\t\t// Build info text\n\t\tlet info = `${chalk.bold(\"Session Info\")}\\n\\n`;\n\t\tinfo += `${chalk.dim(\"File:\")} ${sessionFile}\\n`;\n\t\tinfo += `${chalk.dim(\"ID:\")} ${this.sessionManager.getSessionId()}\\n\\n`;\n\t\tinfo += `${chalk.bold(\"Messages\")}\\n`;\n\t\tinfo += `${chalk.dim(\"User:\")} ${userMessages}\\n`;\n\t\tinfo += `${chalk.dim(\"Assistant:\")} ${assistantMessages}\\n`;\n\t\tinfo += `${chalk.dim(\"Tool Calls:\")} ${toolCalls}\\n`;\n\t\tinfo += `${chalk.dim(\"Tool Results:\")} ${toolResults}\\n`;\n\t\tinfo += `${chalk.dim(\"Total:\")} ${totalMessages}\\n\\n`;\n\t\tinfo += `${chalk.bold(\"Tokens\")}\\n`;\n\t\tinfo += `${chalk.dim(\"Input:\")} ${totalInput.toLocaleString()}\\n`;\n\t\tinfo += `${chalk.dim(\"Output:\")} ${totalOutput.toLocaleString()}\\n`;\n\t\tif (totalCacheRead > 0) {\n\t\t\tinfo += `${chalk.dim(\"Cache Read:\")} ${totalCacheRead.toLocaleString()}\\n`;\n\t\t}\n\t\tif (totalCacheWrite > 0) {\n\t\t\tinfo += `${chalk.dim(\"Cache Write:\")} ${totalCacheWrite.toLocaleString()}\\n`;\n\t\t}\n\t\tinfo += `${chalk.dim(\"Total:\")} ${totalTokens.toLocaleString()}\\n`;\n\n\t\tif (totalCost > 0) {\n\t\t\tinfo += `\\n${chalk.bold(\"Cost\")}\\n`;\n\t\t\tinfo += `${chalk.dim(\"Total:\")} ${totalCost.toFixed(4)}`;\n\t\t}\n\n\t\t// Show info in chat\n\t\tthis.chatContainer.addChild(new Spacer(1));\n\t\tthis.chatContainer.addChild(new Text(info, 1, 0));\n\t\tthis.ui.requestRender();\n\t}\n\n\tstop(): void {\n\t\tif (this.loadingAnimation) {\n\t\t\tthis.loadingAnimation.stop();\n\t\t\tthis.loadingAnimation = null;\n\t\t}\n\t\tif (this.isInitialized) {\n\t\t\tthis.ui.stop();\n\t\t\tthis.isInitialized = false;\n\t\t}\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mariozechner/pi-coding-agent",
3
- "version": "0.6.2",
3
+ "version": "0.7.1",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,8 +20,8 @@
20
20
  "prepublishOnly": "npm run clean && npm run build"
21
21
  },
22
22
  "dependencies": {
23
- "@mariozechner/pi-agent": "^0.6.2",
24
- "@mariozechner/pi-ai": "^0.6.2",
23
+ "@mariozechner/pi-agent": "^0.7.1",
24
+ "@mariozechner/pi-ai": "^0.7.1",
25
25
  "chalk": "^5.5.0",
26
26
  "diff": "^8.0.2",
27
27
  "glob": "^11.0.3"