@agent-native/core 0.214.0 → 0.216.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.
Files changed (161) hide show
  1. package/dist/action.js +54 -1
  2. package/dist/agent/actions/disconnect-chatgpt-subscription.js +5 -0
  3. package/dist/agent/actions/get-agentkit-capabilities.js +3 -3
  4. package/dist/agent/actions/manage-builder-connection.js +5 -0
  5. package/dist/agent/actions/manage-provider-key-policy.js +5 -0
  6. package/dist/agent/actions/manage-provider-models.js +5 -0
  7. package/dist/agent/actions/manage-service-providers.js +5 -0
  8. package/dist/agent/actions/select-chatgpt-subscription-account.js +5 -0
  9. package/dist/agent/context-xray/actions/context-evict.js +5 -0
  10. package/dist/agent/context-xray/actions/context-pin.js +5 -0
  11. package/dist/agent/context-xray/actions/context-report.js +5 -0
  12. package/dist/agent/context-xray/actions/context-restore.js +5 -0
  13. package/dist/agent/execution-fence.d.ts +1 -0
  14. package/dist/agent/execution-fence.js +5 -0
  15. package/dist/agent/production-agent.d.ts +35 -2
  16. package/dist/agent/production-agent.js +629 -32
  17. package/dist/agent/run-manager.d.ts +2 -0
  18. package/dist/agent/run-manager.js +397 -115
  19. package/dist/agent/run-slot-lock.d.ts +2 -0
  20. package/dist/agent/run-slot-lock.js +6 -0
  21. package/dist/agent/run-store.d.ts +5 -0
  22. package/dist/agent/run-store.js +423 -156
  23. package/dist/agent/thread-data-builder.js +27 -5
  24. package/dist/agent/types.d.ts +6 -12
  25. package/dist/agentkit/capabilities.d.ts +1 -1
  26. package/dist/application-state/store.d.ts +5 -0
  27. package/dist/application-state/store.js +26 -0
  28. package/dist/chat-threads/schema-migrations.js +17 -0
  29. package/dist/chat-threads/store.d.ts +21 -1
  30. package/dist/chat-threads/store.js +223 -41
  31. package/dist/cli/skills-content/turn-into-app-skill.d.ts +1 -1
  32. package/dist/cli/skills-content/turn-into-app-skill.js +20 -18
  33. package/dist/cli/templates-meta.js +14 -0
  34. package/dist/client/analytics.js +167 -17
  35. package/dist/client/chat/agentkit-agent-native.js +79 -3
  36. package/dist/client/chat/agentkit-protocol.js +80 -42
  37. package/dist/client/chat/runtime.d.ts +5 -1
  38. package/dist/client/chat/runtime.js +46 -3
  39. package/dist/client/consent.d.ts +36 -0
  40. package/dist/client/consent.js +283 -0
  41. package/dist/client/review/use-review.d.ts +2 -0
  42. package/dist/client/review/use-review.js +28 -1
  43. package/dist/client/sharing/useResourceAccessGate.d.ts +3 -2
  44. package/dist/client/sharing/useResourceAccessGate.js +9 -1
  45. package/dist/client/use-chat-thread-list.d.ts +2 -0
  46. package/dist/client/use-chat-thread-list.js +12 -2
  47. package/dist/client/use-chat-threads.d.ts +1 -0
  48. package/dist/client/use-chat-threads.js +2 -0
  49. package/dist/collab/awareness.d.ts +2 -2
  50. package/dist/collab/routes.d.ts +2 -2
  51. package/dist/collab/struct-routes.d.ts +1 -1
  52. package/dist/connections/catalog.d.ts +5 -5
  53. package/dist/connections/catalog.js +22 -3
  54. package/dist/data-widgets/index.d.ts +36 -2
  55. package/dist/data-widgets/index.js +94 -6
  56. package/dist/db/migrations.js +4 -1
  57. package/dist/db/schema-snapshot.generated.js +8 -1
  58. package/dist/feature-flags/actions/set-feature-flag.js +5 -0
  59. package/dist/feature-flags/index.d.ts +1 -1
  60. package/dist/feature-flags/index.js +1 -1
  61. package/dist/feature-flags/store.d.ts +12 -0
  62. package/dist/feature-flags/store.js +79 -7
  63. package/dist/file-upload/actions/manage-file-storage.js +5 -0
  64. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  65. package/dist/file-upload/actions/upload-image.js +5 -0
  66. package/dist/history/actions/create-resource-version.js +5 -0
  67. package/dist/history/actions/restore-resource-version.js +5 -0
  68. package/dist/ingestion/index.d.ts +1 -1
  69. package/dist/ingestion/index.js +1 -1
  70. package/dist/ingestion/pptx.d.ts +11 -0
  71. package/dist/ingestion/pptx.js +81 -1
  72. package/dist/integrations/actions/manage-messaging-channel.js +5 -0
  73. package/dist/jobs/scheduler.js +17 -1
  74. package/dist/labs/actions/set-lab.js +5 -0
  75. package/dist/localization/actions/set-localization-preference.js +5 -0
  76. package/dist/observability/actions/save-observability-instruction-update.js +5 -0
  77. package/dist/observability/actions/save-observability-review-summary.js +5 -0
  78. package/dist/progress/routes.d.ts +1 -1
  79. package/dist/provider-api/actions/custom-provider-registration.d.ts +18 -18
  80. package/dist/provider-api/actions/provider-api.d.ts +14 -14
  81. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  82. package/dist/provider-api/index.js +9 -2
  83. package/dist/review/actions/consume-review-feedback.js +5 -0
  84. package/dist/review/actions/create-review-comment.js +5 -0
  85. package/dist/review/actions/delete-review-comment.js +5 -0
  86. package/dist/review/actions/react-to-review-comment.js +5 -0
  87. package/dist/review/actions/reply-review-comment.js +5 -0
  88. package/dist/review/actions/resolve-review-thread.js +5 -0
  89. package/dist/review/actions/send-review-thread-to-agent.js +5 -0
  90. package/dist/review/actions/set-review-status.js +5 -0
  91. package/dist/review/actions/set-review-thread-muted.js +5 -0
  92. package/dist/review/actions/set-review-thread-unread.js +5 -0
  93. package/dist/review/actions/set-review-threads-unread.js +5 -0
  94. package/dist/review/actions/update-review-comment-anchor.js +5 -0
  95. package/dist/review/actions/update-review-comment.js +5 -0
  96. package/dist/review/suggestions/actions.js +60 -21
  97. package/dist/review/suggestions/store.d.ts +2 -0
  98. package/dist/review/suggestions/store.js +28 -0
  99. package/dist/secrets/actions/delete-api-key.js +5 -0
  100. package/dist/secrets/routes.d.ts +6 -6
  101. package/dist/server/agent-chat/context-tools.js +63 -2
  102. package/dist/server/agent-chat-plugin.d.ts +2 -0
  103. package/dist/server/agent-chat-plugin.js +242 -54
  104. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  105. package/dist/server/agent-engine-default-model-route.d.ts +2 -2
  106. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  107. package/dist/server/application-state-maintenance.d.ts +2 -0
  108. package/dist/server/application-state-maintenance.js +2 -0
  109. package/dist/server/attribution.d.ts +14 -0
  110. package/dist/server/attribution.js +62 -2
  111. package/dist/server/auth.js +7 -0
  112. package/dist/server/consent-route.d.ts +41 -0
  113. package/dist/server/consent-route.js +102 -0
  114. package/dist/server/first-touch-route.d.ts +39 -0
  115. package/dist/server/first-touch-route.js +115 -0
  116. package/dist/server/index.d.ts +1 -0
  117. package/dist/server/index.js +1 -0
  118. package/dist/server/queued-submission-inbox-drain.d.ts +9 -0
  119. package/dist/server/queued-submission-inbox-drain.js +12 -0
  120. package/dist/server/request-origin.d.ts +6 -0
  121. package/dist/server/request-origin.js +12 -0
  122. package/dist/settings/org-settings.d.ts +2 -1
  123. package/dist/settings/org-settings.js +4 -1
  124. package/dist/settings/store.d.ts +8 -0
  125. package/dist/settings/store.js +14 -5
  126. package/dist/shared/consent.d.ts +31 -0
  127. package/dist/shared/consent.js +145 -0
  128. package/dist/shared/first-touch-persistence.d.ts +31 -0
  129. package/dist/shared/first-touch-persistence.js +53 -0
  130. package/dist/sharing/actions/set-resource-visibility.js +5 -0
  131. package/dist/sharing/actions/share-resource.js +5 -0
  132. package/dist/sharing/actions/unshare-resource.js +5 -0
  133. package/dist/templates/chat/.agents/skills/actions/references/action-fields.md +1 -1
  134. package/dist/templates/default/.agents/skills/actions/references/action-fields.md +1 -1
  135. package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +36 -80
  136. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +54 -16
  137. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +50 -73
  138. package/dist/templates/headless/.agents/skills/actions/references/action-fields.md +1 -1
  139. package/dist/templates/workspace-core/.agents/skills/actions/references/action-fields.md +1 -1
  140. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +20 -18
  141. package/dist/triggers/actions/manage-automation.d.ts +1 -1
  142. package/dist/usage/actions/get-usage-metrics.js +27 -4
  143. package/dist/usage/actions/manage-usage-alert.js +5 -0
  144. package/dist/vite/client.js +8 -6
  145. package/dist/workspace-connections/actions/bulk-update-workspace-user-groups.js +5 -0
  146. package/dist/workspace-connections/actions/delete-workspace-user-group.js +5 -0
  147. package/dist/workspace-connections/actions/upsert-workspace-user-group.js +5 -0
  148. package/docs/content/actions-access-control.mdx +8 -8
  149. package/docs/content/actions-defining.mdx +9 -8
  150. package/docs/content/template-design-developers.mdx +9 -9
  151. package/docs/content/template-design-features.mdx +1 -3
  152. package/docs/content/turn-into-app.mdx +4 -0
  153. package/package.json +4 -3
  154. package/src/templates/chat/.agents/skills/actions/references/action-fields.md +1 -1
  155. package/src/templates/default/.agents/skills/actions/references/action-fields.md +1 -1
  156. package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +36 -80
  157. package/src/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +54 -16
  158. package/src/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +50 -73
  159. package/src/templates/headless/.agents/skills/actions/references/action-fields.md +1 -1
  160. package/src/templates/workspace-core/.agents/skills/actions/references/action-fields.md +1 -1
  161. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +20 -18
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Cookie consent for the regions that need it. The regions and categories
3
+ * follow builder.io's consent banner: visitors in these countries opt in, and
4
+ * every other visitor is tracked as before, with no banner.
5
+ */
6
+ /**
7
+ * Countries that see the banner: builder.io's "GDPR" rule, i.e. the EU, the
8
+ * rest of the EEA, Monaco, San Marino, Åland and France's overseas regions.
9
+ * The UK and Switzerland are not on it, matching builder.io.
10
+ */
11
+ const CONSENT_REGION_COUNTRY_CODES = new Set([
12
+ "AT",
13
+ "AX",
14
+ "BE",
15
+ "BG",
16
+ "CY",
17
+ "CZ",
18
+ "DE",
19
+ "DK",
20
+ "EE",
21
+ "ES",
22
+ "FI",
23
+ "FR",
24
+ "GF",
25
+ "GP",
26
+ "GR",
27
+ "HR",
28
+ "HU",
29
+ "IE",
30
+ "IS",
31
+ "IT",
32
+ "LI",
33
+ "LT",
34
+ "LU",
35
+ "LV",
36
+ "MC",
37
+ "MF",
38
+ "MQ",
39
+ "MT",
40
+ "NL",
41
+ "NO",
42
+ "PL",
43
+ "PT",
44
+ "RE",
45
+ "RO",
46
+ "SE",
47
+ "SI",
48
+ "SK",
49
+ "SM",
50
+ "YT",
51
+ ]);
52
+ /** The IANA zones of those countries, for a request with no country header. */
53
+ const CONSENT_REGION_TIME_ZONES = new Set([
54
+ "Africa/Ceuta",
55
+ "America/Cayenne",
56
+ "America/Guadeloupe",
57
+ "America/Marigot",
58
+ "America/Martinique",
59
+ "Asia/Famagusta",
60
+ "Asia/Nicosia",
61
+ "Atlantic/Azores",
62
+ "Atlantic/Canary",
63
+ "Atlantic/Madeira",
64
+ "Atlantic/Reykjavik",
65
+ "Europe/Amsterdam",
66
+ "Europe/Athens",
67
+ "Europe/Berlin",
68
+ "Europe/Bratislava",
69
+ "Europe/Brussels",
70
+ "Europe/Bucharest",
71
+ "Europe/Budapest",
72
+ "Europe/Busingen",
73
+ "Europe/Copenhagen",
74
+ "Europe/Dublin",
75
+ "Europe/Helsinki",
76
+ "Europe/Lisbon",
77
+ "Europe/Ljubljana",
78
+ "Europe/Luxembourg",
79
+ "Europe/Madrid",
80
+ "Europe/Malta",
81
+ "Europe/Mariehamn",
82
+ "Europe/Monaco",
83
+ "Europe/Nicosia",
84
+ "Europe/Oslo",
85
+ "Europe/Paris",
86
+ "Europe/Prague",
87
+ "Europe/Riga",
88
+ "Europe/Rome",
89
+ "Europe/San_Marino",
90
+ "Europe/Sofia",
91
+ "Europe/Stockholm",
92
+ "Europe/Tallinn",
93
+ "Europe/Vaduz",
94
+ "Europe/Vienna",
95
+ "Europe/Vilnius",
96
+ "Europe/Warsaw",
97
+ "Europe/Zagreb",
98
+ "Indian/Mayotte",
99
+ "Indian/Reunion",
100
+ "CET",
101
+ "EET",
102
+ "Eire",
103
+ "Iceland",
104
+ "MET",
105
+ "Poland",
106
+ "Portugal",
107
+ "WET",
108
+ ]);
109
+ export function isConsentRegionCountry(code) {
110
+ return CONSENT_REGION_COUNTRY_CODES.has(code?.trim().toUpperCase() ?? "");
111
+ }
112
+ export function isConsentRegionTimeZone(timeZone) {
113
+ return !!timeZone && CONSENT_REGION_TIME_ZONES.has(timeZone);
114
+ }
115
+ export const CONSENT_COOKIE_NAME = "an_consent";
116
+ /** Asked again a year later, like builder.io. */
117
+ export const CONSENT_COOKIE_LIFETIME_SECONDS = 365 * 24 * 60 * 60;
118
+ export const CONSENT_REGION_PATH = "/_agent-native/auth/consent-region";
119
+ export const CONSENT_PATH = "/_agent-native/auth/consent";
120
+ /**
121
+ * One choice covers the site and every app on agent-native.com. The browser
122
+ * and the server must agree, or the two copies become two cookies.
123
+ */
124
+ export function consentCookieDomain(hostname) {
125
+ const host = hostname.toLowerCase();
126
+ return host === "agent-native.com" || host.endsWith(".agent-native.com")
127
+ ? "agent-native.com"
128
+ : null;
129
+ }
130
+ const CONSENT_COOKIE_VERSION = "1";
131
+ /** `1.p1.t0`: version, then one flag per category. */
132
+ export function serializeConsent(choice) {
133
+ return [
134
+ CONSENT_COOKIE_VERSION,
135
+ `p${choice.performance ? 1 : 0}`,
136
+ `t${choice.targeting ? 1 : 0}`,
137
+ ].join(".");
138
+ }
139
+ /** The stored choice, or `null` when there is none this version can read. */
140
+ export function parseConsent(value) {
141
+ const match = /^1\.p([01])\.t([01])$/.exec(value?.trim() ?? "");
142
+ if (!match)
143
+ return null;
144
+ return { performance: match[1] === "1", targeting: match[2] === "1" };
145
+ }
@@ -0,0 +1,31 @@
1
+ import { type AttributionTouch } from "./attribution-source.js";
2
+ /**
3
+ * The durable copy of first touch. Our server sets it, because Safari caps
4
+ * cookies written by page JavaScript at seven days; the browser's own `an_ft`
5
+ * cookie and localStorage copy stay the immediate, 30-day copies. Signup and
6
+ * the browser both pick between the copies with `preferFirstTouch`, so they
7
+ * agree on which visit is first touch.
8
+ */
9
+ export declare const FIRST_TOUCH_SERVER_COOKIE_NAME = "an_fts";
10
+ export declare const FIRST_TOUCH_SERVER_COOKIE_VERSION = "1";
11
+ export declare const FIRST_TOUCH_SERVER_PATH = "/_agent-native/auth/attribution";
12
+ /** Counted from the first visit, never extended by later ones. */
13
+ export declare const FIRST_TOUCH_SERVER_LIFETIME_SECONDS: number;
14
+ /** `name=value` budget, so this and the browser's cookies fit one header. */
15
+ export declare const FIRST_TOUCH_SERVER_COOKIE_MAX_BYTES = 1500;
16
+ export interface TimedFirstTouch extends AttributionTouch {
17
+ landed_at?: string;
18
+ }
19
+ /**
20
+ * Which of two records is first touch: the first visit that had a source,
21
+ * else the first visit. `incumbent` wins a tie, so a copy never displaces the
22
+ * value it came from.
23
+ */
24
+ export declare function preferFirstTouch<T extends TimedFirstTouch>(incumbent: T | null | undefined, challenger: T | null | undefined): T | null;
25
+ /** Whether two records are the same visit, however each copy was trimmed. */
26
+ export declare function isSameFirstTouch(a: TimedFirstTouch | null | undefined, b: TimedFirstTouch | null | undefined): boolean;
27
+ /**
28
+ * When the durable copy expires: a year after the visit, or `null` once that
29
+ * year is over. A missing or future visit time counts from now.
30
+ */
31
+ export declare function firstTouchServerExpiry(touch: TimedFirstTouch, now?: number): Date | null;
@@ -0,0 +1,53 @@
1
+ import { hasAttributionSource, } from "./attribution-source.js";
2
+ /**
3
+ * The durable copy of first touch. Our server sets it, because Safari caps
4
+ * cookies written by page JavaScript at seven days; the browser's own `an_ft`
5
+ * cookie and localStorage copy stay the immediate, 30-day copies. Signup and
6
+ * the browser both pick between the copies with `preferFirstTouch`, so they
7
+ * agree on which visit is first touch.
8
+ */
9
+ export const FIRST_TOUCH_SERVER_COOKIE_NAME = "an_fts";
10
+ export const FIRST_TOUCH_SERVER_COOKIE_VERSION = "1";
11
+ export const FIRST_TOUCH_SERVER_PATH = "/_agent-native/auth/attribution";
12
+ /** Counted from the first visit, never extended by later ones. */
13
+ export const FIRST_TOUCH_SERVER_LIFETIME_SECONDS = 365 * 24 * 60 * 60;
14
+ /** `name=value` budget, so this and the browser's cookies fit one header. */
15
+ export const FIRST_TOUCH_SERVER_COOKIE_MAX_BYTES = 1500;
16
+ // A record with no readable visit time was trimmed to fit its cookie, so it
17
+ // was stored before any dated record it is compared with.
18
+ function landedAtMs(touch) {
19
+ const time = Date.parse(touch.landed_at ?? "");
20
+ return Number.isFinite(time) ? time : Number.NEGATIVE_INFINITY;
21
+ }
22
+ /**
23
+ * Which of two records is first touch: the first visit that had a source,
24
+ * else the first visit. `incumbent` wins a tie, so a copy never displaces the
25
+ * value it came from.
26
+ */
27
+ export function preferFirstTouch(incumbent, challenger) {
28
+ if (!incumbent)
29
+ return challenger ?? null;
30
+ if (!challenger)
31
+ return incumbent;
32
+ const incumbentSourced = hasAttributionSource(incumbent);
33
+ if (incumbentSourced !== hasAttributionSource(challenger)) {
34
+ return incumbentSourced ? incumbent : challenger;
35
+ }
36
+ return landedAtMs(challenger) < landedAtMs(incumbent)
37
+ ? challenger
38
+ : incumbent;
39
+ }
40
+ /** Whether two records are the same visit, however each copy was trimmed. */
41
+ export function isSameFirstTouch(a, b) {
42
+ return !!a?.landed_at && a.landed_at === b?.landed_at;
43
+ }
44
+ /**
45
+ * When the durable copy expires: a year after the visit, or `null` once that
46
+ * year is over. A missing or future visit time counts from now.
47
+ */
48
+ export function firstTouchServerExpiry(touch, now = Date.now()) {
49
+ const landed = Date.parse(touch.landed_at ?? "");
50
+ const start = Number.isFinite(landed) && landed <= now ? landed : now;
51
+ const expires = start + FIRST_TOUCH_SERVER_LIFETIME_SECONDS * 1000;
52
+ return expires > now ? new Date(expires) : null;
53
+ }
@@ -29,6 +29,11 @@ export default defineAction({
29
29
  const access = await assertAccess(args.resourceType, args.resourceId, "admin", undefined, { skipResourceBody: true });
30
30
  return access.resource.visibility !== "public";
31
31
  },
32
+ mcpAnnotations: {
33
+ readOnlyHint: false,
34
+ destructiveHint: true,
35
+ openWorldHint: true,
36
+ },
32
37
  run: async (args, ctx) => {
33
38
  assertWidgetShareWriteGrant(ctx, "set-resource-visibility", args);
34
39
  const reg = requireShareableResource(args.resourceType);
@@ -124,6 +124,11 @@ export default defineAction({
124
124
  .describe("Optional short note included in the notification email to an individual recipient."),
125
125
  }),
126
126
  needsApproval: needsExternalShareApproval,
127
+ mcpAnnotations: {
128
+ readOnlyHint: false,
129
+ destructiveHint: true,
130
+ openWorldHint: true,
131
+ },
127
132
  run: async (args, ctx) => {
128
133
  assertWidgetShareWriteGrant(ctx, "share-resource", args);
129
134
  const reg = requireShareableResource(args.resourceType);
@@ -26,6 +26,11 @@ export default defineAction({
26
26
  principalType: z.enum(["user", "group", "org"]),
27
27
  principalId: z.string(),
28
28
  }),
29
+ mcpAnnotations: {
30
+ readOnlyHint: false,
31
+ destructiveHint: true,
32
+ openWorldHint: false,
33
+ },
29
34
  run: async (args, ctx) => {
30
35
  assertWidgetShareWriteGrant(ctx, "unshare-resource", args);
31
36
  const reg = requireShareableResource(args.resourceType);
@@ -140,6 +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.
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 direct HTTP caller (bearer or API key, MCP OAuth token, org service token, extension iframe, or mobile app) gets `409 approval_required`, and a service identity can never approve. A2A tasks use their separate approval flow. The task pauses until its owner approves, then replays the approved tool call under its approval lease. 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. The agent loop, plan mode registry, HTTP action route, generated Workers entry, and MCP dispatch evaluate the gate through `preparedCallNeedsApproval`, so a throwing predicate asks there too. The local `pnpm action ...` CLI calls `handler.run()` directly and does not evaluate `needsApproval`. Treat it as a local development tool, not an approval boundary. A caller that must run unattended needs a different action, never a bypass of the gate.
144
144
 
145
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.
@@ -140,6 +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.
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 direct HTTP caller (bearer or API key, MCP OAuth token, org service token, extension iframe, or mobile app) gets `409 approval_required`, and a service identity can never approve. A2A tasks use their separate approval flow. The task pauses until its owner approves, then replays the approved tool call under its approval lease. 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. The agent loop, plan mode registry, HTTP action route, generated Workers entry, and MCP dispatch evaluate the gate through `preparedCallNeedsApproval`, so a throwing predicate asks there too. The local `pnpm action ...` CLI calls `handler.run()` directly and does not evaluate `needsApproval`. Treat it as a local development tool, not an approval boundary. A caller that must run unattended needs a different action, never a bypass of the gate.
144
144
 
145
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.
@@ -21,25 +21,15 @@ 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 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.
24
+ Output is fixes. For every unheld in-scope report root this task owns, keep one
25
+ current same-actor status reply after the latest human message. Write one short
26
+ sentence in plain, non-technical language. Say what changed. Use `🎫` only for
27
+ unresolved work; when you use it, briefly say what still needs investigation or
28
+ what action is needed, and name its owner when known. Do not mention or explain
29
+ absent reactions. Keep scope details and proof in the internal ledger. Add and
30
+ read back root reactions as soon as their proof bar passes, preserve reaction
31
+ history, and follow [`slack-reaction-replies.md`](references/slack-reaction-replies.md).
32
+ Do not reply on held, person-assigned, or `:mag:` items.
43
33
 
44
34
  ## Slack channels and ownership
45
35
 
@@ -133,11 +123,13 @@ source window, the three matching owned-mark searches, and older roots linked
133
123
  by in-window replies. For every unheld owned root, compare the latest human
134
124
  message, current status reply, verified scopes, open scopes, and root marks.
135
125
  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.
126
+ pending human action; keep evidence, scopes, and full actions/owners in the
127
+ internal ledger. The status reply must follow the latest human message and use
128
+ the concise reply rule above: say what changed, and when `🎫` applies, briefly
129
+ name the unresolved action and its owner when known. A verified scope missing
130
+ `✅`, a pending human action missing `🎫`, or no current status is unfinished.
131
+ Finish the complete bounded set once, then stop; do not chase unrelated
132
+ historical marks.
141
133
 
142
134
  Split reports into distinct scopes. Add root `✅` as soon as any named scope
143
135
  passes direct post-change proof, even while its PR is open; say **Verified
@@ -146,9 +138,10 @@ proof. Add root `🎫` for each distinct unfinished action another human must ta
146
138
  including a requested reporter URL, access grant, evidence, or clarification.
147
139
  The mark tracks an outstanding human action, not an accepted team handoff.
148
140
  Record reporter requests in `reporterRequests[]`, separately from
149
- team/engineering `handoffs[]`. Enumerate each verified scope, open scope,
150
- reporter request, and handoff in the status reply; explain any absent mark. Keep
151
- marks after completion and never remove them. For a closed reporter request,
141
+ team/engineering `handoffs[]`. Keep verified scopes, open scopes, reporter
142
+ requests, and handoffs in the internal ledger; keep the Slack reply focused on
143
+ the current outcome and any `🎫` follow-up. Keep marks after completion and
144
+ never remove them. For a closed reporter request,
152
145
  preserve its request and closure evidence in the historical ticket record; see
153
146
  [`slack-reaction-replies.md`](references/slack-reaction-replies.md).
154
147
 
@@ -382,29 +375,22 @@ instruction or prompt exception.
382
375
  Thank issue reporters for opening issues and Slack reporters for sharing.
383
376
  Before posting, inspect the full thread and follow the one-reply gate in
384
377
  `address-feedback-with-replies` and the [Slack reply reference](references/slack-reaction-replies.md):
385
- send one concise disposition covering done, remaining, and next action/owner.
386
- Do not reply again to announce merge, publication, or live beta; answer direct
387
- reporter follow-ups. End Slack replies with `this was sent from a bot.`.
388
-
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.
400
-
401
- Keep publisher and beta checks in the internal recap after the disposition;
402
- they do not trigger a second reporter reply.
403
-
404
- ### After a PR merges
405
-
406
- Track publisher state and beta verification in the internal recap. Do not send
407
- a routine post-merge reporter reply; answer only a direct reporter follow-up.
378
+ send one short sentence in plain, non-technical language, then end with the
379
+ exact bot disclosure `this was sent from a bot.` Say what changed. Use `🎫`
380
+ only for unresolved work; when it is used, briefly say what still needs
381
+ investigation or action and name its owner when known. Do not explain absent
382
+ reactions or mention a closed, historical `🎫`. Keep hashes, branches, CI,
383
+ publisher details, and proof in the internal recap. Never explain workflow
384
+ mechanics or external tracker tickets in Slack. Never send **Open**/**pending**
385
+ or a symptom recap. Ask for reporter input only when it changes the next step.
386
+
387
+ Every root this actor marks with `👀`, `✅`, or `🎫` also needs a current
388
+ same-actor status reply after the latest human reply. One concise reply can
389
+ answer the reporter and record the disposition. Keep verified scopes, open
390
+ scopes, reaction history, and evidence in the internal ledger; do not enumerate
391
+ them or explain absent reactions in Slack. A Slack `🎫` reaction tracks a
392
+ distinct human action, not an external ticket, and remains as history after the
393
+ action closes. Never imply that an external ticket was created or assigned.
408
394
 
409
395
  For a fixed behavior with a Slack `🎫` handoff, state the done behavior and
410
396
  remaining action/owner separately; don't imply the fixed scope is open. The
@@ -453,36 +439,6 @@ otherwise draft the reply, leave the feedback unresolved, and link it in the
453
439
  recap. This exception is only for PR review feedback, not proactive PR comments
454
440
  or issue comments.
455
441
 
456
- ## Publishing
457
-
458
- Use this worktree's branch. Batch fixes with one
459
- `corepack pnpm ship:push -m "<specific fix>"`; each head reruns CI. Sync
460
- `origin/main` only for GitHub conflicts during review-only work. If the same
461
- task is already running an explicitly authorized `/ship`, follow that skill's
462
- safe-sync rule for a confirmed required-check fix on `origin/main`; do not ask
463
- for another approval. Behind/pending alone never justify syncing.
464
-
465
- Use `/ship` for PR ownership, push, and merge checks. Never push to another
466
- person's PR without explicit authorization for that exact PR in this request.
467
- Push-only authorization means `ship_mode=ready-only`; merging requires separate
468
- authorization for that PR. Without push authorization, hand off as pending.
469
- Carry feedback evidence and dispositions into PRs; reference only issues the
470
- PR fixes.
471
- Keep source-tested, built, deployed, and observed-live claims separate.
472
-
473
- Carry exact tracker row ids and the reproduction ledger into the PR or release
474
- recap; tracker status is not shipping evidence. Mark each row independently: a
475
- reaction, checkmark, or "reviewed" cannot cover multiple rows, including
476
- expected, docs-owned, cross-team, or duplicate items. Link each source/docs
477
- change to its exact PR or commit, and each non-coding disposition to its
478
- evidence or owner.
479
-
480
- If no fix is verified, recap why shipping did not start. Unavailable connectors
481
- and external failures are not shipping blockers.
482
- While waiting, **Clarification needed** stays open with `👀` and no `✅`; it
483
- must not block independent fixes unless it could affect a PR change. Later runs
484
- skip the eye under the ownership gate.
485
-
486
442
  ## Recap
487
443
 
488
444
  Every eligible item gets a row. Report gated skips by count only, without
@@ -329,10 +329,26 @@ chase arrivals or expand a finite catch-up request into an all-history audit.
329
329
  administrative post or duplicate; record the reason and link a duplicate to
330
330
  its canonical report. Include
331
331
  the root `threadParentTs`, reply timestamp, author ID, matching disposition,
332
- evidence, and read-back text. For each human/team handoff, the visible status
333
- reply must name its distinct scope, owner state, and action. Every distinct
334
- unfinished team action gets this actor's root `🎫`, even when no person has
335
- accepted it. For `status: "unassigned"`, record the responsible team/role or
332
+ evidence, and read-back text. Keep exact scope, proof, and handoff details in
333
+ the ledger. For `Owned elsewhere`, keep the owner's full proof in
334
+ `otherOwnerEvidence.statusReply.evidence`; set its `publicEvidenceSummary` to
335
+ a brief plain-language phrase (2–12 words) that appears in both the evidence
336
+ and the owner's captured reply. Do not require the full internal evidence in
337
+ their Slack text. For active `🎫` actions, add a short plain-language
338
+ `pendingActionSummary` to `statusReply` that names every open action and its
339
+ known owner, and include it in the reply; omit it when no tracked action
340
+ remains open. Keep the exact action in the internal ledger. When that wording
341
+ is too technical or long for Slack, add a
342
+ concise plain-language `publicActionSummary` using the opening words of the
343
+ action and leaving trailing implementation detail out. It must have at least
344
+ two meaningful words and no more than 12 words; the checker requires the
345
+ phrase to match the action. Use the summary in public status replies and
346
+ other readbacks that do not prove owner acceptance. For an accepted handoff,
347
+ the owner's explicit acceptance and the direct request before contextual
348
+ assent must name the full recorded action, even when a shorter public
349
+ summary exists. Every
350
+ distinct unfinished team action gets this actor's root `🎫`, even when no
351
+ person has accepted it. For `status: "unassigned"`, record the responsible team/role or
336
352
  `unassigned`; omit the owner Slack ID and acceptance. If this actor asked a
337
353
  named person to take the action but they have not accepted, record the exact
338
354
  request and keep the handoff unassigned. For `status: "accepted"` or
@@ -363,7 +379,19 @@ chase arrivals or expand a finite catch-up request into an all-history audit.
363
379
  replies and bot messages. The status reply must be newer than this human
364
380
  reply. If a human answers later, update the disposition and status before
365
381
  reconciling it.
366
- Record each verified `fixedScopes` entry with its proof; record each
382
+ Record each verified `fixedScopes` entry with its proof. For a status with
383
+ fixed scopes, also set `statusReply.publicEvidenceSummary` to a brief,
384
+ plain-language phrase (2–12 words) that can appear in the public reply. For
385
+ every fixed scope, put that scope and the summary together in one sentence of
386
+ its `verification`. Include the summary in both `statusReply.evidence` and
387
+ the captured `statusReply.text`; keep detailed proof in the ledger. The
388
+ summary may paraphrase the internal scope. Record each
389
+ `unfinishedScopes` entry in the ledger. When any remain, set
390
+ `statusReply.unfinishedScopeSummary` to a brief plain-language summary
391
+ (3–12 words) that names remaining work using wording grounded in its recorded
392
+ scope or evidence, and says it is still open; include it in the public status
393
+ even when no separate `🎫` action exists. Omit this field when no unfinished
394
+ scope remains. Record each
367
395
  unfinished team/engineering action in `handoffs[]` and each outstanding
368
396
  reporter input request in `reporterRequests[]` with its scope, exact action,
369
397
  and exact `requestReply`. For
@@ -386,9 +414,9 @@ chase arrivals or expand a finite catch-up request into an all-history audit.
386
414
  action is completed, keep it in `handoffs[]` with
387
415
  `status: "completed"` and `completion: { evidence, sourceUrl }` or an exact
388
416
  owner `reply` (`ts`, `threadParentTs`, `actorId`, and verbatim `text`). The
389
- current status must cite that completion evidence. Keep the root `🎫` as
390
- history; completed actions are not open follow-ups and do not block a closed
391
- disposition. Only an owner withdrawal moves an accepted action to
417
+ `statusReply.evidence` must cite that completion evidence. Keep the root
418
+ `🎫` as history; completed actions are not open follow-ups and do not block a
419
+ closed disposition. Only an owner withdrawal moves an accepted action to
392
420
  `historicalReactionMarks[]`. As soon as direct post-change proof passes, add this actor's
393
421
  `✅` to the root and read it back before moving to another scope or report,
394
422
  including while its PR is open. Before merge, record **Verified locally** and
@@ -403,9 +431,10 @@ chase arrivals or expand a finite catch-up request into an all-history audit.
403
431
  mark remains as history after completion. For an eligible open scope this
404
432
  workflow takes, retain the root `👀` as permanent history; do not use it to
405
433
  infer whether work remains.
406
- The same-thread status reply must name the verified scope(s), the remaining
407
- open scope(s), each open scope's blocker and next action, and why `✅` or `🎫`
408
- is absent when it does not apply. A reply does not replace a
434
+ Keep the same-thread status reply to one short, plain-language paragraph.
435
+ Say what changed; when an active `🎫` remains, name its next action through
436
+ `pendingActionSummary`. Do not list every scope or explain absent reactions.
437
+ A reply does not replace a
409
438
  reaction, and a reaction does not replace a reply. Do not add status
410
439
  requirements or reactions to another actor's eye. When later evidence
411
440
  invalidates a retained `✅`, remove that scope from active `fixedScopes[]`
@@ -415,9 +444,17 @@ chase arrivals or expand a finite catch-up request into an all-history audit.
415
444
  withdraws, move that action out of current `handoffs[]` and record a historical
416
445
  `🎫` with its earlier accepted `handoff` plus the owner's exact
417
446
  `withdrawalReply` (`ts`, `threadParentTs`, `actorId`, and verbatim `text`).
418
- Explain each retained mark in the current status and its reaction decision;
419
- preserve it without presenting it as current proof or an active handoff. The
420
- checker validates historical evidence against the captured thread. When every report scope
447
+ Record why each retained mark is historical in the internal reaction
448
+ decision; preserve it without presenting it as current proof or an active
449
+ handoff. Keep detailed proof in `previousStatus.evidence` in the internal
450
+ ledger. For a historical `✅`, also record a brief, non-sensitive
451
+ `publicEvidenceSummary` (2–12 words) drawn from that proof. It may paraphrase
452
+ the exact scope, but the scope and summary must appear together in one proof
453
+ sentence. Include the summary in the captured `previousStatus.text`; the
454
+ checker verifies that it is tied to proof for the recorded scope.
455
+ Never put secrets, customer details, or raw logs in the summary. For a
456
+ historical `🎫`, the prior reply must name both the accepted action and its
457
+ owner. The checker validates these details against the captured thread. When every report scope
421
458
  and follow-up is closed, post or reuse the final same-thread status and read
422
459
  back the status and reactions. Preserve every reaction; disposition and
423
460
  status show that work is closed even when the root still has `👀`.
@@ -481,8 +518,9 @@ back `🎫` even when it is unassigned or unaccepted. Record acceptance only
481
518
  after a named person's exact reply; otherwise state `owner: unassigned` and
482
519
  `status: unassigned`. For reporter evidence, access, or clarification
483
520
  requests, record `reporterRequests[]` and add/read back `🎫`; explain that it
484
- tracks requested input, not a team handoff. In the current status, state the
485
- exact proof gap, blocker, and next owner/action. An
521
+ tracks requested input, not a team handoff. In the internal ledger, record the
522
+ exact proof gap, blocker, and next owner/action. Keep the current status brief
523
+ and plain-language. An
486
524
  eye-only root with unfinished work is pending; an eye-only root with a closed
487
525
  verified disposition may retain the eye as history. Never remove or clean up a
488
526
  reaction. Slack thread replies appear under the root's reply count, not as