Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 2x 2x 2x 2x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | import {
BadRequestException,
forwardRef,
Inject,
Injectable,
Logger,
NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as fs from 'fs/promises';
import * as path from 'path';
import { ContextTemplate, Session, SessionInput } from '../core-entities';
import {
CreateSessionDto,
CreateSessionFromContextDto,
} from '../sessions/dto/session.dto';
import { SessionsService } from '../sessions/sessions.service';
@Injectable()
export class SessionFollowupService {
private readonly logger = new Logger(SessionFollowupService.name);
private readonly projectRoot =
process.env.REPOBURG_PROJECT_PATH || process.cwd();
constructor(
@InjectRepository(Session)
private sessionsRepository: Repository<Session>,
@InjectRepository(ContextTemplate)
private contextTemplatesRepository: Repository<ContextTemplate>,
@InjectRepository(SessionInput)
private sessionInputsRepository: Repository<SessionInput>,
@Inject(forwardRef(() => SessionsService))
private sessionsService: SessionsService,
) {}
private resolveAndValidatePath(unsafePath: string): string {
const normalizedPath = path.normalize(unsafePath);
Iif (path.isAbsolute(normalizedPath)) {
throw new BadRequestException(
`Absolute paths are not allowed: ${unsafePath}`,
);
}
const resolvedPath = path.resolve(this.projectRoot, normalizedPath);
Iif (!resolvedPath.startsWith(this.projectRoot)) {
this.logger.warn(`Path traversal attempt detected: ${unsafePath}`);
throw new BadRequestException(
`Path traversal is not allowed. Access denied for path: ${unsafePath}`,
);
}
return resolvedPath;
}
async createFromContext(
sessionId: string,
createDto: CreateSessionFromContextDto,
): Promise<Session> {
const sourceSession = await this.sessionsRepository.findOne({
where: { id: sessionId },
relations: ['sessionInputs', 'sessionInputs.aiActions'],
});
Iif (!sourceSession) {
throw new NotFoundException(
`Source session with ID "${sessionId}" not found.`,
);
}
this.logger.log(
`Creating new session from context of session ${sessionId}`,
);
const filePaths = new Set<string>();
const folderPaths = new Set<string>();
for (const input of sourceSession.sessionInputs) {
Iif (input.ad_hoc_context_definition) {
try {
const adhocContext = JSON.parse(input.ad_hoc_context_definition);
Iif (adhocContext.files && Array.isArray(adhocContext.files)) {
adhocContext.files.forEach((f) => filePaths.add(f));
}
Iif (adhocContext.folders && Array.isArray(adhocContext.folders)) {
adhocContext.folders.forEach((f) => folderPaths.add(f));
}
} catch (e) {
this.logger.warn(
`Could not parse ad_hoc_context_definition for input ${input.id}`,
);
}
}
for (const action of input.aiActions) {
Iif (
(action.action_type === 'create_file' ||
action.action_type === 'overwrite_file' ||
action.action_type === 'patch') &&
action.file_path
) {
filePaths.add(action.file_path);
} else if (action.action_type === 'request_context') {
if (action.files) {
action.files
.split(',')
.map((f) => f.trim())
.filter(Boolean)
.forEach((f) => filePaths.add(f));
}
if (action.folders) {
action.folders
.split(',')
.map((f) => f.trim())
.filter(Boolean)
.forEach((f) => folderPaths.add(f));
}
}
}
}
const validatedFiles: string[] = [];
for (const filePath of filePaths) {
try {
const safePath = this.resolveAndValidatePath(filePath);
await fs.access(safePath);
validatedFiles.push(filePath);
} catch (error) {
this.logger.warn(
`File "${filePath}" from source session context not found on filesystem, skipping.`,
);
}
}
const validatedFolders: string[] = [];
for (const folderPath of folderPaths) {
try {
const safePath = this.resolveAndValidatePath(folderPath);
const stats = await fs.stat(safePath);
if (stats.isDirectory()) {
validatedFolders.push(folderPath);
}
} catch (error) {
this.logger.warn(
`Folder "${folderPath}" from source session context not found on filesystem, skipping.`,
);
}
}
let initialTemplateForFollowup =
await this.contextTemplatesRepository.findOneBy({
is_default_condensed: true,
});
Iif (initialTemplateForFollowup) {
this.logger.log(
`Using default condensed template "${initialTemplateForFollowup.template_name}" as initial template for new follow-up session.`,
);
} else {
this.logger.log(
`No default condensed template found. Falling back to default initial template.`,
);
initialTemplateForFollowup =
await this.contextTemplatesRepository.findOneBy({
is_default_initial: true,
});
Iif (initialTemplateForFollowup) {
this.logger.log(
`Using default initial template "${initialTemplateForFollowup.template_name}" for new follow-up session.`,
);
}
}
const createSessionPayload: CreateSessionDto = {
session_title: createDto.session_title,
};
Iif (initialTemplateForFollowup) {
createSessionPayload.default_initial_context_template_id =
initialTemplateForFollowup.id;
} else {
this.logger.log(
`No default condensed or initial template found for follow-up session.`,
);
}
const newSession = await this.sessionsService.create(createSessionPayload);
if (validatedFiles.length > 0 || validatedFolders.length > 0) {
const contextDefinition = {
files: validatedFiles,
folders: validatedFolders,
};
const newSessionInput = this.sessionInputsRepository.create({
session_id: newSession.id,
user_prompt: 'Inherited context from previous session.',
ad_hoc_context_definition: JSON.stringify(contextDefinition),
generated_context_string: '',
execution_strategy: 'review_first', // Default strategy
raw_llm_response: null,
is_context_carrier: true,
});
await this.sessionInputsRepository.save(newSessionInput);
this.logger.log(
`Created initial SessionInput with inherited context for new session ${newSession.id}`,
);
}
return this.sessionsService.findOne(newSession.id);
}
}
|