diff --git a/docs-site/src/content/docs/ja/troubleshooting/windows-memory.md b/docs-site/src/content/docs/ja/troubleshooting/windows-memory.md index 37dcc60fc..9188a0570 100644 --- a/docs-site/src/content/docs/ja/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/ja/troubleshooting/windows-memory.md @@ -24,7 +24,7 @@ Windows では、#32111 クラッシュを回避するために、opencodex は - **メモリ ウォッチドッグ** - プロキシは、毎分自身のメモリをサンプリングし、ログに記録します。 観測されたメモリが 4 GiB を超えると、レート制限の警告が表示されます。 Windows ワーキング セット/RSS カウンターがコミットされた外部保持を過小報告する可能性があるため、観測されたメモリは RSS、`external`、および `arrayBuffers` の最大値になります (これらの合計ではありません)。 - **`ocx doctor`** — 「メモリ / ランタイム」セクションには *サービス* が表示されます -プロセスの Bun バージョン、RSS、外部/ArrayBuffers カウンター、JS ヒープ コンテキスト、およびストリーム モードの決定。バンドルされている Bun 1.3.14 ランタイムでは、`heapUsed` / `jscHeap` 単独ではリーク識別子ではありません。アプリレベルのリークを割り当てる前に、観察されたメモリを `responseState` および繰り返しサンプルと比較します。 +プロセスの Bun バージョン、RSS、外部/ArrayBuffers カウンター、JS ヒープ コンテキスト、ストリーム モードの決定、Bun のリビジョン、およびランタイムがバンドル版か `OPENCODEX_BUN_PATH` オーバーライドかも表示します。アクティブなオーバーライドはそのように識別され、同じオーバーライドを再度設定するよう促されることはありません。バンドルされている Bun 1.3.14 ランタイムでは、`heapUsed` / `jscHeap` 単独ではリーク識別子ではありません。アプリレベルのリークを割り当てる前に、観察されたメモリを `responseState` および繰り返しサンプルと比較します。 - **`GET /api/system/memory`** — 認証済みの同じデータ ダッシュボードまたはスクリプトの管理 API。 RSS/ヒープ/外部カウンターとともに、プロキシのメモリ内 `previous_response_id` 継続ストアのスカラー `responseState` ブロック (エントリ数、シリアル化された合計/最大バイト数、最も古いエントリの経過時間) を報告します。これはさらに成長に起因します。観察された記憶の上昇下での `responseState.totalBytes` の上昇は会話の保持を指します (長い `store:false` チェーンはターンごとに再拡張します)。一方、観察された記憶の上昇の下での横ばいの `responseState` はそのストアから遠ざかることを示します。値はスカラーのみであり、リクエスト本文、トークン、パス、アカウント識別子はありません。また、読み取りには副作用はありません (プルーニングや削除は行われません)。ダッシュボードの **メモリ可観測性** カードは同じフィールドをレンダリングし、確認ゲート付き **ドレインと再起動** アクションを提供します。現在のアクティブ ターン数を表示し、アクティブ ターンを最大 60 秒待機し (既存の 503 + `Retry-After` ドレインを再利用)、残りのターンを中止し、ライブ ポート (または障害専用サービス スーパーバイザ) 上の `ocx start` 経由でプロキシを再起動します。 respawn)Codex インジェクションを破棄せずに。これは、`POST /api/stop` の短いドレインよりも長く、情報に基づいたリサイクルです。 - **ゲートされた代替ストリーム パス** — 制限された単一リーダー リレー。 diff --git a/docs-site/src/content/docs/ko/troubleshooting/windows-memory.md b/docs-site/src/content/docs/ko/troubleshooting/windows-memory.md index ecc25aba9..92127cd3c 100644 --- a/docs-site/src/content/docs/ko/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/ko/troubleshooting/windows-memory.md @@ -22,7 +22,7 @@ Windows에서는 opencodex가 #32111 충돌을 피하기 위해 스트리밍 응 완화와 가시성만 제공합니다. **해결책은 아닙니다**. 번들된 1.3.14 런타임에서는 누수 자체가 여전히 상위 문제입니다. - **메모리 감시기** - 프록시는 1분마다 자체 메모리를 샘플링하고, 관측된 메모리가 4 GiB를 넘으면 속도 제한이 걸린 경고를 기록합니다. 관측된 메모리는 RSS, `external`, `arrayBuffers`의 합이 아니라 그중 가장 큰 값입니다. Windows의 working-set/RSS 카운터가 커밋된 external 잔존량을 낮게 잡을 수 있기 때문입니다. -- **`ocx doctor`** - "Memory / runtime" 섹션에서 *서비스* 프로세스의 Bun 버전, RSS, external/ArrayBuffers 카운터, JS 힙 문맥, 스트림 모드 결정을 보여줍니다. 번들된 Bun 1.3.14 런타임에서는 `heapUsed` / `jscHeap`만으로 누수를 판별할 수 없습니다. 애플리케이션 수준 누수로 단정하기 전에 관측된 메모리, `responseState`, 반복 샘플을 함께 보아야 합니다. +- **`ocx doctor`** - "Memory / runtime" 섹션에서 *서비스* 프로세스의 Bun 버전, RSS, external/ArrayBuffers 카운터, JS 힙 문맥, 스트림 모드 결정, Bun 리비전, 런타임이 번들판인지 `OPENCODEX_BUN_PATH` 오버라이드인지도 보여줍니다. 활성 오버라이드는 그 자체로 식별되며, 같은 오버라이드를 다시 설정하라는 안내는 반복되지 않습니다. 번들된 Bun 1.3.14 런타임에서는 `heapUsed` / `jscHeap`만으로 누수를 판별할 수 없습니다. 애플리케이션 수준 누수로 단정하기 전에 관측된 메모리, `responseState`, 반복 샘플을 함께 보아야 합니다. - **`GET /api/system/memory`** - 대시보드나 스크립트에서 쓸 수 있도록 같은 데이터를 인증된 관리 API로 제공합니다. RSS/heap/external 카운터와 함께, 프록시의 메모리 내 `previous_response_id` 이어받기 저장소에 대한 스칼라 `responseState` 블록(항목 수, 직렬화된 총/최대 바이트, 가장 오래된 항목의 경과 시간)을 보고합니다. 이를 통해 증가 원인을 더 잘 구분할 수 있습니다. 관측된 메모리가 함께 증가하면서 `responseState.totalBytes`도 늘면 대화 보존(long `store:false` 체인이 매 턴 다시 확장되는 경우)을 가리키고, 관측된 메모리는 늘지만 `responseState`는 평평하면 그 저장소와는 무관한 원인을 가리킵니다. 값은 스칼라만 포함하며 요청 본문, 토큰, 경로, 계정 식별자는 포함하지 않습니다. 또한 읽기 동작은 부작용이 없습니다. 절대 prune하거나 evict하지 않습니다. 대시보드의 **Memory observability** 카드는 같은 필드를 렌더링하고, 확인을 거쳐야 하는 **Drain & restart** 동작도 제공합니다. 현재 활성 턴 수를 보여주고, 기존 503 + `Retry-After` 드레인과 같은 방식으로 최대 60초 동안 활성 턴을 기다린 뒤, 남아 있는 턴을 강제로 중단하고 Codex 주입을 해제하지 않은 채 라이브 포트의 `ocx start`(또는 실패했을 때만 동작하는 서비스 슈퍼바이저 재기동)를 통해 프록시를 재시작합니다. 이는 `POST /api/stop`의 짧은 드레인보다 더 길고, 더 많은 정보를 반영한 재순환입니다. - **가드된 대체 스트림 경로** - unbounded buffering 형태를 완전히 제거하는 bounded single-reader relay입니다. Windows에서는 번들된 Bun 릴리스가 #32111 수정을 실제로 포함하고 있음이 확인되면 자동으로 기본값이 됩니다. 지금은 아래에서 설명하는 opt-in만 가능합니다. macOS에서는 그런 릴리스 이후에도 계속 opt-in입니다. macOS의 `auto`를 바꾸는 것은 별도의 결정입니다. diff --git a/docs-site/src/content/docs/ru/troubleshooting/windows-memory.md b/docs-site/src/content/docs/ru/troubleshooting/windows-memory.md index 1015f67ea..727ea70e0 100644 --- a/docs-site/src/content/docs/ru/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/ru/troubleshooting/windows-memory.md @@ -34,7 +34,9 @@ opencodex поставляет рантайм Bun (сейчас это **1.3.14* RSS, `external` и `arrayBuffers` (а не их сумма), потому что счётчики working set/RSS на Windows могут занижать коммит удерживаемой external-памяти. - **`ocx doctor`** — раздел "Memory / runtime" показывает версию Bun у *service*-процесса, RSS, - счётчики external/ArrayBuffers, контекст JS-heap и выбранный stream mode. На комплектном Bun + счётчики external/ArrayBuffers, контекст JS-heap, выбранный stream mode, ревизию Bun и источник + рантайма (комплектный или через `OPENCODEX_BUN_PATH`); активный оверрайд идентифицируется как + таковой, и инструкция по его установке не повторяется. На комплектном Bun 1.3.14 одних только `heapUsed` / `jscHeap` недостаточно, чтобы отличить утечку; сравнивайте наблюдаемую память с `responseState` и повторными сэмплами, прежде чем записывать это на app-level leak. diff --git a/docs-site/src/content/docs/troubleshooting/windows-memory.md b/docs-site/src/content/docs/troubleshooting/windows-memory.md index 149db672f..533b16416 100644 --- a/docs-site/src/content/docs/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/troubleshooting/windows-memory.md @@ -37,7 +37,10 @@ runtime the leak itself remains an upstream problem: retention. - **`ocx doctor`** — a "Memory / runtime" section shows the *service* process's Bun version, RSS, external/ArrayBuffers counters, JS-heap context, - and stream-mode decision. On the bundled Bun 1.3.14 runtime, `heapUsed` / + stream-mode decision, the Bun revision, and whether the runtime is bundled + or an `OPENCODEX_BUN_PATH` override. An active override is identified as + such instead of being told to set the same override again. On the bundled + Bun 1.3.14 runtime, `heapUsed` / `jscHeap` alone are not a leak discriminator; compare observed memory with `responseState` and repeated samples before assigning an app-level leak. - **`GET /api/system/memory`** — the same data over the authenticated diff --git a/docs-site/src/content/docs/zh-cn/troubleshooting/windows-memory.md b/docs-site/src/content/docs/zh-cn/troubleshooting/windows-memory.md index 73aa3d600..b2f93d3c2 100644 --- a/docs-site/src/content/docs/zh-cn/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/zh-cn/troubleshooting/windows-memory.md @@ -22,7 +22,7 @@ opencodex 打包了 Bun 运行时(当前为 **1.3.14**)。这类内存增长 这是有界缓解和可见性措施,不是修复。对于捆绑的 1.3.14 运行时,泄漏本身仍然是上游问题: - **内存监视器** — 代理每分钟采样一次自身内存,并在观测到的内存超过 4 GiB 时记录限频告警。观测到的内存取 RSS、`external` 和 `arrayBuffers` 三者中的最大值(不是它们的总和),因为 Windows 的工作集/RSS 计数可能低报已提交的外部保留。 -- **`ocx doctor`** — `"Memory / runtime"` 部分会显示*服务*进程的 Bun 版本、RSS、external/ArrayBuffers 计数、JS 堆上下文以及流模式决策。在捆绑的 Bun 1.3.14 运行时上,单看 `heapUsed` / `jscHeap` 不能作为泄漏判据;在认定为应用层泄漏之前,应把观测到的内存与 `responseState` 以及多次采样一起比较。 +- **`ocx doctor`** — `"Memory / runtime"` 部分会显示*服务*进程的 Bun 版本、RSS、external/ArrayBuffers 计数、JS 堆上下文、流模式决策、Bun 修订号以及运行时来源(捆绑或 `OPENCODEX_BUN_PATH` 覆盖)。当覆盖已激活时会被明确标识,而不会再次提示设置同一个环境变量。在捆绑的 Bun 1.3.14 运行时上,单看 `heapUsed` / `jscHeap` 不能作为泄漏判据;在认定为应用层泄漏之前,应把观测到的内存与 `responseState` 以及多次采样一起比较。 - **`GET /api/system/memory`** — 通过已认证的管理 API 提供同样的数据,便于仪表板或脚本使用。除了 RSS/heap/external 计数之外,它还会报告一个标量的 `responseState` 块(条目数、序列化总字节数/最大字节数、最老条目的年龄),对应代理内存中的 `previous_response_id` 续接存储。这能进一步归因增长:在观测到的内存上升时,如果 `responseState.totalBytes` 也在上升,说明是对话保留在增长(较长的 `store:false` 链在每轮中重新扩张);而在观测到的内存上升时,如果 `responseState` 保持平稳,则更像不是这个存储造成的。返回值只包含标量,不包含请求正文、token、路径或账户标识,而且读取没有副作用(不会执行 prune,也不会 evict)。仪表板中的 **Memory observability** 卡片会渲染相同字段,并提供一个需要确认的 **Drain & restart** 操作:它会显示当前活动轮次数量,最多等待 60 秒让活动轮次结束(复用现有的 503 + `Retry-After` 排空机制),然后中止剩余轮次,并通过 `ocx start` 在当前端口重启代理(或者在仅故障时由服务监督程序重新拉起),同时不拆除 Codex 注入。这是一种比 `POST /api/stop` 的短排空更长、更知情的回收方式。 - **有门控的替代流路径** — 一种有界的单读者中继,能彻底消除无界缓冲的形态。在 Windows 上,一旦某个捆绑的 Bun 版本可验证地包含 #32111 的修复,它就会自动成为默认路径;目前它仅支持显式启用(见下文)。在 macOS 上,即使到了那样的版本,它也仍然保持显式启用状态;切换 macOS 的 `auto` 是另一项独立决定。 diff --git a/src/cli/doctor.ts b/src/cli/doctor.ts index 901491761..ecbae68b8 100644 --- a/src/cli/doctor.ts +++ b/src/cli/doctor.ts @@ -523,6 +523,8 @@ export async function probeWham(fetchImpl: typeof fetch = fetch): Promise { expect(lines.join("\n")).not.toContain("bundled binary"); }); + test("guidance gating: active override is identified, not re-suggested (#848)", () => { + const revision = "0d9b296af33f2b851fcbf4df3e9ec89751734ba4"; + const overrideLines = formatServiceMemoryLines({ + status: "ok", + data: { ...baseData, bunRevision: revision, runtime: { source: "override" } }, + }); + const rendered = overrideLines.join("\n"); + expect(rendered).toContain("OPENCODEX_BUN_PATH override is already active"); + expect(rendered).toContain("(rev 0d9b296a)"); + expect(rendered).toContain("(runtime source: override via OPENCODEX_BUN_PATH)"); + expect(rendered).not.toContain("set OPENCODEX_BUN_PATH"); + expect(rendered).not.toContain("wait for a bundled runtime update"); + + // Same reported version on the bundled runtime still offers the override step. + const bundledLines = formatServiceMemoryLines({ + status: "ok", + data: { ...baseData, bunRevision: revision, runtime: { source: "bundled" } }, + }); + expect(bundledLines.some(l => l.includes("set OPENCODEX_BUN_PATH"))).toBe(true); + }); + test("guidance gating: darwin auto-off or fixed Windows runtime prints no override guidance", () => { const darwin = formatServiceMemoryLines({ status: "ok", diff --git a/tests/memory-watchdog.test.ts b/tests/memory-watchdog.test.ts index ab2453c4a..54db98307 100644 --- a/tests/memory-watchdog.test.ts +++ b/tests/memory-watchdog.test.ts @@ -198,11 +198,13 @@ describe("GET /api/system/memory", () => { itemCapEvictions: number; postCancelDrainStops: number; }; streamMode: string; eagerRelay: unknown; + runtime: { source: string }; watchdog: { samples: unknown[]; warnThresholdBytes: number; observedBytes: number; observedMetric: string } | null; activeTurnCount: number; isDraining: boolean; }; expect(body.pid).toBe(process.pid); expect(body.bunVersion).toBe(Bun.version); + expect(["bundled", "override", "process"]).toContain(body.runtime.source); expect(body.rss).toBeGreaterThan(0); expect(body.heapUsed).toBeGreaterThan(0); expect(body.external).toBeGreaterThanOrEqual(0); @@ -253,6 +255,21 @@ describe("GET /api/system/memory", () => { expect(typeof body.isDraining).toBe("boolean"); }); + test("GET system memory reports an active OPENCODEX_BUN_PATH override (#848)", async () => { + const previousOverride = process.env.OPENCODEX_BUN_PATH; + process.env.OPENCODEX_BUN_PATH = process.execPath; + try { + const req = new Request("http://127.0.0.1:10100/api/system/memory"); + const body = await (await handleManagementAPI(req, new URL(req.url), config()))!.json() as { + runtime: { source: string }; + }; + expect(body.runtime.source).toBe("override"); + } finally { + if (previousOverride === undefined) delete process.env.OPENCODEX_BUN_PATH; + else process.env.OPENCODEX_BUN_PATH = previousOverride; + } + }); + test("watchdog null when no instance is running", async () => { getActiveMemoryWatchdog()?.stop(); const req = new Request("http://127.0.0.1:10100/api/system/memory");