@agent-native/core 0.178.0 → 0.178.1-nightly-20260909221943

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 (88) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/.agents/skills/dictate/SKILL.md +1 -0
  3. package/corpus/templates/clips/DESIGN.md +10 -7
  4. package/corpus/templates/clips/actions/list-organization-state.ts +56 -3
  5. package/corpus/templates/clips/actions/list-recordings.ts +2 -0
  6. package/corpus/templates/clips/actions/search-dictations.ts +81 -0
  7. package/corpus/templates/clips/app/components/agent-destination-logos.tsx +50 -0
  8. package/corpus/templates/clips/app/components/bug-report/bug-report-dialog.tsx +60 -0
  9. package/corpus/templates/clips/app/components/bug-report/bug-report-form.tsx +206 -0
  10. package/corpus/templates/clips/app/components/clips-command-menu.tsx +653 -0
  11. package/corpus/templates/clips/app/components/library/empty-state.tsx +52 -17
  12. package/corpus/templates/clips/app/components/library/folder-card.tsx +34 -0
  13. package/corpus/templates/clips/app/components/library/folder-tree.tsx +109 -64
  14. package/corpus/templates/clips/app/components/library/library-action-hrefs.ts +27 -0
  15. package/corpus/templates/clips/app/components/library/library-grid.tsx +295 -103
  16. package/corpus/templates/clips/app/components/library/library-layout.tsx +381 -203
  17. package/corpus/templates/clips/app/components/library/library-primary-actions.tsx +48 -0
  18. package/corpus/templates/clips/app/components/library/page-header.tsx +48 -8
  19. package/corpus/templates/clips/app/components/library/recording-card.tsx +58 -55
  20. package/corpus/templates/clips/app/components/library/search-bar.tsx +5 -19
  21. package/corpus/templates/clips/app/components/library/sidebar-feedback-button.tsx +48 -0
  22. package/corpus/templates/clips/app/components/player/comments-panel.tsx +2 -2
  23. package/corpus/templates/clips/app/components/player/share-dialog.tsx +344 -260
  24. package/corpus/templates/clips/app/components/player/viewer-controls.tsx +2 -2
  25. package/corpus/templates/clips/app/components/recorder/camera-visualizer.tsx +0 -33
  26. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -1
  27. package/corpus/templates/clips/app/components/ui/tabs.tsx +1 -1
  28. package/corpus/templates/clips/app/hooks/use-command-search.ts +53 -0
  29. package/corpus/templates/clips/app/hooks/use-navigation-state.ts +32 -13
  30. package/corpus/templates/clips/app/hooks/use-upload-video-picker.tsx +1 -0
  31. package/corpus/templates/clips/app/i18n/en-US.ts +7 -0
  32. package/corpus/templates/clips/app/lib/agent-share.ts +16 -0
  33. package/corpus/templates/clips/app/lib/command-events.ts +7 -0
  34. package/corpus/templates/clips/app/root.tsx +6 -82
  35. package/corpus/templates/clips/app/routes/_app.dictate.tsx +25 -5
  36. package/corpus/templates/clips/app/routes/_app.library._index.tsx +2 -30
  37. package/corpus/templates/clips/app/routes/_app.library.folder.$folderId.tsx +13 -2
  38. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +4 -17
  39. package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +111 -114
  40. package/corpus/templates/clips/app/routes/_app.spaces.$spaceId.folder.$folderId.tsx +25 -2
  41. package/corpus/templates/clips/app/routes/_app.spaces.$spaceId.tsx +14 -61
  42. package/corpus/templates/clips/app/routes/_app.spaces._index.tsx +34 -24
  43. package/corpus/templates/clips/app/routes/_app.trash.tsx +1 -1
  44. package/corpus/templates/clips/app/routes/_app.tsx +2 -4
  45. package/corpus/templates/clips/app/routes/bug-report.tsx +11 -189
  46. package/corpus/templates/clips/app/routes/share.$shareId.tsx +148 -123
  47. package/corpus/templates/clips/changelog/2026-09-08-comments-and-desktop-window-controls.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-09-08-global-command-search.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-09-09-agent-sidebar-fits-below-toolbar.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-09-09-desktop-switches-stay-clear-in-dark-mode.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-09-09-recording-agent-uses-global-panel.md +6 -0
  52. package/corpus/templates/clips/desktop/src/app.tsx +2 -2
  53. package/corpus/templates/clips/desktop/src/components/MediaDeviceRow.tsx +5 -3
  54. package/corpus/templates/clips/desktop/src/components/SourceRow.tsx +1 -0
  55. package/corpus/templates/clips/desktop/src/components/Switch.tsx +2 -5
  56. package/corpus/templates/clips/desktop/src/components/ui/switch.tsx +21 -3
  57. package/corpus/templates/clips/desktop/src/hooks/useMediaDevices.ts +72 -52
  58. package/corpus/templates/clips/desktop/src/styles.css +18 -19
  59. package/corpus/templates/clips/desktop/src/tailwind.css +3 -0
  60. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +18 -10
  61. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +3 -5
  62. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +14 -3
  63. package/corpus/templates/design/AGENTS.md +2 -1
  64. package/corpus/templates/design/actions/export-png.ts +119 -0
  65. package/corpus/templates/design/actions/take-design-screenshot.ts +47 -13
  66. package/corpus/templates/design/server/plugins/agent-chat.ts +3 -1
  67. package/corpus/templates/factory/actions/dispatch-factory-item.ts +51 -22
  68. package/corpus/templates/factory/actions/list-triage-items.ts +21 -11
  69. package/corpus/templates/factory/actions/poll-slack-channel.ts +7 -2
  70. package/corpus/templates/factory/changelog/2026-09-09-slack-feedback-keeps-claimed-markers-after-poll.md +6 -0
  71. package/corpus/templates/factory/changelog/2026-09-09-slack-feedback-preserves-started-claimed-threads.md +6 -0
  72. package/corpus/templates/factory/changelog/2026-09-09-slack-feedback-skips-claimed-parents.md +6 -0
  73. package/corpus/templates/factory/server/lib/factory-automation-config.ts +1 -1
  74. package/corpus/templates/factory/server/lib/slack-feedback-prompt.ts +47 -0
  75. package/corpus/templates/factory/server/plugins/agent-chat.ts +5 -2
  76. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +5 -4
  77. package/corpus/templates/factory/server/triage/review-skill-alignment.ts +12 -7
  78. package/corpus/templates/factory/server/triage/slack-review-window.ts +39 -0
  79. package/dist/client/CommandMenu.js +13 -1
  80. package/dist/client/analytics.js +10 -0
  81. package/dist/collab/routes.d.ts +1 -1
  82. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  83. package/dist/notifications/routes.d.ts +2 -2
  84. package/dist/observability/routes.d.ts +5 -5
  85. package/dist/vite/client.js +25 -7
  86. package/dist/vite/sentry-source-maps.d.ts +12 -0
  87. package/dist/vite/sentry-source-maps.js +105 -0
  88. package/package.json +2 -1
@@ -48,6 +48,7 @@ import { and, eq } from "drizzle-orm";
48
48
  import { z } from "zod";
49
49
 
50
50
  import { getDb, schema } from "../server/db/index.js";
51
+ import { isAllowedFigmaSvgRenderRequest } from "../server/lib/design-to-figma-svg.js";
51
52
  import "../server/db/index.js"; // ensure registerShareableResource runs
52
53
 
53
54
  // ---------------------------------------------------------------------------
@@ -95,12 +96,22 @@ export interface ScreenshotDiagnostics {
95
96
  zeroSizeOrOffscreen: DiagnosticsZeroSizeEntry[];
96
97
  }
97
98
 
99
+ export type ScreenshotUploadError =
100
+ | {
101
+ code: "file_storage_not_configured";
102
+ message: string;
103
+ }
104
+ | {
105
+ code: "file_upload_failed";
106
+ message: string;
107
+ };
108
+
98
109
  export interface ScreenshotResult {
99
110
  viewport: ScreenshotViewport;
100
111
  url: string;
101
112
  /** True when persisted via a durable upload provider. */
102
113
  persisted: boolean;
103
- uploadError?: string;
114
+ uploadError?: ScreenshotUploadError;
104
115
  bytes: number;
105
116
  diagnostics: ScreenshotDiagnostics;
106
117
  }
@@ -586,6 +597,17 @@ export default defineAction({
586
597
  await context.addInitScript(
587
598
  "globalThis.__name = globalThis.__name || function (value) { return value; };",
588
599
  );
600
+ await context.route("**/*", async (route) => {
601
+ if (await isAllowedFigmaSvgRenderRequest(route.request().url())) {
602
+ await route.continue();
603
+ } else {
604
+ await route.abort("blockedbyclient");
605
+ }
606
+ });
607
+ // A static PNG never needs a live socket. Leaving a routed WebSocket
608
+ // unconnected blocks the separate WebSocket transport that
609
+ // BrowserContext.route("**/*") does not see.
610
+ await context.routeWebSocket("**/*", () => {});
589
611
  const page = await context.newPage();
590
612
  const consoleErrors: string[] = [];
591
613
  const fontLoadFailures: string[] = [];
@@ -677,23 +699,35 @@ export default defineAction({
677
699
  // for tall complex screens, reproduced on a 1440x3200 fixture.
678
700
  const png = await page.screenshot({ type: "png", fullPage: true });
679
701
 
680
- const uploaded = await uploadFile({
681
- data: png,
682
- mimeType: "image/png",
683
- filename: `design-${file.designId}-${file.filename}-${viewport.label}.png`,
684
- ownerEmail,
685
- }).catch(() => null);
702
+ let uploaded: Awaited<ReturnType<typeof uploadFile>> | null = null;
703
+ let uploadError: ScreenshotUploadError | undefined;
704
+ try {
705
+ uploaded = await uploadFile({
706
+ data: png,
707
+ mimeType: "image/png",
708
+ filename: `design-${file.designId}-${file.filename}-${viewport.label}.png`,
709
+ ownerEmail,
710
+ });
711
+ } catch {
712
+ uploadError = {
713
+ code: "file_upload_failed",
714
+ message:
715
+ "Configured file storage rejected the screenshot upload.",
716
+ };
717
+ }
718
+ if (!uploaded && !uploadError) {
719
+ uploadError = {
720
+ code: "file_storage_not_configured",
721
+ message:
722
+ "Screenshot was rendered but not returned because file storage is not configured.",
723
+ };
724
+ }
686
725
 
687
726
  screenshots.push({
688
727
  viewport,
689
728
  url: uploaded?.url ?? "",
690
729
  persisted: !!uploaded,
691
- ...(uploaded?.url
692
- ? {}
693
- : {
694
- uploadError:
695
- "Screenshot was rendered but not returned because file storage is not configured.",
696
- }),
730
+ ...(uploadError ? { uploadError } : {}),
697
731
  bytes: png.byteLength,
698
732
  diagnostics: {
699
733
  viewport,
@@ -39,6 +39,7 @@ const EXTERNAL_CONNECTOR_TOOL_NAMES = [
39
39
  "create-file",
40
40
  "update-file",
41
41
  "rename-screen",
42
+ "export-png",
42
43
  ];
43
44
 
44
45
  const INITIAL_TOOL_NAMES = [
@@ -77,6 +78,7 @@ const INITIAL_TOOL_NAMES = [
77
78
  "create-file",
78
79
  "update-file",
79
80
  "rename-screen",
81
+ "export-png",
80
82
  "navigate",
81
83
  "provider-api-catalog",
82
84
  "provider-api-docs",
@@ -235,7 +237,7 @@ export default createAgentChatPlugin({
235
237
  mcp: {
236
238
  connectorCatalog: EXTERNAL_CONNECTOR_TOOL_NAMES,
237
239
  instructions:
238
- "Resolve a named template or prior design first with list-design-templates / list-designs; copy with create-design-from-template, then adapt with edit-design — never regenerate a copied screen with generate-design. For new-design exploration use create-design then present-design-variants (2-5 variants) and surface the returned open link; do not navigate. Hand-off goes through export-html / export-zip / export-coding-handoff / export-design-as-figma-svg. Persist early: create or update the design and its files as soon as a coherent candidate exists. " +
240
+ "Resolve a named template or prior design first with list-design-templates / list-designs; copy with create-design-from-template, then adapt with edit-design — never regenerate a copied screen with generate-design. For new-design exploration use create-design then present-design-variants (2-5 variants) and surface the returned open link; do not navigate. Hand-off goes through export-png for one screen, or export-html / export-zip / export-coding-handoff / export-design-as-figma-svg for other formats. Persist early: create or update the design and its files as soon as a coherent candidate exists. " +
239
241
  'Design system: get-design, get-design-snapshot, and view-screen return `designSystem` (a bounded summary with scope "summary" and a `next` line); call get-design-system { id } once before the first screen you author for the full context (create-design returns it in full), then reuse it. Apply designSystem.agentContext, plus index-design-tokens for an existing design, before authoring or restyling; never invent a generic palette. For a new design, pass the exact title as `designSystem` or a designSystemId; omit both to link the caller\'s default. Preserve existing screen composition as well as linked system tokens, fonts, assets, and custom instructions. Read back the saved file after every visual mutation.',
240
242
  },
241
243
  externalAgents: { writes: "allowlisted" },
@@ -47,6 +47,7 @@ import {
47
47
  createSlackReader,
48
48
  isAgentNativeSlackUserName,
49
49
  } from "../server/triage/slack-client.js";
50
+ import { dispatchSkipStatusWrite } from "../server/triage/slack-review-window.js";
50
51
 
51
52
  /** Slack notifies only with `<@USERID>`. Plaintext @handles do not ping anyone. */
52
53
  const REPLY_INSTRUCTION =
@@ -280,7 +281,7 @@ export async function recordAutomaticBuilderDecision(input: {
280
281
  userEmail: string;
281
282
  orgId: string;
282
283
  factoryId: string;
283
- outcome: "propose_fix" | "needs_manual";
284
+ outcome: "propose_fix" | "needs_manual" | "observe";
284
285
  reason: string;
285
286
  guardResults: Array<{ code: string; passed: boolean; reason: string }>;
286
287
  }) {
@@ -323,13 +324,20 @@ export async function recordAutomaticBuilderDecision(input: {
323
324
 
324
325
  export default defineAction({
325
326
  description:
326
- "Tag Builder for a Factory item, or record a skip when clearBug is false. Slack items stay in-thread: this action pings Builder with the configured Slack member id; do not post Slack messages or @handles yourself. Pass optional reaction (an emoji name such as robot_face) to mark the item source when that provider can; omit it to add no reaction. Grouped Slack repeats share one Builder thread. GitHub issues and Sentry errors tag @builderio-bot on a GitHub issue in the factory repository. Owner-managed Clips, Design, and Content items are always left for their owner.",
327
+ "Tag Builder for a Factory item, or record a skip when clearBug is false or alreadyClaimed is true. Slack items stay in-thread: this action pings Builder with the configured Slack member id; do not post Slack messages or @handles yourself. Pass optional reaction (an emoji name such as robot_face) to mark the item source when that provider can; omit it to add no reaction. Grouped Slack repeats share one Builder thread. GitHub issues and Sentry errors tag @builderio-bot on a GitHub issue in the factory repository. Owner-managed Clips, Design, and Content items are always left for their owner.",
327
328
  schema: z.object({
328
329
  itemId: z.string().min(1),
330
+ alreadyClaimed: z
331
+ .boolean()
332
+ .default(false)
333
+ .describe(
334
+ "True when the Slack parent already has eyes or robot_face. Records the skip without starting Builder work: received items become needs_manual so they leave needsReview; items that already started keep their status. clearBug may be omitted or false.",
335
+ ),
329
336
  clearBug: z
330
337
  .boolean()
338
+ .default(false)
331
339
  .describe(
332
- "True when the item is a concrete, reproducible defect with enough evidence to investigate (including visual/UI defects such as a duplicate control, broken layout, or incorrect state). False for feature requests, vague questions, and incomplete threads.",
340
+ "True when the item is a concrete, reproducible defect with enough evidence to investigate (including visual/UI defects such as a duplicate control, broken layout, or incorrect state). False for feature requests, vague questions, incomplete threads, and claimed Slack parents. May be omitted when alreadyClaimed is true.",
333
341
  ),
334
342
  reason: z.string().trim().min(1).max(4_000),
335
343
  productUxImplications: z
@@ -359,6 +367,7 @@ export default defineAction({
359
367
  run: async (
360
368
  {
361
369
  itemId,
370
+ alreadyClaimed,
362
371
  clearBug,
363
372
  reason,
364
373
  productUxImplications,
@@ -494,13 +503,19 @@ export default defineAction({
494
503
  ]
495
504
  : []),
496
505
  ];
497
- const blocked = guardResults.some((guard) => !guard.passed);
506
+ const blocked =
507
+ alreadyClaimed || guardResults.some((guard) => !guard.passed);
508
+ const skipStatus = dispatchSkipStatusWrite(item.status);
498
509
  const decisionId = await recordAutomaticBuilderDecision({
499
510
  itemId,
500
511
  userEmail,
501
512
  orgId,
502
513
  factoryId,
503
- outcome: blocked ? "needs_manual" : "propose_fix",
514
+ outcome: blocked
515
+ ? skipStatus.statusPreserved
516
+ ? "observe"
517
+ : "needs_manual"
518
+ : "propose_fix",
504
519
  reason,
505
520
  guardResults,
506
521
  });
@@ -518,35 +533,49 @@ export default defineAction({
518
533
  summary: reason,
519
534
  details: {
520
535
  decisionId,
536
+ alreadyClaimed,
521
537
  clearBug,
522
538
  productUxImplications,
523
539
  ownerOwnedArea: ownerManagedArea,
540
+ statusPreserved: blocked ? skipStatus.statusPreserved : false,
524
541
  guardResults,
525
542
  },
526
543
  },
527
544
  );
528
545
  if (blocked) {
529
- await db.transaction(async (tx) => {
530
- await tx
531
- .update(triageItems)
532
- .set({ status: "needs_manual", updatedAt: new Date().toISOString() })
533
- .where(
534
- and(
535
- eq(triageItems.id, itemId),
536
- eq(triageItems.orgId, orgId),
537
- factoryStillPresent(tx as unknown as typeof db, orgId, factoryId),
538
- ),
546
+ const nextStatus = skipStatus.nextStatus;
547
+ if (nextStatus) {
548
+ await db.transaction(async (tx) => {
549
+ await tx
550
+ .update(triageItems)
551
+ .set({
552
+ status: nextStatus,
553
+ updatedAt: new Date().toISOString(),
554
+ })
555
+ .where(
556
+ and(
557
+ eq(triageItems.id, itemId),
558
+ eq(triageItems.orgId, orgId),
559
+ factoryStillPresent(
560
+ tx as unknown as typeof db,
561
+ orgId,
562
+ factoryId,
563
+ ),
564
+ ),
565
+ );
566
+ await requireExistingFactory(
567
+ tx as unknown as typeof db,
568
+ orgId,
569
+ factoryId,
539
570
  );
540
- await requireExistingFactory(
541
- tx as unknown as typeof db,
542
- orgId,
543
- factoryId,
544
- );
545
- });
571
+ });
572
+ }
546
573
  return {
547
574
  ok: true,
548
575
  started: false,
549
- needsManual: true,
576
+ needsManual: skipStatus.needsManual,
577
+ alreadyClaimed,
578
+ statusPreserved: skipStatus.statusPreserved,
550
579
  decisionId,
551
580
  reason,
552
581
  };
@@ -33,6 +33,7 @@ import {
33
33
  triageItemAuthorId,
34
34
  } from "../server/triage/metadata.js";
35
35
  import { babysitLeavesReviewWindow } from "../server/triage/pr-babysit.js";
36
+ import { slackFeedbackLeavesReviewWindow } from "../server/triage/slack-review-window.js";
36
37
  import { readStoredUserLabels } from "../server/triage/slack-user-labels.js";
37
38
 
38
39
  export default defineAction({
@@ -79,24 +80,20 @@ export default defineAction({
79
80
  const fetchLimit =
80
81
  context?.caller === "automation" &&
81
82
  calling &&
82
- calling.config.source === "github"
83
+ (calling.config.source === "github" || calling.config.source === "slack")
83
84
  ? Math.min(100, Math.max(effectiveLimit * 10, effectiveLimit))
84
85
  : effectiveLimit;
85
86
  const parsedCursor = cursor ? decodeInboxCursor(cursor) : null;
86
87
  const updatedAfterBound = parseUpdatedAfter(updatedAfter);
87
88
  const db = getDb();
88
- const reviewStatuses =
89
- source === "github"
90
- ? ["pr_observed"]
91
- : source === "slack"
92
- ? ["received", "automation_started", "evidence_ready"]
93
- : ["received"];
94
- const filterGithubReviewPage =
89
+ const reviewStatuses = source === "github" ? ["pr_observed"] : ["received"];
90
+ const filterReviewPage =
95
91
  (context?.caller === "automation" &&
96
92
  calling &&
97
- calling.config.source === "github") ||
98
- (needsReview && source === "github");
99
- const maxScanPages = filterGithubReviewPage ? 10 : 1;
93
+ (calling.config.source === "github" ||
94
+ calling.config.source === "slack")) ||
95
+ (needsReview && (source === "github" || source === "slack"));
96
+ const maxScanPages = filterReviewPage ? 10 : 1;
100
97
  const eligible: Array<(typeof triageItems)["$inferSelect"]> = [];
101
98
  let scanCursor = parsedCursor;
102
99
  let lastExamined: (typeof triageItems)["$inferSelect"] | undefined;
@@ -165,6 +162,19 @@ export default defineAction({
165
162
  ) {
166
163
  continue;
167
164
  }
165
+ if (
166
+ needsReview &&
167
+ source === "slack" &&
168
+ slackFeedbackLeavesReviewWindow({
169
+ status: item.status,
170
+ slackReactionName: metadataString(
171
+ parseTriageMetadata(item.metadataJson),
172
+ "slackReactionName",
173
+ ),
174
+ })
175
+ ) {
176
+ continue;
177
+ }
168
178
  eligible.push(item);
169
179
  lastKept = item;
170
180
  if (eligible.length === effectiveLimit) {
@@ -26,6 +26,7 @@ import {
26
26
  import { recordFactoryAudit } from "../server/triage/audit.js";
27
27
  import type { IngestionEnvelope } from "../server/triage/contracts.js";
28
28
  import { itemDedupeKey } from "../server/triage/ids.js";
29
+ import { mergeTriageMetadata } from "../server/triage/metadata.js";
29
30
  import {
30
31
  hasTriageSourceChanged,
31
32
  statusAfterTriageSourceUpdate,
@@ -178,6 +179,10 @@ export default defineAction({
178
179
  const lastSeenAt = sourceChanged
179
180
  ? sourceLastSeenAt
180
181
  : (existing?.lastSeenAt ?? now);
182
+ const metadataJson = mergeTriageMetadata(
183
+ existing?.metadataJson ?? "{}",
184
+ envelope.metadata ?? {},
185
+ );
181
186
  await tx
182
187
  .insert(triageItems)
183
188
  .values({
@@ -196,7 +201,7 @@ export default defineAction({
196
201
  headSha: envelope.headSha ?? null,
197
202
  coverage: envelope.coverage,
198
203
  dedupeKey: id,
199
- metadataJson: JSON.stringify(envelope.metadata ?? {}),
204
+ metadataJson,
200
205
  lastSeenAt,
201
206
  createdAt: now,
202
207
  updatedAt,
@@ -213,7 +218,7 @@ export default defineAction({
213
218
  channelId: envelope.channelId ?? null,
214
219
  threadTs: envelope.threadTs ?? null,
215
220
  coverage: envelope.coverage,
216
- metadataJson: JSON.stringify(envelope.metadata ?? {}),
221
+ metadataJson,
217
222
  status,
218
223
  lastSeenAt,
219
224
  updatedAt,
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-09
4
+ ---
5
+
6
+ Slack feedback keeps a claimed 👀/🤖 marker after polling, so those threads stay out of the review queue.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-09
4
+ ---
5
+
6
+ Slack feedback no longer moves an already-started thread to needs-manual just because the parent already has 👀 or 🤖.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-09-09
4
+ ---
5
+
6
+ Slack feedback jobs skip threads whose parent already has 👀 or 🤖, instead of tagging Builder again.
@@ -479,7 +479,7 @@ export function buildGuardrailsText(
479
479
  }
480
480
  if (config.source === "slack") {
481
481
  lines.push(
482
- "Never post Slack messages, reactions, or plaintext @handles. If the prompt names a reaction, pass it as reaction on dispatch-factory-item; that action adds it on the source when possible.",
482
+ "Never post Slack messages, reactions, or plaintext @handles. Pass reaction on dispatch-factory-item only when the prompt says to mark that item; omit it on skips. That action adds it on the source when possible.",
483
483
  );
484
484
  }
485
485
  const extraText = extra?.trim();
@@ -17,6 +17,44 @@ non-bug report.`;
17
17
  const OBSOLETE_SLACK_TAG_PARAGRAPH =
18
18
  /The Builder reply must tag @builder\.io[\s\S]*?non-bug\s+report\.\s*/;
19
19
 
20
+ const RETIRED_UNCONDITIONAL_ROBOT_FACE = `For each item, call dispatch-factory-item with clearBug true or false,
21
+ productUxImplications false unless it is a pure product or design decision
22
+ with no single correct fix, a short reason, and reaction robot_face 🤖.`;
23
+
24
+ const RETIRED_CLAIMED_SKIP_VIA_CLEAR_BUG_FALSE = `Look at the parent message reactions from get-slack-feedback-context. If the
25
+ parent already has eyes 👀 or robot_face 🤖, it has already been looked at:
26
+ call dispatch-factory-item with clearBug false, omit reaction, and a short
27
+ reason that names the existing marker. Do not start Builder work on it.
28
+
29
+ For every other item, call dispatch-factory-item with clearBug true or false,
30
+ productUxImplications false unless it is a pure product or design decision
31
+ with no single correct fix, and a short reason. Pass reaction robot_face 🤖
32
+ only when clearBug is true and the parent has neither eyes nor robot_face.
33
+ Omit reaction on skips.`;
34
+
35
+ const RETIRED_CLAIMED_SKIP_OMITS_CLEAR_BUG = `Look at the parent message reactions from get-slack-feedback-context. If the
36
+ parent already has eyes 👀 or robot_face 🤖, it has already been looked at:
37
+ call dispatch-factory-item with alreadyClaimed true, omit reaction, and a short
38
+ reason that names the existing marker. Do not start Builder work on it.
39
+
40
+ For every other item, call dispatch-factory-item with clearBug true or false,
41
+ productUxImplications false unless it is a pure product or design decision
42
+ with no single correct fix, and a short reason. Pass reaction robot_face 🤖
43
+ only when clearBug is true and the parent has neither eyes nor robot_face.
44
+ Omit reaction on skips.`;
45
+
46
+ export const SLACK_FEEDBACK_DISPATCH_INSTRUCTIONS = `Look at the parent message reactions from get-slack-feedback-context. If the
47
+ parent already has eyes 👀 or robot_face 🤖, it has already been looked at:
48
+ call dispatch-factory-item with alreadyClaimed true (clearBug may be omitted
49
+ or false), omit reaction, and a short reason that names the existing marker.
50
+ Do not start Builder work on it.
51
+
52
+ For every other item, call dispatch-factory-item with clearBug true or false,
53
+ productUxImplications false unless it is a pure product or design decision
54
+ with no single correct fix, and a short reason. Pass reaction robot_face 🤖
55
+ only when clearBug is true and the parent has neither eyes nor robot_face.
56
+ Omit reaction on skips.`;
57
+
20
58
  function stripPastedGuard(content: string, pasted: string): string {
21
59
  return content.split(pasted).join("");
22
60
  }
@@ -35,5 +73,14 @@ export function repairSlackFeedbackPrompt(content: string): string {
35
73
  next = stripPastedGuard(next, PASTED_HANDOFF_INSTRUCTION);
36
74
  next = stripPastedGuard(next, PASTED_MENTION_GUARD);
37
75
  next = stripPastedGuard(next, PASTED_SKIP_GUARD);
76
+ next = next
77
+ .split(RETIRED_UNCONDITIONAL_ROBOT_FACE)
78
+ .join(SLACK_FEEDBACK_DISPATCH_INSTRUCTIONS);
79
+ next = next
80
+ .split(RETIRED_CLAIMED_SKIP_VIA_CLEAR_BUG_FALSE)
81
+ .join(SLACK_FEEDBACK_DISPATCH_INSTRUCTIONS);
82
+ next = next
83
+ .split(RETIRED_CLAIMED_SKIP_OMITS_CLEAR_BUG)
84
+ .join(SLACK_FEEDBACK_DISPATCH_INSTRUCTIONS);
38
85
  return `${next.replace(/\n{3,}/g, "\n\n").trimEnd()}\n`;
39
86
  }
@@ -99,8 +99,11 @@ manual. Clips, Design, and Content are fully owner-managed: never react, tag
99
99
  Builder, auto-approve, or auto-merge those items. Slack clear bugs use the
100
100
  thread-preserving dispatch-factory-item flow; GitHub issues and Sentry clear
101
101
  bugs tag @builderio-bot on a GitHub issue. Slack repeat reports must be clustered
102
- by underlying symptom, with one Builder thread for the cluster. After classifying an item, call dispatch-factory-item with
103
- clearBug true or false and a short reason so a skip is recorded. Pass reaction when the prompt names one. Do not post
102
+ by underlying symptom, with one Builder thread for the cluster. After classifying an item, call dispatch-factory-item so the skip or
103
+ dispatch is recorded. If a Slack parent already has eyes or robot_face, pass
104
+ alreadyClaimed true (clearBug may be omitted or false) so started work is not
105
+ rewritten to needs_manual. Otherwise pass clearBug true or false and a short reason.
106
+ Pass reaction only when the prompt says to mark that item; omit it on skips. Do not post
104
107
  Slack messages, reactions, or @mentions yourself; dispatch-factory-item owns
105
108
  the Builder ping. Use /address-feedback for the repository feedback workflow.
106
109
  For pull requests, follow review-prs: read the complete diff and review
@@ -56,7 +56,10 @@ import {
56
56
  BABYSIT_SCOPE_INSTRUCTION,
57
57
  repairPrBabysitPrompt,
58
58
  } from "../lib/pr-babysit-prompt.js";
59
- import { repairSlackFeedbackPrompt } from "../lib/slack-feedback-prompt.js";
59
+ import {
60
+ repairSlackFeedbackPrompt,
61
+ SLACK_FEEDBACK_DISPATCH_INSTRUCTIONS,
62
+ } from "../lib/slack-feedback-prompt.js";
60
63
  import {
61
64
  syncManagedReviewSkillAlignment,
62
65
  type FactoryAutomationName,
@@ -225,9 +228,7 @@ enough evidence to investigate — including visual/UI defects such as a
225
228
  duplicate control or broken layout. Feature requests, vague questions, and
226
229
  incomplete threads are not.
227
230
 
228
- For each item, call dispatch-factory-item with clearBug true or false,
229
- productUxImplications false unless it is a pure product or design decision
230
- with no single correct fix, a short reason, and reaction robot_face 🤖.
231
+ ${SLACK_FEEDBACK_DISPATCH_INSTRUCTIONS}
231
232
  Cluster only items listed in this run: one dispatch with relatedItemIds. Do
232
233
  not dispatch needs_manual items or items that already started.
233
234
 
@@ -21,10 +21,14 @@ contract is evidence-first and reply-producing:
21
21
  state.
22
22
  - Check the existing Slack reaction marker and owner before any write.
23
23
  Preserve an existing marker; if reactions cannot be read, do not guess or
24
- add one. Do not react to or dispatch Design UX/interaction work (Sid) or
25
- any Content work (Alice); record the owner instead.
26
- - For an actionable repo-owned Slack item with no existing marker, pass
27
- \`reaction: robot_face\` 🤖 on \`dispatch-factory-item\`. Every parent this run
24
+ add one. If the parent already has eyes 👀 or robot_face 🤖, call
25
+ \`dispatch-factory-item\` with \`alreadyClaimed: true\` (\`clearBug\` may be
26
+ omitted or \`false\`), omit reaction, and do not start Builder work. Do not
27
+ react to or dispatch Design UX/interaction
28
+ work (Sid) or any Content work (Alice); record the owner instead.
29
+ - For an actionable repo-owned Slack item with no existing eyes 👀 or
30
+ robot_face 🤖 marker, pass \`reaction: robot_face\` 🤖 on
31
+ \`dispatch-factory-item\`. Do not pass reaction eyes. Every parent this run
28
32
  marks must later receive a verified @agent-native Fixed, In progress, or
29
33
  Clarification needed reply; a reaction, forward, generic acknowledgement,
30
34
  or another person's reply is not a disposition. Group only genuinely
@@ -37,9 +41,10 @@ contract is evidence-first and reply-producing:
37
41
  separate. Do not claim a fix, PR, reply, or deployment without confirmation
38
42
  from the relevant action or runtime evidence.
39
43
 
40
- After classifying every processed item, call \`dispatch-factory-item\` with
41
- \`clearBug: true\` or \`false\` and a concise evidence-grounded reason so every
42
- skip or dispatch is recorded.`;
44
+ After classifying every processed item, call \`dispatch-factory-item\` so every
45
+ skip or dispatch is recorded: \`alreadyClaimed: true\` (\`clearBug\` may be
46
+ omitted or \`false\`) when the parent already has eyes or robot_face, otherwise
47
+ \`clearBug: true\` or \`false\` with a concise evidence-grounded reason.`;
43
48
 
44
49
  const PR_ALIGNMENT = `## Current review-prs contract
45
50
 
@@ -0,0 +1,39 @@
1
+ export function isClaimedSlackReactionName(
2
+ name: string | null | undefined,
3
+ ): boolean {
4
+ const normalized = name?.trim().toLowerCase();
5
+ return normalized === "eyes" || normalized === "robot_face";
6
+ }
7
+
8
+ export function dispatchSkipPreservesItemStatus(status: string): boolean {
9
+ return status === "automation_started" || status === "evidence_ready";
10
+ }
11
+
12
+ export function slackFeedbackLeavesReviewWindow(input: {
13
+ status: string;
14
+ slackReactionName?: string | null;
15
+ }): boolean {
16
+ return (
17
+ dispatchSkipPreservesItemStatus(input.status) ||
18
+ isClaimedSlackReactionName(input.slackReactionName)
19
+ );
20
+ }
21
+
22
+ export function dispatchSkipStatusWrite(status: string): {
23
+ nextStatus: "needs_manual" | null;
24
+ needsManual: boolean;
25
+ statusPreserved: boolean;
26
+ } {
27
+ if (dispatchSkipPreservesItemStatus(status)) {
28
+ return {
29
+ nextStatus: null,
30
+ needsManual: false,
31
+ statusPreserved: true,
32
+ };
33
+ }
34
+ return {
35
+ nextStatus: "needs_manual",
36
+ needsManual: true,
37
+ statusPreserved: false,
38
+ };
39
+ }
@@ -224,6 +224,15 @@ export function CommandMenu({ open, onOpenChange, children, renderResults, place
224
224
  if (!React.isValidElement(child))
225
225
  return child;
226
226
  const props = child.props;
227
+ if (child.type === React.Fragment) {
228
+ const fragmentChildren = filterChildren(props.children);
229
+ if (React.Children.count(fragmentChildren) === 0)
230
+ return null;
231
+ return React.cloneElement(child, {
232
+ ...props,
233
+ children: fragmentChildren,
234
+ });
235
+ }
227
236
  // If it's a CommandGroup, filter its children
228
237
  if (child.type === CommandGroup) {
229
238
  const groupChildren = filterChildren(props.children);
@@ -324,6 +333,10 @@ export function useCommandMenuShortcut(onOpen, options = {}) {
324
333
  useEffect(() => {
325
334
  const handleKeyDown = (e) => {
326
335
  if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
336
+ // Claim the shortcut before checking the focused element so an outer
337
+ // host cannot open its own command menu while this one is focused.
338
+ e.preventDefault();
339
+ e.stopPropagation();
327
340
  // Don't trigger if user is typing in a native form control.
328
341
  const target = e.target instanceof HTMLElement ? e.target : null;
329
342
  const isContentEditable = target?.isContentEditable;
@@ -333,7 +346,6 @@ export function useCommandMenuShortcut(onOpen, options = {}) {
333
346
  (!options.allowContentEditable && isContentEditable)) {
334
347
  return;
335
348
  }
336
- e.preventDefault();
337
349
  onOpen();
338
350
  }
339
351
  };
@@ -7,6 +7,7 @@ import { toPostHogExceptionProperties } from "../tracking/posthog-exception.js";
7
7
  import { getAnalyticsClientPlatform } from "./analytics-platform.js";
8
8
  import { getOrCreateAnalyticsAnonymousId, getOrCreateAnalyticsSessionId, } from "./analytics-session.js";
9
9
  import { injectedAgentNativeConfig } from "./app-config.js";
10
+ import { clientBuildId } from "./build-compatibility.js";
10
11
  export { clearAnalyticsSessionId, setAnalyticsSessionId, } from "./analytics-session.js";
11
12
  import { fetchAgentEngineStatus, fetchAuthSessionStatus, } from "./client-status-requests.js";
12
13
  import { installErrorCapture, } from "./error-capture.js";
@@ -721,6 +722,14 @@ function resolveClientDeploymentEnvironment() {
721
722
  env.MODE ||
722
723
  "production");
723
724
  }
725
+ /**
726
+ * Must match `resolveSentryClientRelease()` in `vite/sentry-source-maps.ts`
727
+ * exactly — that's the release name uploaded source maps are attached to, so
728
+ * a mismatch here means captured events never resolve against them.
729
+ */
730
+ function resolveClientRelease() {
731
+ return `agent-native-client@${clientBuildId() || "development"}`;
732
+ }
724
733
  function captureWithSentry(module, error, context) {
725
734
  return module.withScope((scope) => {
726
735
  if (context.tags) {
@@ -764,6 +773,7 @@ function ensureSentry(loadWithoutDsn = false) {
764
773
  module.init({
765
774
  dsn,
766
775
  environment: resolveClientDeploymentEnvironment(),
776
+ release: resolveClientRelease(),
767
777
  beforeSend(event) {
768
778
  if (isSyntheticBrowserTraffic())
769
779
  return null;
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
26
26
  * Body: { update: string (base64), requestSource?: string }
27
27
  */
28
28
  export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
29
- error: string;
30
29
  ok?: undefined;
30
+ error: string;
31
31
  } | {
32
32
  error?: undefined;
33
33
  ok: boolean;
@@ -17,11 +17,11 @@ declare const _default: import("../../action.js").ActionDefinition<{
17
17
  id?: undefined;
18
18
  provider?: undefined;
19
19
  } | {
20
+ error?: undefined;
20
21
  configured?: undefined;
21
22
  connectPath?: undefined;
22
23
  url: string;
23
24
  id: string;
24
25
  provider: string;
25
- error?: undefined;
26
26
  }>;
27
27
  export default _default;