@agent-native/core 0.168.7 → 0.168.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/actions/mutate-dashboard.ts +8 -1
- package/corpus/templates/calendar/actions/delete-event.ts +34 -18
- package/corpus/templates/calendar/actions/delete-events.ts +13 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +22 -3
- package/corpus/templates/calendar/app/lib/calendar-timezone.ts +19 -0
- package/corpus/templates/calendar/server/lib/google-api.ts +18 -1
- package/corpus/templates/clips/actions/finalize-meeting.ts +16 -0
- package/corpus/templates/clips/actions/list-meetings.ts +1 -0
- package/corpus/templates/clips/actions/sync-calendars.ts +10 -29
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +28 -12
- package/corpus/templates/clips/desktop/src/lib/meeting-stop.ts +16 -0
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +19 -9
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +1 -0
- package/corpus/templates/content/app/global.css +5 -0
- package/corpus/templates/content/docs/solutions/2026-08-20-code-block-slash-production-regression-shape.md +492 -0
- package/corpus/templates/content/docs/solutions/2026-08-20-code-block-surface-background-shape.md +273 -0
- package/corpus/templates/factory/.agents/skills/factory-graphs/SKILL.md +5 -0
- package/corpus/templates/factory/AGENTS.md +12 -22
- package/corpus/templates/factory/README.md +11 -8
- package/corpus/templates/factory/actions/approve-factory-item.ts +35 -8
- package/corpus/templates/factory/actions/babysit-agent-native-pull-request.ts +24 -9
- package/corpus/templates/factory/actions/create-factory.ts +211 -0
- package/corpus/templates/factory/actions/evaluate-triage-item.ts +29 -5
- package/corpus/templates/factory/actions/get-factory-graph.ts +1 -1
- package/corpus/templates/factory/actions/get-slack-feedback-context.ts +20 -11
- package/corpus/templates/factory/actions/get-triage-config.ts +13 -12
- package/corpus/templates/factory/actions/get-triage-item.ts +28 -6
- package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +33 -17
- package/corpus/templates/factory/actions/ingest-github-observation.ts +10 -4
- package/corpus/templates/factory/actions/list-factory-audit.ts +11 -4
- package/corpus/templates/factory/actions/list-factory-automations.ts +34 -12
- package/corpus/templates/factory/actions/list-triage-items.ts +11 -3
- package/corpus/templates/factory/actions/list-triage-rules.ts +11 -4
- package/corpus/templates/factory/actions/poll-github-sources.ts +24 -10
- package/corpus/templates/factory/actions/poll-sentry-errors.ts +26 -11
- package/corpus/templates/factory/actions/poll-slack-channel.ts +31 -13
- package/corpus/templates/factory/actions/reconcile-triage-run.ts +59 -9
- package/corpus/templates/factory/actions/record-triage-feedback.ts +14 -3
- package/corpus/templates/factory/actions/run-factory-automation.ts +12 -2
- package/corpus/templates/factory/actions/save-factory-automation.ts +82 -21
- package/corpus/templates/factory/actions/save-factory-graph.ts +10 -1
- package/corpus/templates/factory/actions/save-triage-config.ts +90 -36
- package/corpus/templates/factory/actions/save-triage-rule.ts +16 -3
- package/corpus/templates/factory/actions/start-builder-for-item.ts +28 -20
- package/corpus/templates/factory/actions/suggest-factory-rules.ts +11 -4
- package/corpus/templates/factory/actions/view-screen.ts +22 -30
- package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +7 -2
- package/corpus/templates/factory/app/components/factory/FactoryWorkspaceActions.tsx +7 -22
- package/corpus/templates/factory/app/components/factory/NewFactoryForm.tsx +260 -0
- package/corpus/templates/factory/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +2 -1
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +3 -3
- package/corpus/templates/factory/app/i18n/en-US.ts +18 -1
- package/corpus/templates/factory/app/routes/factory.tsx +78 -62
- package/corpus/templates/factory/app/routes/{factory-settings.tsx → new-factory.tsx} +12 -9
- package/corpus/templates/factory/server/db/schema.ts +18 -3
- package/corpus/templates/factory/server/factory-graph/contracts.ts +24 -0
- package/corpus/templates/factory/server/factory-graph/store.ts +16 -9
- package/corpus/templates/factory/server/lib/factory-automation-plan.ts +136 -0
- package/corpus/templates/factory/server/lib/factory-automation-repair.ts +31 -0
- package/corpus/templates/factory/server/lib/factory-scope.ts +416 -0
- package/corpus/templates/factory/server/lib/require-factory-automation.ts +11 -0
- package/corpus/templates/factory/server/plugins/agent-chat.ts +4 -2
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +80 -0
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +142 -43
- package/corpus/templates/factory/server/triage/audit.ts +4 -0
- package/corpus/templates/factory/server/triage/ids.ts +4 -0
- package/dist/cli/native-dependencies.js +4 -1
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.js +9 -4
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat/index.js +1 -1
- package/dist/client/chat-first/apps-rail.js +6 -6
- package/dist/client/chat-first/primary-nav.js +14 -6
- package/dist/client/integrations/IntegrationsPanel.d.ts +15 -0
- package/dist/client/integrations/IntegrationsPanel.js +29 -21
- package/dist/client/integrations/index.d.ts +1 -1
- package/dist/client/integrations/index.js +1 -1
- package/dist/client/resources/McpIntegrationDialog.js +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.js +10 -3
- package/package.json +1 -1
|
@@ -4,6 +4,11 @@ import { z } from "zod";
|
|
|
4
4
|
|
|
5
5
|
import { getDb } from "../server/db/index.js";
|
|
6
6
|
import { triageItems, triageRuns } from "../server/db/schema.js";
|
|
7
|
+
import { DEFAULT_FACTORY_ID } from "../server/factory-graph/store.js";
|
|
8
|
+
import {
|
|
9
|
+
factoryIdSchema,
|
|
10
|
+
orgFactoryScopedItemWhere,
|
|
11
|
+
} from "../server/lib/factory-scope.js";
|
|
7
12
|
import {
|
|
8
13
|
requireWorkspaceMember,
|
|
9
14
|
workspaceMemberIdentityFromContext,
|
|
@@ -48,6 +53,7 @@ export default defineAction({
|
|
|
48
53
|
"Reconcile an observe-only pull-request monitoring run from ai-services callback and provider observations. Missing callbacks or provider reads remain typed failure states; no executor or GitHub write occurs.",
|
|
49
54
|
schema: z.object({
|
|
50
55
|
itemId: z.string().min(1),
|
|
56
|
+
factoryId: factoryIdSchema.optional(),
|
|
51
57
|
runId: z.string().min(1),
|
|
52
58
|
source: triageSourceSchema.default("github"),
|
|
53
59
|
callback: z
|
|
@@ -70,6 +76,7 @@ export default defineAction({
|
|
|
70
76
|
run: async (
|
|
71
77
|
{
|
|
72
78
|
itemId,
|
|
79
|
+
factoryId: factoryIdInput,
|
|
73
80
|
runId,
|
|
74
81
|
source,
|
|
75
82
|
callback,
|
|
@@ -82,8 +89,25 @@ export default defineAction({
|
|
|
82
89
|
const { userEmail, orgId } = await requireWorkspaceMember(
|
|
83
90
|
workspaceMemberIdentityFromContext(context),
|
|
84
91
|
);
|
|
92
|
+
const db = getDb();
|
|
93
|
+
const item = (
|
|
94
|
+
await db
|
|
95
|
+
.select({ factoryId: triageItems.factoryId })
|
|
96
|
+
.from(triageItems)
|
|
97
|
+
.where(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)))
|
|
98
|
+
.limit(1)
|
|
99
|
+
)[0];
|
|
100
|
+
if (!item)
|
|
101
|
+
throw new Error("Factory item not found for run reconciliation.");
|
|
102
|
+
const itemFactoryId = item.factoryId ?? DEFAULT_FACTORY_ID;
|
|
103
|
+
if (factoryIdInput && factoryIdInput !== itemFactoryId) {
|
|
104
|
+
throw new Error("Factory item does not belong to this factory.");
|
|
105
|
+
}
|
|
106
|
+
const factoryId = itemFactoryId;
|
|
85
107
|
const now = new Date().toISOString();
|
|
86
|
-
const
|
|
108
|
+
const scopedRunId = stableId("run", orgId, factoryId, runId);
|
|
109
|
+
const legacyRunId =
|
|
110
|
+
factoryId === DEFAULT_FACTORY_ID ? stableId("run", orgId, runId) : null;
|
|
87
111
|
const result = reconcilePullRequestRun({
|
|
88
112
|
triageItemId: itemId,
|
|
89
113
|
runId,
|
|
@@ -93,17 +117,42 @@ export default defineAction({
|
|
|
93
117
|
now,
|
|
94
118
|
timeoutMs,
|
|
95
119
|
});
|
|
96
|
-
const db = getDb();
|
|
97
120
|
const existingRun = (
|
|
98
121
|
await db
|
|
99
|
-
.select({
|
|
122
|
+
.select({
|
|
123
|
+
id: triageRuns.id,
|
|
124
|
+
progressLogJson: triageRuns.progressLogJson,
|
|
125
|
+
factoryId: triageRuns.factoryId,
|
|
126
|
+
})
|
|
100
127
|
.from(triageRuns)
|
|
101
|
-
.where(
|
|
102
|
-
and(eq(triageRuns.id, databaseRunId), eq(triageRuns.orgId, orgId)),
|
|
103
|
-
)
|
|
128
|
+
.where(and(eq(triageRuns.id, scopedRunId), eq(triageRuns.orgId, orgId)))
|
|
104
129
|
.limit(1)
|
|
105
130
|
)[0];
|
|
106
|
-
|
|
131
|
+
let databaseRunId = scopedRunId;
|
|
132
|
+
let existing = existingRun;
|
|
133
|
+
if (!existing && legacyRunId) {
|
|
134
|
+
const legacyRun = (
|
|
135
|
+
await db
|
|
136
|
+
.select({
|
|
137
|
+
id: triageRuns.id,
|
|
138
|
+
progressLogJson: triageRuns.progressLogJson,
|
|
139
|
+
factoryId: triageRuns.factoryId,
|
|
140
|
+
})
|
|
141
|
+
.from(triageRuns)
|
|
142
|
+
.where(
|
|
143
|
+
and(eq(triageRuns.id, legacyRunId), eq(triageRuns.orgId, orgId)),
|
|
144
|
+
)
|
|
145
|
+
.limit(1)
|
|
146
|
+
)[0];
|
|
147
|
+
if (legacyRun) {
|
|
148
|
+
existing = legacyRun;
|
|
149
|
+
databaseRunId = legacyRunId;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (existing && (existing.factoryId ?? DEFAULT_FACTORY_ID) !== factoryId) {
|
|
153
|
+
throw new Error("Run id is already used by another factory.");
|
|
154
|
+
}
|
|
155
|
+
const progressLog = appendProgressLog(existing?.progressLogJson, {
|
|
107
156
|
at: now,
|
|
108
157
|
state: result.state,
|
|
109
158
|
reason: result.reason,
|
|
@@ -131,6 +180,7 @@ export default defineAction({
|
|
|
131
180
|
: null,
|
|
132
181
|
ownerEmail: userEmail,
|
|
133
182
|
orgId,
|
|
183
|
+
factoryId,
|
|
134
184
|
})
|
|
135
185
|
.onConflictDoUpdate({
|
|
136
186
|
target: triageRuns.id,
|
|
@@ -154,12 +204,12 @@ export default defineAction({
|
|
|
154
204
|
await tx
|
|
155
205
|
.update(triageItems)
|
|
156
206
|
.set(result.triageItemPatch)
|
|
157
|
-
.where(
|
|
207
|
+
.where(orgFactoryScopedItemWhere(itemId, orgId, factoryId));
|
|
158
208
|
} else if (result.state === "reconciliation_required") {
|
|
159
209
|
await tx
|
|
160
210
|
.update(triageItems)
|
|
161
211
|
.set({ status: "reconciliation_required", updatedAt: now })
|
|
162
|
-
.where(
|
|
212
|
+
.where(orgFactoryScopedItemWhere(itemId, orgId, factoryId));
|
|
163
213
|
}
|
|
164
214
|
});
|
|
165
215
|
|
|
@@ -6,6 +6,11 @@ import { z } from "zod";
|
|
|
6
6
|
|
|
7
7
|
import { getDb } from "../server/db/index.js";
|
|
8
8
|
import { triageDecisions, triageFeedback } from "../server/db/schema.js";
|
|
9
|
+
import { DEFAULT_FACTORY_ID } from "../server/factory-graph/store.js";
|
|
10
|
+
import {
|
|
11
|
+
factoryIdSchema,
|
|
12
|
+
orgFactoryDecisionFilter,
|
|
13
|
+
} from "../server/lib/factory-scope.js";
|
|
9
14
|
import {
|
|
10
15
|
requireWorkspaceMember,
|
|
11
16
|
workspaceMemberIdentityFromContext,
|
|
@@ -18,27 +23,32 @@ export default defineAction({
|
|
|
18
23
|
"Record human feedback on a Factory shadow decision. Feedback is append-only and changes no provider or executor state.",
|
|
19
24
|
schema: z.object({
|
|
20
25
|
decisionId: z.string().min(1),
|
|
26
|
+
factoryId: factoryIdSchema.default(DEFAULT_FACTORY_ID),
|
|
21
27
|
verdict: verdictSchema,
|
|
22
28
|
note: z.string().trim().max(2_000).optional(),
|
|
23
29
|
}),
|
|
24
30
|
http: { method: "POST" },
|
|
25
|
-
run: async ({ decisionId, verdict, note }, context) => {
|
|
31
|
+
run: async ({ decisionId, factoryId, verdict, note }, context) => {
|
|
26
32
|
const { userEmail, orgId } = await requireWorkspaceMember(
|
|
27
33
|
workspaceMemberIdentityFromContext(context),
|
|
28
34
|
);
|
|
29
35
|
const decision = (
|
|
30
36
|
await getDb()
|
|
31
|
-
.select({
|
|
37
|
+
.select({
|
|
38
|
+
id: triageDecisions.id,
|
|
39
|
+
factoryId: triageDecisions.factoryId,
|
|
40
|
+
})
|
|
32
41
|
.from(triageDecisions)
|
|
33
42
|
.where(
|
|
34
43
|
and(
|
|
35
44
|
eq(triageDecisions.id, decisionId),
|
|
36
|
-
|
|
45
|
+
orgFactoryDecisionFilter(orgId, factoryId),
|
|
37
46
|
),
|
|
38
47
|
)
|
|
39
48
|
.limit(1)
|
|
40
49
|
)[0];
|
|
41
50
|
if (!decision) throw new Error("Triage decision not found");
|
|
51
|
+
const resolvedFactoryId = decision.factoryId ?? factoryId;
|
|
42
52
|
|
|
43
53
|
const createdAt = new Date().toISOString();
|
|
44
54
|
const id = randomUUID();
|
|
@@ -52,6 +62,7 @@ export default defineAction({
|
|
|
52
62
|
createdAt,
|
|
53
63
|
ownerEmail: userEmail,
|
|
54
64
|
orgId,
|
|
65
|
+
factoryId: resolvedFactoryId,
|
|
55
66
|
});
|
|
56
67
|
return { ok: true, id };
|
|
57
68
|
},
|
|
@@ -5,6 +5,10 @@ import {
|
|
|
5
5
|
} from "@agent-native/core/triggers";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
|
|
8
|
+
import {
|
|
9
|
+
factoryIdSchema,
|
|
10
|
+
readAutomationFactoryId,
|
|
11
|
+
} from "../server/lib/factory-scope.js";
|
|
8
12
|
import {
|
|
9
13
|
requireWorkspaceMember,
|
|
10
14
|
workspaceMemberIdentityFromContext,
|
|
@@ -15,11 +19,11 @@ export default defineAction({
|
|
|
15
19
|
"Queue one organization-scoped Factory automation for an immediate run and return its durable run id.",
|
|
16
20
|
agentTool: false,
|
|
17
21
|
schema: z.object({
|
|
18
|
-
factoryId:
|
|
22
|
+
factoryId: factoryIdSchema,
|
|
19
23
|
automationId: z.string().trim().min(1),
|
|
20
24
|
}),
|
|
21
25
|
http: { method: "POST" },
|
|
22
|
-
run: async ({ automationId }, context) => {
|
|
26
|
+
run: async ({ factoryId, automationId }, context) => {
|
|
23
27
|
const { userEmail, orgId } = await requireWorkspaceMember(
|
|
24
28
|
workspaceMemberIdentityFromContext(context),
|
|
25
29
|
);
|
|
@@ -32,6 +36,12 @@ export default defineAction({
|
|
|
32
36
|
entry.meta.domain === "factory" && entry.resource.id === automationId,
|
|
33
37
|
);
|
|
34
38
|
if (!definition) throw new Error("Factory automation not found.");
|
|
39
|
+
if (
|
|
40
|
+
readAutomationFactoryId(definition.meta, definition.resource.content) !==
|
|
41
|
+
factoryId
|
|
42
|
+
) {
|
|
43
|
+
throw new Error("Factory automation not found.");
|
|
44
|
+
}
|
|
35
45
|
return queueAutomationRunNow({
|
|
36
46
|
userEmail,
|
|
37
47
|
orgId,
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
|
+
import { isValidCron, nextOccurrence } from "@agent-native/core/jobs";
|
|
2
3
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "@agent-native/core/
|
|
4
|
+
resourceGetByPath,
|
|
5
|
+
resourcePutIfCurrent,
|
|
6
|
+
} from "@agent-native/core/resources";
|
|
7
|
+
import { listAutomationDefinitions } from "@agent-native/core/triggers";
|
|
6
8
|
import { z } from "zod";
|
|
7
9
|
|
|
10
|
+
import {
|
|
11
|
+
factoryIdSchema,
|
|
12
|
+
patchAutomationResource,
|
|
13
|
+
readAutomationEnabled,
|
|
14
|
+
readAutomationFactoryId,
|
|
15
|
+
readAutomationModel,
|
|
16
|
+
readAutomationSchedule,
|
|
17
|
+
resolveAutomationDisplayName,
|
|
18
|
+
setAutomationFrontmatterField,
|
|
19
|
+
} from "../server/lib/factory-scope.js";
|
|
8
20
|
import {
|
|
9
21
|
requireWorkspaceMember,
|
|
10
22
|
workspaceMemberIdentityFromContext,
|
|
@@ -12,12 +24,13 @@ import {
|
|
|
12
24
|
|
|
13
25
|
export default defineAction({
|
|
14
26
|
description:
|
|
15
|
-
"Edit a Factory automation's prompt, model, schedule, or enabled state in its organization-owned markdown resource.",
|
|
27
|
+
"Edit a Factory automation's display name, prompt, model, schedule, or enabled state in its organization-owned markdown resource.",
|
|
16
28
|
agentTool: false,
|
|
17
29
|
schema: z.object({
|
|
18
|
-
factoryId:
|
|
30
|
+
factoryId: factoryIdSchema,
|
|
19
31
|
automationId: z.string().trim().min(1),
|
|
20
32
|
name: z.string().trim().min(1).max(120),
|
|
33
|
+
displayName: z.string().trim().max(120).optional(),
|
|
21
34
|
prompt: z.string().trim().min(1).max(20_000),
|
|
22
35
|
model: z.string().trim().max(200).optional(),
|
|
23
36
|
schedule: z.string().trim().min(1).max(100),
|
|
@@ -25,7 +38,16 @@ export default defineAction({
|
|
|
25
38
|
}),
|
|
26
39
|
http: { method: "POST" },
|
|
27
40
|
run: async (
|
|
28
|
-
{
|
|
41
|
+
{
|
|
42
|
+
factoryId,
|
|
43
|
+
automationId,
|
|
44
|
+
name,
|
|
45
|
+
displayName,
|
|
46
|
+
prompt,
|
|
47
|
+
model,
|
|
48
|
+
schedule,
|
|
49
|
+
enabled,
|
|
50
|
+
},
|
|
29
51
|
context,
|
|
30
52
|
) => {
|
|
31
53
|
const { userEmail, orgId } = await requireWorkspaceMember(
|
|
@@ -40,30 +62,69 @@ export default defineAction({
|
|
|
40
62
|
entry.meta.domain === "factory" && entry.resource.id === automationId,
|
|
41
63
|
);
|
|
42
64
|
if (!definition) throw new Error("Factory automation not found.");
|
|
65
|
+
if (
|
|
66
|
+
readAutomationFactoryId(definition.meta, definition.resource.content) !==
|
|
67
|
+
factoryId
|
|
68
|
+
) {
|
|
69
|
+
throw new Error("Factory automation not found.");
|
|
70
|
+
}
|
|
43
71
|
if (definition.name !== name) {
|
|
44
72
|
throw new Error(
|
|
45
73
|
"Factory automation id and name do not refer to the same automation.",
|
|
46
74
|
);
|
|
47
75
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
76
|
+
if (!definition.canUpdate) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"Only the automation's creator or an organization admin can update it.",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (definition.meta.triggerType === "schedule" && !isValidCron(schedule)) {
|
|
82
|
+
throw new Error(`Invalid cron expression "${schedule}".`);
|
|
83
|
+
}
|
|
84
|
+
const resource = await resourceGetByPath(
|
|
85
|
+
definition.resource.owner,
|
|
86
|
+
definition.resource.path,
|
|
58
87
|
);
|
|
88
|
+
if (!resource) throw new Error("Factory automation not found.");
|
|
89
|
+
let content = patchAutomationResource(resource.content, {
|
|
90
|
+
body: prompt,
|
|
91
|
+
enabled,
|
|
92
|
+
schedule,
|
|
93
|
+
model: model?.trim() || null,
|
|
94
|
+
...(displayName !== undefined ? { displayName } : {}),
|
|
95
|
+
});
|
|
96
|
+
content = setAutomationFrontmatterField(content, "factoryId", factoryId);
|
|
97
|
+
if (definition.meta.triggerType === "schedule" && isValidCron(schedule)) {
|
|
98
|
+
const nextRun = nextOccurrence(
|
|
99
|
+
schedule,
|
|
100
|
+
undefined,
|
|
101
|
+
definition.meta.timezone,
|
|
102
|
+
).toISOString();
|
|
103
|
+
content = setAutomationFrontmatterField(content, "nextRun", nextRun);
|
|
104
|
+
}
|
|
105
|
+
const updated = await resourcePutIfCurrent({
|
|
106
|
+
owner: definition.resource.owner,
|
|
107
|
+
path: definition.resource.path,
|
|
108
|
+
content,
|
|
109
|
+
mimeType: "text/markdown",
|
|
110
|
+
expectedId: resource.id,
|
|
111
|
+
expectedUpdatedAt: resource.updatedAt,
|
|
112
|
+
expectedContent: resource.content,
|
|
113
|
+
});
|
|
114
|
+
if (!updated) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
"Factory automation changed concurrently. Refresh and try again.",
|
|
117
|
+
);
|
|
118
|
+
}
|
|
59
119
|
return {
|
|
60
120
|
ok: true,
|
|
61
121
|
id: definition.resource.id,
|
|
62
|
-
name:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
122
|
+
name: definition.name,
|
|
123
|
+
displayName: resolveAutomationDisplayName(definition.name, content),
|
|
124
|
+
prompt,
|
|
125
|
+
model: readAutomationModel(content),
|
|
126
|
+
schedule: readAutomationSchedule(content),
|
|
127
|
+
enabled: readAutomationEnabled(content),
|
|
67
128
|
};
|
|
68
129
|
},
|
|
69
130
|
});
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
requireWorkspaceMember,
|
|
18
18
|
workspaceMemberIdentityFromContext,
|
|
19
19
|
} from "../server/lib/require-workspace-member.js";
|
|
20
|
+
import { ensureFactoryAutomations } from "../server/plugins/factory-scheduler-job.js";
|
|
20
21
|
import { stableId } from "../server/triage/ids.js";
|
|
21
22
|
|
|
22
23
|
export default defineAction({
|
|
@@ -63,8 +64,9 @@ export default defineAction({
|
|
|
63
64
|
workspaceMemberIdentityFromContext(context),
|
|
64
65
|
);
|
|
65
66
|
const db = getDb();
|
|
67
|
+
let createdNewFactory = false;
|
|
66
68
|
|
|
67
|
-
|
|
69
|
+
const result = await db.transaction(async (tx) => {
|
|
68
70
|
const existing = (
|
|
69
71
|
await tx
|
|
70
72
|
.select()
|
|
@@ -123,6 +125,7 @@ export default defineAction({
|
|
|
123
125
|
);
|
|
124
126
|
}
|
|
125
127
|
} else {
|
|
128
|
+
createdNewFactory = true;
|
|
126
129
|
await tx.insert(factoryDefinitions).values({
|
|
127
130
|
id: factoryId,
|
|
128
131
|
name,
|
|
@@ -158,5 +161,11 @@ export default defineAction({
|
|
|
158
161
|
source,
|
|
159
162
|
};
|
|
160
163
|
});
|
|
164
|
+
if (createdNewFactory) {
|
|
165
|
+
await ensureFactoryAutomations(userEmail, orgId, factoryId, {
|
|
166
|
+
enabled: false,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
161
170
|
},
|
|
162
171
|
});
|
|
@@ -1,20 +1,38 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
|
-
import { and, eq } from "drizzle-orm";
|
|
3
2
|
import { z } from "zod";
|
|
4
3
|
|
|
5
4
|
import { getDb } from "../server/db/index.js";
|
|
6
5
|
import { triageConfig } from "../server/db/schema.js";
|
|
6
|
+
import {
|
|
7
|
+
readFactoryDefinition,
|
|
8
|
+
DEFAULT_FACTORY_ID,
|
|
9
|
+
} from "../server/factory-graph/store.js";
|
|
10
|
+
import {
|
|
11
|
+
isFactorySlackChannelConflict,
|
|
12
|
+
resolveEnabledAutomationsFromSavedConfig,
|
|
13
|
+
} from "../server/lib/factory-automation-plan.js";
|
|
14
|
+
import {
|
|
15
|
+
assertUniqueSlackChannelForFactory,
|
|
16
|
+
factoryConfigRowId,
|
|
17
|
+
factoryIdSchema,
|
|
18
|
+
readTriageConfigRow,
|
|
19
|
+
} from "../server/lib/factory-scope.js";
|
|
7
20
|
import {
|
|
8
21
|
requireWorkspaceMember,
|
|
9
22
|
workspaceMemberIdentityFromContext,
|
|
10
23
|
} from "../server/lib/require-workspace-member.js";
|
|
24
|
+
import {
|
|
25
|
+
ensureFactoryAutomations,
|
|
26
|
+
syncFactoryAutomationEnabledStates,
|
|
27
|
+
} from "../server/plugins/factory-scheduler-job.js";
|
|
11
28
|
|
|
12
29
|
const workspaceSchema = z.enum(["primary", "secondary"]);
|
|
13
30
|
|
|
14
31
|
export default defineAction({
|
|
15
32
|
description:
|
|
16
|
-
"Save Factory observation and automation alert settings. Provider credentials live in shared workspace integrations; source routing metadata including the Builder Slack member id is stored on Factory config.",
|
|
33
|
+
"Save Factory observation and automation alert settings for the selected factory. Provider credentials live in shared workspace integrations; source routing metadata including the Builder Slack member id is stored on Factory config.",
|
|
17
34
|
schema: z.object({
|
|
35
|
+
factoryId: factoryIdSchema.default(DEFAULT_FACTORY_ID),
|
|
18
36
|
slackWorkspace: workspaceSchema.optional(),
|
|
19
37
|
slackChannelId: z.string().trim().max(128).optional(),
|
|
20
38
|
slackChannelName: z.string().trim().max(200).optional(),
|
|
@@ -41,6 +59,7 @@ export default defineAction({
|
|
|
41
59
|
http: { method: "POST" },
|
|
42
60
|
run: async (
|
|
43
61
|
{
|
|
62
|
+
factoryId,
|
|
44
63
|
slackWorkspace,
|
|
45
64
|
slackChannelId,
|
|
46
65
|
slackChannelName,
|
|
@@ -62,13 +81,11 @@ export default defineAction({
|
|
|
62
81
|
);
|
|
63
82
|
const now = new Date().toISOString();
|
|
64
83
|
const db = getDb();
|
|
65
|
-
|
|
66
|
-
await
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
.limit(1)
|
|
71
|
-
)[0];
|
|
84
|
+
if (factoryId !== DEFAULT_FACTORY_ID) {
|
|
85
|
+
const factory = await readFactoryDefinition(orgId, factoryId);
|
|
86
|
+
if (!factory) throw new Error("Factory not found.");
|
|
87
|
+
}
|
|
88
|
+
const existing = await readTriageConfigRow(db, orgId, factoryId);
|
|
72
89
|
const persistText = (
|
|
73
90
|
next: string | undefined,
|
|
74
91
|
previous: string | null | undefined,
|
|
@@ -79,6 +96,12 @@ export default defineAction({
|
|
|
79
96
|
slackChannelId,
|
|
80
97
|
existing?.slackChannelId,
|
|
81
98
|
);
|
|
99
|
+
await assertUniqueSlackChannelForFactory(
|
|
100
|
+
db,
|
|
101
|
+
orgId,
|
|
102
|
+
factoryId,
|
|
103
|
+
persistedSlackChannelId,
|
|
104
|
+
);
|
|
82
105
|
const persistedSlackChannelName = persistText(
|
|
83
106
|
slackChannelName,
|
|
84
107
|
existing?.slackChannelName,
|
|
@@ -130,31 +153,13 @@ export default defineAction({
|
|
|
130
153
|
automationFailureAlertEmail,
|
|
131
154
|
existing?.automationFailureAlertEmail,
|
|
132
155
|
);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
builderSlackUserId: persistedBuilderSlackUserId,
|
|
141
|
-
pollingEnabled: persistedPollingEnabled,
|
|
142
|
-
githubPollingEnabled: persistedGithubPollingEnabled,
|
|
143
|
-
sentryPollingEnabled: persistedSentryPollingEnabled,
|
|
144
|
-
sentryOrgSlug: persistedSentryOrgSlug,
|
|
145
|
-
sentryProjectSlug: persistedSentryProjectSlug,
|
|
146
|
-
sentryEnvironment: persistedSentryEnvironment,
|
|
147
|
-
repository: persistedRepository,
|
|
148
|
-
automationFailureAlertsEnabled: persistedAutomationFailureAlertsEnabled,
|
|
149
|
-
automationFailureAlertEmail: persistedAutomationFailureAlertEmail,
|
|
150
|
-
createdAt: now,
|
|
151
|
-
updatedAt: now,
|
|
152
|
-
ownerEmail: userEmail,
|
|
153
|
-
orgId,
|
|
154
|
-
})
|
|
155
|
-
.onConflictDoUpdate({
|
|
156
|
-
target: triageConfig.id,
|
|
157
|
-
set: {
|
|
156
|
+
const configId = factoryConfigRowId(orgId, factoryId);
|
|
157
|
+
try {
|
|
158
|
+
await db
|
|
159
|
+
.insert(triageConfig)
|
|
160
|
+
.values({
|
|
161
|
+
id: configId,
|
|
162
|
+
factoryId,
|
|
158
163
|
slackWorkspace: persistedSlackWorkspace,
|
|
159
164
|
slackChannelId: persistedSlackChannelId,
|
|
160
165
|
slackChannelName: persistedSlackChannelName,
|
|
@@ -169,12 +174,61 @@ export default defineAction({
|
|
|
169
174
|
automationFailureAlertsEnabled:
|
|
170
175
|
persistedAutomationFailureAlertsEnabled,
|
|
171
176
|
automationFailureAlertEmail: persistedAutomationFailureAlertEmail,
|
|
177
|
+
createdAt: now,
|
|
172
178
|
updatedAt: now,
|
|
173
179
|
ownerEmail: userEmail,
|
|
174
|
-
|
|
175
|
-
|
|
180
|
+
orgId,
|
|
181
|
+
})
|
|
182
|
+
.onConflictDoUpdate({
|
|
183
|
+
target: triageConfig.id,
|
|
184
|
+
set: {
|
|
185
|
+
factoryId,
|
|
186
|
+
slackWorkspace: persistedSlackWorkspace,
|
|
187
|
+
slackChannelId: persistedSlackChannelId,
|
|
188
|
+
slackChannelName: persistedSlackChannelName,
|
|
189
|
+
builderSlackUserId: persistedBuilderSlackUserId,
|
|
190
|
+
pollingEnabled: persistedPollingEnabled,
|
|
191
|
+
githubPollingEnabled: persistedGithubPollingEnabled,
|
|
192
|
+
sentryPollingEnabled: persistedSentryPollingEnabled,
|
|
193
|
+
sentryOrgSlug: persistedSentryOrgSlug,
|
|
194
|
+
sentryProjectSlug: persistedSentryProjectSlug,
|
|
195
|
+
sentryEnvironment: persistedSentryEnvironment,
|
|
196
|
+
repository: persistedRepository,
|
|
197
|
+
automationFailureAlertsEnabled:
|
|
198
|
+
persistedAutomationFailureAlertsEnabled,
|
|
199
|
+
automationFailureAlertEmail: persistedAutomationFailureAlertEmail,
|
|
200
|
+
updatedAt: now,
|
|
201
|
+
ownerEmail: userEmail,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (isFactorySlackChannelConflict(error)) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
"That Slack channel is already used by another Factory in this workspace.",
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const enabledNames = resolveEnabledAutomationsFromSavedConfig({
|
|
214
|
+
pollingEnabled: persistedPollingEnabled,
|
|
215
|
+
githubPollingEnabled: persistedGithubPollingEnabled,
|
|
216
|
+
sentryPollingEnabled: persistedSentryPollingEnabled,
|
|
217
|
+
slackChannelId: persistedSlackChannelId,
|
|
218
|
+
repository: persistedRepository,
|
|
219
|
+
sentryOrgSlug: persistedSentryOrgSlug,
|
|
220
|
+
sentryProjectSlug: persistedSentryProjectSlug,
|
|
221
|
+
});
|
|
222
|
+
await ensureFactoryAutomations(userEmail, orgId, factoryId, {
|
|
223
|
+
enabledNames,
|
|
224
|
+
});
|
|
225
|
+
await syncFactoryAutomationEnabledStates(userEmail, orgId, factoryId, [
|
|
226
|
+
...enabledNames,
|
|
227
|
+
]);
|
|
228
|
+
|
|
176
229
|
return {
|
|
177
230
|
ok: true,
|
|
231
|
+
factoryId,
|
|
178
232
|
pollingEnabled: persistedPollingEnabled === 1,
|
|
179
233
|
githubPollingEnabled: persistedGithubPollingEnabled === 1,
|
|
180
234
|
sentryPollingEnabled: persistedSentryPollingEnabled === 1,
|
|
@@ -4,6 +4,11 @@ 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,
|
|
@@ -20,6 +25,7 @@ export default defineAction({
|
|
|
20
25
|
description:
|
|
21
26
|
"Create or update a Factory rule. Disabled and shadow modes are accepted; execution still requires an explicit approval action.",
|
|
22
27
|
schema: z.object({
|
|
28
|
+
factoryId: factoryIdSchema.default(DEFAULT_FACTORY_ID),
|
|
23
29
|
id: z.string().min(1).optional(),
|
|
24
30
|
name: z.string().trim().min(1).max(120),
|
|
25
31
|
description: z.string().trim().max(500).default(""),
|
|
@@ -30,19 +36,24 @@ export default defineAction({
|
|
|
30
36
|
}),
|
|
31
37
|
http: { method: "POST" },
|
|
32
38
|
run: async (
|
|
33
|
-
{ id, name, description, promptText, mode, enabled, guards },
|
|
39
|
+
{ factoryId, id, name, description, promptText, mode, enabled, guards },
|
|
34
40
|
context,
|
|
35
41
|
) => {
|
|
36
42
|
const { userEmail, orgId } = await requireWorkspaceMember(
|
|
37
43
|
workspaceMemberIdentityFromContext(context),
|
|
38
44
|
);
|
|
39
45
|
const now = new Date().toISOString();
|
|
40
|
-
const ruleId = id ?? stableId("rule", orgId, name);
|
|
46
|
+
const ruleId = id ?? stableId("rule", orgId, factoryId, name);
|
|
41
47
|
const existing = (
|
|
42
48
|
await getDb()
|
|
43
49
|
.select({ promptVersion: triageRules.promptVersion })
|
|
44
50
|
.from(triageRules)
|
|
45
|
-
.where(
|
|
51
|
+
.where(
|
|
52
|
+
and(
|
|
53
|
+
eq(triageRules.id, ruleId),
|
|
54
|
+
orgFactoryRuleFilter(orgId, factoryId),
|
|
55
|
+
),
|
|
56
|
+
)
|
|
46
57
|
.limit(1)
|
|
47
58
|
)[0];
|
|
48
59
|
if (id && !existing) {
|
|
@@ -68,6 +79,7 @@ export default defineAction({
|
|
|
68
79
|
updatedAt: now,
|
|
69
80
|
ownerEmail: userEmail,
|
|
70
81
|
orgId,
|
|
82
|
+
factoryId,
|
|
71
83
|
})
|
|
72
84
|
.onConflictDoUpdate({
|
|
73
85
|
target: triageRules.id,
|
|
@@ -81,6 +93,7 @@ export default defineAction({
|
|
|
81
93
|
promptVersion: nextPromptVersion,
|
|
82
94
|
updatedAt: now,
|
|
83
95
|
ownerEmail: userEmail,
|
|
96
|
+
factoryId,
|
|
84
97
|
},
|
|
85
98
|
});
|
|
86
99
|
|