diff --git a/apps/web/app/lib/daemon-images.test.tsx b/apps/web/app/lib/daemon-images.test.tsx index 1fb4379fc..f8cb376c5 100644 --- a/apps/web/app/lib/daemon-images.test.tsx +++ b/apps/web/app/lib/daemon-images.test.tsx @@ -152,7 +152,6 @@ describe('Markdown の画像(デーモンの添付)', () => { expect(stub.calls).toEqual([]); expect(createObjectURL).not.toHaveBeenCalled(); - // remoteImages={false} は今までどおり文字とリンク expect(screen.getByRole('link', { name: '画像: y' })).toBeTruthy(); }); diff --git a/apps/web/app/lib/daemon-images.tsx b/apps/web/app/lib/daemon-images.tsx index 9e376be54..713f81290 100644 --- a/apps/web/app/lib/daemon-images.tsx +++ b/apps/web/app/lib/daemon-images.tsx @@ -6,7 +6,6 @@ import { DaemonImagesProvider } from '@alteroid/ui'; import type { DaemonImages } from '@alteroid/ui'; /** - * Markdown の画像の `src` が、いま接続しているデーモンの添付(`/attachments/`)なら id を返す。それ以外は `undefined`。 * 資格(Bearer)を送ってよいかの線はここだけに置く: * - 絶対 URL に加え、`/attachments/` と `attachments/` の相対パスも、デーモンの base の下のものとして扱う: * 取りに行く先は `src` ではなく接続中の base URL と id から組むので、資格がよそへ行く経路にならない。 diff --git a/apps/web/app/routes/chat.follow-up-order.test.tsx b/apps/web/app/routes/chat.follow-up-order.test.tsx index 4da80095f..e1164572a 100644 --- a/apps/web/app/routes/chat.follow-up-order.test.tsx +++ b/apps/web/app/routes/chat.follow-up-order.test.tsx @@ -8,15 +8,11 @@ import { json, Providers, sse, storeTestBaseUrl, stubFetch } from '~/test-suppor import Chat from './chat'; -// #4391: 返信の本文が流れ終わってもターンが続いているあいだに人間が続けて発言すると、その発言が返信より上に出ていた。 -// デーモンは発言を記録するとき、それまでの返信を先に日誌へ書き、同じ同期区間で `queued` を流す。画面はその `queued` で返信の行を分ける。 - const ID = 'conv-follow-up-order'; const QUESTION = '二つ目の質問'; const REPLY = 'AIの回答2の本文'; const FOLLOW = '回答2への返事'; const TAIL = '返事を受けた続き'; -// 道具を挟んで再開した本文: 日誌では区切りの空行(#4339)を挟んで TAIL と1発言になる const AFTER_TOOL = '道具の後の本文'; const ChatRoute = Chat as unknown as (props: { @@ -80,7 +76,6 @@ describe.each([ { label: '割るデーモン(#4391 以降)', split: true, - // 発言の記録の前に、そこまでの返信が書かれる whenQueued: [ question, { id: 'm2', at: '2026-10-10T01:00:19.000Z', role: 'outbound', text: REPLY }, @@ -105,7 +100,6 @@ describe.each([ whenDone: [ question, follow, - // 追送の割り目には区切りが入らず、道具の境目にだけ入る { id: 'm4', at: '2026-10-10T01:00:40.000Z', @@ -193,7 +187,6 @@ describe.each([ expect(chatCalls).toBe(2); }); - // デーモンが追送を記録した: 返信の行が分かれ、履歴に追送が入る messages = whenQueued; queued.open(); recorded.open(); @@ -211,7 +204,6 @@ describe.each([ const shown = items(); expect(countOf(shown, TAIL)).toBe(1); expect(countOf(shown, AFTER_TOOL)).toBe(1); - // 追送の後に流れた続きは、追送より下に出る expect(indexOf(shown, FOLLOW)).toBeLessThan(indexOf(shown, TAIL)); }); diff --git a/apps/web/app/routes/chat.reply-segment-separator.test.tsx b/apps/web/app/routes/chat.reply-segment-separator.test.tsx index aa0708581..1ec65f1b9 100644 --- a/apps/web/app/routes/chat.reply-segment-separator.test.tsx +++ b/apps/web/app/routes/chat.reply-segment-separator.test.tsx @@ -8,10 +8,6 @@ import { json, Providers, sse, storeTestBaseUrl, stubFetch } from '~/test-suppor import Chat from './chat'; -// デーモンは、1つのターンの中で本文が道具を挟んで再開したとき、日誌の本文の境目に空行を入れる(#4339)。 -// 画面へ流れる SSE の text には空行が入らないので、pendingOwnLines は分かれた返信の行を -// 区切りあり(空行)と区切りなし(古いデーモン)の両方で日誌の1発言と照合する。どちらでも確定後に写しが末尾に残らない。 - const ID = 'conv-tail'; const BEFORE_HEAD = '待ち時間を 35 秒ほどにすれば、必ず Gotenberg が先に切れて'; const BEFORE_TAIL = '、その理由を受け取れます。'; @@ -188,7 +184,6 @@ describe.each([ afterTool.open(); await screen.findByText(AFTER); - // 道具の前の行は、末尾まで書かれている expect(items().some((text) => text.includes(BEFORE_HEAD + BEFORE_TAIL))).toBe(true); replied = true; @@ -199,7 +194,6 @@ describe.each([ const shown = items(); expect(shown.filter((text) => text.includes(AFTER))).toHaveLength(1); expect(shown.filter((text) => text.includes(BEFORE_HEAD))).toHaveLength(1); - // 確定した1発言が最後に在り、手元の写しが後ろに居座らない expect(shown.at(-1)).toContain(BEFORE_HEAD + BEFORE_TAIL); expect(shown.at(-1)).toContain(AFTER); }); diff --git a/apps/web/app/routes/chat.reply-segment-tail.test.tsx b/apps/web/app/routes/chat.reply-segment-tail.test.tsx index b80e3fe14..3e7c609e0 100644 --- a/apps/web/app/routes/chat.reply-segment-tail.test.tsx +++ b/apps/web/app/routes/chat.reply-segment-tail.test.tsx @@ -8,10 +8,6 @@ import { json, Providers, sse, storeTestBaseUrl, stubFetch } from '~/test-suppor import Chat from './chat'; -// 道具の直前の本文の末尾と `tool` が同じ網の塊で届くと、末尾が画面の行に書かれないまま落ちていた。 -// 行の本文が日誌の本文(ターンの本文を区切りなしで連結した1発言)と食い違い、pendingOwnLines の照合に永久に当たらず、 -// 前半(途中で切れた版)と後半が会話の末尾に居座った(会話 82d90cd7 の返答 36291767、2026-10-09)。 - const ID = 'conv-tail'; const BEFORE_HEAD = '待ち時間を 35 秒ほどにすれば、必ず Gotenberg が先に切れて'; const BEFORE_TAIL = '、その理由を受け取れます。'; @@ -149,7 +145,6 @@ describe('道具の直前の本文の末尾と tool が同じ塊で届く(会 id: 'm2', at: '2026-10-09T08:27:32.000Z', role: 'outbound', - // 日誌はターンの本文を区切りなしで連結した1発言として載せる text: BEFORE_HEAD + BEFORE_TAIL + AFTER, }, ] @@ -185,7 +180,6 @@ describe('道具の直前の本文の末尾と tool が同じ塊で届く(会 afterTool.open(); await screen.findByText(AFTER); - // 道具の前の行は、末尾まで書かれている expect(items().some((text) => text.includes(BEFORE_HEAD + BEFORE_TAIL))).toBe(true); replied = true; @@ -196,7 +190,6 @@ describe('道具の直前の本文の末尾と tool が同じ塊で届く(会 const shown = items(); expect(shown.filter((text) => text.includes(AFTER))).toHaveLength(1); expect(shown.filter((text) => text.includes(BEFORE_HEAD))).toHaveLength(1); - // 確定した1発言が最後に在り、手元の写しが後ろに居座らない expect(shown.at(-1)).toContain(BEFORE_HEAD + BEFORE_TAIL + AFTER); }); }); diff --git a/apps/web/app/routes/chat.tsx b/apps/web/app/routes/chat.tsx index 3527c2463..1d27d70b7 100644 --- a/apps/web/app/routes/chat.tsx +++ b/apps/web/app/routes/chat.tsx @@ -293,7 +293,7 @@ interface Line { turnFailure?: 'failed' | 'held'; turnFailureKind?: TurnFailureKind; replyGroup?: string; - // 返信の途中に人間の発言が届いて(`queued`)分けた行の印: デーモンはそこで返信を割って日誌へ書くので(#4391)、割らない古いデーモンの本文と照合するときはこの境目に区切りを入れない + // 返信の途中に人間の発言が届いて(`queued`)分けた行の印: デーモンはそこで返信を割って日誌へ書くので、割らない古いデーモンの本文と照合するときはこの境目に区切りを入れない splitByHuman?: true; journalId?: string; attachments?: readonly MessageAttachment[]; @@ -449,7 +449,7 @@ export function pendingOwnLines( if (line.approval !== undefined) historyApprovals.set(line.approval.id, line.approval); } // 分かれた返信は連結した本文で照合する: 日誌はターンの本文を1つの発言として載せるので、行ごとだと二重に出る。 - // 連続した行の並びごとに照合する(長い並びを先に当てる): デーモンは承認カードやターン中に届いた人間の発言(#4391)でターンの本文を割って書くので、1ターンが日誌の複数の発言になる + // 連続した行の並びごとに照合する(長い並びを先に当てる): デーモンは承認カードやターン中に届いた人間の発言でターンの本文を割って書くので、1ターンが日誌の複数の発言になる const groups = new Map(); for (const line of owned) { if (line.replyGroup === undefined || line.role !== 'clone') continue; @@ -462,7 +462,7 @@ export function pendingOwnLines( let next = start + 1; for (let end = members.length; end > start + 1; end -= 1) { const run = members.slice(start, end); - // 区切りあり(#4339 以降のデーモン: メッセージの境目に空行)と区切りなし(古いデーモン)の両方を受ける: Web とデーモンは版がずれる。 + // 区切りあり(メッセージの境目に空行)と区切りなし(古いデーモン)の両方を受ける: Web とデーモンは版がずれる。 // 区切りありを先に当てる。どちらも履歴に在るなら、区切りありのほうが新しい形 const key = REPLY_SEGMENT_JOINERS.map( (joiner) => @@ -1822,7 +1822,7 @@ export function ChatPane({ }; const apply = (event: ChatStreamEvent) => { switch (event.type) { - // 返信の行をここで分ける(#4391): デーモンは人間の発言を記録するとき、それまでの返信を先に日誌へ書く。続きを同じ行へ足すと、その発言より上に出るうえ、日誌の本文とも合わない + // 返信の行をここで分ける:デーモンは人間の発言を記録するとき、それまでの返信を先に日誌へ書く。続きを同じ行へ足すと、その発言より上に出るうえ、日誌の本文とも合わない case 'queued': { // 続けて届いた2通目でも印を落とさない: 1通目で行は既に閉じている const split = replyKey !== undefined || splitByHuman; @@ -2785,7 +2785,7 @@ export function ChatPane({ first.at, ); const open = data.reachedStart === false; - // 取り下げた発言は数えない(#4357): 吹き出しとして出していないため + // 取り下げた発言は数えない:吹き出しとして出していないため const counted = visible.filter((message) => message.delivery !== 'withdrawn').length; return `${formatDateTime(startedAt)}${open ? ' 以降' : ' に開始'} · 発言 ${counted} 件${open ? '以上' : ''}`; })(); diff --git a/apps/web/app/routes/env-vars.test.tsx b/apps/web/app/routes/env-vars.test.tsx index a870f4352..a0610fefc 100644 --- a/apps/web/app/routes/env-vars.test.tsx +++ b/apps/web/app/routes/env-vars.test.tsx @@ -470,7 +470,6 @@ describe('/env-vars 画面 — 置く・編集・削除', () => { fireEvent.change(within(dialog).getByLabelText('値'), { target: { value: 'half-typed' } }); fireEvent.change(within(dialog).getByLabelText('渡す先'), { target: { value: 'app' } }); fireEvent.click(within(dialog).getByRole('button', { name: 'やめる' })); - // 書きかけがあるので確認を挟む(#3418)。ここでは破棄を選んで閉じる fireEvent.click(await screen.findByRole('button', { name: '破棄して閉じる' })); await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull());