@agent-native/core 0.168.7 → 0.168.9
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/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/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/package.json +1 -1
|
@@ -8,6 +8,13 @@ import {
|
|
|
8
8
|
triageItems,
|
|
9
9
|
triageRuns,
|
|
10
10
|
} from "../server/db/schema.js";
|
|
11
|
+
import { DEFAULT_FACTORY_ID } from "../server/factory-graph/store.js";
|
|
12
|
+
import {
|
|
13
|
+
factoryIdSchema,
|
|
14
|
+
orgFactoryDecisionFilter,
|
|
15
|
+
orgFactoryItemFilter,
|
|
16
|
+
orgFactoryRunFilter,
|
|
17
|
+
} from "../server/lib/factory-scope.js";
|
|
11
18
|
import {
|
|
12
19
|
requireWorkspaceMember,
|
|
13
20
|
workspaceMemberIdentityFromContext,
|
|
@@ -19,6 +26,7 @@ export default defineAction({
|
|
|
19
26
|
description:
|
|
20
27
|
"Explicitly approve one Factory item and start one deduplicated Builder coding run. The approver, decision, guard results, and callback state are recorded before provider work is considered successful.",
|
|
21
28
|
schema: z.object({
|
|
29
|
+
factoryId: factoryIdSchema.default(DEFAULT_FACTORY_ID),
|
|
22
30
|
itemId: z.string().min(1),
|
|
23
31
|
decisionId: z.string().min(1).optional(),
|
|
24
32
|
confirm: z.literal(true),
|
|
@@ -27,7 +35,7 @@ export default defineAction({
|
|
|
27
35
|
// must remain paused until a human approves this exact agent tool call.
|
|
28
36
|
needsApproval: true,
|
|
29
37
|
http: { method: "POST" },
|
|
30
|
-
run: async ({ itemId, decisionId, confirm }, context) => {
|
|
38
|
+
run: async ({ factoryId, itemId, decisionId, confirm }, context) => {
|
|
31
39
|
if (!confirm) throw new Error("Explicit confirmation is required.");
|
|
32
40
|
const { userEmail, orgId } = await requireWorkspaceMember(
|
|
33
41
|
workspaceMemberIdentityFromContext(context),
|
|
@@ -37,7 +45,12 @@ export default defineAction({
|
|
|
37
45
|
await db
|
|
38
46
|
.select()
|
|
39
47
|
.from(triageItems)
|
|
40
|
-
.where(
|
|
48
|
+
.where(
|
|
49
|
+
and(
|
|
50
|
+
eq(triageItems.id, itemId),
|
|
51
|
+
orgFactoryItemFilter(orgId, factoryId),
|
|
52
|
+
),
|
|
53
|
+
)
|
|
41
54
|
.limit(1)
|
|
42
55
|
)[0];
|
|
43
56
|
if (!item) throw new Error("Factory item not found.");
|
|
@@ -47,8 +60,8 @@ export default defineAction({
|
|
|
47
60
|
.from(triageDecisions)
|
|
48
61
|
.where(
|
|
49
62
|
and(
|
|
50
|
-
eq(triageDecisions.orgId, orgId),
|
|
51
63
|
eq(triageDecisions.itemId, itemId),
|
|
64
|
+
orgFactoryDecisionFilter(orgId, factoryId),
|
|
52
65
|
),
|
|
53
66
|
)
|
|
54
67
|
.orderBy(asc(triageDecisions.createdAt));
|
|
@@ -72,7 +85,9 @@ export default defineAction({
|
|
|
72
85
|
await db
|
|
73
86
|
.select()
|
|
74
87
|
.from(triageRuns)
|
|
75
|
-
.where(
|
|
88
|
+
.where(
|
|
89
|
+
and(eq(triageRuns.id, runId), orgFactoryRunFilter(orgId, factoryId)),
|
|
90
|
+
)
|
|
76
91
|
.limit(1)
|
|
77
92
|
)[0];
|
|
78
93
|
if (
|
|
@@ -110,6 +125,7 @@ export default defineAction({
|
|
|
110
125
|
error: null,
|
|
111
126
|
ownerEmail: item.ownerEmail,
|
|
112
127
|
orgId,
|
|
128
|
+
factoryId,
|
|
113
129
|
})
|
|
114
130
|
.onConflictDoNothing();
|
|
115
131
|
|
|
@@ -117,7 +133,9 @@ export default defineAction({
|
|
|
117
133
|
await db
|
|
118
134
|
.select()
|
|
119
135
|
.from(triageRuns)
|
|
120
|
-
.where(
|
|
136
|
+
.where(
|
|
137
|
+
and(eq(triageRuns.id, runId), orgFactoryRunFilter(orgId, factoryId)),
|
|
138
|
+
)
|
|
121
139
|
.limit(1)
|
|
122
140
|
)[0];
|
|
123
141
|
if (!run) throw new Error("Factory run could not be recorded.");
|
|
@@ -155,11 +173,18 @@ export default defineAction({
|
|
|
155
173
|
progressLogJson: JSON.stringify(progress),
|
|
156
174
|
heartbeatAt: new Date().toISOString(),
|
|
157
175
|
})
|
|
158
|
-
.where(
|
|
176
|
+
.where(
|
|
177
|
+
and(eq(triageRuns.id, runId), orgFactoryRunFilter(orgId, factoryId)),
|
|
178
|
+
);
|
|
159
179
|
await db
|
|
160
180
|
.update(triageItems)
|
|
161
181
|
.set({ status: "classified", updatedAt: new Date().toISOString() })
|
|
162
|
-
.where(
|
|
182
|
+
.where(
|
|
183
|
+
and(
|
|
184
|
+
eq(triageItems.id, itemId),
|
|
185
|
+
orgFactoryItemFilter(orgId, factoryId),
|
|
186
|
+
),
|
|
187
|
+
);
|
|
163
188
|
return {
|
|
164
189
|
ok: true,
|
|
165
190
|
deduplicated: false,
|
|
@@ -176,7 +201,9 @@ export default defineAction({
|
|
|
176
201
|
completedAt: new Date().toISOString(),
|
|
177
202
|
heartbeatAt: new Date().toISOString(),
|
|
178
203
|
})
|
|
179
|
-
.where(
|
|
204
|
+
.where(
|
|
205
|
+
and(eq(triageRuns.id, runId), orgFactoryRunFilter(orgId, factoryId)),
|
|
206
|
+
);
|
|
180
207
|
throw new Error(
|
|
181
208
|
`Factory approval was recorded, but Builder dispatch failed: ${message}`,
|
|
182
209
|
);
|
|
@@ -4,7 +4,12 @@ import { z } from "zod";
|
|
|
4
4
|
|
|
5
5
|
import { resolveConnectorSecret } from "../server/connectors/credentials.js";
|
|
6
6
|
import { getDb } from "../server/db/index.js";
|
|
7
|
-
import {
|
|
7
|
+
import { triageItems } from "../server/db/schema.js";
|
|
8
|
+
import { DEFAULT_FACTORY_ID } from "../server/factory-graph/store.js";
|
|
9
|
+
import {
|
|
10
|
+
factoryIdSchema,
|
|
11
|
+
readTriageConfigRow,
|
|
12
|
+
} from "../server/lib/factory-scope.js";
|
|
8
13
|
import { requireFactoryAutomation } from "../server/lib/require-factory-automation.js";
|
|
9
14
|
import {
|
|
10
15
|
requireWorkspaceMember,
|
|
@@ -85,13 +90,13 @@ export default defineAction({
|
|
|
85
90
|
"Watch one builder-io-bot pull request, post the bounded feedback-fix request when CI, mergeability, or review feedback needs work, and stop after 20 quiet minutes. The action never merges or approves.",
|
|
86
91
|
schema: z.object({
|
|
87
92
|
itemId: z.string().min(1),
|
|
93
|
+
factoryId: factoryIdSchema.optional(),
|
|
88
94
|
}),
|
|
89
95
|
http: false,
|
|
90
|
-
run: async ({ itemId }, context) => {
|
|
96
|
+
run: async ({ itemId, factoryId: factoryIdInput }, context) => {
|
|
91
97
|
const { userEmail, orgId } = await requireWorkspaceMember(
|
|
92
98
|
workspaceMemberIdentityFromContext(context),
|
|
93
99
|
);
|
|
94
|
-
await requireFactoryAutomation(context, { userEmail, orgId }, "prBabysit");
|
|
95
100
|
|
|
96
101
|
const db = getDb();
|
|
97
102
|
const item = (
|
|
@@ -102,6 +107,16 @@ export default defineAction({
|
|
|
102
107
|
.limit(1)
|
|
103
108
|
)[0];
|
|
104
109
|
if (!item) throw new Error("Factory item not found for PR babysitting.");
|
|
110
|
+
const factoryId = factoryIdInput ?? item.factoryId ?? DEFAULT_FACTORY_ID;
|
|
111
|
+
if ((item.factoryId ?? DEFAULT_FACTORY_ID) !== factoryId) {
|
|
112
|
+
throw new Error("Factory item does not belong to this factory.");
|
|
113
|
+
}
|
|
114
|
+
await requireFactoryAutomation(
|
|
115
|
+
context,
|
|
116
|
+
{ userEmail, orgId },
|
|
117
|
+
"prBabysit",
|
|
118
|
+
factoryId,
|
|
119
|
+
);
|
|
105
120
|
if (
|
|
106
121
|
item.source !== "github" ||
|
|
107
122
|
!item.repository ||
|
|
@@ -115,12 +130,8 @@ export default defineAction({
|
|
|
115
130
|
}
|
|
116
131
|
|
|
117
132
|
const configuredRepository = (
|
|
118
|
-
await db
|
|
119
|
-
|
|
120
|
-
.from(triageConfig)
|
|
121
|
-
.where(and(eq(triageConfig.id, orgId), eq(triageConfig.orgId, orgId)))
|
|
122
|
-
.limit(1)
|
|
123
|
-
)[0]?.repository;
|
|
133
|
+
await readTriageConfigRow(db, orgId, factoryId)
|
|
134
|
+
)?.repository;
|
|
124
135
|
if (
|
|
125
136
|
!configuredRepository ||
|
|
126
137
|
configuredRepository.trim() !== item.repository.trim()
|
|
@@ -154,6 +165,7 @@ export default defineAction({
|
|
|
154
165
|
summary: "Skipped because the pull request is closed or a draft.",
|
|
155
166
|
details: { state: pullRequest.state, draft: pullRequest.draft },
|
|
156
167
|
},
|
|
168
|
+
factoryId,
|
|
157
169
|
);
|
|
158
170
|
return { ok: true, action: "skipped", reason: "PR is closed or draft." };
|
|
159
171
|
}
|
|
@@ -182,6 +194,7 @@ export default defineAction({
|
|
|
182
194
|
summary: `${ownerOwnedArea} is owner-managed; no bot feedback was posted.`,
|
|
183
195
|
details: { ownerOwnedArea },
|
|
184
196
|
},
|
|
197
|
+
factoryId,
|
|
185
198
|
);
|
|
186
199
|
return {
|
|
187
200
|
ok: true,
|
|
@@ -257,6 +270,7 @@ export default defineAction({
|
|
|
257
270
|
reviewFeedbackClean: signal.isClean,
|
|
258
271
|
},
|
|
259
272
|
},
|
|
273
|
+
factoryId,
|
|
260
274
|
);
|
|
261
275
|
const now = new Date();
|
|
262
276
|
const nowIso = now.toISOString();
|
|
@@ -326,6 +340,7 @@ export default defineAction({
|
|
|
326
340
|
summary: "Posted the bounded feedback-fix request to builder-io-bot.",
|
|
327
341
|
details: { commentUrl: comment.htmlUrl, quietForMinutes: 0 },
|
|
328
342
|
},
|
|
343
|
+
factoryId,
|
|
329
344
|
);
|
|
330
345
|
await updateBabysitMetadata(itemId, orgId, {
|
|
331
346
|
prBabysitState: "active",
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core/action";
|
|
2
|
+
import { buildDeepLink } from "@agent-native/core/server";
|
|
3
|
+
import { and, eq } from "drizzle-orm";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
import { getDb } from "../server/db/index.js";
|
|
7
|
+
import {
|
|
8
|
+
factoryDefinitions,
|
|
9
|
+
factoryGraphVersions,
|
|
10
|
+
triageConfig,
|
|
11
|
+
} from "../server/db/schema.js";
|
|
12
|
+
import {
|
|
13
|
+
minimalFactoryGraph,
|
|
14
|
+
normalizeFactoryGraph,
|
|
15
|
+
} from "../server/factory-graph/contracts.js";
|
|
16
|
+
import {
|
|
17
|
+
resolveEnabledAutomations,
|
|
18
|
+
isFactoryIdConflict,
|
|
19
|
+
isFactorySlackChannelConflict,
|
|
20
|
+
} from "../server/lib/factory-automation-plan.js";
|
|
21
|
+
import {
|
|
22
|
+
assertUniqueSlackChannelForFactory,
|
|
23
|
+
factoryConfigRowId,
|
|
24
|
+
resolveUniqueFactoryId,
|
|
25
|
+
} from "../server/lib/factory-scope.js";
|
|
26
|
+
import {
|
|
27
|
+
requireWorkspaceMember,
|
|
28
|
+
workspaceMemberIdentityFromContext,
|
|
29
|
+
} from "../server/lib/require-workspace-member.js";
|
|
30
|
+
import {
|
|
31
|
+
ensureFactoryAutomations,
|
|
32
|
+
removeFactoryAutomationResources,
|
|
33
|
+
syncFactoryAutomationEnabledStates,
|
|
34
|
+
} from "../server/plugins/factory-scheduler-job.js";
|
|
35
|
+
import { stableId } from "../server/triage/ids.js";
|
|
36
|
+
|
|
37
|
+
export default defineAction({
|
|
38
|
+
description:
|
|
39
|
+
"Create a new Factory with a minimal blueprint, optional observation settings, and seeded automations. Name is required; sources and automations are optional.",
|
|
40
|
+
schema: z.object({
|
|
41
|
+
name: z.string().trim().min(1).max(120),
|
|
42
|
+
description: z.string().trim().max(500).optional(),
|
|
43
|
+
slackChannelId: z.string().trim().max(128).optional(),
|
|
44
|
+
slackChannelName: z.string().trim().max(200).optional(),
|
|
45
|
+
observeSlack: z.boolean().optional(),
|
|
46
|
+
repository: z.string().trim().max(256).optional(),
|
|
47
|
+
observeGithub: z.boolean().optional(),
|
|
48
|
+
sentryOrgSlug: z.string().trim().max(200).optional(),
|
|
49
|
+
sentryProjectSlug: z.string().trim().max(200).optional(),
|
|
50
|
+
sentryEnvironment: z.string().trim().max(200).optional(),
|
|
51
|
+
observeSentry: z.boolean().optional(),
|
|
52
|
+
}),
|
|
53
|
+
http: { method: "POST" },
|
|
54
|
+
link: ({ result }) => ({
|
|
55
|
+
url: buildDeepLink({
|
|
56
|
+
app: "factory",
|
|
57
|
+
view: "factory",
|
|
58
|
+
params: { factoryId: result.factoryId },
|
|
59
|
+
}),
|
|
60
|
+
label: `Open ${result.name} in Factory`,
|
|
61
|
+
}),
|
|
62
|
+
run: async (input, context) => {
|
|
63
|
+
const { userEmail, orgId } = await requireWorkspaceMember(
|
|
64
|
+
workspaceMemberIdentityFromContext(context),
|
|
65
|
+
);
|
|
66
|
+
const db = getDb();
|
|
67
|
+
const description = input.description?.trim() ?? "";
|
|
68
|
+
const graph = normalizeFactoryGraph(
|
|
69
|
+
minimalFactoryGraph(input.name, description),
|
|
70
|
+
);
|
|
71
|
+
const now = new Date().toISOString();
|
|
72
|
+
const automationPlan = resolveEnabledAutomations({
|
|
73
|
+
observeSlack: input.observeSlack === true,
|
|
74
|
+
slackChannelId: input.slackChannelId,
|
|
75
|
+
observeGithub: input.observeGithub === true,
|
|
76
|
+
repository: input.repository,
|
|
77
|
+
observeSentry: input.observeSentry === true,
|
|
78
|
+
sentryOrgSlug: input.sentryOrgSlug,
|
|
79
|
+
sentryProjectSlug: input.sentryProjectSlug,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const MAX_FACTORY_CREATE_ATTEMPTS = 5;
|
|
83
|
+
let factoryId = "";
|
|
84
|
+
for (let attempt = 0; attempt < MAX_FACTORY_CREATE_ATTEMPTS; attempt++) {
|
|
85
|
+
factoryId = await resolveUniqueFactoryId(db, orgId, input.name);
|
|
86
|
+
const versionId = stableId("factory-graph", orgId, factoryId, "1");
|
|
87
|
+
try {
|
|
88
|
+
await db.transaction(async (tx) => {
|
|
89
|
+
await tx.insert(factoryDefinitions).values({
|
|
90
|
+
id: factoryId,
|
|
91
|
+
name: input.name,
|
|
92
|
+
description,
|
|
93
|
+
prompt: "",
|
|
94
|
+
graphVersion: 1,
|
|
95
|
+
graphJson: JSON.stringify(graph),
|
|
96
|
+
createdAt: now,
|
|
97
|
+
updatedAt: now,
|
|
98
|
+
ownerEmail: userEmail,
|
|
99
|
+
orgId,
|
|
100
|
+
});
|
|
101
|
+
await tx.insert(factoryGraphVersions).values({
|
|
102
|
+
id: versionId,
|
|
103
|
+
factoryId,
|
|
104
|
+
version: 1,
|
|
105
|
+
graphJson: JSON.stringify(graph),
|
|
106
|
+
source: "manual",
|
|
107
|
+
changeSummary: "Created from the new factory dialog.",
|
|
108
|
+
createdAt: now,
|
|
109
|
+
createdBy: userEmail,
|
|
110
|
+
ownerEmail: userEmail,
|
|
111
|
+
orgId,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (automationPlan.hasConfig) {
|
|
115
|
+
const slackChannelId = input.slackChannelId?.trim() || null;
|
|
116
|
+
await assertUniqueSlackChannelForFactory(
|
|
117
|
+
tx as unknown as typeof db,
|
|
118
|
+
orgId,
|
|
119
|
+
factoryId,
|
|
120
|
+
slackChannelId,
|
|
121
|
+
);
|
|
122
|
+
await tx.insert(triageConfig).values({
|
|
123
|
+
id: factoryConfigRowId(orgId, factoryId),
|
|
124
|
+
factoryId,
|
|
125
|
+
slackWorkspace: "primary",
|
|
126
|
+
slackChannelId,
|
|
127
|
+
slackChannelName: input.slackChannelName?.trim() || null,
|
|
128
|
+
builderSlackUserId: null,
|
|
129
|
+
pollingEnabled: automationPlan.pollingEnabled ? 1 : 0,
|
|
130
|
+
githubPollingEnabled: automationPlan.githubPollingEnabled ? 1 : 0,
|
|
131
|
+
sentryPollingEnabled: automationPlan.sentryPollingEnabled ? 1 : 0,
|
|
132
|
+
sentryOrgSlug: input.sentryOrgSlug?.trim() || null,
|
|
133
|
+
sentryProjectSlug: input.sentryProjectSlug?.trim() || null,
|
|
134
|
+
sentryEnvironment: input.sentryEnvironment?.trim() || null,
|
|
135
|
+
repository: input.repository?.trim() || null,
|
|
136
|
+
automationFailureAlertsEnabled: 1,
|
|
137
|
+
automationFailureAlertEmail: null,
|
|
138
|
+
createdAt: now,
|
|
139
|
+
updatedAt: now,
|
|
140
|
+
ownerEmail: userEmail,
|
|
141
|
+
orgId,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
break;
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (isFactorySlackChannelConflict(error)) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
"That Slack channel is already used by another Factory in this workspace.",
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (
|
|
153
|
+
isFactoryIdConflict(error) &&
|
|
154
|
+
attempt < MAX_FACTORY_CREATE_ATTEMPTS - 1
|
|
155
|
+
) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (!factoryId) {
|
|
162
|
+
throw new Error("Could not allocate a unique factory id.");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
await ensureFactoryAutomations(userEmail, orgId, factoryId, {
|
|
167
|
+
enabledNames: automationPlan.enabledNames,
|
|
168
|
+
});
|
|
169
|
+
if (automationPlan.enabledNames.size > 0) {
|
|
170
|
+
await syncFactoryAutomationEnabledStates(userEmail, orgId, factoryId, [
|
|
171
|
+
...automationPlan.enabledNames,
|
|
172
|
+
]);
|
|
173
|
+
}
|
|
174
|
+
} catch (error) {
|
|
175
|
+
await removeFactoryAutomationResources(orgId, factoryId);
|
|
176
|
+
await db
|
|
177
|
+
.delete(triageConfig)
|
|
178
|
+
.where(
|
|
179
|
+
and(
|
|
180
|
+
eq(triageConfig.orgId, orgId),
|
|
181
|
+
eq(triageConfig.factoryId, factoryId),
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
await db
|
|
185
|
+
.delete(factoryGraphVersions)
|
|
186
|
+
.where(
|
|
187
|
+
and(
|
|
188
|
+
eq(factoryGraphVersions.orgId, orgId),
|
|
189
|
+
eq(factoryGraphVersions.factoryId, factoryId),
|
|
190
|
+
),
|
|
191
|
+
);
|
|
192
|
+
await db
|
|
193
|
+
.delete(factoryDefinitions)
|
|
194
|
+
.where(
|
|
195
|
+
and(
|
|
196
|
+
eq(factoryDefinitions.id, factoryId),
|
|
197
|
+
eq(factoryDefinitions.orgId, orgId),
|
|
198
|
+
),
|
|
199
|
+
);
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
ok: true,
|
|
205
|
+
factoryId,
|
|
206
|
+
name: input.name,
|
|
207
|
+
graphVersion: 1,
|
|
208
|
+
enabledAutomations: [...automationPlan.enabledNames],
|
|
209
|
+
};
|
|
210
|
+
},
|
|
211
|
+
});
|
|
@@ -11,6 +11,12 @@ import {
|
|
|
11
11
|
triageItems,
|
|
12
12
|
triageRules,
|
|
13
13
|
} from "../server/db/schema.js";
|
|
14
|
+
import { DEFAULT_FACTORY_ID } from "../server/factory-graph/store.js";
|
|
15
|
+
import {
|
|
16
|
+
factoryIdSchema,
|
|
17
|
+
orgFactoryItemFilter,
|
|
18
|
+
orgFactoryRuleFilter,
|
|
19
|
+
} from "../server/lib/factory-scope.js";
|
|
14
20
|
import {
|
|
15
21
|
requireWorkspaceMember,
|
|
16
22
|
workspaceMemberIdentityFromContext,
|
|
@@ -25,9 +31,12 @@ import { evaluateStructuredGuards } from "../server/triage/guards.js";
|
|
|
25
31
|
export default defineAction({
|
|
26
32
|
description:
|
|
27
33
|
"Triage one Factory item against enabled rules and append structured guard decisions. This records proposals only and never changes a provider.",
|
|
28
|
-
schema: z.object({
|
|
34
|
+
schema: z.object({
|
|
35
|
+
factoryId: factoryIdSchema.default(DEFAULT_FACTORY_ID),
|
|
36
|
+
itemId: z.string().min(1),
|
|
37
|
+
}),
|
|
29
38
|
http: { method: "POST" },
|
|
30
|
-
run: async ({ itemId }, context) => {
|
|
39
|
+
run: async ({ factoryId, itemId }, context) => {
|
|
31
40
|
const { userEmail, orgId } = await requireWorkspaceMember(
|
|
32
41
|
workspaceMemberIdentityFromContext(context),
|
|
33
42
|
);
|
|
@@ -36,7 +45,12 @@ export default defineAction({
|
|
|
36
45
|
await db
|
|
37
46
|
.select()
|
|
38
47
|
.from(triageItems)
|
|
39
|
-
.where(
|
|
48
|
+
.where(
|
|
49
|
+
and(
|
|
50
|
+
eq(triageItems.id, itemId),
|
|
51
|
+
orgFactoryItemFilter(orgId, factoryId),
|
|
52
|
+
),
|
|
53
|
+
)
|
|
40
54
|
.limit(1)
|
|
41
55
|
)[0];
|
|
42
56
|
if (!item) throw new Error("Triage item not found");
|
|
@@ -44,7 +58,9 @@ export default defineAction({
|
|
|
44
58
|
const rules = await db
|
|
45
59
|
.select()
|
|
46
60
|
.from(triageRules)
|
|
47
|
-
.where(
|
|
61
|
+
.where(
|
|
62
|
+
and(eq(triageRules.enabled, 1), orgFactoryRuleFilter(orgId, factoryId)),
|
|
63
|
+
);
|
|
48
64
|
const metadata = parseMetadata(item.metadataJson);
|
|
49
65
|
const evidence = {
|
|
50
66
|
repository: item.repository ?? undefined,
|
|
@@ -77,6 +93,7 @@ export default defineAction({
|
|
|
77
93
|
{
|
|
78
94
|
action: "evaluate-triage-item",
|
|
79
95
|
kind: "decision",
|
|
96
|
+
factoryId,
|
|
80
97
|
status: "skipped",
|
|
81
98
|
itemId,
|
|
82
99
|
source: item.source,
|
|
@@ -116,6 +133,7 @@ export default defineAction({
|
|
|
116
133
|
createdAt: now,
|
|
117
134
|
ownerEmail: userEmail,
|
|
118
135
|
orgId,
|
|
136
|
+
factoryId,
|
|
119
137
|
};
|
|
120
138
|
});
|
|
121
139
|
|
|
@@ -131,7 +149,12 @@ export default defineAction({
|
|
|
131
149
|
: "shadow_decided",
|
|
132
150
|
updatedAt: now,
|
|
133
151
|
})
|
|
134
|
-
.where(
|
|
152
|
+
.where(
|
|
153
|
+
and(
|
|
154
|
+
eq(triageItems.id, itemId),
|
|
155
|
+
orgFactoryItemFilter(orgId, factoryId),
|
|
156
|
+
),
|
|
157
|
+
);
|
|
135
158
|
});
|
|
136
159
|
|
|
137
160
|
for (const decision of decisions) {
|
|
@@ -141,6 +164,7 @@ export default defineAction({
|
|
|
141
164
|
{
|
|
142
165
|
action: "evaluate-triage-item",
|
|
143
166
|
kind: "decision",
|
|
167
|
+
factoryId,
|
|
144
168
|
itemId,
|
|
145
169
|
source: item.source,
|
|
146
170
|
sourceUrl: item.sourceUrl,
|
|
@@ -28,7 +28,7 @@ export default defineAction({
|
|
|
28
28
|
const row = await readFactoryDefinition(orgId, factoryId);
|
|
29
29
|
const fallback = defaultFactoryDefinition();
|
|
30
30
|
const graph = row ? parseFactoryGraph(row.graphJson) : fallback.graph;
|
|
31
|
-
const metrics = await readFactoryMetrics(orgId);
|
|
31
|
+
const metrics = await readFactoryMetrics(orgId, factoryId);
|
|
32
32
|
|
|
33
33
|
const metricValues: Record<string, number> = {
|
|
34
34
|
items: metrics.totalItems,
|
|
@@ -3,7 +3,13 @@ import { and, eq } from "drizzle-orm";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
5
|
import { getDb } from "../server/db/index.js";
|
|
6
|
-
import {
|
|
6
|
+
import { triageItems } from "../server/db/schema.js";
|
|
7
|
+
import { DEFAULT_FACTORY_ID } from "../server/factory-graph/store.js";
|
|
8
|
+
import {
|
|
9
|
+
factoryIdSchema,
|
|
10
|
+
orgFactoryItemFilter,
|
|
11
|
+
readTriageConfigRow,
|
|
12
|
+
} from "../server/lib/factory-scope.js";
|
|
7
13
|
import {
|
|
8
14
|
requireWorkspaceMember,
|
|
9
15
|
workspaceMemberIdentityFromContext,
|
|
@@ -14,10 +20,13 @@ import { createSlackReader } from "../server/triage/slack-client.js";
|
|
|
14
20
|
export default defineAction({
|
|
15
21
|
description:
|
|
16
22
|
"Read the bounded full Slack thread for one Factory feedback item. Use this before deciding whether a report is a clear bug; unreadable or truncated context is not a green light.",
|
|
17
|
-
schema: z.object({
|
|
23
|
+
schema: z.object({
|
|
24
|
+
factoryId: factoryIdSchema.default(DEFAULT_FACTORY_ID),
|
|
25
|
+
itemId: z.string().min(1),
|
|
26
|
+
}),
|
|
18
27
|
http: { method: "GET" },
|
|
19
28
|
readOnly: true,
|
|
20
|
-
run: async ({ itemId }, context) => {
|
|
29
|
+
run: async ({ factoryId, itemId }, context) => {
|
|
21
30
|
const { userEmail, orgId } = await requireWorkspaceMember(
|
|
22
31
|
workspaceMemberIdentityFromContext(context),
|
|
23
32
|
);
|
|
@@ -25,7 +34,12 @@ export default defineAction({
|
|
|
25
34
|
await getDb()
|
|
26
35
|
.select()
|
|
27
36
|
.from(triageItems)
|
|
28
|
-
.where(
|
|
37
|
+
.where(
|
|
38
|
+
and(
|
|
39
|
+
eq(triageItems.id, itemId),
|
|
40
|
+
orgFactoryItemFilter(orgId, factoryId),
|
|
41
|
+
),
|
|
42
|
+
)
|
|
29
43
|
.limit(1)
|
|
30
44
|
)[0];
|
|
31
45
|
if (!item) throw new Error("Factory item not found.");
|
|
@@ -33,13 +47,7 @@ export default defineAction({
|
|
|
33
47
|
throw new Error("Factory item is not a readable Slack feedback item.");
|
|
34
48
|
}
|
|
35
49
|
|
|
36
|
-
const config = (
|
|
37
|
-
await getDb()
|
|
38
|
-
.select({ slackWorkspace: triageConfig.slackWorkspace })
|
|
39
|
-
.from(triageConfig)
|
|
40
|
-
.where(and(eq(triageConfig.id, orgId), eq(triageConfig.orgId, orgId)))
|
|
41
|
-
.limit(1)
|
|
42
|
-
)[0];
|
|
50
|
+
const config = await readTriageConfigRow(getDb(), orgId, factoryId);
|
|
43
51
|
const workspace =
|
|
44
52
|
config?.slackWorkspace === "secondary" ? "secondary" : "primary";
|
|
45
53
|
const slack = createSlackReader({ ownerEmail: userEmail, orgId });
|
|
@@ -66,6 +74,7 @@ export default defineAction({
|
|
|
66
74
|
messageCount: messages.length,
|
|
67
75
|
},
|
|
68
76
|
},
|
|
77
|
+
factoryId,
|
|
69
78
|
);
|
|
70
79
|
|
|
71
80
|
return {
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { getEmailReadiness } from "@agent-native/core/server";
|
|
3
|
-
import { and, eq } from "drizzle-orm";
|
|
4
3
|
import { z } from "zod";
|
|
5
4
|
|
|
6
5
|
import { getDb } from "../server/db/index.js";
|
|
7
|
-
import {
|
|
6
|
+
import { DEFAULT_FACTORY_ID } from "../server/factory-graph/store.js";
|
|
7
|
+
import {
|
|
8
|
+
factoryIdSchema,
|
|
9
|
+
readTriageConfigRow,
|
|
10
|
+
} from "../server/lib/factory-scope.js";
|
|
8
11
|
import {
|
|
9
12
|
requireWorkspaceMember,
|
|
10
13
|
workspaceMemberIdentityFromContext,
|
|
@@ -12,24 +15,21 @@ import {
|
|
|
12
15
|
|
|
13
16
|
export default defineAction({
|
|
14
17
|
description:
|
|
15
|
-
"Read Factory observation settings for the
|
|
16
|
-
schema: z.object({
|
|
18
|
+
"Read Factory observation settings for the selected factory. Secret values are never returned.",
|
|
19
|
+
schema: z.object({
|
|
20
|
+
factoryId: factoryIdSchema.default(DEFAULT_FACTORY_ID),
|
|
21
|
+
}),
|
|
17
22
|
http: { method: "GET" },
|
|
18
23
|
readOnly: true,
|
|
19
|
-
run: async (
|
|
24
|
+
run: async ({ factoryId }, context) => {
|
|
20
25
|
const { orgId } = await requireWorkspaceMember(
|
|
21
26
|
workspaceMemberIdentityFromContext(context),
|
|
22
27
|
);
|
|
23
28
|
const emailReadiness = await getEmailReadiness();
|
|
24
|
-
const row = (
|
|
25
|
-
await getDb()
|
|
26
|
-
.select()
|
|
27
|
-
.from(triageConfig)
|
|
28
|
-
.where(and(eq(triageConfig.id, orgId), eq(triageConfig.orgId, orgId)))
|
|
29
|
-
.limit(1)
|
|
30
|
-
)[0];
|
|
29
|
+
const row = await readTriageConfigRow(getDb(), orgId, factoryId);
|
|
31
30
|
if (!row) {
|
|
32
31
|
return {
|
|
32
|
+
factoryId,
|
|
33
33
|
slackWorkspace: "primary",
|
|
34
34
|
slackChannelId: null,
|
|
35
35
|
slackChannelName: null,
|
|
@@ -51,6 +51,7 @@ export default defineAction({
|
|
|
51
51
|
}
|
|
52
52
|
return {
|
|
53
53
|
...row,
|
|
54
|
+
factoryId,
|
|
54
55
|
pollingEnabled: row.pollingEnabled === 1,
|
|
55
56
|
githubPollingEnabled: row.githubPollingEnabled === 1,
|
|
56
57
|
sentryPollingEnabled: row.sentryPollingEnabled === 1,
|