@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
|
@@ -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,
|
|
@@ -9,6 +9,14 @@ import {
|
|
|
9
9
|
triageItems,
|
|
10
10
|
triageRuns,
|
|
11
11
|
} from "../server/db/schema.js";
|
|
12
|
+
import { DEFAULT_FACTORY_ID } from "../server/factory-graph/store.js";
|
|
13
|
+
import {
|
|
14
|
+
factoryIdSchema,
|
|
15
|
+
orgFactoryDecisionFilter,
|
|
16
|
+
orgFactoryFeedbackFilter,
|
|
17
|
+
orgFactoryItemFilter,
|
|
18
|
+
orgFactoryRunFilter,
|
|
19
|
+
} from "../server/lib/factory-scope.js";
|
|
12
20
|
import {
|
|
13
21
|
requireWorkspaceMember,
|
|
14
22
|
workspaceMemberIdentityFromContext,
|
|
@@ -18,10 +26,13 @@ import { recordFactoryAudit } from "../server/triage/audit.js";
|
|
|
18
26
|
export default defineAction({
|
|
19
27
|
description:
|
|
20
28
|
"Inspect one Factory item, including its append-only shadow decisions, feedback, and run reconciliation state.",
|
|
21
|
-
schema: z.object({
|
|
29
|
+
schema: z.object({
|
|
30
|
+
factoryId: factoryIdSchema.default(DEFAULT_FACTORY_ID),
|
|
31
|
+
itemId: z.string().min(1),
|
|
32
|
+
}),
|
|
22
33
|
http: { method: "GET" },
|
|
23
34
|
readOnly: true,
|
|
24
|
-
run: async ({ itemId }, context) => {
|
|
35
|
+
run: async ({ factoryId, itemId }, context) => {
|
|
25
36
|
const { userEmail, orgId } = await requireWorkspaceMember(
|
|
26
37
|
workspaceMemberIdentityFromContext(context),
|
|
27
38
|
);
|
|
@@ -30,7 +41,12 @@ export default defineAction({
|
|
|
30
41
|
await db
|
|
31
42
|
.select()
|
|
32
43
|
.from(triageItems)
|
|
33
|
-
.where(
|
|
44
|
+
.where(
|
|
45
|
+
and(
|
|
46
|
+
eq(triageItems.id, itemId),
|
|
47
|
+
orgFactoryItemFilter(orgId, factoryId),
|
|
48
|
+
),
|
|
49
|
+
)
|
|
34
50
|
.limit(1)
|
|
35
51
|
)[0];
|
|
36
52
|
if (!item) throw new Error("Triage item not found");
|
|
@@ -41,19 +57,24 @@ export default defineAction({
|
|
|
41
57
|
.where(
|
|
42
58
|
and(
|
|
43
59
|
eq(triageDecisions.itemId, itemId),
|
|
44
|
-
|
|
60
|
+
orgFactoryDecisionFilter(orgId, factoryId),
|
|
45
61
|
),
|
|
46
62
|
)
|
|
47
63
|
.orderBy(asc(triageDecisions.createdAt));
|
|
48
64
|
const feedback = await db
|
|
49
65
|
.select()
|
|
50
66
|
.from(triageFeedback)
|
|
51
|
-
.where(
|
|
67
|
+
.where(orgFactoryFeedbackFilter(orgId, factoryId))
|
|
52
68
|
.orderBy(asc(triageFeedback.createdAt));
|
|
53
69
|
const runs = await db
|
|
54
70
|
.select()
|
|
55
71
|
.from(triageRuns)
|
|
56
|
-
.where(
|
|
72
|
+
.where(
|
|
73
|
+
and(
|
|
74
|
+
eq(triageRuns.itemId, itemId),
|
|
75
|
+
orgFactoryRunFilter(orgId, factoryId),
|
|
76
|
+
),
|
|
77
|
+
)
|
|
57
78
|
.orderBy(asc(triageRuns.startedAt));
|
|
58
79
|
|
|
59
80
|
const matchingFeedback = feedback.filter((entry) =>
|
|
@@ -76,6 +97,7 @@ export default defineAction({
|
|
|
76
97
|
coverage: item.coverage,
|
|
77
98
|
},
|
|
78
99
|
},
|
|
100
|
+
factoryId,
|
|
79
101
|
);
|
|
80
102
|
|
|
81
103
|
return {
|
|
@@ -6,10 +6,17 @@ import { resolveConnectorSecret } from "../server/connectors/credentials.js";
|
|
|
6
6
|
import { getDb } from "../server/db/index.js";
|
|
7
7
|
import {
|
|
8
8
|
triageDecisions,
|
|
9
|
-
triageConfig,
|
|
10
9
|
triageItems,
|
|
11
10
|
triageRuns,
|
|
12
11
|
} from "../server/db/schema.js";
|
|
12
|
+
import { DEFAULT_FACTORY_ID } from "../server/factory-graph/store.js";
|
|
13
|
+
import {
|
|
14
|
+
factoryIdSchema,
|
|
15
|
+
orgFactoryItemFilter,
|
|
16
|
+
orgFactoryRunFilter,
|
|
17
|
+
orgFactoryScopedItemWhere,
|
|
18
|
+
readTriageConfigRow,
|
|
19
|
+
} from "../server/lib/factory-scope.js";
|
|
13
20
|
import { requireFactoryAutomation } from "../server/lib/require-factory-automation.js";
|
|
14
21
|
import {
|
|
15
22
|
requireWorkspaceMember,
|
|
@@ -50,11 +57,12 @@ function requiredAiServicesEnv(
|
|
|
50
57
|
async function hasVerifiedFactoryRun(input: {
|
|
51
58
|
itemId: string | undefined;
|
|
52
59
|
orgId: string;
|
|
60
|
+
factoryId: string;
|
|
53
61
|
pullRequestBody: string | null;
|
|
54
62
|
headRef: string;
|
|
55
63
|
}): Promise<boolean> {
|
|
56
64
|
const db = getDb();
|
|
57
|
-
const runConditions = [
|
|
65
|
+
const runConditions = [orgFactoryRunFilter(input.orgId, input.factoryId)];
|
|
58
66
|
if (input.itemId) {
|
|
59
67
|
runConditions.push(eq(triageRuns.itemId, input.itemId));
|
|
60
68
|
}
|
|
@@ -91,7 +99,7 @@ async function hasVerifiedFactoryRun(input: {
|
|
|
91
99
|
const items = await db
|
|
92
100
|
.select({ id: triageItems.id, sourceUrl: triageItems.sourceUrl })
|
|
93
101
|
.from(triageItems)
|
|
94
|
-
.where(
|
|
102
|
+
.where(orgFactoryItemFilter(input.orgId, input.factoryId))
|
|
95
103
|
.orderBy(desc(triageItems.updatedAt))
|
|
96
104
|
.limit(100);
|
|
97
105
|
return items.some((item) => {
|
|
@@ -108,6 +116,7 @@ export default defineAction({
|
|
|
108
116
|
description:
|
|
109
117
|
"Govern one agent-native pull request after fetching bounded GitHub and ai-services evidence. Auto-approve only under the current review-prs membership, owner, evidence, and ultra-scary gates. Never auto-merge. Clips, Design, and Content feedback remains owner-managed while their verified PR-owner exceptions still apply.",
|
|
110
118
|
schema: z.object({
|
|
119
|
+
factoryId: factoryIdSchema.default(DEFAULT_FACTORY_ID),
|
|
111
120
|
repo: z.string().trim().min(1).max(256),
|
|
112
121
|
pullRequestNumber: z.number().int().positive(),
|
|
113
122
|
itemId: z.string().min(1).optional(),
|
|
@@ -118,6 +127,7 @@ export default defineAction({
|
|
|
118
127
|
http: false,
|
|
119
128
|
run: async (
|
|
120
129
|
{
|
|
130
|
+
factoryId,
|
|
121
131
|
repo,
|
|
122
132
|
pullRequestNumber,
|
|
123
133
|
itemId,
|
|
@@ -130,15 +140,16 @@ export default defineAction({
|
|
|
130
140
|
const { userEmail, orgId } = await requireWorkspaceMember(
|
|
131
141
|
workspaceMemberIdentityFromContext(context),
|
|
132
142
|
);
|
|
133
|
-
await requireFactoryAutomation(
|
|
143
|
+
await requireFactoryAutomation(
|
|
144
|
+
context,
|
|
145
|
+
{ userEmail, orgId },
|
|
146
|
+
"governance",
|
|
147
|
+
factoryId,
|
|
148
|
+
);
|
|
134
149
|
const repository = repositoryRef(repo);
|
|
135
150
|
const configuredRepository = (
|
|
136
|
-
await getDb()
|
|
137
|
-
|
|
138
|
-
.from(triageConfig)
|
|
139
|
-
.where(and(eq(triageConfig.id, orgId), eq(triageConfig.orgId, orgId)))
|
|
140
|
-
.limit(1)
|
|
141
|
-
)[0]?.repository;
|
|
151
|
+
await readTriageConfigRow(getDb(), orgId, factoryId)
|
|
152
|
+
)?.repository;
|
|
142
153
|
if (!configuredRepository || configuredRepository.trim() !== repo.trim()) {
|
|
143
154
|
throw new Error(
|
|
144
155
|
"PR governance is restricted to the configured Factory repository.",
|
|
@@ -152,7 +163,7 @@ export default defineAction({
|
|
|
152
163
|
pullRequestNumber: triageItems.pullRequestNumber,
|
|
153
164
|
})
|
|
154
165
|
.from(triageItems)
|
|
155
|
-
.where(
|
|
166
|
+
.where(orgFactoryScopedItemWhere(itemId, orgId, factoryId))
|
|
156
167
|
.limit(1)
|
|
157
168
|
)[0];
|
|
158
169
|
if (
|
|
@@ -178,7 +189,7 @@ export default defineAction({
|
|
|
178
189
|
status: "pr_observed",
|
|
179
190
|
updatedAt: new Date().toISOString(),
|
|
180
191
|
})
|
|
181
|
-
.where(
|
|
192
|
+
.where(orgFactoryScopedItemWhere(itemId, orgId, factoryId));
|
|
182
193
|
}
|
|
183
194
|
return {
|
|
184
195
|
ok: true,
|
|
@@ -222,6 +233,7 @@ export default defineAction({
|
|
|
222
233
|
"Skipped the pull request because it already has a current non-dismissed approval.",
|
|
223
234
|
details: { repo, pullRequestNumber },
|
|
224
235
|
},
|
|
236
|
+
factoryId,
|
|
225
237
|
);
|
|
226
238
|
if (itemId) {
|
|
227
239
|
await getDb()
|
|
@@ -230,7 +242,7 @@ export default defineAction({
|
|
|
230
242
|
status: "pr_observed",
|
|
231
243
|
updatedAt: new Date().toISOString(),
|
|
232
244
|
})
|
|
233
|
-
.where(
|
|
245
|
+
.where(orgFactoryScopedItemWhere(itemId, orgId, factoryId));
|
|
234
246
|
}
|
|
235
247
|
return {
|
|
236
248
|
ok: true,
|
|
@@ -267,6 +279,7 @@ export default defineAction({
|
|
|
267
279
|
const factoryTriggered = await hasVerifiedFactoryRun({
|
|
268
280
|
itemId,
|
|
269
281
|
orgId,
|
|
282
|
+
factoryId,
|
|
270
283
|
pullRequestBody: pullRequest.body,
|
|
271
284
|
headRef: pullRequest.headRef,
|
|
272
285
|
});
|
|
@@ -312,6 +325,7 @@ export default defineAction({
|
|
|
312
325
|
guardResults: governance.guardResults,
|
|
313
326
|
},
|
|
314
327
|
},
|
|
328
|
+
factoryId,
|
|
315
329
|
);
|
|
316
330
|
|
|
317
331
|
if (itemId) {
|
|
@@ -319,7 +333,7 @@ export default defineAction({
|
|
|
319
333
|
await getDb()
|
|
320
334
|
.select()
|
|
321
335
|
.from(triageItems)
|
|
322
|
-
.where(
|
|
336
|
+
.where(orgFactoryScopedItemWhere(itemId, orgId, factoryId))
|
|
323
337
|
.limit(1)
|
|
324
338
|
)[0];
|
|
325
339
|
if (!item) throw new Error("Factory item not found for PR governance.");
|
|
@@ -348,6 +362,7 @@ export default defineAction({
|
|
|
348
362
|
createdAt: new Date().toISOString(),
|
|
349
363
|
ownerEmail: userEmail,
|
|
350
364
|
orgId,
|
|
365
|
+
factoryId,
|
|
351
366
|
})
|
|
352
367
|
.onConflictDoNothing();
|
|
353
368
|
}
|
|
@@ -360,7 +375,7 @@ export default defineAction({
|
|
|
360
375
|
status: governance.ownerOwnedArea ? "needs_manual" : "pr_observed",
|
|
361
376
|
updatedAt: new Date().toISOString(),
|
|
362
377
|
})
|
|
363
|
-
.where(
|
|
378
|
+
.where(orgFactoryScopedItemWhere(itemId, orgId, factoryId));
|
|
364
379
|
}
|
|
365
380
|
return {
|
|
366
381
|
ok: true,
|
|
@@ -381,7 +396,7 @@ export default defineAction({
|
|
|
381
396
|
status: triageItems.status,
|
|
382
397
|
})
|
|
383
398
|
.from(triageItems)
|
|
384
|
-
.where(
|
|
399
|
+
.where(orgFactoryScopedItemWhere(itemId, orgId, factoryId))
|
|
385
400
|
.limit(1)
|
|
386
401
|
)[0];
|
|
387
402
|
if (!item) throw new Error("Factory item disappeared after PR approval.");
|
|
@@ -410,7 +425,7 @@ export default defineAction({
|
|
|
410
425
|
status: "auto_approved",
|
|
411
426
|
updatedAt: new Date().toISOString(),
|
|
412
427
|
})
|
|
413
|
-
.where(
|
|
428
|
+
.where(orgFactoryScopedItemWhere(itemId, orgId, factoryId));
|
|
414
429
|
}
|
|
415
430
|
} else {
|
|
416
431
|
approvalUrl = (
|
|
@@ -437,6 +452,7 @@ export default defineAction({
|
|
|
437
452
|
"Approved the pull request under the current review-prs policy.",
|
|
438
453
|
details: { repo, pullRequestNumber, approvalUrl },
|
|
439
454
|
},
|
|
455
|
+
factoryId,
|
|
440
456
|
);
|
|
441
457
|
return {
|
|
442
458
|
ok: true,
|