@agent-native/core 0.222.0-nightly-20261029032851 β†’ 0.222.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9382,6 +9382,8 @@ export async function chainServerDrivenContinuation(opts) {
9382
9382
  // Approval selectors apply only to the request that consumes their one-shot grants.
9383
9383
  delete continuationBody.approvedToolCalls;
9384
9384
  delete continuationBody.approvedToolCallAskIds;
9385
+ // The initial admission consumed this one-time queued-message claim.
9386
+ delete continuationBody.queuedMessageClaimId;
9385
9387
  delete continuationBody[AGENT_CHAT_BACKGROUND_RUN_FIELD];
9386
9388
  const markRunsAbortedOrThrow = async (...runIds) => {
9387
9389
  const results = await Promise.allSettled(runIds.map(async (targetRunId) => {
@@ -11536,12 +11538,15 @@ export function createProductionAgentHandler(options) {
11536
11538
  let durableDispatchPayload;
11537
11539
  if (dispatchToBackground) {
11538
11540
  try {
11539
- durableDispatchPayload = serializeDurableDispatchPayload({
11541
+ const dispatchBody = {
11540
11542
  ...body,
11541
11543
  ...(Array.isArray(attachments) || requestAttachments.length > 0
11542
11544
  ? { attachments: requestAttachments }
11543
11545
  : {}),
11544
- });
11546
+ };
11547
+ // This one-time claim authorizes only the request that consumes it.
11548
+ delete dispatchBody.queuedMessageClaimId;
11549
+ durableDispatchPayload = serializeDurableDispatchPayload(dispatchBody);
11545
11550
  }
11546
11551
  catch (error) {
11547
11552
  if (!(error instanceof DurableAttachmentReferenceRequiredError)) {
@@ -21,6 +21,10 @@ not judged by ledgers, receipts, or checker output, so don't produce them.
21
21
 
22
22
  ## The contract
23
23
 
24
+ The detailed Slack reaction and reply rules are in
25
+ [slack-reaction-replies.md](references/slack-reaction-replies.md); read it before
26
+ marking, replying to, or closing any Slack report.
27
+
24
28
  Slack reactions on a report's root message mean exactly this:
25
29
 
26
30
  | Mark | Meaning | When to add |
@@ -43,7 +47,8 @@ might need to take back. Every πŸ‘€ you add ends the run with a reply, plus
43
47
  either βœ… or 🎫, or a fix PR that is still open and lists the report (its
44
48
  merge adds the βœ…). A πŸ‘€ with no follow-up is the most common complaint about this workflow.
45
49
 
46
- Every claimed report gets **one** reply in its thread per outcome. People
50
+ Every claimed report gets **one** reply in its thread per outcome. The one exception is
51
+ the merged `βœ…` close-out after an in-review reply, which gets no second reply. People
47
52
  have pushed back on replies that were long, technical, condescending, vague
48
53
  about who does what next, or that handed them work. Each reply must be:
49
54
 
@@ -0,0 +1,287 @@
1
+ # Slack Reaction Replies
2
+
3
+ Use the connected Slack integration for every Slack read and write. Never use a browser, screenshots, or UI automation to operate Slack.
4
+
5
+ At sweep exit, reconcile every report root in the scoped inventory that has
6
+ this task's `πŸ‘€`, `βœ…`, or `🎫`, including marks left by earlier runs. Capture
7
+ the exact Slack actor ID used by the connected tool at sweep start. A mark is
8
+ owned by this task only when its reaction owner ID matches that actor ID and the
9
+ root is the workflow's by the parent skill's evidence: a bot-disclosure reply from
10
+ that account, or a root listed under "Claimed, not yet answered" in memory.md. A
11
+ matching reaction without that evidence is a manual mark: leave it alone. Keep
12
+ the disposition, evidence, scopes, and human actions in the internal inventory.
13
+ Post a reporter reply when this run adds a root reaction, when a newer human reporter
14
+ message needs a direct answer, or when this workflow owns a root from an earlier run
15
+ that has no verified reply in its thread yet, unless a person is already handling the root
16
+ or has asked the workflow to stop. The reply is one short, plain-language
17
+ sentence (about 25 words at most), plus the exact bot disclosure, which does not count toward
18
+ that limit. If the root asks a question, that sentence answers
19
+ it first and also explains only the reaction(s) added in this run. An answer to a newer human message
20
+ answers only that message. Skip the reply when the only reaction added is the merged
21
+ `βœ…` close-out after an earlier "in review" reply; that close-out gets no second
22
+ reply. Do not send progress, merge, or deployment updates. Never explain absent or
23
+ unchanged reactions or whether a tracker ticket exists. If reporter input is
24
+ needed, add `🎫` and ask one direct question in that reply. Read back each new
25
+ reaction and reply.
26
+
27
+ Keep prior replies as history. Do not edit or add a status reply merely to
28
+ describe changed evidence, an existing reaction, or completed follow-up work.
29
+ When a new reaction is added, use one short sentence plus the exact bot
30
+ disclosure.
31
+
32
+ When editing an ordinary Slack reply, pass its visible body as
33
+ `message.markdown_text` to `slack_edit_message`; `message.text` is a fallback
34
+ for structured block messages and may leave the visible reply unchanged. Treat
35
+ the returned message ID as acknowledgment only. Read the exact reply back with
36
+ `slack_read_thread` and compare the stored body to the intended status before
37
+ counting it as posted. Use `slack_send_message` only when a new reply is
38
+ needed, with its `message` field and the root `thread_ts`.
39
+
40
+ `slack_read_thread` may append a synthetic `Status marks:` summary after the
41
+ thread. That is connector metadata, not part of any Slack message; do not copy
42
+ it into a reply. Likewise, omit connector-added `Sent using ChatGPT` footer
43
+ text from the intended body. `slack_send_message` may append that footer to a
44
+ new reply; if it does, remove it by editing with `message.markdown_text`, then
45
+ read the reply back again. Include the exact bot disclosure in the visible body
46
+ yourself; the connector footer is not a substitute.
47
+
48
+ Apply reactions to the report root, not the status reply: call
49
+ `slack_add_reaction` with the root's `channel_id`, its timestamp as
50
+ `message_ts`, and the emoji name without colons (`eyes`, `white_check_mark`, or
51
+ `ticket`). A colon-coded emoji mentioned in reply text does not add a reaction.
52
+ After each reaction call, read the root with `slack_get_reactions` and verify
53
+ the intended emoji and this actor's ID are present before recording it or
54
+ moving to another report. A successful tool response is only an acknowledgment;
55
+ the readback is the proof. If a reaction cannot be reconciled, record the exact
56
+ blocker instead of treating the reply text as a substitute.
57
+
58
+ Channel reads may expose a thread reply as a separate row. Resolve its
59
+ `thread_ts` or permalink and inventory it as a reply to the root, not as a new
60
+ report. Put new reactions for an ordinary report on its root; do not add another
61
+ `πŸ‘€` to a reply just because it appears as a channel row. Preserve any legacy
62
+ reaction already on a reply, read it back, and reconcile the actual root
63
+ separately. A reply that raises a genuinely distinct issue stays in the source
64
+ manifest as a reply row, but scope, disposition, handoff, and checkoff evidence
65
+ belong to the actual root row. Record the distinct issue as a separate scope on
66
+ that root and verify it independently. Do not invent a second root or put a
67
+ root checkoff on the reply. Record both the source message timestamp and root
68
+ thread timestamp; if the connector hides `thread_ts`, resolve the permalink
69
+ before recording it.
70
+
71
+ The parent inventory must retain the reaction-owner IDs and the same-parent
72
+ reply timestamp, author ID, disposition, evidence, and read-back text for every
73
+ root this actor marks with `πŸ‘€`, `βœ…`, or `🎫`. Record an explicit decision for
74
+ both `βœ…` and `🎫` on every such root: `present` with its scope, or `none` with
75
+ the reason no mark applies. Record verified fixed scopes with their proof and
76
+ each distinct human handoff with its scope, owner, and action. Before the recap, reconcile every owned root in the inventory by hand. Incomplete
77
+ source coverage or owned-root reconciliation means the sweep is not done. For
78
+ explicitly narrowed scope, keep the active user request as a separate record in
79
+ the inventory. This step does not authorize
80
+ changing another actor's reactions or replies.
81
+
82
+ ## Prior questions and reporter replies
83
+
84
+ At sweep start, search the exact bot disclosure in each scoped channel and
85
+ exhaust the pages to find prior questions and their context:
86
+
87
+ ```text
88
+ slack_search: "this was sent from a bot." in:<#CHANNEL>
89
+ sort=timestamp sort_dir=asc include_context=true max_context_length=300
90
+ ```
91
+
92
+ Also scan channel messages from the saved reply cursor; on the first run, scan
93
+ available history. Since `after:` is date-only, start one day before the cursor,
94
+ exhaust pages, resolve hits to their root, and advance the cursor only through
95
+ processed messages. Read inherited threads carrying any of this actor's root marks (`πŸ‘€`, `βœ…`,
96
+ or `🎫`): a checkmark closes only its verified scope, not a newer reporter
97
+ answer or another open scope. Do not reply or react on another actor's root
98
+ when work is handed off for investigation; that handoff permits local
99
+ investigation only. A separate explicit authorization is required to post a
100
+ reporter-facing reply or change that actor's reactions.
101
+
102
+ Count an answer only when a human posts after the question, without this
103
+ workflow's disclosure, and the reply addresses it. Read the full thread to
104
+ exclude partial, unrelated, or deferred answers; a newer human post reopens the
105
+ question. Attempt the fix with new evidence before asking again. Do not send a
106
+ progress reply when no reaction is added. Do not ask again while the question
107
+ is less than four days old. At four days, record **Abandoned - no answer in 4
108
+ days** internally without re-asking, then carry any remaining defect as
109
+ internal work. For legacy replies without disclosure or eyes, search once per
110
+ connected actor and verify the author and thread.
111
+
112
+ End every new or edited Slack reply with the exact sentence
113
+ `this was sent from a bot.`. A connector-added `Sent using ChatGPT` footer is
114
+ not a substitute.
115
+
116
+ Reactions are permanent history and do not replace the internal disposition.
117
+ At the root, `πŸ‘€` records this workflow's claim, `βœ…` marks one or
118
+ more named scopes that passed direct post-change proof, and `🎫` tracks distinct
119
+ human/team actions, including actions with no named owner yet. The eye alone
120
+ does not mean work remains open. One root `βœ…` covers every verified scope
121
+ listed in `fixedScopes[]`; one root `🎫` covers every action listed in
122
+ `handoffs[]`, including unassigned, accepted, and completed actions. Completion
123
+ evidence changes its status, not the historical `🎫` reaction. Unclaimed items
124
+ and another actor's marks are not ours to reply to. Reconcile each report as
125
+ one unit: update and read back applicable root reactions before advancing to
126
+ the next report. Add `βœ…` once a reported scope passes the direct post-change proof bar on `main`.
127
+ An open PR never gets `βœ…`. Use
128
+ **Partially fixed** for the internal disposition when another scope remains.
129
+ Do not wait for an entire multi-symptom report to be resolved before checking
130
+ off its verified scope. Keep all existing reactions after scopes close. Read
131
+ back every reaction and any reply sent. Never remove or clean up any reaction.
132
+ While a fix PR is open, `πŸ‘€` may remain; `βœ…` still waits for the merge to `main`.
133
+ The marks are independent. The current disposition says what remains.
134
+ For UI behavior, a focused
135
+ regression qualifies only if it exercises the changed component and asserts the
136
+ reported visible result or persisted state; callback-only assertions, mocked
137
+ children, class-name checks, and source inspection do not. For clipping,
138
+ wrapping, contrast, or alignment, inspect rendered geometry at the reported
139
+ viewport or replay the flow in the running app. Use an app/preview replay when
140
+ the regression does not meet that bar. For non-UI scopes, use the area-specific
141
+ action, query, or test proof. Record merge or rollout state in the internal
142
+ inventory; the checkmark records a verified scope and does not claim it is
143
+ shipped or live.
144
+ Before leaving each report, compare verified scopes and human/team handoffs
145
+ with a fresh root-reaction read. Ensure one `βœ…` is present when any scope is fixed on `main`, and one `🎫` whenever any distinct unfinished action requires another
146
+ person or team to act, even before ownership is assigned or accepted. Keep the
147
+ mark as history after completion; read back each changed root reaction and
148
+ verify its owner. Record whether each mark applies and any historical-reaction
149
+ details in the internal inventory only; never explain missing reactions in a
150
+ reporter reply. Emoji text in a reply is not a reaction. Preserve every
151
+ existing reaction. If later proof invalidates a prior checkmark, move that
152
+ scope out of active `fixedScopes[]` and add a `historicalReactionMarks[]` entry
153
+ with `emoji`, `scope`, `reason`, and the exact prior `previousStatus`. If the
154
+ owner withdraws an accepted action tracked by `🎫`, preserve the exact earlier
155
+ accepted `handoff` and owner `withdrawalReply`, then update the internal record.
156
+ Keep these records consistent with the captured thread; never remove
157
+ or clean up a reaction.
158
+ Add `🎫` for each distinct unfinished team or engineering action that another
159
+ person or team must perform outside this workflow, whether or not a named
160
+ person has accepted it or an external tracker ticket exists. Record each such
161
+ action in `handoffs[]`
162
+ with its exact scope, action, and responsible person or team. A team or role owner, or no owner
163
+ at all, keeps `status: "unassigned"` until a named person commits. Another person's
164
+ reply never changes an existing named owner. Do not invent a
165
+ person, Slack ID, request, or acceptance. A handoff becomes `accepted` only when a named person makes an explicit first-person
166
+ commitment to the recorded action in the thread. That person becomes its owner: record
167
+ their Slack ID and exact reply, and change the status to
168
+ `"accepted"`; when the accepted action is done, use `status: "completed"` with
169
+ completion evidence. The `🎫` reaction remains as history after completion and
170
+ no longer counts as an open blocker. An owner withdrawal sets the handoff back to `status: "unassigned"` and keeps it in
171
+ `handoffs[]` with its `🎫`, so unfinished work stays tracked. It leaves current
172
+ handoffs only when the action is completed. An
173
+ unambiguous contextual β€œyes”/β€œsure” may accept a
174
+ preceding direct request when it names the human and exact action. That request
175
+ may ask whether the named person's TODO or PR already includes the action; a
176
+ clear β€œyes, it's on my PR” with a stated next step such as β€œtaking a look now”
177
+ counts. Record both
178
+ the request and acceptance reply, including timestamps and actor IDs. The
179
+ human's explicit first-person commitment to the recorded action also counts,
180
+ even without a preceding request (for example, β€œI'll test this today” or β€œI'll
181
+ expand it to all apps”). Record that exact acceptance reply. Tentative language
182
+ such as β€œI might test this” is not acceptance. Keep acceptance and refusal verbs
183
+ consistent with the examples in this file.
184
+ The `🎫` reaction covers only that recorded action: do not infer a broader fix,
185
+ PR, or delivery promise. A role assignment, generic team ping, or ask without a reply
186
+ is not acceptance, but it still gets `🎫` when a distinct team action remains
187
+ unfinished. Ambiguous or hedged assent does not change `status: "unassigned"`.
188
+ A partial fix with a separately unfinished human/team action can have both
189
+ `βœ…` and `🎫`.
190
+ Work that remains with this workflow is active and needs no `🎫` by itself, but a fix
191
+ this workflow could not land gets `🎫` as a human handoff, matching the parent skill.
192
+ Requests for a reporter-provided URL, evidence, or clarification are separate human
193
+ actions, not team handoffs. Access, credentials, admin rights, and account changes
194
+ are never asked of the reporter. They are Steve's internal work: record them in `handoffs[]` with a `🎫`, owner `"unassigned"`,
195
+ and `status: "unassigned"` until Steve explicitly commits in the thread; name Steve in
196
+ the action text, not the owner field. They
197
+ are not in `reporterRequests[]`, so they do not expire with the reporter-request
198
+ lifecycle.
199
+ When this workflow asks a reporter for a URL, evidence, or clarification,
200
+ record the requested action in `reporterRequests[]` and add/read back `🎫` while
201
+ that request is open. This tracks a requested human action; it is not a team
202
+ handoff and does not claim accepted ownership. When the reporter answers, or
203
+ after four days with no later human reply, move it out of active
204
+ `reporterRequests[]` and preserve the root mark in `historicalReactionMarks[]`.
205
+ Keep its original `scope`, `action`, `requestReply`, and ticket-bearing
206
+ `previousStatus` under `closedReporterRequest`. For an answer, set
207
+ `resolution: "answered"` and include the exact human `closureReply` from the
208
+ same thread. For an unanswered request,
209
+ set `resolution: "abandoned_no_answer"` only after four days with no later human
210
+ reply, and omit `closureReply`. Keep the historical action and its evidence in
211
+ the internal inventory. Link an external tracker ticket only
212
+ if one already exists; do not create or promise one without authorization. If a
213
+ human later refuses the accepted action, preserve `🎫` as history and record
214
+ the refusal and blocker in the internal inventory.
215
+
216
+ Use the same connected Slack identity for reactions, replies, and read-back.
217
+ Compare exact actor IDs returned for the reaction and reply; do not describe a
218
+ separate β€œworkflow identity” when the connector posts as the invoking user. An
219
+ explicit handoff from another eye owner permits investigation only; it does
220
+ not transfer the eye or authorize changing that actor's reactions. If the eye
221
+ owner differs from the connected actor, or an actor ID cannot be verified,
222
+ record the mismatch internally and leave the item unreconciled for its owner.
223
+ Do not claim that eye or create a duplicate reply under another actor.
224
+ Never remove or clean up another person's or this workflow's reaction. When
225
+ new evidence changes the meaning of an existing mark, preserve it and update
226
+ the internal disposition and inventory. Read back every new reaction and reply.
227
+
228
+ An unanswered request for a reporter-provided URL, evidence, or
229
+ clarification is **Asked** or **Clarification needed** and remains an open
230
+ requested human action: record it in `reporterRequests[]`, keep `πŸ‘€`, and
231
+ add/read back `🎫`. Ask the direct question in the short reply that accompanies
232
+ the new `🎫`. It is not a team handoff, so it does not require or imply an
233
+ accepted owner in `handoffs[]`.
234
+ When no other person needs to act and only this workflow owns the next step, do
235
+ not add `🎫`.
236
+
237
+ Resolve duplicate clusters before claiming. Record non-owner duplicates by
238
+ permalink and **Clustered** in the internal inventory; do not send a status
239
+ reply solely to report clustering. For a late duplicate that already has `πŸ‘€`,
240
+ recheck ownership. If this task still owns it, keep the eye and record its
241
+ owner permalink; do not send a second owner update. If another task owns it or
242
+ ownership is unclear, preserve the eye, record the source in your run notes, and
243
+ stop. Keep
244
+ distinct scopes and their status clear.
245
+
246
+ Compare each reported symptom with the evidence in the internal inventory.
247
+ Use one root `βœ…` when at least one fixed scope is merged to `main` and verified; it covers every
248
+ verified scope listed in `fixedScopes[]`, with proof recorded internally. Use
249
+ one root `🎫` whenever at least one distinct unfinished action
250
+ requires another person to act; it covers the actions listed in `handoffs[]`
251
+ and active requests in `reporterRequests[]`. Handoffs include unassigned
252
+ actions and completed actions whose evidence and `status: "completed"` remain
253
+ in your run notes; reporter requests stay active only while the requested input is
254
+ outstanding.
255
+ Do not add duplicate copies of either reaction for additional scopes. Link an
256
+ external tracker ticket if there is one. Record a person's Slack ID and exact reply
257
+ only after that person accepts; until then record the responsible team/role or
258
+ `unassigned` and set `status: "unassigned"`. Keep `πŸ‘€` while work remains. An open reporter request gets `🎫` even though it is not a team handoff; a
259
+ team handoff also gets `🎫`. Do not create or promise an external tracker
260
+ ticket without authorization. Never imply a
261
+ partial fix resolved the whole report.
262
+
263
+ For beta and package fixes, verify publisher and release state and keep the
264
+ details in the internal recap. Do not send progress or deployment narration.
265
+ If this run adds a reaction, explain only that new reaction in the short reply.
266
+ Keep technical details, IDs, CI, workflow details, and run IDs out of reporter
267
+ replies.
268
+
269
+ For every reply in each final full-thread snapshot, record a read-back of the
270
+ workflow reactions on that reply. If the connector shows `πŸ‘€`, `βœ…`, or `🎫`,
271
+ call `slack_get_reactions` for the reply's own timestamp and record exact owner
272
+ IDs for those workflow emoji; record `"none"` with `{}` when none appears and
273
+ omit unrelated emoji. These reads are separate from the root read-back. Do not
274
+ add new workflow reactions to replies. Preserve any legacy reply-level mark and
275
+ record its exact message timestamp and owner; then audit the report root
276
+ independently. A reply mark never checks off the root scope. Keep it attributed; never move
277
+ or remove it.
278
+
279
+ Read back the reaction and any reply through the same connector, and verify
280
+ their actor IDs match the recorded sweep identity before recording the item as
281
+ replied. Keep the full mark, scope, and action inventory internally. In the
282
+ user-facing recap, mention only reactions added in this run and explain them
283
+ briefly. Do not explain absent or unchanged reactions or whether a tracker
284
+ ticket exists. Keep any existing ticket link in the internal inventory. Do not
285
+ balance reactions for appearance. Treat
286
+ reaction text in a reply, another actor's mark, or a reply-row reaction as no
287
+ substitute for the required root read-back.
@@ -423,6 +423,29 @@ invocation explicitly authorizes that communication on the exact PR. This does
423
423
  not add a separate authorization step for replies required on the active
424
424
  user's own PR under `/ship`, `/ship-now`, or `/babysit-pr`.
425
425
 
426
+ When a PR needs product, UX, or app-owner review, record that gate only in the
427
+ recap and leave the PR thread alone. Do not comment or reply to ask for, announce,
428
+ or explain that owner review is needed. This silence rule applies even after an
429
+ author update; do not send a thanks-only or status reply while that review gate is
430
+ outstanding.
431
+ This silence rule does not suppress replies required on the active user's own PR
432
+ under `/ship`, `/ship-now`, or `/babysit-pr`; those follow the own-PR reply rules.
433
+
434
+ When a reply is needed to resolve a review thread on the exact PR the current
435
+ invocation authorizes, only a verified external author gets one. Confirm the author
436
+ is a nonmember first. An unknown-membership author gets no reply, and the membership
437
+ check goes in the recap. Verified BuilderIO members get no reply here. The active
438
+ user's own PR is the one exception: it gets the disposition only, with no thank-you.
439
+ For a verified external author, an explicit reply request sends the reply even
440
+ without an author update. Otherwise reply only if the author has updated since the
441
+ last message, and begin by thanking them for the PR or for their update. An explicit
442
+ request does not override the owner-review silence rule above, except on the active
443
+ user's own PR. Never write β€œI have no further feedback” or another vague closing;
444
+ state the disposition and the next actor and action, such as the exact change
445
+ requested, that no changes are requested and the PR is ready for maintainer
446
+ merge, or which named check is still pending. If the next step is a product,
447
+ UX, or app-owner decision, use the silence rule above.
448
+
426
449
  Bot-authored PRs, including Dependabot, are outside this skill's review and
427
450
  merge scope and must remain completely untouched.
428
451
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.222.0-nightly-20261029032851",
3
+ "version": "0.222.0",
4
4
  "description": "The agentic application framework for building autonomous agents with intuitive UIs",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -742,8 +742,8 @@
742
742
  "yaml": "^2.9.0",
743
743
  "yjs": "^13.6.33",
744
744
  "zod": "^4.3.6",
745
- "@agent-native/agentkit": "^0.222.0-nightly-20261029032851",
746
- "@agent-native/recap-cli": "^0.5.90"
745
+ "@agent-native/agentkit": "^0.222.0",
746
+ "@agent-native/recap-cli": "^0.5.91"
747
747
  },
748
748
  "devDependencies": {
749
749
  "@ai-sdk/anthropic": "^3.0.71",
@@ -814,7 +814,7 @@
814
814
  "xlsx": "^0.18.5"
815
815
  },
816
816
  "peerDependencies": {
817
- "@agent-native/core-corpus": "0.222.0-nightly-20261029032851",
817
+ "@agent-native/core-corpus": ">=0.196.0",
818
818
  "@ai-sdk/anthropic": ">=3",
819
819
  "@ai-sdk/cohere": ">=3",
820
820
  "@ai-sdk/google": ">=3",
@@ -21,6 +21,10 @@ not judged by ledgers, receipts, or checker output, so don't produce them.
21
21
 
22
22
  ## The contract
23
23
 
24
+ The detailed Slack reaction and reply rules are in
25
+ [slack-reaction-replies.md](references/slack-reaction-replies.md); read it before
26
+ marking, replying to, or closing any Slack report.
27
+
24
28
  Slack reactions on a report's root message mean exactly this:
25
29
 
26
30
  | Mark | Meaning | When to add |
@@ -43,7 +47,8 @@ might need to take back. Every πŸ‘€ you add ends the run with a reply, plus
43
47
  either βœ… or 🎫, or a fix PR that is still open and lists the report (its
44
48
  merge adds the βœ…). A πŸ‘€ with no follow-up is the most common complaint about this workflow.
45
49
 
46
- Every claimed report gets **one** reply in its thread per outcome. People
50
+ Every claimed report gets **one** reply in its thread per outcome. The one exception is
51
+ the merged `βœ…` close-out after an in-review reply, which gets no second reply. People
47
52
  have pushed back on replies that were long, technical, condescending, vague
48
53
  about who does what next, or that handed them work. Each reply must be:
49
54
 
@@ -0,0 +1,287 @@
1
+ # Slack Reaction Replies
2
+
3
+ Use the connected Slack integration for every Slack read and write. Never use a browser, screenshots, or UI automation to operate Slack.
4
+
5
+ At sweep exit, reconcile every report root in the scoped inventory that has
6
+ this task's `πŸ‘€`, `βœ…`, or `🎫`, including marks left by earlier runs. Capture
7
+ the exact Slack actor ID used by the connected tool at sweep start. A mark is
8
+ owned by this task only when its reaction owner ID matches that actor ID and the
9
+ root is the workflow's by the parent skill's evidence: a bot-disclosure reply from
10
+ that account, or a root listed under "Claimed, not yet answered" in memory.md. A
11
+ matching reaction without that evidence is a manual mark: leave it alone. Keep
12
+ the disposition, evidence, scopes, and human actions in the internal inventory.
13
+ Post a reporter reply when this run adds a root reaction, when a newer human reporter
14
+ message needs a direct answer, or when this workflow owns a root from an earlier run
15
+ that has no verified reply in its thread yet, unless a person is already handling the root
16
+ or has asked the workflow to stop. The reply is one short, plain-language
17
+ sentence (about 25 words at most), plus the exact bot disclosure, which does not count toward
18
+ that limit. If the root asks a question, that sentence answers
19
+ it first and also explains only the reaction(s) added in this run. An answer to a newer human message
20
+ answers only that message. Skip the reply when the only reaction added is the merged
21
+ `βœ…` close-out after an earlier "in review" reply; that close-out gets no second
22
+ reply. Do not send progress, merge, or deployment updates. Never explain absent or
23
+ unchanged reactions or whether a tracker ticket exists. If reporter input is
24
+ needed, add `🎫` and ask one direct question in that reply. Read back each new
25
+ reaction and reply.
26
+
27
+ Keep prior replies as history. Do not edit or add a status reply merely to
28
+ describe changed evidence, an existing reaction, or completed follow-up work.
29
+ When a new reaction is added, use one short sentence plus the exact bot
30
+ disclosure.
31
+
32
+ When editing an ordinary Slack reply, pass its visible body as
33
+ `message.markdown_text` to `slack_edit_message`; `message.text` is a fallback
34
+ for structured block messages and may leave the visible reply unchanged. Treat
35
+ the returned message ID as acknowledgment only. Read the exact reply back with
36
+ `slack_read_thread` and compare the stored body to the intended status before
37
+ counting it as posted. Use `slack_send_message` only when a new reply is
38
+ needed, with its `message` field and the root `thread_ts`.
39
+
40
+ `slack_read_thread` may append a synthetic `Status marks:` summary after the
41
+ thread. That is connector metadata, not part of any Slack message; do not copy
42
+ it into a reply. Likewise, omit connector-added `Sent using ChatGPT` footer
43
+ text from the intended body. `slack_send_message` may append that footer to a
44
+ new reply; if it does, remove it by editing with `message.markdown_text`, then
45
+ read the reply back again. Include the exact bot disclosure in the visible body
46
+ yourself; the connector footer is not a substitute.
47
+
48
+ Apply reactions to the report root, not the status reply: call
49
+ `slack_add_reaction` with the root's `channel_id`, its timestamp as
50
+ `message_ts`, and the emoji name without colons (`eyes`, `white_check_mark`, or
51
+ `ticket`). A colon-coded emoji mentioned in reply text does not add a reaction.
52
+ After each reaction call, read the root with `slack_get_reactions` and verify
53
+ the intended emoji and this actor's ID are present before recording it or
54
+ moving to another report. A successful tool response is only an acknowledgment;
55
+ the readback is the proof. If a reaction cannot be reconciled, record the exact
56
+ blocker instead of treating the reply text as a substitute.
57
+
58
+ Channel reads may expose a thread reply as a separate row. Resolve its
59
+ `thread_ts` or permalink and inventory it as a reply to the root, not as a new
60
+ report. Put new reactions for an ordinary report on its root; do not add another
61
+ `πŸ‘€` to a reply just because it appears as a channel row. Preserve any legacy
62
+ reaction already on a reply, read it back, and reconcile the actual root
63
+ separately. A reply that raises a genuinely distinct issue stays in the source
64
+ manifest as a reply row, but scope, disposition, handoff, and checkoff evidence
65
+ belong to the actual root row. Record the distinct issue as a separate scope on
66
+ that root and verify it independently. Do not invent a second root or put a
67
+ root checkoff on the reply. Record both the source message timestamp and root
68
+ thread timestamp; if the connector hides `thread_ts`, resolve the permalink
69
+ before recording it.
70
+
71
+ The parent inventory must retain the reaction-owner IDs and the same-parent
72
+ reply timestamp, author ID, disposition, evidence, and read-back text for every
73
+ root this actor marks with `πŸ‘€`, `βœ…`, or `🎫`. Record an explicit decision for
74
+ both `βœ…` and `🎫` on every such root: `present` with its scope, or `none` with
75
+ the reason no mark applies. Record verified fixed scopes with their proof and
76
+ each distinct human handoff with its scope, owner, and action. Before the recap, reconcile every owned root in the inventory by hand. Incomplete
77
+ source coverage or owned-root reconciliation means the sweep is not done. For
78
+ explicitly narrowed scope, keep the active user request as a separate record in
79
+ the inventory. This step does not authorize
80
+ changing another actor's reactions or replies.
81
+
82
+ ## Prior questions and reporter replies
83
+
84
+ At sweep start, search the exact bot disclosure in each scoped channel and
85
+ exhaust the pages to find prior questions and their context:
86
+
87
+ ```text
88
+ slack_search: "this was sent from a bot." in:<#CHANNEL>
89
+ sort=timestamp sort_dir=asc include_context=true max_context_length=300
90
+ ```
91
+
92
+ Also scan channel messages from the saved reply cursor; on the first run, scan
93
+ available history. Since `after:` is date-only, start one day before the cursor,
94
+ exhaust pages, resolve hits to their root, and advance the cursor only through
95
+ processed messages. Read inherited threads carrying any of this actor's root marks (`πŸ‘€`, `βœ…`,
96
+ or `🎫`): a checkmark closes only its verified scope, not a newer reporter
97
+ answer or another open scope. Do not reply or react on another actor's root
98
+ when work is handed off for investigation; that handoff permits local
99
+ investigation only. A separate explicit authorization is required to post a
100
+ reporter-facing reply or change that actor's reactions.
101
+
102
+ Count an answer only when a human posts after the question, without this
103
+ workflow's disclosure, and the reply addresses it. Read the full thread to
104
+ exclude partial, unrelated, or deferred answers; a newer human post reopens the
105
+ question. Attempt the fix with new evidence before asking again. Do not send a
106
+ progress reply when no reaction is added. Do not ask again while the question
107
+ is less than four days old. At four days, record **Abandoned - no answer in 4
108
+ days** internally without re-asking, then carry any remaining defect as
109
+ internal work. For legacy replies without disclosure or eyes, search once per
110
+ connected actor and verify the author and thread.
111
+
112
+ End every new or edited Slack reply with the exact sentence
113
+ `this was sent from a bot.`. A connector-added `Sent using ChatGPT` footer is
114
+ not a substitute.
115
+
116
+ Reactions are permanent history and do not replace the internal disposition.
117
+ At the root, `πŸ‘€` records this workflow's claim, `βœ…` marks one or
118
+ more named scopes that passed direct post-change proof, and `🎫` tracks distinct
119
+ human/team actions, including actions with no named owner yet. The eye alone
120
+ does not mean work remains open. One root `βœ…` covers every verified scope
121
+ listed in `fixedScopes[]`; one root `🎫` covers every action listed in
122
+ `handoffs[]`, including unassigned, accepted, and completed actions. Completion
123
+ evidence changes its status, not the historical `🎫` reaction. Unclaimed items
124
+ and another actor's marks are not ours to reply to. Reconcile each report as
125
+ one unit: update and read back applicable root reactions before advancing to
126
+ the next report. Add `βœ…` once a reported scope passes the direct post-change proof bar on `main`.
127
+ An open PR never gets `βœ…`. Use
128
+ **Partially fixed** for the internal disposition when another scope remains.
129
+ Do not wait for an entire multi-symptom report to be resolved before checking
130
+ off its verified scope. Keep all existing reactions after scopes close. Read
131
+ back every reaction and any reply sent. Never remove or clean up any reaction.
132
+ While a fix PR is open, `πŸ‘€` may remain; `βœ…` still waits for the merge to `main`.
133
+ The marks are independent. The current disposition says what remains.
134
+ For UI behavior, a focused
135
+ regression qualifies only if it exercises the changed component and asserts the
136
+ reported visible result or persisted state; callback-only assertions, mocked
137
+ children, class-name checks, and source inspection do not. For clipping,
138
+ wrapping, contrast, or alignment, inspect rendered geometry at the reported
139
+ viewport or replay the flow in the running app. Use an app/preview replay when
140
+ the regression does not meet that bar. For non-UI scopes, use the area-specific
141
+ action, query, or test proof. Record merge or rollout state in the internal
142
+ inventory; the checkmark records a verified scope and does not claim it is
143
+ shipped or live.
144
+ Before leaving each report, compare verified scopes and human/team handoffs
145
+ with a fresh root-reaction read. Ensure one `βœ…` is present when any scope is fixed on `main`, and one `🎫` whenever any distinct unfinished action requires another
146
+ person or team to act, even before ownership is assigned or accepted. Keep the
147
+ mark as history after completion; read back each changed root reaction and
148
+ verify its owner. Record whether each mark applies and any historical-reaction
149
+ details in the internal inventory only; never explain missing reactions in a
150
+ reporter reply. Emoji text in a reply is not a reaction. Preserve every
151
+ existing reaction. If later proof invalidates a prior checkmark, move that
152
+ scope out of active `fixedScopes[]` and add a `historicalReactionMarks[]` entry
153
+ with `emoji`, `scope`, `reason`, and the exact prior `previousStatus`. If the
154
+ owner withdraws an accepted action tracked by `🎫`, preserve the exact earlier
155
+ accepted `handoff` and owner `withdrawalReply`, then update the internal record.
156
+ Keep these records consistent with the captured thread; never remove
157
+ or clean up a reaction.
158
+ Add `🎫` for each distinct unfinished team or engineering action that another
159
+ person or team must perform outside this workflow, whether or not a named
160
+ person has accepted it or an external tracker ticket exists. Record each such
161
+ action in `handoffs[]`
162
+ with its exact scope, action, and responsible person or team. A team or role owner, or no owner
163
+ at all, keeps `status: "unassigned"` until a named person commits. Another person's
164
+ reply never changes an existing named owner. Do not invent a
165
+ person, Slack ID, request, or acceptance. A handoff becomes `accepted` only when a named person makes an explicit first-person
166
+ commitment to the recorded action in the thread. That person becomes its owner: record
167
+ their Slack ID and exact reply, and change the status to
168
+ `"accepted"`; when the accepted action is done, use `status: "completed"` with
169
+ completion evidence. The `🎫` reaction remains as history after completion and
170
+ no longer counts as an open blocker. An owner withdrawal sets the handoff back to `status: "unassigned"` and keeps it in
171
+ `handoffs[]` with its `🎫`, so unfinished work stays tracked. It leaves current
172
+ handoffs only when the action is completed. An
173
+ unambiguous contextual β€œyes”/β€œsure” may accept a
174
+ preceding direct request when it names the human and exact action. That request
175
+ may ask whether the named person's TODO or PR already includes the action; a
176
+ clear β€œyes, it's on my PR” with a stated next step such as β€œtaking a look now”
177
+ counts. Record both
178
+ the request and acceptance reply, including timestamps and actor IDs. The
179
+ human's explicit first-person commitment to the recorded action also counts,
180
+ even without a preceding request (for example, β€œI'll test this today” or β€œI'll
181
+ expand it to all apps”). Record that exact acceptance reply. Tentative language
182
+ such as β€œI might test this” is not acceptance. Keep acceptance and refusal verbs
183
+ consistent with the examples in this file.
184
+ The `🎫` reaction covers only that recorded action: do not infer a broader fix,
185
+ PR, or delivery promise. A role assignment, generic team ping, or ask without a reply
186
+ is not acceptance, but it still gets `🎫` when a distinct team action remains
187
+ unfinished. Ambiguous or hedged assent does not change `status: "unassigned"`.
188
+ A partial fix with a separately unfinished human/team action can have both
189
+ `βœ…` and `🎫`.
190
+ Work that remains with this workflow is active and needs no `🎫` by itself, but a fix
191
+ this workflow could not land gets `🎫` as a human handoff, matching the parent skill.
192
+ Requests for a reporter-provided URL, evidence, or clarification are separate human
193
+ actions, not team handoffs. Access, credentials, admin rights, and account changes
194
+ are never asked of the reporter. They are Steve's internal work: record them in `handoffs[]` with a `🎫`, owner `"unassigned"`,
195
+ and `status: "unassigned"` until Steve explicitly commits in the thread; name Steve in
196
+ the action text, not the owner field. They
197
+ are not in `reporterRequests[]`, so they do not expire with the reporter-request
198
+ lifecycle.
199
+ When this workflow asks a reporter for a URL, evidence, or clarification,
200
+ record the requested action in `reporterRequests[]` and add/read back `🎫` while
201
+ that request is open. This tracks a requested human action; it is not a team
202
+ handoff and does not claim accepted ownership. When the reporter answers, or
203
+ after four days with no later human reply, move it out of active
204
+ `reporterRequests[]` and preserve the root mark in `historicalReactionMarks[]`.
205
+ Keep its original `scope`, `action`, `requestReply`, and ticket-bearing
206
+ `previousStatus` under `closedReporterRequest`. For an answer, set
207
+ `resolution: "answered"` and include the exact human `closureReply` from the
208
+ same thread. For an unanswered request,
209
+ set `resolution: "abandoned_no_answer"` only after four days with no later human
210
+ reply, and omit `closureReply`. Keep the historical action and its evidence in
211
+ the internal inventory. Link an external tracker ticket only
212
+ if one already exists; do not create or promise one without authorization. If a
213
+ human later refuses the accepted action, preserve `🎫` as history and record
214
+ the refusal and blocker in the internal inventory.
215
+
216
+ Use the same connected Slack identity for reactions, replies, and read-back.
217
+ Compare exact actor IDs returned for the reaction and reply; do not describe a
218
+ separate β€œworkflow identity” when the connector posts as the invoking user. An
219
+ explicit handoff from another eye owner permits investigation only; it does
220
+ not transfer the eye or authorize changing that actor's reactions. If the eye
221
+ owner differs from the connected actor, or an actor ID cannot be verified,
222
+ record the mismatch internally and leave the item unreconciled for its owner.
223
+ Do not claim that eye or create a duplicate reply under another actor.
224
+ Never remove or clean up another person's or this workflow's reaction. When
225
+ new evidence changes the meaning of an existing mark, preserve it and update
226
+ the internal disposition and inventory. Read back every new reaction and reply.
227
+
228
+ An unanswered request for a reporter-provided URL, evidence, or
229
+ clarification is **Asked** or **Clarification needed** and remains an open
230
+ requested human action: record it in `reporterRequests[]`, keep `πŸ‘€`, and
231
+ add/read back `🎫`. Ask the direct question in the short reply that accompanies
232
+ the new `🎫`. It is not a team handoff, so it does not require or imply an
233
+ accepted owner in `handoffs[]`.
234
+ When no other person needs to act and only this workflow owns the next step, do
235
+ not add `🎫`.
236
+
237
+ Resolve duplicate clusters before claiming. Record non-owner duplicates by
238
+ permalink and **Clustered** in the internal inventory; do not send a status
239
+ reply solely to report clustering. For a late duplicate that already has `πŸ‘€`,
240
+ recheck ownership. If this task still owns it, keep the eye and record its
241
+ owner permalink; do not send a second owner update. If another task owns it or
242
+ ownership is unclear, preserve the eye, record the source in your run notes, and
243
+ stop. Keep
244
+ distinct scopes and their status clear.
245
+
246
+ Compare each reported symptom with the evidence in the internal inventory.
247
+ Use one root `βœ…` when at least one fixed scope is merged to `main` and verified; it covers every
248
+ verified scope listed in `fixedScopes[]`, with proof recorded internally. Use
249
+ one root `🎫` whenever at least one distinct unfinished action
250
+ requires another person to act; it covers the actions listed in `handoffs[]`
251
+ and active requests in `reporterRequests[]`. Handoffs include unassigned
252
+ actions and completed actions whose evidence and `status: "completed"` remain
253
+ in your run notes; reporter requests stay active only while the requested input is
254
+ outstanding.
255
+ Do not add duplicate copies of either reaction for additional scopes. Link an
256
+ external tracker ticket if there is one. Record a person's Slack ID and exact reply
257
+ only after that person accepts; until then record the responsible team/role or
258
+ `unassigned` and set `status: "unassigned"`. Keep `πŸ‘€` while work remains. An open reporter request gets `🎫` even though it is not a team handoff; a
259
+ team handoff also gets `🎫`. Do not create or promise an external tracker
260
+ ticket without authorization. Never imply a
261
+ partial fix resolved the whole report.
262
+
263
+ For beta and package fixes, verify publisher and release state and keep the
264
+ details in the internal recap. Do not send progress or deployment narration.
265
+ If this run adds a reaction, explain only that new reaction in the short reply.
266
+ Keep technical details, IDs, CI, workflow details, and run IDs out of reporter
267
+ replies.
268
+
269
+ For every reply in each final full-thread snapshot, record a read-back of the
270
+ workflow reactions on that reply. If the connector shows `πŸ‘€`, `βœ…`, or `🎫`,
271
+ call `slack_get_reactions` for the reply's own timestamp and record exact owner
272
+ IDs for those workflow emoji; record `"none"` with `{}` when none appears and
273
+ omit unrelated emoji. These reads are separate from the root read-back. Do not
274
+ add new workflow reactions to replies. Preserve any legacy reply-level mark and
275
+ record its exact message timestamp and owner; then audit the report root
276
+ independently. A reply mark never checks off the root scope. Keep it attributed; never move
277
+ or remove it.
278
+
279
+ Read back the reaction and any reply through the same connector, and verify
280
+ their actor IDs match the recorded sweep identity before recording the item as
281
+ replied. Keep the full mark, scope, and action inventory internally. In the
282
+ user-facing recap, mention only reactions added in this run and explain them
283
+ briefly. Do not explain absent or unchanged reactions or whether a tracker
284
+ ticket exists. Keep any existing ticket link in the internal inventory. Do not
285
+ balance reactions for appearance. Treat
286
+ reaction text in a reply, another actor's mark, or a reply-row reaction as no
287
+ substitute for the required root read-back.
@@ -423,6 +423,29 @@ invocation explicitly authorizes that communication on the exact PR. This does
423
423
  not add a separate authorization step for replies required on the active
424
424
  user's own PR under `/ship`, `/ship-now`, or `/babysit-pr`.
425
425
 
426
+ When a PR needs product, UX, or app-owner review, record that gate only in the
427
+ recap and leave the PR thread alone. Do not comment or reply to ask for, announce,
428
+ or explain that owner review is needed. This silence rule applies even after an
429
+ author update; do not send a thanks-only or status reply while that review gate is
430
+ outstanding.
431
+ This silence rule does not suppress replies required on the active user's own PR
432
+ under `/ship`, `/ship-now`, or `/babysit-pr`; those follow the own-PR reply rules.
433
+
434
+ When a reply is needed to resolve a review thread on the exact PR the current
435
+ invocation authorizes, only a verified external author gets one. Confirm the author
436
+ is a nonmember first. An unknown-membership author gets no reply, and the membership
437
+ check goes in the recap. Verified BuilderIO members get no reply here. The active
438
+ user's own PR is the one exception: it gets the disposition only, with no thank-you.
439
+ For a verified external author, an explicit reply request sends the reply even
440
+ without an author update. Otherwise reply only if the author has updated since the
441
+ last message, and begin by thanking them for the PR or for their update. An explicit
442
+ request does not override the owner-review silence rule above, except on the active
443
+ user's own PR. Never write β€œI have no further feedback” or another vague closing;
444
+ state the disposition and the next actor and action, such as the exact change
445
+ requested, that no changes are requested and the PR is ready for maintainer
446
+ merge, or which named check is still pending. If the next step is a product,
447
+ UX, or app-owner decision, use the silence rule above.
448
+
426
449
  Bot-authored PRs, including Dependabot, are outside this skill's review and
427
450
  merge scope and must remain completely untouched.
428
451