@agent-native/core 0.131.7 → 0.132.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 +2 -2
- package/corpus/core/CHANGELOG.md +123 -0
- package/corpus/core/docs/content/durable-background-runs.mdx +15 -6
- package/corpus/core/docs/design/durable-agent-runs.md +15 -11
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/activity.ts +127 -10
- package/corpus/core/src/a2a/index.ts +3 -0
- package/corpus/core/src/agent/durable-background.ts +38 -28
- package/corpus/core/src/agent/harness/background.ts +12 -2
- package/corpus/core/src/agent/production-agent.ts +18 -15
- package/corpus/core/src/agent/run-store.ts +45 -3
- package/corpus/core/src/application-state/handlers.ts +45 -0
- package/corpus/core/src/application-state/index.ts +3 -0
- package/corpus/core/src/application-state/store.ts +38 -34
- package/corpus/core/src/audit/redact.ts +104 -18
- package/corpus/core/src/cli/create.ts +115 -16
- package/corpus/core/src/client/AgentPanel.tsx +2 -7
- package/corpus/core/src/client/AssistantChat.tsx +10 -2
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +3 -0
- package/corpus/core/src/client/RunStuckBanner.tsx +29 -6
- package/corpus/core/src/client/agent-chat.ts +4 -9
- package/corpus/core/src/client/application-state.ts +115 -12
- package/corpus/core/src/client/dynamic-suggestions.ts +4 -12
- package/corpus/core/src/client/frame-protocol.ts +5 -1
- package/corpus/core/src/client/guided-questions.tsx +12 -23
- package/corpus/core/src/db/client.ts +148 -28
- package/corpus/core/src/db/create-get-db.ts +39 -6
- package/corpus/core/src/db/runtime-diagnostics.ts +41 -6
- package/corpus/core/src/deploy/build.ts +26 -33
- package/corpus/core/src/feature-flags/store.ts +10 -5
- package/corpus/core/src/integrations/a2a-continuations-store.ts +11 -0
- package/corpus/core/src/integrations/config-store.ts +13 -0
- package/corpus/core/src/integrations/google-docs-poller.ts +26 -2
- package/corpus/core/src/mcp-client/routes.ts +33 -2
- package/corpus/core/src/onboarding/plugin.ts +31 -25
- package/corpus/core/src/org/accept-pending.ts +2 -0
- package/corpus/core/src/org/auto-join-domain.ts +2 -0
- package/corpus/core/src/org/context.ts +35 -10
- package/corpus/core/src/org/handlers.ts +6 -0
- package/corpus/core/src/org/request-org-cache.ts +71 -0
- package/corpus/core/src/resources/store.ts +10 -9
- package/corpus/core/src/scripts/runner.ts +6 -3
- package/corpus/core/src/secrets/storage.ts +97 -5
- package/corpus/core/src/server/action-change.ts +33 -0
- package/corpus/core/src/server/action-routes.ts +11 -9
- package/corpus/core/src/server/agent-teams.ts +10 -2
- package/corpus/core/src/server/better-auth-instance.ts +39 -12
- package/corpus/core/src/server/core-routes-plugin.ts +19 -2
- package/corpus/core/src/server/credential-provider.ts +57 -14
- package/corpus/core/src/server/http-response-telemetry.ts +1 -0
- package/corpus/core/src/server/poll.ts +95 -57
- package/corpus/core/src/server/voice-providers-status.ts +19 -4
- package/corpus/core/src/workspace-files/tool.ts +18 -11
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +38 -0
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1 -1
- package/corpus/templates/analytics/actions/ensure-native-v2-dashboards.ts +299 -0
- package/corpus/templates/analytics/actions/list-native-v2-dashboard-templates.ts +34 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +82 -0
- package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +2 -2
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +40 -34
- package/corpus/templates/analytics/app/hooks/use-user-pref.ts +3 -3
- package/corpus/templates/analytics/app/i18n-data.ts +40 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +18 -10
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +14 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +1 -0
- package/corpus/templates/analytics/app/routes/chart.tsx +3 -0
- package/corpus/templates/analytics/changelog/2026-07-30-analytics-dashboards-now-expose-native-funnel-heatmap-callou.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-30-analytics-now-ships-reusable-native-v2-dashboard-manifests-w.md +6 -0
- package/corpus/templates/analytics/server/lib/dashboard-report-render.ts +81 -1
- package/corpus/templates/analytics/server/lib/native-v2-dashboards.ts +457 -0
- package/corpus/templates/analytics/server/lib/report-chart-svg.ts +73 -2
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/analytics/shared/dashboard-funnel.ts +83 -0
- package/corpus/templates/clips/actions/generate-workflow.ts +6 -4
- package/corpus/templates/clips/actions/reconcile-workflow-generation.ts +257 -0
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +166 -4
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +1 -1
- package/corpus/templates/clips/changelog/2026-07-27-stopped-agent-runs-no-longer-leave-generated-workflow-cards-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-30-workflow-generation-now-stops-retrying-after-repeated-failur.md +6 -0
- package/corpus/templates/clips/shared/workflow.ts +5 -0
- package/corpus/templates/content/changelog/2026-07-30-long-running-delegated-requests-now-continue-reliably.md +6 -0
- package/corpus/templates/content/netlify.toml +3 -0
- package/corpus/templates/content/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +2 -1
- package/corpus/templates/slides/actions/patch-deck.ts +25 -1
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +17 -4
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +9 -1
- package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +11 -3
- package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +39 -11
- package/corpus/templates/slides/app/pages/Index.tsx +2 -2
- package/corpus/templates/slides/changelog/2026-07-30-slide-previews-keep-their-aspect-ratio-isolate-embedded-styl.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-slides-no-longer-lose-decks-while-organization-access-is-loa.md +6 -0
- package/corpus/toolkit/CHANGELOG.md +8 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/composer/runtime-adapters.tsx +16 -11
- package/dist/a2a/activity.d.ts +22 -4
- package/dist/a2a/activity.d.ts.map +1 -1
- package/dist/a2a/activity.js +97 -11
- package/dist/a2a/activity.js.map +1 -1
- package/dist/a2a/index.d.ts +1 -1
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js +1 -1
- package/dist/a2a/index.js.map +1 -1
- package/dist/agent/durable-background.d.ts +13 -8
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +36 -28
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/harness/background.d.ts.map +1 -1
- package/dist/agent/harness/background.js +12 -2
- package/dist/agent/harness/background.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +18 -15
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-store.d.ts +2 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +44 -3
- package/dist/agent/run-store.js.map +1 -1
- package/dist/application-state/handlers.d.ts +15 -0
- package/dist/application-state/handlers.d.ts.map +1 -1
- package/dist/application-state/handlers.js +42 -2
- package/dist/application-state/handlers.js.map +1 -1
- package/dist/application-state/index.d.ts +2 -2
- package/dist/application-state/index.d.ts.map +1 -1
- package/dist/application-state/index.js +2 -2
- package/dist/application-state/index.js.map +1 -1
- package/dist/application-state/store.d.ts +11 -0
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +37 -40
- package/dist/application-state/store.js.map +1 -1
- package/dist/audit/redact.d.ts +25 -2
- package/dist/audit/redact.d.ts.map +1 -1
- package/dist/audit/redact.js +85 -21
- package/dist/audit/redact.js.map +1 -1
- package/dist/cli/create.d.ts +8 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +98 -16
- package/dist/cli/create.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +2 -7
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +10 -2
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +1 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/RunStuckBanner.d.ts +11 -1
- package/dist/client/RunStuckBanner.d.ts.map +1 -1
- package/dist/client/RunStuckBanner.js +20 -7
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +4 -6
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/application-state.d.ts +11 -0
- package/dist/client/application-state.d.ts.map +1 -1
- package/dist/client/application-state.js +87 -9
- package/dist/client/application-state.js.map +1 -1
- package/dist/client/dynamic-suggestions.d.ts.map +1 -1
- package/dist/client/dynamic-suggestions.js +4 -13
- package/dist/client/dynamic-suggestions.js.map +1 -1
- package/dist/client/frame-protocol.d.ts +1 -0
- package/dist/client/frame-protocol.d.ts.map +1 -1
- package/dist/client/frame-protocol.js.map +1 -1
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +6 -22
- package/dist/client/guided-questions.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +57 -10
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +122 -31
- package/dist/db/client.js.map +1 -1
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +31 -7
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/db/runtime-diagnostics.d.ts +1 -0
- package/dist/db/runtime-diagnostics.d.ts.map +1 -1
- package/dist/db/runtime-diagnostics.js +31 -6
- package/dist/db/runtime-diagnostics.js.map +1 -1
- package/dist/deploy/build.d.ts +5 -20
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +18 -30
- package/dist/deploy/build.js.map +1 -1
- package/dist/feature-flags/store.d.ts.map +1 -1
- package/dist/feature-flags/store.js +10 -5
- package/dist/feature-flags/store.js.map +1 -1
- package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
- package/dist/integrations/a2a-continuations-store.js +12 -0
- package/dist/integrations/a2a-continuations-store.js.map +1 -1
- package/dist/integrations/config-store.d.ts +1 -0
- package/dist/integrations/config-store.d.ts.map +1 -1
- package/dist/integrations/config-store.js +11 -0
- package/dist/integrations/config-store.js.map +1 -1
- package/dist/integrations/google-docs-poller.d.ts.map +1 -1
- package/dist/integrations/google-docs-poller.js +19 -2
- package/dist/integrations/google-docs-poller.js.map +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
- package/dist/mcp-client/routes.d.ts.map +1 -1
- package/dist/mcp-client/routes.js +30 -2
- package/dist/mcp-client/routes.js.map +1 -1
- package/dist/notifications/routes.d.ts +6 -6
- package/dist/onboarding/plugin.d.ts.map +1 -1
- package/dist/onboarding/plugin.js +12 -8
- package/dist/onboarding/plugin.js.map +1 -1
- package/dist/org/accept-pending.d.ts.map +1 -1
- package/dist/org/accept-pending.js +2 -0
- package/dist/org/accept-pending.js.map +1 -1
- package/dist/org/auto-join-domain.d.ts.map +1 -1
- package/dist/org/auto-join-domain.js +2 -0
- package/dist/org/auto-join-domain.js.map +1 -1
- package/dist/org/context.d.ts.map +1 -1
- package/dist/org/context.js +29 -9
- package/dist/org/context.js.map +1 -1
- package/dist/org/handlers.d.ts.map +1 -1
- package/dist/org/handlers.js +5 -0
- package/dist/org/handlers.js.map +1 -1
- package/dist/org/request-org-cache.d.ts +16 -0
- package/dist/org/request-org-cache.d.ts.map +1 -0
- package/dist/org/request-org-cache.js +62 -0
- package/dist/org/request-org-cache.js.map +1 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +11 -9
- package/dist/resources/store.js.map +1 -1
- package/dist/scripts/runner.d.ts.map +1 -1
- package/dist/scripts/runner.js +3 -3
- package/dist/scripts/runner.js.map +1 -1
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +82 -5
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/action-change.d.ts +20 -0
- package/dist/server/action-change.d.ts.map +1 -1
- package/dist/server/action-change.js +29 -0
- package/dist/server/action-change.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +7 -8
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-teams.d.ts.map +1 -1
- package/dist/server/agent-teams.js +10 -2
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +31 -14
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +1 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +18 -4
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/credential-provider.d.ts +16 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +49 -13
- package/dist/server/credential-provider.js.map +1 -1
- package/dist/server/http-response-telemetry.d.ts.map +1 -1
- package/dist/server/http-response-telemetry.js +1 -0
- package/dist/server/http-response-telemetry.js.map +1 -1
- package/dist/server/poll.d.ts +6 -0
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +84 -47
- package/dist/server/poll.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/voice-providers-status.d.ts.map +1 -1
- package/dist/server/voice-providers-status.js +12 -3
- package/dist/server/voice-providers-status.js.map +1 -1
- package/dist/workspace-files/tool.d.ts.map +1 -1
- package/dist/workspace-files/tool.js +18 -11
- package/dist/workspace-files/tool.js.map +1 -1
- package/docs/content/durable-background-runs.mdx +15 -6
- package/docs/design/durable-agent-runs.md +15 -11
- package/package.json +3 -3
- package/src/a2a/activity.ts +127 -10
- package/src/a2a/index.ts +3 -0
- package/src/agent/durable-background.ts +38 -28
- package/src/agent/harness/background.ts +12 -2
- package/src/agent/production-agent.ts +18 -15
- package/src/agent/run-store.ts +45 -3
- package/src/application-state/handlers.ts +45 -0
- package/src/application-state/index.ts +3 -0
- package/src/application-state/store.ts +38 -34
- package/src/audit/redact.ts +104 -18
- package/src/cli/create.ts +115 -16
- package/src/client/AgentPanel.tsx +2 -7
- package/src/client/AssistantChat.tsx +10 -2
- package/src/client/MultiTabAssistantChat.tsx +3 -0
- package/src/client/RunStuckBanner.tsx +29 -6
- package/src/client/agent-chat.ts +4 -9
- package/src/client/application-state.ts +115 -12
- package/src/client/dynamic-suggestions.ts +4 -12
- package/src/client/frame-protocol.ts +5 -1
- package/src/client/guided-questions.tsx +12 -23
- package/src/db/client.ts +148 -28
- package/src/db/create-get-db.ts +39 -6
- package/src/db/runtime-diagnostics.ts +41 -6
- package/src/deploy/build.ts +26 -33
- package/src/feature-flags/store.ts +10 -5
- package/src/integrations/a2a-continuations-store.ts +11 -0
- package/src/integrations/config-store.ts +13 -0
- package/src/integrations/google-docs-poller.ts +26 -2
- package/src/mcp-client/routes.ts +33 -2
- package/src/onboarding/plugin.ts +31 -25
- package/src/org/accept-pending.ts +2 -0
- package/src/org/auto-join-domain.ts +2 -0
- package/src/org/context.ts +35 -10
- package/src/org/handlers.ts +6 -0
- package/src/org/request-org-cache.ts +71 -0
- package/src/resources/store.ts +10 -9
- package/src/scripts/runner.ts +6 -3
- package/src/secrets/storage.ts +97 -5
- package/src/server/action-change.ts +33 -0
- package/src/server/action-routes.ts +11 -9
- package/src/server/agent-teams.ts +10 -2
- package/src/server/better-auth-instance.ts +39 -12
- package/src/server/core-routes-plugin.ts +19 -2
- package/src/server/credential-provider.ts +57 -14
- package/src/server/http-response-telemetry.ts +1 -0
- package/src/server/poll.ts +95 -57
- package/src/server/voice-providers-status.ts +19 -4
- package/src/workspace-files/tool.ts +18 -11
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
compareAndSetAppState,
|
|
4
|
+
compareAndSetManyAppState,
|
|
5
|
+
readAppState,
|
|
6
|
+
} from "@agent-native/core/application-state";
|
|
7
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
import { WorkflowKindSchema } from "../shared/workflow.js";
|
|
11
|
+
|
|
12
|
+
const WorkflowStateSchema = z
|
|
13
|
+
.object({
|
|
14
|
+
kind: WorkflowKindSchema.optional(),
|
|
15
|
+
status: z.string().optional(),
|
|
16
|
+
content: z.string().optional(),
|
|
17
|
+
recordingId: z.string().optional(),
|
|
18
|
+
requestedAt: z.string().optional(),
|
|
19
|
+
tabId: z.string().optional(),
|
|
20
|
+
claimedAt: z.string().optional(),
|
|
21
|
+
})
|
|
22
|
+
.passthrough();
|
|
23
|
+
|
|
24
|
+
const WorkflowRequestSchema = z
|
|
25
|
+
.object({
|
|
26
|
+
requestedAt: z.string(),
|
|
27
|
+
deliveredAt: z.string().optional(),
|
|
28
|
+
deliveredTabId: z.string().optional(),
|
|
29
|
+
})
|
|
30
|
+
.passthrough();
|
|
31
|
+
|
|
32
|
+
const CLAIM_LEASE_MS = 30_000;
|
|
33
|
+
|
|
34
|
+
export default defineAction({
|
|
35
|
+
description:
|
|
36
|
+
"Track and reconcile the agent run responsible for a generated workflow.",
|
|
37
|
+
agentTool: false,
|
|
38
|
+
schema: z.object({
|
|
39
|
+
operation: z.enum([
|
|
40
|
+
"track",
|
|
41
|
+
"release",
|
|
42
|
+
"mark-delivered",
|
|
43
|
+
"consume",
|
|
44
|
+
"stop",
|
|
45
|
+
]),
|
|
46
|
+
recordingId: z.string().min(1),
|
|
47
|
+
requestedAt: z.string().min(1),
|
|
48
|
+
tabId: z.string().min(1),
|
|
49
|
+
}),
|
|
50
|
+
run: async ({ operation, recordingId, requestedAt, tabId }) => {
|
|
51
|
+
await assertAccess("recording", recordingId, "viewer");
|
|
52
|
+
|
|
53
|
+
const requestKey = `clips-ai-request-${recordingId}`;
|
|
54
|
+
if (operation === "mark-delivered" || operation === "consume") {
|
|
55
|
+
const rawRequest = await readAppState(requestKey);
|
|
56
|
+
if (rawRequest === null) {
|
|
57
|
+
return operation === "consume"
|
|
58
|
+
? { reconciled: false, consumed: true, reason: "missing" as const }
|
|
59
|
+
: { reconciled: false, delivered: true, reason: "missing" as const };
|
|
60
|
+
}
|
|
61
|
+
const parsedRequest = WorkflowRequestSchema.safeParse(rawRequest);
|
|
62
|
+
if (!parsedRequest.success) {
|
|
63
|
+
throw new Error(`Invalid workflow request state for ${recordingId}`);
|
|
64
|
+
}
|
|
65
|
+
if (parsedRequest.data.requestedAt !== requestedAt) {
|
|
66
|
+
return operation === "consume"
|
|
67
|
+
? {
|
|
68
|
+
reconciled: false,
|
|
69
|
+
consumed: true,
|
|
70
|
+
reason: "newer-request" as const,
|
|
71
|
+
}
|
|
72
|
+
: {
|
|
73
|
+
reconciled: false,
|
|
74
|
+
delivered: true,
|
|
75
|
+
reason: "newer-request" as const,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (operation === "mark-delivered") {
|
|
79
|
+
if (parsedRequest.data.deliveredTabId === tabId) {
|
|
80
|
+
return { reconciled: false, delivered: true };
|
|
81
|
+
}
|
|
82
|
+
if (parsedRequest.data.deliveredTabId) {
|
|
83
|
+
return {
|
|
84
|
+
reconciled: false,
|
|
85
|
+
delivered: false,
|
|
86
|
+
reason: "different-run" as const,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const stateKey = `clips-workflow-${recordingId}`;
|
|
91
|
+
const rawState = await readAppState(stateKey);
|
|
92
|
+
if (rawState === null) {
|
|
93
|
+
return { reconciled: false, delivered: false, reason: "missing" };
|
|
94
|
+
}
|
|
95
|
+
const parsedState = WorkflowStateSchema.safeParse(rawState);
|
|
96
|
+
if (!parsedState.success) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Invalid generated workflow state for ${recordingId}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (parsedState.data.status !== "generating") {
|
|
102
|
+
const consumed = await compareAndSetAppState(
|
|
103
|
+
requestKey,
|
|
104
|
+
rawRequest,
|
|
105
|
+
null,
|
|
106
|
+
);
|
|
107
|
+
return consumed
|
|
108
|
+
? {
|
|
109
|
+
reconciled: false,
|
|
110
|
+
delivered: true,
|
|
111
|
+
consumed: true,
|
|
112
|
+
reason: "terminal",
|
|
113
|
+
}
|
|
114
|
+
: {
|
|
115
|
+
reconciled: false,
|
|
116
|
+
delivered: false,
|
|
117
|
+
consumed: false,
|
|
118
|
+
reason: "stale",
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (
|
|
122
|
+
parsedState.data.requestedAt !== requestedAt ||
|
|
123
|
+
parsedState.data.tabId !== tabId
|
|
124
|
+
) {
|
|
125
|
+
return {
|
|
126
|
+
reconciled: false,
|
|
127
|
+
delivered: false,
|
|
128
|
+
reason: "different-run",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const deliveredAt = new Date().toISOString();
|
|
133
|
+
const delivered = await compareAndSetManyAppState([
|
|
134
|
+
{
|
|
135
|
+
key: stateKey,
|
|
136
|
+
expectedValue: rawState,
|
|
137
|
+
nextValue: { ...rawState, claimedAt: deliveredAt },
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
key: requestKey,
|
|
141
|
+
expectedValue: rawRequest,
|
|
142
|
+
nextValue: { ...rawRequest, deliveredAt, deliveredTabId: tabId },
|
|
143
|
+
},
|
|
144
|
+
]);
|
|
145
|
+
return delivered
|
|
146
|
+
? { reconciled: false, delivered: true }
|
|
147
|
+
: { reconciled: false, delivered: false, reason: "stale" as const };
|
|
148
|
+
}
|
|
149
|
+
if (parsedRequest.data.deliveredTabId !== tabId) {
|
|
150
|
+
return {
|
|
151
|
+
reconciled: false,
|
|
152
|
+
consumed: false,
|
|
153
|
+
reason: "not-delivered" as const,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const consumed = await compareAndSetAppState(
|
|
157
|
+
requestKey,
|
|
158
|
+
rawRequest,
|
|
159
|
+
null,
|
|
160
|
+
);
|
|
161
|
+
return consumed
|
|
162
|
+
? { reconciled: false, consumed: true }
|
|
163
|
+
: { reconciled: false, consumed: false, reason: "stale" as const };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const stateKey = `clips-workflow-${recordingId}`;
|
|
167
|
+
const rawState = await readAppState(stateKey);
|
|
168
|
+
if (rawState === null) {
|
|
169
|
+
return { reconciled: false, reason: "missing" as const };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const parsedState = WorkflowStateSchema.safeParse(rawState);
|
|
173
|
+
if (!parsedState.success) {
|
|
174
|
+
throw new Error(`Invalid generated workflow state for ${recordingId}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const state = parsedState.data;
|
|
178
|
+
if (state.status !== "generating") {
|
|
179
|
+
return { reconciled: false, reason: "terminal" as const };
|
|
180
|
+
}
|
|
181
|
+
if (state.requestedAt !== requestedAt) {
|
|
182
|
+
return { reconciled: false, reason: "newer-request" as const };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (operation === "track") {
|
|
186
|
+
if (state.tabId === tabId) {
|
|
187
|
+
return { reconciled: false, tracked: true };
|
|
188
|
+
}
|
|
189
|
+
if (state.tabId) {
|
|
190
|
+
const claimedAt = Date.parse(
|
|
191
|
+
state.claimedAt ?? state.requestedAt ?? "",
|
|
192
|
+
);
|
|
193
|
+
if (
|
|
194
|
+
!Number.isFinite(claimedAt) ||
|
|
195
|
+
Date.now() - claimedAt < CLAIM_LEASE_MS
|
|
196
|
+
) {
|
|
197
|
+
return {
|
|
198
|
+
reconciled: false,
|
|
199
|
+
tracked: false,
|
|
200
|
+
reason: "claimed" as const,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const rawRequest = await readAppState(requestKey);
|
|
205
|
+
if (rawRequest === null) {
|
|
206
|
+
return { reconciled: false, reason: "request-missing" as const };
|
|
207
|
+
}
|
|
208
|
+
const parsedRequest = WorkflowRequestSchema.safeParse(rawRequest);
|
|
209
|
+
if (!parsedRequest.success) {
|
|
210
|
+
throw new Error(`Invalid workflow request state for ${recordingId}`);
|
|
211
|
+
}
|
|
212
|
+
if (parsedRequest.data.requestedAt !== requestedAt) {
|
|
213
|
+
return { reconciled: false, reason: "newer-request" as const };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const tracked = await compareAndSetAppState(stateKey, rawState, {
|
|
217
|
+
...rawState,
|
|
218
|
+
tabId,
|
|
219
|
+
claimedAt: new Date().toISOString(),
|
|
220
|
+
});
|
|
221
|
+
return tracked
|
|
222
|
+
? { reconciled: false, tracked: true }
|
|
223
|
+
: { reconciled: false, tracked: false, reason: "stale" as const };
|
|
224
|
+
}
|
|
225
|
+
if (state.tabId !== tabId) {
|
|
226
|
+
return operation === "release"
|
|
227
|
+
? {
|
|
228
|
+
reconciled: false,
|
|
229
|
+
released: true,
|
|
230
|
+
reason: "different-run" as const,
|
|
231
|
+
}
|
|
232
|
+
: { reconciled: false, reason: "different-run" as const };
|
|
233
|
+
}
|
|
234
|
+
if (operation === "release") {
|
|
235
|
+
const untrackedState = { ...rawState };
|
|
236
|
+
delete untrackedState.tabId;
|
|
237
|
+
delete untrackedState.claimedAt;
|
|
238
|
+
const released = await compareAndSetAppState(
|
|
239
|
+
stateKey,
|
|
240
|
+
rawState,
|
|
241
|
+
untrackedState,
|
|
242
|
+
);
|
|
243
|
+
return released
|
|
244
|
+
? { reconciled: false, released: true }
|
|
245
|
+
: { reconciled: false, released: false, reason: "stale" as const };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const reconciled = await compareAndSetAppState(stateKey, rawState, {
|
|
249
|
+
...rawState,
|
|
250
|
+
status: "failed",
|
|
251
|
+
failedAt: new Date().toISOString(),
|
|
252
|
+
});
|
|
253
|
+
return reconciled
|
|
254
|
+
? { reconciled: true }
|
|
255
|
+
: { reconciled: false, reason: "stale" as const };
|
|
256
|
+
},
|
|
257
|
+
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
generateTabId,
|
|
2
3
|
sendToAgentChat,
|
|
4
|
+
sendToAgentChatAndConfirm,
|
|
3
5
|
type AgentChatMessage,
|
|
4
6
|
} from "@agent-native/core/client/agent-chat";
|
|
5
7
|
import { agentNativePath } from "@agent-native/core/client/api-path";
|
|
@@ -11,6 +13,8 @@ import { useRecordings, type RecordingSummary } from "./use-library";
|
|
|
11
13
|
|
|
12
14
|
const DEFAULT_TITLE = "Untitled recording";
|
|
13
15
|
const TWO_MINUTES_MS = 2 * 60 * 1000;
|
|
16
|
+
export const WORKFLOW_ACTION_MAX_ATTEMPTS = 5;
|
|
17
|
+
const WORKFLOW_ACTION_RETRY_DELAY_MS = 1000;
|
|
14
18
|
|
|
15
19
|
/** True when `title` is blank or equal to the server-seeded default. */
|
|
16
20
|
export function isDefaultTitle(title: string | null | undefined): boolean {
|
|
@@ -45,6 +49,8 @@ interface AiRequest {
|
|
|
45
49
|
message?: string;
|
|
46
50
|
includeFullVideoInAi?: boolean;
|
|
47
51
|
openInChat?: boolean;
|
|
52
|
+
deliveredAt?: string;
|
|
53
|
+
deliveredTabId?: string;
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
const DISPATCHABLE_REQUESTS = new Set([
|
|
@@ -99,6 +105,36 @@ export function useAutoTitleBridge(): void {
|
|
|
99
105
|
const dispatched = useRef<Set<string>>(new Set());
|
|
100
106
|
const inflight = useRef<boolean>(false);
|
|
101
107
|
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
const handleChatRunning = (event: Event) => {
|
|
110
|
+
const detail = (event as CustomEvent).detail;
|
|
111
|
+
if (
|
|
112
|
+
detail?.isRunning !== false ||
|
|
113
|
+
(detail.reason !== "stopped" && detail.reason !== "failed") ||
|
|
114
|
+
typeof detail.tabId !== "string"
|
|
115
|
+
)
|
|
116
|
+
return;
|
|
117
|
+
|
|
118
|
+
const recordingId = recordingIdFromTab(detail.tabId);
|
|
119
|
+
const requestedAt = requestedAtFromTab(detail.tabId);
|
|
120
|
+
if (!recordingId || !requestedAt) return;
|
|
121
|
+
|
|
122
|
+
void retryWorkflowAction(
|
|
123
|
+
{
|
|
124
|
+
operation: "stop",
|
|
125
|
+
recordingId,
|
|
126
|
+
requestedAt,
|
|
127
|
+
tabId: detail.tabId,
|
|
128
|
+
},
|
|
129
|
+
"reconciled",
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
window.addEventListener("agentNative.chatRunning", handleChatRunning);
|
|
134
|
+
return () =>
|
|
135
|
+
window.removeEventListener("agentNative.chatRunning", handleChatRunning);
|
|
136
|
+
}, []);
|
|
137
|
+
|
|
102
138
|
const readyRecordings = recordings.filter((r) => r.status === "ready");
|
|
103
139
|
const readyRecordingsKey = readyRecordings
|
|
104
140
|
.map(
|
|
@@ -144,7 +180,6 @@ export function useAutoTitleBridge(): void {
|
|
|
144
180
|
request.requestedAt ?? "0"
|
|
145
181
|
}`;
|
|
146
182
|
if (dispatched.current.has(dispatchKey)) continue;
|
|
147
|
-
dispatched.current.add(dispatchKey);
|
|
148
183
|
if (
|
|
149
184
|
request.kind === "generate-metadata" ||
|
|
150
185
|
request.kind === "regenerate-title"
|
|
@@ -155,8 +190,67 @@ export function useAutoTitleBridge(): void {
|
|
|
155
190
|
dispatched.current.add(`${rec.id}:fallback`);
|
|
156
191
|
}
|
|
157
192
|
|
|
158
|
-
|
|
193
|
+
if (
|
|
194
|
+
request.kind === "generate-workflow" &&
|
|
195
|
+
typeof request.requestedAt === "string"
|
|
196
|
+
) {
|
|
197
|
+
if (request.deliveredTabId) {
|
|
198
|
+
dispatched.current.add(dispatchKey);
|
|
199
|
+
void consumeWorkflowRequest({
|
|
200
|
+
recordingId: rec.id,
|
|
201
|
+
requestedAt: request.requestedAt,
|
|
202
|
+
tabId: request.deliveredTabId,
|
|
203
|
+
});
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
159
206
|
|
|
207
|
+
const tabId = workflowTabId(rec.id, request.requestedAt);
|
|
208
|
+
try {
|
|
209
|
+
const result = (await callAction(
|
|
210
|
+
"reconcile-workflow-generation" as any,
|
|
211
|
+
{
|
|
212
|
+
operation: "track",
|
|
213
|
+
recordingId: rec.id,
|
|
214
|
+
requestedAt: request.requestedAt,
|
|
215
|
+
tabId,
|
|
216
|
+
} as any,
|
|
217
|
+
)) as { tracked?: boolean };
|
|
218
|
+
if (result.tracked !== true) {
|
|
219
|
+
fallbackTimer = setTimeout(() => void tick(), 1000);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
} catch {
|
|
223
|
+
fallbackTimer = setTimeout(() => void tick(), 1000);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const delivery = await sendToAgentChatAndConfirm({
|
|
227
|
+
...buildAiRequestChatOptions(rec, request),
|
|
228
|
+
tabId,
|
|
229
|
+
chatTarget: "local",
|
|
230
|
+
});
|
|
231
|
+
if (!delivery.delivered) {
|
|
232
|
+
await retryWorkflowAction(
|
|
233
|
+
{
|
|
234
|
+
operation: "release",
|
|
235
|
+
recordingId: rec.id,
|
|
236
|
+
requestedAt: request.requestedAt,
|
|
237
|
+
tabId,
|
|
238
|
+
},
|
|
239
|
+
"released",
|
|
240
|
+
);
|
|
241
|
+
fallbackTimer = setTimeout(() => void tick(), 1000);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
dispatched.current.add(dispatchKey);
|
|
245
|
+
void persistAndConsumeWorkflowRequest({
|
|
246
|
+
recordingId: rec.id,
|
|
247
|
+
requestedAt: request.requestedAt,
|
|
248
|
+
tabId,
|
|
249
|
+
});
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
dispatchAiRequest(rec, request);
|
|
253
|
+
dispatched.current.add(dispatchKey);
|
|
160
254
|
void clearRequest(rec.id);
|
|
161
255
|
} else if (isAutoTitleReplaceable(rec.title, rec.titleSource)) {
|
|
162
256
|
// No server-queued delegation. Only dispatch the fallback for
|
|
@@ -294,8 +388,76 @@ export function buildAiRequestChatOptions(
|
|
|
294
388
|
};
|
|
295
389
|
}
|
|
296
390
|
|
|
297
|
-
|
|
298
|
-
|
|
391
|
+
interface WorkflowRunRequest {
|
|
392
|
+
recordingId: string;
|
|
393
|
+
requestedAt: string;
|
|
394
|
+
tabId: string;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export async function retryWorkflowAction(
|
|
398
|
+
request: WorkflowRunRequest & { operation: string },
|
|
399
|
+
successKey: string,
|
|
400
|
+
): Promise<boolean> {
|
|
401
|
+
for (let attempt = 0; attempt < WORKFLOW_ACTION_MAX_ATTEMPTS; attempt += 1) {
|
|
402
|
+
try {
|
|
403
|
+
const result = (await callAction(
|
|
404
|
+
"reconcile-workflow-generation" as any,
|
|
405
|
+
request as any,
|
|
406
|
+
)) as Record<string, unknown>;
|
|
407
|
+
if (result[successKey] === true) return true;
|
|
408
|
+
if (typeof result.reason === "string" && result.reason !== "stale") {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
} catch {}
|
|
412
|
+
|
|
413
|
+
if (attempt === WORKFLOW_ACTION_MAX_ATTEMPTS - 1) return false;
|
|
414
|
+
await new Promise((resolve) =>
|
|
415
|
+
setTimeout(resolve, WORKFLOW_ACTION_RETRY_DELAY_MS * 2 ** attempt),
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function consumeWorkflowRequest(
|
|
423
|
+
request: WorkflowRunRequest,
|
|
424
|
+
): Promise<boolean> {
|
|
425
|
+
return retryWorkflowAction({ ...request, operation: "consume" }, "consumed");
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async function persistAndConsumeWorkflowRequest(
|
|
429
|
+
request: WorkflowRunRequest,
|
|
430
|
+
): Promise<void> {
|
|
431
|
+
const delivered = await retryWorkflowAction(
|
|
432
|
+
{ ...request, operation: "mark-delivered" },
|
|
433
|
+
"delivered",
|
|
434
|
+
);
|
|
435
|
+
if (delivered) await consumeWorkflowRequest(request);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function workflowTabId(recordingId: string, requestedAt: string) {
|
|
439
|
+
return `clips-workflow:${recordingId}:${encodeURIComponent(requestedAt)}:${generateTabId()}`;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function recordingIdFromTab(tabId: string) {
|
|
443
|
+
const match = /^clips-workflow:([^:]+):/.exec(tabId);
|
|
444
|
+
return match?.[1];
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function requestedAtFromTab(tabId: string) {
|
|
448
|
+
const match = /^clips-workflow:[^:]+:([^:]+):/.exec(tabId);
|
|
449
|
+
return match ? decodeURIComponent(match[1]) : undefined;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function dispatchAiRequest(
|
|
453
|
+
rec: RecordingSummary,
|
|
454
|
+
request: AiRequest,
|
|
455
|
+
tabId?: string,
|
|
456
|
+
) {
|
|
457
|
+
return sendToAgentChat({
|
|
458
|
+
...buildAiRequestChatOptions(rec, request),
|
|
459
|
+
...(tabId ? { tabId } : {}),
|
|
460
|
+
});
|
|
299
461
|
}
|
|
300
462
|
|
|
301
463
|
function parseJsonArray(raw: string | undefined): unknown[] {
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
DASHBOARD_REDIRECT_VALUE,
|
|
28
28
|
REF_PARAM,
|
|
29
29
|
} from "@shared/share-attribution";
|
|
30
|
+
import type { WorkflowKind } from "@shared/workflow";
|
|
30
31
|
import {
|
|
31
32
|
IconShare3,
|
|
32
33
|
IconArrowLeft,
|
|
@@ -109,7 +110,6 @@ export function meta() {
|
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
type SidePanel = "transcript" | "comments" | "insights" | "agent" | "settings";
|
|
112
|
-
type WorkflowKind = "pr" | "sop" | "ticket" | "email";
|
|
113
113
|
|
|
114
114
|
const WORKFLOW_MENU_ITEMS: Array<{
|
|
115
115
|
kind: WorkflowKind;
|
|
@@ -10,6 +10,9 @@ GTM_CONTAINER_ID = "GTM-N3WSTXZ"
|
|
|
10
10
|
NITRO_PRESET = "netlify"
|
|
11
11
|
NPM_CONFIG_PRODUCTION = "false"
|
|
12
12
|
AGENT_RUN_SOFT_TIMEOUT_MS = "55000"
|
|
13
|
+
# Content agent turns can delegate long-running work to sibling apps. Emit and
|
|
14
|
+
# use the async worker so those calls are not cut off by the foreground wall.
|
|
15
|
+
AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
13
16
|
|
|
14
17
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
15
18
|
# The framework does not impose a default agent-run timeout; if a template sets
|
|
@@ -31,6 +31,7 @@ const INITIAL_TOOL_NAMES = [
|
|
|
31
31
|
|
|
32
32
|
export default createAgentChatPlugin({
|
|
33
33
|
appId: "content",
|
|
34
|
+
durableBackgroundRuns: true,
|
|
34
35
|
actions: loadActionsFromStaticRegistry(actionsRegistry),
|
|
35
36
|
initialToolNames: INITIAL_TOOL_NAMES,
|
|
36
37
|
anonymousOwner: resolvePublicViewerOwner,
|
|
@@ -23,7 +23,8 @@ description: How to create a new deck with slides from scratch. Read this before
|
|
|
23
23
|
When the UI has already created the empty deck, keep its id and rename it before
|
|
24
24
|
adding the first slide. Call `patch-deck` with a `patch-deck-fields` operation
|
|
25
25
|
whose `fields.title` is the generated title. Do not leave the pre-created deck's
|
|
26
|
-
placeholder title in place.
|
|
26
|
+
placeholder title in place. Include only `title` in that operation's `fields`;
|
|
27
|
+
omit all other optional fields.
|
|
27
28
|
|
|
28
29
|
Follow the creative-context reuse ladder before inventing a slide language:
|
|
29
30
|
reuse an approved native template unchanged, compose approved pieces, lightly
|
|
@@ -141,6 +141,27 @@ export const OperationSchema = z.discriminatedUnion("op", [
|
|
|
141
141
|
|
|
142
142
|
export type Operation = z.infer<typeof OperationSchema>;
|
|
143
143
|
|
|
144
|
+
// The browser uses the full operation union above, but the agent only needs
|
|
145
|
+
// this action to rename the empty deck created by the UI. Keeping the
|
|
146
|
+
// advertised schema sparse prevents the model from filling editor-only
|
|
147
|
+
// metadata fields with empty defaults that the runtime correctly rejects.
|
|
148
|
+
const AgentPatchDeckInputSchema = z.object({
|
|
149
|
+
deckId: z.string().describe("Deck ID"),
|
|
150
|
+
operations: z
|
|
151
|
+
.array(
|
|
152
|
+
z.object({
|
|
153
|
+
op: z.literal("patch-deck-fields"),
|
|
154
|
+
fields: z.object({
|
|
155
|
+
title: z
|
|
156
|
+
.string()
|
|
157
|
+
.describe("The concise, specific title to apply to the deck"),
|
|
158
|
+
}),
|
|
159
|
+
}),
|
|
160
|
+
)
|
|
161
|
+
.min(1)
|
|
162
|
+
.describe("Rename the pre-created deck before adding its first slide"),
|
|
163
|
+
});
|
|
164
|
+
|
|
144
165
|
const CreativeContextReuseLabelSchema = z.object({
|
|
145
166
|
itemId: z.string().min(1).optional(),
|
|
146
167
|
itemVersionId: z.string().min(1).optional(),
|
|
@@ -323,7 +344,9 @@ export default defineAction({
|
|
|
323
344
|
description:
|
|
324
345
|
"Granular deck patch used by the browser editor for concurrent-safe writes. " +
|
|
325
346
|
"Each operation touches only the target slide or field — concurrent writers " +
|
|
326
|
-
"on different slides never overwrite each other's work."
|
|
347
|
+
"on different slides never overwrite each other's work. When called by the " +
|
|
348
|
+
"agent, use it only to set the generated deck title and include no other " +
|
|
349
|
+
"fields.",
|
|
327
350
|
schema: z.object({
|
|
328
351
|
deckId: z.string().describe("Deck ID"),
|
|
329
352
|
operations: z
|
|
@@ -344,6 +367,7 @@ export default defineAction({
|
|
|
344
367
|
"Optional exact Creative Context provenance for context-backed slide patch operations.",
|
|
345
368
|
),
|
|
346
369
|
}),
|
|
370
|
+
agentInputSchema: AgentPatchDeckInputSchema,
|
|
347
371
|
run: async ({ deckId, operations, creativeContext }) => {
|
|
348
372
|
await assertAccess("deck", deckId, "editor");
|
|
349
373
|
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
DropdownMenuSeparator,
|
|
24
24
|
} from "@/components/ui/dropdown-menu";
|
|
25
25
|
import type { Deck } from "@/context/DeckContext";
|
|
26
|
+
import { getAspectRatioDims } from "@/lib/aspect-ratios";
|
|
26
27
|
|
|
27
28
|
import SlideRenderer from "./SlideRenderer";
|
|
28
29
|
|
|
@@ -51,6 +52,7 @@ export default function DeckCard({
|
|
|
51
52
|
}: DeckCardProps) {
|
|
52
53
|
const t = useT();
|
|
53
54
|
const firstSlide = deck.slides?.[0];
|
|
55
|
+
const previewDims = getAspectRatioDims(deck.aspectRatio);
|
|
54
56
|
const [isRenaming, setIsRenaming] = useState(false);
|
|
55
57
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
56
58
|
const [renameValue, setRenameValue] = useState(deck.title);
|
|
@@ -104,7 +106,12 @@ export default function DeckCard({
|
|
|
104
106
|
}}
|
|
105
107
|
>
|
|
106
108
|
{/* Slide Preview */}
|
|
107
|
-
<div
|
|
109
|
+
<div
|
|
110
|
+
className="relative overflow-hidden bg-muted/30"
|
|
111
|
+
style={{
|
|
112
|
+
aspectRatio: `${previewDims.width} / ${previewDims.height}`,
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
108
115
|
{firstSlide && (
|
|
109
116
|
<SlideRenderer
|
|
110
117
|
slide={firstSlide}
|
|
@@ -112,7 +119,7 @@ export default function DeckCard({
|
|
|
112
119
|
aspectRatio={deck.aspectRatio}
|
|
113
120
|
/>
|
|
114
121
|
)}
|
|
115
|
-
<div className="absolute inset-0 bg-gradient-to-t from-[hsl(240,5%,8%)] via-transparent to-transparent opacity-60" />
|
|
122
|
+
<div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-[hsl(240,5%,8%)] via-transparent to-transparent opacity-60" />
|
|
116
123
|
</div>
|
|
117
124
|
|
|
118
125
|
{/* Info */}
|
|
@@ -171,7 +178,9 @@ export default function DeckCard({
|
|
|
171
178
|
onToggleStar(deck.id, !deck.starred);
|
|
172
179
|
}}
|
|
173
180
|
className={`rounded-md border border-border bg-black/60 p-2 backdrop-blur-sm hover:bg-black/80 sm:p-1.5 ${
|
|
174
|
-
deck.starred
|
|
181
|
+
deck.starred
|
|
182
|
+
? ""
|
|
183
|
+
: "sm:invisible sm:pointer-events-none sm:opacity-0 sm:group-hover:visible sm:group-hover:pointer-events-auto sm:group-hover:opacity-100 sm:group-focus-within:visible sm:group-focus-within:pointer-events-auto sm:group-focus-within:opacity-100"
|
|
175
184
|
}`}
|
|
176
185
|
aria-label={
|
|
177
186
|
deck.starred ? t("home.unstarDeck") : t("home.starDeck")
|
|
@@ -192,7 +201,11 @@ export default function DeckCard({
|
|
|
192
201
|
e.preventDefault();
|
|
193
202
|
e.stopPropagation();
|
|
194
203
|
}}
|
|
195
|
-
className=
|
|
204
|
+
className={`rounded-md border border-border bg-black/60 p-2 backdrop-blur-sm hover:bg-black/80 sm:p-1.5 ${
|
|
205
|
+
menuOpen
|
|
206
|
+
? "sm:visible sm:pointer-events-auto sm:opacity-100"
|
|
207
|
+
: "sm:invisible sm:pointer-events-none sm:opacity-0 sm:group-hover:visible sm:group-hover:pointer-events-auto sm:group-hover:opacity-100 sm:group-focus-within:visible sm:group-focus-within:pointer-events-auto sm:group-focus-within:opacity-100"
|
|
208
|
+
}`}
|
|
196
209
|
aria-label={t("raw.deckOptions")}
|
|
197
210
|
>
|
|
198
211
|
<IconDots className="w-3.5 h-3.5 text-foreground/70" />
|