@agent-native/core 0.164.12 → 0.164.17
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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/netlify.toml +4 -0
- package/corpus/templates/clips/actions/request-transcript.ts +10 -6
- package/corpus/templates/content/actions/_database-source-utils.ts +25 -2
- package/corpus/templates/content/actions/_database-utils.ts +2 -0
- package/corpus/templates/content/actions/_local-folder-source.ts +186 -2
- package/corpus/templates/content/actions/connect-local-folder-source.ts +23 -8
- package/corpus/templates/content/actions/disconnect-local-folder-source.ts +3 -3
- package/corpus/templates/content/actions/resolve-local-folder-conflict.ts +2 -2
- package/corpus/templates/content/actions/sync-local-folder-source.ts +130 -12
- package/corpus/templates/content/app/components/LocalFolderLiveSync.tsx +77 -0
- package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +298 -3
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +354 -8
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +23 -1
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +81 -1
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +36 -0
- package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +18 -4
- package/corpus/templates/content/app/components/editor/local-source-write-state.ts +52 -0
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +244 -6
- package/corpus/templates/content/app/components/sidebar/local-source-hierarchy.ts +189 -0
- package/corpus/templates/content/app/hooks/use-create-page.ts +26 -4
- package/corpus/templates/content/app/hooks/use-documents.ts +83 -0
- package/corpus/templates/content/app/i18n-data.ts +239 -0
- package/corpus/templates/content/app/lib/desktop-content-files.ts +57 -1
- package/corpus/templates/content/app/lib/local-content-source-files.ts +210 -34
- package/corpus/templates/content/app/lib/local-folder-live-sync.ts +191 -0
- package/corpus/templates/content/app/root.tsx +2 -1
- package/corpus/templates/content/app/routes/_app.local-files.tsx +103 -38
- package/corpus/templates/content/docs/product/capabilities/content.source.file-folder.md +2 -2
- package/corpus/templates/content/docs/product/features/content.feature.bring-your-local-work.md +3 -3
- package/corpus/templates/content/docs/product/roadmap.md +2 -2
- package/corpus/templates/content/docs/solutions/2026-08-18-colored-text-highlighting-shape.md +693 -0
- package/corpus/templates/content/docs/solutions/2026-08-18-content-navigation-coherence-shape.md +482 -0
- package/corpus/templates/content/package.json +1 -0
- package/corpus/templates/content/shared/api.ts +25 -0
- package/corpus/templates/content/shared/nfm.ts +14 -3
- package/corpus/templates/factory/AGENTS.md +24 -29
- package/corpus/templates/factory/actions/get-slack-feedback-context.ts +8 -28
- package/corpus/templates/factory/actions/get-triage-config.ts +1 -0
- package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +67 -59
- package/corpus/templates/factory/actions/list-triage-items.ts +6 -1
- package/corpus/templates/factory/actions/reconcile-triage-run.ts +7 -1
- package/corpus/templates/factory/actions/save-triage-config.ts +27 -7
- package/corpus/templates/factory/actions/start-builder-for-item.ts +113 -31
- package/corpus/templates/factory/actions/view-screen.ts +12 -0
- package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +457 -0
- package/corpus/templates/factory/app/components/factory/FactoryWorkspaceActions.tsx +53 -0
- package/corpus/templates/factory/app/components/layout/Layout.tsx +2 -0
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +3 -1
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +10 -7
- package/corpus/templates/factory/app/i18n/en-US.ts +3 -0
- package/corpus/templates/factory/app/routes/agents.tsx +22 -1
- package/corpus/templates/factory/app/routes/factory-settings.tsx +34 -0
- package/corpus/templates/factory/app/routes/factory.tsx +33 -323
- package/corpus/templates/factory/server/connectors/slack.ts +62 -0
- package/corpus/templates/factory/server/db/schema.ts +1 -0
- package/corpus/templates/factory/server/lib/slack-feedback-prompt.ts +30 -0
- package/corpus/templates/factory/server/plugins/agent-chat.ts +9 -8
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +7 -0
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +43 -25
- package/corpus/templates/factory/server/triage/ai-services-git.ts +2 -0
- package/corpus/templates/factory/server/triage/pr-monitor.ts +2 -1
- package/corpus/templates/factory/server/triage/pr-policy.ts +197 -19
- package/corpus/templates/factory/server/triage/review-skill-alignment.ts +127 -0
- package/corpus/templates/factory/server/triage/slack-client.ts +79 -6
- package/dist/app-config/auth.d.ts +4 -0
- package/dist/app-config/auth.js +7 -0
- package/dist/app-config/schema.d.ts +3 -0
- package/dist/app-config/schema.js +2 -0
- package/dist/cli/code-agent-executor.js +6 -1
- package/dist/cli/code-agent-mcp-config.d.ts +2 -2
- package/dist/cli/code-agent-mcp-config.js +34 -9
- package/dist/client/MultiTabAssistantChat.js +28 -1
- package/dist/client/org/OrgSwitcher.js +8 -4
- package/dist/client/use-chat-threads.d.ts +1 -0
- package/dist/client/use-chat-threads.js +13 -3
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/mcp-client/config.d.ts +11 -0
- package/dist/mcp-client/config.js +33 -26
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +17 -3
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/vite/client.js +2 -0
- package/package.json +1 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AgentToggleButton,
|
|
3
2
|
requestAgentChatThreadOpen,
|
|
4
3
|
useChatModels,
|
|
5
4
|
} from "@agent-native/core/client/agent-chat";
|
|
@@ -8,8 +7,6 @@ import {
|
|
|
8
7
|
useActionQuery,
|
|
9
8
|
} from "@agent-native/core/client/hooks";
|
|
10
9
|
import { useT } from "@agent-native/core/client/i18n";
|
|
11
|
-
import { buildSettingsRoute } from "@agent-native/core/client/navigation";
|
|
12
|
-
import { SettingsGroup, SettingsRow } from "@agent-native/core/client/settings";
|
|
13
10
|
import {
|
|
14
11
|
IconAlertCircle,
|
|
15
12
|
IconArrowLeft,
|
|
@@ -20,7 +17,7 @@ import {
|
|
|
20
17
|
IconRefresh,
|
|
21
18
|
} from "@tabler/icons-react";
|
|
22
19
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
23
|
-
import { Link, useSearchParams } from "react-router";
|
|
20
|
+
import { Link, Navigate, useSearchParams } from "react-router";
|
|
24
21
|
import { toast } from "sonner";
|
|
25
22
|
|
|
26
23
|
import { FactoryAgentsView } from "@/components/factory/FactoryAgentsView";
|
|
@@ -33,13 +30,13 @@ import {
|
|
|
33
30
|
} from "@/components/factory/FactoryCanvas";
|
|
34
31
|
import { FactoryHistoryView } from "@/components/factory/FactoryHistoryView";
|
|
35
32
|
import { FactoryInspector } from "@/components/factory/FactoryInspector";
|
|
33
|
+
import { FactoryWorkspaceActions } from "@/components/factory/FactoryWorkspaceActions";
|
|
36
34
|
import { TriageStatusPill } from "@/components/triage/triage-status-pill";
|
|
37
35
|
import { Button } from "@/components/ui/button";
|
|
38
36
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
39
37
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
40
38
|
import { Input } from "@/components/ui/input";
|
|
41
39
|
import { Label } from "@/components/ui/label";
|
|
42
|
-
import { Switch } from "@/components/ui/switch";
|
|
43
40
|
import { Textarea } from "@/components/ui/textarea";
|
|
44
41
|
|
|
45
42
|
type FactoryGraphResponse = {
|
|
@@ -104,25 +101,6 @@ type TriageRule = {
|
|
|
104
101
|
promptVersion: number;
|
|
105
102
|
};
|
|
106
103
|
|
|
107
|
-
type TriageConfig = {
|
|
108
|
-
slackWorkspace?: "primary" | "secondary";
|
|
109
|
-
slackChannelId?: string | null;
|
|
110
|
-
slackChannelName?: string | null;
|
|
111
|
-
pollingEnabled?: boolean;
|
|
112
|
-
githubPollingEnabled?: boolean;
|
|
113
|
-
sentryPollingEnabled?: boolean;
|
|
114
|
-
sentryOrgSlug?: string | null;
|
|
115
|
-
sentryProjectSlug?: string | null;
|
|
116
|
-
sentryEnvironment?: string | null;
|
|
117
|
-
repository?: string | null;
|
|
118
|
-
automationFailureAlertsEnabled?: boolean;
|
|
119
|
-
automationFailureAlertEmail?: string | null;
|
|
120
|
-
emailReadiness?: {
|
|
121
|
-
status: "ready" | "not-configured" | "misconfigured" | "unavailable";
|
|
122
|
-
provider: string;
|
|
123
|
-
};
|
|
124
|
-
};
|
|
125
|
-
|
|
126
104
|
type Verdict = "correct" | "incorrect" | "uncertain";
|
|
127
105
|
type WorkspaceTab =
|
|
128
106
|
| "overview"
|
|
@@ -132,8 +110,7 @@ type WorkspaceTab =
|
|
|
132
110
|
| "automations"
|
|
133
111
|
| "agents"
|
|
134
112
|
| "audit"
|
|
135
|
-
| "history"
|
|
136
|
-
| "settings";
|
|
113
|
+
| "history";
|
|
137
114
|
|
|
138
115
|
type FactoryAutomationRun = {
|
|
139
116
|
id?: string;
|
|
@@ -145,14 +122,6 @@ type FactoryAutomationRun = {
|
|
|
145
122
|
threadId?: string | null;
|
|
146
123
|
};
|
|
147
124
|
|
|
148
|
-
type FactoryAutomationHealth = {
|
|
149
|
-
status: "healthy" | "stale" | "error" | "no-data";
|
|
150
|
-
lastCheckedAt?: number | null;
|
|
151
|
-
lastDispatchedAt?: number | null;
|
|
152
|
-
lastError?: string | null;
|
|
153
|
-
runtime?: string | null;
|
|
154
|
-
};
|
|
155
|
-
|
|
156
125
|
type FactoryAutomation = {
|
|
157
126
|
id: string;
|
|
158
127
|
name: string;
|
|
@@ -229,6 +198,7 @@ export default function FactoryRoute() {
|
|
|
229
198
|
next.delete("itemId");
|
|
230
199
|
next.delete("automationId");
|
|
231
200
|
next.delete("auditRunId");
|
|
201
|
+
next.delete("new");
|
|
232
202
|
if (options?.tab) {
|
|
233
203
|
if (options.tab === "overview") next.delete("tab");
|
|
234
204
|
else next.set("tab", options.tab);
|
|
@@ -250,6 +220,7 @@ export default function FactoryRoute() {
|
|
|
250
220
|
next.delete("itemId");
|
|
251
221
|
next.delete("automationId");
|
|
252
222
|
next.delete("auditRunId");
|
|
223
|
+
next.delete("new");
|
|
253
224
|
return next;
|
|
254
225
|
},
|
|
255
226
|
{ replace: true },
|
|
@@ -408,6 +379,11 @@ export default function FactoryRoute() {
|
|
|
408
379
|
setSelectedEdgeId(null);
|
|
409
380
|
}
|
|
410
381
|
|
|
382
|
+
useEffect(() => {
|
|
383
|
+
if (searchParams.get("new") !== "1") return;
|
|
384
|
+
startNewFactory();
|
|
385
|
+
}, [searchParams]);
|
|
386
|
+
|
|
411
387
|
async function saveGraph() {
|
|
412
388
|
if (!graph || !selectedFactoryId) return;
|
|
413
389
|
const submittedDraftRevision = draftRevisionRef.current;
|
|
@@ -493,22 +469,28 @@ export default function FactoryRoute() {
|
|
|
493
469
|
}
|
|
494
470
|
}
|
|
495
471
|
|
|
472
|
+
if (searchParams.get("tab") === "settings") {
|
|
473
|
+
return <Navigate to="/factory-settings" replace />;
|
|
474
|
+
}
|
|
475
|
+
if (selectedFactoryId && searchParams.get("tab") === "agents") {
|
|
476
|
+
return <Navigate to="/factory?tab=agents" replace />;
|
|
477
|
+
}
|
|
478
|
+
|
|
496
479
|
if (!selectedFactoryId && activeTab === "agents") {
|
|
497
480
|
return (
|
|
498
481
|
<div className="flex h-full min-h-0 flex-col overflow-y-auto bg-background">
|
|
499
482
|
<div className="flex items-center gap-3 px-4 py-4 lg:px-6">
|
|
500
|
-
<Button
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
aria-label={t("factoryRoute.backToFactories")}
|
|
505
|
-
onClick={() => setActiveTab("overview")}
|
|
506
|
-
>
|
|
507
|
-
<IconArrowLeft className="size-4" />
|
|
483
|
+
<Button asChild type="button" variant="ghost" size="icon">
|
|
484
|
+
<Link to="/factory" aria-label={t("factoryRoute.backToFactories")}>
|
|
485
|
+
<IconArrowLeft className="size-4" />
|
|
486
|
+
</Link>
|
|
508
487
|
</Button>
|
|
509
488
|
<h1 className="text-sm font-medium sm:text-base">
|
|
510
489
|
{t("factoryRoute.agentsTitle")}
|
|
511
490
|
</h1>
|
|
491
|
+
<div className="ms-auto">
|
|
492
|
+
<FactoryWorkspaceActions onNewFactory={startNewFactory} />
|
|
493
|
+
</div>
|
|
512
494
|
</div>
|
|
513
495
|
<FactoryAgentsView />
|
|
514
496
|
</div>
|
|
@@ -521,21 +503,7 @@ export default function FactoryRoute() {
|
|
|
521
503
|
<section className="mx-auto flex w-full max-w-6xl flex-1 flex-col gap-6 p-4 lg:p-6">
|
|
522
504
|
<div className="flex flex-wrap items-end justify-between gap-4">
|
|
523
505
|
<h1 className="text-2xl font-semibold tracking-tight">Factories</h1>
|
|
524
|
-
<
|
|
525
|
-
<Button
|
|
526
|
-
type="button"
|
|
527
|
-
variant="outline"
|
|
528
|
-
size="sm"
|
|
529
|
-
onClick={() => setActiveTab("agents")}
|
|
530
|
-
>
|
|
531
|
-
{t("factoryRoute.agentsTab")}
|
|
532
|
-
</Button>
|
|
533
|
-
<Button type="button" size="sm" onClick={startNewFactory}>
|
|
534
|
-
<IconPlus className="size-4" />
|
|
535
|
-
{t("factoryRoute.newFactory")}
|
|
536
|
-
</Button>
|
|
537
|
-
<AgentToggleButton />
|
|
538
|
-
</div>
|
|
506
|
+
<FactoryWorkspaceActions onNewFactory={startNewFactory} />
|
|
539
507
|
</div>
|
|
540
508
|
|
|
541
509
|
{factoryListQuery.isError ? (
|
|
@@ -631,10 +599,7 @@ export default function FactoryRoute() {
|
|
|
631
599
|
</Button>
|
|
632
600
|
<div className="h-5 w-48 animate-pulse rounded bg-muted" />
|
|
633
601
|
</div>
|
|
634
|
-
<
|
|
635
|
-
<div className="h-9 w-28 animate-pulse rounded bg-muted" />
|
|
636
|
-
<AgentToggleButton />
|
|
637
|
-
</div>
|
|
602
|
+
<FactoryWorkspaceActions onNewFactory={startNewFactory} />
|
|
638
603
|
</div>
|
|
639
604
|
</header>
|
|
640
605
|
<main className="flex flex-1 items-center justify-center p-6">
|
|
@@ -681,16 +646,7 @@ export default function FactoryRoute() {
|
|
|
681
646
|
</span>
|
|
682
647
|
</div>
|
|
683
648
|
<div className="flex items-center gap-2">
|
|
684
|
-
<
|
|
685
|
-
type="button"
|
|
686
|
-
variant="outline"
|
|
687
|
-
size="sm"
|
|
688
|
-
onClick={startNewFactory}
|
|
689
|
-
>
|
|
690
|
-
<IconPlus className="size-4" />
|
|
691
|
-
{t("factoryRoute.newFactory")}
|
|
692
|
-
</Button>
|
|
693
|
-
<AgentToggleButton />
|
|
649
|
+
<FactoryWorkspaceActions onNewFactory={startNewFactory} />
|
|
694
650
|
</div>
|
|
695
651
|
</div>
|
|
696
652
|
<div className="flex items-center gap-2 overflow-x-auto px-2 py-2 sm:px-4 lg:px-6">
|
|
@@ -728,12 +684,6 @@ export default function FactoryRoute() {
|
|
|
728
684
|
>
|
|
729
685
|
{t("factoryRoute.automationsTab")}
|
|
730
686
|
</TabButton>
|
|
731
|
-
<TabButton
|
|
732
|
-
active={activeTab === "agents"}
|
|
733
|
-
onClick={() => setActiveTab("agents")}
|
|
734
|
-
>
|
|
735
|
-
{t("factoryRoute.agentsTab")}
|
|
736
|
-
</TabButton>
|
|
737
687
|
<TabButton
|
|
738
688
|
active={activeTab === "audit"}
|
|
739
689
|
onClick={() => setActiveTab("audit")}
|
|
@@ -746,12 +696,6 @@ export default function FactoryRoute() {
|
|
|
746
696
|
>
|
|
747
697
|
{t("factoryRoute.historyTab")}
|
|
748
698
|
</TabButton>
|
|
749
|
-
<TabButton
|
|
750
|
-
active={activeTab === "settings"}
|
|
751
|
-
onClick={() => setActiveTab("settings")}
|
|
752
|
-
>
|
|
753
|
-
Settings
|
|
754
|
-
</TabButton>
|
|
755
699
|
</nav>
|
|
756
700
|
{activeTab === "audit" && (
|
|
757
701
|
<Button
|
|
@@ -779,7 +723,6 @@ export default function FactoryRoute() {
|
|
|
779
723
|
onOpenReview={() => setActiveTab("inbox")}
|
|
780
724
|
onOpenAutomations={() => setActiveTab("automations")}
|
|
781
725
|
onOpenActivity={() => setActiveTab("audit")}
|
|
782
|
-
onOpenSettings={() => setActiveTab("settings")}
|
|
783
726
|
onOpenFlow={() => setActiveTab("map")}
|
|
784
727
|
/>
|
|
785
728
|
) : activeTab === "map" ? (
|
|
@@ -832,8 +775,6 @@ export default function FactoryRoute() {
|
|
|
832
775
|
<RulesView t={t} />
|
|
833
776
|
) : activeTab === "automations" ? (
|
|
834
777
|
<AutomationsView factoryId={factoryId} t={t} />
|
|
835
|
-
) : activeTab === "agents" ? (
|
|
836
|
-
<FactoryAgentsView />
|
|
837
778
|
) : activeTab === "audit" ? (
|
|
838
779
|
<FactoryAuditView
|
|
839
780
|
factoryId={factoryId}
|
|
@@ -846,9 +787,7 @@ export default function FactoryRoute() {
|
|
|
846
787
|
hasUnsavedChanges={dirty}
|
|
847
788
|
onRestored={handleFactoryRestored}
|
|
848
789
|
/>
|
|
849
|
-
) :
|
|
850
|
-
<SettingsView t={t} />
|
|
851
|
-
)}
|
|
790
|
+
) : null}
|
|
852
791
|
</main>
|
|
853
792
|
</div>
|
|
854
793
|
);
|
|
@@ -882,8 +821,7 @@ function parseWorkspaceTab(value: string | null): WorkspaceTab {
|
|
|
882
821
|
value === "automations" ||
|
|
883
822
|
value === "agents" ||
|
|
884
823
|
value === "audit" ||
|
|
885
|
-
value === "history"
|
|
886
|
-
value === "settings"
|
|
824
|
+
value === "history"
|
|
887
825
|
? value
|
|
888
826
|
: "overview";
|
|
889
827
|
}
|
|
@@ -896,7 +834,6 @@ function OverviewView({
|
|
|
896
834
|
onOpenReview,
|
|
897
835
|
onOpenAutomations,
|
|
898
836
|
onOpenActivity,
|
|
899
|
-
onOpenSettings,
|
|
900
837
|
onOpenFlow,
|
|
901
838
|
}: {
|
|
902
839
|
graph: FactoryCanvasGraph;
|
|
@@ -906,7 +843,6 @@ function OverviewView({
|
|
|
906
843
|
onOpenReview: () => void;
|
|
907
844
|
onOpenAutomations: () => void;
|
|
908
845
|
onOpenActivity: () => void;
|
|
909
|
-
onOpenSettings: () => void;
|
|
910
846
|
onOpenFlow: () => void;
|
|
911
847
|
}) {
|
|
912
848
|
return (
|
|
@@ -984,8 +920,10 @@ function OverviewView({
|
|
|
984
920
|
<Button type="button" variant="outline" onClick={onOpenActivity}>
|
|
985
921
|
Activity
|
|
986
922
|
</Button>
|
|
987
|
-
<Button type="button" variant="outline"
|
|
988
|
-
|
|
923
|
+
<Button asChild type="button" variant="outline">
|
|
924
|
+
<Link to="/factory-settings">
|
|
925
|
+
{t("factoryRoute.factorySettings")}
|
|
926
|
+
</Link>
|
|
989
927
|
</Button>
|
|
990
928
|
<Button type="button" onClick={onOpenFlow}>
|
|
991
929
|
{t("factoryRoute.editFlow")}
|
|
@@ -1910,234 +1848,6 @@ function RulesView({ t }: { t: ReturnType<typeof useT> }) {
|
|
|
1910
1848
|
);
|
|
1911
1849
|
}
|
|
1912
1850
|
|
|
1913
|
-
function SettingsView({ t }: { t: ReturnType<typeof useT> }) {
|
|
1914
|
-
const [automationFailureAlertsEnabled, setAutomationFailureAlertsEnabled] =
|
|
1915
|
-
useState(true);
|
|
1916
|
-
const [automationFailureAlertEmail, setAutomationFailureAlertEmail] =
|
|
1917
|
-
useState("");
|
|
1918
|
-
const query = useActionQuery("get-triage-config", {});
|
|
1919
|
-
const schedulerHealthQuery = useActionQuery<FactoryAutomationHealth>(
|
|
1920
|
-
"get-factory-automation-health",
|
|
1921
|
-
{},
|
|
1922
|
-
{ refetchInterval: 60_000 },
|
|
1923
|
-
);
|
|
1924
|
-
const mutation = useActionMutation("save-triage-config");
|
|
1925
|
-
|
|
1926
|
-
useEffect(() => {
|
|
1927
|
-
const data = query.data as TriageConfig | undefined;
|
|
1928
|
-
if (!data) return;
|
|
1929
|
-
setAutomationFailureAlertsEnabled(
|
|
1930
|
-
data.automationFailureAlertsEnabled ?? true,
|
|
1931
|
-
);
|
|
1932
|
-
setAutomationFailureAlertEmail(data.automationFailureAlertEmail ?? "");
|
|
1933
|
-
}, [query.data]);
|
|
1934
|
-
|
|
1935
|
-
const saveSettings = () => {
|
|
1936
|
-
mutation.mutate({
|
|
1937
|
-
automationFailureAlertsEnabled,
|
|
1938
|
-
...(automationFailureAlertEmail.trim()
|
|
1939
|
-
? { automationFailureAlertEmail: automationFailureAlertEmail.trim() }
|
|
1940
|
-
: {}),
|
|
1941
|
-
});
|
|
1942
|
-
};
|
|
1943
|
-
|
|
1944
|
-
if (query.isLoading) {
|
|
1945
|
-
return (
|
|
1946
|
-
<div className="mx-auto w-full max-w-3xl space-y-6 p-4 lg:p-6">
|
|
1947
|
-
<FactorySettingsSkeleton t={t} />
|
|
1948
|
-
</div>
|
|
1949
|
-
);
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
return (
|
|
1953
|
-
<div className="mx-auto w-full max-w-3xl space-y-6 p-4 lg:p-6">
|
|
1954
|
-
<SettingsGroup variant="soft">
|
|
1955
|
-
<SettingsRow
|
|
1956
|
-
label={t("factoryRoute.workspaceIntegrations")}
|
|
1957
|
-
description={t("settings.workspaceDescription")}
|
|
1958
|
-
control={
|
|
1959
|
-
<Button asChild type="button" variant="outline">
|
|
1960
|
-
<Link to={buildSettingsRoute("integrations")}>Manage</Link>
|
|
1961
|
-
</Button>
|
|
1962
|
-
}
|
|
1963
|
-
/>
|
|
1964
|
-
<SettingsRow
|
|
1965
|
-
label={t("factoryRoute.agentAccess")}
|
|
1966
|
-
description={t("settings.agentDescription")}
|
|
1967
|
-
control={
|
|
1968
|
-
<Button asChild type="button" variant="outline">
|
|
1969
|
-
<Link to={buildSettingsRoute("agent")}>Manage</Link>
|
|
1970
|
-
</Button>
|
|
1971
|
-
}
|
|
1972
|
-
/>
|
|
1973
|
-
</SettingsGroup>
|
|
1974
|
-
|
|
1975
|
-
<SettingsGroup variant="soft">
|
|
1976
|
-
<SettingsRow
|
|
1977
|
-
label={t("factoryRoute.automationFailureAlertsTitle")}
|
|
1978
|
-
description={t("factoryRoute.automationFailureAlertsDescription")}
|
|
1979
|
-
control={
|
|
1980
|
-
<Switch
|
|
1981
|
-
aria-label={t("factoryRoute.automationFailureAlertsEnabled")}
|
|
1982
|
-
checked={automationFailureAlertsEnabled}
|
|
1983
|
-
onCheckedChange={(checked) =>
|
|
1984
|
-
setAutomationFailureAlertsEnabled(checked === true)
|
|
1985
|
-
}
|
|
1986
|
-
/>
|
|
1987
|
-
}
|
|
1988
|
-
/>
|
|
1989
|
-
<SettingsRow
|
|
1990
|
-
label={t("factoryRoute.automationFailureAlertEmail")}
|
|
1991
|
-
description={t("factoryRoute.automationFailureAlertEmailPlaceholder")}
|
|
1992
|
-
control={
|
|
1993
|
-
<Input
|
|
1994
|
-
aria-label={t("factoryRoute.automationFailureAlertEmail")}
|
|
1995
|
-
type="email"
|
|
1996
|
-
value={automationFailureAlertEmail}
|
|
1997
|
-
onChange={(event) =>
|
|
1998
|
-
setAutomationFailureAlertEmail(event.target.value)
|
|
1999
|
-
}
|
|
2000
|
-
placeholder={t(
|
|
2001
|
-
"factoryRoute.automationFailureAlertEmailPlaceholder",
|
|
2002
|
-
)}
|
|
2003
|
-
className="h-9 w-full sm:w-64"
|
|
2004
|
-
/>
|
|
2005
|
-
}
|
|
2006
|
-
/>
|
|
2007
|
-
<SettingsRow
|
|
2008
|
-
label={t("factoryRoute.automationFailureEmailReadiness")}
|
|
2009
|
-
description={t("factoryRoute.automationEmailReadinessHint")}
|
|
2010
|
-
control={
|
|
2011
|
-
<span className="rounded-full bg-muted px-2.5 py-1 text-xs font-medium text-muted-foreground">
|
|
2012
|
-
{(query.data as TriageConfig | undefined)?.emailReadiness
|
|
2013
|
-
?.status ?? "unknown"}
|
|
2014
|
-
</span>
|
|
2015
|
-
}
|
|
2016
|
-
/>
|
|
2017
|
-
{query.isError && (
|
|
2018
|
-
<div className="px-5 py-3 text-xs text-destructive sm:px-6">
|
|
2019
|
-
{t("factoryRoute.automationDiagnosticsLoadError")}{" "}
|
|
2020
|
-
{query.error instanceof Error
|
|
2021
|
-
? query.error.message
|
|
2022
|
-
: String(query.error)}
|
|
2023
|
-
</div>
|
|
2024
|
-
)}
|
|
2025
|
-
</SettingsGroup>
|
|
2026
|
-
|
|
2027
|
-
<div className="flex flex-wrap items-center justify-end gap-3">
|
|
2028
|
-
{mutation.isError && (
|
|
2029
|
-
<p className="text-sm text-destructive" role="alert">
|
|
2030
|
-
{t("triage.settingsError")}
|
|
2031
|
-
</p>
|
|
2032
|
-
)}
|
|
2033
|
-
{mutation.isSuccess && (
|
|
2034
|
-
<p className="text-sm text-muted-foreground" role="status">
|
|
2035
|
-
{t("triage.settingsSaved")}
|
|
2036
|
-
</p>
|
|
2037
|
-
)}
|
|
2038
|
-
<Button onClick={saveSettings} disabled={mutation.isPending}>
|
|
2039
|
-
{mutation.isPending && <IconLoader2 className="animate-spin" />}
|
|
2040
|
-
{t("triage.saveSettings")}
|
|
2041
|
-
</Button>
|
|
2042
|
-
</div>
|
|
2043
|
-
|
|
2044
|
-
<SchedulerHealthStatus
|
|
2045
|
-
health={schedulerHealthQuery.data}
|
|
2046
|
-
isError={schedulerHealthQuery.isError}
|
|
2047
|
-
error={schedulerHealthQuery.error}
|
|
2048
|
-
t={t}
|
|
2049
|
-
/>
|
|
2050
|
-
</div>
|
|
2051
|
-
);
|
|
2052
|
-
}
|
|
2053
|
-
|
|
2054
|
-
function FactorySettingsSkeleton({ t }: { t: ReturnType<typeof useT> }) {
|
|
2055
|
-
return (
|
|
2056
|
-
<div className="space-y-6" aria-label={t("triage.loading")}>
|
|
2057
|
-
{Array.from({ length: 2 }).map((_, index) => (
|
|
2058
|
-
<div key={index} className="grid gap-2">
|
|
2059
|
-
<div className="grid gap-2">
|
|
2060
|
-
{Array.from({ length: index === 0 ? 5 : 3 }).map((_, rowIndex) => (
|
|
2061
|
-
<div
|
|
2062
|
-
key={rowIndex}
|
|
2063
|
-
className="flex min-h-20 items-center justify-between gap-4 rounded-xl bg-card px-5 py-4 shadow-sm sm:px-6"
|
|
2064
|
-
>
|
|
2065
|
-
<div className="h-3.5 w-36 animate-pulse rounded bg-muted" />
|
|
2066
|
-
<div className="h-9 w-24 animate-pulse rounded bg-muted" />
|
|
2067
|
-
</div>
|
|
2068
|
-
))}
|
|
2069
|
-
</div>
|
|
2070
|
-
</div>
|
|
2071
|
-
))}
|
|
2072
|
-
</div>
|
|
2073
|
-
);
|
|
2074
|
-
}
|
|
2075
|
-
function SchedulerHealthStatus({
|
|
2076
|
-
health,
|
|
2077
|
-
isError,
|
|
2078
|
-
error,
|
|
2079
|
-
t,
|
|
2080
|
-
}: {
|
|
2081
|
-
health?: FactoryAutomationHealth;
|
|
2082
|
-
isError: boolean;
|
|
2083
|
-
error: unknown;
|
|
2084
|
-
t: ReturnType<typeof useT>;
|
|
2085
|
-
}) {
|
|
2086
|
-
const healthLabel = isError
|
|
2087
|
-
? t("factoryRoute.automationHealthError")
|
|
2088
|
-
: health
|
|
2089
|
-
? {
|
|
2090
|
-
healthy: t("factoryRoute.automationHealthHealthy"),
|
|
2091
|
-
stale: t("factoryRoute.automationHealthStale"),
|
|
2092
|
-
error: t("factoryRoute.automationHealthError"),
|
|
2093
|
-
"no-data": t("factoryRoute.automationHealthNoData"),
|
|
2094
|
-
}[health.status]
|
|
2095
|
-
: t("factoryRoute.automationHealthNoData");
|
|
2096
|
-
const hasNoHeartbeat =
|
|
2097
|
-
!isError &&
|
|
2098
|
-
(!health || health.status === "no-data") &&
|
|
2099
|
-
!health?.lastCheckedAt;
|
|
2100
|
-
const healthDescription = isError
|
|
2101
|
-
? `${t("factoryRoute.automationDiagnosticsLoadError")} ${error instanceof Error ? error.message : String(error)}`
|
|
2102
|
-
: health?.lastError
|
|
2103
|
-
? `${t("factoryRoute.automationHealthErrorDetail")}: ${health.lastError}`
|
|
2104
|
-
: health?.status === "stale"
|
|
2105
|
-
? t("factoryRoute.automationHealthStaleHint")
|
|
2106
|
-
: hasNoHeartbeat
|
|
2107
|
-
? t("factoryRoute.automationHealthNoDataHint")
|
|
2108
|
-
: undefined;
|
|
2109
|
-
|
|
2110
|
-
return (
|
|
2111
|
-
<SettingsGroup variant="soft">
|
|
2112
|
-
<SettingsRow
|
|
2113
|
-
label={t("factoryRoute.automationHealthTitle")}
|
|
2114
|
-
description={
|
|
2115
|
-
healthDescription ?? t("factoryRoute.automationHealthDescription")
|
|
2116
|
-
}
|
|
2117
|
-
control={
|
|
2118
|
-
<div className="flex flex-wrap items-center justify-end gap-x-4 gap-y-1">
|
|
2119
|
-
<span className="rounded-full bg-muted px-2.5 py-1 text-xs font-medium">
|
|
2120
|
-
{healthLabel}
|
|
2121
|
-
</span>
|
|
2122
|
-
{health?.lastCheckedAt && (
|
|
2123
|
-
<span className="text-xs text-muted-foreground">
|
|
2124
|
-
{t("factoryRoute.automationLastCheck")}:{" "}
|
|
2125
|
-
{formatAutomationDate(health.lastCheckedAt)}
|
|
2126
|
-
</span>
|
|
2127
|
-
)}
|
|
2128
|
-
{health?.lastDispatchedAt && (
|
|
2129
|
-
<span className="text-xs text-muted-foreground">
|
|
2130
|
-
{t("factoryRoute.automationLastDispatch")}:{" "}
|
|
2131
|
-
{formatAutomationDate(health.lastDispatchedAt)}
|
|
2132
|
-
</span>
|
|
2133
|
-
)}
|
|
2134
|
-
</div>
|
|
2135
|
-
}
|
|
2136
|
-
/>
|
|
2137
|
-
</SettingsGroup>
|
|
2138
|
-
);
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
1851
|
function ErrorState({
|
|
2142
1852
|
message,
|
|
2143
1853
|
onRetry,
|
|
@@ -14,6 +14,14 @@ export interface SlackMessage {
|
|
|
14
14
|
thread_ts?: string;
|
|
15
15
|
reply_count?: number;
|
|
16
16
|
permalink?: string;
|
|
17
|
+
reactions?: Array<{ name: string; count?: number; users?: string[] }>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SlackAuthTestResult {
|
|
21
|
+
userId: string;
|
|
22
|
+
userName: string;
|
|
23
|
+
teamId: string;
|
|
24
|
+
teamName: string;
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
export interface SlackTeamInfo {
|
|
@@ -45,6 +53,10 @@ export interface SlackReactionResult {
|
|
|
45
53
|
already_present: boolean;
|
|
46
54
|
}
|
|
47
55
|
|
|
56
|
+
export interface SlackReactionState {
|
|
57
|
+
eyesPresent: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
48
60
|
export interface SlackPostMessageResult {
|
|
49
61
|
ok?: boolean;
|
|
50
62
|
error?: string;
|
|
@@ -219,6 +231,56 @@ export async function getThread(
|
|
|
219
231
|
};
|
|
220
232
|
}
|
|
221
233
|
|
|
234
|
+
export async function authTest(
|
|
235
|
+
workspace: Workspace,
|
|
236
|
+
tokenResolver?: SlackTokenResolver,
|
|
237
|
+
): Promise<SlackAuthTestResult> {
|
|
238
|
+
const data = await slackApi<{
|
|
239
|
+
user_id?: string;
|
|
240
|
+
user?: string;
|
|
241
|
+
team_id?: string;
|
|
242
|
+
team?: string;
|
|
243
|
+
}>(workspace, "auth.test", undefined, tokenResolver);
|
|
244
|
+
if (
|
|
245
|
+
typeof data.user_id !== "string" ||
|
|
246
|
+
typeof data.user !== "string" ||
|
|
247
|
+
typeof data.team_id !== "string" ||
|
|
248
|
+
typeof data.team !== "string"
|
|
249
|
+
) {
|
|
250
|
+
throw new Error("Slack auth.test response is missing bot identity.");
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
userId: data.user_id,
|
|
254
|
+
userName: data.user,
|
|
255
|
+
teamId: data.team_id,
|
|
256
|
+
teamName: data.team,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export async function getEyesReaction(
|
|
261
|
+
workspace: Workspace,
|
|
262
|
+
channelId: string,
|
|
263
|
+
timestamp: string,
|
|
264
|
+
tokenResolver?: SlackTokenResolver,
|
|
265
|
+
): Promise<SlackReactionState> {
|
|
266
|
+
const data = await slackApi<{
|
|
267
|
+
message?: { reactions?: Array<{ name?: string; count?: number }> };
|
|
268
|
+
}>(
|
|
269
|
+
workspace,
|
|
270
|
+
"reactions.get",
|
|
271
|
+
{ channel: channelId, timestamp },
|
|
272
|
+
tokenResolver,
|
|
273
|
+
);
|
|
274
|
+
if (!data.message) {
|
|
275
|
+
throw new Error("Slack reaction response is missing the message.");
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
eyesPresent: (data.message.reactions ?? []).some(
|
|
279
|
+
(reaction) => reaction.name === "eyes" && (reaction.count ?? 0) > 0,
|
|
280
|
+
),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
222
284
|
export async function addEyesReaction(
|
|
223
285
|
workspace: Workspace,
|
|
224
286
|
channelId: string,
|
|
@@ -110,6 +110,7 @@ export const triageConfig = table("factory_config", {
|
|
|
110
110
|
slackWorkspace: text("slack_workspace").notNull().default("primary"),
|
|
111
111
|
slackChannelId: text("slack_channel_id"),
|
|
112
112
|
slackChannelName: text("slack_channel_name"),
|
|
113
|
+
builderSlackUserId: text("builder_slack_user_id"),
|
|
113
114
|
pollingEnabled: integer("polling_enabled").notNull().default(0),
|
|
114
115
|
lastSlackTs: text("last_slack_ts"),
|
|
115
116
|
slackHistoryCursor: text("slack_history_cursor"),
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const SLACK_MENTION_GUARD =
|
|
2
|
+
"Never post Slack messages, reactions, or plaintext @handles yourself. Call start-builder-for-item; that action pings Builder with a Slack user id. Plaintext @builder.io does not notify anyone.";
|
|
3
|
+
|
|
4
|
+
export const SLACK_HANDOFF_INSTRUCTION = `Do not post to Slack, add reactions, or type @handles yourself. Call
|
|
5
|
+
start-builder-for-item; that action adds 👀 and pings Builder with a Slack
|
|
6
|
+
user id so it runs /address-feedback. The posted reply points Builder at the
|
|
7
|
+
relevant repository skills, the representative source, every related source,
|
|
8
|
+
and the need to fix the underlying boundary across the whole cluster. Never
|
|
9
|
+
call that action for owner-managed Clips, Design, or Content work, or for a
|
|
10
|
+
non-bug report.`;
|
|
11
|
+
|
|
12
|
+
const OBSOLETE_SLACK_TAG_PARAGRAPH =
|
|
13
|
+
/The Builder reply must tag @builder\.io[\s\S]*?non-bug\s+report\.\s*/;
|
|
14
|
+
|
|
15
|
+
export function repairSlackFeedbackPrompt(content: string): string {
|
|
16
|
+
let next = content.replace(
|
|
17
|
+
OBSOLETE_SLACK_TAG_PARAGRAPH,
|
|
18
|
+
`${SLACK_HANDOFF_INSTRUCTION}\n\n`,
|
|
19
|
+
);
|
|
20
|
+
if (/tag @builder\.io/i.test(next)) {
|
|
21
|
+
next = next
|
|
22
|
+
.split("\n")
|
|
23
|
+
.filter((line) => !/tag @builder\.io/i.test(line))
|
|
24
|
+
.join("\n");
|
|
25
|
+
}
|
|
26
|
+
if (!next.includes(SLACK_MENTION_GUARD)) {
|
|
27
|
+
next = `${next.trimEnd()}\n\n${SLACK_MENTION_GUARD}\n`;
|
|
28
|
+
}
|
|
29
|
+
return next;
|
|
30
|
+
}
|
|
@@ -92,14 +92,15 @@ thread-preserving start-builder-for-item flow; GitHub and Sentry clear bugs use
|
|
|
92
92
|
the Builder agent-run flow. Slack repeat reports must be clustered by underlying
|
|
93
93
|
symptom, with one Builder thread for the cluster and 👀 on every grouped report.
|
|
94
94
|
After classifying an item, call start-builder-for-item with clearBug true or
|
|
95
|
-
false and a short reason so a skip is recorded.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
95
|
+
false and a short reason so a skip is recorded. Do not post Slack messages,
|
|
96
|
+
reactions, or @mentions yourself; start-builder-for-item owns the Builder ping.
|
|
97
|
+
Use /address-feedback for the repository feedback workflow. For pull requests,
|
|
98
|
+
follow review-prs: read the complete diff and review evidence, verify current
|
|
99
|
+
BuilderIO membership, preserve the ultra-scary safety gate, and distinguish
|
|
100
|
+
unknown or unresolved checks from clean ones. Auto-merge also requires a
|
|
101
|
+
verified Factory Builder run. When a user says to do a review-gated item now,
|
|
102
|
+
use the explicit approval action, which records the approver and applies the
|
|
103
|
+
rule's configured executor policy. Keep Slack replies concise and link to the
|
|
103
104
|
Factory item when a review is needed. The scheduled builder-io-bot PR babysitter
|
|
104
105
|
posts its exact feedback-fix request through GitHub, persists a 20-minute quiet
|
|
105
106
|
window, and never approves or merges.`,
|
|
@@ -287,6 +287,13 @@ const migrations = [
|
|
|
287
287
|
ON factory_audit_events (org_id, item_id, created_at);
|
|
288
288
|
`,
|
|
289
289
|
},
|
|
290
|
+
{
|
|
291
|
+
version: 19,
|
|
292
|
+
name: "factory-config-builder-slack-user-id",
|
|
293
|
+
sql: `
|
|
294
|
+
ALTER TABLE factory_config ADD COLUMN builder_slack_user_id TEXT;
|
|
295
|
+
`,
|
|
296
|
+
},
|
|
290
297
|
];
|
|
291
298
|
|
|
292
299
|
export const runFactoryMigrations = runMigrations(migrations, {
|