Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions Packs/pai-hook-system/src/hooks/QuestionAnswered.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,44 @@
* - Kitty unavailable: Silent failure
*/

import { existsSync } from 'fs';

const TAB_WORKING_BG = '#804000'; // Dark orange - actively working
const ACTIVE_TAB_BG = '#002B80'; // Dark blue - active tab always
const ACTIVE_TEXT = '#FFFFFF';
const INACTIVE_TEXT = '#A0A0A0';

/**
* Get kitty socket path - required for socket-only remote control.
* Using socket-based control prevents escape sequence leaks (P@kitty-cmd artifacts).
*/
function getKittySocket(): string | null {
if (process.env.KITTY_LISTEN_ON) {
return process.env.KITTY_LISTEN_ON;
}
const defaultSocket = `/tmp/kitty-${process.env.USER}`;
try {
if (existsSync(defaultSocket)) {
return `unix:${defaultSocket}`;
}
} catch {}
return null;
}

async function main() {
try {
const socket = getKittySocket();

if (!socket) {
console.error('[QuestionAnswered] No kitty socket available, skipping');
process.exit(0);
}

// Set tab color: active stays dark blue, inactive shows orange
await Bun.$`kitten @ set-tab-color --self active_bg=${ACTIVE_TAB_BG} active_fg=${ACTIVE_TEXT} inactive_bg=${TAB_WORKING_BG} inactive_fg=${INACTIVE_TEXT}`.quiet();
await Bun.$`kitten @ --to ${socket} set-tab-color --self active_bg=${ACTIVE_TAB_BG} active_fg=${ACTIVE_TEXT} inactive_bg=${TAB_WORKING_BG} inactive_fg=${INACTIVE_TEXT}`.quiet();

// Set working title
await Bun.$`kitty @ set-tab-title "⚙️Processing answer…"`.quiet();
await Bun.$`kitty @ --to ${socket} set-tab-title "⚙️Processing answer…"`.quiet();

console.error('[QuestionAnswered] Tab reset to working state (orange on inactive only)');
} catch (error) {
Expand Down
30 changes: 28 additions & 2 deletions Packs/pai-hook-system/src/hooks/SetQuestionTab.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
* - Typical execution: <50ms
*/

import { existsSync } from 'fs';

const TAB_AWAITING_BG = '#085050'; // Dark teal - waiting for user input
const ACTIVE_TAB_BG = '#002B80'; // Dark blue - active tab always
const TAB_TEXT = '#FFFFFF';
Expand All @@ -49,13 +51,37 @@ const INACTIVE_TEXT = '#A0A0A0';
// Simple question indicator - teal background does the work
const QUESTION_TITLE = '❓ Question';

/**
* Get kitty socket path - required for socket-only remote control.
* Using socket-based control prevents escape sequence leaks (P@kitty-cmd artifacts).
*/
function getKittySocket(): string | null {
if (process.env.KITTY_LISTEN_ON) {
return process.env.KITTY_LISTEN_ON;
}
const defaultSocket = `/tmp/kitty-${process.env.USER}`;
try {
if (existsSync(defaultSocket)) {
return `unix:${defaultSocket}`;
}
} catch {}
return null;
}

async function main() {
try {
const socket = getKittySocket();

if (!socket) {
console.error('[SetQuestionTab] No kitty socket available, skipping');
process.exit(0);
}

// Set tab color: active stays dark blue, inactive shows teal
await Bun.$`kitten @ set-tab-color --self active_bg=${ACTIVE_TAB_BG} active_fg=${TAB_TEXT} inactive_bg=${TAB_AWAITING_BG} inactive_fg=${INACTIVE_TEXT}`;
await Bun.$`kitten @ --to ${socket} set-tab-color --self active_bg=${ACTIVE_TAB_BG} active_fg=${TAB_TEXT} inactive_bg=${TAB_AWAITING_BG} inactive_fg=${INACTIVE_TEXT}`;

// Set simple question title - teal background provides visual distinction
await Bun.$`kitty @ set-tab-title ${QUESTION_TITLE}`;
await Bun.$`kitty @ --to ${socket} set-tab-title ${QUESTION_TITLE}`;

console.error('[SetQuestionTab] Tab set to teal with question indicator');
} catch (error) {
Expand Down
47 changes: 33 additions & 14 deletions Packs/pai-hook-system/src/hooks/UpdateTabTitle.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,28 @@
*/

import { execSync } from 'child_process';
import { readFileSync } from 'fs';
import { readFileSync, existsSync } from 'fs';
import { inference } from '../skills/CORE/Tools/Inference';
import { isValidTabSummary, getTabFallback } from './lib/response-format';

/**
* Get kitty socket path - required for socket-only remote control.
* Using socket-based control prevents escape sequence leaks (P@kitty-cmd artifacts)
* that occur when hooks run as subprocesses.
*/
function getKittySocket(): string | null {
if (process.env.KITTY_LISTEN_ON) {
return process.env.KITTY_LISTEN_ON;
}
const defaultSocket = `/tmp/kitty-${process.env.USER}`;
try {
if (existsSync(defaultSocket)) {
return `unix:${defaultSocket}`;
}
} catch {}
return null;
}

// Tab colors - different states
const TAB_WORKING_BG = '#804000'; // Dark orange - actively working
const TAB_INFERENCE_BG = '#1E0A3C'; // Very dark purple - inference/AI thinking
Expand Down Expand Up @@ -218,9 +236,9 @@ ${prompt.slice(0, 800)}`;
type TabState = 'normal' | 'working' | 'inference';

/**
* Set terminal tab title and color based on state
* Uses Kitty remote control if available (hooks run without TTY),
* falls back to escape codes for other terminals
* Set terminal tab title and color based on state.
* Uses Kitty socket-based remote control to avoid escape sequence leaks.
* Falls back to escape codes for non-Kitty terminals.
*/
function setTabTitle(title: string, state: TabState = 'normal'): void {
try {
Expand All @@ -229,31 +247,32 @@ function setTabTitle(title: string, state: TabState = 'normal'): void {
const truncated = titleWithSuffix.length > 50 ? titleWithSuffix.slice(0, 47) + '…' : titleWithSuffix;
const escaped = truncated.replace(/'/g, "'\\''");

// Check if we're in Kitty (TERM=xterm-kitty or KITTY_LISTEN_ON set)
const isKitty = process.env.TERM === 'xterm-kitty' || process.env.KITTY_LISTEN_ON;
// Get kitty socket for socket-based remote control
const socket = getKittySocket();

if (isKitty) {
// Use Kitty remote control - works even without TTY
execSync(`kitty @ set-tab-title "${escaped}"`, { stdio: 'ignore', timeout: 2000 });
if (socket) {
// Use socket-based remote control - prevents escape sequence leaks
execSync(`kitty @ --to ${socket} set-tab-title "${escaped}"`, { stdio: 'ignore', timeout: 2000 });

// Set color based on state
if (state === 'inference') {
// Purple for inference/AI thinking - active tab stays dark blue, inactive shows purple
execSync(
`kitten @ set-tab-color --self active_bg=${ACTIVE_TAB_BG} active_fg=${ACTIVE_TEXT} inactive_bg=${TAB_INFERENCE_BG} inactive_fg=${INACTIVE_TEXT}`,
`kitten @ --to ${socket} set-tab-color --self active_bg=${ACTIVE_TAB_BG} active_fg=${ACTIVE_TEXT} inactive_bg=${TAB_INFERENCE_BG} inactive_fg=${INACTIVE_TEXT}`,
{ stdio: 'ignore', timeout: 2000 }
);
console.error('[UpdateTabTitle] Set inference color (purple on inactive only)');
} else if (state === 'working') {
// Orange for actively working - active tab stays dark blue, inactive shows orange
execSync(
`kitten @ set-tab-color --self active_bg=${ACTIVE_TAB_BG} active_fg=${ACTIVE_TEXT} inactive_bg=${TAB_WORKING_BG} inactive_fg=${INACTIVE_TEXT}`,
`kitten @ --to ${socket} set-tab-color --self active_bg=${ACTIVE_TAB_BG} active_fg=${ACTIVE_TEXT} inactive_bg=${TAB_WORKING_BG} inactive_fg=${INACTIVE_TEXT}`,
{ stdio: 'ignore', timeout: 2000 }
);
console.error('[UpdateTabTitle] Set working color (orange on inactive only)');
}

console.error('[UpdateTabTitle] Set via Kitty remote control');
console.error('[UpdateTabTitle] Set via Kitty socket');
} else if (process.env.TERM === 'xterm-kitty') {
// Kitty detected but no socket - skip to avoid escape sequence leaks
console.error('[UpdateTabTitle] Kitty detected but no socket available, skipping');
} else {
// Fallback to escape codes for other terminals
execSync(`printf '\\033]0;${escaped}\\007' >&2`, { stdio: ['pipe', 'pipe', 'inherit'] });
Expand Down
31 changes: 29 additions & 2 deletions Packs/pai-hook-system/src/hooks/handlers/tab-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
*
* Pure handler: receives pre-parsed transcript data, updates Kitty tab.
* No I/O for transcript reading - that's done by orchestrator.
* Uses socket-based remote control to prevent escape sequence leaks.
*/

import { existsSync } from 'fs';
import { isValidVoiceCompletion, getTabFallback } from '../lib/response-format';
import type { ParsedTranscript, ResponseState } from '../../skills/CORE/Tools/TranscriptParser';

Expand All @@ -25,6 +27,23 @@ const ACTIVE_TAB_COLOR = '#002B80'; // Dark blue
const ACTIVE_TEXT_COLOR = '#FFFFFF';
const INACTIVE_TEXT_COLOR = '#A0A0A0';

/**
* Get kitty socket path - required for socket-only remote control.
* Using socket-based control prevents escape sequence leaks (P@kitty-cmd artifacts).
*/
function getKittySocket(): string | null {
if (process.env.KITTY_LISTEN_ON) {
return process.env.KITTY_LISTEN_ON;
}
const defaultSocket = `/tmp/kitty-${process.env.USER}`;
try {
if (existsSync(defaultSocket)) {
return `unix:${defaultSocket}`;
}
} catch {}
return null;
}

/**
* Handle tab state update with pre-parsed transcript data.
*/
Expand Down Expand Up @@ -56,11 +75,19 @@ export async function handleTabState(parsed: ParsedTranscript): Promise<void> {

console.error(`[TabState] State: ${state}, Color: ${stateColor}, Suffix: "${suffix}"`);

// Get socket for kitty remote control
const socket = getKittySocket();

if (!socket) {
console.error('[TabState] No kitty socket available, skipping tab update');
return;
}

// Set tab colors: active tab always dark blue, inactive shows state color
await Bun.$`kitten @ set-tab-color --self active_bg=${ACTIVE_TAB_COLOR} active_fg=${ACTIVE_TEXT_COLOR} inactive_bg=${stateColor} inactive_fg=${INACTIVE_TEXT_COLOR}`;
await Bun.$`kitten @ --to ${socket} set-tab-color --self active_bg=${ACTIVE_TAB_COLOR} active_fg=${ACTIVE_TEXT_COLOR} inactive_bg=${stateColor} inactive_fg=${INACTIVE_TEXT_COLOR}`;

// Set tab title
await Bun.$`kitty @ set-tab-title ${tabTitle}`;
await Bun.$`kitty @ --to ${socket} set-tab-title ${tabTitle}`;
} catch (error) {
console.error('[TabState] Failed to update Kitty tab:', error);
}
Expand Down