Commit Graph

3 Commits

Author SHA1 Message Date
Victor Kyriazakos 5210dd48b8 fix(gateway+relay): approval prompts survive ambiguity without duplicates; streamed finals keep block formatting
Three live findings from rc.4 staging, all on the relay-fronted Slack
path, all with the failure observed in live logs before the fix:

1. Approval-send timeout is AMBIGUOUS, not failed (no re-ask).
   send_exec_approval through the connector can time out with the card
   already rendered — the connector may ack after the deadline (slow
   platform API call, transient backpressure, event-loop stall) — and
   the timeout-as-failure path re-sent and produced duplicate cards.
   The outcome is now tri-state: sent / failed / ambiguous. Ambiguous =
   no re-send, no text fallback; the prompt registration stays armed so
   a late tap still resolves. Only a definite send error falls back to
   text.

2. pending_approval tool results forbid re-issuing the command.
   With one card correctly armed, the agent could still mint a SECOND
   card by re-running a rephrased variant of the gated command after
   reading the pending_approval tool result (observed live: same
   command re-issued in a different form, two cards). The tool message
   now instructs: do not re-run/rephrase; wait or report pending.
   Applied to both the terminal and execute_code arms.

3. Draft interim AND seal frames carry format_hints.
   format_hints are stamped on send, edit, and send_for_platform, but
   both draft-frame builders (send_draft interim + _seal_open_draft
   seal) shipped bare metadata. A streamed final therefore arrived at
   the connector hintless and sealed as a plain code block while
   non-streamed sends rendered native markdown blocks (observed live:
   language-tagged block on send/edit, downgrade on streamed seal).
   Both sites now stamp _with_format_hints_for_chat
   (destination-resolved, same pattern as the existing lanes).
   Verified live after the fix against the platform's stored message
   payload: rich_text_preformatted with language field on a streamed
   seal.

Tests: tri-state outcome unit tests (5), draft/seal hint stamping + knobs-
off regression control (2, RED-first), existing format-hints suite intact
(14/14). Mutation-verified: reverting the adapter hunk sends
test_draft_interim_and_seal_frames_carry_hints red; restore -> green.

Boundary sweep (text egress lanes crossing the frame contract): send ✓
(pre-existing) edit ✓ (pre-existing) send_for_platform ✓ (pre-existing)
draft-interim ✓ (this PR) draft-seal ✓ (this PR); task_card lane carries
no text content — exempt.
2026-08-20 19:56:01 -07:00
Ben Barclay 79c39025c0 fix(relay): format hints resolve the DESTINATION platform, and stamp on send_for_platform
Two gaps in the block-formatting hint stamping:

1. Wrong descriptor: _format_hints gated on self.descriptor — the PRIMARY
   identity's scalar — while one RelayAdapter fronts N platforms. A
   Slack-primary adapter stamped Slack hints onto known Discord chats; a
   Discord-primary adapter suppressed hints for Slack chats whose own
   negotiated descriptor advertised the bit. Resolve per destination:
   send/edit use _descriptor_for_chat (the same seam max_message_length
   already uses) plus the chat's logical platform for the config
   sub-block; the knob lookup is now per-logical-platform
   (platforms.relay.extra.<platform>.*) instead of hardwired to slack.

2. Missing lane: send_for_platform — the scheduled/persisted-home lane
   (gateway/delivery.py), i.e. the CRON delivery path, the flagship
   consumer of the in_channel brief — never stamped hints at all. Stamp
   there too, resolving descriptor_for_platform(logical) off the
   transport; the scalar descriptor is used only when it belongs to that
   exact platform (fail closed).

Tests: Slack-primary/Discord-chat no-leak, Discord-primary/Slack-chat
still-stamps, send_for_platform stamps for capable platform and stays
clean for incapable — all against a two-platform negotiated-descriptor
transport. Existing single-platform suite unchanged and green.
2026-08-20 20:10:20 +10:00
Victor Kyriazakos 31a4b8503d feat(relay): block-formatting hints on relay text egress (rich/markdown blocks)
Field report (enterprise side-by-side, 2026-08-18, finding 2): identical
agent output renders native rich_text lists, Block Kit tables, and
highlighted code on native Slack, but literal '-' bullets and code-fence
tables on the relay lane. Native reads platforms.slack.extra.rich_blocks /
markdown_blocks and renders Block Kit locally; relay frames carried no
formatting signal, so the connector had no way to know the operator wants
block rendering.

Contract (additive, v1): the connector advertises supports_block_formatting
in its capability descriptor. When it does AND the operator enables
platforms.relay.extra.slack.rich_blocks / markdown_blocks (same per-platform
sub-block and same _coerce_flag semantics as the other relay Slack knobs),
the gateway stamps format_hints into outbound metadata on BOTH text egress
lanes — send and edit (a streamed reply's final edit carries the finished
markdown, so it must signal too or streams seal as plain text). The
connector renders blocks and keeps plain text as the fallback.

Old connector: never advertises -> no dead metadata ever sent. Old gateway:
never stamps -> connector renders plain text as today. Knobs default OFF,
matching native's opt-in posture.

8 new tests: descriptor default/from_json, hint stamping (capable+enabled),
capability-absent suppression, knobs-off suppression, YAML-quoted-false
coercion, partial knobs, edit-lane parity.
2026-08-19 13:47:18 +00:00