mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
* fix(claude): separate skills from slash commands * fix(claude): make native skills ready before use * fix(claude): keep skill commands before attachments * fix(claude): align skill catalog with launch args * fix(claude): include launch plugin skills * fix(claude): use native loaded skill list * refactor(claude): remove obsolete metadata callback * fix(claude): wait for skill catalog before draining messages * fix(claude): cancel deferred startup messages * fix(claude): bound skill catalog startup probe
159 lines
6.0 KiB
TypeScript
159 lines
6.0 KiB
TypeScript
import { ApiClient, ApiSessionClient } from '@/lib';
|
|
import { MessageQueue2 } from '@/utils/MessageQueue2';
|
|
import { logger } from '@/ui/logger';
|
|
import { AgentSessionBase } from '@/agent/sessionBase';
|
|
import type { SessionEffort, SessionModel } from '@/api/types';
|
|
import type { EnhancedMode } from './loop';
|
|
import type { PermissionMode } from './loop';
|
|
import type { LocalLaunchExitReason } from '@/agent/localLaunchPolicy';
|
|
|
|
type LocalLaunchFailure = {
|
|
message: string;
|
|
exitReason: LocalLaunchExitReason;
|
|
};
|
|
|
|
export class Session extends AgentSessionBase<EnhancedMode> {
|
|
readonly claudeEnvVars?: Record<string, string>;
|
|
claudeArgs?: string[];
|
|
readonly mcpServers: Record<string, any>;
|
|
readonly allowedTools?: string[];
|
|
readonly hookSettingsPath: string;
|
|
/** Settings for the interactive TUI: also forwards permission-mode-carrying hooks. */
|
|
readonly localHookSettingsPath: string;
|
|
readonly startedBy: 'runner' | 'terminal';
|
|
readonly startingMode: 'local' | 'remote';
|
|
localLaunchFailure: LocalLaunchFailure | null = null;
|
|
private nativeSkillNames = new Set<string>();
|
|
|
|
constructor(opts: {
|
|
api: ApiClient;
|
|
client: ApiSessionClient;
|
|
path: string;
|
|
logPath: string;
|
|
sessionId: string | null;
|
|
claudeEnvVars?: Record<string, string>;
|
|
claudeArgs?: string[];
|
|
mcpServers: Record<string, any>;
|
|
messageQueue: MessageQueue2<EnhancedMode>;
|
|
onModeChange: (mode: 'local' | 'remote') => void;
|
|
allowedTools?: string[];
|
|
mode?: 'local' | 'remote';
|
|
startedBy: 'runner' | 'terminal';
|
|
startingMode: 'local' | 'remote';
|
|
hookSettingsPath: string;
|
|
localHookSettingsPath?: string;
|
|
permissionMode?: PermissionMode;
|
|
model?: SessionModel;
|
|
effort?: SessionEffort;
|
|
}) {
|
|
super({
|
|
api: opts.api,
|
|
client: opts.client,
|
|
path: opts.path,
|
|
logPath: opts.logPath,
|
|
sessionId: opts.sessionId,
|
|
messageQueue: opts.messageQueue,
|
|
onModeChange: opts.onModeChange,
|
|
mode: opts.mode,
|
|
sessionLabel: 'Session',
|
|
sessionIdLabel: 'Claude Code',
|
|
applySessionIdToMetadata: (metadata, sessionId) => ({
|
|
...metadata,
|
|
claudeSessionId: sessionId
|
|
}),
|
|
permissionMode: opts.permissionMode,
|
|
model: opts.model,
|
|
effort: opts.effort
|
|
});
|
|
|
|
this.claudeEnvVars = opts.claudeEnvVars;
|
|
this.claudeArgs = opts.claudeArgs;
|
|
this.mcpServers = opts.mcpServers;
|
|
this.allowedTools = opts.allowedTools;
|
|
this.hookSettingsPath = opts.hookSettingsPath;
|
|
this.localHookSettingsPath = opts.localHookSettingsPath ?? opts.hookSettingsPath;
|
|
this.startedBy = opts.startedBy;
|
|
this.startingMode = opts.startingMode;
|
|
this.permissionMode = opts.permissionMode;
|
|
this.model = opts.model;
|
|
this.effort = opts.effort;
|
|
}
|
|
|
|
setPermissionMode = (mode: PermissionMode): void => {
|
|
this.permissionMode = mode;
|
|
};
|
|
|
|
// Override base getPermissionMode to return the Claude-narrow type. Safe
|
|
// because the only writer (setPermissionMode above) accepts only Claude
|
|
// PermissionMode, so the field cannot hold a foreign flavor value.
|
|
getPermissionMode(): PermissionMode | undefined {
|
|
return this.permissionMode as PermissionMode | undefined;
|
|
}
|
|
|
|
setModel = (model: SessionModel): void => {
|
|
this.model = model;
|
|
};
|
|
|
|
setEffort = (effort: SessionEffort): void => {
|
|
this.effort = effort;
|
|
};
|
|
|
|
setNativeSkillNames = (names: readonly string[]): void => {
|
|
this.nativeSkillNames = new Set(names);
|
|
};
|
|
|
|
expandSkillReference = (message: string, trailingContext = ''): string => {
|
|
const match = /^\s*\$([^\s]+)(?=\s|$)/.exec(message);
|
|
if (!match || !this.nativeSkillNames.has(match[1])) return message;
|
|
const expanded = `/${match[1]}${message.slice(match[0].length)}`;
|
|
return trailingContext ? `${expanded}\n\n${trailingContext}` : expanded;
|
|
};
|
|
|
|
recordLocalLaunchFailure = (message: string, exitReason: LocalLaunchExitReason): void => {
|
|
this.localLaunchFailure = { message, exitReason };
|
|
};
|
|
|
|
/**
|
|
* Clear the current session ID (used by /clear command)
|
|
*/
|
|
clearSessionId = (): void => {
|
|
this.sessionId = null;
|
|
logger.debug('[Session] Session ID cleared');
|
|
};
|
|
|
|
/**
|
|
* Consume one-time Claude flags from claudeArgs after Claude spawn
|
|
* Currently handles: --resume (with or without session ID)
|
|
*/
|
|
consumeOneTimeFlags = (): void => {
|
|
if (!this.claudeArgs) return;
|
|
|
|
const filteredArgs: string[] = [];
|
|
for (let i = 0; i < this.claudeArgs.length; i++) {
|
|
if (this.claudeArgs[i] === '--resume') {
|
|
// Check if next arg looks like a UUID (contains dashes and alphanumeric)
|
|
if (i + 1 < this.claudeArgs.length) {
|
|
const nextArg = this.claudeArgs[i + 1];
|
|
// Simple UUID pattern check - contains dashes and is not another flag
|
|
if (!nextArg.startsWith('-') && nextArg.includes('-')) {
|
|
// Skip both --resume and the UUID
|
|
i++; // Skip the UUID
|
|
logger.debug(`[Session] Consumed --resume flag with session ID: ${nextArg}`);
|
|
} else {
|
|
// Just --resume without UUID
|
|
logger.debug('[Session] Consumed --resume flag (no session ID)');
|
|
}
|
|
} else {
|
|
// --resume at the end of args
|
|
logger.debug('[Session] Consumed --resume flag (no session ID)');
|
|
}
|
|
} else {
|
|
filteredArgs.push(this.claudeArgs[i]);
|
|
}
|
|
}
|
|
|
|
this.claudeArgs = filteredArgs.length > 0 ? filteredArgs : undefined;
|
|
logger.debug(`[Session] Consumed one-time flags, remaining args:`, this.claudeArgs);
|
|
};
|
|
}
|