Make the agent→human hand-off visible to plugins - #3
Merged
Merged
Conversation
An agent that needs a person to decide something cannot park the task as `blocked` and name that person as the unblock owner — the board answers `403 Agents may only name themselves as an unblock owner`, deliberately. What it does instead is open an issue-thread interaction and stop. That logs `issue.thread_interaction_created`, an action in neither PLUGIN_EVENT_TYPES nor ACTIVITY_ACTION_TO_PLUGIN_EVENT, so eventTypeForActivityAction returned null and no plugin ever saw it. The terminal state of a maintenance loop produced no notification anywhere. Adds `issue.interaction.created` and `issue.interaction.resolved`. Which actions collapse into `resolved`, and why: accepted, rejected, answered, withdrawn, cancelled, expired and item_verdicts_submitted all end the same lifecycle, and a subscriber that showed "waiting for you" only needs to know the wait is over — not which of the seven exits it took. That is the shape approval.decided already has for its three decision actions. The exit stays readable in `payload.interactionStatus`. item_verdicts_submitted is in that set even though a partial submission leaves the interaction pending. The event name cannot depend on the payload, and a second event name for the same lifecycle would be worse than one event whose status field is authoritative — which is asserted. The payload a subscriber gets is entityId (the issue), interactionId, interactionKind, interactionStatus and addresseeAgentId. None of those key names match the redactor's secret pattern and none of the values are JWT-shaped, so all of them survive redactActivityDetails; there is a test pinning that, because it is the redactor's decision and not ours. The Telegram notifier subscribes and sends its existing "waiting for you" message on the new path. Its old path — the `issue.updated` transition into `blocked` — is unchanged and still catches the parkings an agent does not cause. Since one hand-off can produce both signals, both paths now claim a single per-issue slot in plugin state before sending, and only the signal that claimed it releases it: the board path when the task leaves `blocked`, the interaction path on `issue.interaction.resolved`. A partial verdict submission holds the slot, because nobody is off the hook yet. Messages stay identifiers and links: the interaction *kind* is named, the question is not. Interactions addressed to an agent are skipped — the agent loop answers those and no phone is involved. Additive throughout. No existing event changed, no activity action added or renamed, and the 403 policy is untouched. server 12 new tests in activity-log-plugin-events; telegram-notify 36 to 48.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes agent-company-kit#13.
When an agent hands a task to a human it opens a
request_confirmationissue-thread interaction — it cannot park the task asblockednaming a person as unblock owner, that is a deliberate403. Creating the interaction logged activity asissue.thread_interaction_created, which was in neitherPLUGIN_EVENT_TYPESnor the activity-to-event map, soeventTypeForActivityActionreturnednulland no plugin ever saw it. The terminal state of a maintenance loop produced no notification on any surface.The change
issue.interaction.createdandissue.interaction.resolvedjoinPLUGIN_EVENT_TYPES, mapped from the eight thread-interaction activity actions. All seven ways an interaction ends collapse to oneresolvedevent — the same shape the three approval decisions already collapse toapproval.decided. A subscriber that showed "waiting for you" needs to know the wait is over, not which exit it took; the exit stays inpayload.interactionStatus.item_verdicts_submittedis in the resolved set even though a partial submission leaves the interaction pending. The event name cannot depend on the payload,interactionStatusis authoritative, and a second name for the same lifecycle would be worse. The notifier holds its dedup slot while the status is stillpending.The payload was checked, not assumed.
entityIdis the issue id; details carryinteractionId,interactionKind,interactionStatus,addresseeAgentIdand both resolver policies. No key matches the redactor's secret pattern and no value is JWT-shaped, so everything survivesredactActivityDetails— pinned by a test, because an event whose payload is redacted to nothing is worse than no event.telegram-notifysubscribes to both. Its existingblocked-transition path stays; the two share a claim/release slot so one hand-off produces exactly one message in either arrival order.Verified
@paperclipai/shared/@paperclipai/server/ plugin typecheckserver/src/__tests__/activity-log-plugin-events.test.tspaperclip-plugin-telegram-notifypackages/shared/packages/plugins/sdkScope
Additive only: nothing removed, nothing renamed, the
403policy untouched, no existing event changed. 520 insertions, 6 deletions. Worth offering upstream — this is a gap in the host's event surface, not a fork concern.