Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
f2da1b3
docs: define OpenTake Beta 5 release design
Aug 13, 2026
582443c
docs: plan OpenTake Beta 5 implementation
Aug 13, 2026
3576548
feat(mcp): add keychain-backed pairing catalog
Aug 13, 2026
df7820c
fix(mcp): fail closed on catalog durability errors
Aug 13, 2026
539a734
feat(mcp): authenticate managed loopback sessions
Aug 13, 2026
24ec56c
fix(mcp): drain managed requests on shutdown
Aug 13, 2026
0799a83
refactor(mcp): share live project dispatcher with external sessions
Aug 13, 2026
a6aefce
test(mcp): verify production session undo isolation
Aug 13, 2026
b18b70b
feat(mcp): manage persistent external endpoint lifecycle
Aug 13, 2026
1ec4d99
feat(ui): add shared disclosure motion
Aug 13, 2026
344e73e
fix(mcp): harden external client lifecycle
Aug 13, 2026
a7e787d
fix(settings): keep only stable dark window layouts
Aug 13, 2026
be07769
fix(settings): remove legacy theme marker
Aug 13, 2026
a1d5100
fix(settings): animate model removal confirmation
Aug 13, 2026
a6097e7
fix(mcp): close external lifecycle races
Aug 13, 2026
7a61f0a
fix(settings): stabilize model clear lifecycle
Aug 13, 2026
dc92c97
fix(library): place Home navigation in the global rail
Aug 13, 2026
9405237
fix(library): preserve titlebar safe area
Aug 13, 2026
3f6899f
feat(settings): manage external MCP pairings
Aug 13, 2026
67cdc21
fix(home): simplify activity and show useful project previews
Aug 13, 2026
e0478f7
fix(settings): preserve MCP pairing receipts
Aug 13, 2026
1f81089
fix(home): use truthful project preview metadata
Aug 13, 2026
0506e9c
fix(home): require complete preview metadata
Aug 13, 2026
3f1e749
feat(home): save composited project cover frames
Aug 13, 2026
5f4301f
test(mcp): verify persistent external connection boundary
Aug 13, 2026
26c6182
test(mcp): isolate persistent connection harness
Aug 13, 2026
5811e16
fix(home): harden composite project cover saves
Aug 13, 2026
d44787f
refactor(agent): stream authoritative ordered content blocks
Aug 13, 2026
129010d
fix(agent): isolate ordered streams by session and message
Aug 13, 2026
c67d3ba
fix(agent): preserve provider block order
Aug 13, 2026
931edb9
fix(agent): reject incomplete provider streams
Aug 13, 2026
64e3695
fix(agent): enforce ordered event sequences
Aug 13, 2026
04a391a
fix(agent): validate exact event retries
Aug 14, 2026
5c214b8
fix(agent): finalize streamed tool result messages
Aug 14, 2026
5015b02
fix(agent): bound exact event fingerprints
Aug 14, 2026
98f1f3c
feat(agent): render tools inline in continuous replies
Aug 14, 2026
23dfc81
fix(agent): match tool error terminal contract
Aug 14, 2026
1ed8583
fix(home): make cover publication race-safe
Aug 14, 2026
a3a9c34
fix(agent): make conversation resync authoritative
Aug 14, 2026
a0ec431
build(motion): add licensed CodeMirror editor dependencies
Aug 14, 2026
a577270
feat(agent): show composited result after clearing timeline
Aug 14, 2026
2e9dc13
fix(agent): bind resync to exact turn snapshot
Aug 14, 2026
c3b9b65
fix(agent): retry failed history resync
Aug 14, 2026
7420861
fix(agent): harden timeline result capture
Aug 14, 2026
814ecf4
feat(motion): persist confined HTML and CSS documents
Aug 14, 2026
0eebe92
feat(motion): preview real HTML and CSS deterministically
Aug 14, 2026
6a81c7e
feat(motion): add Motion Studio as a primary view
Aug 14, 2026
55e74a2
feat(motion): build HTML and CSS authoring workspace
Aug 14, 2026
5bbc907
feat(motion): publish Studio documents atomically
Aug 14, 2026
354cd71
feat(agent): edit Motion Studio documents with hash-safe tools
Aug 14, 2026
b3210bf
test(motion): verify Beta 5 Studio end to end
Aug 14, 2026
6a63bb3
fix(shell): align macOS traffic lights with titlebar controls
Aug 14, 2026
35de353
chore(release): prepare v1.0.0-beta.5 metadata
Aug 14, 2026
3226456
fix(release): enforce CodeMirror license inventory
Aug 14, 2026
152c24c
fix(settings): allow packaged window resizing
Aug 14, 2026
b79d81d
test(shell): measure packaged titlebar alignment
Aug 14, 2026
d9a12ce
fix(agent): preserve Codex MCP image results
Aug 14, 2026
a6e6ebf
fix(shell): calibrate packaged traffic lights
Aug 14, 2026
beef934
docs(release): record Beta 5 packaged candidate
Aug 14, 2026
f6e6125
Merge remote-tracking branch 'origin/main' into release/v1.0.0-beta.5
Aug 14, 2026
5fefb5f
fix(ci): make motion durability cross-platform
Aug 14, 2026
aae0ae6
fix(motion): sync retained media directories
Aug 14, 2026
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 .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,11 @@ jobs:
- name: pnpm install
run: pnpm -C web install --frozen-lockfile

- name: Validate Web dependency licenses
run: |
python3 -B -m unittest discover -s scripts -p 'test_check_license_inventory.py'
python3 -B scripts/check_license_inventory.py

- name: pnpm build
run: pnpm -C web build

Expand Down
36 changes: 32 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@ jobs:
rf"(?:-{identifier}(?:\.{identifier})*)?$"
)
tag = os.environ["RELEASE_TAG"]
event_name = os.environ["GITHUB_EVENT_NAME"]
if event_name == "workflow_dispatch":
expected_version = "1.0.0-beta.4"
expected_wix_version = "1.0.0.4"
else:
expected_version = "1.0.0-beta.5"
expected_wix_version = "1.0.0.5"
if "+" in tag:
raise SystemExit("SemVer build metadata is unsupported for updater asset URLs")
if SEMVER_RE.fullmatch(tag) is None:
Expand All @@ -170,15 +177,19 @@ jobs:
}
if versions != {version}:
raise SystemExit(f"tag/version mismatch: {tag} != {sorted(versions)}")
if version != expected_version:
raise SystemExit(
f"release identity mismatch for {event_name}: {version} != {expected_version}"
)
wix_version = tauri["bundle"]["windows"]["wix"]["version"]
if wix_version != "1.0.0.4":
if wix_version != expected_wix_version:
raise SystemExit(f"unexpected Windows installer version: {wix_version}")
notes = Path("docs/releases") / f"{version}.md"
if not notes.is_file() or not notes.read_text(encoding="utf-8").strip():
raise SystemExit(f"release notes are missing or empty: {notes}")
prerelease = "-" in version.split("+", 1)[0]
if version == "1.0.0-beta.4" and not prerelease:
raise SystemExit("OpenTake 1.0.0-beta.4 must remain a prerelease")
if version == "1.0.0-beta.5" and not prerelease:
raise SystemExit("OpenTake 1.0.0-beta.5 must remain a prerelease")
if not prerelease:
raise SystemExit("this release workflow publishes prereleases only")

Expand Down Expand Up @@ -215,6 +226,7 @@ jobs:
env:
TARGET_SHA: ${{ needs.validate.outputs.source_sha }}
RELEASE_TOOLING_SHA: ${{ needs.validate.outputs.tooling_sha }}
OPENTAKE_EXPECTED_RELEASE_VERSION: ${{ needs.validate.outputs.version }}
CI: true
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
Expand Down Expand Up @@ -321,7 +333,7 @@ jobs:
> "$tooling_root/test_provision_ffmpeg_sidecars.py"
git cat-file blob "$RELEASE_TOOLING_SHA:.github/workflows/release.yml" \
> "$tooling_root/release.yml"
git cat-file blob "$RELEASE_TOOLING_SHA:docs/releases/1.0.0-beta.4.md" \
git cat-file blob "$RELEASE_TOOLING_SHA:docs/releases/1.0.0-beta.5.md" \
> "$tooling_root/release-notes.md"
test -s "$tooling_root/check_release_workflow.py"
test -s "$tooling_root/test_check_release_workflow.py"
Expand Down Expand Up @@ -356,6 +368,22 @@ jobs:
- name: Install locked Web dependencies
run: pnpm -C web install --frozen-lockfile

- name: Validate Web dependency licenses
run: |
set -euo pipefail
case "$OPENTAKE_EXPECTED_RELEASE_VERSION" in
1.0.0-beta.5)
python3 -B -m unittest discover -s scripts -p 'test_check_license_inventory.py'
python3 -B scripts/check_license_inventory.py
;;
1.0.0-beta.4)
node -e 'const d=require("./web/package.json").dependencies??{}; if(Object.keys(d).some((name)=>name==="codemirror"||name.startsWith("@codemirror/"))) process.exit(1)'
;;
*)
exit 1
;;
esac

- name: Rust formatting
run: cargo fmt --all --check

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Task 1 report — ordered Rust chat block protocol

Date: 2026-08-14 (Asia/Shanghai)

Base: `26c6182`

Branch: `release/v1.0.0-beta.5`

## Result

- `ChatMessage.blocks` is authoritative. Ordered constructors and mutations preserve interleaved text/tool blocks, consolidate only adjacent text deltas, and derive Beta 4 `content` / `toolCalls` fields one-way through `refresh_legacy_fields`.
- Legacy messages without blocks migrate deterministically to text followed by their persisted tool-call order. Native tool-result text/image blocks retain their exact serialization order.
- `LoopEvent` now exposes block-addressed `BlockDelta` and `BlockUpsert` variants and an ID-addressed `Done`. The normal provider loop mints the message ID before streaming and reuses the active ID for completion, cancellation, and provider errors.
- The Tauri normal-provider and Official Codex paths emit `sessionId`, `messageId`, and `blockIndex`. Codex tool updates and final text share one pre-minted message ID. Cancellation, history-save errors, and other terminal failures reuse the active message ID.
- During the Beta 4 development-window transition, Tauri retains the existing `chat_delta` / `chat_tool_call` event names and the legacy `toolCall` field for tool-use upserts; Beta 5 decoders additionally consume the authoritative block address and block payload.

## TDD evidence

RED was observed before implementation. The plan's literal command has two positional Cargo test filters and Cargo rejected the second filter. Running the filters separately produced the intended compile failures: missing ordered constructors/mutations, missing `LoopEvent::BlockDelta` / `BlockUpsert`, and missing `Done.message_id`.

GREEN verification on the final tree:

- `cargo test -p opentake-agent chat:: -- --nocapture` — 47 passed, 0 failed.
- `cargo test -p opentake-tauri chat::tests --lib` — 19 passed, 0 failed.
- `cargo fmt --all -- --check` — passed.
- `cargo clippy -p opentake-agent --all-targets -- -D warnings` — passed.
- `cargo clippy -p opentake-tauri --lib -- -D warnings` — passed.
- `git diff --check` for the four owned chat files — passed.

The combined Tauri all-target clippy gate was also attempted. It is blocked outside Task 1 by the concurrent unowned change at `src-tauri/src/commands.rs:2997` (`clippy::field_reassign_with_default`). This task did not modify or revert that file.

## Scope review

Owned changes are limited to:

- `crates/opentake-agent/src/chat/session.rs`
- `crates/opentake-agent/src/chat/loop.rs`
- `crates/opentake-agent/src/chat/mod.rs`
- `src-tauri/src/chat.rs`
- this report

Concurrent core, project, media, render, commands, home, and audit changes were not staged or reverted.

## Review fix round 1

Commit target: `fix(agent): preserve provider block order`

### Result

- The live Anthropic SSE decoder now treats provider `content_block_start`, `content_block_delta`, and `content_block_stop` indices as authoritative. It emits block upserts at start/stop and indexed text deltas between them, so `text A → tool use → text B` remains in that order through loop events and persistence.
- Anthropic `input_json_delta.partial_json` is accumulated on its addressed tool block. The live HTTP path and the deterministic SSE regression test share the same decoder.
- OpenAI and Anthropic request builders now derive text, tool calls, tool-use IDs, and native tool-result content directly from `ChatMessage.blocks`. In particular, the next Anthropic round serializes interleaved assistant blocks in their original order instead of reconstructing `text + tools` from Beta 4 compatibility fields.
- The deserialize wire uses `Option<Vec<AgentContentBlock>>`: only a missing `blocks` property migrates legacy fields. Explicit `blocks: []` remains empty, clears stale flat fields/tool metadata, and serializes back as a Beta 5 empty array.
- Large inline tests moved to `chat/session/tests.rs` and `chat/loop/tests.rs`; production `session.rs` is 533 lines and `loop.rs` is 610 lines.

### RED evidence

- The explicit-empty tests first failed with a missing serialized array (`null` versus `[]`) and with stale legacy text/tool calls being migrated into non-empty blocks.
- The authoritative Anthropic body test first failed with `text AB → tool` instead of `text A → tool → text B`.
- The interleaved SSE test first failed to compile because the indexed Anthropic decoder and shared loop event application path did not exist; after implementation it exercises split raw SSE chunks through loop events into the next-round request body.
- The explicit-empty tool-message test first failed because stale `toolCallId` survived an authoritative empty block array.

### Fresh verification

- `cargo test -p opentake-agent chat:: -- --nocapture` — 53 passed, 0 failed.
- `cargo test -p opentake-tauri chat::tests --lib -- --nocapture` — 19 passed, 0 failed.
- `cargo clippy -p opentake-agent --all-targets -- -D warnings` — passed.
- `cargo clippy -p opentake-tauri --lib -- -D warnings` — passed (only Cargo's existing future-incompatibility notice for `block v0.1.6`).
- `cargo fmt -p opentake-agent -- --check` — passed.
- Owned-file `git diff --check` — passed.
- `cargo fmt --all -- --check` was also run; its only diff is the concurrent unowned `crates/opentake-render/src/plan/build.rs` formatting change, so Task 1 did not rewrite it.

### Round 1 scope

- `crates/opentake-agent/src/chat/llm.rs`
- `crates/opentake-agent/src/chat/session.rs`
- `crates/opentake-agent/src/chat/session/tests.rs`
- `crates/opentake-agent/src/chat/loop.rs`
- `crates/opentake-agent/src/chat/loop/tests.rs`
- this report

## Review fix round 2

Commit target: `fix(agent): reject incomplete provider streams`

### Result

- Anthropic stream completion is now fail-closed: every opened content block must receive exactly one matching `content_block_stop`, the stream must receive exactly one `message_stop`, and EOF cannot finalize a partial text/tool block. Premature/repeated stops, deltas after block stop, and events after message stop return `LlmError::Stream`.
- A failed/truncated provider turn never reaches the loop's persistence or tool-dispatch phase; those phases remain after successful decoder finalization.
- `llm.rs` is now a 228-line façade. Provider production code lives in `chat/llm/openai.rs` (235 lines) and `chat/llm/anthropic.rs` (490 lines); tests live in `chat/llm/tests.rs` (401 lines). Every file is below 800 lines.
- Every Rust block delta/upsert/done carries a per-message monotonic `sequence: u64`, starting at 0 and increasing in emission order. Provider rounds, tool-result messages, guide/error/cancel terminal messages, save failures, and Official Codex events use the same contract.
- The Tauri payloads expose `sequence` in camelCase JSON and reject duplicate or gapped sequences per message before emitting to the window.

### RED evidence

- Seven Anthropic lifecycle tests initially accepted invalid partial streams: truncated text/tool blocks, missing `message_stop`, premature `message_stop`, repeated block/message stops, and a delta after block stop all returned partial `TurnResult` values.
- Sequence tests initially failed to compile because `LoopEvent`, `LoopError`, and the three Tauri payloads had no sequence field; the strict duplicate/gap gate did not exist.

### Fresh verification

- `cargo test -p opentake-agent chat:: -- --nocapture` — 60 passed, 0 failed.
- `cargo test -p opentake-tauri chat::tests --lib -- --nocapture` — 20 passed, 0 failed.
- `cargo clippy -p opentake-agent --all-targets -- -D warnings` — passed.
- `cargo clippy -p opentake-tauri --lib -- -D warnings` — passed (only Cargo's existing future-incompatibility notice for `block v0.1.6`).
- Direct `rustfmt --check` on the seven owned Rust chat files — passed.
- Owned-file `git diff --check` — passed.

### Round 2 scope

- `crates/opentake-agent/src/chat/llm.rs`
- `crates/opentake-agent/src/chat/llm/openai.rs`
- `crates/opentake-agent/src/chat/llm/anthropic.rs`
- `crates/opentake-agent/src/chat/llm/tests.rs`
- `crates/opentake-agent/src/chat/loop.rs`
- `crates/opentake-agent/src/chat/loop/tests.rs`
- `src-tauri/src/chat.rs`
- this report

## Cross-layer tool-result finalization follow-up

Commit target: `fix(agent): finalize streamed tool result messages`

### Result

- Each normal-loop tool-result message is persisted and then emits its own ordered stream: one `BlockUpsert` per authoritative block beginning at sequence 0, followed immediately by `Done` at the next sequence (sequence 1 for the current single-block tool result) before the next assistant round.
- The tool-result upsert and terminal event share the same session ID and message ID, and the terminal payload contains the exact persisted role=`tool` message.
- Tauri admits a terminal role=`tool` only when `blocks` is non-empty, every block is `ToolResult`, every `toolUseId` matches the message `toolCallId`, block/message error markers match, and the legacy `toolCalls` list is empty. System/user terminal messages, tool-only fields on assistants, and tool-result blocks on assistants remain rejected.

### RED evidence

- The loop regression test first failed to compile because no persistence-and-finalization path existed; the production path only emitted `BlockUpsert { sequence: 0 }` and pushed the tool message.
- The Tauri terminal-contract test first failed to compile because no role-aware terminal gate existed. A second RED run then showed that a mismatched message/block error marker was still accepted before the exact-match check was added.

### Fresh verification

- `cargo test -p opentake-agent 'chat::' -- --nocapture` — 61 passed, 0 failed.
- `cargo test -p opentake-tauri 'chat::tests' --lib -- --nocapture` — 22 passed, 0 failed.
- `cargo clippy -p opentake-agent --all-targets -- -D warnings` — passed.
- `cargo clippy -p opentake-tauri --lib -- -D warnings` — passed (only Cargo's existing future-incompatibility notice for `block v0.1.6`).
- Direct `rustfmt` on the three owned Rust chat files and owned-file `git diff --check` — passed.
- The combined all-target clippy command was attempted and is blocked by the concurrent unowned change at `src-tauri/src/commands.rs:2997` (`clippy::field_reassign_with_default`). This follow-up did not modify that file.

### Follow-up scope

- `crates/opentake-agent/src/chat/loop.rs`
- `crates/opentake-agent/src/chat/loop/tests.rs`
- `src-tauri/src/chat.rs`
- this report
Loading
Loading