@ykurilov/agent-chat-core 0.64.0 → 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 +6 -0
- package/README.md +1 -1
- package/dist/{chunk-EWEEYX6U.js → chunk-SA3DXJYB.js} +43 -10
- package/dist/chunk-SA3DXJYB.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/storage-contract.js +1 -1
- package/dist/types/access/chat-capabilities.d.ts +11 -7
- package/dist/types/app-state/state-messages.d.ts +10 -2
- package/dist/types/chat-service.d.ts +7 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
- package/dist/chunk-EWEEYX6U.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @ykurilov/agent-chat-core
|
|
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
|
+
|
|
3
9
|
## 0.64.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -82,7 +82,7 @@ import type { ChatStorage } from "@ykurilov/agent-chat-core";
|
|
|
82
82
|
- Данные чата без записи: `service.getChatData(userId, chatId)` — копия, проверки владения как у `updateChatData`.
|
|
83
83
|
- Проверка ответа виджета на сервере: `checkAnswer(answer, ctx)` у своего виджета и общая `agent.widgets.checkAnswer(call, answer, ctx)` (для всех виджетов без своей). Зовётся после схемы под замком чата. Исход: `undefined` — принять, `{ answer }` — принять с заменой, `{ refuse: "текст" }` — отказать (`WidgetAnswerRefusedError`, сервер — 409 `widget_answer_refused`, виджет остаётся открытым). `ctx` — `ownerId`, `chatId`, `messageId`, `toolName`, `toolCallId`, `input`, `subject?`, `chatData?`, `state` (записи ложатся в ответ вместе с результатом виджета, хук `onStateChanged` — с причиной `widgetAnswer`), `emitAppEvent` (событие приложения вместе с ответом виджета), `signal` (отмена по сроку или «Стопу»: приложение слушает его само). Срок — `agent.widgets.checkAnswerTimeoutMs` (5 000 мс, заметно меньше `requestTimeoutMs` страницы). Исключение — 500, ничего не записано, в журнале только имя ошибки; срок вышел — `WidgetAnswerCheckTimeoutError`, сервер — 504 `widget_check_timeout`. Чат закрыли во время проверки — `ChatClosedError`, ответ не пишется.
|
|
84
84
|
- Набор виджетов по чату: `agent.widgets.enabledFor({ chat: { id, ownerId, data?, subject? } })` → имена включённых. Выключенных модель не видит (нет в инструментах запроса), их прошлые вызовы идут модели текстом. Синхронная, те же данные — тот же набор; смена набора сбрасывает кэш модели. Неизвестное имя — строка в журнал, исключение — ошибка хода. Подробно — [docs/widgets.md](../../docs/widgets.md).
|
|
85
|
-
- Права человека в чате: `chatCapabilities({ userId, chat })` возвращает запреты (`edit`, `regenerate`, `retry`, `branches`, `modelChoice`, `answerEditing`, `chatList`, `compact`, `attachments`, `voice`, `reasoning`, `toolDetails`, `steer`, `modelDetails`, `errorDetails`; по умолчанию всё `true`, кроме `errorDetails
|
|
85
|
+
- Права человека в чате: `chatCapabilities({ userId, chat })` возвращает запреты (`edit`, `regenerate`, `retry`, `branches`, `modelChoice`, `answerEditing`, `chatList`, `compact`, `attachments`, `voice`, `reasoning`, `toolDetails`, `steer`, `modelDetails`, `errorDetails`, `appNotices`; по умолчанию всё `true`, кроме `errorDetails` и `appNotices` — служебных пометок приложения на экране, без текста для модели и только через белый список `appNoticeViews` (имя пометки → безопасные данные для экрана; без функции пометка не уходит); `modelDetails: false` выключает `modelChoice`, `errorDetails` и `appNotices`). Неизвестное право или не булево значение — `TypeError`. Запрещённое действие — `ActionForbiddenError(action)` до любой записи и до `beforeTurn`. За один вызов службы права спрашиваются не больше раза. История несёт `capabilities`. Вручную — `service.assertCapability(userId, chatId, capability)`.
|
|
86
86
|
- Скрытие частей ответа: `reasoning: false` — мысли модели, `toolDetails: false` — служебные инструменты не уходят в историю, поток и повтор кусков после переподключения; хранилище, модель и `afterTurn` получают всё. Виджеты, браузерные инструменты, вызовы с запросом подтверждения и инструменты с `metadata: { showOnScreen: true }` в описании видны всегда. `modelDetails: false` убирает у чата истории `ownerId`, `modelId`, `providerId`, `reasoning`, у сообщений — `metadata.modelId`, `providerId`, `usage`, у частей и кусков — `providerMetadata`/`callProviderMetadata`/`resultProviderMetadata`, у `data-step-reset` — провайдера; отметки сжатия идут без текста сводки (`summary: ""`, `summaryTokens: 0`) в истории и `getCompactions`. Подробности сбоя провайдера (`metadata.error.details`: `providerId`, `modelId`, `statusCode`, `reason` — одна строка до 300 знаков без секретов, параметров адресов, `org-…`/`proj_…` и e-mail, `resetsAt` — ISO, только будущее не дальше двух лет) хранятся всегда, а в историю и поток идут только при `errorDetails: true` (например, админу); без права — только `error.kind`. Ответ модерации в `reason` может повторять текст человека — отдавайте право только тому, кому этот текст можно видеть. Меню моделей — опция `modelMenu({ userId })`: `false` — `getModelMenu(userId)` бросает `ActionForbiddenError("modelDetails")`. История несёт `modelAcceptsImages` (видит ли модель чата картинки). Права — на того, кто открыл поток.
|
|
87
87
|
- Закрытый чат: `service.closeChat({ ownerId, chatId, code, message })` и `reopenChat({ ownerId, chatId })` (события `chat-closed`, `chat-reopened`), из инструмента — `model.closeChat({ code, message })`. Закрытие — запись `$closed` на ветке: из инструмента — в ответе сразу при вызове, из кода — служебной записью в конце ветки (идёт ход — после него). Правка, «Переделать», выбор ветки и правка ответа до точки закрытия открывают чат; закрыть навсегда — правами (`chatCapabilities` видит `chat.closed`). Закрытие побеждает паузу: ждущие подтверждения и виджеты закрываются отказом, как при «Стопе». В закрытом чате отправка, «Повторить», подтверждения и виджеты — `ChatClosedError`; служебные сообщения пишутся без хода. `listChats` отдаёт `closed` — копию закрытия текущей ветки (`setChatClosed`); права и история берут закрытие с ветки, не записавшуюся копию дописывает следующее открытие истории. Закрытие чатов 0.27 переносится в запись лениво, при первой правке или смене ветки.
|
|
88
88
|
- `beforeCreateChat({ userId, origin? })` — можно ли человеку завести новый чат (`createChat`): `{ code, message }` → `ChatCreationRefusedError`. Чаты объектов и импорт хук не спрашивают. У чата `closed?: { code, message }`, хранилище — необязательный `setChatClosed(chatId, closed | null)`.
|
|
@@ -1545,12 +1545,14 @@ function publicAppStateParts(parts, hidden = isReservedAppStateKey) {
|
|
|
1545
1545
|
return data === void 0 ? [] : [{ ...part, data }];
|
|
1546
1546
|
});
|
|
1547
1547
|
}
|
|
1548
|
-
function publicMessages(messages, hidden = isReservedAppStateKey) {
|
|
1548
|
+
function publicMessages(messages, hidden = isReservedAppStateKey, options2 = {}) {
|
|
1549
|
+
const views = options2.appNoticeViews;
|
|
1550
|
+
const toScreen = views ? (part) => appNoticesForScreen(part, views) : withoutAppNotices;
|
|
1549
1551
|
const shown = [];
|
|
1550
1552
|
let waiting = [];
|
|
1551
1553
|
for (const message of messages) {
|
|
1552
1554
|
const hasService = message.parts.some((part) => part.type === APP_STATE_PART_TYPE || isModelOnlyPart(part) || hasAppNotice(part));
|
|
1553
|
-
const parts = hasService ? publicAppStateParts(message.parts.filter((part) => !isModelOnlyPart(part)).flatMap(
|
|
1555
|
+
const parts = hasService ? publicAppStateParts(message.parts.filter((part) => !isModelOnlyPart(part)).flatMap(toScreen), hidden) : [...message.parts];
|
|
1554
1556
|
if (message.role === "user" && parts.length === 0) continue;
|
|
1555
1557
|
const visible = hasService ? { ...message, parts } : message;
|
|
1556
1558
|
if (isStateMessage(message)) {
|
|
@@ -1574,6 +1576,24 @@ function withoutAppNotices(part) {
|
|
|
1574
1576
|
const notices = noticesOfPart(part).filter((notice) => notice.kind !== "app");
|
|
1575
1577
|
return notices.length === 0 ? [] : [{ ...part, data: { ...part.data, notices } }];
|
|
1576
1578
|
}
|
|
1579
|
+
function appNoticesForScreen(part, views) {
|
|
1580
|
+
if (part.type !== AGENT_NOTICE_PART_TYPE || !hasAppNotice(part)) return [part];
|
|
1581
|
+
const notices = noticesOfPart(part).flatMap((notice) => {
|
|
1582
|
+
if (notice.kind !== "app") return [notice];
|
|
1583
|
+
const data = screenDataOf(notice.name, notice.data, views);
|
|
1584
|
+
return data === void 0 ? [] : [{ kind: "app", name: notice.name, data }];
|
|
1585
|
+
});
|
|
1586
|
+
return notices.length === 0 ? [] : [{ ...part, data: { ...part.data, notices } }];
|
|
1587
|
+
}
|
|
1588
|
+
function screenDataOf(name, data, views) {
|
|
1589
|
+
const view = name !== void 0 && Object.hasOwn(views, name) ? views[name] : void 0;
|
|
1590
|
+
if (!view) return void 0;
|
|
1591
|
+
try {
|
|
1592
|
+
return view(data);
|
|
1593
|
+
} catch {
|
|
1594
|
+
return void 0;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1577
1597
|
function isModelOnlyPart(part) {
|
|
1578
1598
|
return part.type === FROZEN_CONTEXT_PART_TYPE || part.type === END_TURN_PART_TYPE;
|
|
1579
1599
|
}
|
|
@@ -9533,9 +9553,10 @@ var CHAT_CAPABILITIES = [
|
|
|
9533
9553
|
"toolDetails",
|
|
9534
9554
|
"steer",
|
|
9535
9555
|
"modelDetails",
|
|
9536
|
-
"errorDetails"
|
|
9556
|
+
"errorDetails",
|
|
9557
|
+
"appNotices"
|
|
9537
9558
|
];
|
|
9538
|
-
var OFF_BY_DEFAULT = /* @__PURE__ */ new Set(["errorDetails"]);
|
|
9559
|
+
var OFF_BY_DEFAULT = /* @__PURE__ */ new Set(["errorDetails", "appNotices"]);
|
|
9539
9560
|
function resolveChatCapabilities(given) {
|
|
9540
9561
|
const resolved = Object.fromEntries(CHAT_CAPABILITIES.map((capability) => [capability, !OFF_BY_DEFAULT.has(capability)]));
|
|
9541
9562
|
if (given === void 0 || given === null) return resolved;
|
|
@@ -9551,6 +9572,7 @@ function resolveChatCapabilities(given) {
|
|
|
9551
9572
|
if (!resolved.modelDetails) {
|
|
9552
9573
|
resolved.modelChoice = false;
|
|
9553
9574
|
resolved.errorDetails = false;
|
|
9575
|
+
resolved.appNotices = false;
|
|
9554
9576
|
}
|
|
9555
9577
|
return resolved;
|
|
9556
9578
|
}
|
|
@@ -12346,10 +12368,17 @@ function createChatService(input) {
|
|
|
12346
12368
|
return scope.capabilities;
|
|
12347
12369
|
}
|
|
12348
12370
|
let warnedErrorDetailsWithoutModel = false;
|
|
12371
|
+
let warnedAppNoticesWithoutModel = false;
|
|
12349
12372
|
function warnErrorDetailsWithoutModel(given) {
|
|
12350
|
-
if (
|
|
12351
|
-
warnedErrorDetailsWithoutModel
|
|
12352
|
-
|
|
12373
|
+
if (given?.modelDetails !== false) return;
|
|
12374
|
+
if (!warnedErrorDetailsWithoutModel && given.errorDetails === true) {
|
|
12375
|
+
warnedErrorDetailsWithoutModel = true;
|
|
12376
|
+
logError("chatCapabilities: errorDetails: true \u043F\u0440\u0438 modelDetails: false \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u2014 \u043F\u043E\u0434\u0440\u043E\u0431\u043D\u043E\u0441\u0442\u0438 \u0441\u0431\u043E\u044F \u043D\u0430\u0437\u044B\u0432\u0430\u044E\u0442 \u043C\u043E\u0434\u0435\u043B\u044C \u0438 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430.");
|
|
12377
|
+
}
|
|
12378
|
+
if (!warnedAppNoticesWithoutModel && given.appNotices === true) {
|
|
12379
|
+
warnedAppNoticesWithoutModel = true;
|
|
12380
|
+
logError("chatCapabilities: appNotices: true \u043F\u0440\u0438 modelDetails: false \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u2014 \u0441\u043B\u0443\u0436\u0435\u0431\u043D\u044B\u0435 \u043F\u043E\u043C\u0435\u0442\u043A\u0438 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u044E\u0442 \u0440\u0430\u0431\u043E\u0442\u0443 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F.");
|
|
12381
|
+
}
|
|
12353
12382
|
}
|
|
12354
12383
|
async function askCapabilities(userId, chat) {
|
|
12355
12384
|
if (!options2.chatCapabilities) return resolveChatCapabilities(void 0);
|
|
@@ -12559,6 +12588,7 @@ function createChatService(input) {
|
|
|
12559
12588
|
const pending = runningAt < 0 ? [] : branch.slice(runningAt + 1);
|
|
12560
12589
|
const capabilities = await capabilitiesOf(userId, withClosure(stored, closed), scope);
|
|
12561
12590
|
const visibility = clientVisibilityOf(capabilities, isScreenTool);
|
|
12591
|
+
const screenNotices = capabilities.appNotices && options2.appNoticeViews ? { appNoticeViews: options2.appNoticeViews } : {};
|
|
12562
12592
|
const undelivered = undeliveredIdsOf(branch);
|
|
12563
12593
|
const markedUndelivered = (message) => undelivered.has(message.id) ? { ...message, metadata: { ...message.metadata, undelivered: true } } : message;
|
|
12564
12594
|
const visible = (list) => visibility.showsEverything ? list : list.map((message) => visibility.visibleMessage(message));
|
|
@@ -12570,10 +12600,13 @@ function createChatService(input) {
|
|
|
12570
12600
|
messages: visible(
|
|
12571
12601
|
publicMessages(
|
|
12572
12602
|
shown.filter((message) => message.id !== runningAnswerId || resumeBases.get(chatId)?.id === runningAnswerId).map((message) => message.id === runningAnswerId ? resumeBases.get(chatId) : markedUndelivered(toAgentChatMessage(message))),
|
|
12573
|
-
hiddenStateKey
|
|
12603
|
+
hiddenStateKey,
|
|
12604
|
+
screenNotices
|
|
12574
12605
|
)
|
|
12575
12606
|
),
|
|
12576
|
-
...pending.length > 0 && {
|
|
12607
|
+
...pending.length > 0 && {
|
|
12608
|
+
pendingMessages: visible(publicMessages(pending.map(toAgentChatMessage), hiddenStateKey, screenNotices))
|
|
12609
|
+
},
|
|
12577
12610
|
// Ход передаётся новому ответу на поправки: он вот-вот пойдёт, поток подождёт его.
|
|
12578
12611
|
running: runningId !== null || handOffs.has(chatId),
|
|
12579
12612
|
detachedAnswer: isDetachedAnswer(answerBeforeCorrections(branch), runningId),
|
|
@@ -14421,4 +14454,4 @@ export {
|
|
|
14421
14454
|
InvalidWidgetAnswerError,
|
|
14422
14455
|
createChatService
|
|
14423
14456
|
};
|
|
14424
|
-
//# sourceMappingURL=chunk-
|
|
14457
|
+
//# sourceMappingURL=chunk-SA3DXJYB.js.map
|