@agent-native/core 0.168.7 → 0.168.10
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/actions/mutate-dashboard.ts +8 -1
- package/corpus/templates/calendar/actions/delete-event.ts +34 -18
- package/corpus/templates/calendar/actions/delete-events.ts +13 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +22 -3
- package/corpus/templates/calendar/app/lib/calendar-timezone.ts +19 -0
- package/corpus/templates/calendar/server/lib/google-api.ts +18 -1
- package/corpus/templates/clips/actions/finalize-meeting.ts +16 -0
- package/corpus/templates/clips/actions/list-meetings.ts +1 -0
- package/corpus/templates/clips/actions/sync-calendars.ts +10 -29
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +28 -12
- package/corpus/templates/clips/desktop/src/lib/meeting-stop.ts +16 -0
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +19 -9
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +1 -0
- package/corpus/templates/content/app/global.css +5 -0
- package/corpus/templates/content/docs/solutions/2026-08-20-code-block-slash-production-regression-shape.md +492 -0
- package/corpus/templates/content/docs/solutions/2026-08-20-code-block-surface-background-shape.md +273 -0
- package/corpus/templates/factory/.agents/skills/factory-graphs/SKILL.md +5 -0
- package/corpus/templates/factory/AGENTS.md +12 -22
- package/corpus/templates/factory/README.md +11 -8
- package/corpus/templates/factory/actions/approve-factory-item.ts +35 -8
- package/corpus/templates/factory/actions/babysit-agent-native-pull-request.ts +24 -9
- package/corpus/templates/factory/actions/create-factory.ts +211 -0
- package/corpus/templates/factory/actions/evaluate-triage-item.ts +29 -5
- package/corpus/templates/factory/actions/get-factory-graph.ts +1 -1
- package/corpus/templates/factory/actions/get-slack-feedback-context.ts +20 -11
- package/corpus/templates/factory/actions/get-triage-config.ts +13 -12
- package/corpus/templates/factory/actions/get-triage-item.ts +28 -6
- package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +33 -17
- package/corpus/templates/factory/actions/ingest-github-observation.ts +10 -4
- package/corpus/templates/factory/actions/list-factory-audit.ts +11 -4
- package/corpus/templates/factory/actions/list-factory-automations.ts +34 -12
- package/corpus/templates/factory/actions/list-triage-items.ts +11 -3
- package/corpus/templates/factory/actions/list-triage-rules.ts +11 -4
- package/corpus/templates/factory/actions/poll-github-sources.ts +24 -10
- package/corpus/templates/factory/actions/poll-sentry-errors.ts +26 -11
- package/corpus/templates/factory/actions/poll-slack-channel.ts +31 -13
- package/corpus/templates/factory/actions/reconcile-triage-run.ts +59 -9
- package/corpus/templates/factory/actions/record-triage-feedback.ts +14 -3
- package/corpus/templates/factory/actions/run-factory-automation.ts +12 -2
- package/corpus/templates/factory/actions/save-factory-automation.ts +82 -21
- package/corpus/templates/factory/actions/save-factory-graph.ts +10 -1
- package/corpus/templates/factory/actions/save-triage-config.ts +90 -36
- package/corpus/templates/factory/actions/save-triage-rule.ts +16 -3
- package/corpus/templates/factory/actions/start-builder-for-item.ts +28 -20
- package/corpus/templates/factory/actions/suggest-factory-rules.ts +11 -4
- package/corpus/templates/factory/actions/view-screen.ts +22 -30
- package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +7 -2
- package/corpus/templates/factory/app/components/factory/FactoryWorkspaceActions.tsx +7 -22
- package/corpus/templates/factory/app/components/factory/NewFactoryForm.tsx +260 -0
- package/corpus/templates/factory/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +2 -1
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +3 -3
- package/corpus/templates/factory/app/i18n/en-US.ts +18 -1
- package/corpus/templates/factory/app/routes/factory.tsx +78 -62
- package/corpus/templates/factory/app/routes/{factory-settings.tsx → new-factory.tsx} +12 -9
- package/corpus/templates/factory/server/db/schema.ts +18 -3
- package/corpus/templates/factory/server/factory-graph/contracts.ts +24 -0
- package/corpus/templates/factory/server/factory-graph/store.ts +16 -9
- package/corpus/templates/factory/server/lib/factory-automation-plan.ts +136 -0
- package/corpus/templates/factory/server/lib/factory-automation-repair.ts +31 -0
- package/corpus/templates/factory/server/lib/factory-scope.ts +416 -0
- package/corpus/templates/factory/server/lib/require-factory-automation.ts +11 -0
- package/corpus/templates/factory/server/plugins/agent-chat.ts +4 -2
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +80 -0
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +142 -43
- package/corpus/templates/factory/server/triage/audit.ts +4 -0
- package/corpus/templates/factory/server/triage/ids.ts +4 -0
- package/dist/cli/native-dependencies.js +4 -1
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.js +9 -4
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat/index.js +1 -1
- package/dist/client/chat-first/apps-rail.js +6 -6
- package/dist/client/chat-first/primary-nav.js +14 -6
- package/dist/client/integrations/IntegrationsPanel.d.ts +15 -0
- package/dist/client/integrations/IntegrationsPanel.js +29 -21
- package/dist/client/integrations/index.d.ts +1 -1
- package/dist/client/integrations/index.js +1 -1
- package/dist/client/resources/McpIntegrationDialog.js +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.js +10 -3
- package/package.json +1 -1
|
@@ -4,11 +4,16 @@ import { z } from "zod";
|
|
|
4
4
|
|
|
5
5
|
import { getDb } from "../server/db/index.js";
|
|
6
6
|
import {
|
|
7
|
-
triageConfig,
|
|
8
7
|
triageDecisions,
|
|
9
8
|
triageItems,
|
|
10
9
|
triageRuns,
|
|
11
10
|
} from "../server/db/schema.js";
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_FACTORY_ID,
|
|
13
|
+
orgFactoryItemFilter,
|
|
14
|
+
orgFactoryRunFilter,
|
|
15
|
+
} from "../server/lib/factory-scope.js";
|
|
16
|
+
import { readTriageConfigRow } from "../server/lib/factory-scope.js";
|
|
12
17
|
import { requireFactoryAutomation } from "../server/lib/require-factory-automation.js";
|
|
13
18
|
import {
|
|
14
19
|
requireWorkspaceMember,
|
|
@@ -188,6 +193,7 @@ export async function recordAutomaticBuilderDecision(input: {
|
|
|
188
193
|
itemId: string;
|
|
189
194
|
userEmail: string;
|
|
190
195
|
orgId: string;
|
|
196
|
+
factoryId: string;
|
|
191
197
|
outcome: "propose_fix" | "needs_manual";
|
|
192
198
|
reason: string;
|
|
193
199
|
guardResults: Array<{ code: string; passed: boolean; reason: string }>;
|
|
@@ -214,6 +220,7 @@ export async function recordAutomaticBuilderDecision(input: {
|
|
|
214
220
|
createdAt: now,
|
|
215
221
|
ownerEmail: input.userEmail,
|
|
216
222
|
orgId: input.orgId,
|
|
223
|
+
factoryId: input.factoryId,
|
|
217
224
|
})
|
|
218
225
|
.onConflictDoUpdate({
|
|
219
226
|
target: triageDecisions.id,
|
|
@@ -260,11 +267,6 @@ export default defineAction({
|
|
|
260
267
|
const { userEmail, orgId } = await requireWorkspaceMember(
|
|
261
268
|
workspaceMemberIdentityFromContext(context),
|
|
262
269
|
);
|
|
263
|
-
await requireFactoryAutomation(
|
|
264
|
-
context,
|
|
265
|
-
{ userEmail, orgId },
|
|
266
|
-
"builderDispatch",
|
|
267
|
-
);
|
|
268
270
|
const db = getDb();
|
|
269
271
|
const item = (
|
|
270
272
|
await db
|
|
@@ -274,6 +276,13 @@ export default defineAction({
|
|
|
274
276
|
.limit(1)
|
|
275
277
|
)[0];
|
|
276
278
|
if (!item) throw new Error("Factory item not found.");
|
|
279
|
+
const factoryId = item.factoryId ?? DEFAULT_FACTORY_ID;
|
|
280
|
+
await requireFactoryAutomation(
|
|
281
|
+
context,
|
|
282
|
+
{ userEmail, orgId },
|
|
283
|
+
"builderDispatch",
|
|
284
|
+
factoryId,
|
|
285
|
+
);
|
|
277
286
|
const metadata = parseTriageMetadata(item.metadataJson);
|
|
278
287
|
const relatedIds = [...new Set(relatedItemIds)].filter(
|
|
279
288
|
(relatedId) => relatedId !== itemId,
|
|
@@ -285,7 +294,7 @@ export default defineAction({
|
|
|
285
294
|
.from(triageItems)
|
|
286
295
|
.where(
|
|
287
296
|
and(
|
|
288
|
-
|
|
297
|
+
orgFactoryItemFilter(orgId, factoryId),
|
|
289
298
|
inArray(triageItems.id, relatedIds),
|
|
290
299
|
),
|
|
291
300
|
)
|
|
@@ -319,7 +328,7 @@ export default defineAction({
|
|
|
319
328
|
.from(triageRuns)
|
|
320
329
|
.where(
|
|
321
330
|
and(
|
|
322
|
-
|
|
331
|
+
orgFactoryRunFilter(orgId, factoryId),
|
|
323
332
|
inArray(triageRuns.itemId, relatedIds),
|
|
324
333
|
),
|
|
325
334
|
)
|
|
@@ -385,6 +394,7 @@ export default defineAction({
|
|
|
385
394
|
itemId,
|
|
386
395
|
userEmail,
|
|
387
396
|
orgId,
|
|
397
|
+
factoryId,
|
|
388
398
|
outcome: blocked ? "needs_manual" : "propose_fix",
|
|
389
399
|
reason,
|
|
390
400
|
guardResults,
|
|
@@ -395,6 +405,7 @@ export default defineAction({
|
|
|
395
405
|
{
|
|
396
406
|
action: "start-builder-for-item",
|
|
397
407
|
kind: "decision",
|
|
408
|
+
factoryId,
|
|
398
409
|
itemId,
|
|
399
410
|
source: item.source,
|
|
400
411
|
sourceUrl: item.sourceUrl,
|
|
@@ -483,6 +494,7 @@ export default defineAction({
|
|
|
483
494
|
error: null,
|
|
484
495
|
ownerEmail: item.ownerEmail,
|
|
485
496
|
orgId,
|
|
497
|
+
factoryId: item.factoryId ?? DEFAULT_FACTORY_ID,
|
|
486
498
|
});
|
|
487
499
|
}
|
|
488
500
|
|
|
@@ -493,18 +505,11 @@ export default defineAction({
|
|
|
493
505
|
"Slack feedback is missing its channel or thread identity.",
|
|
494
506
|
);
|
|
495
507
|
}
|
|
496
|
-
const config = (
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
})
|
|
502
|
-
.from(triageConfig)
|
|
503
|
-
.where(
|
|
504
|
-
and(eq(triageConfig.id, orgId), eq(triageConfig.orgId, orgId)),
|
|
505
|
-
)
|
|
506
|
-
.limit(1)
|
|
507
|
-
)[0];
|
|
508
|
+
const config = await readTriageConfigRow(
|
|
509
|
+
db,
|
|
510
|
+
orgId,
|
|
511
|
+
item.factoryId ?? DEFAULT_FACTORY_ID,
|
|
512
|
+
);
|
|
508
513
|
const workspace =
|
|
509
514
|
config?.slackWorkspace === "secondary" ? "secondary" : "primary";
|
|
510
515
|
const builderSlackUserId = requireBuilderSlackUserId(
|
|
@@ -638,6 +643,7 @@ export default defineAction({
|
|
|
638
643
|
itemId: related.id,
|
|
639
644
|
userEmail,
|
|
640
645
|
orgId,
|
|
646
|
+
factoryId: related.factoryId ?? factoryId,
|
|
641
647
|
outcome: "propose_fix",
|
|
642
648
|
reason: `Grouped with Factory item ${itemId}: ${reason}`,
|
|
643
649
|
guardResults,
|
|
@@ -656,6 +662,7 @@ export default defineAction({
|
|
|
656
662
|
{
|
|
657
663
|
action: "start-builder-for-item",
|
|
658
664
|
kind: "external_action",
|
|
665
|
+
factoryId,
|
|
659
666
|
itemId,
|
|
660
667
|
source: item.source,
|
|
661
668
|
sourceUrl: item.sourceUrl,
|
|
@@ -728,6 +735,7 @@ export default defineAction({
|
|
|
728
735
|
{
|
|
729
736
|
action: "start-builder-for-item",
|
|
730
737
|
kind: "external_action",
|
|
738
|
+
factoryId,
|
|
731
739
|
itemId,
|
|
732
740
|
source: item.source,
|
|
733
741
|
sourceUrl: item.sourceUrl,
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
|
-
import { desc
|
|
2
|
+
import { desc } from "drizzle-orm";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
5
|
import { getDb } from "../server/db/index.js";
|
|
6
6
|
import { triageDecisions, triageFeedback } from "../server/db/schema.js";
|
|
7
|
+
import { DEFAULT_FACTORY_ID } from "../server/factory-graph/store.js";
|
|
8
|
+
import {
|
|
9
|
+
factoryIdSchema,
|
|
10
|
+
orgFactoryDecisionFilter,
|
|
11
|
+
orgFactoryFeedbackFilter,
|
|
12
|
+
} from "../server/lib/factory-scope.js";
|
|
7
13
|
import {
|
|
8
14
|
requireWorkspaceMember,
|
|
9
15
|
workspaceMemberIdentityFromContext,
|
|
@@ -13,11 +19,12 @@ export default defineAction({
|
|
|
13
19
|
description:
|
|
14
20
|
"Mine human feedback on Factory decisions into inspectable rule proposals. This never edits a rule or enables execution.",
|
|
15
21
|
schema: z.object({
|
|
22
|
+
factoryId: factoryIdSchema.default(DEFAULT_FACTORY_ID),
|
|
16
23
|
limit: z.coerce.number().int().min(1).max(50).default(10),
|
|
17
24
|
}),
|
|
18
25
|
http: { method: "GET" },
|
|
19
26
|
readOnly: true,
|
|
20
|
-
run: async ({ limit }, context) => {
|
|
27
|
+
run: async ({ factoryId, limit }, context) => {
|
|
21
28
|
const { orgId } = await requireWorkspaceMember(
|
|
22
29
|
workspaceMemberIdentityFromContext(context),
|
|
23
30
|
);
|
|
@@ -25,13 +32,13 @@ export default defineAction({
|
|
|
25
32
|
const decisions = await db
|
|
26
33
|
.select()
|
|
27
34
|
.from(triageDecisions)
|
|
28
|
-
.where(
|
|
35
|
+
.where(orgFactoryDecisionFilter(orgId, factoryId))
|
|
29
36
|
.orderBy(desc(triageDecisions.createdAt))
|
|
30
37
|
.limit(500);
|
|
31
38
|
const feedback = await db
|
|
32
39
|
.select()
|
|
33
40
|
.from(triageFeedback)
|
|
34
|
-
.where(
|
|
41
|
+
.where(orgFactoryFeedbackFilter(orgId, factoryId))
|
|
35
42
|
.orderBy(desc(triageFeedback.createdAt))
|
|
36
43
|
.limit(500);
|
|
37
44
|
const decisionsById = new Map(
|
|
@@ -41,18 +41,6 @@ export default defineAction({
|
|
|
41
41
|
const screen: Record<string, unknown> = {};
|
|
42
42
|
if (navigation) screen.navigation = navigation;
|
|
43
43
|
|
|
44
|
-
if (
|
|
45
|
-
navigation &&
|
|
46
|
-
typeof navigation === "object" &&
|
|
47
|
-
"view" in navigation &&
|
|
48
|
-
navigation.view === "factory-settings"
|
|
49
|
-
) {
|
|
50
|
-
screen.factorySettings = {
|
|
51
|
-
scope: "workspace",
|
|
52
|
-
path: "/factory-settings",
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
44
|
if (
|
|
57
45
|
navigation &&
|
|
58
46
|
typeof navigation === "object" &&
|
|
@@ -63,25 +51,29 @@ export default defineAction({
|
|
|
63
51
|
workspaceMemberIdentityFromContext(context),
|
|
64
52
|
);
|
|
65
53
|
const state = navigation as Record<string, unknown>;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
54
|
+
if (state.creatingFactory === true) {
|
|
55
|
+
screen.factory = { creating: true };
|
|
56
|
+
} else {
|
|
57
|
+
const factoryId =
|
|
58
|
+
typeof state.factoryId === "string" && state.factoryId.trim()
|
|
59
|
+
? state.factoryId
|
|
60
|
+
: DEFAULT_FACTORY_ID;
|
|
61
|
+
const row = await readFactoryDefinition(orgId, factoryId);
|
|
62
|
+
const fallback = defaultFactoryDefinition();
|
|
63
|
+
const graph = row ? parseFactoryGraph(row.graphJson) : fallback.graph;
|
|
64
|
+
const selectedNodeId =
|
|
65
|
+
typeof state.factoryNodeId === "string" ? state.factoryNodeId : null;
|
|
66
|
+
const selectedEdgeId =
|
|
67
|
+
typeof state.factoryEdgeId === "string" ? state.factoryEdgeId : null;
|
|
77
68
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
69
|
+
screen.factory = {
|
|
70
|
+
id: factoryId,
|
|
71
|
+
name: row?.name ?? fallback.name,
|
|
72
|
+
graphVersion: row?.graphVersion ?? graph.version,
|
|
73
|
+
selectedNode: graph.nodes.find((node) => node.id === selectedNodeId),
|
|
74
|
+
selectedEdge: graph.edges.find((edge) => edge.id === selectedEdgeId),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
85
77
|
|
|
86
78
|
if (state.factoryTab === "agents") {
|
|
87
79
|
const [apps, agents] = await Promise.all([
|
|
@@ -49,7 +49,11 @@ function formatAutomationDate(value: string | number | null | undefined) {
|
|
|
49
49
|
return Number.isNaN(date.getTime()) ? String(value) : date.toLocaleString();
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
export function FactorySettingsView(
|
|
52
|
+
export function FactorySettingsView({
|
|
53
|
+
factoryId = "product-feedback",
|
|
54
|
+
}: {
|
|
55
|
+
factoryId?: string;
|
|
56
|
+
}) {
|
|
53
57
|
const t = useT();
|
|
54
58
|
const [workspace, setWorkspace] = useState<"primary" | "secondary">(
|
|
55
59
|
"primary",
|
|
@@ -68,7 +72,7 @@ export function FactorySettingsView() {
|
|
|
68
72
|
useState(true);
|
|
69
73
|
const [automationFailureAlertEmail, setAutomationFailureAlertEmail] =
|
|
70
74
|
useState("");
|
|
71
|
-
const query = useActionQuery("get-triage-config", {});
|
|
75
|
+
const query = useActionQuery("get-triage-config", { factoryId });
|
|
72
76
|
const schedulerHealthQuery = useActionQuery<FactoryAutomationHealth>(
|
|
73
77
|
"get-factory-automation-health",
|
|
74
78
|
{},
|
|
@@ -105,6 +109,7 @@ export function FactorySettingsView() {
|
|
|
105
109
|
}
|
|
106
110
|
try {
|
|
107
111
|
await mutation.mutateAsync({
|
|
112
|
+
factoryId,
|
|
108
113
|
slackWorkspace: workspace,
|
|
109
114
|
slackChannelId: channelId,
|
|
110
115
|
slackChannelName: channelName,
|
|
@@ -6,14 +6,9 @@ import { Link, useLocation, useSearchParams } from "react-router";
|
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
7
7
|
|
|
8
8
|
export const FACTORY_AGENTS_HREF = "/factory?tab=agents";
|
|
9
|
-
export const
|
|
10
|
-
export const FACTORY_NEW_HREF = "/factory?new=1";
|
|
9
|
+
export const NEW_FACTORY_HREF = "/new-factory";
|
|
11
10
|
|
|
12
|
-
export function FactoryWorkspaceActions({
|
|
13
|
-
onNewFactory,
|
|
14
|
-
}: {
|
|
15
|
-
onNewFactory?: () => void;
|
|
16
|
-
}) {
|
|
11
|
+
export function FactoryWorkspaceActions() {
|
|
17
12
|
const t = useT();
|
|
18
13
|
const location = useLocation();
|
|
19
14
|
const [searchParams] = useSearchParams();
|
|
@@ -22,31 +17,21 @@ export function FactoryWorkspaceActions({
|
|
|
22
17
|
(location.pathname === "/factory" &&
|
|
23
18
|
searchParams.get("tab") === "agents" &&
|
|
24
19
|
!searchParams.get("factoryId"));
|
|
25
|
-
const
|
|
20
|
+
const onNewFactoryPage = location.pathname === "/new-factory";
|
|
26
21
|
|
|
27
22
|
return (
|
|
28
23
|
<div className="flex items-center gap-2">
|
|
29
24
|
<Button asChild variant={onAgents ? "default" : "outline"} size="sm">
|
|
30
25
|
<Link to={FACTORY_AGENTS_HREF}>{t("factoryRoute.agentsTab")}</Link>
|
|
31
26
|
</Button>
|
|
32
|
-
|
|
33
|
-
<
|
|
34
|
-
{
|
|
35
|
-
</Link>
|
|
36
|
-
</Button>
|
|
37
|
-
{onNewFactory ? (
|
|
38
|
-
<Button type="button" size="sm" onClick={onNewFactory}>
|
|
39
|
-
<IconPlus className="size-4" />
|
|
40
|
-
{t("factoryRoute.newFactory")}
|
|
41
|
-
</Button>
|
|
42
|
-
) : (
|
|
43
|
-
<Button asChild size="sm">
|
|
44
|
-
<Link to={FACTORY_NEW_HREF}>
|
|
27
|
+
{!onNewFactoryPage ? (
|
|
28
|
+
<Button asChild variant="outline" size="sm">
|
|
29
|
+
<Link to={NEW_FACTORY_HREF}>
|
|
45
30
|
<IconPlus className="size-4" />
|
|
46
31
|
{t("factoryRoute.newFactory")}
|
|
47
32
|
</Link>
|
|
48
33
|
</Button>
|
|
49
|
-
)}
|
|
34
|
+
) : null}
|
|
50
35
|
<AgentToggleButton />
|
|
51
36
|
</div>
|
|
52
37
|
);
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { useActionMutation } from "@agent-native/core/client/hooks";
|
|
2
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
3
|
+
import {
|
|
4
|
+
IconAlertCircle,
|
|
5
|
+
IconBrandGithub,
|
|
6
|
+
IconBrandSlack,
|
|
7
|
+
IconLoader2,
|
|
8
|
+
} from "@tabler/icons-react";
|
|
9
|
+
import { useState } from "react";
|
|
10
|
+
import { Link } from "react-router";
|
|
11
|
+
import { toast } from "sonner";
|
|
12
|
+
|
|
13
|
+
import { Button } from "@/components/ui/button";
|
|
14
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
15
|
+
import { Input } from "@/components/ui/input";
|
|
16
|
+
import { Label } from "@/components/ui/label";
|
|
17
|
+
import { Switch } from "@/components/ui/switch";
|
|
18
|
+
import { Textarea } from "@/components/ui/textarea";
|
|
19
|
+
|
|
20
|
+
type CreateFactoryResult = {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
factoryId: string;
|
|
23
|
+
name: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function NewFactoryForm({
|
|
27
|
+
onCreated,
|
|
28
|
+
}: {
|
|
29
|
+
onCreated: (factoryId: string) => void;
|
|
30
|
+
}) {
|
|
31
|
+
const t = useT();
|
|
32
|
+
const createMutation = useActionMutation("create-factory");
|
|
33
|
+
const [name, setName] = useState("");
|
|
34
|
+
const [description, setDescription] = useState("");
|
|
35
|
+
const [slackChannelId, setSlackChannelId] = useState("");
|
|
36
|
+
const [slackChannelName, setSlackChannelName] = useState("");
|
|
37
|
+
const [observeSlack, setObserveSlack] = useState(false);
|
|
38
|
+
const [repository, setRepository] = useState("");
|
|
39
|
+
const [observeGithub, setObserveGithub] = useState(false);
|
|
40
|
+
const [sentryOrgSlug, setSentryOrgSlug] = useState("");
|
|
41
|
+
const [sentryProjectSlug, setSentryProjectSlug] = useState("");
|
|
42
|
+
const [sentryEnvironment, setSentryEnvironment] = useState("");
|
|
43
|
+
const [observeSentry, setObserveSentry] = useState(false);
|
|
44
|
+
|
|
45
|
+
async function handleSubmit(event: React.FormEvent) {
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
const trimmedName = name.trim();
|
|
48
|
+
if (!trimmedName) {
|
|
49
|
+
toast.error(t("factoryRoute.createFactoryNameRequired"));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const result = (await createMutation.mutateAsync({
|
|
54
|
+
name: trimmedName,
|
|
55
|
+
...(description.trim() ? { description: description.trim() } : {}),
|
|
56
|
+
...(slackChannelId.trim()
|
|
57
|
+
? { slackChannelId: slackChannelId.trim() }
|
|
58
|
+
: {}),
|
|
59
|
+
...(slackChannelName.trim()
|
|
60
|
+
? { slackChannelName: slackChannelName.trim() }
|
|
61
|
+
: {}),
|
|
62
|
+
...(observeSlack ? { observeSlack: true } : {}),
|
|
63
|
+
...(repository.trim() ? { repository: repository.trim() } : {}),
|
|
64
|
+
...(observeGithub ? { observeGithub: true } : {}),
|
|
65
|
+
...(sentryOrgSlug.trim()
|
|
66
|
+
? { sentryOrgSlug: sentryOrgSlug.trim() }
|
|
67
|
+
: {}),
|
|
68
|
+
...(sentryProjectSlug.trim()
|
|
69
|
+
? { sentryProjectSlug: sentryProjectSlug.trim() }
|
|
70
|
+
: {}),
|
|
71
|
+
...(sentryEnvironment.trim()
|
|
72
|
+
? { sentryEnvironment: sentryEnvironment.trim() }
|
|
73
|
+
: {}),
|
|
74
|
+
...(observeSentry ? { observeSentry: true } : {}),
|
|
75
|
+
})) as CreateFactoryResult;
|
|
76
|
+
toast.success(t("factoryRoute.createFactorySuccess"));
|
|
77
|
+
onCreated(result.factoryId);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
toast.error(
|
|
80
|
+
error instanceof Error
|
|
81
|
+
? error.message
|
|
82
|
+
: t("factoryRoute.createFactoryFailed"),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<form
|
|
89
|
+
className="mx-auto flex w-full max-w-2xl flex-col gap-6"
|
|
90
|
+
onSubmit={(event) => void handleSubmit(event)}
|
|
91
|
+
>
|
|
92
|
+
<div className="space-y-2">
|
|
93
|
+
<Label htmlFor="new-factory-name">
|
|
94
|
+
{t("factoryRoute.createFactoryNameLabel")}
|
|
95
|
+
</Label>
|
|
96
|
+
<Input
|
|
97
|
+
id="new-factory-name"
|
|
98
|
+
value={name}
|
|
99
|
+
onChange={(event) => setName(event.target.value)}
|
|
100
|
+
placeholder={t("factoryRoute.newFactory")}
|
|
101
|
+
autoFocus
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
<div className="space-y-2">
|
|
105
|
+
<Label htmlFor="new-factory-description">
|
|
106
|
+
{t("factoryRoute.createFactoryDescriptionLabel")}
|
|
107
|
+
</Label>
|
|
108
|
+
<Textarea
|
|
109
|
+
id="new-factory-description"
|
|
110
|
+
value={description}
|
|
111
|
+
onChange={(event) => setDescription(event.target.value)}
|
|
112
|
+
rows={2}
|
|
113
|
+
/>
|
|
114
|
+
</div>
|
|
115
|
+
<div className="space-y-4">
|
|
116
|
+
<Card>
|
|
117
|
+
<CardHeader className="px-4 pb-0 pt-4">
|
|
118
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
119
|
+
<IconBrandSlack className="size-4" />
|
|
120
|
+
{t("factoryRoute.createFactorySlackSourceOptional")}
|
|
121
|
+
</CardTitle>
|
|
122
|
+
</CardHeader>
|
|
123
|
+
<CardContent className="space-y-3 px-4 pb-4">
|
|
124
|
+
<div className="space-y-2">
|
|
125
|
+
<Label htmlFor="new-factory-slack-channel">
|
|
126
|
+
{t("triage.slackChannelId")}
|
|
127
|
+
</Label>
|
|
128
|
+
<Input
|
|
129
|
+
id="new-factory-slack-channel"
|
|
130
|
+
value={slackChannelId}
|
|
131
|
+
onChange={(event) => setSlackChannelId(event.target.value)}
|
|
132
|
+
placeholder={t("triage.slackChannelPlaceholder")}
|
|
133
|
+
/>
|
|
134
|
+
</div>
|
|
135
|
+
<div className="space-y-2">
|
|
136
|
+
<Label htmlFor="new-factory-slack-channel-name">
|
|
137
|
+
{t("triage.slackChannelName")}
|
|
138
|
+
</Label>
|
|
139
|
+
<Input
|
|
140
|
+
id="new-factory-slack-channel-name"
|
|
141
|
+
value={slackChannelName}
|
|
142
|
+
onChange={(event) => setSlackChannelName(event.target.value)}
|
|
143
|
+
placeholder={t("triage.slackChannelNamePlaceholder")}
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
146
|
+
<div className="flex items-center justify-between gap-3">
|
|
147
|
+
<Label htmlFor="new-factory-observe-slack">
|
|
148
|
+
{t("factoryRoute.createFactoryObserveSlack")}
|
|
149
|
+
</Label>
|
|
150
|
+
<Switch
|
|
151
|
+
id="new-factory-observe-slack"
|
|
152
|
+
checked={observeSlack}
|
|
153
|
+
onCheckedChange={(checked) => setObserveSlack(checked === true)}
|
|
154
|
+
/>
|
|
155
|
+
</div>
|
|
156
|
+
</CardContent>
|
|
157
|
+
</Card>
|
|
158
|
+
<Card>
|
|
159
|
+
<CardHeader className="px-4 pb-0 pt-4">
|
|
160
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
161
|
+
<IconBrandGithub className="size-4" />
|
|
162
|
+
{t("factoryRoute.createFactoryGithubSourceOptional")}
|
|
163
|
+
</CardTitle>
|
|
164
|
+
</CardHeader>
|
|
165
|
+
<CardContent className="space-y-3 px-4 pb-4">
|
|
166
|
+
<div className="space-y-2">
|
|
167
|
+
<Label htmlFor="new-factory-repository">
|
|
168
|
+
{t("triage.repository")}
|
|
169
|
+
</Label>
|
|
170
|
+
<Input
|
|
171
|
+
id="new-factory-repository"
|
|
172
|
+
value={repository}
|
|
173
|
+
onChange={(event) => setRepository(event.target.value)}
|
|
174
|
+
placeholder={t("triage.repositoryPlaceholder")}
|
|
175
|
+
/>
|
|
176
|
+
</div>
|
|
177
|
+
<div className="flex items-center justify-between gap-3">
|
|
178
|
+
<Label htmlFor="new-factory-observe-github">
|
|
179
|
+
{t("factoryRoute.createFactoryObserveGithub")}
|
|
180
|
+
</Label>
|
|
181
|
+
<Switch
|
|
182
|
+
id="new-factory-observe-github"
|
|
183
|
+
checked={observeGithub}
|
|
184
|
+
onCheckedChange={(checked) =>
|
|
185
|
+
setObserveGithub(checked === true)
|
|
186
|
+
}
|
|
187
|
+
/>
|
|
188
|
+
</div>
|
|
189
|
+
</CardContent>
|
|
190
|
+
</Card>
|
|
191
|
+
<Card>
|
|
192
|
+
<CardHeader className="px-4 pb-0 pt-4">
|
|
193
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
194
|
+
<IconAlertCircle className="size-4" />
|
|
195
|
+
{t("factoryRoute.createFactorySentrySourceOptional")}
|
|
196
|
+
</CardTitle>
|
|
197
|
+
</CardHeader>
|
|
198
|
+
<CardContent className="space-y-3 px-4 pb-4">
|
|
199
|
+
<div className="space-y-2">
|
|
200
|
+
<Label htmlFor="new-factory-sentry-org">
|
|
201
|
+
{t("triage.sentryOrgSlug")}
|
|
202
|
+
</Label>
|
|
203
|
+
<Input
|
|
204
|
+
id="new-factory-sentry-org"
|
|
205
|
+
value={sentryOrgSlug}
|
|
206
|
+
onChange={(event) => setSentryOrgSlug(event.target.value)}
|
|
207
|
+
placeholder={t("triage.sentryOrgPlaceholder")}
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
210
|
+
<div className="space-y-2">
|
|
211
|
+
<Label htmlFor="new-factory-sentry-project">
|
|
212
|
+
{t("triage.sentryProjectSlug")}
|
|
213
|
+
</Label>
|
|
214
|
+
<Input
|
|
215
|
+
id="new-factory-sentry-project"
|
|
216
|
+
value={sentryProjectSlug}
|
|
217
|
+
onChange={(event) => setSentryProjectSlug(event.target.value)}
|
|
218
|
+
placeholder={t("triage.sentryProjectPlaceholder")}
|
|
219
|
+
/>
|
|
220
|
+
</div>
|
|
221
|
+
<div className="space-y-2">
|
|
222
|
+
<Label htmlFor="new-factory-sentry-environment">
|
|
223
|
+
{t("triage.sentryEnvironment")}
|
|
224
|
+
</Label>
|
|
225
|
+
<Input
|
|
226
|
+
id="new-factory-sentry-environment"
|
|
227
|
+
value={sentryEnvironment}
|
|
228
|
+
onChange={(event) => setSentryEnvironment(event.target.value)}
|
|
229
|
+
placeholder={t("triage.sentryEnvironmentPlaceholder")}
|
|
230
|
+
/>
|
|
231
|
+
</div>
|
|
232
|
+
<div className="flex items-center justify-between gap-3">
|
|
233
|
+
<Label htmlFor="new-factory-observe-sentry">
|
|
234
|
+
{t("factoryRoute.createFactoryObserveSentry")}
|
|
235
|
+
</Label>
|
|
236
|
+
<Switch
|
|
237
|
+
id="new-factory-observe-sentry"
|
|
238
|
+
checked={observeSentry}
|
|
239
|
+
onCheckedChange={(checked) =>
|
|
240
|
+
setObserveSentry(checked === true)
|
|
241
|
+
}
|
|
242
|
+
/>
|
|
243
|
+
</div>
|
|
244
|
+
</CardContent>
|
|
245
|
+
</Card>
|
|
246
|
+
</div>
|
|
247
|
+
<div className="flex justify-end gap-2">
|
|
248
|
+
<Button asChild type="button" variant="outline">
|
|
249
|
+
<Link to="/factory">{t("factoryRoute.createFactoryCancel")}</Link>
|
|
250
|
+
</Button>
|
|
251
|
+
<Button type="submit" disabled={createMutation.isPending}>
|
|
252
|
+
{createMutation.isPending ? (
|
|
253
|
+
<IconLoader2 className="size-4 animate-spin" />
|
|
254
|
+
) : null}
|
|
255
|
+
{t("factoryRoute.createFactorySubmit")}
|
|
256
|
+
</Button>
|
|
257
|
+
</div>
|
|
258
|
+
</form>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
@@ -42,7 +42,7 @@ function routeOwnsToolbar(pathname: string): boolean {
|
|
|
42
42
|
pathname === "/chat" ||
|
|
43
43
|
pathname.startsWith("/chat/") ||
|
|
44
44
|
pathname === "/factory" ||
|
|
45
|
-
pathname === "/factory
|
|
45
|
+
pathname === "/new-factory" ||
|
|
46
46
|
pathname === "/agents" ||
|
|
47
47
|
pathname === "/database" ||
|
|
48
48
|
pathname.startsWith("/extensions")
|
|
@@ -445,7 +445,8 @@ export function Sidebar({
|
|
|
445
445
|
item.href === "/"
|
|
446
446
|
? isChatRoute
|
|
447
447
|
: item.href === "/factory"
|
|
448
|
-
? location.pathname === "/factory"
|
|
448
|
+
? location.pathname === "/factory" ||
|
|
449
|
+
location.pathname === "/new-factory"
|
|
449
450
|
: location.pathname.startsWith(item.href);
|
|
450
451
|
const link = (
|
|
451
452
|
<Link
|
|
@@ -13,6 +13,7 @@ export interface NavigationState {
|
|
|
13
13
|
factoryAuditRunId?: string;
|
|
14
14
|
factoryNodeId?: string;
|
|
15
15
|
factoryEdgeId?: string;
|
|
16
|
+
creatingFactory?: boolean;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export function useNavigationState() {
|
|
@@ -48,6 +49,7 @@ export function useNavigationState() {
|
|
|
48
49
|
...(pathname === "/factory" && searchParams.get("edge")
|
|
49
50
|
? { factoryEdgeId: searchParams.get("edge") ?? undefined }
|
|
50
51
|
: {}),
|
|
52
|
+
...(pathname === "/new-factory" ? { creatingFactory: true } : {}),
|
|
51
53
|
};
|
|
52
54
|
},
|
|
53
55
|
getCommandPath: (command) =>
|
|
@@ -73,7 +75,7 @@ function viewForPath(pathname: string): string {
|
|
|
73
75
|
if (pathname.startsWith("/extensions")) return "extensions";
|
|
74
76
|
if (pathname.startsWith("/observability")) return "observability";
|
|
75
77
|
if (pathname.startsWith("/agents")) return "agents";
|
|
76
|
-
if (pathname === "/factory
|
|
78
|
+
if (pathname === "/new-factory") return "factory";
|
|
77
79
|
if (pathname === "/factory") return "factory";
|
|
78
80
|
if (pathname.startsWith("/settings/agent") || pathname.startsWith("/agent")) {
|
|
79
81
|
return "agent";
|
|
@@ -97,8 +99,6 @@ function pathForView(view?: string): string {
|
|
|
97
99
|
return "/observability";
|
|
98
100
|
case "factory":
|
|
99
101
|
return "/factory";
|
|
100
|
-
case "factory-settings":
|
|
101
|
-
return "/factory-settings";
|
|
102
102
|
case "agents":
|
|
103
103
|
return "/agents";
|
|
104
104
|
case "agent":
|