@agent-native/core 0.168.6 → 0.168.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/actions/mutate-dashboard.ts +8 -1
- package/corpus/templates/calendar/actions/delete-event.ts +34 -18
- package/corpus/templates/calendar/actions/delete-events.ts +13 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +22 -3
- package/corpus/templates/calendar/app/lib/calendar-timezone.ts +19 -0
- package/corpus/templates/calendar/server/lib/google-api.ts +18 -1
- package/corpus/templates/clips/actions/finalize-meeting.ts +16 -0
- package/corpus/templates/clips/actions/list-meetings.ts +1 -0
- package/corpus/templates/clips/actions/list-recordings.ts +14 -0
- package/corpus/templates/clips/actions/sync-calendars.ts +10 -29
- package/corpus/templates/clips/app/components/library/recording-card.tsx +2 -2
- package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +20 -3
- package/corpus/templates/clips/app/hooks/use-library.ts +1 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +12 -5
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +28 -12
- package/corpus/templates/clips/desktop/src/lib/meeting-stop.ts +16 -0
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +19 -9
- package/corpus/templates/factory/.agents/skills/factory-graphs/SKILL.md +5 -0
- package/corpus/templates/factory/AGENTS.md +12 -22
- package/corpus/templates/factory/README.md +11 -8
- package/corpus/templates/factory/actions/approve-factory-item.ts +35 -8
- package/corpus/templates/factory/actions/babysit-agent-native-pull-request.ts +24 -9
- package/corpus/templates/factory/actions/create-factory.ts +211 -0
- package/corpus/templates/factory/actions/evaluate-triage-item.ts +29 -5
- package/corpus/templates/factory/actions/get-factory-graph.ts +1 -1
- package/corpus/templates/factory/actions/get-slack-feedback-context.ts +20 -11
- package/corpus/templates/factory/actions/get-triage-config.ts +13 -12
- package/corpus/templates/factory/actions/get-triage-item.ts +28 -6
- package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +33 -17
- package/corpus/templates/factory/actions/ingest-github-observation.ts +10 -4
- package/corpus/templates/factory/actions/list-factory-audit.ts +11 -4
- package/corpus/templates/factory/actions/list-factory-automations.ts +34 -12
- package/corpus/templates/factory/actions/list-triage-items.ts +11 -3
- package/corpus/templates/factory/actions/list-triage-rules.ts +11 -4
- package/corpus/templates/factory/actions/poll-github-sources.ts +24 -10
- package/corpus/templates/factory/actions/poll-sentry-errors.ts +26 -11
- package/corpus/templates/factory/actions/poll-slack-channel.ts +31 -13
- package/corpus/templates/factory/actions/reconcile-triage-run.ts +59 -9
- package/corpus/templates/factory/actions/record-triage-feedback.ts +14 -3
- package/corpus/templates/factory/actions/run-factory-automation.ts +12 -2
- package/corpus/templates/factory/actions/save-factory-automation.ts +82 -21
- package/corpus/templates/factory/actions/save-factory-graph.ts +10 -1
- package/corpus/templates/factory/actions/save-triage-config.ts +90 -36
- package/corpus/templates/factory/actions/save-triage-rule.ts +16 -3
- package/corpus/templates/factory/actions/start-builder-for-item.ts +28 -20
- package/corpus/templates/factory/actions/suggest-factory-rules.ts +11 -4
- package/corpus/templates/factory/actions/view-screen.ts +22 -30
- package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +7 -2
- package/corpus/templates/factory/app/components/factory/FactoryWorkspaceActions.tsx +7 -22
- package/corpus/templates/factory/app/components/factory/NewFactoryForm.tsx +260 -0
- package/corpus/templates/factory/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +2 -1
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +3 -3
- package/corpus/templates/factory/app/i18n/en-US.ts +18 -1
- package/corpus/templates/factory/app/routes/factory.tsx +78 -62
- package/corpus/templates/factory/app/routes/{factory-settings.tsx → new-factory.tsx} +12 -9
- package/corpus/templates/factory/server/db/schema.ts +18 -3
- package/corpus/templates/factory/server/factory-graph/contracts.ts +24 -0
- package/corpus/templates/factory/server/factory-graph/store.ts +16 -9
- package/corpus/templates/factory/server/lib/factory-automation-plan.ts +136 -0
- package/corpus/templates/factory/server/lib/factory-automation-repair.ts +31 -0
- package/corpus/templates/factory/server/lib/factory-scope.ts +416 -0
- package/corpus/templates/factory/server/lib/require-factory-automation.ts +11 -0
- package/corpus/templates/factory/server/plugins/agent-chat.ts +4 -2
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +80 -0
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +142 -43
- package/corpus/templates/factory/server/triage/audit.ts +4 -0
- package/corpus/templates/factory/server/triage/ids.ts +4 -0
- package/dist/cli/native-dependencies.js +4 -1
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.js +9 -4
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat/index.js +1 -1
- package/dist/client/chat-first/apps-rail.js +6 -6
- package/dist/client/chat-first/primary-nav.js +14 -6
- package/dist/client/integrations/IntegrationsPanel.d.ts +15 -0
- package/dist/client/integrations/IntegrationsPanel.js +29 -21
- package/dist/client/integrations/index.d.ts +1 -1
- package/dist/client/integrations/index.js +1 -1
- package/dist/client/resources/McpIntegrationDialog.js +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/ssr-handler.js +19 -1
- package/package.json +1 -1
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
|
|
24
24
|
export const CALENDAR_MEETING_ID_PREFIX = "gcal";
|
|
25
25
|
|
|
26
|
+
const CALENDAR_REQUEST_SAFETY_MARGIN_MS = 60 * 1000;
|
|
27
|
+
|
|
26
28
|
export interface CalendarAccountForEvents {
|
|
27
29
|
id: string;
|
|
28
30
|
provider: string;
|
|
@@ -74,8 +76,7 @@ export function shouldMarkNeedsReauth(message: string): boolean {
|
|
|
74
76
|
lower.includes("google calendar event failed (401)") ||
|
|
75
77
|
lower.includes("invalid_grant") ||
|
|
76
78
|
lower.includes("invalid_token") ||
|
|
77
|
-
lower.includes("insufficient_scope")
|
|
78
|
-
lower.includes("token refresh failed")
|
|
79
|
+
lower.includes("insufficient_scope")
|
|
79
80
|
);
|
|
80
81
|
}
|
|
81
82
|
|
|
@@ -122,10 +123,16 @@ export async function resolveCalendarAccessToken(
|
|
|
122
123
|
});
|
|
123
124
|
} catch (err) {
|
|
124
125
|
// Only a permanent failure (dead refresh token / bad OAuth client) means
|
|
125
|
-
// "needs-reauth" — collapse those to `null` as before.
|
|
126
|
-
//
|
|
127
|
-
// record it as a soft sync error without flipping account status.
|
|
126
|
+
// "needs-reauth" — collapse those to `null` as before. During the refresh
|
|
127
|
+
// buffer, a transient failure can safely reuse the still-valid token.
|
|
128
128
|
if (isPermanentRefreshFailure(err)) return null;
|
|
129
|
+
if (
|
|
130
|
+
bundle?.accessToken &&
|
|
131
|
+
bundle.expiresAt &&
|
|
132
|
+
bundle.expiresAt > Date.now() + CALENDAR_REQUEST_SAFETY_MARGIN_MS
|
|
133
|
+
) {
|
|
134
|
+
return bundle.accessToken;
|
|
135
|
+
}
|
|
129
136
|
throw err;
|
|
130
137
|
}
|
|
131
138
|
if (!refreshed.access_token) return null;
|
|
@@ -158,13 +165,13 @@ export async function recordCalendarFetchSuccess(
|
|
|
158
165
|
.set({
|
|
159
166
|
lastSyncedAt: now,
|
|
160
167
|
lastSyncError: null,
|
|
161
|
-
status: "connected",
|
|
162
168
|
updatedAt: now,
|
|
163
169
|
})
|
|
164
170
|
.where(
|
|
165
171
|
and(
|
|
166
172
|
eq(schema.calendarAccounts.id, account.id),
|
|
167
173
|
eq(schema.calendarAccounts.ownerEmail, account.ownerEmail),
|
|
174
|
+
eq(schema.calendarAccounts.status, "connected"),
|
|
168
175
|
),
|
|
169
176
|
);
|
|
170
177
|
}
|
|
@@ -172,10 +179,11 @@ export async function recordCalendarFetchSuccess(
|
|
|
172
179
|
export async function recordCalendarFetchError(
|
|
173
180
|
account: CalendarAccountForEvents,
|
|
174
181
|
error: unknown,
|
|
182
|
+
options: { needsReauth?: boolean } = {},
|
|
175
183
|
): Promise<CalendarFetchError> {
|
|
176
184
|
const message =
|
|
177
185
|
error instanceof Error ? error.message : String(error || "Calendar failed");
|
|
178
|
-
const needsReauth = shouldMarkNeedsReauth(message);
|
|
186
|
+
const needsReauth = options.needsReauth ?? shouldMarkNeedsReauth(message);
|
|
179
187
|
if (!account.ownerEmail) {
|
|
180
188
|
return { accountId: account.id, error: message, needsReauth };
|
|
181
189
|
}
|
|
@@ -183,7 +191,7 @@ export async function recordCalendarFetchError(
|
|
|
183
191
|
await db
|
|
184
192
|
.update(schema.calendarAccounts)
|
|
185
193
|
.set({
|
|
186
|
-
|
|
194
|
+
...(needsReauth ? { status: "needs-reauth" as const } : {}),
|
|
187
195
|
lastSyncError: needsReauth
|
|
188
196
|
? "Google Calendar needs to be reconnected."
|
|
189
197
|
: message,
|
|
@@ -320,7 +328,9 @@ export async function fetchLiveCalendarEventFromId(virtualId: string) {
|
|
|
320
328
|
return null;
|
|
321
329
|
}
|
|
322
330
|
if (!accessToken) {
|
|
323
|
-
await recordCalendarFetchError(account, new Error("Token refresh failed")
|
|
331
|
+
await recordCalendarFetchError(account, new Error("Token refresh failed"), {
|
|
332
|
+
needsReauth: true,
|
|
333
|
+
});
|
|
324
334
|
return null;
|
|
325
335
|
}
|
|
326
336
|
|
|
@@ -16,6 +16,11 @@ explains them, but never hides a topology change in prose. Until an execution
|
|
|
16
16
|
binding is explicitly added, the blueprint must not be described as the runtime
|
|
17
17
|
router.
|
|
18
18
|
|
|
19
|
+
Runtime work — settings, inbox, rules, automations, and activity — is scoped by
|
|
20
|
+
`factoryId`. Reusable agents stay workspace-wide; graph nodes may reference any
|
|
21
|
+
workspace agent as blueprint only. Pass `factoryId` on triage, config, automation,
|
|
22
|
+
and audit actions for the factory the user is viewing.
|
|
23
|
+
|
|
19
24
|
## Workflow
|
|
20
25
|
|
|
21
26
|
1. Read `view-screen` to identify the selected Factory, tab, node, or edge.
|
|
@@ -21,22 +21,11 @@ decisions, feedback, agent runs, and provider audit records.
|
|
|
21
21
|
reconciliation is not success; preserve typed failure or
|
|
22
22
|
`reconciliation_required` state.
|
|
23
23
|
- Deduplicate by Factory item and rule/run identity, not provider comment ID.
|
|
24
|
-
- Slack clear bugs go through `start-builder-for-item
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
clarification limits, durable rechecks, and the required `@agent-native`
|
|
30
|
-
disposition after every actionable 👀. Clips, Design, and Content stay
|
|
31
|
-
owner-managed outside autonomous dispatch and PR governance.
|
|
32
|
-
- PR governance follows `review-prs`: verify BuilderIO membership and complete
|
|
33
|
-
diff/review/check evidence; skip drafts/current approvals and never approve
|
|
34
|
-
external authors. Keep ultra-scary security, auth, tenant-isolation,
|
|
35
|
-
secrets, data-loss, execution, payment, and deployment risks manual.
|
|
36
|
-
Verified members may pass ordinary uncertain checks or review feedback, but
|
|
37
|
-
record exact states and never call them clean. Apply the Alice/Content,
|
|
38
|
-
Nick/Slides, Enzo/Factory, Sid/Design, and docs-only exceptions only after
|
|
39
|
-
membership and the ultra-scary gate. Never auto-merge.
|
|
24
|
+
- Slack clear bugs go through `start-builder-for-item`; never post Slack
|
|
25
|
+
messages or `@handles`. GitHub/Sentry use the Builder run API. Read
|
|
26
|
+
`review-latest-feedback` for thread evidence and disposition rules.
|
|
27
|
+
- PR governance follows `review-prs`: verify membership and evidence; skip
|
|
28
|
+
drafts and external authors; keep ultra-scary risks manual; never auto-merge.
|
|
40
29
|
- Graph edits create immutable blueprint versions. AI proposes with `source=ai`;
|
|
41
30
|
a person reviews and publishes through the same action surface.
|
|
42
31
|
- Provider credentials belong to Dispatch/shared workspace integrations, never
|
|
@@ -46,16 +35,16 @@ decisions, feedback, agent runs, and provider audit records.
|
|
|
46
35
|
|
|
47
36
|
## Application state
|
|
48
37
|
|
|
49
|
-
- `navigation.view` is `factory
|
|
50
|
-
|
|
51
|
-
`
|
|
52
|
-
`factoryEdgeId` hold Factory context. Read `view-screen` first.
|
|
38
|
+
- `navigation.view` is `factory` or `agents`. Runtime data is scoped by
|
|
39
|
+
`factoryId`; reusable agents stay workspace-wide. Read `view-screen` and
|
|
40
|
+
`factory-graphs` for tab and selection keys.
|
|
53
41
|
|
|
54
42
|
## Action contract
|
|
55
43
|
|
|
56
44
|
| Action | Purpose |
|
|
57
45
|
| --- | --- |
|
|
58
|
-
| `list-triage-items` / `get-triage-item` | Inspect
|
|
46
|
+
| `list-triage-items` / `get-triage-item` | Inspect queue evidence; pass `factoryId`. |
|
|
47
|
+
| `get-triage-config` / `save-triage-config` | Read or save observation settings for one factory. |
|
|
59
48
|
| `poll-slack-channel` | Observe Slack history; never writes to Slack. |
|
|
60
49
|
| `get-slack-feedback-context` | Read the bounded full Slack thread before classification. |
|
|
61
50
|
| `poll-github-sources` / `poll-sentry-errors` | Observe bounded source queues. |
|
|
@@ -67,11 +56,12 @@ decisions, feedback, agent runs, and provider audit records.
|
|
|
67
56
|
| `start-builder-for-item` | Govern clear-bug dispatch through Slack or Builder API, or record a skip reason. |
|
|
68
57
|
| `govern-agent-native-pull-request` | Apply PR evidence and ownership gates. |
|
|
69
58
|
| `list-factory-automations` / `save-factory-automation` / `run-factory-automation` | Inspect or edit org-owned automations. |
|
|
70
|
-
| `list-factory-audit` | Inspect
|
|
59
|
+
| `list-factory-audit` | Inspect runs, evidence, and provider actions for one factory. |
|
|
71
60
|
| `get-factory-automation-health` | Inspect scheduler heartbeat and last error. |
|
|
72
61
|
| `suggest-factory-rules` | Mine feedback into proposals. |
|
|
73
62
|
| `reconcile-triage-run` | Persist callback/provider reconciliation. |
|
|
74
63
|
| `list-factories` / `get-factory-graph` | Inspect definitions, versions, and metrics. |
|
|
64
|
+
| `create-factory` | Create a factory from `/new-factory` with optional sources. |
|
|
75
65
|
| `save-factory-graph` | Create/version a graph with inspected `expectedGraphVersion`; never starts provider work. |
|
|
76
66
|
| graph history actions | Factory graph version history. |
|
|
77
67
|
| `list-factory-comments` / `add-factory-comment` | Read or attach comments to a canvas, node, or edge. |
|
|
@@ -44,19 +44,22 @@ shared `app_secrets` rows must use the same
|
|
|
44
44
|
encryption fallback). Never copy raw tokens between apps or add a second
|
|
45
45
|
env-only read in a provider client.
|
|
46
46
|
|
|
47
|
-
Factory's
|
|
48
|
-
|
|
49
|
-
queue adapters.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
Factory's observer keeps **per-factory** source metadata — Slack channel,
|
|
48
|
+
repository, Sentry project, and related polling settings — for its normalized
|
|
49
|
+
queue adapters. Each factory has its own inbox, rules, automations, and activity.
|
|
50
|
+
Reusable agents and workspace credentials stay shared. Each factory's observation
|
|
51
|
+
settings live on its Settings tab in the factory detail view. That metadata is
|
|
52
|
+
not a credential or a per-factory
|
|
53
|
+
integration setup. GitHub issue polling defaults to hourly; Slack and
|
|
54
|
+
pull-request checks use shorter bounded cadences because those sources can need
|
|
55
|
+
faster feedback. Every poll preserves errors for reconciliation.
|
|
53
56
|
|
|
54
57
|
Factory agents can discover connected provider APIs with
|
|
55
58
|
`provider-api-catalog`, inspect their docs with `provider-api-docs`, and call
|
|
56
59
|
them through `provider-api-request`. Scheduled Factory runs also receive the
|
|
57
60
|
workspace's connected MCP tools, subject to the same workspace and request
|
|
58
|
-
scope gates. The three normalized pollers are compatibility adapters
|
|
59
|
-
|
|
61
|
+
scope gates. The three normalized pollers are compatibility adapters scoped by
|
|
62
|
+
`factoryId`, not the agent's capability limit.
|
|
60
63
|
|
|
61
64
|
The generic Slack bot is wired to Factory. Mention `@agent-native` in a feedback
|
|
62
65
|
thread to inspect the linked item, explain its decision, tune a rule, or say
|
|
@@ -8,6 +8,13 @@ import {
|
|
|
8
8
|
triageItems,
|
|
9
9
|
triageRuns,
|
|
10
10
|
} from "../server/db/schema.js";
|
|
11
|
+
import { DEFAULT_FACTORY_ID } from "../server/factory-graph/store.js";
|
|
12
|
+
import {
|
|
13
|
+
factoryIdSchema,
|
|
14
|
+
orgFactoryDecisionFilter,
|
|
15
|
+
orgFactoryItemFilter,
|
|
16
|
+
orgFactoryRunFilter,
|
|
17
|
+
} from "../server/lib/factory-scope.js";
|
|
11
18
|
import {
|
|
12
19
|
requireWorkspaceMember,
|
|
13
20
|
workspaceMemberIdentityFromContext,
|
|
@@ -19,6 +26,7 @@ export default defineAction({
|
|
|
19
26
|
description:
|
|
20
27
|
"Explicitly approve one Factory item and start one deduplicated Builder coding run. The approver, decision, guard results, and callback state are recorded before provider work is considered successful.",
|
|
21
28
|
schema: z.object({
|
|
29
|
+
factoryId: factoryIdSchema.default(DEFAULT_FACTORY_ID),
|
|
22
30
|
itemId: z.string().min(1),
|
|
23
31
|
decisionId: z.string().min(1).optional(),
|
|
24
32
|
confirm: z.literal(true),
|
|
@@ -27,7 +35,7 @@ export default defineAction({
|
|
|
27
35
|
// must remain paused until a human approves this exact agent tool call.
|
|
28
36
|
needsApproval: true,
|
|
29
37
|
http: { method: "POST" },
|
|
30
|
-
run: async ({ itemId, decisionId, confirm }, context) => {
|
|
38
|
+
run: async ({ factoryId, itemId, decisionId, confirm }, context) => {
|
|
31
39
|
if (!confirm) throw new Error("Explicit confirmation is required.");
|
|
32
40
|
const { userEmail, orgId } = await requireWorkspaceMember(
|
|
33
41
|
workspaceMemberIdentityFromContext(context),
|
|
@@ -37,7 +45,12 @@ export default defineAction({
|
|
|
37
45
|
await db
|
|
38
46
|
.select()
|
|
39
47
|
.from(triageItems)
|
|
40
|
-
.where(
|
|
48
|
+
.where(
|
|
49
|
+
and(
|
|
50
|
+
eq(triageItems.id, itemId),
|
|
51
|
+
orgFactoryItemFilter(orgId, factoryId),
|
|
52
|
+
),
|
|
53
|
+
)
|
|
41
54
|
.limit(1)
|
|
42
55
|
)[0];
|
|
43
56
|
if (!item) throw new Error("Factory item not found.");
|
|
@@ -47,8 +60,8 @@ export default defineAction({
|
|
|
47
60
|
.from(triageDecisions)
|
|
48
61
|
.where(
|
|
49
62
|
and(
|
|
50
|
-
eq(triageDecisions.orgId, orgId),
|
|
51
63
|
eq(triageDecisions.itemId, itemId),
|
|
64
|
+
orgFactoryDecisionFilter(orgId, factoryId),
|
|
52
65
|
),
|
|
53
66
|
)
|
|
54
67
|
.orderBy(asc(triageDecisions.createdAt));
|
|
@@ -72,7 +85,9 @@ export default defineAction({
|
|
|
72
85
|
await db
|
|
73
86
|
.select()
|
|
74
87
|
.from(triageRuns)
|
|
75
|
-
.where(
|
|
88
|
+
.where(
|
|
89
|
+
and(eq(triageRuns.id, runId), orgFactoryRunFilter(orgId, factoryId)),
|
|
90
|
+
)
|
|
76
91
|
.limit(1)
|
|
77
92
|
)[0];
|
|
78
93
|
if (
|
|
@@ -110,6 +125,7 @@ export default defineAction({
|
|
|
110
125
|
error: null,
|
|
111
126
|
ownerEmail: item.ownerEmail,
|
|
112
127
|
orgId,
|
|
128
|
+
factoryId,
|
|
113
129
|
})
|
|
114
130
|
.onConflictDoNothing();
|
|
115
131
|
|
|
@@ -117,7 +133,9 @@ export default defineAction({
|
|
|
117
133
|
await db
|
|
118
134
|
.select()
|
|
119
135
|
.from(triageRuns)
|
|
120
|
-
.where(
|
|
136
|
+
.where(
|
|
137
|
+
and(eq(triageRuns.id, runId), orgFactoryRunFilter(orgId, factoryId)),
|
|
138
|
+
)
|
|
121
139
|
.limit(1)
|
|
122
140
|
)[0];
|
|
123
141
|
if (!run) throw new Error("Factory run could not be recorded.");
|
|
@@ -155,11 +173,18 @@ export default defineAction({
|
|
|
155
173
|
progressLogJson: JSON.stringify(progress),
|
|
156
174
|
heartbeatAt: new Date().toISOString(),
|
|
157
175
|
})
|
|
158
|
-
.where(
|
|
176
|
+
.where(
|
|
177
|
+
and(eq(triageRuns.id, runId), orgFactoryRunFilter(orgId, factoryId)),
|
|
178
|
+
);
|
|
159
179
|
await db
|
|
160
180
|
.update(triageItems)
|
|
161
181
|
.set({ status: "classified", updatedAt: new Date().toISOString() })
|
|
162
|
-
.where(
|
|
182
|
+
.where(
|
|
183
|
+
and(
|
|
184
|
+
eq(triageItems.id, itemId),
|
|
185
|
+
orgFactoryItemFilter(orgId, factoryId),
|
|
186
|
+
),
|
|
187
|
+
);
|
|
163
188
|
return {
|
|
164
189
|
ok: true,
|
|
165
190
|
deduplicated: false,
|
|
@@ -176,7 +201,9 @@ export default defineAction({
|
|
|
176
201
|
completedAt: new Date().toISOString(),
|
|
177
202
|
heartbeatAt: new Date().toISOString(),
|
|
178
203
|
})
|
|
179
|
-
.where(
|
|
204
|
+
.where(
|
|
205
|
+
and(eq(triageRuns.id, runId), orgFactoryRunFilter(orgId, factoryId)),
|
|
206
|
+
);
|
|
180
207
|
throw new Error(
|
|
181
208
|
`Factory approval was recorded, but Builder dispatch failed: ${message}`,
|
|
182
209
|
);
|
|
@@ -4,7 +4,12 @@ import { z } from "zod";
|
|
|
4
4
|
|
|
5
5
|
import { resolveConnectorSecret } from "../server/connectors/credentials.js";
|
|
6
6
|
import { getDb } from "../server/db/index.js";
|
|
7
|
-
import {
|
|
7
|
+
import { triageItems } from "../server/db/schema.js";
|
|
8
|
+
import { DEFAULT_FACTORY_ID } from "../server/factory-graph/store.js";
|
|
9
|
+
import {
|
|
10
|
+
factoryIdSchema,
|
|
11
|
+
readTriageConfigRow,
|
|
12
|
+
} from "../server/lib/factory-scope.js";
|
|
8
13
|
import { requireFactoryAutomation } from "../server/lib/require-factory-automation.js";
|
|
9
14
|
import {
|
|
10
15
|
requireWorkspaceMember,
|
|
@@ -85,13 +90,13 @@ export default defineAction({
|
|
|
85
90
|
"Watch one builder-io-bot pull request, post the bounded feedback-fix request when CI, mergeability, or review feedback needs work, and stop after 20 quiet minutes. The action never merges or approves.",
|
|
86
91
|
schema: z.object({
|
|
87
92
|
itemId: z.string().min(1),
|
|
93
|
+
factoryId: factoryIdSchema.optional(),
|
|
88
94
|
}),
|
|
89
95
|
http: false,
|
|
90
|
-
run: async ({ itemId }, context) => {
|
|
96
|
+
run: async ({ itemId, factoryId: factoryIdInput }, context) => {
|
|
91
97
|
const { userEmail, orgId } = await requireWorkspaceMember(
|
|
92
98
|
workspaceMemberIdentityFromContext(context),
|
|
93
99
|
);
|
|
94
|
-
await requireFactoryAutomation(context, { userEmail, orgId }, "prBabysit");
|
|
95
100
|
|
|
96
101
|
const db = getDb();
|
|
97
102
|
const item = (
|
|
@@ -102,6 +107,16 @@ export default defineAction({
|
|
|
102
107
|
.limit(1)
|
|
103
108
|
)[0];
|
|
104
109
|
if (!item) throw new Error("Factory item not found for PR babysitting.");
|
|
110
|
+
const factoryId = factoryIdInput ?? item.factoryId ?? DEFAULT_FACTORY_ID;
|
|
111
|
+
if ((item.factoryId ?? DEFAULT_FACTORY_ID) !== factoryId) {
|
|
112
|
+
throw new Error("Factory item does not belong to this factory.");
|
|
113
|
+
}
|
|
114
|
+
await requireFactoryAutomation(
|
|
115
|
+
context,
|
|
116
|
+
{ userEmail, orgId },
|
|
117
|
+
"prBabysit",
|
|
118
|
+
factoryId,
|
|
119
|
+
);
|
|
105
120
|
if (
|
|
106
121
|
item.source !== "github" ||
|
|
107
122
|
!item.repository ||
|
|
@@ -115,12 +130,8 @@ export default defineAction({
|
|
|
115
130
|
}
|
|
116
131
|
|
|
117
132
|
const configuredRepository = (
|
|
118
|
-
await db
|
|
119
|
-
|
|
120
|
-
.from(triageConfig)
|
|
121
|
-
.where(and(eq(triageConfig.id, orgId), eq(triageConfig.orgId, orgId)))
|
|
122
|
-
.limit(1)
|
|
123
|
-
)[0]?.repository;
|
|
133
|
+
await readTriageConfigRow(db, orgId, factoryId)
|
|
134
|
+
)?.repository;
|
|
124
135
|
if (
|
|
125
136
|
!configuredRepository ||
|
|
126
137
|
configuredRepository.trim() !== item.repository.trim()
|
|
@@ -154,6 +165,7 @@ export default defineAction({
|
|
|
154
165
|
summary: "Skipped because the pull request is closed or a draft.",
|
|
155
166
|
details: { state: pullRequest.state, draft: pullRequest.draft },
|
|
156
167
|
},
|
|
168
|
+
factoryId,
|
|
157
169
|
);
|
|
158
170
|
return { ok: true, action: "skipped", reason: "PR is closed or draft." };
|
|
159
171
|
}
|
|
@@ -182,6 +194,7 @@ export default defineAction({
|
|
|
182
194
|
summary: `${ownerOwnedArea} is owner-managed; no bot feedback was posted.`,
|
|
183
195
|
details: { ownerOwnedArea },
|
|
184
196
|
},
|
|
197
|
+
factoryId,
|
|
185
198
|
);
|
|
186
199
|
return {
|
|
187
200
|
ok: true,
|
|
@@ -257,6 +270,7 @@ export default defineAction({
|
|
|
257
270
|
reviewFeedbackClean: signal.isClean,
|
|
258
271
|
},
|
|
259
272
|
},
|
|
273
|
+
factoryId,
|
|
260
274
|
);
|
|
261
275
|
const now = new Date();
|
|
262
276
|
const nowIso = now.toISOString();
|
|
@@ -326,6 +340,7 @@ export default defineAction({
|
|
|
326
340
|
summary: "Posted the bounded feedback-fix request to builder-io-bot.",
|
|
327
341
|
details: { commentUrl: comment.htmlUrl, quietForMinutes: 0 },
|
|
328
342
|
},
|
|
343
|
+
factoryId,
|
|
329
344
|
);
|
|
330
345
|
await updateBabysitMetadata(itemId, orgId, {
|
|
331
346
|
prBabysitState: "active",
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core/action";
|
|
2
|
+
import { buildDeepLink } from "@agent-native/core/server";
|
|
3
|
+
import { and, eq } from "drizzle-orm";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
import { getDb } from "../server/db/index.js";
|
|
7
|
+
import {
|
|
8
|
+
factoryDefinitions,
|
|
9
|
+
factoryGraphVersions,
|
|
10
|
+
triageConfig,
|
|
11
|
+
} from "../server/db/schema.js";
|
|
12
|
+
import {
|
|
13
|
+
minimalFactoryGraph,
|
|
14
|
+
normalizeFactoryGraph,
|
|
15
|
+
} from "../server/factory-graph/contracts.js";
|
|
16
|
+
import {
|
|
17
|
+
resolveEnabledAutomations,
|
|
18
|
+
isFactoryIdConflict,
|
|
19
|
+
isFactorySlackChannelConflict,
|
|
20
|
+
} from "../server/lib/factory-automation-plan.js";
|
|
21
|
+
import {
|
|
22
|
+
assertUniqueSlackChannelForFactory,
|
|
23
|
+
factoryConfigRowId,
|
|
24
|
+
resolveUniqueFactoryId,
|
|
25
|
+
} from "../server/lib/factory-scope.js";
|
|
26
|
+
import {
|
|
27
|
+
requireWorkspaceMember,
|
|
28
|
+
workspaceMemberIdentityFromContext,
|
|
29
|
+
} from "../server/lib/require-workspace-member.js";
|
|
30
|
+
import {
|
|
31
|
+
ensureFactoryAutomations,
|
|
32
|
+
removeFactoryAutomationResources,
|
|
33
|
+
syncFactoryAutomationEnabledStates,
|
|
34
|
+
} from "../server/plugins/factory-scheduler-job.js";
|
|
35
|
+
import { stableId } from "../server/triage/ids.js";
|
|
36
|
+
|
|
37
|
+
export default defineAction({
|
|
38
|
+
description:
|
|
39
|
+
"Create a new Factory with a minimal blueprint, optional observation settings, and seeded automations. Name is required; sources and automations are optional.",
|
|
40
|
+
schema: z.object({
|
|
41
|
+
name: z.string().trim().min(1).max(120),
|
|
42
|
+
description: z.string().trim().max(500).optional(),
|
|
43
|
+
slackChannelId: z.string().trim().max(128).optional(),
|
|
44
|
+
slackChannelName: z.string().trim().max(200).optional(),
|
|
45
|
+
observeSlack: z.boolean().optional(),
|
|
46
|
+
repository: z.string().trim().max(256).optional(),
|
|
47
|
+
observeGithub: z.boolean().optional(),
|
|
48
|
+
sentryOrgSlug: z.string().trim().max(200).optional(),
|
|
49
|
+
sentryProjectSlug: z.string().trim().max(200).optional(),
|
|
50
|
+
sentryEnvironment: z.string().trim().max(200).optional(),
|
|
51
|
+
observeSentry: z.boolean().optional(),
|
|
52
|
+
}),
|
|
53
|
+
http: { method: "POST" },
|
|
54
|
+
link: ({ result }) => ({
|
|
55
|
+
url: buildDeepLink({
|
|
56
|
+
app: "factory",
|
|
57
|
+
view: "factory",
|
|
58
|
+
params: { factoryId: result.factoryId },
|
|
59
|
+
}),
|
|
60
|
+
label: `Open ${result.name} in Factory`,
|
|
61
|
+
}),
|
|
62
|
+
run: async (input, context) => {
|
|
63
|
+
const { userEmail, orgId } = await requireWorkspaceMember(
|
|
64
|
+
workspaceMemberIdentityFromContext(context),
|
|
65
|
+
);
|
|
66
|
+
const db = getDb();
|
|
67
|
+
const description = input.description?.trim() ?? "";
|
|
68
|
+
const graph = normalizeFactoryGraph(
|
|
69
|
+
minimalFactoryGraph(input.name, description),
|
|
70
|
+
);
|
|
71
|
+
const now = new Date().toISOString();
|
|
72
|
+
const automationPlan = resolveEnabledAutomations({
|
|
73
|
+
observeSlack: input.observeSlack === true,
|
|
74
|
+
slackChannelId: input.slackChannelId,
|
|
75
|
+
observeGithub: input.observeGithub === true,
|
|
76
|
+
repository: input.repository,
|
|
77
|
+
observeSentry: input.observeSentry === true,
|
|
78
|
+
sentryOrgSlug: input.sentryOrgSlug,
|
|
79
|
+
sentryProjectSlug: input.sentryProjectSlug,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const MAX_FACTORY_CREATE_ATTEMPTS = 5;
|
|
83
|
+
let factoryId = "";
|
|
84
|
+
for (let attempt = 0; attempt < MAX_FACTORY_CREATE_ATTEMPTS; attempt++) {
|
|
85
|
+
factoryId = await resolveUniqueFactoryId(db, orgId, input.name);
|
|
86
|
+
const versionId = stableId("factory-graph", orgId, factoryId, "1");
|
|
87
|
+
try {
|
|
88
|
+
await db.transaction(async (tx) => {
|
|
89
|
+
await tx.insert(factoryDefinitions).values({
|
|
90
|
+
id: factoryId,
|
|
91
|
+
name: input.name,
|
|
92
|
+
description,
|
|
93
|
+
prompt: "",
|
|
94
|
+
graphVersion: 1,
|
|
95
|
+
graphJson: JSON.stringify(graph),
|
|
96
|
+
createdAt: now,
|
|
97
|
+
updatedAt: now,
|
|
98
|
+
ownerEmail: userEmail,
|
|
99
|
+
orgId,
|
|
100
|
+
});
|
|
101
|
+
await tx.insert(factoryGraphVersions).values({
|
|
102
|
+
id: versionId,
|
|
103
|
+
factoryId,
|
|
104
|
+
version: 1,
|
|
105
|
+
graphJson: JSON.stringify(graph),
|
|
106
|
+
source: "manual",
|
|
107
|
+
changeSummary: "Created from the new factory dialog.",
|
|
108
|
+
createdAt: now,
|
|
109
|
+
createdBy: userEmail,
|
|
110
|
+
ownerEmail: userEmail,
|
|
111
|
+
orgId,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (automationPlan.hasConfig) {
|
|
115
|
+
const slackChannelId = input.slackChannelId?.trim() || null;
|
|
116
|
+
await assertUniqueSlackChannelForFactory(
|
|
117
|
+
tx as unknown as typeof db,
|
|
118
|
+
orgId,
|
|
119
|
+
factoryId,
|
|
120
|
+
slackChannelId,
|
|
121
|
+
);
|
|
122
|
+
await tx.insert(triageConfig).values({
|
|
123
|
+
id: factoryConfigRowId(orgId, factoryId),
|
|
124
|
+
factoryId,
|
|
125
|
+
slackWorkspace: "primary",
|
|
126
|
+
slackChannelId,
|
|
127
|
+
slackChannelName: input.slackChannelName?.trim() || null,
|
|
128
|
+
builderSlackUserId: null,
|
|
129
|
+
pollingEnabled: automationPlan.pollingEnabled ? 1 : 0,
|
|
130
|
+
githubPollingEnabled: automationPlan.githubPollingEnabled ? 1 : 0,
|
|
131
|
+
sentryPollingEnabled: automationPlan.sentryPollingEnabled ? 1 : 0,
|
|
132
|
+
sentryOrgSlug: input.sentryOrgSlug?.trim() || null,
|
|
133
|
+
sentryProjectSlug: input.sentryProjectSlug?.trim() || null,
|
|
134
|
+
sentryEnvironment: input.sentryEnvironment?.trim() || null,
|
|
135
|
+
repository: input.repository?.trim() || null,
|
|
136
|
+
automationFailureAlertsEnabled: 1,
|
|
137
|
+
automationFailureAlertEmail: null,
|
|
138
|
+
createdAt: now,
|
|
139
|
+
updatedAt: now,
|
|
140
|
+
ownerEmail: userEmail,
|
|
141
|
+
orgId,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
break;
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (isFactorySlackChannelConflict(error)) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
"That Slack channel is already used by another Factory in this workspace.",
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (
|
|
153
|
+
isFactoryIdConflict(error) &&
|
|
154
|
+
attempt < MAX_FACTORY_CREATE_ATTEMPTS - 1
|
|
155
|
+
) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (!factoryId) {
|
|
162
|
+
throw new Error("Could not allocate a unique factory id.");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
await ensureFactoryAutomations(userEmail, orgId, factoryId, {
|
|
167
|
+
enabledNames: automationPlan.enabledNames,
|
|
168
|
+
});
|
|
169
|
+
if (automationPlan.enabledNames.size > 0) {
|
|
170
|
+
await syncFactoryAutomationEnabledStates(userEmail, orgId, factoryId, [
|
|
171
|
+
...automationPlan.enabledNames,
|
|
172
|
+
]);
|
|
173
|
+
}
|
|
174
|
+
} catch (error) {
|
|
175
|
+
await removeFactoryAutomationResources(orgId, factoryId);
|
|
176
|
+
await db
|
|
177
|
+
.delete(triageConfig)
|
|
178
|
+
.where(
|
|
179
|
+
and(
|
|
180
|
+
eq(triageConfig.orgId, orgId),
|
|
181
|
+
eq(triageConfig.factoryId, factoryId),
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
await db
|
|
185
|
+
.delete(factoryGraphVersions)
|
|
186
|
+
.where(
|
|
187
|
+
and(
|
|
188
|
+
eq(factoryGraphVersions.orgId, orgId),
|
|
189
|
+
eq(factoryGraphVersions.factoryId, factoryId),
|
|
190
|
+
),
|
|
191
|
+
);
|
|
192
|
+
await db
|
|
193
|
+
.delete(factoryDefinitions)
|
|
194
|
+
.where(
|
|
195
|
+
and(
|
|
196
|
+
eq(factoryDefinitions.id, factoryId),
|
|
197
|
+
eq(factoryDefinitions.orgId, orgId),
|
|
198
|
+
),
|
|
199
|
+
);
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
ok: true,
|
|
205
|
+
factoryId,
|
|
206
|
+
name: input.name,
|
|
207
|
+
graphVersion: 1,
|
|
208
|
+
enabledAutomations: [...automationPlan.enabledNames],
|
|
209
|
+
};
|
|
210
|
+
},
|
|
211
|
+
});
|