Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f67788e
fix(responses): fail closed on replayed agent_message ciphertext (#4454)
lidge-jun Sep 13, 2026
0b89a77
fix(catalog): bound custom native-id effort lists on gateways [skip ci]
lidge-jun Sep 13, 2026
a42e319
fix(provider): treat deepseek-flash as native multimodal
jaychou0642-create Sep 13, 2026
7755142
merge origin/dev into the lane I3 bottom link [skip ci]
lidge-jun Sep 13, 2026
b0080b7
merge the lane I3 bottom link into the DeepSeek vision carry
lidge-jun Sep 13, 2026
9f318cb
test(routing): accept native-vision DeepSeek Flash in inherited regis…
lidge-jun Sep 13, 2026
2bf3eef
fix(responses): omit replayed agent_message ciphertext before dispatch
lidge-jun Sep 13, 2026
34edd42
chore(release): open dev at 2.54.0 before releasing 2.53.0
github-actions[bot] Sep 13, 2026
7a01428
Merge pull request #4506 from lidge-jun/codex/dev-version-2.54.0
lidge-jun Sep 13, 2026
94063d0
Merge pull request #4500 from lidge-jun/codex/260913-i3-carry-4467-de…
lidge-jun Sep 13, 2026
0f2105d
fix(responses): widen the agent_message ciphertext repair after review
lidge-jun Sep 13, 2026
0226c07
fix(responses): match free text strictly, and restore canonical-path …
lidge-jun Sep 13, 2026
8e6c996
Merge pull request #4498 from lidge-jun/codex/260913-4454-encrypted-a…
lidge-jun Sep 13, 2026
53d9ac2
docs(devlog): roadmap for the contributor carry train
lidge-jun Sep 13, 2026
442e6ca
docs(devlog): record the wave-1 outcome of the contributor carry train
lidge-jun Sep 13, 2026
59aac5a
docs(devlog): close wave 1 with the lane S security review
lidge-jun Sep 13, 2026
b27cb2d
docs(devlog): correct wave 2 after the dispatch-time ownership re-check
lidge-jun Sep 13, 2026
56118c9
docs(devlog): record lane H as a noop and lane I5 as dev-based
lidge-jun Sep 13, 2026
a7b72a1
docs(credits): record the xAI Fast proposal overtaken by independent …
lidge-jun Sep 13, 2026
f0eb40e
docs(devlog): record the contributor carry train outcome
lidge-jun Sep 13, 2026
5b707d3
fix(web-search): arm non-Ollama passthrough bridge backends
lidge-jun Sep 13, 2026
c5d7f6a
feat(devin): pass user and tool-result images to the wire (#4513)
lidge-jun Sep 13, 2026
18e01cc
Merge pull request #4514 from lidge-jun/codex/260913-carry-train-record
lidge-jun Sep 13, 2026
4e18382
fix(web-search): break passthrough-bridge barrel cycle
lidge-jun Sep 13, 2026
d7c7b49
Make the Codex desktop-app restart cross-platform and fold it into --…
lidge-jun Sep 13, 2026
4b6e8cc
docs(devlog): close devin image passthrough unit with merge record (#…
lidge-jun Sep 13, 2026
cb2e15b
Merge pull request #4515 from lidge-jun/codex/260913-4429-passthrough…
lidge-jun Sep 13, 2026
df7dc1b
docs(devlog): record the desktop-restart verification outcome (#4520)
lidge-jun Sep 13, 2026
a84e6e8
fix(codex): scope the history preflight to the relabel unit (#4531)
lidge-jun Sep 13, 2026
8e532c5
release: promote verified 2.54.0 product tree to main
lidge-jun Sep 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,42 @@ unnecessary.
If you find a landing that belongs on this page, open an issue. Being missed is
the defect this file documents, not a claim you have to argue for.

### 2026-09-13: independent work that overtook an open proposal

The gate fires on what a pull request *says*. It cannot fire on a landing that
never mentions the proposal it overtakes, which is how this one happened.

[#4077](https://github.com/lidge-jun/opencodex/pull/4077) by
[@laerad777](https://github.com/laerad777) proposed opening the xAI Grok OAuth
lane to `service_tier: "priority"` and correcting the Fast-tier catalog copy.
The registry half landed independently through #4431 at `7ca00ffe7`, derived
from its own live probe, with no reference to #4077 and no trailer. The landed
scope is narrower on evidence — `grok-4.20-multi-agent-0309` stays excluded
because the gateway answers `service_tier: "default"` when sent `priority` —
so this is genuinely independent work rather than a silent carry.

The copy correction was still unlanded, and it was the part #4077 identified
first. It landed through #4474 with a `Co-authored-by` trailer naming the
author. The registry half is recorded here as an acknowledgement rather than as
carried code, because that is what the evidence supports.

The generalizable point: "independent" and "first" are different claims, and
only the second one is visible from the open queue.

### 2026-09-13: the gate also fires on prose about carrying

The matcher reads the description, so a pull request that merely *describes* a
carry train trips `missing_coauthor_credit` even when it has no source author.
[#4499](https://github.com/lidge-jun/opencodex/pull/4499) was an ordinary
implementation with no source branch; the phrases "contributor-carry train" and
"Head commit carries `[skip ci]`" were enough to fail the gate. Rewording
cleared it.

That is a false positive rather than a defect worth loosening the matcher for.
A gate that occasionally asks an author to justify wording is cheaper than one
that misses a real uncredited carry, which is the failure this whole page
documents. Write around it.

### A gap the gate does not close

The gate checks that a trailer is **present**. It cannot check that the trailer
Expand Down
114 changes: 114 additions & 0 deletions devlog/_fin/260913_devin_image_passthrough/000_plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# 000 — Devin 이미지 패스스루

- 단위: `260913_devin_image_passthrough`
- 세션: `01a0985e-ce1a-7d12-81b9-c2e93a2bce67` (HOTL, cxc-loop)
- 기준: `origin/dev`

## 증상

사용자가 Codex composer에 이미지를 붙여넣고 devin/swe-2에 보냈더니 턴이 0초에 죽었다.
이미지가 전달되지 않아 tesseract OCR로 우회하려던 상황이었다.

## 원인 — 세 층이 겹쳐서

와이어 계층(`src/adapters/devin/cloud-direct/chat.ts`)은 이미 멀티모달이다.
`ContentPart`에 `{type:"image", mimeType, base64Data, caption}`이 있고
`encodeImageData`(:189-196)가 이를 `ChatMessagePrompt` 필드 #10 `ImageData`
`{#1 base64_data, #2 mime_type, #3 caption}`로 인코딩한다. extension.js 대조 검증 완료.

그런데 매핑 계층(`src/adapters/devin.ts`)이 이미지를 버린다.

| 함수 | 줄 | 하는 일 |
|---|---|---|
| `textFromParts` | :205-209 | `type:"text"`만 뽑아 문자열로 반환 — 이미지는 빈 문자열 기여 |
| `mapOneMessage` (user) | :293-294 | 텍스트만 남기고 `if (!text) return undefined` — **이미지만 있는 메시지가 통째로 사라짐** |
| `toolResultText` | :211-214 | 같은 방식으로 툴 결과의 이미지도 버림 |

사용자의 스크린샷에서 data: URI가 텍스트 첨부로 보인 것은 UI 표시이고, 실제로는
`OcxImageContent`(`types/request.ts:189-195`)의 `imageUrl`이 data: URL로 들어온다.
매핑이 그것을 인식하지 못하고 텍스트 추출에서 빈 문자열을 얻어 메시지를 드롭한다.

## 수정 — `src/adapters/devin.ts`

### 1) NEW: `mapOcxContentToWire` 헬퍼

```ts
import type { ContentPart } from "./devin/cloud-direct/chat";

/**
* Convert inbound content parts to the wire shape the encoder accepts.
*
* The wire layer is already multimodal (ChatMessagePrompt field #10 ImageData),
* but every image was discarded here: textFromParts returned text-only strings,
* and a message whose only content was an image was dropped entirely. A data:
* URL carries everything field #10 needs; a remote https URL cannot be inlined
* without a fetch, so it stays as an explicit text reference rather than
* pretending the model can see a picture it cannot. Video has no Devin field.
*/
function mapOcxContentToWire(content: string | OcxContentPart[] | undefined): string | ContentPart[] {
if (typeof content === "string" || !Array.isArray(content)) return content ?? "";
const out: ContentPart[] = [];
for (const part of content) {
if (part.type === "text" && part.text) out.push({ type: "text", text: part.text });
else if (part.type === "image") {
const m = part.imageUrl.match(/^data:([^;]+);base64,(.+)$/);
if (m) out.push({ type: "image", mimeType: m[1]!, base64Data: m[2]! });
else out.push({ type: "text", text: "[image url: " + part.imageUrl + "]" });
}
}
return out;
}
```

### 2) MODIFY: `mapOneMessage` user/developer 분기

```ts
// before
const text = textFromParts(message.content).trim();
if (!text) return undefined;
return { role: ..., content: text };

// after
const content = mapOcxContentToWire(message.content);
// 텍스트 없이 이미지만 있는 메시지도 유효하다 — 드롭하면 안 된다.
if (typeof content === "string" ? !content.trim() : content.length === 0) return undefined;
return { role: ..., content };
```

### 3) MODIFY: 툴 결과

```ts
// before
content: toolResultText(message),

// after — 오류 접두사는 유지하되, 이미지가 있으면 ContentPart[]로 넘긴다
const wireContent = mapOcxContentToWire(message.content);
content: message.isError
? (typeof wireContent === "string" ? "ERROR: " + wireContent
: [{ type: "text", text: "ERROR:" }, ...wireContent])
: wireContent,
```

## NEW: tests/providers/devin-image-passthrough.test.ts

| 케이스 | 기대 |
|---|---|
| data: URL 이미지 파트가 ContentPart image로 변환 | `{type:"image", mimeType:"image/png", base64Data:"iVBOR..."}` |
| 이미지만 있는 user 메시지가 드롭되지 않음 | items에 존재 |
| 텍스트 + 이미지 혼합 | 순서 보존 |
| https URL 이미지 | 텍스트 참조로 남음 |
| 툴 결과의 이미지 | ContentPart[]로 전달 |
| 툴 결과 오류 + 이미지 | ERROR 접두사 유지 |
| 와이어 인코딩 | buildGetChatMessageRequestForTests가 field #10을 냄 |

## 레이아웃 등록

- `scripts/test-layout/layout.json` explicit → providers
- `tests/fixtures/test-layout-expected.json`


## 결과 (2026-09-13)

- PR [#4513](https://github.com/lidge-jun/opencodex/pull/4513) squash merge: `c5d7f6a6efc22ab2fc17b377e0d6aae79c77b6a8`
- exact-head CI (`6106478389`): test 1-4/4, macos 1-2/2, keyring/docker/npm-global/hygiene/gates 전부 green (windows shard는 runner 선택으로 skip)
- 로컬 포커스 테스트: devin 도메인 9개 파일 154 pass / 0 fail (디버깅용; 제품 스위트·typecheck·build는 NOT RUN, 호스티드 CI가 머지 증거)
137 changes: 137 additions & 0 deletions devlog/_plan/260913_contributor_carry_train/000_plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Contributor carry train — 60+ scored work into dev

## Objective

Land the highest-value contributor work that is still open on this repository.
Seventeen open pull requests score 60 or higher by maintainer review once the
maintainer's own #4462 is set aside; 16 of them are dispatched here and #3389 is
deferred for a recorded reason. Eight issues score 60 or higher with no pull
request owning them. The goal also closes everything those landings actually
resolve. `origin/dev` was `2df82f412` when this roadmap was written.

This unit follows the 36-PR lane-stack merge in
`devlog/_plan/260913_lane_stack_merge/`, which landed the maintainer-authored
backlog. That batch is the precedent for the mechanism here; what changes is the
authorship. Every branch in this train belongs to someone else, so attribution is
a correctness requirement rather than a courtesy.

## Selection

Candidates come from the maintainer's own `## 리뷰 · 우선순위 NN / 80` comments,
harvested live from every open issue and pull request through the GraphQL API on
2026-09-13. All 73 open pull requests and all 58 open issues carry a score, so
the 60 cut is a real threshold rather than a sample.

The inventory and its verification live in `001_candidate_inventory.md`.

## Attribution is a gate, not a footnote

`AGENTS.md` requires a `Co-authored-by` trailer naming the original author on any
landing that reimplements, supersedes, carries, or rebases their pull request.
`CREDITS.md` records 27 landings that failed this and explains why prose credit is
not equivalent: GitHub reads the trailer, and nothing reads a sentence in a commit
body.

Two rules follow for every lane in this unit:

- The trailer address is taken from the author's GitHub account, in the numeric
`users.noreply.github.com` form, not from the commit metadata on their branch.
- The trailer is verified in the **actual landing commit** after the squash, not
in the pull request description. A custom squash message silently drops text
that was only in the body.

## CI economy

Unchanged from the previous batch and re-verified there. Each lane is a cumulative
stack: the bottom branch merges `origin/dev`, each branch above merges its parent's
resulting commit, so merging bottom-up produces shrinking diffs. Every non-tip head
commit carries `[skip ci]`, which suppresses `ci.yml`, `react-doctor`,
`service-lifecycle` and `issue-quality-tests`. Only `enforce-pr-target`,
`pr-hygiene` and `pr-labeler` still run, because `pull_request_target` ignores the
skip marker.

Only the lane tip runs the full matrix, and that tip run is the merge gate for the
whole lane. Squash messages never contain `[skip ci]`, because the dev-branch run
each merge triggers is the regression gate.

This is an owner-authorized deviation from the MAINTAINERS.md requirement that
every pull request carry its own successful required check. Each merge comment
states it explicitly: the owner authorization, the tip pull request and run id
that covers the branch, and the fact that this branch's own `ci` check never ran.

## Common principles

Pushes use `git push --no-verify`, fast-forward only. No `--force` anywhere.
Nothing is pushed to `dev`, `main` or `preview`.

Local full-suite runs are forbidden. Allowed local checks are `bun run typecheck`,
`bun run structure:check`, `bun run privacy:scan`, and `bun test` limited to the
files a pull request touches. Hosted CI on the lane tip is the only suite proof
this goal accepts.

Each lane thread works in its own managed worktree and may fan out unlimited
`xai/grok-4.6` subagents inside that worktree. Subagents share their parent's
checkout, so a lane's subagents never run branch-level git operations concurrently.

Lane threads never merge, never mark a pull request ready, and never close
anything. They push and report. The main session performs every merge.

Conflicts are resolved by reading both sides and judging which matches current
behavior. A genuinely ambiguous conflict stops that link and is reported with both
sides and the reasoning, never guessed past.

## Lane map

| Lane | Doc | Contents, bottom to top | Owner model |
| --- | --- | --- | --- |
| R responses/core | 010 | 4455, 4086, 4409, 4387 | anthropic/claude-opus-5 |
| C chat + adapters | 010 | 4438, 4389, 4457 | anthropic/claude-opus-5 |
| L cli + hub | 010 | 4382, 4413, 4170 | xai/grok-4.6 |
| B bridge + images + auth | 010 | 4381, 4388, 4460 | xai/grok-4.6 |
| S security-review hold | 010 | 4447 | xai/grok-4.6 |
| X small carries | 010 | 4077 copy fix, 4171 dedupe | xai/grok-4.6 |
| I1 Windows issues | 010 | 4425, 4442 | kimi/k3[1m] |
| I2 config + account issues | 010 | 4430, 4435 | xai/grok-4.6 |
| H context history | 030 | 3663 | anthropic/claude-opus-5 |
| I3 routing compatibility | 030 | 3775, 4436, 4429 | xai/grok-4.6 |
| I4 encrypted history regression | 030 | 4454 | anthropic/claude-opus-5 |

Wave 1 prepares R, C, L, B, S, X, I1 and I2 in parallel. Wave 2 is I3, I4 and H.

Wave 2 is not parallel throughout. I3 prepares alongside, but I4 and H must be
serialized in that order: both land in the routed Responses request path, so H
rebases onto I4 rather than preparing beside it. Preparing them as peers would
put two `src/server/responses/core.ts` writers in one wave, which is the exact
thing lane R exists to prevent.

Lane S is prepared in wave 1 but is deliberately not merged in wp3. `#4447`
touches CORS and the management provider routes, which is inside the
MAINTAINERS.md security-review boundary, and a tip merge would have landed it on
a CI signal that was never meant to certify it. Splitting it out of lane B keeps
lane B's tip at `#4460` and keeps the hold visible at merge time rather than
three documents away.

## Work phases

wp1 is this roadmap. wp2 prepares wave 1, wp3 merges it, wp4 prepares wave 2
against landed `dev`, wp5 merges wave 2 and confirms the dev regression run, and
wp6 closes what landed and records the outcome.

## Risks

Five of the carried pull requests touch `src/server/responses/core.ts`: #4455,
#4086, #4409 and #4387 in lane R, plus #3663 in lane H. Issue #4454 lands in the
same path without being a pull request at all. #3389 is a sixth `core.ts`
toucher and is deferred for an unrelated reason recorded in
`001_candidate_inventory.md`. Lane R serializes its four; H and I4 serialize
after R. A lane merged out of order defeats the shrinking-diff property.

GitHub had not computed `mergeable` for most of these branches when the roadmap
was written, so lane assignment rests on file overlap rather than a proven
conflict-free merge. Each lane thread discovers its real conflicts at prepare time
and reports them.

All four issue lanes — I1, I2, I3 and I4 — have no branch to carry at all. Those
are ordinary implementations by the lane thread, and they carry no
`Co-authored-by` trailer because there is no source branch; the reporter is
credited in the description instead.
Loading
Loading