@agent-native/core 0.173.2-nightly-20260825163448 → 0.174.0

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 (73) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/factory/AGENTS.md +1 -1
  3. package/corpus/templates/factory/actions/babysit-agent-native-pull-request.ts +23 -8
  4. package/corpus/templates/factory/actions/delete-factory.ts +174 -0
  5. package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +28 -20
  6. package/corpus/templates/factory/actions/poll-github-sources.ts +6 -0
  7. package/corpus/templates/factory/actions/poll-sentry-errors.ts +16 -1
  8. package/corpus/templates/factory/actions/poll-slack-channel.ts +12 -0
  9. package/corpus/templates/factory/actions/start-builder-for-item.ts +74 -42
  10. package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +117 -1
  11. package/corpus/templates/factory/app/i18n/en-US.ts +12 -0
  12. package/corpus/templates/factory/app/routes/factory.tsx +6 -1
  13. package/corpus/templates/factory/changelog/2026-08-24-slack-polling-accepts-the-agentnative-bot-handle.md +6 -0
  14. package/corpus/templates/factory/changelog/2026-08-24-you-can-delete-a-user-created-factory-from-settings.md +6 -0
  15. package/corpus/templates/factory/server/lib/factory-automation-repair.ts +6 -0
  16. package/corpus/templates/factory/server/lib/factory-scope.ts +45 -1
  17. package/corpus/templates/factory/server/lib/require-factory-automation.ts +5 -0
  18. package/corpus/templates/factory/server/triage/slack-client.ts +10 -2
  19. package/corpus/templates/plan/server/plugins/core-routes.ts +6 -1
  20. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +4 -1
  21. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +4 -2
  22. package/dist/app-config/app-identity.d.ts +20 -0
  23. package/dist/app-config/app-identity.js +41 -0
  24. package/dist/app-config/app.d.ts +2 -0
  25. package/dist/app-config/app.js +14 -0
  26. package/dist/app-config/observability.d.ts +27 -0
  27. package/dist/app-config/observability.js +76 -0
  28. package/dist/app-config/schema.d.ts +13 -0
  29. package/dist/app-config/schema.js +2 -0
  30. package/dist/app-config/store.d.ts +14 -4
  31. package/dist/app-config/store.js +18 -3
  32. package/dist/collab/awareness.d.ts +2 -2
  33. package/dist/collab/routes.d.ts +1 -1
  34. package/dist/email-catalog/actions/list-email-log.js +2 -2
  35. package/dist/email-catalog/actions/list-transactional-emails.js +2 -2
  36. package/dist/email-catalog/registry.js +3 -3
  37. package/dist/mcp/connect-route.js +15 -2
  38. package/dist/notifications/routes.d.ts +3 -3
  39. package/dist/observability/index.d.ts +1 -2
  40. package/dist/observability/index.js +0 -1
  41. package/dist/observability/posthog-ai.js +11 -3
  42. package/dist/observability/sentiment.d.ts +0 -1
  43. package/dist/observability/sentiment.js +8 -5
  44. package/dist/observability/traces.js +6 -12
  45. package/dist/observability/types.d.ts +0 -7
  46. package/dist/observability/types.js +1 -12
  47. package/dist/progress/routes.d.ts +1 -1
  48. package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
  49. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  50. package/dist/resources/handlers.d.ts +1 -1
  51. package/dist/secrets/routes.d.ts +3 -3
  52. package/dist/server/agent-chat/plugin-options.d.ts +17 -1
  53. package/dist/server/agent-chat-plugin.d.ts +10 -0
  54. package/dist/server/agent-chat-plugin.js +75 -67
  55. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  56. package/dist/server/core-routes/mcp-connect-options.d.ts +70 -0
  57. package/dist/server/core-routes/mcp-connect-options.js +77 -0
  58. package/dist/server/core-routes-plugin.d.ts +15 -11
  59. package/dist/server/core-routes-plugin.js +8 -7
  60. package/dist/server/edge.d.ts +1 -0
  61. package/dist/server/email-template.js +2 -2
  62. package/dist/server/email-templates.js +7 -7
  63. package/dist/server/email.js +4 -4
  64. package/dist/server/google-oauth.js +2 -2
  65. package/dist/server/http-response-telemetry.js +4 -4
  66. package/dist/server/identity-sso.js +1 -2
  67. package/dist/server/index.d.ts +1 -0
  68. package/dist/server/realtime-token.d.ts +1 -1
  69. package/dist/server/social-og-image.js +2 -2
  70. package/docs/content/observability.mdx +25 -31
  71. package/package.json +3 -3
  72. package/dist/server/app-name.d.ts +0 -33
  73. package/dist/server/app-name.js +0 -82
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 8611
34
+ - template files: 8614
@@ -63,7 +63,7 @@ decisions, feedback, agent runs, and provider audit records.
63
63
  | `get-factory-automation-health` | Inspect scheduler heartbeat and last error. |
64
64
  | `suggest-factory-rules` | Mine feedback into proposals. |
65
65
  | `reconcile-triage-run` | Persist callback/provider reconciliation. |
66
- | `list-factories` / `get-factory-graph` | Inspect definitions, versions, and metrics. |
66
+ | `list-factories` / `get-factory-graph` / `delete-factory` | Inspect Factory definitions, versions, and metrics, or permanently delete a user-created Factory after exact-name confirmation. A committed delete whose follow-up read cannot confirm the row is gone returns `verified:false`, not a failed deletion. |
67
67
  | `create-factory` | Create a factory from `/new-factory` with optional sources. |
68
68
  | `save-factory-graph` | Create/version a graph with inspected `expectedGraphVersion`; never starts provider work. |
69
69
  | graph history actions | Factory graph version history. |
@@ -8,7 +8,9 @@ import { triageItems } from "../server/db/schema.js";
8
8
  import { DEFAULT_FACTORY_ID } from "../server/factory-graph/store.js";
9
9
  import {
10
10
  factoryIdSchema,
11
+ factoryStillPresent,
11
12
  readTriageConfigRow,
13
+ requireExistingFactory,
12
14
  } from "../server/lib/factory-scope.js";
13
15
  import { requireFactoryAutomation } from "../server/lib/require-factory-automation.js";
14
16
  import {
@@ -68,21 +70,34 @@ async function updateBabysitMetadata(
68
70
  const db = getDb();
69
71
  const item = (
70
72
  await db
71
- .select({ metadataJson: triageItems.metadataJson })
73
+ .select({
74
+ metadataJson: triageItems.metadataJson,
75
+ factoryId: triageItems.factoryId,
76
+ })
72
77
  .from(triageItems)
73
78
  .where(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)))
74
79
  .limit(1)
75
80
  )[0];
76
81
  if (!item) throw new Error("Factory item disappeared during PR babysitting.");
82
+ const factoryId = item.factoryId ?? DEFAULT_FACTORY_ID;
77
83
  const metadata = parseTriageMetadata(item.metadataJson);
78
84
  Object.assign(metadata, patch);
79
- await db
80
- .update(triageItems)
81
- .set({
82
- metadataJson: serializeTriageMetadata(metadata),
83
- updatedAt: new Date().toISOString(),
84
- })
85
- .where(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)));
85
+ await db.transaction(async (tx) => {
86
+ await tx
87
+ .update(triageItems)
88
+ .set({
89
+ metadataJson: serializeTriageMetadata(metadata),
90
+ updatedAt: new Date().toISOString(),
91
+ })
92
+ .where(
93
+ and(
94
+ eq(triageItems.id, itemId),
95
+ eq(triageItems.orgId, orgId),
96
+ factoryStillPresent(tx as unknown as typeof db, orgId, factoryId),
97
+ ),
98
+ );
99
+ await requireExistingFactory(tx as unknown as typeof db, orgId, factoryId);
100
+ });
86
101
  }
87
102
 
88
103
  export default defineAction({
@@ -0,0 +1,174 @@
1
+ import { defineAction } from "@agent-native/core/action";
2
+ import { and, eq } from "drizzle-orm";
3
+ import { z } from "zod";
4
+
5
+ import { getDb } from "../server/db/index.js";
6
+ import {
7
+ factoryAuditEvents,
8
+ factoryComments,
9
+ factoryDefinitions,
10
+ factoryGraphVersions,
11
+ triageConfig,
12
+ triageDecisions,
13
+ triageFeedback,
14
+ triageItems,
15
+ triageRules,
16
+ triageRuns,
17
+ } from "../server/db/schema.js";
18
+ import {
19
+ DEFAULT_FACTORY_ID,
20
+ readFactoryDefinition,
21
+ } from "../server/factory-graph/store.js";
22
+ import { resolveEnabledAutomationsFromSavedConfig } from "../server/lib/factory-automation-plan.js";
23
+ import {
24
+ factoryIdSchema,
25
+ readTriageConfigRow,
26
+ } from "../server/lib/factory-scope.js";
27
+ import {
28
+ requireWorkspaceMember,
29
+ workspaceMemberIdentityFromContext,
30
+ } from "../server/lib/require-workspace-member.js";
31
+ import {
32
+ ensureFactoryAutomations,
33
+ removeFactoryAutomationResources,
34
+ } from "../server/plugins/factory-scheduler-job.js";
35
+
36
+ export default defineAction({
37
+ description:
38
+ "Permanently delete a user-created Factory and all Factory-owned graph versions, comments, observations, rules, decisions, runs, feedback, audit events, settings, and scheduled automations. The default product-feedback Factory cannot be deleted. Requires the Factory's exact current name as confirmation; provider work already in progress is not cancelled. When the delete commits but the follow-up read cannot confirm the Factory is gone, the action returns ok with verified:false instead of reporting a failed deletion.",
39
+ schema: z.object({
40
+ factoryId: factoryIdSchema,
41
+ confirmName: z.string().trim().min(1).max(120),
42
+ }),
43
+ http: { method: "DELETE" },
44
+ needsApproval: true,
45
+ run: async ({ factoryId, confirmName }, context) => {
46
+ const { userEmail, orgId } = await requireWorkspaceMember(
47
+ workspaceMemberIdentityFromContext(context),
48
+ );
49
+ if (factoryId === DEFAULT_FACTORY_ID) {
50
+ throw new Error("The default Factory cannot be deleted.");
51
+ }
52
+
53
+ const factory = await readFactoryDefinition(orgId, factoryId);
54
+ if (!factory) throw new Error("Factory not found.");
55
+ if (confirmName !== factory.name) {
56
+ throw new Error("Factory name confirmation does not match.");
57
+ }
58
+
59
+ const config = await readTriageConfigRow(getDb(), orgId, factoryId);
60
+ const enabledNames = resolveEnabledAutomationsFromSavedConfig({
61
+ pollingEnabled: config?.pollingEnabled ?? 0,
62
+ githubPollingEnabled: config?.githubPollingEnabled ?? 0,
63
+ sentryPollingEnabled: config?.sentryPollingEnabled ?? 0,
64
+ slackChannelId: config?.slackChannelId,
65
+ repository: config?.repository,
66
+ sentryOrgSlug: config?.sentryOrgSlug,
67
+ sentryProjectSlug: config?.sentryProjectSlug,
68
+ });
69
+
70
+ const db = getDb();
71
+ try {
72
+ // Remove schedules before SQL so no new run can start; restore both if
73
+ // either step fails so a partial cleanup cannot disable a surviving Factory.
74
+ await removeFactoryAutomationResources(orgId, factoryId);
75
+ await db.transaction(async (tx) => {
76
+ const deleted = await tx
77
+ .delete(factoryDefinitions)
78
+ .where(
79
+ and(
80
+ eq(factoryDefinitions.id, factoryId),
81
+ eq(factoryDefinitions.orgId, orgId),
82
+ eq(factoryDefinitions.name, confirmName),
83
+ ),
84
+ )
85
+ .returning({ id: factoryDefinitions.id });
86
+ if (deleted.length === 0) {
87
+ const existing = (
88
+ await tx
89
+ .select({ name: factoryDefinitions.name })
90
+ .from(factoryDefinitions)
91
+ .where(
92
+ and(
93
+ eq(factoryDefinitions.id, factoryId),
94
+ eq(factoryDefinitions.orgId, orgId),
95
+ ),
96
+ )
97
+ .limit(1)
98
+ )[0];
99
+ if (existing) {
100
+ throw new Error(
101
+ "Factory changed before deletion. Confirm its current name and try again.",
102
+ );
103
+ }
104
+ }
105
+
106
+ const scopedTables = [
107
+ factoryComments,
108
+ factoryGraphVersions,
109
+ factoryAuditEvents,
110
+ triageFeedback,
111
+ triageDecisions,
112
+ triageRuns,
113
+ triageRules,
114
+ triageItems,
115
+ triageConfig,
116
+ ] as const;
117
+ for (const table of scopedTables) {
118
+ await tx
119
+ .delete(table)
120
+ .where(and(eq(table.orgId, orgId), eq(table.factoryId, factoryId)));
121
+ }
122
+ });
123
+ } catch (error) {
124
+ try {
125
+ await ensureFactoryAutomations(userEmail, orgId, factoryId, {
126
+ enabledNames,
127
+ });
128
+ } catch (repairError) {
129
+ throw new Error(
130
+ `Factory deletion failed and its automations could not be restored. Deletion error: ${
131
+ error instanceof Error ? error.message : String(error)
132
+ }. Restore error: ${
133
+ repairError instanceof Error
134
+ ? repairError.message
135
+ : String(repairError)
136
+ }.`,
137
+ );
138
+ }
139
+ throw error;
140
+ }
141
+
142
+ let remaining: Awaited<ReturnType<typeof readFactoryDefinition>>;
143
+ try {
144
+ remaining = await readFactoryDefinition(orgId, factoryId);
145
+ } catch (error) {
146
+ return {
147
+ ok: true,
148
+ factoryId,
149
+ name: factory.name,
150
+ verified: false,
151
+ verificationError:
152
+ error instanceof Error ? error.message : String(error),
153
+ };
154
+ }
155
+ if (remaining) {
156
+ try {
157
+ await ensureFactoryAutomations(userEmail, orgId, factoryId, {
158
+ enabledNames,
159
+ });
160
+ } catch (repairError) {
161
+ throw new Error(
162
+ `Factory still exists and its automations could not be restored. Restore error: ${
163
+ repairError instanceof Error
164
+ ? repairError.message
165
+ : String(repairError)
166
+ }.`,
167
+ );
168
+ }
169
+ throw new Error("Factory deletion could not be verified.");
170
+ }
171
+
172
+ return { ok: true, factoryId, name: factory.name, verified: true };
173
+ },
174
+ });
@@ -16,6 +16,7 @@ import {
16
16
  orgFactoryRunFilter,
17
17
  orgFactoryScopedItemWhere,
18
18
  readTriageConfigRow,
19
+ requireExistingFactory,
19
20
  } from "../server/lib/factory-scope.js";
20
21
  import { requireFactoryAutomation } from "../server/lib/require-factory-automation.js";
21
22
  import {
@@ -343,28 +344,35 @@ export default defineAction({
343
344
  itemId,
344
345
  snapshot.headSha,
345
346
  );
346
- await getDb()
347
- .insert(triageDecisions)
348
- .values({
349
- id: decisionId,
350
- itemId,
351
- ruleId: null,
352
- mode: "automation",
353
- outcome: governance.autoMerge
354
- ? "auto_merge"
355
- : governance.autoApprove
356
- ? "auto_approve"
357
- : "needs_manual",
358
- reason: `${reason} ${governance.reason}`.trim(),
359
- guardResultsJson: JSON.stringify(governance.guardResults),
360
- model: "factory-pr-governance",
361
- promptVersion: 1,
362
- createdAt: new Date().toISOString(),
363
- ownerEmail: userEmail,
347
+ await getDb().transaction(async (tx) => {
348
+ await tx
349
+ .insert(triageDecisions)
350
+ .values({
351
+ id: decisionId,
352
+ itemId,
353
+ ruleId: null,
354
+ mode: "automation",
355
+ outcome: governance.autoMerge
356
+ ? "auto_merge"
357
+ : governance.autoApprove
358
+ ? "auto_approve"
359
+ : "needs_manual",
360
+ reason: `${reason} ${governance.reason}`.trim(),
361
+ guardResultsJson: JSON.stringify(governance.guardResults),
362
+ model: "factory-pr-governance",
363
+ promptVersion: 1,
364
+ createdAt: new Date().toISOString(),
365
+ ownerEmail: userEmail,
366
+ orgId,
367
+ factoryId,
368
+ })
369
+ .onConflictDoNothing();
370
+ await requireExistingFactory(
371
+ tx as unknown as ReturnType<typeof getDb>,
364
372
  orgId,
365
373
  factoryId,
366
- })
367
- .onConflictDoNothing();
374
+ );
375
+ });
368
376
  }
369
377
 
370
378
  if (!governance.autoApprove) {
@@ -8,6 +8,7 @@ import { repairFactoryAutomationsFromConfig } from "../server/lib/factory-automa
8
8
  import {
9
9
  factoryIdSchema,
10
10
  readTriageConfigRow,
11
+ requireExistingFactory,
11
12
  } from "../server/lib/factory-scope.js";
12
13
  import { requireFactoryAutomation } from "../server/lib/require-factory-automation.js";
13
14
  import {
@@ -239,6 +240,11 @@ export default defineAction({
239
240
  });
240
241
  pullRequestCount += 1;
241
242
  }
243
+ await requireExistingFactory(
244
+ tx as unknown as ReturnType<typeof getDb>,
245
+ orgId,
246
+ factoryId,
247
+ );
242
248
  });
243
249
 
244
250
  if (issues.length === 0 && pullRequests.length === 0) {
@@ -7,7 +7,9 @@ import { triageConfig, triageItems } from "../server/db/schema.js";
7
7
  import { repairFactoryAutomationsFromConfig } from "../server/lib/factory-automation-repair.js";
8
8
  import {
9
9
  factoryIdSchema,
10
+ factoryStillPresent,
10
11
  readTriageConfigRow,
12
+ requireExistingFactory,
11
13
  triageConfigUpdateRowId,
12
14
  } from "../server/lib/factory-scope.js";
13
15
  import { requireFactoryAutomation } from "../server/lib/require-factory-automation.js";
@@ -177,8 +179,21 @@ export default defineAction({
177
179
  updatedAt: now,
178
180
  })
179
181
  .where(
180
- and(eq(triageConfig.id, configRowId), eq(triageConfig.orgId, orgId)),
182
+ and(
183
+ eq(triageConfig.id, configRowId),
184
+ eq(triageConfig.orgId, orgId),
185
+ factoryStillPresent(
186
+ tx as unknown as ReturnType<typeof getDb>,
187
+ orgId,
188
+ factoryId,
189
+ ),
190
+ ),
181
191
  );
192
+ await requireExistingFactory(
193
+ tx as unknown as ReturnType<typeof getDb>,
194
+ orgId,
195
+ factoryId,
196
+ );
182
197
  });
183
198
 
184
199
  if (observedIssues.length === 0) {
@@ -7,7 +7,9 @@ import { triageConfig, triageItems } from "../server/db/schema.js";
7
7
  import { repairFactoryAutomationsFromConfig } from "../server/lib/factory-automation-repair.js";
8
8
  import {
9
9
  factoryIdSchema,
10
+ factoryStillPresent,
10
11
  readTriageConfigRow,
12
+ requireExistingFactory,
11
13
  triageConfigUpdateRowId,
12
14
  } from "../server/lib/factory-scope.js";
13
15
  import { requireFactoryAutomation } from "../server/lib/require-factory-automation.js";
@@ -159,9 +161,19 @@ export default defineAction({
159
161
  and(
160
162
  eq(triageConfig.id, configRowId),
161
163
  eq(triageConfig.orgId, orgId),
164
+ factoryStillPresent(
165
+ tx as unknown as ReturnType<typeof getDb>,
166
+ orgId,
167
+ factoryId,
168
+ ),
162
169
  ),
163
170
  );
164
171
  }
172
+ await requireExistingFactory(
173
+ tx as unknown as ReturnType<typeof getDb>,
174
+ orgId,
175
+ factoryId,
176
+ );
165
177
  });
166
178
 
167
179
  if (result.envelopes.length === 0) {
@@ -10,10 +10,12 @@ import {
10
10
  } from "../server/db/schema.js";
11
11
  import {
12
12
  DEFAULT_FACTORY_ID,
13
+ factoryStillPresent,
13
14
  orgFactoryItemFilter,
14
15
  orgFactoryRunFilter,
16
+ readTriageConfigRow,
17
+ requireExistingFactory,
15
18
  } from "../server/lib/factory-scope.js";
16
- import { readTriageConfigRow } from "../server/lib/factory-scope.js";
17
19
  import { requireFactoryAutomation } from "../server/lib/require-factory-automation.js";
18
20
  import {
19
21
  requireWorkspaceMember,
@@ -28,7 +30,10 @@ import {
28
30
  serializeTriageMetadata,
29
31
  } from "../server/triage/metadata.js";
30
32
  import { detectOwnerOwnedArea } from "../server/triage/pr-policy.js";
31
- import { createSlackReader } from "../server/triage/slack-client.js";
33
+ import {
34
+ createSlackReader,
35
+ isAgentNativeSlackUserName,
36
+ } from "../server/triage/slack-client.js";
32
37
 
33
38
  /** Slack notifies only with `<@USERID>`. Plaintext @handles do not ping anyone. */
34
39
  const REPLY_INSTRUCTION =
@@ -421,10 +426,23 @@ export default defineAction({
421
426
  },
422
427
  );
423
428
  if (blocked) {
424
- await db
425
- .update(triageItems)
426
- .set({ status: "needs_manual", updatedAt: new Date().toISOString() })
427
- .where(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)));
429
+ await db.transaction(async (tx) => {
430
+ await tx
431
+ .update(triageItems)
432
+ .set({ status: "needs_manual", updatedAt: new Date().toISOString() })
433
+ .where(
434
+ and(
435
+ eq(triageItems.id, itemId),
436
+ eq(triageItems.orgId, orgId),
437
+ factoryStillPresent(tx as unknown as typeof db, orgId, factoryId),
438
+ ),
439
+ );
440
+ await requireExistingFactory(
441
+ tx as unknown as typeof db,
442
+ orgId,
443
+ factoryId,
444
+ );
445
+ });
428
446
  return {
429
447
  ok: true,
430
448
  started: false,
@@ -459,44 +477,57 @@ export default defineAction({
459
477
  };
460
478
  }
461
479
 
462
- if (retryableSlackRun) {
463
- await db
464
- .update(triageRuns)
465
- .set({
480
+ await db.transaction(async (tx) => {
481
+ if (retryableSlackRun) {
482
+ await tx
483
+ .update(triageRuns)
484
+ .set({
485
+ status: "submitted",
486
+ error: null,
487
+ completedAt: null,
488
+ heartbeatAt: now,
489
+ dispatchAttempts: (existing.dispatchAttempts ?? 0) + 1,
490
+ })
491
+ .where(
492
+ and(
493
+ eq(triageRuns.id, runId),
494
+ eq(triageRuns.orgId, orgId),
495
+ factoryStillPresent(tx as unknown as typeof db, orgId, factoryId),
496
+ ),
497
+ );
498
+ } else {
499
+ await tx.insert(triageRuns).values({
500
+ id: runId,
501
+ itemId,
502
+ source: item.source,
503
+ provider: isSlack ? "bot-tag" : "builder-http",
504
+ dedupeKey,
505
+ approvalEmail: null,
466
506
  status: "submitted",
467
- error: null,
468
- completedAt: null,
507
+ progressLogJson: JSON.stringify([
508
+ {
509
+ at: now,
510
+ state: "submitted",
511
+ reason: `Factory automation ${context?.automation?.triggerName ?? "unknown"} recorded a clear bug.`,
512
+ },
513
+ ]),
514
+ dispatchAttempts: 1,
515
+ needsContinuation: 0,
516
+ startedAt: now,
469
517
  heartbeatAt: now,
470
- dispatchAttempts: (existing.dispatchAttempts ?? 0) + 1,
471
- })
472
- .where(and(eq(triageRuns.id, runId), eq(triageRuns.orgId, orgId)));
473
- } else {
474
- await db.insert(triageRuns).values({
475
- id: runId,
476
- itemId,
477
- source: item.source,
478
- provider: isSlack ? "bot-tag" : "builder-http",
479
- dedupeKey,
480
- approvalEmail: null,
481
- status: "submitted",
482
- progressLogJson: JSON.stringify([
483
- {
484
- at: now,
485
- state: "submitted",
486
- reason: `Factory automation ${context?.automation?.triggerName ?? "unknown"} recorded a clear bug.`,
487
- },
488
- ]),
489
- dispatchAttempts: 1,
490
- needsContinuation: 0,
491
- startedAt: now,
492
- heartbeatAt: now,
493
- completedAt: null,
494
- error: null,
495
- ownerEmail: item.ownerEmail,
518
+ completedAt: null,
519
+ error: null,
520
+ ownerEmail: item.ownerEmail,
521
+ orgId,
522
+ factoryId: item.factoryId ?? DEFAULT_FACTORY_ID,
523
+ });
524
+ }
525
+ await requireExistingFactory(
526
+ tx as unknown as typeof db,
496
527
  orgId,
497
- factoryId: item.factoryId ?? DEFAULT_FACTORY_ID,
498
- });
499
- }
528
+ factoryId,
529
+ );
530
+ });
500
531
 
501
532
  try {
502
533
  if (isSlack) {
@@ -603,7 +634,8 @@ export default defineAction({
603
634
  const hasAgentNativeDisposition = feedbackThread.messages.some(
604
635
  (message) =>
605
636
  (message.user === agentNative.userId ||
606
- message.username?.trim().toLowerCase() === "agent-native") &&
637
+ (message.username != null &&
638
+ isAgentNativeSlackUserName(message.username))) &&
607
639
  /^(Fixed|In progress|Clarification needed):/i.test(
608
640
  message.text.trim(),
609
641
  ),