@agent-native/core 0.136.5 → 0.137.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.
- package/corpus/README.md +1 -1
- package/corpus/templates/clips/app/components/import-menu.tsx +109 -0
- package/corpus/templates/clips/app/components/library/library-grid.tsx +8 -18
- package/corpus/templates/clips/app/components/library/library-layout.tsx +14 -50
- package/corpus/templates/clips/app/components/library/sort-menu.tsx +20 -6
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +10 -34
- package/corpus/templates/clips/app/routes/_app.library._index.tsx +0 -2
- package/corpus/templates/clips/changelog/2026-08-04-cleaner-library-controls-with-an-icon-only-sort-button.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-04-upload-and-loom-imports-now-live-under-one-import-menu.md +6 -0
- package/corpus/templates/content/actions/_database-utils.ts +6 -0
- package/corpus/templates/content/actions/bind-content-database-source-field.ts +273 -97
- package/corpus/templates/content/actions/change-content-database-source-role.ts +65 -29
- package/corpus/templates/content/actions/configure-document-property.ts +113 -32
- package/corpus/templates/content/actions/delete-content-database.ts +5 -7
- package/corpus/templates/content/actions/delete-document-property.ts +109 -62
- package/corpus/templates/content/actions/delete-document.ts +178 -36
- package/corpus/templates/content/actions/duplicate-database-item.ts +27 -0
- package/corpus/templates/content/actions/duplicate-database-items.ts +31 -1
- package/corpus/templates/content/actions/permanently-delete-document.ts +4 -6
- package/corpus/templates/content/actions/remove-database-items.ts +13 -0
- package/corpus/templates/content/actions/set-document-property.ts +69 -1
- package/corpus/templates/content/actions/upsert-database-item-by-key.ts +814 -0
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +1 -0
- package/corpus/templates/content/server/db/schema.ts +27 -0
- package/corpus/templates/content/server/plugins/db.ts +24 -0
- package/corpus/templates/design/app/components/design/BreakpointBar.tsx +122 -114
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +2 -2
- package/corpus/templates/design/app/components/design/EditPanel.tsx +68 -34
- package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +78 -4
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +8 -0
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +5 -2
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +10 -9
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +31 -69
- package/corpus/templates/design/app/components/design/inspector/design-icons.tsx +80 -106
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -0
- package/corpus/templates/design/app/hooks/use-editor-preferences.ts +75 -0
- package/corpus/templates/design/app/i18n-data.ts +8 -0
- package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +61 -0
- package/corpus/templates/design/app/pages/design-editor/editor-preferences.ts +98 -0
- package/corpus/templates/design/app/pages/design-editor/nudge-intent.ts +491 -0
- package/corpus/templates/design/app/pages/design-editor/paste-placement.ts +133 -0
- package/corpus/templates/design/app/pages/design-editor/png-export-render.ts +8 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +5 -12
- package/corpus/templates/design/changelog/2026-08-03-arrow-keys-now-reorder-a-layer-inside-an-auto-layout-frame.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-auto-layout-flow-icons-show-the-direction-at-a-glance-instea.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-auto-layout-icons-share-one-outlined-square-style-so-flow-ga.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-export-has-a-named-preview-section-you-can-expand-replacing-.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-export-preview-shows-the-selected-element-instead-of-the-who.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-inspector-sections-show-an-expand-indicator-so-it-is-clear-w.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-paste-lands-inside-a-selected-frame-instead-of-beside-it.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-add-breakpoint-button-sits-outside-the-breakpoint-select.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-agent-s-follow-up-questions-keep-space-below-the-continu.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-agent-s-follow-up-questions-share-the-chat-panel-s-backg.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-export-panel-no-longer-stacks-a-duplicate-export-block-o.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-text-tool-uses-a-t-icon-matching-the-letter-it-inserts.md +6 -0
- package/corpus/templates/factory/.env.example +17 -0
- package/corpus/templates/factory/AGENTS.md +27 -17
- package/corpus/templates/factory/README.md +19 -9
- package/corpus/templates/factory/actions/get-slack-feedback-context.ts +90 -0
- package/corpus/templates/factory/actions/get-triage-config.ts +14 -3
- package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +386 -0
- package/corpus/templates/factory/actions/list-factory-automations.ts +53 -0
- package/corpus/templates/factory/actions/list-triage-items.ts +7 -2
- package/corpus/templates/factory/actions/poll-github-sources.ts +203 -0
- package/corpus/templates/factory/actions/poll-sentry-errors.ts +161 -0
- package/corpus/templates/factory/actions/poll-slack-channel.ts +4 -2
- package/corpus/templates/factory/actions/run-factory-automation.ts +42 -0
- package/corpus/templates/factory/actions/save-factory-automation.ts +69 -0
- package/corpus/templates/factory/actions/save-triage-config.ts +28 -2
- package/corpus/templates/factory/actions/start-builder-for-item.ts +411 -0
- package/corpus/templates/factory/app/components/factory/FactoryCanvas.tsx +14 -3
- package/corpus/templates/factory/app/components/ui/checkbox.tsx +1 -0
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +7 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +35 -1
- package/corpus/templates/factory/app/routes/factory.tsx +462 -9
- package/corpus/templates/factory/changelog/2026-08-04-added-organization-owned-factory-automations-for-slack-bug-t.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-04-automation-editor-controls-and-model-picker.md +6 -0
- package/corpus/templates/factory/netlify.toml +2 -2
- package/corpus/templates/factory/server/connectors/credentials.ts +6 -5
- package/corpus/templates/factory/server/connectors/slack.ts +133 -5
- package/corpus/templates/factory/server/db/schema.ts +6 -0
- package/corpus/templates/factory/server/lib/require-factory-automation.ts +65 -0
- package/corpus/templates/factory/server/plugins/agent-chat.ts +29 -11
- package/corpus/templates/factory/server/plugins/auth.ts +1 -0
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +12 -0
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +218 -67
- package/corpus/templates/factory/server/triage/builder-executor.ts +4 -0
- package/corpus/templates/factory/server/triage/contracts.ts +14 -1
- package/corpus/templates/factory/server/triage/github-client.ts +404 -0
- package/corpus/templates/factory/server/triage/metadata.ts +44 -0
- package/corpus/templates/factory/server/triage/pr-babysit.ts +5 -5
- package/corpus/templates/factory/server/triage/pr-policy.ts +144 -0
- package/corpus/templates/factory/server/triage/sentry-client.ts +180 -0
- package/corpus/templates/factory/server/triage/slack-client.ts +43 -0
- package/corpus/templates/factory/server/triage/slack-poller.ts +9 -2
- package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +27 -0
- package/corpus/templates/plan/.agents/skills/visual-plan/references/canvas.md +14 -4
- package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +7 -0
- package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +7 -0
- package/corpus/templates/plan/actions/create-plan-design.ts +8 -6
- package/corpus/templates/plan/actions/create-prototype-plan.ts +8 -6
- package/corpus/templates/plan/actions/create-ui-plan.ts +7 -7
- package/corpus/templates/plan/actions/get-plan-blocks.ts +7 -1
- package/corpus/templates/plan/actions/validate-plan-input.ts +39 -0
- package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +167 -11
- package/corpus/templates/plan/app/i18n/en-US.ts +1 -0
- package/corpus/templates/plan/changelog/2026-08-04-canvas-zoom-controls-now-explain-the-command-ctrl-plus-scrol.md +6 -0
- package/corpus/templates/plan/changelog/2026-08-04-pinch-to-zoom-is-now-supported-on-touchscreens-in-visual-pla.md +6 -0
- package/corpus/templates/plan/shared/plan-content.ts +17 -2
- package/dist/cli/skills-content/canvas.d.ts +3 -3
- package/dist/cli/skills-content/canvas.js +16 -5
- package/dist/cli/skills-content/visual-plan-skill.d.ts +1 -1
- package/dist/cli/skills-content/visual-plan-skill.js +27 -0
- package/dist/cli/skills-content/wireframe.d.ts +2 -2
- package/dist/cli/skills-content/wireframe.js +7 -0
- package/dist/client/AgentPanel.js +23 -8
- package/dist/client/api-path.d.ts +6 -0
- package/dist/client/api-path.js +55 -0
- package/dist/client/onboarding/FirstRunOnboarding.d.ts +3 -0
- package/dist/client/onboarding/FirstRunOnboarding.js +93 -0
- package/dist/client/onboarding/first-run-enabled.d.ts +2 -0
- package/dist/client/onboarding/first-run-enabled.js +9 -0
- package/dist/client/onboarding/index.d.ts +2 -1
- package/dist/client/onboarding/index.js +1 -0
- package/dist/client/onboarding/use-onboarding.d.ts +6 -1
- package/dist/client/onboarding/use-onboarding.js +32 -1
- package/dist/client/route-state.js +10 -0
- package/dist/connections/catalog.d.ts +4 -4
- package/dist/connections/catalog.js +3 -3
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +5 -5
- package/dist/observability/routes.d.ts +3 -3
- package/dist/onboarding/app-profile.d.ts +4 -0
- package/dist/onboarding/app-profile.js +378 -0
- package/dist/onboarding/index.d.ts +2 -1
- package/dist/onboarding/index.js +1 -0
- package/dist/onboarding/plugin.d.ts +2 -0
- package/dist/onboarding/plugin.js +21 -1
- package/dist/onboarding/types.d.ts +19 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
- package/dist/provider-api/actions/provider-api.d.ts +12 -12
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/auth.js +21 -1
- package/dist/server/better-auth-instance.d.ts +3 -2
- package/dist/server/better-auth-instance.js +9 -7
- package/dist/server/email-markdown.js +3 -8
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/first-run-onboarding.d.ts +3 -0
- package/dist/shared/first-run-onboarding.js +3 -0
- package/dist/triggers/index.d.ts +3 -0
- package/dist/triggers/index.js +5 -0
- package/docs/content/cross-app-sso.mdx +26 -0
- package/package.json +1 -1
|
@@ -0,0 +1,411 @@
|
|
|
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
|
+
triageConfig,
|
|
8
|
+
triageDecisions,
|
|
9
|
+
triageItems,
|
|
10
|
+
triageRuns,
|
|
11
|
+
} from "../server/db/schema.js";
|
|
12
|
+
import { requireFactoryAutomation } from "../server/lib/require-factory-automation.js";
|
|
13
|
+
import {
|
|
14
|
+
requireWorkspaceMember,
|
|
15
|
+
workspaceMemberIdentityFromContext,
|
|
16
|
+
} from "../server/lib/require-workspace-member.js";
|
|
17
|
+
import { startBuilderRun } from "../server/triage/builder-executor.js";
|
|
18
|
+
import { stableId } from "../server/triage/ids.js";
|
|
19
|
+
import {
|
|
20
|
+
metadataBoolean,
|
|
21
|
+
parseTriageMetadata,
|
|
22
|
+
serializeTriageMetadata,
|
|
23
|
+
} from "../server/triage/metadata.js";
|
|
24
|
+
import { detectOwnerOwnedArea } from "../server/triage/pr-policy.js";
|
|
25
|
+
import { createSlackReader } from "../server/triage/slack-client.js";
|
|
26
|
+
|
|
27
|
+
const replyTextPrefix =
|
|
28
|
+
"@builderio please fix this in a reply. This is a clear bug based on the feedback above. Please send a PR when ready.";
|
|
29
|
+
|
|
30
|
+
function replyTextForItem(item: {
|
|
31
|
+
id: string;
|
|
32
|
+
sourceUrl: string | null;
|
|
33
|
+
}): string {
|
|
34
|
+
return [
|
|
35
|
+
replyTextPrefix,
|
|
36
|
+
`Factory item: ${item.id}`,
|
|
37
|
+
item.sourceUrl ? `Source: ${item.sourceUrl}` : "",
|
|
38
|
+
"Please include the Factory item and source link in the PR description.",
|
|
39
|
+
]
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.join("\n");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function writeMetadata(
|
|
45
|
+
itemId: string,
|
|
46
|
+
orgId: string,
|
|
47
|
+
patch: Record<string, unknown>,
|
|
48
|
+
): Promise<void> {
|
|
49
|
+
const db = getDb();
|
|
50
|
+
const item = (
|
|
51
|
+
await db
|
|
52
|
+
.select({ metadataJson: triageItems.metadataJson })
|
|
53
|
+
.from(triageItems)
|
|
54
|
+
.where(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)))
|
|
55
|
+
.limit(1)
|
|
56
|
+
)[0];
|
|
57
|
+
if (!item)
|
|
58
|
+
throw new Error("Factory item disappeared while recording dispatch.");
|
|
59
|
+
const metadata = parseTriageMetadata(item.metadataJson);
|
|
60
|
+
Object.assign(metadata, patch);
|
|
61
|
+
await db
|
|
62
|
+
.update(triageItems)
|
|
63
|
+
.set({
|
|
64
|
+
metadataJson: serializeTriageMetadata(metadata),
|
|
65
|
+
updatedAt: new Date().toISOString(),
|
|
66
|
+
})
|
|
67
|
+
.where(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function recordDecision(input: {
|
|
71
|
+
itemId: string;
|
|
72
|
+
userEmail: string;
|
|
73
|
+
orgId: string;
|
|
74
|
+
outcome: "propose_fix" | "needs_manual";
|
|
75
|
+
reason: string;
|
|
76
|
+
guardResults: Array<{ code: string; passed: boolean; reason: string }>;
|
|
77
|
+
}) {
|
|
78
|
+
const id = stableId(
|
|
79
|
+
"decision",
|
|
80
|
+
input.orgId,
|
|
81
|
+
input.itemId,
|
|
82
|
+
"automatic-builder",
|
|
83
|
+
);
|
|
84
|
+
await getDb()
|
|
85
|
+
.insert(triageDecisions)
|
|
86
|
+
.values({
|
|
87
|
+
id,
|
|
88
|
+
itemId: input.itemId,
|
|
89
|
+
ruleId: null,
|
|
90
|
+
mode: "automation",
|
|
91
|
+
outcome: input.outcome,
|
|
92
|
+
reason: input.reason,
|
|
93
|
+
guardResultsJson: JSON.stringify(input.guardResults),
|
|
94
|
+
model: "factory-automation",
|
|
95
|
+
promptVersion: 1,
|
|
96
|
+
createdAt: new Date().toISOString(),
|
|
97
|
+
ownerEmail: input.userEmail,
|
|
98
|
+
orgId: input.orgId,
|
|
99
|
+
})
|
|
100
|
+
.onConflictDoNothing();
|
|
101
|
+
return id;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export default defineAction({
|
|
105
|
+
description:
|
|
106
|
+
"Start the governed clear-bug Builder flow for a Factory item. Slack items are kept in-thread by adding 👀 and tagging @builderio; GitHub issues and Sentry errors use the Builder agent run API. Owner-managed Clips, Design, and Content items are always left for their owner.",
|
|
107
|
+
schema: z.object({
|
|
108
|
+
itemId: z.string().min(1),
|
|
109
|
+
clearBug: z.boolean(),
|
|
110
|
+
reason: z.string().trim().min(1).max(4_000),
|
|
111
|
+
productUxImplications: z.boolean().default(false),
|
|
112
|
+
clearErrorReport: z.string().trim().max(8_000).optional(),
|
|
113
|
+
}),
|
|
114
|
+
http: false,
|
|
115
|
+
run: async (
|
|
116
|
+
{ itemId, clearBug, reason, productUxImplications, clearErrorReport },
|
|
117
|
+
context,
|
|
118
|
+
) => {
|
|
119
|
+
const { userEmail, orgId } = await requireWorkspaceMember(
|
|
120
|
+
workspaceMemberIdentityFromContext(context),
|
|
121
|
+
);
|
|
122
|
+
await requireFactoryAutomation(
|
|
123
|
+
context,
|
|
124
|
+
{ userEmail, orgId },
|
|
125
|
+
"builderDispatch",
|
|
126
|
+
);
|
|
127
|
+
const db = getDb();
|
|
128
|
+
const item = (
|
|
129
|
+
await db
|
|
130
|
+
.select()
|
|
131
|
+
.from(triageItems)
|
|
132
|
+
.where(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)))
|
|
133
|
+
.limit(1)
|
|
134
|
+
)[0];
|
|
135
|
+
if (!item) throw new Error("Factory item not found.");
|
|
136
|
+
const metadata = parseTriageMetadata(item.metadataJson);
|
|
137
|
+
const ownerOwnedArea = detectOwnerOwnedArea([
|
|
138
|
+
item.title,
|
|
139
|
+
item.summary,
|
|
140
|
+
item.repository,
|
|
141
|
+
typeof metadata.productArea === "string"
|
|
142
|
+
? metadata.productArea
|
|
143
|
+
: undefined,
|
|
144
|
+
typeof metadata.path === "string" ? metadata.path : undefined,
|
|
145
|
+
]);
|
|
146
|
+
const guardResults = [
|
|
147
|
+
{
|
|
148
|
+
code: "unknown_change",
|
|
149
|
+
passed: clearBug,
|
|
150
|
+
reason: clearBug
|
|
151
|
+
? "The automation classified a concrete, reproducible bug or error report."
|
|
152
|
+
: "The report is not a clear bug, so no external work was started.",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
code: "unknown_change",
|
|
156
|
+
passed: !productUxImplications,
|
|
157
|
+
reason: productUxImplications
|
|
158
|
+
? "Product or UX implications require manual ownership."
|
|
159
|
+
: "No product or UX decision was detected.",
|
|
160
|
+
},
|
|
161
|
+
...(ownerOwnedArea
|
|
162
|
+
? [
|
|
163
|
+
{
|
|
164
|
+
code: "owner_owned",
|
|
165
|
+
passed: false,
|
|
166
|
+
reason: `${ownerOwnedArea} is fully owned by its product owner and is excluded from autonomous Builder work.`,
|
|
167
|
+
},
|
|
168
|
+
]
|
|
169
|
+
: []),
|
|
170
|
+
];
|
|
171
|
+
const blocked = guardResults.some((guard) => !guard.passed);
|
|
172
|
+
const decisionId = await recordDecision({
|
|
173
|
+
itemId,
|
|
174
|
+
userEmail,
|
|
175
|
+
orgId,
|
|
176
|
+
outcome: blocked ? "needs_manual" : "propose_fix",
|
|
177
|
+
reason: blocked
|
|
178
|
+
? guardResults
|
|
179
|
+
.filter((guard) => !guard.passed)
|
|
180
|
+
.map((guard) => guard.reason)
|
|
181
|
+
.join(" ")
|
|
182
|
+
: reason,
|
|
183
|
+
guardResults,
|
|
184
|
+
});
|
|
185
|
+
if (blocked) {
|
|
186
|
+
await db
|
|
187
|
+
.update(triageItems)
|
|
188
|
+
.set({ status: "needs_manual", updatedAt: new Date().toISOString() })
|
|
189
|
+
.where(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)));
|
|
190
|
+
return {
|
|
191
|
+
ok: true,
|
|
192
|
+
started: false,
|
|
193
|
+
needsManual: true,
|
|
194
|
+
decisionId,
|
|
195
|
+
reason: guardResults
|
|
196
|
+
.filter((guard) => !guard.passed)
|
|
197
|
+
.map((guard) => guard.reason)
|
|
198
|
+
.join(" "),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const dedupeKey = stableId("automatic-builder", orgId, itemId);
|
|
203
|
+
const runId = stableId("run", dedupeKey);
|
|
204
|
+
const now = new Date().toISOString();
|
|
205
|
+
const isSlack = item.source === "slack";
|
|
206
|
+
const existing = (
|
|
207
|
+
await db
|
|
208
|
+
.select()
|
|
209
|
+
.from(triageRuns)
|
|
210
|
+
.where(and(eq(triageRuns.id, runId), eq(triageRuns.orgId, orgId)))
|
|
211
|
+
.limit(1)
|
|
212
|
+
)[0];
|
|
213
|
+
const retryableSlackRun =
|
|
214
|
+
isSlack &&
|
|
215
|
+
existing &&
|
|
216
|
+
(existing.status === "failed" || existing.status === "cancelled");
|
|
217
|
+
if (existing && !retryableSlackRun) {
|
|
218
|
+
return {
|
|
219
|
+
ok: true,
|
|
220
|
+
started: true,
|
|
221
|
+
deduplicated: true,
|
|
222
|
+
runId,
|
|
223
|
+
status: existing.status,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (retryableSlackRun) {
|
|
228
|
+
await db
|
|
229
|
+
.update(triageRuns)
|
|
230
|
+
.set({
|
|
231
|
+
status: "submitted",
|
|
232
|
+
error: null,
|
|
233
|
+
completedAt: null,
|
|
234
|
+
heartbeatAt: now,
|
|
235
|
+
dispatchAttempts: (existing.dispatchAttempts ?? 0) + 1,
|
|
236
|
+
})
|
|
237
|
+
.where(and(eq(triageRuns.id, runId), eq(triageRuns.orgId, orgId)));
|
|
238
|
+
} else {
|
|
239
|
+
await db.insert(triageRuns).values({
|
|
240
|
+
id: runId,
|
|
241
|
+
itemId,
|
|
242
|
+
source: item.source,
|
|
243
|
+
provider: isSlack ? "bot-tag" : "builder-http",
|
|
244
|
+
dedupeKey,
|
|
245
|
+
approvalEmail: null,
|
|
246
|
+
status: "submitted",
|
|
247
|
+
progressLogJson: JSON.stringify([
|
|
248
|
+
{
|
|
249
|
+
at: now,
|
|
250
|
+
state: "submitted",
|
|
251
|
+
reason: `Factory automation ${context?.automation?.triggerName ?? "unknown"} recorded a clear bug.`,
|
|
252
|
+
},
|
|
253
|
+
]),
|
|
254
|
+
dispatchAttempts: 1,
|
|
255
|
+
needsContinuation: 0,
|
|
256
|
+
startedAt: now,
|
|
257
|
+
heartbeatAt: now,
|
|
258
|
+
completedAt: null,
|
|
259
|
+
error: null,
|
|
260
|
+
ownerEmail: item.ownerEmail,
|
|
261
|
+
orgId,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
if (isSlack) {
|
|
267
|
+
if (!item.channelId || !item.threadTs) {
|
|
268
|
+
throw new Error(
|
|
269
|
+
"Slack feedback is missing its channel or thread identity.",
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
const config = (
|
|
273
|
+
await db
|
|
274
|
+
.select({ slackWorkspace: triageConfig.slackWorkspace })
|
|
275
|
+
.from(triageConfig)
|
|
276
|
+
.where(
|
|
277
|
+
and(eq(triageConfig.id, orgId), eq(triageConfig.orgId, orgId)),
|
|
278
|
+
)
|
|
279
|
+
.limit(1)
|
|
280
|
+
)[0];
|
|
281
|
+
const workspace =
|
|
282
|
+
config?.slackWorkspace === "secondary" ? "secondary" : "primary";
|
|
283
|
+
const slack = createSlackReader({ ownerEmail: userEmail, orgId });
|
|
284
|
+
if (!metadataBoolean(metadata, "slackEyesReactedAt")) {
|
|
285
|
+
const reaction = await slack.addEyesReaction(
|
|
286
|
+
workspace,
|
|
287
|
+
item.channelId,
|
|
288
|
+
item.threadTs,
|
|
289
|
+
);
|
|
290
|
+
await writeMetadata(itemId, orgId, {
|
|
291
|
+
slackEyesReactedAt: new Date().toISOString(),
|
|
292
|
+
slackEyesReactionAlreadyPresent: reaction.already_present,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
const thread = await slack.getThread(
|
|
296
|
+
workspace,
|
|
297
|
+
item.channelId,
|
|
298
|
+
item.threadTs,
|
|
299
|
+
100,
|
|
300
|
+
);
|
|
301
|
+
if (
|
|
302
|
+
thread.has_more &&
|
|
303
|
+
!thread.messages.some((message) =>
|
|
304
|
+
message.text.includes(replyTextPrefix),
|
|
305
|
+
)
|
|
306
|
+
) {
|
|
307
|
+
throw new Error(
|
|
308
|
+
"Slack thread is truncated; refusing to risk a duplicate Builder handoff.",
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
if (
|
|
312
|
+
!metadataBoolean(metadata, "slackBuilderReplyAt") &&
|
|
313
|
+
!thread.messages.some((message) =>
|
|
314
|
+
message.text.includes(replyTextPrefix),
|
|
315
|
+
)
|
|
316
|
+
) {
|
|
317
|
+
const posted = await slack.postThreadReply(
|
|
318
|
+
workspace,
|
|
319
|
+
item.channelId,
|
|
320
|
+
item.threadTs,
|
|
321
|
+
replyTextForItem(item),
|
|
322
|
+
);
|
|
323
|
+
await writeMetadata(itemId, orgId, {
|
|
324
|
+
slackBuilderReplyAt: new Date().toISOString(),
|
|
325
|
+
slackBuilderReplyTs: posted.ts,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
await db
|
|
329
|
+
.update(triageItems)
|
|
330
|
+
.set({
|
|
331
|
+
status: "automation_started",
|
|
332
|
+
updatedAt: new Date().toISOString(),
|
|
333
|
+
})
|
|
334
|
+
.where(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)));
|
|
335
|
+
return {
|
|
336
|
+
ok: true,
|
|
337
|
+
started: true,
|
|
338
|
+
deduplicated: false,
|
|
339
|
+
runId,
|
|
340
|
+
provider: "bot-tag",
|
|
341
|
+
awaitingBuilderReply: true,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const result = await startBuilderRun({
|
|
346
|
+
runId,
|
|
347
|
+
itemId,
|
|
348
|
+
ownerEmail: userEmail,
|
|
349
|
+
orgId,
|
|
350
|
+
repository: item.repository,
|
|
351
|
+
summary: item.summary,
|
|
352
|
+
sourceUrl: item.sourceUrl,
|
|
353
|
+
instructions: [
|
|
354
|
+
reason,
|
|
355
|
+
clearErrorReport ? `Error report:\n${clearErrorReport}` : "",
|
|
356
|
+
]
|
|
357
|
+
.filter(Boolean)
|
|
358
|
+
.join("\n\n"),
|
|
359
|
+
});
|
|
360
|
+
await db
|
|
361
|
+
.update(triageRuns)
|
|
362
|
+
.set({
|
|
363
|
+
status: "acknowledged",
|
|
364
|
+
providerTaskId: result.providerTaskId ?? null,
|
|
365
|
+
progressLogJson: JSON.stringify([
|
|
366
|
+
{ at: now, state: "submitted", reason },
|
|
367
|
+
{
|
|
368
|
+
at: new Date().toISOString(),
|
|
369
|
+
state: "acknowledged",
|
|
370
|
+
reason:
|
|
371
|
+
"Builder accepted the fire-and-forget request; waiting for the signed callback.",
|
|
372
|
+
},
|
|
373
|
+
]),
|
|
374
|
+
heartbeatAt: new Date().toISOString(),
|
|
375
|
+
})
|
|
376
|
+
.where(and(eq(triageRuns.id, runId), eq(triageRuns.orgId, orgId)));
|
|
377
|
+
await writeMetadata(itemId, orgId, {
|
|
378
|
+
builderProviderTaskId: result.providerTaskId ?? null,
|
|
379
|
+
builderBranchName: result.branchName,
|
|
380
|
+
});
|
|
381
|
+
await db
|
|
382
|
+
.update(triageItems)
|
|
383
|
+
.set({
|
|
384
|
+
status: "automation_started",
|
|
385
|
+
updatedAt: new Date().toISOString(),
|
|
386
|
+
})
|
|
387
|
+
.where(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)));
|
|
388
|
+
return {
|
|
389
|
+
ok: true,
|
|
390
|
+
started: true,
|
|
391
|
+
deduplicated: false,
|
|
392
|
+
runId,
|
|
393
|
+
provider: "builder-http",
|
|
394
|
+
};
|
|
395
|
+
} catch (error) {
|
|
396
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
397
|
+
await db
|
|
398
|
+
.update(triageRuns)
|
|
399
|
+
.set({
|
|
400
|
+
status: "failed",
|
|
401
|
+
error: message,
|
|
402
|
+
completedAt: new Date().toISOString(),
|
|
403
|
+
heartbeatAt: new Date().toISOString(),
|
|
404
|
+
})
|
|
405
|
+
.where(and(eq(triageRuns.id, runId), eq(triageRuns.orgId, orgId)));
|
|
406
|
+
throw new Error(
|
|
407
|
+
`Factory Builder dispatch failed after recording the run: ${message}`,
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
});
|
|
@@ -89,8 +89,11 @@ type DragState = {
|
|
|
89
89
|
nodeId: string;
|
|
90
90
|
offsetX: number;
|
|
91
91
|
offsetY: number;
|
|
92
|
+
startPosition: { x: number; y: number };
|
|
92
93
|
};
|
|
93
94
|
|
|
95
|
+
const DRAG_THRESHOLD = 3;
|
|
96
|
+
|
|
94
97
|
export function FactoryCanvas({
|
|
95
98
|
graph,
|
|
96
99
|
nodeMetrics = {},
|
|
@@ -171,6 +174,7 @@ export function FactoryCanvas({
|
|
|
171
174
|
nodeId: node.id,
|
|
172
175
|
offsetX: (event.clientX - rect.left) / scale - node.position.x,
|
|
173
176
|
offsetY: (event.clientY - rect.top) / scale - node.position.y,
|
|
177
|
+
startPosition: node.position,
|
|
174
178
|
});
|
|
175
179
|
dragMoved.current = false;
|
|
176
180
|
event.currentTarget.setPointerCapture(event.pointerId);
|
|
@@ -186,10 +190,17 @@ export function FactoryCanvas({
|
|
|
186
190
|
(event.clientX - rect.left) / scale - dragState.offsetX,
|
|
187
191
|
(event.clientY - rect.top) / scale - dragState.offsetY,
|
|
188
192
|
);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
193
|
+
const movedX = Math.abs(position.x - dragState.startPosition.x);
|
|
194
|
+
const movedY = Math.abs(position.y - dragState.startPosition.y);
|
|
195
|
+
if (
|
|
196
|
+
!dragMoved.current &&
|
|
197
|
+
movedX <= DRAG_THRESHOLD &&
|
|
198
|
+
movedY <= DRAG_THRESHOLD
|
|
199
|
+
) {
|
|
200
|
+
return;
|
|
192
201
|
}
|
|
202
|
+
dragMoved.current = true;
|
|
203
|
+
onMoveNode(dragState.nodeId, position);
|
|
193
204
|
}
|
|
194
205
|
|
|
195
206
|
function endDrag(event: React.PointerEvent<HTMLButtonElement>) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@agent-native/toolkit/ui/checkbox";
|
|
@@ -9,6 +9,7 @@ export interface NavigationState {
|
|
|
9
9
|
threadId?: string;
|
|
10
10
|
factoryId?: string;
|
|
11
11
|
factoryTab?: string;
|
|
12
|
+
factoryAutomationId?: string;
|
|
12
13
|
factoryNodeId?: string;
|
|
13
14
|
factoryEdgeId?: string;
|
|
14
15
|
}
|
|
@@ -29,6 +30,12 @@ export function useNavigationState() {
|
|
|
29
30
|
...(pathname.startsWith("/factory") && searchParams.get("tab")
|
|
30
31
|
? { factoryTab: searchParams.get("tab") ?? undefined }
|
|
31
32
|
: {}),
|
|
33
|
+
...(pathname.startsWith("/factory") && searchParams.get("automationId")
|
|
34
|
+
? {
|
|
35
|
+
factoryAutomationId:
|
|
36
|
+
searchParams.get("automationId") ?? undefined,
|
|
37
|
+
}
|
|
38
|
+
: {}),
|
|
32
39
|
...(pathname.startsWith("/factory") && searchParams.get("node")
|
|
33
40
|
? { factoryNodeId: searchParams.get("node") ?? undefined }
|
|
34
41
|
: {}),
|
|
@@ -109,6 +109,14 @@ const messages = {
|
|
|
109
109
|
repository: "Repository",
|
|
110
110
|
repositoryPlaceholder: "For example, BuilderIO/agent-native",
|
|
111
111
|
enablePolling: "Enable one-minute observation polling",
|
|
112
|
+
enableGithubPolling: "Enable GitHub issue and pull-request polling",
|
|
113
|
+
enableSentryPolling: "Enable daily Sentry error polling",
|
|
114
|
+
sentryOrgSlug: "Sentry organization slug",
|
|
115
|
+
sentryProjectSlug: "Sentry project slug",
|
|
116
|
+
sentryEnvironment: "Sentry environment",
|
|
117
|
+
sentryOrgPlaceholder: "For example, builder",
|
|
118
|
+
sentryProjectPlaceholder: "Optional project slug",
|
|
119
|
+
sentryEnvironmentPlaceholder: "For example, production",
|
|
112
120
|
saveSettings: "Save settings",
|
|
113
121
|
settingsError: "Could not save observation settings.",
|
|
114
122
|
settingsSaved: "Observation settings saved.",
|
|
@@ -137,13 +145,39 @@ const messages = {
|
|
|
137
145
|
rulesDescription: "Every rule is shadow-only until deliberately promoted.",
|
|
138
146
|
editRule: "Edit triage rule",
|
|
139
147
|
settingsDescription:
|
|
140
|
-
"Connect
|
|
148
|
+
"Connect Slack, GitHub, and Sentry sources. Automations can dispatch governed work and expose their prompts and runs below.",
|
|
141
149
|
blueprintTitle: "Blueprint mode.",
|
|
142
150
|
blueprintDescription:
|
|
143
151
|
"The current runtime evaluates enabled rules in parallel and stays in shadow mode. This map records the intended handoffs; it does not silently change execution behavior.",
|
|
144
152
|
metricSignals: "Signals",
|
|
145
153
|
metricDecisions: "Decisions",
|
|
146
154
|
metricRuns: "Runs",
|
|
155
|
+
rulesTab: "Rules",
|
|
156
|
+
automationsTab: "Automations",
|
|
157
|
+
automationsTitle: "Automations",
|
|
158
|
+
automationsDescription: "Schedule and run Factory prompts.",
|
|
159
|
+
automationsLoading: "Loading automations...",
|
|
160
|
+
automationsEmpty: "No automations configured yet.",
|
|
161
|
+
automationEnabled: "Enabled",
|
|
162
|
+
automationDisabled: "Disabled",
|
|
163
|
+
automationEditorTitle: "Automation editor",
|
|
164
|
+
automationEditorDescription: "Configure the prompt, model, and schedule.",
|
|
165
|
+
automationTrigger: "Trigger",
|
|
166
|
+
automationEvent: "Event",
|
|
167
|
+
automationTimezone: "Timezone",
|
|
168
|
+
selectAutomation: "Select an automation to edit.",
|
|
169
|
+
automationModel: "Model",
|
|
170
|
+
automationModelPlaceholder: "For example, gpt-5",
|
|
171
|
+
automationSchedule: "Schedule",
|
|
172
|
+
automationSchedulePlaceholder: "For example, every weekday at 9:00 AM",
|
|
173
|
+
automationEnabledLabel: "Automation is enabled",
|
|
174
|
+
automationPrompt: "Prompt",
|
|
175
|
+
automationPromptPlaceholder: "What should Factory do when this runs?",
|
|
176
|
+
promptEditorHint: "Edit here, then save the automation.",
|
|
177
|
+
pastRuns: "Past runs",
|
|
178
|
+
pastRunsEmpty: "No runs recorded yet.",
|
|
179
|
+
saveAutomation: "Save",
|
|
180
|
+
runNow: "Run now",
|
|
147
181
|
},
|
|
148
182
|
factoryCanvas: {
|
|
149
183
|
dragHint: "Drag to arrange",
|