@agent-native/core 0.95.1 → 0.96.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 +2 -2
- package/corpus/core/CHANGELOG.md +25 -0
- package/corpus/core/package.json +3 -1
- package/corpus/core/src/a2a/client.ts +11 -1
- package/corpus/core/src/agent/production-agent.ts +87 -27
- package/corpus/core/src/agent/thread-data-builder.ts +7 -5
- package/corpus/core/src/chat-threads/store.ts +20 -1
- package/corpus/core/src/cli/code-agent-executor.ts +1 -0
- package/corpus/core/src/cli/code-agent-runs.ts +14 -0
- package/corpus/core/src/client/AgentPanel.tsx +32 -25
- package/corpus/core/src/client/AssistantChat.tsx +36 -20
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +141 -90
- package/corpus/core/src/client/chat/ChatHistoryList.tsx +404 -0
- package/corpus/core/src/client/chat/index.ts +6 -0
- package/corpus/core/src/client/chat/tool-call-display.tsx +37 -3
- package/corpus/core/src/client/code-agent-chat-adapter.ts +72 -4
- package/corpus/core/src/client/composer/RealtimeVoiceMode.tsx +52 -11
- package/corpus/core/src/client/composer/TiptapComposer.tsx +157 -20
- package/corpus/core/src/client/composer/VoiceButton.tsx +28 -0
- package/corpus/core/src/client/composer/useRealtimeVoiceMode.tsx +5 -0
- package/corpus/core/src/client/conversation/code-agent-transcript.ts +4 -5
- package/corpus/core/src/client/guided-questions.tsx +2 -1
- package/corpus/core/src/client/index.ts +11 -0
- package/corpus/core/src/client/use-chat-threads.ts +10 -1
- package/corpus/core/src/client/voice-provider-status.ts +44 -0
- package/corpus/core/src/code-agents/transcript-normalizer.ts +76 -0
- package/corpus/core/src/deploy/build.ts +12 -11
- package/corpus/core/src/integrations/a2a-continuation-processor.ts +35 -5
- package/corpus/core/src/integrations/adapters/slack.ts +74 -21
- package/corpus/core/src/integrations/webhook-handler.ts +14 -0
- package/corpus/core/src/localization/default-messages.ts +6 -0
- package/corpus/core/src/observability/hosted-model-experiment.ts +118 -0
- package/corpus/core/src/observability/routes.ts +17 -0
- package/corpus/core/src/observability/store.ts +4 -1
- package/corpus/core/src/observability/traces.ts +41 -2
- package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +19 -1
- package/corpus/core/src/scripts/call-agent.ts +53 -3
- package/corpus/core/src/scripts/chat/search-chats.ts +11 -8
- package/corpus/core/src/secrets/register-framework-secrets.ts +1 -1
- package/corpus/core/src/server/agent-chat-plugin.ts +41 -6
- package/corpus/core/src/server/core-routes-plugin.ts +15 -0
- package/corpus/core/src/server/realtime-voice.ts +71 -24
- package/corpus/core/src/server/voice-providers-status.ts +4 -3
- package/corpus/core/src/shared/cache-control.ts +2 -8
- package/corpus/core/src/styles/agent-native.css +1 -0
- package/corpus/core/src/styles/chat-history-list.css +313 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +20 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +8 -4
- package/corpus/core/src/vite/client.ts +1 -0
- package/corpus/templates/analytics/AGENTS.md +7 -0
- package/corpus/templates/analytics/actions/bigquery.ts +26 -2
- package/corpus/templates/analytics/actions/data-source-status.ts +18 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +5 -4
- package/corpus/templates/analytics/app/global.css +10 -0
- package/corpus/templates/analytics/app/pages/Ask.tsx +25 -2
- package/corpus/templates/analytics/changelog/2026-07-10-analytics-answers-simple-time-bounded-metrics-without-unnece.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-10-analytics-chat-reliably-uses-connected-data-sources-across-s.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-10-chat-threads-now-stay-clear-of-top-actions-and-fade-smoothly.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-10-context-chips-can-now-be-selected-and-removed-one-at-a-time-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-10-the-ask-composer-no-longer-carries-dashboard-or-panel-contex.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-10-the-ask-section-stays-highlighted-while-its-chat-history-is-.md +6 -0
- package/corpus/templates/analytics/server/lib/bigquery.ts +90 -14
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +82 -7
- package/dist/a2a/client.d.ts +7 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +2 -1
- package/dist/a2a/client.js.map +1 -1
- package/dist/agent/production-agent.d.ts +6 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +63 -26
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts +1 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +6 -5
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/chat-threads/store.d.ts +10 -0
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +6 -0
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/cli/code-agent-executor.d.ts.map +1 -1
- package/dist/cli/code-agent-executor.js +1 -0
- package/dist/cli/code-agent-executor.js.map +1 -1
- package/dist/cli/code-agent-runs.d.ts +12 -0
- package/dist/cli/code-agent-runs.d.ts.map +1 -1
- package/dist/cli/code-agent-runs.js +1 -0
- package/dist/cli/code-agent-runs.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +3 -3
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +11 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +7 -4
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +83 -10
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/chat/ChatHistoryList.d.ts +88 -0
- package/dist/client/chat/ChatHistoryList.d.ts.map +1 -0
- package/dist/client/chat/ChatHistoryList.js +83 -0
- package/dist/client/chat/ChatHistoryList.js.map +1 -0
- package/dist/client/chat/index.d.ts +1 -0
- package/dist/client/chat/index.d.ts.map +1 -1
- package/dist/client/chat/index.js +1 -0
- package/dist/client/chat/index.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +13 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +10 -2
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/code-agent-chat-adapter.d.ts +17 -1
- package/dist/client/code-agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/code-agent-chat-adapter.js +47 -3
- package/dist/client/code-agent-chat-adapter.js.map +1 -1
- package/dist/client/composer/RealtimeVoiceMode.d.ts +11 -1
- package/dist/client/composer/RealtimeVoiceMode.d.ts.map +1 -1
- package/dist/client/composer/RealtimeVoiceMode.js +5 -3
- package/dist/client/composer/RealtimeVoiceMode.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +25 -2
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +96 -5
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/VoiceButton.d.ts.map +1 -1
- package/dist/client/composer/VoiceButton.js +21 -1
- package/dist/client/composer/VoiceButton.js.map +1 -1
- package/dist/client/composer/useRealtimeVoiceMode.d.ts.map +1 -1
- package/dist/client/composer/useRealtimeVoiceMode.js +5 -0
- package/dist/client/composer/useRealtimeVoiceMode.js.map +1 -1
- package/dist/client/conversation/code-agent-transcript.d.ts +2 -0
- package/dist/client/conversation/code-agent-transcript.d.ts.map +1 -1
- package/dist/client/conversation/code-agent-transcript.js +3 -5
- package/dist/client/conversation/code-agent-transcript.js.map +1 -1
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +2 -1
- package/dist/client/guided-questions.js.map +1 -1
- package/dist/client/index.d.ts +3 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +3 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js +9 -1
- package/dist/client/use-chat-threads.js.map +1 -1
- package/dist/client/voice-provider-status.d.ts +10 -0
- package/dist/client/voice-provider-status.d.ts.map +1 -0
- package/dist/client/voice-provider-status.js +32 -0
- package/dist/client/voice-provider-status.js.map +1 -0
- package/dist/code-agents/transcript-normalizer.d.ts +29 -0
- package/dist/code-agents/transcript-normalizer.d.ts.map +1 -1
- package/dist/code-agents/transcript-normalizer.js +55 -0
- package/dist/code-agents/transcript-normalizer.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +13 -11
- package/dist/deploy/build.js.map +1 -1
- package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
- package/dist/integrations/a2a-continuation-processor.js +25 -5
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/integrations/adapters/slack.d.ts.map +1 -1
- package/dist/integrations/adapters/slack.js +55 -21
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +11 -0
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/localization/default-messages.d.ts +5 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +5 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/hosted-model-experiment.d.ts +39 -0
- package/dist/observability/hosted-model-experiment.d.ts.map +1 -0
- package/dist/observability/hosted-model-experiment.js +90 -0
- package/dist/observability/hosted-model-experiment.js.map +1 -0
- package/dist/observability/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts.map +1 -1
- package/dist/observability/routes.js +16 -0
- package/dist/observability/routes.js.map +1 -1
- package/dist/observability/store.d.ts.map +1 -1
- package/dist/observability/store.js +4 -1
- package/dist/observability/store.js.map +1 -1
- package/dist/observability/traces.d.ts +6 -0
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +28 -2
- package/dist/observability/traces.js.map +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.js +19 -1
- package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +43 -3
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/scripts/chat/search-chats.d.ts +1 -1
- package/dist/scripts/chat/search-chats.d.ts.map +1 -1
- package/dist/scripts/chat/search-chats.js +11 -8
- package/dist/scripts/chat/search-chats.js.map +1 -1
- package/dist/secrets/register-framework-secrets.js +1 -1
- package/dist/secrets/register-framework-secrets.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +11 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +27 -6
- 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/core-routes-plugin.d.ts +3 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +13 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/realtime-voice.d.ts.map +1 -1
- package/dist/server/realtime-voice.js +55 -21
- package/dist/server/realtime-voice.js.map +1 -1
- package/dist/server/voice-providers-status.d.ts.map +1 -1
- package/dist/server/voice-providers-status.js +2 -3
- package/dist/server/voice-providers-status.js.map +1 -1
- package/dist/shared/cache-control.d.ts +4 -6
- package/dist/shared/cache-control.d.ts.map +1 -1
- package/dist/shared/cache-control.js +2 -7
- package/dist/shared/cache-control.js.map +1 -1
- package/dist/styles/agent-native.css +1 -0
- package/dist/styles/chat-history-list.css +313 -0
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +20 -0
- package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +8 -4
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +1 -0
- package/dist/vite/client.js.map +1 -1
- package/package.json +3 -1
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +20 -0
- package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +8 -4
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IconDots,
|
|
3
|
+
IconPencil,
|
|
4
|
+
IconPinned,
|
|
5
|
+
IconPinnedOff,
|
|
6
|
+
IconSearch,
|
|
7
|
+
IconTrash,
|
|
8
|
+
} from "@tabler/icons-react";
|
|
9
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
10
|
+
|
|
11
|
+
import { cn } from "../utils.js";
|
|
12
|
+
|
|
13
|
+
/** A single row in a chat history list. Purely data — no fetching, no
|
|
14
|
+
* assistant-ui context. Formatting (relative time, "Active"/"Open" labels,
|
|
15
|
+
* status spinners) is the caller's responsibility; hand in the already
|
|
16
|
+
* rendered node via `timestamp`. */
|
|
17
|
+
export interface ChatHistoryItem {
|
|
18
|
+
id: string;
|
|
19
|
+
title: React.ReactNode;
|
|
20
|
+
/** Plain-text form of `title`, used to seed the inline rename input.
|
|
21
|
+
* Only needed when `onRename` is supplied. */
|
|
22
|
+
titleText?: string;
|
|
23
|
+
/** Secondary line — e.g. a message preview. */
|
|
24
|
+
subtitle?: React.ReactNode;
|
|
25
|
+
/** Tertiary line — e.g. a scope/folder label. */
|
|
26
|
+
detail?: React.ReactNode;
|
|
27
|
+
/** Pre-formatted trailing value: a relative time, "Active"/"Open", or a
|
|
28
|
+
* status node (spinner, unread dot). This component does not format
|
|
29
|
+
* dates or compute status itself. */
|
|
30
|
+
timestamp?: React.ReactNode;
|
|
31
|
+
pinned?: boolean;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ChatHistorySection {
|
|
36
|
+
id: string;
|
|
37
|
+
label?: React.ReactNode;
|
|
38
|
+
items: ChatHistoryItem[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ChatHistoryListProps {
|
|
42
|
+
/** Flat item list. Ignored when `sections` is provided. */
|
|
43
|
+
items?: ChatHistoryItem[];
|
|
44
|
+
/** Grouped item list (e.g. "This deck" / "All chats", or pinned/recent). */
|
|
45
|
+
sections?: ChatHistorySection[];
|
|
46
|
+
/** Id of the currently active/selected item — highlighted in the list. */
|
|
47
|
+
activeId?: string | null;
|
|
48
|
+
onSelect: (id: string) => void;
|
|
49
|
+
/** Fired on double-click / explicit "open" gesture, distinct from select. */
|
|
50
|
+
onOpen?: (id: string) => void;
|
|
51
|
+
/** Presence enables the pin/unpin row action. */
|
|
52
|
+
onTogglePin?: (id: string) => void;
|
|
53
|
+
/** Presence enables the inline "Rename" row action. */
|
|
54
|
+
onRename?: (id: string, nextTitle: string) => void;
|
|
55
|
+
/** Presence enables the "Delete" row action. */
|
|
56
|
+
onDelete?: (id: string) => void;
|
|
57
|
+
/** Escape hatch: fully custom row action menu content, replacing the
|
|
58
|
+
* built-in rename/pin/delete items. Still gated by the trigger button,
|
|
59
|
+
* which shows whenever this or any of the on* callbacks above is set. */
|
|
60
|
+
renderRowActions?: (item: ChatHistoryItem) => React.ReactNode;
|
|
61
|
+
|
|
62
|
+
/** Controlled search input. Omit `onSearchChange` to hide the search box
|
|
63
|
+
* entirely and let the host render its own (results are always supplied
|
|
64
|
+
* by the caller via `items`/`sections` either way). */
|
|
65
|
+
searchValue?: string;
|
|
66
|
+
onSearchChange?: (value: string) => void;
|
|
67
|
+
searchPlaceholder?: string;
|
|
68
|
+
searchInputRef?: React.Ref<HTMLInputElement>;
|
|
69
|
+
|
|
70
|
+
/** True while results are being fetched — shows `loadingLabel` instead of
|
|
71
|
+
* the list. */
|
|
72
|
+
loading?: boolean;
|
|
73
|
+
loadingLabel?: React.ReactNode;
|
|
74
|
+
/** Shown instead of the list when set (takes priority over `loading`). */
|
|
75
|
+
error?: React.ReactNode;
|
|
76
|
+
emptyLabel?: React.ReactNode;
|
|
77
|
+
/** Shown instead of `emptyLabel` when `searchValue` is non-empty. */
|
|
78
|
+
emptySearchLabel?: React.ReactNode;
|
|
79
|
+
|
|
80
|
+
/** Rendered after the list, inside the scroll container (e.g. a
|
|
81
|
+
* "Load older chats" button). */
|
|
82
|
+
footer?: React.ReactNode;
|
|
83
|
+
|
|
84
|
+
/** "popover" (default) matches core's compact HistoryPopover list.
|
|
85
|
+
* "rail" is a denser variant with a solid active background, closer to
|
|
86
|
+
* a sidebar run rail. */
|
|
87
|
+
variant?: "popover" | "rail";
|
|
88
|
+
className?: string;
|
|
89
|
+
listClassName?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Presentational chat history list shared by core's `HistoryPopover`
|
|
94
|
+
* (MultiTabAssistantChat.tsx) and Agent-Native Code's run rail
|
|
95
|
+
* (code-agents-ui). Styling is driven by stable `an-chat-history*` class
|
|
96
|
+
* names (see `styles/chat-history-list.css`) rather than Tailwind utilities
|
|
97
|
+
* so the same component renders correctly in a Tailwind host and in
|
|
98
|
+
* code-agents-ui's plain-CSS host.
|
|
99
|
+
*
|
|
100
|
+
* This component owns no data: search filtering, pin/rename persistence,
|
|
101
|
+
* and time formatting all stay with the caller. It only renders whatever
|
|
102
|
+
* `items`/`sections` it is given, plus the optional search box and
|
|
103
|
+
* loading/empty/error states.
|
|
104
|
+
*/
|
|
105
|
+
export function ChatHistoryList({
|
|
106
|
+
items,
|
|
107
|
+
sections,
|
|
108
|
+
activeId = null,
|
|
109
|
+
onSelect,
|
|
110
|
+
onOpen,
|
|
111
|
+
onTogglePin,
|
|
112
|
+
onRename,
|
|
113
|
+
onDelete,
|
|
114
|
+
renderRowActions,
|
|
115
|
+
searchValue,
|
|
116
|
+
onSearchChange,
|
|
117
|
+
searchPlaceholder = "Search chats...",
|
|
118
|
+
searchInputRef,
|
|
119
|
+
loading = false,
|
|
120
|
+
loadingLabel = "Searching...",
|
|
121
|
+
error,
|
|
122
|
+
emptyLabel = "No chats yet",
|
|
123
|
+
emptySearchLabel = "No matching chats",
|
|
124
|
+
footer,
|
|
125
|
+
variant = "popover",
|
|
126
|
+
className,
|
|
127
|
+
listClassName,
|
|
128
|
+
}: ChatHistoryListProps) {
|
|
129
|
+
const resolvedSections: ChatHistorySection[] =
|
|
130
|
+
sections ?? (items ? [{ id: "default", items }] : []);
|
|
131
|
+
const totalCount = resolvedSections.reduce(
|
|
132
|
+
(sum, section) => sum + section.items.length,
|
|
133
|
+
0,
|
|
134
|
+
);
|
|
135
|
+
const hasSearchValue = Boolean(searchValue?.trim());
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<div
|
|
139
|
+
className={cn(
|
|
140
|
+
"an-chat-history",
|
|
141
|
+
variant === "rail" && "an-chat-history--rail",
|
|
142
|
+
className,
|
|
143
|
+
)}
|
|
144
|
+
data-agent-native="chat-history-list"
|
|
145
|
+
>
|
|
146
|
+
{onSearchChange && (
|
|
147
|
+
<div className="an-chat-history__search">
|
|
148
|
+
<IconSearch size={13} className="an-chat-history__search-icon" />
|
|
149
|
+
<input
|
|
150
|
+
ref={searchInputRef}
|
|
151
|
+
type="text"
|
|
152
|
+
value={searchValue ?? ""}
|
|
153
|
+
onChange={(event) => onSearchChange(event.target.value)}
|
|
154
|
+
placeholder={searchPlaceholder}
|
|
155
|
+
aria-label={searchPlaceholder}
|
|
156
|
+
className="an-chat-history__search-input"
|
|
157
|
+
/>
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
<div className={cn("an-chat-history__list", listClassName)}>
|
|
161
|
+
{error ? (
|
|
162
|
+
<div className="an-chat-history__state an-chat-history__state--error">
|
|
163
|
+
{error}
|
|
164
|
+
</div>
|
|
165
|
+
) : loading ? (
|
|
166
|
+
<div className="an-chat-history__state">{loadingLabel}</div>
|
|
167
|
+
) : totalCount === 0 ? (
|
|
168
|
+
<div className="an-chat-history__state">
|
|
169
|
+
{hasSearchValue ? emptySearchLabel : emptyLabel}
|
|
170
|
+
</div>
|
|
171
|
+
) : (
|
|
172
|
+
resolvedSections.map(
|
|
173
|
+
(section) =>
|
|
174
|
+
section.items.length > 0 && (
|
|
175
|
+
<div key={section.id} className="an-chat-history__section">
|
|
176
|
+
{section.label && (
|
|
177
|
+
<div className="an-chat-history__section-label">
|
|
178
|
+
{section.label}
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
{section.items.map((item) => (
|
|
182
|
+
<ChatHistoryRow
|
|
183
|
+
key={item.id}
|
|
184
|
+
item={item}
|
|
185
|
+
active={item.id === activeId}
|
|
186
|
+
onSelect={onSelect}
|
|
187
|
+
onOpen={onOpen}
|
|
188
|
+
onTogglePin={onTogglePin}
|
|
189
|
+
onRename={onRename}
|
|
190
|
+
onDelete={onDelete}
|
|
191
|
+
renderRowActions={renderRowActions}
|
|
192
|
+
/>
|
|
193
|
+
))}
|
|
194
|
+
</div>
|
|
195
|
+
),
|
|
196
|
+
)
|
|
197
|
+
)}
|
|
198
|
+
{footer}
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function ChatHistoryRow({
|
|
205
|
+
item,
|
|
206
|
+
active,
|
|
207
|
+
onSelect,
|
|
208
|
+
onOpen,
|
|
209
|
+
onTogglePin,
|
|
210
|
+
onRename,
|
|
211
|
+
onDelete,
|
|
212
|
+
renderRowActions,
|
|
213
|
+
}: {
|
|
214
|
+
item: ChatHistoryItem;
|
|
215
|
+
active: boolean;
|
|
216
|
+
onSelect: (id: string) => void;
|
|
217
|
+
onOpen?: (id: string) => void;
|
|
218
|
+
onTogglePin?: (id: string) => void;
|
|
219
|
+
onRename?: (id: string, nextTitle: string) => void;
|
|
220
|
+
onDelete?: (id: string) => void;
|
|
221
|
+
renderRowActions?: (item: ChatHistoryItem) => React.ReactNode;
|
|
222
|
+
}) {
|
|
223
|
+
const [isRenaming, setIsRenaming] = useState(false);
|
|
224
|
+
const [draftTitle, setDraftTitle] = useState("");
|
|
225
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
226
|
+
const renameInputRef = useRef<HTMLInputElement | null>(null);
|
|
227
|
+
const menuRef = useRef<HTMLDivElement | null>(null);
|
|
228
|
+
|
|
229
|
+
const hasMenu = Boolean(
|
|
230
|
+
onTogglePin || onRename || onDelete || renderRowActions,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
if (!menuOpen) return;
|
|
235
|
+
function handlePointerDown(event: PointerEvent) {
|
|
236
|
+
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
|
237
|
+
setMenuOpen(false);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
241
|
+
if (event.key === "Escape") setMenuOpen(false);
|
|
242
|
+
}
|
|
243
|
+
document.addEventListener("pointerdown", handlePointerDown);
|
|
244
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
245
|
+
return () => {
|
|
246
|
+
document.removeEventListener("pointerdown", handlePointerDown);
|
|
247
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
248
|
+
};
|
|
249
|
+
}, [menuOpen]);
|
|
250
|
+
|
|
251
|
+
function startRename() {
|
|
252
|
+
setDraftTitle(item.titleText ?? "");
|
|
253
|
+
setIsRenaming(true);
|
|
254
|
+
setMenuOpen(false);
|
|
255
|
+
window.requestAnimationFrame(() => {
|
|
256
|
+
renameInputRef.current?.select();
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function commitRename() {
|
|
261
|
+
const trimmed = draftTitle.trim();
|
|
262
|
+
setIsRenaming(false);
|
|
263
|
+
if (trimmed && trimmed !== item.titleText) {
|
|
264
|
+
onRename?.(item.id, trimmed);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function handleRenameKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
|
|
269
|
+
if (event.key === "Enter") {
|
|
270
|
+
event.preventDefault();
|
|
271
|
+
commitRename();
|
|
272
|
+
} else if (event.key === "Escape") {
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
setIsRenaming(false);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return (
|
|
279
|
+
<div
|
|
280
|
+
className={cn(
|
|
281
|
+
"an-chat-history-row",
|
|
282
|
+
active && "an-chat-history-row--active",
|
|
283
|
+
item.pinned && "an-chat-history-row--pinned",
|
|
284
|
+
isRenaming && "an-chat-history-row--renaming",
|
|
285
|
+
item.disabled && "an-chat-history-row--disabled",
|
|
286
|
+
)}
|
|
287
|
+
>
|
|
288
|
+
{isRenaming ? (
|
|
289
|
+
<div className="an-chat-history-row__rename">
|
|
290
|
+
<input
|
|
291
|
+
ref={renameInputRef}
|
|
292
|
+
className="an-chat-history-row__rename-input"
|
|
293
|
+
value={draftTitle}
|
|
294
|
+
onChange={(event) => setDraftTitle(event.target.value)}
|
|
295
|
+
onKeyDown={handleRenameKeyDown}
|
|
296
|
+
onBlur={commitRename}
|
|
297
|
+
autoFocus
|
|
298
|
+
aria-label="Rename chat"
|
|
299
|
+
/>
|
|
300
|
+
</div>
|
|
301
|
+
) : (
|
|
302
|
+
<button
|
|
303
|
+
type="button"
|
|
304
|
+
className="an-chat-history-row__button"
|
|
305
|
+
onClick={() => !item.disabled && onSelect(item.id)}
|
|
306
|
+
onDoubleClick={() => !item.disabled && onOpen?.(item.id)}
|
|
307
|
+
disabled={item.disabled}
|
|
308
|
+
>
|
|
309
|
+
<div className="an-chat-history-row__topline">
|
|
310
|
+
<span className="an-chat-history-row__title">{item.title}</span>
|
|
311
|
+
{item.timestamp != null && (
|
|
312
|
+
<span className="an-chat-history-row__timestamp">
|
|
313
|
+
{item.timestamp}
|
|
314
|
+
</span>
|
|
315
|
+
)}
|
|
316
|
+
</div>
|
|
317
|
+
{item.subtitle != null && (
|
|
318
|
+
<div className="an-chat-history-row__subtitle">{item.subtitle}</div>
|
|
319
|
+
)}
|
|
320
|
+
{item.detail != null && (
|
|
321
|
+
<div className="an-chat-history-row__detail">{item.detail}</div>
|
|
322
|
+
)}
|
|
323
|
+
</button>
|
|
324
|
+
)}
|
|
325
|
+
|
|
326
|
+
{!isRenaming && hasMenu && (
|
|
327
|
+
<div className="an-chat-history-row__menu" ref={menuRef}>
|
|
328
|
+
<button
|
|
329
|
+
type="button"
|
|
330
|
+
className={cn(
|
|
331
|
+
"an-chat-history-row__menu-trigger",
|
|
332
|
+
item.pinned && "an-chat-history-row__menu-trigger--pinned",
|
|
333
|
+
)}
|
|
334
|
+
aria-label="Chat options"
|
|
335
|
+
aria-haspopup="menu"
|
|
336
|
+
aria-expanded={menuOpen}
|
|
337
|
+
onClick={() => setMenuOpen((open) => !open)}
|
|
338
|
+
>
|
|
339
|
+
{item.pinned ? (
|
|
340
|
+
<IconPinned size={13} strokeWidth={1.8} />
|
|
341
|
+
) : (
|
|
342
|
+
<IconDots size={14} strokeWidth={1.8} />
|
|
343
|
+
)}
|
|
344
|
+
</button>
|
|
345
|
+
{menuOpen && (
|
|
346
|
+
<div className="an-chat-history-row__menu-content" role="menu">
|
|
347
|
+
{renderRowActions ? (
|
|
348
|
+
renderRowActions(item)
|
|
349
|
+
) : (
|
|
350
|
+
<>
|
|
351
|
+
{onRename && (
|
|
352
|
+
<button
|
|
353
|
+
type="button"
|
|
354
|
+
role="menuitem"
|
|
355
|
+
className="an-chat-history-row__menu-item"
|
|
356
|
+
onClick={startRename}
|
|
357
|
+
>
|
|
358
|
+
<IconPencil size={13} strokeWidth={1.8} />
|
|
359
|
+
<span>Rename</span>
|
|
360
|
+
</button>
|
|
361
|
+
)}
|
|
362
|
+
{onTogglePin && (
|
|
363
|
+
<button
|
|
364
|
+
type="button"
|
|
365
|
+
role="menuitem"
|
|
366
|
+
className="an-chat-history-row__menu-item"
|
|
367
|
+
onClick={() => {
|
|
368
|
+
setMenuOpen(false);
|
|
369
|
+
onTogglePin(item.id);
|
|
370
|
+
}}
|
|
371
|
+
>
|
|
372
|
+
{item.pinned ? (
|
|
373
|
+
<IconPinnedOff size={13} strokeWidth={1.8} />
|
|
374
|
+
) : (
|
|
375
|
+
<IconPinned size={13} strokeWidth={1.8} />
|
|
376
|
+
)}
|
|
377
|
+
<span>
|
|
378
|
+
{item.pinned ? "Unpin from top" : "Pin to top"}
|
|
379
|
+
</span>
|
|
380
|
+
</button>
|
|
381
|
+
)}
|
|
382
|
+
{onDelete && (
|
|
383
|
+
<button
|
|
384
|
+
type="button"
|
|
385
|
+
role="menuitem"
|
|
386
|
+
className="an-chat-history-row__menu-item an-chat-history-row__menu-item--danger"
|
|
387
|
+
onClick={() => {
|
|
388
|
+
setMenuOpen(false);
|
|
389
|
+
onDelete(item.id);
|
|
390
|
+
}}
|
|
391
|
+
>
|
|
392
|
+
<IconTrash size={13} strokeWidth={1.8} />
|
|
393
|
+
<span>Delete</span>
|
|
394
|
+
</button>
|
|
395
|
+
)}
|
|
396
|
+
</>
|
|
397
|
+
)}
|
|
398
|
+
</div>
|
|
399
|
+
)}
|
|
400
|
+
</div>
|
|
401
|
+
)}
|
|
402
|
+
</div>
|
|
403
|
+
);
|
|
404
|
+
}
|
|
@@ -151,5 +151,11 @@ export {
|
|
|
151
151
|
type ChatThreadShareState,
|
|
152
152
|
type UseChatThreadsOptions,
|
|
153
153
|
} from "../use-chat-threads.js";
|
|
154
|
+
export {
|
|
155
|
+
ChatHistoryList,
|
|
156
|
+
type ChatHistoryItem,
|
|
157
|
+
type ChatHistorySection,
|
|
158
|
+
type ChatHistoryListProps,
|
|
159
|
+
} from "./ChatHistoryList.js";
|
|
154
160
|
export * from "../composer/index.js";
|
|
155
161
|
export * from "../conversation/index.js";
|
|
@@ -67,11 +67,23 @@ export const ChatRunningContext = React.createContext(false);
|
|
|
67
67
|
* Human-in-the-loop approval bridge. `AssistantChatInner` provides a value that
|
|
68
68
|
* re-issues the turn approving a specific paused tool call (opt-in
|
|
69
69
|
* `needsApproval` actions). When null, the Approve button is not rendered.
|
|
70
|
-
* Deny
|
|
70
|
+
* Deny defaults to local-only (the action stays un-run) unless `onDeny` is
|
|
71
|
+
* provided, and "Always allow" only renders when `onAlwaysAllow` is provided
|
|
72
|
+
* — both are additive so existing action-approval consumers are unaffected.
|
|
71
73
|
*/
|
|
72
74
|
export type ApprovalContextValue = {
|
|
73
75
|
/** Re-issue the turn so the server runs the approved call. */
|
|
74
76
|
onApprove: (approvalKey: string) => void;
|
|
77
|
+
/**
|
|
78
|
+
* Optional host hook invoked in addition to the local "denied" state, e.g.
|
|
79
|
+
* so a Code session can also resolve its own pending approval as denied.
|
|
80
|
+
*/
|
|
81
|
+
onDeny?: (approvalKey: string) => void;
|
|
82
|
+
/**
|
|
83
|
+
* Optional host hook that persists this exact call so future occurrences
|
|
84
|
+
* skip the approval gate. When absent, no "Always allow" button renders.
|
|
85
|
+
*/
|
|
86
|
+
onAlwaysAllow?: (approvalKey: string) => void;
|
|
75
87
|
};
|
|
76
88
|
export const ApprovalContext = React.createContext<ApprovalContextValue | null>(
|
|
77
89
|
null,
|
|
@@ -476,7 +488,9 @@ function ApprovalAffordance({
|
|
|
476
488
|
</div>
|
|
477
489
|
);
|
|
478
490
|
}
|
|
479
|
-
// Deny
|
|
491
|
+
// Deny defaults to local-only (the action simply stays un-run). When the
|
|
492
|
+
// host also provided `onDeny` (e.g. a Code session resolving its own
|
|
493
|
+
// pending approval), it fires alongside the local state.
|
|
480
494
|
if (denied) {
|
|
481
495
|
return (
|
|
482
496
|
<div className="mt-1.5 text-xs text-muted-foreground">
|
|
@@ -506,9 +520,29 @@ function ApprovalAffordance({
|
|
|
506
520
|
Approve
|
|
507
521
|
</button>
|
|
508
522
|
)}
|
|
523
|
+
{ctx?.onAlwaysAllow && (
|
|
524
|
+
<button
|
|
525
|
+
type="button"
|
|
526
|
+
onClick={() => {
|
|
527
|
+
setApproved(true);
|
|
528
|
+
ctx.onAlwaysAllow?.(approval.approvalKey);
|
|
529
|
+
}}
|
|
530
|
+
title="Approve and always allow this exact command"
|
|
531
|
+
className={cn(
|
|
532
|
+
"inline-flex items-center gap-1 rounded-md border border-border px-2.5 py-1 text-xs font-medium transition-colors",
|
|
533
|
+
"text-foreground hover:bg-muted",
|
|
534
|
+
)}
|
|
535
|
+
>
|
|
536
|
+
<IconShieldCheck className="h-3.5 w-3.5" />
|
|
537
|
+
Always allow
|
|
538
|
+
</button>
|
|
539
|
+
)}
|
|
509
540
|
<button
|
|
510
541
|
type="button"
|
|
511
|
-
onClick={() =>
|
|
542
|
+
onClick={() => {
|
|
543
|
+
setDenied(true);
|
|
544
|
+
ctx?.onDeny?.(approval.approvalKey);
|
|
545
|
+
}}
|
|
512
546
|
className={cn(
|
|
513
547
|
"inline-flex items-center gap-1 rounded-md border border-border px-2.5 py-1 text-xs font-medium transition-colors",
|
|
514
548
|
"text-foreground hover:bg-muted",
|
|
@@ -61,7 +61,14 @@ export interface CodeAgentChatController {
|
|
|
61
61
|
}): Promise<CodeAgentChatControlResult>;
|
|
62
62
|
control(input: {
|
|
63
63
|
runId: string;
|
|
64
|
-
|
|
64
|
+
/**
|
|
65
|
+
* `"approve"` resolves the run's current pending `needsApproval` gate
|
|
66
|
+
* (see `requestCodeAgentApproval` in `cli/code-agent-executor.ts`) — the
|
|
67
|
+
* same effect as the host UI's "Approve" control. Deny / always-allow are
|
|
68
|
+
* intentionally NOT routed through this method; hosts wire those directly
|
|
69
|
+
* via `AssistantChatProps.approvalActions` instead (see CodeAgentsApp).
|
|
70
|
+
*/
|
|
71
|
+
command: "stop" | "approve";
|
|
65
72
|
}): Promise<CodeAgentChatControlResult>;
|
|
66
73
|
}
|
|
67
74
|
|
|
@@ -101,13 +108,24 @@ export function createCodeAgentChatAdapter(
|
|
|
101
108
|
const stopOnAbort = options.stopOnAbort === true;
|
|
102
109
|
|
|
103
110
|
return {
|
|
104
|
-
async *run({ messages, abortSignal }: ChatModelRunOptions) {
|
|
111
|
+
async *run({ messages, abortSignal, runConfig }: ChatModelRunOptions) {
|
|
105
112
|
const runId = options.runIdRef.current;
|
|
106
113
|
if (!runId) {
|
|
107
114
|
yield errorResult("Select an Agent-Native Code session first.");
|
|
108
115
|
return;
|
|
109
116
|
}
|
|
110
117
|
|
|
118
|
+
// Human-in-the-loop: `ApprovalContext.onApprove` (tool-call-display.tsx)
|
|
119
|
+
// re-issues the turn carrying the pending approval's key in
|
|
120
|
+
// `approvedToolCalls`, the same mechanism regular SSE agent-chat
|
|
121
|
+
// approvals use. For Code sessions there is no server-side gate to
|
|
122
|
+
// notify — resolve the run's own pending approval directly instead of
|
|
123
|
+
// treating the accompanying "Approved..." text as a new prompt.
|
|
124
|
+
const approvedToolCalls = extractApprovedToolCalls(runConfig);
|
|
125
|
+
const isApprovalTurn = Boolean(
|
|
126
|
+
approvedToolCalls && approvedToolCalls.length > 0,
|
|
127
|
+
);
|
|
128
|
+
|
|
111
129
|
const lastUserMessage = latestUserMessage(messages);
|
|
112
130
|
const attachments = lastUserMessage
|
|
113
131
|
? extractPromptAttachmentsFromAssistantMessage(lastUserMessage)
|
|
@@ -115,7 +133,7 @@ export function createCodeAgentChatAdapter(
|
|
|
115
133
|
const prompt =
|
|
116
134
|
latestUserText(messages).trim() ||
|
|
117
135
|
(attachments.length > 0 ? "Use the attached context." : "");
|
|
118
|
-
if (!prompt.trim()) {
|
|
136
|
+
if (!isApprovalTurn && !prompt.trim()) {
|
|
119
137
|
yield errorResult("Enter a follow-up prompt.");
|
|
120
138
|
return;
|
|
121
139
|
}
|
|
@@ -138,7 +156,19 @@ export function createCodeAgentChatAdapter(
|
|
|
138
156
|
const seenEventIds = new Set(initialEvents.map((event) => event.id));
|
|
139
157
|
const tailedEvents: CodeAgentChatTranscriptEvent[] = [];
|
|
140
158
|
|
|
141
|
-
if (
|
|
159
|
+
if (isApprovalTurn) {
|
|
160
|
+
const response = await options.controller.control({
|
|
161
|
+
runId,
|
|
162
|
+
command: "approve",
|
|
163
|
+
});
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
yield errorResult(
|
|
166
|
+
response.error ?? response.message ?? "Could not approve.",
|
|
167
|
+
runId,
|
|
168
|
+
);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
} else if (!options.attachOnlyRef?.current) {
|
|
142
172
|
const beforeSendRun = await options.controller.get(runId);
|
|
143
173
|
const response = await options.controller.sendFollowUp({
|
|
144
174
|
runId,
|
|
@@ -218,6 +248,41 @@ export function createCodeAgentChatAdapter(
|
|
|
218
248
|
};
|
|
219
249
|
}
|
|
220
250
|
|
|
251
|
+
function extractApprovedToolCalls(
|
|
252
|
+
runConfig: ChatModelRunOptions["runConfig"] | undefined,
|
|
253
|
+
): string[] | undefined {
|
|
254
|
+
const raw =
|
|
255
|
+
runConfig?.custom &&
|
|
256
|
+
typeof runConfig.custom === "object" &&
|
|
257
|
+
"approvedToolCalls" in runConfig.custom
|
|
258
|
+
? (runConfig.custom as { approvedToolCalls?: unknown }).approvedToolCalls
|
|
259
|
+
: undefined;
|
|
260
|
+
if (!Array.isArray(raw)) return undefined;
|
|
261
|
+
const keys = raw.filter(
|
|
262
|
+
(key): key is string => typeof key === "string" && key.length > 0,
|
|
263
|
+
);
|
|
264
|
+
return keys.length > 0 ? keys : undefined;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Whether any tool-call in this Code transcript currently carries an
|
|
269
|
+
* unresolved approval (see `NormalizedCodeAgentToolEvent.pendingApprovalKey`).
|
|
270
|
+
* Hosts (e.g. `CodeAgentsApp`) use this to keep their standalone approval
|
|
271
|
+
* banner as a fallback only for transcripts where the inline
|
|
272
|
+
* `ApprovalAffordance` cannot be joined to a tool-call part, instead of
|
|
273
|
+
* showing both at once.
|
|
274
|
+
*/
|
|
275
|
+
export function codeAgentTranscriptHasPendingApproval(
|
|
276
|
+
events: readonly CodeAgentChatTranscriptEvent[],
|
|
277
|
+
): boolean {
|
|
278
|
+
const normalized = normalizeCodeAgentTranscript(
|
|
279
|
+
events.map(toCoreCodeAgentTranscriptEvent),
|
|
280
|
+
);
|
|
281
|
+
return normalized.items.some(
|
|
282
|
+
(item) => item.type === "tool" && Boolean(item.pendingApprovalKey),
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
221
286
|
export function codeAgentTranscriptEventsToContent(
|
|
222
287
|
events: readonly CodeAgentChatTranscriptEvent[],
|
|
223
288
|
): ContentPart[] {
|
|
@@ -377,6 +442,9 @@ function toolContentPartForCodeAgentTranscriptItem(
|
|
|
377
442
|
: {}),
|
|
378
443
|
...(item.mcpApp ? { mcpApp: item.mcpApp } : {}),
|
|
379
444
|
...(item.structuredMeta ? { structuredMeta: item.structuredMeta } : {}),
|
|
445
|
+
...(item.pendingApprovalKey
|
|
446
|
+
? { approval: { approvalKey: item.pendingApprovalKey } }
|
|
447
|
+
: {}),
|
|
380
448
|
};
|
|
381
449
|
}
|
|
382
450
|
|