@ykurilov/agent-chat-core 0.55.0 → 0.56.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,22 @@
1
1
  # @ykurilov/agent-chat-core
2
2
 
3
+ ## 0.56.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 24c313e: Новый пакет `@ykurilov/agent-chat-mcp`: серверы MCP, которые подключает админ приложения.
8
+
9
+ - `createMcpServers({ store, secretKey, isAdmin, proxy?, spending?, limits?, syncIntervalMs? })` → `mcp.tools(model, appTools?)`
10
+ для `agent.tools` (первым; при одинаковом имени побеждает инструмент приложения), `mcp.admin`, `mcp.reload()`, `mcp.sync()`, `mcp.close()`. «Только админы» — по `model.viewer.isAdmin`;
11
+ `isAdmin` (может быть асинхронной) — для экрана. Правка доходит до всех копий сервера по отметке версии в хранилище. Только удалённые серверы (Streamable HTTP, SSE). Инструменты — из
12
+ сохранённого снимка списка: запрос к модели стабилен, упавший сервер не ломает начало запроса. Соединение — на
13
+ проверенный адрес DNS; заголовки прокси — только на `proxy.origin`.
14
+ - `createMcpAdminHandler({ mcp, resolveUser, allowedOrigins })` — адреса `/api/mcp`, не админу 404.
15
+ - Хранилища: `createMemoryMcpStore`, `/sqlite` `createSqliteMcpStore`, `/postgres` `createPostgresMcpStore`; миграции сами.
16
+ - `/react`: `McpAdmin`, `useMcpAdmin`, договор скина `McpSkin` v1 (с формой правки `McpServerCardView.edit`), надписи ru/en. Скин Тандема: `./mcp` → `tandemMcpSkin`.
17
+ - Ядро: штатный вид расхода `mcp_tool`; `agent.viewer` — права человека раз в начале хода, полем `viewer` в
18
+ `contextBlocks` и `AgentToolsModel`. Песочница: права тестового чата — по владельцу чата. web-tools: наружу `dnsHostResolver`.
19
+
3
20
  ## 0.55.0
4
21
 
5
22
  ### Minor Changes
@@ -41,7 +41,8 @@ var BUILT_IN_USAGE_SOURCES = [
41
41
  "judge",
42
42
  "first_message",
43
43
  "voice_input",
44
- "voice_polish"
44
+ "voice_polish",
45
+ "mcp_tool"
45
46
  ];
46
47
 
47
48
  // src/conversation/branches.ts
@@ -2284,6 +2285,57 @@ function jsonBytes(value) {
2284
2285
  }
2285
2286
  }
2286
2287
 
2288
+ // src/agent-run/agent-viewer.ts
2289
+ var AgentViewerError = class extends Error {
2290
+ constructor(message, options2) {
2291
+ super(message, options2);
2292
+ this.name = "AgentViewerError";
2293
+ }
2294
+ };
2295
+ async function resolveAgentViewer(source, input) {
2296
+ input.abortSignal.throwIfAborted();
2297
+ let viewer;
2298
+ try {
2299
+ viewer = await Promise.race([
2300
+ Promise.resolve().then(() => source(input)),
2301
+ new Promise((_, reject) => input.abortSignal.addEventListener("abort", () => reject(input.abortSignal.reason), { once: true }))
2302
+ ]);
2303
+ } catch (error) {
2304
+ if (input.abortSignal.aborted) throw error;
2305
+ throw new AgentViewerError(`agent.viewer \u0431\u0440\u043E\u0441\u0438\u043B\u0430: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
2306
+ }
2307
+ if (typeof viewer !== "object" || viewer === null || Array.isArray(viewer)) throw new AgentViewerError("agent.viewer \u0432\u0435\u0440\u043D\u0443\u043B\u0430 \u043D\u0435 \u043E\u0431\u044A\u0435\u043A\u0442");
2308
+ return Object.freeze({ ...viewer });
2309
+ }
2310
+ function viewerInput(turn) {
2311
+ const ownerId = turn.ownerId ?? "";
2312
+ return {
2313
+ userId: turn.staff?.id ?? ownerId,
2314
+ chatId: turn.chatId,
2315
+ ownerId,
2316
+ messageId: turn.messageId,
2317
+ chat: structuredClone({ ...turn.chatData && { data: turn.chatData } }),
2318
+ ...turn.subject && { subject: structuredClone(turn.subject) },
2319
+ channel: turn.channel ?? "web",
2320
+ audience: turn.audience,
2321
+ ...turn.staff && { staff: structuredClone(turn.staff) },
2322
+ abortSignal: turn.abortSignal
2323
+ };
2324
+ }
2325
+ var VIEWER_OUTSIDE_TURN_TIMEOUT_MS = 1e4;
2326
+ async function resolveViewerOutsideTurn(source, turn, signal) {
2327
+ const deadline = new AbortController();
2328
+ const timer = setTimeout(
2329
+ () => deadline.abort(new AgentViewerError(`agent.viewer \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B\u0430 \u0437\u0430 ${VIEWER_OUTSIDE_TURN_TIMEOUT_MS / 1e3} \u0441`)),
2330
+ VIEWER_OUTSIDE_TURN_TIMEOUT_MS
2331
+ );
2332
+ try {
2333
+ return await resolveAgentViewer(source, viewerInput({ ...turn, abortSignal: signal ? AbortSignal.any([signal, deadline.signal]) : deadline.signal }));
2334
+ } finally {
2335
+ clearTimeout(timer);
2336
+ }
2337
+ }
2338
+
2287
2339
  // src/helpers/run-helper.ts
2288
2340
  import { generateText as generateText2, stepCountIs as stepCountIs2 } from "ai";
2289
2341
 
@@ -5436,6 +5488,7 @@ async function freezeTurnContext(input, turn, base, place) {
5436
5488
  channel: input.channel ?? "web",
5437
5489
  audience: input.group ? "group" : "private",
5438
5490
  ...input.staff && { staff: structuredClone(input.staff) },
5491
+ ...turn.viewer && { viewer: turn.viewer },
5439
5492
  modelId: input.modelId,
5440
5493
  providerId: input.providerId
5441
5494
  }
@@ -5577,10 +5630,12 @@ async function runTurn(turnInput, emitRaw) {
5577
5630
  let errorKind;
5578
5631
  let providerId = input.providerId;
5579
5632
  let answeredProviderId = input.providerId;
5633
+ let viewer;
5580
5634
  try {
5581
5635
  const { history: historySource } = input;
5582
5636
  const fullHistory = typeof historySource === "function" ? await historySource() : historySource;
5583
5637
  const branchState = input.appState ?? foldAppState(fullHistory);
5638
+ viewer = input.viewer && await resolveAgentViewer(input.viewer, viewerInput({ ...input, audience: input.group ? "group" : "private" }));
5584
5639
  const prepared = input.prepareTurn && await input.prepareTurn({ state: branchState, abortSignal: input.abortSignal });
5585
5640
  const turnNotices = prepared && prepared.notices.length > 0 ? prepared.notices : void 0;
5586
5641
  const stateBase = prepared?.state ?? branchState;
@@ -5596,10 +5651,11 @@ async function runTurn(turnInput, emitRaw) {
5596
5651
  const frozenOfTurn = await frozenContextForTurn(input, {
5597
5652
  history: answer.request().history,
5598
5653
  answer: resume?.message ?? fullHistory.find((message) => message.id === messageId),
5599
- state: publicAppState(appState.values())
5654
+ state: publicAppState(appState.values()),
5655
+ ...viewer && { viewer }
5600
5656
  });
5601
5657
  const frozen = turnNotices && frozenOfTurn.fresh ? { ...frozenOfTurn, data: { ...frozenOfTurn.data, notices: turnNotices } } : frozenOfTurn;
5602
- let resumeNote = resume && !resume.replay && !frozen.fresh ? await resumeNoteForTurn(input, { history: answer.request().history, answer: resume.message, state: publicAppState(appState.values()) }) : void 0;
5658
+ let resumeNote = resume && !resume.replay && !frozen.fresh ? await resumeNoteForTurn(input, { history: answer.request().history, answer: resume.message, state: publicAppState(appState.values()), ...viewer && { viewer } }) : void 0;
5603
5659
  if (resume && turnNotices && !frozen.fresh) {
5604
5660
  const seen = latestFrozenContextOf(resume.message.parts);
5605
5661
  if (seen) resumeNote = { ...resumeNote ?? { blocks: seen.blocks, sent: { changed: [], removed: [] }, base: messageId }, notices: turnNotices };
@@ -5629,7 +5685,7 @@ async function runTurn(turnInput, emitRaw) {
5629
5685
  if (resume && !resume.replay) {
5630
5686
  const continued = helpersToContinue(resume.message.parts);
5631
5687
  if (continued.length > 0) {
5632
- await continueHelpers(input, continued, { toolQueue, dataChanges, appEvents, chatClosure, appState, emit, logError });
5688
+ await continueHelpers(input, continued, { ...viewer && { viewer }, toolQueue, dataChanges, appEvents, chatClosure, appState, emit, logError });
5633
5689
  const parts2 = await saver.snapshot();
5634
5690
  answer.update(parts2, { modelId, providerId: answeredProviderId });
5635
5691
  mainLoopWaits = input.abortSignal.aborted || pendingHumanInput(parts2, input.waitingWidgets).length > 0;
@@ -5671,6 +5727,7 @@ async function runTurn(turnInput, emitRaw) {
5671
5727
  steps: { done: stepsDone, max: maxSteps, beforeResume: stepsBeforeResume },
5672
5728
  historyRewritten,
5673
5729
  frozenContext: frozen.data,
5730
+ ...viewer && { viewer },
5674
5731
  toolQueue,
5675
5732
  endTurns,
5676
5733
  browser,
@@ -5853,7 +5910,8 @@ async function runTurn(turnInput, emitRaw) {
5853
5910
  }
5854
5911
  } catch (error2) {
5855
5912
  if (!input.abortSignal.aborted) {
5856
- errorKind = classifyProviderError(error2, void 0);
5913
+ if (error2 instanceof AgentViewerError) logError(`[agent.viewer] \u0447\u0430\u0442=${input.chatId} \u0445\u043E\u0434 \u043D\u0435 \u043D\u0430\u0447\u0430\u0442: ${error2.message}`);
5914
+ errorKind = error2 instanceof AgentViewerError ? "other" : classifyProviderError(error2, void 0);
5857
5915
  errors = [{ type: "error", errorText: errorKind }];
5858
5916
  }
5859
5917
  }
@@ -6462,6 +6520,7 @@ function turnToolsModel(input, found, turn) {
6462
6520
  ...input.subject && { subject: input.subject },
6463
6521
  chat: { ...input.chatData && { data: input.chatData } },
6464
6522
  ...input.staff && { staff: input.staff },
6523
+ ...turn.viewer && { viewer: turn.viewer },
6465
6524
  ...input.closeChat && { closeChat: turn.chatClosure.request },
6466
6525
  // Инструменту — только чтение и запись: конец записи и служебные ключи — дело ядра.
6467
6526
  state: { get: turn.appState.get, set: turn.appState.set, unset: turn.appState.unset }
@@ -9717,7 +9776,7 @@ function createCompactionService(options2) {
9717
9776
  chatId,
9718
9777
  model,
9719
9778
  instructions: await instructionsOf(chat),
9720
- tools: resolveTools(model, chat, chatId, ownerId),
9779
+ tools: await resolveTools(model, chat, chatId, ownerId, branch, signal),
9721
9780
  ...options2.switchableTools && { switchableTools: options2.switchableTools },
9722
9781
  ...observation(ownerId, answerId),
9723
9782
  history,
@@ -9764,19 +9823,26 @@ function createCompactionService(options2) {
9764
9823
  function observation(ownerId, answerId) {
9765
9824
  return options2.turnObserver ? { observe: { observer: options2.turnObserver, ownerId, messageId: answerId ?? null } } : {};
9766
9825
  }
9767
- function resolveTools(model, chat, chatId, ownerId) {
9768
- const { tools } = options2.agent;
9826
+ async function resolveTools(model, chat, chatId, ownerId, branch, signal) {
9827
+ const { tools, viewer: viewerSource } = options2.agent;
9769
9828
  if (typeof tools !== "function") return tools;
9770
9829
  const subject = chat ? subjectOfChat(chat) : void 0;
9830
+ const chatData = chat ? chatDataOfInput(chat) : void 0;
9831
+ const { channel, group } = turnChannelOf(branch);
9832
+ const audience = group ? "group" : "private";
9833
+ const viewer = viewerSource && await resolveViewerOutsideTurn(viewerSource, { chatId, ownerId, messageId: "", ...chatData && { chatData }, ...subject && { subject }, channel, audience }, signal);
9771
9834
  return tools({
9772
9835
  profile: findModel(catalog, model.modelId, model.providerId).profile,
9773
9836
  providerId: model.providerId,
9774
9837
  chatId,
9775
9838
  ownerId,
9776
9839
  place: null,
9840
+ channel,
9841
+ audience,
9777
9842
  notifyDataChanged: (change) => logError(`\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442 \u0441\u043E\u043E\u0431\u0449\u0438\u043B \u043E\u0431 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0438 \u0434\u0430\u043D\u043D\u044B\u0445 \xAB${String(change?.tag)}\xBB \u0432\u043E \u0432\u0440\u0435\u043C\u044F \u0441\u0436\u0430\u0442\u0438\u044F \u0447\u0430\u0442\u0430 ${chatId}: \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E`),
9778
9843
  ...subject && { subject },
9779
- chat: { ...chat && chatDataOfInput(chat) && { data: chatDataOfInput(chat) } }
9844
+ chat: { ...chatData && { data: chatData } },
9845
+ ...viewer && { viewer }
9780
9846
  });
9781
9847
  }
9782
9848
  return {
@@ -9853,7 +9919,7 @@ function createCompactionService(options2) {
9853
9919
  chatId,
9854
9920
  model,
9855
9921
  instructions: await instructionsOf(chat),
9856
- tools: resolveTools(model, chat, chatId, ownerId),
9922
+ tools: await resolveTools(model, chat, chatId, ownerId, history, abortSignal),
9857
9923
  ...options2.switchableTools && { switchableTools: options2.switchableTools },
9858
9924
  ...observation(ownerId, answerId),
9859
9925
  history: [...history, { id: `${chatId}-answer-steps`, role: "assistant", parts: steps }],
@@ -12632,6 +12698,9 @@ function createChatService(input) {
12632
12698
  const choice = effectiveChoiceOfChat(options2.catalog, env, chat, defaultChoice);
12633
12699
  const found = findModel(options2.catalog, choice.modelId, choice.providerId);
12634
12700
  const subject = subjectOfChat(chat);
12701
+ const { channel, group } = turnChannelOf(await loadCurrentBranch(storage, chatId));
12702
+ const audience = group ? "group" : "private";
12703
+ const viewer = options2.agent.viewer && await resolveViewerOutsideTurn(options2.agent.viewer, { chatId, ownerId, messageId: "", chatData: chatDataOfInput(chat), subject, channel, audience });
12635
12704
  const model = {
12636
12705
  profile: found.profile,
12637
12706
  providerId: found.provider.id,
@@ -12641,11 +12710,12 @@ function createChatService(input) {
12641
12710
  // Вкладки у фоновой задачи нет: сообщать об изменённых данных некому.
12642
12711
  notifyDataChanged: () => {
12643
12712
  },
12644
- channel: "web",
12645
- audience: "private",
12713
+ channel,
12714
+ audience,
12646
12715
  messageId: "",
12647
12716
  ...subject && { subject },
12648
- chat: { ...chatDataOfInput(chat) && { data: chatDataOfInput(chat) } }
12717
+ chat: { ...chatDataOfInput(chat) && { data: chatDataOfInput(chat) } },
12718
+ ...viewer && { viewer }
12649
12719
  };
12650
12720
  return helperToolsModel(model, {
12651
12721
  storage,
@@ -12856,6 +12926,7 @@ export {
12856
12926
  ChatClosedError,
12857
12927
  isScreenPlaceInput,
12858
12928
  isTabId,
12929
+ AgentViewerError,
12859
12930
  runHelper,
12860
12931
  MAX_SERVER_TURNS_IN_ROW,
12861
12932
  createChatEvents,
@@ -12928,4 +12999,4 @@ export {
12928
12999
  InvalidWidgetAnswerError,
12929
13000
  createChatService
12930
13001
  };
12931
- //# sourceMappingURL=chunk-PWW3R3VL.js.map
13002
+ //# sourceMappingURL=chunk-7M7M7DAN.js.map