@agent-native/core 0.164.4 → 0.164.11
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/calendar/app/components/calendar/CreateEventDialog.tsx +36 -47
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +3 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +18 -1
- package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +256 -38
- package/corpus/templates/calendar/app/hooks/use-google-auth.ts +43 -3
- package/corpus/templates/calendar/app/i18n-data.ts +120 -0
- package/corpus/templates/calendar/app/lib/event-form-utils.ts +113 -0
- package/corpus/templates/calendar/server/handlers/google-auth.ts +119 -3
- package/corpus/templates/calendar/server/routes/[base]/_agent-native/google/add-account/auth-url.post.ts +1 -0
- package/corpus/templates/calendar/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/calendar/server/routes/_agent-native/google/add-account/auth-url.post.ts +1 -0
- package/corpus/templates/calendar/server/routes/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/clips/app/components/player/video-player.tsx +12 -0
- package/corpus/templates/clips/app/routes/_app.dictate.tsx +18 -4
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +114 -20
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +83 -78
- package/corpus/templates/clips/changelog/2026-08-19-clips-nightly-builds-are-available-for-trying-the-latest-upd.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-19-social-preview-video-frames.md +1 -0
- package/corpus/templates/clips/desktop/README.md +19 -4
- package/corpus/templates/clips/desktop/src/app.tsx +78 -16
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +3 -2
- package/corpus/templates/clips/desktop/src-tauri/tauri.conf.json +1 -0
- package/corpus/templates/clips/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.get.ts +41 -3
- package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/clips/server/routes/_agent-native/google/callback.get.ts +22 -1
- package/corpus/templates/clips/server/routes/api/clips-updater.json.get.ts +41 -22
- package/corpus/templates/factory/AGENTS.md +1 -1
- package/corpus/templates/factory/actions/list-factory-automations.ts +4 -0
- package/corpus/templates/factory/actions/start-builder-for-item.ts +19 -17
- package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +100 -36
- package/corpus/templates/factory/app/i18n/en-US.ts +4 -0
- package/corpus/templates/factory/app/routes/factory.tsx +209 -46
- package/corpus/templates/factory/server/connectors/slack.ts +44 -2
- package/corpus/templates/factory/server/plugins/agent-chat.ts +9 -7
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +13 -0
- package/corpus/templates/factory/server/triage/slack-client.ts +5 -0
- package/corpus/templates/factory/server/triage/slack-poller.ts +72 -4
- package/corpus/templates/mail/actions/view-screen.ts +1 -1
- package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +100 -18
- package/corpus/templates/mail/server/handlers/google-auth.ts +125 -24
- package/corpus/templates/mail/server/lib/gmail-query.ts +5 -2
- package/corpus/templates/mail/server/routes/[base]/_agent-native/google/add-account/auth-url.post.ts +1 -0
- package/corpus/templates/mail/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/mail/server/routes/_agent-native/google/add-account/auth-url.post.ts +1 -0
- package/corpus/templates/mail/server/routes/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +0 -12
- package/corpus/templates/plan/changelog/2026-08-19-plan-prompts-now-guide-you-to-connect-ai-before-sending-and-.md +6 -0
- package/corpus/templates/slides/actions/approve-deck-access-request.ts +234 -0
- package/corpus/templates/slides/actions/get-deck-access-status.ts +35 -2
- package/corpus/templates/slides/actions/request-deck-access.ts +263 -64
- package/corpus/templates/slides/app/components/editor/MissingDeckAccessPane.tsx +196 -0
- package/corpus/templates/slides/app/components/editor/RequestDeckAccessDialog.tsx +101 -0
- package/corpus/templates/slides/app/hooks/use-deck-access.ts +7 -4
- package/corpus/templates/slides/app/i18n/en-US.ts +29 -6
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +147 -169
- package/corpus/templates/slides/app/routes/access-request.approve.tsx +250 -0
- package/corpus/templates/slides/server/db/schema.ts +8 -0
- package/corpus/templates/slides/server/lib/access-request-email.ts +36 -0
- package/corpus/templates/slides/server/lib/emails.ts +26 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +7 -0
- package/corpus/templates/slides/server/plugins/db.ts +50 -0
- package/corpus/templates/slides/shared/deck-access.ts +31 -0
- package/dist/a2a/task-store.d.ts +1 -0
- package/dist/a2a/task-store.js +1 -1
- package/dist/agent/observational-memory/store.d.ts +1 -0
- package/dist/agent/observational-memory/store.js +1 -1
- package/dist/agent/run-store.d.ts +1 -0
- package/dist/agent/run-store.js +1 -1
- package/dist/agent/tool-approval-store.d.ts +1 -0
- package/dist/agent/tool-approval-store.js +1 -1
- package/dist/application-state/store.d.ts +1 -0
- package/dist/application-state/store.js +1 -1
- package/dist/browser-sessions/store.d.ts +1 -0
- package/dist/browser-sessions/store.js +1 -1
- package/dist/checkpoints/store.d.ts +1 -0
- package/dist/checkpoints/store.js +1 -1
- package/dist/cli/code-agent-schedules.d.ts +11 -0
- package/dist/cli/code-agent-schedules.js +44 -5
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.js +2 -2
- package/dist/client/AssistantChat.d.ts +5 -1
- package/dist/client/AssistantChat.js +40 -6
- package/dist/client/EnvironmentBadge.d.ts +7 -13
- package/dist/client/EnvironmentBadge.js +43 -92
- package/dist/client/agent-chat/index.d.ts +1 -0
- package/dist/client/agent-chat/index.js +1 -0
- package/dist/client/agent-chat-adapter.js +76 -50
- package/dist/client/app-providers.js +4 -4
- package/dist/client/chat/message-components.d.ts +4 -0
- package/dist/client/chat/message-components.js +28 -1
- package/dist/client/chat/run-recovery.js +26 -0
- package/dist/client/chat-first/apps-rail.d.ts +1 -0
- package/dist/client/chat-first/apps-rail.js +28 -2
- package/dist/client/sse-event-processor.js +13 -8
- package/dist/client/use-active-agent-chat-run.d.ts +2 -0
- package/dist/client/use-active-agent-chat-run.js +22 -0
- package/dist/coding-tools/sandbox/executions-store.d.ts +1 -0
- package/dist/coding-tools/sandbox/executions-store.js +1 -1
- package/dist/collab/awareness-store.d.ts +1 -0
- package/dist/collab/awareness-store.js +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/storage.d.ts +1 -0
- package/dist/collab/storage.js +1 -1
- package/dist/db/client.js +3 -3
- package/dist/db/ddl-guard.js +8 -0
- package/dist/db/migration-runtime.d.ts +23 -0
- package/dist/db/migration-runtime.js +41 -0
- package/dist/db/migrations.d.ts +1 -2
- package/dist/db/migrations.js +2 -24
- package/dist/email-catalog/log.d.ts +1 -0
- package/dist/email-catalog/log.js +1 -1
- package/dist/guards/index.d.ts +1 -0
- package/dist/guards/index.js +1 -0
- package/dist/guards/release-schema-complete.d.ts +22 -0
- package/dist/guards/release-schema-complete.js +144 -0
- package/dist/integrations/a2a-continuations-store.d.ts +1 -0
- package/dist/integrations/a2a-continuations-store.js +1 -1
- package/dist/integrations/awaiting-input-store.d.ts +1 -0
- package/dist/integrations/awaiting-input-store.js +1 -1
- package/dist/integrations/config-store.d.ts +1 -0
- package/dist/integrations/config-store.js +1 -1
- package/dist/integrations/controls-store.d.ts +1 -0
- package/dist/integrations/controls-store.js +1 -1
- package/dist/integrations/identity-links-store.d.ts +1 -0
- package/dist/integrations/identity-links-store.js +1 -1
- package/dist/integrations/installations-store.d.ts +1 -0
- package/dist/integrations/installations-store.js +1 -1
- package/dist/integrations/remote-commands-store.d.ts +1 -0
- package/dist/integrations/remote-commands-store.js +1 -1
- package/dist/integrations/remote-devices-store.d.ts +1 -0
- package/dist/integrations/remote-devices-store.js +1 -1
- package/dist/integrations/remote-push-store.d.ts +1 -0
- package/dist/integrations/remote-push-store.js +1 -1
- package/dist/integrations/remote-run-events-store.d.ts +1 -0
- package/dist/integrations/remote-run-events-store.js +1 -1
- package/dist/integrations/scope-store.d.ts +1 -0
- package/dist/integrations/scope-store.js +1 -1
- package/dist/integrations/thread-mapping-store.d.ts +1 -0
- package/dist/integrations/thread-mapping-store.js +1 -1
- package/dist/integrations/usage-budget-store.d.ts +1 -0
- package/dist/integrations/usage-budget-store.js +1 -1
- package/dist/jobs/background-automation-runner.js +111 -9
- package/dist/jobs/run-history.d.ts +1 -0
- package/dist/jobs/run-history.js +1 -1
- package/dist/jobs/scheduler-health.d.ts +1 -0
- package/dist/jobs/scheduler-health.js +1 -1
- package/dist/mcp/approval-store.d.ts +1 -0
- package/dist/mcp/approval-store.js +1 -1
- package/dist/mcp/connect-store.d.ts +1 -0
- package/dist/mcp/connect-store.js +1 -1
- package/dist/mcp/oauth-store.d.ts +1 -0
- package/dist/mcp/oauth-store.js +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/notifications/store.d.ts +1 -0
- package/dist/notifications/store.js +1 -1
- package/dist/oauth-tokens/store.d.ts +1 -0
- package/dist/oauth-tokens/store.js +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/store.d.ts +1 -0
- package/dist/progress/store.js +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +5 -5
- package/dist/provider-api/actions/provider-api.d.ts +4 -4
- package/dist/provider-api/corpus-jobs-store.d.ts +1 -0
- package/dist/provider-api/corpus-jobs-store.js +1 -1
- package/dist/provider-api/custom-registry.d.ts +1 -0
- package/dist/provider-api/custom-registry.js +1 -1
- package/dist/provider-api/quota-governor.d.ts +1 -0
- package/dist/provider-api/quota-governor.js +1 -1
- package/dist/provider-api/staged-datasets-store.d.ts +1 -0
- package/dist/provider-api/staged-datasets-store.js +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/resources/store.d.ts +1 -0
- package/dist/resources/store.js +1 -1
- package/dist/scripts/dev/index.js +15 -1
- package/dist/search/index.js +3 -0
- package/dist/secrets/storage.d.ts +1 -0
- package/dist/secrets/storage.js +1 -1
- package/dist/server/agent-chat-plugin.js +38 -9
- package/dist/server/agent-teams-run-queue.d.ts +1 -0
- package/dist/server/agent-teams-run-queue.js +1 -1
- package/dist/server/auth-marketing.js +3 -1
- package/dist/server/auth.d.ts +18 -1
- package/dist/server/auth.js +370 -154
- package/dist/server/embed-session.d.ts +1 -0
- package/dist/server/embed-session.js +1 -1
- package/dist/server/google-auth-plugin.js +3 -1
- package/dist/server/google-oauth.d.ts +10 -0
- package/dist/server/google-oauth.js +25 -1
- package/dist/server/identity-sso-store.d.ts +1 -0
- package/dist/server/identity-sso-store.js +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/onboarding-html.js +200 -31
- package/dist/server/poll.d.ts +2 -0
- package/dist/server/poll.js +19 -1
- package/dist/server/release-migrations.js +5 -0
- package/dist/server/release-schema.d.ts +46 -0
- package/dist/server/release-schema.js +307 -0
- package/dist/settings/store.d.ts +1 -0
- package/dist/settings/store.js +1 -1
- package/dist/shared/environment-lanes.d.ts +28 -0
- package/dist/shared/environment-lanes.js +61 -0
- package/dist/sharing/actions/share-resource.js +22 -2
- package/dist/styles/agent-native.css +17 -15
- package/dist/triggers/routes.d.ts +11 -0
- package/dist/triggers/routes.js +10 -0
- package/dist/usage/alerts-store.d.ts +1 -0
- package/dist/usage/alerts-store.js +1 -1
- package/dist/usage/store.d.ts +1 -0
- package/dist/usage/store.js +1 -1
- package/package.json +2 -2
|
@@ -4,10 +4,14 @@ import {
|
|
|
4
4
|
isElectron,
|
|
5
5
|
resolveGoogleSignInCredentials,
|
|
6
6
|
resolveOAuthRedirectUri,
|
|
7
|
+
registerDesktopExchange,
|
|
8
|
+
prepareDesktopOAuthBrowserBinding,
|
|
7
9
|
safeReturnPath,
|
|
8
10
|
} from "@agent-native/core/server";
|
|
9
11
|
import {
|
|
10
12
|
defineEventHandler,
|
|
13
|
+
getHeader,
|
|
14
|
+
getMethod,
|
|
11
15
|
getQuery,
|
|
12
16
|
setResponseStatus,
|
|
13
17
|
type H3Event,
|
|
@@ -54,11 +58,39 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
54
58
|
isElectron(event) || q.desktop === "1" || q.desktop === "true";
|
|
55
59
|
const flowId =
|
|
56
60
|
desktop && typeof q.flow_id === "string" ? q.flow_id : undefined;
|
|
61
|
+
if (getMethod(event) === "POST" && (!desktop || !flowId)) {
|
|
62
|
+
setResponseStatus(event, 400);
|
|
63
|
+
return { error: "Invalid desktop exchange challenge." };
|
|
64
|
+
}
|
|
65
|
+
const calendarConnect =
|
|
66
|
+
q.calendar === "1" || q.calendar === "true" || q.product === "calendar";
|
|
67
|
+
let desktopVerifierHash: string | undefined;
|
|
68
|
+
let desktopBrowserBindingHash: string | undefined;
|
|
69
|
+
if (flowId && !calendarConnect) {
|
|
70
|
+
if (getMethod(event) !== "POST" || q.redirect !== undefined) {
|
|
71
|
+
setResponseStatus(event, 400);
|
|
72
|
+
return { error: "Invalid desktop exchange challenge." };
|
|
73
|
+
}
|
|
74
|
+
const verifier = getHeader(event, "x-agent-native-desktop-verifier");
|
|
75
|
+
if (!verifier || q.verifier !== undefined) {
|
|
76
|
+
setResponseStatus(event, 400);
|
|
77
|
+
return { error: "Invalid desktop exchange challenge." };
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
desktopBrowserBindingHash = prepareDesktopOAuthBrowserBinding(event);
|
|
81
|
+
desktopVerifierHash = await registerDesktopExchange(
|
|
82
|
+
flowId,
|
|
83
|
+
verifier,
|
|
84
|
+
desktopBrowserBindingHash,
|
|
85
|
+
);
|
|
86
|
+
} catch {
|
|
87
|
+
setResponseStatus(event, 400);
|
|
88
|
+
return { error: "Invalid desktop exchange challenge." };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
57
91
|
const requestedReturn =
|
|
58
92
|
typeof q.return === "string" ? safeReturnPath(q.return) : "/";
|
|
59
93
|
const returnUrl = requestedReturn !== "/" ? requestedReturn : undefined;
|
|
60
|
-
const calendarConnect =
|
|
61
|
-
q.calendar === "1" || q.calendar === "true" || q.product === "calendar";
|
|
62
94
|
const credentials = calendarConnect
|
|
63
95
|
? ((await resolveGoogleOAuthCredentialCandidates())[0] ?? null)
|
|
64
96
|
: resolveGoogleSignInCredentials();
|
|
@@ -84,10 +116,16 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
84
116
|
redirectUri,
|
|
85
117
|
owner,
|
|
86
118
|
desktop,
|
|
119
|
+
desktopWebview: desktop && q.webview === "1" && !calendarConnect,
|
|
87
120
|
addAccount: calendarConnect,
|
|
88
121
|
app: CLIPS_GOOGLE_OAUTH_APP_ID,
|
|
89
|
-
returnUrl
|
|
122
|
+
returnUrl:
|
|
123
|
+
desktop && q.webview === "1" && !calendarConnect
|
|
124
|
+
? "/?desktop_auth=complete"
|
|
125
|
+
: returnUrl,
|
|
90
126
|
flowId: calendarConnect ? undefined : flowId,
|
|
127
|
+
desktopVerifierHash,
|
|
128
|
+
desktopBrowserBindingHash,
|
|
91
129
|
});
|
|
92
130
|
|
|
93
131
|
const params = new URLSearchParams({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./auth-url.get.js";
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
oauthErrorPage,
|
|
7
7
|
resolveGoogleSignInCredentials,
|
|
8
8
|
resolveOAuthOwner,
|
|
9
|
+
matchesDesktopOAuthBrowserBinding,
|
|
9
10
|
setDesktopExchange,
|
|
10
11
|
type OAuthStatePayload,
|
|
11
12
|
} from "@agent-native/core/server";
|
|
@@ -31,6 +32,17 @@ async function handleGoogleSignInCallback(
|
|
|
31
32
|
) {
|
|
32
33
|
const desktop = state.desktop;
|
|
33
34
|
const flowId = state.flowId;
|
|
35
|
+
if (
|
|
36
|
+
flowId &&
|
|
37
|
+
(!state.desktopVerifierHash ||
|
|
38
|
+
!state.desktopBrowserBindingHash ||
|
|
39
|
+
!matchesDesktopOAuthBrowserBinding(
|
|
40
|
+
event,
|
|
41
|
+
state.desktopBrowserBindingHash,
|
|
42
|
+
))
|
|
43
|
+
) {
|
|
44
|
+
return oauthErrorPage("Desktop OAuth browser binding is invalid.");
|
|
45
|
+
}
|
|
34
46
|
|
|
35
47
|
try {
|
|
36
48
|
const query = getQuery(event);
|
|
@@ -107,12 +119,21 @@ async function handleGoogleSignInCallback(
|
|
|
107
119
|
});
|
|
108
120
|
|
|
109
121
|
if (flowId && sessionToken) {
|
|
110
|
-
|
|
122
|
+
if (!state.desktopVerifierHash) {
|
|
123
|
+
throw new Error("Missing desktop exchange challenge.");
|
|
124
|
+
}
|
|
125
|
+
await setDesktopExchange(
|
|
126
|
+
flowId,
|
|
127
|
+
sessionToken,
|
|
128
|
+
email,
|
|
129
|
+
state.desktopVerifierHash,
|
|
130
|
+
);
|
|
111
131
|
}
|
|
112
132
|
|
|
113
133
|
return oauthCallbackResponse(event, email, {
|
|
114
134
|
sessionToken,
|
|
115
135
|
desktop,
|
|
136
|
+
desktopWebview: state.desktopWebview,
|
|
116
137
|
returnUrl: state.returnUrl,
|
|
117
138
|
flowId,
|
|
118
139
|
appName: "Clips",
|
|
@@ -1,17 +1,23 @@
|
|
|
1
|
-
import { defineEventHandler, setResponseHeaders } from "h3";
|
|
1
|
+
import { defineEventHandler, getQuery, setResponseHeaders } from "h3";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Tauri updater manifest endpoint for the Clips desktop app.
|
|
5
5
|
*
|
|
6
6
|
* The installed app only needs this endpoint to be valid JSON when no signed
|
|
7
|
-
* updater bundle is available. If the
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* updater bundle is available. If the channel's GitHub pointer release exists,
|
|
8
|
+
* we proxy its signed manifest. If it does not, return a deliberately old
|
|
9
|
+
* no-update manifest so the desktop UI stays quiet instead of surfacing a
|
|
10
10
|
* release-channel setup error to end users.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
type ClipsUpdateChannel = "production" | "nightly";
|
|
14
|
+
|
|
15
|
+
const GITHUB_MANIFEST_URL: Record<ClipsUpdateChannel, string> = {
|
|
16
|
+
production:
|
|
17
|
+
"https://github.com/BuilderIO/agent-native/releases/download/clips-latest/clips-latest.json",
|
|
18
|
+
nightly:
|
|
19
|
+
"https://github.com/BuilderIO/agent-native/releases/download/clips-nightly-latest/clips-nightly-latest.json",
|
|
20
|
+
};
|
|
15
21
|
const CACHE_TTL_MS = 5 * 60_000;
|
|
16
22
|
|
|
17
23
|
// Tauri throws a red-banner error if the requesting client's target triple is
|
|
@@ -48,8 +54,8 @@ export const INERT_MANIFEST = {
|
|
|
48
54
|
},
|
|
49
55
|
};
|
|
50
56
|
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
const cache = new Map<ClipsUpdateChannel, { data: unknown; ts: number }>();
|
|
58
|
+
const inFlight = new Map<ClipsUpdateChannel, Promise<unknown>>();
|
|
53
59
|
|
|
54
60
|
function isManifestLike(value: unknown): value is Record<string, unknown> {
|
|
55
61
|
if (!value || typeof value !== "object") return false;
|
|
@@ -72,8 +78,10 @@ export function hasAllRequiredPlatforms(value: unknown): boolean {
|
|
|
72
78
|
return REQUIRED_PLATFORM_KEYS.every((k) => k in platforms);
|
|
73
79
|
}
|
|
74
80
|
|
|
75
|
-
async function fetchSignedManifest(
|
|
76
|
-
|
|
81
|
+
async function fetchSignedManifest(
|
|
82
|
+
channel: ClipsUpdateChannel,
|
|
83
|
+
): Promise<unknown> {
|
|
84
|
+
const res = await fetch(GITHUB_MANIFEST_URL[channel], {
|
|
77
85
|
headers: {
|
|
78
86
|
accept: "application/json",
|
|
79
87
|
"user-agent": "clips-updater-manifest",
|
|
@@ -94,38 +102,49 @@ async function fetchSignedManifest(): Promise<unknown> {
|
|
|
94
102
|
return json;
|
|
95
103
|
}
|
|
96
104
|
|
|
97
|
-
|
|
105
|
+
function normalizeChannel(value: unknown): ClipsUpdateChannel {
|
|
106
|
+
return value === "nightly" ? "nightly" : "production";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function getManifest(
|
|
110
|
+
channel: ClipsUpdateChannel = "production",
|
|
111
|
+
): Promise<unknown> {
|
|
98
112
|
const now = Date.now();
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
|
|
113
|
+
const cached = cache.get(channel);
|
|
114
|
+
if (cached && now - cached.ts < CACHE_TTL_MS) return cached.data;
|
|
115
|
+
const pending = inFlight.get(channel);
|
|
116
|
+
if (pending) return pending;
|
|
117
|
+
const request = (async () => {
|
|
102
118
|
try {
|
|
103
|
-
const data = await fetchSignedManifest();
|
|
104
|
-
cache
|
|
119
|
+
const data = await fetchSignedManifest(channel);
|
|
120
|
+
cache.set(channel, { data, ts: Date.now() });
|
|
105
121
|
return data;
|
|
106
122
|
} catch {
|
|
107
123
|
// A validation or network failure may serve the last good manifest (or
|
|
108
124
|
// inert fallback), but it must not make that fallback fresh again.
|
|
109
125
|
// Otherwise an incomplete release manifest can indefinitely extend the
|
|
110
126
|
// stale cache window on every failed refresh.
|
|
111
|
-
return cache?.data ?? INERT_MANIFEST;
|
|
127
|
+
return cache.get(channel)?.data ?? INERT_MANIFEST;
|
|
112
128
|
} finally {
|
|
113
|
-
inFlight
|
|
129
|
+
inFlight.delete(channel);
|
|
114
130
|
}
|
|
115
131
|
})();
|
|
116
|
-
|
|
132
|
+
inFlight.set(channel, request);
|
|
133
|
+
return request;
|
|
117
134
|
}
|
|
118
135
|
|
|
119
136
|
export const __clipsUpdaterTest = {
|
|
120
137
|
getManifest,
|
|
121
138
|
reset() {
|
|
122
|
-
cache
|
|
123
|
-
inFlight
|
|
139
|
+
cache.clear();
|
|
140
|
+
inFlight.clear();
|
|
124
141
|
},
|
|
125
142
|
};
|
|
126
143
|
|
|
127
144
|
export default defineEventHandler(async (event) => {
|
|
128
|
-
const
|
|
145
|
+
const query = getQuery(event);
|
|
146
|
+
const channel = normalizeChannel(query.channel);
|
|
147
|
+
const manifest = await getManifest(channel);
|
|
129
148
|
setResponseHeaders(event, {
|
|
130
149
|
"content-type": "application/json; charset=utf-8",
|
|
131
150
|
"cache-control": "public, max-age=60",
|
|
@@ -63,7 +63,7 @@ decisions, feedback, agent runs, and provider audit records.
|
|
|
63
63
|
| `evaluate-triage-item` | Append a decision. |
|
|
64
64
|
| `record-triage-feedback` | Capture human correction for learning. |
|
|
65
65
|
| `approve-factory-item` | Explicitly authorize one bounded run. |
|
|
66
|
-
| `start-builder-for-item` | Govern clear-bug dispatch through Slack or Builder API. |
|
|
66
|
+
| `start-builder-for-item` | Govern clear-bug dispatch through Slack or Builder API, or record a skip reason. |
|
|
67
67
|
| `govern-agent-native-pull-request` | Apply PR evidence and ownership gates. |
|
|
68
68
|
| `list-factory-automations` / `save-factory-automation` / `run-factory-automation` | Inspect or edit org-owned automations. |
|
|
69
69
|
| `list-factory-audit` | Inspect automation runs, evidence, decisions, and provider actions. |
|
|
@@ -41,6 +41,10 @@ export default defineAction({
|
|
|
41
41
|
timezone: meta.timezone ?? null,
|
|
42
42
|
condition: meta.condition ?? null,
|
|
43
43
|
createdBy: meta.createdBy ?? null,
|
|
44
|
+
updatedAt:
|
|
45
|
+
Number.isFinite(resource.updatedAt) && resource.updatedAt > 0
|
|
46
|
+
? new Date(resource.updatedAt).toISOString()
|
|
47
|
+
: null,
|
|
44
48
|
canUpdate,
|
|
45
49
|
runs: await listAutomationRuns({
|
|
46
50
|
owners: [resource.owner],
|
|
@@ -153,7 +153,7 @@ async function writeMetadata(
|
|
|
153
153
|
.where(and(eq(triageItems.id, itemId), eq(triageItems.orgId, orgId)));
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
async function
|
|
156
|
+
export async function recordAutomaticBuilderDecision(input: {
|
|
157
157
|
itemId: string;
|
|
158
158
|
userEmail: string;
|
|
159
159
|
orgId: string;
|
|
@@ -167,6 +167,7 @@ async function recordDecision(input: {
|
|
|
167
167
|
input.itemId,
|
|
168
168
|
"automatic-builder",
|
|
169
169
|
);
|
|
170
|
+
const now = new Date().toISOString();
|
|
170
171
|
await getDb()
|
|
171
172
|
.insert(triageDecisions)
|
|
172
173
|
.values({
|
|
@@ -179,17 +180,26 @@ async function recordDecision(input: {
|
|
|
179
180
|
guardResultsJson: JSON.stringify(input.guardResults),
|
|
180
181
|
model: "factory-automation",
|
|
181
182
|
promptVersion: 1,
|
|
182
|
-
createdAt:
|
|
183
|
+
createdAt: now,
|
|
183
184
|
ownerEmail: input.userEmail,
|
|
184
185
|
orgId: input.orgId,
|
|
185
186
|
})
|
|
186
|
-
.
|
|
187
|
+
.onConflictDoUpdate({
|
|
188
|
+
target: triageDecisions.id,
|
|
189
|
+
set: {
|
|
190
|
+
outcome: input.outcome,
|
|
191
|
+
reason: input.reason,
|
|
192
|
+
guardResultsJson: JSON.stringify(input.guardResults),
|
|
193
|
+
createdAt: now,
|
|
194
|
+
ownerEmail: input.userEmail,
|
|
195
|
+
},
|
|
196
|
+
});
|
|
187
197
|
return id;
|
|
188
198
|
}
|
|
189
199
|
|
|
190
200
|
export default defineAction({
|
|
191
201
|
description:
|
|
192
|
-
"Start the governed clear-bug Builder flow for a Factory item. Slack items are kept in-thread by adding 👀 and tagging @builder.io; grouped Slack repeats share one Builder thread. GitHub issues and Sentry errors use the Builder agent run API. Owner-managed Clips, Design, and Content items are always left for their owner.",
|
|
202
|
+
"Start the governed clear-bug Builder flow for a Factory item, or record a skip with a reason when clearBug is false. Slack items are kept in-thread by adding 👀 and tagging @builder.io; grouped Slack repeats share one Builder thread. GitHub issues and Sentry errors use the Builder agent run API. Owner-managed Clips, Design, and Content items are always left for their owner.",
|
|
193
203
|
schema: z.object({
|
|
194
204
|
itemId: z.string().min(1),
|
|
195
205
|
clearBug: z.boolean(),
|
|
@@ -340,17 +350,12 @@ export default defineAction({
|
|
|
340
350
|
: []),
|
|
341
351
|
];
|
|
342
352
|
const blocked = guardResults.some((guard) => !guard.passed);
|
|
343
|
-
const decisionId = await
|
|
353
|
+
const decisionId = await recordAutomaticBuilderDecision({
|
|
344
354
|
itemId,
|
|
345
355
|
userEmail,
|
|
346
356
|
orgId,
|
|
347
357
|
outcome: blocked ? "needs_manual" : "propose_fix",
|
|
348
|
-
reason
|
|
349
|
-
? guardResults
|
|
350
|
-
.filter((guard) => !guard.passed)
|
|
351
|
-
.map((guard) => guard.reason)
|
|
352
|
-
.join(" ")
|
|
353
|
-
: reason,
|
|
358
|
+
reason,
|
|
354
359
|
guardResults,
|
|
355
360
|
});
|
|
356
361
|
await recordFactoryAudit(
|
|
@@ -363,7 +368,7 @@ export default defineAction({
|
|
|
363
368
|
source: item.source,
|
|
364
369
|
sourceUrl: item.sourceUrl,
|
|
365
370
|
status: blocked ? "skipped" : "success",
|
|
366
|
-
summary:
|
|
371
|
+
summary: reason,
|
|
367
372
|
details: {
|
|
368
373
|
decisionId,
|
|
369
374
|
clearBug,
|
|
@@ -383,10 +388,7 @@ export default defineAction({
|
|
|
383
388
|
started: false,
|
|
384
389
|
needsManual: true,
|
|
385
390
|
decisionId,
|
|
386
|
-
reason
|
|
387
|
-
.filter((guard) => !guard.passed)
|
|
388
|
-
.map((guard) => guard.reason)
|
|
389
|
-
.join(" "),
|
|
391
|
+
reason,
|
|
390
392
|
};
|
|
391
393
|
}
|
|
392
394
|
|
|
@@ -550,7 +552,7 @@ export default defineAction({
|
|
|
550
552
|
.where(
|
|
551
553
|
and(eq(triageItems.id, related.id), eq(triageItems.orgId, orgId)),
|
|
552
554
|
);
|
|
553
|
-
await
|
|
555
|
+
await recordAutomaticBuilderDecision({
|
|
554
556
|
itemId: related.id,
|
|
555
557
|
userEmail,
|
|
556
558
|
orgId,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { requestAgentChatThreadOpen } from "@agent-native/core/client/agent-chat";
|
|
1
2
|
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
2
3
|
import { useT } from "@agent-native/core/client/i18n";
|
|
3
4
|
import {
|
|
@@ -8,7 +9,7 @@ import {
|
|
|
8
9
|
IconExternalLink,
|
|
9
10
|
IconSearch,
|
|
10
11
|
} from "@tabler/icons-react";
|
|
11
|
-
import { useEffect, useMemo } from "react";
|
|
12
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
12
13
|
import { useSearchParams } from "react-router";
|
|
13
14
|
|
|
14
15
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
@@ -70,6 +71,7 @@ export function FactoryAuditView({
|
|
|
70
71
|
}) {
|
|
71
72
|
const t = useT();
|
|
72
73
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
74
|
+
const viewRef = useRef<HTMLDivElement>(null);
|
|
73
75
|
const selectedRunId = searchParams.get("auditRunId");
|
|
74
76
|
const auditQuery = useActionQuery<FactoryAuditResponse>(
|
|
75
77
|
"list-factory-audit",
|
|
@@ -109,8 +111,13 @@ export function FactoryAuditView({
|
|
|
109
111
|
);
|
|
110
112
|
}
|
|
111
113
|
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (!selectedRunId) return;
|
|
116
|
+
viewRef.current?.scrollIntoView({ block: "start" });
|
|
117
|
+
}, [selectedRunId]);
|
|
118
|
+
|
|
112
119
|
return (
|
|
113
|
-
<div className="p-4 lg:p-6">
|
|
120
|
+
<div ref={viewRef} className="p-4 lg:p-6">
|
|
114
121
|
{auditQuery.isError ? (
|
|
115
122
|
<Card>
|
|
116
123
|
<CardContent className="flex items-start gap-2 p-4 text-sm text-destructive">
|
|
@@ -144,30 +151,38 @@ export function FactoryAuditView({
|
|
|
144
151
|
</CardHeader>
|
|
145
152
|
<CardContent className="p-0">
|
|
146
153
|
<div className="grid gap-1.5 p-2">
|
|
147
|
-
{runs.map((run) =>
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
{run.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
154
|
+
{runs.map((run) => {
|
|
155
|
+
const selected = selectedRun?.id === run.id;
|
|
156
|
+
return (
|
|
157
|
+
<button
|
|
158
|
+
key={run.id}
|
|
159
|
+
type="button"
|
|
160
|
+
aria-current={selected ? "true" : undefined}
|
|
161
|
+
className={`w-full cursor-pointer rounded-lg p-3 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring ${
|
|
162
|
+
selected
|
|
163
|
+
? "bg-primary/10 ring-1 ring-inset ring-primary/40"
|
|
164
|
+
: "bg-muted/20 hover:bg-muted/50"
|
|
165
|
+
}`}
|
|
166
|
+
onClick={() => selectRun(run.id)}
|
|
167
|
+
>
|
|
168
|
+
<div className="flex items-center justify-between gap-3">
|
|
169
|
+
<span className="min-w-0 truncate text-sm font-medium">
|
|
170
|
+
{formatAutomationName(run.automation)}
|
|
171
|
+
</span>
|
|
172
|
+
<AuditStatus status={run.status} />
|
|
173
|
+
</div>
|
|
174
|
+
<div className="mt-1 flex items-center justify-between gap-2 text-xs text-muted-foreground">
|
|
175
|
+
<span className="shrink-0">
|
|
176
|
+
{formatAuditAge(run.startedAt)}
|
|
177
|
+
</span>
|
|
178
|
+
<span className="shrink-0">
|
|
179
|
+
{run.events.length}{" "}
|
|
180
|
+
{formatAuditCountLabel(run.events.length)}
|
|
181
|
+
</span>
|
|
182
|
+
</div>
|
|
183
|
+
</button>
|
|
184
|
+
);
|
|
185
|
+
})}
|
|
171
186
|
</div>
|
|
172
187
|
</CardContent>
|
|
173
188
|
</Card>
|
|
@@ -190,7 +205,9 @@ export function FactoryAuditView({
|
|
|
190
205
|
</div>
|
|
191
206
|
</CardHeader>
|
|
192
207
|
<CardContent className="pt-4">
|
|
193
|
-
{selectedRun &&
|
|
208
|
+
{selectedRun && (
|
|
209
|
+
<AuditRunDetail run={selectedRun} factoryId={factoryId} />
|
|
210
|
+
)}
|
|
194
211
|
</CardContent>
|
|
195
212
|
</Card>
|
|
196
213
|
</div>
|
|
@@ -199,7 +216,13 @@ export function FactoryAuditView({
|
|
|
199
216
|
);
|
|
200
217
|
}
|
|
201
218
|
|
|
202
|
-
function AuditRunDetail({
|
|
219
|
+
function AuditRunDetail({
|
|
220
|
+
run,
|
|
221
|
+
factoryId,
|
|
222
|
+
}: {
|
|
223
|
+
run: FactoryAuditRun;
|
|
224
|
+
factoryId: string;
|
|
225
|
+
}) {
|
|
203
226
|
const t = useT();
|
|
204
227
|
const groups = useMemo(() => groupAuditEvents(run.events), [run.events]);
|
|
205
228
|
const itemCount = new Set(
|
|
@@ -229,10 +252,22 @@ function AuditRunDetail({ run }: { run: FactoryAuditRun }) {
|
|
|
229
252
|
{run.threadId && (
|
|
230
253
|
<a
|
|
231
254
|
href={`/chat/${encodeURIComponent(run.threadId)}`}
|
|
232
|
-
className="ml-auto inline-flex items-center
|
|
255
|
+
className="ml-auto inline-flex items-center text-primary hover:underline"
|
|
256
|
+
onClick={(event) => {
|
|
257
|
+
if (
|
|
258
|
+
event.metaKey ||
|
|
259
|
+
event.ctrlKey ||
|
|
260
|
+
event.shiftKey ||
|
|
261
|
+
event.altKey ||
|
|
262
|
+
event.button !== 0
|
|
263
|
+
) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
event.preventDefault();
|
|
267
|
+
requestAgentChatThreadOpen({ threadId: run.threadId! });
|
|
268
|
+
}}
|
|
233
269
|
>
|
|
234
270
|
{t("factoryRoute.auditOpenThread")}
|
|
235
|
-
<IconExternalLink className="size-3" />
|
|
236
271
|
</a>
|
|
237
272
|
)}
|
|
238
273
|
</div>
|
|
@@ -260,7 +295,11 @@ function AuditRunDetail({ run }: { run: FactoryAuditRun }) {
|
|
|
260
295
|
) : (
|
|
261
296
|
<div className="grid gap-1.5">
|
|
262
297
|
{groups.map((group) => (
|
|
263
|
-
<AuditItemRow
|
|
298
|
+
<AuditItemRow
|
|
299
|
+
key={group.key}
|
|
300
|
+
group={group}
|
|
301
|
+
factoryId={factoryId}
|
|
302
|
+
/>
|
|
264
303
|
))}
|
|
265
304
|
</div>
|
|
266
305
|
)}
|
|
@@ -269,15 +308,20 @@ function AuditRunDetail({ run }: { run: FactoryAuditRun }) {
|
|
|
269
308
|
);
|
|
270
309
|
}
|
|
271
310
|
|
|
272
|
-
function AuditItemRow({
|
|
311
|
+
function AuditItemRow({
|
|
312
|
+
group,
|
|
313
|
+
factoryId,
|
|
314
|
+
}: {
|
|
315
|
+
group: AuditItemGroup;
|
|
316
|
+
factoryId: string;
|
|
317
|
+
}) {
|
|
273
318
|
const t = useT();
|
|
274
319
|
const decision = group.decision;
|
|
275
320
|
const rationale = decision?.summary ?? group.externalAction?.summary ?? null;
|
|
276
|
-
const sourceLink =
|
|
277
|
-
group.sourceUrl ?? group.events.find((event) => event.sourceUrl)?.sourceUrl;
|
|
321
|
+
const sourceLink = resolveAuditSourceLink(group);
|
|
278
322
|
|
|
279
323
|
return (
|
|
280
|
-
<details className="group rounded-lg bg-muted/20">
|
|
324
|
+
<details className="group overflow-hidden rounded-lg border border-border bg-muted/20">
|
|
281
325
|
<summary className="flex cursor-pointer list-none items-center gap-3 rounded-lg px-3 py-3 hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring [&::-webkit-details-marker]:hidden">
|
|
282
326
|
<AuditSourceIcon source={group.source} />
|
|
283
327
|
<div className="min-w-0 flex-1">
|
|
@@ -336,7 +380,7 @@ function AuditItemRow({ group }: { group: AuditItemGroup }) {
|
|
|
336
380
|
<div className="mt-4 flex flex-wrap items-center gap-x-4 gap-y-2 bg-background/40 px-3 py-3 text-xs">
|
|
337
381
|
{group.itemId && (
|
|
338
382
|
<a
|
|
339
|
-
href={`/factory?tab=inbox&itemId=${encodeURIComponent(group.itemId)}`}
|
|
383
|
+
href={`/factory?factoryId=${encodeURIComponent(factoryId)}&tab=inbox&itemId=${encodeURIComponent(group.itemId)}`}
|
|
340
384
|
className="text-primary hover:underline"
|
|
341
385
|
>
|
|
342
386
|
{t("factoryRoute.auditOpenItem")}
|
|
@@ -579,6 +623,26 @@ function formatAuditSource(source: string | null): string {
|
|
|
579
623
|
return source ? formatAuditLabel(source) : "Factory";
|
|
580
624
|
}
|
|
581
625
|
|
|
626
|
+
function resolveAuditSourceLink(group: AuditItemGroup): string | null {
|
|
627
|
+
const storedUrl =
|
|
628
|
+
group.sourceUrl ??
|
|
629
|
+
group.events.find((event) => event.sourceUrl)?.sourceUrl ??
|
|
630
|
+
null;
|
|
631
|
+
if (storedUrl) return storedUrl;
|
|
632
|
+
|
|
633
|
+
for (const event of group.events) {
|
|
634
|
+
const channelId = readStringDetail(event.details, "channelId");
|
|
635
|
+
const threadTs = readStringDetail(event.details, "threadTs");
|
|
636
|
+
if (channelId && threadTs) return slackThreadUrl(channelId, threadTs);
|
|
637
|
+
}
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function slackThreadUrl(channelId: string, threadTs: string): string {
|
|
642
|
+
const compactTs = threadTs.replace(".", "");
|
|
643
|
+
return `https://slack.com/archives/${encodeURIComponent(channelId)}/p${compactTs}?thread_ts=${encodeURIComponent(threadTs)}`;
|
|
644
|
+
}
|
|
645
|
+
|
|
582
646
|
function formatAutomationName(value: string): string {
|
|
583
647
|
const words = value
|
|
584
648
|
.replace(/^factory-/, "")
|
|
@@ -69,6 +69,7 @@ const messages = {
|
|
|
69
69
|
status: "Status",
|
|
70
70
|
coverage: "Coverage",
|
|
71
71
|
reason: "Reason",
|
|
72
|
+
updatedAt: "Updated",
|
|
72
73
|
detailTitle: "Decision detail",
|
|
73
74
|
selectItem: "Select an item to inspect its evidence and decisions.",
|
|
74
75
|
detailError: "Could not load this observation.",
|
|
@@ -221,11 +222,14 @@ const messages = {
|
|
|
221
222
|
automationSchedulePlaceholder: "For example, every weekday at 9:00 AM",
|
|
222
223
|
automationEnabledLabel: "Automation is enabled",
|
|
223
224
|
automationPrompt: "Prompt",
|
|
225
|
+
automationLastUpdated: "last updated",
|
|
224
226
|
automationPromptPlaceholder: "What should Factory do when this runs?",
|
|
225
227
|
promptEditorHint: "Edit here, then save the automation.",
|
|
226
228
|
pastRuns: "Past runs",
|
|
227
229
|
pastRunsEmpty: "No runs recorded yet.",
|
|
228
230
|
saveAutomation: "Save",
|
|
231
|
+
automationSaved: "Saved",
|
|
232
|
+
automationSaveFailed: "Could not save the automation.",
|
|
229
233
|
runNow: "Run now",
|
|
230
234
|
automationHealthTitle: "Scheduler health",
|
|
231
235
|
automationHealthDescription:
|