mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
Adds the foundation for serving local files to the browser as live external attachments with a stable id, so requests never carry arbitrary absolute paths. - attachment-registry: in-memory, session-scoped registry. registerExternalAttachment validates an absolute path, resolves symlinks, enforces the path guard, and mints an `att_<uuid>` id; records are cleared when the session is removed. - attachment path guard: a configurable blocklist (secret locations + /root,/etc trees, extendable via attachmentBlockedPaths / CODEMAN_ATTACHMENT_BLOCKED_PATHS) plus an optional, default-off workspace-confinement mode. Shares one sensitive-path blocklist (web/sensitive-path.ts) with /api/download, which is refactored to use the extracted module instead of an inline copy. - terminal magic links: the session scans output for codeman://attach?path=... and emits `attachmentRequested`; the web server registers the file and broadcasts an `attachment:detected` SSE event. `codeman attach <path>` (CLI) prints the magic link or POSTs directly when a session id is known. - image watcher: detects png/pdf/docx/pptx dropped into a session's working dir and emits `attachment:detected`. - routes: POST /api/sessions/:id/attachments (register) and GET /api/sessions/:id/attachments/:attachmentId/raw (serve), both re-checking the guard before streaming. Document previews/thumbnails and the attachment-history drawer build on this foundation and land separately. Verified: tsc --noEmit, lint, format, frontend-syntax, full test:ci (2846 passed), and a server boot smoke (/api/status 200).
58 lines
1.9 KiB
TypeScript
58 lines
1.9 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { Session } from '../src/session.js';
|
|
import { parseAttachmentMagicLinks } from '../src/attachment-magic.js';
|
|
|
|
describe('attachment magic links', () => {
|
|
it('extracts absolute paths from codeman attach magic URLs', () => {
|
|
const links = parseAttachmentMagicLinks(
|
|
'Preview this: codeman://attach?path=%2Fmnt%2Fc%2FDecks%2FBoard%20Update.pptx'
|
|
);
|
|
|
|
expect(links).toEqual(['/mnt/c/Decks/Board Update.pptx']);
|
|
});
|
|
|
|
it('ignores duplicate links in one terminal chunk', () => {
|
|
const links = parseAttachmentMagicLinks(
|
|
[
|
|
'codeman://attach?path=/tmp/report.pdf',
|
|
'codeman://attach?path=/tmp/report.pdf',
|
|
'codeman://attach?path=/tmp/brief.docx',
|
|
].join('\n')
|
|
);
|
|
|
|
expect(links).toEqual(['/tmp/report.pdf', '/tmp/brief.docx']);
|
|
});
|
|
|
|
it('accepts markdown and plain-text magic paths', () => {
|
|
const links = parseAttachmentMagicLinks(
|
|
['codeman://attach?path=/tmp/notes.md', 'codeman://attach?path=/tmp/run.txt'].join('\n')
|
|
);
|
|
|
|
expect(links).toEqual(['/tmp/notes.md', '/tmp/run.txt']);
|
|
});
|
|
|
|
it('rejects relative or unsupported magic paths', () => {
|
|
const links = parseAttachmentMagicLinks(
|
|
[
|
|
'codeman://attach?path=relative.pdf',
|
|
'codeman://attach?path=/tmp/archive.zip',
|
|
'codeman://attach?path=/tmp/deck.pptx',
|
|
].join('\n')
|
|
);
|
|
|
|
expect(links).toEqual(['/tmp/deck.pptx']);
|
|
});
|
|
|
|
it('emits attachmentRequested from raw terminal output', () => {
|
|
const session = new Session({ id: 'session-attach-test', workingDir: '/tmp', mode: 'codex' });
|
|
const requested: string[] = [];
|
|
session.on('attachmentRequested', (event: { path: string }) => requested.push(event.path));
|
|
|
|
(session as unknown as { _handleTerminalOutput(data: string): void })._handleTerminalOutput(
|
|
'codeman://attach?path=%2Ftmp%2Fdeck.pptx'
|
|
);
|
|
|
|
expect(requested).toEqual(['/tmp/deck.pptx']);
|
|
});
|
|
});
|