Skip to content
Draft
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
12 changes: 12 additions & 0 deletions core/workflow_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,18 @@ def applier(*, state: RunState, result: Mapping[str, Any], run: Any | None = Non
client = _client_factory(state.installation_id, github_client_factory)
repo_handle = client.get_repo(state.repo)
progress = workflow.progress_for_state(repo_handle, state=state)
# Record the Oz session link onto the progress comment before
# applying the result. A run that reaches a terminal SUCCEEDED
# state on the first cron poll never passes through
# ``non_terminal_handler``, so without this the completion comment
# posted back to the PR/issue -- including the "completed with no
# work" message -- would omit the link back to the Warp
# conversation that explains what the agent did. ``run_adapter``
# derives its ``session_link`` from the progress comment, so this
# must run before the adapter is built. Mirrors the failure
# handler, which records the link before ``report_error``.
if run is not None:
record_session_link_safely(progress, run)
run_adapter = workflow.run_adapter_for_state(state=state, progress=progress, run=run)
try:
workflow.apply_result(
Expand Down
51 changes: 51 additions & 0 deletions tests/test_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,57 @@ def test_result_applier_invokes_apply_pr_comment_result(self) -> None:
# posting onto the PR conversation.
repo_handle.get_pull.assert_called_once_with(7)

def test_result_applier_records_session_link_for_terminal_run(self) -> None:
# A run that is already terminal on the first cron poll never
# passes through ``non_terminal_handler``. The applier must still
# record the session link so the completion comment posted back
# to the PR (including the "completed with no work" message)
# links to the Warp conversation that explains what the agent did.
from core.handlers import build_respond_handlers

github_client = MagicMock()
github_client.get_repo.return_value = MagicMock(name="repo")
handlers = build_respond_handlers(_factory(github_client))

state = _state(
"respond-to-pr-comment",
payload_subset={
"owner": "acme",
"repo": "widgets",
"pr_number": 7,
"head_branch": "feature",
"trigger_kind": "conversation",
"requester": "alice",
"progress_comment_id": 6543,
},
)
run = MagicMock(
state="SUCCEEDED",
session_link="https://app.warp.dev/run/abc",
run_id="oz-run-123",
)
handlers.result_applier(state=state, result={}, run=run)

helpers = sys.modules["oz.helpers"]
helpers.record_run_session_link.assert_called_once_with( # type: ignore[attr-defined]
self.progress_instances[-1], run
)

def test_result_applier_skips_session_link_when_run_missing(self) -> None:
# Synchronous callers and tests may invoke the applier without a
# run handle; recording must be skipped rather than passing
# ``None`` into ``record_run_session_link``.
from core.handlers import build_respond_handlers

github_client = MagicMock()
github_client.get_repo.return_value = MagicMock(name="repo")
handlers = build_respond_handlers(_factory(github_client))

handlers.result_applier(state=_state("respond-to-pr-comment"), result={})

helpers = sys.modules["oz.helpers"]
helpers.record_run_session_link.assert_not_called() # type: ignore[attr-defined]


class VerifyHandlersTest(_HandlerTestBase):
def test_artifact_loader_calls_load_run_artifact_with_report_filename(self) -> None:
Expand Down
Loading