Skip to content

[#721] add-claude-terminal-provider#727

Open
nrslib wants to merge 3 commits into
mainfrom
takt/721/add-claude-terminal-provider
Open

[#721] add-claude-terminal-provider#727
nrslib wants to merge 3 commits into
mainfrom
takt/721/add-claude-terminal-provider

Conversation

@nrslib
Copy link
Copy Markdown
Owner

@nrslib nrslib commented May 16, 2026

Summary

概要

Claude Code を headless 実行ではなく、通常の interactive terminal session として起動する provider: claude-terminal を追加したい。

現在の provider: claude は headless CLI 実行を前提にしている。一方で、TAKT の workflow step から通常の Claude Code session を扱える provider があると、既存の Claude Code 環境、ログイン状態、worktree 上のファイル操作と自然に統合しやすい。

この provider は既存 provider: claude を置き換えるものではなく、interactive terminal 実行を選びたい場合の新しい experimental provider として追加する。

目的

  • provider: claude-terminal を新規 provider として追加する
  • Claude Code を interactive terminal session として起動する
  • 実行 cwd は workflow / task の worktree に揃える
  • workflow engine からは既存の Provider interface と同じ形で扱えるようにする
  • 既存の ProviderCallOptions で渡される主要機能を claude-terminal でも扱えるようにする
  • 応答は既存の AgentResponse に正規化する
  • terminal backend は interface 化し、最初は tmux backend のみ対応する

必要な互換性

claude-terminal は新しい provider だが、TAKT workflow から見た provider としては既存 provider と同じ契約を満たす必要がある。

次の項目は対応対象とする。ただし interactive terminal 実行の制約により headless provider と同じ粒度で扱えない可能性がある。実装できない項目は黙って無視せず、明確な unsupported error、provider error、または usageMissing: true のような明示的な欠落情報として扱う。

  • cwd
  • abortSignal
  • sessionId
  • model
  • allowedTools
  • mcpServers
  • maxTurns
  • permissionMode
  • providerOptions
  • onStream
  • onPermissionRequest
  • onAskUserQuestion
  • bypassPermissions
  • outputSchema

また、戻り値では次の情報を可能な限り既存 provider と同じ意味で埋める。取得できない値は推測で埋めず、欠落理由を明示する。

  • content
  • status
  • sessionId
  • error
  • errorKind
  • rateLimitInfo
  • failureCategory
  • structuredOutput
  • providerUsage

特に workflow 実行上重要な abortSignalpermissionModeallowedToolsmcpServersonStreamoutputSchema は初期実装の対応対象に含める。完全に同等の観測性を保証できない場合でも、その制約を明示して失敗または欠落として表現する。

非目的

  • 既存の provider: claude を置き換えない
  • provider: claude の内部実装を claude-terminal に寄せない
  • terminal UI や transcript の内部形式を TAKT の public API にしない
  • 画像の multimodal 直渡しはこの Issue では扱わない

利用例

steps:
  - name: implement
    kind: agent
    agent: coder
    provider: claude-terminal
    model: opus
    provider_options:
      claude:
        effort: high
        allowed_tools:
          - Read
          - Edit
          - Bash
      claude_terminal:
        backend: tmux
        timeout_ms: 900000
        keep_session: false

providermodel は既存仕様通り step と同じ階層に置く。

provider_options.claude は Claude 共通の option として扱う。
provider_options.claude_terminal は terminal 実行固有の option として扱う。

設計案

新しい provider type を追加する。

export type ProviderType =
  | 'claude'
  | 'claude-sdk'
  | 'claude-terminal'
  | 'codex'
  | 'opencode'
  | 'cursor'
  | 'copilot'
  | 'mock';

provider 実装は既存の Provider interface に乗せる。

export class ClaudeTerminalProvider implements Provider {
  readonly supportsStructuredOutput = true;

  setup(config: AgentSetup): ProviderAgent {
    return {
      call: (prompt, options) => callClaudeTerminal(config, prompt, options),
    };
  }
}

supportsStructuredOutputoutputSchema を受け取ったときに structuredOutput を返せることを意味する。terminal 実行で native structured output が使えない場合でも、provider 境界で schema 指示、応答抽出、検証、失敗時エラー化まで責務を持つ。

内部では terminal 操作と Claude Code の応答検出を分離する。

export interface TerminalBackend {
  start(options: TerminalStartOptions): Promise<TerminalSession>;
  pasteText(session: TerminalSession, text: string): Promise<void>;
  captureScreen(session: TerminalSession): Promise<string>;
  stop(session: TerminalSession): Promise<void>;
}
export interface ClaudeTranscriptReader {
  findSession(options: FindClaudeSessionOptions): Promise<ClaudeSessionRef>;
  waitForAssistantResponse(
    session: ClaudeSessionRef,
    options: WaitForClaudeResponseOptions,
  ): Promise<ClaudeTerminalResponse>;
}

想定する責務分離は次の通り。

Workflow engine
  -> ProviderRegistry
    -> ClaudeTerminalProvider
      -> TerminalBackend
      -> ClaudeTranscriptReader
      -> AgentResponse normalizer

provider_options

terminal 固有 option を追加する。

export interface ClaudeTerminalProviderOptions {
  backend?: 'tmux';
  timeoutMs?: number;
  keepSession?: boolean;
  transcriptPollIntervalMs?: number;
}

export interface StepProviderOptions {
  codex?: CodexProviderOptions;
  opencode?: OpenCodeProviderOptions;
  claude?: ClaudeProviderOptions;
  claudeTerminal?: ClaudeTerminalProviderOptions;
  copilot?: CopilotProviderOptions;
}

YAML では snake_case で指定する。

provider_options:
  claude_terminal:
    backend: tmux
    timeout_ms: 900000
    keep_session: false
    transcript_poll_interval_ms: 500

実行フロー

  1. 既存の workflow provider 解決処理で provider / model / provider_options を解決する
  2. ProviderCallOptionsclaude-terminal の実行設定へ変換する
  3. options.cwd で terminal session を開始する
  4. terminal session 内で Claude Code を起動する
  5. modelpermissionModeallowedToolsmcpServersbypassPermissions を Claude Code 起動設定へ反映する
  6. outputSchema がある場合は structured output 用の指示と検証を有効にする
  7. step prompt を terminal に paste する
  8. Claude Code の transcript から対象 session を特定する
  9. assistant 応答、tool event、permission request、ask-user question を監視する
  10. onStream / onPermissionRequest / onAskUserQuestion を既存 provider と同じ意味で呼び出す
  11. 応答を AgentResponse に変換する
  12. keep_session が false の場合は terminal session を停止する

エラー処理

  • tmux が存在しない場合は明確なエラーで fail fast する
  • terminal session を開始できない場合は fail fast する
  • 対応対象の ProviderCallOptions を反映できない場合は、黙って無視せず明確な unsupported error または provider error とする
  • timeout 内に transcript を特定できない場合は provider error とする
  • timeout 内に assistant 応答を検出できない場合は provider error とする
  • abortSignal が発火した場合は terminal session を停止する
  • outputSchema 指定時に構造化出力を抽出・検証できない場合は provider error とする
  • 未対応の provider_options.claude_terminal は黙って無視しない

テスト観点

  • ProviderRegistryclaude-terminal を解決できる
  • provider_options.claude_terminal を normalize / denormalize できる
  • ProviderCallOptions が terminal 実行設定へ正しく変換される
  • terminal backend の command 組み立てを検証する
  • fixture の transcript から session / assistant 応答 / tool event を検出できる
  • onStream が既存 provider と同じ event 意味で呼ばれる
  • permission request / ask-user question が callback へ橋渡しされる
  • outputSchema 指定時は structuredOutput を返すか、抽出・検証できない理由を明示した provider error にする
  • AgentResponse への変換を検証する
  • timeout / abort を mock backend で検証する

受け入れ条件

  • workflow step で provider: claude-terminal を指定できる
  • Claude Code が step の cwd で起動される
  • step prompt が terminal backend 経由で送信される
  • modelallowedToolsmcpServerspermissionModebypassPermissions が反映される
  • abortSignal により terminal session が停止される
  • onStreamonPermissionRequestonAskUserQuestion は対応対象とし、terminal 実行の制約で同等に扱えない場合は明示的に unsupported とする
  • outputSchema 指定時は structuredOutput を返すか、抽出・検証できない理由を明示した provider error にする
  • provider が既存 workflow engine で扱える AgentResponse を返す
  • provider_options.claude_terminal が YAML から正規化され provider に渡る
  • provider 登録、option 正規化、backend 抽象、transcript 読み取り、response 変換、structured output、callback、abort のテストがある

Execution Report

Workflow takt-default completed successfully.

Closes #721

Summary by CodeRabbit

  • New Features

    • Added Claude Terminal provider (claude-terminal) supporting interactive terminal sessions with tmux backend.
    • New provider configuration options: backend selection, timeout, session persistence, and transcript polling interval.
    • Interactive permission requests and user question handling during terminal sessions.
    • MCP server configuration support for Claude Terminal provider.
    • Environment variable overrides for provider configuration options.
  • Documentation

    • Updated configuration reference with Claude Terminal provider details and usage examples.

Review Change Stack

@nrslib
Copy link
Copy Markdown
Owner Author

nrslib commented May 16, 2026

/review

@nrslib
Copy link
Copy Markdown
Owner Author

nrslib commented May 16, 2026

/review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

📝 Walkthrough

Walkthrough

This PR implements a new claude-terminal provider that runs Claude Code as an interactive tmux session instead of headless execution. It includes type contracts, terminal backend management, transcript parsing, provider wiring, configuration support, capability filtering for maxTurns, and comprehensive testing across unit and integration scenarios.

Changes

Claude Terminal Provider Implementation

Layer / File(s) Summary
Type contracts and schemas
src/shared/types/provider.ts, src/infra/claude-terminal/types.ts, src/core/models/schema-base.ts, src/core/models/workflow-provider-options.ts, src/core/models/workflow-types.ts, src/core/models/provider-profiles.ts
Added claude-terminal to ProviderType union; defined terminal backend lifecycle, session, command, event, and transcript interfaces; added ClaudeTerminalProviderOptions with backend, timeoutMs, keepSession, transcriptPollIntervalMs; added validation to reject network_access and sandbox for claude-terminal provider blocks.
Terminal backend and command building
src/infra/claude-terminal/tmux-backend.ts, src/infra/claude-terminal/command.ts
Implemented TmuxTerminalBackend with start(), pasteText(), stop() using tmux CLI; added command construction with model, effort, tools, MCP, permissions, system prompt, and output schema; included error handling, sensitive argument redaction, and session name generation.
Transcript reading and parsing
src/infra/claude-terminal/transcript-reader.ts
Implemented ProjectClaudeTranscriptReader to parse JSONL transcripts, extract session id/assistant text/tool events, detect completion, handle baselines, validate session id safety, and provide abort-aware polling for session discovery and response waiting.
Response normalization and provider
src/infra/claude-terminal/response-normalizer.ts, src/infra/providers/claude-terminal.ts, src/infra/providers/index.ts
Added normalizeClaudeTerminalResponse to parse structured output, detect rate limits, emit streaming events, and construct error responses; implemented ClaudeTerminalProvider bridging to callClaudeTerminal; registered provider in ProviderRegistry.
Client and main call orchestration
src/infra/claude-terminal/client.ts
Implemented callClaudeTerminal orchestrating terminal lifecycle: backend start, MCP config preparation, command building, prompt pasting, transcript reading, interactive event handling (permission/ask-user callbacks), abort cleanup, and response normalization.
MCP config refactoring
src/infra/claude/mcp-config.ts, src/infra/claude-headless/client.ts
Extracted MCP config file creation into shared prepareClaudeMcpConfig module; updated headless client to use refactored helper instead of inline implementation.
Max turns capability system
src/infra/providers/provider-capabilities.ts, src/agents/provider-call-options.ts, src/agents/judge-status-usecase.ts, src/agents/decompose-task-usecase.ts, src/agents/structured-caller/prompt-based-structured-caller.ts, src/core/workflow/engine/OptionsBuilder.ts, src/core/workflow/report-phase-runner.ts
Added providerSupportsMaxTurns capability with MAX_TURNS_PROVIDERS allowlist excluding claude-terminal; introduced buildMaxTurnsOption helper to conditionally include maxTurns based on provider support; updated agent use cases and phase runners to use dynamic max-turns construction.
Configuration normalization
src/infra/config/providerOptions.ts, src/infra/config/providerOptionsContract.ts, src/infra/config/configNormalizers.ts, src/infra/config/global/initialization.ts
Extended provider option handling to support claude_terminal with snake_case↔camelCase conversion, merging, effective resolution, source attribution (PROVIDER_OPTION_PATHS), environment variable specs, trace paths, and denormalization back to raw config.
Phase runner updates
src/core/workflow/phase-runner.ts, src/core/workflow/status-judgment-phase.ts
Removed resolveProvider fallback from PhaseRunnerContext; enforced resolveStepProviderModel as sole provider resolution source with helper that throws when missing.
Documentation
docs/configuration.md, docs/configuration.ja.md, src/app/cli/program.ts
Added claude-terminal to global/project configuration examples and field references; documented terminal-specific options (backend, timeout, keep_session, transcript_poll_interval); added environment variable override examples.
Unit tests: contracts and schemas
src/__tests__/claude-terminal-provider-contract.test.ts, src/__tests__/claude-terminal-provider-options.test.ts, src/__tests__/provider-options-contract.test.ts
Verified schema parsing, option normalization/denormalization, path alignment, trace-path entries, and capability reporting for claude-terminal provider.
Unit tests: backend and transcript
src/__tests__/claude-terminal-tmux-backend.test.ts, src/__tests__/claude-terminal-transcript-reader.test.ts
Tested tmux command construction, buffer operations, cleanup, error handling, transcript parsing, session discovery, completion detection, abort-aware polling, and safety checks.
Unit tests: client and interactivity
src/__tests__/claude-terminal-client.test.ts, src/__tests__/claude-terminal-client-abort-cleanup.test.ts, src/__tests__/claude-terminal-response-normalizer.test.ts, src/__tests__/claude-terminal-command.test.ts, src/__tests__/claude-terminal-provider-wiring.test.ts
Tested default session flow, MCP config creation/cleanup, session preservation, multiple abort scenarios, interactive event handling (permission requests, ask-user questions), callback invocation, structured output parsing, rate-limit detection, provider error responses, and sandbox rejection.
Unit tests: provider capabilities and options
src/__tests__/provider-capabilities.test.ts, src/__tests__/options-builder.test.ts, src/__tests__/agent-usecases.test.ts, src/__tests__/structured-caller-prompt-based.test.ts
Verified providerSupportsMaxTurns helper, maxTurns omission for claude-terminal across agent use cases, report/resume phase option filtering, and engine option resolution.
Unit tests: workflow integration
src/__tests__/engine-provider-options.test.ts, src/__tests__/status-judgment-phase.test.ts, src/__tests__/status-judgment-phase-structured-caller.test.ts, src/__tests__/it-provider-config-block.test.ts, src/__tests__/initialization.test.ts
Tested WorkflowEngine provider option resolution, phase runner provider resolution updates, YAML configuration propagation, and provider selection UI.
Integration tests: execution
src/__tests__/workflowExecution-claude-terminal.test.ts
End-to-end workflow execution with mocked tmux backend and transcript reader across baseline, report-phase, and multi-rule scenarios.

🎯 4 (Complex) | ⏱️ ~60 minutes

A rabbit hops through the codebase, building bridges of terminals and transcripts,
Where Claude meets tmux in a interactive dance,
No more headless whispers—just flowing session chants! 🐰💬

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch takt/721/add-claude-terminal-provider

@nrslib
Copy link
Copy Markdown
Owner Author

nrslib commented May 17, 2026

コード品質観点で 1 点、merge 前に直した方がよいと思います。

src/infra/claude-terminal/client.tswithAbort() は呼び出し側には即座に abort を返しますが、渡された operation 自体は止まりません。実装上、その operation には ProjectClaudeTranscriptReader.waitForAssistantResponse() / findSession() が入り、内部では src/infra/claude-terminal/transcript-reader.tspollUntil()sleep(pollIntervalMs) しながら最大 timeoutMs まで polling します。

そのため Ctrl+C / abort 後も、裏で transcript polling のタイマーが最大 15 分残り続ける可能性があります。現在のテストは waitForAssistantResponse を never-resolving mock にしていて、実 pollUntil() の timer が abort 後に止まるかまでは検出できていません。

修正案:

  • ClaudeTranscriptReaderfindSession / waitForAssistantResponse オプションに abortSignal を渡す
  • pollUntil() 側で signal.aborted をチェックする
  • sleep(pollIntervalMs, undefined, { signal }) などを使って sleep 自体も abort 可能にする
  • abort 後に polling が継続しないテストを追加する

CI とローカル npm test -- --run は通っていますが、ここは実運用の停止性に関わるので、merge 前に潰したいです。

@nrslib
Copy link
Copy Markdown
Owner Author

nrslib commented May 17, 2026

Summary

タスク指示書: claude-terminal の abort 後 polling 継続問題を修正する

目的

provider: claude-terminal 実行中に abortSignal が発火した場合、呼び出し側だけでなく transcript polling 処理自体も速やかに停止するよう修正する。

現在の指摘事項では、src/infra/claude-terminal/client.tswithAbort() が呼び出し元には即座に abort を返す一方、内部の ProjectClaudeTranscriptReader.findSession() / waitForAssistantResponse() が継続し、src/infra/claude-terminal/transcript-reader.ts の polling timer が最大 timeoutMs まで残る可能性がある。

優先度: 高

対象ファイル / モジュール

  • src/infra/claude-terminal/client.ts
  • src/infra/claude-terminal/transcript-reader.ts
  • src/infra/claude-terminal/types.ts
  • src/__tests__/claude-terminal-client-abort-cleanup.test.ts
  • src/__tests__/claude-terminal-transcript-reader.test.ts
  • 必要に応じて、既存の claude-terminal 関連テスト

作業内容

1. ClaudeTranscriptReader の option に abort signal を伝搬する

src/infra/claude-terminal/types.ts を確認し、findSession() / waitForAssistantResponse() に渡す option 型へ abortSignal を追加する。

対象候補:

  • FindClaudeSessionOptions
  • WaitForClaudeResponseOptions

abortSignal は既存の ProviderCallOptions.abortSignal から伝搬する。

2. client.ts から transcript reader へ abort signal を渡す

src/infra/claude-terminal/client.ts を修正し、以下の transcript reader 呼び出しに abortSignal を渡す。

  • reader.findSession(...)
  • reader.waitForAssistantResponse(...)

withAbort() で呼び出し側だけを Promise.race 的に止めるのではなく、内部 operation が abort を観測できる配線にする。

3. pollUntil() を abort 対応にする

src/infra/claude-terminal/transcript-reader.ts の polling 処理を修正する。

必要な挙動:

  • polling 開始前に signal.aborted を確認する
  • 各 polling iteration の前後で abort を確認する
  • sleep(pollIntervalMs) 中も abort 可能にする
  • abort 発火後は次の polling を継続しない
  • abort 時は既存の provider 側 abort エラー処理と整合する形で例外または失敗を返す

Node.js の標準 API で対応できる場合は node:timers/promises の abort 対応 sleep を使う。既存コードに sleep helper がある場合は、既存パターンを優先して abort 対応を追加する。

4. abort 後に polling が継続しないテストを追加する

既存の src/__tests__/claude-terminal-client-abort-cleanup.test.ts は never-resolving mock による検証に留まっている可能性があるため、実際の ProjectClaudeTranscriptReader / pollUntil() 経路で timer が止まることを検証する。

追加・更新する観点:

  • findSession() の polling 中に abort した場合、次の polling が走らない
  • waitForAssistantResponse() の polling 中に abort した場合、次の polling が走らない
  • abort 後に pending timer が残らない
  • client 経由の abort でも terminal session cleanup と transcript polling cleanup が両方成立する

Vitest の fake timers を使う場合は、abort 後に timer を進めても polling 回数が増えないことを確認する。

確認方法

以下を実行して確認する。

npm test -- --run src/__tests__/claude-terminal-client-abort-cleanup.test.ts src/__tests__/claude-terminal-transcript-reader.test.ts

可能であれば追加で実行する。

npm test -- --run
npm run lint

受け入れ条件

  • abortSignalclient.ts から transcript reader の findSession() / waitForAssistantResponse() まで伝搬している
  • pollUntil() が abort を検知し、sleep 中も速やかに中断する
  • abort 後に transcript polling の timer が timeoutMs まで残り続けない
  • abort cleanup のテストが実 polling 経路を検証している
  • 既存の claude-terminal provider の正常系・timeout 系の挙動を壊していない

Open Questions

なし。

Execution Report

Workflow takt-default completed successfully.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/workflow/engine/OptionsBuilder.ts`:
- Around line 246-247: The options object creation in OptionsBuilder is
currently assigning a maxTurns property even when unsupported (via
resolveSupportedMaxTurns returning undefined); change to conditional object
spreading so maxTurns is only present when defined: call
resolveSupportedMaxTurns(step, overrides.maxTurns, runtime) into a local
variable (e.g., const maxTurns = this.resolveSupportedMaxTurns(...)) and then
include ...(maxTurns !== undefined ? { maxTurns } : {}) when building the
options in the methods that construct the options (where maxTurns is currently
added, referenced by OptionsBuilder and resolveSupportedMaxTurns) so unsupported
providers receive no maxTurns key at all.

In `@src/core/workflow/status-judgment-phase.ts`:
- Around line 119-121: Move the call to resolveStepProviderInfo(step, ctx)
inside the existing try block so any exception it throws is caught and triggers
the existing error handling path (including the onPhaseComplete(..., 'error',
...) call). Locate the current resolveStepProviderInfo invocation (near
stepProvider variable) and hoist its declaration if needed, then assign
stepProvider within the try before any code that relies on it; ensure subsequent
references use that variable so the catch block executes for resolve failures.

In `@src/infra/claude-terminal/response-normalizer.ts`:
- Around line 95-96: The rate-limit field builder always hardcodes source:
'stream_marker' which mislabels non-marker detections; update the call to
buildRateLimitedResponseFields('claude-terminal', ..., input.assistantText) to
pass the actual detector source determined from the matched detector (e.g., use
the matched detector's type or a boolean like isMarker to choose between
'stream_marker' and 'error_text'); find other similar invocations around the
matched detector handling (the block that currently accepts both marker-based
and error-text-based detections at the later lines) and apply the same dynamic
source selection so all rate-limit diagnostics use the correct source value.

In `@src/infra/claude-terminal/transcript-reader.ts`:
- Around line 193-206: parseClaudeTerminalTranscript currently hard-fails when
parseTranscriptLine throws on a partially-written last JSONL line; change
behavior to tolerate transient incomplete lines by skipping (or deferring) the
final line parse if JSON parsing fails for the last line instead of throwing.
Specifically, in parseClaudeTerminalTranscript (and the same logic area around
the block using transcriptSinceBaseline -> .split -> .reduce), catch JSON/parse
errors from parseTranscriptLine for the last raw line (index === last) and
simply return the accumulated parsed state without appending that entry (so
polling code like findSession/waitForAssistantResponse can retry), while still
propagating non-transient errors for earlier lines; keep use of
appendTranscriptEntry and lineNumberOffset unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 70e7b6f6-5d85-48bf-8009-b56d796415ab

📥 Commits

Reviewing files that changed from the base of the PR and between d39fffc and f6f1ab7.

📒 Files selected for processing (53)
  • docs/configuration.ja.md
  • docs/configuration.md
  • src/__tests__/agent-usecases.test.ts
  • src/__tests__/claude-mcp-config.test.ts
  • src/__tests__/claude-terminal-client-abort-cleanup.test.ts
  • src/__tests__/claude-terminal-client.test.ts
  • src/__tests__/claude-terminal-command.test.ts
  • src/__tests__/claude-terminal-provider-contract.test.ts
  • src/__tests__/claude-terminal-provider-options.test.ts
  • src/__tests__/claude-terminal-provider-wiring.test.ts
  • src/__tests__/claude-terminal-response-normalizer.test.ts
  • src/__tests__/claude-terminal-tmux-backend.test.ts
  • src/__tests__/claude-terminal-transcript-reader.test.ts
  • src/__tests__/engine-provider-options.test.ts
  • src/__tests__/initialization.test.ts
  • src/__tests__/it-provider-config-block.test.ts
  • src/__tests__/options-builder.test.ts
  • src/__tests__/provider-capabilities.test.ts
  • src/__tests__/provider-options-contract.test.ts
  • src/__tests__/status-judgment-phase-structured-caller.test.ts
  • src/__tests__/status-judgment-phase.test.ts
  • src/__tests__/structured-caller-prompt-based.test.ts
  • src/__tests__/workflowExecution-claude-terminal.test.ts
  • src/agents/decompose-task-usecase.ts
  • src/agents/judge-status-usecase.ts
  • src/agents/provider-call-options.ts
  • src/agents/structured-caller/prompt-based-structured-caller.ts
  • src/app/cli/program.ts
  • src/core/models/provider-profiles.ts
  • src/core/models/schema-base.ts
  • src/core/models/workflow-provider-options.ts
  • src/core/models/workflow-types.ts
  • src/core/workflow/engine/OptionsBuilder.ts
  • src/core/workflow/permission-profile-resolution.ts
  • src/core/workflow/phase-runner.ts
  • src/core/workflow/report-phase-runner.ts
  • src/core/workflow/status-judgment-phase.ts
  • src/infra/claude-headless/client.ts
  • src/infra/claude-terminal/client.ts
  • src/infra/claude-terminal/command.ts
  • src/infra/claude-terminal/response-normalizer.ts
  • src/infra/claude-terminal/tmux-backend.ts
  • src/infra/claude-terminal/transcript-reader.ts
  • src/infra/claude-terminal/types.ts
  • src/infra/claude/mcp-config.ts
  • src/infra/config/configNormalizers.ts
  • src/infra/config/global/initialization.ts
  • src/infra/config/providerOptions.ts
  • src/infra/config/providerOptionsContract.ts
  • src/infra/providers/claude-terminal.ts
  • src/infra/providers/index.ts
  • src/infra/providers/provider-capabilities.ts
  • src/shared/types/provider.ts

Comment on lines +246 to 247
maxTurns: this.resolveSupportedMaxTurns(step, overrides.maxTurns, runtime),
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Truly omit unsupported maxTurns instead of assigning undefined.

Line 246 and Line 260 still emit a maxTurns property even when unsupported. Prefer conditional object spreading so unsupported providers receive no maxTurns key at all.

Suggested patch
   buildResumeOptions(
@@
   ): RunAgentOptions {
+    const supportedMaxTurns = this.resolveSupportedMaxTurns(step, overrides.maxTurns, runtime);
     return {
       ...this.buildBaseOptions(step, undefined, runtime),
       // Report/status phases are read-only regardless of step settings.
       permissionMode: 'readonly',
       sessionId,
       allowedTools: [],
-      maxTurns: this.resolveSupportedMaxTurns(step, overrides.maxTurns, runtime),
+      ...(supportedMaxTurns !== undefined ? { maxTurns: supportedMaxTurns } : {}),
     };
   }
@@
   ): RunAgentOptions {
+    const supportedMaxTurns = this.resolveSupportedMaxTurns(step, overrides.maxTurns, runtime);
     return {
       ...this.buildBaseOptions(step, undefined, runtime),
       permissionMode: 'readonly',
       allowedTools: overrides.allowedTools,
-      maxTurns: this.resolveSupportedMaxTurns(step, overrides.maxTurns, runtime),
+      ...(supportedMaxTurns !== undefined ? { maxTurns: supportedMaxTurns } : {}),
     };
   }

Also applies to: 260-261

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/workflow/engine/OptionsBuilder.ts` around lines 246 - 247, The
options object creation in OptionsBuilder is currently assigning a maxTurns
property even when unsupported (via resolveSupportedMaxTurns returning
undefined); change to conditional object spreading so maxTurns is only present
when defined: call resolveSupportedMaxTurns(step, overrides.maxTurns, runtime)
into a local variable (e.g., const maxTurns =
this.resolveSupportedMaxTurns(...)) and then include ...(maxTurns !== undefined
? { maxTurns } : {}) when building the options in the methods that construct the
options (where maxTurns is currently added, referenced by OptionsBuilder and
resolveSupportedMaxTurns) so unsupported providers receive no maxTurns key at
all.

Comment on lines +119 to 121
const stepProvider = resolveStepProviderInfo(step, ctx);

try {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Resolve step provider inside the try block to preserve error callbacks.

resolveStepProviderInfo() can throw on Line 119 before try starts, which skips onPhaseComplete(..., 'error', ...) for that failure path.

Suggested patch
-  const stepProvider = resolveStepProviderInfo(step, ctx);
-
   try {
+    const stepProvider = resolveStepProviderInfo(step, ctx);
     const result = await ctx.structuredCaller.judgeStatus(structuredInstruction, tagInstruction, step.rules, {
       cwd: ctx.cwd,
       stepName: step.name,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/workflow/status-judgment-phase.ts` around lines 119 - 121, Move the
call to resolveStepProviderInfo(step, ctx) inside the existing try block so any
exception it throws is caught and triggers the existing error handling path
(including the onPhaseComplete(..., 'error', ...) call). Locate the current
resolveStepProviderInfo invocation (near stepProvider variable) and hoist its
declaration if needed, then assign stepProvider within the try before any code
that relies on it; ensure subsequent references use that variable so the catch
block executes for resolve failures.

Comment on lines +95 to +96
...buildRateLimitedResponseFields('claude-terminal', 'stream_marker', input.assistantText),
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use the matched rate-limit detector to set source correctly.

Line 114 accepts both marker-based and error-text-based rate-limit detections, but Line 95 always emits source: 'stream_marker'. That misclassifies non-marker cases and degrades rate-limit diagnostics.

Also applies to: 114-116

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/infra/claude-terminal/response-normalizer.ts` around lines 95 - 96, The
rate-limit field builder always hardcodes source: 'stream_marker' which
mislabels non-marker detections; update the call to
buildRateLimitedResponseFields('claude-terminal', ..., input.assistantText) to
pass the actual detector source determined from the matched detector (e.g., use
the matched detector's type or a boolean like isMarker to choose between
'stream_marker' and 'error_text'); find other similar invocations around the
matched detector handling (the block that currently accepts both marker-based
and error-text-based detections at the later lines) and apply the same dynamic
source selection so all rate-limit diagnostics use the correct source value.

Comment on lines +193 to +206
export function parseClaudeTerminalTranscript(
transcript: string,
baseline?: ClaudeTranscriptBaseline,
): ClaudeTerminalTranscript {
const lineNumberOffset = baseline?.lineNumberOffset ?? 0;
return transcriptSinceBaseline(transcript, baseline)
.split(/\r?\n/)
.reduce<ClaudeTerminalTranscript>((parsed, rawLine, index) => {
const line = rawLine.trim();
if (line.length === 0) {
return parsed;
}
return appendTranscriptEntry(parsed, parseTranscriptLine(line, index + 1 + lineNumberOffset));
}, { sessionId: '', assistantText: '', events: [] });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle in-progress JSONL writes without hard-failing polling.

The polling path currently fails if the transcript’s last line is temporarily incomplete while Claude is writing. That turns a transient state into a hard error instead of retrying, which can prematurely abort findSession/waitForAssistantResponse.

Suggested fix
-export function parseClaudeTerminalTranscript(
-  transcript: string,
-  baseline?: ClaudeTranscriptBaseline,
-): ClaudeTerminalTranscript {
+export function parseClaudeTerminalTranscript(
+  transcript: string,
+  baseline?: ClaudeTranscriptBaseline,
+  options?: { allowIncompleteTrailingLine?: boolean },
+): ClaudeTerminalTranscript {
   const lineNumberOffset = baseline?.lineNumberOffset ?? 0;
-  return transcriptSinceBaseline(transcript, baseline)
-    .split(/\r?\n/)
+  const sliced = transcriptSinceBaseline(transcript, baseline);
+  const lines = sliced.split(/\r?\n/);
+  return lines
     .reduce<ClaudeTerminalTranscript>((parsed, rawLine, index) => {
       const line = rawLine.trim();
       if (line.length === 0) {
         return parsed;
       }
-      return appendTranscriptEntry(parsed, parseTranscriptLine(line, index + 1 + lineNumberOffset));
+      try {
+        return appendTranscriptEntry(parsed, parseTranscriptLine(line, index + 1 + lineNumberOffset));
+      } catch (error) {
+        const isTrailingLine = index === lines.length - 1;
+        if (
+          options?.allowIncompleteTrailingLine
+          && isTrailingLine
+          && !sliced.endsWith('\n')
+          && error instanceof Error
+          && /Malformed Claude terminal transcript JSON/.test(error.message)
+        ) {
+          return parsed;
+        }
+        throw error;
+      }
     }, { sessionId: '', assistantText: '', events: [] });
 }
-        const parsed = parseClaudeTerminalTranscript(transcript);
+        const parsed = parseClaudeTerminalTranscript(transcript, undefined, {
+          allowIncompleteTrailingLine: true,
+        });
-        const parsed = parseClaudeTerminalTranscript(transcript, options.baseline);
+        const parsed = parseClaudeTerminalTranscript(transcript, options.baseline, {
+          allowIncompleteTrailingLine: true,
+        });

Also applies to: 326-367

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/infra/claude-terminal/transcript-reader.ts` around lines 193 - 206,
parseClaudeTerminalTranscript currently hard-fails when parseTranscriptLine
throws on a partially-written last JSONL line; change behavior to tolerate
transient incomplete lines by skipping (or deferring) the final line parse if
JSON parsing fails for the last line instead of throwing. Specifically, in
parseClaudeTerminalTranscript (and the same logic area around the block using
transcriptSinceBaseline -> .split -> .reduce), catch JSON/parse errors from
parseTranscriptLine for the last raw line (index === last) and simply return the
accumulated parsed state without appending that entry (so polling code like
findSession/waitForAssistantResponse can retry), while still propagating
non-transient errors for earlier lines; keep use of appendTranscriptEntry and
lineNumberOffset unchanged.

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.

experimental provider として claude-terminal を追加する

1 participant