@agent-native/core 0.84.20 → 0.84.22
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/core/CHANGELOG.md +16 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/caller-auth.ts +37 -8
- package/corpus/core/src/a2a/client.ts +178 -67
- package/corpus/core/src/agent/production-agent.ts +19 -3
- package/corpus/core/src/agent/types.ts +1 -1
- package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
- package/corpus/core/src/cli/recap.ts +43 -34
- package/corpus/core/src/client/AssistantChat.tsx +27 -4
- package/corpus/core/src/client/PoweredByBadge.tsx +70 -17
- package/corpus/core/src/client/agent-chat-adapter.ts +5 -1
- package/corpus/core/src/client/guided-questions.tsx +11 -6
- package/corpus/core/src/client/i18n.tsx +10 -5
- package/corpus/core/src/client/sse-event-processor.ts +147 -5
- package/corpus/core/src/client/tool-display.ts +8 -0
- package/corpus/core/src/file-upload/builder.ts +40 -14
- package/corpus/core/src/file-upload/types.ts +3 -0
- package/corpus/core/src/integrations/a2a-continuation-processor.ts +49 -24
- package/corpus/core/src/mcp/builtin-tools.ts +3 -0
- package/corpus/core/src/mcp/org-directory.ts +66 -20
- package/corpus/core/src/notifications/channels.ts +235 -38
- package/corpus/core/src/scripts/call-agent.ts +7 -2
- package/corpus/core/src/server/analytics.ts +32 -6
- package/corpus/core/src/server/ssr-handler.ts +31 -12
- package/corpus/templates/analytics/AGENTS.md +21 -0
- package/corpus/templates/analytics/README.md +35 -0
- package/corpus/templates/analytics/actions/delete-analytics-alert-rule.ts +23 -0
- package/corpus/templates/analytics/actions/list-analytics-alert-rules.ts +20 -0
- package/corpus/templates/analytics/actions/run-analytics-alerts.ts +23 -0
- package/corpus/templates/analytics/actions/save-analytics-alert-rule.ts +69 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +161 -12
- package/corpus/templates/analytics/changelog/2026-07-01-analytics-can-alert-when-first-party-events-spike.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-01-session-replays-load-large-production-recordings-from-scoped.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-01-session-replays-play-back-in-production-instead-of-showing.md +6 -0
- package/corpus/templates/analytics/netlify.toml +3 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +109 -0
- package/corpus/templates/analytics/server/db/schema.ts +55 -0
- package/corpus/templates/analytics/server/handlers/session-replay.ts +5 -2
- package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +71 -0
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +734 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +10 -3
- package/corpus/templates/analytics/server/plugins/analytics-alert-jobs.ts +54 -0
- package/corpus/templates/analytics/server/plugins/db.ts +100 -0
- package/corpus/templates/analytics/server/routes/api/analytics-alerts/run.post.ts +60 -0
- package/corpus/templates/assets/README.md +30 -8
- package/corpus/templates/brain/README.md +20 -552
- package/corpus/templates/calendar/README.md +22 -28
- package/corpus/templates/calendar/app/pages/BookingPage.tsx +14 -8
- package/corpus/templates/calendar/changelog/2026-07-01-meeting-invite-pages-have-cleaner-branding-a-black-dark-mo.md +6 -0
- package/corpus/templates/chat/README.md +32 -0
- package/corpus/templates/clips/README.md +37 -0
- package/corpus/templates/clips/actions/create-recording.ts +7 -2
- package/corpus/templates/clips/actions/finalize-recording.ts +11 -0
- package/corpus/templates/clips/actions/get-recording-player-data.ts +4 -2
- package/corpus/templates/clips/actions/import-loom-recording.ts +1 -0
- package/corpus/templates/clips/actions/lib/loom-video.ts +16 -0
- package/corpus/templates/clips/app/components/library/library-grid.tsx +18 -7
- package/corpus/templates/clips/app/components/library/recording-card.tsx +42 -5
- package/corpus/templates/clips/app/components/player/scrubber.tsx +2 -2
- package/corpus/templates/clips/app/components/recorder/countdown-overlay.tsx +4 -3
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +39 -12
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +106 -2
- package/corpus/templates/clips/app/components/recorder/recording-toolbar.tsx +1 -22
- package/corpus/templates/clips/app/routes/record.tsx +48 -8
- package/corpus/templates/clips/changelog/2026-07-01-clip-uploads-and-loom-imports-finish-sooner-by-skipping-the-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-clip-uploads-and-playback-recover-more-reliably-from-storage.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-desktop-recording-avoids-unsupported-browser-tab-capture.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-embedded-player-progress-is-easier-to-see-on-dark-videos.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-loom-imports-show-clear-guidance-when-loom-does-not-provide-a-downloadable-video.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-move-to-folder-now-opens-folder-choices-directly-instead-of.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-preserve-in-flight-streaming-uploads-when-the-streaming-kill.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-recording-controls-are-cleaner-and-the-countdown-uses-shadow.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-recordings-now-recover-when-final-upload-status-gets-lost.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +7 -224
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +64 -6
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +87 -5
- package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +7 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +51 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +4 -1
- package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +96 -1
- package/corpus/templates/clips/shared/finalize-recovery.ts +242 -0
- package/corpus/templates/content/AGENTS.md +7 -3
- package/corpus/templates/content/README.md +22 -34
- package/corpus/templates/content/actions/list-trashed-content-databases.ts +4 -0
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +58 -6
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +257 -24
- package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +41 -15
- package/corpus/templates/content/app/i18n/zh-TW.ts +25 -7
- package/corpus/templates/content/app/i18n-data.ts +278 -40
- package/corpus/templates/content/changelog/2026-06-30-database-once-again-creates-an-inline-database-in-the-curren.md +6 -0
- package/corpus/templates/content/shared/api.ts +1 -0
- package/corpus/templates/design/AGENTS.md +7 -6
- package/corpus/templates/design/README.md +33 -0
- package/corpus/templates/design/actions/delete-file.ts +86 -10
- package/corpus/templates/design/actions/get-design-snapshot.ts +13 -0
- package/corpus/templates/design/actions/present-design-variants.ts +11 -13
- package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
- package/corpus/templates/design/app/pages/DesignEditor.tsx +2 -2
- package/corpus/templates/design/changelog/2026-07-01-design-recovers-sooner-when-an-agent-gets-stuck-preparing-a-.md +6 -0
- package/corpus/templates/dispatch/README.md +34 -0
- package/corpus/templates/forms/README.md +34 -0
- package/corpus/templates/macros/README.md +28 -0
- package/corpus/templates/mail/README.md +35 -0
- package/corpus/templates/plan/README.md +30 -118
- package/corpus/templates/plan/app/global.css +1 -1
- package/corpus/templates/slides/README.md +34 -0
- package/corpus/templates/videos/README.md +22 -201
- package/dist/a2a/caller-auth.d.ts +1 -0
- package/dist/a2a/caller-auth.d.ts.map +1 -1
- package/dist/a2a/caller-auth.js +34 -8
- package/dist/a2a/caller-auth.js.map +1 -1
- package/dist/a2a/client.d.ts +4 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +148 -58
- package/dist/a2a/client.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +19 -4
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/types.d.ts +1 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/cli/recap.d.ts +3 -3
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +41 -34
- package/dist/cli/recap.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +28 -4
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/PoweredByBadge.d.ts +9 -3
- package/dist/client/PoweredByBadge.d.ts.map +1 -1
- package/dist/client/PoweredByBadge.js +47 -15
- package/dist/client/PoweredByBadge.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +5 -1
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +10 -6
- package/dist/client/guided-questions.js.map +1 -1
- package/dist/client/i18n.d.ts +1 -1
- package/dist/client/i18n.d.ts.map +1 -1
- package/dist/client/i18n.js +6 -2
- package/dist/client/i18n.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +2 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +121 -5
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/tool-display.d.ts.map +1 -1
- package/dist/client/tool-display.js +8 -0
- package/dist/client/tool-display.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/file-upload/builder.d.ts.map +1 -1
- package/dist/file-upload/builder.js +26 -8
- package/dist/file-upload/builder.js.map +1 -1
- package/dist/file-upload/types.d.ts +5 -1
- package/dist/file-upload/types.d.ts.map +1 -1
- package/dist/file-upload/types.js.map +1 -1
- package/dist/integrations/a2a-continuation-processor.js +43 -18
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +3 -0
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/mcp/org-directory.d.ts.map +1 -1
- package/dist/mcp/org-directory.js +58 -20
- package/dist/mcp/org-directory.js.map +1 -1
- package/dist/notifications/channels.d.ts +10 -0
- package/dist/notifications/channels.d.ts.map +1 -1
- package/dist/notifications/channels.js +172 -20
- package/dist/notifications/channels.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +2 -2
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/analytics.d.ts +16 -0
- package/dist/server/analytics.d.ts.map +1 -1
- package/dist/server/analytics.js +30 -7
- package/dist/server/analytics.js.map +1 -1
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +28 -12
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
type FetchLike = (
|
|
2
|
+
input: RequestInfo | URL,
|
|
3
|
+
init?: RequestInit,
|
|
4
|
+
) => Promise<Response>;
|
|
5
|
+
|
|
6
|
+
export type RecoveredReadyRecording = {
|
|
7
|
+
ok: true;
|
|
8
|
+
finalized: true;
|
|
9
|
+
recoveredAfterFinalizeError: true;
|
|
10
|
+
id: string;
|
|
11
|
+
recordingId: string;
|
|
12
|
+
status: "ready";
|
|
13
|
+
videoUrl: string;
|
|
14
|
+
durationMs?: number;
|
|
15
|
+
width?: number;
|
|
16
|
+
height?: number;
|
|
17
|
+
hasAudio?: boolean;
|
|
18
|
+
hasCamera?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type ProbeResult =
|
|
22
|
+
| { ready: true; result: RecoveredReadyRecording }
|
|
23
|
+
| { ready: false; terminal: boolean; status?: string };
|
|
24
|
+
|
|
25
|
+
const DEFAULT_READY_RECOVERY_TIMEOUT_MS = 90_000;
|
|
26
|
+
const DEFAULT_READY_RECOVERY_INTERVAL_MS = 3_000;
|
|
27
|
+
const DEFAULT_READY_RECOVERY_FETCH_TIMEOUT_MS = 2_000;
|
|
28
|
+
|
|
29
|
+
function sleep(ms: number): Promise<void> {
|
|
30
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function absoluteUploadUrl(uploadUrl: string): URL {
|
|
34
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(uploadUrl)) return new URL(uploadUrl);
|
|
35
|
+
const origin =
|
|
36
|
+
typeof globalThis.location?.origin === "string"
|
|
37
|
+
? globalThis.location.origin
|
|
38
|
+
: "http://localhost";
|
|
39
|
+
return new URL(uploadUrl, origin);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function maybeRelativeUrl(url: URL, original: string): string {
|
|
43
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(original)) return url.toString();
|
|
44
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function publicRecordingStatusUrl(
|
|
48
|
+
uploadUrl: string,
|
|
49
|
+
recordingId: string,
|
|
50
|
+
): string {
|
|
51
|
+
const url = absoluteUploadUrl(uploadUrl);
|
|
52
|
+
const match = url.pathname.match(/^(.*)\/api\/uploads\/[^/]+\/chunk$/);
|
|
53
|
+
const basePath = match?.[1] ?? "";
|
|
54
|
+
url.pathname = `${basePath}/api/public-recording`;
|
|
55
|
+
url.search = "";
|
|
56
|
+
url.searchParams.set("id", recordingId);
|
|
57
|
+
return maybeRelativeUrl(url, uploadUrl);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function authenticatedRecordingStatusUrl(
|
|
61
|
+
uploadUrl: string,
|
|
62
|
+
recordingId: string,
|
|
63
|
+
): string {
|
|
64
|
+
const url = absoluteUploadUrl(uploadUrl);
|
|
65
|
+
const match = url.pathname.match(/^(.*)\/api\/uploads\/[^/]+\/chunk$/);
|
|
66
|
+
const basePath = match?.[1] ?? "";
|
|
67
|
+
url.pathname = `${basePath}/api/uploads/${encodeURIComponent(
|
|
68
|
+
recordingId,
|
|
69
|
+
)}/status`;
|
|
70
|
+
url.search = "";
|
|
71
|
+
return maybeRelativeUrl(url, uploadUrl);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function optionalNumber(value: unknown): number | undefined {
|
|
75
|
+
return typeof value === "number" && Number.isFinite(value)
|
|
76
|
+
? value
|
|
77
|
+
: undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function optionalBoolean(value: unknown): boolean | undefined {
|
|
81
|
+
return typeof value === "boolean" ? value : undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function readyRecordingFromPublicPayload(
|
|
85
|
+
payload: unknown,
|
|
86
|
+
fallbackRecordingId: string,
|
|
87
|
+
): ProbeResult {
|
|
88
|
+
const root =
|
|
89
|
+
payload && typeof payload === "object"
|
|
90
|
+
? (payload as Record<string, unknown>)
|
|
91
|
+
: null;
|
|
92
|
+
const recording =
|
|
93
|
+
root?.recording && typeof root.recording === "object"
|
|
94
|
+
? (root.recording as Record<string, unknown>)
|
|
95
|
+
: null;
|
|
96
|
+
const status =
|
|
97
|
+
typeof recording?.status === "string" ? recording.status : undefined;
|
|
98
|
+
|
|
99
|
+
if (!recording) return { ready: false, terminal: false };
|
|
100
|
+
if (status === "failed") return { ready: false, terminal: true, status };
|
|
101
|
+
|
|
102
|
+
const videoUrl =
|
|
103
|
+
typeof recording.videoUrl === "string" ? recording.videoUrl : "";
|
|
104
|
+
if (status !== "ready" || !videoUrl) {
|
|
105
|
+
return { ready: false, terminal: false, status };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const id =
|
|
109
|
+
typeof recording.id === "string" && recording.id
|
|
110
|
+
? recording.id
|
|
111
|
+
: fallbackRecordingId;
|
|
112
|
+
return {
|
|
113
|
+
ready: true,
|
|
114
|
+
result: {
|
|
115
|
+
ok: true,
|
|
116
|
+
finalized: true,
|
|
117
|
+
recoveredAfterFinalizeError: true,
|
|
118
|
+
id,
|
|
119
|
+
recordingId: id,
|
|
120
|
+
status: "ready",
|
|
121
|
+
videoUrl,
|
|
122
|
+
durationMs: optionalNumber(recording.durationMs),
|
|
123
|
+
width: optionalNumber(recording.width),
|
|
124
|
+
height: optionalNumber(recording.height),
|
|
125
|
+
hasAudio: optionalBoolean(recording.hasAudio),
|
|
126
|
+
hasCamera: optionalBoolean(recording.hasCamera),
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function fetchWithTimeout(
|
|
132
|
+
fetchImpl: FetchLike,
|
|
133
|
+
url: string,
|
|
134
|
+
init: RequestInit,
|
|
135
|
+
timeoutMs: number,
|
|
136
|
+
): Promise<Response> {
|
|
137
|
+
if (timeoutMs <= 0 || typeof AbortController === "undefined") {
|
|
138
|
+
return fetchImpl(url, init);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const controller = new AbortController();
|
|
142
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
143
|
+
try {
|
|
144
|
+
return await fetchImpl(url, { ...init, signal: controller.signal });
|
|
145
|
+
} finally {
|
|
146
|
+
clearTimeout(timer);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function waitForReadyRecordingAfterFinalizeError(args: {
|
|
151
|
+
uploadUrl: string;
|
|
152
|
+
recordingId: string;
|
|
153
|
+
authToken?: string | null;
|
|
154
|
+
preferAuthenticated?: boolean;
|
|
155
|
+
fetchImpl?: FetchLike;
|
|
156
|
+
sleepImpl?: (ms: number) => Promise<void>;
|
|
157
|
+
timeoutMs?: number;
|
|
158
|
+
intervalMs?: number;
|
|
159
|
+
fetchTimeoutMs?: number;
|
|
160
|
+
}): Promise<RecoveredReadyRecording | null> {
|
|
161
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
162
|
+
const sleepImpl = args.sleepImpl ?? sleep;
|
|
163
|
+
const timeoutMs = Math.max(
|
|
164
|
+
1,
|
|
165
|
+
args.timeoutMs ?? DEFAULT_READY_RECOVERY_TIMEOUT_MS,
|
|
166
|
+
);
|
|
167
|
+
const intervalMs = Math.max(
|
|
168
|
+
1,
|
|
169
|
+
args.intervalMs ?? DEFAULT_READY_RECOVERY_INTERVAL_MS,
|
|
170
|
+
);
|
|
171
|
+
const attempts = Math.max(1, Math.ceil(timeoutMs / intervalMs));
|
|
172
|
+
let url: string;
|
|
173
|
+
let authenticatedUrl: string | null = null;
|
|
174
|
+
try {
|
|
175
|
+
url = publicRecordingStatusUrl(args.uploadUrl, args.recordingId);
|
|
176
|
+
authenticatedUrl =
|
|
177
|
+
args.authToken || args.preferAuthenticated
|
|
178
|
+
? authenticatedRecordingStatusUrl(args.uploadUrl, args.recordingId)
|
|
179
|
+
: null;
|
|
180
|
+
} catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
185
|
+
const urls = authenticatedUrl
|
|
186
|
+
? [
|
|
187
|
+
{ url: authenticatedUrl, authenticated: true },
|
|
188
|
+
{ url, authenticated: false },
|
|
189
|
+
]
|
|
190
|
+
: [{ url, authenticated: false }];
|
|
191
|
+
|
|
192
|
+
for (const endpoint of urls) {
|
|
193
|
+
const headers: Record<string, string> = {
|
|
194
|
+
Accept: "application/json",
|
|
195
|
+
"X-Agent-Native-Frontend": "1",
|
|
196
|
+
};
|
|
197
|
+
if (endpoint.authenticated && args.authToken) {
|
|
198
|
+
headers.Authorization = `Bearer ${args.authToken}`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const response = await fetchWithTimeout(
|
|
203
|
+
fetchImpl,
|
|
204
|
+
endpoint.url,
|
|
205
|
+
{
|
|
206
|
+
method: "GET",
|
|
207
|
+
headers,
|
|
208
|
+
credentials: "include",
|
|
209
|
+
cache: "no-store",
|
|
210
|
+
},
|
|
211
|
+
args.fetchTimeoutMs ?? DEFAULT_READY_RECOVERY_FETCH_TIMEOUT_MS,
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
if (response.ok) {
|
|
215
|
+
const payload = await response.json().catch(() => null);
|
|
216
|
+
const probe = readyRecordingFromPublicPayload(
|
|
217
|
+
payload,
|
|
218
|
+
args.recordingId,
|
|
219
|
+
);
|
|
220
|
+
if (probe.ready) return probe.result;
|
|
221
|
+
if (probe.terminal) return null;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (
|
|
226
|
+
response.status >= 400 &&
|
|
227
|
+
response.status < 500 &&
|
|
228
|
+
!endpoint.authenticated &&
|
|
229
|
+
!authenticatedUrl
|
|
230
|
+
) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
} catch {
|
|
234
|
+
// Try the public endpoint fallback below, then keep polling.
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (attempt < attempts - 1) await sleepImpl(intervalMs);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
@@ -108,7 +108,10 @@ cd templates/content && pnpm action <name> [args]
|
|
|
108
108
|
| `list-local-component-files` | | List registered local MDX component source files |
|
|
109
109
|
| `write-local-component-file` | `--workspaceId <id> --path <relative-component-path> --content <source>` | Create or update a file in a registered local `components/` folder |
|
|
110
110
|
| `create-content-database` | `[--documentId <id>] [--parentId <id>] [--title <text>]` | Create a database page or convert an existing page into a database |
|
|
111
|
+
| `create-inline-content-database` | `--hostDocumentId <id> [--title <text>]` | Create a database owned by an inline database block in the host document |
|
|
111
112
|
| `get-content-database` | `--databaseId <id>` or `--documentId <id>` | Get a database table with property schema and item pages |
|
|
113
|
+
| `list-trashed-content-databases` | | List soft-deleted databases visible in the sidebar Trash surface |
|
|
114
|
+
| `restore-content-database` | `--databaseId <id>` | Restore a soft-deleted database from the sidebar Trash surface |
|
|
112
115
|
| `get-content-database-source` | `--databaseId <id>` or `--documentId <id>` | Inspect local/no-source or source-backed status, mappings, row identity, freshness, and change sets |
|
|
113
116
|
| `attach-content-database-source` | `--databaseId <id>` or `--documentId <id> [--sourceType mock\|builder-cms] [--sourceName] [--sourceTable] [--relationshipMode items\|details] [--join <json>]` | Attach a source binding; use `items` to add more rows and `details` to match a source onto existing rows |
|
|
114
117
|
| `change-content-database-source-role` | `--databaseId <id>` or `--documentId <id> --sourceId <id> --relationshipMode items\|details [--join <json>]` | Change an attached source between adding rows and adding matched detail columns without removing the source |
|
|
@@ -569,9 +572,10 @@ columns updates that row page's grouping property through
|
|
|
569
572
|
multi-select, users can add a new board group from the board itself; this
|
|
570
573
|
appends a new option to the grouped property definition.
|
|
571
574
|
Use
|
|
572
|
-
`create-content-database`, `
|
|
573
|
-
`
|
|
574
|
-
`
|
|
575
|
+
`create-content-database`, `create-inline-content-database`,
|
|
576
|
+
`get-content-database`, `list-trashed-content-databases`,
|
|
577
|
+
`restore-content-database`, `add-database-item`, `duplicate-database-item`,
|
|
578
|
+
`duplicate-database-items`, `delete-database-items`, `move-database-item`,
|
|
575
579
|
`update-content-database-view`, `list-document-properties`,
|
|
576
580
|
`configure-document-property`, `set-document-property`,
|
|
577
581
|
`duplicate-document-property`, and `delete-document-property`; do not edit
|
|
@@ -1,46 +1,34 @@
|
|
|
1
1
|
# Content
|
|
2
2
|
|
|
3
|
-
Open-source Obsidian for MDX
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Open-source Obsidian for MDX — an agent-native, local-file document workspace and
|
|
4
|
+
a self-hostable alternative to Notion and Obsidian. Edit local Markdown/MDX files,
|
|
5
|
+
build Notion-style databases, and write with an AI agent.
|
|
6
|
+
|
|
7
|
+
**Live app: [content.agent-native.com](https://content.agent-native.com)**
|
|
8
|
+
|
|
9
|
+
Open a doc and ask "rewrite this paragraph to be more concise" or "create a page
|
|
10
|
+
called Q4 Planning with sub-pages for Goals, Metrics, and Risks" — the same result
|
|
11
|
+
whether you do it yourself or ask. Documents can live in your SQL database or as
|
|
12
|
+
local Markdown/MDX files.
|
|
6
13
|
|
|
7
14
|
## Features
|
|
8
15
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
- Agent can create, read, update, and search documents
|
|
16
|
-
- Auto-save with debouncing
|
|
17
|
-
- Dark/light theme
|
|
16
|
+
- Rich-text editor (Tiptap) with slash commands and hierarchical pages.
|
|
17
|
+
- Notion-style databases with table, board, calendar, gallery, and timeline views.
|
|
18
|
+
- Local Markdown/MDX file editing plus custom interactive MDX components.
|
|
19
|
+
- Inline comments, version history, and full-text search.
|
|
20
|
+
- Private-by-default sharing with per-user / per-org grants and public pages.
|
|
21
|
+
- Optional Notion and Builder.io sync.
|
|
18
22
|
|
|
19
|
-
##
|
|
23
|
+
## Develop locally
|
|
24
|
+
|
|
25
|
+
Scaffold your own copy and run it:
|
|
20
26
|
|
|
21
27
|
```bash
|
|
28
|
+
npx @agent-native/core@latest create my-content --standalone --template content
|
|
29
|
+
cd my-content
|
|
22
30
|
pnpm install
|
|
23
31
|
pnpm dev
|
|
24
32
|
```
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
## Data
|
|
29
|
-
|
|
30
|
-
Documents are stored in the app's SQL database. Local development defaults to SQLite at `data/app.db`; deployed apps should set `DATABASE_URL` to a persistent SQL database. The agent should use content actions for normal document operations and reserve `db-query` / `db-exec` for inspection or maintenance.
|
|
31
|
-
|
|
32
|
-
## Enable Builder live writes
|
|
33
|
-
|
|
34
|
-
Connect Builder through the existing Builder Connect flow, the same connection used by the AI assistant. Once connected, Content resolves the key automatically for the user, or for org owners/admins through the org connection. There is no separate key entry. In local development, `BUILDER_PRIVATE_KEY` and `BUILDER_PUBLIC_KEY` in `.env.local` also work; see `DEVELOPING.md` for local env opt-in details.
|
|
35
|
-
|
|
36
|
-
Live writes are only allowed for the safe write model `agent-native-blog-article-test` (`BUILDER_CMS_SAFE_WRITE_MODEL`). Other Builder models stay read-only by design.
|
|
37
|
-
|
|
38
|
-
Write access is an intentional per-source choice from the source panel selector. Sources start at `read_only`, can move to `stage_only` to push approved changes as Builder autosave revisions, and can move to `publish_updates` for state-preserving live writes. Autosave staging stays quiet; update-in-place and publish writes trigger Builder webhooks so downstream rebuilds can run.
|
|
39
|
-
|
|
40
|
-
In `publish_updates`, Content PATCHes the Builder entry in place and preserves its current publication state: published entries stay published and drafts stay draft. Content never sends a `published` field, so a normal content push cannot publish or unpublish. Builder merges the field changes and preserves the entry envelope, including scheduling and targeting.
|
|
41
|
-
|
|
42
|
-
Publication transitions are explicit per-row choices in the review/diff, only when the source enables **Allow publish/unpublish per item**. Publishing a draft or unpublishing a published entry is never a bulk default, and unpublish requires confirmation because it takes live content down. New Builder entries are created as drafts.
|
|
43
|
-
|
|
44
|
-
**Push all approved (N)** runs approved rows in a concurrency-capped batch. It continues after individual errors, reports per-item `succeeded`, `blocked`, or `failed` status, and can be resumed because already-succeeded items are skipped.
|
|
45
|
-
|
|
46
|
-
Before each write, Content reads the target's current state from Builder. A write blocks if the entry changed or was deleted since the diff, or if a requested publish/unpublish transition no longer matches the real Builder state. Body diffs remain non-executable for now; they are planned for a later slice.
|
|
34
|
+
Full docs: [agent-native.com/docs/template-content](https://agent-native.com/docs/template-content).
|
|
@@ -33,6 +33,7 @@ export default defineAction({
|
|
|
33
33
|
ownerDocumentId: schema.contentDatabases.ownerDocumentId,
|
|
34
34
|
deletedAt: schema.contentDatabases.deletedAt,
|
|
35
35
|
documentTitle: schema.documents.title,
|
|
36
|
+
documentParentId: schema.documents.parentId,
|
|
36
37
|
})
|
|
37
38
|
.from(schema.contentDatabases)
|
|
38
39
|
.innerJoin(
|
|
@@ -80,6 +81,9 @@ export default defineAction({
|
|
|
80
81
|
documentId: row.documentId,
|
|
81
82
|
ownerDocumentId: row.ownerDocumentId,
|
|
82
83
|
deletedAt: row.deletedAt!,
|
|
84
|
+
canPermanentlyDelete:
|
|
85
|
+
row.ownerDocumentId === null ||
|
|
86
|
+
row.documentParentId !== row.ownerDocumentId,
|
|
83
87
|
})),
|
|
84
88
|
};
|
|
85
89
|
},
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useSendToAgentChat, useT } from "@agent-native/core/client";
|
|
2
|
+
import type { CreateInlineDatabaseResponse } from "@shared/api";
|
|
2
3
|
import { collapseExactRepeatedNfm, docToNfm } from "@shared/nfm";
|
|
4
|
+
import { serializeRegistryBlockToMdx } from "@shared/nfm-registry";
|
|
3
5
|
import {
|
|
4
6
|
IconTypography,
|
|
5
7
|
IconH1,
|
|
@@ -33,7 +35,7 @@ import {
|
|
|
33
35
|
PopoverContent,
|
|
34
36
|
PopoverTrigger,
|
|
35
37
|
} from "@/components/ui/popover";
|
|
36
|
-
import {
|
|
38
|
+
import { useCreateInlineContentDatabase } from "@/hooks/use-content-database";
|
|
37
39
|
import { useCreatePage } from "@/hooks/use-create-page";
|
|
38
40
|
import { cn } from "@/lib/utils";
|
|
39
41
|
import { localContentComponents } from "@/local-components";
|
|
@@ -146,6 +148,36 @@ export function parseSlashCommandQuery(textBeforeCursor: string) {
|
|
|
146
148
|
return textBeforeCursor.match(/^\s*\/([a-zA-Z0-9]*)$/)?.[1] ?? null;
|
|
147
149
|
}
|
|
148
150
|
|
|
151
|
+
export function inlineDatabaseBlockContent(
|
|
152
|
+
block: CreateInlineDatabaseResponse["block"],
|
|
153
|
+
) {
|
|
154
|
+
return {
|
|
155
|
+
type: "registryBlock",
|
|
156
|
+
attrs: {
|
|
157
|
+
blockType: "inline-database",
|
|
158
|
+
blockId: block.ownerBlockId,
|
|
159
|
+
title: null,
|
|
160
|
+
summary: null,
|
|
161
|
+
__raw: serializeRegistryBlockToMdx("inline-database", {
|
|
162
|
+
id: block.ownerBlockId,
|
|
163
|
+
data: block,
|
|
164
|
+
}),
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function insertInlineDatabaseBlock(
|
|
170
|
+
editor: Editor,
|
|
171
|
+
block: CreateInlineDatabaseResponse["block"],
|
|
172
|
+
position?: number | { from: number; to: number } | null,
|
|
173
|
+
) {
|
|
174
|
+
const content = inlineDatabaseBlockContent(block);
|
|
175
|
+
const chain = editor.chain().focus();
|
|
176
|
+
return position != null
|
|
177
|
+
? chain.insertContentAt(position, content).run()
|
|
178
|
+
: chain.insertContent(content).run();
|
|
179
|
+
}
|
|
180
|
+
|
|
149
181
|
const commands: CommandTemplate[] = [
|
|
150
182
|
{
|
|
151
183
|
titleKey: "editor.slash.text",
|
|
@@ -407,7 +439,9 @@ export function SlashCommandMenu({
|
|
|
407
439
|
const { send } = useSendToAgentChat();
|
|
408
440
|
const navigate = useNavigate();
|
|
409
441
|
const createPage = useCreatePage({ navigate: false, awaitPersist: true });
|
|
410
|
-
const
|
|
442
|
+
const createInlineDatabase = useCreateInlineContentDatabase(
|
|
443
|
+
documentId ?? null,
|
|
444
|
+
);
|
|
411
445
|
|
|
412
446
|
const [isOpen, setIsOpen] = useState(false);
|
|
413
447
|
const [isTurnInto, setIsTurnInto] = useState(false);
|
|
@@ -609,18 +643,36 @@ export function SlashCommandMenu({
|
|
|
609
643
|
title: t("editor.slash.database"),
|
|
610
644
|
description: t("editor.slash.databaseDescription"),
|
|
611
645
|
icon: IconDatabase,
|
|
612
|
-
|
|
646
|
+
preserveSlashRange: true,
|
|
647
|
+
action: async (editor, { slashRange }) => {
|
|
613
648
|
if (!documentId) {
|
|
614
649
|
toast.error(t("editor.noDocumentSelected"));
|
|
615
650
|
return;
|
|
616
651
|
}
|
|
652
|
+
if (slashRange) {
|
|
653
|
+
editor.chain().focus().deleteRange(slashRange).run();
|
|
654
|
+
}
|
|
617
655
|
const toastId = toast.loading(t("editor.creatingDatabase"));
|
|
618
656
|
try {
|
|
619
|
-
const result = await
|
|
620
|
-
|
|
657
|
+
const result = await createInlineDatabase.mutateAsync({
|
|
658
|
+
hostDocumentId: documentId,
|
|
621
659
|
title: t("editor.untitledDatabase"),
|
|
622
660
|
});
|
|
623
|
-
|
|
661
|
+
const inserted = insertInlineDatabaseBlock(editor, result.block);
|
|
662
|
+
if (!inserted) throw new Error(t("empty.genericError"));
|
|
663
|
+
await waitForEditorUpdateFrame();
|
|
664
|
+
const content = collapseExactRepeatedNfm(
|
|
665
|
+
docToNfm(editor.getJSON() as any),
|
|
666
|
+
{
|
|
667
|
+
requiredText: result.block.ownerBlockId,
|
|
668
|
+
},
|
|
669
|
+
);
|
|
670
|
+
if (onDraftPersisted) {
|
|
671
|
+
const persisted = await onDraftPersisted(content);
|
|
672
|
+
if (!persisted) throw new Error(t("empty.genericError"));
|
|
673
|
+
} else {
|
|
674
|
+
await onDraftCommitted?.();
|
|
675
|
+
}
|
|
624
676
|
toast.success(t("editor.databaseCreated"), { id: toastId });
|
|
625
677
|
} catch (error) {
|
|
626
678
|
toast.error(t("editor.failedToCreateDatabase"), {
|