Skip to content

chore(deps): 主要依存を最新メジャーへ更新#3

Merged
shomatan merged 1 commit into
mainfrom
chore/deps-major-upgrade
Apr 22, 2026
Merged

chore(deps): 主要依存を最新メジャーへ更新#3
shomatan merged 1 commit into
mainfrom
chore/deps-major-upgrade

Conversation

@shomatan
Copy link
Copy Markdown
Contributor

Summary

  • zod 3→4 / vitest 2→4 / biome 1→2 / Next.js 15→16 / TypeScript 5→6 を含む主要依存を最新メジャーへ
  • packageManager を pnpm 9→10、engines を node>=22 / pnpm>=10 に更新
  • @types/dompurify は DOMPurify v3 が型を同梱しているため削除、@vitejs/plugin-react を新規追加

破壊的変更への対応

  • zod v4: z.record(z.unknown())z.record(z.string(), z.unknown())
  • vitest v4: environmentMatchGlobs 廃止 → projects で jsdom / node を分離。vi.fn(() => fake)new で呼べなくなったため function 形式に
  • biome v2: biome migrate で config 自動変換。useExhaustiveDependencies 強化により app/page.tsxreloaduseCallback でラップ。import 順が多数自動整理されている
  • Next.js 16: next-env.d.ts を build が自動更新(triple-slash → import)

Test plan

  • pnpm typecheck(全4パッケージ)
  • pnpm lint(0 errors / 3 warnings)
  • pnpm test(65 files / 447 tests)
  • pnpm build(Next 16 を含む)
  • pnpm dev で実アプリの挙動を動作確認(キャンバス / AI Engine WebSocket / プロジェクト作成)

… ts 6)

- Root: biome 1.9→2.4, typescript 5→6, vitest 2→4, @vitest/coverage-v8 2→4, @types/node 22→24, packageManager pnpm 9→10, engines node>=22/pnpm>=10
- frontend: next 15→16, zod 3→4, dompurify 3.4.0→3.4.1, @vitejs/plugin-react 追加, @types/dompurify 削除(v3は型同梱)
- ai-engine: @anthropic-ai/claude-agent-sdk 0.2.114→0.2.117, zod 3→4, tsx 4.19→4.21
- core/storage: zod 3→4

破壊的変更への対応:
- zod v4: z.record 第一引数(キー型)必須化
- vitest 4: environmentMatchGlobs 廃止 → projects で jsdom/node 分離、vi.fn の arrow → function(new 対応)
- biome 2: biome migrate で config 変換、import 順自動整理、useExhaustiveDependencies 強化で reload を useCallback 化
- Next 16: next-env.d.ts を build が自動更新

検証: typecheck / lint / test(447) / build 全て通過
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

Warning

Rate limit exceeded

@shomatan has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 28 minutes and 45 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 28 minutes and 45 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 75767f15-535b-49bf-a802-c0ade93526cf

📥 Commits

Reviewing files that changed from the base of the PR and between 8d0dd9b and 506537f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (66)
  • biome.json
  • package.json
  • packages/ai-engine/package.json
  • packages/ai-engine/src/agent-runner.test.ts
  • packages/ai-engine/src/agents/codebase-anchor.test.ts
  • packages/ai-engine/src/agents/decompose-to-stories.ts
  • packages/ai-engine/src/agents/ingest-document.test.ts
  • packages/ai-engine/src/chat-runner.ts
  • packages/ai-engine/src/index.ts
  • packages/ai-engine/src/server.ts
  • packages/ai-engine/src/tools/create-node.ts
  • packages/core/package.json
  • packages/core/src/index.ts
  • packages/core/src/schema.test.ts
  • packages/frontend/next-env.d.ts
  • packages/frontend/package.json
  • packages/frontend/src/app/api/projects/[id]/edges/edges-route.test.ts
  • packages/frontend/src/app/api/projects/[id]/nodes/nodes-route.test.ts
  • packages/frontend/src/app/api/projects/[id]/route.ts
  • packages/frontend/src/app/page.tsx
  • packages/frontend/src/app/projects/[id]/canvas-client.tsx
  • packages/frontend/src/app/projects/[id]/page.tsx
  • packages/frontend/src/components/ai-actions/use-codebase-selector.ts
  • packages/frontend/src/components/canvas/canvas.tsx
  • packages/frontend/src/components/chat/chat-messages.tsx
  • packages/frontend/src/components/details/coderef-detail.tsx
  • packages/frontend/src/components/details/common-fields.tsx
  • packages/frontend/src/components/details/detail-sheet.tsx
  • packages/frontend/src/components/details/edge-detail.tsx
  • packages/frontend/src/components/details/proposal-detail.tsx
  • packages/frontend/src/components/details/question-detail.tsx
  • packages/frontend/src/components/details/requirement-detail.tsx
  • packages/frontend/src/components/details/userstory-detail.tsx
  • packages/frontend/src/components/dialog/bulk-adopt-dialog.tsx
  • packages/frontend/src/components/dialog/folder-browser-dialog.test.tsx
  • packages/frontend/src/components/dialog/folder-browser-dialog.tsx
  • packages/frontend/src/components/dialog/mermaid-export-dialog.tsx
  • packages/frontend/src/components/dialog/new-project-dialog.test.tsx
  • packages/frontend/src/components/dialog/new-project-dialog.tsx
  • packages/frontend/src/components/dialog/project-settings-dialog.tsx
  • packages/frontend/src/components/edges/typed-edge.tsx
  • packages/frontend/src/components/nodes/coderef-node.tsx
  • packages/frontend/src/components/nodes/issue-node.tsx
  • packages/frontend/src/components/nodes/node-card.tsx
  • packages/frontend/src/components/nodes/proposal-node.tsx
  • packages/frontend/src/components/nodes/question-node.tsx
  • packages/frontend/src/components/nodes/requirement-node.tsx
  • packages/frontend/src/components/nodes/usecase-node.tsx
  • packages/frontend/src/components/nodes/userstory-node.tsx
  • packages/frontend/src/components/palette/node-palette.tsx
  • packages/frontend/src/lib/api.test.ts
  • packages/frontend/src/lib/layout.ts
  • packages/frontend/src/lib/mermaid.test.ts
  • packages/frontend/src/lib/mermaid.ts
  • packages/frontend/src/lib/store.test.ts
  • packages/frontend/src/lib/store.ts
  • packages/frontend/src/lib/ws.test.ts
  • packages/frontend/vitest.config.ts
  • packages/storage/package.json
  • packages/storage/src/chat-store.test.ts
  • packages/storage/src/clear-project.test.ts
  • packages/storage/src/index.ts
  • packages/storage/src/init-project.ts
  • packages/storage/src/project-store.ts
  • packages/storage/src/yaml.test.ts
  • packages/storage/src/yaml.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/deps-major-upgrade

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@shomatan shomatan self-assigned this Apr 22, 2026
@shomatan shomatan merged commit 10f7a57 into main Apr 22, 2026
1 check passed
@shomatan shomatan deleted the chore/deps-major-upgrade branch April 22, 2026 10:24
shomatan added a commit that referenced this pull request Apr 26, 2026
* feat(ai-engine): チャットにコンテキストノードを渡せるようにする

issue #11 のうちサーバ側を実装する。

- ChatRunner.runUserTurn が contextNodeIds: string[] を受け取り、ProjectStore
  から該当 Node を引いて prompt 内の <context_nodes> ブロックに埋め込む。
- Node 型ごとの主要属性 (priority / options / filePath など) は formatNodeForContext
  で簡潔にテキスト化。座標など UI ノイズは省く。
- /chat WS の user_message スキーマに contextNodeIds (optional) を追加。
- システムプロンプトに「context_nodes は AI 直接編集ではなく proposal 経由で
  応答する」旨を明記し、ADR-0005 (AI 提案フロー) を尊重する。

ノードの「更新・削除」は AI が直接行わず、新規 proposal を出してユーザーに
採用判断を委ねる方針 (CLAUDE.md 大原則 #3)。

* feat(frontend): Chat タブにノードを @ メンションする UI を追加

issue #11 のフロント側実装。

- ChatContextBar コンポーネントを ChatInput の上に配置し、
  「+ ノードを追加」ボタンでキャンバスのノードを type 別に選択できる。
- 選択中ノード (canvas で 1 件選択中) を一発添付するショートカットも用意。
- chip の × / 「すべて解除」で個別 / 一括解除。
- Zustand に chatContextNodeIds 配列と add/remove/clear アクションを追加。
- sendChatMessage 時に store の context を WS フレームに同梱する。
  削除済みノード id はクライアント側でフィルタしてから送る。
- スレッド切替・close・reset・clearBoard で context を自動クリア
  (永続化はしない / chats/<id>.yaml にも書かない)。

加えて React 19.2 では `React.act` が development build にしか含まれず、
testing-library が実行時に参照するため jsdom テストの NODE_ENV を development
固定にした (vitest.config.ts)。

ノードの「更新・削除」依頼は AI が直接実行せず proposal で応答する
既存のフロー (ADR-0005) に乗せる。

* test(frontend): Chat コンテキストノード操作の E2E を追加

- ChatContextBar/chip に data-testid を付与し E2E から特定可能に
- 5 ケース: 初期状態、選択中ノード追加、ピッカー追加、x削除、すべて解除
- ai-engine 未起動でも UI と Zustand store の挙動だけを検証する
- 送信は WS 必須なので未入力時の送信ボタン disabled で代替確認

* fix(ai-engine): context_nodes をユーザーメッセージより前に配置

runUserTurn 内で空 assistant message を append した後に buildChatPrompt を
呼んでいたため、履歴末尾が assistant となり <current_user_message> タグが
出ず、結果として <context_nodes> がユーザー入力より後ろに並ぶバグがあった。

prompt スナップショット → 空 assistant append の順に並べ替え、
履歴 → context → current の正しい順序を保証する。

回帰防止のため順序検証テストを追加:
- <context_nodes> が <current_user_message> より前に位置すること
- 今ターンの user 入力が <conversation_history> 内に出現しないこと

併せて proposal ノードの formatNodeForContext から sourceAgentId を除外し
「未採用の AI 提案」note を追加 (codex セカンドオピニオン Minor 採用)。
sourceAgentId は AI にとって意味の無い内部属性のため。

* feat(ai-engine): contextNodeIds の最大件数を 20 に制限

WS の user_message スキーマで contextNodeIds に .max(20) を付与する。
prompt 肥大化と悪意ある大量送信を抑止する (codex セカンドオピニオン Major-2)。
上限超過は zod エラーとして bad_request でクライアントに返る。

* chore: CodeRabbit レビューを再トリガー

* chore: CodeRabbit レビュー再トリガー

* chore: CodeRabbit レビューを再トリガー

* chore: CodeRabbit 再トリガー (marker)

* refactor(frontend): Chat コンテキスト UI のアクセシビリティとロジックを整理

- ピッカートグルボタンの aria-label を pickerOpen に応じて動的に切り替え、
  SR でも開閉状態が伝わるようにする
- ピッカーの空状態にも role=dialog / aria-label / 閉じるボタンを付与し、
  非空状態と対称な構造にする
- type 別グルーピングを Map 構築 + 再走査の 2 段から、NODE_META のキー順で
  Object.values(nodes).filter(...) を直接組む 1 パス構成に書き直す

* test(frontend): Chat コンテキスト E2E の selector を plain string に

new RegExp(TARGET_TITLE_PICKER) は escape 漏れリスクがあり Playwright の
getByRole({ name }) は plain string でも部分一致するため、regex を外して
意図を明確にする。

* fix(frontend): クライアント側にも contextNodeIds 上限ガードを追加

サーバ側 (ai-engine の MAX_CHAT_CONTEXT_NODES = 20) と同じ値で
クライアントの addChatContextNode でも先回りで弾く。
名前付き定数 MAX_CHAT_CONTEXT_NODES で意図を明示し、
無駄な WS フレーム送信と UI/サーバの状態乖離を防ぐ。

* test(ai-engine): contextNodeIds テスト名から「後方互換」表現を削除
shomatan added a commit that referenced this pull request May 1, 2026
CodeRabbit (PR #21) Major 4 件 + Minor 1 件への対応。

[Heavy lift / Major #4] OAuth callback を構造化 WS メッセージで送る
これまで callback URL paste 後に「[OAuth callback for ${mcpServerId}] ...」
という自然文を sendChatMessage で送り、AI に mcpServerId を解釈させて
mcp__<id>__complete_authentication を呼ばせていた。複数 MCP server を
同時運用する状況で AI が別 server の complete_authentication を選び、
元の auth_request カードが pending のまま残るリスクがあった。

UI から WS の構造化メッセージ {type:'oauth_callback', mcpServerId,
callbackUrl} を送り、サーバ側で確定的に「指定 server の
complete_authentication を呼べ」とプロンプトを生成する設計に変更。

- ai-engine/server.ts: ChatOAuthCallbackSchema 追加、handler 分岐
- ai-engine/chat-runner.ts: runOAuthCallback メソッド追加 (runUserTurn ラッパー)
- frontend/lib/ws.ts: sendOAuthCallback 追加
- frontend/lib/store.ts: sendOAuthCallback action 追加
- frontend/auth-request-card.tsx: sendChatMessage → sendOAuthCallback
- 関連 test 7 件を新 API に追従

[Quick win / Major #1] chat_auth_request を discriminated union 化
ai-engine/stream.ts: status='failed' のときのみ failureMessage を必須に
する型分割。schema.ts (AuthRequestBlockSchema) の superRefine と型レベル
で整合させ、永続化時の検証エラーを型チェックで早期検出。chat-runner の
emit 側と test の type narrowing も追従。

[Quick win / Major #3] callback URL の username/password 拒否
auth-request-card.tsx: isLikelyCallbackUrl で URL 内資格情報 (user:pass@)
を弾く。schema.ts (McpServerConfigSchema.url) と整合。

[Quick win / Major #5] 最新の pending auth_request を更新
frontend/lib/store.ts: chat_auth_request reducer の走査を末尾起点に変更。
再認証で複数 pending が並ぶケースで、古いカードを更新して新しいカードが
pending のまま残る問題を解消。

[Quick win / Minor #2] window.open spy の cleanup を afterEach に
auth-request-card.test.tsx: vi.restoreAllMocks() を afterEach に集約し、
テスト失敗時に spy が漏れて後続に影響する経路を塞ぐ。

[見送り / Minor (codex)] auth-detector.ts の id 命名規則 ADR
codex セカンドオピニオン Minor m2 (regex / schema 二重メンテの ADR メモ) は
本 PR スコープ外として見送り。
shomatan added a commit that referenced this pull request May 1, 2026
)

* refactor: MCP 認証を OAuth 2.1 / SDK 任せに切替、Tally から PAT/API key を完全排除

Premise 9 撤回: PAT only → MCP プロトコルの OAuth 2.1 採用 (user sovereignty)。

理由:
- Atlassian 公式 Rovo MCP は OAuth 2.1 ネイティブ、Claude Agent SDK は MCP の
  'needs-auth' status を扱う built-in support を持つ → SDK 任せで auto auth flow が回る
- sooperset/mcp-atlassian を使う場合も PAT は MCP server 自身の env で持つ前提に
- Tally プロセスから PAT/API key の概念が完全消滅 (project.yaml / メモリ / ログのいずれにも残らない)

実装変更:
- core: McpServerConfigSchema から auth フィールド削除、関連 hardening (envVar
  regex / Basic+Bearer 分岐) も削除。schema test を auth-less に整理
- ai-engine: buildMcpServers の auth header 構築ロジック削除、url のみで HTTP
  config (req は SDK が必要に応じて WWW-Authenticate を解釈)。chat-runner /
  agent-runner も auth に依存しない。env 未設定 throw test は仕様変更のため削除
- frontend: 設定 dialog から auth scheme dropdown / envVar 入力欄を削除、URL
  のみのシンプル UI、caption も「OAuth 2.1 / API token は MCP 任せ」に書き換え

* feat(chat): OAuth 2.1 認証フローを auth_request ブロックで 1 等地化

外部 MCP の OAuth 認証フロー (authenticate / complete_authentication tool_use)
を assistant 文中の生 tool_use ではなく、専用カードで扱う。「Atlassian で認証」
ボタン (新規タブ) と callback URL paste 入力欄を統合。

- packages/core/src/schema.ts: ChatBlock に \`auth_request\` variant を追加
  (mcpServerId / mcpServerLabel / authUrl / status / failureMessage)。
  status と failureMessage の整合を superRefine で固定: failed なのに message
  無し / pending・completed に message が付いているケースを reject。
- packages/ai-engine/src/auth-detector.ts (新規): \`mcp__<id>__authenticate\` /
  \`complete_authentication\` パターンの分解、tool_result.output からの auth URL
  抽出。文末の句読点・括弧を URL に含めない処理 (`...state=xyz.` のような
  自然文出力で認可リンクが壊れるのを防ぐ)。
- packages/ai-engine/src/stream.ts: \`chat_auth_request\` event を ChatEvent に追加。
- packages/ai-engine/src/chat-runner.ts: tool_use を stash → tool_result とペアに
  なった瞬間に auth_request ブロックへ変換 (raw な tool_use/tool_result は出さない)。
  \`handleAuthToolResult\` / \`findLatestPendingAuthRequest\` を新設。
  complete_authentication 受領時は同 mcpServerId の最新 pending を更新。
- packages/frontend/src/components/chat/auth-request-card.tsx (新規):
  認証ボタン (新規タブ) と callback URL paste 入力欄を 1 カードに集約。
  callback URL は localhost / 127.0.0.1 のみ受け付ける軽い形式チェック付き。
- packages/frontend/src/components/chat/chat-message.tsx: auth_request →
  AuthRequestCard dispatch。
- packages/frontend/src/lib/store.ts: \`chat_auth_request\` event 反映 (pending
  append / completed・failed in-place 更新)。

- core/schema.test.ts: auth_request バリエーション 6 件 (基本 + 状態遷移 reject)
- ai-engine/auth-detector.test.ts: parseAuthToolName / extractAuthUrl 計 13 件
  (末尾句読点 strip 含む)
- ai-engine/chat-runner.test.ts: auth_request 変換フロー 3 件
  (authenticate / complete success / complete failure)
- frontend/auth-request-card.test.tsx: pending/completed/failed 描画と
  paste 検証 7 件

注: ChatRunner は per-turn sdk.query() のままで、long-lived Query 化は別 PR
(PR-C) で扱う。本 PR の auth_request UX 単体では「auth 1 回 → 同 thread の
次 turn で再 auth が必要」(SDK subprocess が turn ごとに再生成されるため
OAuth state が引き継がれない) という挙動を残す。

* test(frontend): mcpServers PATCH の事前登録を assert

「mcpServers を空配列で全消去できる」テストの事前 PATCH の status
を assert していなかった。失敗していると後続の「空配列で削除」ケース
が偽の成功 (空 → 空) で通る恐れがあったため修正。

* fix(frontend): chat_auth_request reducer に pending 不在 failed の append 分岐

これまで failed/completed イベントは「同 mcpServerId の pending ブロック
を更新」する経路しか持たず、URL 抽出失敗等で pending を append する
前に failed が確定するケースで in-memory state に反映されなかった
(YAML には書かれるがリロードまで UI に出ない → 「何も起きない」と見える)。

evt.status==='failed' で同 mcpServerId の pending が見つからない場合、
pending と同様に該当 messageId に新規 append する。completed で
pending 不在は異常系のため無視 (現状維持)。

codex セカンドオピニオン (PR #21) Major M1 指摘対応。

* fix(ai-engine): complete_authentication 専用 turn の空 assistant をプレースホルダで埋める

callback URL paste 後の turn では SDK 応答が complete_authentication
tool_use/tool_result のみで、handleAuthToolResult は過去 message の
pending ブロックを更新するだけ。結果として現 turn の assistantMsgId
には何も append されず、空のアシスタント bubble が thread に蓄積し、
再生時の prompt にもノイズとして入り込む問題があった。

textBuffer が空 + 対象 message のブロックが 0 件なら、プレースホルダ
「(認証処理を完了しました)」テキストを replaceMessageBlocks で書く。

codex セカンドオピニオン (PR #21) Major M2 指摘対応。

* fix(frontend): auth-request-card で IPv6 loopback (::1) も callback URL として受付

McpServerConfigSchema.url は loopback として localhost / 127.0.0.1 /
::1 / [::1] を許容しているが、AuthRequestCard 側の isLikelyCallbackUrl
は localhost / 127.0.0.1 のみ。IPv6 優先環境で SDK がリダイレクト先を
http://[::1]:XXXXX/callback?... で通知すると「認証完了」ボタンが永久
に無効になる可能性があった。schema.ts と loopback 判定を揃える。

codex セカンドオピニオン (PR #21) Minor m1 指摘対応。

* fix(chat): PR-B CR 3 周目 5 件対応 (Quick win 4 + Heavy lift 1)

CodeRabbit (PR #21) Major 4 件 + Minor 1 件への対応。

[Heavy lift / Major #4] OAuth callback を構造化 WS メッセージで送る
これまで callback URL paste 後に「[OAuth callback for ${mcpServerId}] ...」
という自然文を sendChatMessage で送り、AI に mcpServerId を解釈させて
mcp__<id>__complete_authentication を呼ばせていた。複数 MCP server を
同時運用する状況で AI が別 server の complete_authentication を選び、
元の auth_request カードが pending のまま残るリスクがあった。

UI から WS の構造化メッセージ {type:'oauth_callback', mcpServerId,
callbackUrl} を送り、サーバ側で確定的に「指定 server の
complete_authentication を呼べ」とプロンプトを生成する設計に変更。

- ai-engine/server.ts: ChatOAuthCallbackSchema 追加、handler 分岐
- ai-engine/chat-runner.ts: runOAuthCallback メソッド追加 (runUserTurn ラッパー)
- frontend/lib/ws.ts: sendOAuthCallback 追加
- frontend/lib/store.ts: sendOAuthCallback action 追加
- frontend/auth-request-card.tsx: sendChatMessage → sendOAuthCallback
- 関連 test 7 件を新 API に追従

[Quick win / Major #1] chat_auth_request を discriminated union 化
ai-engine/stream.ts: status='failed' のときのみ failureMessage を必須に
する型分割。schema.ts (AuthRequestBlockSchema) の superRefine と型レベル
で整合させ、永続化時の検証エラーを型チェックで早期検出。chat-runner の
emit 側と test の type narrowing も追従。

[Quick win / Major #3] callback URL の username/password 拒否
auth-request-card.tsx: isLikelyCallbackUrl で URL 内資格情報 (user:pass@)
を弾く。schema.ts (McpServerConfigSchema.url) と整合。

[Quick win / Major #5] 最新の pending auth_request を更新
frontend/lib/store.ts: chat_auth_request reducer の走査を末尾起点に変更。
再認証で複数 pending が並ぶケースで、古いカードを更新して新しいカードが
pending のまま残る問題を解消。

[Quick win / Minor #2] window.open spy の cleanup を afterEach に
auth-request-card.test.tsx: vi.restoreAllMocks() を afterEach に集約し、
テスト失敗時に spy が漏れて後続に影響する経路を塞ぐ。

[見送り / Minor (codex)] auth-detector.ts の id 命名規則 ADR
codex セカンドオピニオン Minor m2 (regex / schema 二重メンテの ADR メモ) は
本 PR スコープ外として見送り。

* fix(chat): runOAuthCallback を ephemeral 実装 + サーバ側 callback URL 検証強化

CodeRabbit (PR #21) 4 周目 Major 2 件対応。

[Major #1 / Heavy lift] runOAuthCallback の ephemeral 化
これまで runOAuthCallback は \`yield* this.runUserTurn(text, [])\` を
呼んでおり、callback URL (code/state 含む) が user message として永続化
され、後続の prompt 再構築でも再送される問題があった。さらに通常 turn の
ままなので、AI が他のツール (create_node 等) を実行する余地が残っていた。

専用の ephemeral 実行経路に切替:
- chatStore.appendMessage を呼ばない (callback URL を chat 履歴に残さない)
- assistantMsgId は ephemeral (handleAuthToolResult の orphan 失敗 fallback
  以外では参照されない)
- 対象 server の config のみ buildMcpServers に渡す
- allowedTools = [\`mcp__<id>__complete_authentication\`] の 1 件のみ
- tool_use は kind === 'complete_authentication' && id 一致のみ stash
- text block と stash 不一致の tool_result は emit せず黙って捨てる
- handleAuthToolResult が過去 message の最新 pending を更新する経路は維持

[Major #2] サーバ側 callbackUrl 検証強化
ChatOAuthCallbackSchema.callbackUrl が \`z.string().url()\` のみで、
loopback / no-credentials / code+state 必須を確認していなかった。
WS は UI 経由でない外部クライアントからも到達しうるので、isValidCallbackUrl
ヘルパで isLikelyCallbackUrl (UI 側) と同じ条件をサーバ側でも検証する。
shomatan added a commit that referenced this pull request May 1, 2026
CodeRabbit (PR #22) 2 周目への対応。Heavy lift #3 (OAuth callback の
ephemeral 化) は前 PR と同様 long-lived 維持で見送り、CR thread に返信。

[Major / chat-runner.ts:349] close と ensureQuery の競合
close() が startQueryInternal の途中 (await projectStore.getProjectMeta() 等)
で走ると、shutdown 後に sdk.query() が作られて subprocess が孤立する race
があった。close() で ensureQueryInflight を join してから tearDown する。

[Major / chat-runner.ts:358] SDK メッセージの常時ログによる機密漏洩
runOutputLoop の console.log が全 SDK メッセージを redactMcpSecrets でラップ
してログしていたが、message.content や result の OAuth callback URL の
code/state がサーバーログに残るリスクがあった。type だけ出す形に絞り、
本文は出さない。redactMcpSecrets の import も削除。

[Major / chat-runner.ts:386] 異常終了後の pendingApprovals 残存
runOutputLoop の catch / EOF ブランチで queue を閉じるだけだったので、
turn 失敗後に UI から approveTool() が来ると create_node / create_edge 等
の side effect が走る恐れがあった。rejectAllPendingApprovals helper を
新設し、両ブランチで pendingApprovals を一括 reject(false) する。

[Major / chat-runner.ts:625] runOAuthCallback の currentTurn 解放保証
this.currentTurn = turnState の直後から全体を try/finally で囲み、
appendMessage 等の中間ステップで throw しても currentTurn が解放される
ことを保証 (turn_in_progress で次の turn を受け付けられなくなるのを防ぐ)。
runUserTurn 側も同じパターンで全体を try/finally に統一。
shomatan added a commit that referenced this pull request May 1, 2026
…保持) (PR-C、stacked on #21) (#22)

* refactor(chat): ChatRunner を long-lived Query 化 + codex 指摘 4 件対応

PR-C の主機能: 1 ChatRunner = 1 sdk.query() = 1 subprocess に固定して、
MCP HTTP transport の OAuth 状態 (PKCE / token) を turn 跨ぎで保持する。

main 最新 (PR-A の MCP 統合 + PR-B の OAuth 2.1 / auth_request UX +
私の修正多数: XML escape / externalToolUseIds / ephemeral runOAuthCallback
/ 空 assistant プレースホルダ等) と統合した形で再実装。

## 新規ファイル

- packages/ai-engine/src/async-input.ts: SDK の streaming input mode 用
  AsyncIterable<SdkUserMessage> 実装。push 可能 + close 即時 teardown
  (CR Major 反映: close 後 next() 即 done / FIFO waiter キュー)
- packages/ai-engine/src/async-input.test.ts: 8 件 (push / waiter / close /
  FIFO / close 後 即 done)

## agent-runner.ts

- SdkLike.query を `prompt: string | AsyncIterable<SdkUserMessageLike>` に拡張
- SdkUserMessageLike + SdkQueryHandle (任意 close()) 型追加

## chat-runner.ts

新規フィールド:
- query / input / outputLoopDone / outputLoopFailed / currentTurn /
  cachedExternalConfigById
- ensureQueryInflight (Promise キャッシュ、再入ガード)

新規 interface:
- TurnState: 1 user turn の間だけ生きる mutable state
  (assistantMsgId / queue / textBuffer / stashedAuthUses /
  externalConfigById / externalToolUseIds)

新規メソッド:
- ensureQuery: 1 度だけ query 立ち上げ + 出力ループ起動。inflight
  Promise キャッシュで再入ガード (codex Major 3)
- startQueryInternal: 内部の起動本体
- runOutputLoop: SDK message を currentTurn の queue に振り分け
- dispatchSdkMessage: 1 メッセージ処理 (text/tool_use/tool_result/result)
- tearDownQuery / close: リソース cleanup。close は outputLoopDone を退避
  してから tearDownQuery を呼ぶ (codex 致命 Minor: close 順序)

挙動変更:
- runUserTurn: 入口で currentTurn ガード (codex Major 1: turn 並走防止)
- input null チェックを invariant assertion で表明 (codex Major 2)
- 空 assistant message 永続化を ensureQuery 前に移動 (long-lived では
  bg loop が即起動するため、後置きだと race で空 message 残る)
- ephemeral runOAuthCallback も long-lived query 経由に統合
  (callback URL は input.push 経由で SDK に渡るが chatStore には
  永続化されない)

main 最新の機能を保持:
- auth-detector / handleAuthToolResult / stashedAuthUses
- externalToolUseIds (TurnState に統合)
- escapeXmlText / escapeXmlAttr (buildChatPrompt)
- 空 assistant プレースホルダ「(認証処理を完了しました)」
  (dispatchSdkMessage の result 処理に統合)

buildMcpServer は 1 引数化、handler 内で this.currentTurn から
assistantMsgId / emit を動的解決 (1 度作った MCP サーバを turn 跨ぎ
で使い回せる、turn 中は不変)。

## server.ts

- WS close 時に runner.close() を呼ぶ。close 内部の reject は
  .catch で warn (codex Major: void close() で unhandled rejection 化を回避)

## chat-runner.test.ts

- startCapturePromptText helper を追加: prompt が AsyncIterable<...>
  に変わったので、最初に push された user message の content を
  奪取する (string にも互換)
- 既存 test の prompt キャプチャ 4 箇所を helper 経由に書き換え

## テスト

pnpm typecheck: 4/4 PASS
pnpm test: core 93 + storage 88 + ai-engine 213 + frontend 273 = 667 PASS
pnpm lint: exit 0

* fix(chat): CR 5 周目 Major 1 + Minor 1 対応 (Major 1 は見送り)

CodeRabbit (PR #22) 1 周目への対応。

[Minor / chat-runner.ts:246] ensureQuery 失敗時の空 assistant ロールバック
long-lived Query 化に伴い空 assistant message を ensureQuery 前に append
するよう変更したが、ensureQuery 失敗時に空バブルが履歴に残る問題があった。
chatStore に message 単位の delete API が無いため、catch 内で
replaceMessageBlocks でエラー内容を blocks に書き込む形でロールバックする。

[Major / chat-runner.ts:361] 正常 EOF と subprocess 終了の区別
runOutputLoop の正常 EOF 経路では従来 chat_turn_ended のみを emit していたが、
これだと「予期しない subprocess 終了」も「明示 shutdown」も区別できず、
途中で切れた turn が正常完了に見えてしまう問題があった。
ChatRunner.isClosing フラグを追加し、close() 内で立てる。runOutputLoop の
EOF ブランチで isClosing が false なら agent_failed event を先に emit してから
chat_turn_ended で turn を閉じる。

[Major / chat-runner.ts:599] OAuth callback の long-lived 経路統合
(見送り) callback URL を this.input に push することで会話 context に
turn 跨ぎで残る、allowedTools 単一制約が prompt 依存になる、という懸念。

PR-C の主目的 (OAuth state を turn 跨ぎで保持) と SDK 制約のトレードオフ。
ephemeral 化すると subprocess 分離で state が引き継がれず再認証が必要になる。
本 PR では long-lived 維持を選択し、prompt 内で「他 tool は呼ぶな」と明示する
形でモデル依存の防御を入れる。SDK の MCP transport が状態共有 API を提供する
までは追加対応見送り。CR thread に詳細を返信。

* fix(chat): CR 2 周目 Major 4 件対応 (Quick win)

CodeRabbit (PR #22) 2 周目への対応。Heavy lift #3 (OAuth callback の
ephemeral 化) は前 PR と同様 long-lived 維持で見送り、CR thread に返信。

[Major / chat-runner.ts:349] close と ensureQuery の競合
close() が startQueryInternal の途中 (await projectStore.getProjectMeta() 等)
で走ると、shutdown 後に sdk.query() が作られて subprocess が孤立する race
があった。close() で ensureQueryInflight を join してから tearDown する。

[Major / chat-runner.ts:358] SDK メッセージの常時ログによる機密漏洩
runOutputLoop の console.log が全 SDK メッセージを redactMcpSecrets でラップ
してログしていたが、message.content や result の OAuth callback URL の
code/state がサーバーログに残るリスクがあった。type だけ出す形に絞り、
本文は出さない。redactMcpSecrets の import も削除。

[Major / chat-runner.ts:386] 異常終了後の pendingApprovals 残存
runOutputLoop の catch / EOF ブランチで queue を閉じるだけだったので、
turn 失敗後に UI から approveTool() が来ると create_node / create_edge 等
の side effect が走る恐れがあった。rejectAllPendingApprovals helper を
新設し、両ブランチで pendingApprovals を一括 reject(false) する。

[Major / chat-runner.ts:625] runOAuthCallback の currentTurn 解放保証
this.currentTurn = turnState の直後から全体を try/finally で囲み、
appendMessage 等の中間ステップで throw しても currentTurn が解放される
ことを保証 (turn_in_progress で次の turn を受け付けられなくなるのを防ぐ)。
runUserTurn 側も同じパターンで全体を try/finally に統一。
shomatan added a commit that referenced this pull request May 3, 2026
ADR-0011 PR-E5 (最終): buildMcpServers が expiry 近接 token を transparent に
refresh し、E2E テストで PR-E1〜E4 の統合シナリオを通す。docs を整備して ADR-0011
を Accepted に確定。

ai-engine:
- buildMcpServers: REFRESH_BUFFER_MS (5 min) 以内に expire する token は
  refreshAccessToken を呼んで store に書き戻す。失敗時は expired ? null : token で
  fall back (refresh_token 失効 → MCP 401 → UI 再認証経路へ)。
  loadUsableToken ヘルパで 1 server 分の取得を切り出し
- build-mcp-servers.test.ts: refresh path 5 ケース追加 (expiry 直前 + refresh 成功 /
  rotate 無し refresh / refresh 4xx 失敗 → null / refreshToken 無し expired → null /
  期限十分先で refresh しない)
- 新規 oauth-e2e.test.ts: orchestrator → loopback callback → token store →
  buildMcpServers → header 注入の 1 シナリオ + 透過 refresh の 1 シナリオ

docs:
- README に「外部 MCP (Atlassian) の OAuth セットアップ」section 追加
- docs/adr/0011-tally-managed-oauth-flow.md: status を Accepted (PR-E5 merge をもって
  確定) に、PR 分割実績テーブル更新 (E1-E2-E3a-E3b-E4-E5)、完了後の確定事項セクション追加

codex セカンドオピニオン対応:
- Major #1: refresh 後の expiresAt は HTTP ラウンドトリップ後の Date.now() を起点に計算
- Major #2: refresh の write race を comment 化 (MVP 単一ユーザー前提なので last-write-wins
  許容、将来 multi-tenant 化時に mutex 追加が必要)
- Minor #3: README の redirect URI を「Tally 起動後に表示される実ポートを Atlassian console
  に登録」と明記 (port=0 静的登録は Atlassian が拒否するため)
- Minor #4: ADR status を 'Accepted (PR-E5 merge をもって確定)' に hedge

テスト: ai-engine 242 pass (+7 PR-E5 新規)、core 94 / storage 97 / frontend 282 すべて pass
typecheck clean / biome lint clean
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.

1 participant