@agent-native/core-corpus 0.210.2 → 0.211.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 (192) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/actions/references/action-fields.md +5 -3
  3. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +41 -2
  4. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +4 -2
  5. package/corpus/templates/analytics/CHANGELOG.md +18 -0
  6. package/corpus/templates/analytics/actions/build-data-index.ts +11 -2
  7. package/corpus/templates/analytics/actions/configure-content-collection.ts +29 -4
  8. package/corpus/templates/analytics/actions/configure-first-party-events-model.ts +9 -7
  9. package/corpus/templates/analytics/actions/create-analytics-public-key.ts +4 -0
  10. package/corpus/templates/analytics/actions/estimate-content-signups-cost.ts +72 -0
  11. package/corpus/templates/analytics/actions/estimate-query-cost.ts +16 -134
  12. package/corpus/templates/analytics/actions/list-sigma-workbooks.ts +123 -42
  13. package/corpus/templates/analytics/actions/migrate-sigma-workbook.ts +20 -17
  14. package/corpus/templates/analytics/actions/revoke-analytics-public-key.ts +4 -0
  15. package/corpus/templates/analytics/app/components/dashboard/SigmaMigrationDialog.tsx +246 -680
  16. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
  17. package/corpus/templates/analytics/app/i18n/en-US.ts +54 -55
  18. package/corpus/templates/analytics/app/pages/DashboardOverview.tsx +0 -1
  19. package/corpus/templates/analytics/app/pages/DataSources.tsx +56 -0
  20. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/filter-vars.ts +4 -3
  21. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +26 -8
  22. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/interpolate.ts +3 -1
  23. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/panel-cost-notice.ts +172 -0
  24. package/corpus/templates/analytics/app/pages/data-sources/SearchConsoleDetail.tsx +441 -0
  25. package/corpus/templates/analytics/changelog/2026-10-15-a-content-dashboard-lists-every-blog-article-with-its-author.md +6 -0
  26. package/corpus/templates/analytics/changelog/2026-10-15-data-sources-has-a-google-search-console-card-that-shows-the.md +6 -0
  27. package/corpus/templates/analytics/changelog/2026-10-16-the-content-dashboard-can-show-signups-per-blog-article.md +6 -0
  28. package/corpus/templates/analytics/changelog/2026-10-19-creating-or-revoking-a-public-analytics-key-now-asks-you-to-.md +6 -0
  29. package/corpus/templates/analytics/changelog/2026-10-19-search-sigma-workbooks-by-name-and-open-ones-a-teammate-alre.md +6 -0
  30. package/corpus/templates/analytics/changelog/2026-10-20-blog-signup-attribution-excludes-signups-before-the-first-vi.md +6 -0
  31. package/corpus/templates/analytics/changelog/2026-10-20-collapsed-analytics-navigation-starts-at-the-top.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-10-20-content-keeps-successful-coverage-dates-and-missing-day-gaps.md +6 -0
  33. package/corpus/templates/analytics/changelog/2026-10-20-private-github-repositories-can-be-indexed-with-your-connect.md +6 -0
  34. package/corpus/templates/analytics/changelog/2026-10-20-search-console-preserves-backfill-and-paused-collection.md +6 -0
  35. package/corpus/templates/analytics/changelog/2026-10-20-search-console-refreshes-historical-coverage-when-properties.md +6 -0
  36. package/corpus/templates/analytics/changelog/2026-10-20-search-console-setup-keeps-saved-properties-readable-and-sup.md +6 -0
  37. package/corpus/templates/analytics/seeds/dashboards/content-view.json +182 -0
  38. package/corpus/templates/analytics/server/db/schema.ts +1 -0
  39. package/corpus/templates/analytics/server/lib/bi-migration/external-dashboards-store.ts +56 -18
  40. package/corpus/templates/analytics/server/lib/bi-migration/unfinished-import.ts +64 -0
  41. package/corpus/templates/analytics/server/lib/builtin-dashboard-pass.ts +17 -2
  42. package/corpus/templates/analytics/server/lib/content-metrics/collect.ts +2 -0
  43. package/corpus/templates/analytics/server/lib/content-metrics/config.ts +92 -3
  44. package/corpus/templates/analytics/server/lib/content-metrics/legacy-content-view.json +172 -0
  45. package/corpus/templates/analytics/server/lib/content-metrics/migration.ts +2 -0
  46. package/corpus/templates/analytics/server/lib/content-metrics/steps/daily-metrics.ts +8 -3
  47. package/corpus/templates/analytics/server/lib/content-metrics/steps/search-console.ts +16 -3
  48. package/corpus/templates/analytics/server/lib/content-metrics/steps/signups.ts +240 -0
  49. package/corpus/templates/analytics/server/lib/content-metrics/store.ts +5 -0
  50. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +21 -0
  51. package/corpus/templates/analytics/server/lib/dashboard-panel-verification.ts +300 -5
  52. package/corpus/templates/analytics/server/lib/dashboard-seeds.ts +2 -0
  53. package/corpus/templates/analytics/server/lib/dashboards-store.ts +28 -0
  54. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +128 -24
  55. package/corpus/templates/analytics/server/lib/google-analytics.ts +56 -16
  56. package/corpus/templates/analytics/server/lib/index-build-github.ts +5 -0
  57. package/corpus/templates/analytics/server/lib/provider-credentials.ts +47 -2
  58. package/corpus/templates/analytics/server/lib/query-cost-estimate.ts +184 -5
  59. package/corpus/templates/analytics/server/plugins/db.ts +9 -1
  60. package/corpus/templates/analytics/shared/builtin-dashboard-ids.ts +2 -0
  61. package/corpus/templates/analytics/shared/search-console-property.ts +78 -0
  62. package/corpus/templates/assets/.agents/skills/actions/references/action-fields.md +5 -3
  63. package/corpus/templates/brain/.agents/skills/actions/references/action-fields.md +5 -3
  64. package/corpus/templates/calendar/.agents/skills/actions/references/action-fields.md +5 -3
  65. package/corpus/templates/calendar/actions/delete-event.ts +1 -3
  66. package/corpus/templates/calendar/actions/delete-events.ts +1 -2
  67. package/corpus/templates/calendar/actions/event-action-helpers.ts +0 -4
  68. package/corpus/templates/calendar/actions/update-event.ts +2 -6
  69. package/corpus/templates/chat/.agents/skills/actions/references/action-fields.md +5 -3
  70. package/corpus/templates/clips/.agents/skills/actions/references/action-fields.md +5 -3
  71. package/corpus/templates/clips/CHANGELOG.md +11 -24
  72. package/corpus/templates/clips/actions/create-recording-upload.ts +5 -1
  73. package/corpus/templates/clips/app/components/player/chapter-list.tsx +7 -6
  74. package/corpus/templates/clips/app/components/player/comment-composer.tsx +5 -1
  75. package/corpus/templates/clips/app/components/player/player-controls.tsx +1 -0
  76. package/corpus/templates/clips/app/components/player/scrubber.tsx +4 -2
  77. package/corpus/templates/clips/app/i18n/en-US.ts +3 -3
  78. package/corpus/templates/clips/app/lib/extension-auth.ts +70 -0
  79. package/corpus/templates/clips/app/root.tsx +22 -10
  80. package/corpus/templates/clips/app/routes/_app.notifications.tsx +3 -0
  81. package/corpus/templates/clips/app/routes/import.tsx +115 -110
  82. package/corpus/templates/clips/changelog/2026-10-16-agent-uploads-can-use-free-clips-storage.md +6 -0
  83. package/corpus/templates/clips/changelog/2026-10-19-agents-see-a-clip-s-frames-as-soon-as-it-finishes-processing.md +6 -0
  84. package/corpus/templates/clips/changelog/2026-10-19-chapter-list-times-now-match-the-player-s-clock-and-each-cha.md +6 -0
  85. package/corpus/templates/clips/changelog/2026-10-19-editing-a-comment-puts-the-cursor-after-its-text-and-reply-o.md +6 -0
  86. package/corpus/templates/clips/changelog/2026-10-19-youtube-import-followups.md +6 -0
  87. package/corpus/templates/clips/changelog/2026-10-20-locally-saved-desktop-recordings-now-appear-in-the-recovery-.md +6 -0
  88. package/corpus/templates/clips/desktop/src/app.tsx +29 -186
  89. package/corpus/templates/clips/desktop/src/components/RecordingRecovery.tsx +171 -16
  90. package/corpus/templates/clips/desktop/src/lib/record-first.ts +9 -32
  91. package/corpus/templates/clips/desktop/src/lib/recording-recovery.ts +30 -1
  92. package/corpus/templates/clips/server/lib/agent-frame-request.ts +5 -2
  93. package/corpus/templates/clips/server/lib/recording-upload-ticket.ts +8 -0
  94. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +2 -0
  95. package/corpus/templates/content/.agents/skills/actions/references/action-fields.md +5 -3
  96. package/corpus/templates/content/CHANGELOG.md +12 -0
  97. package/corpus/templates/content/actions/_content-database-personal-view.ts +5 -15
  98. package/corpus/templates/content/actions/_database-navigation.ts +3 -3
  99. package/corpus/templates/content/actions/_database-utils.ts +10 -22
  100. package/corpus/templates/content/actions/_database-view-setup.ts +16 -76
  101. package/corpus/templates/content/actions/create-comment-ai-suggestion.ts +93 -44
  102. package/corpus/templates/content/actions/export-document.ts +2 -14
  103. package/corpus/templates/content/actions/suggest-document-edit.ts +5 -7
  104. package/corpus/templates/content/actions/update-content-database-view.ts +2 -14
  105. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +86 -30
  106. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +64 -28
  107. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +6 -46
  108. package/corpus/templates/content/app/components/editor/PageDraftRecovery.tsx +72 -7
  109. package/corpus/templates/content/app/components/editor/TableHoverControls.tsx +292 -103
  110. package/corpus/templates/content/app/components/editor/ThreadSuggestion.tsx +147 -40
  111. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +108 -11
  112. package/corpus/templates/content/app/components/editor/database/ContentTable.tsx +2 -1
  113. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +150 -166
  114. package/corpus/templates/content/app/components/editor/database/database-cell-comments.tsx +515 -0
  115. package/corpus/templates/content/app/components/editor/database/database-item-comments.tsx +5 -43
  116. package/corpus/templates/content/app/components/editor/database/filter-sort.ts +8 -783
  117. package/corpus/templates/content/app/components/editor/live-editor-session.ts +5 -1
  118. package/corpus/templates/content/app/components/editor/page-draft-journal.ts +61 -1
  119. package/corpus/templates/content/app/components/editor/property-comments.tsx +35 -4
  120. package/corpus/templates/content/app/components/editor/property-date-format.ts +45 -0
  121. package/corpus/templates/content/app/components/editor/thread-suggestions.ts +30 -0
  122. package/corpus/templates/content/app/components/editor/useDocumentReconcileRecovery.ts +23 -4
  123. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +36 -10
  124. package/corpus/templates/content/app/global.css +23 -0
  125. package/corpus/templates/content/app/i18n-data.ts +1 -0
  126. package/corpus/templates/content/changelog/2026-10-16-opening-a-page-in-a-new-tab-while-another-tab-is-still-savin.md +6 -0
  127. package/corpus/templates/content/changelog/2026-10-16-typing-steadily-in-one-tab-no-longer-makes-another-open-tab-.md +6 -0
  128. package/corpus/templates/content/changelog/2026-10-19-database-views-can-filter-the-same-property-more-than-once-n.md +6 -0
  129. package/corpus/templates/content/changelog/2026-10-19-edits-made-from-a-block-s-menu-or-a-table-s-row-and-column-m.md +6 -0
  130. package/corpus/templates/content/changelog/2026-10-19-starting-a-comment-puts-the-cursor-in-the-comment-box-ready-.md +6 -0
  131. package/corpus/templates/content/changelog/2026-10-19-table-row-and-column-options-now-appear-for-the-row-or-colum.md +6 -0
  132. package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +5 -1
  133. package/corpus/templates/content/docs/product/capabilities/content.revision.suggestions.md +3 -1
  134. package/corpus/templates/content/e2e/helpers.ts +10 -0
  135. package/corpus/templates/content/shared/api.ts +18 -12
  136. package/corpus/templates/content/shared/database-query.ts +107 -13
  137. package/corpus/templates/content/shared/suggestion-diff.ts +35 -0
  138. package/corpus/templates/crm/.agents/skills/actions/references/action-fields.md +5 -3
  139. package/corpus/templates/design/.agents/skills/actions/references/action-fields.md +5 -3
  140. package/corpus/templates/design/CHANGELOG.md +2 -0
  141. package/corpus/templates/design/actions/delete-file.ts +1 -4
  142. package/corpus/templates/design/app/components/design/LayersPanel.tsx +10 -1
  143. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +24 -4
  144. package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +14 -9
  145. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +23 -2
  146. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +29 -0
  147. package/corpus/templates/design/changelog/2026-10-19-reply-and-edit-on-a-comment-in-the-sidebar-or-on-a-canvas-pi.md +6 -0
  148. package/corpus/templates/design/changelog/2026-10-20-comments-return-to-the-mode-they-came-from-and-layers-search.md +6 -0
  149. package/corpus/templates/design/e2e/global-setup.ts +185 -85
  150. package/corpus/templates/dispatch/.agents/skills/actions/references/action-fields.md +5 -3
  151. package/corpus/templates/dispatch/CHANGELOG.md +4 -0
  152. package/corpus/templates/dispatch/actions/apply-workspace-connection-setup.ts +4 -0
  153. package/corpus/templates/dispatch/actions/delete-workspace-connection.ts +4 -0
  154. package/corpus/templates/dispatch/actions/set-workspace-connection-grant.ts +4 -0
  155. package/corpus/templates/dispatch/actions/upsert-workspace-connection.ts +4 -0
  156. package/corpus/templates/dispatch/changelog/2026-10-19-vault-secrets-grants-link-tokens-app-access-and-workspace-co.md +6 -0
  157. package/corpus/templates/factory/.agents/skills/actions/references/action-fields.md +5 -3
  158. package/corpus/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +105 -33
  159. package/corpus/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +128 -74
  160. package/corpus/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +39 -11
  161. package/corpus/templates/forms/.agents/skills/actions/references/action-fields.md +5 -3
  162. package/corpus/templates/mail/.agents/skills/actions/references/action-fields.md +5 -3
  163. package/corpus/templates/mail/.agents/skills/contacts-and-crm/SKILL.md +22 -17
  164. package/corpus/templates/mail/AGENTS.md +1 -1
  165. package/corpus/templates/mail/CHANGELOG.md +14 -0
  166. package/corpus/templates/mail/actions/provider-api-request.ts +3 -3
  167. package/corpus/templates/mail/app/components/email/IntegrationsSidebar.tsx +211 -54
  168. package/corpus/templates/mail/app/hooks/use-inbox-threads.ts +26 -2
  169. package/corpus/templates/mail/app/i18n/en-US.ts +5 -1
  170. package/corpus/templates/mail/app/lib/gong-connection.ts +27 -0
  171. package/corpus/templates/mail/app/lib/thread-cache.ts +110 -9
  172. package/corpus/templates/mail/app/root.tsx +10 -2
  173. package/corpus/templates/mail/changelog/2026-10-20-connected-gong-settings-now-open-the-shared-integration-mana.md +6 -0
  174. package/corpus/templates/mail/changelog/2026-10-20-gong-connection-details-now-match-the-call-data-available-in.md +6 -0
  175. package/corpus/templates/mail/changelog/2026-10-20-improve-gong-setup-with-a-click-to-connect-flow-and-permissi.md +6 -0
  176. package/corpus/templates/mail/changelog/2026-10-20-mail-lets-you-retry-when-it-cannot-check-your-gong-access.md +6 -0
  177. package/corpus/templates/mail/changelog/2026-10-20-mail-thread-cache-stays-isolated-when-mailbox-scope-changes.md +6 -0
  178. package/corpus/templates/mail/changelog/2026-10-20-the-inbox-and-recently-opened-threads-now-open-instantly-fro.md +6 -0
  179. package/corpus/templates/mail/server/handlers/emails.ts +9 -0
  180. package/corpus/templates/plan/.agents/skills/actions/references/action-fields.md +5 -3
  181. package/corpus/templates/plan/CHANGELOG.md +4 -0
  182. package/corpus/templates/plan/app/pages/PlansPage.tsx +173 -33
  183. package/corpus/templates/plan/changelog/2026-10-19-opening-a-comment-thread-puts-the-cursor-in-the-reply-box-an.md +6 -0
  184. package/corpus/templates/slides/.agents/skills/actions/references/action-fields.md +5 -3
  185. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +6 -42
  186. package/corpus/templates/slides/app/components/editor/FloatingToolbar.tsx +3 -7
  187. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +5 -14
  188. package/corpus/templates/slides/app/i18n/en-US.ts +0 -2
  189. package/corpus/templates/slides/app/pages/DeckEditor.tsx +27 -33
  190. package/corpus/templates/slides/scripts/edit-fidelity/run.ts +1 -3
  191. package/corpus/templates/tasks/.agents/skills/actions/references/action-fields.md +5 -3
  192. package/package.json +1 -1
@@ -28,6 +28,46 @@ const STATE_PATH = path.join(AUTH_DIR, "state.json");
28
28
  const SEED_PATH = path.join(AUTH_DIR, "seed.json");
29
29
  const BROWSER_CHANNEL = process.env.E2E_BROWSER_CHANNEL;
30
30
  const LOOPBACK_INSTANCE_HEADER = "x-agent-native-loopback-instance";
31
+ const GLOBAL_SETUP_BUDGET_MS = 11 * 60_000;
32
+ const SEED_BUDGET_MS = 2 * 60_000;
33
+ const HOME_WARMUP_BUDGET_MS = 6 * 60_000;
34
+ const HOME_RECOVERY_DETECTION_BUDGET_MS = 30_000;
35
+ const HOME_RECOVERY_RETRY_BUDGET_MS = 2 * 60_000;
36
+ const HOME_INITIAL_NAVIGATION_BUDGET_MS =
37
+ HOME_WARMUP_BUDGET_MS -
38
+ HOME_RECOVERY_DETECTION_BUDGET_MS -
39
+ HOME_RECOVERY_RETRY_BUDGET_MS;
40
+ const EDITOR_WARMUP_BUDGET_MS = 3 * 60_000;
41
+
42
+ async function withDeadline<T>(
43
+ label: string,
44
+ deadline: number,
45
+ action: () => Promise<T>,
46
+ ): Promise<T> {
47
+ const remainingMs = deadline - Date.now();
48
+ if (remainingMs <= 0) throw new Error(`${label} budget expired`);
49
+
50
+ let timeout: ReturnType<typeof setTimeout> | undefined;
51
+ try {
52
+ return await Promise.race([
53
+ action(),
54
+ new Promise<never>((_, reject) => {
55
+ timeout = setTimeout(
56
+ () => reject(new Error(`${label} budget expired`)),
57
+ remainingMs,
58
+ );
59
+ }),
60
+ ]);
61
+ } finally {
62
+ if (timeout) clearTimeout(timeout);
63
+ }
64
+ }
65
+
66
+ function timeoutBefore(deadline: number, requestedMs: number): number {
67
+ const remainingMs = deadline - Date.now();
68
+ if (remainingMs <= 0) throw new Error("the warmup budget expired");
69
+ return Math.min(requestedMs, remainingMs);
70
+ }
31
71
 
32
72
  export async function startLoopbackProvider(port: number): Promise<void> {
33
73
  const runRoot = designE2eRunRoot(path.resolve(import.meta.dirname, ".."));
@@ -331,6 +371,7 @@ async function seedMentionMember(
331
371
  }
332
372
 
333
373
  export default async function globalSetup(config: FullConfig) {
374
+ const globalSetupDeadline = Date.now() + GLOBAL_SETUP_BUDGET_MS;
334
375
  if (process.env.E2E_AI_SIDEBAR_LOOPBACK === "1") {
335
376
  await startLoopbackProvider(config.metadata.sidebarLoopbackPort as number);
336
377
  await startHttpsAttachmentStorage(
@@ -348,79 +389,165 @@ export default async function globalSetup(config: FullConfig) {
348
389
  const context = await browser.newContext();
349
390
 
350
391
  try {
351
- const registration = await context.request.post(
352
- `${baseURL}/_agent-native/auth/register`,
353
- {
354
- data: {
355
- email: E2E_EMAIL,
356
- password: E2E_PASSWORD,
357
- },
358
- headers: { "Content-Type": "application/json" },
359
- },
392
+ const seedDeadline = Math.min(
393
+ Date.now() + SEED_BUDGET_MS,
394
+ globalSetupDeadline - HOME_WARMUP_BUDGET_MS - EDITOR_WARMUP_BUDGET_MS,
360
395
  );
361
- if (!registration.ok() && registration.status() !== 409) {
362
- throw new Error(
363
- `registration failed: ${registration.status()} ${await registration.text()}`,
364
- );
365
- }
396
+ const designId = await withDeadline(
397
+ "Design E2E seeding",
398
+ seedDeadline,
399
+ async () => {
400
+ const registration = await context.request.post(
401
+ `${baseURL}/_agent-native/auth/register`,
402
+ {
403
+ data: {
404
+ email: E2E_EMAIL,
405
+ password: E2E_PASSWORD,
406
+ },
407
+ headers: { "Content-Type": "application/json" },
408
+ },
409
+ );
410
+ if (!registration.ok() && registration.status() !== 409) {
411
+ throw new Error(
412
+ `registration failed: ${registration.status()} ${await registration.text()}`,
413
+ );
414
+ }
366
415
 
367
- const login = await context.request.post(
368
- `${baseURL}/_agent-native/auth/login`,
369
- {
370
- data: {
371
- email: E2E_EMAIL,
372
- password: E2E_PASSWORD,
373
- },
374
- headers: { "Content-Type": "application/json" },
375
- },
376
- );
377
- if (!login.ok()) {
378
- throw new Error(`login failed: ${login.status()} ${await login.text()}`);
379
- }
416
+ const login = await context.request.post(
417
+ `${baseURL}/_agent-native/auth/login`,
418
+ {
419
+ data: {
420
+ email: E2E_EMAIL,
421
+ password: E2E_PASSWORD,
422
+ },
423
+ headers: { "Content-Type": "application/json" },
424
+ },
425
+ );
426
+ if (!login.ok()) {
427
+ throw new Error(
428
+ `login failed: ${login.status()} ${await login.text()}`,
429
+ );
430
+ }
380
431
 
381
- await context.storageState({ path: STATE_PATH });
432
+ await context.storageState({ path: STATE_PATH });
382
433
 
383
- const created = await postAction(
384
- context.request,
385
- baseURL,
386
- "create-design",
387
- {
388
- title: SEED_TITLE,
389
- projectType: "prototype",
434
+ const created = await postAction(
435
+ context.request,
436
+ baseURL,
437
+ "create-design",
438
+ {
439
+ title: SEED_TITLE,
440
+ projectType: "prototype",
441
+ },
442
+ );
443
+ const createdDesignId: string =
444
+ created?.id ?? created?.data?.id ?? created?.design?.id;
445
+ if (!createdDesignId) {
446
+ throw new Error(
447
+ `create-design did not return an id: ${JSON.stringify(created)}`,
448
+ );
449
+ }
450
+ await seedMentionMember(browser, context, baseURL);
451
+ await postAction(context.request, baseURL, "create-file", {
452
+ designId: createdDesignId,
453
+ filename: "index.html",
454
+ content: FIXTURE_HTML,
455
+ fileType: "html",
456
+ });
457
+ await postAction(context.request, baseURL, "index-components", {
458
+ designId: createdDesignId,
459
+ });
460
+
461
+ await writeFile(
462
+ SEED_PATH,
463
+ JSON.stringify({ designId: createdDesignId }, null, 2),
464
+ );
465
+ // eslint-disable-next-line no-console
466
+ console.log(`[e2e] seeded design ${createdDesignId} for ${E2E_EMAIL}`);
467
+ return createdDesignId;
390
468
  },
391
469
  );
392
- const designId: string =
393
- created?.id ?? created?.data?.id ?? created?.design?.id;
394
- if (!designId) {
395
- throw new Error(
396
- `create-design did not return an id: ${JSON.stringify(created)}`,
397
- );
398
- }
399
- await seedMentionMember(browser, context, baseURL);
400
- await postAction(context.request, baseURL, "create-file", {
401
- designId,
402
- filename: "index.html",
403
- content: FIXTURE_HTML,
404
- fileType: "html",
405
- });
406
- await postAction(context.request, baseURL, "index-components", {
407
- designId,
408
- });
409
-
410
- await writeFile(SEED_PATH, JSON.stringify({ designId }, null, 2));
411
- // eslint-disable-next-line no-console
412
- console.log(`[e2e] seeded design ${designId} for ${E2E_EMAIL}`);
413
470
 
471
+ const homeDeadline = Math.min(
472
+ Date.now() + HOME_WARMUP_BUDGET_MS,
473
+ globalSetupDeadline - EDITOR_WARMUP_BUDGET_MS,
474
+ );
475
+ const homeRecoveryDeadline = homeDeadline - HOME_RECOVERY_RETRY_BUDGET_MS;
476
+ const homeInitialNavigationDeadline =
477
+ homeRecoveryDeadline - HOME_RECOVERY_DETECTION_BUDGET_MS;
414
478
  const warmupPage = await context.newPage();
415
479
  try {
480
+ const homeURL = `${baseURL}/home`;
481
+ const homeComposer = warmupPage.locator("[data-design-home-composer]");
482
+ let homeComposerWarmed = false;
483
+ try {
484
+ await warmupPage.goto(homeURL, {
485
+ waitUntil: "domcontentloaded",
486
+ timeout: timeoutBefore(
487
+ homeInitialNavigationDeadline,
488
+ HOME_INITIAL_NAVIGATION_BUDGET_MS,
489
+ ),
490
+ });
491
+ const homeRecoveryDetectionDeadline = Math.min(
492
+ homeRecoveryDeadline,
493
+ Date.now() + HOME_RECOVERY_DETECTION_BUDGET_MS,
494
+ );
495
+ await homeComposer.waitFor({
496
+ state: "visible",
497
+ timeout: timeoutBefore(
498
+ homeRecoveryDetectionDeadline,
499
+ HOME_RECOVERY_DETECTION_BUDGET_MS,
500
+ ),
501
+ });
502
+ homeComposerWarmed = true;
503
+ } catch (error) {
504
+ const hasViteRecoveryMarker = new URL(
505
+ warmupPage.url(),
506
+ ).searchParams.has("__an_vite_dev_recovery");
507
+ if (hasViteRecoveryMarker) {
508
+ try {
509
+ await warmupPage.goto(homeURL, {
510
+ waitUntil: "domcontentloaded",
511
+ timeout: timeoutBefore(homeDeadline, HOME_WARMUP_BUDGET_MS),
512
+ });
513
+ await homeComposer.waitFor({
514
+ state: "visible",
515
+ timeout: timeoutBefore(homeDeadline, HOME_WARMUP_BUDGET_MS),
516
+ });
517
+ homeComposerWarmed = true;
518
+ } catch (recoveryError) {
519
+ // eslint-disable-next-line no-console
520
+ console.warn(
521
+ `[e2e] home warmup did not finish after Vite recovery (${(recoveryError as Error).message.split("\n")[0]}); ` +
522
+ "the first test touching /home will pay the compile.",
523
+ );
524
+ }
525
+ } else {
526
+ // Home tests remain responsible for reporting any warmup failure.
527
+ // eslint-disable-next-line no-console
528
+ console.warn(
529
+ `[e2e] home warmup did not finish (${(error as Error).message.split("\n")[0]}); ` +
530
+ "the first test touching /home will pay the compile.",
531
+ );
532
+ }
533
+ }
534
+ if (homeComposerWarmed) {
535
+ // eslint-disable-next-line no-console
536
+ console.log("[e2e] home composer warm");
537
+ }
538
+
539
+ const editorDeadline = Math.min(
540
+ Date.now() + EDITOR_WARMUP_BUDGET_MS,
541
+ globalSetupDeadline,
542
+ );
416
543
  try {
417
544
  await warmupPage.goto(`${baseURL}/design/${designId}`, {
418
545
  waitUntil: "domcontentloaded",
419
- timeout: 180_000,
546
+ timeout: timeoutBefore(editorDeadline, 180_000),
420
547
  });
421
548
  await warmupPage
422
549
  .getByRole("button", { name: "Move", exact: true })
423
- .waitFor({ timeout: 180_000 });
550
+ .waitFor({ timeout: timeoutBefore(editorDeadline, 180_000) });
424
551
  // eslint-disable-next-line no-console
425
552
  console.log("[e2e] editor warm");
426
553
  } catch (error) {
@@ -432,33 +559,6 @@ export default async function globalSetup(config: FullConfig) {
432
559
  "the first test will pay the compile.",
433
560
  );
434
561
  }
435
-
436
- if (process.env.E2E_AI_SIDEBAR_LOOPBACK === "1") {
437
- const homeURL = `${baseURL}/home`;
438
- const homeComposer = warmupPage.locator("[data-design-home-composer]");
439
- await warmupPage.goto(homeURL, {
440
- waitUntil: "domcontentloaded",
441
- timeout: 180_000,
442
- });
443
- try {
444
- await homeComposer.waitFor({ state: "visible", timeout: 30_000 });
445
- } catch (error) {
446
- if (
447
- !new URL(warmupPage.url()).searchParams.has(
448
- "__an_vite_dev_recovery",
449
- )
450
- ) {
451
- throw error;
452
- }
453
- await warmupPage.goto(homeURL, {
454
- waitUntil: "domcontentloaded",
455
- timeout: 180_000,
456
- });
457
- await homeComposer.waitFor({ state: "visible", timeout: 30_000 });
458
- }
459
- // eslint-disable-next-line no-console
460
- console.log("[e2e] home composer warm");
461
- }
462
562
  } finally {
463
563
  await warmupPage.close();
464
564
  }
@@ -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.
@@ -9,6 +9,10 @@ time from the command menu (Cmd+K → "What's new").
9
9
 
10
10
  - Apps no longer keep server functions and databases warm by default; set `AGENT_NATIVE_ENABLE_KEEP_WARM=1` to opt in.
11
11
 
12
+ ### Security
13
+
14
+ - Vault secrets, grants, link tokens, app access, and workspace connections now ask you to approve each change the agent or another app makes.
15
+
12
16
  ## 2026-10-14
13
17
 
14
18
  ### Fixed
@@ -118,6 +118,10 @@ export default defineAction({
118
118
  .optional()
119
119
  .describe("Workspace user group IDs allowed to use the connection."),
120
120
  }),
121
+ // It creates, changes, or revokes a credential or who may use one: every
122
+ // call needs the user's approval, and "always allow" is not offered.
123
+ needsApproval: true,
124
+ allowPersistentApproval: false,
121
125
  run: async (args, ctx) => {
122
126
  const provider = getWorkspaceConnectionProvider(args.provider);
123
127
  if (!provider) {
@@ -27,6 +27,10 @@ export default defineAction({
27
27
  schema: z.object({
28
28
  id: z.string().describe("Workspace connection ID to delete."),
29
29
  }),
30
+ // It creates, changes, or revokes a credential or who may use one: every
31
+ // call needs the user's approval, and "always allow" is not offered.
32
+ needsApproval: true,
33
+ allowPersistentApproval: false,
30
34
  run: async ({ id }, ctx) => {
31
35
  const connection = await getWorkspaceConnection(id);
32
36
  if (!connection) {
@@ -73,6 +73,10 @@ export default defineAction({
73
73
  "Known workspace app IDs. Used when converting an all-app connection into selected-app grants.",
74
74
  ),
75
75
  }),
76
+ // It creates, changes, or revokes a credential or who may use one: every
77
+ // call needs the user's approval, and "always allow" is not offered.
78
+ needsApproval: true,
79
+ allowPersistentApproval: false,
76
80
  run: async (args, ctx) => {
77
81
  const connection = await getWorkspaceConnection(args.connectionId);
78
82
  if (!connection) {
@@ -209,6 +209,10 @@ export default defineAction({
209
209
  ),
210
210
  lastError: z.string().nullable().optional(),
211
211
  }),
212
+ // It creates, changes, or revokes a credential or who may use one: every
213
+ // call needs the user's approval, and "always allow" is not offered.
214
+ needsApproval: true,
215
+ allowPersistentApproval: false,
212
216
  run: async (args, ctx) => {
213
217
  await assertWorkspaceConnectionManager(ctx, args.allowedApps);
214
218
  const provider = getWorkspaceConnectionProvider(args.provider);
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: security
3
+ date: 2026-10-19
4
+ ---
5
+
6
+ Vault secrets, grants, link tokens, app access, and workspace connections now ask you to approve each change the agent or another app makes.
@@ -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.
@@ -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.