@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.
Files changed (91) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/actions/mutate-dashboard.ts +8 -1
  3. package/corpus/templates/calendar/actions/delete-event.ts +34 -18
  4. package/corpus/templates/calendar/actions/delete-events.ts +13 -1
  5. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +22 -3
  6. package/corpus/templates/calendar/app/lib/calendar-timezone.ts +19 -0
  7. package/corpus/templates/calendar/server/lib/google-api.ts +18 -1
  8. package/corpus/templates/clips/actions/finalize-meeting.ts +16 -0
  9. package/corpus/templates/clips/actions/list-meetings.ts +1 -0
  10. package/corpus/templates/clips/actions/sync-calendars.ts +10 -29
  11. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +28 -12
  12. package/corpus/templates/clips/desktop/src/lib/meeting-stop.ts +16 -0
  13. package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +19 -9
  14. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +1 -0
  15. package/corpus/templates/content/app/global.css +5 -0
  16. package/corpus/templates/content/docs/solutions/2026-08-20-code-block-slash-production-regression-shape.md +492 -0
  17. package/corpus/templates/content/docs/solutions/2026-08-20-code-block-surface-background-shape.md +273 -0
  18. package/corpus/templates/factory/.agents/skills/factory-graphs/SKILL.md +5 -0
  19. package/corpus/templates/factory/AGENTS.md +12 -22
  20. package/corpus/templates/factory/README.md +11 -8
  21. package/corpus/templates/factory/actions/approve-factory-item.ts +35 -8
  22. package/corpus/templates/factory/actions/babysit-agent-native-pull-request.ts +24 -9
  23. package/corpus/templates/factory/actions/create-factory.ts +211 -0
  24. package/corpus/templates/factory/actions/evaluate-triage-item.ts +29 -5
  25. package/corpus/templates/factory/actions/get-factory-graph.ts +1 -1
  26. package/corpus/templates/factory/actions/get-slack-feedback-context.ts +20 -11
  27. package/corpus/templates/factory/actions/get-triage-config.ts +13 -12
  28. package/corpus/templates/factory/actions/get-triage-item.ts +28 -6
  29. package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +33 -17
  30. package/corpus/templates/factory/actions/ingest-github-observation.ts +10 -4
  31. package/corpus/templates/factory/actions/list-factory-audit.ts +11 -4
  32. package/corpus/templates/factory/actions/list-factory-automations.ts +34 -12
  33. package/corpus/templates/factory/actions/list-triage-items.ts +11 -3
  34. package/corpus/templates/factory/actions/list-triage-rules.ts +11 -4
  35. package/corpus/templates/factory/actions/poll-github-sources.ts +24 -10
  36. package/corpus/templates/factory/actions/poll-sentry-errors.ts +26 -11
  37. package/corpus/templates/factory/actions/poll-slack-channel.ts +31 -13
  38. package/corpus/templates/factory/actions/reconcile-triage-run.ts +59 -9
  39. package/corpus/templates/factory/actions/record-triage-feedback.ts +14 -3
  40. package/corpus/templates/factory/actions/run-factory-automation.ts +12 -2
  41. package/corpus/templates/factory/actions/save-factory-automation.ts +82 -21
  42. package/corpus/templates/factory/actions/save-factory-graph.ts +10 -1
  43. package/corpus/templates/factory/actions/save-triage-config.ts +90 -36
  44. package/corpus/templates/factory/actions/save-triage-rule.ts +16 -3
  45. package/corpus/templates/factory/actions/start-builder-for-item.ts +28 -20
  46. package/corpus/templates/factory/actions/suggest-factory-rules.ts +11 -4
  47. package/corpus/templates/factory/actions/view-screen.ts +22 -30
  48. package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +7 -2
  49. package/corpus/templates/factory/app/components/factory/FactoryWorkspaceActions.tsx +7 -22
  50. package/corpus/templates/factory/app/components/factory/NewFactoryForm.tsx +260 -0
  51. package/corpus/templates/factory/app/components/layout/Layout.tsx +1 -1
  52. package/corpus/templates/factory/app/components/layout/Sidebar.tsx +2 -1
  53. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +3 -3
  54. package/corpus/templates/factory/app/i18n/en-US.ts +18 -1
  55. package/corpus/templates/factory/app/routes/factory.tsx +78 -62
  56. package/corpus/templates/factory/app/routes/{factory-settings.tsx → new-factory.tsx} +12 -9
  57. package/corpus/templates/factory/server/db/schema.ts +18 -3
  58. package/corpus/templates/factory/server/factory-graph/contracts.ts +24 -0
  59. package/corpus/templates/factory/server/factory-graph/store.ts +16 -9
  60. package/corpus/templates/factory/server/lib/factory-automation-plan.ts +136 -0
  61. package/corpus/templates/factory/server/lib/factory-automation-repair.ts +31 -0
  62. package/corpus/templates/factory/server/lib/factory-scope.ts +416 -0
  63. package/corpus/templates/factory/server/lib/require-factory-automation.ts +11 -0
  64. package/corpus/templates/factory/server/plugins/agent-chat.ts +4 -2
  65. package/corpus/templates/factory/server/plugins/factory-migrations.ts +80 -0
  66. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +142 -43
  67. package/corpus/templates/factory/server/triage/audit.ts +4 -0
  68. package/corpus/templates/factory/server/triage/ids.ts +4 -0
  69. package/dist/cli/native-dependencies.js +4 -1
  70. package/dist/client/AgentPanel.d.ts +5 -1
  71. package/dist/client/AgentPanel.js +9 -4
  72. package/dist/client/agent-chat/index.d.ts +1 -1
  73. package/dist/client/agent-chat/index.js +1 -1
  74. package/dist/client/chat-first/apps-rail.js +6 -6
  75. package/dist/client/chat-first/primary-nav.js +14 -6
  76. package/dist/client/integrations/IntegrationsPanel.d.ts +15 -0
  77. package/dist/client/integrations/IntegrationsPanel.js +29 -21
  78. package/dist/client/integrations/index.d.ts +1 -1
  79. package/dist/client/integrations/index.js +1 -1
  80. package/dist/client/resources/McpIntegrationDialog.js +1 -1
  81. package/dist/collab/struct-routes.d.ts +1 -1
  82. package/dist/deploy/build.js +1 -1
  83. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  84. package/dist/observability/routes.d.ts +3 -3
  85. package/dist/progress/routes.d.ts +1 -1
  86. package/dist/secrets/routes.d.ts +6 -6
  87. package/dist/server/realtime-token.d.ts +1 -1
  88. package/dist/server/transcribe-voice.d.ts +1 -1
  89. package/dist/shared/mcp-embed-headers.d.ts +1 -1
  90. package/dist/shared/mcp-embed-headers.js +10 -3
  91. 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({ itemId: z.string().min(1) }),
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(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)))
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(and(eq(triageRules.orgId, orgId), eq(triageRules.enabled, 1)));
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(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)));
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 { triageConfig, triageItems } from "../server/db/schema.js";
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({ itemId: z.string().min(1) }),
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(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)))
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 { triageConfig } from "../server/db/schema.js";
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 active workspace. Secret values are never returned.",
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 (_, context) => {
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({ itemId: z.string().min(1) }),
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(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)))
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
- eq(triageDecisions.orgId, orgId),
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(eq(triageFeedback.orgId, orgId))
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(and(eq(triageRuns.itemId, itemId), eq(triageRuns.orgId, orgId)))
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 = [eq(triageRuns.orgId, input.orgId)];
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(eq(triageItems.orgId, input.orgId))
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(context, { userEmail, orgId }, "governance");
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
- .select({ repository: triageConfig.repository })
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(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)))
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(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)));
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(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)));
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(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)))
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(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)));
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(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)))
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(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)));
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,