@things-factory/ai-assistant 10.1.208 → 10.1.210
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/client/components/assistant-chat.ts +238 -9
- package/client/components/assistant-dock.ts +9 -4
- package/client/components/assistant-invite.ts +138 -0
- package/client/components/assistant-session-manager.ts +53 -12
- package/client/components/proposal-request.test.ts +10 -0
- package/client/components/roster-identity.test.ts +2 -12
- package/client/components/roster-identity.ts +0 -6
- package/client/utils/assistant-session-actions.ts +3 -1
- package/client/utils/assistant-session-admin.ts +9 -2
- package/client/utils/assistant-session-controller.ts +1 -1
- package/client/utils/assistant-typing.test.ts +33 -0
- package/client/utils/assistant-typing.ts +53 -0
- package/dist-client/components/assistant-chat.d.ts +28 -0
- package/dist-client/components/assistant-chat.js +259 -8
- package/dist-client/components/assistant-chat.js.map +1 -1
- package/dist-client/components/assistant-dock.js +12 -4
- package/dist-client/components/assistant-dock.js.map +1 -1
- package/dist-client/components/assistant-invite.d.ts +39 -0
- package/dist-client/components/assistant-invite.js +150 -0
- package/dist-client/components/assistant-invite.js.map +1 -0
- package/dist-client/components/assistant-session-manager.d.ts +5 -1
- package/dist-client/components/assistant-session-manager.js +40 -10
- package/dist-client/components/assistant-session-manager.js.map +1 -1
- package/dist-client/components/proposal-request.test.js +8 -0
- package/dist-client/components/proposal-request.test.js.map +1 -1
- package/dist-client/components/roster-identity.d.ts +0 -2
- package/dist-client/components/roster-identity.js +0 -7
- package/dist-client/components/roster-identity.js.map +1 -1
- package/dist-client/components/roster-identity.test.js +2 -13
- package/dist-client/components/roster-identity.test.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/assistant-session-actions.d.ts +1 -0
- package/dist-client/utils/assistant-session-actions.js +3 -1
- package/dist-client/utils/assistant-session-actions.js.map +1 -1
- package/dist-client/utils/assistant-session-admin.d.ts +5 -2
- package/dist-client/utils/assistant-session-admin.js +3 -0
- package/dist-client/utils/assistant-session-admin.js.map +1 -1
- package/dist-client/utils/assistant-session-controller.d.ts +1 -1
- package/dist-client/utils/assistant-session-controller.js.map +1 -1
- package/dist-client/utils/assistant-typing.d.ts +20 -0
- package/dist-client/utils/assistant-typing.js +48 -0
- package/dist-client/utils/assistant-typing.js.map +1 -0
- package/dist-client/utils/assistant-typing.test.d.ts +1 -0
- package/dist-client/utils/assistant-typing.test.js +30 -0
- package/dist-client/utils/assistant-typing.test.js.map +1 -0
- package/dist-server/service/assistant-chat-resolver.js +5 -12
- package/dist-server/service/assistant-chat-resolver.js.map +1 -1
- package/dist-server/service/assistant-mode/index.d.ts +1 -1
- package/dist-server/service/assistant-mode/index.js +2 -1
- package/dist-server/service/assistant-mode/index.js.map +1 -1
- package/dist-server/service/assistant-mode/mode-categories.js +9 -2
- package/dist-server/service/assistant-mode/mode-categories.js.map +1 -1
- package/dist-server/service/assistant-mode/screen-rules.d.ts +6 -0
- package/dist-server/service/assistant-mode/screen-rules.js +10 -3
- package/dist-server/service/assistant-mode/screen-rules.js.map +1 -1
- package/dist-server/service/chat-message/chat-message-room.js +3 -1
- package/dist-server/service/chat-message/chat-message-room.js.map +1 -1
- package/dist-server/service/chat-message/chat-typing-subscription.d.ts +10 -0
- package/dist-server/service/chat-message/chat-typing-subscription.js +68 -0
- package/dist-server/service/chat-message/chat-typing-subscription.js.map +1 -0
- package/dist-server/service/chat-message/chat-typing.d.ts +28 -0
- package/dist-server/service/chat-message/chat-typing.js +40 -0
- package/dist-server/service/chat-message/chat-typing.js.map +1 -0
- package/dist-server/service/chat-session/attachment-tool-result.d.ts +1 -0
- package/dist-server/service/chat-session/attachment-tool-result.js +6 -0
- package/dist-server/service/chat-session/attachment-tool-result.js.map +1 -1
- package/dist-server/service/chat-session/attachment-tools.js +2 -2
- package/dist-server/service/chat-session/attachment-tools.js.map +1 -1
- package/dist-server/service/chat-session/chat-session.d.ts +2 -1
- package/dist-server/service/chat-session/chat-session.js +1 -1
- package/dist-server/service/chat-session/chat-session.js.map +1 -1
- package/dist-server/service/chat-session/session-activity-broadcast.d.ts +12 -0
- package/dist-server/service/chat-session/session-activity-broadcast.js +15 -0
- package/dist-server/service/chat-session/session-activity-broadcast.js.map +1 -1
- package/dist-server/service/chat-session/session-activity-publish.d.ts +2 -0
- package/dist-server/service/chat-session/session-activity-publish.js +5 -2
- package/dist-server/service/chat-session/session-activity-publish.js.map +1 -1
- package/dist-server/service/chat-session/session-visibility.d.ts +17 -2
- package/dist-server/service/chat-session/session-visibility.js +28 -3
- package/dist-server/service/chat-session/session-visibility.js.map +1 -1
- package/dist-server/service/chat-session/session-write-gate.d.ts +1 -1
- package/dist-server/service/chat-session/session-write-gate.js +1 -1
- package/dist-server/service/chat-session/session-write-gate.js.map +1 -1
- package/dist-server/service/chat-session-participant/index.d.ts +1 -0
- package/dist-server/service/chat-session-participant/index.js +4 -1
- package/dist-server/service/chat-session-participant/index.js.map +1 -1
- package/dist-server/service/chat-session-participant/invite-notification.d.ts +16 -1
- package/dist-server/service/chat-session-participant/invite-notification.js +34 -5
- package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -1
- package/dist-server/service/chat-session-participant/mention-invite.d.ts +30 -4
- package/dist-server/service/chat-session-participant/mention-invite.js +78 -31
- package/dist-server/service/chat-session-participant/mention-invite.js.map +1 -1
- package/dist-server/service/chat-session-participant/read-roster.js +5 -2
- package/dist-server/service/chat-session-participant/read-roster.js.map +1 -1
- package/dist-server/service/chat-session-resolver.d.ts +27 -0
- package/dist-server/service/chat-session-resolver.js +226 -25
- package/dist-server/service/chat-session-resolver.js.map +1 -1
- package/dist-server/service/data-entry/entry-target.d.ts +6 -0
- package/dist-server/service/data-entry/entry-target.js.map +1 -1
- package/dist-server/service/data-entry/entry-tools.js +2 -2
- package/dist-server/service/data-entry/entry-tools.js.map +1 -1
- package/dist-server/service/data-entry/entry-update.js +1 -1
- package/dist-server/service/data-entry/entry-update.js.map +1 -1
- package/dist-server/service/data-read/list-read.d.ts +10 -0
- package/dist-server/service/data-read/list-read.js +12 -0
- package/dist-server/service/data-read/list-read.js.map +1 -1
- package/dist-server/service/data-read/read-target.d.ts +5 -0
- package/dist-server/service/data-read/read-target.js.map +1 -1
- package/dist-server/service/index.d.ts +4 -2
- package/dist-server/service/index.js +9 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/testing/conversation-script.d.ts +1 -1
- package/dist-server/service/testing/conversation-script.js.map +1 -1
- package/dist-server/service/testing/mode-conformance.js +2 -0
- package/dist-server/service/testing/mode-conformance.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/server/service/assistant-chat-resolver.ts +7 -12
- package/server/service/assistant-mode/index.ts +1 -1
- package/server/service/assistant-mode/mode-categories.ts +11 -3
- package/server/service/assistant-mode/mode-front.test.ts +53 -1
- package/server/service/assistant-mode/screen-rules.test.ts +7 -1
- package/server/service/assistant-mode/screen-rules.ts +11 -2
- package/server/service/chat-message/chat-message-room.test.ts +7 -2
- package/server/service/chat-message/chat-message-room.ts +3 -1
- package/server/service/chat-message/chat-typing-subscription.ts +52 -0
- package/server/service/chat-message/chat-typing.test.ts +45 -0
- package/server/service/chat-message/chat-typing.ts +48 -0
- package/server/service/chat-session/attachment-tool-result.test.ts +9 -1
- package/server/service/chat-session/attachment-tool-result.ts +7 -0
- package/server/service/chat-session/attachment-tools.ts +3 -3
- package/server/service/chat-session/chat-session.ts +3 -2
- package/server/service/chat-session/group-room.test.ts +233 -0
- package/server/service/chat-session/session-activity-broadcast.ts +20 -0
- package/server/service/chat-session/session-activity-publish.ts +6 -1
- package/server/service/chat-session/session-activity-receivers.test.ts +26 -0
- package/server/service/chat-session/session-visibility.test.ts +32 -1
- package/server/service/chat-session/session-visibility.ts +33 -4
- package/server/service/chat-session/session-write-gate.test.ts +7 -1
- package/server/service/chat-session/session-write-gate.ts +2 -2
- package/server/service/chat-session-participant/index.ts +2 -0
- package/server/service/chat-session-participant/invite-notification.test.ts +36 -1
- package/server/service/chat-session-participant/invite-notification.ts +43 -5
- package/server/service/chat-session-participant/mention-invite.test.ts +49 -1
- package/server/service/chat-session-participant/mention-invite.ts +82 -28
- package/server/service/chat-session-participant/read-roster.test.ts +13 -4
- package/server/service/chat-session-participant/read-roster.ts +6 -3
- package/server/service/chat-session-resolver.ts +204 -27
- package/server/service/data-entry/data-entry-update.test.ts +8 -0
- package/server/service/data-entry/entry-target.ts +6 -0
- package/server/service/data-entry/entry-tools.ts +2 -2
- package/server/service/data-entry/entry-update.ts +1 -1
- package/server/service/data-read/list-missing.test.ts +49 -0
- package/server/service/data-read/list-read.ts +18 -0
- package/server/service/data-read/read-target.ts +5 -0
- package/server/service/index.ts +6 -1
- package/server/service/testing/conversation-script.ts +1 -1
- package/server/service/testing/mode-conformance.ts +1 -0
- package/test/assistant-dock.test.ts +5 -1
- package/test/assistant-session-manager.test.ts +4 -2
- package/test/open-doors.test.ts +10 -5
- package/translations/en.json +29 -4
- package/translations/es.json +29 -4
- package/translations/ja.json +29 -4
- package/translations/ko.json +29 -4
- package/translations/ms.json +29 -4
- package/translations/zh.json +29 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant-chat-resolver.js","sourceRoot":"","sources":["../../server/service/assistant-chat-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The neutral door into a conversation.\n *\n * ── Why this exists (2026-09-11) ───────────────────────────────────────────────\n * The chat component moved here from `board-ai`, but the only mutation it could call stayed\n * behind. operato-figure mounted the dock, registered four tools, and got\n * `Cannot query field \"boardAIChat\" on type \"Mutation\"` — it does not load board-ai. The twin has\n * the same shape and it does not show: its space deliberation runs through board-ai's door purely\n * because the board authoring half of the same product already loaded that package.\n *\n * ── Why the board resolver was not moved ───────────────────────────────────────\n * `board-ai-resolver.ts` is 692 lines and the larger part of it is board authoring itself:\n * `currentBoard` as a BoardModel, PatchEntry, board patch broadcasts, refid injection for\n * component mentions. Moving that here would teach this package the board document, which is the\n * dependency we just cut, pointing the other way.\n *\n * So the door moved and the editor did not. `boardAIChat` stays in board-ai for board authoring;\n * this one is for a surface that asks questions and runs registered tools.\n *\n * ── What this is not ───────────────────────────────────────────────────────────\n * It has no document, so it takes no document and returns no patch. Tools that write to a\n * document are not offered here — `registryAssistantChat` enforces that, and it decides what may\n * be dispatched from the same list it offers, so the two cannot drift apart.\n */\nimport { Arg, Ctx, Directive, Field, InputType, Mutation, ObjectType, Resolver } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\nimport '@things-factory/auth-base'\n\nimport { getRepository, rethrowSpoken } from '@things-factory/shell'\nimport { aiClientFor, findToolSpec, openSubjectLine, OPEN_TOOLS, registryAssistantChat, type AgentClient } from '@things-factory/ai-client-base'\n\nimport { ChatSession } from './chat-session/chat-session.js'\nimport { assertStationSession } from './chat-session/station-session-guard.js'\nimport { ChatMessage } from './chat-message/chat-message.js'\nimport { buildLlmHistory } from './chat-message/llm-history.js'\nimport { offeredCardLines } from './chat-message/proposal-outcome.js'\nimport { splitByCap } from './chat-message/history-summary.js'\nimport { foldSessionHistory } from './chat-message/fold-session-history.js'\nimport { groundingSourcesFor } from './grounding-sources.js'\nimport { publishChatMessage } from './chat-message/chat-message-publish.js'\nimport { publishSessionActivity } from './chat-session/session-activity-publish.js'\nimport { activityPatch, autoSessionName } from './chat-session/session-inbox.js'\nimport { toolAccessFor } from './tool-access.js'\nimport { ChatSessionParticipant } from './chat-session-participant/chat-session-participant.js'\nimport { doorsForAnswer } from './chat-message/message-doors.js'\nimport { judgeSessionKind } from './chat-session/session-kind.js'\nimport { readerGate } from './chat-message/reader-gate.js'\nimport { inviteMentionedUsers } from './chat-session-participant/mention-invite.js'\nimport { buildInviteNotification } from './chat-session-participant/invite-notification.js'\nimport { isSessionParticipant } from './chat-session-participant/participant-lookup.js'\nimport { assertMayWriteSession } from './chat-session/session-write-gate.js'\nimport { attachmentHistoryLine, refuseAttachments, textCharsOf } from './chat-session/attachment-policy.js'\nimport { attachmentForModel, describeAttachment, findChatAttachment } from './chat-session/session-attachments.js'\nimport { readTurnAttachments } from './chat-session/turn-attachments.js'\nimport { getAssistantMode } from './assistant-mode/registry.js'\nimport { resolveModeSettings } from './assistant-mode/resolve.js'\nimport { modeFrontCategories } from './assistant-mode/mode-categories.js'\nimport { usedToolNames } from './chat-message/used-tools.js'\nimport { splitNextRequest } from './assistant-mode/screen-rules.js'\nimport { requestTranslator } from './request-translator.js'\n\n/**\n * How many stored messages reach the model.\n *\n * A deliberation runs for days. Without a cap the prompt grows without bound and the oldest turns\n * bury the recent ones. Kept the same as the board conversation's cap so the two surfaces of one\n * product do not remember differently.\n */\nconst LLM_HISTORY_MAX_TURNS = 40\n\n@InputType()\nclass AssistantMessageInput {\n @Field({ description: \"Role: 'user' or 'assistant'\" })\n role: string\n\n @Field({ description: 'Message content' })\n content: string\n}\n\n@InputType({ description: 'Which conversation this is, and what it is about.' })\nclass AssistantModeInput {\n @Field({ description: 'A mode kind the app server registered — `<app>.<surface>`, e.g. \"twin.space\".' })\n kind: string\n\n @Field({\n nullable: true,\n description:\n 'What this conversation is about — the space, the station, the model being edited. With `kind` it is the conversation\\'s lifetime: when either changes the screen closes the conversation and starts another.'\n })\n target?: string\n}\n\n@InputType({ description: 'One turn of a conversation that has no document of its own.' })\nclass AssistantChatInput {\n @Field({\n nullable: true,\n description: 'ChatSession id. Omit for a one-off question with no persistence.'\n })\n sessionId?: string\n\n @Field(() => [AssistantMessageInput], {\n description:\n 'Conversation, newest last. For a persisted session only the last user message is appended; the rest is read from storage.'\n })\n messages: AssistantMessageInput[]\n\n @Field(() => AssistantModeInput, {\n description:\n 'Which conversation this is. The persona, the tool categories, the model and the limits come from the mode the app server registered (ADR-0061 decision 7) — a caller choosing its own tool categories would be a caller choosing its own authority.'\n })\n mode: AssistantModeInput\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'What this surface is looking at (e.g. { figureId } while modelling). Tools read it so the model never has to ask the user for identifiers. Client-supplied — never used for authorization; a tool verifies the scope belongs to the caller tenant.'\n })\n hostContext?: any\n\n @Field({\n nullable: true,\n description:\n 'Last message to keep from the stored history. The client keeps its own truncation when a user edits a message, so pass the last kept id to hide the retracted span from the model.'\n })\n truncateAfterMessageId?: string\n\n @Field(() => [String], {\n nullable: true,\n description: 'Files stored with this conversation by attachToConversation, sent with this turn.'\n })\n attachmentIds?: string[]\n\n @Field({\n nullable: true,\n description:\n 'The AI agent the person picked for this turn, from aiAgentChoices. Omitted takes the domain default. An agent this domain cannot use is refused - the provider, the model and the key are always the server\\'s.'\n })\n agentId?: string\n\n @Field({\n nullable: true,\n description:\n 'The route of the page the dock is on (cash-transaction-list). It decides which tools the model is offered first; what a person may use is decided as without it.'\n })\n page?: string\n}\n\n@ObjectType()\nclass AssistantChatOutput {\n @Field({ description: 'The reply, including the notice when the loop stopped early.' })\n reply: string\n\n @Field(() => String, {\n nullable: true,\n description:\n 'The request the person is likely to make next, as the reply named it. The input shows it and Tab writes it in. Not stored with the reply.'\n })\n suggestion?: string | null\n\n @Field({ description: 'AI client identifier (provider:model).' })\n clientId: string\n\n @Field({ nullable: true, description: 'The AI agent that answered.' })\n agentId?: string\n\n @Field({ nullable: true, description: 'Echo of the session id, when persisted.' })\n sessionId?: string\n\n @Field({ nullable: true, description: 'Stored ChatMessage id of the user input.' })\n userMessageId?: string\n\n @Field({ nullable: true, description: 'Stored ChatMessage id of the reply.' })\n assistantMessageId?: string\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Set when this turn did not fit: { omitted, summarized } — how many earlier messages the model did not see, and whether a summary stood in for them. Without it the person watches the conversation lose its beginning in silence, while the model is told.'\n })\n historyFolded?: any\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description: 'Tool usages during the turn — {name, arguments, result, kind}. The surface shows them foldably.'\n })\n toolUsages?: any\n\n @Field(() => [String], {\n nullable: true,\n description:\n 'Tool names offered to the model this turn. A surface that expected a category and sees it missing here knows the registration did not happen, rather than reading a vague answer as a refusal.'\n })\n offeredTools?: string[]\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Actions a tool recorded without executing — each carries what the tool returned alongside `proposed: true`. The surface renders a confirm button; executing is the user\\'s act.'\n })\n proposals?: any\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Identifiers in the reply that appear in nothing the model received. Hallucination candidates surfaced to the user; the reply itself is not altered. Null when the answer is grounded.'\n })\n groundingWarnings?: any\n}\n\n@Resolver()\nexport class AssistantChatResolver {\n /*\n * No `@transaction`, for the same reason the board conversation has none: a model call takes\n * seconds to tens of seconds, and holding a write lock for that long collides with everything\n * else on SQLite. Writes are short and around the call. For a conversation, a partly stored\n * history is safer than a lost one.\n */\n @Mutation(() => AssistantChatOutput, {\n description:\n 'Ask the assistant a question on a surface that has no document of its own. Tools come from the registry; with a sessionId the conversation is persisted.'\n })\n /*\n * `ai-assistant:mutation` — the same door as the board conversation's, which moved here on\n * 2026-09-10. No `legacy` alias: this operation is new, so no one was ever passing under an old\n * name, and declaring an alias would open a second name that nothing needs.\n */\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"mutation\", domainOwnerGranted: true)')\n async assistantChat(@Arg('input') input: AssistantChatInput, @Ctx() context: any): Promise<AssistantChatOutput> {\n const { domain, user } = context.state\n\n /*\n * The domain's agent - the one picked, or the default (design/ai/agent-settings.md §4). Asked before anything is\n * stored: a domain with no AI, or an agent it cannot use, is refused as itself.\n */\n let client: AgentClient\n try {\n client = await aiClientFor({\n domain,\n user,\n agentId: input.agentId,\n modeKind: input.mode.kind,\n sessionId: input.sessionId\n })\n } catch (error) {\n rethrowSpoken(error, context)\n }\n /* A tool that asks a model of its own within this turn answers with the same agent (ToolCallContext.state). */\n context.state.aiAgentId = client.agent.id\n\n /*\n * Read the declaration before anything is stored. A kind this server does not have is a\n * mismatched deployment, and the person should see that instead of a conversation with no\n * persona and no tools.\n */\n const t = requestTranslator(context)\n const settings = resolveModeSettings(input.mode, t)\n\n const sessionRepo = getRepository(ChatSession)\n const messageRepo = getRepository(ChatMessage)\n\n let session: ChatSession | undefined\n if (input.sessionId) {\n const found = await sessionRepo.findOneBy({ id: input.sessionId, domain: { id: domain.id } as any })\n if (!found) throw new Error(`ChatSession ${input.sessionId} not found`)\n /*\n * A private conversation is written by its people only - and here more than anywhere: the answer is\n * built on this conversation's history, so an outsider's question would read it back to them.\n */\n await assertMayWriteSession({\n session: found as any,\n userId: user?.id,\n isParticipant: () => isSessionParticipant(domain.id, found.id!, user?.id),\n t: (key, options) => t(key, options as any)\n })\n if (found.anchorType === 'figure' && input.hostContext?.figureId !== found.anchorId) {\n throw new Error(\n context.t('ai-assistant.text.other-figure-history', {\n defaultValue: 'this conversation belongs to another figure. open the conversation of the model you have open.'\n })\n )\n }\n if (found.anchorType === 'application' && (input.hostContext?.app !== found.anchorId || input.hostContext?.figureId)) {\n throw new Error(\n context.t('ai-assistant.text.application-vs-model-history', {\n defaultValue: 'this conversation is about the application, not about a model. open the one you want.'\n })\n )\n }\n /*\n * 이 세션이 **이 대화의** 세션인가 — ADR-0072 결정 3. 다른 대화의 세션에 말을 붙이면 그 이력이\n * 이 대화의 프롬프트로 들어가고, 화면은 줄이 사라져 새 대화처럼 보이므로 보낸 사람은 모른다.\n */\n const verdict = judgeSessionKind((found as any).kind, input.mode.kind)\n if (verdict.action === 'refuse') {\n throw new Error(\n t('ai-assistant.text.session-kind-mismatch', {\n ...verdict.params,\n defaultValue: 'this conversation is {sessionKind}, and you are writing as {requested}. open the conversation you mean.'\n })\n )\n }\n if (verdict.action === 'stamp') {\n /* 칸이 서기 전에 난 세션 — 요청한 대화로 한 번 찍는다. 다음부터는 위 규칙 하나만 남는다. */\n await sessionRepo.update(found.id!, { kind: verdict.kind } as any)\n ;(found as any).kind = verdict.kind\n }\n session = found\n }\n\n assertStationSession(session, input.hostContext, t)\n\n /*\n * 열린 대상 — 서버가 저장한 앵커에서 턴마다 한 줄(design/ai/screen-pattern.md §2-1). 화면이 보낸 hostContext 는\n * 쓰지 않는다. 대상이 바뀌면 다음 턴의 줄도 바뀌므로 옛 대상이 대화에 남지 않는다.\n */\n const subjectLine = session\n ? await openSubjectLine({\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n domain,\n state: context.state\n })\n : undefined\n const systemPrompt = subjectLine ? `${settings.systemPrompt}\\n\\n${subjectLine}` : settings.systemPrompt\n\n const lastUserMessage = input.messages[input.messages.length - 1]\n let userMessageId: string | undefined\n\n /*\n * Files sent with this turn (design/ai/shared-dock.md §9). They were stored with this conversation by\n * attachToConversation; one that is not this conversation's is refused, never read. The policy is the\n * mode's - checked again here, since the door is the authority, not the screen.\n */\n const attachmentRows: any[] = []\n if (input.attachmentIds?.length) {\n if (!session) throw new Error(t('ai-assistant.text.attachments-need-session', { defaultValue: 'attachments need a saved conversation.' }))\n for (const id of input.attachmentIds) {\n const row = await findChatAttachment(domain.id, session.id!, id)\n if (!row) throw new Error(t('ai-assistant.text.attachment-not-in-session', { defaultValue: 'an attachment does not belong to this conversation.' }))\n attachmentRows.push(row)\n }\n const refusal = refuseAttachments(\n getAssistantMode(input.mode.kind)?.attachments,\n attachmentRows.map(row => ({ name: row.name, type: row.mimetype, size: Number(row.size) }))\n )\n if (refusal) throw new Error(t(`ai-assistant.text.${refusal.code}`, { ...(refusal as any), defaultValue: refusal.code }))\n }\n\n if (session && lastUserMessage?.role === 'user') {\n const previousLast = await messageRepo.findOne({\n where: { session: { id: session.id } as any },\n order: { createdAt: 'DESC' }\n })\n const saved = await messageRepo.save({\n session: { id: session.id } as any,\n role: 'user',\n content: lastUserMessage.content,\n /*\n * This door is the one people call to ask the assistant, so a turn stored here was for the\n * assistant (ADR-0070 A15 decision ①). The value comes from which door was called, never\n * from the request — two sources for one fact can disagree.\n */\n addressee: 'assistant',\n attachments: attachmentRows.length ? JSON.stringify(attachmentRows.map(describeAttachment)) : undefined,\n creator: user,\n updater: user,\n parentMessage: previousLast ? ({ id: previousLast.id } as any) : undefined\n } as any)\n userMessageId = saved.id\n\n /*\n * Session activity is denormalised for the list: most-recent ordering and another\n * participant's unread badge read it. Best effort — a conversation must not fail because a\n * list sorts one position late.\n */\n try {\n const patch: any = activityPatch(lastUserMessage.content)\n if (!session.name) patch.name = autoSessionName(lastUserMessage.content)\n await sessionRepo.update(session.id!, patch)\n } catch {\n /* noop */\n }\n\n /*\n * 사람이 `@이름` 으로 부른 사람은 이 대화에 들어온다(ADR-0071 결정 1). 이 문은 **사람의 턴**을\n * 저장하는 문이라 그 일을 한다 — 모델의 답이나 도구 결과는 아무도 초대하지 않는다.\n */\n const invite = await inviteMentionedUsers({\n content: lastUserMessage.content,\n sessionId: session.id!,\n domainId: domain.id,\n inviter: user,\n participants: getRepository(ChatSessionParticipant),\n visibility: (session as any).visibility\n })\n if (invite.invited.length > 0) {\n const payload = buildInviteNotification(invite.invited, undefined, user, (session as any)?.name)\n try {\n if (payload) await (context.state as any)?.notify?.(payload)\n } catch {\n /* 편지가 못 갔다고 방에 들어온 사실이 취소되지는 않는다. */\n }\n }\n\n publishChatMessage({ domainId: domain.id, sessionId: session.id, message: saved } as any)\n publishSessionActivity({\n domainId: domain.id,\n sessionId: session.id!,\n kind: 'message',\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n preview: lastUserMessage.content,\n actorId: user?.id\n })\n }\n\n /*\n * 이번 턴에 상한 밖으로 밀려난 것이 있는가 — 화면이 「대화가 길어졌다」를 알릴 근거다.\n *\n * 이 문은 그 사실을 내지 않았다. 대화창에는 그 안내를 그리는 자리가 있고(`historyFolded`), 보드\n * 문만 그 값을 채웠다. 그래서 앱의 대화는 앞부분이 잘려 나가도 **사람에게는 아무 말도 하지 않았다** —\n * 모델에게는 「N 개 생략」을 적어 보내면서.\n */\n const readRows = session\n ? await messageRepo.find({\n where: { session: { id: session.id } as any },\n order: { createdAt: 'ASC' },\n relations: ['creator']\n })\n : []\n /*\n * 모델이 읽는 이력도 **부른 사람** 기준으로 가린다 — ADR-0073 판정 1. 가리지 않으면, 문을 한 번도\n * 안 지난 사람이 \"아까 그 수 다시 말해 줘\" 한 마디로 값을 가져간다. 모델이 옮겨 적는 순간 그 값은\n * 문 없는 새 답이 된다. 가린 자리를 지우지 않고 한 줄로 남기는 이유도 같다 — 지우면 모델이 그런\n * 말이 없었다고 답하고, 그것은 거짓말이다.\n */\n const storedRows = await readerGate(context)(readRows)\n const capped = splitByCap(\n storedRows.map(row => ({ id: row.id, role: row.role, content: row.content ?? '' })),\n LLM_HISTORY_MAX_TURNS\n )\n const historyFolded =\n capped.dropped.length > 0\n ? { omitted: capped.dropped.length, summarized: Boolean((session as any)?.lastSummary) }\n : undefined\n\n const conversation = session\n ? buildLlmHistory(\n /*\n * The model reads who spoke and who they spoke to. The entity carries the speaker as a\n * relation and the addressee as a column; the history wants them as plain fields, and\n * without this mapping every line arrived unattributed — a room of people read as one\n * voice (ADR-0070 A15 decision ②).\n */\n storedRows.map(message => ({\n id: message.id,\n role: message.role,\n /*\n * A file sent in an earlier turn is not sent again - one line names it, and the model opens it\n * with readChatAttachment when it needs to (cost is a usage axis, ADR-0061 decision 6).\n */\n content: [\n message.content,\n ...(message.id !== userMessageId && Array.isArray((message as any).attachmentsJson) ? (message as any).attachmentsJson.map(attachmentHistoryLine) : []),\n /* An answer that offered cards names them, so a later outcome row reads as about one of them (audit N7). */\n ...(message.role === 'assistant' ? offeredCardLines((message as any).proposals) : [])\n ].join('\\n'),\n senderId: (message as any).creator?.id,\n senderName: (message as any).creator?.name,\n addressee: (message as any).addressee\n })),\n {\n maxTurns: LLM_HISTORY_MAX_TURNS,\n truncateAfterMessageId: input.truncateAfterMessageId,\n /* The message just stored is this turn's question — truncation must never drop it. */\n keepMessageIds: userMessageId ? [userMessageId] : undefined,\n summary: (session as any).lastSummary\n ? { text: (session as any).lastSummary, upToMessageId: (session as any).summaryUpToMessageId }\n : undefined\n }\n )\n : input.messages.map(message => ({ role: message.role as any, content: message.content }))\n\n /*\n * The files checked above, read once: the model reads them on this turn's question, and the tools get the same\n * files as `context.attachments` (ADR-0061, 2026-10-16 decision 2) - figure's step generator draws from the\n * picture the person sent. Never mixed into `hostContext`, which the client fills.\n */\n const turnFiles = await readTurnAttachments(\n attachmentRows,\n textCharsOf(getAssistantMode(input.mode?.kind ?? '')?.attachments),\n attachmentForModel\n )\n if (attachmentRows.length && conversation.length > 0) {\n const last = conversation[conversation.length - 1]\n if (last.role === 'user' && typeof last.content === 'string') {\n last.content = [{ type: 'text', text: last.content }, ...turnFiles.parts] as any\n }\n } else if (input.hostContext?.image?.data && conversation.length > 0) {\n /* The screen-image path (figure's rendered view) - not a file of the conversation. */\n const last = conversation[conversation.length - 1]\n if (last.role === 'user' && typeof last.content === 'string') {\n const raw = input.hostContext.image\n const rawData = typeof raw.data === 'string' ? raw.data.replace(/^data:image\\/[a-zA-Z]+;base64,/, '') : raw.data\n const buf = typeof rawData === 'string' ? Buffer.from(rawData, 'base64') : Buffer.from(rawData)\n last.content = [\n { type: 'text', text: last.content },\n { type: 'image', source: { kind: 'base64', data: buf, mediaType: raw.mediaType || 'image/png' } }\n ] as any\n }\n }\n\n const result = await registryAssistantChat({\n client,\n messages: conversation as any,\n systemPrompt,\n toolCategories: settings.toolCategories,\n /*\n * What goes in front when the mode's tools do not fit in one call (ADR-0061 decision 9): the open page's and the\n * anchored record's, then what this conversation used. The anchor is the stored one, never the client's.\n */\n front: {\n categories: modeFrontCategories(getAssistantMode(input.mode.kind) ?? {}, {\n page: input.page,\n subjectAxis: (session as any)?.anchorType === 'subject' ? String((session as any).anchorId ?? '').split(':')[0] || undefined : undefined\n }),\n tools: usedToolNames(storedRows as any, [OPEN_TOOLS])\n },\n /* 도구가 「어느 대화에서 불렸나」를 안다 — 서버의 값이다(ADR-0061 결정 3). */\n mode: { kind: input.mode.kind, ...(input.mode.target ? { target: input.mode.target } : {}) },\n toolAccess: toolAccessFor(context),\n /* The stop notice is a sentence a person reads — the server writes it in their language (ADR-0054). */\n t,\n hostContext: input.hostContext,\n attachments: turnFiles.forTools,\n /*\n * 근거는 도구 결과만이 아니다 — 시스템 프롬프트 · 이 대화의 말 · 화면이 보낸 스냅숏도 근거다\n * (`ai-client-base/utils/grounding`). 이 문은 그 셋을 넘기지 않아, **사람이 방금 타이핑한\n * 식별자**를 되받아 적은 답이 「근거 없음」으로 표시됐다. 그런 경고가 몇 번 나오면 사람이 경고\n * 자체를 안 보게 된다.\n */\n groundingSources: groundingSourcesFor({\n systemPrompt,\n messages: conversation as any,\n hostContext: input.hostContext\n }),\n state: context.state,\n temperature: settings.temperature,\n maxTokens: settings.maxTokens,\n requireToolOnFirstTurn: settings.requireToolOnFirstTurn\n })\n\n /*\n * 이 답이 지나온 문 — ADR-0073 판정 1. 행에도, 목록 미리보기에도 같은 값이 간다. 한쪽만 적으면\n * 가려진 답이 목록에는 그대로 뜬다.\n */\n /* The reply ends with the next request when one is natural; the input offers it and the stored answer does not keep it (§4-1). */\n const { reply, suggestion } = splitNextRequest(result.reply)\n\n const answerDoors = doorsForAnswer(\n (result.loop.toolUsages ?? []).map((usage: any) => usage?.name).filter(Boolean),\n name => (findToolSpec(name, settings.toolCategories) as any)?.doors\n )\n\n let assistantMessageId: string | undefined\n if (session) {\n const stored = await messageRepo.save({\n session: { id: session.id } as any,\n role: 'assistant',\n content: reply,\n /*\n * 이 답이 지나온 문을 함께 적는다 — ADR-0073 판정 1. 방은 도메인 안에서 공개이므로, 이 문장이\n * 실어 나르는 값을 나중에 읽는 사람이 열 수 있는지 판정할 근거가 행에 있어야 한다. 적지 않으면\n * 빈 목록이 되고, 빈 목록은 누구나 읽는다는 뜻이다.\n */\n doors: answerDoors,\n toolUsages: result.loop.toolUsages?.length ? JSON.stringify(result.loop.toolUsages) : undefined,\n proposals: result.loop.proposals?.length ? JSON.stringify(result.loop.proposals) : undefined,\n groundingWarnings: result.groundingWarnings?.length ? JSON.stringify(result.groundingWarnings) : undefined,\n creator: user,\n updater: user,\n parentMessage: userMessageId ? ({ id: userMessageId } as any) : undefined\n })\n assistantMessageId = stored.id\n\n publishChatMessage({ domainId: domain.id, sessionId: session.id, message: stored } as any)\n /* The reply is session activity too. Leaving it out shows other participants \"nothing new\". */\n publishSessionActivity({\n domainId: domain.id,\n sessionId: session.id!,\n kind: 'message',\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n preview: reply,\n actorId: user?.id\n })\n\n await sessionRepo.update(session.id!, {\n aiClientId: client.id,\n agentId: client.agent.id,\n updater: user,\n ...activityPatch(reply, new Date(), answerDoors)\n } as any)\n\n /*\n * 상한 밖으로 밀려난 앞부분을 요약으로 접는다 — **답을 돌려준 뒤**에 한다(사용자 대기 시간에\n * 요약 호출을 얹지 않는다). 기다리지 않으므로 실패해도 대화에는 영향이 없다: 요약이 없으면 다음\n * 턴이 「N 개 생략」으로 정직하게 알린다.\n *\n * 이 문에는 이 호출이 없었다. 조각은 다 있었는데(상한 · 생략 알림 · `lastSummary` · 순수 규칙)\n * 부르는 쪽이 보드 제품 하나뿐이어서, 앱의 대화는 며칠이 지나도 앞부분을 요약하지 않고 매 턴\n * 「생략했다」고만 말했다 — 협의가 길어질수록 처음에 합의한 것이 먼저 사라진다.\n */\n void foldSessionHistory({\n sessionId: session.id!,\n domainId: domain.id,\n client,\n maxTurns: LLM_HISTORY_MAX_TURNS,\n locale: context.state?.lng\n })\n }\n\n return {\n reply,\n suggestion: suggestion ?? null,\n clientId: client.id,\n agentId: client.agent.id,\n sessionId: session?.id,\n userMessageId,\n assistantMessageId,\n toolUsages: result.loop.toolUsages?.length ? result.loop.toolUsages : null,\n offeredTools: result.offeredTools,\n proposals: result.loop.proposals?.length ? result.loop.proposals : null,\n groundingWarnings: result.groundingWarnings?.length ? result.groundingWarnings : null,\n historyFolded: historyFolded ?? null\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"assistant-chat-resolver.js","sourceRoot":"","sources":["../../server/service/assistant-chat-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The neutral door into a conversation.\n *\n * ── Why this exists (2026-09-11) ───────────────────────────────────────────────\n * The chat component moved here from `board-ai`, but the only mutation it could call stayed\n * behind. operato-figure mounted the dock, registered four tools, and got\n * `Cannot query field \"boardAIChat\" on type \"Mutation\"` — it does not load board-ai. The twin has\n * the same shape and it does not show: its space deliberation runs through board-ai's door purely\n * because the board authoring half of the same product already loaded that package.\n *\n * ── Why the board resolver was not moved ───────────────────────────────────────\n * `board-ai-resolver.ts` is 692 lines and the larger part of it is board authoring itself:\n * `currentBoard` as a BoardModel, PatchEntry, board patch broadcasts, refid injection for\n * component mentions. Moving that here would teach this package the board document, which is the\n * dependency we just cut, pointing the other way.\n *\n * So the door moved and the editor did not. `boardAIChat` stays in board-ai for board authoring;\n * this one is for a surface that asks questions and runs registered tools.\n *\n * ── What this is not ───────────────────────────────────────────────────────────\n * It has no document, so it takes no document and returns no patch. Tools that write to a\n * document are not offered here — `registryAssistantChat` enforces that, and it decides what may\n * be dispatched from the same list it offers, so the two cannot drift apart.\n */\nimport { Arg, Ctx, Directive, Field, InputType, Mutation, ObjectType, Resolver } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\nimport '@things-factory/auth-base'\n\nimport { getRepository, rethrowSpoken } from '@things-factory/shell'\nimport { aiClientFor, findToolSpec, openSubjectLine, OPEN_TOOLS, registryAssistantChat, type AgentClient } from '@things-factory/ai-client-base'\n\nimport { ChatSession } from './chat-session/chat-session.js'\nimport { assertStationSession } from './chat-session/station-session-guard.js'\nimport { ChatMessage } from './chat-message/chat-message.js'\nimport { buildLlmHistory } from './chat-message/llm-history.js'\nimport { offeredCardLines } from './chat-message/proposal-outcome.js'\nimport { splitByCap } from './chat-message/history-summary.js'\nimport { foldSessionHistory } from './chat-message/fold-session-history.js'\nimport { groundingSourcesFor } from './grounding-sources.js'\nimport { publishChatMessage } from './chat-message/chat-message-publish.js'\nimport { publishSessionActivity } from './chat-session/session-activity-publish.js'\nimport { activityPatch, autoSessionName } from './chat-session/session-inbox.js'\nimport { toolAccessFor } from './tool-access.js'\nimport { ChatSessionParticipant } from './chat-session-participant/chat-session-participant.js'\nimport { doorsForAnswer } from './chat-message/message-doors.js'\nimport { judgeSessionKind } from './chat-session/session-kind.js'\nimport { readerGate } from './chat-message/reader-gate.js'\nimport { seatTurn } from './chat-session-participant/mention-invite.js'\nimport { sendInviteNotification } from './chat-session-participant/invite-notification.js'\nimport { isSessionParticipant } from './chat-session-participant/participant-lookup.js'\nimport { assertMayWriteSession } from './chat-session/session-write-gate.js'\nimport { attachmentHistoryLine, refuseAttachments, textCharsOf } from './chat-session/attachment-policy.js'\nimport { attachmentForModel, describeAttachment, findChatAttachment } from './chat-session/session-attachments.js'\nimport { readTurnAttachments } from './chat-session/turn-attachments.js'\nimport { getAssistantMode } from './assistant-mode/registry.js'\nimport { resolveModeSettings } from './assistant-mode/resolve.js'\nimport { modeFrontCategories } from './assistant-mode/mode-categories.js'\nimport { usedToolNames } from './chat-message/used-tools.js'\nimport { splitNextRequest } from './assistant-mode/screen-rules.js'\nimport { requestTranslator } from './request-translator.js'\n\n/**\n * How many stored messages reach the model.\n *\n * A deliberation runs for days. Without a cap the prompt grows without bound and the oldest turns\n * bury the recent ones. Kept the same as the board conversation's cap so the two surfaces of one\n * product do not remember differently.\n */\nconst LLM_HISTORY_MAX_TURNS = 40\n\n@InputType()\nclass AssistantMessageInput {\n @Field({ description: \"Role: 'user' or 'assistant'\" })\n role: string\n\n @Field({ description: 'Message content' })\n content: string\n}\n\n@InputType({ description: 'Which conversation this is, and what it is about.' })\nclass AssistantModeInput {\n @Field({ description: 'A mode kind the app server registered — `<app>.<surface>`, e.g. \"twin.space\".' })\n kind: string\n\n @Field({\n nullable: true,\n description:\n 'What this conversation is about — the space, the station, the model being edited. With `kind` it is the conversation\\'s lifetime: when either changes the screen closes the conversation and starts another.'\n })\n target?: string\n}\n\n@InputType({ description: 'One turn of a conversation that has no document of its own.' })\nclass AssistantChatInput {\n @Field({\n nullable: true,\n description: 'ChatSession id. Omit for a one-off question with no persistence.'\n })\n sessionId?: string\n\n @Field(() => [AssistantMessageInput], {\n description:\n 'Conversation, newest last. For a persisted session only the last user message is appended; the rest is read from storage.'\n })\n messages: AssistantMessageInput[]\n\n @Field(() => AssistantModeInput, {\n description:\n 'Which conversation this is. The persona, the tool categories, the model and the limits come from the mode the app server registered (ADR-0061 decision 7) — a caller choosing its own tool categories would be a caller choosing its own authority.'\n })\n mode: AssistantModeInput\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'What this surface is looking at (e.g. { figureId } while modelling). Tools read it so the model never has to ask the user for identifiers. Client-supplied — never used for authorization; a tool verifies the scope belongs to the caller tenant.'\n })\n hostContext?: any\n\n @Field({\n nullable: true,\n description:\n 'Last message to keep from the stored history. The client keeps its own truncation when a user edits a message, so pass the last kept id to hide the retracted span from the model.'\n })\n truncateAfterMessageId?: string\n\n @Field(() => [String], {\n nullable: true,\n description: 'Files stored with this conversation by attachToConversation, sent with this turn.'\n })\n attachmentIds?: string[]\n\n @Field({\n nullable: true,\n description:\n 'The AI agent the person picked for this turn, from aiAgentChoices. Omitted takes the domain default. An agent this domain cannot use is refused - the provider, the model and the key are always the server\\'s.'\n })\n agentId?: string\n\n @Field({\n nullable: true,\n description:\n 'The route of the page the dock is on (cash-transaction-list). It decides which tools the model is offered first; what a person may use is decided as without it.'\n })\n page?: string\n}\n\n@ObjectType()\nclass AssistantChatOutput {\n @Field({ description: 'The reply, including the notice when the loop stopped early.' })\n reply: string\n\n @Field(() => String, {\n nullable: true,\n description:\n 'The request the person is likely to make next, as the reply named it. The input shows it and Tab writes it in. Not stored with the reply.'\n })\n suggestion?: string | null\n\n @Field({ description: 'AI client identifier (provider:model).' })\n clientId: string\n\n @Field({ nullable: true, description: 'The AI agent that answered.' })\n agentId?: string\n\n @Field({ nullable: true, description: 'Echo of the session id, when persisted.' })\n sessionId?: string\n\n @Field({ nullable: true, description: 'Stored ChatMessage id of the user input.' })\n userMessageId?: string\n\n @Field({ nullable: true, description: 'Stored ChatMessage id of the reply.' })\n assistantMessageId?: string\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Set when this turn did not fit: { omitted, summarized } — how many earlier messages the model did not see, and whether a summary stood in for them. Without it the person watches the conversation lose its beginning in silence, while the model is told.'\n })\n historyFolded?: any\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description: 'Tool usages during the turn — {name, arguments, result, kind}. The surface shows them foldably.'\n })\n toolUsages?: any\n\n @Field(() => [String], {\n nullable: true,\n description:\n 'Tool names offered to the model this turn. A surface that expected a category and sees it missing here knows the registration did not happen, rather than reading a vague answer as a refusal.'\n })\n offeredTools?: string[]\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Actions a tool recorded without executing — each carries what the tool returned alongside `proposed: true`. The surface renders a confirm button; executing is the user\\'s act.'\n })\n proposals?: any\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Identifiers in the reply that appear in nothing the model received. Hallucination candidates surfaced to the user; the reply itself is not altered. Null when the answer is grounded.'\n })\n groundingWarnings?: any\n}\n\n@Resolver()\nexport class AssistantChatResolver {\n /*\n * No `@transaction`, for the same reason the board conversation has none: a model call takes\n * seconds to tens of seconds, and holding a write lock for that long collides with everything\n * else on SQLite. Writes are short and around the call. For a conversation, a partly stored\n * history is safer than a lost one.\n */\n @Mutation(() => AssistantChatOutput, {\n description:\n 'Ask the assistant a question on a surface that has no document of its own. Tools come from the registry; with a sessionId the conversation is persisted.'\n })\n /*\n * `ai-assistant:mutation` — the same door as the board conversation's, which moved here on\n * 2026-09-10. No `legacy` alias: this operation is new, so no one was ever passing under an old\n * name, and declaring an alias would open a second name that nothing needs.\n */\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"mutation\", domainOwnerGranted: true)')\n async assistantChat(@Arg('input') input: AssistantChatInput, @Ctx() context: any): Promise<AssistantChatOutput> {\n const { domain, user } = context.state\n\n /*\n * The domain's agent - the one picked, or the default (design/ai/agent-settings.md §4). Asked before anything is\n * stored: a domain with no AI, or an agent it cannot use, is refused as itself.\n */\n let client: AgentClient\n try {\n client = await aiClientFor({\n domain,\n user,\n agentId: input.agentId,\n modeKind: input.mode.kind,\n sessionId: input.sessionId\n })\n } catch (error) {\n rethrowSpoken(error, context)\n }\n /* A tool that asks a model of its own within this turn answers with the same agent (ToolCallContext.state). */\n context.state.aiAgentId = client.agent.id\n\n /*\n * Read the declaration before anything is stored. A kind this server does not have is a\n * mismatched deployment, and the person should see that instead of a conversation with no\n * persona and no tools.\n */\n const t = requestTranslator(context)\n const settings = resolveModeSettings(input.mode, t)\n\n const sessionRepo = getRepository(ChatSession)\n const messageRepo = getRepository(ChatMessage)\n\n let session: ChatSession | undefined\n if (input.sessionId) {\n const found = await sessionRepo.findOneBy({ id: input.sessionId, domain: { id: domain.id } as any })\n if (!found) throw new Error(`ChatSession ${input.sessionId} not found`)\n /*\n * A private conversation is written by its people only - and here more than anywhere: the answer is\n * built on this conversation's history, so an outsider's question would read it back to them.\n */\n await assertMayWriteSession({\n session: found as any,\n userId: user?.id,\n isParticipant: () => isSessionParticipant(domain.id, found.id!, user?.id),\n t: (key, options) => t(key, options as any)\n })\n if (found.anchorType === 'figure' && input.hostContext?.figureId !== found.anchorId) {\n throw new Error(\n context.t('ai-assistant.text.other-figure-history', {\n defaultValue: 'this conversation belongs to another figure. open the conversation of the model you have open.'\n })\n )\n }\n if (found.anchorType === 'application' && (input.hostContext?.app !== found.anchorId || input.hostContext?.figureId)) {\n throw new Error(\n context.t('ai-assistant.text.application-vs-model-history', {\n defaultValue: 'this conversation is about the application, not about a model. open the one you want.'\n })\n )\n }\n /*\n * 이 세션이 **이 대화의** 세션인가 — ADR-0072 결정 3. 다른 대화의 세션에 말을 붙이면 그 이력이\n * 이 대화의 프롬프트로 들어가고, 화면은 줄이 사라져 새 대화처럼 보이므로 보낸 사람은 모른다.\n */\n const verdict = judgeSessionKind((found as any).kind, input.mode.kind)\n if (verdict.action === 'refuse') {\n throw new Error(\n t('ai-assistant.text.session-kind-mismatch', {\n ...verdict.params,\n defaultValue: 'this conversation is {sessionKind}, and you are writing as {requested}. open the conversation you mean.'\n })\n )\n }\n if (verdict.action === 'stamp') {\n /* 칸이 서기 전에 난 세션 — 요청한 대화로 한 번 찍는다. 다음부터는 위 규칙 하나만 남는다. */\n await sessionRepo.update(found.id!, { kind: verdict.kind } as any)\n ;(found as any).kind = verdict.kind\n }\n session = found\n }\n\n assertStationSession(session, input.hostContext, t)\n\n /*\n * 열린 대상 — 서버가 저장한 앵커에서 턴마다 한 줄(design/ai/screen-pattern.md §2-1). 화면이 보낸 hostContext 는\n * 쓰지 않는다. 대상이 바뀌면 다음 턴의 줄도 바뀌므로 옛 대상이 대화에 남지 않는다.\n */\n const subjectLine = session\n ? await openSubjectLine({\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n domain,\n state: context.state\n })\n : undefined\n const systemPrompt = subjectLine ? `${settings.systemPrompt}\\n\\n${subjectLine}` : settings.systemPrompt\n\n const lastUserMessage = input.messages[input.messages.length - 1]\n let userMessageId: string | undefined\n\n /*\n * Files sent with this turn (design/ai/shared-dock.md §9). They were stored with this conversation by\n * attachToConversation; one that is not this conversation's is refused, never read. The policy is the\n * mode's - checked again here, since the door is the authority, not the screen.\n */\n const attachmentRows: any[] = []\n if (input.attachmentIds?.length) {\n if (!session) throw new Error(t('ai-assistant.text.attachments-need-session', { defaultValue: 'attachments need a saved conversation.' }))\n for (const id of input.attachmentIds) {\n const row = await findChatAttachment(domain.id, session.id!, id)\n if (!row) throw new Error(t('ai-assistant.text.attachment-not-in-session', { defaultValue: 'an attachment does not belong to this conversation.' }))\n attachmentRows.push(row)\n }\n const refusal = refuseAttachments(\n getAssistantMode(input.mode.kind)?.attachments,\n attachmentRows.map(row => ({ name: row.name, type: row.mimetype, size: Number(row.size) }))\n )\n if (refusal) throw new Error(t(`ai-assistant.text.${refusal.code}`, { ...(refusal as any), defaultValue: refusal.code }))\n }\n\n if (session && lastUserMessage?.role === 'user') {\n const previousLast = await messageRepo.findOne({\n where: { session: { id: session.id } as any },\n order: { createdAt: 'DESC' }\n })\n const saved = await messageRepo.save({\n session: { id: session.id } as any,\n role: 'user',\n content: lastUserMessage.content,\n /*\n * This door is the one people call to ask the assistant, so a turn stored here was for the\n * assistant (ADR-0070 A15 decision ①). The value comes from which door was called, never\n * from the request — two sources for one fact can disagree.\n */\n addressee: 'assistant',\n attachments: attachmentRows.length ? JSON.stringify(attachmentRows.map(describeAttachment)) : undefined,\n creator: user,\n updater: user,\n parentMessage: previousLast ? ({ id: previousLast.id } as any) : undefined\n } as any)\n userMessageId = saved.id\n\n /*\n * Session activity is denormalised for the list: most-recent ordering and another\n * participant's unread badge read it. Best effort — a conversation must not fail because a\n * list sorts one position late.\n */\n try {\n const patch: any = activityPatch(lastUserMessage.content)\n if (!session.name) patch.name = autoSessionName(lastUserMessage.content)\n await sessionRepo.update(session.id!, patch)\n } catch {\n /* noop */\n }\n\n /*\n * 사람이 `@이름` 으로 부른 사람은 이 대화에 들어온다(ADR-0071 결정 1). 이 문은 **사람의 턴**을\n * 저장하는 문이라 그 일을 한다 — 모델의 답이나 도구 결과는 아무도 초대하지 않는다. 공개 방에서 말한 사람도\n * 이때 자리에 앉는다(`seatTurn`) — 참여자 줄 · 접속 표시 · 안 읽음이 실제로 말한 사람과 맞도록.\n */\n const invite = await seatTurn({\n content: lastUserMessage.content,\n sessionId: session.id!,\n domainId: domain.id,\n inviter: user,\n participants: getRepository(ChatSessionParticipant),\n visibility: (session as any).visibility\n })\n /* 편지가 못 갔다고 방에 들어온 사실이 취소되지는 않는다(`sendInviteNotification`). */\n await sendInviteNotification((context.state as any)?.notify, invite.invited, user, (session as any)?.name, undefined, requestTranslator(context))\n\n publishChatMessage({ domainId: domain.id, sessionId: session.id, message: saved } as any)\n publishSessionActivity({\n domainId: domain.id,\n sessionId: session.id!,\n kind: 'message',\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n preview: lastUserMessage.content,\n actorId: user?.id\n })\n }\n\n /*\n * 이번 턴에 상한 밖으로 밀려난 것이 있는가 — 화면이 「대화가 길어졌다」를 알릴 근거다.\n *\n * 이 문은 그 사실을 내지 않았다. 대화창에는 그 안내를 그리는 자리가 있고(`historyFolded`), 보드\n * 문만 그 값을 채웠다. 그래서 앱의 대화는 앞부분이 잘려 나가도 **사람에게는 아무 말도 하지 않았다** —\n * 모델에게는 「N 개 생략」을 적어 보내면서.\n */\n const readRows = session\n ? await messageRepo.find({\n where: { session: { id: session.id } as any },\n order: { createdAt: 'ASC' },\n relations: ['creator']\n })\n : []\n /*\n * 모델이 읽는 이력도 **부른 사람** 기준으로 가린다 — ADR-0073 판정 1. 가리지 않으면, 문을 한 번도\n * 안 지난 사람이 \"아까 그 수 다시 말해 줘\" 한 마디로 값을 가져간다. 모델이 옮겨 적는 순간 그 값은\n * 문 없는 새 답이 된다. 가린 자리를 지우지 않고 한 줄로 남기는 이유도 같다 — 지우면 모델이 그런\n * 말이 없었다고 답하고, 그것은 거짓말이다.\n */\n const storedRows = await readerGate(context)(readRows)\n const capped = splitByCap(\n storedRows.map(row => ({ id: row.id, role: row.role, content: row.content ?? '' })),\n LLM_HISTORY_MAX_TURNS\n )\n const historyFolded =\n capped.dropped.length > 0\n ? { omitted: capped.dropped.length, summarized: Boolean((session as any)?.lastSummary) }\n : undefined\n\n const conversation = session\n ? buildLlmHistory(\n /*\n * The model reads who spoke and who they spoke to. The entity carries the speaker as a\n * relation and the addressee as a column; the history wants them as plain fields, and\n * without this mapping every line arrived unattributed — a room of people read as one\n * voice (ADR-0070 A15 decision ②).\n */\n storedRows.map(message => ({\n id: message.id,\n role: message.role,\n /*\n * A file sent in an earlier turn is not sent again - one line names it, and the model opens it\n * with readChatAttachment when it needs to (cost is a usage axis, ADR-0061 decision 6).\n */\n content: [\n message.content,\n ...(message.id !== userMessageId && Array.isArray((message as any).attachmentsJson) ? (message as any).attachmentsJson.map(attachmentHistoryLine) : []),\n /* An answer that offered cards names them, so a later outcome row reads as about one of them (audit N7). */\n ...(message.role === 'assistant' ? offeredCardLines((message as any).proposals) : [])\n ].join('\\n'),\n senderId: (message as any).creator?.id,\n senderName: (message as any).creator?.name,\n addressee: (message as any).addressee\n })),\n {\n maxTurns: LLM_HISTORY_MAX_TURNS,\n truncateAfterMessageId: input.truncateAfterMessageId,\n /* The message just stored is this turn's question — truncation must never drop it. */\n keepMessageIds: userMessageId ? [userMessageId] : undefined,\n summary: (session as any).lastSummary\n ? { text: (session as any).lastSummary, upToMessageId: (session as any).summaryUpToMessageId }\n : undefined\n }\n )\n : input.messages.map(message => ({ role: message.role as any, content: message.content }))\n\n /*\n * The files checked above, read once: the model reads them on this turn's question, and the tools get the same\n * files as `context.attachments` (ADR-0061, 2026-10-16 decision 2) - figure's step generator draws from the\n * picture the person sent. Never mixed into `hostContext`, which the client fills.\n */\n const turnFiles = await readTurnAttachments(\n attachmentRows,\n textCharsOf(getAssistantMode(input.mode?.kind ?? '')?.attachments),\n attachmentForModel\n )\n if (attachmentRows.length && conversation.length > 0) {\n const last = conversation[conversation.length - 1]\n if (last.role === 'user' && typeof last.content === 'string') {\n last.content = [{ type: 'text', text: last.content }, ...turnFiles.parts] as any\n }\n } else if (input.hostContext?.image?.data && conversation.length > 0) {\n /* The screen-image path (figure's rendered view) - not a file of the conversation. */\n const last = conversation[conversation.length - 1]\n if (last.role === 'user' && typeof last.content === 'string') {\n const raw = input.hostContext.image\n const rawData = typeof raw.data === 'string' ? raw.data.replace(/^data:image\\/[a-zA-Z]+;base64,/, '') : raw.data\n const buf = typeof rawData === 'string' ? Buffer.from(rawData, 'base64') : Buffer.from(rawData)\n last.content = [\n { type: 'text', text: last.content },\n { type: 'image', source: { kind: 'base64', data: buf, mediaType: raw.mediaType || 'image/png' } }\n ] as any\n }\n }\n\n const result = await registryAssistantChat({\n client,\n messages: conversation as any,\n systemPrompt,\n toolCategories: settings.toolCategories,\n /*\n * What goes in front when the mode's tools do not fit in one call (ADR-0061 decision 9): the open page's and the\n * anchored record's, then what this conversation used. The anchor is the stored one, never the client's.\n */\n front: {\n categories: modeFrontCategories(getAssistantMode(input.mode.kind) ?? {}, {\n page: input.page,\n subjectAxis: (session as any)?.anchorType === 'subject' ? String((session as any).anchorId ?? '').split(':')[0] || undefined : undefined\n }),\n tools: usedToolNames(storedRows as any, [OPEN_TOOLS])\n },\n /* 도구가 「어느 대화에서 불렸나」를 안다 — 서버의 값이다(ADR-0061 결정 3). */\n mode: { kind: input.mode.kind, ...(input.mode.target ? { target: input.mode.target } : {}) },\n toolAccess: toolAccessFor(context),\n /* The stop notice is a sentence a person reads — the server writes it in their language (ADR-0054). */\n t,\n hostContext: input.hostContext,\n attachments: turnFiles.forTools,\n /*\n * 근거는 도구 결과만이 아니다 — 시스템 프롬프트 · 이 대화의 말 · 화면이 보낸 스냅숏도 근거다\n * (`ai-client-base/utils/grounding`). 이 문은 그 셋을 넘기지 않아, **사람이 방금 타이핑한\n * 식별자**를 되받아 적은 답이 「근거 없음」으로 표시됐다. 그런 경고가 몇 번 나오면 사람이 경고\n * 자체를 안 보게 된다.\n */\n groundingSources: groundingSourcesFor({\n systemPrompt,\n messages: conversation as any,\n hostContext: input.hostContext\n }),\n state: context.state,\n temperature: settings.temperature,\n maxTokens: settings.maxTokens,\n requireToolOnFirstTurn: settings.requireToolOnFirstTurn\n })\n\n /*\n * 이 답이 지나온 문 — ADR-0073 판정 1. 행에도, 목록 미리보기에도 같은 값이 간다. 한쪽만 적으면\n * 가려진 답이 목록에는 그대로 뜬다.\n */\n /* The reply ends with the next request when one is natural; the input offers it and the stored answer does not keep it (§4-1). */\n const { reply, suggestion } = splitNextRequest(result.reply)\n\n const answerDoors = doorsForAnswer(\n (result.loop.toolUsages ?? []).map((usage: any) => usage?.name).filter(Boolean),\n name => (findToolSpec(name, settings.toolCategories) as any)?.doors\n )\n\n let assistantMessageId: string | undefined\n if (session) {\n const stored = await messageRepo.save({\n session: { id: session.id } as any,\n role: 'assistant',\n content: reply,\n /*\n * 이 답이 지나온 문을 함께 적는다 — ADR-0073 판정 1. 방은 도메인 안에서 공개이므로, 이 문장이\n * 실어 나르는 값을 나중에 읽는 사람이 열 수 있는지 판정할 근거가 행에 있어야 한다. 적지 않으면\n * 빈 목록이 되고, 빈 목록은 누구나 읽는다는 뜻이다.\n */\n doors: answerDoors,\n toolUsages: result.loop.toolUsages?.length ? JSON.stringify(result.loop.toolUsages) : undefined,\n proposals: result.loop.proposals?.length ? JSON.stringify(result.loop.proposals) : undefined,\n groundingWarnings: result.groundingWarnings?.length ? JSON.stringify(result.groundingWarnings) : undefined,\n creator: user,\n updater: user,\n parentMessage: userMessageId ? ({ id: userMessageId } as any) : undefined\n })\n assistantMessageId = stored.id\n\n publishChatMessage({ domainId: domain.id, sessionId: session.id, message: stored } as any)\n /* The reply is session activity too. Leaving it out shows other participants \"nothing new\". */\n publishSessionActivity({\n domainId: domain.id,\n sessionId: session.id!,\n kind: 'message',\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n preview: reply,\n actorId: user?.id\n })\n\n await sessionRepo.update(session.id!, {\n aiClientId: client.id,\n agentId: client.agent.id,\n updater: user,\n ...activityPatch(reply, new Date(), answerDoors)\n } as any)\n\n /*\n * 상한 밖으로 밀려난 앞부분을 요약으로 접는다 — **답을 돌려준 뒤**에 한다(사용자 대기 시간에\n * 요약 호출을 얹지 않는다). 기다리지 않으므로 실패해도 대화에는 영향이 없다: 요약이 없으면 다음\n * 턴이 「N 개 생략」으로 정직하게 알린다.\n *\n * 이 문에는 이 호출이 없었다. 조각은 다 있었는데(상한 · 생략 알림 · `lastSummary` · 순수 규칙)\n * 부르는 쪽이 보드 제품 하나뿐이어서, 앱의 대화는 며칠이 지나도 앞부분을 요약하지 않고 매 턴\n * 「생략했다」고만 말했다 — 협의가 길어질수록 처음에 합의한 것이 먼저 사라진다.\n */\n void foldSessionHistory({\n sessionId: session.id!,\n domainId: domain.id,\n client,\n maxTurns: LLM_HISTORY_MAX_TURNS,\n locale: context.state?.lng\n })\n }\n\n return {\n reply,\n suggestion: suggestion ?? null,\n clientId: client.id,\n agentId: client.agent.id,\n sessionId: session?.id,\n userMessageId,\n assistantMessageId,\n toolUsages: result.loop.toolUsages?.length ? result.loop.toolUsages : null,\n offeredTools: result.offeredTools,\n proposals: result.loop.proposals?.length ? result.loop.proposals : null,\n groundingWarnings: result.groundingWarnings?.length ? result.groundingWarnings : null,\n historyFolded: historyFolded ?? null\n }\n }\n}\n"]}
|
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { registerAssistantMode, getAssistantMode, listAssistantModes, assistantModeForSubject, getAssistantModeWarnings, clearAssistantModes } from './registry.js';
|
|
7
7
|
export { resolveModeSettings, assistantModeView } from './resolve.js';
|
|
8
|
-
export { ABSENCE_RULE, FINISH_RULE, NEXT_REQUEST_RULE, conversationPrompt, isPersonRequest, splitNextRequest } from './screen-rules.js';
|
|
8
|
+
export { ABSENCE_RULE, FINISH_RULE, NEXT_REQUEST_RULE, SPEAKER_RULE, conversationPrompt, isPersonRequest, splitNextRequest } from './screen-rules.js';
|
|
9
9
|
export type { AssistantModeSpec, AssistantModeSlash, AssistantModeWarning, UnattendedLimits } from './types.js';
|
|
10
10
|
export type { AssistantModeRef, ModeSettings, AssistantModeView, AssistantModeSlashView, Translate } from './resolve.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.splitNextRequest = exports.isPersonRequest = exports.conversationPrompt = exports.NEXT_REQUEST_RULE = exports.FINISH_RULE = exports.ABSENCE_RULE = exports.assistantModeView = exports.resolveModeSettings = exports.clearAssistantModes = exports.getAssistantModeWarnings = exports.assistantModeForSubject = exports.listAssistantModes = exports.getAssistantMode = exports.registerAssistantMode = void 0;
|
|
3
|
+
exports.splitNextRequest = exports.isPersonRequest = exports.conversationPrompt = exports.SPEAKER_RULE = exports.NEXT_REQUEST_RULE = exports.FINISH_RULE = exports.ABSENCE_RULE = exports.assistantModeView = exports.resolveModeSettings = exports.clearAssistantModes = exports.getAssistantModeWarnings = exports.assistantModeForSubject = exports.listAssistantModes = exports.getAssistantMode = exports.registerAssistantMode = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Modes — what each conversation is, declared by the app server that owns the screen.
|
|
6
6
|
*
|
|
@@ -20,6 +20,7 @@ var screen_rules_js_1 = require("./screen-rules.js");
|
|
|
20
20
|
Object.defineProperty(exports, "ABSENCE_RULE", { enumerable: true, get: function () { return screen_rules_js_1.ABSENCE_RULE; } });
|
|
21
21
|
Object.defineProperty(exports, "FINISH_RULE", { enumerable: true, get: function () { return screen_rules_js_1.FINISH_RULE; } });
|
|
22
22
|
Object.defineProperty(exports, "NEXT_REQUEST_RULE", { enumerable: true, get: function () { return screen_rules_js_1.NEXT_REQUEST_RULE; } });
|
|
23
|
+
Object.defineProperty(exports, "SPEAKER_RULE", { enumerable: true, get: function () { return screen_rules_js_1.SPEAKER_RULE; } });
|
|
23
24
|
Object.defineProperty(exports, "conversationPrompt", { enumerable: true, get: function () { return screen_rules_js_1.conversationPrompt; } });
|
|
24
25
|
Object.defineProperty(exports, "isPersonRequest", { enumerable: true, get: function () { return screen_rules_js_1.isPersonRequest; } });
|
|
25
26
|
Object.defineProperty(exports, "splitNextRequest", { enumerable: true, get: function () { return screen_rules_js_1.splitNextRequest; } });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/index.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,6CAOsB;AANpB,oHAAA,qBAAqB,OAAA;AACrB,+GAAA,gBAAgB,OAAA;AAChB,iHAAA,kBAAkB,OAAA;AAClB,sHAAA,uBAAuB,OAAA;AACvB,uHAAA,wBAAwB,OAAA;AACxB,kHAAA,mBAAmB,OAAA;AAErB,2CAAqE;AAA5D,iHAAA,mBAAmB,OAAA;AAAE,+GAAA,iBAAiB,OAAA;AAC/C,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/index.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,6CAOsB;AANpB,oHAAA,qBAAqB,OAAA;AACrB,+GAAA,gBAAgB,OAAA;AAChB,iHAAA,kBAAkB,OAAA;AAClB,sHAAA,uBAAuB,OAAA;AACvB,uHAAA,wBAAwB,OAAA;AACxB,kHAAA,mBAAmB,OAAA;AAErB,2CAAqE;AAA5D,iHAAA,mBAAmB,OAAA;AAAE,+GAAA,iBAAiB,OAAA;AAC/C,qDAAqJ;AAA5I,+GAAA,YAAY,OAAA;AAAE,8GAAA,WAAW,OAAA;AAAE,oHAAA,iBAAiB,OAAA;AAAE,+GAAA,YAAY,OAAA;AAAE,qHAAA,kBAAkB,OAAA;AAAE,kHAAA,eAAe,OAAA;AAAE,mHAAA,gBAAgB,OAAA","sourcesContent":["/**\n * Modes — what each conversation is, declared by the app server that owns the screen.\n *\n * Design: ADR-0061 decision 7 (`operato-application/adr/ADR-0061.md`) and the AI standard's A2.\n */\nexport {\n registerAssistantMode,\n getAssistantMode,\n listAssistantModes,\n assistantModeForSubject,\n getAssistantModeWarnings,\n clearAssistantModes\n} from './registry.js'\nexport { resolveModeSettings, assistantModeView } from './resolve.js'\nexport { ABSENCE_RULE, FINISH_RULE, NEXT_REQUEST_RULE, SPEAKER_RULE, conversationPrompt, isPersonRequest, splitNextRequest } from './screen-rules.js'\nexport type { AssistantModeSpec, AssistantModeSlash, AssistantModeWarning, UnattendedLimits } from './types.js'\nexport type { AssistantModeRef, ModeSettings, AssistantModeView, AssistantModeSlashView, Translate } from './resolve.js'\n"]}
|
|
@@ -6,6 +6,7 @@ exports.screenTargetKeys = screenTargetKeys;
|
|
|
6
6
|
exports.modeFrontCategories = modeFrontCategories;
|
|
7
7
|
exports.modeScreens = modeScreens;
|
|
8
8
|
/* Read by the resolver and by the boot report, so the report and the conversation open the same tools. */
|
|
9
|
+
const entry_target_js_1 = require("../data-entry/entry-target.js");
|
|
9
10
|
const read_target_js_1 = require("../data-read/read-target.js");
|
|
10
11
|
/**
|
|
11
12
|
* The categories a mode opens: its own, the AI 작업판 (design/ai/workspace.md), the attachment reader when it
|
|
@@ -37,7 +38,13 @@ function unattendedToolCategories(mode) {
|
|
|
37
38
|
*/
|
|
38
39
|
function screenTargetKeys(mode, screen, routeOf = key => (0, read_target_js_1.readTargetOf)(key)?.screen?.route) {
|
|
39
40
|
const keys = [...new Set([...(mode.readTargets ?? []), ...(mode.entryTargets ?? [])])];
|
|
40
|
-
return keys.filter(key => (!!screen.page && routeOf(key) === screen.page) || (!!screen.subjectAxis && key
|
|
41
|
+
return keys.filter(key => (!!screen.page && routeOf(key) === screen.page) || (!!screen.subjectAxis && axesOfKey(mode, key).includes(screen.subjectAxis)));
|
|
42
|
+
}
|
|
43
|
+
/** The records a key is about: its last part, and the axes a card joins or a whole answer covers (EntryTarget.axes · DetailRead.axes). */
|
|
44
|
+
function axesOfKey(mode, key) {
|
|
45
|
+
const card = (mode.entryTargets ?? []).includes(key) ? (0, entry_target_js_1.entryTargetOf)(key)?.axes ?? [] : [];
|
|
46
|
+
const answer = (mode.readTargets ?? []).includes(key) ? (0, read_target_js_1.detailReadOf)(key)?.axes ?? [] : [];
|
|
47
|
+
return [key.split('.').pop(), ...card, ...answer];
|
|
41
48
|
}
|
|
42
49
|
/**
|
|
43
50
|
* The categories a turn puts in front when the mode's tools do not fit in one call: the mode's own, the AI 작업판, the
|
|
@@ -59,7 +66,7 @@ function modeFrontCategories(mode, screen = {}) {
|
|
|
59
66
|
function modeScreens(mode, routeOf = key => (0, read_target_js_1.readTargetOf)(key)?.screen?.route) {
|
|
60
67
|
const keys = [...new Set([...(mode.readTargets ?? []), ...(mode.entryTargets ?? [])])];
|
|
61
68
|
const pages = [...new Set(keys.map(routeOf).filter(Boolean))];
|
|
62
|
-
const axes = [...new Set(keys.
|
|
69
|
+
const axes = [...new Set(keys.flatMap(key => axesOfKey(mode, key)))];
|
|
63
70
|
return [{}, ...pages.map(page => ({ page })), ...axes.map(subjectAxis => ({ subjectAxis }))];
|
|
64
71
|
}
|
|
65
72
|
//# sourceMappingURL=mode-categories.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mode-categories.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/mode-categories.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"mode-categories.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/mode-categories.ts"],"names":[],"mappings":"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","sourcesContent":["/* Read by the resolver and by the boot report, so the report and the conversation open the same tools. */\nimport { entryTargetOf } from '../data-entry/entry-target.js'\nimport { detailReadOf, readTargetOf } from '../data-read/read-target.js'\n/**\n * The categories a mode opens: its own, the AI 작업판 (design/ai/workspace.md), the attachment reader when it\n * takes files (design/ai/shared-dock.md §9-4), and one propose tool per record it may enter (design/ai/data-entry.md).\n */\nexport function modeToolCategories(mode: { toolCategories?: string[]; attachments?: { kinds?: string[] }; entryTargets?: string[]; readTargets?: string[] }): string[] | undefined {\n const extra = [\n /* Every conversation may put a large result on the AI 작업판 (design/ai/workspace.md). */\n 'assistant-workspace',\n ...(mode.attachments?.kinds?.length ? ['chat-attachments'] : []),\n ...(mode.entryTargets ?? []).map(key => `data-entry:${key}`),\n ...(mode.readTargets ?? []).map(key => `data-read:${key}`)\n ]\n return [...new Set([...(mode.toolCategories ?? []), ...extra])]\n}\n\n/**\n * The categories an unattended run of a mode opens: its own and the lists it reads (design/ai/unattended-run.md §3).\n *\n * Not the 입력안 tools - nobody is there to press the card - nor the AI 작업판 or the attachment reader: there is no\n * conversation for a card or a file to belong to.\n */\nexport function unattendedToolCategories(mode: { toolCategories?: string[]; readTargets?: string[] }): string[] {\n return [...new Set([...(mode.toolCategories ?? []), ...(mode.readTargets ?? []).map(key => `data-read:${key}`)])]\n}\n\n/** The screen a turn is asked from - the page the dock is on, and the record its conversation is anchored to. */\nexport interface TurnScreen {\n /** The application's route of the open page (`cash-transaction-list`), as the dock sends it. */\n page?: string\n /** The axis of the record the conversation is anchored to (`contract` of `subject` · `contract:<id>`), from the stored anchor. */\n subjectAxis?: string\n}\n\ntype FrontMode = { toolCategories?: string[]; attachments?: { kinds?: string[] }; entryTargets?: string[]; readTargets?: string[] }\n\n/**\n * The records the open screen is about, among those the mode reads and enters (ADR-0061 decision 9). Nothing new is\n * declared: a read target's screen route is the page that lists it, and a key's last part is the axis of its record\n * (`office.contract` is the record of the axis `contract`) - reading and entering one record share its key.\n */\nexport function screenTargetKeys(mode: FrontMode, screen: TurnScreen, routeOf: (key: string) => string | undefined = key => readTargetOf(key)?.screen?.route): string[] {\n const keys = [...new Set([...(mode.readTargets ?? []), ...(mode.entryTargets ?? [])])]\n return keys.filter(key => (!!screen.page && routeOf(key) === screen.page) || (!!screen.subjectAxis && axesOfKey(mode, key).includes(screen.subjectAxis)))\n}\n\n/** The records a key is about: its last part, and the axes a card joins or a whole answer covers (EntryTarget.axes · DetailRead.axes). */\nfunction axesOfKey(mode: FrontMode, key: string): string[] {\n const card = (mode.entryTargets ?? []).includes(key) ? entryTargetOf(key)?.axes ?? [] : []\n const answer = (mode.readTargets ?? []).includes(key) ? detailReadOf(key)?.axes ?? [] : []\n return [key.split('.').pop()!, ...card, ...answer]\n}\n\n/**\n * The categories a turn puts in front when the mode's tools do not fit in one call: the mode's own, the AI 작업판, the\n * attachment reader, and the open screen's records. The rest the model opens with `openTools`.\n */\nexport function modeFrontCategories(mode: FrontMode, screen: TurnScreen = {}): string[] {\n const keys = screenTargetKeys(mode, screen)\n return [\n ...new Set([\n ...(mode.toolCategories ?? []),\n 'assistant-workspace',\n ...(mode.attachments?.kinds?.length ? ['chat-attachments'] : []),\n ...keys.filter(key => (mode.readTargets ?? []).includes(key)).map(key => `data-read:${key}`),\n ...keys.filter(key => (mode.entryTargets ?? []).includes(key)).map(key => `data-entry:${key}`)\n ])\n ]\n}\n\n/** Each screen the mode could be asked from - a page that lists one of its records, a record's axis - for the boot report. */\nexport function modeScreens(mode: FrontMode, routeOf: (key: string) => string | undefined = key => readTargetOf(key)?.screen?.route): TurnScreen[] {\n const keys = [...new Set([...(mode.readTargets ?? []), ...(mode.entryTargets ?? [])])]\n const pages = [...new Set(keys.map(routeOf).filter(Boolean) as string[])]\n const axes = [...new Set(keys.flatMap(key => axesOfKey(mode, key)))]\n return [{}, ...pages.map(page => ({ page })), ...axes.map(subjectAxis => ({ subjectAxis }))]\n}\n"]}
|
|
@@ -16,6 +16,12 @@ export declare const ABSENCE_RULE: string;
|
|
|
16
16
|
* the person's review; asking before it is asking twice.
|
|
17
17
|
*/
|
|
18
18
|
export declare const FINISH_RULE: string;
|
|
19
|
+
/**
|
|
20
|
+
* Who spoke, never what was asked (ADR-0070 A15 decision 2 labels every person's message `[writer → addressee]`, a
|
|
21
|
+
* conversation of one included). Asked 「거래처 미등록 입출금에 등록 거래처를 연결해 줘」, the model read the asker's name in
|
|
22
|
+
* that bracket as part of the request and searched the bank lines for it (2026-10-24).
|
|
23
|
+
*/
|
|
24
|
+
export declare const SPEAKER_RULE: string;
|
|
19
25
|
/** §4-1 — the reply names the next request, and the input offers it. */
|
|
20
26
|
export declare const NEXT_REQUEST_RULE: string;
|
|
21
27
|
/** A mode's persona with the rules every conversation follows. */
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Model-facing text is English (the AI standard's A11); the person's language comes from the request.
|
|
10
10
|
*/
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.NEXT_REQUEST_RULE = exports.FINISH_RULE = exports.ABSENCE_RULE = void 0;
|
|
12
|
+
exports.NEXT_REQUEST_RULE = exports.SPEAKER_RULE = exports.FINISH_RULE = exports.ABSENCE_RULE = void 0;
|
|
13
13
|
exports.conversationPrompt = conversationPrompt;
|
|
14
14
|
exports.isPersonRequest = isPersonRequest;
|
|
15
15
|
exports.splitNextRequest = splitNextRequest;
|
|
@@ -27,17 +27,24 @@ exports.FINISH_RULE = 'Finish what was asked in this turn: read what it needs an
|
|
|
27
27
|
'resend the request. Do not ask for an id, a number or a name your tools can read. Ask only when the records leave a ' +
|
|
28
28
|
'choice that only the person can make, and then ask just that choice. Answer briefly: what was found and what the card ' +
|
|
29
29
|
'does, in the person\'s words - no internal ids, keys, field names or codes.';
|
|
30
|
+
/**
|
|
31
|
+
* Who spoke, never what was asked (ADR-0070 A15 decision 2 labels every person's message `[writer → addressee]`, a
|
|
32
|
+
* conversation of one included). Asked 「거래처 미등록 입출금에 등록 거래처를 연결해 줘」, the model read the asker's name in
|
|
33
|
+
* that bracket as part of the request and searched the bank lines for it (2026-10-24).
|
|
34
|
+
*/
|
|
35
|
+
exports.SPEAKER_RULE = "A person's message begins with [writer → addressee]: it says who wrote it and to whom, nothing more. Never search for, " +
|
|
36
|
+
'filter by or fill a field with the writer\'s name unless the request itself names them.';
|
|
30
37
|
/** The line a reply ends with when a next request is natural (§4-1). */
|
|
31
38
|
const NEXT_PREFIX = 'Next:';
|
|
32
39
|
/** §4-1 — the reply names the next request, and the input offers it. */
|
|
33
|
-
exports.NEXT_REQUEST_RULE = `After answering, when there is a natural next request, end the reply with one last line "${NEXT_PREFIX} <request>": ` +
|
|
40
|
+
exports.NEXT_REQUEST_RULE = `After answering, when there is a natural next request, end the reply with one last line "${NEXT_PREFIX} <request>" - the word ${NEXT_PREFIX} in English whatever language you answer in: ` +
|
|
34
41
|
'a new request the person is most likely to make next, exactly the words they would type, in the language they wrote in - ' +
|
|
35
42
|
'no quotation marks, no "say ..." or "ask me ...", no ids or keys. Never a yes to your own question and never what this ' +
|
|
36
43
|
'reply could have done - do that now instead. Offer only what this conversation can do with its tools. ' +
|
|
37
44
|
'When nothing natural follows, leave the line out.';
|
|
38
45
|
/** A mode's persona with the rules every conversation follows. */
|
|
39
46
|
function conversationPrompt(modePrompt) {
|
|
40
|
-
return [modePrompt.trim(), exports.ABSENCE_RULE, exports.FINISH_RULE, exports.NEXT_REQUEST_RULE].join('\n\n');
|
|
47
|
+
return [modePrompt.trim(), exports.ABSENCE_RULE, exports.FINISH_RULE, exports.SPEAKER_RULE, exports.NEXT_REQUEST_RULE].join('\n\n');
|
|
41
48
|
}
|
|
42
49
|
/** Quotation marks inside a request: it quotes another sentence, so it is about a request, not one. */
|
|
43
50
|
const QUOTED = /["“”‘「」『』`]/;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screen-rules.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/screen-rules.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;
|
|
1
|
+
{"version":3,"file":"screen-rules.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/screen-rules.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAyCH,gDAEC;AAYD,0CAEC;AAQD,4CAYC;AA3ED,6EAA6E;AAChE,QAAA,YAAY,GACvB,0FAA0F;IAC1F,sIAAsI,CAAA;AAExI;;;;;GAKG;AACU,QAAA,WAAW,GACtB,2HAA2H;IAC3H,uHAAuH;IACvH,sHAAsH;IACtH,wHAAwH;IACxH,6EAA6E,CAAA;AAE/E;;;;GAIG;AACU,QAAA,YAAY,GACvB,yHAAyH;IACzH,yFAAyF,CAAA;AAE3F,wEAAwE;AACxE,MAAM,WAAW,GAAG,OAAO,CAAA;AAE3B,wEAAwE;AAC3D,QAAA,iBAAiB,GAC5B,4FAA4F,WAAW,0BAA0B,WAAW,+CAA+C;IAC3L,2HAA2H;IAC3H,yHAAyH;IACzH,wGAAwG;IACxG,mDAAmD,CAAA;AAErD,kEAAkE;AAClE,SAAgB,kBAAkB,CAAC,UAAkB;IACnD,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,oBAAY,EAAE,mBAAW,EAAE,oBAAY,EAAE,yBAAiB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;AACrG,CAAC;AAED,uGAAuG;AACvG,MAAM,MAAM,GAAG,aAAa,CAAA;AAC5B,sDAAsD;AACtD,MAAM,OAAO,GAAG,kGAAkG,CAAA;AAElH;;;;GAIG;AACH,SAAgB,eAAe,CAAC,IAAY;IAC1C,OAAO,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC5D,CAAC;AAED;;;;;GAKG;AACH,SAAgB,gBAAgB,CAAC,KAAa;IAC5C,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;IAChC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAClD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;IAC1C,MAAM,KAAK,GAAG,yCAAyC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAClE,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IAElC,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACpE,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAC9D,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IAChD,kGAAkG;IAClG,OAAO,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;AACpF,CAAC","sourcesContent":["/**\n * The rules every conversation follows, whatever app declared it (operato-application design/ai/screen-pattern.md).\n *\n * Five apps carried their own copy of the absence rule, word for word or nearly. A rule every conversation needs\n * belongs to the door that builds every conversation's prompt, so an app's prompt says only who the assistant is\n * there and what it may not claim.\n *\n * Model-facing text is English (the AI standard's A11); the person's language comes from the request.\n */\n\n/** §4-2 — an absence or a refusal is a fact, with what the person can do. */\nexport const ABSENCE_RULE =\n 'When a tool says something is absent or refuses, state that and what the person can do. ' +\n 'Do not guess why, and do not describe our machinery - words like passed, delivered or loaded name our plumbing, not their situation.'\n\n/**\n * §4-3 — a request is finished in the turn it is asked (2026-10-23, the user: 「한번에 끝낼 일을 왜 자꾸 확인하고, 프롬프트를\n * 또 입력하게 만드는가」). Asked to link a deposit, the model found the invoice, then asked whether to propose the link\n * and offered 「…진행해줘\"라고 해줘」 as the next request - a second turn for what the first could do. A proposal card is\n * the person's review; asking before it is asking twice.\n */\nexport const FINISH_RULE =\n 'Finish what was asked in this turn: read what it needs and, when it changes records, propose the card in the same turn - ' +\n 'the card is the person\\'s review, so never ask whether to propose it, and never ask the person to confirm, repeat or ' +\n 'resend the request. Do not ask for an id, a number or a name your tools can read. Ask only when the records leave a ' +\n 'choice that only the person can make, and then ask just that choice. Answer briefly: what was found and what the card ' +\n 'does, in the person\\'s words - no internal ids, keys, field names or codes.'\n\n/**\n * Who spoke, never what was asked (ADR-0070 A15 decision 2 labels every person's message `[writer → addressee]`, a\n * conversation of one included). Asked 「거래처 미등록 입출금에 등록 거래처를 연결해 줘」, the model read the asker's name in\n * that bracket as part of the request and searched the bank lines for it (2026-10-24).\n */\nexport const SPEAKER_RULE =\n \"A person's message begins with [writer → addressee]: it says who wrote it and to whom, nothing more. Never search for, \" +\n 'filter by or fill a field with the writer\\'s name unless the request itself names them.'\n\n/** The line a reply ends with when a next request is natural (§4-1). */\nconst NEXT_PREFIX = 'Next:'\n\n/** §4-1 — the reply names the next request, and the input offers it. */\nexport const NEXT_REQUEST_RULE =\n `After answering, when there is a natural next request, end the reply with one last line \"${NEXT_PREFIX} <request>\" - the word ${NEXT_PREFIX} in English whatever language you answer in: ` +\n 'a new request the person is most likely to make next, exactly the words they would type, in the language they wrote in - ' +\n 'no quotation marks, no \"say ...\" or \"ask me ...\", no ids or keys. Never a yes to your own question and never what this ' +\n 'reply could have done - do that now instead. Offer only what this conversation can do with its tools. ' +\n 'When nothing natural follows, leave the line out.'\n\n/** A mode's persona with the rules every conversation follows. */\nexport function conversationPrompt(modePrompt: string): string {\n return [modePrompt.trim(), ABSENCE_RULE, FINISH_RULE, SPEAKER_RULE, NEXT_REQUEST_RULE].join('\\n\\n')\n}\n\n/** Quotation marks inside a request: it quotes another sentence, so it is about a request, not one. */\nconst QUOTED = /[\"“”‘「」『』`]/\n/** An id or a record key - nothing a person types. */\nconst MACHINE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|\\b[a-z]+(?:-[a-z]+)*:[0-9a-f-]{8,}/i\n\n/**\n * Whether the line names a request the person could send as it is. The input offers it to be sent with one key, so a\n * line that tells the person what to say (「\"…진행해줘\"라고 해줘」) or carries an id is no request: offered, it is sent\n * back to the model as the person's words.\n */\nexport function isPersonRequest(text: string): boolean {\n return !!text && !QUOTED.test(text) && !MACHINE.test(text)\n}\n\n/**\n * The reply without its `Next:` line, and the request that line named.\n *\n * Only the last non-empty line counts: a `Next:` in the middle of an answer is part of the answer. The line is\n * taken off before the reply is stored, so a conversation read later shows the answer alone.\n */\nexport function splitNextRequest(reply: string): { reply: string; suggestion?: string } {\n const text = String(reply ?? '')\n const lines = text.replace(/\\s+$/, '').split('\\n')\n const last = lines[lines.length - 1] ?? ''\n const match = /^\\s*(?:\\*\\*)?Next:(?:\\*\\*)?\\s*(.+?)\\s*$/.exec(last)\n if (!match) return { reply: text }\n\n const suggestion = match[1].replace(/^[\"“'`]+|[\"”'`]+$/g, '').trim()\n const rest = lines.slice(0, -1).join('\\n').replace(/\\s+$/, '')\n if (!suggestion || !rest) return { reply: text }\n /* The line is ours either way - a line that is no request is taken off and offered as nothing. */\n return isPersonRequest(suggestion) ? { reply: rest, suggestion } : { reply: rest }\n}\n"]}
|
|
@@ -16,6 +16,7 @@ exports.chatMessageStream = chatMessageStream;
|
|
|
16
16
|
const graphql_yoga_1 = require("graphql-yoga");
|
|
17
17
|
const shell_1 = require("@things-factory/shell");
|
|
18
18
|
const chat_message_broadcast_js_1 = require("./chat-message-broadcast.js");
|
|
19
|
+
const participant_lookup_roles_js_1 = require("../chat-session-participant/participant-lookup-roles.js");
|
|
19
20
|
/**
|
|
20
21
|
* The two lookups about a room. **Both search within the caller's domain** — the same condition as
|
|
21
22
|
* the stored path's query (`chatMessages`).
|
|
@@ -32,7 +33,8 @@ function chatRoomLookup(repos, who) {
|
|
|
32
33
|
const seat = await repos.participants.findOne({
|
|
33
34
|
where: { session: { id: who.sessionId }, user: { id: who.userId }, domain: { id: who.domainId } }
|
|
34
35
|
});
|
|
35
|
-
|
|
36
|
+
/* A hide-only `viewer` row is not a seat - the stored path asks the same (`isSessionParticipant`). */
|
|
37
|
+
return (0, participant_lookup_roles_js_1.isRoomRole)(seat?.role);
|
|
36
38
|
}
|
|
37
39
|
};
|
|
38
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-message-room.js","sourceRoot":"","sources":["../../../server/service/chat-message/chat-message-room.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"chat-message-room.js","sourceRoot":"","sources":["../../../server/service/chat-message/chat-message-room.ts"],"names":[],"mappings":";;AAyBA,wCAmBC;AAGD,8CAEC;AAjDD;;;;;;;;;;GAUG;AACH,+CAA2C;AAC3C,iDAA8C;AAE9C,2EAA4G;AAC5G,yGAAoF;AAMpF;;;GAGG;AACH,SAAgB,cAAc,CAC5B,KAAmD,EACnD,GAA6D;IAE7D,OAAO;QACL,KAAK,CAAC,UAAU;YACd,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAA;YAC5G,OAAO,OAAO,CAAC,CAAC,CAAC,CAAE,OAAe,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QACpE,CAAC;QAED,KAAK,CAAC,aAAa;YACjB,IAAI,CAAC,GAAG,CAAC,MAAM;gBAAE,OAAO,KAAK,CAAA;YAC7B,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC;gBAC5C,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,EAAE;aAClG,CAAC,CAAA;YACF,sGAAsG;YACtG,OAAO,IAAA,wCAAU,EAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QAC/B,CAAC;KACF,CAAA;AACH,CAAC;AAED,sHAAsH;AACtH,SAAgB,iBAAiB,CAAC,QAAgB,EAAE,SAAiB,EAAE,IAAoB;IACzF,OAAO,IAAA,mBAAI,EAAC,cAAM,CAAC,SAAS,CAAC,8CAAkB,CAAC,EAAE,IAAA,qBAAM,EAAC,IAAA,iDAAqB,EAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;AAC7G,CAAC","sourcesContent":["/*\n * The room on the broadcast path — the stream the subscription returns, and the two lookups that\n * ask about the room.\n *\n * jest cannot read the subscription resolver (parameter decorators: @Root · @Arg), so composing the\n * stream lives here — the test runs **the same thing** the resolver returns\n * (`chat-message-room.test.ts`).\n *\n * The caller passes the storage in. Importing the entities here would make this file unreadable to\n * the tests too.\n */\nimport { filter, pipe } from 'graphql-yoga'\nimport { pubsub } from '@things-factory/shell'\n\nimport { CHAT_MESSAGE_TOPIC, chatMessageRoomFilter, type ChatRoomLookup } from './chat-message-broadcast.js'\nimport { isRoomRole } from '../chat-session-participant/participant-lookup-roles.js'\n\ninterface FindOne {\n findOne(options: { where: any; select?: any }): Promise<any>\n}\n\n/**\n * The two lookups about a room. **Both search within the caller's domain** — the same condition as\n * the stored path's query (`chatMessages`).\n */\nexport function chatRoomLookup(\n repos: { sessions: FindOne; participants: FindOne },\n who: { domainId: string; sessionId: string; userId?: string }\n): ChatRoomLookup {\n return {\n async visibility() {\n const session = await repos.sessions.findOne({ where: { id: who.sessionId, domain: { id: who.domainId } } })\n return session ? ((session as any).visibility ?? null) : undefined\n },\n\n async isParticipant() {\n if (!who.userId) return false\n const seat = await repos.participants.findOne({\n where: { session: { id: who.sessionId }, user: { id: who.userId }, domain: { id: who.domainId } }\n })\n /* A hide-only `viewer` row is not a seat - the stored path asks the same (`isSessionParticipant`). */\n return isRoomRole(seat?.role)\n }\n }\n}\n\n/** The stream the subscription returns — same domain + session, and the room open to this person at every message. */\nexport function chatMessageStream(domainId: string, sessionId: string, room: ChatRoomLookup) {\n return pipe(pubsub.subscribe(CHAT_MESSAGE_TOPIC), filter(chatMessageRoomFilter(domainId, sessionId, room)))\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ChatTypingPayload } from './chat-typing.js';
|
|
2
|
+
export declare class ChatTyping {
|
|
3
|
+
sessionId: string;
|
|
4
|
+
userId: string;
|
|
5
|
+
name: string;
|
|
6
|
+
at: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class ChatTypingSubscription {
|
|
9
|
+
chatTyping(payload: ChatTypingPayload, sessionId: string): ChatTyping;
|
|
10
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ChatTypingSubscription = exports.ChatTyping = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
/*
|
|
6
|
+
* ChatTypingSubscription - "someone is typing" in a conversation, live. The rules are in `chat-typing.ts`;
|
|
7
|
+
* this file holds only pubsub and the decorators, which jest cannot read.
|
|
8
|
+
*/
|
|
9
|
+
const type_graphql_1 = require("type-graphql");
|
|
10
|
+
const graphql_yoga_1 = require("graphql-yoga");
|
|
11
|
+
const shell_1 = require("@things-factory/shell");
|
|
12
|
+
const chat_session_js_1 = require("../chat-session/chat-session.js");
|
|
13
|
+
const chat_session_participant_js_1 = require("../chat-session-participant/chat-session-participant.js");
|
|
14
|
+
const chat_message_broadcast_js_1 = require("./chat-message-broadcast.js");
|
|
15
|
+
const chat_message_room_js_1 = require("./chat-message-room.js");
|
|
16
|
+
const chat_typing_js_1 = require("./chat-typing.js");
|
|
17
|
+
let ChatTyping = class ChatTyping {
|
|
18
|
+
};
|
|
19
|
+
exports.ChatTyping = ChatTyping;
|
|
20
|
+
tslib_1.__decorate([
|
|
21
|
+
(0, type_graphql_1.Field)({ description: 'Conversation the person is typing in.' }),
|
|
22
|
+
tslib_1.__metadata("design:type", String)
|
|
23
|
+
], ChatTyping.prototype, "sessionId", void 0);
|
|
24
|
+
tslib_1.__decorate([
|
|
25
|
+
(0, type_graphql_1.Field)({ description: 'User who is typing.' }),
|
|
26
|
+
tslib_1.__metadata("design:type", String)
|
|
27
|
+
], ChatTyping.prototype, "userId", void 0);
|
|
28
|
+
tslib_1.__decorate([
|
|
29
|
+
(0, type_graphql_1.Field)({ description: 'Name the room shows for that user.' }),
|
|
30
|
+
tslib_1.__metadata("design:type", String)
|
|
31
|
+
], ChatTyping.prototype, "name", void 0);
|
|
32
|
+
tslib_1.__decorate([
|
|
33
|
+
(0, type_graphql_1.Field)({ description: 'When the notice was sent (ISO).' }),
|
|
34
|
+
tslib_1.__metadata("design:type", String)
|
|
35
|
+
], ChatTyping.prototype, "at", void 0);
|
|
36
|
+
exports.ChatTyping = ChatTyping = tslib_1.__decorate([
|
|
37
|
+
(0, type_graphql_1.ObjectType)({ description: 'Someone in the conversation is typing. Nothing is stored; the screen lets the notice fade without the next one.' })
|
|
38
|
+
], ChatTyping);
|
|
39
|
+
let ChatTypingSubscription = class ChatTypingSubscription {
|
|
40
|
+
chatTyping(payload, sessionId) {
|
|
41
|
+
return { sessionId: payload.sessionId, userId: payload.userId, name: payload.name, at: payload.at };
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
exports.ChatTypingSubscription = ChatTypingSubscription;
|
|
45
|
+
tslib_1.__decorate([
|
|
46
|
+
(0, type_graphql_1.Subscription)(() => ChatTyping, {
|
|
47
|
+
description: 'Subscribes to "someone is typing" notices of a conversation the caller may read - never the caller\'s own.',
|
|
48
|
+
subscribe: async ({ args, context }) => {
|
|
49
|
+
const { domain, user } = context.state;
|
|
50
|
+
const { sessionId } = args;
|
|
51
|
+
await (0, shell_1.assertDomainSubscribeAllowed)(domain, user, (d, u) => process.superUserGranted(d, u));
|
|
52
|
+
/* The same room question the messages ask - refused in the words of a missing session. */
|
|
53
|
+
const room = (0, chat_message_room_js_1.chatRoomLookup)({ sessions: (0, shell_1.getRepository)(chat_session_js_1.ChatSession), participants: (0, shell_1.getRepository)(chat_session_participant_js_1.ChatSessionParticipant) }, { domainId: domain.id, sessionId, userId: user?.id });
|
|
54
|
+
if (!(await (0, chat_message_broadcast_js_1.mayHearRoom)(room)))
|
|
55
|
+
throw new Error('chat session not found');
|
|
56
|
+
return (0, graphql_yoga_1.pipe)(shell_1.pubsub.subscribe(chat_typing_js_1.CHAT_TYPING_TOPIC), (0, graphql_yoga_1.filter)((0, chat_typing_js_1.chatTypingFilter)(domain.id, sessionId, user?.id, room)));
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
59
|
+
tslib_1.__param(0, (0, type_graphql_1.Root)()),
|
|
60
|
+
tslib_1.__param(1, (0, type_graphql_1.Arg)('sessionId')),
|
|
61
|
+
tslib_1.__metadata("design:type", Function),
|
|
62
|
+
tslib_1.__metadata("design:paramtypes", [Object, String]),
|
|
63
|
+
tslib_1.__metadata("design:returntype", ChatTyping)
|
|
64
|
+
], ChatTypingSubscription.prototype, "chatTyping", null);
|
|
65
|
+
exports.ChatTypingSubscription = ChatTypingSubscription = tslib_1.__decorate([
|
|
66
|
+
(0, type_graphql_1.Resolver)(ChatTyping)
|
|
67
|
+
], ChatTypingSubscription);
|
|
68
|
+
//# sourceMappingURL=chat-typing-subscription.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-typing-subscription.js","sourceRoot":"","sources":["../../../server/service/chat-message/chat-typing-subscription.ts"],"names":[],"mappings":";;;;AAAA;;;GAGG;AACH,+CAAmF;AACnF,+CAA2C;AAC3C,iDAA2F;AAE3F,qEAA6D;AAC7D,yGAAgG;AAChG,2EAAyD;AACzD,iEAAuD;AACvD,qDAA8F;AAGvF,IAAM,UAAU,GAAhB,MAAM,UAAU;CAYtB,CAAA;AAZY,gCAAU;AAErB;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,uCAAuC,EAAE,CAAC;;6CAC9C;AAGlB;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,qBAAqB,EAAE,CAAC;;0CAC/B;AAGf;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,oCAAoC,EAAE,CAAC;;wCAChD;AAGb;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,iCAAiC,EAAE,CAAC;;sCAC/C;qBAXA,UAAU;IADtB,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,iHAAiH,EAAE,CAAC;GAClI,UAAU,CAYtB;AAGM,IAAM,sBAAsB,GAA5B,MAAM,sBAAsB;IAkBjC,UAAU,CAAS,OAA0B,EAAoB,SAAiB;QAChF,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAA;IACrG,CAAC;CACF,CAAA;AArBY,wDAAsB;AAkBjC;IAjBC,IAAA,2BAAY,EAAC,GAAG,EAAE,CAAC,UAAU,EAAE;QAC9B,WAAW,EAAE,4GAA4G;QACzH,SAAS,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;YACrC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;YACtC,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAA;YAC1B,MAAM,IAAA,oCAA4B,EAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;YAE1F,0FAA0F;YAC1F,MAAM,IAAI,GAAG,IAAA,qCAAc,EACzB,EAAE,QAAQ,EAAE,IAAA,qBAAa,EAAC,6BAAW,CAAC,EAAE,YAAY,EAAE,IAAA,qBAAa,EAAC,oDAAsB,CAAC,EAAE,EAC7F,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,CACrD,CAAA;YACD,IAAI,CAAC,CAAC,MAAM,IAAA,uCAAW,EAAC,IAAI,CAAC,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAA;YAEzE,OAAO,IAAA,mBAAI,EAAC,cAAM,CAAC,SAAS,CAAC,kCAAiB,CAAC,EAAE,IAAA,qBAAM,EAAC,IAAA,iCAAgB,EAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;QAClH,CAAC;KACF,CAAC;IACU,mBAAA,IAAA,mBAAI,GAAE,CAAA;IAA8B,mBAAA,IAAA,kBAAG,EAAC,WAAW,CAAC,CAAA;;;4CAAqB,UAAU;wDAE9F;iCApBU,sBAAsB;IADlC,IAAA,uBAAQ,EAAC,UAAU,CAAC;GACR,sBAAsB,CAqBlC","sourcesContent":["/*\n * ChatTypingSubscription - \"someone is typing\" in a conversation, live. The rules are in `chat-typing.ts`;\n * this file holds only pubsub and the decorators, which jest cannot read.\n */\nimport { Resolver, Subscription, Root, Arg, ObjectType, Field } from 'type-graphql'\nimport { filter, pipe } from 'graphql-yoga'\nimport { assertDomainSubscribeAllowed, getRepository, pubsub } from '@things-factory/shell'\n\nimport { ChatSession } from '../chat-session/chat-session.js'\nimport { ChatSessionParticipant } from '../chat-session-participant/chat-session-participant.js'\nimport { mayHearRoom } from './chat-message-broadcast.js'\nimport { chatRoomLookup } from './chat-message-room.js'\nimport { CHAT_TYPING_TOPIC, chatTypingFilter, type ChatTypingPayload } from './chat-typing.js'\n\n@ObjectType({ description: 'Someone in the conversation is typing. Nothing is stored; the screen lets the notice fade without the next one.' })\nexport class ChatTyping {\n @Field({ description: 'Conversation the person is typing in.' })\n sessionId!: string\n\n @Field({ description: 'User who is typing.' })\n userId!: string\n\n @Field({ description: 'Name the room shows for that user.' })\n name!: string\n\n @Field({ description: 'When the notice was sent (ISO).' })\n at!: string\n}\n\n@Resolver(ChatTyping)\nexport class ChatTypingSubscription {\n @Subscription(() => ChatTyping, {\n description: 'Subscribes to \"someone is typing\" notices of a conversation the caller may read - never the caller\\'s own.',\n subscribe: async ({ args, context }) => {\n const { domain, user } = context.state\n const { sessionId } = args\n await assertDomainSubscribeAllowed(domain, user, (d, u) => process.superUserGranted(d, u))\n\n /* The same room question the messages ask - refused in the words of a missing session. */\n const room = chatRoomLookup(\n { sessions: getRepository(ChatSession), participants: getRepository(ChatSessionParticipant) },\n { domainId: domain.id, sessionId, userId: user?.id }\n )\n if (!(await mayHearRoom(room))) throw new Error('chat session not found')\n\n return pipe(pubsub.subscribe(CHAT_TYPING_TOPIC), filter(chatTypingFilter(domain.id, sessionId, user?.id, room)))\n }\n })\n chatTyping(@Root() payload: ChatTypingPayload, @Arg('sessionId') sessionId: string): ChatTyping {\n return { sessionId: payload.sessionId, userId: payload.userId, name: payload.name, at: payload.at }\n }\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ChatRoomLookup } from './chat-message-broadcast.js';
|
|
2
|
+
export declare const CHAT_TYPING_TOPIC = "ai-assistant-chat-typing";
|
|
3
|
+
/** How long a notice stands on the screen without the next one. The client sends one every few seconds while typing. */
|
|
4
|
+
export declare const TYPING_NOTICE_MS = 6000;
|
|
5
|
+
export interface ChatTypingPayload {
|
|
6
|
+
domainId: string;
|
|
7
|
+
sessionId: string;
|
|
8
|
+
userId: string;
|
|
9
|
+
/** The name the room shows - the same name a message's sender line shows. */
|
|
10
|
+
name: string;
|
|
11
|
+
/** ISO time the notice was sent. */
|
|
12
|
+
at: string;
|
|
13
|
+
}
|
|
14
|
+
/** The notice a typing person sends - a name is cut to one line, as the room's other names are. */
|
|
15
|
+
export declare function typingNotice(input: {
|
|
16
|
+
domainId: string;
|
|
17
|
+
sessionId: string;
|
|
18
|
+
user: {
|
|
19
|
+
id?: string;
|
|
20
|
+
name?: string | null;
|
|
21
|
+
email?: string | null;
|
|
22
|
+
};
|
|
23
|
+
}, now?: Date): ChatTypingPayload | null;
|
|
24
|
+
/**
|
|
25
|
+
* Passes a notice of the same domain and session, not the listener's own, and only while the room is open to
|
|
26
|
+
* the listener.
|
|
27
|
+
*/
|
|
28
|
+
export declare function chatTypingFilter(domainId: string, sessionId: string, listenerId: string | undefined, room: ChatRoomLookup): (payload: ChatTypingPayload) => Promise<boolean>;
|