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
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,19 @@ jobs:
- run: npx wrangler types --strict-vars=false --check
- run: npm run typecheck
- run: npm test

image:
# Starts the Playwright driver inside the real container image. Unit tests mock the
# browser, so a driver that cannot run in python:3.12-slim (v0.5.0: playwright 1.65.0
# needed libatomic.so.1) only shows up here.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref || github.sha }}
persist-credentials: false
- run: docker build --platform linux/amd64 -t ifd-ci .
- run: >-
docker run --rm --platform linux/amd64 ifd-ci uv run --locked --no-dev python -c
'from playwright.sync_api import sync_playwright;
p = sync_playwright().start(); p.stop(); print("playwright driver ok")'
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,22 @@ A running server reports its version at `/health`.

## [Unreleased]

## [0.5.1] - 2026-11-13

### Fixed

- SimBrief generation works again. v0.5.0 shipped playwright 1.65.0, whose driver needs
`libatomic.so.1`, which the `python:3.12-slim` image lacks, so every Generate failed within
seconds before the click. Playwright is pinned to 1.64.1, and production ran v0.4.7 again
until this release.
- `turn_status` reports `cancelled` after `cancel_trip` instead of repeating the withdrawn
`approval_required` result and its approve instruction.

### Changed

- CI builds the container image and starts the Playwright driver inside it, so a dependency
that cannot run in the deployed image fails before release.

## [0.5.0] - 2026-11-13

### Added
Expand Down Expand Up @@ -326,6 +342,7 @@ First public release, and the first version live on the self-hosted Cloudflare d
of blocking it permanently ([#3](https://github.com/IntelIP/InfiniteFlightAgent/pull/3)).

[Unreleased]: https://github.com/IntelIP/InfiniteFlightAgent/compare/v0.4.6...HEAD
[0.5.1]: https://github.com/IntelIP/InfiniteFlightAgent/compare/v0.5.0...v0.5.1
[0.5.0]: https://github.com/IntelIP/InfiniteFlightAgent/compare/v0.4.7...v0.5.0
[0.4.7]: https://github.com/IntelIP/InfiniteFlightAgent/compare/v0.4.6...v0.4.7
[0.4.6]: https://github.com/IntelIP/InfiniteFlightAgent/compare/v0.4.5...v0.4.6
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "infinite-flight-dispatcher"
version = "0.5.0"
version = "0.5.1"
description = "AI dispatcher for Infinite Flight: real flights, SimBrief plans, and takeoff N1 estimates over MCP"
requires-python = ">=3.11"
license = "GPL-3.0-only"
Expand All @@ -15,7 +15,7 @@ dependencies = [
"langgraph-checkpoint-sqlite>=3.1.3",
"pyjwt[crypto]>=2.15.1",
"browserbase>=1.23.0",
"playwright>=1.64.1",
"playwright==1.64.1", # 1.65.0 driver needs libatomic.so.1, absent from python:3.12-slim
"psycopg[binary]>=3.3.6",
"psycopg-pool>=3.2",
"langgraph-checkpoint-postgres>=3.2.0",
Expand Down
14 changes: 13 additions & 1 deletion src/ifdispatcher/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,20 @@ async def turn_status(thread_id: str) -> dict[str, Any]:
"""Result of the latest plan_flight or approve_dispatch call on thread_id after it returned
status running. Returns running again (call later), or the reply / approval request.
"""
finished = thread_id in _turns and _turns[thread_id].done()
record = progress.read(thread_id) if finished or thread_id not in _turns else None
if finished and record and record.get("status") == "cancelled":
# The cached result predates cancel_trip (e.g. a withdrawn approval_required);
# report the cancellation instead of asking for an approval that is refused.
return {
"status": "cancelled",
"thread_id": thread_id,
"turn": progress.turn_view(record, live=False),
"next": "This turn was cancelled; any pending approval was withdrawn and cannot be "
"approved. Call trip_progress for kept legs, or send plan_flight to start again.",
}
if thread_id not in _turns:
turn = progress.turn_view(progress.read(thread_id), live=False)
turn = progress.turn_view(record, live=False)
if turn:
return {
"status": turn["status"],
Expand Down
20 changes: 20 additions & 0 deletions tests/test_trip_progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,26 @@ def test_cancel_pending_approval_keeps_completed_legs_and_refuses_approval(store
assert store.read_meta("T1", "1", 1)["briefing"] == "**SPAWN:** gate"


def test_turn_status_reports_cancel_instead_of_stale_approval(store, monkeypatch):
"""Observed live on v0.5.0: turn_status kept returning the finished approval_required
result after cancel_trip withdrew that approval."""
from concurrent.futures import Future

prepare(store, leg="1")
state = pending_state(store)
monkeypatch.setattr(remote, "_get_agent", lambda: NoModel(state))
done = Future()
done.set_result({"status": "approval_required", "thread_id": "thread-s"})
monkeypatch.setattr(remote, "_turns", {"thread-s": done})

assert asyncio.run(remote.turn_status("thread-s"))["status"] == "approval_required"
assert asyncio.run(remote.cancel_trip("thread-s"))["status"] == "cancelled"
status = asyncio.run(remote.turn_status("thread-s"))
assert status["status"] == "cancelled"
assert "cannot be approved" in status["next"]
assert status["turn"]["status"] == "cancelled"


def test_trip_progress_not_found_and_records_approval_history(store, monkeypatch):
monkeypatch.setattr(remote, "_get_agent", lambda: NoModel(empty_state()))
assert remote.trip_progress("missing")["status"] == "not_found"
Expand Down
58 changes: 46 additions & 12 deletions uv.lock

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

Loading