@agent-native/core 0.157.6 → 0.157.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/agent-native.config.ts +5 -0
- package/corpus/templates/analytics/netlify.toml +1 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +23 -5
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +4 -0
- package/corpus/templates/assets/netlify.toml +3 -0
- package/corpus/templates/brain/netlify.toml +3 -0
- package/corpus/templates/calendar/agent-native.config.ts +5 -0
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +4 -1
- package/corpus/templates/calendar/netlify.toml +3 -0
- package/corpus/templates/chat/agent-native.config.ts +5 -0
- package/corpus/templates/chat/netlify.toml +3 -0
- package/corpus/templates/clips/actions/lib/meeting-content.ts +28 -0
- package/corpus/templates/clips/actions/list-meetings.ts +33 -11
- package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
- package/corpus/templates/clips/app/lib/meeting-history-query.ts +11 -0
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +47 -21
- package/corpus/templates/clips/netlify.toml +4 -0
- package/corpus/templates/content/netlify.toml +3 -0
- package/corpus/templates/design/app/i18n-data.ts +2 -0
- package/corpus/templates/design/netlify.toml +3 -0
- package/corpus/templates/dispatch/netlify.toml +3 -0
- package/corpus/templates/factory/netlify.toml +3 -0
- package/corpus/templates/forms/netlify.toml +3 -0
- package/corpus/templates/macros/netlify.toml +3 -0
- package/corpus/templates/mail/agent-native.config.ts +5 -0
- package/corpus/templates/mail/netlify.toml +3 -0
- package/corpus/templates/plan/netlify.toml +3 -0
- package/corpus/templates/slides/netlify.toml +3 -0
- package/dist/agent/harness/acp-adapter.js +17 -2
- package/dist/agent/harness/ai-sdk-adapter.js +14 -3
- package/dist/agent/harness/background.js +10 -5
- package/dist/agent/harness/hosted.d.ts +20 -0
- package/dist/agent/harness/hosted.js +100 -0
- package/dist/agent/harness/runner.js +100 -77
- package/dist/agent/observational-memory/observer.js +12 -0
- package/dist/agent/production-agent.d.ts +3 -0
- package/dist/agent/production-agent.js +74 -4
- package/dist/agent/thread-data-builder.d.ts +12 -0
- package/dist/agent/thread-data-builder.js +23 -0
- package/dist/agent/types.d.ts +6 -0
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.js +113 -17
- package/dist/client/AgentTaskCard.d.ts +2 -2
- package/dist/client/AgentTaskCard.js +8 -22
- package/dist/client/AssistantChat.d.ts +8 -0
- package/dist/client/AssistantChat.js +106 -59
- package/dist/client/MultiTabAssistantChat.js +50 -6
- package/dist/client/NewWorkspaceAppFlow.js +7 -1
- package/dist/client/agent-chat-adapter.d.ts +6 -0
- package/dist/client/agent-chat-adapter.js +36 -8
- package/dist/client/app-chat-sidebar.d.ts +5 -0
- package/dist/client/app-chat-sidebar.js +53 -9
- package/dist/client/chat/composer-draft.d.ts +4 -0
- package/dist/client/chat/composer-draft.js +63 -0
- package/dist/client/chat-first/apps-rail.js +10 -1
- package/dist/client/chat-first/primary-nav.js +10 -5
- package/dist/client/frame-protocol.d.ts +1 -0
- package/dist/client/hooks/index.d.ts +1 -1
- package/dist/client/hooks/index.js +1 -1
- package/dist/client/i18n.js +5 -2
- package/dist/client/progress/RunsTray.js +129 -16
- package/dist/client/settings/SettingsRow.js +1 -1
- package/dist/client/use-chat-threads.js +11 -6
- package/dist/config.d.ts +11 -0
- package/dist/config.js +52 -0
- package/dist/extensions/actions.js +22 -1
- package/dist/hosted-harness/actions/get-hosted-harness-config.d.ts +2 -0
- package/dist/hosted-harness/actions/get-hosted-harness-config.js +13 -0
- package/dist/hosted-harness/actions/set-hosted-harness-enabled.d.ts +4 -0
- package/dist/hosted-harness/actions/set-hosted-harness-enabled.js +32 -0
- package/dist/localization/default-messages.d.ts +4 -1
- package/dist/localization/default-messages.js +5 -2
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/actions/custom-provider-registration.d.ts +6 -6
- package/dist/provider-api/actions/provider-api.d.ts +4 -4
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.js +12 -0
- package/dist/server/agent-chat-plugin.js +24 -4
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-teams.d.ts +20 -12
- package/dist/server/agent-teams.js +69 -35
- package/dist/server/hosted-harness-policy.d.ts +17 -0
- package/dist/server/hosted-harness-policy.js +38 -0
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/request-context.d.ts +4 -0
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +39 -0
- package/dist/templates/chat/agent-native.config.ts +5 -0
- package/dist/templates/chat/netlify.toml +3 -0
- package/dist/vite/action-types-plugin.js +8 -0
- package/package.json +5 -3
- package/src/templates/chat/agent-native.config.ts +5 -0
- package/src/templates/chat/netlify.toml +3 -0
package/corpus/README.md
CHANGED
|
@@ -19,6 +19,7 @@ AGENT_PROD_CODE_EXECUTION = "sandboxed"
|
|
|
19
19
|
AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
20
20
|
|
|
21
21
|
[context.production.environment]
|
|
22
|
+
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
22
23
|
# Analytics owns its schema at release time (`migrate:production` above), so
|
|
23
24
|
# production request functions skip migrations. Preview functions still run
|
|
24
25
|
# request-path migrations because previews do not run the production release
|
|
@@ -651,9 +651,13 @@ function rewriteSqlFunctionCalls(
|
|
|
651
651
|
return match ? result : result + sql.slice(cursor);
|
|
652
652
|
}
|
|
653
653
|
|
|
654
|
-
function
|
|
655
|
-
const
|
|
656
|
-
|
|
654
|
+
function coerceDateComparisonOperands(sql: string): string {
|
|
655
|
+
const dateField = "(?:event_date|cohort_date)";
|
|
656
|
+
const qualifiedDateField = `(?:[A-Za-z_][A-Za-z0-9_]*\\.)?${dateField}`;
|
|
657
|
+
const comparisonRe = new RegExp(
|
|
658
|
+
`\\b${qualifiedDateField}\\s*(?:<=|>=|<>|=|<|>)\\s*`,
|
|
659
|
+
"gi",
|
|
660
|
+
);
|
|
657
661
|
let cursor = 0;
|
|
658
662
|
let result = "";
|
|
659
663
|
let match = comparisonRe.exec(sql);
|
|
@@ -685,7 +689,10 @@ function coerceEventDateComparisonOperands(sql: string): string {
|
|
|
685
689
|
}
|
|
686
690
|
result += sql.slice(cursor);
|
|
687
691
|
return result.replace(
|
|
688
|
-
|
|
692
|
+
new RegExp(
|
|
693
|
+
`(\\b${qualifiedDateField}\\s*(?:<=|>=|<>|=|<|>)\\s*)'(\\d{4}-\\d{2}-\\d{2})'`,
|
|
694
|
+
"gi",
|
|
695
|
+
),
|
|
689
696
|
"$1DATE '$2'",
|
|
690
697
|
);
|
|
691
698
|
}
|
|
@@ -813,6 +820,17 @@ function translatePostgresJsonOperators(sql: string): string {
|
|
|
813
820
|
|
|
814
821
|
function translateFirstPartyAnalyticsBigQuerySql(sql: string): string {
|
|
815
822
|
let translated = translatePostgresJsonOperators(sql);
|
|
823
|
+
translated = rewriteSqlFunctionCalls(translated, "coalesce", (args) => {
|
|
824
|
+
const uniqueArgs: string[] = [];
|
|
825
|
+
const seen = new Set<string>();
|
|
826
|
+
for (const arg of args) {
|
|
827
|
+
const normalized = arg.trim();
|
|
828
|
+
if (seen.has(normalized)) continue;
|
|
829
|
+
seen.add(normalized);
|
|
830
|
+
uniqueArgs.push(arg);
|
|
831
|
+
}
|
|
832
|
+
return `COALESCE(${uniqueArgs.join(", ")})`;
|
|
833
|
+
});
|
|
816
834
|
translated = translated.replace(
|
|
817
835
|
/\bINTERVAL\s*'(\d+)\s+(day|days|week|weeks|month|months)'/gi,
|
|
818
836
|
(_match, amount: string, unit: string) =>
|
|
@@ -975,7 +993,7 @@ export function renderFirstPartyAnalyticsBigQuerySql(
|
|
|
975
993
|
translateFirstPartyAnalyticsBigQuerySql(normalizedScopeSql);
|
|
976
994
|
const bound = bindSqlArguments(translated, args);
|
|
977
995
|
return addPartitionPrunedEventDeduplication(
|
|
978
|
-
|
|
996
|
+
coerceDateComparisonOperands(qualifyQuerySources(bound, table)),
|
|
979
997
|
table,
|
|
980
998
|
);
|
|
981
999
|
}
|
|
@@ -61,6 +61,10 @@ function hasSuccessfulDashboardSave(
|
|
|
61
61
|
"update-dashboard",
|
|
62
62
|
"mutate-dashboard",
|
|
63
63
|
"compose-dashboard",
|
|
64
|
+
// An extension edit is the whole job when the dashboard panel IS the
|
|
65
|
+
// extension. Leaving it out meant a turn that saved exactly what the user
|
|
66
|
+
// asked for still had to prove itself with a data query.
|
|
67
|
+
"update-extension",
|
|
64
68
|
]);
|
|
65
69
|
return (toolResults ?? []).some((result) => {
|
|
66
70
|
if (result.isError) return false;
|
|
@@ -11,6 +11,9 @@ NITRO_PRESET = "netlify"
|
|
|
11
11
|
NPM_CONFIG_PRODUCTION = "false"
|
|
12
12
|
AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
13
13
|
|
|
14
|
+
[context.production.environment]
|
|
15
|
+
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
16
|
+
|
|
14
17
|
# Emit the Netlify background function used by Assets' durable agent-chat opt-in.
|
|
15
18
|
# The normal server timeout still protects non-agent image/video HTTP handlers.
|
|
16
19
|
[functions."*"]
|
|
@@ -10,6 +10,9 @@ GTM_CONTAINER_ID = "GTM-N3WSTXZ"
|
|
|
10
10
|
NITRO_PRESET = "netlify"
|
|
11
11
|
NPM_CONFIG_PRODUCTION = "false"
|
|
12
12
|
|
|
13
|
+
[context.production.environment]
|
|
14
|
+
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
15
|
+
|
|
13
16
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
14
17
|
# The framework does not impose a default agent-run timeout; if a template sets
|
|
15
18
|
# AGENT_RUN_SOFT_TIMEOUT_MS, keep it below this function timeout so the hidden
|
|
@@ -385,7 +385,10 @@ export function AppLayout({ children }: AppLayoutProps) {
|
|
|
385
385
|
open={shortcutsHelpOpen}
|
|
386
386
|
onClose={() => setShortcutsHelpOpen(false)}
|
|
387
387
|
/>
|
|
388
|
-
<div
|
|
388
|
+
<div
|
|
389
|
+
className="agent-layout-shell flex h-screen overflow-hidden bg-background"
|
|
390
|
+
data-agent-native-shell-variant="custom"
|
|
391
|
+
>
|
|
389
392
|
<Sidebar
|
|
390
393
|
open={sidebarOpen}
|
|
391
394
|
onClose={() => setSidebarOpen(false)}
|
|
@@ -10,6 +10,9 @@ GTM_CONTAINER_ID = "GTM-N3WSTXZ"
|
|
|
10
10
|
NITRO_PRESET = "netlify"
|
|
11
11
|
NPM_CONFIG_PRODUCTION = "false"
|
|
12
12
|
|
|
13
|
+
[context.production.environment]
|
|
14
|
+
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
15
|
+
|
|
13
16
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
14
17
|
# The framework does not impose a default agent-run timeout; if a template sets
|
|
15
18
|
# AGENT_RUN_SOFT_TIMEOUT_MS, keep it below this function timeout so the hidden
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a persisted meeting contains something a user can reopen.
|
|
3
|
+
*
|
|
4
|
+
* Calendar events are materialized before they are recorded, so the meetings
|
|
5
|
+
* table also contains empty calendar rows. History should keep notes and
|
|
6
|
+
* completed meeting rows even when no recording was linked.
|
|
7
|
+
*/
|
|
8
|
+
export interface MeetingContentFields {
|
|
9
|
+
recordingId?: string | null;
|
|
10
|
+
actualStart?: string | null;
|
|
11
|
+
actualEnd?: string | null;
|
|
12
|
+
summaryMd?: string | null;
|
|
13
|
+
userNotesMd?: string | null;
|
|
14
|
+
bulletsJson?: string | null;
|
|
15
|
+
actionItemsJson?: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function meetingRowHasContent(row: MeetingContentFields): boolean {
|
|
19
|
+
return Boolean(
|
|
20
|
+
row.recordingId ||
|
|
21
|
+
row.actualStart ||
|
|
22
|
+
row.actualEnd ||
|
|
23
|
+
(row.summaryMd ?? "").trim() ||
|
|
24
|
+
(row.userNotesMd ?? "").trim() ||
|
|
25
|
+
(row.bulletsJson ?? "[]") !== "[]" ||
|
|
26
|
+
(row.actionItemsJson ?? "[]") !== "[]",
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
lt,
|
|
27
27
|
gte,
|
|
28
28
|
lte,
|
|
29
|
+
ne,
|
|
29
30
|
or,
|
|
30
31
|
sql,
|
|
31
32
|
} from "drizzle-orm";
|
|
@@ -48,9 +49,23 @@ import {
|
|
|
48
49
|
} from "../server/lib/calendar-event-meetings.js";
|
|
49
50
|
import { listEvents } from "../server/lib/google-calendar-client.js";
|
|
50
51
|
import { booleanParam } from "./lib/cli-params.js";
|
|
52
|
+
import { meetingRowHasContent } from "./lib/meeting-content.js";
|
|
51
53
|
|
|
52
54
|
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
|
|
53
55
|
|
|
56
|
+
/** SQL mirror of `meetingRowHasContent`. Keep history filtering consistent. */
|
|
57
|
+
function meetingHasContentFilter() {
|
|
58
|
+
return or(
|
|
59
|
+
isNotNull(schema.meetings.recordingId),
|
|
60
|
+
isNotNull(schema.meetings.actualStart),
|
|
61
|
+
isNotNull(schema.meetings.actualEnd),
|
|
62
|
+
sql`trim(${schema.meetings.summaryMd}) <> ''`,
|
|
63
|
+
sql`trim(${schema.meetings.userNotesMd}) <> ''`,
|
|
64
|
+
ne(schema.meetings.bulletsJson, "[]"),
|
|
65
|
+
ne(schema.meetings.actionItemsJson, "[]"),
|
|
66
|
+
)!;
|
|
67
|
+
}
|
|
68
|
+
|
|
54
69
|
export default defineAction({
|
|
55
70
|
description:
|
|
56
71
|
"List meetings (Granola-style) the current user has access to. Connected calendars are read live; use view='upcoming' / 'past' / 'all' / 'trash' to filter by lifecycle.",
|
|
@@ -64,6 +79,11 @@ export default defineAction({
|
|
|
64
79
|
recordedOnly: booleanParam
|
|
65
80
|
.default(false)
|
|
66
81
|
.describe("Only return persisted meetings that have a linked recording."),
|
|
82
|
+
hasContent: booleanParam
|
|
83
|
+
.default(false)
|
|
84
|
+
.describe(
|
|
85
|
+
"Only return persisted meetings that contain a recording, completed capture, notes, a summary, bullets, or action items.",
|
|
86
|
+
),
|
|
67
87
|
includeLiveCalendar: booleanParam
|
|
68
88
|
.default(true)
|
|
69
89
|
.describe(
|
|
@@ -109,8 +129,9 @@ export default defineAction({
|
|
|
109
129
|
// slice(offset, offset + limit) at the end. To make that final slice
|
|
110
130
|
// correct we must fetch enough rows from BOTH sources to cover the whole
|
|
111
131
|
// offset + limit window before merging — fetching only `limit` would drop
|
|
112
|
-
// events once offset > 0 or the calendar is large. Keep the
|
|
113
|
-
//
|
|
132
|
+
// events once offset > 0 or the calendar is large. Keep the live-event cap
|
|
133
|
+
// so a huge calendar can't blow up the request. Persisted history needs
|
|
134
|
+
// the full sentinel window so pages remain available after 500 rows.
|
|
114
135
|
const windowCount = args.offset + args.limit;
|
|
115
136
|
const upcomingWindowMaxIso = args.upcomingWithinMin
|
|
116
137
|
? new Date(
|
|
@@ -160,6 +181,9 @@ export default defineAction({
|
|
|
160
181
|
if (args.recordedOnly) {
|
|
161
182
|
whereClauses.push(isNotNull(schema.meetings.recordingId));
|
|
162
183
|
}
|
|
184
|
+
if (args.hasContent) {
|
|
185
|
+
whereClauses.push(meetingHasContentFilter());
|
|
186
|
+
}
|
|
163
187
|
|
|
164
188
|
const orderBy =
|
|
165
189
|
args.view === "upcoming"
|
|
@@ -175,7 +199,7 @@ export default defineAction({
|
|
|
175
199
|
.from(schema.meetings)
|
|
176
200
|
.where(and(...whereClauses))
|
|
177
201
|
.orderBy(...orderBy)
|
|
178
|
-
.limit(
|
|
202
|
+
.limit(windowCount + 1)
|
|
179
203
|
.offset(0);
|
|
180
204
|
|
|
181
205
|
// Add a derived `summaryPreview` (first ~100 chars of summaryMd) so the
|
|
@@ -383,13 +407,7 @@ export default defineAction({
|
|
|
383
407
|
if (
|
|
384
408
|
liveEventEmitted &&
|
|
385
409
|
meeting.source === "calendar" &&
|
|
386
|
-
!meeting
|
|
387
|
-
!meeting.actualStart &&
|
|
388
|
-
!meeting.actualEnd &&
|
|
389
|
-
!(meeting.summaryMd ?? "").trim() &&
|
|
390
|
-
!(meeting.userNotesMd ?? "").trim() &&
|
|
391
|
-
(meeting.bulletsJson ?? "[]") === "[]" &&
|
|
392
|
-
(meeting.actionItemsJson ?? "[]") === "[]"
|
|
410
|
+
!meetingRowHasContent(meeting)
|
|
393
411
|
) {
|
|
394
412
|
continue;
|
|
395
413
|
}
|
|
@@ -407,6 +425,10 @@ export default defineAction({
|
|
|
407
425
|
|
|
408
426
|
const meetings = combined.slice(args.offset, args.offset + args.limit);
|
|
409
427
|
|
|
410
|
-
return {
|
|
428
|
+
return {
|
|
429
|
+
meetings,
|
|
430
|
+
calendarErrors,
|
|
431
|
+
hasMore: combined.length > args.offset + args.limit,
|
|
432
|
+
};
|
|
411
433
|
},
|
|
412
434
|
});
|
|
@@ -1575,6 +1575,7 @@ All notable user-facing changes to Clips are documented here. Open it any time f
|
|
|
1575
1575
|
transcriptPending: "Transcript pending",
|
|
1576
1576
|
notesPending: "Notes pending",
|
|
1577
1577
|
pastRecordings: "Past recordings",
|
|
1578
|
+
loadOlder: "Load older",
|
|
1578
1579
|
calendarNeedsReconnect:
|
|
1579
1580
|
"Google Calendar needs to be reconnected to keep showing your upcoming meetings.",
|
|
1580
1581
|
connectGoogleCalendar: "Connect Google Calendar",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { agentNativePath } from "@agent-native/core/client/api-path";
|
|
2
|
-
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
2
|
+
import { callAction, useActionQuery } from "@agent-native/core/client/hooks";
|
|
3
3
|
import { useT } from "@agent-native/core/client/i18n";
|
|
4
4
|
import {
|
|
5
5
|
IconAlertTriangle,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
IconSettings,
|
|
18
18
|
IconX,
|
|
19
19
|
} from "@tabler/icons-react";
|
|
20
|
-
import { useQueryClient } from "@tanstack/react-query";
|
|
20
|
+
import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
|
|
21
21
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
22
22
|
import { NavLink, useSearchParams } from "react-router";
|
|
23
23
|
import { toast } from "sonner";
|
|
@@ -50,6 +50,10 @@ import {
|
|
|
50
50
|
} from "@/components/ui/dropdown-menu";
|
|
51
51
|
import { Input } from "@/components/ui/input";
|
|
52
52
|
import enMessages from "@/i18n/en-US";
|
|
53
|
+
import {
|
|
54
|
+
buildMeetingHistoryQuery,
|
|
55
|
+
MEETING_HISTORY_PAGE_SIZE,
|
|
56
|
+
} from "@/lib/meeting-history-query";
|
|
53
57
|
|
|
54
58
|
export function meta() {
|
|
55
59
|
return [{ title: enMessages.meetingsRoute.pageTitle }];
|
|
@@ -88,6 +92,7 @@ interface CalendarFetchError {
|
|
|
88
92
|
interface ListMeetingsResponse {
|
|
89
93
|
meetings?: Meeting[];
|
|
90
94
|
calendarErrors?: CalendarFetchError[];
|
|
95
|
+
hasMore?: boolean;
|
|
91
96
|
}
|
|
92
97
|
|
|
93
98
|
interface CalendarAccount {
|
|
@@ -849,13 +854,20 @@ export default function MeetingsIndexRoute() {
|
|
|
849
854
|
{},
|
|
850
855
|
{ retry: false },
|
|
851
856
|
);
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
857
|
+
const history = useInfiniteQuery({
|
|
858
|
+
queryKey: ["action", "list-meetings", "history"],
|
|
859
|
+
initialPageParam: 0,
|
|
860
|
+
queryFn: ({ pageParam, signal }) =>
|
|
861
|
+
callAction("list-meetings", buildMeetingHistoryQuery(pageParam), {
|
|
862
|
+
method: "GET",
|
|
863
|
+
signal,
|
|
864
|
+
}) as Promise<ListMeetingsResponse>,
|
|
865
|
+
getNextPageParam: (lastPage, allPages) =>
|
|
866
|
+
lastPage?.hasMore
|
|
867
|
+
? allPages.length * MEETING_HISTORY_PAGE_SIZE
|
|
868
|
+
: undefined,
|
|
869
|
+
retry: false,
|
|
870
|
+
});
|
|
859
871
|
// Upcoming calendar events, read live from connected calendars. This is the
|
|
860
872
|
// Granola-style surface that lets the user record/join a meeting that's about
|
|
861
873
|
// to start. Poll every 30s so a freshly-added calendar event (or one moving
|
|
@@ -886,7 +898,7 @@ export default function MeetingsIndexRoute() {
|
|
|
886
898
|
queryClient.setQueriesData<any>(
|
|
887
899
|
{ queryKey: ["action", "list-meetings"] },
|
|
888
900
|
(prev: any) => {
|
|
889
|
-
if (!prev || Array.isArray(prev)) return prev;
|
|
901
|
+
if (!prev || !Array.isArray(prev.meetings)) return prev;
|
|
890
902
|
return { ...prev, calendarErrors: [] };
|
|
891
903
|
},
|
|
892
904
|
);
|
|
@@ -905,7 +917,7 @@ export default function MeetingsIndexRoute() {
|
|
|
905
917
|
if (connected) break;
|
|
906
918
|
await new Promise((resolve) => window.setTimeout(resolve, 500));
|
|
907
919
|
}
|
|
908
|
-
await
|
|
920
|
+
await history.refetch();
|
|
909
921
|
if (connected) toast.success(t("meetingsRoute.calendarConnected"));
|
|
910
922
|
} catch (err) {
|
|
911
923
|
toast.error(
|
|
@@ -919,14 +931,12 @@ export default function MeetingsIndexRoute() {
|
|
|
919
931
|
queryKey: ["action", "list-meetings"],
|
|
920
932
|
});
|
|
921
933
|
}
|
|
922
|
-
}, [accounts, clearCalendarConnectionWarnings,
|
|
934
|
+
}, [accounts, clearCalendarConnectionWarnings, history, queryClient]);
|
|
923
935
|
|
|
924
|
-
const meetings: Meeting[] = useMemo(
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
return data.meetings ?? [];
|
|
929
|
-
}, [meetingsQuery.data]);
|
|
936
|
+
const meetings: Meeting[] = useMemo(
|
|
937
|
+
() => (history.data?.pages ?? []).flatMap((page) => page?.meetings ?? []),
|
|
938
|
+
[history.data],
|
|
939
|
+
);
|
|
930
940
|
|
|
931
941
|
const upcomingMeetings: Meeting[] = useMemo(() => {
|
|
932
942
|
const data = upcomingQuery.data;
|
|
@@ -961,11 +971,11 @@ export default function MeetingsIndexRoute() {
|
|
|
961
971
|
);
|
|
962
972
|
}, [handleCalendarConnected]);
|
|
963
973
|
|
|
964
|
-
const isLoading = accounts.isLoading ||
|
|
974
|
+
const isLoading = accounts.isLoading || history.isLoading;
|
|
965
975
|
|
|
966
976
|
const calendarLoadError = accounts.isError
|
|
967
977
|
? "Couldn't check your calendar connection. Try again in a moment."
|
|
968
|
-
:
|
|
978
|
+
: history.isError
|
|
969
979
|
? "Couldn't load meetings. Try again in a moment."
|
|
970
980
|
: null;
|
|
971
981
|
|
|
@@ -1111,10 +1121,26 @@ export default function MeetingsIndexRoute() {
|
|
|
1111
1121
|
<UpcomingMeetingsList meetings={filteredUpcoming} />
|
|
1112
1122
|
)}
|
|
1113
1123
|
<RecordedMeetingsList meetings={recordedMeetings} />
|
|
1124
|
+
{history.hasNextPage && (
|
|
1125
|
+
<div className="flex justify-center">
|
|
1126
|
+
<Button
|
|
1127
|
+
variant="outline"
|
|
1128
|
+
size="sm"
|
|
1129
|
+
onClick={() => history.fetchNextPage()}
|
|
1130
|
+
disabled={history.isFetchingNextPage}
|
|
1131
|
+
className="gap-1.5 cursor-pointer"
|
|
1132
|
+
>
|
|
1133
|
+
{history.isFetchingNextPage ? (
|
|
1134
|
+
<IconLoader2 className="h-3.5 w-3.5 animate-spin" />
|
|
1135
|
+
) : null}
|
|
1136
|
+
{t("meetingsRoute.loadOlder", { defaultValue: "Load older" })}
|
|
1137
|
+
</Button>
|
|
1138
|
+
</div>
|
|
1139
|
+
)}
|
|
1114
1140
|
</div>
|
|
1115
1141
|
)}
|
|
1116
1142
|
|
|
1117
|
-
{(
|
|
1143
|
+
{(history.isFetching || upcomingQuery.isFetching) && !isLoading && (
|
|
1118
1144
|
<div className="flex items-center justify-center mt-6 text-xs text-muted-foreground gap-1.5">
|
|
1119
1145
|
<IconLoader2 className="h-3.5 w-3.5 animate-spin" />
|
|
1120
1146
|
{t("meetingsRoute.refreshing")}
|
|
@@ -13,6 +13,10 @@ AUTO_CREATE_DEFAULT_ORG = "1"
|
|
|
13
13
|
# Emit the shared 15-minute Netlify background function used by agent chat and
|
|
14
14
|
# Clips post-recording transcription/seekability workers.
|
|
15
15
|
AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
16
|
+
|
|
17
|
+
[context.production.environment]
|
|
18
|
+
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
19
|
+
|
|
16
20
|
# Netlify Functions run on linux/x64 (AWS Lambda). This tells the deploy build
|
|
17
21
|
# to bundle the matching ffmpeg-static binary into the server bundle so frame
|
|
18
22
|
# extraction, WebM seekability, filmstrips, and audio-only transcription work
|
|
@@ -14,6 +14,9 @@ AGENT_RUN_SOFT_TIMEOUT_MS = "55000"
|
|
|
14
14
|
# use the async worker so those calls are not cut off by the foreground wall.
|
|
15
15
|
AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
16
16
|
|
|
17
|
+
[context.production.environment]
|
|
18
|
+
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
19
|
+
|
|
17
20
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
18
21
|
# The framework does not impose a default agent-run timeout; if a template sets
|
|
19
22
|
# AGENT_RUN_SOFT_TIMEOUT_MS, keep it below this function timeout so the hidden
|
|
@@ -1084,6 +1084,8 @@ const enUS = {
|
|
|
1084
1084
|
"PNG capture isn't available in read-only previews",
|
|
1085
1085
|
pngSaveError: "Could not save PNG",
|
|
1086
1086
|
pngExportError: "Could not export PNG",
|
|
1087
|
+
pdfExportError: "Could not export PDF",
|
|
1088
|
+
pdfDownloaded: "PDF downloaded",
|
|
1087
1089
|
pdfAllScreensDownloaded: "PDF downloaded (all screens)",
|
|
1088
1090
|
openScreenSvg: "Open a screen before exporting SVG",
|
|
1089
1091
|
svgDownloaded: "SVG downloaded",
|
|
@@ -11,6 +11,9 @@ NITRO_PRESET = "netlify"
|
|
|
11
11
|
NPM_CONFIG_PRODUCTION = "false"
|
|
12
12
|
AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
13
13
|
|
|
14
|
+
[context.production.environment]
|
|
15
|
+
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
16
|
+
|
|
14
17
|
# Emit the Netlify background function used by Design's durable agent-chat opt-in.
|
|
15
18
|
# The normal server timeout still protects non-background HTTP handlers.
|
|
16
19
|
[functions."*"]
|
|
@@ -13,6 +13,9 @@ NPM_CONFIG_PRODUCTION = "false"
|
|
|
13
13
|
# function, including workspace-resource updates delegated by sibling apps.
|
|
14
14
|
AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
15
15
|
|
|
16
|
+
[context.production.environment]
|
|
17
|
+
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
18
|
+
|
|
16
19
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
17
20
|
# The framework does not impose a default agent-run timeout; if a template sets
|
|
18
21
|
# AGENT_RUN_SOFT_TIMEOUT_MS, keep it below this function timeout so the hidden
|
|
@@ -10,6 +10,9 @@ GTM_CONTAINER_ID = "GTM-N3WSTXZ"
|
|
|
10
10
|
NITRO_PRESET = "netlify"
|
|
11
11
|
NPM_CONFIG_PRODUCTION = "false"
|
|
12
12
|
|
|
13
|
+
[context.production.environment]
|
|
14
|
+
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
15
|
+
|
|
13
16
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
14
17
|
# The framework does not impose a default agent-run timeout; if a template sets
|
|
15
18
|
# AGENT_RUN_SOFT_TIMEOUT_MS, keep it below this function timeout so the hidden
|
|
@@ -10,6 +10,9 @@ GTM_CONTAINER_ID = "GTM-N3WSTXZ"
|
|
|
10
10
|
NITRO_PRESET = "netlify"
|
|
11
11
|
NPM_CONFIG_PRODUCTION = "false"
|
|
12
12
|
|
|
13
|
+
[context.production.environment]
|
|
14
|
+
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
15
|
+
|
|
13
16
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
14
17
|
# The framework does not impose a default agent-run timeout; if a template sets
|
|
15
18
|
# AGENT_RUN_SOFT_TIMEOUT_MS, keep it below this function timeout so the hidden
|
|
@@ -11,6 +11,9 @@ NITRO_PRESET = "netlify"
|
|
|
11
11
|
NPM_CONFIG_PRODUCTION = "false"
|
|
12
12
|
AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
13
13
|
|
|
14
|
+
[context.production.environment]
|
|
15
|
+
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
16
|
+
|
|
14
17
|
# A2A/MCP calls may include plan generation, artifact serialization, and
|
|
15
18
|
# feedback polling. Keep this aligned with other agent-heavy hosted templates.
|
|
16
19
|
# Agent chat runs are dispatched to the emitted Netlify background function;
|
|
@@ -14,6 +14,9 @@ NITRO_PRESET = "netlify"
|
|
|
14
14
|
NPM_CONFIG_PRODUCTION = "false"
|
|
15
15
|
AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
16
16
|
|
|
17
|
+
[context.production.environment]
|
|
18
|
+
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
19
|
+
|
|
17
20
|
# Function wall for the non-agent long-request paths (deck import/export,
|
|
18
21
|
# image generation). Agent chat opts into the separate Netlify background
|
|
19
22
|
# function so hosted creative turns can use the longer background budget.
|
|
@@ -167,8 +167,12 @@ class AcpHarnessSession {
|
|
|
167
167
|
this.acpSessionId = resume.sessionId;
|
|
168
168
|
return;
|
|
169
169
|
}
|
|
170
|
-
catch {
|
|
171
|
-
|
|
170
|
+
catch (error) {
|
|
171
|
+
if (!isExplicitlyMissingAcpSession(error))
|
|
172
|
+
throw error;
|
|
173
|
+
// A provider can legitimately evict an old session. Only that
|
|
174
|
+
// explicit condition may fall back to a fresh session; transport or
|
|
175
|
+
// permission failures must remain visible to the caller.
|
|
172
176
|
}
|
|
173
177
|
}
|
|
174
178
|
const created = await this.connection.newSession({
|
|
@@ -321,6 +325,9 @@ class AcpHarnessSession {
|
|
|
321
325
|
return { content: sliceTextFile(raw, params.line, params.limit) };
|
|
322
326
|
}
|
|
323
327
|
async handleWriteTextFile(params) {
|
|
328
|
+
if (this.permissionMode === "allow-reads") {
|
|
329
|
+
throw new Error("[acp-harness] File writes are disabled in allow-reads mode.");
|
|
330
|
+
}
|
|
324
331
|
const abs = resolveAcpWorkspacePath(this.cwd, params.path);
|
|
325
332
|
const existed = await fileExists(abs);
|
|
326
333
|
await fs.mkdir(path.dirname(abs), { recursive: true });
|
|
@@ -604,6 +611,14 @@ function acpErrorMessage(error) {
|
|
|
604
611
|
}
|
|
605
612
|
return typeof error === "string" ? error : "ACP request failed";
|
|
606
613
|
}
|
|
614
|
+
function isExplicitlyMissingAcpSession(error) {
|
|
615
|
+
const message = error instanceof Error
|
|
616
|
+
? error.message
|
|
617
|
+
: typeof error === "string"
|
|
618
|
+
? error
|
|
619
|
+
: "";
|
|
620
|
+
return /(?:session|conversation).*(?:not found|does not exist|unknown|unsupported)|(?:unknown|unsupported).*(?:session|conversation)/i.test(message);
|
|
621
|
+
}
|
|
607
622
|
/** Minimal async queue bridging ACP's callback updates to an async iterable. */
|
|
608
623
|
class AsyncEventQueue {
|
|
609
624
|
values = [];
|
|
@@ -199,8 +199,11 @@ async function createNativeSession(agent, options) {
|
|
|
199
199
|
try {
|
|
200
200
|
return await agent.createSession({ resumeState: options.resumeState });
|
|
201
201
|
}
|
|
202
|
-
catch {
|
|
203
|
-
|
|
202
|
+
catch (error) {
|
|
203
|
+
if (isExplicitlyMissingHarnessSession(error)) {
|
|
204
|
+
return agent.createSession();
|
|
205
|
+
}
|
|
206
|
+
throw error;
|
|
204
207
|
}
|
|
205
208
|
}
|
|
206
209
|
if (typeof agent.createSession !== "function") {
|
|
@@ -237,7 +240,7 @@ class AiSdkHarnessSession {
|
|
|
237
240
|
}
|
|
238
241
|
async *continueTurn(input = {}) {
|
|
239
242
|
if (typeof this.agent.continueStream !== "function") {
|
|
240
|
-
|
|
243
|
+
throw new Error("[agent-harness] This runtime cannot continue a harness turn.");
|
|
241
244
|
}
|
|
242
245
|
const result = await this.agent.continueStream({
|
|
243
246
|
session: this.nativeSession,
|
|
@@ -353,3 +356,11 @@ function normalizeFileOperation(value) {
|
|
|
353
356
|
? value
|
|
354
357
|
: "unknown";
|
|
355
358
|
}
|
|
359
|
+
function isExplicitlyMissingHarnessSession(error) {
|
|
360
|
+
const message = error instanceof Error
|
|
361
|
+
? error.message
|
|
362
|
+
: typeof error === "string"
|
|
363
|
+
? error
|
|
364
|
+
: "";
|
|
365
|
+
return /(?:session|conversation).*(?:not found|does not exist|unknown|unsupported)|(?:unknown|unsupported).*(?:session|conversation)/i.test(message);
|
|
366
|
+
}
|