@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.
Files changed (84) 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/factory/.agents/skills/factory-graphs/SKILL.md +5 -0
  15. package/corpus/templates/factory/AGENTS.md +12 -22
  16. package/corpus/templates/factory/README.md +11 -8
  17. package/corpus/templates/factory/actions/approve-factory-item.ts +35 -8
  18. package/corpus/templates/factory/actions/babysit-agent-native-pull-request.ts +24 -9
  19. package/corpus/templates/factory/actions/create-factory.ts +211 -0
  20. package/corpus/templates/factory/actions/evaluate-triage-item.ts +29 -5
  21. package/corpus/templates/factory/actions/get-factory-graph.ts +1 -1
  22. package/corpus/templates/factory/actions/get-slack-feedback-context.ts +20 -11
  23. package/corpus/templates/factory/actions/get-triage-config.ts +13 -12
  24. package/corpus/templates/factory/actions/get-triage-item.ts +28 -6
  25. package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +33 -17
  26. package/corpus/templates/factory/actions/ingest-github-observation.ts +10 -4
  27. package/corpus/templates/factory/actions/list-factory-audit.ts +11 -4
  28. package/corpus/templates/factory/actions/list-factory-automations.ts +34 -12
  29. package/corpus/templates/factory/actions/list-triage-items.ts +11 -3
  30. package/corpus/templates/factory/actions/list-triage-rules.ts +11 -4
  31. package/corpus/templates/factory/actions/poll-github-sources.ts +24 -10
  32. package/corpus/templates/factory/actions/poll-sentry-errors.ts +26 -11
  33. package/corpus/templates/factory/actions/poll-slack-channel.ts +31 -13
  34. package/corpus/templates/factory/actions/reconcile-triage-run.ts +59 -9
  35. package/corpus/templates/factory/actions/record-triage-feedback.ts +14 -3
  36. package/corpus/templates/factory/actions/run-factory-automation.ts +12 -2
  37. package/corpus/templates/factory/actions/save-factory-automation.ts +82 -21
  38. package/corpus/templates/factory/actions/save-factory-graph.ts +10 -1
  39. package/corpus/templates/factory/actions/save-triage-config.ts +90 -36
  40. package/corpus/templates/factory/actions/save-triage-rule.ts +16 -3
  41. package/corpus/templates/factory/actions/start-builder-for-item.ts +28 -20
  42. package/corpus/templates/factory/actions/suggest-factory-rules.ts +11 -4
  43. package/corpus/templates/factory/actions/view-screen.ts +22 -30
  44. package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +7 -2
  45. package/corpus/templates/factory/app/components/factory/FactoryWorkspaceActions.tsx +7 -22
  46. package/corpus/templates/factory/app/components/factory/NewFactoryForm.tsx +260 -0
  47. package/corpus/templates/factory/app/components/layout/Layout.tsx +1 -1
  48. package/corpus/templates/factory/app/components/layout/Sidebar.tsx +2 -1
  49. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +3 -3
  50. package/corpus/templates/factory/app/i18n/en-US.ts +18 -1
  51. package/corpus/templates/factory/app/routes/factory.tsx +78 -62
  52. package/corpus/templates/factory/app/routes/{factory-settings.tsx → new-factory.tsx} +12 -9
  53. package/corpus/templates/factory/server/db/schema.ts +18 -3
  54. package/corpus/templates/factory/server/factory-graph/contracts.ts +24 -0
  55. package/corpus/templates/factory/server/factory-graph/store.ts +16 -9
  56. package/corpus/templates/factory/server/lib/factory-automation-plan.ts +136 -0
  57. package/corpus/templates/factory/server/lib/factory-automation-repair.ts +31 -0
  58. package/corpus/templates/factory/server/lib/factory-scope.ts +416 -0
  59. package/corpus/templates/factory/server/lib/require-factory-automation.ts +11 -0
  60. package/corpus/templates/factory/server/plugins/agent-chat.ts +4 -2
  61. package/corpus/templates/factory/server/plugins/factory-migrations.ts +80 -0
  62. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +142 -43
  63. package/corpus/templates/factory/server/triage/audit.ts +4 -0
  64. package/corpus/templates/factory/server/triage/ids.ts +4 -0
  65. package/dist/cli/native-dependencies.js +4 -1
  66. package/dist/client/AgentPanel.d.ts +5 -1
  67. package/dist/client/AgentPanel.js +9 -4
  68. package/dist/client/agent-chat/index.d.ts +1 -1
  69. package/dist/client/agent-chat/index.js +1 -1
  70. package/dist/client/chat-first/apps-rail.js +6 -6
  71. package/dist/client/chat-first/primary-nav.js +14 -6
  72. package/dist/client/integrations/IntegrationsPanel.d.ts +15 -0
  73. package/dist/client/integrations/IntegrationsPanel.js +29 -21
  74. package/dist/client/integrations/index.d.ts +1 -1
  75. package/dist/client/integrations/index.js +1 -1
  76. package/dist/client/resources/McpIntegrationDialog.js +1 -1
  77. package/dist/collab/struct-routes.d.ts +1 -1
  78. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  79. package/dist/observability/routes.d.ts +3 -3
  80. package/dist/progress/routes.d.ts +1 -1
  81. package/dist/secrets/routes.d.ts +6 -6
  82. package/dist/server/realtime-token.d.ts +1 -1
  83. package/dist/server/transcribe-voice.d.ts +1 -1
  84. package/package.json +1 -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,
@@ -3,6 +3,8 @@ import { z } from "zod";
3
3
 
4
4
  import { getDb } from "../server/db/index.js";
5
5
  import { triageItems } from "../server/db/schema.js";
6
+ import { DEFAULT_FACTORY_ID } from "../server/factory-graph/store.js";
7
+ import { factoryIdSchema } from "../server/lib/factory-scope.js";
6
8
  import {
7
9
  requireWorkspaceMember,
8
10
  workspaceMemberIdentityFromContext,
@@ -31,6 +33,7 @@ export default defineAction({
31
33
  description:
32
34
  "Ingest one read-only GitHub pull-request observation from the existing ai-services boundary into Factory. This action records evidence only and never writes to GitHub.",
33
35
  schema: z.object({
36
+ factoryId: factoryIdSchema.default(DEFAULT_FACTORY_ID),
34
37
  repo: z.string().trim().min(1).max(256),
35
38
  pullRequestNumber: z.number().int().positive(),
36
39
  headSha: z.string().trim().min(1).max(128),
@@ -50,8 +53,9 @@ export default defineAction({
50
53
  const { userEmail, orgId } = await requireWorkspaceMember(
51
54
  workspaceMemberIdentityFromContext(context),
52
55
  );
53
- const envelope = pullRequestSnapshotToEnvelope(input);
54
- const id = itemDedupeKey(envelope, orgId);
56
+ const { factoryId, ...observation } = input;
57
+ const envelope = pullRequestSnapshotToEnvelope(observation);
58
+ const id = itemDedupeKey(envelope, orgId, factoryId);
55
59
  const now = new Date().toISOString();
56
60
  const db = getDb();
57
61
 
@@ -77,6 +81,7 @@ export default defineAction({
77
81
  updatedAt: now,
78
82
  ownerEmail: userEmail,
79
83
  orgId,
84
+ factoryId,
80
85
  })
81
86
  .onConflictDoUpdate({
82
87
  target: triageItems.id,
@@ -105,10 +110,11 @@ export default defineAction({
105
110
  repository: envelope.repository,
106
111
  pullRequestNumber: envelope.pullRequestNumber,
107
112
  coverage: envelope.coverage,
108
- reviewCount: input.reviews.length,
109
- checkCount: input.checks.length,
113
+ reviewCount: observation.reviews.length,
114
+ checkCount: observation.checks.length,
110
115
  },
111
116
  },
117
+ factoryId,
112
118
  );
113
119
 
114
120
  return {
@@ -8,6 +8,10 @@ import { z } from "zod";
8
8
 
9
9
  import { getDb } from "../server/db/index.js";
10
10
  import { factoryAuditEvents } from "../server/db/schema.js";
11
+ import {
12
+ factoryIdSchema,
13
+ readAutomationFactoryId,
14
+ } from "../server/lib/factory-scope.js";
11
15
  import {
12
16
  requireWorkspaceMember,
13
17
  workspaceMemberIdentityFromContext,
@@ -18,13 +22,13 @@ export default defineAction({
18
22
  "List recent Factory automation runs with the bounded source observations, decisions, and external actions recorded for each run.",
19
23
  agentTool: false,
20
24
  schema: z.object({
21
- factoryId: z.string().trim().min(1).optional(),
25
+ factoryId: factoryIdSchema,
22
26
  automation: z.string().trim().min(1).optional(),
23
27
  limit: z.coerce.number().int().min(1).max(50).default(20),
24
28
  }),
25
29
  http: { method: "GET" },
26
30
  readOnly: true,
27
- run: async ({ automation, limit }, context) => {
31
+ run: async ({ factoryId, automation, limit }, context) => {
28
32
  const { userEmail, orgId } = await requireWorkspaceMember(
29
33
  workspaceMemberIdentityFromContext(context),
30
34
  );
@@ -33,8 +37,10 @@ export default defineAction({
33
37
  "organization",
34
38
  );
35
39
  const factoryDefinitions = definitions.filter(
36
- ({ meta, name }) =>
37
- meta.domain === "factory" && (!automation || name === automation),
40
+ ({ meta, name, resource }) =>
41
+ meta.domain === "factory" &&
42
+ readAutomationFactoryId(meta, resource.content) === factoryId &&
43
+ (!automation || name === automation),
38
44
  );
39
45
  const runGroups = await Promise.all(
40
46
  factoryDefinitions.map(async ({ name, resource }) =>
@@ -60,6 +66,7 @@ export default defineAction({
60
66
  .where(
61
67
  and(
62
68
  eq(factoryAuditEvents.orgId, orgId),
69
+ eq(factoryAuditEvents.factoryId, factoryId),
63
70
  inArray(factoryAuditEvents.automationRunId, runIds),
64
71
  ),
65
72
  )
@@ -5,32 +5,58 @@ import {
5
5
  } from "@agent-native/core/triggers";
6
6
  import { z } from "zod";
7
7
 
8
+ import { readFactoryDefinition } from "../server/factory-graph/store.js";
9
+ import {
10
+ DEFAULT_FACTORY_ID,
11
+ factoryIdSchema,
12
+ readAutomationFactoryId,
13
+ resolveAutomationDisplayName,
14
+ } from "../server/lib/factory-scope.js";
8
15
  import {
9
16
  requireWorkspaceMember,
10
17
  workspaceMemberIdentityFromContext,
11
18
  } from "../server/lib/require-workspace-member.js";
12
19
 
20
+ function automationRunHistoryKey(path: string): string {
21
+ return path.replace(/^jobs\//, "").replace(/\.md$/, "");
22
+ }
23
+
13
24
  export default defineAction({
14
25
  description:
15
26
  "List the organization-scoped Factory automations with their trigger, editable prompt, model, schedule, enabled state, and recent runs.",
16
27
  agentTool: false,
17
- schema: z.object({ factoryId: z.string().trim().min(1).optional() }),
28
+ schema: z.object({ factoryId: factoryIdSchema }),
18
29
  http: { method: "GET" },
19
30
  readOnly: true,
20
- run: async (_, context) => {
31
+ run: async ({ factoryId }, context) => {
21
32
  const { userEmail, orgId } = await requireWorkspaceMember(
22
33
  workspaceMemberIdentityFromContext(context),
23
34
  );
35
+ const factory = await readFactoryDefinition(orgId, factoryId);
36
+ if (!factory && factoryId !== DEFAULT_FACTORY_ID) {
37
+ throw new Error("Factory not found.");
38
+ }
24
39
  const definitions = await listAutomationDefinitions(
25
40
  { userEmail, orgId, appId: "factory" },
26
41
  "organization",
27
42
  );
43
+ const scoped = definitions.filter(
44
+ ({ meta, resource }) =>
45
+ meta.domain === "factory" &&
46
+ readAutomationFactoryId(meta, resource.content) === factoryId,
47
+ );
28
48
  return Promise.all(
29
- definitions
30
- .filter(({ meta }) => meta.domain === "factory")
31
- .map(async ({ resource, name, meta, body, canUpdate }) => ({
49
+ scoped.map(async ({ resource, name, meta, body, canUpdate }) => {
50
+ const runs = await listAutomationRuns({
51
+ owners: [resource.owner],
52
+ automation: automationRunHistoryKey(resource.path),
53
+ appId: "factory",
54
+ limit: 20,
55
+ });
56
+ return {
32
57
  id: resource.id,
33
58
  name,
59
+ displayName: resolveAutomationDisplayName(name, resource.content),
34
60
  prompt: body,
35
61
  body,
36
62
  model: meta.model ?? null,
@@ -46,13 +72,9 @@ export default defineAction({
46
72
  ? new Date(resource.updatedAt).toISOString()
47
73
  : null,
48
74
  canUpdate,
49
- runs: await listAutomationRuns({
50
- owners: [resource.owner],
51
- automation: name,
52
- appId: "factory",
53
- limit: 20,
54
- }),
55
- })),
75
+ runs: runs.filter((run) => run.path === resource.path),
76
+ };
77
+ }),
56
78
  );
57
79
  },
58
80
  });
@@ -4,6 +4,12 @@ import { z } from "zod";
4
4
 
5
5
  import { getDb } from "../server/db/index.js";
6
6
  import { triageItems, triageDecisions } from "../server/db/schema.js";
7
+ import { DEFAULT_FACTORY_ID } from "../server/factory-graph/store.js";
8
+ import {
9
+ factoryIdSchema,
10
+ orgFactoryDecisionFilter,
11
+ orgFactoryItemFilter,
12
+ } from "../server/lib/factory-scope.js";
7
13
  import {
8
14
  requireWorkspaceMember,
9
15
  workspaceMemberIdentityFromContext,
@@ -18,6 +24,7 @@ export default defineAction({
18
24
  description:
19
25
  "List the Factory observation queue. Results are scoped to the active workspace and include the latest shadow decision summary. Scheduled reviewers must pass needsReview true with a bounded source and limit so unchanged items are not re-reviewed.",
20
26
  schema: z.object({
27
+ factoryId: factoryIdSchema.default(DEFAULT_FACTORY_ID),
21
28
  status: triageItemStatusSchema.optional(),
22
29
  source: triageSourceSchema.optional(),
23
30
  needsReview: z.boolean().default(false),
@@ -25,7 +32,7 @@ export default defineAction({
25
32
  }),
26
33
  http: { method: "GET" },
27
34
  readOnly: true,
28
- run: async ({ status, source, needsReview, limit }, context) => {
35
+ run: async ({ factoryId, status, source, needsReview, limit }, context) => {
29
36
  const { userEmail, orgId } = await requireWorkspaceMember(
30
37
  workspaceMemberIdentityFromContext(context),
31
38
  );
@@ -41,7 +48,7 @@ export default defineAction({
41
48
  .from(triageItems)
42
49
  .where(
43
50
  and(
44
- eq(triageItems.orgId, orgId),
51
+ orgFactoryItemFilter(orgId, factoryId),
45
52
  needsReview
46
53
  ? inArray(triageItems.status, reviewStatuses)
47
54
  : status
@@ -56,7 +63,7 @@ export default defineAction({
56
63
  const decisions = await db
57
64
  .select()
58
65
  .from(triageDecisions)
59
- .where(eq(triageDecisions.orgId, orgId))
66
+ .where(orgFactoryDecisionFilter(orgId, factoryId))
60
67
  .orderBy(desc(triageDecisions.createdAt))
61
68
  .limit(500);
62
69
  const latestByItem = new Map<string, (typeof decisions)[number]>();
@@ -116,6 +123,7 @@ export default defineAction({
116
123
  decision: item.latestDecision?.outcome ?? null,
117
124
  },
118
125
  },
126
+ factoryId,
119
127
  );
120
128
  }
121
129
  return listedItems;
@@ -1,9 +1,14 @@
1
1
  import { defineAction } from "@agent-native/core/action";
2
- import { desc, eq } from "drizzle-orm";
2
+ import { desc } from "drizzle-orm";
3
3
  import { z } from "zod";
4
4
 
5
5
  import { getDb } from "../server/db/index.js";
6
6
  import { triageRules } from "../server/db/schema.js";
7
+ import { DEFAULT_FACTORY_ID } from "../server/factory-graph/store.js";
8
+ import {
9
+ factoryIdSchema,
10
+ orgFactoryRuleFilter,
11
+ } from "../server/lib/factory-scope.js";
7
12
  import {
8
13
  requireWorkspaceMember,
9
14
  workspaceMemberIdentityFromContext,
@@ -13,17 +18,19 @@ import { normalizeTriagePolicyGuards } from "../server/triage/contracts.js";
13
18
  export default defineAction({
14
19
  description:
15
20
  "List editable Factory rules for the active workspace. Rules are disabled or shadow-only until the owner explicitly promotes them.",
16
- schema: z.object({}),
21
+ schema: z.object({
22
+ factoryId: factoryIdSchema.default(DEFAULT_FACTORY_ID),
23
+ }),
17
24
  http: { method: "GET" },
18
25
  readOnly: true,
19
- run: async (_, context) => {
26
+ run: async ({ factoryId }, context) => {
20
27
  const { orgId } = await requireWorkspaceMember(
21
28
  workspaceMemberIdentityFromContext(context),
22
29
  );
23
30
  const rows = await getDb()
24
31
  .select()
25
32
  .from(triageRules)
26
- .where(eq(triageRules.orgId, orgId))
33
+ .where(orgFactoryRuleFilter(orgId, factoryId))
27
34
  .orderBy(desc(triageRules.updatedAt));
28
35
  return rows.map((row) => ({
29
36
  ...row,
@@ -3,7 +3,12 @@ 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 { repairFactoryAutomationsFromConfig } from "../server/lib/factory-automation-repair.js";
8
+ import {
9
+ factoryIdSchema,
10
+ readTriageConfigRow,
11
+ } from "../server/lib/factory-scope.js";
7
12
  import { requireFactoryAutomation } from "../server/lib/require-factory-automation.js";
8
13
  import {
9
14
  requireWorkspaceMember,
@@ -30,11 +35,12 @@ export default defineAction({
30
35
  description:
31
36
  "Poll the configured GitHub repository for bounded open issues and pull requests and record them in the Factory queue. This does not write to GitHub.",
32
37
  schema: z.object({
38
+ factoryId: factoryIdSchema,
33
39
  includeIssues: z.boolean().default(true),
34
40
  includePullRequests: z.boolean().default(true),
35
41
  }),
36
42
  http: false,
37
- run: async ({ includeIssues, includePullRequests }, context) => {
43
+ run: async ({ factoryId, includeIssues, includePullRequests }, context) => {
38
44
  const { userEmail, orgId } = await requireWorkspaceMember(
39
45
  workspaceMemberIdentityFromContext(context),
40
46
  );
@@ -42,14 +48,11 @@ export default defineAction({
42
48
  context,
43
49
  { userEmail, orgId },
44
50
  "sourcePolling",
51
+ factoryId,
45
52
  );
46
- const config = (
47
- await getDb()
48
- .select()
49
- .from(triageConfig)
50
- .where(and(eq(triageConfig.id, orgId), eq(triageConfig.orgId, orgId)))
51
- .limit(1)
52
- )[0];
53
+ const db = getDb();
54
+ const config = await readTriageConfigRow(db, orgId, factoryId);
55
+ await repairFactoryAutomationsFromConfig(userEmail, orgId, factoryId);
53
56
  if (config?.githubPollingEnabled !== 1) {
54
57
  throw new Error("Enable GitHub polling before polling GitHub sources.");
55
58
  }
@@ -68,7 +71,6 @@ export default defineAction({
68
71
  ? client.listOpenPullRequests(repository, 50)
69
72
  : Promise.resolve([]),
70
73
  ]);
71
- const db = getDb();
72
74
  const now = new Date().toISOString();
73
75
  let issueCount = 0;
74
76
  let pullRequestCount = 0;
@@ -81,6 +83,7 @@ export default defineAction({
81
83
  externalId: `${repositoryName}#${issue.number}`,
82
84
  },
83
85
  orgId,
86
+ factoryId,
84
87
  );
85
88
  const existing = (
86
89
  await tx
@@ -133,6 +136,7 @@ export default defineAction({
133
136
  updatedAt,
134
137
  ownerEmail: existing?.ownerEmail ?? userEmail,
135
138
  orgId,
139
+ factoryId,
136
140
  })
137
141
  .onConflictDoUpdate({
138
142
  target: triageItems.id,
@@ -144,6 +148,7 @@ export default defineAction({
144
148
  metadataJson: metadata,
145
149
  lastSeenAt,
146
150
  updatedAt,
151
+ factoryId,
147
152
  },
148
153
  });
149
154
  issueCount += 1;
@@ -158,6 +163,7 @@ export default defineAction({
158
163
  pullRequestNumber: pullRequest.number,
159
164
  },
160
165
  orgId,
166
+ factoryId,
161
167
  );
162
168
  const existing = (
163
169
  await tx
@@ -213,6 +219,7 @@ export default defineAction({
213
219
  updatedAt,
214
220
  ownerEmail: existing?.ownerEmail ?? userEmail,
215
221
  orgId,
222
+ factoryId,
216
223
  })
217
224
  .onConflictDoUpdate({
218
225
  target: triageItems.id,
@@ -227,6 +234,7 @@ export default defineAction({
227
234
  metadataJson: metadata,
228
235
  lastSeenAt,
229
236
  updatedAt,
237
+ factoryId,
230
238
  },
231
239
  });
232
240
  pullRequestCount += 1;
@@ -244,6 +252,7 @@ export default defineAction({
244
252
  summary: "No open GitHub issues or pull requests were observed.",
245
253
  details: { repository: repositoryName },
246
254
  },
255
+ factoryId,
247
256
  );
248
257
  } else {
249
258
  for (const issue of issues) {
@@ -259,12 +268,14 @@ export default defineAction({
259
268
  externalId: `${repositoryName}#${issue.number}`,
260
269
  },
261
270
  orgId,
271
+ factoryId,
262
272
  ),
263
273
  source: "github_issue",
264
274
  sourceUrl: issue.htmlUrl,
265
275
  summary: issue.title,
266
276
  details: { repository: repositoryName, number: issue.number },
267
277
  },
278
+ factoryId,
268
279
  );
269
280
  }
270
281
  for (const pullRequest of pullRequests) {
@@ -282,18 +293,21 @@ export default defineAction({
282
293
  pullRequestNumber: pullRequest.number,
283
294
  },
284
295
  orgId,
296
+ factoryId,
285
297
  ),
286
298
  source: "github",
287
299
  sourceUrl: pullRequest.htmlUrl,
288
300
  summary: pullRequest.title,
289
301
  details: { repository: repositoryName, number: pullRequest.number },
290
302
  },
303
+ factoryId,
291
304
  );
292
305
  }
293
306
  }
294
307
 
295
308
  return {
296
309
  ok: true,
310
+ factoryId,
297
311
  repository: repositoryName,
298
312
  issues: issueCount,
299
313
  pullRequests: pullRequestCount,