Skip to content

fix(btw): fold the result delivery outcome into the work status - #172

Merged
YUZHEthefool merged 1 commit into
fix/btw-submit-handoff-compensation-155from
fix/btw-work-delivery-status-156
Sep 14, 2026
Merged

fix(btw): fold the result delivery outcome into the work status#172
YUZHEthefool merged 1 commit into
fix/btw-submit-handoff-compensation-155from
fix/btw-work-delivery-status-156

Conversation

@YUZHEthefool

@YUZHEthefool YUZHEthefool commented Sep 11, 2026

Copy link
Copy Markdown
Member

Summary

A work run whose final result the platform explicitly rejected still reported
completed, and so did a send whose outcome is unknown. Expected the platform's
answer about the result to be reflected in the work status, with an explicit
rejection and an unknown outcome kept apart.

Related issue

Fixes #156

Root cause

RespondStage converts a rejected send into a DeliveryReceipt and returns
normally, so the detached runner saw no failure. An unknown outcome — a raised
send or a platform that returned no acceptance receipt — is worse: it does not
prove the user received anything, yet it also read as completed.

Reproduction

Wire the real PipelineScheduler, RespondStage, and WorkLoop with a platform
stub that accepts the acknowledgement and answers the final result with
PlatformSendResult(success=False), then read the event's delivery_receipt and
the WorkSession: receipt failed, session completed. A second stub that
raises on send gives receipt unknown and session completed.

Implementation notes

Read the receipt after the detached run has delivered and adjust a completed
session:

  • an explicit rejection becomes failed with a generic message, so the
    platform's own error text never surfaces;
  • an unknown outcome becomes the new WorkSessionStatus.UNCONFIRMED, which is
    distinct from both completed and failed;
  • accepted, partial, and skipped keep the execution status (skipped means
    there was nothing to deliver).

update_status_if applies the change only from COMPLETED, so a failed or
cancelled run keeps the stronger answer it already has. The new state is
user-visible, so it lands with both locale bundles and the status list in the
docs/zh and docs/en command pages.

Validation

.venv/Scripts/python.exe -m pytest tests/unit -q
  5224 passed, 6 skipped

.venv/Scripts/python.exe -m ruff format --check <touched files>   # clean
.venv/Scripts/python.exe -m ruff check <touched files>            # clean
.venv/Scripts/python.exe -m pyright --project pyrightconfig.quality.json <touched source files>
  0 errors
node node_modules/prettier/bin/prettier.cjs --check docs/{zh,en}/use/command.md   # clean
node node_modules/markdownlint-cli2/markdownlint-cli2.mjs docs/{zh,en}/use/command.md   # exit 0

Both new tests were run against the unmodified tree first, where they fail with
WorkSessionStatus.COMPLETED, so they are real regression tests.

pnpm run docs:build was not run locally (the docs dependencies are not
installed in this checkout), and neither were make check, make quality, or the
coverage-gated --test-profile all run; this PR relies on CI for those, as #168
did.

After #168 was rebased onto the updated master head, this branch was rebased onto
the new head as well and the whole stack was re-validated at its tip:
pytest tests/unit -q 5233 passed / 6 skipped and
pytest --test-profile blocking -q 5354 passed / 6 skipped / 1 deselected.
The count above was taken on this branch before that rebase.

Compatibility and risk

  • New user-visible status unconfirmed (zh: 投递未确认) reachable through
    /work status, with a matching key added to both built-in command locale
    bundles.
  • Behavior change: a work run whose final result the platform explicitly rejected
    is now failed, and one with an unknown outcome is unconfirmed, instead of
    both reading completed.
  • Every existing test that uses a stub send stage stores no receipt, so it keeps
    passing unchanged. No route, schema, configuration, or dependency change.

Checklist

  • The change is focused and does not include unrelated refactoring.
  • I added or updated a regression test, or explained why a test is not practical.
  • I ran the relevant formatting, lint, build, and test commands.
  • User-visible behavior updates both docs/zh/ and docs/en/ when needed.
  • OpenAPI, generated client, docs/public/openapi.json, and tests change together when routes or schemas change. (No route or schema change.)
  • No secrets committed. Runtime Python deps update pyproject.toml, requirements.txt, and uv.lock together. (No dependency change.)
  • I did not restore legacy shims, Python <3.14 fallbacks, or upstream publish/docs URLs as fork artifacts.
  • Breaking API or behavior changes use ! and a BREAKING CHANGE: footer. (None; the status change is the fix.)
  • I will not merge this PR myself. Merge needs a human maintainer review plus a separate AI-assisted review (AI_POLICY.md).
  • AI use follows AI_POLICY.md. Keep exactly one author note below. Do not fabricate the other.

Agent note

Goal: resolve #156 by folding the platform's answer about the
result into the work status, keeping BTW off by default and preserving
authorization and request identity.

Paths touched: astrbot/core/agent/btw/types.py, astrbot/core/agent/btw/i18n.py,
astrbot/core/agent/btw/work_loop.py, astrbot/core/agent/btw/work_sessions.py,
astrbot/builtin_stars/builtin_commands/.astrbot-plugin/i18n/{zh-CN,en-US}.json,
docs/{zh,en}/use/command.md, and tests/unit/test_btw_delivery.py.

Checks run: the commands under Validation, plus the new tests against the
unmodified tree.

Residual risk: the receipt is read after the detached dispatch loop, so a run
whose last delivery happens after that point would not be reflected. The
dispatch loop drives each delivery, so the final receipt is stored before the
loop ends; the tests use the real RespondStage to confirm this end to end.

Tools used: Claude Code (Opus 5) with the repository's AGENTS.md and AI_POLICY.md.

This PR is stacked on #171; merge that first.

The respond stage reports a refused or unconfirmed send as a delivery receipt
and returns normally, so a work run whose final result the platform rejected
still ended as completed.  An unknown outcome -- a raised send or a platform
that returned no acceptance receipt -- was worse: it does not prove the user
received anything, yet it also read as completed.

Read the receipt after the detached run delivers and adjust a completed
session: an explicit rejection is a failure, and an unknown outcome gets its
own status so the two stay distinguishable.  A failed or cancelled run keeps
the stronger answer it already has, which update_status_if enforces.

The new state is user-visible, so it lands with both locale bundles and the
status list in the zh and en command docs.

Fixes #156
AI-Generated: true
Generated-At: 2026-09-11T14:16:28Z
@YUZHEthefool
YUZHEthefool added this pull request to stack #174 September 11, 2026 15:57
@YUZHEthefool
YUZHEthefool force-pushed the fix/btw-work-delivery-status-156 branch from 8ecbe04 to 8e3adc1 Compare September 11, 2026 16:00
@YUZHEthefool
YUZHEthefool merged commit 393fd93 into master Sep 14, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] BTW 平台明确拒绝工作结果后仍记录任务已完成

1 participant