@things-factory/ai-assistant 10.1.26 → 10.1.28

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.
Files changed (194) hide show
  1. package/client/components/assistant-chat.ts +653 -59
  2. package/client/components/assistant-mode.test.ts +86 -55
  3. package/client/components/assistant-mode.ts +124 -82
  4. package/client/components/chat-echo-dedup.test.ts +23 -0
  5. package/client/components/chat-echo-dedup.ts +29 -3
  6. package/client/components/chat-input-builder.test.ts +20 -20
  7. package/client/components/chat-input-builder.ts +16 -14
  8. package/client/components/chat-proposal-survival.test.ts +73 -0
  9. package/client/components/mention-popup.ts +31 -8
  10. package/client/components/panel-aria.test.ts +34 -0
  11. package/client/components/proposal-outcome-line.ts +33 -0
  12. package/client/utils/assistant-session-controller.test.ts +75 -0
  13. package/client/utils/assistant-session-controller.ts +23 -0
  14. package/client/utils/assistant-session-transport.ts +11 -3
  15. package/dist-client/components/assistant-chat.d.ts +128 -13
  16. package/dist-client/components/assistant-chat.js +613 -67
  17. package/dist-client/components/assistant-chat.js.map +1 -1
  18. package/dist-client/components/assistant-mode.d.ts +80 -68
  19. package/dist-client/components/assistant-mode.js +63 -19
  20. package/dist-client/components/assistant-mode.js.map +1 -1
  21. package/dist-client/components/assistant-mode.test.js +74 -53
  22. package/dist-client/components/assistant-mode.test.js.map +1 -1
  23. package/dist-client/components/chat-echo-dedup.d.ts +2 -0
  24. package/dist-client/components/chat-echo-dedup.js +27 -4
  25. package/dist-client/components/chat-echo-dedup.js.map +1 -1
  26. package/dist-client/components/chat-echo-dedup.test.js +20 -0
  27. package/dist-client/components/chat-echo-dedup.test.js.map +1 -1
  28. package/dist-client/components/chat-input-builder.d.ts +12 -7
  29. package/dist-client/components/chat-input-builder.js +8 -10
  30. package/dist-client/components/chat-input-builder.js.map +1 -1
  31. package/dist-client/components/chat-input-builder.test.js +17 -18
  32. package/dist-client/components/chat-input-builder.test.js.map +1 -1
  33. package/dist-client/components/chat-proposal-survival.test.d.ts +1 -0
  34. package/dist-client/components/chat-proposal-survival.test.js +61 -0
  35. package/dist-client/components/chat-proposal-survival.test.js.map +1 -0
  36. package/dist-client/components/mention-popup.js +28 -8
  37. package/dist-client/components/mention-popup.js.map +1 -1
  38. package/dist-client/components/panel-aria.test.d.ts +1 -0
  39. package/dist-client/components/panel-aria.test.js +31 -0
  40. package/dist-client/components/panel-aria.test.js.map +1 -0
  41. package/dist-client/components/proposal-outcome-line.d.ts +9 -0
  42. package/dist-client/components/proposal-outcome-line.js +15 -0
  43. package/dist-client/components/proposal-outcome-line.js.map +1 -0
  44. package/dist-client/tsconfig.tsbuildinfo +1 -1
  45. package/dist-client/utils/assistant-session-controller.d.ts +10 -0
  46. package/dist-client/utils/assistant-session-controller.js +16 -0
  47. package/dist-client/utils/assistant-session-controller.js.map +1 -1
  48. package/dist-client/utils/assistant-session-controller.test.d.ts +1 -0
  49. package/dist-client/utils/assistant-session-controller.test.js +56 -0
  50. package/dist-client/utils/assistant-session-controller.test.js.map +1 -0
  51. package/dist-client/utils/assistant-session-transport.js +11 -3
  52. package/dist-client/utils/assistant-session-transport.js.map +1 -1
  53. package/dist-server/service/assistant-capability-resolver.d.ts +17 -0
  54. package/dist-server/service/assistant-capability-resolver.js +88 -0
  55. package/dist-server/service/assistant-capability-resolver.js.map +1 -0
  56. package/dist-server/service/assistant-chat-resolver.d.ts +6 -6
  57. package/dist-server/service/assistant-chat-resolver.js +166 -44
  58. package/dist-server/service/assistant-chat-resolver.js.map +1 -1
  59. package/dist-server/service/assistant-mode/assistant-mode-resolver.d.ts +25 -0
  60. package/dist-server/service/assistant-mode/assistant-mode-resolver.js +140 -0
  61. package/dist-server/service/assistant-mode/assistant-mode-resolver.js.map +1 -0
  62. package/dist-server/service/assistant-mode/index.d.ts +9 -0
  63. package/dist-server/service/assistant-mode/index.js +19 -0
  64. package/dist-server/service/assistant-mode/index.js.map +1 -0
  65. package/dist-server/service/assistant-mode/registry.d.ts +18 -0
  66. package/dist-server/service/assistant-mode/registry.js +136 -0
  67. package/dist-server/service/assistant-mode/registry.js.map +1 -0
  68. package/dist-server/service/assistant-mode/resolve.d.ts +33 -0
  69. package/dist-server/service/assistant-mode/resolve.js +60 -0
  70. package/dist-server/service/assistant-mode/resolve.js.map +1 -0
  71. package/dist-server/service/assistant-mode/types.d.ts +99 -0
  72. package/dist-server/service/assistant-mode/types.js +12 -0
  73. package/dist-server/service/assistant-mode/types.js.map +1 -0
  74. package/dist-server/service/chat-message/carried-turns.d.ts +48 -0
  75. package/dist-server/service/chat-message/carried-turns.js +80 -0
  76. package/dist-server/service/chat-message/carried-turns.js.map +1 -0
  77. package/dist-server/service/chat-message/chat-message-subscription.d.ts +1 -1
  78. package/dist-server/service/chat-message/chat-message-subscription.js +24 -6
  79. package/dist-server/service/chat-message/chat-message-subscription.js.map +1 -1
  80. package/dist-server/service/chat-message/chat-message.d.ts +32 -0
  81. package/dist-server/service/chat-message/chat-message.js +17 -0
  82. package/dist-server/service/chat-message/chat-message.js.map +1 -1
  83. package/dist-server/service/chat-message/door-access.d.ts +15 -0
  84. package/dist-server/service/chat-message/door-access.js +26 -0
  85. package/dist-server/service/chat-message/door-access.js.map +1 -0
  86. package/dist-server/service/chat-message/fold-session-history.d.ts +30 -0
  87. package/dist-server/service/chat-message/fold-session-history.js +133 -0
  88. package/dist-server/service/chat-message/fold-session-history.js.map +1 -0
  89. package/dist-server/service/chat-message/llm-history.d.ts +7 -0
  90. package/dist-server/service/chat-message/llm-history.js +23 -11
  91. package/dist-server/service/chat-message/llm-history.js.map +1 -1
  92. package/dist-server/service/chat-message/message-doors.d.ts +29 -0
  93. package/dist-server/service/chat-message/message-doors.js +107 -0
  94. package/dist-server/service/chat-message/message-doors.js.map +1 -0
  95. package/dist-server/service/chat-message/proposal-outcome.d.ts +18 -0
  96. package/dist-server/service/chat-message/proposal-outcome.js +31 -0
  97. package/dist-server/service/chat-message/proposal-outcome.js.map +1 -0
  98. package/dist-server/service/chat-message/reader-gate.d.ts +3 -0
  99. package/dist-server/service/chat-message/reader-gate.js +37 -0
  100. package/dist-server/service/chat-message/reader-gate.js.map +1 -0
  101. package/dist-server/service/chat-message/room-message.d.ts +4 -0
  102. package/dist-server/service/chat-message/room-message.js +25 -0
  103. package/dist-server/service/chat-message/room-message.js.map +1 -0
  104. package/dist-server/service/chat-session/chat-session.d.ts +24 -0
  105. package/dist-server/service/chat-session/chat-session.js +26 -2
  106. package/dist-server/service/chat-session/chat-session.js.map +1 -1
  107. package/dist-server/service/chat-session/session-inbox.d.ts +4 -1
  108. package/dist-server/service/chat-session/session-inbox.js +3 -2
  109. package/dist-server/service/chat-session/session-inbox.js.map +1 -1
  110. package/dist-server/service/chat-session/session-visibility.d.ts +15 -0
  111. package/dist-server/service/chat-session/session-visibility.js +28 -0
  112. package/dist-server/service/chat-session/session-visibility.js.map +1 -0
  113. package/dist-server/service/chat-session/station-session-guard.d.ts +1 -5
  114. package/dist-server/service/chat-session/station-session-guard.js +7 -2
  115. package/dist-server/service/chat-session/station-session-guard.js.map +1 -1
  116. package/dist-server/service/chat-session-participant/mention-invite.d.ts +29 -0
  117. package/dist-server/service/chat-session-participant/mention-invite.js +103 -0
  118. package/dist-server/service/chat-session-participant/mention-invite.js.map +1 -0
  119. package/dist-server/service/chat-session-resolver.d.ts +15 -0
  120. package/dist-server/service/chat-session-resolver.js +267 -15
  121. package/dist-server/service/chat-session-resolver.js.map +1 -1
  122. package/dist-server/service/grounding-sources.d.ts +14 -0
  123. package/dist-server/service/grounding-sources.js +26 -0
  124. package/dist-server/service/grounding-sources.js.map +1 -0
  125. package/dist-server/service/index.d.ts +8 -1
  126. package/dist-server/service/index.js +19 -0
  127. package/dist-server/service/index.js.map +1 -1
  128. package/dist-server/service/request-translator.d.ts +2 -0
  129. package/dist-server/service/request-translator.js +20 -0
  130. package/dist-server/service/request-translator.js.map +1 -0
  131. package/dist-server/service/testing/conversation-script.d.ts +123 -0
  132. package/dist-server/service/testing/conversation-script.js +202 -0
  133. package/dist-server/service/testing/conversation-script.js.map +1 -0
  134. package/dist-server/service/testing/mode-conformance.d.ts +40 -0
  135. package/dist-server/service/testing/mode-conformance.js +90 -0
  136. package/dist-server/service/testing/mode-conformance.js.map +1 -0
  137. package/dist-server/tool-registry-boot-report.d.ts +3 -0
  138. package/dist-server/tool-registry-boot-report.js +25 -2
  139. package/dist-server/tool-registry-boot-report.js.map +1 -1
  140. package/dist-server/tsconfig.tsbuildinfo +1 -1
  141. package/package.json +5 -5
  142. package/server/explicit-graphql-types.test.ts +56 -0
  143. package/server/service/assistant-capability-resolver.ts +66 -0
  144. package/server/service/assistant-chat-resolver.ts +184 -42
  145. package/server/service/assistant-mode/assistant-mode-resolver.ts +102 -0
  146. package/server/service/assistant-mode/index.ts +16 -0
  147. package/server/service/assistant-mode/registry.test.ts +105 -0
  148. package/server/service/assistant-mode/registry.ts +147 -0
  149. package/server/service/assistant-mode/resolve.test.ts +78 -0
  150. package/server/service/assistant-mode/resolve.ts +96 -0
  151. package/server/service/assistant-mode/subject-defaults.test.ts +86 -0
  152. package/server/service/assistant-mode/types.ts +103 -0
  153. package/server/service/chat-message/carried-turns.test.ts +69 -0
  154. package/server/service/chat-message/carried-turns.ts +99 -0
  155. package/server/service/chat-message/chat-message-subscription.ts +20 -4
  156. package/server/service/chat-message/chat-message.ts +44 -0
  157. package/server/service/chat-message/door-access.ts +45 -0
  158. package/server/service/chat-message/fold-session-history.test.ts +98 -0
  159. package/server/service/chat-message/fold-session-history.ts +115 -0
  160. package/server/service/chat-message/llm-history.test.ts +58 -7
  161. package/server/service/chat-message/llm-history.ts +30 -11
  162. package/server/service/chat-message/message-doors.test.ts +87 -0
  163. package/server/service/chat-message/message-doors.ts +117 -0
  164. package/server/service/chat-message/proposal-outcome.test.ts +48 -0
  165. package/server/service/chat-message/proposal-outcome.ts +42 -0
  166. package/server/service/chat-message/reader-gate.ts +42 -0
  167. package/server/service/chat-message/room-message.test.ts +21 -0
  168. package/server/service/chat-message/room-message.ts +22 -0
  169. package/server/service/chat-session/chat-session.ts +31 -1
  170. package/server/service/chat-session/session-inbox.test.ts +18 -0
  171. package/server/service/chat-session/session-inbox.ts +9 -2
  172. package/server/service/chat-session/session-visibility.test.ts +36 -0
  173. package/server/service/chat-session/session-visibility.ts +26 -0
  174. package/server/service/chat-session/station-session-guard.ts +10 -2
  175. package/server/service/chat-session-participant/mention-invite.test.ts +125 -0
  176. package/server/service/chat-session-participant/mention-invite.ts +121 -0
  177. package/server/service/chat-session-resolver.ts +272 -15
  178. package/server/service/grounding-sources.test.ts +55 -0
  179. package/server/service/grounding-sources.ts +46 -0
  180. package/server/service/index.ts +19 -0
  181. package/server/service/request-translator.test.ts +45 -0
  182. package/server/service/request-translator.ts +33 -0
  183. package/server/service/station-session-guard.test.ts +12 -5
  184. package/server/service/testing/conversation-scenario.test.ts +163 -0
  185. package/server/service/testing/conversation-script.test.ts +131 -0
  186. package/server/service/testing/conversation-script.ts +288 -0
  187. package/server/service/testing/mode-conformance.ts +107 -0
  188. package/server/tool-registry-boot-report.ts +25 -2
  189. package/test/assistant-chat-call-sites.test.ts +82 -0
  190. package/translations/en.json +44 -2
  191. package/translations/ja.json +44 -2
  192. package/translations/ko.json +44 -2
  193. package/translations/ms.json +44 -2
  194. package/translations/zh.json +44 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-mode-resolver.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/assistant-mode-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The screen asks what its conversation offers.\n *\n * The `/` sentences, the `@` catalog and the two notices used to travel from the browser into the\n * request, which meant the screen and the door could describe the same conversation differently.\n * They are declared once, in the mode, as i18n keys — and this query hands them over already\n * written in the request's locale (ADR-0054, and the same rule A11 applied to the stop notice).\n * The persona, the tool categories and the limits stay on the server: nothing here returns them.\n */\nimport { Arg, Ctx, Directive, Field, ObjectType, Query, Resolver } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\nimport '@things-factory/auth-base'\n\nimport { assistantModeView } from './resolve.js'\nimport { assistantModesForSubject } from './registry.js'\nimport { requestTranslator } from '../request-translator.js'\n\n@ObjectType({ description: 'One `/` sentence, in the words of this request.' })\nclass AssistantModeSlashView {\n @Field({ description: 'Short name shown in the list.' })\n name: string\n\n @Field({ description: 'One line about what it does.' })\n description: string\n\n @Field({ description: 'The sentence that lands in the input.' })\n template: string\n}\n\n@ObjectType({ description: 'What a conversation offers on screen.' })\nclass AssistantModeView {\n @Field({ description: 'The mode kind this describes.' })\n kind: string\n\n @Field(() => [AssistantModeSlashView], { description: 'What `/` offers.' })\n slashTemplates: AssistantModeSlashView[]\n\n @Field(() => GraphQLJSON, { description: 'What `@` offers.' })\n catalogEntries: any[]\n\n @Field(() => GraphQLJSON, { description: 'Follow-up chips for a proposal that arrived without its own.' })\n proposalChoices: any[]\n\n @Field({ nullable: true, description: 'What the empty input says — what this conversation is for.' })\n placeholder?: string\n\n @Field({ nullable: true, description: 'The line under an empty conversation.' })\n footerNotice?: string\n\n @Field({ nullable: true, description: 'The line shown once the host reports a proposal staged.' })\n stagedNotice?: string\n}\n\n@ObjectType({ description: 'A conversation a screen may open for what it is showing.' })\nclass AssistantModeChoice {\n @Field({ description: 'The mode kind to open.' })\n kind: string\n\n @Field({ description: 'What to call it where a person chooses — already in the words of this request.' })\n label: string\n\n @Field({ description: 'Whether this is the one to open when the person has not chosen.' })\n isDefault: boolean\n}\n\n@Resolver()\nexport class AssistantModeResolver {\n /*\n * 이 화면이 보여 주는 것으로 열 수 있는 대화들 — ADR-0072 A17 ①.\n *\n * 화면은 자기가 무엇을 보여 주는지(`subject`)만 안다. 그 위에 어느 대화가 서는지는 앱 서버가\n * 선언한다. 화면이 종류 목록을 손으로 들고 있으면, 대화를 하나 더 세운 날 그 화면만 모른 채로 남는다.\n *\n * 하나만 오면 고르개를 내지 않는다 — 고를 것이 없는 고르개는 자리만 차지한다.\n */\n @Query(() => [AssistantModeChoice], {\n description:\n 'Conversations that stand for this kind of subject, the default first. A screen showing one subject opens the head of this list and offers the rest; a single entry means there is nothing to choose.'\n })\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"query\")')\n async assistantModes(@Arg('subject') subject: string, @Ctx() context: any): Promise<AssistantModeChoice[]> {\n const t = requestTranslator(context)\n\n return assistantModesForSubject(subject).map(mode => ({\n kind: mode.kind,\n /*\n * 이름이 없는 대화는 종류를 그대로 쓴다. 부팅 보고가 이미 그것을 경고로 말하므로 여기서 숨기지\n * 않는다 — 화면에 `twin.space` 가 뜨면 그것이 빠진 선언을 가리킨다.\n */\n label: mode.labelKey ? t(mode.labelKey) : mode.kind,\n isDefault: (mode.defaultFor ?? []).includes(subject)\n }))\n }\n @Query(() => AssistantModeView, {\n description: 'What this conversation offers on screen — `/` sentences, the `@` catalog and the notices, translated.'\n })\n /* Reading what a conversation offers is reading, and the same door the session list uses. */\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"query\")')\n async assistantMode(@Arg('kind') kind: string, @Ctx() context: any): Promise<AssistantModeView> {\n return assistantModeView(kind, requestTranslator(context)) as AssistantModeView\n }\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Modes — what each conversation is, declared by the app server that owns the screen.
3
+ *
4
+ * Design: ADR-0061 decision 7 (`operato-twin/design/04-decisions.md`) and the AI standard's A2.
5
+ */
6
+ export { registerAssistantMode, getAssistantMode, listAssistantModes, assistantModesForSubject, getAssistantModeWarnings, clearAssistantModes } from './registry.js';
7
+ export { resolveModeSettings, assistantModeView } from './resolve.js';
8
+ export type { AssistantModeSpec, AssistantModeSlash, AssistantModeWarning } from './types.js';
9
+ export type { AssistantModeRef, ModeSettings, AssistantModeView, AssistantModeSlashView, Translate } from './resolve.js';
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assistantModeView = exports.resolveModeSettings = exports.clearAssistantModes = exports.getAssistantModeWarnings = exports.assistantModesForSubject = exports.listAssistantModes = exports.getAssistantMode = exports.registerAssistantMode = void 0;
4
+ /**
5
+ * Modes — what each conversation is, declared by the app server that owns the screen.
6
+ *
7
+ * Design: ADR-0061 decision 7 (`operato-twin/design/04-decisions.md`) and the AI standard's A2.
8
+ */
9
+ var registry_js_1 = require("./registry.js");
10
+ Object.defineProperty(exports, "registerAssistantMode", { enumerable: true, get: function () { return registry_js_1.registerAssistantMode; } });
11
+ Object.defineProperty(exports, "getAssistantMode", { enumerable: true, get: function () { return registry_js_1.getAssistantMode; } });
12
+ Object.defineProperty(exports, "listAssistantModes", { enumerable: true, get: function () { return registry_js_1.listAssistantModes; } });
13
+ Object.defineProperty(exports, "assistantModesForSubject", { enumerable: true, get: function () { return registry_js_1.assistantModesForSubject; } });
14
+ Object.defineProperty(exports, "getAssistantModeWarnings", { enumerable: true, get: function () { return registry_js_1.getAssistantModeWarnings; } });
15
+ Object.defineProperty(exports, "clearAssistantModes", { enumerable: true, get: function () { return registry_js_1.clearAssistantModes; } });
16
+ var resolve_js_1 = require("./resolve.js");
17
+ Object.defineProperty(exports, "resolveModeSettings", { enumerable: true, get: function () { return resolve_js_1.resolveModeSettings; } });
18
+ Object.defineProperty(exports, "assistantModeView", { enumerable: true, get: function () { return resolve_js_1.assistantModeView; } });
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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,uHAAA,wBAAwB,OAAA;AACxB,uHAAA,wBAAwB,OAAA;AACxB,kHAAA,mBAAmB,OAAA;AAErB,2CAAqE;AAA5D,iHAAA,mBAAmB,OAAA;AAAE,+GAAA,iBAAiB,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-twin/design/04-decisions.md`) and the AI standard's A2.\n */\nexport {\n registerAssistantMode,\n getAssistantMode,\n listAssistantModes,\n assistantModesForSubject,\n getAssistantModeWarnings,\n clearAssistantModes\n} from './registry.js'\nexport { resolveModeSettings, assistantModeView } from './resolve.js'\nexport type { AssistantModeSpec, AssistantModeSlash, AssistantModeWarning } from './types.js'\nexport type { AssistantModeRef, ModeSettings, AssistantModeView, AssistantModeSlashView, Translate } from './resolve.js'\n"]}
@@ -0,0 +1,18 @@
1
+ import type { AssistantModeSpec, AssistantModeWarning } from './types';
2
+ export declare function registerAssistantMode(spec: AssistantModeSpec): void;
3
+ export declare function getAssistantMode(kind: string): AssistantModeSpec | undefined;
4
+ export declare function listAssistantModes(): ReadonlyArray<AssistantModeSpec>;
5
+ /**
6
+ * The promises a mode makes that its tools cannot keep, as of now.
7
+ *
8
+ * Read after boot: a mode may register before the category its slash sentences use.
9
+ */
10
+ export declare function getAssistantModeWarnings(): ReadonlyArray<AssistantModeWarning>;
11
+ /**
12
+ * 이 subject 종류에 서는 대화들 — 등록 순서대로, 기본이 먼저.
13
+ *
14
+ * 화면은 이 목록으로 고르개를 낸다. 하나뿐이면 고르개를 내지 않는다(ADR-0072 A17 ①).
15
+ */
16
+ export declare function assistantModesForSubject(subject: string): ReadonlyArray<AssistantModeSpec>;
17
+ /** Test only. */
18
+ export declare function clearAssistantModes(): void;
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerAssistantMode = registerAssistantMode;
4
+ exports.getAssistantMode = getAssistantMode;
5
+ exports.listAssistantModes = listAssistantModes;
6
+ exports.getAssistantModeWarnings = getAssistantModeWarnings;
7
+ exports.assistantModesForSubject = assistantModesForSubject;
8
+ exports.clearAssistantModes = clearAssistantModes;
9
+ /**
10
+ * The mode registry — one place that knows what each conversation is.
11
+ *
12
+ * Registration refuses what cannot be true later: a kind that is not `<app>.<surface>` (the door
13
+ * looks a mode up by that name), an empty persona (the field this whole change exists to take off
14
+ * the wire), and a second declaration of a kind that is already declared — two declarations of one
15
+ * conversation disagree, and the one that wins would be whichever module booted last.
16
+ *
17
+ * What it does not refuse is a `/` sentence naming a tool the mode does not expose. That check
18
+ * reads the tool registry, which fills up as modules boot, so it runs when asked rather than at
19
+ * registration: `getAssistantModeWarnings()` is the answer as of now, and the boot report reads it
20
+ * after every module is in.
21
+ */
22
+ const ai_client_base_1 = require("@things-factory/ai-client-base");
23
+ const modes = new Map();
24
+ const KIND = /^[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*$/;
25
+ function registerAssistantMode(spec) {
26
+ if (!KIND.test(String(spec?.kind ?? ''))) {
27
+ throw new Error(`[ai-assistant/assistant-mode] '${spec?.kind}' is not a mode kind. Use <app>.<surface> in lowercase — ` +
28
+ "'twin.space', 'plant.station', 'figure.modeller'. The client names the conversation by this.");
29
+ }
30
+ if (!String(spec.systemPrompt ?? '').trim()) {
31
+ throw new Error(`[ai-assistant/assistant-mode] mode '${spec.kind}' has no systemPrompt. Who the assistant is on a ` +
32
+ 'surface belongs to the server that owns the surface (ADR-0061 decision 7).');
33
+ }
34
+ if (modes.has(spec.kind)) {
35
+ throw new Error(`[ai-assistant/assistant-mode] mode '${spec.kind}' is already registered. Two declarations of one ` +
36
+ 'conversation disagree, and which one wins would depend on boot order. Give the second one its own kind.');
37
+ }
38
+ modes.set(spec.kind, spec);
39
+ }
40
+ function getAssistantMode(kind) {
41
+ return modes.get(kind);
42
+ }
43
+ function listAssistantModes() {
44
+ return Array.from(modes.values());
45
+ }
46
+ /**
47
+ * The promises a mode makes that its tools cannot keep, as of now.
48
+ *
49
+ * Read after boot: a mode may register before the category its slash sentences use.
50
+ */
51
+ function getAssistantModeWarnings() {
52
+ const out = [];
53
+ /*
54
+ * 한 화면이 어느 대화를 기본으로 여는가 — 등록 순서로 정하지 않는다(ADR-0072 A17 ②). 없으면 화면이
55
+ * 무엇을 열지 모르고, 둘이면 마지막에 부팅한 모듈이 조용히 이긴다. 둘 다 여기서 말한다.
56
+ */
57
+ const defaults = new Map();
58
+ const claimed = new Set();
59
+ for (const mode of modes.values()) {
60
+ for (const subject of mode.subjects ?? [])
61
+ claimed.add(subject);
62
+ for (const subject of mode.defaultFor ?? []) {
63
+ defaults.set(subject, [...(defaults.get(subject) ?? []), mode.kind]);
64
+ }
65
+ }
66
+ for (const subject of claimed) {
67
+ const owners = defaults.get(subject) ?? [];
68
+ if (owners.length === 0) {
69
+ out.push({
70
+ code: 'assistant-mode-subject-without-default',
71
+ params: { kind: '', subject },
72
+ message: `no conversation is the default for subject '${subject}' — a screen showing it has nothing to open`
73
+ });
74
+ }
75
+ else if (owners.length > 1) {
76
+ out.push({
77
+ code: 'assistant-mode-subject-default-twice',
78
+ params: { kind: owners[0], subject, kinds: owners },
79
+ message: `subject '${subject}' has more than one default (${owners.join(', ')}) — boot order would decide it`
80
+ });
81
+ }
82
+ }
83
+ for (const mode of modes.values()) {
84
+ /*
85
+ * 고를 수 있는 대화에는 부를 이름이 있어야 한다 — 고르개에 종류 이름(`twin.space`)이 뜨면 그것은
86
+ * 우리 내부 단위를 사람에게 떠넘기는 것이다.
87
+ */
88
+ const shared = (mode.subjects ?? []).some(subject => countModesFor(subject) > 1);
89
+ if (shared && !mode.labelKey) {
90
+ out.push({
91
+ code: 'assistant-mode-offered-without-label',
92
+ params: { kind: mode.kind },
93
+ message: `mode '${mode.kind}' can be offered beside another on the same subject but has no labelKey`
94
+ });
95
+ }
96
+ }
97
+ for (const mode of modes.values()) {
98
+ const exposed = new Set((0, ai_client_base_1.getToolSpecsFor)(mode.toolCategories ?? []).map(spec => spec.name));
99
+ for (const slash of mode.slashTemplates ?? []) {
100
+ for (const tool of slash.tools ?? []) {
101
+ if (exposed.has(tool))
102
+ continue;
103
+ out.push({
104
+ code: 'assistant-mode-slash-outside-tools',
105
+ params: { kind: mode.kind, slash: slash.name, tool },
106
+ message: `mode '${mode.kind}' offers '/${slash.name}', which needs '${tool}' — not in its tool categories ` +
107
+ `[${(mode.toolCategories ?? []).join(', ')}]. The person presses it and the model answers without doing it.`
108
+ });
109
+ }
110
+ }
111
+ }
112
+ return out;
113
+ }
114
+ /** 이 subject 종류에 서는 대화가 몇인가 — 고르개가 필요한지, 이름이 필요한지를 가른다. */
115
+ function countModesFor(subject) {
116
+ let count = 0;
117
+ for (const mode of modes.values()) {
118
+ if ((mode.subjects ?? []).includes(subject))
119
+ count++;
120
+ }
121
+ return count;
122
+ }
123
+ /**
124
+ * 이 subject 종류에 서는 대화들 — 등록 순서대로, 기본이 먼저.
125
+ *
126
+ * 화면은 이 목록으로 고르개를 낸다. 하나뿐이면 고르개를 내지 않는다(ADR-0072 A17 ①).
127
+ */
128
+ function assistantModesForSubject(subject) {
129
+ const standing = [...modes.values()].filter(mode => (mode.subjects ?? []).includes(subject));
130
+ return standing.sort((a, b) => Number((b.defaultFor ?? []).includes(subject)) - Number((a.defaultFor ?? []).includes(subject)));
131
+ }
132
+ /** Test only. */
133
+ function clearAssistantModes() {
134
+ modes.clear();
135
+ }
136
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/registry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The mode registry — one place that knows what each conversation is.\n *\n * Registration refuses what cannot be true later: a kind that is not `<app>.<surface>` (the door\n * looks a mode up by that name), an empty persona (the field this whole change exists to take off\n * the wire), and a second declaration of a kind that is already declared — two declarations of one\n * conversation disagree, and the one that wins would be whichever module booted last.\n *\n * What it does not refuse is a `/` sentence naming a tool the mode does not expose. That check\n * reads the tool registry, which fills up as modules boot, so it runs when asked rather than at\n * registration: `getAssistantModeWarnings()` is the answer as of now, and the boot report reads it\n * after every module is in.\n */\nimport { getToolSpecsFor } from '@things-factory/ai-client-base'\n\nimport type { AssistantModeSpec, AssistantModeWarning } from './types'\n\nconst modes = new Map<string, AssistantModeSpec>()\n\nconst KIND = /^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$/\n\nexport function registerAssistantMode(spec: AssistantModeSpec): void {\n if (!KIND.test(String(spec?.kind ?? ''))) {\n throw new Error(\n `[ai-assistant/assistant-mode] '${spec?.kind}' is not a mode kind. Use <app>.<surface> in lowercase — ` +\n \"'twin.space', 'plant.station', 'figure.modeller'. The client names the conversation by this.\"\n )\n }\n\n if (!String(spec.systemPrompt ?? '').trim()) {\n throw new Error(\n `[ai-assistant/assistant-mode] mode '${spec.kind}' has no systemPrompt. Who the assistant is on a ` +\n 'surface belongs to the server that owns the surface (ADR-0061 decision 7).'\n )\n }\n\n if (modes.has(spec.kind)) {\n throw new Error(\n `[ai-assistant/assistant-mode] mode '${spec.kind}' is already registered. Two declarations of one ` +\n 'conversation disagree, and which one wins would depend on boot order. Give the second one its own kind.'\n )\n }\n\n modes.set(spec.kind, spec)\n}\n\nexport function getAssistantMode(kind: string): AssistantModeSpec | undefined {\n return modes.get(kind)\n}\n\nexport function listAssistantModes(): ReadonlyArray<AssistantModeSpec> {\n return Array.from(modes.values())\n}\n\n/**\n * The promises a mode makes that its tools cannot keep, as of now.\n *\n * Read after boot: a mode may register before the category its slash sentences use.\n */\nexport function getAssistantModeWarnings(): ReadonlyArray<AssistantModeWarning> {\n const out: AssistantModeWarning[] = []\n\n /*\n * 한 화면이 어느 대화를 기본으로 여는가 — 등록 순서로 정하지 않는다(ADR-0072 A17 ②). 없으면 화면이\n * 무엇을 열지 모르고, 둘이면 마지막에 부팅한 모듈이 조용히 이긴다. 둘 다 여기서 말한다.\n */\n const defaults = new Map<string, string[]>()\n const claimed = new Set<string>()\n for (const mode of modes.values()) {\n for (const subject of mode.subjects ?? []) claimed.add(subject)\n for (const subject of mode.defaultFor ?? []) {\n defaults.set(subject, [...(defaults.get(subject) ?? []), mode.kind])\n }\n }\n for (const subject of claimed) {\n const owners = defaults.get(subject) ?? []\n if (owners.length === 0) {\n out.push({\n code: 'assistant-mode-subject-without-default',\n params: { kind: '', subject },\n message: `no conversation is the default for subject '${subject}' — a screen showing it has nothing to open`\n })\n } else if (owners.length > 1) {\n out.push({\n code: 'assistant-mode-subject-default-twice',\n params: { kind: owners[0], subject, kinds: owners },\n message: `subject '${subject}' has more than one default (${owners.join(', ')}) — boot order would decide it`\n })\n }\n }\n\n for (const mode of modes.values()) {\n /*\n * 고를 수 있는 대화에는 부를 이름이 있어야 한다 — 고르개에 종류 이름(`twin.space`)이 뜨면 그것은\n * 우리 내부 단위를 사람에게 떠넘기는 것이다.\n */\n const shared = (mode.subjects ?? []).some(subject => countModesFor(subject) > 1)\n if (shared && !mode.labelKey) {\n out.push({\n code: 'assistant-mode-offered-without-label',\n params: { kind: mode.kind },\n message: `mode '${mode.kind}' can be offered beside another on the same subject but has no labelKey`\n })\n }\n }\n\n for (const mode of modes.values()) {\n const exposed = new Set(getToolSpecsFor(mode.toolCategories ?? []).map(spec => spec.name))\n for (const slash of mode.slashTemplates ?? []) {\n for (const tool of slash.tools ?? []) {\n if (exposed.has(tool)) continue\n out.push({\n code: 'assistant-mode-slash-outside-tools',\n params: { kind: mode.kind, slash: slash.name, tool },\n message:\n `mode '${mode.kind}' offers '/${slash.name}', which needs '${tool}' — not in its tool categories ` +\n `[${(mode.toolCategories ?? []).join(', ')}]. The person presses it and the model answers without doing it.`\n })\n }\n }\n }\n return out\n}\n\n/** 이 subject 종류에 서는 대화가 몇인가 — 고르개가 필요한지, 이름이 필요한지를 가른다. */\nfunction countModesFor(subject: string): number {\n let count = 0\n for (const mode of modes.values()) {\n if ((mode.subjects ?? []).includes(subject)) count++\n }\n return count\n}\n\n/**\n * 이 subject 종류에 서는 대화들 — 등록 순서대로, 기본이 먼저.\n *\n * 화면은 이 목록으로 고르개를 낸다. 하나뿐이면 고르개를 내지 않는다(ADR-0072 A17 ①).\n */\nexport function assistantModesForSubject(subject: string): ReadonlyArray<AssistantModeSpec> {\n const standing = [...modes.values()].filter(mode => (mode.subjects ?? []).includes(subject))\n return standing.sort((a, b) => Number((b.defaultFor ?? []).includes(subject)) - Number((a.defaultFor ?? []).includes(subject)))\n}\n\n/** Test only. */\nexport function clearAssistantModes(): void {\n modes.clear()\n}\n"]}
@@ -0,0 +1,33 @@
1
+ export type Translate = (key: string, params?: Record<string, any>) => string;
2
+ export interface AssistantModeRef {
3
+ /** Which conversation this is — a registered `<app>.<surface>`. */
4
+ kind: string;
5
+ /** What it is about. Part of the conversation's lifetime; the tools read it, not the settings. */
6
+ target?: string;
7
+ }
8
+ export interface ModeSettings {
9
+ systemPrompt: string;
10
+ toolCategories?: string[];
11
+ requireToolOnFirstTurn?: boolean;
12
+ model?: string;
13
+ temperature?: number;
14
+ maxTokens?: number;
15
+ }
16
+ /** The conversation's settings, from the server's declaration. */
17
+ export declare function resolveModeSettings(ref: AssistantModeRef, t: Translate): ModeSettings;
18
+ export interface AssistantModeSlashView {
19
+ name: string;
20
+ description: string;
21
+ template: string;
22
+ }
23
+ export interface AssistantModeView {
24
+ kind: string;
25
+ slashTemplates: AssistantModeSlashView[];
26
+ catalogEntries: any[];
27
+ proposalChoices: any[];
28
+ placeholder?: string;
29
+ footerNotice?: string;
30
+ stagedNotice?: string;
31
+ }
32
+ /** What the screen shows, in the words of the request's locale. */
33
+ export declare function assistantModeView(kind: string, t: Translate): AssistantModeView;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveModeSettings = resolveModeSettings;
4
+ exports.assistantModeView = assistantModeView;
5
+ /**
6
+ * What the door and the screen each take from a mode.
7
+ *
8
+ * Both start from the same declaration, so a screen cannot show `/` sentences for a conversation
9
+ * the server would answer differently.
10
+ */
11
+ const registry_1 = require("./registry");
12
+ /**
13
+ * A kind nobody registered. The sentence is the person's (ADR-0054); the code and the kind are for
14
+ * whoever reads the log — a screen naming a conversation the server does not have is a mismatched
15
+ * deployment, not something the person did.
16
+ */
17
+ function unknownModeError(kind, t) {
18
+ const error = new Error(t('ai-assistant.text.unknown-mode', {
19
+ defaultValue: 'this screen asked for a conversation this server does not have ({kind}). reload the page.',
20
+ kind: kind || '—'
21
+ }));
22
+ error.code = 'unknown-assistant-mode';
23
+ error.params = { kind };
24
+ return error;
25
+ }
26
+ /** The conversation's settings, from the server's declaration. */
27
+ function resolveModeSettings(ref, t) {
28
+ const kind = String(ref?.kind ?? '');
29
+ const mode = kind ? (0, registry_1.getAssistantMode)(kind) : undefined;
30
+ if (!mode)
31
+ throw unknownModeError(kind, t);
32
+ return {
33
+ systemPrompt: mode.systemPrompt,
34
+ toolCategories: mode.toolCategories,
35
+ requireToolOnFirstTurn: mode.requireToolOnFirstTurn,
36
+ model: mode.model,
37
+ temperature: mode.temperature,
38
+ maxTokens: mode.maxTokens
39
+ };
40
+ }
41
+ /** What the screen shows, in the words of the request's locale. */
42
+ function assistantModeView(kind, t) {
43
+ const mode = (0, registry_1.getAssistantMode)(kind);
44
+ if (!mode)
45
+ throw unknownModeError(kind, t);
46
+ return {
47
+ kind: mode.kind,
48
+ slashTemplates: (mode.slashTemplates ?? []).map((slash) => ({
49
+ name: slash.name,
50
+ description: t(slash.descriptionKey),
51
+ template: t(slash.templateKey)
52
+ })),
53
+ catalogEntries: mode.catalogEntries ?? [],
54
+ proposalChoices: mode.proposalChoices ?? [],
55
+ placeholder: mode.placeholderKey ? t(mode.placeholderKey) : undefined,
56
+ footerNotice: mode.footerNoticeKey ? t(mode.footerNoticeKey) : undefined,
57
+ stagedNotice: mode.stagedNoticeKey ? t(mode.stagedNoticeKey) : undefined
58
+ };
59
+ }
60
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/resolve.ts"],"names":[],"mappings":";;AA6CA,kDAcC;AAmBD,8CAiBC;AA/FD;;;;;GAKG;AACH,yCAA6C;AAY7C;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,IAAY,EAAE,CAAY;IAClD,MAAM,KAAK,GAAQ,IAAI,KAAK,CAC1B,CAAC,CAAC,gCAAgC,EAAE;QAClC,YAAY,EAAE,2FAA2F;QACzG,IAAI,EAAE,IAAI,IAAI,GAAG;KAClB,CAAC,CACH,CAAA;IACD,KAAK,CAAC,IAAI,GAAG,wBAAwB,CAAA;IACrC,KAAK,CAAC,MAAM,GAAG,EAAE,IAAI,EAAE,CAAA;IACvB,OAAO,KAAK,CAAA;AACd,CAAC;AAWD,kEAAkE;AAClE,SAAgB,mBAAmB,CAAC,GAAqB,EAAE,CAAY;IACrE,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,EAAE,IAAI,IAAI,EAAE,CAAC,CAAA;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAA,2BAAgB,EAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAEtD,IAAI,CAAC,IAAI;QAAE,MAAM,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;IAE1C,OAAO;QACL,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,sBAAsB,EAAE,IAAI,CAAC,sBAAsB;QACnD,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,SAAS,EAAE,IAAI,CAAC,SAAS;KAC1B,CAAA;AACH,CAAC;AAkBD,mEAAmE;AACnE,SAAgB,iBAAiB,CAAC,IAAY,EAAE,CAAY;IAC1D,MAAM,IAAI,GAAG,IAAA,2BAAgB,EAAC,IAAI,CAAC,CAAA;IACnC,IAAI,CAAC,IAAI;QAAE,MAAM,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;IAE1C,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,cAAc,EAAE,CAAC,IAAI,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAyB,EAAE,EAAE,CAAC,CAAC;YAC9E,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC;YACpC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC;SAC/B,CAAC,CAAC;QACH,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,EAAE;QACzC,eAAe,EAAE,IAAI,CAAC,eAAe,IAAI,EAAE;QAC3C,WAAW,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,SAAS;QACrE,YAAY,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,SAAS;QACxE,YAAY,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,SAAS;KACzE,CAAA;AACH,CAAC","sourcesContent":["/**\n * What the door and the screen each take from a mode.\n *\n * Both start from the same declaration, so a screen cannot show `/` sentences for a conversation\n * the server would answer differently.\n */\nimport { getAssistantMode } from './registry'\nimport type { AssistantModeSlash } from './types'\n\nexport type Translate = (key: string, params?: Record<string, any>) => string\n\nexport interface AssistantModeRef {\n /** Which conversation this is — a registered `<app>.<surface>`. */\n kind: string\n /** What it is about. Part of the conversation's lifetime; the tools read it, not the settings. */\n target?: string\n}\n\n/**\n * A kind nobody registered. The sentence is the person's (ADR-0054); the code and the kind are for\n * whoever reads the log — a screen naming a conversation the server does not have is a mismatched\n * deployment, not something the person did.\n */\nfunction unknownModeError(kind: string, t: Translate): Error {\n const error: any = new Error(\n t('ai-assistant.text.unknown-mode', {\n defaultValue: 'this screen asked for a conversation this server does not have ({kind}). reload the page.',\n kind: kind || '—'\n })\n )\n error.code = 'unknown-assistant-mode'\n error.params = { kind }\n return error\n}\n\nexport interface ModeSettings {\n systemPrompt: string\n toolCategories?: string[]\n requireToolOnFirstTurn?: boolean\n model?: string\n temperature?: number\n maxTokens?: number\n}\n\n/** The conversation's settings, from the server's declaration. */\nexport function resolveModeSettings(ref: AssistantModeRef, t: Translate): ModeSettings {\n const kind = String(ref?.kind ?? '')\n const mode = kind ? getAssistantMode(kind) : undefined\n\n if (!mode) throw unknownModeError(kind, t)\n\n return {\n systemPrompt: mode.systemPrompt,\n toolCategories: mode.toolCategories,\n requireToolOnFirstTurn: mode.requireToolOnFirstTurn,\n model: mode.model,\n temperature: mode.temperature,\n maxTokens: mode.maxTokens\n }\n}\n\nexport interface AssistantModeSlashView {\n name: string\n description: string\n template: string\n}\n\nexport interface AssistantModeView {\n kind: string\n slashTemplates: AssistantModeSlashView[]\n catalogEntries: any[]\n proposalChoices: any[]\n placeholder?: string\n footerNotice?: string\n stagedNotice?: string\n}\n\n/** What the screen shows, in the words of the request's locale. */\nexport function assistantModeView(kind: string, t: Translate): AssistantModeView {\n const mode = getAssistantMode(kind)\n if (!mode) throw unknownModeError(kind, t)\n\n return {\n kind: mode.kind,\n slashTemplates: (mode.slashTemplates ?? []).map((slash: AssistantModeSlash) => ({\n name: slash.name,\n description: t(slash.descriptionKey),\n template: t(slash.templateKey)\n })),\n catalogEntries: mode.catalogEntries ?? [],\n proposalChoices: mode.proposalChoices ?? [],\n placeholder: mode.placeholderKey ? t(mode.placeholderKey) : undefined,\n footerNotice: mode.footerNoticeKey ? t(mode.footerNoticeKey) : undefined,\n stagedNotice: mode.stagedNoticeKey ? t(mode.stagedNoticeKey) : undefined\n }\n}\n"]}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * What a conversation is, declared by the app server that owns the screen (ADR-0061 decision 7).
3
+ *
4
+ * ADR-0058 put these five in one value so they could not fall out of step, but the value lived in
5
+ * the browser and travelled on every request: the persona, the tool categories, the model and the
6
+ * limits were the caller's to choose. A caller choosing its own tool categories is a caller
7
+ * choosing its own authority, so the declaration moved here and the client sends only which one it
8
+ * is talking in (`kind`) and what about (`target`).
9
+ */
10
+ /** A `/` sentence this mode offers, and what it needs to be answerable. */
11
+ export interface AssistantModeSlash {
12
+ /** Short name shown in the list. */
13
+ name: string;
14
+ /** i18n key for the one-line description. The server translates it into the request's locale. */
15
+ descriptionKey: string;
16
+ /** i18n key for the sentence that lands in the input. */
17
+ templateKey: string;
18
+ /**
19
+ * The tools this sentence needs. A mode must not offer work it cannot do (ADR-0058 decision 3),
20
+ * and a name outside `toolCategories` is reported at boot rather than when a person presses it.
21
+ */
22
+ tools?: string[];
23
+ }
24
+ export interface AssistantModeSpec {
25
+ /** `<app>.<surface>` — `twin.space`, `plant.station`, `figure.modeller`. */
26
+ kind: string;
27
+ /**
28
+ * Which kinds of subject this conversation stands for — `['board', 'space']` (ADR-0072 A17 ①).
29
+ *
30
+ * A screen says what it is showing (`context.subject`), and these say which conversations that
31
+ * subject can carry. It is a list of types and not a predicate on purpose: a list can be counted
32
+ * at boot and pinned by a test, while a function can only be run.
33
+ *
34
+ * On the twin modeller nothing could be asked about operations, because an open board pinned the
35
+ * authoring conversation and that conversation opens one category. A conversation that also
36
+ * stands for `board` is how the same screen carries both.
37
+ */
38
+ subjects?: string[];
39
+ /**
40
+ * The conversation a screen opens when the person has not chosen — one per subject type
41
+ * (ADR-0072 A17 ②).
42
+ *
43
+ * Registration order must not decide this: whichever module booted last would silently become
44
+ * the default. Exactly one mode declares each subject type, and the boot report says so when a
45
+ * type has none or more than one.
46
+ */
47
+ defaultFor?: string[];
48
+ /**
49
+ * i18n key for what this conversation is called where a person chooses between them —
50
+ * "이 그림 고치기", "이 현장 지금 어때". A mode a screen may offer needs one.
51
+ */
52
+ labelKey?: string;
53
+ /** Who the assistant is here, and what it must not claim to do. The server's, not the caller's. */
54
+ systemPrompt: string;
55
+ /** Which tool categories this conversation may use. Absent or empty exposes none. */
56
+ toolCategories?: string[];
57
+ /** Make the model read before it answers — for surfaces whose subject is live state. */
58
+ requireToolOnFirstTurn?: boolean;
59
+ /** Model and limits are policy, not a request field. */
60
+ model?: string;
61
+ temperature?: number;
62
+ maxTokens?: number;
63
+ /** What `/` offers. Declared as keys; the server translates. */
64
+ slashTemplates?: AssistantModeSlash[];
65
+ /** What `@` offers. */
66
+ catalogEntries?: any[];
67
+ /**
68
+ * i18n key for the words in the empty input.
69
+ *
70
+ * The screen says what this conversation is for, and that differs by mode: figure's modeller had
71
+ * "describe the figure you want" while the person was there to change the figure already open.
72
+ */
73
+ placeholderKey?: string;
74
+ /** i18n key for the line under an empty conversation. */
75
+ footerNoticeKey?: string;
76
+ /** i18n key for the line shown once the host reports a proposal staged. */
77
+ stagedNoticeKey?: string;
78
+ /** Follow-up chips for a proposal that arrived without its own. */
79
+ proposalChoices?: any[];
80
+ }
81
+ /** A declaration that registered but is not what the contract asks for. Code + params, not prose. */
82
+ export interface AssistantModeWarning {
83
+ /**
84
+ * `assistant-mode-slash-outside-tools` — a `/` sentence needs a tool this mode does not open.
85
+ * `assistant-mode-subject-without-default` — a subject type no conversation claims as its default.
86
+ * `assistant-mode-subject-default-twice` — two conversations claim the same subject type.
87
+ * `assistant-mode-offered-without-label` — a conversation a screen may offer with nothing to call it.
88
+ */
89
+ code: 'assistant-mode-slash-outside-tools' | 'assistant-mode-subject-without-default' | 'assistant-mode-subject-default-twice' | 'assistant-mode-offered-without-label';
90
+ params: {
91
+ kind: string;
92
+ slash?: string;
93
+ tool?: string;
94
+ subject?: string;
95
+ kinds?: string[];
96
+ };
97
+ /** For a person reading the boot log; callers act on the code and params. */
98
+ message: string;
99
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ /**
3
+ * What a conversation is, declared by the app server that owns the screen (ADR-0061 decision 7).
4
+ *
5
+ * ADR-0058 put these five in one value so they could not fall out of step, but the value lived in
6
+ * the browser and travelled on every request: the persona, the tool categories, the model and the
7
+ * limits were the caller's to choose. A caller choosing its own tool categories is a caller
8
+ * choosing its own authority, so the declaration moved here and the client sends only which one it
9
+ * is talking in (`kind`) and what about (`target`).
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../server/service/assistant-mode/types.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG","sourcesContent":["/**\n * What a conversation is, declared by the app server that owns the screen (ADR-0061 decision 7).\n *\n * ADR-0058 put these five in one value so they could not fall out of step, but the value lived in\n * the browser and travelled on every request: the persona, the tool categories, the model and the\n * limits were the caller's to choose. A caller choosing its own tool categories is a caller\n * choosing its own authority, so the declaration moved here and the client sends only which one it\n * is talking in (`kind`) and what about (`target`).\n */\n\n/** A `/` sentence this mode offers, and what it needs to be answerable. */\nexport interface AssistantModeSlash {\n /** Short name shown in the list. */\n name: string\n /** i18n key for the one-line description. The server translates it into the request's locale. */\n descriptionKey: string\n /** i18n key for the sentence that lands in the input. */\n templateKey: string\n /**\n * The tools this sentence needs. A mode must not offer work it cannot do (ADR-0058 decision 3),\n * and a name outside `toolCategories` is reported at boot rather than when a person presses it.\n */\n tools?: string[]\n}\n\nexport interface AssistantModeSpec {\n /** `<app>.<surface>` — `twin.space`, `plant.station`, `figure.modeller`. */\n kind: string\n\n /**\n * Which kinds of subject this conversation stands for — `['board', 'space']` (ADR-0072 A17 ①).\n *\n * A screen says what it is showing (`context.subject`), and these say which conversations that\n * subject can carry. It is a list of types and not a predicate on purpose: a list can be counted\n * at boot and pinned by a test, while a function can only be run.\n *\n * On the twin modeller nothing could be asked about operations, because an open board pinned the\n * authoring conversation and that conversation opens one category. A conversation that also\n * stands for `board` is how the same screen carries both.\n */\n subjects?: string[]\n\n /**\n * The conversation a screen opens when the person has not chosen — one per subject type\n * (ADR-0072 A17 ②).\n *\n * Registration order must not decide this: whichever module booted last would silently become\n * the default. Exactly one mode declares each subject type, and the boot report says so when a\n * type has none or more than one.\n */\n defaultFor?: string[]\n\n /**\n * i18n key for what this conversation is called where a person chooses between them —\n * \"이 그림 고치기\", \"이 현장 지금 어때\". A mode a screen may offer needs one.\n */\n labelKey?: string\n /** Who the assistant is here, and what it must not claim to do. The server's, not the caller's. */\n systemPrompt: string\n /** Which tool categories this conversation may use. Absent or empty exposes none. */\n toolCategories?: string[]\n /** Make the model read before it answers — for surfaces whose subject is live state. */\n requireToolOnFirstTurn?: boolean\n /** Model and limits are policy, not a request field. */\n model?: string\n temperature?: number\n maxTokens?: number\n /** What `/` offers. Declared as keys; the server translates. */\n slashTemplates?: AssistantModeSlash[]\n /** What `@` offers. */\n catalogEntries?: any[]\n /**\n * i18n key for the words in the empty input.\n *\n * The screen says what this conversation is for, and that differs by mode: figure's modeller had\n * \"describe the figure you want\" while the person was there to change the figure already open.\n */\n placeholderKey?: string\n /** i18n key for the line under an empty conversation. */\n footerNoticeKey?: string\n /** i18n key for the line shown once the host reports a proposal staged. */\n stagedNoticeKey?: string\n /** Follow-up chips for a proposal that arrived without its own. */\n proposalChoices?: any[]\n}\n\n/** A declaration that registered but is not what the contract asks for. Code + params, not prose. */\nexport interface AssistantModeWarning {\n /**\n * `assistant-mode-slash-outside-tools` — a `/` sentence needs a tool this mode does not open.\n * `assistant-mode-subject-without-default` — a subject type no conversation claims as its default.\n * `assistant-mode-subject-default-twice` — two conversations claim the same subject type.\n * `assistant-mode-offered-without-label` — a conversation a screen may offer with nothing to call it.\n */\n code:\n | 'assistant-mode-slash-outside-tools'\n | 'assistant-mode-subject-without-default'\n | 'assistant-mode-subject-default-twice'\n | 'assistant-mode-offered-without-label'\n params: { kind: string; slash?: string; tool?: string; subject?: string; kinds?: string[] }\n /** For a person reading the boot log; callers act on the code and params. */\n message: string\n}\n"]}
@@ -0,0 +1,48 @@
1
+ /** One turn as the caller carried it over. */
2
+ export interface CarriedTurn {
3
+ role?: string;
4
+ content?: string;
5
+ /** Where an assistant turn came from, so the server can read the original instead of trusting it. */
6
+ sourceSessionId?: string;
7
+ sourceMessageId?: string;
8
+ }
9
+ export interface PlannedRow {
10
+ role: 'user' | 'assistant' | 'system';
11
+ content: string;
12
+ /** People's words are addressed to the room — this door is not the one that asks the assistant. */
13
+ addressee?: 'room';
14
+ /** Set when the row must be filled from a stored message before it is written. */
15
+ copyOf?: {
16
+ sessionId?: string;
17
+ messageId: string;
18
+ };
19
+ }
20
+ /** How much of a carried conversation is kept — context, not an archive. */
21
+ export declare const CARRIED_TURN_LIMIT = 40;
22
+ export declare const CARRIED_CONTENT_LIMIT = 4000;
23
+ /**
24
+ * What to write, before anything is looked up.
25
+ *
26
+ * Pure: the caller resolves `copyOf` against storage and decides what a missing original becomes.
27
+ */
28
+ export declare function planCarriedTurns(turns: CarriedTurn[]): PlannedRow[];
29
+ /**
30
+ * An answer with no original, kept as a quotation.
31
+ *
32
+ * English, like the other lines the model reads (`[system]`, the omission notices): this row is
33
+ * context for the model first, and a person reading it sees the quoted text either way.
34
+ */
35
+ export declare function quoteCarriedAnswer(content: string): string;
36
+ /**
37
+ * 원본을 찾는 조건.
38
+ *
39
+ * 문의 설명은 「`sourceSessionId` 와 `sourceMessageId` 로 원본을 댄다」고 말한다. 조회가 메시지 id 와
40
+ * 테넌트만 보면, 서로 어긋난 두 인자가 와도 설명과 다르게 통과한다 — 설명과 코드가 갈리는 자리다
41
+ * [수석 아키텍트가 ADR-0070 구현 검토에서 찾음, 2026-09-18].
42
+ *
43
+ * 테넌트는 언제나 건다. 대화를 대면 그것도 건다.
44
+ */
45
+ export declare function carriedOriginalWhere(domainId: string, copyOf: {
46
+ sessionId?: string;
47
+ messageId: string;
48
+ }): any;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ /*
3
+ * A conversation carried in from somewhere else, turned into rows this session can hold.
4
+ *
5
+ * ── Why the assistant's turns are not simply written (ADR-0070 A15 decision ③) ──
6
+ * `appendSessionTurns` took whatever roles the caller sent, so a client could write a row that
7
+ * reads as the assistant speaking. Every participant then sees a sentence the assistant never
8
+ * said, and the model reads it back as its own earlier turn. Roles are the server's to write.
9
+ *
10
+ * So a carried assistant turn is kept only when its original can be found — the caller names it
11
+ * (`sourceSessionId` · `sourceMessageId`) and the server copies the stored text. Without an
12
+ * original it is kept as a quotation on a `system` row: the content is preserved, and nobody is
13
+ * told the assistant said it here.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.CARRIED_CONTENT_LIMIT = exports.CARRIED_TURN_LIMIT = void 0;
17
+ exports.planCarriedTurns = planCarriedTurns;
18
+ exports.quoteCarriedAnswer = quoteCarriedAnswer;
19
+ exports.carriedOriginalWhere = carriedOriginalWhere;
20
+ /** How much of a carried conversation is kept — context, not an archive. */
21
+ exports.CARRIED_TURN_LIMIT = 40;
22
+ exports.CARRIED_CONTENT_LIMIT = 4000;
23
+ /**
24
+ * What to write, before anything is looked up.
25
+ *
26
+ * Pure: the caller resolves `copyOf` against storage and decides what a missing original becomes.
27
+ */
28
+ function planCarriedTurns(turns) {
29
+ const planned = [];
30
+ for (const turn of Array.isArray(turns) ? turns : []) {
31
+ const content = (turn?.content ?? '').trim().slice(0, exports.CARRIED_CONTENT_LIMIT);
32
+ if (!content)
33
+ continue;
34
+ const role = turn?.role === 'ai' || turn?.role === 'assistant' ? 'assistant' : turn?.role === 'user' ? 'user' : 'system';
35
+ if (role === 'user') {
36
+ planned.push({ role: 'user', content, addressee: 'room' });
37
+ continue;
38
+ }
39
+ if (role === 'assistant') {
40
+ if (turn?.sourceMessageId) {
41
+ planned.push({
42
+ role: 'assistant',
43
+ content,
44
+ copyOf: { sessionId: turn.sourceSessionId, messageId: turn.sourceMessageId }
45
+ });
46
+ }
47
+ else {
48
+ planned.push({ role: 'system', content: quoteCarriedAnswer(content) });
49
+ }
50
+ continue;
51
+ }
52
+ planned.push({ role: 'system', content });
53
+ }
54
+ return planned.slice(-exports.CARRIED_TURN_LIMIT);
55
+ }
56
+ /**
57
+ * An answer with no original, kept as a quotation.
58
+ *
59
+ * English, like the other lines the model reads (`[system]`, the omission notices): this row is
60
+ * context for the model first, and a person reading it sees the quoted text either way.
61
+ */
62
+ function quoteCarriedAnswer(content) {
63
+ return `carried from another conversation, quoted — the assistant did not say this here: "${content}"`;
64
+ }
65
+ /**
66
+ * 원본을 찾는 조건.
67
+ *
68
+ * 문의 설명은 「`sourceSessionId` 와 `sourceMessageId` 로 원본을 댄다」고 말한다. 조회가 메시지 id 와
69
+ * 테넌트만 보면, 서로 어긋난 두 인자가 와도 설명과 다르게 통과한다 — 설명과 코드가 갈리는 자리다
70
+ * [수석 아키텍트가 ADR-0070 구현 검토에서 찾음, 2026-09-18].
71
+ *
72
+ * 테넌트는 언제나 건다. 대화를 대면 그것도 건다.
73
+ */
74
+ function carriedOriginalWhere(domainId, copyOf) {
75
+ return {
76
+ id: copyOf.messageId,
77
+ session: copyOf.sessionId ? { id: copyOf.sessionId, domain: { id: domainId } } : { domain: { id: domainId } }
78
+ };
79
+ }
80
+ //# sourceMappingURL=carried-turns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"carried-turns.js","sourceRoot":"","sources":["../../../server/service/chat-message/carried-turns.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;;AA6BH,4CA+BC;AAQD,gDAEC;AAWD,oDAKC;AAlED,4EAA4E;AAC/D,QAAA,kBAAkB,GAAG,EAAE,CAAA;AACvB,QAAA,qBAAqB,GAAG,IAAI,CAAA;AAEzC;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,KAAoB;IACnD,MAAM,OAAO,GAAiB,EAAE,CAAA;IAEhC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACrD,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,6BAAqB,CAAC,CAAA;QAC5E,IAAI,CAAC,OAAO;YAAE,SAAQ;QAEtB,MAAM,IAAI,GAAG,IAAI,EAAE,IAAI,KAAK,IAAI,IAAI,IAAI,EAAE,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAA;QAExH,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAA;YAC1D,SAAQ;QACV,CAAC;QAED,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;YACzB,IAAI,IAAI,EAAE,eAAe,EAAE,CAAC;gBAC1B,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,WAAW;oBACjB,OAAO;oBACP,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE;iBAC7E,CAAC,CAAA;YACJ,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;YACxE,CAAC;YACD,SAAQ;QACV,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAA;IAC3C,CAAC;IAED,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,0BAAkB,CAAC,CAAA;AAC3C,CAAC;AAED;;;;;GAKG;AACH,SAAgB,kBAAkB,CAAC,OAAe;IAChD,OAAO,qFAAqF,OAAO,GAAG,CAAA;AACxG,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,oBAAoB,CAAC,QAAgB,EAAE,MAAiD;IACtG,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,SAAS;QACpB,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE;KAC9G,CAAA;AACH,CAAC","sourcesContent":["/*\n * A conversation carried in from somewhere else, turned into rows this session can hold.\n *\n * ── Why the assistant's turns are not simply written (ADR-0070 A15 decision ③) ──\n * `appendSessionTurns` took whatever roles the caller sent, so a client could write a row that\n * reads as the assistant speaking. Every participant then sees a sentence the assistant never\n * said, and the model reads it back as its own earlier turn. Roles are the server's to write.\n *\n * So a carried assistant turn is kept only when its original can be found — the caller names it\n * (`sourceSessionId` · `sourceMessageId`) and the server copies the stored text. Without an\n * original it is kept as a quotation on a `system` row: the content is preserved, and nobody is\n * told the assistant said it here.\n */\n\n/** One turn as the caller carried it over. */\nexport interface CarriedTurn {\n role?: string\n content?: string\n /** Where an assistant turn came from, so the server can read the original instead of trusting it. */\n sourceSessionId?: string\n sourceMessageId?: string\n}\n\nexport interface PlannedRow {\n role: 'user' | 'assistant' | 'system'\n content: string\n /** People's words are addressed to the room — this door is not the one that asks the assistant. */\n addressee?: 'room'\n /** Set when the row must be filled from a stored message before it is written. */\n copyOf?: { sessionId?: string; messageId: string }\n}\n\n/** How much of a carried conversation is kept — context, not an archive. */\nexport const CARRIED_TURN_LIMIT = 40\nexport const CARRIED_CONTENT_LIMIT = 4000\n\n/**\n * What to write, before anything is looked up.\n *\n * Pure: the caller resolves `copyOf` against storage and decides what a missing original becomes.\n */\nexport function planCarriedTurns(turns: CarriedTurn[]): PlannedRow[] {\n const planned: PlannedRow[] = []\n\n for (const turn of Array.isArray(turns) ? turns : []) {\n const content = (turn?.content ?? '').trim().slice(0, CARRIED_CONTENT_LIMIT)\n if (!content) continue\n\n const role = turn?.role === 'ai' || turn?.role === 'assistant' ? 'assistant' : turn?.role === 'user' ? 'user' : 'system'\n\n if (role === 'user') {\n planned.push({ role: 'user', content, addressee: 'room' })\n continue\n }\n\n if (role === 'assistant') {\n if (turn?.sourceMessageId) {\n planned.push({\n role: 'assistant',\n content,\n copyOf: { sessionId: turn.sourceSessionId, messageId: turn.sourceMessageId }\n })\n } else {\n planned.push({ role: 'system', content: quoteCarriedAnswer(content) })\n }\n continue\n }\n\n planned.push({ role: 'system', content })\n }\n\n return planned.slice(-CARRIED_TURN_LIMIT)\n}\n\n/**\n * An answer with no original, kept as a quotation.\n *\n * English, like the other lines the model reads (`[system]`, the omission notices): this row is\n * context for the model first, and a person reading it sees the quoted text either way.\n */\nexport function quoteCarriedAnswer(content: string): string {\n return `carried from another conversation, quoted — the assistant did not say this here: \"${content}\"`\n}\n\n/**\n * 원본을 찾는 조건.\n *\n * 문의 설명은 「`sourceSessionId` 와 `sourceMessageId` 로 원본을 댄다」고 말한다. 조회가 메시지 id 와\n * 테넌트만 보면, 서로 어긋난 두 인자가 와도 설명과 다르게 통과한다 — 설명과 코드가 갈리는 자리다\n * [수석 아키텍트가 ADR-0070 구현 검토에서 찾음, 2026-09-18].\n *\n * 테넌트는 언제나 건다. 대화를 대면 그것도 건다.\n */\nexport function carriedOriginalWhere(domainId: string, copyOf: { sessionId?: string; messageId: string }): any {\n return {\n id: copyOf.messageId,\n session: copyOf.sessionId ? { id: copyOf.sessionId, domain: { id: domainId } } : { domain: { id: domainId } }\n }\n}\n"]}
@@ -4,5 +4,5 @@ export { publishChatMessage } from './chat-message-publish.js';
4
4
  export { CHAT_MESSAGE_TOPIC, chatMessageFilter } from './chat-message-broadcast.js';
5
5
  export type { ChatMessagePayload } from './chat-message-broadcast.js';
6
6
  export declare class ChatMessageSubscription {
7
- chatMessageActivity(payload: ChatMessagePayload, sessionId: string): ChatMessage;
7
+ chatMessageActivity(payload: ChatMessagePayload, sessionId: string, context: any): Promise<ChatMessage>;
8
8
  }