@ykurilov/agent-chat-react 0.63.2 → 0.65.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @ykurilov/agent-chat-react
2
2
 
3
+ ## 0.65.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 814ee7f: Пометка «Агент написал первым — человек молчал 1 ч 7 мин» перед ответом, который агент написал сам. Ядро: право чата `appNotices` (по умолчанию выключено) — служебные пометки приложения (`kind: "app"`) уходят в историю без текста для модели и только через белый список `appNoticeViews` (имя → безопасные данные для экрана; без функции пометка не уходит); `modelDetails: false` выключает и `appNotices`. React: `noticeLines` у `AgentChat` (имя пометки → строка), договор скина 27 — необязательный `ServiceLine`. Сценарии: в данных пометки `silentMs` (от последнего сообщения человека) и `exit`, проекция для экрана регистрируется в `withChat`, готовые строки `flowNoticeLines()` с подписями `russianFlowNoticeLabels`/`englishFlowNoticeLabels` (`FlowLabels.notices`); `FlowTestChat` передаёт их в `renderChat(chatId, { noticeLines })`. Тандем рисует `ServiceLine`. Как подключить — CHANGES.md, раздел 0.65.0.
8
+
9
+ ## 0.64.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 99cded9: Подробности сбоя провайдера для админа. Ответ, упавший на провайдере, хранит `error.details`: провайдер, модель, код, причина одной строкой (до 300 знаков; ключи окружения, `Bearer`, значения `key=`/`token`/`api-key`/`password`, маски ключей со `*`, параметры адресов, `org-…`/`proj_…` и e-mail вычищены; модерация может повторять текст человека) и `resetsAt` — когда провайдер снимет предел (OpenRouter «resets 2026-11-01»; только будущее до двух лет, без пояса — UTC). В браузер подробности уходят только по новому праву `chatCapabilities` → `errorDetails: true` (по умолчанию `false`, при `modelDetails: false` тоже `false`) — в истории, потоке и при переподключении; остальным — только `error.kind`. `afterTurn` получает их всегда. Экран: подпись-функция `labels.errorText(error, plain)`, без неё или если она бросила исключение — прежний текст `labels.errors[kind]`; типы `ChatError`, `ChatErrorDetails`, помощник `customErrorText`. Хранилища: тип колонки `error` — `ChatError`, миграций нет.
14
+
3
15
  ## 0.63.2
4
16
 
5
17
  No changes in this release.
package/README.md CHANGED
@@ -39,6 +39,11 @@ import { AgentChat, ChatLabelsProvider, russianLabels } from "@ykurilov/agent-ch
39
39
  необязательными `wide` (по ширине ответа со своей пропорцией) и `caption` (подпись под картинкой). Договор скина
40
40
  версии 26; скин версии 25 работает без правок: плитка ближайшей обычной пропорции без подписи —
41
41
  [docs/skin-v26.md](https://github.com/ykurilov/agent-chat-sdk/blob/main/docs/skin-v26.md).
42
+ - Служебные пометки приложения (`kind: "app"`) — серой строкой перед ответом агента: «Агент написал первым — человек
43
+ молчал 1 ч 7 мин». Нужны право чата `appNotices` на сервере (по умолчанию выключено), `noticeLines` у `AgentChat`
44
+ (имя пометки → строка; для сценариев — `flowNoticeLines()`) и `ServiceLine` у скина. Договор скина версии 27; скин
45
+ версии 26 работает без правок: пометок не видно —
46
+ [docs/skin-v27.md](https://github.com/ykurilov/agent-chat-sdk/blob/main/docs/skin-v27.md).
42
47
  - `ChatWorkspace` — список чатов и открытый чат вместе: создать, переименовать, удалить (нужен `chatList: true` на сервере). Хук — `useChatList`. Скину дополнительно нужны `ChatWorkspace` и `ChatList` (`ChatListSkin`, договор версии 7).
43
48
  - `AgentMemory` — экран долгой памяти агента: записи по разделам, добавить, изменить, удалить, «Стереть всю память» (нужна опция `memory` на сервере, пакет `@ykurilov/agent-chat-memory`). `api` — `createMemoryApi({ api: "/api/chat" })`, создавать один раз; `skin` — `MemorySkin` (`tandemMemorySkin`). Хук — `useMemoryNotes`. Без памяти на сервере экран пишет, что память не включена. Договор скина версии 11 — отдельный `MemorySkin`, `ChatSkin` не менялся ([contracts/memory.md](../../specs/017-long-term-memory/contracts/memory.md)).
44
49
  - Панель агента: вход `@ykurilov/agent-chat-react/panel`; основной вход его не подтягивает. `AgentPanelProvider` ставится в корневой макет выше страниц (`api`, `userKey`, `browserTools`, `mode` — `overlay` или `push`, `width`, `onDataChanged`, `fetch` — свой `fetch` только для списка чатов), `AgentPanel` рисует кнопку, рамку и чат через скин. Хуки: `useAgentPanel` (`open`, `openChat`, `openNewChat({ draft })`, `pageInset` — отступ страницы в режиме `push`, на экране ≤ 640 px он 0, `busy`), `useAgentPlace({ description, data? })` (где человек; модель видит только описание, `data` — только инструменты), `useAgentDataChanges(tag, handler)` (страница узнаёт, что инструмент изменил данные). Браузерные инструменты — функции в `browserTools` с теми же именами, что описания на сервере (`BrowserToolImplementations`); нет функции — модель получает «Инструмент недоступен на этой странице» (надпись `agentPanelToolUnavailable`). Подробно — [docs/getting-started.md](../../docs/getting-started.md#панель-агента-в-приложении-по-желанию). Общие части: `AgentAppBridge` (связь чата с приложением; `appBridge` в `AgentChat`, `useAgentChat`, `ChatWorkspace`), `initialDraft` (текст в поле ввода при открытии чата), `rememberKey` в `useChatList` (свой ключ запомненного чата). Договор скина версии 12 — отдельный `AgentPanelSkin` (`AgentPanelLauncher`, `AgentPanel`), надписи `agentPanel…` в словаре; `ChatSkin`, `ChatListSkin` и `MemorySkin` не менялись ([contracts/skin.md](../../specs/021-agent-panel/contracts/skin.md)).
@@ -125,6 +130,18 @@ import { AgentChat, ChatLabelsProvider, russianLabels } from "@ykurilov/agent-ch
125
130
  (`process.env.NODE_ENV !== "production"`) в консоли подсказка, какой адрес вписать.
126
131
  Состояние — `refusal: "signed_out" | "too_many_turns" | "forbidden_origin" | null` у `useAgentChat`; скин получает их в `ErrorNotice` как `kind`
127
132
  (текст — `labels.errors[kind]`).
133
+ Сбой ответа подписывается `labels.errors[kind]`. Своя подпись — функция `labels.errorText(error, plain)`: `error` — вид и,
134
+ если сервер их отдал (право `errorDetails`), подробности `details` (`providerId`, `modelId`, `statusCode`, `reason`,
135
+ `resetsAt`); `plain` — `errors[kind]`. Текст уходит скину в `ErrorNotice` как `text` и диктору. Функция бросила
136
+ исключение — подпись `plain` и одна запись в консоль. Например, админу:
137
+
138
+ ```tsx
139
+ const labels = {
140
+ ...russianLabels,
141
+ errorText: (error, plain) =>
142
+ error.details?.resetsAt ? `${plain}. Предел снимут ${new Date(error.details.resetsAt).toLocaleDateString("ru")}` : plain,
143
+ };
144
+ ```
128
145
  Ответ брошен упавшей копией сервера — экран сам перечитывает историю раз в 3 с и через 30 с показывает «прерван» с «Повторить»; обновлять страницу не нужно.
129
146
  Подробности — [contracts/liveness.md](../../specs/002-fast-feed-and-liveness/contracts/liveness.md).
130
147
 
@@ -214,6 +214,20 @@ function resetDate(moment, timeZone = "UTC", withTime = false) {
214
214
  var resetPhrase = (info) => info.resetsAt ? `, \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u0441\u044F ${resetDate(info.resetsAt, info.timeZone, info.period === "rolling")}` : "";
215
215
  var mergeDate = new Intl.DateTimeFormat("ru-RU", { day: "numeric", month: "long", hour: "2-digit", minute: "2-digit" });
216
216
  var DEFAULT_VOICE_LIMIT_SECONDS = 300;
217
+ var reportedErrorTextFailure = false;
218
+ function customErrorText(labels, error) {
219
+ if (!error || !labels.errorText) return void 0;
220
+ const plain = labels.errors[error.kind];
221
+ try {
222
+ return labels.errorText(error, plain);
223
+ } catch (failure) {
224
+ if (!reportedErrorTextFailure) {
225
+ reportedErrorTextFailure = true;
226
+ console.error("labels.errorText \u0443\u043F\u0430\u043B \u2014 \u043F\u043E\u043A\u0430\u0437\u0430\u043D\u0430 \u043E\u0431\u044B\u0447\u043D\u0430\u044F \u043F\u043E\u0434\u043F\u0438\u0441\u044C \u0441\u0431\u043E\u044F.", failure);
227
+ }
228
+ return plain;
229
+ }
230
+ }
217
231
  function voiceErrorText(errors, kind, details) {
218
232
  if (kind !== "time_limit") return errors[kind] ?? errors.other;
219
233
  const label = errors.time_limit;
@@ -2173,8 +2187,17 @@ function useBackgroundTasks({ chatUrl: chatUrl2, enabled, version, fetch: custom
2173
2187
 
2174
2188
  // src/notices/agent-notices.ts
2175
2189
  var AGENT_NOTICE_PART_TYPE = "data-agent-notice";
2190
+ var isAgentNoticePart = (part) => part.type === AGENT_NOTICE_PART_TYPE;
2176
2191
  function agentNoticesOf(message) {
2177
- return rawNoticesOf(message).filter((notice) => notice.kind !== STAFF_NOTICE_KIND);
2192
+ return rawNoticesOf(message).filter((notice) => notice.kind !== STAFF_NOTICE_KIND && notice.kind !== APP_NOTICE_KIND);
2193
+ }
2194
+ var APP_NOTICE_KIND = "app";
2195
+ function appNoticesOf(message) {
2196
+ return rawNoticesOf(message).flatMap((notice) => {
2197
+ if (notice.kind !== APP_NOTICE_KIND) return [];
2198
+ const { name, data } = notice;
2199
+ return typeof name === "string" ? [{ name, data }] : [];
2200
+ });
2178
2201
  }
2179
2202
  var STAFF_NOTICE_KIND = "staff";
2180
2203
  function staffRepliesOf(message) {
@@ -4079,7 +4102,7 @@ function announcementFor(messages, labels) {
4079
4102
  const answer = messages.findLast((message) => message.role === "assistant");
4080
4103
  if (!answer) return "";
4081
4104
  const status = answer.metadata?.status ?? "complete";
4082
- if (status === "error") return labels.errors[answer.metadata?.error?.kind ?? "other"];
4105
+ if (status === "error") return customErrorText(labels, answer.metadata?.error) ?? labels.errors[answer.metadata?.error?.kind ?? "other"];
4083
4106
  if (status === "stopped") return labels.answerStopped;
4084
4107
  if (status === "interrupted") return labels.answerInterrupted;
4085
4108
  if (status === "awaiting_approval") return widgetQuestionOf(answer, labels) ?? labels.approvalAnnouncement;
@@ -5776,7 +5799,7 @@ function SubjectAgentChat(props) {
5776
5799
  if (found.state === "failed") return /* @__PURE__ */ jsx9(props.skin.ErrorNotice, { kind: "other", onRetry: found.retry });
5777
5800
  return /* @__PURE__ */ jsx9(SubjectChatLoading, { skin: props.skin });
5778
5801
  }
5779
- function AgentChatBody({ api, chatId, skin, liveness, voice, voiceDialog, attachments: attachmentsPlugin, spendingLimits, onBusyChange, appBridge, initialDraft, answerEditing, onFeedChange, messageActions, onTurnFinished, onTurnRefused, composerActions, onAppEvent, onChatAccessChange, onStateChange, onModelChosen, onChatUnavailable, reloadKey, steering, widgets, diagrams, liveDemos, images, videos, appEvents, workView, stepPhrases }) {
5802
+ function AgentChatBody({ api, chatId, skin, liveness, voice, voiceDialog, attachments: attachmentsPlugin, spendingLimits, onBusyChange, appBridge, initialDraft, answerEditing, onFeedChange, messageActions, onTurnFinished, onTurnRefused, composerActions, onAppEvent, onChatAccessChange, onStateChange, onModelChosen, onChatUnavailable, reloadKey, steering, widgets, diagrams, liveDemos, images, videos, appEvents, workView, stepPhrases, noticeLines }) {
5780
5803
  const spendingNoticeId = useId2();
5781
5804
  const closedNoticeId = useId2();
5782
5805
  const chat = useAgentChat({
@@ -5817,9 +5840,10 @@ function AgentChatBody({ api, chatId, skin, liveness, voice, voiceDialog, attach
5817
5840
  ...videos && { videos },
5818
5841
  ...appEvents && { appEvents },
5819
5842
  ...workView && { workView },
5820
- ...stepPhrases && { stepPhrases }
5843
+ ...stepPhrases && { stepPhrases },
5844
+ ...noticeLines && { noticeLines }
5821
5845
  }),
5822
- [widgets, diagrams, liveDemos, imageClients, videos, appEvents, workView, stepPhrases]
5846
+ [widgets, diagrams, liveDemos, imageClients, videos, appEvents, workView, stepPhrases, noticeLines]
5823
5847
  );
5824
5848
  const scrollRef = useRef17(null);
5825
5849
  const feedRef = useRef17(null);
@@ -5985,7 +6009,10 @@ ${text}` : text),
5985
6009
  const NoticeView = skin.AgentNoticeView;
5986
6010
  const notices2 = agentNoticesOf(message);
5987
6011
  const staffReplies = staffRepliesOf(message);
5988
- const shown = staffReplies.length > 0 ? /* @__PURE__ */ jsx9(StaffReplies, { skin, replies: staffReplies, label: labels.notices.staffSaid }) : notices2.length > 0 ? NoticeView ? /* @__PURE__ */ jsx9(Fragment2, { children: notices2.map((notice2, noticeIndex) => /* @__PURE__ */ jsx9(NoticeView, { ...noticeViewOf(notice2, noticeControls) }, noticeIndex)) }) : (
6012
+ const appNotices = appNoticesOf(message);
6013
+ const serviceLines = appNotices.length > 0 && /* @__PURE__ */ jsx9(ServiceLines, { skin, notices: appNotices, lines: extensions.noticeLines });
6014
+ const onlyAppNotices = appNotices.length > 0 && notices2.length === 0 && staffReplies.length === 0 && message.parts.every(isAgentNoticePart);
6015
+ const content = onlyAppNotices ? /* @__PURE__ */ jsx9(Fragment2, {}) : staffReplies.length > 0 ? /* @__PURE__ */ jsx9(StaffReplies, { skin, replies: staffReplies, label: labels.notices.staffSaid }) : notices2.length > 0 ? NoticeView ? /* @__PURE__ */ jsx9(Fragment2, { children: notices2.map((notice2, noticeIndex) => /* @__PURE__ */ jsx9(NoticeView, { ...noticeViewOf(notice2, noticeControls) }, noticeIndex)) }) : (
5989
6016
  // Скин без плашек (v16): короткая строка из словаря, не пустой пузырь; править служебное сообщение нельзя.
5990
6017
  /* @__PURE__ */ jsx9(skin.UserMessage, { text: noticesAsText(notices2, labels.notices), ...branchSwitcher && { branchSwitcher } })
5991
6018
  ) : message.role === "user" ? editingId === message.id ? /* @__PURE__ */ jsx9(
@@ -6058,6 +6085,10 @@ ${text}` : text),
6058
6085
  }
6059
6086
  }
6060
6087
  );
6088
+ const shown = serviceLines ? /* @__PURE__ */ jsxs(Fragment2, { children: [
6089
+ serviceLines,
6090
+ content
6091
+ ] }) : content;
6061
6092
  const markers = dividers.get(message.id);
6062
6093
  return markers ? /* @__PURE__ */ jsxs(Fragment2, { children: [
6063
6094
  shown,
@@ -6255,6 +6286,15 @@ var EDIT_REFUSAL_ERRORS = {
6255
6286
  // Текст сервера идёт в редактор как есть; надпись — на случай, если текста почему-то нет.
6256
6287
  server_refused: "rejected"
6257
6288
  };
6289
+ function ServiceLines({ skin, notices, lines }) {
6290
+ const View = skin.ServiceLine;
6291
+ if (!View || !lines) return null;
6292
+ const texts = notices.flatMap((notice) => {
6293
+ const text = Object.hasOwn(lines, notice.name) ? lines[notice.name](notice) : void 0;
6294
+ return text ? [{ name: notice.name, text }] : [];
6295
+ });
6296
+ return /* @__PURE__ */ jsx9(Fragment2, { children: texts.map(({ name, text }, index) => /* @__PURE__ */ jsx9(View, { text }, `${name}-${index}`)) });
6297
+ }
6258
6298
  function StaffReplies({ skin, replies, label }) {
6259
6299
  const View = skin.StaffMessage;
6260
6300
  return /* @__PURE__ */ jsx9(Fragment2, { children: replies.map(
@@ -6503,7 +6543,8 @@ var AgentAnswer = memo4(function AgentAnswer2({
6503
6543
  busy: runningActionIds?.includes(action.id) ?? false,
6504
6544
  onRun: () => onRunAction?.(action, message, text)
6505
6545
  }));
6506
- const notice = status === "error" ? /* @__PURE__ */ jsx9(skin.ErrorNotice, { kind: message.metadata?.error?.kind ?? "other", ...onRetry && { onRetry } }) : void 0;
6546
+ const errorText = status === "error" ? customErrorText(labels, message.metadata?.error) : void 0;
6547
+ const notice = status === "error" ? /* @__PURE__ */ jsx9(skin.ErrorNotice, { kind: message.metadata?.error?.kind ?? "other", ...errorText !== void 0 && { text: errorText }, ...onRetry && { onRetry } }) : void 0;
6507
6548
  return /* @__PURE__ */ jsx9(ThinkingClockProvider, { id: message.id, waiting: status === "streaming", children: /* @__PURE__ */ jsxs(
6508
6549
  skin.AgentMessage,
6509
6550
  {
@@ -7016,6 +7057,7 @@ export {
7016
7057
  splitVoiceTags,
7017
7058
  stripVoiceTags,
7018
7059
  voiceAnswerIds,
7060
+ customErrorText,
7019
7061
  voiceErrorText,
7020
7062
  englishWorkViewLabels,
7021
7063
  englishWorkLabels,
@@ -7042,4 +7084,4 @@ export {
7042
7084
  activeChatTitle,
7043
7085
  useWorkspaceListSync
7044
7086
  };
7045
- //# sourceMappingURL=chunk-Y5HL6THS.js.map
7087
+ //# sourceMappingURL=chunk-JLGBVDAR.js.map