fix(review): harden Codex generated-artifact attachment pipeline (PR #150)

- 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>
This commit is contained in:
Codeman maintainer
2026-07-12 17:50:21 +02:00
parent 978ca57343
commit 13c877f938
10 changed files with 238 additions and 42 deletions
+38 -1
View File
@@ -58,7 +58,8 @@ describe('attachment magic links', () => {
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'
'Saved to: file:///Users/aamer/.codex-personal/generated_images/mockup%20one.png',
{ codexArtifacts: true }
);
expect(requests).toEqual([
@@ -69,6 +70,28 @@ describe('attachment magic links', () => {
]);
});
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 }> = [];
@@ -88,6 +111,20 @@ describe('attachment magic links', () => {
]);
});
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);