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
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"description": "Electron + React desktop MVP for Coding Agent",
"main": "dist-electron/main/main.js",
"scripts": {
"dev": "pnpm run build:electron && concurrently -k -n renderer,electron-ts,electron \"vite --host 127.0.0.1\" \"tsc -p tsconfig.electron.json --watch --preserveWatchOutput\" \"wait-on tcp:127.0.0.1:5173 && cross-env VITE_DEV_SERVER_URL=http://127.0.0.1:5173 electron .\"",
"dev": "pnpm run build:electron && concurrently -k -n renderer,electron-ts,electron \"vite --host 127.0.0.1\" \"tsc -p tsconfig.electron.json --watch --preserveWatchOutput\" \"wait-on tcp:127.0.0.1:5173 && cross-env ELECTRON_GET_USE_PROXY=true VITE_DEV_SERVER_URL=http://127.0.0.1:5173 electron .\"",
"build:electron": "tsc -p tsconfig.electron.json",
"build:renderer": "vite build",
"build": "pnpm run build:electron && pnpm run build:renderer",
Expand Down
27 changes: 9 additions & 18 deletions apps/desktop/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions apps/desktop/pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
onlyBuiltDependencies:
- electron

overrides:
yauzl: ^3.3.1
10 changes: 7 additions & 3 deletions packages/app/src/coding_agent/core/agent_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ def new_session(self) -> SessionManager:
leak into the old JSONL file.
"""
previous = self.session_manager
if not previous.in_memory:
if not previous.in_memory and previous.entries:
previous.flush()

new_manager = SessionManager.create(
Expand Down Expand Up @@ -552,8 +552,12 @@ def get_stats(self) -> SessionStats:
# ── Lifecycle ─────────────────────────────────────────────────────────

def dispose(self) -> None:
"""Clean up resources. Flushes the session to disk if needed."""
if self.session_manager is not None and not self.session_manager.in_memory:
"""Clean up resources, persisting only sessions that contain entries."""
if (
self.session_manager is not None
and not self.session_manager.in_memory
and self.session_manager.entries
):
self.session_manager.flush()

def _restore_persisted_context(self) -> None:
Expand Down
41 changes: 41 additions & 0 deletions packages/app/tests/test_desktop_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pathlib import Path

import pytest
from agent_core import SessionManager
from agent_llm import AssistantMessage, Model, ModelCost, TextContent, ToolCall, UserMessage

from coding_agent.core.agent_session import AgentSession, AgentSessionConfig
Expand Down Expand Up @@ -91,3 +92,43 @@ def test_desktop_command_catalog_only_exposes_supported_commands() -> None:
assert [command["name"] for command in commands] == [
"help", "clear", "model", "compact", "session", "new",
]


def test_opening_saved_session_does_not_persist_abandoned_empty_session(
tmp_path: Path,
) -> None:
import asyncio
import coding_agent.core.config as config

workspace = Path(tmp_path.anchor)

saved = SessionManager.create(
cwd=str(workspace),
sessions_dir=config.get_sessions_dir(),
)
saved.append_message(UserMessage(content="existing question"))
saved.append_message(AssistantMessage(content=[TextContent(text="existing answer")]))

async def exercise() -> None:
runtime = DesktopRuntime(lambda _event: None)
try:
opened = await runtime.dispatch(
"workspace.open",
{"path": str(workspace), "resume": True},
)
assert opened["sessionId"] == saved.header.id

created = await runtime.dispatch("session.new", {})
assert created["sessionId"] != saved.header.id
assert [item["id"] for item in await runtime.dispatch("session.list", {})] == [
saved.header.id,
]

await runtime.dispatch("session.open", {"sessionId": saved.header.id})
assert [item["id"] for item in await runtime.dispatch("session.list", {})] == [
saved.header.id,
]
finally:
await runtime.dispose()

asyncio.run(exercise())
10 changes: 10 additions & 0 deletions packages/app/tests/test_release_p0.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ def test_new_session_switches_manager_and_storage(tmp_path: Path):
assert old.path.exists()


def test_new_session_does_not_persist_empty_previous_manager(tmp_path: Path):
old = SessionManager.create(cwd=str(tmp_path), agent_dir=tmp_path)
session = AgentSession(AgentSessionConfig(model=_model(), tools=[], session_manager=old))

session.new_session()

assert old.path is not None
assert not old.path.exists()


def test_model_shorthand_sets_provider_and_thinking():
args = Args(model="zhipu/glm-5.2:high")
_normalize_model_options(args)
Expand Down