mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
- Pass the attachment request `source` through the server deps lambda and make it a required param on SessionListenerDeps.registerAttachment + the wiring event type (the 2-arg lambda silently dropped `source`, force-confining every codex-generated artifact — the feature never worked outside the workspace); new test/session-listener-wiring.test.ts asserts the pass-through - Gate the Codex `Saved to: file://` scanner on mode === 'codex' via a codexArtifacts option threaded from the session call site; magic links stay mode-agnostic; tests assert claude/shell sessions never emit codex-generated requests - Decide the generated-artifact trust policy on the realpath-RESOLVED path (unresolvable → force-confined) and anchor the ~/.codex marker dirs to os.homedir() prefixes with startsWith instead of substring matching; symlink escape + unanchored-marker regression tests added - Run the Codex scanner on stripAnsi'd data so trailing SGR sequences don't ride into the captured URL; styled 'Saved to:' test added - Extend generateFirstPageThumbnail with jpg/jpeg/gif/webp passthrough and per-extension content types (mirrors the png passthrough) so the PR's new image formats render real thumbnails instead of 204 letter-tiles Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
134 lines
4.9 KiB
TypeScript
134 lines
4.9 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { Session } from '../src/session.js';
|
|
import { parseAttachmentMagicLinks, parseTerminalAttachmentRequests } from '../src/attachment-magic.js';
|
|
import { isSupportedAttachmentExtension } from '../src/attachment-registry.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']);
|
|
});
|
|
|
|
it('extracts Codex generated image file URLs from saved-to terminal output', () => {
|
|
const requests = parseTerminalAttachmentRequests(
|
|
'Saved to: file:///Users/aamer/.codex-personal/generated_images/mockup%20one.png',
|
|
{ codexArtifacts: true }
|
|
);
|
|
|
|
expect(requests).toEqual([
|
|
{
|
|
path: '/Users/aamer/.codex-personal/generated_images/mockup one.png',
|
|
source: 'codex-generated',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('ignores Codex saved-to output unless the codex scanner is enabled', () => {
|
|
const requests = parseTerminalAttachmentRequests(
|
|
'Saved to: file:///Users/aamer/.codex-personal/generated_images/mockup.png'
|
|
);
|
|
|
|
expect(requests).toEqual([]);
|
|
});
|
|
|
|
it('strips ANSI styling around Codex saved-to lines before capturing the URL', () => {
|
|
const requests = parseTerminalAttachmentRequests(
|
|
'\x1b[1mSaved to:\x1b[0m file:///Users/aamer/.codex/generated_images/mockup.png\x1b[0m\r\n',
|
|
{ codexArtifacts: true }
|
|
);
|
|
|
|
expect(requests).toEqual([
|
|
{
|
|
path: '/Users/aamer/.codex/generated_images/mockup.png',
|
|
source: 'codex-generated',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('emits generated artifact requests from Codex saved-to output', () => {
|
|
const session = new Session({ id: 'session-generated-artifact-test', workingDir: '/tmp', mode: 'codex' });
|
|
const requested: Array<{ path: string; source?: string }> = [];
|
|
session.on('attachmentRequested', (event: { path: string; source?: string }) => requested.push(event));
|
|
|
|
(session as unknown as { _handleTerminalOutput(data: string): void })._handleTerminalOutput(
|
|
'Saved to: file:///Users/aamer/.codex-personal/generated_images/output.png'
|
|
);
|
|
|
|
expect(requested).toEqual([
|
|
{
|
|
sessionId: 'session-generated-artifact-test',
|
|
path: '/Users/aamer/.codex-personal/generated_images/output.png',
|
|
source: 'codex-generated',
|
|
timestamp: expect.any(Number),
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('does not emit codex-generated requests from non-codex session modes', () => {
|
|
for (const mode of ['claude', 'shell'] as const) {
|
|
const session = new Session({ id: `session-generated-artifact-${mode}`, workingDir: '/tmp', mode });
|
|
const requested: Array<{ path: string }> = [];
|
|
session.on('attachmentRequested', (event: { path: string }) => requested.push(event));
|
|
|
|
(session as unknown as { _handleTerminalOutput(data: string): void })._handleTerminalOutput(
|
|
'Saved to: file:///Users/aamer/.codex-personal/generated_images/output.png'
|
|
);
|
|
|
|
expect(requested).toEqual([]);
|
|
}
|
|
});
|
|
|
|
it('supports generated image attachment extensions beyond png', () => {
|
|
expect(isSupportedAttachmentExtension('jpg')).toBe(true);
|
|
expect(isSupportedAttachmentExtension('jpeg')).toBe(true);
|
|
expect(isSupportedAttachmentExtension('webp')).toBe(true);
|
|
});
|
|
});
|