@agent-native/core 0.154.5 → 0.155.0
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/.agents/skills/account-health/SKILL.md +76 -0
- package/corpus/templates/analytics/.agents/skills/bigquery/SKILL.md +3 -0
- package/corpus/templates/analytics/.agents/skills/cross-source-analysis/SKILL.md +2 -1
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +6 -5
- package/corpus/templates/analytics/AGENTS.md +3 -0
- package/corpus/templates/analytics/actions/account-deep-dive.ts +3 -2
- package/corpus/templates/analytics/actions/bigquery.ts +1 -1
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +4 -1
- package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +1 -0
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/PropertyCombobox.tsx +12 -4
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/PropertyValueCombobox.tsx +5 -2
- package/corpus/templates/analytics/app/pages/adhoc/explorer/property-label.ts +32 -0
- package/corpus/templates/analytics/changelog/2026-08-13-account-health-readouts-verify-customer-scope-and-contract-metrics.md +7 -0
- package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +43 -17
- package/corpus/templates/analytics/server/lib/analytics-query-catalog.ts +17 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +4 -0
- package/corpus/templates/calendar/AGENTS.md +2 -0
- package/corpus/templates/calendar/DEVELOPING.md +4 -4
- package/corpus/templates/calendar/actions/get-settings.ts +4 -11
- package/corpus/templates/calendar/actions/update-availability.ts +15 -4
- package/corpus/templates/calendar/actions/update-settings.ts +15 -4
- package/corpus/templates/calendar/app/components/booking/DatePicker.tsx +9 -3
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +21 -19
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +9 -6
- package/corpus/templates/calendar/app/components/calendar/FindTimePanel.tsx +25 -3
- package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +10 -3
- package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +9 -9
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -3
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +10 -4
- package/corpus/templates/calendar/app/i18n-data.ts +30 -0
- package/corpus/templates/calendar/app/lib/availability-schedule.ts +53 -0
- package/corpus/templates/calendar/app/lib/event-form-utils.ts +5 -0
- package/corpus/templates/calendar/app/pages/AvailabilitySettings.tsx +90 -28
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +135 -61
- package/corpus/templates/calendar/app/pages/BookingPage.tsx +2 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +30 -15
- package/corpus/templates/calendar/app/pages/Settings.tsx +60 -1
- package/corpus/templates/calendar/changelog/2026-08-13-find-a-time-now-closes-from-a-clear-header-action.md +6 -0
- package/corpus/templates/calendar/server/handlers/bookings.ts +2 -1
- package/corpus/templates/calendar/server/handlers/settings.ts +12 -16
- package/corpus/templates/calendar/shared/api.ts +1 -0
- package/corpus/templates/calendar/shared/availability-schedule.ts +64 -0
- package/corpus/templates/calendar/shared/calendar-week.ts +21 -0
- package/corpus/templates/calendar/shared/settings.ts +44 -0
- package/corpus/templates/clips/AGENTS.md +2 -0
- package/corpus/templates/clips/actions/add-comment.ts +5 -2
- package/corpus/templates/clips/actions/finalize-recording.ts +31 -1
- package/corpus/templates/clips/actions/get-recording-player-data.ts +9 -0
- package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +21 -1
- package/corpus/templates/clips/actions/reply-to-comment.ts +5 -2
- package/corpus/templates/clips/actions/update-comment.ts +9 -2
- package/corpus/templates/clips/app/components/library/search-bar.tsx +26 -13
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +135 -3
- package/corpus/templates/clips/app/components/player/comments-panel.tsx +5 -99
- package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +7 -3
- package/corpus/templates/clips/app/components/player/video-player.tsx +84 -24
- package/corpus/templates/clips/app/components/workspace/notifications-list.tsx +11 -3
- package/corpus/templates/clips/app/i18n/en-US.ts +12 -0
- package/corpus/templates/clips/app/lib/public-meeting.ts +11 -3
- package/corpus/templates/clips/app/lib/search-focus.ts +15 -0
- package/corpus/templates/clips/app/root.tsx +2 -1
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +94 -26
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +24 -7
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +8 -4
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +14 -7
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +8 -4
- package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +72 -26
- package/corpus/templates/clips/changelog/2026-08-13-inline-markdown-comments.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-13-private-meeting-agent-links.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +116 -19
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +13 -29
- package/corpus/templates/clips/desktop/src/lib/recording-request.ts +39 -0
- package/corpus/templates/clips/desktop/src/lib/settings-navigation.ts +11 -0
- package/corpus/templates/clips/desktop/src/lib/whisper-model-picker.ts +7 -0
- package/corpus/templates/clips/desktop/src/styles.css +117 -0
- package/corpus/templates/clips/server/db/index.ts +10 -0
- package/corpus/templates/clips/server/db/schema.ts +1 -0
- package/corpus/templates/clips/server/lib/recordings.ts +3 -1
- package/corpus/templates/clips/server/lib/seekable-media-state.ts +90 -0
- package/corpus/templates/clips/server/plugins/db.ts +5 -0
- package/corpus/templates/clips/server/routes/api/public-meeting.get.ts +37 -4
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +16 -5
- package/corpus/templates/clips/shared/clips-ai-prefs.ts +3 -0
- package/corpus/templates/clips/shared/meeting-agent-access.ts +5 -0
- package/corpus/templates/content/actions/add-comment.ts +2 -1
- package/corpus/templates/content/actions/update-comment.ts +1 -1
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +24 -15
- package/corpus/templates/design/actions/generate-design.ts +10 -7
- package/corpus/templates/design/actions/present-design-variants.ts +35 -6
- package/corpus/templates/design/actions/run-design-audit.ts +33 -0
- package/corpus/templates/design/changelog/2026-08-13-the-design-audit-now-catches-a-screen-that-renders-behind-an.md +6 -0
- package/corpus/templates/design/shared/design-review.ts +1 -0
- package/corpus/templates/design/shared/html-integrity.ts +66 -0
- package/corpus/templates/dispatch/.env.example +5 -0
- package/corpus/templates/dispatch/AGENTS.md +24 -19
- package/corpus/templates/dispatch/app/i18n-data.ts +20 -0
- package/corpus/templates/dispatch/server/lib/identity-sso.ts +21 -90
- package/corpus/templates/dispatch/server/plugins/identity-sso.ts +3 -12
- package/corpus/templates/dispatch/shared/feature-flags.ts +4 -0
- package/corpus/templates/factory/AGENTS.md +5 -1
- package/corpus/templates/factory/README.md +23 -0
- package/corpus/templates/factory/actions/view-screen.ts +17 -0
- package/corpus/templates/factory/app/components/factory/FactoryAgentsView.tsx +239 -0
- package/corpus/templates/factory/app/components/factory/FactoryCanvas.tsx +2 -0
- package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +130 -7
- package/corpus/templates/factory/app/i18n/en-US.ts +20 -0
- package/corpus/templates/factory/app/routes/factory.tsx +41 -0
- package/corpus/templates/factory/package.json +1 -0
- package/corpus/templates/factory/server/factory-graph/contracts.ts +2 -0
- package/corpus/templates/factory/server/plugins/agent-chat.ts +9 -0
- package/corpus/templates/factory/server/plugins/dispatch-db.ts +1 -0
- package/corpus/templates/factory/server/plugins/setup-dispatch.ts +3 -0
- package/corpus/templates/mail/app/lib/native-mcp-exclusions.ts +3 -0
- package/corpus/templates/mail/vite.config.ts +7 -0
- package/corpus/templates/plan/actions/reply-to-plan-comment.ts +7 -2
- package/corpus/templates/plan/actions/resolve-plan-comment.ts +2 -2
- package/corpus/templates/plan/actions/update-visual-plan.ts +1 -1
- package/corpus/templates/plan/app/pages/PlansPage.tsx +29 -20
- package/corpus/templates/slides/actions/add-slide-comment.ts +1 -1
- package/corpus/templates/slides/actions/update-slide-comment.ts +1 -1
- package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +9 -6
- package/dist/agent/production-agent.js +5 -1
- package/dist/agent/thread-data-builder.js +17 -1
- package/dist/cli/skills-content/help.js +6 -4
- package/dist/cli/skills.js +17 -2
- package/dist/cli/workspacify.d.ts +5 -0
- package/dist/cli/workspacify.js +65 -8
- package/dist/client/AgentPanel.d.ts +5 -0
- package/dist/client/AgentPanel.js +20 -4
- package/dist/client/AssistantChat.d.ts +21 -0
- package/dist/client/AssistantChat.js +66 -68
- package/dist/client/active-run-state.d.ts +14 -1
- package/dist/client/active-run-state.js +0 -0
- package/dist/client/agent-chat-adapter.js +107 -5
- package/dist/client/analytics.js +9 -2
- package/dist/client/chat/tool-call-display.js +4 -4
- package/dist/client/chat-first/apps-rail.js +9 -4
- package/dist/client/chat-first/primary-nav.d.ts +2 -1
- package/dist/client/chat-first/primary-nav.js +3 -3
- package/dist/client/chat-first/types.d.ts +5 -1
- package/dist/client/markdown/InlineMarkdown.d.ts +23 -0
- package/dist/client/markdown/InlineMarkdown.js +65 -0
- package/dist/client/markdown/index.d.ts +1 -0
- package/dist/client/markdown/index.js +1 -0
- package/dist/client/review/ReviewThreadPanel.js +2 -1
- package/dist/client/run-stream-ownership.d.ts +39 -0
- package/dist/client/run-stream-ownership.js +65 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/feature-flags/index.d.ts +1 -1
- package/dist/feature-flags/index.js +1 -1
- package/dist/feature-flags/store.d.ts +10 -0
- package/dist/feature-flags/store.js +112 -1
- package/dist/guards/no-env-credentials.js +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/integrations/adapters/slack.js +5 -1
- package/dist/localization/default-messages.d.ts +1 -0
- package/dist/localization/default-messages.js +1 -0
- package/dist/observability/routes.d.ts +6 -6
- 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/agents.js +40 -5
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/resources/metadata.d.ts +10 -0
- package/dist/resources/metadata.js +7 -3
- package/dist/review/actions/create-review-comment.js +1 -1
- package/dist/review/actions/reply-review-comment.js +1 -1
- package/dist/review/actions/resolve-review-thread.js +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/action-routes.js +32 -6
- package/dist/server/agent-discovery.js +14 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/core-routes-plugin.js +1 -1
- package/dist/server/identity-sso-migrations.d.ts +7 -0
- package/dist/server/identity-sso-migrations.js +36 -0
- package/dist/server/identity-sso-store.d.ts +3 -2
- package/dist/server/identity-sso-store.js +8 -3
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/release-migrations.js +4 -0
- package/dist/tracking/providers.js +13 -3
- package/dist/workspace-files/tool.js +1 -1
- package/package.json +3 -2
|
@@ -6,6 +6,7 @@ import { getOrCreateAnalyticsSessionId } from "./analytics-session.js";
|
|
|
6
6
|
import { captureError } from "./analytics.js";
|
|
7
7
|
import { agentNativePath } from "./api-path.js";
|
|
8
8
|
import { formatChatErrorText, normalizeChatError } from "./error-format.js";
|
|
9
|
+
import { createRunStreamToken, preemptRunStream, releaseRunStream, } from "./run-stream-ownership.js";
|
|
9
10
|
import { AgentAutoContinueSignal, INTERRUPTED_TOOL_RESULT, appendMissingFinalResponseWarning, readSSEStream, settleInterruptedToolCalls, } from "./sse-event-processor.js";
|
|
10
11
|
import { isDelegatedAgentToolCall, isToolCallInFlight, } from "./tool-display.js";
|
|
11
12
|
const TEXT_ATTACHMENT_CONTENT_TYPES = new Set([
|
|
@@ -1006,6 +1007,26 @@ function snapshotContent(content) {
|
|
|
1006
1007
|
? { ...part }
|
|
1007
1008
|
: { ...part, args: { ...part.args } });
|
|
1008
1009
|
}
|
|
1010
|
+
function hasMissingFinalResponseAfterTool(content) {
|
|
1011
|
+
const warning = appendMissingFinalResponseWarning(snapshotContent(content));
|
|
1012
|
+
return warning?.errorCode === "final_response_missing_after_tool";
|
|
1013
|
+
}
|
|
1014
|
+
function missingFinalResponseWarningFromResult(result) {
|
|
1015
|
+
const metadata = result.metadata;
|
|
1016
|
+
const custom = metadata?.custom;
|
|
1017
|
+
if (!custom || typeof custom !== "object")
|
|
1018
|
+
return null;
|
|
1019
|
+
const warning = custom.runWarning;
|
|
1020
|
+
if (!warning || typeof warning !== "object")
|
|
1021
|
+
return null;
|
|
1022
|
+
const warningRecord = warning;
|
|
1023
|
+
if (warningRecord.errorCode !== "final_response_missing_after_tool") {
|
|
1024
|
+
return null;
|
|
1025
|
+
}
|
|
1026
|
+
return {
|
|
1027
|
+
message: typeof warningRecord.message === "string" ? warningRecord.message : "",
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1009
1030
|
function stableJson(value) {
|
|
1010
1031
|
try {
|
|
1011
1032
|
return JSON.stringify(value);
|
|
@@ -1618,7 +1639,19 @@ export function createAgentChatAdapter(options) {
|
|
|
1618
1639
|
activeRun.threadId === threadId &&
|
|
1619
1640
|
activeRun.runId === runId));
|
|
1620
1641
|
};
|
|
1642
|
+
// The adapter's own stream outranks AssistantChat's reconnect fallback:
|
|
1643
|
+
// when it attaches to a run, any reconnect reader folding the same run
|
|
1644
|
+
// must stop writing UI state or both folds render at once.
|
|
1645
|
+
const streamOwnershipToken = createRunStreamToken(`adapter:${turnId}`);
|
|
1646
|
+
const takeRunStreamOwnership = () => {
|
|
1647
|
+
if (threadId && runId) {
|
|
1648
|
+
preemptRunStream(threadId, runId, streamOwnershipToken);
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1621
1651
|
const settleTerminalChatRun = () => {
|
|
1652
|
+
if (threadId && runId) {
|
|
1653
|
+
releaseRunStream(threadId, runId, streamOwnershipToken);
|
|
1654
|
+
}
|
|
1622
1655
|
if (!ownsActiveRunState())
|
|
1623
1656
|
return;
|
|
1624
1657
|
if (threadId && runId) {
|
|
@@ -1972,10 +2005,12 @@ export function createAgentChatAdapter(options) {
|
|
|
1972
2005
|
break;
|
|
1973
2006
|
}
|
|
1974
2007
|
updateCurrentRunDispatchMode(reconnectRes.headers.get("X-Dispatch-Mode"));
|
|
2008
|
+
takeRunStreamOwnership();
|
|
1975
2009
|
for await (const result of readSSEStream(reconnectRes.body, content, toolCallCounter, tabId, (seq, isProgress) => {
|
|
1976
2010
|
rememberRunSeq(seq);
|
|
1977
|
-
if (threadId)
|
|
1978
|
-
updateActiveRunSeq(seq, isProgress);
|
|
2011
|
+
if (threadId && runId) {
|
|
2012
|
+
updateActiveRunSeq(threadId, runId, seq, isProgress);
|
|
2013
|
+
}
|
|
1979
2014
|
}, runId, currentSSEOptions())) {
|
|
1980
2015
|
const nextResult = withRequestModeMetadata(result);
|
|
1981
2016
|
if (isTerminalChatModelRunResult(nextResult)) {
|
|
@@ -2311,20 +2346,45 @@ export function createAgentChatAdapter(options) {
|
|
|
2311
2346
|
}
|
|
2312
2347
|
attached = true;
|
|
2313
2348
|
updateCurrentRunDispatchMode(eventsRes.headers.get("X-Dispatch-Mode"));
|
|
2349
|
+
takeRunStreamOwnership();
|
|
2350
|
+
let missingFinalResponseResult = null;
|
|
2314
2351
|
for await (const result of readSSEStream(eventsRes.body, content, toolCallCounter, tabId, (seq, isProgress) => {
|
|
2315
2352
|
rememberRunSeq(seq);
|
|
2316
|
-
if (threadId)
|
|
2317
|
-
updateActiveRunSeq(seq, isProgress);
|
|
2353
|
+
if (threadId && runId) {
|
|
2354
|
+
updateActiveRunSeq(threadId, runId, seq, isProgress);
|
|
2355
|
+
}
|
|
2318
2356
|
}, runId, currentSSEOptions({
|
|
2319
2357
|
noProgressTimeoutMs: BACKGROUND_FOLLOW_ATTACH_WATCHDOG_MS,
|
|
2320
2358
|
actionPreparationStallTimeoutMs: BACKGROUND_FOLLOW_ATTACH_WATCHDOG_MS,
|
|
2321
2359
|
}))) {
|
|
2322
2360
|
const nextResult = withRequestModeMetadata(result);
|
|
2361
|
+
if (isDurableBackgroundDispatch() &&
|
|
2362
|
+
missingFinalResponseWarningFromResult(nextResult)) {
|
|
2363
|
+
missingFinalResponseResult = nextResult;
|
|
2364
|
+
continue;
|
|
2365
|
+
}
|
|
2323
2366
|
if (isTerminalChatModelRunResult(nextResult)) {
|
|
2324
2367
|
settleTerminalChatRun();
|
|
2325
2368
|
}
|
|
2326
2369
|
yield nextResult;
|
|
2327
2370
|
}
|
|
2371
|
+
if (missingFinalResponseResult) {
|
|
2372
|
+
const warning = missingFinalResponseWarningFromResult(missingFinalResponseResult);
|
|
2373
|
+
const lastContentPart = content.at(-1);
|
|
2374
|
+
if (warning?.message &&
|
|
2375
|
+
lastContentPart?.type === "text" &&
|
|
2376
|
+
lastContentPart.text === warning.message) {
|
|
2377
|
+
content.pop();
|
|
2378
|
+
}
|
|
2379
|
+
if (continueAfterMissingFinalResponse()) {
|
|
2380
|
+
await delay(250, abortSignal);
|
|
2381
|
+
return "client_continue";
|
|
2382
|
+
}
|
|
2383
|
+
settleTerminalChatRun();
|
|
2384
|
+
yield missingFinalResponseResult;
|
|
2385
|
+
clearActiveRun();
|
|
2386
|
+
return "completed";
|
|
2387
|
+
}
|
|
2328
2388
|
// readSSEStream returned normally: a terminal done/error was
|
|
2329
2389
|
// consumed and rendered — the turn is over.
|
|
2330
2390
|
clearActiveRun();
|
|
@@ -2614,6 +2674,11 @@ export function createAgentChatAdapter(options) {
|
|
|
2614
2674
|
continue;
|
|
2615
2675
|
}
|
|
2616
2676
|
}
|
|
2677
|
+
if (hasMissingFinalResponseAfterTool(content) &&
|
|
2678
|
+
continueAfterMissingFinalResponse()) {
|
|
2679
|
+
await delay(250, abortSignal);
|
|
2680
|
+
return "client_continue";
|
|
2681
|
+
}
|
|
2617
2682
|
yield* emitBackgroundTerminalOutcome(active);
|
|
2618
2683
|
return "completed";
|
|
2619
2684
|
}
|
|
@@ -2642,6 +2707,9 @@ export function createAgentChatAdapter(options) {
|
|
|
2642
2707
|
}
|
|
2643
2708
|
const seqBeforeAttach = lastSeq;
|
|
2644
2709
|
const attach = yield* followAttachOnce();
|
|
2710
|
+
if (attach === "client_continue") {
|
|
2711
|
+
return "client_continue";
|
|
2712
|
+
}
|
|
2645
2713
|
if (attach === "completed" || attach === "aborted") {
|
|
2646
2714
|
return "completed";
|
|
2647
2715
|
}
|
|
@@ -2964,6 +3032,17 @@ export function createAgentChatAdapter(options) {
|
|
|
2964
3032
|
nonAdvancing: !advanced,
|
|
2965
3033
|
};
|
|
2966
3034
|
};
|
|
3035
|
+
const continueAfterMissingFinalResponse = () => {
|
|
3036
|
+
const continuation = prepareAutoContinuation(new AgentAutoContinueSignal({ reason: "stream_ended" }));
|
|
3037
|
+
if (!continuation.ok)
|
|
3038
|
+
return false;
|
|
3039
|
+
if (typeof window !== "undefined") {
|
|
3040
|
+
window.dispatchEvent(new CustomEvent("agent-chat:auto-continue", {
|
|
3041
|
+
detail: { tabId },
|
|
3042
|
+
}));
|
|
3043
|
+
}
|
|
3044
|
+
return true;
|
|
3045
|
+
};
|
|
2967
3046
|
while (true) {
|
|
2968
3047
|
try {
|
|
2969
3048
|
runId = null;
|
|
@@ -3221,18 +3300,41 @@ export function createAgentChatAdapter(options) {
|
|
|
3221
3300
|
clearPendingTurnIfMatches(threadId, turnId);
|
|
3222
3301
|
setActiveRun({ threadId, runId, turnId, lastSeq: -1 });
|
|
3223
3302
|
}
|
|
3303
|
+
takeRunStreamOwnership();
|
|
3304
|
+
let missingFinalResponseResult = null;
|
|
3224
3305
|
for await (const result of readSSEStream(res.body, content, toolCallCounter, tabId, (seq, isProgress) => {
|
|
3225
3306
|
rememberRunSeq(seq);
|
|
3226
3307
|
if (runId && threadId) {
|
|
3227
|
-
updateActiveRunSeq(seq, isProgress);
|
|
3308
|
+
updateActiveRunSeq(threadId, runId, seq, isProgress);
|
|
3228
3309
|
}
|
|
3229
3310
|
}, runId, currentSSEOptions())) {
|
|
3230
3311
|
const nextResult = withRequestModeMetadata(result);
|
|
3312
|
+
if (isDurableBackgroundDispatch() &&
|
|
3313
|
+
missingFinalResponseWarningFromResult(nextResult)) {
|
|
3314
|
+
missingFinalResponseResult = nextResult;
|
|
3315
|
+
continue;
|
|
3316
|
+
}
|
|
3231
3317
|
if (isTerminalChatModelRunResult(nextResult)) {
|
|
3232
3318
|
settleTerminalChatRun();
|
|
3233
3319
|
}
|
|
3234
3320
|
yield nextResult;
|
|
3235
3321
|
}
|
|
3322
|
+
if (isDurableBackgroundDispatch() && missingFinalResponseResult) {
|
|
3323
|
+
const warning = missingFinalResponseWarningFromResult(missingFinalResponseResult);
|
|
3324
|
+
const lastContentPart = content.at(-1);
|
|
3325
|
+
if (warning?.message &&
|
|
3326
|
+
lastContentPart?.type === "text" &&
|
|
3327
|
+
lastContentPart.text === warning.message) {
|
|
3328
|
+
content.pop();
|
|
3329
|
+
}
|
|
3330
|
+
if (continueAfterMissingFinalResponse()) {
|
|
3331
|
+
continue;
|
|
3332
|
+
}
|
|
3333
|
+
settleTerminalChatRun();
|
|
3334
|
+
yield missingFinalResponseResult;
|
|
3335
|
+
clearActiveRun();
|
|
3336
|
+
return;
|
|
3337
|
+
}
|
|
3236
3338
|
// Run completed normally — clear active run state
|
|
3237
3339
|
clearActiveRun();
|
|
3238
3340
|
return;
|
package/dist/client/analytics.js
CHANGED
|
@@ -1030,6 +1030,12 @@ function exceptionEventProperties(event) {
|
|
|
1030
1030
|
...(event.extra ? { exceptionExtra: event.extra } : {}),
|
|
1031
1031
|
};
|
|
1032
1032
|
}
|
|
1033
|
+
function amplitudeEventProperties(name, properties) {
|
|
1034
|
+
if (name !== AGENT_NATIVE_EXCEPTION_EVENT_NAME)
|
|
1035
|
+
return properties;
|
|
1036
|
+
const { exceptionTags: _exceptionTags, exceptionExtra: _exceptionExtra, ...stableProperties } = properties;
|
|
1037
|
+
return stableProperties;
|
|
1038
|
+
}
|
|
1033
1039
|
/**
|
|
1034
1040
|
* Resolve browser PostHog config, or `undefined` when error capture should not
|
|
1035
1041
|
* reach PostHog. Reads the SSR-injected shell config first, then Vite env for
|
|
@@ -1557,13 +1563,14 @@ export function trackEvent(name, params) {
|
|
|
1557
1563
|
return;
|
|
1558
1564
|
ensureSentry();
|
|
1559
1565
|
const props = resolveProps(name, params);
|
|
1566
|
+
const amplitudeProps = amplitudeEventProperties(name, props);
|
|
1560
1567
|
window.gtag?.("event", name.replace(/\s+/g, "_"), props);
|
|
1561
1568
|
if (ensureAmplitude()) {
|
|
1562
|
-
_amplitudeModule?.track(name,
|
|
1569
|
+
_amplitudeModule?.track(name, amplitudeProps);
|
|
1563
1570
|
}
|
|
1564
1571
|
else if (_amplitudeApiKey) {
|
|
1565
1572
|
if (_pendingAmplitudeEvents.length < 100) {
|
|
1566
|
-
_pendingAmplitudeEvents.push([name,
|
|
1573
|
+
_pendingAmplitudeEvents.push([name, amplitudeProps]);
|
|
1567
1574
|
}
|
|
1568
1575
|
}
|
|
1569
1576
|
sendAgentNativeAnalytics(name, props);
|
|
@@ -258,19 +258,19 @@ function ApprovalAffordance({ toolName, toolCallId, approval, }) {
|
|
|
258
258
|
if (resolution === "denied") {
|
|
259
259
|
return (_jsxs("div", { className: "mt-1.5 text-xs text-muted-foreground", children: ["Denied. ", toolName, " did not run."] }));
|
|
260
260
|
}
|
|
261
|
-
return (_jsxs("div", { className: "mt-1.5 flex items-center gap-2 rounded-md border border-border bg-muted/40 px-2.5 py-1.5", children: [_jsx(IconShieldCheck, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), _jsxs("span", { className: "
|
|
261
|
+
return (_jsxs("div", { className: "mt-1.5 flex min-w-0 flex-wrap items-center gap-2 rounded-md border border-border bg-muted/40 px-2.5 py-1.5", children: [_jsx(IconShieldCheck, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), _jsxs("span", { className: "min-w-0 flex-1 text-xs text-muted-foreground", children: ["Approve to run ", toolName, "?"] }), ctx && (_jsxs("button", { type: "button", onClick: () => {
|
|
262
262
|
setLocalResolution("approved");
|
|
263
263
|
ctx.onApprovalResolved?.(approval.approvalKey, "approved", toolCallId);
|
|
264
264
|
ctx.onApprove(approval.approvalKey);
|
|
265
|
-
}, className: cn("inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90"), children: [_jsx(IconCheck, { className: "h-3.5 w-3.5" }), "Approve"] })), ctx?.onAlwaysAllow && (_jsxs("button", { type: "button", onClick: () => {
|
|
265
|
+
}, className: cn("inline-flex shrink-0 items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90"), children: [_jsx(IconCheck, { className: "h-3.5 w-3.5" }), "Approve"] })), ctx?.onAlwaysAllow && (_jsxs("button", { type: "button", onClick: () => {
|
|
266
266
|
setLocalResolution("approved");
|
|
267
267
|
ctx.onApprovalResolved?.(approval.approvalKey, "approved", toolCallId);
|
|
268
268
|
ctx.onAlwaysAllow?.(approval.approvalKey);
|
|
269
|
-
}, title: "Approve and always allow this exact command", className: cn("inline-flex items-center gap-1 rounded-md border border-border px-2.5 py-1 text-xs font-medium transition-colors", "text-foreground hover:bg-muted"), children: [_jsx(IconShieldCheck, { className: "h-3.5 w-3.5" }), "Always allow"] })), _jsxs("button", { type: "button", onClick: () => {
|
|
269
|
+
}, title: "Approve and always allow this exact command", className: cn("inline-flex shrink-0 items-center gap-1 rounded-md border border-border px-2.5 py-1 text-xs font-medium transition-colors", "text-foreground hover:bg-muted"), children: [_jsx(IconShieldCheck, { className: "h-3.5 w-3.5" }), "Always allow"] })), _jsxs("button", { type: "button", onClick: () => {
|
|
270
270
|
setLocalResolution("denied");
|
|
271
271
|
ctx?.onApprovalResolved?.(approval.approvalKey, "denied", toolCallId);
|
|
272
272
|
ctx?.onDeny?.(approval.approvalKey);
|
|
273
|
-
}, className: cn("inline-flex items-center gap-1 rounded-md border border-border px-2.5 py-1 text-xs font-medium transition-colors", "text-foreground hover:bg-muted"), children: [_jsx(IconX, { className: "h-3.5 w-3.5" }), "Deny"] })] }));
|
|
273
|
+
}, className: cn("inline-flex shrink-0 items-center gap-1 rounded-md border border-border px-2.5 py-1 text-xs font-medium transition-colors", "text-foreground hover:bg-muted"), children: [_jsx(IconX, { className: "h-3.5 w-3.5" }), "Deny"] })] }));
|
|
274
274
|
}
|
|
275
275
|
// ─── ToolCallDisplay ──────────────────────────────────────────────────────────
|
|
276
276
|
export function ToolCallDisplay({ toolName, toolCallId, argsText, args, result, mcpApp, chatUI, isRunning, outcome, structuredMeta, activity, approval, repeatCount, isLatestRunning = isRunning, isActiveTail, }) {
|
|
@@ -5,6 +5,11 @@ import { memo, useMemo, useState } from "react";
|
|
|
5
5
|
import { orderChatFirstAppIds, readChatFirstAppLayout, writeChatFirstAppLayout, } from "../chat-first.js";
|
|
6
6
|
import { cn } from "../utils.js";
|
|
7
7
|
import { defaultChatFirstCopy } from "./copy.js";
|
|
8
|
+
function ChatFirstRailAppIcon({ app, activeAppId, renderIcon, }) {
|
|
9
|
+
const isActive = activeAppId !== undefined && activeAppId === app.id;
|
|
10
|
+
const isInactive = activeAppId !== undefined && !isActive;
|
|
11
|
+
return (_jsx("span", { "data-chat-first-app-icon": true, className: cn("transition-[filter]", isInactive && "grayscale"), children: renderIcon(app, { isActive, isInactive }) }));
|
|
12
|
+
}
|
|
8
13
|
function AppRows({ apps, activeAppId, layout, onDragStart, onDrop, onDragEnd, onOpenApp, onRemoveApp, onTogglePinned, onMove, renderIcon, copy, }) {
|
|
9
14
|
const orderedIds = orderChatFirstAppIds(apps.map((app) => app.id), layout);
|
|
10
15
|
const appsById = new Map(apps.map((app) => [app.id, app]));
|
|
@@ -16,7 +21,7 @@ function AppRows({ apps, activeAppId, layout, onDragStart, onDrop, onDragEnd, on
|
|
|
16
21
|
const pinned = layout.pinnedIds.includes(app.id);
|
|
17
22
|
const index = orderedApps.indexOf(app);
|
|
18
23
|
return (_jsxs(ContextMenu, { children: [_jsx(ContextMenuTrigger, { asChild: true, children: _jsxs("li", { draggable: true, "data-chat-first-app": true, "data-app-id": app.id, className: cn("group flex h-8 w-full min-w-0 items-center gap-1 rounded-md px-0 text-sm", active
|
|
19
|
-
? "
|
|
24
|
+
? "font-medium text-sidebar-foreground"
|
|
20
25
|
: "text-sidebar-foreground/75 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), onDragStart: (event) => {
|
|
21
26
|
event.dataTransfer.effectAllowed = "move";
|
|
22
27
|
onDragStart(app.id);
|
|
@@ -31,7 +36,7 @@ function AppRows({ apps, activeAppId, layout, onDragStart, onDrop, onDragEnd, on
|
|
|
31
36
|
event.preventDefault();
|
|
32
37
|
onMove(app.id, 1);
|
|
33
38
|
}
|
|
34
|
-
}, "aria-keyshortcuts": "Alt+ArrowUp Alt+ArrowDown", "aria-label": copy("openApp", { name: app.name }), children: [
|
|
39
|
+
}, "aria-keyshortcuts": "Alt+ArrowUp Alt+ArrowDown", "aria-label": copy("openApp", { name: app.name }), children: [_jsx(ChatFirstRailAppIcon, { app: app, activeAppId: activeAppId, renderIcon: renderIcon }), _jsx("span", { className: "truncate", children: app.name })] }), _jsx("button", { type: "button", className: cn("flex size-6 shrink-0 items-center justify-center rounded text-sidebar-foreground/45 opacity-0 hover:bg-sidebar-accent hover:text-sidebar-foreground group-hover:opacity-100 focus-visible:opacity-100", pinned && "text-sidebar-foreground/70 opacity-100"), "aria-label": copy(pinned ? "unpinApp" : "pinApp", {
|
|
35
40
|
name: app.name,
|
|
36
41
|
}), "aria-pressed": pinned, onClick: () => onTogglePinned(app.id), children: _jsx(IconPin, { size: 13, strokeWidth: pinned ? 2.2 : 1.6, "aria-hidden": "true" }) }), _jsx("span", { className: "sr-only", children: `${index + 1} of ${orderedApps.length}` })] }) }), _jsxs(ContextMenuContent, { children: [_jsxs(ContextMenuItem, { onSelect: () => onTogglePinned(app.id), children: [_jsx(IconPin, { size: 14, "aria-hidden": "true" }), pinned ? copy("removePinned") : copy("pinTop")] }), _jsx(ContextMenuSeparator, {}), _jsxs(ContextMenuItem, { disabled: index === 0, onSelect: () => onMove(app.id, -1), children: [_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" }), copy("moveUp")] }), _jsxs(ContextMenuItem, { disabled: index === orderedApps.length - 1, onSelect: () => onMove(app.id, 1), children: [_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" }), copy("moveDown")] }), onRemoveApp ? (_jsxs(_Fragment, { children: [_jsx(ContextMenuSeparator, {}), _jsxs(ContextMenuItem, { onSelect: () => onRemoveApp(app), className: "text-destructive focus:text-destructive", children: [_jsx(IconTrash, { size: 14, "aria-hidden": "true" }), copy("removeApp")] })] })) : null] })] }, app.id));
|
|
37
42
|
}) }));
|
|
@@ -96,8 +101,8 @@ export const ChatFirstAppsRail = memo(function ChatFirstAppsRail({ apps, activeA
|
|
|
96
101
|
return (_jsxs("section", { "data-chat-first-apps-rail": true, className: "flex flex-col items-center gap-1 px-1.5 pt-2", "aria-label": copy("workspaceApps"), children: [createTrigger, loading && apps.length === 0
|
|
97
102
|
? [0, 1, 2].map((index) => (_jsx(Skeleton, { className: "size-9 rounded-md" }, index)))
|
|
98
103
|
: visibleApps.map((app) => (_jsx("button", { type: "button", "data-chat-first-app": true, "data-app-id": app.id, className: cn("flex size-9 items-center justify-center rounded-md", activeAppId === app.id
|
|
99
|
-
? "
|
|
100
|
-
: "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), onClick: () => onOpenApp(app), "aria-label": copy("openApp", { name: app.name }), title: app.name, children:
|
|
104
|
+
? "text-sidebar-foreground"
|
|
105
|
+
: "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), onClick: () => onOpenApp(app), "aria-label": copy("openApp", { name: app.name }), title: app.name, children: _jsx(ChatFirstRailAppIcon, { app: app, activeAppId: activeAppId, renderIcon: renderIcon }) }, app.id))), onOpenAllApps ? (_jsx("button", { type: "button", "data-chat-first-all-apps": true, className: "flex size-9 items-center justify-center rounded-md text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", onClick: onOpenAllApps, "aria-label": copy("allApps"), title: copy("allApps"), children: _jsx(IconApps, { size: 16, "aria-hidden": "true" }) })) : null, error ? (_jsx("span", { className: "size-1.5 rounded-full bg-destructive", title: error, "aria-label": error })) : null] }));
|
|
101
106
|
}
|
|
102
107
|
return (_jsxs("section", { "data-chat-first-apps-rail": true, className: "mt-3 px-2 pb-2 pt-2", "aria-label": copy("workspaceApps"), children: [_jsxs("div", { className: "mb-1 flex items-center gap-1.5 px-2 text-[11px] font-medium text-sidebar-foreground/50", children: [_jsx("span", { children: copy("workspaceApps") }), _jsx("span", { className: "ml-auto", children: createTrigger })] }), loading && apps.length === 0 ? (_jsx("div", { className: "space-y-0.5 px-2", children: [0, 1, 2].map((index) => (_jsx(Skeleton, { className: "h-8 w-full rounded-md" }, index))) })) : apps.length === 0 ? (_jsxs("div", { className: "px-2", children: [_jsx("p", { className: "text-xs text-sidebar-foreground/55", children: copy("noWorkspaceApps") }), createAppTrigger ? (createTrigger) : onCreateApp ? (_jsxs("button", { type: "button", className: "mt-1 inline-flex h-6 items-center gap-1.5 rounded-md border border-sidebar-border px-2 text-xs text-sidebar-foreground/75 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onCreateApp, children: [_jsx(IconPlus, { size: 13, "aria-hidden": "true" }), copy("createApp")] })) : (createTrigger)] })) : (_jsx(AppRows, { apps: visibleApps, activeAppId: activeAppId, layout: layout, onDragStart: setDraggedAppId, onDrop: reorderApps, onDragEnd: () => setDraggedAppId(null), onOpenApp: onOpenApp, onRemoveApp: onRemoveApp, onTogglePinned: togglePinned, onMove: moveApp, renderIcon: renderIcon, copy: copy })), hasMoreApps || showAllApps ? (_jsxs("button", { type: "button", className: "mt-0.5 flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: () => setShowAllApps((value) => !value), children: [showAllApps ? (_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" })) : (_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" })), showAllApps ? copy("showLess") : copy("showMore")] })) : null, onOpenAllApps ? (_jsx("div", { className: "mt-1 border-t border-sidebar-border/60 pt-1", children: _jsxs("button", { type: "button", "data-chat-first-all-apps": true, className: "flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/55 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onOpenAllApps, children: [_jsx(IconApps, { size: 14, "aria-hidden": "true" }), _jsx("span", { children: copy("allApps") })] }) })) : null, error ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "mt-1 flex items-center gap-1 px-2 text-[10px] text-destructive/80", onClick: onRetry, "aria-label": error, children: [_jsx("span", { className: "size-1.5 rounded-full bg-destructive" }), onRetry ? copy("retry") : copy("appsLoadError")] }) }), _jsx(TooltipContent, { side: "right", children: error })] })) : null] }));
|
|
103
108
|
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { ChatFirstCopy } from "./types.js";
|
|
2
2
|
export type ChatFirstPrimaryTab = "new-chat" | "integrations" | "scheduled";
|
|
3
|
-
export declare function ChatFirstPrimaryNavigation({ onNewChat, onOpenIntegrations, onOpenScheduled, onSearch, activeTab, stickyNewChat, copy, }: {
|
|
3
|
+
export declare function ChatFirstPrimaryNavigation({ onNewChat, onOpenIntegrations, onOpenScheduled, onSearch, activeTab, collapsed, stickyNewChat, copy, }: {
|
|
4
4
|
onNewChat?: () => void;
|
|
5
5
|
onOpenIntegrations: () => void;
|
|
6
6
|
onOpenScheduled: () => void;
|
|
7
7
|
onSearch?: () => void;
|
|
8
8
|
activeTab?: ChatFirstPrimaryTab;
|
|
9
|
+
collapsed?: boolean;
|
|
9
10
|
stickyNewChat?: boolean;
|
|
10
11
|
copy?: ChatFirstCopy;
|
|
11
12
|
}): import("react").JSX.Element;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { IconClock, IconPlus, IconPlugConnected, IconSearch, } from "@tabler/icons-react";
|
|
3
3
|
import { defaultChatFirstCopy } from "./copy.js";
|
|
4
|
-
export function ChatFirstPrimaryNavigation({ onNewChat, onOpenIntegrations, onOpenScheduled, onSearch, activeTab, stickyNewChat = false, copy = defaultChatFirstCopy, }) {
|
|
4
|
+
export function ChatFirstPrimaryNavigation({ onNewChat, onOpenIntegrations, onOpenScheduled, onSearch, activeTab, collapsed = false, stickyNewChat = false, copy = defaultChatFirstCopy, }) {
|
|
5
5
|
const tabClassName = (tab) => `flex h-8 w-full items-center gap-2 rounded-md px-2 text-[13px] font-medium transition-[background-color,color] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${activeTab === tab
|
|
6
6
|
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
|
7
7
|
: "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"}`;
|
|
8
|
-
const renderTab = (tab, content, onSelect, className) => (_jsx("button", { type: "button", role: "tab", "aria-selected": activeTab === tab, className: [tabClassName(tab), className].filter(Boolean).join(" "), onClick: onSelect, children: content }));
|
|
8
|
+
const renderTab = (tab, content, onSelect, className) => (_jsx("button", { type: "button", role: "tab", "aria-selected": activeTab === tab, "aria-label": tab === "new-chat" ? copy("newChat") : undefined, title: tab === "new-chat" && collapsed ? copy("newChat") : undefined, className: [tabClassName(tab), className].filter(Boolean).join(" "), onClick: onSelect, children: content }));
|
|
9
9
|
const newChatTab = onNewChat
|
|
10
|
-
? renderTab("new-chat", _jsxs(_Fragment, { children: [_jsx(IconPlus, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: copy("newChat") })] }), onNewChat, "code-agents-primary-new-chat")
|
|
10
|
+
? renderTab("new-chat", _jsxs(_Fragment, { children: [_jsx(IconPlus, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { className: collapsed ? "sr-only" : undefined, children: copy("newChat") })] }), onNewChat, "code-agents-primary-new-chat")
|
|
11
11
|
: null;
|
|
12
12
|
const integrationsTab = renderTab("integrations", _jsxs(_Fragment, { children: [_jsx(IconPlugConnected, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: copy("integrations") })] }), onOpenIntegrations);
|
|
13
13
|
const scheduledTab = renderTab("scheduled", _jsxs(_Fragment, { children: [_jsx(IconClock, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: copy("scheduled") })] }), onOpenScheduled);
|
|
@@ -8,6 +8,10 @@ export interface ChatFirstAppItem {
|
|
|
8
8
|
color?: string;
|
|
9
9
|
}
|
|
10
10
|
export type ChatFirstCopy = (key: string, values?: Record<string, string>) => string;
|
|
11
|
+
export interface ChatFirstAppIconRenderOptions {
|
|
12
|
+
isActive: boolean;
|
|
13
|
+
isInactive: boolean;
|
|
14
|
+
}
|
|
11
15
|
export interface ChatFirstEmbedTarget {
|
|
12
16
|
url: string;
|
|
13
17
|
title?: string;
|
|
@@ -51,7 +55,7 @@ export interface ChatFirstAppRailProps {
|
|
|
51
55
|
onOpenAllApps?: () => void;
|
|
52
56
|
onCreateApp?: () => void;
|
|
53
57
|
createAppTrigger?: ReactNode;
|
|
54
|
-
renderIcon: (app: ChatFirstAppItem) => ReactNode;
|
|
58
|
+
renderIcon: (app: ChatFirstAppItem, options?: ChatFirstAppIconRenderOptions) => ReactNode;
|
|
55
59
|
copy?: ChatFirstCopy;
|
|
56
60
|
}
|
|
57
61
|
export interface ChatFirstBrowserPaneProps {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export interface InlineMarkdownProps {
|
|
3
|
+
content: string;
|
|
4
|
+
className?: string;
|
|
5
|
+
linkClassName?: string;
|
|
6
|
+
codeClassName?: string;
|
|
7
|
+
inline?: boolean;
|
|
8
|
+
protectedSpans?: readonly InlineMarkdownProtectedSpan[];
|
|
9
|
+
renderProtectedSpan?: (span: InlineMarkdownProtectedSpan, children: ReactNode) => ReactNode;
|
|
10
|
+
}
|
|
11
|
+
export interface InlineMarkdownProtectedSpan {
|
|
12
|
+
source: string;
|
|
13
|
+
label: string;
|
|
14
|
+
className?: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Render user-authored Markdown for compact text surfaces.
|
|
19
|
+
*
|
|
20
|
+
* This intentionally has no block-level Markdown elements: headings, lists,
|
|
21
|
+
* quotes, and raw HTML are either flattened to their text or omitted.
|
|
22
|
+
*/
|
|
23
|
+
export declare function InlineMarkdown({ content, className, linkClassName, codeClassName, inline, protectedSpans, renderProtectedSpan, }: InlineMarkdownProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import ReactMarkdown, { defaultUrlTransform, } from "react-markdown";
|
|
3
|
+
import remarkGfm from "remark-gfm";
|
|
4
|
+
import { cn } from "../utils.js";
|
|
5
|
+
const INLINE_MARKDOWN_ELEMENTS = [
|
|
6
|
+
"a",
|
|
7
|
+
"br",
|
|
8
|
+
"code",
|
|
9
|
+
"del",
|
|
10
|
+
"em",
|
|
11
|
+
"p",
|
|
12
|
+
"s",
|
|
13
|
+
"strong",
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Render user-authored Markdown for compact text surfaces.
|
|
17
|
+
*
|
|
18
|
+
* This intentionally has no block-level Markdown elements: headings, lists,
|
|
19
|
+
* quotes, and raw HTML are either flattened to their text or omitted.
|
|
20
|
+
*/
|
|
21
|
+
export function InlineMarkdown({ content, className, linkClassName, codeClassName, inline = false, protectedSpans = [], renderProtectedSpan, }) {
|
|
22
|
+
const protectedSpanByHref = new Map(protectedSpans
|
|
23
|
+
.map((span, index) => [`${PROTECTED_SPAN_PREFIX}${index}`, span])
|
|
24
|
+
.filter(([, span]) => span.source.length > 0));
|
|
25
|
+
const renderedContent = protectInlineMarkdownSpans(content, protectedSpans);
|
|
26
|
+
const components = {
|
|
27
|
+
a: ({ children, href }) => {
|
|
28
|
+
const protectedSpan = href ? protectedSpanByHref.get(href) : undefined;
|
|
29
|
+
if (protectedSpan) {
|
|
30
|
+
return renderProtectedSpan ? (renderProtectedSpan(protectedSpan, children)) : (_jsx("span", { className: protectedSpan.className, title: protectedSpan.title, children: children }));
|
|
31
|
+
}
|
|
32
|
+
const safeHref = href
|
|
33
|
+
? defaultUrlTransform(normalizeInlineMarkdownHref(href))
|
|
34
|
+
: "";
|
|
35
|
+
if (!safeHref)
|
|
36
|
+
return _jsx(_Fragment, { children: children });
|
|
37
|
+
return (_jsx("a", { href: safeHref, target: "_blank", rel: "noopener noreferrer", className: cn("text-primary underline-offset-2 hover:underline", linkClassName), children: children }));
|
|
38
|
+
},
|
|
39
|
+
code: ({ children, className: syntaxClassName }) => (_jsx("code", { className: cn("rounded bg-muted px-1 py-0.5 font-mono text-[0.9em]", syntaxClassName, codeClassName), children: children })),
|
|
40
|
+
p: ({ children }) => inline ? _jsx("span", { children: children }) : _jsx("p", { className: "m-0", children: children }),
|
|
41
|
+
};
|
|
42
|
+
const Root = inline ? "span" : "div";
|
|
43
|
+
return (_jsx(Root, { className: cn("whitespace-pre-wrap break-words", className), children: _jsx(ReactMarkdown, { allowedElements: INLINE_MARKDOWN_ELEMENTS, components: components, remarkPlugins: [remarkGfm], skipHtml: true, unwrapDisallowed: true, children: renderedContent }) }));
|
|
44
|
+
}
|
|
45
|
+
const PROTECTED_SPAN_PREFIX = "https://inline-markdown-protected.invalid/span/";
|
|
46
|
+
function protectInlineMarkdownSpans(content, spans) {
|
|
47
|
+
return [...spans]
|
|
48
|
+
.map((span, index) => ({ span, index }))
|
|
49
|
+
.filter(({ span }) => span.source.length > 0)
|
|
50
|
+
.sort((a, b) => b.span.source.length - a.span.source.length)
|
|
51
|
+
.reduce((markdown, { span, index }) => markdown
|
|
52
|
+
.split(span.source)
|
|
53
|
+
.join(`[${escapeProtectedSpanLabel(span.label)}](${PROTECTED_SPAN_PREFIX}${index})`), content);
|
|
54
|
+
}
|
|
55
|
+
function escapeProtectedSpanLabel(label) {
|
|
56
|
+
return label.replace(/[\\[\]]/g, "\\$&");
|
|
57
|
+
}
|
|
58
|
+
function normalizeInlineMarkdownHref(href) {
|
|
59
|
+
if (/^www\./i.test(href))
|
|
60
|
+
return `https://${href}`;
|
|
61
|
+
if (/^http:\/\/www\./i.test(href)) {
|
|
62
|
+
return `https://${href.slice("http://".length)}`;
|
|
63
|
+
}
|
|
64
|
+
return href;
|
|
65
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { InlineMarkdown, type InlineMarkdownProtectedSpan, type InlineMarkdownProps, } from "./InlineMarkdown.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { InlineMarkdown, } from "./InlineMarkdown.js";
|
|
@@ -8,6 +8,7 @@ import { Spinner } from "@agent-native/toolkit/ui/spinner";
|
|
|
8
8
|
import { IconCircleCheck, IconDots, IconMessageCircle, IconSend, IconTrash, IconX, } from "@tabler/icons-react";
|
|
9
9
|
import { useMemo, useState } from "react";
|
|
10
10
|
import { useFormatters } from "../i18n.js";
|
|
11
|
+
import { InlineMarkdown } from "../markdown/index.js";
|
|
11
12
|
import { cn } from "../utils.js";
|
|
12
13
|
import { ReviewCommentComposer } from "./ReviewCommentComposer.js";
|
|
13
14
|
import { ReviewStatusBadge } from "./ReviewStatusBadge.js";
|
|
@@ -126,7 +127,7 @@ function CommentBubble({ comment, compact = false, resolvedLabel, reviewerLabel,
|
|
|
126
127
|
const bodyIsResolutionNote = resolutionNote !== null &&
|
|
127
128
|
resolutionNote === comment.body.trim() &&
|
|
128
129
|
isResolutionNoteMetadata(comment.metadata);
|
|
129
|
-
return (_jsxs("div", { className: "flex min-w-0 items-start gap-2.5", children: [_jsx(Avatar, { className: compact ? "size-5" : "size-7", children: _jsx(AvatarFallback, { className: "text-[10px] font-semibold text-muted-foreground", children: authorInitials(author) }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("span", { className: "truncate text-sm font-medium", children: author }), _jsx("time", { className: "shrink-0 text-[11px] text-muted-foreground", dateTime: comment.createdAt, children: formatCommentDate(comment.createdAt, formatDate) }), comment.status === "resolved" ? (_jsx("span", { className: "hidden shrink-0 rounded-full bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground @xs/review:inline-flex", children: resolvedLabel })) : null] }), !bodyIsResolutionNote ? (_jsx(
|
|
130
|
+
return (_jsxs("div", { className: "flex min-w-0 items-start gap-2.5", children: [_jsx(Avatar, { className: compact ? "size-5" : "size-7", children: _jsx(AvatarFallback, { className: "text-[10px] font-semibold text-muted-foreground", children: authorInitials(author) }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("span", { className: "truncate text-sm font-medium", children: author }), _jsx("time", { className: "shrink-0 text-[11px] text-muted-foreground", dateTime: comment.createdAt, children: formatCommentDate(comment.createdAt, formatDate) }), comment.status === "resolved" ? (_jsx("span", { className: "hidden shrink-0 rounded-full bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground @xs/review:inline-flex", children: resolvedLabel })) : null] }), !bodyIsResolutionNote ? (_jsx(InlineMarkdown, { content: comment.body, className: cn("mt-1 text-foreground", compact ? "text-xs leading-5" : "text-sm leading-5") })) : null, resolutionNote ? (_jsxs("div", { className: "mt-1.5 flex min-w-0 items-start gap-1.5 rounded-md bg-muted/60 px-2 py-1.5 text-muted-foreground", "aria-label": resolvedLabel, children: [_jsx(IconCircleCheck, { className: "mt-0.5 size-3.5 shrink-0" }), _jsx(InlineMarkdown, { content: resolutionNote, className: "min-w-0 text-xs leading-4" })] })) : null] })] }));
|
|
130
131
|
}
|
|
131
132
|
function capabilityAllowsThread(capability, thread) {
|
|
132
133
|
return typeof capability === "function" ? capability(thread) : capability;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exactly one SSE reader may fold a given run into UI state.
|
|
3
|
+
*
|
|
4
|
+
* Two readers attach to the same run routinely: the chat adapter's own stream,
|
|
5
|
+
* and AssistantChat's reconnect reader for runs with no live adapter stream
|
|
6
|
+
* (page reload, tab restore). When both are attached they build two independent
|
|
7
|
+
* accumulators from the same events, and the UI renders both — duplicate tool
|
|
8
|
+
* cards (one spinning, one static) and the same assistant text streaming twice.
|
|
9
|
+
*
|
|
10
|
+
* The previous guard read a React ref (`isRuntimeRunningRef`) at attach time and
|
|
11
|
+
* re-checked it from a 1s poll. That is a race, not a lock: the ref lags a
|
|
12
|
+
* render behind, the poll is skipped while the tab is hidden, and the refs were
|
|
13
|
+
* per-component-instance while MultiTabAssistantChat mounts several instances
|
|
14
|
+
* against one run. Ownership therefore lives here — module scope, one registry
|
|
15
|
+
* per browser tab, claimed and checked synchronously.
|
|
16
|
+
*
|
|
17
|
+
* Claims are advisory in one direction only: a reader that does not hold the
|
|
18
|
+
* claim must not mutate UI state. It may still drain its socket to completion.
|
|
19
|
+
*/
|
|
20
|
+
/** Mint a token identifying one reader. Not shared between readers. */
|
|
21
|
+
export declare function createRunStreamToken(label?: string): symbol;
|
|
22
|
+
/**
|
|
23
|
+
* Take ownership of a run's UI fold. Returns false when another live reader
|
|
24
|
+
* already holds it — the caller must not attach, or must attach read-only.
|
|
25
|
+
* Re-claiming with the same token is a no-op success so retry loops are safe.
|
|
26
|
+
*/
|
|
27
|
+
export declare function claimRunStream(threadId: string, runId: string, token: symbol): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Take ownership even if another reader holds it, and return whether the claim
|
|
30
|
+
* changed hands. The adapter's own stream outranks the reconnect fallback: when
|
|
31
|
+
* the user sends a message the adapter legitimately becomes the owner, and the
|
|
32
|
+
* displaced reader learns it lost via `ownsRunStream`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function preemptRunStream(threadId: string, runId: string, token: symbol): boolean;
|
|
35
|
+
export declare function ownsRunStream(threadId: string, runId: string, token: symbol): boolean;
|
|
36
|
+
/** Release only if still held by this token, so a late unmount cannot free a successor's claim. */
|
|
37
|
+
export declare function releaseRunStream(threadId: string, runId: string, token: symbol): void;
|
|
38
|
+
/** Test seam. */
|
|
39
|
+
export declare function __resetRunStreamOwnership(): void;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exactly one SSE reader may fold a given run into UI state.
|
|
3
|
+
*
|
|
4
|
+
* Two readers attach to the same run routinely: the chat adapter's own stream,
|
|
5
|
+
* and AssistantChat's reconnect reader for runs with no live adapter stream
|
|
6
|
+
* (page reload, tab restore). When both are attached they build two independent
|
|
7
|
+
* accumulators from the same events, and the UI renders both — duplicate tool
|
|
8
|
+
* cards (one spinning, one static) and the same assistant text streaming twice.
|
|
9
|
+
*
|
|
10
|
+
* The previous guard read a React ref (`isRuntimeRunningRef`) at attach time and
|
|
11
|
+
* re-checked it from a 1s poll. That is a race, not a lock: the ref lags a
|
|
12
|
+
* render behind, the poll is skipped while the tab is hidden, and the refs were
|
|
13
|
+
* per-component-instance while MultiTabAssistantChat mounts several instances
|
|
14
|
+
* against one run. Ownership therefore lives here — module scope, one registry
|
|
15
|
+
* per browser tab, claimed and checked synchronously.
|
|
16
|
+
*
|
|
17
|
+
* Claims are advisory in one direction only: a reader that does not hold the
|
|
18
|
+
* claim must not mutate UI state. It may still drain its socket to completion.
|
|
19
|
+
*/
|
|
20
|
+
const owners = new Map();
|
|
21
|
+
function runKey(threadId, runId) {
|
|
22
|
+
return `${threadId} ${runId}`;
|
|
23
|
+
}
|
|
24
|
+
/** Mint a token identifying one reader. Not shared between readers. */
|
|
25
|
+
export function createRunStreamToken(label) {
|
|
26
|
+
return Symbol(label ?? "run-stream-reader");
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Take ownership of a run's UI fold. Returns false when another live reader
|
|
30
|
+
* already holds it — the caller must not attach, or must attach read-only.
|
|
31
|
+
* Re-claiming with the same token is a no-op success so retry loops are safe.
|
|
32
|
+
*/
|
|
33
|
+
export function claimRunStream(threadId, runId, token) {
|
|
34
|
+
const key = runKey(threadId, runId);
|
|
35
|
+
const current = owners.get(key);
|
|
36
|
+
if (current && current !== token)
|
|
37
|
+
return false;
|
|
38
|
+
owners.set(key, token);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Take ownership even if another reader holds it, and return whether the claim
|
|
43
|
+
* changed hands. The adapter's own stream outranks the reconnect fallback: when
|
|
44
|
+
* the user sends a message the adapter legitimately becomes the owner, and the
|
|
45
|
+
* displaced reader learns it lost via `ownsRunStream`.
|
|
46
|
+
*/
|
|
47
|
+
export function preemptRunStream(threadId, runId, token) {
|
|
48
|
+
const key = runKey(threadId, runId);
|
|
49
|
+
const current = owners.get(key);
|
|
50
|
+
owners.set(key, token);
|
|
51
|
+
return current !== token;
|
|
52
|
+
}
|
|
53
|
+
export function ownsRunStream(threadId, runId, token) {
|
|
54
|
+
return owners.get(runKey(threadId, runId)) === token;
|
|
55
|
+
}
|
|
56
|
+
/** Release only if still held by this token, so a late unmount cannot free a successor's claim. */
|
|
57
|
+
export function releaseRunStream(threadId, runId, token) {
|
|
58
|
+
const key = runKey(threadId, runId);
|
|
59
|
+
if (owners.get(key) === token)
|
|
60
|
+
owners.delete(key);
|
|
61
|
+
}
|
|
62
|
+
/** Test seam. */
|
|
63
|
+
export function __resetRunStreamOwnership() {
|
|
64
|
+
owners.clear();
|
|
65
|
+
}
|
|
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
|
|
|
62
62
|
error: string;
|
|
63
63
|
states?: undefined;
|
|
64
64
|
} | {
|
|
65
|
-
error?: undefined;
|
|
66
65
|
states: {
|
|
67
66
|
clientId: number;
|
|
68
67
|
state: string;
|
|
69
68
|
}[];
|
|
69
|
+
error?: undefined;
|
|
70
70
|
}>>;
|
|
71
71
|
/**
|
|
72
72
|
* GET /_agent-native/collab/:docId/users
|
|
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
|
|
|
77
77
|
error: string;
|
|
78
78
|
users?: undefined;
|
|
79
79
|
} | {
|
|
80
|
-
error?: undefined;
|
|
81
80
|
users: {
|
|
82
81
|
clientId: number;
|
|
83
82
|
lastSeen: number;
|
|
84
83
|
}[];
|
|
84
|
+
error?: undefined;
|
|
85
85
|
}>>;
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
ok?: undefined;
|
|
17
16
|
error: string;
|
|
17
|
+
ok?: undefined;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|