@agent-native/core 0.155.0 → 0.157.1
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/server/jobs/analytics-bigquery-backfill.ts +19 -4
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +7 -2
- package/corpus/templates/assets/app/components/asset/AssetPreviewDialog.tsx +3 -1
- package/corpus/templates/assets/app/global.css +4 -0
- package/corpus/templates/assets/app/i18n-data.ts +21 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +18 -15
- package/corpus/templates/brain/app/routes/sources.tsx +1 -2
- package/corpus/templates/calendar/app/hooks/use-bookings.ts +12 -2
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +64 -2
- package/corpus/templates/calendar/server/handlers/bookings.ts +219 -93
- package/corpus/templates/calendar/server/plugins/auth.ts +0 -2
- package/corpus/templates/clips/actions/create-folder.ts +7 -1
- package/corpus/templates/clips/actions/create-meeting.ts +6 -2
- package/corpus/templates/clips/actions/create-recording.ts +5 -3
- package/corpus/templates/clips/actions/finalize-recording.ts +97 -65
- package/corpus/templates/clips/actions/import-loom-recording.ts +5 -3
- package/corpus/templates/clips/actions/request-recording-access.ts +272 -0
- package/corpus/templates/clips/actions/stitch-recordings.ts +5 -3
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +38 -63
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +177 -134
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +3 -21
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +13 -30
- package/corpus/templates/clips/app/i18n/en-US.ts +10 -0
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +5 -5
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +24 -17
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +151 -20
- package/corpus/templates/clips/changelog/2026-08-14-long-resumable-uploads-now-abort-cleanly-and-show-errors.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-14-standardized-share-popovers.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +11 -3
- package/corpus/templates/clips/chrome-extension/src/github-preview-content.ts +37 -8
- package/corpus/templates/clips/chrome-extension/src/github-preview.ts +3 -3
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +8 -25
- package/corpus/templates/clips/desktop/src/app.tsx +24 -0
- package/corpus/templates/clips/desktop/src/lib/media-capture-constraints.ts +42 -13
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +11 -18
- package/corpus/templates/clips/desktop/src/overlays/onboarding.tsx +1 -0
- package/corpus/templates/clips/desktop/src/shared/config.ts +1 -0
- package/corpus/templates/clips/desktop/src/styles.css +24 -9
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +3 -0
- package/corpus/templates/clips/desktop/src-tauri/src/config.rs +8 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +154 -176
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +115 -31
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +7 -0
- package/corpus/templates/clips/server/db/index.ts +1 -0
- package/corpus/templates/clips/server/db/schema.ts +1 -0
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +6 -1
- package/corpus/templates/clips/server/lib/emails.ts +23 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +55 -0
- package/corpus/templates/clips/server/lib/recordings.ts +2 -1
- package/corpus/templates/clips/server/lib/resumable-upload-cleanup.ts +49 -0
- package/corpus/templates/clips/server/lib/s3-upload-provider.ts +34 -7
- package/corpus/templates/clips/server/lib/upload-interruption.ts +15 -1
- package/corpus/templates/clips/server/lib/upload-lease.ts +94 -9
- package/corpus/templates/clips/server/routes/api/agent-context.json.get.ts +82 -7
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +1 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +71 -12
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/interrupt.post.ts +4 -3
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +19 -0
- package/corpus/templates/clips/server/routes/api/uploads/reap.post.ts +8 -1
- package/corpus/templates/clips/shared/recording-link.ts +4 -0
- package/corpus/templates/clips/shared/share-loader-response.ts +2 -1
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +7 -16
- package/corpus/templates/factory/AGENTS.md +5 -1
- package/corpus/templates/factory/README.md +6 -2
- package/corpus/templates/factory/actions/view-screen.ts +15 -0
- package/corpus/templates/factory/app/components/factory/FactoryAgentsView.tsx +58 -189
- package/corpus/templates/factory/app/components/layout/Header.tsx +1 -0
- package/corpus/templates/factory/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +7 -0
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +3 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +3 -1
- package/corpus/templates/factory/app/routes/agents.tsx +9 -0
- package/corpus/templates/mail/server/plugins/auth.ts +0 -2
- package/corpus/templates/plan/app/components/plan/LocalCodebasePicker.tsx +50 -14
- package/corpus/templates/plan/app/pages/PlansPage.tsx +9 -19
- package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +98 -29
- package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +45 -91
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +94 -2
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +17 -29
- package/corpus/templates/slides/app/components/editor/HistoryPanel.tsx +7 -1
- package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +1 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +108 -19
- package/corpus/templates/slides/app/global.css +7 -40
- package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
- package/corpus/templates/slides/app/lib/editor-shortcuts.ts +46 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +143 -45
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/production-agent.d.ts +30 -0
- package/dist/agent/production-agent.js +100 -8
- package/dist/agent/types.d.ts +9 -0
- package/dist/client/AgentPanel.d.ts +13 -1
- package/dist/client/AgentPanel.js +22 -17
- package/dist/client/AssistantChat.d.ts +15 -1
- package/dist/client/AssistantChat.js +135 -48
- package/dist/client/HighlightedCodeBlock.js +12 -2
- package/dist/client/MultiTabAssistantChat.js +44 -27
- package/dist/client/agent-chat/index.d.ts +1 -0
- package/dist/client/agent-chat/index.js +1 -0
- package/dist/client/chat/markdown-renderer.js +20 -5
- package/dist/client/chat/message-components.d.ts +2 -2
- package/dist/client/chat/message-components.js +69 -35
- package/dist/client/chat/run-recovery.d.ts +4 -1
- package/dist/client/chat/run-recovery.js +62 -30
- package/dist/client/chat/tool-call-display.d.ts +12 -3
- package/dist/client/chat/tool-call-display.js +79 -40
- package/dist/client/chat/widgets/DataChartRenderer.js +3 -1
- package/dist/client/chat/widgets/DataChartWidget.js +8 -2
- package/dist/client/chat/widgets/DataInsightsWidget.js +3 -1
- package/dist/client/chat/widgets/DataTableWidget.js +23 -9
- package/dist/client/chat/widgets/builtin-tool-renderers.js +3 -1
- package/dist/client/chat-first/apps-rail.js +1 -1
- package/dist/client/components/ui/message-scroller.d.ts +1 -1
- package/dist/client/components/ui/message-scroller.js +7 -3
- package/dist/client/composer/runtime-adapters.js +3 -2
- package/dist/client/context-xray/ContextMeter.js +5 -1
- package/dist/client/context-xray/ContextXRayPanel.js +5 -5
- package/dist/client/error-format.d.ts +4 -0
- package/dist/client/error-format.js +93 -5
- package/dist/client/i18n.js +83 -17
- package/dist/client/observability/ThumbsFeedback.js +10 -8
- package/dist/client/settings/AccountSettingsCard.js +48 -19
- package/dist/client/sharing/ShareButton.d.ts +1 -2
- package/dist/client/sharing/ShareButton.js +151 -81
- package/dist/client/sharing/ShareDialog.js +5 -4
- package/dist/client/use-chat-threads.js +10 -14
- package/dist/coding-tools/run-code.js +2 -1
- package/dist/coding-tools/sandbox/background.d.ts +1 -0
- package/dist/coding-tools/sandbox/background.js +8 -1
- package/dist/coding-tools/sandbox/executions-store.d.ts +2 -0
- package/dist/coding-tools/sandbox/executions-store.js +41 -3
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.d.ts +3 -0
- package/dist/localization/core-messages/ar-SA.js +541 -0
- package/dist/localization/core-messages/de-DE.d.ts +3 -0
- package/dist/localization/core-messages/de-DE.js +513 -0
- package/dist/localization/core-messages/en-US.d.ts +513 -0
- package/dist/localization/core-messages/en-US.js +513 -0
- package/dist/localization/core-messages/es-ES.d.ts +3 -0
- package/dist/localization/core-messages/es-ES.js +520 -0
- package/dist/localization/core-messages/fr-FR.d.ts +3 -0
- package/dist/localization/core-messages/fr-FR.js +520 -0
- package/dist/localization/core-messages/hi-IN.d.ts +3 -0
- package/dist/localization/core-messages/hi-IN.js +513 -0
- package/dist/localization/core-messages/ja-JP.d.ts +3 -0
- package/dist/localization/core-messages/ja-JP.js +506 -0
- package/dist/localization/core-messages/ko-KR.d.ts +3 -0
- package/dist/localization/core-messages/ko-KR.js +506 -0
- package/dist/localization/core-messages/pt-BR.d.ts +3 -0
- package/dist/localization/core-messages/pt-BR.js +520 -0
- package/dist/localization/core-messages/zh-CN.d.ts +3 -0
- package/dist/localization/core-messages/zh-CN.js +506 -0
- package/dist/localization/core-messages/zh-TW.d.ts +3 -0
- package/dist/localization/core-messages/zh-TW.js +506 -0
- package/dist/localization/core-messages.d.ts +524 -0
- package/dist/localization/core-messages.js +124 -0
- package/dist/localization/default-messages.d.ts +3 -0
- package/dist/localization/default-messages.js +3 -0
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +15 -15
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat/plugin-options.d.ts +7 -2
- package/dist/server/agent-chat-plugin.d.ts +10 -0
- package/dist/server/agent-chat-plugin.js +144 -41
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-run-context.d.ts +7 -1
- package/dist/server/agent-run-context.js +9 -2
- package/dist/server/agent-teams-run-queue.d.ts +3 -0
- package/dist/server/agent-teams.js +38 -9
- package/dist/server/auth-marketing.d.ts +1 -0
- package/dist/server/auth-marketing.js +0 -2
- package/dist/server/auth.d.ts +1 -0
- package/dist/server/auth.js +18 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/onboarding-html.d.ts +1 -0
- package/dist/server/onboarding-html.js +2 -88
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/request-context.d.ts +7 -0
- package/dist/server/request-context.js +15 -0
- package/dist/shared/markdown-block-split.d.ts +16 -0
- package/dist/shared/markdown-block-split.js +81 -5
- package/dist/styles/agent-native.css +13 -0
- package/dist/voice/voice-cleanup-prompt.js +1 -0
- package/docs/content/agent-surfaces.mdx +30 -0
- package/package.json +2 -2
- package/corpus/templates/clips/shared/mic-audio-cleanup.ts +0 -350
|
@@ -784,13 +784,40 @@ export const s3FileUploadProvider: FileUploadProvider = {
|
|
|
784
784
|
)
|
|
785
785
|
.join("") +
|
|
786
786
|
"</CompleteMultipartUpload>";
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
787
|
+
let res: Response;
|
|
788
|
+
try {
|
|
789
|
+
res = await signedS3Request(cfg, meta.objectKey, {
|
|
790
|
+
method: "POST",
|
|
791
|
+
query: { uploadId: session.sessionId },
|
|
792
|
+
body: new TextEncoder().encode(manifest),
|
|
793
|
+
contentType: "application/xml",
|
|
794
|
+
timeoutMs: S3_PUT_TIMEOUT_MS,
|
|
795
|
+
});
|
|
796
|
+
} catch (error) {
|
|
797
|
+
// R2 can finish the object while the HTTP response is lost. Reconcile
|
|
798
|
+
// that ambiguous case before the caller aborts the provider session.
|
|
799
|
+
try {
|
|
800
|
+
if (await verifyCompletedMultipartObject(cfg, meta)) {
|
|
801
|
+
await deleteObject(cfg, meta.stagingKey).catch((cleanupError) => {
|
|
802
|
+
console.warn(
|
|
803
|
+
"[s3-upload] failed to delete multipart staging object:",
|
|
804
|
+
cleanupError instanceof Error
|
|
805
|
+
? cleanupError.message
|
|
806
|
+
: String(cleanupError),
|
|
807
|
+
);
|
|
808
|
+
});
|
|
809
|
+
return publicObjectUrl(cfg, meta.objectKey);
|
|
810
|
+
}
|
|
811
|
+
} catch (verificationError) {
|
|
812
|
+
console.warn(
|
|
813
|
+
"[s3-upload] completion reconciliation failed:",
|
|
814
|
+
verificationError,
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
throw new Error(
|
|
818
|
+
`S3 CompleteMultipartUpload failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
819
|
+
);
|
|
820
|
+
}
|
|
794
821
|
const body = await res.text().catch(() => "");
|
|
795
822
|
if (!res.ok || /<Error(?:\s|>)/.test(body)) {
|
|
796
823
|
// CompleteMultipartUpload is not idempotent at the S3 API level. If
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
export const RETRYABLE_UPLOAD_INTERRUPTION_REASON =
|
|
2
2
|
"Upload was interrupted. The local recording is safe; retry from the Clips desktop app.";
|
|
3
3
|
|
|
4
|
+
export function retryableUploadInterruptionReason(
|
|
5
|
+
detail: string | null | undefined,
|
|
6
|
+
): string {
|
|
7
|
+
const normalized = detail?.trim();
|
|
8
|
+
return normalized
|
|
9
|
+
? `${RETRYABLE_UPLOAD_INTERRUPTION_REASON} Last error: ${normalized}`
|
|
10
|
+
: RETRYABLE_UPLOAD_INTERRUPTION_REASON;
|
|
11
|
+
}
|
|
12
|
+
|
|
4
13
|
export function isRetryableUploadInterruption(
|
|
5
14
|
failureReason: string | null | undefined,
|
|
6
15
|
): boolean {
|
|
7
|
-
return
|
|
16
|
+
return (
|
|
17
|
+
failureReason === RETRYABLE_UPLOAD_INTERRUPTION_REASON ||
|
|
18
|
+
failureReason?.startsWith(
|
|
19
|
+
`${RETRYABLE_UPLOAD_INTERRUPTION_REASON} Last error:`,
|
|
20
|
+
) === true
|
|
21
|
+
);
|
|
8
22
|
}
|
|
@@ -15,6 +15,8 @@ import { getDbExec, isPostgres } from "@agent-native/core/db";
|
|
|
15
15
|
import { and, eq, inArray, isNull, sql } from "drizzle-orm";
|
|
16
16
|
|
|
17
17
|
import { getDb, schema } from "../db/index.js";
|
|
18
|
+
import type { StoredResumableSession } from "./resumable-session.js";
|
|
19
|
+
import { abortResumableUploadSession } from "./resumable-upload-cleanup.js";
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* ponytail: one horizon for both in-progress statuses. A paused recorder emits
|
|
@@ -148,6 +150,61 @@ export interface ReapResult {
|
|
|
148
150
|
expired: ReapedUpload[];
|
|
149
151
|
failed: number;
|
|
150
152
|
scratchKeysDeleted: number;
|
|
153
|
+
resumableSessionsAborted: number;
|
|
154
|
+
resumableCleanupFailed: number;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function resumableSessionKey(
|
|
158
|
+
recordingId: string,
|
|
159
|
+
generationId: string | null,
|
|
160
|
+
): string {
|
|
161
|
+
return generationId
|
|
162
|
+
? `resumable-session-${recordingId}-${generationId}`
|
|
163
|
+
: `resumable-session-${recordingId}`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function parseStoredResumableSession(
|
|
167
|
+
value: unknown,
|
|
168
|
+
): StoredResumableSession | null {
|
|
169
|
+
let parsed: unknown = value;
|
|
170
|
+
if (typeof parsed === "string") {
|
|
171
|
+
try {
|
|
172
|
+
parsed = JSON.parse(parsed);
|
|
173
|
+
} catch {
|
|
174
|
+
// coercion-ok: malformed persisted session is absent, not an active session.
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
179
|
+
const candidate = parsed as Record<string, unknown>;
|
|
180
|
+
if (
|
|
181
|
+
typeof candidate.providerId !== "string" ||
|
|
182
|
+
typeof candidate.sessionId !== "string" ||
|
|
183
|
+
!candidate.meta ||
|
|
184
|
+
typeof candidate.meta !== "object" ||
|
|
185
|
+
typeof candidate.bytesUploaded !== "number" ||
|
|
186
|
+
!Number.isFinite(candidate.bytesUploaded) ||
|
|
187
|
+
candidate.bytesUploaded < 0
|
|
188
|
+
) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
return candidate as unknown as StoredResumableSession;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function readResumableSessionState(
|
|
195
|
+
exec: ReturnType<typeof getDbExec>,
|
|
196
|
+
placeholder: string,
|
|
197
|
+
key: string,
|
|
198
|
+
): Promise<{ present: boolean; session: StoredResumableSession | null }> {
|
|
199
|
+
const result = await exec.execute({
|
|
200
|
+
sql: `SELECT value FROM application_state WHERE key = ${placeholder}`,
|
|
201
|
+
args: [key],
|
|
202
|
+
});
|
|
203
|
+
const row = (result.rows as Array<{ value?: unknown }> | undefined)?.[0];
|
|
204
|
+
return {
|
|
205
|
+
present: row !== undefined,
|
|
206
|
+
session: row === undefined ? null : parseStoredResumableSession(row.value),
|
|
207
|
+
};
|
|
151
208
|
}
|
|
152
209
|
|
|
153
210
|
/**
|
|
@@ -196,6 +253,8 @@ export async function reapExpiredUploads(
|
|
|
196
253
|
}));
|
|
197
254
|
|
|
198
255
|
let failed = 0;
|
|
256
|
+
let resumableSessionsAborted = 0;
|
|
257
|
+
let resumableCleanupFailed = 0;
|
|
199
258
|
if (expired.length > 0 && !dryRun) {
|
|
200
259
|
const ids = expired.map((row) => row.id);
|
|
201
260
|
const result = await exec.execute({
|
|
@@ -223,16 +282,35 @@ export async function reapExpiredUploads(
|
|
|
223
282
|
failed = terminated.size;
|
|
224
283
|
|
|
225
284
|
for (const id of terminated) {
|
|
226
|
-
const generationId =
|
|
227
|
-
(row) => row.id === id
|
|
228
|
-
)
|
|
285
|
+
const generationId =
|
|
286
|
+
expired.find((row) => row.id === id)?.uploadGenerationId ?? null;
|
|
287
|
+
const sessionKey = resumableSessionKey(id, generationId);
|
|
288
|
+
const sessionState = await readResumableSessionState(
|
|
289
|
+
exec,
|
|
290
|
+
p(1),
|
|
291
|
+
sessionKey,
|
|
292
|
+
);
|
|
293
|
+
if (sessionState.present && !sessionState.session) {
|
|
294
|
+
resumableCleanupFailed += 1;
|
|
295
|
+
console.warn(
|
|
296
|
+
`[upload-reaper-${id}] resumable session state is unreadable; keeping it for manual cleanup`,
|
|
297
|
+
);
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (sessionState.session) {
|
|
301
|
+
const cleaned = await abortResumableUploadSession(
|
|
302
|
+
sessionState.session,
|
|
303
|
+
{ label: `upload-reaper-${id}` },
|
|
304
|
+
);
|
|
305
|
+
if (!cleaned) {
|
|
306
|
+
resumableCleanupFailed += 1;
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
resumableSessionsAborted += 1;
|
|
310
|
+
}
|
|
229
311
|
await exec.execute({
|
|
230
312
|
sql: `DELETE FROM application_state WHERE key = ${p(1)}`,
|
|
231
|
-
args: [
|
|
232
|
-
generationId
|
|
233
|
-
? `resumable-session-${id}-${generationId}`
|
|
234
|
-
: `resumable-session-${id}`,
|
|
235
|
-
],
|
|
313
|
+
args: [sessionKey],
|
|
236
314
|
});
|
|
237
315
|
}
|
|
238
316
|
}
|
|
@@ -241,7 +319,14 @@ export async function reapExpiredUploads(
|
|
|
241
319
|
? (await selectUnclaimedChunkKeys(limit)).length
|
|
242
320
|
: await deleteUnclaimedChunkScratch(limit);
|
|
243
321
|
|
|
244
|
-
return {
|
|
322
|
+
return {
|
|
323
|
+
dryRun,
|
|
324
|
+
expired,
|
|
325
|
+
failed,
|
|
326
|
+
scratchKeysDeleted,
|
|
327
|
+
resumableSessionsAborted,
|
|
328
|
+
resumableCleanupFailed,
|
|
329
|
+
};
|
|
245
330
|
}
|
|
246
331
|
|
|
247
332
|
/**
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* smaller agent-oriented shape plus discoverable transcript/frame APIs.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { asc, count, eq } from "drizzle-orm";
|
|
9
10
|
import {
|
|
10
11
|
defineEventHandler,
|
|
11
12
|
getQuery,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
type H3Event,
|
|
14
15
|
} from "h3";
|
|
15
16
|
|
|
17
|
+
import { getDb, schema } from "../../db/index.js";
|
|
16
18
|
import {
|
|
17
19
|
applyAgentJsonHeaders,
|
|
18
20
|
buildPublicAgentContext,
|
|
@@ -21,6 +23,7 @@ import {
|
|
|
21
23
|
loadAgentCtas,
|
|
22
24
|
loadAgentTranscript,
|
|
23
25
|
loadPublicAgentAccess,
|
|
26
|
+
MAX_PUBLIC_AGENT_HISTORY_ITEMS,
|
|
24
27
|
parseAgentChapters,
|
|
25
28
|
queryString,
|
|
26
29
|
CLIPS_AGENT_ACCESS_PARAM,
|
|
@@ -42,14 +45,76 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
const recording = accessResult.access.recording;
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
const db = getDb();
|
|
49
|
+
const historyQueryLimit = MAX_PUBLIC_AGENT_HISTORY_ITEMS + 1;
|
|
50
|
+
const [
|
|
51
|
+
{ transcript, agentSegments },
|
|
52
|
+
commentRows,
|
|
53
|
+
reactionRows,
|
|
54
|
+
commentCountRows,
|
|
55
|
+
reactionCountRows,
|
|
56
|
+
ctas,
|
|
57
|
+
browserDiagnostics,
|
|
58
|
+
bugReport,
|
|
59
|
+
] = await Promise.all([
|
|
60
|
+
loadAgentTranscript(recording.id, recording.durationMs),
|
|
61
|
+
recording.enableComments
|
|
62
|
+
? db
|
|
63
|
+
.select({
|
|
64
|
+
id: schema.recordingComments.id,
|
|
65
|
+
recordingId: schema.recordingComments.recordingId,
|
|
66
|
+
threadId: schema.recordingComments.threadId,
|
|
67
|
+
parentId: schema.recordingComments.parentId,
|
|
68
|
+
authorName: schema.recordingComments.authorName,
|
|
69
|
+
content: schema.recordingComments.content,
|
|
70
|
+
videoTimestampMs: schema.recordingComments.videoTimestampMs,
|
|
71
|
+
resolved: schema.recordingComments.resolved,
|
|
72
|
+
createdAt: schema.recordingComments.createdAt,
|
|
73
|
+
updatedAt: schema.recordingComments.updatedAt,
|
|
74
|
+
})
|
|
75
|
+
.from(schema.recordingComments)
|
|
76
|
+
.where(eq(schema.recordingComments.recordingId, recording.id))
|
|
77
|
+
.orderBy(
|
|
78
|
+
asc(schema.recordingComments.videoTimestampMs),
|
|
79
|
+
asc(schema.recordingComments.createdAt),
|
|
80
|
+
)
|
|
81
|
+
.limit(historyQueryLimit)
|
|
82
|
+
: Promise.resolve([]),
|
|
83
|
+
recording.enableReactions
|
|
84
|
+
? db
|
|
85
|
+
.select({
|
|
86
|
+
id: schema.recordingReactions.id,
|
|
87
|
+
emoji: schema.recordingReactions.emoji,
|
|
88
|
+
videoTimestampMs: schema.recordingReactions.videoTimestampMs,
|
|
89
|
+
viewerName: schema.recordingReactions.viewerName,
|
|
90
|
+
createdAt: schema.recordingReactions.createdAt,
|
|
91
|
+
})
|
|
92
|
+
.from(schema.recordingReactions)
|
|
93
|
+
.where(eq(schema.recordingReactions.recordingId, recording.id))
|
|
94
|
+
.orderBy(asc(schema.recordingReactions.createdAt))
|
|
95
|
+
.limit(historyQueryLimit)
|
|
96
|
+
: Promise.resolve([]),
|
|
97
|
+
recording.enableComments
|
|
98
|
+
? db
|
|
99
|
+
.select({ count: count() })
|
|
100
|
+
.from(schema.recordingComments)
|
|
101
|
+
.where(eq(schema.recordingComments.recordingId, recording.id))
|
|
102
|
+
: Promise.resolve([{ count: 0 }]),
|
|
103
|
+
recording.enableReactions
|
|
104
|
+
? db
|
|
105
|
+
.select({ count: count() })
|
|
106
|
+
.from(schema.recordingReactions)
|
|
107
|
+
.where(eq(schema.recordingReactions.recordingId, recording.id))
|
|
108
|
+
: Promise.resolve([{ count: 0 }]),
|
|
109
|
+
loadAgentCtas(recording.id),
|
|
110
|
+
loadAgentBrowserDiagnostics(recording.id),
|
|
111
|
+
loadAgentBugReport(recording.id),
|
|
112
|
+
]);
|
|
52
113
|
const chapters = parseAgentChapters(recording);
|
|
114
|
+
const comments = commentRows.slice(0, MAX_PUBLIC_AGENT_HISTORY_ITEMS);
|
|
115
|
+
const reactions = reactionRows.slice(0, MAX_PUBLIC_AGENT_HISTORY_ITEMS);
|
|
116
|
+
const commentCount = Number(commentCountRows[0]?.count ?? comments.length);
|
|
117
|
+
const reactionCount = Number(reactionCountRows[0]?.count ?? reactions.length);
|
|
53
118
|
|
|
54
119
|
return buildPublicAgentContext({
|
|
55
120
|
event,
|
|
@@ -57,6 +122,16 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
57
122
|
transcript,
|
|
58
123
|
agentSegments,
|
|
59
124
|
chapters,
|
|
125
|
+
comments,
|
|
126
|
+
reactions,
|
|
127
|
+
commentCount,
|
|
128
|
+
commentsTruncated:
|
|
129
|
+
commentRows.length > MAX_PUBLIC_AGENT_HISTORY_ITEMS ||
|
|
130
|
+
commentCount > comments.length,
|
|
131
|
+
reactionCount,
|
|
132
|
+
reactionsTruncated:
|
|
133
|
+
reactionRows.length > MAX_PUBLIC_AGENT_HISTORY_ITEMS ||
|
|
134
|
+
reactionCount > reactions.length,
|
|
60
135
|
ctas,
|
|
61
136
|
browserDiagnostics,
|
|
62
137
|
bugReport,
|
|
@@ -263,6 +263,7 @@ export default defineEventHandler(async (event) => {
|
|
|
263
263
|
!viewerIsOrgMember &&
|
|
264
264
|
!tokenAllowsAgentAccess
|
|
265
265
|
) {
|
|
266
|
+
setResponseHeader(event, "Cache-Control", "private, max-age=0, no-store");
|
|
266
267
|
setResponseStatus(event, 404);
|
|
267
268
|
return { error: "Not found" };
|
|
268
269
|
}
|
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
setResumableSession,
|
|
53
53
|
type StoredResumableSession,
|
|
54
54
|
} from "../../../../lib/resumable-session.js";
|
|
55
|
+
import { abortResumableUploadSession } from "../../../../lib/resumable-upload-cleanup.js";
|
|
55
56
|
import { resolveResumableUploadProvider } from "../../../../lib/resumable-upload-provider.js";
|
|
56
57
|
import { isStreamingUploadDisabled } from "../../../../lib/streaming-upload-mode.js";
|
|
57
58
|
import { renewUploadLease } from "../../../../lib/upload-lease.js";
|
|
@@ -854,6 +855,23 @@ async function handleResumableChunk(
|
|
|
854
855
|
`[resumable-chunk-${recordingId}] resumable session exists - bytesUploaded=${session.bytesUploaded} index=${index} isFinal=${isFinal}`,
|
|
855
856
|
);
|
|
856
857
|
|
|
858
|
+
const cleanupFailedFinalSession = async () => {
|
|
859
|
+
const cleaned = await abortResumableUploadSession(session, {
|
|
860
|
+
provider: uploadProvider,
|
|
861
|
+
label: `resumable-final-${recordingId}`,
|
|
862
|
+
});
|
|
863
|
+
if (cleaned) {
|
|
864
|
+
await deleteResumableSession(recordingId, uploadGenerationId).catch(
|
|
865
|
+
(error) =>
|
|
866
|
+
console.warn(
|
|
867
|
+
`[resumable-chunk-${recordingId}] failed to retire aborted session:`,
|
|
868
|
+
error,
|
|
869
|
+
),
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
return !cleaned;
|
|
873
|
+
};
|
|
874
|
+
|
|
857
875
|
const raw = await readRawBody(event, false);
|
|
858
876
|
const bytes: Uint8Array = raw ?? new Uint8Array(0);
|
|
859
877
|
let finalizedSourceSizeBytes = session.bytesUploaded;
|
|
@@ -884,19 +902,37 @@ async function handleResumableChunk(
|
|
|
884
902
|
// 0-byte sentinel from the recorder after stop(). All data chunks have
|
|
885
903
|
// already been PUT to the provider; send Content-Range: bytes */<total>
|
|
886
904
|
// to close the session before handing off to finalize-recording.
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
905
|
+
let closeRes;
|
|
906
|
+
try {
|
|
907
|
+
closeRes = await uploadProvider.resumable.relayChunk(
|
|
908
|
+
{ sessionId: session.sessionId, meta: session.meta },
|
|
909
|
+
`bytes */${session.bytesUploaded}`,
|
|
910
|
+
new Uint8Array(0),
|
|
911
|
+
);
|
|
912
|
+
} catch (error) {
|
|
913
|
+
const cleanupFailed = await cleanupFailedFinalSession();
|
|
914
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
915
|
+
console.error(
|
|
916
|
+
`[resumable-chunk-${recordingId}] session close threw:`,
|
|
917
|
+
error,
|
|
918
|
+
);
|
|
919
|
+
setResponseStatus(event, 502);
|
|
920
|
+
return {
|
|
921
|
+
ok: false,
|
|
922
|
+
error: `Resumable session close failed: ${detail}`,
|
|
923
|
+
...(cleanupFailed ? { cleanupFailed: true } : {}),
|
|
924
|
+
};
|
|
925
|
+
}
|
|
892
926
|
if (!closeRes.ok || closeRes.status === 308) {
|
|
893
927
|
console.error(
|
|
894
928
|
`[resumable-chunk-${recordingId}] session close failed (${closeRes.status})`,
|
|
895
929
|
);
|
|
930
|
+
const cleanupFailed = await cleanupFailedFinalSession();
|
|
896
931
|
setResponseStatus(event, 502);
|
|
897
932
|
return {
|
|
898
933
|
ok: false,
|
|
899
934
|
error: `Resumable session close failed (${closeRes.status})`,
|
|
935
|
+
...(cleanupFailed ? { cleanupFailed: true } : {}),
|
|
900
936
|
};
|
|
901
937
|
}
|
|
902
938
|
if (closeRes.updatedMeta) {
|
|
@@ -964,12 +1000,31 @@ async function handleResumableChunk(
|
|
|
964
1000
|
: `bytes ${start}-${end}/*`;
|
|
965
1001
|
|
|
966
1002
|
const putT0 = Date.now();
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1003
|
+
let putResult;
|
|
1004
|
+
try {
|
|
1005
|
+
putResult = await uploadProvider.resumable.relayChunk(
|
|
1006
|
+
{ sessionId: session.sessionId, meta: session.meta },
|
|
1007
|
+
contentRange,
|
|
1008
|
+
bytes,
|
|
1009
|
+
{ mimeType: mimeType.split(";")[0].trim() },
|
|
1010
|
+
);
|
|
1011
|
+
} catch (error) {
|
|
1012
|
+
if (isFinal) {
|
|
1013
|
+
const cleanupFailed = await cleanupFailedFinalSession();
|
|
1014
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1015
|
+
console.error(
|
|
1016
|
+
`[resumable-chunk-${recordingId}] final chunk upload threw:`,
|
|
1017
|
+
error,
|
|
1018
|
+
);
|
|
1019
|
+
setResponseStatus(event, 502);
|
|
1020
|
+
return {
|
|
1021
|
+
ok: false,
|
|
1022
|
+
error: `Final chunk upload failed: ${detail}`,
|
|
1023
|
+
...(cleanupFailed ? { cleanupFailed: true } : {}),
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
throw error;
|
|
1027
|
+
}
|
|
973
1028
|
console.log(
|
|
974
1029
|
`[resumable-chunk-${recordingId}] PUT ${Date.now() - putT0}ms status=${putResult.status} range="${contentRange}"`,
|
|
975
1030
|
);
|
|
@@ -980,7 +1035,10 @@ async function handleResumableChunk(
|
|
|
980
1035
|
if (!resultOk) {
|
|
981
1036
|
const restartRequired =
|
|
982
1037
|
putResult.status === 404 || putResult.status === 410;
|
|
983
|
-
|
|
1038
|
+
const cleanupFailed = isFinal
|
|
1039
|
+
? await cleanupFailedFinalSession()
|
|
1040
|
+
: false;
|
|
1041
|
+
if (restartRequired && !cleanupFailed) {
|
|
984
1042
|
await deleteResumableSession(recordingId, uploadGenerationId).catch(
|
|
985
1043
|
() => {},
|
|
986
1044
|
);
|
|
@@ -990,6 +1048,7 @@ async function handleResumableChunk(
|
|
|
990
1048
|
ok: false,
|
|
991
1049
|
error: `Chunk upload failed (${putResult.status})`,
|
|
992
1050
|
...(restartRequired ? { restartRequired: true } : {}),
|
|
1051
|
+
...(cleanupFailed ? { cleanupFailed: true } : {}),
|
|
993
1052
|
};
|
|
994
1053
|
}
|
|
995
1054
|
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
} from "../../../../lib/recordings.js";
|
|
32
32
|
import {
|
|
33
33
|
isRetryableUploadInterruption,
|
|
34
|
-
|
|
34
|
+
retryableUploadInterruptionReason,
|
|
35
35
|
} from "../../../../lib/upload-interruption.js";
|
|
36
36
|
import abortUpload from "./abort.post.js";
|
|
37
37
|
|
|
@@ -131,12 +131,13 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
131
131
|
const uploadStateRaw = await readAppState(uploadStateKey);
|
|
132
132
|
const uploadState = uploadStateRaw ?? {};
|
|
133
133
|
const interruptedAt = new Date().toISOString();
|
|
134
|
+
const failureReason = retryableUploadInterruptionReason(interruptionDetail);
|
|
134
135
|
if (!alreadyInterrupted) {
|
|
135
136
|
const interrupted = await db
|
|
136
137
|
.update(schema.recordings)
|
|
137
138
|
.set({
|
|
138
139
|
status: "failed",
|
|
139
|
-
failureReason
|
|
140
|
+
failureReason,
|
|
140
141
|
updatedAt: interruptedAt,
|
|
141
142
|
})
|
|
142
143
|
.where(
|
|
@@ -167,7 +168,7 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
167
168
|
...uploadState,
|
|
168
169
|
recordingId,
|
|
169
170
|
status: "failed",
|
|
170
|
-
failureReason
|
|
171
|
+
failureReason,
|
|
171
172
|
retryableInterruption: true,
|
|
172
173
|
uploadAttemptId: attemptId,
|
|
173
174
|
uploadGenerationId,
|
|
@@ -55,8 +55,10 @@ import {
|
|
|
55
55
|
} from "../../../../lib/recordings.js";
|
|
56
56
|
import {
|
|
57
57
|
deleteResumableSession,
|
|
58
|
+
getResumableSession,
|
|
58
59
|
setResumableSession,
|
|
59
60
|
} from "../../../../lib/resumable-session.js";
|
|
61
|
+
import { abortResumableUploadSession } from "../../../../lib/resumable-upload-cleanup.js";
|
|
60
62
|
import { shouldEnableStreamingUpload } from "../../../../lib/streaming-upload-mode.js";
|
|
61
63
|
import {
|
|
62
64
|
renewUploadLease,
|
|
@@ -217,6 +219,10 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
217
219
|
const nextGenerationId = useGenerationFence ? randomUUID() : null;
|
|
218
220
|
const uploadStateKey = `recording-upload-${recordingId}`;
|
|
219
221
|
const uploadStateSnapshot = await readAppState(uploadStateKey);
|
|
222
|
+
const discardedResumableSession = await getResumableSession(
|
|
223
|
+
recordingId,
|
|
224
|
+
existingGenerationId,
|
|
225
|
+
);
|
|
220
226
|
const reset = await db
|
|
221
227
|
.update(schema.recordings)
|
|
222
228
|
.set({
|
|
@@ -251,6 +257,19 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
251
257
|
};
|
|
252
258
|
}
|
|
253
259
|
|
|
260
|
+
if (discardedResumableSession) {
|
|
261
|
+
const cleaned = await abortResumableUploadSession(
|
|
262
|
+
discardedResumableSession,
|
|
263
|
+
{ label: `reset-${recordingId}` },
|
|
264
|
+
);
|
|
265
|
+
if (!cleaned) {
|
|
266
|
+
setResponseStatus(event, 502);
|
|
267
|
+
return {
|
|
268
|
+
error:
|
|
269
|
+
"The previous recording upload could not be cleaned up. Retry the upload restart.",
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
254
273
|
const cleared = await deleteRecordingChunks(
|
|
255
274
|
ownerEmail,
|
|
256
275
|
recordingId,
|
|
@@ -59,10 +59,17 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
59
59
|
const result = await reapExpiredUploads({
|
|
60
60
|
dryRun: query.dryRun === "1" || query.dryRun === "true",
|
|
61
61
|
});
|
|
62
|
-
if (
|
|
62
|
+
if (
|
|
63
|
+
result.failed > 0 ||
|
|
64
|
+
result.scratchKeysDeleted > 0 ||
|
|
65
|
+
result.resumableSessionsAborted > 0 ||
|
|
66
|
+
result.resumableCleanupFailed > 0
|
|
67
|
+
) {
|
|
63
68
|
console.log("[uploads] reaped expired uploads", {
|
|
64
69
|
failed: result.failed,
|
|
65
70
|
scratchKeysDeleted: result.scratchKeysDeleted,
|
|
71
|
+
resumableSessionsAborted: result.resumableSessionsAborted,
|
|
72
|
+
resumableCleanupFailed: result.resumableCleanupFailed,
|
|
66
73
|
});
|
|
67
74
|
}
|
|
68
75
|
return { ok: true, ...result };
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { withShareAttribution } from "./share-attribution";
|
|
14
14
|
|
|
15
|
+
/** Short-lived capability used by a signed-in private share viewer to request access. */
|
|
16
|
+
export const CLIPS_ACCESS_REQUEST_TOKEN_PREFIX = "clips-access-request";
|
|
17
|
+
export const CLIPS_ACCESS_REQUEST_TOKEN_TTL_SECONDS = 10 * 60;
|
|
18
|
+
|
|
15
19
|
/** Public share path for a recording, relative to the app base path. */
|
|
16
20
|
export function recordingSharePath(recordingId: string): string {
|
|
17
21
|
return `/share/${encodeURIComponent(recordingId)}`;
|
|
@@ -5,8 +5,9 @@ export const PRIVATE_SHARE_RESPONSE_HEADERS = {
|
|
|
5
5
|
"Referrer-Policy": "no-referrer",
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
export function privateShareLoaderData<T>(payload: T) {
|
|
8
|
+
export function privateShareLoaderData<T>(payload: T, status = 200) {
|
|
9
9
|
return data(payload, {
|
|
10
|
+
status,
|
|
10
11
|
headers: PRIVATE_SHARE_RESPONSE_HEADERS,
|
|
11
12
|
});
|
|
12
13
|
}
|
|
@@ -6,6 +6,7 @@ import { useT } from "@agent-native/core/client/i18n";
|
|
|
6
6
|
import { ShareButton } from "@agent-native/core/client/sharing";
|
|
7
7
|
import { CreativeContextShareTab } from "@agent-native/creative-context/client";
|
|
8
8
|
import { PresenceBar } from "@agent-native/toolkit/collab-ui";
|
|
9
|
+
import { ShareTrigger } from "@agent-native/toolkit/sharing";
|
|
9
10
|
import type { DocumentSourceInfo } from "@shared/api";
|
|
10
11
|
import {
|
|
11
12
|
IconArrowBarDown,
|
|
@@ -31,7 +32,6 @@ import {
|
|
|
31
32
|
IconLink,
|
|
32
33
|
IconMessageCircle,
|
|
33
34
|
IconRefresh,
|
|
34
|
-
IconShare3,
|
|
35
35
|
IconTrash,
|
|
36
36
|
} from "@tabler/icons-react";
|
|
37
37
|
import { useQueryClient } from "@tanstack/react-query";
|
|
@@ -891,22 +891,13 @@ export function DocumentToolbar({
|
|
|
891
891
|
className="mr-1"
|
|
892
892
|
/>
|
|
893
893
|
{isLocalFileDocument ? (
|
|
894
|
-
<
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
className="h-9 gap-1.5 rounded-lg px-3"
|
|
894
|
+
<ShareTrigger
|
|
895
|
+
className="h-9 rounded-lg px-3"
|
|
896
|
+
pending={shareLocalFile.isPending}
|
|
898
897
|
disabled={shareLocalFile.isPending}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
<IconLoader2 className="h-4 w-4 animate-spin" />
|
|
903
|
-
) : (
|
|
904
|
-
<IconShare3 className="h-4 w-4" />
|
|
905
|
-
)}
|
|
906
|
-
<span className="hidden sm:inline">
|
|
907
|
-
{t("editor.toolbar.share")}
|
|
908
|
-
</span>
|
|
909
|
-
</Button>
|
|
898
|
+
label={t("editor.toolbar.share")}
|
|
899
|
+
onPress={() => void handleShareLocalFile()}
|
|
900
|
+
/>
|
|
910
901
|
) : (
|
|
911
902
|
<>
|
|
912
903
|
<ShareButton
|
|
@@ -35,7 +35,8 @@ decisions, feedback, agent runs, and provider audit records.
|
|
|
35
35
|
|
|
36
36
|
## Application state
|
|
37
37
|
|
|
38
|
-
- `navigation.view`: `factory`
|
|
38
|
+
- `navigation.view`: `factory` for the Factory workspace or `agents` for the
|
|
39
|
+
shared Agents sidebar surface.
|
|
39
40
|
- `navigation.factoryId`: selected Factory id when present.
|
|
40
41
|
- `navigation.factoryTab`: `map` | `inbox` | `rules` | `automations` | `agents` | `audit` | `settings`.
|
|
41
42
|
- `navigation.factoryAuditRunId`: selected automation run in the audit view when present.
|
|
@@ -43,6 +44,9 @@ decisions, feedback, agent runs, and provider audit records.
|
|
|
43
44
|
- A selected graph node or edge is part of `navigation` context. Read
|
|
44
45
|
`view-screen` before answering why a route exists or changing the selected
|
|
45
46
|
Factory.
|
|
47
|
+
- The `/agents` surface shows mounted agentic apps and reusable agent packs from
|
|
48
|
+
the same Dispatch SQL/action registry. Use `view-screen` there before
|
|
49
|
+
answering questions about the visible agent inventory.
|
|
46
50
|
|
|
47
51
|
## Action contract
|
|
48
52
|
|