bridle:admin.jsonl(385 KB) exceedsMAX_BYTES = 256 KBin api/src/slices/agent/file/data/file.gateway.ts:22.- Chat (
/agents/:id/transcript) silently returns{ messages: [] }— user sees empty chat. - Files viewer in admin gets a 400 → frontend store crashes on
env.dataof undefined.
- Chat (
Two independent fixes, both lazy:
- Chat transcript — change
GET /agents/:id/transcriptto a tail-first paginated endpoint. Default returns the most recent N messages + a cursor; admin chat UI requests older pages when the user scrolls to the top. - File viewer — add
rangesupport toGET /agents/:id/files/content. Default returns the first chunk + ahasMoreflag for binary-safe text files; UI shows a "Load more" button (or auto-loads on scroll bottom).
The 256 KB write cap stays — it's about edit safety. The new endpoints bypass it for read-only access by streaming/chunking instead of loading the whole file into memory.
| Slice | Layer | Change |
|---|---|---|
bridle (api) |
controller + dto | New ?limit+?cursor query, paginated response, tail-first parsing |
agent/file (api) |
gateway + controller + dto | New readRange(agentId, path, offset, limit) method; controller exposes ?offset=&limit= |
bridle (admin) |
store + Provider | First load fetches latest page, scroll-up triggers older-page fetch with cursor |
agent/file (admin) |
store + Viewer | Chunked load: first 256 KB by default, "Load more" appends next chunk |
| Method | Endpoint | Change |
|---|---|---|
| GET | /agents/:agentId/transcript |
Add limit (default 50) and cursor (opaque). Response gains nextCursor: string | null, hasMore: boolean |
| GET | /agents/:agentId/files/content |
Add offset (default 0) and limit (default 262144). Response gains totalSize, nextOffset | null, hasMore. Existing content/size/updatedAt keep their meaning for the returned slice. |
None — sessions live in S3/MinIO, not in Prisma.
FIXED — NestJS + Nuxt + Pinia + shadcn-vue. No new dependencies.
domain/file.gateway.ts- Add
abstract readRange(agentId: string, path: string, offset: number, limit: number): Promise<IFileChunk>
- Add
domain/file.types.ts- Add
IFileChunk { path, content, size, totalSize, offset, nextOffset: number | null, hasMore: boolean, updatedAt }
- Add
data/file.gateway.ts- Implement
readRangeusing S3GetObjectCommandwithRange: bytes=offset-end HeadObjectCommandfirst to getContentLengthfortotalSize- Cap
limitatMAX_READ_CHUNK = 512 * 1024(twice the edit cap; range read is cheap) - Keep existing
read()as-is for now (it still enforcesMAX_BYTESfor save flow)
- Implement
file.controller.tsGET contentaccepts@Query() ReadFileQueryDto(path + offset + limit), delegates toreadRange- Backward-compat: omitted offset/limit → first 256 KB chunk
dtos/- New
readFile.query.dto.ts(path: string, offset?: number, limit?: number) - New
fileChunk.dto.tsfor response
- New
dtos/transcript.dto.ts- Add
nextCursor?: string,hasMore: booleantoTranscriptResponseDto - New
TranscriptQueryDto(channel?: string, limit?: number, cursor?: string)
- Add
bridle.controller.ts:transcript- Drop
fileGateway.read(256 KB blocker), use newfileGateway.readRangein reverse - Strategy: read file size via
HeadObject(already insidereadRange), then read from end in 64 KB blocks until N user/assistant messages collected (cursor = byte offset). Stream-style, never loads whole file. - On 404 → empty page, hasMore=false
- Cursor format: base64 of the byte offset to read backward from (opaque string in DTO)
- Drop
- Helper in
bridle.gateway.ts(data layer) — keeps controller thin per CleanSlice rules. Method:loadTranscriptPage(agentId, channel, limit, cursor): Promise<TranscriptPage>
stores/agentFile.ts- Update
fetchContenttofetchContent(agentId, path, offset?, limit?)and returnIFileChunk - Handle the envelope properly (
res.data?.data ?? throw new Error(res.error))
- Update
components/agentFile/Provider.vue- Track
loadedSizeper open file - Append on "Load more" —
content = original + chunk.content - Disable editor when
hasMore(it's a partial file — editing would truncate)
- Track
components/agentFile/Viewer.vue- Show "Loaded X KB of Y KB" hint + "Load more" button when
hasMore
- Show "Loaded X KB of Y KB" hint + "Load more" button when
stores/bridle.ts- Add
nextCursor,hasMoreOlderstate loadTranscriptbecomes initial-page fetch- New action
loadOlderTranscript()— fetches next page, prepends tomessages - SDK call: switch from raw
fetchto generatedBridleService.getBridleTranscript(consistent with other admin slices)
- Add
components/bridle/Provider.vue- On scroll near top of ScrollArea viewport → call
loadOlderTranscript() - Preserve scroll position after prepend (compute delta of scrollHeight before/after)
- Visual "Load older messages…" spinner near top while loading
- On scroll near top of ScrollArea viewport → call
| Path | Change |
|---|---|
api/src/slices/agent/file/domain/file.gateway.ts |
add readRange |
api/src/slices/agent/file/domain/file.types.ts |
add IFileChunk |
api/src/slices/agent/file/data/file.gateway.ts |
impl readRange via S3 Range |
api/src/slices/agent/file/dtos/readFile.query.dto.ts |
new |
api/src/slices/agent/file/dtos/fileChunk.dto.ts |
new |
api/src/slices/agent/file/dtos/index.ts |
export |
api/src/slices/agent/file/file.controller.ts |
use new query DTO, return chunk |
api/src/slices/bridle/dtos/transcript.dto.ts |
add cursor fields + query DTO |
api/src/slices/bridle/dtos/index.ts |
export |
api/src/slices/bridle/data/bridle.gateway.ts |
impl loadTranscriptPage |
api/src/slices/bridle/domain/bridle.gateway.ts |
abstract loadTranscriptPage |
api/src/slices/bridle/bridle.controller.ts |
thin out, use new gateway method |
admin/slices/agent/file/stores/agentFile.ts |
chunked fetchContent |
admin/slices/agent/file/components/agentFile/Provider.vue |
append-on-load + disable edit on partial |
admin/slices/agent/file/components/agentFile/Viewer.vue |
"Load more" button + size hint |
admin/slices/bridle/stores/bridle.ts |
pagination state + loadOlder action |
admin/slices/bridle/components/bridle/Provider.vue |
scroll-top auto-load + position preserve |
- Empty file / 404 — returns
content='',totalSize=0,hasMore=false. No crash. - Multi-line JSONL message — JSONL writers always end each event with
\n, so reading backwards by newline boundaries is safe. - Cursor races vs. live socket — chat append from WS still goes to the end of
messages. Pagination only prepends. They don't collide. - Edit on partial — disabled in Viewer (the only writable extensions are
.md/.jsonand a partial.jsonwould fail validation anyway, but explicit is clearer). - Backward compat — existing callers that don't pass
offset/limitget the first 256 KB. Same shape just withhasMore=falseif the file fits.
- Open the broken agent (
agent-31a6fbd1-...) chat — should show recent messages, scroll up loads older. - Open Files →
bridle:admin.jsonl— should display first 256 KB with "Load more" prompt. - Open a small file (
access.json) — should display as before, no "Load more". - Reset chat — empty state still works.
- Edit
MEMORY.md(small file) — save still works.
Pure addition: new query params default to original behavior. Reverting means undoing the four commits.
Approve to proceed.