@agent-native/core 0.84.12 → 0.84.14
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 +19 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +42 -5
- package/corpus/core/src/agent/production-agent.ts +72 -10
- package/corpus/core/src/agent/run-manager.ts +84 -4
- package/corpus/core/src/agent/run-store.ts +55 -11
- package/corpus/core/src/chat-threads/store.ts +2 -0
- package/corpus/core/src/cli/skills.ts +10 -6
- package/corpus/core/src/client/AssistantChat.tsx +40 -9
- package/corpus/core/src/client/agent-chat-adapter.ts +44 -5
- package/corpus/core/src/client/blocks/library/diagram.tsx +3 -3
- package/corpus/core/src/client/chat/message-components.tsx +17 -0
- package/corpus/core/src/client/chat/run-recovery.tsx +52 -44
- package/corpus/core/src/client/chat/tool-call-display.tsx +7 -2
- package/corpus/core/src/observability/traces.ts +17 -3
- package/corpus/core/src/server/agent-chat-plugin.ts +12 -1
- package/corpus/core/src/shared/streaming-text-smoothing.ts +10 -5
- package/corpus/core/src/styles/blocks.css +44 -1
- package/corpus/templates/clips/app/components/library/recording-card.tsx +14 -4
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +10 -12
- package/corpus/templates/clips/app/hooks/use-library.ts +7 -4
- package/corpus/templates/clips/app/lib/recording-status.ts +32 -0
- package/corpus/templates/clips/changelog/2026-07-01-chrome-extension-recordings-stream-uploads-while-recording.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-dictation-keeps-listening-through-natural-pauses-and-gives-c.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-redoing-a-paused-desktop-recording-no-longer-leaves-the-recording-controls-disabled.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-stalled-clip-uploads-now-update-in-the-library-and-surface-as-failed.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/background.ts +8 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +61 -16
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +36 -18
- package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +99 -27
- package/corpus/templates/clips/desktop/src/overlays/flow-bar.tsx +4 -43
- package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +3 -0
- package/corpus/templates/clips/desktop/src/styles.css +5 -48
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +4 -5
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +15 -6
- package/corpus/templates/design/AGENTS.md +8 -4
- package/corpus/templates/design/actions/create-design.ts +1 -0
- package/corpus/templates/design/actions/duplicate-design.ts +3 -1
- package/corpus/templates/design/actions/edit-design.ts +43 -13
- package/corpus/templates/design/actions/generate-design.ts +5 -1
- package/corpus/templates/design/actions/present-design-variants.ts +17 -12
- 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-org-members-can-see-design-projects-created-in-their-workspace.md +6 -0
- package/corpus/templates/design/server/plugins/db.ts +9 -0
- package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +44 -1
- package/corpus/templates/plan/changelog/2026-07-01-visual-plan-and-recap-diagram-labels-now-wrap-inside-their-b.md +6 -0
- package/dist/agent/durable-background.d.ts +3 -0
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +33 -3
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/production-agent.d.ts +1 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +55 -9
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +2 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +68 -5
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +9 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +47 -11
- package/dist/agent/run-store.js.map +1 -1
- package/dist/chat-threads/store.d.ts +1 -0
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +2 -0
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +10 -6
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +10 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +26 -8
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +40 -5
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/blocks/library/diagram.js +3 -3
- package/dist/client/blocks/library/diagram.js.map +1 -1
- package/dist/client/chat/message-components.d.ts +3 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +16 -2
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/run-recovery.d.ts.map +1 -1
- package/dist/client/chat/run-recovery.js +15 -10
- package/dist/client/chat/run-recovery.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +1 -0
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +3 -2
- package/dist/client/chat/tool-call-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/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +6 -6
- package/dist/observability/traces.d.ts +5 -0
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +9 -3
- package/dist/observability/traces.js.map +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-chat-plugin.d.ts +2 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +8 -3
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/streaming-text-smoothing.d.ts.map +1 -1
- package/dist/shared/streaming-text-smoothing.js +8 -5
- package/dist/shared/streaming-text-smoothing.js.map +1 -1
- package/dist/styles/blocks.css +44 -1
- package/package.json +1 -1
|
@@ -513,7 +513,9 @@ export function RunErrorRecoveryCard({
|
|
|
513
513
|
onDismiss: () => void;
|
|
514
514
|
}) {
|
|
515
515
|
const [detailsOpen, setDetailsOpen] = useState(false);
|
|
516
|
-
const [
|
|
516
|
+
const [copyState, setCopyState] = useState<"idle" | "copied" | "failed">(
|
|
517
|
+
"idle",
|
|
518
|
+
);
|
|
517
519
|
const [forking, setForking] = useState(false);
|
|
518
520
|
const [forkError, setForkError] = useState<string | null>(null);
|
|
519
521
|
const builderReconnect = useBuilderConnectFlow({
|
|
@@ -538,11 +540,15 @@ export function RunErrorRecoveryCard({
|
|
|
538
540
|
]
|
|
539
541
|
.filter(Boolean)
|
|
540
542
|
.join("\n\n");
|
|
541
|
-
void writeClipboardText(text)
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
543
|
+
void writeClipboardText(text)
|
|
544
|
+
.then((ok) => {
|
|
545
|
+
setCopyState(ok ? "copied" : "failed");
|
|
546
|
+
setTimeout(() => setCopyState("idle"), 1600);
|
|
547
|
+
})
|
|
548
|
+
.catch(() => {
|
|
549
|
+
setCopyState("failed");
|
|
550
|
+
setTimeout(() => setCopyState("idle"), 1600);
|
|
551
|
+
});
|
|
546
552
|
}, [info]);
|
|
547
553
|
const startNewChat = useCallback(() => {
|
|
548
554
|
window.dispatchEvent(new CustomEvent("agent-chat:new-chat"));
|
|
@@ -705,8 +711,20 @@ export function RunErrorRecoveryCard({
|
|
|
705
711
|
onClick={copyDetails}
|
|
706
712
|
className="ml-auto inline-flex h-8 items-center gap-1.5 rounded-md px-2.5 text-xs font-medium text-muted-foreground hover:bg-background/80 hover:text-foreground"
|
|
707
713
|
>
|
|
708
|
-
{copied ?
|
|
709
|
-
|
|
714
|
+
{copyState === "copied" ? (
|
|
715
|
+
<IconCheck size={13} />
|
|
716
|
+
) : copyState === "failed" ? (
|
|
717
|
+
<IconX size={13} />
|
|
718
|
+
) : (
|
|
719
|
+
<IconCopy size={13} />
|
|
720
|
+
)}
|
|
721
|
+
<span aria-live="polite">
|
|
722
|
+
{copyState === "copied"
|
|
723
|
+
? "Copied"
|
|
724
|
+
: copyState === "failed"
|
|
725
|
+
? "Copy failed"
|
|
726
|
+
: copyLabel}
|
|
727
|
+
</span>
|
|
710
728
|
</button>
|
|
711
729
|
</div>
|
|
712
730
|
{shouldShowBuilderReconnect && builderReconnect.error && (
|
|
@@ -913,42 +931,32 @@ export function PlanModeCallout({
|
|
|
913
931
|
onSwitchToAct: () => void;
|
|
914
932
|
}) {
|
|
915
933
|
return (
|
|
916
|
-
<div className="shrink-0 px-3 pt-
|
|
917
|
-
<div className="rounded-
|
|
918
|
-
<
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
<button
|
|
943
|
-
type="button"
|
|
944
|
-
onClick={onSwitchToAct}
|
|
945
|
-
className="inline-flex h-8 shrink-0 items-center gap-1.5 rounded-md border border-border bg-background px-3 text-xs font-medium text-foreground hover:bg-accent"
|
|
946
|
-
>
|
|
947
|
-
Act
|
|
948
|
-
<IconArrowRight size={13} />
|
|
949
|
-
</button>
|
|
950
|
-
)}
|
|
951
|
-
</div>
|
|
934
|
+
<div className="shrink-0 px-3 pt-1">
|
|
935
|
+
<div className="ml-auto flex w-fit max-w-full items-center gap-2 rounded-full border border-border/70 bg-background/95 px-2 py-1.5 text-xs text-muted-foreground shadow-sm">
|
|
936
|
+
<IconClipboardList size={13} className="shrink-0" />
|
|
937
|
+
<span className="min-w-0 truncate">
|
|
938
|
+
{canImplementPlan ? "Plan ready" : "Plan mode"}
|
|
939
|
+
</span>
|
|
940
|
+
{canImplementPlan ? (
|
|
941
|
+
<button
|
|
942
|
+
type="button"
|
|
943
|
+
onClick={onImplementPlan}
|
|
944
|
+
className="inline-flex h-6 shrink-0 items-center gap-1 rounded-full bg-foreground px-2.5 text-[11px] font-medium text-background hover:opacity-90"
|
|
945
|
+
>
|
|
946
|
+
<IconPlayerPlay size={12} />
|
|
947
|
+
Implement
|
|
948
|
+
</button>
|
|
949
|
+
) : (
|
|
950
|
+
<button
|
|
951
|
+
type="button"
|
|
952
|
+
onClick={onSwitchToAct}
|
|
953
|
+
className="inline-flex h-6 shrink-0 items-center gap-1 rounded-full border border-border bg-background px-2.5 text-[11px] font-medium text-foreground hover:bg-accent"
|
|
954
|
+
aria-label="Switch to Act mode"
|
|
955
|
+
>
|
|
956
|
+
Act
|
|
957
|
+
<IconArrowRight size={12} />
|
|
958
|
+
</button>
|
|
959
|
+
)}
|
|
952
960
|
</div>
|
|
953
961
|
</div>
|
|
954
962
|
);
|
|
@@ -692,10 +692,12 @@ export function ToolCallFallback({
|
|
|
692
692
|
mcpApp?: AgentMcpAppPayload;
|
|
693
693
|
chatUI?: ActionChatUIConfig;
|
|
694
694
|
structuredMeta?: Record<string, unknown>;
|
|
695
|
+
activity?: boolean;
|
|
695
696
|
approval?: { approvalKey: string; dismissed?: boolean };
|
|
696
697
|
}) {
|
|
697
698
|
const chatRunning = React.useContext(ChatRunningContext);
|
|
698
|
-
const isRunning =
|
|
699
|
+
const isRunning =
|
|
700
|
+
result === undefined && (chatRunning || rest.activity === true);
|
|
699
701
|
return (
|
|
700
702
|
<ToolCallDisplay
|
|
701
703
|
toolName={toolName}
|
|
@@ -757,7 +759,10 @@ export function ReconnectStreamMessage({
|
|
|
757
759
|
mcpApp={part.mcpApp}
|
|
758
760
|
chatUI={part.chatUI}
|
|
759
761
|
structuredMeta={part.structuredMeta}
|
|
760
|
-
isRunning={
|
|
762
|
+
isRunning={
|
|
763
|
+
part.result === undefined &&
|
|
764
|
+
(chatRunning || part.activity === true)
|
|
765
|
+
}
|
|
761
766
|
approval={part.approval}
|
|
762
767
|
/>
|
|
763
768
|
);
|
|
@@ -89,6 +89,14 @@ export async function instrumentAgentLoop(opts: {
|
|
|
89
89
|
* reads. */
|
|
90
90
|
userId: string | null;
|
|
91
91
|
config: ObservabilityConfig;
|
|
92
|
+
classifyError?: (error: unknown) =>
|
|
93
|
+
| {
|
|
94
|
+
status?: "success" | "error";
|
|
95
|
+
errorMessage?: string | null;
|
|
96
|
+
metadata?: Record<string, unknown> | null;
|
|
97
|
+
}
|
|
98
|
+
| null
|
|
99
|
+
| undefined;
|
|
92
100
|
}): Promise<AgentLoopUsage> {
|
|
93
101
|
const { runAgentLoop, loopOpts, runId, threadId, userId, config } = opts;
|
|
94
102
|
const runStart = Date.now();
|
|
@@ -260,11 +268,17 @@ export async function instrumentAgentLoop(opts: {
|
|
|
260
268
|
let usage: AgentLoopUsage | undefined;
|
|
261
269
|
let runStatus: "success" | "error" = "success";
|
|
262
270
|
let errorMessage: string | null = null;
|
|
271
|
+
let runMetadata: Record<string, unknown> | null = null;
|
|
263
272
|
try {
|
|
264
273
|
usage = await runAgentLoop({ ...loopOpts, send: instrumentedSend });
|
|
265
274
|
} catch (err: any) {
|
|
266
|
-
|
|
267
|
-
|
|
275
|
+
const classification = opts.classifyError?.(err) ?? null;
|
|
276
|
+
runStatus = classification?.status ?? "error";
|
|
277
|
+
errorMessage =
|
|
278
|
+
classification?.errorMessage === undefined
|
|
279
|
+
? (err?.message ?? String(err))
|
|
280
|
+
: classification.errorMessage;
|
|
281
|
+
runMetadata = classification?.metadata ?? null;
|
|
268
282
|
throw err;
|
|
269
283
|
} finally {
|
|
270
284
|
const runEnd = Date.now();
|
|
@@ -325,7 +339,7 @@ export async function instrumentAgentLoop(opts: {
|
|
|
325
339
|
durationMs: totalDurationMs,
|
|
326
340
|
status: runStatus,
|
|
327
341
|
errorMessage,
|
|
328
|
-
metadata:
|
|
342
|
+
metadata: runMetadata,
|
|
329
343
|
createdAt: runStart,
|
|
330
344
|
};
|
|
331
345
|
spans.push(parentSpan);
|
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
recordRunDiagnostic,
|
|
70
70
|
RUN_DIAG_STAGE,
|
|
71
71
|
setRunError,
|
|
72
|
+
setRunTerminalReason,
|
|
72
73
|
updateRunStatusIfRunning,
|
|
73
74
|
} from "../agent/run-store.js";
|
|
74
75
|
import { buildRuntimeContextPrompt } from "../agent/runtime-context.js";
|
|
@@ -3876,6 +3877,7 @@ type AgentChatProcessRunFailureDeps = {
|
|
|
3876
3877
|
readBackgroundRunClaim?: typeof readBackgroundRunClaim;
|
|
3877
3878
|
recordRunDiagnostic?: typeof recordRunDiagnostic;
|
|
3878
3879
|
setRunError?: typeof setRunError;
|
|
3880
|
+
setRunTerminalReason?: typeof setRunTerminalReason;
|
|
3879
3881
|
updateRunStatusIfRunning?: typeof updateRunStatusIfRunning;
|
|
3880
3882
|
ensureTerminalRunEvent?: typeof ensureTerminalRunEvent;
|
|
3881
3883
|
};
|
|
@@ -3888,6 +3890,7 @@ export async function finalizeClaimedAgentChatProcessRunFailure(
|
|
|
3888
3890
|
const readClaim = deps.readBackgroundRunClaim ?? readBackgroundRunClaim;
|
|
3889
3891
|
const record = deps.recordRunDiagnostic ?? recordRunDiagnostic;
|
|
3890
3892
|
const setError = deps.setRunError ?? setRunError;
|
|
3893
|
+
const setTerminalReason = deps.setRunTerminalReason ?? setRunTerminalReason;
|
|
3891
3894
|
const updateStatus =
|
|
3892
3895
|
deps.updateRunStatusIfRunning ?? updateRunStatusIfRunning;
|
|
3893
3896
|
const ensureTerminal = deps.ensureTerminalRunEvent ?? ensureTerminalRunEvent;
|
|
@@ -3908,7 +3911,13 @@ export async function finalizeClaimedAgentChatProcessRunFailure(
|
|
|
3908
3911
|
CLAIMED_BACKGROUND_WORKER_FAILED_ERROR_EVENT.errorCode,
|
|
3909
3912
|
`${CLAIMED_BACKGROUND_WORKER_FAILED_ERROR_EVENT.details} setupError=${message}`,
|
|
3910
3913
|
).catch(() => {});
|
|
3911
|
-
await updateStatus(runId, "errored").catch(() =>
|
|
3914
|
+
const statusUpdated = await updateStatus(runId, "errored").catch(() => false);
|
|
3915
|
+
if (statusUpdated) {
|
|
3916
|
+
await setTerminalReason(
|
|
3917
|
+
runId,
|
|
3918
|
+
CLAIMED_BACKGROUND_WORKER_FAILED_ERROR_EVENT.errorCode,
|
|
3919
|
+
).catch(() => {});
|
|
3920
|
+
}
|
|
3912
3921
|
await ensureTerminal(
|
|
3913
3922
|
runId,
|
|
3914
3923
|
CLAIMED_BACKGROUND_WORKER_FAILED_ERROR_EVENT,
|
|
@@ -7400,6 +7409,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
7400
7409
|
// unreadable Netlify background-function logs — read
|
|
7401
7410
|
// `/runs/active?threadId=...` and inspect `diagStage`.
|
|
7402
7411
|
dispatchMode: run.dispatchMode ?? null,
|
|
7412
|
+
terminalReason: run.terminalReason ?? null,
|
|
7403
7413
|
diagStage: run.diagStage ?? null,
|
|
7404
7414
|
workerStage: workerClaim?.workerStage ?? null,
|
|
7405
7415
|
// Server clock so the client computes "stuck" elapsed time
|
|
@@ -7694,6 +7704,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
7694
7704
|
nextTitle,
|
|
7695
7705
|
nextPreview,
|
|
7696
7706
|
newMessageCount,
|
|
7707
|
+
{ ignoreConflicts: true },
|
|
7697
7708
|
);
|
|
7698
7709
|
// Scope updates piggyback on the PUT — the client uses this
|
|
7699
7710
|
// path for both "detach" (scope: null) and "retag" flows.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const SMOOTH_STREAMING_COMMIT_INTERVAL_MS = 16;
|
|
2
2
|
export const SMOOTH_STREAMING_LONG_TEXT_THRESHOLD_GRAPHEMES = 640;
|
|
3
3
|
export const SMOOTH_STREAMING_LONG_TEXT_TAIL_GRAPHEMES = 180;
|
|
4
|
+
const SMOOTH_STREAMING_SPEED_MULTIPLIER = 2;
|
|
4
5
|
|
|
5
6
|
type SegmenterInstance = {
|
|
6
7
|
segment(input: string): Iterable<{ segment: string }>;
|
|
@@ -137,7 +138,7 @@ export function smoothStreamingRevealCount({
|
|
|
137
138
|
return 0;
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
const
|
|
141
|
+
const baseCharactersPerSecond = inputDone
|
|
141
142
|
? backlog > 800
|
|
142
143
|
? 900
|
|
143
144
|
: 420
|
|
@@ -148,8 +149,12 @@ export function smoothStreamingRevealCount({
|
|
|
148
149
|
: backlog > 180
|
|
149
150
|
? 190
|
|
150
151
|
: 95;
|
|
152
|
+
const charactersPerSecond =
|
|
153
|
+
baseCharactersPerSecond * SMOOTH_STREAMING_SPEED_MULTIPLIER;
|
|
151
154
|
|
|
152
|
-
const maxBurst =
|
|
155
|
+
const maxBurst =
|
|
156
|
+
(inputDone ? 160 : backlog > 1400 ? 120 : 72) *
|
|
157
|
+
SMOOTH_STREAMING_SPEED_MULTIPLIER;
|
|
153
158
|
const count = Math.floor((elapsedMs / 1000) * charactersPerSecond);
|
|
154
159
|
|
|
155
160
|
return Math.min(backlog, Math.max(1, count), maxBurst);
|
|
@@ -164,15 +169,15 @@ export function smoothStreamingPunctuationDelayMs(
|
|
|
164
169
|
}
|
|
165
170
|
|
|
166
171
|
if (grapheme === "\n") {
|
|
167
|
-
return 80;
|
|
172
|
+
return 80 / SMOOTH_STREAMING_SPEED_MULTIPLIER;
|
|
168
173
|
}
|
|
169
174
|
|
|
170
175
|
if (/[.!?)]/.test(grapheme)) {
|
|
171
|
-
return 70;
|
|
176
|
+
return 70 / SMOOTH_STREAMING_SPEED_MULTIPLIER;
|
|
172
177
|
}
|
|
173
178
|
|
|
174
179
|
if (/[,;:]/.test(grapheme)) {
|
|
175
|
-
return 35;
|
|
180
|
+
return 35 / SMOOTH_STREAMING_SPEED_MULTIPLIER;
|
|
176
181
|
}
|
|
177
182
|
|
|
178
183
|
return 0;
|
|
@@ -1355,8 +1355,37 @@
|
|
|
1355
1355
|
color: var(--wf-ink) !important;
|
|
1356
1356
|
}
|
|
1357
1357
|
|
|
1358
|
-
.plan-diagram-frame
|
|
1358
|
+
.plan-diagram-frame
|
|
1359
|
+
:is(
|
|
1360
|
+
.diagram-panel,
|
|
1361
|
+
.diagram-card,
|
|
1362
|
+
.diagram-node,
|
|
1363
|
+
.diagram-box,
|
|
1364
|
+
[class*="card"],
|
|
1365
|
+
[class*="panel"],
|
|
1366
|
+
[class*="box"]
|
|
1367
|
+
) {
|
|
1359
1368
|
max-width: 100%;
|
|
1369
|
+
overflow-wrap: anywhere !important;
|
|
1370
|
+
word-break: break-word;
|
|
1371
|
+
white-space: normal !important;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
.plan-diagram-frame
|
|
1375
|
+
:is(
|
|
1376
|
+
.diagram-panel,
|
|
1377
|
+
.diagram-card,
|
|
1378
|
+
.diagram-node,
|
|
1379
|
+
.diagram-box,
|
|
1380
|
+
[class*="card"],
|
|
1381
|
+
[class*="panel"],
|
|
1382
|
+
[class*="box"]
|
|
1383
|
+
)
|
|
1384
|
+
:is(h1, h2, h3, p, small, strong, span, li) {
|
|
1385
|
+
max-width: 100%;
|
|
1386
|
+
overflow-wrap: anywhere !important;
|
|
1387
|
+
word-break: break-word;
|
|
1388
|
+
white-space: normal !important;
|
|
1360
1389
|
}
|
|
1361
1390
|
|
|
1362
1391
|
/* Base padding so primitive text never touches the box edge, even when an author
|
|
@@ -1422,6 +1451,7 @@
|
|
|
1422
1451
|
display: inline-flex;
|
|
1423
1452
|
align-items: center;
|
|
1424
1453
|
justify-content: center;
|
|
1454
|
+
flex-wrap: wrap;
|
|
1425
1455
|
/* A pill hugs its label. `fit-content` is a definite cross size, so a flex
|
|
1426
1456
|
* column with the default `align-items: stretch` no longer blows the pill out
|
|
1427
1457
|
* to full width (the number chips and short status labels stay pill-shaped).
|
|
@@ -1434,6 +1464,19 @@
|
|
|
1434
1464
|
padding: 3px 11px;
|
|
1435
1465
|
color: var(--wf-ink);
|
|
1436
1466
|
background: var(--wf-paper);
|
|
1467
|
+
overflow-wrap: anywhere !important;
|
|
1468
|
+
text-align: center;
|
|
1469
|
+
white-space: normal !important;
|
|
1470
|
+
word-break: break-word;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
.plan-diagram-frame
|
|
1474
|
+
:is(.diagram-pill, [class*="pill"], [class*="badge"], [class*="chip"])
|
|
1475
|
+
> * {
|
|
1476
|
+
min-width: 0;
|
|
1477
|
+
max-width: 100%;
|
|
1478
|
+
overflow-wrap: anywhere !important;
|
|
1479
|
+
white-space: normal !important;
|
|
1437
1480
|
}
|
|
1438
1481
|
|
|
1439
1482
|
.plan-diagram-frame :is(.diagram-accent, .diagram-pill.accent) {
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from "@/components/ui/dropdown-menu";
|
|
29
29
|
import { isDefaultTitle } from "@/hooks/use-auto-title";
|
|
30
30
|
import type { RecordingSummary } from "@/hooks/use-library";
|
|
31
|
+
import { isStaleRecordingUpload } from "@/lib/recording-status";
|
|
31
32
|
import { isStorageSetupFailureReason } from "@/lib/storage-failures";
|
|
32
33
|
import { cn } from "@/lib/utils";
|
|
33
34
|
|
|
@@ -100,6 +101,12 @@ export function RecordingCard({
|
|
|
100
101
|
const waitingForStorage = isStorageSetupFailureReason(
|
|
101
102
|
recording.failureReason,
|
|
102
103
|
);
|
|
104
|
+
const staleUpload = isStaleRecordingUpload(recording);
|
|
105
|
+
const displayFailed = recording.status === "failed" || staleUpload;
|
|
106
|
+
const failureReason = staleUpload
|
|
107
|
+
? (recording.failureReason ??
|
|
108
|
+
t("recordingPage.processingStuck", { status: recording.status }))
|
|
109
|
+
: (recording.failureReason ?? t("clipsFinalRaw.removeFailedClip"));
|
|
103
110
|
const nativeUploadPaused =
|
|
104
111
|
recording.status === "failed" &&
|
|
105
112
|
/native recording|native fullscreen|screencapture|avconvert/i.test(
|
|
@@ -221,11 +228,15 @@ export function RecordingCard({
|
|
|
221
228
|
{/* Status pill for non-ready recordings */}
|
|
222
229
|
{recording.status !== "ready" && (
|
|
223
230
|
<div className="absolute top-2 end-2 rounded-full bg-black/80 px-2 py-0.5 text-[10px] font-medium text-white uppercase tracking-wide">
|
|
224
|
-
{waitingForStorage
|
|
231
|
+
{waitingForStorage
|
|
232
|
+
? "storage"
|
|
233
|
+
: staleUpload
|
|
234
|
+
? "failed"
|
|
235
|
+
: recording.status}
|
|
225
236
|
</div>
|
|
226
237
|
)}
|
|
227
238
|
|
|
228
|
-
{(
|
|
239
|
+
{(displayFailed || waitingForStorage) && (
|
|
229
240
|
<div
|
|
230
241
|
className={cn(
|
|
231
242
|
"absolute inset-x-2 bottom-2 rounded-md border bg-background/95 p-2 text-start shadow-sm backdrop-blur",
|
|
@@ -252,8 +263,7 @@ export function RecordingCard({
|
|
|
252
263
|
? t("clipsFinalRaw.connectStorageToFinish")
|
|
253
264
|
: nativeUploadPaused
|
|
254
265
|
? t("clipsFinalRaw.retryFromClipsMenu")
|
|
255
|
-
:
|
|
256
|
-
t("clipsFinalRaw.removeFailedClip"))}
|
|
266
|
+
: failureReason}
|
|
257
267
|
</div>
|
|
258
268
|
</div>
|
|
259
269
|
{!waitingForStorage && (
|
|
@@ -1145,6 +1145,9 @@ export class RecorderEngine {
|
|
|
1145
1145
|
// mid-state — the UI spinner is wired to engine state and would
|
|
1146
1146
|
// hang forever otherwise.
|
|
1147
1147
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
1148
|
+
if (e.name !== "AbortError") {
|
|
1149
|
+
this.rememberUploadFailure(e);
|
|
1150
|
+
}
|
|
1148
1151
|
this.transition("error", { message: e.message });
|
|
1149
1152
|
throw e;
|
|
1150
1153
|
} finally {
|
|
@@ -1183,6 +1186,9 @@ export class RecorderEngine {
|
|
|
1183
1186
|
return this.toFinalizeResult(result, meta);
|
|
1184
1187
|
} catch (err) {
|
|
1185
1188
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
1189
|
+
if (e.name !== "AbortError") {
|
|
1190
|
+
this.rememberUploadFailure(e);
|
|
1191
|
+
}
|
|
1186
1192
|
this.transition("error", { message: e.message });
|
|
1187
1193
|
throw e;
|
|
1188
1194
|
} finally {
|
|
@@ -1595,26 +1601,18 @@ export class RecorderEngine {
|
|
|
1595
1601
|
const failure = err instanceof Error ? err : new Error(String(err));
|
|
1596
1602
|
// User-initiated cancel — cancel() already runs the abortUrl path.
|
|
1597
1603
|
if (failure.name === "AbortError") return;
|
|
1598
|
-
|
|
1604
|
+
this.rememberUploadFailure(failure);
|
|
1599
1605
|
this.emitError(failure);
|
|
1600
1606
|
}
|
|
1601
1607
|
});
|
|
1602
1608
|
}
|
|
1603
1609
|
|
|
1604
|
-
private
|
|
1610
|
+
private rememberUploadFailure(err: Error): void {
|
|
1605
1611
|
if (!this.uploadFailure) {
|
|
1606
1612
|
this.uploadFailure = err;
|
|
1607
1613
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
await fetch(this.opts.abortUrl, {
|
|
1611
|
-
method: "POST",
|
|
1612
|
-
headers: { "Content-Type": "application/json" },
|
|
1613
|
-
body: JSON.stringify({ reason: err.message }),
|
|
1614
|
-
});
|
|
1615
|
-
} catch {
|
|
1616
|
-
// ignore — the stop path will surface the original upload error.
|
|
1617
|
-
}
|
|
1614
|
+
// Do not call abortUrl for retryable upload failures. retryUpload() reuses
|
|
1615
|
+
// this recording id; cancel() owns the terminal server-side abort path.
|
|
1618
1616
|
}
|
|
1619
1617
|
|
|
1620
1618
|
private async uploadBlobInSlices(
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useActionQuery, useActionMutation } from "@agent-native/core/client";
|
|
2
2
|
|
|
3
|
+
import { isLiveRecordingUpload } from "@/lib/recording-status";
|
|
4
|
+
|
|
3
5
|
export interface RecordingSummary {
|
|
4
6
|
id: string;
|
|
5
7
|
title: string;
|
|
@@ -72,16 +74,17 @@ export function useRecordings(args: ListRecordingsArgs = {}) {
|
|
|
72
74
|
recordings: Array.isArray(data?.recordings) ? data.recordings : [],
|
|
73
75
|
};
|
|
74
76
|
},
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
77
|
+
// Keep a short poll while uploads/processors are active so the library
|
|
78
|
+
// card does not get stuck if the global refresh signal is missed.
|
|
79
|
+
// Also poll for replaceable seed titles so the card upgrades promptly.
|
|
78
80
|
refetchInterval: (q) => {
|
|
79
81
|
const recs = (q.state.data as any)?.recordings as
|
|
80
82
|
| RecordingSummary[]
|
|
81
83
|
| undefined;
|
|
82
84
|
if (!recs || recs.length === 0) return false;
|
|
85
|
+
const pendingUpload = recs.some((rec) => isLiveRecordingUpload(rec));
|
|
83
86
|
const pendingTitle = recs.some(isAwaitingAutoTitle);
|
|
84
|
-
return pendingTitle ? 3000 : false;
|
|
87
|
+
return pendingUpload || pendingTitle ? 3000 : false;
|
|
85
88
|
},
|
|
86
89
|
},
|
|
87
90
|
);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const STALE_RECORDING_UPLOAD_MS = 30 * 60 * 1000;
|
|
2
|
+
|
|
3
|
+
type RecordingStatusLike = {
|
|
4
|
+
status?: string | null;
|
|
5
|
+
updatedAt?: string | null;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function isActiveRecordingUploadStatus(
|
|
9
|
+
status: string | null | undefined,
|
|
10
|
+
): boolean {
|
|
11
|
+
return status === "uploading" || status === "processing";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isStaleRecordingUpload(
|
|
15
|
+
recording: RecordingStatusLike,
|
|
16
|
+
nowMs = Date.now(),
|
|
17
|
+
): boolean {
|
|
18
|
+
if (!isActiveRecordingUploadStatus(recording.status)) return false;
|
|
19
|
+
const updatedAtMs = Date.parse(recording.updatedAt ?? "");
|
|
20
|
+
if (!Number.isFinite(updatedAtMs)) return false;
|
|
21
|
+
return nowMs - updatedAtMs >= STALE_RECORDING_UPLOAD_MS;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isLiveRecordingUpload(
|
|
25
|
+
recording: RecordingStatusLike,
|
|
26
|
+
nowMs = Date.now(),
|
|
27
|
+
): boolean {
|
|
28
|
+
if (!isActiveRecordingUploadStatus(recording.status)) return false;
|
|
29
|
+
const updatedAtMs = Date.parse(recording.updatedAt ?? "");
|
|
30
|
+
if (!Number.isFinite(updatedAtMs)) return true;
|
|
31
|
+
return nowMs - updatedAtMs < STALE_RECORDING_UPLOAD_MS;
|
|
32
|
+
}
|
|
@@ -32,6 +32,7 @@ const UNQUOTED_SECRET_VALUE_RE = new RegExp(
|
|
|
32
32
|
type CaptureSurface = "browser" | "window" | "monitor" | "camera";
|
|
33
33
|
type ConsoleLevel = "debug" | "log" | "info" | "warn" | "error";
|
|
34
34
|
type NetworkType = "fetch" | "xhr";
|
|
35
|
+
type UploadMode = "streaming" | "buffered";
|
|
35
36
|
|
|
36
37
|
type ExtensionSettings = {
|
|
37
38
|
clipsBaseUrl: string;
|
|
@@ -134,6 +135,7 @@ type NativeRecording = {
|
|
|
134
135
|
recordingId: string;
|
|
135
136
|
clipsBaseUrl: string;
|
|
136
137
|
uploadUrl: string;
|
|
138
|
+
uploadMode: UploadMode;
|
|
137
139
|
targetTabId: number;
|
|
138
140
|
targetTitle: string | null;
|
|
139
141
|
targetUrl: string | null;
|
|
@@ -1049,6 +1051,7 @@ async function armRecording(args: {
|
|
|
1049
1051
|
id?: string;
|
|
1050
1052
|
uploadChunkUrl?: string;
|
|
1051
1053
|
abortUrl?: string;
|
|
1054
|
+
uploadMode?: UploadMode;
|
|
1052
1055
|
};
|
|
1053
1056
|
let created: CreatedRecording;
|
|
1054
1057
|
try {
|
|
@@ -1061,6 +1064,8 @@ async function armRecording(args: {
|
|
|
1061
1064
|
hasAudio:
|
|
1062
1065
|
settings.includeMicrophone || settings.captureSurface !== "camera",
|
|
1063
1066
|
visibility: "public",
|
|
1067
|
+
mimeType: "video/webm",
|
|
1068
|
+
requestStreaming: true,
|
|
1064
1069
|
});
|
|
1065
1070
|
} catch (err) {
|
|
1066
1071
|
captureExtensionError(err, {
|
|
@@ -1091,6 +1096,7 @@ async function armRecording(args: {
|
|
|
1091
1096
|
recordingId: created.id,
|
|
1092
1097
|
clipsBaseUrl: settings.clipsBaseUrl,
|
|
1093
1098
|
uploadUrl,
|
|
1099
|
+
uploadMode: created.uploadMode ?? "buffered",
|
|
1094
1100
|
targetTabId: tab.id as number,
|
|
1095
1101
|
targetTitle: tab.title?.trim() || null,
|
|
1096
1102
|
targetUrl: tab.url?.trim() || null,
|
|
@@ -1122,6 +1128,7 @@ async function armRecording(args: {
|
|
|
1122
1128
|
sessionId,
|
|
1123
1129
|
recordingId: created.id,
|
|
1124
1130
|
uploadUrl,
|
|
1131
|
+
uploadMode: created.uploadMode ?? "buffered",
|
|
1125
1132
|
hasCamera: cameraInvolved,
|
|
1126
1133
|
startDelayMs,
|
|
1127
1134
|
authToken,
|
|
@@ -1274,6 +1281,7 @@ async function handleOverlayRestart() {
|
|
|
1274
1281
|
sessionId: recording.sessionId,
|
|
1275
1282
|
recordingId: recording.recordingId,
|
|
1276
1283
|
uploadUrl: recording.uploadUrl,
|
|
1284
|
+
uploadMode: recording.uploadMode ?? "buffered",
|
|
1277
1285
|
hasCamera:
|
|
1278
1286
|
recording.captureSurface === "camera" || recording.includeCamera,
|
|
1279
1287
|
startDelayMs: COUNTDOWN_SECONDS * 1000,
|