Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions e2e/test_admin_vscode_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ def retry(route):
page.route("**/admin/api/integrations/vscode-chat", status)
page.route("**/admin/api/integrations/vscode-chat/refresh", retry)
page.goto(f"{admin_base_url}/admin/integrations")
# Startup reconciliation can temporarily disable Retry during the click.
# Exercise the action after initial polling has settled.
page.wait_for_function(
"state.startup && !state.startupRequest && state.startupTimer === null"
)
main = page.locator("#openVSCodeChatIntegration")
secondary = page.locator("#retryVSCodeChatIntegration")
if status_read_failed:
Expand Down
26 changes: 14 additions & 12 deletions e2e/test_code_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2055,21 +2055,23 @@ def test_library_reconnect_removes_missed_deletion_and_searches_beyond_first_pag
):
control_feed(page)

async def seed():
first = await code_control.service.create_session(
str(uuid.uuid4()), str(tmp_path)
)
await code_control.service.update_settings(
first.id, first.revision, {"title": "Needle project"}
)
for _ in range(26):
await code_control.service.create_session(str(uuid.uuid4()), str(tmp_path))
return first.id

# Start the isolated service before seeding it.
page.goto(f"{admin_base_url}/admin/code")
expect(page.locator("#codeNew")).to_be_enabled()
session_id = code_control.run(seed())
# Bound each operation, not the cumulative cost of filling a whole page.
first = code_control.run(
code_control.service.create_session(str(uuid.uuid4()), str(tmp_path))
)
code_control.run(
code_control.service.update_settings(
first.id, first.revision, {"title": "Needle project"}
)
)
for _ in range(26):
code_control.run(
code_control.service.create_session(str(uuid.uuid4()), str(tmp_path))
)
session_id = first.id
page.get_by_role("searchbox", name="Search titles and folders").fill("Needle")
expect(page.locator(".session-card")).to_have_count(1)
expect(page.locator(".session-card")).to_contain_text("Needle project")
Expand Down
21 changes: 12 additions & 9 deletions smoke/capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,13 +269,16 @@ class CapabilityContract:
),
CapabilityContract(
"streaming_conversion",
"heuristic_tools",
"heuristic_tool_parser",
"free_claude_code.core.anthropic.tools",
"textual tool-call output",
"native_tools",
"native_tool_assembly",
"free_claude_code.providers.openai_chat.tool_calls",
"native structured tool-call output",
"structured Anthropic tool_use blocks",
"text fallback when malformed",
("tests/providers/test_parsers.py", "tests/contracts/test_stream_contracts.py"),
"raw arguments preserved for harness validation",
(
"tests/providers/test_native_tool_arguments.py",
"tests/contracts/test_stream_contracts.py",
),
(
"test_live_tool_use_when_configured_model_supports_tools",
"test_provider_reasoning_tool_continuation_e2e",
Expand All @@ -285,10 +288,10 @@ class CapabilityContract:
"streaming_conversion",
"subagent_task_control",
"subagent_control",
"free_claude_code.core.anthropic.streaming.AnthropicStreamLedger",
"free_claude_code.providers.openai_chat.tool_calls",
"Task tool call arguments",
"run_in_background=false",
"invalid JSON flushed as safe object",
"arguments preserved with normal alias restoration",
"ordinary tool validation and recovery",
("tests/providers/test_subagent_interception.py",),
),
CapabilityContract(
Expand Down
9 changes: 6 additions & 3 deletions smoke/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,12 @@ def has_pytest_coverage(self) -> bool:
"configured providers must not reject adaptive thinking payloads",
),
FeatureCoverage(
"heuristic_tool_parser",
"native_tool_assembly",
"Tool use and tool result continuation survive provider/client paths",
("tests/providers/test_parsers.py", "tests/contracts/test_stream_contracts.py"),
(
"tests/providers/test_native_tool_arguments.py",
"tests/contracts/test_stream_contracts.py",
),
("test_live_tool_use_when_configured_model_supports_tools",),
(
"test_provider_interleaved_thinking_tool_e2e",
Expand Down Expand Up @@ -405,7 +408,7 @@ def has_pytest_coverage(self) -> bool:
),
FeatureCoverage(
"subagent_control",
"Task-like tool output is rendered and controlled as foreground work",
"Task arguments are preserved and tool output is rendered in transcripts",
("tests/providers/test_subagent_interception.py",),
(),
("test_messaging_subagent_control_e2e",),
Expand Down
21 changes: 21 additions & 0 deletions src/free_claude_code/application/code_sessions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,27 @@ class CodeItemPage:
next_before: tuple[int, int] | None = None


@dataclass(frozen=True, slots=True)
class CodeHistory:
session: CodeSession
run: CodeRun | None
items: tuple[CodeItem, ...]
prompts: tuple[CodePrompt, ...]
runs: tuple[CodeRun, ...]
active_prompt_ids: tuple[str, ...]
next_before: tuple[int, int] | None


@dataclass(frozen=True, slots=True)
class CodeExecutionSeed:
session: CodeSession
run: CodeRun | None
sequence: int
items: tuple[CodeItem, ...]
prompts: tuple[CodePrompt, ...]
runs: tuple[CodeRun, ...]


class CodeModel(Record):
id: str
display_name: str
Expand Down
33 changes: 33 additions & 0 deletions src/free_claude_code/application/code_sessions/ports.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from .models import (
CodeCatalog,
CodeDetail,
CodeExecutionSeed,
CodeHistory,
CodeItem,
CodeItemPage,
CodeMode,
Expand Down Expand Up @@ -86,6 +88,37 @@ async def open_history(self, cwd: str, sink: EventSink) -> HarnessConnection: ..


class CodeStore(Protocol):
async def execution_seed(self, session_id: str) -> CodeExecutionSeed: ...

async def read_history(
self,
session_id: str,
before: tuple[int, int] | None,
include_item_ids: Sequence[str],
) -> CodeHistory: ...

async def get_native_item(
self,
session_id: str,
turn_id: str,
item_id: str,
) -> CodeItem | None: ...

async def run_items(self, session_id: str, run_id: str) -> tuple[CodeItem, ...]: ...

async def get_prompt(
self, session_id: str, prompt_id: str
) -> CodePrompt | None: ...

async def has_prompt_request(
self,
session_id: str,
generation: str,
request_id: str | int,
) -> bool: ...

async def has_native_turn(self, session_id: str, turn_id: str) -> bool: ...

async def start(self) -> None: ...

async def close(self) -> None: ...
Expand Down
Loading
Loading