Skip to content

feat(assistants): implement Codex rollout-based file attachment detection - #508

Merged
8nevil8 merged 9 commits into
codemie-ai:mainfrom
SleepySML:EPMCDME-13885
Aug 27, 2026
Merged

feat(assistants): implement Codex rollout-based file attachment detection#508
8nevil8 merged 9 commits into
codemie-ai:mainfrom
SleepySML:EPMCDME-13885

Conversation

@SleepySML

@SleepySML SleepySML commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Tickets: Fixes EPMCDME-13885, Fixes EPMCDME-13907

Codex side (EPMCDME-13885)

  • src/cli/commands/assistants/chat/codexUploadsDetector.ts: scans Codex rollout JSONL by matching session cwd to the current working directory (symlink-tolerant realpath), extracts input_image blocks from the most-recent user turn via a two-pass scan, OOM-safe base64 size guard.
  • src/agents/plugins/codex/codex-message-types.ts: adds CodexResponseItemMessage and CodexContentBlock; extends CodexEventMsg.
  • src/cli/commands/assistants/chat/index.ts: CODEMIE_AGENT === 'codex' dispatch gate; Codex sessions take the new path, Claude sessions keep the CODEMIE_SESSION_ID path.
  • src/agents/plugins/codex/index.ts: exposes getCodexDiscoverySessionRoots, isCodexInjectedUserText and related types on the public barrel so the CLI imports only the plugin's public contract.

Claude side (EPMCDME-13907)

  • src/cli/commands/assistants/chat/claudeUploadsDetector.ts: replaces the buggy RECENT_MESSAGES_LIMIT=2 window and parent/child assumption with promptId-based current-turn grouping.
  • Additional fix (commit 436a0d9): collect every message of the current prompt turn by promptId regardless of isMeta. Claude Code 2.1.218 stores the uploaded image base64 in the NON-meta turn message (marked [Image #N]) while the [Image: source: /path] filename text is in a separate isMeta message of the same promptId. Collecting isMeta-only detected the filename but no base64 and returned zero attachments. promptId still bounds detection to the current turn (earlier turns not re-sent). A regression test for the real 2.1.218 structure was added.

Verification

  • Codex: a real Codex 0.143 rollout was inspected — input_image with image_url starting data:image/png;base64, the image-name/path wrapper, and event_msg.local_images. Running the real codemie assistants chat with CODEMIE_AGENT=codex detected 1 file, uploaded it, and the assistant confirmed it saw the image.
  • Claude: a real live upload in Claude Code 2.1.218 was fed to the real detector — detection went from 0 → 1 (81 KB base64) after the fix.
  • Gates: typecheck, lint, build pass; full unit suite 204 files / 3020 tests pass (plus the new regression test).

Reviewer feedback from #466

  1. options.conversationId "regression" — WON'T FIX (intended). --conversation-id identifies the assistant chat thread; CODEMIE_SESSION_ID identifies the Claude session whose JSONL holds the uploaded blobs. Feeding conversationId into detectFileUploadsFromSession searches a non-existent session file — the exact bug fixed by commit bfb011c.
  2. CLI imports Codex plugin internals — FIXED (540bef1). The detector imports only the codex plugin's public index.ts barrel; deep imports removed. Remaining deep imports in analytics/* are pre-existing on main; out of scope.
  3. CodexSessionMetadata "5 unused fields" — WON'T FIX (premise incorrect). The type pre-exists on main (diff additions-only) and its fields are consumed by the metrics/conversations processors; stripping breaks the build.

Supersedes #466 (original author is no longer working on it).

SergeyVNikitin and others added 8 commits August 5, 2026 14:38
…oad detection

Replace broken two-pass buildAttachmentMap (wrong JSONL structure assumption)
and RECENT_MESSAGES_LIMIT=2 (too narrow for real sessions with tool-result
messages) with a turn-boundary backward scan that stops at the most recent
assistant message.

Real Claude Code JSONL has isMeta=true messages carrying both base64 attachment
data and [Image: source: /path] filename text in the same message object.
The old code expected base64 in a non-meta parent and filename text in a meta
child, so the attachment map was never populated and zero files were returned.

The scan window of 2 also broke in real sessions where tool-result messages
at positions 1-2 pushed the image meta message to position 3, outside the window.

EPMCDME-13907
…ude sessions

The previous turn-boundary scan (stopping at type==='assistant') failed in
real Claude Code JSONL because:
- Bug A: the assistant tool_use entry appears *after* the user's isMeta
  messages for the same prompt, so the scan broke before reaching the images.
- Bug B: base64 data and the [Image: source: /path] filename live in two
  *separate* isMeta entries (not the same one), so the old single-message
  pass produced the image with a fallback filename.

Fix: use the promptId field that Claude Code stamps on every message in a
single prompt turn. Find the most recent non-meta user message, capture its
promptId, collect all isMeta messages sharing that id, gather filenames
across them first, then match positionally to base64 attachment items.

Also adds promptId to the ClaudeMessage interface and updates test fixtures
to reflect the real split-message JSONL structure.

Generated with AI

Co-Authored-By: codemie-ai <codemie.ai@gmail.com>
…t --conversation-id

--conversation-id identifies the assistant chat thread (e.g. a workflow_id
generated by a skill). CODEMIE_SESSION_ID identifies the Claude session whose
JSONL contains uploaded file blobs. Using --conversation-id for session lookup
caused detectFileUploadsFromSession to look for a non-existent session file,
returning no attachments even when files were uploaded in the current Claude turn.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0162w98kuqJ7EWaA6h2FjDCK
…tion

Adds detectCodexFileUploads() which discovers the active Codex rollout
by scanning ~/.codex/sessions via getCodexDiscoverySessionRoots() and
matching session_meta.cwd to the caller's CWD. Extracts input_image
blocks from the most recent user response_item record.

Wires CODEMIE_AGENT-aware dispatch in chat/index.ts: when CODEMIE_AGENT
equals 'codex', the new detector runs; otherwise the existing Claude
CODEMIE_SESSION_ID path is used (no regression).

Also adds CodexResponseItemMessage, CodexContentBlock, and extends
CodexEventMsg with images/local_images to make attachment types explicit.

Generated with AI

Co-Authored-By: codemie-ai <codemie.ai@gmail.com>
- CR-001: use imageOnlyIndex (increments only on input_image) for local_images
  lookups; the prior localImageIndex incremented on input_file blocks too,
  misaligning filename resolution in mixed file+image turns
- CR-002: find targetEventMsg in a second backward pass bounded to records at
  or before the response_item position, so a follow-up message no longer severs
  the filename chain from the attachment turn
- CR-003: estimate decoded size with base64 length math before the size guard,
  avoiding a full Buffer.from allocation that could OOM and silently drop
  valid attachments; also reject empty data URI payloads early

Generated with AI

Co-Authored-By: codemie-ai <codemie.ai@gmail.com>
Planning, review, and complexity-assessment artifacts for the Codex
file attachment detection implementation (sdlc-light flow).

Generated with AI

Co-Authored-By: codemie-ai <codemie.ai@gmail.com>
Addresses the CLI -> Registry -> Plugin boundary review comment on the
Codex upload detector: it imported plugin internals directly
(codex.paths.js, session/codex-user-prompt.js). Expose
getCodexDiscoverySessionRoots, isCodexInjectedUserText, CodexDiscoveryRoot,
CodexResponseItemMessage and CodexContentBlock from the codex plugin's
public index barrel, and repoint the detector to import only that contract.

Refs: EPMCDME-13885
@SleepySML

Copy link
Copy Markdown
Contributor Author

Disposition of the automated-review blockers from #466

Pushed 540bef1. Status of the three items:

conversationId "regression" — WON'T FIX (working as intended).
--conversation-id identifies the assistant chat thread (e.g. a skill's workflow_id); CODEMIE_SESSION_ID identifies the Claude session whose JSONL holds the uploaded blobs. Feeding conversationId into detectFileUploadsFromSession looks up a non-existent session file — that was the original bug, fixed deliberately in bfb011c. Reverting would re-introduce it.

② CLI imports Codex plugin internals — FIXED (540bef1).
getCodexDiscoverySessionRoots, isCodexInjectedUserText, CodexDiscoveryRoot, CodexResponseItemMessage, CodexContentBlock are now exported from the codex plugin's public index.ts, and codexUploadsDetector.ts imports only that barrel — no more deep codex.paths.js / session/codex-user-prompt.js imports. Typecheck, lint, build, and 763 tests pass.

CodexSessionMetadata "5 unused fields" — WON'T FIX (premise is incorrect).
CodexSessionMetadata already exists on main; this PR's diff to codex-message-types.ts is +27 / -0 (it only adds CodexResponseItemMessage / CodexContentBlock, it never touches CodexSessionMetadata). The fields are also consumed by pre-existing analytics code — e.g. resolveBranch() reads meta.branch and meta.projectPath, and the metrics/conversations processors read meta.createdAt. Stripping them would break the build. Out of scope for this PR.

@SleepySML

Copy link
Copy Markdown
Contributor Author

Verification Summary

Feature: Codex rollout-based file-attachment detection for codemie assistants chat (Ticket: EPMCDME-13885)

Verification Methods

  1. Detector unit harness — 9 edge cases via fixtures (no model calls), all green. Validated: filename from <image name=[…] path="…"> wrapper; fallback to event_msg.local_images; empty result for no-attachment (no regression); two-pass filename retention for two images + follow-up message; foreign-cwd rollout excluded; mixed input_image+input_file → only the image; symlinked cwd matched via realpath; >100 MB size-guard skip; malformed data URI skipped without crash.

  2. Real Codex 0.143.0 rollout (closes the format-drift risk) — ran a live codemie-codex exec -i <image> session under CodeMie SSO. The rollout Codex actually wrote contains a response_item with an input_image whose image_url starts with data:image/png;base64,…, the <image name=[…] path="…"> wrapper text, and event_msg.local_images: ["shot.png"]. Running the real codemie assistants chat with CODEMIE_AGENT=codex against that rollout → 📎 Detected 1 file(s), ✓ Uploaded 1 file(s) to CodeMie, and the assistant confirmed it saw the image. Conclusion: the on-disk format of current Codex (0.143) matches the parser; no parser change needed.

  3. Live E2E runtime (authenticated) — single image; two images + follow-up (two-pass); no-attachment (no regression); --file control path with PNG, JPG, and a larger <100 MB JPG. All detected/loaded, uploaded, and visible to the assistant.

  4. Gatestypecheck, lint, build pass; full unit suite: 204 files / 3020 tests passed.

Review-comment dispositions (verified)

  • CR#1 (dropping conversationId is a regression) — WON'T FIX. Codex detection keys off cwd/rollout match, Claude off CODEMIE_SESSION_ID; neither uses --conversation-id. Runtime check: passing a bogus --conversation-id fake-workflow-123 did not break detection.
  • CR#2 (CLI imports plugin internals) — FIXED in 540bef1. The detector imports only the codex plugin's public index.ts barrel. Note: the remaining deep imports in analytics/* are pre-existing on main (this PR introduces none) → separate tech-debt, out of scope for this PR.
  • CR#3 (5 unused CodexSessionMetadata fields) — WON'T FIX. The type pre-exists on main (this PR's diff to that file is additions-only) and the fields are consumed by the metrics/conversations processors; processor tests are green.

Verdict

Verified end-to-end, including a real Codex 0.143 rollout. Ready for review/merge.

…eta turn message

Claude Code 2.1.218 stores an uploaded image's base64 in the NON-meta user
message of the prompt turn (marked "[Image #N]"), while the
"[Image: source: /path]" filename text is in a separate isMeta message of the
same promptId. The detector collected attachments from isMeta messages only, so
on real sessions it found the filename but no base64 and returned zero
attachments (silent no-op of the automatic detection this path advertises).

Collect every message of the current prompt turn by promptId regardless of
isMeta; promptId still bounds detection to the current turn (earlier turns are
not re-sent). Add a regression test for the real 2.1.218 structure. Verified
end-to-end against a real live upload: detection goes 0 -> 1.

Refs: EPMCDME-13907
@8nevil8
8nevil8 merged commit 5f94aea into codemie-ai:main Aug 27, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants