@agent-native/core 0.84.1 → 0.84.3
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 +15 -0
- package/corpus/core/docs/design/durable-agent-runs.md +34 -35
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/builder-engine.ts +81 -9
- package/corpus/core/src/agent/production-agent.ts +9 -2
- package/corpus/core/src/agent/run-loop-with-resume.ts +38 -8
- package/corpus/core/src/agent/run-manager.ts +8 -7
- package/corpus/core/src/agent/thread-data-builder.ts +6 -0
- package/corpus/core/src/cli/design-connect.ts +175 -0
- package/corpus/core/src/cli/skills.ts +5 -7
- package/corpus/core/src/client/AgentPanel.tsx +8 -0
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +4 -1
- package/corpus/core/src/client/agent-chat-adapter.ts +253 -24
- package/corpus/core/src/client/sharing/ShareButton.tsx +70 -21
- package/corpus/core/src/client/sse-event-processor.ts +46 -4
- package/corpus/core/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
- package/corpus/templates/analytics/AGENTS.md +1 -1
- package/corpus/templates/design/actions/apply-motion-edit.ts +50 -18
- package/corpus/templates/design/actions/get-motion-timeline.ts +4 -14
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +156 -12
- package/corpus/templates/design/app/components/design/MotionDock.tsx +2 -3
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +217 -75
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +39 -39
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +558 -37
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +75 -3
- package/corpus/templates/design/app/components/design/types.ts +13 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +17 -0
- package/corpus/templates/design/app/i18n-data.ts +245 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +890 -252
- package/corpus/templates/design/changelog/2026-06-30-agent-chat-now-reports-saved-design-generations-clearly-when.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-apply-styles-now-appears-only-for-localhost-visual-edit-scre.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-copying-or-dragging-screen-elements-onto-the-infinite-canvas.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-questions-now-use-tighter-editor-typography-and-contr.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-element-drags-can-be-cancelled-with-escape-before-they-commi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-pending-visual-style-edits-now-warn-before-you-leave-the-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-share-options-now-make-export-and-agent-handoff-easier-to-no.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-visual-style-drags-stay-live-while-pending-edits-can-be-appl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-design-chat-no-longer-shows-a-redundant-context-tab-above-the-composer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-motion-track-creation-no-longer-fails-in-local-editors.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-share-general-access-menu-stays-open-when-choosing-organization.md +6 -0
- package/corpus/templates/design/server/plugins/db.ts +10 -0
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +61 -10
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +9 -2
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-loop-with-resume.d.ts +2 -2
- package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
- package/dist/agent/run-loop-with-resume.js +31 -8
- package/dist/agent/run-loop-with-resume.js.map +1 -1
- package/dist/agent/run-manager.d.ts +8 -7
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +8 -7
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts +2 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +5 -0
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/cli/design-connect.d.ts +2 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +140 -0
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +5 -7
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +4 -4
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts +3 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +2 -2
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +216 -22
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +6 -0
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +25 -11
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +18 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +34 -5
- package/dist/client/sse-event-processor.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 +2 -2
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +7 -7
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
- package/docs/design/durable-agent-runs.md +34 -35
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
|
@@ -17,6 +17,7 @@ import { formatChatErrorText, normalizeChatError } from "./error-format.js";
|
|
|
17
17
|
import {
|
|
18
18
|
AgentAutoContinueSignal,
|
|
19
19
|
type AgentActivityTrailEntry,
|
|
20
|
+
type AgentAutoContinueErrorInfo,
|
|
20
21
|
type ContentPart,
|
|
21
22
|
readSSEStream,
|
|
22
23
|
settleInterruptedToolCalls,
|
|
@@ -696,6 +697,41 @@ function hasContinuationProgress(content: ContentPart[]): boolean {
|
|
|
696
697
|
);
|
|
697
698
|
}
|
|
698
699
|
|
|
700
|
+
function lastCompletedSideEffectTool(
|
|
701
|
+
content: ContentPart[],
|
|
702
|
+
): Extract<ContentPart, { type: "tool-call" }> | undefined {
|
|
703
|
+
for (let i = content.length - 1; i >= 0; i--) {
|
|
704
|
+
const part = content[i];
|
|
705
|
+
if (
|
|
706
|
+
part.type === "tool-call" &&
|
|
707
|
+
part.activity !== true &&
|
|
708
|
+
part.result !== undefined &&
|
|
709
|
+
part.isError !== true &&
|
|
710
|
+
part.completedSideEffect === true
|
|
711
|
+
) {
|
|
712
|
+
return part;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return undefined;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function humanizeActionName(toolName: string): string {
|
|
719
|
+
return toolName
|
|
720
|
+
.replace(/^agent:/, "")
|
|
721
|
+
.replace(/[-_]+/g, " ")
|
|
722
|
+
.trim();
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function completedToolTimeoutMessage(toolName: string): string {
|
|
726
|
+
if (toolName === "generate-design" || toolName === "update-design") {
|
|
727
|
+
return "The design was saved, but the assistant timed out before sending its final note. You can open the generated design from the completed tool card above.";
|
|
728
|
+
}
|
|
729
|
+
if (toolName === "present-design-variants") {
|
|
730
|
+
return "The design variants were saved, but the assistant timed out before sending its final note. You can review the completed variants from the tool card above.";
|
|
731
|
+
}
|
|
732
|
+
return `The ${humanizeActionName(toolName)} action completed, but the assistant timed out before sending its final response. The saved result is in the completed tool card above.`;
|
|
733
|
+
}
|
|
734
|
+
|
|
699
735
|
/**
|
|
700
736
|
* Signature of the *unique* sentence-like segments in a continuation's newly
|
|
701
737
|
* streamed text, used to detect a degenerate repetition loop. A stuck model
|
|
@@ -784,6 +820,8 @@ function toolContinuationKey(
|
|
|
784
820
|
stableJson(part.args),
|
|
785
821
|
part.result === undefined ? "pending" : "done",
|
|
786
822
|
part.result ?? "",
|
|
823
|
+
part.isError === true ? "error" : "",
|
|
824
|
+
part.completedSideEffect === true ? "side-effect" : "",
|
|
787
825
|
part.activity === true ? "activity" : "tool",
|
|
788
826
|
part.mcpApp ? "mcp-app" : "",
|
|
789
827
|
].join("\u0000");
|
|
@@ -850,6 +888,8 @@ function incrementalActionGuidance(tool: string): string | undefined {
|
|
|
850
888
|
case "generate-design":
|
|
851
889
|
case "update-design":
|
|
852
890
|
return "persist a minimal first version (fewer files) with `generate-design`, then refine individual files with `edit-design` search/replace instead of resending everything";
|
|
891
|
+
case "present-design-variants":
|
|
892
|
+
return "save compact but complete variant screens with `present-design-variants` first, keeping each HTML direction focused enough to finish, then refine the chosen direction with `generate-design` or `edit-design`";
|
|
853
893
|
case "create-visual-plan":
|
|
854
894
|
case "create-ui-plan":
|
|
855
895
|
case "create-plan-design":
|
|
@@ -1348,11 +1388,13 @@ export function createAgentChatAdapter(
|
|
|
1348
1388
|
// its own budget rather than inheriting the prior payload's.
|
|
1349
1389
|
let lastInFlightToolSignature: string | undefined;
|
|
1350
1390
|
let repeatedInFlightToolCount = 0;
|
|
1391
|
+
let recoveryGaveUpOnInFlightTool = false;
|
|
1351
1392
|
const MAX_REPEATED_INFLIGHT_TOOL_STALLS = 3;
|
|
1352
1393
|
const continuationHistoryFragments: string[] = [];
|
|
1353
1394
|
const structuredContinuationFragments: AgentChatStructuredMessage[] = [];
|
|
1354
1395
|
let visibleContinuationPrefix: ContentPart[] = [];
|
|
1355
1396
|
let lastAutoContinueReason: string | null = null;
|
|
1397
|
+
let lastRecoverableRunError: AgentAutoContinueErrorInfo | null = null;
|
|
1356
1398
|
const attemptedRunIds: string[] = [];
|
|
1357
1399
|
let authRecoveryAttempted = false;
|
|
1358
1400
|
let continuationToolCallCounter = 0;
|
|
@@ -1364,6 +1406,12 @@ export function createAgentChatAdapter(
|
|
|
1364
1406
|
lastAutoContinueReason
|
|
1365
1407
|
? `last_auto_continue_reason: ${lastAutoContinueReason}`
|
|
1366
1408
|
: "",
|
|
1409
|
+
lastRecoverableRunError?.errorCode
|
|
1410
|
+
? `last_recoverable_error_code: ${lastRecoverableRunError.errorCode}`
|
|
1411
|
+
: "",
|
|
1412
|
+
lastRecoverableRunError?.message
|
|
1413
|
+
? `last_recoverable_error: ${lastRecoverableRunError.message}`
|
|
1414
|
+
: "",
|
|
1367
1415
|
`stale_run_continuations: ${staleRunContinuationAttempts}`,
|
|
1368
1416
|
`stalled_transient_continuations: ${stalledTransientContinuationAttempts}`,
|
|
1369
1417
|
`empty_transient_continuations: ${emptyTransientContinuationAttempts}`,
|
|
@@ -1382,6 +1430,9 @@ export function createAgentChatAdapter(
|
|
|
1382
1430
|
};
|
|
1383
1431
|
|
|
1384
1432
|
const exhaustedRecoveryMessage = (reason?: string): string => {
|
|
1433
|
+
if (recoveryGaveUpOnInFlightTool) {
|
|
1434
|
+
return "The agent got stuck waiting for the same tool to finish, so I stopped the automatic retries. The tool did not report a completed result.";
|
|
1435
|
+
}
|
|
1385
1436
|
if (recoveryGaveUpOnRepetition) {
|
|
1386
1437
|
return "The agent got stuck repeating the same response without finishing, so I stopped the automatic retries. This often happens when it tries to re-type a large pasted file into one action — starting a new chat, or asking for a smaller first step, usually gets it unstuck.";
|
|
1387
1438
|
}
|
|
@@ -1666,6 +1717,83 @@ export function createAgentChatAdapter(
|
|
|
1666
1717
|
return false;
|
|
1667
1718
|
};
|
|
1668
1719
|
|
|
1720
|
+
const reconnectBackgroundContinuationForRunTimeout =
|
|
1721
|
+
async function* (): AsyncGenerator<
|
|
1722
|
+
ChatModelRunResult,
|
|
1723
|
+
boolean,
|
|
1724
|
+
unknown
|
|
1725
|
+
> {
|
|
1726
|
+
if (!threadId || !runId) return false;
|
|
1727
|
+
const interruptedRunId = runId;
|
|
1728
|
+
const interruptedLastSeq = lastSeq;
|
|
1729
|
+
let lastActiveRunError: unknown = null;
|
|
1730
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
1731
|
+
if (attempt > 0) {
|
|
1732
|
+
await delay(500, abortSignal);
|
|
1733
|
+
}
|
|
1734
|
+
if (abortSignal.aborted) return true;
|
|
1735
|
+
try {
|
|
1736
|
+
const activeRes = await fetch(
|
|
1737
|
+
`${apiUrl}/runs/active?threadId=${encodeURIComponent(threadId)}`,
|
|
1738
|
+
{ signal: abortSignal },
|
|
1739
|
+
);
|
|
1740
|
+
if (!activeRes.ok) {
|
|
1741
|
+
if (activeRes.status === 404) return false;
|
|
1742
|
+
lastActiveRunError = new Error(
|
|
1743
|
+
`Active run lookup failed: ${activeRes.status}`,
|
|
1744
|
+
);
|
|
1745
|
+
continue;
|
|
1746
|
+
}
|
|
1747
|
+
const active = await activeRes.json();
|
|
1748
|
+
if (!active?.active || !active.runId) return false;
|
|
1749
|
+
const activeRunId = String(active.runId);
|
|
1750
|
+
const dispatchMode =
|
|
1751
|
+
typeof active.dispatchMode === "string"
|
|
1752
|
+
? active.dispatchMode
|
|
1753
|
+
: "";
|
|
1754
|
+
if (activeRunId === interruptedRunId) {
|
|
1755
|
+
if (dispatchMode.startsWith("background")) continue;
|
|
1756
|
+
return false;
|
|
1757
|
+
}
|
|
1758
|
+
const activeTurnId =
|
|
1759
|
+
typeof active.turnId === "string" ? active.turnId : "";
|
|
1760
|
+
const activeStatus =
|
|
1761
|
+
typeof active.status === "string" ? active.status : "";
|
|
1762
|
+
if (!dispatchMode.startsWith("background")) return false;
|
|
1763
|
+
if (activeTurnId && activeTurnId !== turnId) return false;
|
|
1764
|
+
if (activeStatus !== "running" && activeStatus !== "starting") {
|
|
1765
|
+
return false;
|
|
1766
|
+
}
|
|
1767
|
+
runId = activeRunId;
|
|
1768
|
+
if (!attemptedRunIds.includes(activeRunId)) {
|
|
1769
|
+
attemptedRunIds.push(activeRunId);
|
|
1770
|
+
}
|
|
1771
|
+
lastSeq = -1;
|
|
1772
|
+
setActiveRun({ threadId, runId: activeRunId, lastSeq: -1 });
|
|
1773
|
+
const reconnected = yield* reconnectCurrentRun();
|
|
1774
|
+
if (reconnected) return true;
|
|
1775
|
+
} catch (activeErr: unknown) {
|
|
1776
|
+
if (
|
|
1777
|
+
activeErr instanceof Error &&
|
|
1778
|
+
activeErr.name === "AbortError"
|
|
1779
|
+
) {
|
|
1780
|
+
clearActiveRun();
|
|
1781
|
+
return true;
|
|
1782
|
+
}
|
|
1783
|
+
lastActiveRunError = activeErr;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
if (lastActiveRunError) {
|
|
1787
|
+
captureChatClientError(
|
|
1788
|
+
lastActiveRunError,
|
|
1789
|
+
"reconnect-background-continuation-failed",
|
|
1790
|
+
);
|
|
1791
|
+
}
|
|
1792
|
+
runId = interruptedRunId;
|
|
1793
|
+
lastSeq = interruptedLastSeq;
|
|
1794
|
+
return false;
|
|
1795
|
+
};
|
|
1796
|
+
|
|
1669
1797
|
const visibleContentForContinuation = (): ContentPart[] => {
|
|
1670
1798
|
return contentAfterContinuationPrefix(
|
|
1671
1799
|
content,
|
|
@@ -1675,8 +1803,15 @@ export function createAgentChatAdapter(
|
|
|
1675
1803
|
|
|
1676
1804
|
const prepareAutoContinuation = (
|
|
1677
1805
|
signal: AgentAutoContinueSignal,
|
|
1678
|
-
): {
|
|
1806
|
+
): {
|
|
1807
|
+
ok: boolean;
|
|
1808
|
+
resetVisibleContent: boolean;
|
|
1809
|
+
completedToolName?: string;
|
|
1810
|
+
} => {
|
|
1679
1811
|
lastAutoContinueReason = signal.reason;
|
|
1812
|
+
if (signal.errorInfo) {
|
|
1813
|
+
lastRecoverableRunError = signal.errorInfo;
|
|
1814
|
+
}
|
|
1680
1815
|
const isTransient = signal.reason !== "loop_limit";
|
|
1681
1816
|
const visibleContent = visibleContentForContinuation();
|
|
1682
1817
|
const currentPartialHistory =
|
|
@@ -1686,14 +1821,13 @@ export function createAgentChatAdapter(
|
|
|
1686
1821
|
// whitespace-only output cannot keep the run alive indefinitely.
|
|
1687
1822
|
const madeContentProgress = hasContinuationProgress(visibleContent);
|
|
1688
1823
|
// An action was streamed but has not returned yet (a tool_start with
|
|
1689
|
-
// no tool_done)
|
|
1690
|
-
//
|
|
1691
|
-
//
|
|
1692
|
-
//
|
|
1693
|
-
//
|
|
1694
|
-
const hasInFlightTool =
|
|
1695
|
-
|
|
1696
|
-
Boolean(lastActivityTool(signal.activityTrail));
|
|
1824
|
+
// no tool_done). This is durable enough to survive continuation: the
|
|
1825
|
+
// server already emitted a real tool call. A tool-scoped activity
|
|
1826
|
+
// card ("Preparing generate-design") is useful UI, but it happens
|
|
1827
|
+
// before tool_start; treating it as progress caused silent retry
|
|
1828
|
+
// loops when the LLM timed out while assembling a large tool input.
|
|
1829
|
+
const hasInFlightTool = hasInFlightToolCall(visibleContent);
|
|
1830
|
+
const completedSideEffectTool = lastCompletedSideEffectTool(content);
|
|
1697
1831
|
// Either real output or an actively-running tool counts as progress
|
|
1698
1832
|
// for the stalled/empty caps.
|
|
1699
1833
|
const madeProgress = madeContentProgress || hasInFlightTool;
|
|
@@ -1708,11 +1842,11 @@ export function createAgentChatAdapter(
|
|
|
1708
1842
|
// MAX_REPEATED_INFLIGHT_TOOL_STALLS consecutive stream_ended events,
|
|
1709
1843
|
// bail with a clear message.
|
|
1710
1844
|
//
|
|
1711
|
-
//
|
|
1712
|
-
//
|
|
1713
|
-
//
|
|
1714
|
-
//
|
|
1715
|
-
//
|
|
1845
|
+
// Count broken streams (connection drop / reconnect failed) and
|
|
1846
|
+
// no_progress stalls (the client aborts a stream that stayed open but
|
|
1847
|
+
// stopped producing events). Do NOT count run_timeout: with an
|
|
1848
|
+
// in-flight tool that means the server is still actively executing a
|
|
1849
|
+
// slow action and reconnection may recover the result.
|
|
1716
1850
|
const currentInFlightToolPart = visibleContent.find(
|
|
1717
1851
|
(p): p is Extract<ContentPart, { type: "tool-call" }> =>
|
|
1718
1852
|
p.type === "tool-call" &&
|
|
@@ -1723,8 +1857,9 @@ export function createAgentChatAdapter(
|
|
|
1723
1857
|
const currentInFlightToolSignature = currentInFlightToolPart
|
|
1724
1858
|
? inFlightToolInputSignature(currentInFlightToolPart)
|
|
1725
1859
|
: undefined;
|
|
1726
|
-
const
|
|
1727
|
-
|
|
1860
|
+
const isBrokenInFlightTool =
|
|
1861
|
+
signal.reason === "stream_ended" || signal.reason === "no_progress";
|
|
1862
|
+
if (currentInFlightToolName && isBrokenInFlightTool) {
|
|
1728
1863
|
if (
|
|
1729
1864
|
currentInFlightToolName === lastInFlightToolName &&
|
|
1730
1865
|
currentInFlightToolSignature === lastInFlightToolSignature
|
|
@@ -1763,13 +1898,31 @@ export function createAgentChatAdapter(
|
|
|
1763
1898
|
emptyTransientContinuationAttempts = 0;
|
|
1764
1899
|
} else {
|
|
1765
1900
|
totalTransientContinuationAttempts += 1;
|
|
1901
|
+
// If a mutating action already completed, do not turn a missing
|
|
1902
|
+
// closing sentence into a scary connection failure. Give the model
|
|
1903
|
+
// one continuation opportunity (the completed tool itself counts
|
|
1904
|
+
// as progress on the first timeout); if the follow-up produces no
|
|
1905
|
+
// new content, stop locally with a clear "saved, final note timed
|
|
1906
|
+
// out" message.
|
|
1907
|
+
if (
|
|
1908
|
+
completedSideEffectTool &&
|
|
1909
|
+
!hasInFlightToolCall(content) &&
|
|
1910
|
+
!madeContentProgress &&
|
|
1911
|
+
!hasInFlightTool
|
|
1912
|
+
) {
|
|
1913
|
+
return {
|
|
1914
|
+
ok: false,
|
|
1915
|
+
resetVisibleContent: false,
|
|
1916
|
+
completedToolName: completedSideEffectTool.toolName,
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1766
1919
|
// Bail when the same write tool is stuck in-flight across too many
|
|
1767
1920
|
// consecutive continuations. Checked before the text-repeat guard
|
|
1768
1921
|
// because hasInFlightTool=true would mask the repeat as progress.
|
|
1769
1922
|
if (
|
|
1770
1923
|
repeatedInFlightToolCount >= MAX_REPEATED_INFLIGHT_TOOL_STALLS
|
|
1771
1924
|
) {
|
|
1772
|
-
|
|
1925
|
+
recoveryGaveUpOnInFlightTool = true;
|
|
1773
1926
|
return { ok: false, resetVisibleContent: false };
|
|
1774
1927
|
}
|
|
1775
1928
|
// Bail fast on a non-advancing repetition loop, well before the
|
|
@@ -2166,6 +2319,11 @@ export function createAgentChatAdapter(
|
|
|
2166
2319
|
if (err.reason === "no_progress") {
|
|
2167
2320
|
await abortCurrentRun();
|
|
2168
2321
|
}
|
|
2322
|
+
if (err.reason === "run_timeout" && !err.errorInfo) {
|
|
2323
|
+
const reconnected =
|
|
2324
|
+
yield* reconnectBackgroundContinuationForRunTimeout();
|
|
2325
|
+
if (reconnected) return;
|
|
2326
|
+
}
|
|
2169
2327
|
if (err.reason === "stream_ended") {
|
|
2170
2328
|
const reconnected = yield* reconnectCurrentRun();
|
|
2171
2329
|
if (reconnected) return;
|
|
@@ -2174,15 +2332,53 @@ export function createAgentChatAdapter(
|
|
|
2174
2332
|
}
|
|
2175
2333
|
const continuation = prepareAutoContinuation(err);
|
|
2176
2334
|
if (!continuation.ok) {
|
|
2177
|
-
|
|
2335
|
+
if (continuation.completedToolName) {
|
|
2336
|
+
const message = completedToolTimeoutMessage(
|
|
2337
|
+
continuation.completedToolName,
|
|
2338
|
+
);
|
|
2339
|
+
content.push({ type: "text", text: message });
|
|
2340
|
+
yield {
|
|
2341
|
+
content: [...content],
|
|
2342
|
+
status: {
|
|
2343
|
+
type: "complete" as const,
|
|
2344
|
+
reason: "stop" as const,
|
|
2345
|
+
},
|
|
2346
|
+
metadata: {
|
|
2347
|
+
custom: {
|
|
2348
|
+
...(runId ? { runId } : {}),
|
|
2349
|
+
runWarning: {
|
|
2350
|
+
message,
|
|
2351
|
+
errorCode: "final_response_timeout_after_tool",
|
|
2352
|
+
recoverable: true,
|
|
2353
|
+
},
|
|
2354
|
+
},
|
|
2355
|
+
},
|
|
2356
|
+
};
|
|
2357
|
+
clearActiveRun();
|
|
2358
|
+
return;
|
|
2359
|
+
}
|
|
2360
|
+
const preservedError =
|
|
2361
|
+
err.errorInfo ?? lastRecoverableRunError ?? null;
|
|
2362
|
+
const message =
|
|
2363
|
+
preservedError?.message ??
|
|
2364
|
+
exhaustedRecoveryMessage(err.reason);
|
|
2365
|
+
const details = [
|
|
2366
|
+
preservedError?.details,
|
|
2367
|
+
connectionRecoveryDetails(),
|
|
2368
|
+
]
|
|
2369
|
+
.filter(Boolean)
|
|
2370
|
+
.join("\n\n");
|
|
2371
|
+
const errorCode =
|
|
2372
|
+
preservedError?.errorCode ?? "connection_error";
|
|
2178
2373
|
captureChatClientError(err, "auto-continuation-exhausted", {
|
|
2179
2374
|
autoContinueReason: err.reason,
|
|
2375
|
+
...(errorCode ? { errorCode } : {}),
|
|
2180
2376
|
});
|
|
2181
2377
|
const runError = {
|
|
2182
2378
|
message,
|
|
2183
|
-
details:
|
|
2184
|
-
errorCode
|
|
2185
|
-
recoverable: true,
|
|
2379
|
+
...(details ? { details } : {}),
|
|
2380
|
+
errorCode,
|
|
2381
|
+
recoverable: preservedError?.recoverable ?? true,
|
|
2186
2382
|
...(runId ? { runId } : {}),
|
|
2187
2383
|
};
|
|
2188
2384
|
if (typeof window !== "undefined") {
|
|
@@ -2192,10 +2388,16 @@ export function createAgentChatAdapter(
|
|
|
2192
2388
|
}),
|
|
2193
2389
|
);
|
|
2194
2390
|
}
|
|
2195
|
-
settleInterruptedToolCalls(content
|
|
2391
|
+
settleInterruptedToolCalls(content, undefined, {
|
|
2392
|
+
includeActivity: true,
|
|
2393
|
+
});
|
|
2196
2394
|
content.push({
|
|
2197
2395
|
type: "text",
|
|
2198
|
-
text:
|
|
2396
|
+
text: formatChatErrorText(
|
|
2397
|
+
message,
|
|
2398
|
+
preservedError?.upgradeUrl,
|
|
2399
|
+
errorCode,
|
|
2400
|
+
),
|
|
2199
2401
|
});
|
|
2200
2402
|
yield {
|
|
2201
2403
|
content: [...content],
|
|
@@ -2327,6 +2529,31 @@ export function createAgentChatAdapter(
|
|
|
2327
2529
|
new AgentAutoContinueSignal({ reason: "stream_ended" }),
|
|
2328
2530
|
);
|
|
2329
2531
|
if (!continuation.ok) {
|
|
2532
|
+
if (continuation.completedToolName) {
|
|
2533
|
+
const message = completedToolTimeoutMessage(
|
|
2534
|
+
continuation.completedToolName,
|
|
2535
|
+
);
|
|
2536
|
+
content.push({ type: "text", text: message });
|
|
2537
|
+
yield {
|
|
2538
|
+
content: [...content],
|
|
2539
|
+
status: {
|
|
2540
|
+
type: "complete" as const,
|
|
2541
|
+
reason: "stop" as const,
|
|
2542
|
+
},
|
|
2543
|
+
metadata: {
|
|
2544
|
+
custom: {
|
|
2545
|
+
...(runId ? { runId } : {}),
|
|
2546
|
+
runWarning: {
|
|
2547
|
+
message,
|
|
2548
|
+
errorCode: "final_response_timeout_after_tool",
|
|
2549
|
+
recoverable: true,
|
|
2550
|
+
},
|
|
2551
|
+
},
|
|
2552
|
+
},
|
|
2553
|
+
};
|
|
2554
|
+
clearActiveRun();
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2330
2557
|
const message = exhaustedRecoveryMessage("stream_ended");
|
|
2331
2558
|
captureChatClientError(err, "recovery-exhausted");
|
|
2332
2559
|
const runError = {
|
|
@@ -2343,7 +2570,9 @@ export function createAgentChatAdapter(
|
|
|
2343
2570
|
}),
|
|
2344
2571
|
);
|
|
2345
2572
|
}
|
|
2346
|
-
settleInterruptedToolCalls(content
|
|
2573
|
+
settleInterruptedToolCalls(content, undefined, {
|
|
2574
|
+
includeActivity: true,
|
|
2575
|
+
});
|
|
2347
2576
|
content.push({
|
|
2348
2577
|
type: "text",
|
|
2349
2578
|
text: `Something went wrong: ${message}`,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { useQueryClient } from "@tanstack/react-query";
|
|
16
16
|
import {
|
|
17
17
|
useCallback,
|
|
18
|
+
type ComponentPropsWithoutRef,
|
|
18
19
|
useEffect,
|
|
19
20
|
useId,
|
|
20
21
|
useMemo,
|
|
@@ -69,6 +70,10 @@ export interface ShareButtonProps {
|
|
|
69
70
|
/** Where to render share links in the popover. Defaults to the bottom,
|
|
70
71
|
* matching the historical Google-Docs-style share dialog. */
|
|
71
72
|
shareUrlPlacement?: "top" | "bottom";
|
|
73
|
+
/** Whether to render copyable share URL fields. Defaults to true. */
|
|
74
|
+
showShareLinks?: boolean;
|
|
75
|
+
/** Whether to render the bottom Done button. Defaults to true. */
|
|
76
|
+
showDoneButton?: boolean;
|
|
72
77
|
/** Optional placeholder shown in the share-URL slot when `shareUrl` is
|
|
73
78
|
* undefined. Use this to explain *why* there's no link yet (e.g. "Publish
|
|
74
79
|
* this form to get a public response link") instead of leaving the slot
|
|
@@ -99,6 +104,8 @@ export interface ShareButtonProps {
|
|
|
99
104
|
generalAccessLabel?: ReactNode;
|
|
100
105
|
/** Optional note rendered between general access and the copyable link. */
|
|
101
106
|
accessNote?: ReactNode;
|
|
107
|
+
/** Optional host-rendered footer for compact app-specific share actions. */
|
|
108
|
+
shareFooterContent?: ReactNode;
|
|
102
109
|
/** Optional Notion-style organization access control. When present, the
|
|
103
110
|
* share panel exposes a "Hide in search" switch under Advanced for org
|
|
104
111
|
* visibility. */
|
|
@@ -171,6 +178,8 @@ const BUTTON_GHOST_ICON = cn(
|
|
|
171
178
|
);
|
|
172
179
|
const SHARE_POPOVER_SURFACE =
|
|
173
180
|
"border border-border bg-popover text-popover-foreground";
|
|
181
|
+
const SHARE_NESTED_OVERLAY_ATTR = "data-agent-native-share-overlay";
|
|
182
|
+
const SHARE_NESTED_OVERLAY_Z = "z-[100020]";
|
|
174
183
|
const MEMBER_SUGGESTION_LIMIT = 25;
|
|
175
184
|
const MEMBER_SEARCH_DEBOUNCE_MS = 140;
|
|
176
185
|
|
|
@@ -219,6 +228,28 @@ const ROLE_OPTIONS: Array<{ value: Role; label: string; description: string }> =
|
|
|
219
228
|
},
|
|
220
229
|
];
|
|
221
230
|
|
|
231
|
+
type SharePopoverInteractOutsideEvent = Parameters<
|
|
232
|
+
NonNullable<
|
|
233
|
+
ComponentPropsWithoutRef<typeof PopoverContent>["onInteractOutside"]
|
|
234
|
+
>
|
|
235
|
+
>[0];
|
|
236
|
+
|
|
237
|
+
function isShareNestedOverlayTarget(target: EventTarget | null): boolean {
|
|
238
|
+
return (
|
|
239
|
+
target instanceof Element &&
|
|
240
|
+
target.closest(`[${SHARE_NESTED_OVERLAY_ATTR}]`) !== null
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function handleSharePopoverInteractOutside(
|
|
245
|
+
event: SharePopoverInteractOutsideEvent,
|
|
246
|
+
) {
|
|
247
|
+
const originalTarget = event.detail.originalEvent.target;
|
|
248
|
+
if (isShareNestedOverlayTarget(originalTarget)) {
|
|
249
|
+
event.preventDefault();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
222
253
|
/**
|
|
223
254
|
* Framework share control. Renders a shadcn-outline-styled trigger that
|
|
224
255
|
* opens a Google-Docs-style popover anchored beneath it. Uses Tailwind
|
|
@@ -344,12 +375,14 @@ export function ShareButton(props: ShareButtonProps) {
|
|
|
344
375
|
<PopoverContent
|
|
345
376
|
align="end"
|
|
346
377
|
sideOffset={6}
|
|
378
|
+
data-agent-native-share-overlay=""
|
|
347
379
|
className={cn(
|
|
348
380
|
"z-[2000] w-[min(460px,92vw)] rounded-lg p-4 shadow-lg",
|
|
349
381
|
SHARE_POPOVER_SURFACE,
|
|
350
382
|
props.popoverClassName,
|
|
351
383
|
)}
|
|
352
384
|
onOpenAutoFocus={(e) => e.preventDefault()}
|
|
385
|
+
onInteractOutside={handleSharePopoverInteractOutside}
|
|
353
386
|
>
|
|
354
387
|
<SharePanel
|
|
355
388
|
{...props}
|
|
@@ -622,9 +655,11 @@ function SharePanel(
|
|
|
622
655
|
</>
|
|
623
656
|
);
|
|
624
657
|
const showShareLinks =
|
|
625
|
-
|
|
626
|
-
Boolean(props.
|
|
627
|
-
|
|
658
|
+
(props.showShareLinks ?? true) &&
|
|
659
|
+
(Boolean(props.shareUrl) ||
|
|
660
|
+
Boolean(props.shareUrlPlaceholder) ||
|
|
661
|
+
Boolean(props.secondaryShareUrl));
|
|
662
|
+
const showDoneButton = props.showDoneButton ?? true;
|
|
628
663
|
const shareUrlPlacement = props.shareUrlPlacement ?? "bottom";
|
|
629
664
|
const extraTabs = props.shareTabs?.tabs ?? [];
|
|
630
665
|
const hasTabs = extraTabs.length > 0;
|
|
@@ -821,11 +856,13 @@ function SharePanel(
|
|
|
821
856
|
<div className="mb-4 h-7 rounded-md bg-muted animate-pulse" />
|
|
822
857
|
<div className="mb-2 text-sm font-semibold">{generalAccessLabel}</div>
|
|
823
858
|
<div className="mb-4 h-9 rounded-md bg-muted animate-pulse" />
|
|
824
|
-
|
|
825
|
-
<
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
859
|
+
{showDoneButton ? (
|
|
860
|
+
<div className="mt-2 flex justify-end">
|
|
861
|
+
<button type="button" onClick={onClose} className={BUTTON_PRIMARY_SM}>
|
|
862
|
+
Done
|
|
863
|
+
</button>
|
|
864
|
+
</div>
|
|
865
|
+
) : null}
|
|
829
866
|
</div>
|
|
830
867
|
) : (
|
|
831
868
|
<div>
|
|
@@ -990,15 +1027,19 @@ function SharePanel(
|
|
|
990
1027
|
|
|
991
1028
|
{showShareLinks && shareUrlPlacement === "bottom" ? shareLinks : null}
|
|
992
1029
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1030
|
+
{props.shareFooterContent}
|
|
1031
|
+
|
|
1032
|
+
{showDoneButton ? (
|
|
1033
|
+
<div className="mt-2 flex justify-end">
|
|
1034
|
+
<button
|
|
1035
|
+
type="button"
|
|
1036
|
+
onClick={handleDone}
|
|
1037
|
+
className={BUTTON_PRIMARY_SM}
|
|
1038
|
+
>
|
|
1039
|
+
Done
|
|
1040
|
+
</button>
|
|
1041
|
+
</div>
|
|
1042
|
+
) : null}
|
|
1002
1043
|
</div>
|
|
1003
1044
|
);
|
|
1004
1045
|
|
|
@@ -1076,8 +1117,13 @@ function AdvancedAccessPopover({
|
|
|
1076
1117
|
<PopoverContent
|
|
1077
1118
|
align="start"
|
|
1078
1119
|
sideOffset={6}
|
|
1120
|
+
data-agent-native-share-overlay=""
|
|
1079
1121
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
|
1080
|
-
className={cn(
|
|
1122
|
+
className={cn(
|
|
1123
|
+
SHARE_NESTED_OVERLAY_Z,
|
|
1124
|
+
"w-72 p-3 shadow-lg",
|
|
1125
|
+
SHARE_POPOVER_SURFACE,
|
|
1126
|
+
)}
|
|
1081
1127
|
>
|
|
1082
1128
|
<div className="space-y-3">
|
|
1083
1129
|
<div>
|
|
@@ -1291,9 +1337,11 @@ function MemberAutocomplete({
|
|
|
1291
1337
|
<PopoverContent
|
|
1292
1338
|
align="start"
|
|
1293
1339
|
sideOffset={4}
|
|
1340
|
+
data-agent-native-share-overlay=""
|
|
1294
1341
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
|
1295
1342
|
className={cn(
|
|
1296
|
-
|
|
1343
|
+
SHARE_NESTED_OVERLAY_Z,
|
|
1344
|
+
"w-[var(--radix-popper-anchor-width)] min-w-[18rem] rounded-md p-1 shadow-lg",
|
|
1297
1345
|
SHARE_POPOVER_SURFACE,
|
|
1298
1346
|
)}
|
|
1299
1347
|
>
|
|
@@ -1441,8 +1489,7 @@ function CopyLinkField({
|
|
|
1441
1489
|
// Radix Select wrappers styled like shadcn Select (no native <select> anywhere)
|
|
1442
1490
|
// ---------------------------------------------------------------------------
|
|
1443
1491
|
|
|
1444
|
-
const selectContentClass =
|
|
1445
|
-
"z-[2100] min-w-[12rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0";
|
|
1492
|
+
const selectContentClass = `${SHARE_NESTED_OVERLAY_Z} min-w-[12rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0`;
|
|
1446
1493
|
const selectItemClass =
|
|
1447
1494
|
"relative flex w-full cursor-pointer select-none items-start gap-2 rounded-sm py-2 ps-8 pe-3 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50";
|
|
1448
1495
|
|
|
@@ -1517,6 +1564,7 @@ function RoleSelect(props: {
|
|
|
1517
1564
|
</Select.Trigger>
|
|
1518
1565
|
<Select.Portal>
|
|
1519
1566
|
<Select.Content
|
|
1567
|
+
data-agent-native-share-overlay=""
|
|
1520
1568
|
className={selectContentClass}
|
|
1521
1569
|
position="popper"
|
|
1522
1570
|
sideOffset={4}
|
|
@@ -1563,6 +1611,7 @@ function VisibilitySelect(props: {
|
|
|
1563
1611
|
</Select.Trigger>
|
|
1564
1612
|
<Select.Portal>
|
|
1565
1613
|
<Select.Content
|
|
1614
|
+
data-agent-native-share-overlay=""
|
|
1566
1615
|
className={selectContentClass}
|
|
1567
1616
|
position="popper"
|
|
1568
1617
|
sideOffset={4}
|