@agent-native/core 0.210.1 β†’ 0.211.0-nightly-20261020183616

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.
Files changed (62) hide show
  1. package/dist/action.d.ts +8 -0
  2. package/dist/action.js +17 -0
  3. package/dist/agent/production-agent.js +11 -20
  4. package/dist/client/chat/agentkit-protocol.js +48 -5
  5. package/dist/client/chat/runtime.d.ts +2 -0
  6. package/dist/client/chat/runtime.js +6 -1
  7. package/dist/client/hooks/index.d.ts +1 -1
  8. package/dist/client/hooks/index.js +1 -1
  9. package/dist/client/resources/mcp-integration-logos.js +4 -0
  10. package/dist/client/session-identity.d.ts +8 -0
  11. package/dist/client/session-identity.js +13 -0
  12. package/dist/client/use-action.d.ts +14 -0
  13. package/dist/client/use-action.js +117 -28
  14. package/dist/client/use-agent-engine-configured.d.ts +2 -2
  15. package/dist/client/use-agent-engine-configured.js +3 -9
  16. package/dist/client/use-session.js +2 -0
  17. package/dist/collab/awareness.d.ts +2 -2
  18. package/dist/collab/struct-routes.d.ts +1 -1
  19. package/dist/deploy/build.js +24 -3
  20. package/dist/mcp/actions/revoke-org-service-token.js +5 -1
  21. package/dist/mcp/execute-action-call.js +8 -17
  22. package/dist/observability/routes.d.ts +5 -5
  23. package/dist/org/actions/set-app-member-roles.js +4 -0
  24. package/dist/org/actions/set-app-permission-roles.js +4 -0
  25. package/dist/org/actions/set-workspace-app-access.js +4 -0
  26. package/dist/org/index.d.ts +1 -1
  27. package/dist/provider-api/actions/custom-provider-registration.d.ts +17 -17
  28. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  29. package/dist/resources/handlers.d.ts +1 -1
  30. package/dist/review/suggestions/actions.d.ts +1 -0
  31. package/dist/review/suggestions/actions.js +54 -42
  32. package/dist/secrets/actions/delete-api-key.js +4 -0
  33. package/dist/secrets/routes.d.ts +4 -4
  34. package/dist/server/action-approval.d.ts +15 -0
  35. package/dist/server/action-approval.js +39 -0
  36. package/dist/server/action-routes.js +33 -28
  37. package/dist/server/edge.d.ts +1 -0
  38. package/dist/server/edge.js +1 -0
  39. package/dist/server/realtime-token.d.ts +1 -1
  40. package/dist/server/transcribe-voice.d.ts +1 -1
  41. package/dist/server/workspace-provider-oauth.js +1 -0
  42. package/dist/shared/action-error-codes.d.ts +5 -0
  43. package/dist/shared/action-error-codes.js +5 -0
  44. package/dist/shared/index.d.ts +1 -0
  45. package/dist/shared/index.js +1 -0
  46. package/dist/templates/chat/.agents/skills/actions/references/action-fields.md +5 -3
  47. package/dist/templates/default/.agents/skills/actions/references/action-fields.md +5 -3
  48. package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +105 -33
  49. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +128 -74
  50. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +39 -11
  51. package/dist/templates/headless/.agents/skills/actions/references/action-fields.md +5 -3
  52. package/dist/templates/workspace-core/.agents/skills/actions/references/action-fields.md +5 -3
  53. package/dist/vite/client.js +2 -1
  54. package/docs/content/actions-access-control.mdx +3 -3
  55. package/package.json +4 -4
  56. package/src/templates/chat/.agents/skills/actions/references/action-fields.md +5 -3
  57. package/src/templates/default/.agents/skills/actions/references/action-fields.md +5 -3
  58. package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +105 -33
  59. package/src/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +128 -74
  60. package/src/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +39 -11
  61. package/src/templates/headless/.agents/skills/actions/references/action-fields.md +5 -3
  62. package/src/templates/workspace-core/.agents/skills/actions/references/action-fields.md +5 -3
@@ -21,7 +21,25 @@ Four phases, in order. Phase 0 comes before any investigation, not after.
21
21
  2. **Fix** what the evidence actually proves, at the owning boundary.
22
22
  3. **Reply** with useful questions, then recap.
23
23
 
24
- Output is fixes. For every report root this task owns, keep one concise, current same-actor status reply after the latest human message. It names every verified `βœ…` scope and its evidence, every open scope, and each `🎫` action and owner (or why a mark does not apply). Add and read back root `βœ…` as soon as direct post-change proof verifies a scope, and root `🎫` as soon as another person or team must act. An `πŸ‘€` alone never completes the status. Preserve all reaction history, and follow [`slack-reaction-replies.md`](references/slack-reaction-replies.md). Do not reply on person-assigned or `:mag:` items.
24
+ Output is fixes. For every unheld in-scope report root this task owns, keep one concise, current same-actor status reply after the latest human message. It names every verified `βœ…` scope and its evidence, every open scope, and each `🎫` action and owner (or why a mark does not apply). Add and read back root `βœ…` as soon as direct post-change proof verifies a scope, and root `🎫` as soon as another person or team must act. An `πŸ‘€` alone never completes the status. Preserve all reaction history, and follow [`slack-reaction-replies.md`](references/slack-reaction-replies.md). Do not reply on held, person-assigned, or `:mag:` items.
25
+
26
+ ### Read the root marks as history; use the reply for current status
27
+
28
+ - `πŸ‘€` records this workflow's claim. It does not mean the report is still open.
29
+ - `βœ…` records a scope that passed direct post-change proof. Add it as soon as
30
+ any scope passes, even when another scope remains open; name the verified
31
+ scope and evidence in the status reply.
32
+ - `🎫` records a distinct action another person must take, including a reporter
33
+ request. Keep the reaction as history after that action is completed,
34
+ answered, or abandoned; the current reply must say whether the action is
35
+ still open or historical.
36
+ - Every owned root needs one current same-actor reply after the latest human
37
+ reply. Reuse it while the disposition and evidence remain current; do not
38
+ send a duplicate progress reply on every sweep. The reply names verified
39
+ scopes, open scopes, human actions and owners, and why either `βœ…` or `🎫` is
40
+ absent. An eye-only root is correct when no scope is verified and no separate
41
+ human action is pending; say whether the next step is this workflow's
42
+ investigation or no further action.
25
43
 
26
44
  ## Slack channels and ownership
27
45
 
@@ -34,44 +52,93 @@ channel. For a permalink, its channel ID identifies only where the exact linked
34
52
  root must appear; it does not limit the cutoff or channel scope. For an
35
53
  explicitly narrowed sweep, verify that the active request names every selected
36
54
  channel, then record its exact text and IDs in the separate scope-confirmation
37
- file. `#dev-agent-native-feedback` is not excluded.
55
+ file. A request can select exactly two channels by linking one report in the
56
+ first and directly asking for β€œthe same in” the second, without saying β€œonly.”
57
+ The checker permits one adjacent-transposition typo in that target only when it
58
+ uniquely matches a selected canonical channel; it rejects incidental or quoted
59
+ mentions and prefixes. See [`channel-inventory.md`](references/channel-inventory.md)
60
+ for the scope gate and its confirmation requirements. `#dev-agent-native-feedback`
61
+ is not excluded.
38
62
 
39
63
  In every scoped channel, run the complete bot-inclusive `slack_search_public`
40
64
  scan with only the channel filter and that channel's inclusive lower bound.
41
- Exhaust every cursor; `slack_read_channel` cannot replace this inventory. Resolve
42
- each row to its true parent. Read each report's full thread, attachments, and
43
- root reactions before classifying, claiming, or skipping it. Follow
65
+ Freeze one `windowEndTs` at sweep start and use it for every channel and
66
+ owned-mark search. Messages newer than it belong to the next scheduled sweep;
67
+ never move the boundary to chase arrivals. Exhaust every cursor;
68
+ `slack_read_channel` cannot replace this inventory. Resolve
69
+ each row to its true parent and read the root reactions before opening the thread.
70
+ If another actor owns any workflow mark (`πŸ‘€`, `βœ…`, or `🎫`), or this actor owns
71
+ `:mag:`, apply the held-thread boundary below: do not read the thread, react, or
72
+ reply. Otherwise read the report's full thread and attachments before
73
+ classifying or claiming it. Follow
44
74
  [`channel-inventory.md`](references/channel-inventory.md) for exact query
45
- arguments, raw-page receipts, cap recovery, root classification, all-history
46
- searches for this actor's `πŸ‘€`, `βœ…`, and `🎫`, and checker requirements.
75
+ arguments, raw-page receipts, cap recovery, root classification, fixed-window
76
+ source scans, bounded ownership searches for this actor's `πŸ‘€`, `βœ…`, and `🎫`,
77
+ and checker requirements. Owned-mark searches use Slack's exact syntax
78
+ `hasmy::eyes:`, `hasmy::white_check_mark:`, or `hasmy::ticket:` with the channel
79
+ filter and the same inclusive `after` and `before` as that channel's source
80
+ scan. Do not widen a finite catch-up request into an all-history audit. A root
81
+ older than `after` is in scope only when an in-window reply links to it. A
82
+ missing trailing colon is not a valid ownership query.
47
83
 
48
84
  Only the invoking Slack actor's root marks are owned here. Another task's eye
49
85
  plus its current open status remains theirs; an explicit handoff naming this
50
86
  task and scope is required to transfer investigation. An eye is a permanent
51
87
  claim, not proof of open or completed work. A mark on a reply never claims or
52
- checks off its parent. Before exit, reconcile every owned root in the full
53
- inventory, including older marks: latest human reply, current status reply,
54
- verified scopes, open scopes, human/team actions, and exact root reactions.
55
- Never let a high eye count hide an unchecked verified scope or a missing
56
- current reply.
88
+ checks off its parent. Before exit, reconcile every in-window root carrying
89
+ this actor's `πŸ‘€`, `βœ…`, or `🎫` found by the bounded searches. Also reconcile an
90
+ older root when an in-window reply links to it. For every owned root, compare
91
+ the latest human reply, current status, verified scopes, open scopes, human
92
+ actions, and exact root reactions.
93
+ Never let a high eye count hide a verified scope without `βœ…` or a missing
94
+ current status reply.
95
+
96
+ **Held-thread boundary:** A direct root reaction read showing another person's
97
+ `πŸ‘€`, `βœ…`, or `🎫` means the thread belongs to that workflow; this actor's
98
+ `:mag:` means this actor opted out. Record the exact root, reaction owners, and
99
+ one reason (`foreign_workflow_mark` or `mag_opt_out`) in `ownershipSkip`, with
100
+ `Skipped` disposition. When this actor owns both `:mag:` and a workflow mark,
101
+ use `mag_opt_out` and preserve the exact root reaction-owner record as history.
102
+ Do not open/read that thread, inspect reply reactions, add or remove reactions,
103
+ or post a reply. Apply the same skip to channel reply
104
+ rows linked to that root. A linked pre-window root without source text remains
105
+ `unclassified`; do not infer its original report from the reply. This explicit
106
+ exception keeps ownership boundaries intact while allowing the rest of the
107
+ window to be reconciled. A foreign `πŸ‘€` without current status is still held;
108
+ do not read it to decide whether its owner is active.
57
109
 
58
110
  Only Steve's `:mag:` and `:yes-party:` count (`U0GCV21HC`). When Steve invokes,
59
- exhaust `hasmy::mag:` and `hasmy::yes-party:` searches; otherwise inspect root
60
- reactions. `:mag:` opts out and wins over `:yes-party:`. The latter authorizes
61
- only an otherwise review-only item; it never overrides an assignment or another
111
+ exhaust `hasmy::mag:` and `hasmy::yes-party:` searches within each channel's
112
+ same inclusive cutoff; otherwise inspect root reactions in the requested
113
+ window. `:mag:` opts out and wins over `:yes-party:`. The latter authorizes only
114
+ an otherwise review-only item; it never overrides an assignment or another
62
115
  task's claim.
63
116
 
64
117
  ## Phase 0: claim and reconcile
65
118
 
66
119
  Resolve known duplicates before claiming. Check ownership, then add and read
67
120
  back this task's root `πŸ‘€` before investigating an eligible unowned report.
68
- Reconcile every root this actor already owns, including eyes, checks, and
69
- handoffs from prior runs, before discovering new questions. Keep one concise
70
- canonical status by the same actor after the latest human message; read back the
71
- visible reply and root reactions after each write. Use
121
+ Reconcile only owned roots inside this invocation's fixed window and older
122
+ roots explicitly linked by an in-window reply. Never expand a finite sweep to
123
+ all historical roots just because this actor owns an old mark; widen the range
124
+ only when the invocation explicitly requests it or hands off exact roots. Finish
125
+ this bounded reconciliation before discovering new questions. Keep one
126
+ concise canonical status by the same actor after the latest human message; read
127
+ back the visible reply and root reactions after each write. Use
72
128
  [`slack-reaction-replies.md`](references/slack-reaction-replies.md) for exact
73
129
  status and reaction rules.
74
130
 
131
+ **Bounded exit gate:** enumerate the complete candidate set from the fixed
132
+ source window, the three matching owned-mark searches, and older roots linked
133
+ by in-window replies. For every unheld owned root, compare the latest human
134
+ message, current status reply, verified scopes, open scopes, and root marks.
135
+ Add and read back `βœ…` for each newly verified scope and `🎫` for each distinct
136
+ pending human action; the status reply must follow the latest human message and
137
+ name current evidence, remaining work, and owners. An `πŸ‘€` with a verified
138
+ scope missing `βœ…`, a pending human action missing `🎫`, or no current status is
139
+ unfinished. Finish the complete bounded set once, then stop; do not chase
140
+ unrelated historical marks.
141
+
75
142
  Split reports into distinct scopes. Add root `βœ…` as soon as any named scope
76
143
  passes direct post-change proof, even while its PR is open; say **Verified
77
144
  locally** and that merge is pending. Use **Fixed** only after merge and direct
@@ -97,11 +164,14 @@ action and owner. Use **Skipped** only for non-defects.
97
164
 
98
165
  Phase 0 handles new human replies on owned roots first; never hide a newer
99
166
  reporter reply behind a mark. Then find prior unanswered questions using the
100
- exact bot disclosure and the saved channel reply cursor. On a first run, scan
101
- available history. Exhaust pages, use a one-day overlap for date-only cursors,
102
- resolve every hit to its parent, and read the full thread after checking
103
- reactions. Follow [`slack-reaction-replies.md`](references/slack-reaction-replies.md)
104
- for exact searches, legacy identity handling, and answer criteria.
167
+ exact bot disclosure and the saved channel reply cursor, bounded by this
168
+ invocation's fixed window. On a first run, use the same default five-day window;
169
+ never scan all available history to initialize the cursor. Exhaust pages, use a
170
+ one-day overlap for date-only cursors, resolve every hit to its parent, and read
171
+ the full thread after checking reactions. An older root is eligible only when
172
+ an in-window reply links to it. Follow
173
+ [`slack-reaction-replies.md`](references/slack-reaction-replies.md) for exact
174
+ searches, legacy identity handling, and answer criteria.
105
175
 
106
176
  A valid answer is a human post after the question, without this workflow's
107
177
  disclosure, that addresses the question. Rebuild the evidence and attempt the
@@ -316,15 +386,17 @@ send one concise disposition covering done, remaining, and next action/owner.
316
386
  Do not reply again to announce merge, publication, or live beta; answer direct
317
387
  reporter follow-ups. End Slack replies with `this was sent from a bot.`.
318
388
 
319
- Replies are one or two sentences: the change or question, its limit, and the next step. Never explain reactions, ledgers, or tickets. Never send **Open**/**pending** or a symptom recap. Ask for reporter input only when it changes the next step; never request supplied evidence or internal access. Keep hashes, branches, CI, publisher, and run details in the recap.
320
-
321
- That brevity rule is for a reporter-facing reply. Separately, every root this
322
- actor marks with `πŸ‘€`, `βœ…`, or `🎫` still needs the current same-actor status
323
- reply required by the Slack reaction reference. One concise reply may serve both
324
- purposes when it answers the reporter and records the current disposition,
325
- verified/open scopes, owner/action, and any missing-proof reason. A Slack `🎫`
326
- reaction tracks a distinct unfinished human/team action; it does not mean an
327
- external tracker ticket was created or assigned. Never imply that it was.
389
+ Reporter-facing replies are one or two sentences: the change or question, its limit, and the next step. Do not explain workflow mechanics, ledgers, or external tracker tickets. Never send **Open**/**pending** or a symptom recap. Ask for reporter input only when it changes the next step; never request supplied evidence or internal access. Keep hashes, branches, CI, publisher, and run details in the recap.
390
+
391
+ Every root this actor marks with `πŸ‘€`, `βœ…`, or `🎫` also needs the current
392
+ same-actor status reply required by the Slack reaction reference. One concise
393
+ reply may serve both purposes when it answers the reporter and records the
394
+ current disposition, verified/open scopes, owner/action, and any reason a mark
395
+ is absent. This status requirement takes precedence over the reporter-facing
396
+ brevity rule only for those facts; do not explain ledger mechanics or external
397
+ tracker tickets. A Slack `🎫` reaction tracks a distinct human action, not an
398
+ external ticket, and remains as history after the action closes. Never imply
399
+ that an external ticket was created or assigned.
328
400
 
329
401
  Keep publisher and beta checks in the internal recap after the disposition;
330
402
  they do not trigger a second reporter reply.
@@ -3,15 +3,27 @@
3
3
  Use this procedure before investigating, claiming, or using a CI rollup in
4
4
  place of Slack source messages.
5
5
 
6
+ At the start of a sweep, capture one Slack timestamp as `windowEndTs`. Use it
7
+ as the inclusive `before` argument on the source scan and all three `hasmy`
8
+ ownership searches in every scoped channel. Use the same inclusive `after` on
9
+ the source scan and ownership searches. Record the bounds in `scanQuery` and
10
+ each owned search's `query`. Keep `after` fixed through the sweep. Keep the
11
+ exact upper bound for the whole sweep; page-cap recovery slices may narrow only
12
+ `before` using the existing one-microsecond boundary rule. Messages newer than
13
+ `windowEndTs` belong to the next scheduled sweep. Never advance either bound to
14
+ chase arrivals or expand a finite catch-up request into an all-history audit.
15
+
6
16
  - For the exhaustive channel source scan, use `slack_search_public` with only
7
- `filters=in:<#CHANNEL>`, `after=<channel cutoff>`, `keywords=[]`, an empty
17
+ `filters=in:<#CHANNEL>`, `after=<channel cutoff>`,
18
+ `before=<windowEndTs>`, `keywords=[]`, an empty
8
19
  `natural_language_query`, `include_bots=true`, `include_context=false`,
9
20
  `content_types=messages`, `limit=20`, `sort=timestamp`, `sort_dir=desc`, and
10
21
  `response_format=detailed`. Do not add lexical, semantic, date, author, or
11
22
  reaction filters: they silently hide reports. Record these exact arguments
12
23
  under that channel's `scanQuery`; the inventory checker rejects a missing or
13
- narrowed query. `after` is inclusive and must equal that channel's effective
14
- cutoff. Follow every `next_cursor` newest-first to Slack exhaustion, even
24
+ narrowed query. `after` and `before` are inclusive and must equal that
25
+ channel's effective lower cutoff and the sweep's fixed upper bound. Follow
26
+ every `next_cursor` newest-first to Slack exhaustion, even
15
27
  after reaching the cutoff, because later pages can contain replies to older
16
28
  roots. Page and
17
29
  source-row timestamps must descend across the full chain. This is separate
@@ -50,9 +62,18 @@ place of Slack source messages.
50
62
  `ts >= effectiveCutoffTs`, and record the exact `oldest` in `oldestQuery`.
51
63
  - Record `scope.channelIds` as the exact IDs requested and scanned. The default
52
64
  sweep lists all three default feedback channels. For an explicitly narrowed
53
- invocation, inspect the active user request itself; it must explicitly limit
54
- scope and name every selected channel by exact ID or `#channel-name`. A
55
- permalink or time anchor does not narrow channel scope. Ask the user if the
65
+ invocation, inspect the active user request itself. It can name the exact
66
+ channel set with an explicit limit such as β€œonly” and exact IDs or
67
+ `#channel-name`s. It can also select exactly two channels by linking one
68
+ report permalink in the first channel and directly asking for β€œthe same in
69
+ channel-name” (with or without `#`) in the second; that direct linked-report
70
+ form selects exactly those two channels and does not need an exclusivity
71
+ word. It requires exactly one linked root and one direct β€œsame in” target. In
72
+ that clause only, accept a single adjacent transposition in the channel name
73
+ only when it uniquely resolves to one canonical channel in the requested
74
+ set. Do not infer scope from incidental or quoted channel mentions or a
75
+ channel-name prefix. A permalink or time anchor by itself does not narrow
76
+ channel scope. Ask the user if the
56
77
  requested channels are unclear. Store the exact instruction in
57
78
  `scope.userRequest` and `scope.narrowingJudgment`, then create a separate
58
79
  `.tmp/feedback-scope-confirmation.json` from that active request with
@@ -85,10 +106,12 @@ place of Slack source messages.
85
106
  `administrative` decision, and a human rationale tied to that text. This is
86
107
  an attributed human triage judgment; the checker verifies its presence and
87
108
  source-text linkage but cannot prove the interpretation is correct.
88
- For every classified root, including administrative posts, capture a
109
+ For every unheld classified root, including administrative posts, capture a
89
110
  complete thread read and make `sourceText` exactly match its `rootText`.
90
- Search output can confuse user-authored `Files:` or `Reactions:` lines with
91
- connector metadata, so do not classify from a truncated search excerpt.
111
+ A held root keeps the exact channel-scan `sourceText` and direct reaction
112
+ evidence but has no thread-derived fields. Search output can confuse
113
+ user-authored `Files:` or `Reactions:` lines with connector metadata, so do
114
+ not classify from a truncated search excerpt.
92
115
  For a canonical Slack message permalink, inspect its `thread_ts` query value:
93
116
  if it differs from the message's `ts`, the message is a reply and that value
94
117
  is its root. A self-valued `thread_ts` or a canonical root permalink without
@@ -194,9 +217,9 @@ place of Slack source messages.
194
217
  root message.
195
218
  Read and capture a complete, oldest-first `threadSnapshot` after the final
196
219
  channel page for every in-scope classified root, including administrative
197
- posts, unmarked roots, and roots with no replies. The checker rejects a
198
- classified root without this read or with `rootText` that differs from its
199
- `sourceText`.
220
+ posts, unmarked roots, and roots with no replies, except roots held by the
221
+ reaction ownership boundary below. The checker rejects a classified root
222
+ without this read or with `rootText` that differs from its `sourceText`.
200
223
  Supplemental roots must strictly predate the oldest `sourceTimestamps` entry
201
224
  across every scanned page, as well as `cutoffTs`; a root inside the scanned
202
225
  source range must have its own source-manifest row even when it predates the
@@ -205,8 +228,8 @@ place of Slack source messages.
205
228
  `ts === threadParentTs` and must not be duplicated in `threadRoots`. A
206
229
  supplemental root exists to resolve an in-window reply;
207
230
  its presence does not make the older report a new in-scope claim. Read its
208
- exact root reactions and complete thread, then apply the ownership gate to
209
- the root itself. Do not add `πŸ‘€`, `βœ…`, or `🎫`, or post a status, just because
231
+ exact root reactions, then apply the ownership gate before opening the thread.
232
+ Do not add `πŸ‘€`, `βœ…`, or `🎫`, or post a status, just because
210
233
  an unowned older root appears as context. If this actor already owns a mark
211
234
  on that older root, reconcile its current status and scopes as usual; record
212
235
  another actor's marks as ownership evidence and leave them untouched. Their
@@ -214,29 +237,53 @@ place of Slack source messages.
214
237
  shows an open disposition; a later closed status makes that eye historical.
215
238
  Keep
216
239
  the root disposition and any open scope in the audit row without claiming it.
217
- The inventory checker still requires a complete, oldest-first
218
- `threadSnapshot` for each supplemental root, including unowned roots, so a
219
- reply cannot predate or otherwise contradict its reported root.
220
- - After the regular bounded channel scans, discover this actor's older owned
221
- roots with three separate unbounded `slack_search_public` queries per scoped
222
- channel: `in:<#CHANNEL> hasmy::eyes`,
223
- `in:<#CHANNEL> hasmy::white_check_mark`, and
224
- `in:<#CHANNEL> hasmy::ticket`. Use the same bot-inclusive detailed search
225
- arguments as the channel scan, but omit `after`, `before`, keywords, semantic
226
- search, and all other filters. Follow every cursor to explicit exhaustion. If
227
- Slack caps a cursor before exhaustion, preserve that capped page and record
228
- the exact `page_limit_exceeded` response in that search's
240
+ For an unheld supplemental root, the inventory checker still requires a
241
+ complete, oldest-first `threadSnapshot`, so a reply cannot predate or
242
+ otherwise contradict its reported root.
243
+ - **Held thread:** If the exact root reaction read shows another actor owns any
244
+ workflow mark (`πŸ‘€`, `βœ…`, or `🎫`), or this actor owns `:mag:`, do not open or
245
+ read the thread, inspect reply reactions, react, or reply. Record an
246
+ `ownershipSkip` with the root timestamp and `foreign_workflow_mark` or
247
+ `mag_opt_out`; use `Skipped` and leave thread-derived fields empty. If this
248
+ actor also owns a workflow mark, use `mag_opt_out` and preserve the exact
249
+ direct root reaction-owner record as history. The checker treats it as held
250
+ and requires no thread snapshot or status. Mark every
251
+ channel reply linked to that root with the same skip reference. The checker
252
+ requires direct root reaction evidence and the matching skip reason, but no
253
+ full-thread snapshot. For a linked pre-window root without source text, use
254
+ `unclassified`; do not guess from its in-window reply. This boundary also
255
+ applies when the other actor's eye has no visible status reply: ownership is
256
+ not permission to inspect. The direct root-reaction read must still be fresh
257
+ and after the final source page; it is the only read allowed for a held root.
258
+ Preserve the source's exact file ID, name, and MIME type when its raw search
259
+ result includes `Files:`. If that result omits file metadata, leave the held
260
+ root's `files` field absent to represent unknown; do not record `[]`. For an
261
+ unheld root, use the full thread read to record files and require the source
262
+ row and snapshot to agree.
263
+ - After each bounded channel scan, run three separate bounded ownership
264
+ searches for this actor's `πŸ‘€`, `βœ…`, and `🎫` with `slack_search_public`. Each
265
+ query uses only `in:<#CHANNEL> hasmy::EMOJI:` plus the same inclusive `after`
266
+ and `before` as the source scan. These searches cover only this invocation's
267
+ finite window; do not search unrelated channel history. An older root is in
268
+ scope only when an in-window reply links to it. Resolve that reply's true
269
+ parent, read the older root's exact reactions, and apply the held-thread gate
270
+ before opening it. Reconcile every candidate in the window and each linked
271
+ older root; unheld roots need exact reaction and full-thread evidence in the
272
+ source or supplemental-root audit. A candidate missing either audit is
273
+ unresolved, not outside scope.
274
+ If Slack caps a cursor before exhaustion, preserve that capped page and
275
+ record the exact `page_limit_exceeded` response in that search's
229
276
  `pageLimitRecoveries[]`, including its failed cursor and request args, raw
230
277
  response, read time, and the capped page's oldest result as `boundaryTs`.
231
- Restart the same exact `hasmy` query at `cursor: null` with an inclusive
232
- `before` exactly one microsecond below that boundary. Keep that `before` on
233
- every page in the restarted cursor slice and in each raw request wrapper; the
234
- base `filters` query remains unchanged. Follow the restarted slice to
235
- exhaustion, and repeat this recovery for every later cap. The checker proves
236
- each failed cursor against exactly one restarted slice, checks the boundary
237
- and read order, and requires explicit exhaustion. If Slack does not let you
238
- recover a capped tail, report the historical ownership search as incomplete;
239
- do not claim every owned mark was reconciled.
278
+ Restart the same `hasmy` query at `cursor: null` with the same fixed `after`
279
+ and a `before` exactly one microsecond below that boundary. Keep both bounds
280
+ on every page in the restarted cursor slice and in each raw request wrapper.
281
+ Follow the restarted slice to exhaustion and repeat this recovery for every
282
+ later cap. The checker proves each failed cursor against exactly one
283
+ restarted slice, checks the boundary and read order, and requires explicit
284
+ exhaustion. If Slack does not let you recover a capped tail, report the
285
+ bounded ownership search as incomplete; do not claim every in-window owned
286
+ mark was reconciled.
240
287
  Record each exact query and page in `ownedMarkSearches`; save raw wrappers
241
288
  named `owned-marks-{channelName}-{eyes|white_check_mark|ticket}-00.json` and
242
289
  continue numbering for each cursor page. Each wrapper preserves exact
@@ -252,16 +299,19 @@ place of Slack source messages.
252
299
  evidence; if the exact read fails or is incomplete, leave ownership
253
300
  unresolved rather than treating the query as confirmation. A result with
254
301
  `ts !== root` is reply-level evidence: resolve and preserve its parent, but
255
- never count it as a root checkoff. Verify reply reactions from the exact
256
- reply read-back in its complete thread snapshot; if that historical reply
257
- cannot be read back, report reply-level reconciliation as incomplete.
258
- Deduplicate confirmed roots across the three searches. For any
259
- exact-readback-confirmed root missing from the bounded source pages and
260
- `threadRootAudits`, add a
261
- full root audit row to `ownedRootAudits`; an in-scope root must instead
262
- appear in the source pages. Reconcile each discovered root's latest human
263
- message, status reply, scopes, and exact current reaction state. Read its
264
- full thread after the final ownership-search page and preserve every raw
302
+ never count it as a root checkoff. For an unheld root, verify reply reactions
303
+ from the exact reply read-back in its complete thread snapshot; if that
304
+ historical reply cannot be read back, report reply-level reconciliation as
305
+ incomplete. For a held root, preserve the reply parent and matching skip
306
+ reason without opening the thread.
307
+ Deduplicate confirmed roots across the three searches. Every in-window root
308
+ candidate must appear in the source pages. An older root linked by an
309
+ in-window reply belongs in `threadRoots` and `threadRootAudits`. If a bounded
310
+ search hit has no matching source row or linked supplemental root, rerun the
311
+ source scan and include the missing evidence; do not invent an audit row.
312
+ Reconcile each unheld discovered root's latest human message, status reply,
313
+ scopes, and exact current reaction state. Read its full thread after the final
314
+ ownership-search page and preserve every raw
265
315
  `slack_read_thread` page under `thread-{channelName}-{rootTs}-00.json` and
266
316
  contiguous page numbers. Each wrapper records exact request `args`,
267
317
  requested `cursor`, raw response, and `readAt`; the raw response must
@@ -387,9 +437,13 @@ place of Slack source messages.
387
437
  and every source `{ ts, root }` row with the reviewed inventory and raw Slack
388
438
  responses. If it finds a mismatch,
389
439
  rebuild the inventory from the captured Slack pages and rerun the check; never
390
- edit the receipt to fit the inventory. Each receipt page must also be fresh
391
- within the checker's 15-minute window, so repeat the complete channel scan and
392
- required final thread/reaction reads before recap when that window expires.
440
+ edit the receipt to fit the inventory. Source and ownership-search pages are
441
+ immutable evidence for the fixed `after`/`before` interval; their age does not
442
+ restart the sweep. The 15-minute freshness window applies to current root and
443
+ reply reaction reads and full-thread snapshots. If one expires, reread only
444
+ that exact state. After one targeted retry, if a required current-state read
445
+ is still stale or unavailable, mark the inventory incomplete and stop; do
446
+ not restart the channel-wide search or loop.
393
447
  It rejects any root mark this actor owns without a current same-actor
394
448
  status reply, a
395
449
  assigned-human handoff without that person's explicit acceptance, an
@@ -434,19 +488,18 @@ verified disposition may retain the eye as history. Never remove or clean up a
434
488
  reaction. Slack thread replies appear under the root's reply count, not as
435
489
  separate channel posts; do not broadcast routine status replies.
436
490
 
437
- After any long investigation, CI/deploy wait, or handoff, a newest-first scan
438
- from the saved high-water mark can identify new activity. Immediately before
439
- recap, however, rerun the exact original channel-wide `slack_search_public`
440
- query for every scoped channel, including its recorded `scanQuery.after`, and
441
- follow every cursor to Slack exhaustion. Replace the saved page responses,
442
- timestamps, source manifests, and source rows. Do not
443
- append a delta to an older page chain or refresh its `readAt` without rereading
444
- the page. Resolve every newly found reply to its actual root, refresh root
445
- reactions, and reconcile human answers before replacement. Then reopen every
446
- marked root thread after the last channel page, read all replies, and refresh
447
- reply-level workflow reaction owners. If this final scan or any thread read is
448
- older than 15 minutes, repeat the full bounded scan and marked-thread reads
449
- before checking the inventory. Only a complete fresh pass can certify coverage.
491
+ After a long investigation, CI/deploy wait, or handoff, do one final exhaustive
492
+ pass of the exact original channel-wide query for every scoped channel, using
493
+ the same recorded `scanQuery.after` and `scanQuery.before`. Replace the saved
494
+ page responses, timestamps, source manifests, and source rows; never append a
495
+ delta or refresh `readAt` without rereading the page. This is one bounded
496
+ reconciliation pass, not a moving high-water mark. Resolve newly found replies
497
+ to their actual roots and reconcile them inside the fixed interval. Then reread
498
+ current root reactions and full threads for marked roots after the final source
499
+ and ownership-search pages. If an exact current-state read expires, reread that
500
+ target once. If it still cannot pass freshness, mark coverage incomplete and
501
+ stop; do not restart the global scan. Activity newer than `windowEndTs` is for
502
+ the next scheduled sweep.
450
503
 
451
504
  Channel history does not include the contents of existing thread replies. In
452
505
  that final pass, also enumerate every distinct root thread carrying this
@@ -492,21 +545,22 @@ read time as `reactionReadAt`; when this sweep writes a reaction, record
492
545
  also requires each owned root reaction read and the full-thread reaction reads
493
546
  to follow the final channel page.
494
547
 
495
- The normal inventory check also requires every source, owned-mark search, and
496
- thread page or snapshot to have been read within the last 15 minutes, allows at
497
- most one minute of local clock skew, and requires owned-root reaction and thread
498
- read-backs to follow all three exhaustive mark searches. An old, complete
499
- inventory is stale: rerun the channel scan and historical mark searches, then
500
- read every marked root thread again before recap. This freshness gate prevents
501
- a complete earlier scan from silently omitting reports or replies that arrived
502
- during investigation.
548
+ The inventory checker requires source pages to use one recorded inclusive
549
+ `after`/`before` window and ownership-census pages to use the fixed `before`
550
+ upper bound with no `after`. Both page chains must remain in cursor order and
551
+ have valid local ISO read times. Their age does not invalidate those fixed
552
+ bounds. Current root and reply reaction reads and full-thread snapshots must be
553
+ within 15 minutes, allow at most one minute of local clock skew, and follow the
554
+ final source and ownership-search pages. Refresh only a stale current-state
555
+ target; if one targeted reread is still stale or unavailable, mark coverage
556
+ incomplete and stop instead of repeating the full sweep.
503
557
 
504
558
  The inventory checker requires a complete `threadSnapshot` for every in-scope
505
- classified root, every supplemental root included to resolve an in-scope reply,
506
- and every older root discovered by the exhaustive owned-mark searches. It checks
507
- raw thread page cursor exhaustion, Slack's reply count, exact source text,
508
- attachments, and snapshot order, and rejects a newer human or sweep reply than
509
- the recorded status. A failed check means the sweep is not reconciled.
559
+ classified root and every supplemental root included to resolve an in-scope
560
+ reply, except roots recorded under the held-thread boundary. For unheld roots it
561
+ checks raw thread page cursor exhaustion, Slack's reply count, exact source
562
+ text, attachments, and snapshot order, and rejects a newer human or sweep reply
563
+ than the recorded status. A failed check means the sweep is not reconciled.
510
564
 
511
565
  A grouped CI report or attachment is a discovery index, never a replacement for
512
566
  the channel-message inventory.
@@ -2,12 +2,19 @@
2
2
 
3
3
  Use the connected Slack integration for every Slack read and write. Never use a browser, screenshots, or UI automation to operate Slack.
4
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
5
+ At sweep exit, reconcile every unheld report root in the scoped inventory that
6
+ has this task's `πŸ‘€`, `βœ…`, or `🎫`, including marks left by earlier runs. A
7
+ root is held when a direct reaction read shows another actor owns any workflow
8
+ mark or this actor owns `:mag:`. For held roots, record the root and safe-skip
9
+ reason, then stop: do not open the thread, inspect reply reactions, write any
10
+ reaction, or post a status. When this actor owns both `:mag:` and a workflow
11
+ mark, use `mag_opt_out` and preserve the exact direct root reaction-owner
12
+ record as history; the root remains held without a thread read or status reply.
13
+ Capture
7
14
  the exact Slack actor ID used by the connected tool at sweep start. A mark is
8
15
  owned by this task only when its reaction owner ID matches that actor ID. Each
9
- owned root needs a current disposition and a matching status reply by that same
10
- actor under that same parent, even when the root has only `βœ…` or `🎫`. A
16
+ unheld owned root needs a current disposition and a matching status reply by
17
+ that same actor under that same parent, even when the root has only `βœ…` or `🎫`. A
11
18
  same-run fix gets its final reply after verification; continuing work gets a
12
19
  concrete progress reply. An earlier generic β€œtaking a look” does not satisfy
13
20
  this update. If an existing reply already names the exact canonical disposition
@@ -30,6 +37,23 @@ includes human handoffs, the status reply must name each handoff's recorded
30
37
  owner and action; include its scope when the report context does not make it
31
38
  clear.
32
39
 
40
+ Interpret the three root reactions independently from the current disposition:
41
+
42
+ - `πŸ‘€` is a permanent claim/history mark, not an open/closed indicator.
43
+ - `βœ…` is permanent history for named scopes that passed direct post-change
44
+ proof. Add it as soon as any scope passes; do not wait for other scopes or
45
+ merge. Keep it if later evidence changes the scope's status, and explain the
46
+ historical mark in the current reply and inventory.
47
+ - `🎫` is permanent history for a distinct human action, including an
48
+ unassigned team action or reporter request. Keep it after completion,
49
+ answer, or four-day abandonment; label it historical in the current reply
50
+ and inventory when it no longer blocks progress.
51
+
52
+ The current status reply is the source for whether work remains open. When a
53
+ root has only `πŸ‘€`, say that no scope has passed the proof bar for `βœ…`, whether
54
+ the next step belongs to this workflow, and why no separate human action needs
55
+ `🎫`. Reuse a current reply rather than posting again on every sweep.
56
+
33
57
  An evidence-only addendum or a correction is not a current status. When your
34
58
  newest own reply is one of those, edit that reply into the full canonical
35
59
  disposition when possible; otherwise send one final status after the latest
@@ -101,10 +125,13 @@ slack_search: "this was sent from a bot." in:<#CHANNEL>
101
125
  sort=timestamp sort_dir=asc include_context=true max_context_length=300
102
126
  ```
103
127
 
104
- Also scan channel messages from the saved reply cursor; on the first run, scan
105
- available history. Since `after:` is date-only, start one day before the cursor,
106
- exhaust pages, resolve hits to their root, and advance the cursor only through
107
- processed messages. Read inherited threads carrying any of this actor's root marks (`πŸ‘€`, `βœ…`,
128
+ Also scan channel messages from the saved reply cursor within the current
129
+ invocation's fixed window; on the first run, use the default five-day window,
130
+ not all available history. Since `after:` is date-only, start one day before
131
+ the later of the saved cursor and window start, exhaust pages, resolve hits to
132
+ their root, and advance the cursor only through processed messages. An older
133
+ root is eligible only when an in-window reply links to it. Read inherited
134
+ threads carrying any of this actor's root marks (`πŸ‘€`, `βœ…`,
108
135
  or `🎫`): a checkmark closes only its verified scope, not a newer reporter
109
136
  answer or another open scope. Do not reply or react on another actor's root when work is handed off for investigation; that handoff permits local investigation only. A separate explicit authorization is required to post a status or reporter reply there.
110
137
 
@@ -130,9 +157,10 @@ human/team actions, including actions with no named owner yet. The eye alone
130
157
  does not mean work remains open. One root `βœ…` covers every verified scope
131
158
  listed in `fixedScopes[]`; one root `🎫` covers every action listed in
132
159
  `handoffs[]`, including unassigned, accepted, and completed actions. Completion
133
- evidence changes its status, not the historical `🎫` reaction. For every root
134
- carrying any of this actor's marks, post or reuse a current status in that root's thread;
135
- unclaimed items and another actor's marks are not ours to reply to. Reconcile
160
+ evidence changes its status, not the historical `🎫` reaction. For every
161
+ unheld root carrying any of this actor's marks, post or reuse a current status
162
+ in that root's thread; unclaimed items, held roots, and another actor's marks
163
+ are not ours to reply to. Reconcile
136
164
  each report as one unit: send or verify its current same-thread status reply,
137
165
  then immediately update and read back each applicable root reaction before
138
166
  advancing to the next report. Add `βœ…` as soon as any reported scope passes the
@@ -95,9 +95,9 @@ MCP clients see no `outputSchema` by default. Set `mcpOutputSchema: true` (requi
95
95
 
96
96
  `authorize` decides whether the caller may run the action at all. It wraps
97
97
  `run`, so it holds at **all six dispatch sites** β€” agent tool, HTTP, frontend
98
- hook, MCP, A2A, CLI β€” unlike `needsApproval`, which is honoured only in the
99
- agent loop. Use `authorize` for "not everyone may do this"; use `needsApproval`
100
- to ask a human to bless one call a permitted caller is already allowed to make.
98
+ hook, MCP, A2A, CLI. Use `authorize` for "not everyone may do this"; use
99
+ `needsApproval` to ask a human to bless one call a permitted caller is already
100
+ allowed to make.
101
101
 
102
102
  ```ts
103
103
  import { coachAccess } from "../lib/access.js"; // defineAppRoles(...)
@@ -140,4 +140,6 @@ export default defineAction({
140
140
 
141
141
  When the gate is truthy and the call isn't yet approved, the loop emits an `approval_required` event and **stops the turn β€” `run()` never executes**. A predicate gates conditionally (e.g. only external recipients) and **fails closed**: a throw is treated as "approval required". It receives its own copy of the prepared snapshot of the validated arguments, defaults applied, and the approval binds to the snapshot's hash, so an approved call runs only with the arguments that were approved (a Date binds by its time). The human approves via the chat UI's Approve affordance, which re-issues the turn with the call's `approvalKey`, and only then does the action run.
142
142
 
143
+ Approval is given in the app, so HTTP never grants it. The HTTP action route and the generated Workers entry run a gated call only for the proven app UI (a frontend call from the signed-in user, same origin, holding the short-lived UI capability; `isProvenUiRequest`, the proof `uiOnly` uses) or the app's MCP directory widget. Every other HTTP caller (bearer or API key, MCP OAuth token, org service token, A2A, extension iframe, mobile app) gets `409 approval_required`, and a service identity can never approve. The shared client presents the capability with every write from a signed-in session (minted once per user, reused for nine minutes, never for reads or public pages), so a gated button works on its first request. A call without proof gets `403 ui_capability_required`; the client then re-mints and retries once, which is only the fallback for an expired or rejected capability. Every surface evaluates the gate through `preparedCallNeedsApproval`, so a throwing predicate asks there too. A caller that must run unattended needs a different action, never a bypass of the gate.
144
+
143
145
  **Keep approvals rare** β€” the default is off and almost every action should leave it off. The canonical example is Mail's `send-email` (`needsApproval: true`). See the `security` skill and the Human Approval doc.