@things-factory/ai-assistant 10.1.207 → 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.
Files changed (181) hide show
  1. package/client/components/assistant-chat.ts +238 -9
  2. package/client/components/assistant-dock.ts +9 -4
  3. package/client/components/assistant-invite.ts +138 -0
  4. package/client/components/assistant-session-manager.ts +53 -12
  5. package/client/components/proposal-request.test.ts +10 -0
  6. package/client/components/roster-identity.test.ts +2 -12
  7. package/client/components/roster-identity.ts +0 -6
  8. package/client/setup-assistant-dock.ts +1 -0
  9. package/client/utils/assistant-session-actions.ts +3 -1
  10. package/client/utils/assistant-session-admin.ts +9 -2
  11. package/client/utils/assistant-session-controller.ts +1 -1
  12. package/client/utils/assistant-typing.test.ts +33 -0
  13. package/client/utils/assistant-typing.ts +53 -0
  14. package/dist-client/components/assistant-chat.d.ts +28 -0
  15. package/dist-client/components/assistant-chat.js +259 -8
  16. package/dist-client/components/assistant-chat.js.map +1 -1
  17. package/dist-client/components/assistant-dock.js +12 -4
  18. package/dist-client/components/assistant-dock.js.map +1 -1
  19. package/dist-client/components/assistant-invite.d.ts +39 -0
  20. package/dist-client/components/assistant-invite.js +150 -0
  21. package/dist-client/components/assistant-invite.js.map +1 -0
  22. package/dist-client/components/assistant-session-manager.d.ts +5 -1
  23. package/dist-client/components/assistant-session-manager.js +40 -10
  24. package/dist-client/components/assistant-session-manager.js.map +1 -1
  25. package/dist-client/components/proposal-request.test.js +8 -0
  26. package/dist-client/components/proposal-request.test.js.map +1 -1
  27. package/dist-client/components/roster-identity.d.ts +0 -2
  28. package/dist-client/components/roster-identity.js +0 -7
  29. package/dist-client/components/roster-identity.js.map +1 -1
  30. package/dist-client/components/roster-identity.test.js +2 -13
  31. package/dist-client/components/roster-identity.test.js.map +1 -1
  32. package/dist-client/setup-assistant-dock.js +1 -0
  33. package/dist-client/setup-assistant-dock.js.map +1 -1
  34. package/dist-client/tsconfig.tsbuildinfo +1 -1
  35. package/dist-client/utils/assistant-session-actions.d.ts +1 -0
  36. package/dist-client/utils/assistant-session-actions.js +3 -1
  37. package/dist-client/utils/assistant-session-actions.js.map +1 -1
  38. package/dist-client/utils/assistant-session-admin.d.ts +5 -2
  39. package/dist-client/utils/assistant-session-admin.js +3 -0
  40. package/dist-client/utils/assistant-session-admin.js.map +1 -1
  41. package/dist-client/utils/assistant-session-controller.d.ts +1 -1
  42. package/dist-client/utils/assistant-session-controller.js.map +1 -1
  43. package/dist-client/utils/assistant-typing.d.ts +20 -0
  44. package/dist-client/utils/assistant-typing.js +48 -0
  45. package/dist-client/utils/assistant-typing.js.map +1 -0
  46. package/dist-client/utils/assistant-typing.test.d.ts +1 -0
  47. package/dist-client/utils/assistant-typing.test.js +30 -0
  48. package/dist-client/utils/assistant-typing.test.js.map +1 -0
  49. package/dist-server/service/assistant-chat-resolver.js +5 -12
  50. package/dist-server/service/assistant-chat-resolver.js.map +1 -1
  51. package/dist-server/service/assistant-mode/index.d.ts +1 -1
  52. package/dist-server/service/assistant-mode/index.js +4 -1
  53. package/dist-server/service/assistant-mode/index.js.map +1 -1
  54. package/dist-server/service/assistant-mode/mode-categories.js +9 -2
  55. package/dist-server/service/assistant-mode/mode-categories.js.map +1 -1
  56. package/dist-server/service/assistant-mode/screen-rules.d.ts +19 -0
  57. package/dist-server/service/assistant-mode/screen-rules.js +40 -6
  58. package/dist-server/service/assistant-mode/screen-rules.js.map +1 -1
  59. package/dist-server/service/chat-message/chat-message-room.js +3 -1
  60. package/dist-server/service/chat-message/chat-message-room.js.map +1 -1
  61. package/dist-server/service/chat-message/chat-typing-subscription.d.ts +10 -0
  62. package/dist-server/service/chat-message/chat-typing-subscription.js +68 -0
  63. package/dist-server/service/chat-message/chat-typing-subscription.js.map +1 -0
  64. package/dist-server/service/chat-message/chat-typing.d.ts +28 -0
  65. package/dist-server/service/chat-message/chat-typing.js +40 -0
  66. package/dist-server/service/chat-message/chat-typing.js.map +1 -0
  67. package/dist-server/service/chat-session/attachment-tool-result.d.ts +1 -0
  68. package/dist-server/service/chat-session/attachment-tool-result.js +6 -0
  69. package/dist-server/service/chat-session/attachment-tool-result.js.map +1 -1
  70. package/dist-server/service/chat-session/attachment-tools.js +2 -2
  71. package/dist-server/service/chat-session/attachment-tools.js.map +1 -1
  72. package/dist-server/service/chat-session/chat-session.d.ts +2 -1
  73. package/dist-server/service/chat-session/chat-session.js +1 -1
  74. package/dist-server/service/chat-session/chat-session.js.map +1 -1
  75. package/dist-server/service/chat-session/session-activity-broadcast.d.ts +12 -0
  76. package/dist-server/service/chat-session/session-activity-broadcast.js +15 -0
  77. package/dist-server/service/chat-session/session-activity-broadcast.js.map +1 -1
  78. package/dist-server/service/chat-session/session-activity-publish.d.ts +2 -0
  79. package/dist-server/service/chat-session/session-activity-publish.js +5 -2
  80. package/dist-server/service/chat-session/session-activity-publish.js.map +1 -1
  81. package/dist-server/service/chat-session/session-visibility.d.ts +17 -2
  82. package/dist-server/service/chat-session/session-visibility.js +28 -3
  83. package/dist-server/service/chat-session/session-visibility.js.map +1 -1
  84. package/dist-server/service/chat-session/session-write-gate.d.ts +1 -1
  85. package/dist-server/service/chat-session/session-write-gate.js +1 -1
  86. package/dist-server/service/chat-session/session-write-gate.js.map +1 -1
  87. package/dist-server/service/chat-session-participant/index.d.ts +1 -0
  88. package/dist-server/service/chat-session-participant/index.js +4 -1
  89. package/dist-server/service/chat-session-participant/index.js.map +1 -1
  90. package/dist-server/service/chat-session-participant/invite-notification.d.ts +16 -1
  91. package/dist-server/service/chat-session-participant/invite-notification.js +34 -5
  92. package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -1
  93. package/dist-server/service/chat-session-participant/mention-invite.d.ts +30 -4
  94. package/dist-server/service/chat-session-participant/mention-invite.js +78 -31
  95. package/dist-server/service/chat-session-participant/mention-invite.js.map +1 -1
  96. package/dist-server/service/chat-session-participant/read-roster.js +5 -2
  97. package/dist-server/service/chat-session-participant/read-roster.js.map +1 -1
  98. package/dist-server/service/chat-session-resolver.d.ts +27 -0
  99. package/dist-server/service/chat-session-resolver.js +226 -25
  100. package/dist-server/service/chat-session-resolver.js.map +1 -1
  101. package/dist-server/service/data-entry/entry-target.d.ts +19 -0
  102. package/dist-server/service/data-entry/entry-target.js +20 -5
  103. package/dist-server/service/data-entry/entry-target.js.map +1 -1
  104. package/dist-server/service/data-entry/entry-tools.js +2 -2
  105. package/dist-server/service/data-entry/entry-tools.js.map +1 -1
  106. package/dist-server/service/data-entry/entry-update.d.ts +2 -1
  107. package/dist-server/service/data-entry/entry-update.js +5 -3
  108. package/dist-server/service/data-entry/entry-update.js.map +1 -1
  109. package/dist-server/service/data-read/list-read.d.ts +10 -0
  110. package/dist-server/service/data-read/list-read.js +12 -0
  111. package/dist-server/service/data-read/list-read.js.map +1 -1
  112. package/dist-server/service/data-read/read-target.d.ts +7 -0
  113. package/dist-server/service/data-read/read-target.js +2 -2
  114. package/dist-server/service/data-read/read-target.js.map +1 -1
  115. package/dist-server/service/data-read/read-tools.js +1 -1
  116. package/dist-server/service/data-read/read-tools.js.map +1 -1
  117. package/dist-server/service/index.d.ts +4 -2
  118. package/dist-server/service/index.js +9 -1
  119. package/dist-server/service/index.js.map +1 -1
  120. package/dist-server/service/testing/conversation-script.d.ts +1 -1
  121. package/dist-server/service/testing/conversation-script.js.map +1 -1
  122. package/dist-server/service/testing/mode-conformance.js +4 -0
  123. package/dist-server/service/testing/mode-conformance.js.map +1 -1
  124. package/dist-server/tsconfig.tsbuildinfo +1 -1
  125. package/package.json +9 -9
  126. package/server/explicit-graphql-types.test.ts +1 -1
  127. package/server/service/assistant-chat-resolver.ts +7 -12
  128. package/server/service/assistant-mode/index.ts +1 -1
  129. package/server/service/assistant-mode/mode-categories.ts +11 -3
  130. package/server/service/assistant-mode/mode-front.test.ts +53 -1
  131. package/server/service/assistant-mode/resolve.test.ts +2 -1
  132. package/server/service/assistant-mode/screen-rules.test.ts +41 -3
  133. package/server/service/assistant-mode/screen-rules.ts +44 -5
  134. package/server/service/chat-message/chat-message-room.test.ts +7 -2
  135. package/server/service/chat-message/chat-message-room.ts +3 -1
  136. package/server/service/chat-message/chat-typing-subscription.ts +52 -0
  137. package/server/service/chat-message/chat-typing.test.ts +45 -0
  138. package/server/service/chat-message/chat-typing.ts +48 -0
  139. package/server/service/chat-message/fold-history-window.test.ts +1 -1
  140. package/server/service/chat-session/attachment-tool-result.test.ts +9 -1
  141. package/server/service/chat-session/attachment-tool-result.ts +7 -0
  142. package/server/service/chat-session/attachment-tools.ts +3 -3
  143. package/server/service/chat-session/chat-session.ts +3 -2
  144. package/server/service/chat-session/group-room.test.ts +233 -0
  145. package/server/service/chat-session/session-activity-broadcast.ts +20 -0
  146. package/server/service/chat-session/session-activity-publish.ts +6 -1
  147. package/server/service/chat-session/session-activity-receivers.test.ts +26 -0
  148. package/server/service/chat-session/session-visibility.test.ts +32 -1
  149. package/server/service/chat-session/session-visibility.ts +33 -4
  150. package/server/service/chat-session/session-write-gate.test.ts +7 -1
  151. package/server/service/chat-session/session-write-gate.ts +2 -2
  152. package/server/service/chat-session-participant/index.ts +2 -0
  153. package/server/service/chat-session-participant/invite-notification.test.ts +36 -1
  154. package/server/service/chat-session-participant/invite-notification.ts +43 -5
  155. package/server/service/chat-session-participant/mention-invite.test.ts +49 -1
  156. package/server/service/chat-session-participant/mention-invite.ts +82 -28
  157. package/server/service/chat-session-participant/read-roster.test.ts +13 -4
  158. package/server/service/chat-session-participant/read-roster.ts +6 -3
  159. package/server/service/chat-session-resolver.ts +204 -27
  160. package/server/service/data-entry/data-entry-update.test.ts +19 -0
  161. package/server/service/data-entry/data-entry.test.ts +65 -1
  162. package/server/service/data-entry/entry-target.ts +36 -5
  163. package/server/service/data-entry/entry-tools.ts +2 -2
  164. package/server/service/data-entry/entry-update.ts +6 -3
  165. package/server/service/data-read/list-missing.test.ts +49 -0
  166. package/server/service/data-read/list-read.ts +18 -0
  167. package/server/service/data-read/read-target.ts +10 -3
  168. package/server/service/data-read/read-tools.ts +1 -1
  169. package/server/service/data-read/record-keys.test.ts +36 -0
  170. package/server/service/index.ts +6 -1
  171. package/server/service/testing/conversation-script.ts +1 -1
  172. package/server/service/testing/mode-conformance.ts +2 -0
  173. package/test/assistant-dock.test.ts +5 -1
  174. package/test/assistant-session-manager.test.ts +4 -2
  175. package/test/open-doors.test.ts +10 -5
  176. package/translations/en.json +29 -4
  177. package/translations/es.json +29 -4
  178. package/translations/ja.json +29 -4
  179. package/translations/ko.json +30 -5
  180. package/translations/ms.json +29 -4
  181. package/translations/zh.json +29 -4
@@ -5,6 +5,7 @@ import { AssistantCapabilityResolver } from './assistant-capability-resolver.js'
5
5
  import { AiAgentResolver } from './ai-agent-resolver.js';
6
6
  import { MySessionActivitySubscription } from './chat-session/session-activity-subscription.js';
7
7
  import { ChatMessageSubscription } from './chat-message/chat-message-subscription.js';
8
+ import { ChatTypingSubscription } from './chat-message/chat-typing-subscription.js';
8
9
  import './chat-session/attachment-tools.js';
9
10
  import './chat-session/session-purge.js';
10
11
  import './chat-session/attachment-route.js';
@@ -18,7 +19,8 @@ export { requestTranslator } from './request-translator.js';
18
19
  export * from './assistant-capability-resolver.js';
19
20
  export * from './tool-access.js';
20
21
  export * from './data-entry/entry-target.js';
21
- export { registerEntryTarget, entryCategoryOf } from './data-entry/entry-tools.js';
22
+ export { registerEntryTarget, entryCategoryOf, runAsCaller } from './data-entry/entry-tools.js';
23
+ export { proposeChanges, MAX_CHANGES } from './data-entry/entry-update.js';
22
24
  export * from './data-read/read-target.js';
23
25
  export { registerReadTarget, readCategoryOf, registerDetailRead, detailCategoryOf } from './data-read/read-tools.js';
24
26
  export * from './data-read/list-read.js';
@@ -46,5 +48,5 @@ export * from './chat-session-participant/index.js';
46
48
  export * from './unattended-run/index.js';
47
49
  export declare const entities: (typeof import("./chat-message/chat-message.js").ChatMessage | typeof import("./chat-session/chat-session.js").ChatSession | typeof import("./chat-session-participant/chat-session-participant.js").ChatSessionParticipant | typeof import("./unattended-run/assistant-run.js").AssistantRun)[];
48
50
  export declare const schema: {
49
- resolverClasses: (typeof ChatSessionResolver | typeof AssistantChatResolver | typeof AssistantModeResolver | typeof AssistantCapabilityResolver | typeof AiAgentResolver | typeof MySessionActivitySubscription | typeof ChatMessageSubscription | typeof EntryProposalResolver)[];
51
+ resolverClasses: (typeof ChatSessionResolver | typeof AssistantChatResolver | typeof AssistantModeResolver | typeof AssistantCapabilityResolver | typeof AiAgentResolver | typeof MySessionActivitySubscription | typeof ChatMessageSubscription | typeof ChatTypingSubscription | typeof EntryProposalResolver)[];
50
52
  };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.schema = exports.entities = exports.detailCategoryOf = exports.registerDetailRead = exports.readCategoryOf = exports.registerReadTarget = exports.entryCategoryOf = exports.registerEntryTarget = exports.requestTranslator = void 0;
3
+ exports.schema = exports.entities = exports.detailCategoryOf = exports.registerDetailRead = exports.readCategoryOf = exports.registerReadTarget = exports.MAX_CHANGES = exports.proposeChanges = exports.runAsCaller = exports.entryCategoryOf = exports.registerEntryTarget = exports.requestTranslator = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const chat_session_resolver_js_1 = require("./chat-session-resolver.js");
6
6
  const assistant_chat_resolver_js_1 = require("./assistant-chat-resolver.js");
@@ -9,6 +9,7 @@ const assistant_capability_resolver_js_1 = require("./assistant-capability-resol
9
9
  const ai_agent_resolver_js_1 = require("./ai-agent-resolver.js");
10
10
  const session_activity_subscription_js_1 = require("./chat-session/session-activity-subscription.js");
11
11
  const chat_message_subscription_js_1 = require("./chat-message/chat-message-subscription.js");
12
+ const chat_typing_subscription_js_1 = require("./chat-message/chat-typing-subscription.js");
12
13
  const index_js_1 = require("./chat-session/index.js");
13
14
  const index_js_2 = require("./chat-message/index.js");
14
15
  const index_js_3 = require("./chat-session-participant/index.js");
@@ -36,6 +37,11 @@ tslib_1.__exportStar(require("./data-entry/entry-target.js"), exports);
36
37
  var entry_tools_js_1 = require("./data-entry/entry-tools.js");
37
38
  Object.defineProperty(exports, "registerEntryTarget", { enumerable: true, get: function () { return entry_tools_js_1.registerEntryTarget; } });
38
39
  Object.defineProperty(exports, "entryCategoryOf", { enumerable: true, get: function () { return entry_tools_js_1.entryCategoryOf; } });
40
+ Object.defineProperty(exports, "runAsCaller", { enumerable: true, get: function () { return entry_tools_js_1.runAsCaller; } });
41
+ /* An application's tool that prepares many changes at once makes the same change card the model's would (2026-10-24). */
42
+ var entry_update_js_1 = require("./data-entry/entry-update.js");
43
+ Object.defineProperty(exports, "proposeChanges", { enumerable: true, get: function () { return entry_update_js_1.proposeChanges; } });
44
+ Object.defineProperty(exports, "MAX_CHANGES", { enumerable: true, get: function () { return entry_update_js_1.MAX_CHANGES; } });
39
45
  /* Reading an application's lists - an app registers what it lets read; a mode names it (design/ai/read-targets.md). */
40
46
  tslib_1.__exportStar(require("./data-read/read-target.js"), exports);
41
47
  var read_tools_js_1 = require("./data-read/read-tools.js");
@@ -87,6 +93,8 @@ exports.schema = {
87
93
  /* A domain's AI agents - the settings card and the dock's picker (design/ai/agent-settings.md). */
88
94
  ai_agent_resolver_js_1.AiAgentResolver,
89
95
  chat_message_subscription_js_1.ChatMessageSubscription,
96
+ /* 「입력 중」 — 저장하지 않는 방의 한 줄. 듣는 사람은 메시지와 같은 방 판정을 지난다. */
97
+ chat_typing_subscription_js_1.ChatTypingSubscription,
90
98
  /* 내가 참여한 모든 세션의 활동 — 창을 닫아 둔 표면이 배지·목록을 갱신할 수 있게. */
91
99
  session_activity_subscription_js_1.MySessionActivitySubscription,
92
100
  /* 사람이 누른 입력 제안을 쓰는 자리 — 행마다 앱의 코드로, 그 사람의 권한으로. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yEAAgE;AAChE,6EAAoE;AACpE,4FAAmF;AACnF,yFAAgF;AAChF,iEAAwD;AACxD,sGAA+F;AAC/F,8FAAqF;AACrF,sDAAyE;AACzE,sDAAyE;AACzE,kEAAgG;AAChG,8CAA2C;AAC3C,2CAAwC;AACxC,8CAA2C;AAC3C,sEAAsE;AACtE,0CAAuC;AACvC,wDAA6E;AAE7E,qDAA0B;AAC1B,qEAA0C;AAC1C,uEAA4C;AAC5C,oGAAoG;AACpG,oEAAyC;AACzC,iEAA2D;AAAlD,0HAAA,iBAAiB,OAAA;AAC1B,6EAAkD;AAClD;;;GAGG;AACH,2DAAgC;AAChC,+GAA+G;AAC/G,uEAA4C;AAC5C,8DAAkF;AAAzE,qHAAA,mBAAmB,OAAA;AAAE,iHAAA,eAAe,OAAA;AAC7C,uHAAuH;AACvH,qEAA0C;AAC1C,2DAAoH;AAA3G,mHAAA,kBAAkB,OAAA;AAAE,+GAAA,cAAc,OAAA;AAAE,mHAAA,kBAAkB,OAAA;AAAE,iHAAA,gBAAgB,OAAA;AACjF,mEAAwC;AACxC,iFAAiF;AACjF,0EAA+C;AAC/C,gDAAgD;AAChD,iEAAsC;AACtC,sDAAsD;AACtD,2EAAgD;AAChD,gHAAgH;AAChH,+EAAoD;AACpD,kEAAuC;AACvC,8DAA8D;AAC9D,0EAA+C;AAC/C,8EAAmD;AACnD,0EAA+C;AAC/C,qFAA0D;AAC1D,uFAA4D;AAC5D,wEAA6C;AAC7C,4EAAiD;AACjD,yEAA8C;AAC9C,wDAAwD;AACxD,iFAAsD;AACtD,iFAAsD;AACtD,mFAAwD;AACxD,4FAAiE;AACjE,2DAA2D;AAC3D,uFAA4D;AAC5D,iFAAsD;AACtD,kEAAuC;AACvC,8EAAmD;AACnD,4GAA4G;AAC5G,oEAAyC;AAE5B,QAAA,QAAQ,GAAG,CAAC,GAAG,mBAAmB,EAAE,GAAG,mBAAmB,EAAE,GAAG,mBAA8B,EAAE,GAAG,mBAAqB,CAAC,CAAA;AAExH,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,8CAAmB;QACnB,sDAAsD;QACtD,kDAAqB;QACrB,uDAAuD;QACvD,kDAAqB;QACrB,wDAAwD;QACxD,8DAA2B;QAC3B,mGAAmG;QACnG,sCAAe;QACf,sDAAuB;QACvB,qDAAqD;QACrD,gEAA6B;QAC7B,mDAAmD;QACnD,yCAAqB;KACtB;CACF,CAAA","sourcesContent":["import { ChatSessionResolver } from './chat-session-resolver.js'\nimport { AssistantChatResolver } from './assistant-chat-resolver.js'\nimport { AssistantModeResolver } from './assistant-mode/assistant-mode-resolver.js'\nimport { AssistantCapabilityResolver } from './assistant-capability-resolver.js'\nimport { AiAgentResolver } from './ai-agent-resolver.js'\nimport { MySessionActivitySubscription } from './chat-session/session-activity-subscription.js'\nimport { ChatMessageSubscription } from './chat-message/chat-message-subscription.js'\nimport { entities as ChatSessionEntities } from './chat-session/index.js'\nimport { entities as ChatMessageEntities } from './chat-message/index.js'\nimport { entities as ChatSessionParticipantEntities } from './chat-session-participant/index.js'\nimport './chat-session/attachment-tools.js'\nimport './chat-session/session-purge.js'\nimport './chat-session/attachment-route.js'\nimport { EntryProposalResolver } from './data-entry/entry-resolver.js'\nimport './workspace/workspace-tools.js'\nimport { entities as UnattendedRunEntities } from './unattended-run/index.js'\n\nexport * from './types.js'\nexport * from './chat-session-resolver.js'\nexport * from './assistant-chat-resolver.js'\n/* The mode registry — each app server declares what its conversations are (ADR-0061 decision 7). */\nexport * from './assistant-mode/index.js'\nexport { requestTranslator } from './request-translator.js'\nexport * from './assistant-capability-resolver.js'\n/*\n * 대화 각본 하네스는 **런타임 export 에서 뺀다**(ADR-0070 A15 결정 ④) — 시험 도구가 서버 번들에\n * 실린다. 시험은 경로로 가져온다: `…/service/testing/conversation-script.js`.\n */\nexport * from './tool-access.js'\n/* Data entry from material - an app registers what it can enter; a mode names it (design/ai/data-entry.md). */\nexport * from './data-entry/entry-target.js'\nexport { registerEntryTarget, entryCategoryOf } from './data-entry/entry-tools.js'\n/* Reading an application's lists - an app registers what it lets read; a mode names it (design/ai/read-targets.md). */\nexport * from './data-read/read-target.js'\nexport { registerReadTarget, readCategoryOf, registerDetailRead, detailCategoryOf } from './data-read/read-tools.js'\nexport * from './data-read/list-read.js'\n/* The AI 작업판 - large results on a page of their own (design/ai/workspace.md). */\nexport * from './workspace/workspace-blocks.js'\n/* 무엇이 근거인가 — 도구 결과 밖의 셋(프롬프트 · 대화 · 화면 스냅숏). */\nexport * from './grounding-sources.js'\n/* 앵커 순수 로직 — 호스트가 앵커 종류를 참조한다(리졸버는 데코레이터 때문에 무겁다). */\nexport * from './chat-session/session-anchor.js'\n/* Who may read a conversation — one rule, also asked by doors outside this package (board-ai's revertPatch). */\nexport * from './chat-session/session-visibility.js'\nexport * from './chat-session/index.js'\n/* 순수 로직들 — 호스트(board-ai 등)가 같은 규칙을 쓰라고 공개한다. 베끼면 한쪽만 고쳐진다. */\nexport * from './chat-session/session-inbox.js'\nexport * from './chat-session/attachment-policy.js'\nexport * from './chat-session/session-purge.js'\nexport * from './chat-session/session-activity-publish.js'\nexport * from './chat-session/session-activity-broadcast.js'\nexport * from './chat-message/llm-history.js'\nexport * from './chat-message/history-summary.js'\nexport * from './chat-message/fold-history.js'\n/* 상한 밖으로 밀려난 앞부분을 요약으로 접는 orchestration — 중립 문이 부른다. */\nexport * from './chat-message/fold-session-history.js'\nexport * from './chat-message/chat-message-publish.js'\nexport * from './chat-message/chat-message-broadcast.js'\nexport * from './chat-session-participant/invite-notification.js'\n/* 멘션이 사람을 불러들이는 자리 — 사람의 말을 저장하는 문만 부른다(ADR-0071 결정 1). */\nexport * from './chat-session-participant/mention-invite.js'\nexport * from './chat-session-participant/presence.js'\nexport * from './chat-message/index.js'\nexport * from './chat-session-participant/index.js'\n/* A mode run with no person in it - a scheduled draft (operato-application/design/ai/unattended-run.md). */\nexport * from './unattended-run/index.js'\n\nexport const entities = [...ChatSessionEntities, ...ChatMessageEntities, ...ChatSessionParticipantEntities, ...UnattendedRunEntities]\n\nexport const schema = {\n resolverClasses: [\n ChatSessionResolver,\n /* 문서를 갖지 않은 표면의 대화 — board 를 안 싣는 제품이 물어볼 수 있는 자리. */\n AssistantChatResolver,\n /* 화면이 자기 대화가 무엇을 주는지 묻는 자리 — 문구는 서버가 요청 로케일로 옮겨 낸다. */\n AssistantModeResolver,\n /* 「이 배포에 AI 가 있나 · 무엇이 선언 없이 등록됐나」 — 창구 하나(표준 A7-a). */\n AssistantCapabilityResolver,\n /* A domain's AI agents - the settings card and the dock's picker (design/ai/agent-settings.md). */\n AiAgentResolver,\n ChatMessageSubscription,\n /* 내가 참여한 모든 세션의 활동 — 창을 닫아 둔 표면이 배지·목록을 갱신할 수 있게. */\n MySessionActivitySubscription,\n /* 사람이 누른 입력 제안을 쓰는 자리 — 행마다 앱의 코드로, 그 사람의 권한으로. */\n EntryProposalResolver\n ]\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yEAAgE;AAChE,6EAAoE;AACpE,4FAAmF;AACnF,yFAAgF;AAChF,iEAAwD;AACxD,sGAA+F;AAC/F,8FAAqF;AACrF,4FAAmF;AACnF,sDAAyE;AACzE,sDAAyE;AACzE,kEAAgG;AAChG,8CAA2C;AAC3C,2CAAwC;AACxC,8CAA2C;AAC3C,sEAAsE;AACtE,0CAAuC;AACvC,wDAA6E;AAE7E,qDAA0B;AAC1B,qEAA0C;AAC1C,uEAA4C;AAC5C,oGAAoG;AACpG,oEAAyC;AACzC,iEAA2D;AAAlD,0HAAA,iBAAiB,OAAA;AAC1B,6EAAkD;AAClD;;;GAGG;AACH,2DAAgC;AAChC,+GAA+G;AAC/G,uEAA4C;AAC5C,8DAA+F;AAAtF,qHAAA,mBAAmB,OAAA;AAAE,iHAAA,eAAe,OAAA;AAAE,6GAAA,WAAW,OAAA;AAC1D,yHAAyH;AACzH,gEAA0E;AAAjE,iHAAA,cAAc,OAAA;AAAE,8GAAA,WAAW,OAAA;AACpC,uHAAuH;AACvH,qEAA0C;AAC1C,2DAAoH;AAA3G,mHAAA,kBAAkB,OAAA;AAAE,+GAAA,cAAc,OAAA;AAAE,mHAAA,kBAAkB,OAAA;AAAE,iHAAA,gBAAgB,OAAA;AACjF,mEAAwC;AACxC,iFAAiF;AACjF,0EAA+C;AAC/C,gDAAgD;AAChD,iEAAsC;AACtC,sDAAsD;AACtD,2EAAgD;AAChD,gHAAgH;AAChH,+EAAoD;AACpD,kEAAuC;AACvC,8DAA8D;AAC9D,0EAA+C;AAC/C,8EAAmD;AACnD,0EAA+C;AAC/C,qFAA0D;AAC1D,uFAA4D;AAC5D,wEAA6C;AAC7C,4EAAiD;AACjD,yEAA8C;AAC9C,wDAAwD;AACxD,iFAAsD;AACtD,iFAAsD;AACtD,mFAAwD;AACxD,4FAAiE;AACjE,2DAA2D;AAC3D,uFAA4D;AAC5D,iFAAsD;AACtD,kEAAuC;AACvC,8EAAmD;AACnD,4GAA4G;AAC5G,oEAAyC;AAE5B,QAAA,QAAQ,GAAG,CAAC,GAAG,mBAAmB,EAAE,GAAG,mBAAmB,EAAE,GAAG,mBAA8B,EAAE,GAAG,mBAAqB,CAAC,CAAA;AAExH,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,8CAAmB;QACnB,sDAAsD;QACtD,kDAAqB;QACrB,uDAAuD;QACvD,kDAAqB;QACrB,wDAAwD;QACxD,8DAA2B;QAC3B,mGAAmG;QACnG,sCAAe;QACf,sDAAuB;QACvB,wDAAwD;QACxD,oDAAsB;QACtB,qDAAqD;QACrD,gEAA6B;QAC7B,mDAAmD;QACnD,yCAAqB;KACtB;CACF,CAAA","sourcesContent":["import { ChatSessionResolver } from './chat-session-resolver.js'\nimport { AssistantChatResolver } from './assistant-chat-resolver.js'\nimport { AssistantModeResolver } from './assistant-mode/assistant-mode-resolver.js'\nimport { AssistantCapabilityResolver } from './assistant-capability-resolver.js'\nimport { AiAgentResolver } from './ai-agent-resolver.js'\nimport { MySessionActivitySubscription } from './chat-session/session-activity-subscription.js'\nimport { ChatMessageSubscription } from './chat-message/chat-message-subscription.js'\nimport { ChatTypingSubscription } from './chat-message/chat-typing-subscription.js'\nimport { entities as ChatSessionEntities } from './chat-session/index.js'\nimport { entities as ChatMessageEntities } from './chat-message/index.js'\nimport { entities as ChatSessionParticipantEntities } from './chat-session-participant/index.js'\nimport './chat-session/attachment-tools.js'\nimport './chat-session/session-purge.js'\nimport './chat-session/attachment-route.js'\nimport { EntryProposalResolver } from './data-entry/entry-resolver.js'\nimport './workspace/workspace-tools.js'\nimport { entities as UnattendedRunEntities } from './unattended-run/index.js'\n\nexport * from './types.js'\nexport * from './chat-session-resolver.js'\nexport * from './assistant-chat-resolver.js'\n/* The mode registry — each app server declares what its conversations are (ADR-0061 decision 7). */\nexport * from './assistant-mode/index.js'\nexport { requestTranslator } from './request-translator.js'\nexport * from './assistant-capability-resolver.js'\n/*\n * 대화 각본 하네스는 **런타임 export 에서 뺀다**(ADR-0070 A15 결정 ④) — 시험 도구가 서버 번들에\n * 실린다. 시험은 경로로 가져온다: `…/service/testing/conversation-script.js`.\n */\nexport * from './tool-access.js'\n/* Data entry from material - an app registers what it can enter; a mode names it (design/ai/data-entry.md). */\nexport * from './data-entry/entry-target.js'\nexport { registerEntryTarget, entryCategoryOf, runAsCaller } from './data-entry/entry-tools.js'\n/* An application's tool that prepares many changes at once makes the same change card the model's would (2026-10-24). */\nexport { proposeChanges, MAX_CHANGES } from './data-entry/entry-update.js'\n/* Reading an application's lists - an app registers what it lets read; a mode names it (design/ai/read-targets.md). */\nexport * from './data-read/read-target.js'\nexport { registerReadTarget, readCategoryOf, registerDetailRead, detailCategoryOf } from './data-read/read-tools.js'\nexport * from './data-read/list-read.js'\n/* The AI 작업판 - large results on a page of their own (design/ai/workspace.md). */\nexport * from './workspace/workspace-blocks.js'\n/* 무엇이 근거인가 — 도구 결과 밖의 셋(프롬프트 · 대화 · 화면 스냅숏). */\nexport * from './grounding-sources.js'\n/* 앵커 순수 로직 — 호스트가 앵커 종류를 참조한다(리졸버는 데코레이터 때문에 무겁다). */\nexport * from './chat-session/session-anchor.js'\n/* Who may read a conversation — one rule, also asked by doors outside this package (board-ai's revertPatch). */\nexport * from './chat-session/session-visibility.js'\nexport * from './chat-session/index.js'\n/* 순수 로직들 — 호스트(board-ai 등)가 같은 규칙을 쓰라고 공개한다. 베끼면 한쪽만 고쳐진다. */\nexport * from './chat-session/session-inbox.js'\nexport * from './chat-session/attachment-policy.js'\nexport * from './chat-session/session-purge.js'\nexport * from './chat-session/session-activity-publish.js'\nexport * from './chat-session/session-activity-broadcast.js'\nexport * from './chat-message/llm-history.js'\nexport * from './chat-message/history-summary.js'\nexport * from './chat-message/fold-history.js'\n/* 상한 밖으로 밀려난 앞부분을 요약으로 접는 orchestration — 중립 문이 부른다. */\nexport * from './chat-message/fold-session-history.js'\nexport * from './chat-message/chat-message-publish.js'\nexport * from './chat-message/chat-message-broadcast.js'\nexport * from './chat-session-participant/invite-notification.js'\n/* 멘션이 사람을 불러들이는 자리 — 사람의 말을 저장하는 문만 부른다(ADR-0071 결정 1). */\nexport * from './chat-session-participant/mention-invite.js'\nexport * from './chat-session-participant/presence.js'\nexport * from './chat-message/index.js'\nexport * from './chat-session-participant/index.js'\n/* A mode run with no person in it - a scheduled draft (operato-application/design/ai/unattended-run.md). */\nexport * from './unattended-run/index.js'\n\nexport const entities = [...ChatSessionEntities, ...ChatMessageEntities, ...ChatSessionParticipantEntities, ...UnattendedRunEntities]\n\nexport const schema = {\n resolverClasses: [\n ChatSessionResolver,\n /* 문서를 갖지 않은 표면의 대화 — board 를 안 싣는 제품이 물어볼 수 있는 자리. */\n AssistantChatResolver,\n /* 화면이 자기 대화가 무엇을 주는지 묻는 자리 — 문구는 서버가 요청 로케일로 옮겨 낸다. */\n AssistantModeResolver,\n /* 「이 배포에 AI 가 있나 · 무엇이 선언 없이 등록됐나」 — 창구 하나(표준 A7-a). */\n AssistantCapabilityResolver,\n /* A domain's AI agents - the settings card and the dock's picker (design/ai/agent-settings.md). */\n AiAgentResolver,\n ChatMessageSubscription,\n /* 「입력 중」 — 저장하지 않는 방의 한 줄. 듣는 사람은 메시지와 같은 방 판정을 지난다. */\n ChatTypingSubscription,\n /* 내가 참여한 모든 세션의 활동 — 창을 닫아 둔 표면이 배지·목록을 갱신할 수 있게. */\n MySessionActivitySubscription,\n /* 사람이 누른 입력 제안을 쓰는 자리 — 행마다 앱의 코드로, 그 사람의 권한으로. */\n EntryProposalResolver\n ]\n}\n"]}
@@ -77,7 +77,7 @@ export interface ConversationOptions {
77
77
  */
78
78
  participants?: ScriptParticipant[];
79
79
  /** 이 대화가 공개인가 — 기본은 공개(ADR-0073 판정 2). 'private' 이면 멘션이 사람을 앉히지 않는다. */
80
- visibility?: 'open' | 'private';
80
+ visibility?: 'open' | 'group' | 'private';
81
81
  /** Who the assistant is on this surface — in real use it comes from the registered mode. */
82
82
  systemPrompt?: string;
83
83
  /** Which registered categories this conversation may use. Omitted allows none. */
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-script.js","sourceRoot":"","sources":["../../../server/service/testing/conversation-script.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * A conversation with several people in it, written as a script, so a test can say what the\n * assistant should have heard and what it should have done.\n *\n * ── Why a harness (standard A15) ───────────────────────────────────────────────\n * A shared session carries two kinds of talk: people talking to each other, and people talking to\n * the assistant. Everything a person types is stored the same way, and the model reads all of it.\n * So the questions a test must be able to ask are:\n *\n * 누가 말했나 · 누구에게 한 말인가 · 그 턴에 모델을 불렀나 · 모델이 무엇을 읽었나 ·\n * 무엇을 쓸 수 있었나 · 답과 제안은 누구 것인가\n *\n * Those are hard to see from a resolver test, because the interesting part is what the model was\n * handed — and that is a string built from stored rows. The script keeps the rows, builds the same\n * history the door builds, and records every model call.\n *\n * ── What this does not do ──────────────────────────────────────────────────────\n * No database, no GraphQL, no provider. It drives the same two pieces the door drives\n * (`buildLlmHistory` and `registryAssistantChat`) with a recording client, so a failure names a\n * rule rather than a stack. Anything that only the resolver does — storing, broadcasting, session\n * activity — is out of scope here and belongs in a resolver test.\n */\nimport { registryAssistantChat } from '@things-factory/ai-client-base'\n\nimport { buildLlmHistory, type HistoryRow } from '../chat-message/llm-history.js'\nimport { formatProposalOutcomeLine, type ProposalOutcome } from '../chat-message/proposal-outcome.js'\nimport { roomMessageBody } from '../chat-message/room-message.js'\nimport { inviteMentionedUsers } from '../chat-session-participant/mention-invite.js'\n\n/** Who a turn was meant for. The room hears everything; only the assistant answers. */\nexport type Addressee = 'room' | 'assistant'\n\nexport interface ScriptedTurn {\n speaker: string\n addressee: Addressee\n content: string\n /** `mention` 이 부른 사람 — 본문에는 마커로 실린다. */\n mentions?: string[]\n /** 호스트가 카드에 답한 것 — 사람의 말이 아니라 기록이다. */\n outcome?: { proposalKey: string; outcome: ProposalOutcome }\n}\n\n/** One model call, exactly as the provider saw it. */\nexport interface RecordedCall {\n systemPrompt: string\n /** The conversation handed over, newest last. */\n messages: { role: string; content: any }[]\n /** Tool names offered this turn. */\n tools: string[]\n}\n\nexport interface ScriptResult {\n /** Every model call, in order. A room turn must not produce one. */\n calls: RecordedCall[]\n /** What the assistant answered, one per `ask`. */\n replies: string[]\n /** Proposals returned, paired with the turn that asked for them. */\n proposals: { askedBy: string; proposal: any }[]\n /** The rows as they stand, in the order they were said. */\n rows: HistoryRow[]\n /** Who is in the room now, and what each of them may do. */\n participants: ScriptParticipant[]\n /** What each `mention` did — who joined, or why nobody did. */\n invites: { by: string; invited: string[]; notSeated?: string[] }[]\n}\n\n/** A person in the room. `read` is how far they have read — the fact others see as a badge. */\nexport interface ScriptParticipant {\n userId: string\n role: 'owner' | 'member' | 'viewer'\n read?: number\n}\n\n/** A provider that answers with a fixed script and records what it was given. */\nexport interface RecordingModel {\n client: any\n calls: RecordedCall[]\n}\n\n/**\n * A model that answers each call with the next canned reply, and records what it saw.\n *\n * `toolCalls` lets a script make the model ask for a tool on a given turn — the harness then\n * exercises the dispatch path rather than only the prompt.\n */\nexport function recordingModel(\n replies: string[] = ['ok'],\n toolCalls: { name: string; arguments?: any }[][] = []\n): RecordingModel {\n const calls: RecordedCall[] = []\n let turn = 0\n let replyTurn = 0\n\n const client = {\n id: 'recording:test',\n async chatWithTools(messages: any[], tools: any[], options: any) {\n const index = turn++\n calls.push({\n systemPrompt: options?.systemPrompt ?? '',\n messages: messages.map(m => ({ role: m.role, content: m.content })),\n tools: (tools ?? []).map(t => t.name)\n })\n /* The loop asks again after a tool answers, so only the first call of a turn asks for tools. */\n const wanted = toolCalls[index] ?? []\n if (wanted.length > 0) {\n return {\n text: '',\n toolCalls: wanted.map((c, i) => ({ id: `call-${index}-${i}`, name: c.name, arguments: c.arguments ?? {} })),\n stopReason: 'tool_use'\n }\n }\n return { text: replies[Math.min(replyTurn++, replies.length - 1)] ?? 'ok', toolCalls: [], stopReason: 'end_turn' }\n }\n }\n\n return { client, calls }\n}\n\nexport interface ConversationOptions {\n /**\n * Who is in the room when the script starts, and as what.\n *\n * A conversation always has an owner — the person who started it — and `createAISession` puts\n * them there. Since ADR-0073 a mention grants nothing, so the roster no longer decides who may\n * name whom; it still decides who the room reaches and who is already seated.\n */\n participants?: ScriptParticipant[]\n /** 이 대화가 공개인가 — 기본은 공개(ADR-0073 판정 2). 'private' 이면 멘션이 사람을 앉히지 않는다. */\n visibility?: 'open' | 'private'\n /** Who the assistant is on this surface — in real use it comes from the registered mode. */\n systemPrompt?: string\n /** Which registered categories this conversation may use. Omitted allows none. */\n toolCategories?: string[]\n /**\n * Which doors this script's speaker may pass (ADR-0071 decision 2). A tool is offered when the\n * caller passes every door it names, so a script that says nothing here is written as someone who\n * passes them all — the permission axis belongs to `tool-access`, not to a conversation script.\n * Name the doors to write the other case: a person the tool is not offered to.\n */\n toolAccess?: (door: string) => Promise<boolean>\n /** Request state the tools receive (domain, user). */\n state?: any\n /** What the surface is looking at — reaches tools as `ctx.host`, never the model. */\n hostContext?: any\n /** Passed through to the runner; a live-state surface makes the model read first. */\n requireToolOnFirstTurn?: boolean\n}\n\n/**\n * Write the conversation, then run it.\n *\n * ```ts\n * const result = await conversation({ toolCategories: ['twin-ops'] })\n * .say('alice', '3 라인이 멈춘 것 같아요')\n * .say('bob', '아까부터 그랬어')\n * .ask('alice', '왜 멈췄는지 확인해줘')\n * .run(recordingModel(['확인했습니다']))\n * ```\n */\nexport function conversation(options: ConversationOptions = {}) {\n const turns: ScriptedTurn[] = []\n\n const api = {\n /** Someone speaks to the room. Nobody calls the assistant; it reads this next time it is asked. */\n say(speaker: string, content: string) {\n turns.push({ speaker, addressee: 'room', content })\n return api\n },\n /** Someone asks the assistant. This is the turn that makes a model call. */\n ask(speaker: string, content: string) {\n turns.push({ speaker, addressee: 'assistant', content })\n return api\n },\n /**\n * Someone asks the assistant **and names a colleague** — the way a second person joins.\n *\n * The marker is what the browser puts in the text when a name is picked from `@`, and the door\n * reads it from the stored turn. Only a person already in the room may bring another in.\n */\n mention(speaker: string, userIds: string[], content: string) {\n const marked = [...userIds.map(id => `@${id}{userId:${id}}`), content].join(' ')\n turns.push({ speaker, addressee: 'assistant', content: marked, mentions: userIds })\n return api\n },\n\n /** The assistant's own answer, when a script needs one already in the history. */\n answered(content: string) {\n turns.push({ speaker: 'assistant', addressee: 'room', content })\n return api\n },\n\n /**\n * The host says what it did with a proposal — the card's state and the model's next turn both\n * read this row (ADR-0070 A12-a).\n */\n outcome(proposalKey: string, outcome: ProposalOutcome) {\n turns.push({ speaker: 'system', addressee: 'room', content: '', outcome: { proposalKey, outcome } })\n return api\n },\n\n async run(model: RecordingModel = recordingModel()): Promise<ScriptResult> {\n const rows: HistoryRow[] = []\n const replies: string[] = []\n const proposals: { askedBy: string; proposal: any }[] = []\n const invites: { by: string; invited: string[]; notSeated?: string[] }[] = []\n\n /* 참여자 저장소 — 실제 문이 쓰는 것과 같은 규칙을 지나되, 기억에만 둔다. */\n const roster: any[] = (options.participants ?? [{ userId: 'alice', role: 'owner' }]).map((person, index) => ({\n id: `participant-${index + 1}`,\n userId: person.userId,\n role: person.role,\n read: person.read\n }))\n const participants = {\n find: async () => roster,\n save: async (row: any) => {\n roster.push({ id: `participant-${roster.length + 1}`, userId: row.user.id, role: row.role })\n },\n update: async (id: string, patch: any) => {\n const found = roster.find(row => row.id === id)\n if (found) Object.assign(found, patch)\n }\n }\n\n for (const turn of turns) {\n /* 호스트의 답은 사람의 말이 아니다 — 서버가 쓰는 `system` 행이다. */\n if (turn.outcome) {\n rows.push({\n id: `row-${rows.length + 1}`,\n role: 'system',\n content: formatProposalOutcomeLine(turn.outcome.proposalKey, turn.outcome.outcome)\n })\n continue\n }\n\n /* 빈 말은 저장되지 않는다 — 문이 지키는 규칙과 같은 것을 쓴다. */\n const body = roomMessageBody(turn.content)\n if (!body) continue\n\n rows.push({\n id: `row-${rows.length + 1}`,\n role: turn.speaker === 'assistant' ? 'assistant' : 'user',\n content: body,\n senderId: turn.speaker,\n senderName: turn.speaker,\n /* 사람의 말에만 대상이 있다 — 문이 찍는 값과 같은 자리다(ADR-0070 A15 결정 ①). */\n addressee: turn.speaker === 'assistant' ? undefined : turn.addressee\n })\n\n /* 멘션은 사람의 말을 저장하는 문에서만 사람을 불러들인다(ADR-0071 결정 1 ①). */\n if (turn.speaker !== 'assistant') {\n const invite = await inviteMentionedUsers({\n content: body,\n sessionId: 'script-session',\n domainId: 'script-domain',\n inviter: { id: turn.speaker },\n participants,\n visibility: options.visibility\n })\n if (invite.invited.length > 0 || invite.notSeated) {\n invites.push({ by: turn.speaker, invited: invite.invited, notSeated: invite.notSeated })\n }\n }\n\n /* Only a turn addressed to the assistant runs it. Room talk is context for the next one. */\n if (turn.addressee !== 'assistant') continue\n\n const result = await registryAssistantChat({\n client: model.client,\n messages: buildLlmHistory(rows, {}) as any,\n systemPrompt: options.systemPrompt ?? 'you are a test assistant',\n toolCategories: options.toolCategories,\n toolAccess: options.toolAccess ?? (async () => true),\n hostContext: options.hostContext,\n state: options.state ?? { domain: { id: 'dom-1' }, user: { id: 'u-1' } },\n requireToolOnFirstTurn: options.requireToolOnFirstTurn\n })\n\n replies.push(result.reply)\n rows.push({ id: `row-${rows.length + 1}`, role: 'assistant', content: result.reply })\n for (const proposal of result.loop.proposals ?? []) proposals.push({ askedBy: turn.speaker, proposal })\n }\n\n return {\n calls: model.calls,\n replies,\n proposals,\n rows,\n participants: roster.map(row => ({ userId: row.userId, role: row.role, read: row.read })),\n invites\n }\n }\n }\n\n return api\n}\n"]}
1
+ {"version":3,"file":"conversation-script.js","sourceRoot":"","sources":["../../../server/service/testing/conversation-script.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * A conversation with several people in it, written as a script, so a test can say what the\n * assistant should have heard and what it should have done.\n *\n * ── Why a harness (standard A15) ───────────────────────────────────────────────\n * A shared session carries two kinds of talk: people talking to each other, and people talking to\n * the assistant. Everything a person types is stored the same way, and the model reads all of it.\n * So the questions a test must be able to ask are:\n *\n * 누가 말했나 · 누구에게 한 말인가 · 그 턴에 모델을 불렀나 · 모델이 무엇을 읽었나 ·\n * 무엇을 쓸 수 있었나 · 답과 제안은 누구 것인가\n *\n * Those are hard to see from a resolver test, because the interesting part is what the model was\n * handed — and that is a string built from stored rows. The script keeps the rows, builds the same\n * history the door builds, and records every model call.\n *\n * ── What this does not do ──────────────────────────────────────────────────────\n * No database, no GraphQL, no provider. It drives the same two pieces the door drives\n * (`buildLlmHistory` and `registryAssistantChat`) with a recording client, so a failure names a\n * rule rather than a stack. Anything that only the resolver does — storing, broadcasting, session\n * activity — is out of scope here and belongs in a resolver test.\n */\nimport { registryAssistantChat } from '@things-factory/ai-client-base'\n\nimport { buildLlmHistory, type HistoryRow } from '../chat-message/llm-history.js'\nimport { formatProposalOutcomeLine, type ProposalOutcome } from '../chat-message/proposal-outcome.js'\nimport { roomMessageBody } from '../chat-message/room-message.js'\nimport { inviteMentionedUsers } from '../chat-session-participant/mention-invite.js'\n\n/** Who a turn was meant for. The room hears everything; only the assistant answers. */\nexport type Addressee = 'room' | 'assistant'\n\nexport interface ScriptedTurn {\n speaker: string\n addressee: Addressee\n content: string\n /** `mention` 이 부른 사람 — 본문에는 마커로 실린다. */\n mentions?: string[]\n /** 호스트가 카드에 답한 것 — 사람의 말이 아니라 기록이다. */\n outcome?: { proposalKey: string; outcome: ProposalOutcome }\n}\n\n/** One model call, exactly as the provider saw it. */\nexport interface RecordedCall {\n systemPrompt: string\n /** The conversation handed over, newest last. */\n messages: { role: string; content: any }[]\n /** Tool names offered this turn. */\n tools: string[]\n}\n\nexport interface ScriptResult {\n /** Every model call, in order. A room turn must not produce one. */\n calls: RecordedCall[]\n /** What the assistant answered, one per `ask`. */\n replies: string[]\n /** Proposals returned, paired with the turn that asked for them. */\n proposals: { askedBy: string; proposal: any }[]\n /** The rows as they stand, in the order they were said. */\n rows: HistoryRow[]\n /** Who is in the room now, and what each of them may do. */\n participants: ScriptParticipant[]\n /** What each `mention` did — who joined, or why nobody did. */\n invites: { by: string; invited: string[]; notSeated?: string[] }[]\n}\n\n/** A person in the room. `read` is how far they have read — the fact others see as a badge. */\nexport interface ScriptParticipant {\n userId: string\n role: 'owner' | 'member' | 'viewer'\n read?: number\n}\n\n/** A provider that answers with a fixed script and records what it was given. */\nexport interface RecordingModel {\n client: any\n calls: RecordedCall[]\n}\n\n/**\n * A model that answers each call with the next canned reply, and records what it saw.\n *\n * `toolCalls` lets a script make the model ask for a tool on a given turn — the harness then\n * exercises the dispatch path rather than only the prompt.\n */\nexport function recordingModel(\n replies: string[] = ['ok'],\n toolCalls: { name: string; arguments?: any }[][] = []\n): RecordingModel {\n const calls: RecordedCall[] = []\n let turn = 0\n let replyTurn = 0\n\n const client = {\n id: 'recording:test',\n async chatWithTools(messages: any[], tools: any[], options: any) {\n const index = turn++\n calls.push({\n systemPrompt: options?.systemPrompt ?? '',\n messages: messages.map(m => ({ role: m.role, content: m.content })),\n tools: (tools ?? []).map(t => t.name)\n })\n /* The loop asks again after a tool answers, so only the first call of a turn asks for tools. */\n const wanted = toolCalls[index] ?? []\n if (wanted.length > 0) {\n return {\n text: '',\n toolCalls: wanted.map((c, i) => ({ id: `call-${index}-${i}`, name: c.name, arguments: c.arguments ?? {} })),\n stopReason: 'tool_use'\n }\n }\n return { text: replies[Math.min(replyTurn++, replies.length - 1)] ?? 'ok', toolCalls: [], stopReason: 'end_turn' }\n }\n }\n\n return { client, calls }\n}\n\nexport interface ConversationOptions {\n /**\n * Who is in the room when the script starts, and as what.\n *\n * A conversation always has an owner — the person who started it — and `createAISession` puts\n * them there. Since ADR-0073 a mention grants nothing, so the roster no longer decides who may\n * name whom; it still decides who the room reaches and who is already seated.\n */\n participants?: ScriptParticipant[]\n /** 이 대화가 공개인가 — 기본은 공개(ADR-0073 판정 2). 'private' 이면 멘션이 사람을 앉히지 않는다. */\n visibility?: 'open' | 'group' | 'private'\n /** Who the assistant is on this surface — in real use it comes from the registered mode. */\n systemPrompt?: string\n /** Which registered categories this conversation may use. Omitted allows none. */\n toolCategories?: string[]\n /**\n * Which doors this script's speaker may pass (ADR-0071 decision 2). A tool is offered when the\n * caller passes every door it names, so a script that says nothing here is written as someone who\n * passes them all — the permission axis belongs to `tool-access`, not to a conversation script.\n * Name the doors to write the other case: a person the tool is not offered to.\n */\n toolAccess?: (door: string) => Promise<boolean>\n /** Request state the tools receive (domain, user). */\n state?: any\n /** What the surface is looking at — reaches tools as `ctx.host`, never the model. */\n hostContext?: any\n /** Passed through to the runner; a live-state surface makes the model read first. */\n requireToolOnFirstTurn?: boolean\n}\n\n/**\n * Write the conversation, then run it.\n *\n * ```ts\n * const result = await conversation({ toolCategories: ['twin-ops'] })\n * .say('alice', '3 라인이 멈춘 것 같아요')\n * .say('bob', '아까부터 그랬어')\n * .ask('alice', '왜 멈췄는지 확인해줘')\n * .run(recordingModel(['확인했습니다']))\n * ```\n */\nexport function conversation(options: ConversationOptions = {}) {\n const turns: ScriptedTurn[] = []\n\n const api = {\n /** Someone speaks to the room. Nobody calls the assistant; it reads this next time it is asked. */\n say(speaker: string, content: string) {\n turns.push({ speaker, addressee: 'room', content })\n return api\n },\n /** Someone asks the assistant. This is the turn that makes a model call. */\n ask(speaker: string, content: string) {\n turns.push({ speaker, addressee: 'assistant', content })\n return api\n },\n /**\n * Someone asks the assistant **and names a colleague** — the way a second person joins.\n *\n * The marker is what the browser puts in the text when a name is picked from `@`, and the door\n * reads it from the stored turn. Only a person already in the room may bring another in.\n */\n mention(speaker: string, userIds: string[], content: string) {\n const marked = [...userIds.map(id => `@${id}{userId:${id}}`), content].join(' ')\n turns.push({ speaker, addressee: 'assistant', content: marked, mentions: userIds })\n return api\n },\n\n /** The assistant's own answer, when a script needs one already in the history. */\n answered(content: string) {\n turns.push({ speaker: 'assistant', addressee: 'room', content })\n return api\n },\n\n /**\n * The host says what it did with a proposal — the card's state and the model's next turn both\n * read this row (ADR-0070 A12-a).\n */\n outcome(proposalKey: string, outcome: ProposalOutcome) {\n turns.push({ speaker: 'system', addressee: 'room', content: '', outcome: { proposalKey, outcome } })\n return api\n },\n\n async run(model: RecordingModel = recordingModel()): Promise<ScriptResult> {\n const rows: HistoryRow[] = []\n const replies: string[] = []\n const proposals: { askedBy: string; proposal: any }[] = []\n const invites: { by: string; invited: string[]; notSeated?: string[] }[] = []\n\n /* 참여자 저장소 — 실제 문이 쓰는 것과 같은 규칙을 지나되, 기억에만 둔다. */\n const roster: any[] = (options.participants ?? [{ userId: 'alice', role: 'owner' }]).map((person, index) => ({\n id: `participant-${index + 1}`,\n userId: person.userId,\n role: person.role,\n read: person.read\n }))\n const participants = {\n find: async () => roster,\n save: async (row: any) => {\n roster.push({ id: `participant-${roster.length + 1}`, userId: row.user.id, role: row.role })\n },\n update: async (id: string, patch: any) => {\n const found = roster.find(row => row.id === id)\n if (found) Object.assign(found, patch)\n }\n }\n\n for (const turn of turns) {\n /* 호스트의 답은 사람의 말이 아니다 — 서버가 쓰는 `system` 행이다. */\n if (turn.outcome) {\n rows.push({\n id: `row-${rows.length + 1}`,\n role: 'system',\n content: formatProposalOutcomeLine(turn.outcome.proposalKey, turn.outcome.outcome)\n })\n continue\n }\n\n /* 빈 말은 저장되지 않는다 — 문이 지키는 규칙과 같은 것을 쓴다. */\n const body = roomMessageBody(turn.content)\n if (!body) continue\n\n rows.push({\n id: `row-${rows.length + 1}`,\n role: turn.speaker === 'assistant' ? 'assistant' : 'user',\n content: body,\n senderId: turn.speaker,\n senderName: turn.speaker,\n /* 사람의 말에만 대상이 있다 — 문이 찍는 값과 같은 자리다(ADR-0070 A15 결정 ①). */\n addressee: turn.speaker === 'assistant' ? undefined : turn.addressee\n })\n\n /* 멘션은 사람의 말을 저장하는 문에서만 사람을 불러들인다(ADR-0071 결정 1 ①). */\n if (turn.speaker !== 'assistant') {\n const invite = await inviteMentionedUsers({\n content: body,\n sessionId: 'script-session',\n domainId: 'script-domain',\n inviter: { id: turn.speaker },\n participants,\n visibility: options.visibility\n })\n if (invite.invited.length > 0 || invite.notSeated) {\n invites.push({ by: turn.speaker, invited: invite.invited, notSeated: invite.notSeated })\n }\n }\n\n /* Only a turn addressed to the assistant runs it. Room talk is context for the next one. */\n if (turn.addressee !== 'assistant') continue\n\n const result = await registryAssistantChat({\n client: model.client,\n messages: buildLlmHistory(rows, {}) as any,\n systemPrompt: options.systemPrompt ?? 'you are a test assistant',\n toolCategories: options.toolCategories,\n toolAccess: options.toolAccess ?? (async () => true),\n hostContext: options.hostContext,\n state: options.state ?? { domain: { id: 'dom-1' }, user: { id: 'u-1' } },\n requireToolOnFirstTurn: options.requireToolOnFirstTurn\n })\n\n replies.push(result.reply)\n rows.push({ id: `row-${rows.length + 1}`, role: 'assistant', content: result.reply })\n for (const proposal of result.loop.proposals ?? []) proposals.push({ askedBy: turn.speaker, proposal })\n }\n\n return {\n calls: model.calls,\n replies,\n proposals,\n rows,\n participants: roster.map(row => ({ userId: row.userId, role: row.role, read: row.read })),\n invites\n }\n }\n }\n\n return api\n}\n"]}
@@ -127,6 +127,10 @@ function modeCarriesSharedRules(kind) {
127
127
  found.push('absence-rule-copy');
128
128
  if (/^\s*Next:/im.test(prompt) || /end the reply with one last line/i.test(prompt))
129
129
  found.push('next-request-rule-copy');
130
+ if (/finish what was asked in this turn|never ask whether to propose/i.test(prompt))
131
+ found.push('finish-rule-copy');
132
+ if (/\[writer → addressee\]/.test(prompt))
133
+ found.push('speaker-rule-copy');
130
134
  return found;
131
135
  }
132
136
  /** Subjects that more than one conversation names - a screen has one (design/ai/screen-pattern.md §3). */
@@ -1 +1 @@
1
- {"version":3,"file":"mode-conformance.js","sourceRoot":"","sources":["../../../server/service/testing/mode-conformance.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 등록된 모드가 **선언한 대로 도는가** — 글자가 아니라 문을 지나서 본다.\n *\n * 모드 선언은 소스에 적힌 값이고, 사람이 만나는 것은 그 값이 문을 지나 만들어 낸 것이다. 그 둘이\n * 어긋나는 자리가 오늘 하루에만 여럿 나왔다: 범주에 없는 도구를 프롬프트가 부르라고 했고, 키는 있는데\n * 번역이 없었고, 부품에 규칙이 있는데 부르는 쪽이 없었다. 전부 「선언은 맞는데 돌지 않는」 모양이다.\n *\n * 그래서 앱의 시험이 이것으로 **자기 모드를 실제로 돌려 본다**:\n * · 그 이름으로 모드가 서는가\n * · 그 모드가 여는 도구가 무엇인가 — 모델에게 실제로 건네지는 목록\n * · 첫 턴 도구 호출을 강제하는가\n * · 화면에 낼 말이 **키가 아니라 문장**으로 나오는가\n * · 슬래시가 가리키는 도구가 그 모드 안에 있는가\n */\nimport { openSubjectLine, registryAssistantChat } from '@things-factory/ai-client-base'\n\nimport { getAssistantMode, getAssistantModeWarnings } from '../assistant-mode/registry.js'\nimport { assistantModeView, resolveModeSettings, type Translate } from '../assistant-mode/resolve.js'\nimport { modeFrontCategories, type TurnScreen } from '../assistant-mode/mode-categories.js'\n\nexport interface ModeRunResult {\n /** 이 모드가 모델에게 실제로 건넨 도구 이름. */\n offeredTools: string[]\n /** 건네지 않았지만 모델이 openTools 로 꺼낼 수 있던 도구 — 둘을 합치면 이 모드로 쓸 수 있는 전부(ADR-0061 결정 9). */\n openableTools: string[]\n /** 모델이 받은 시스템 프롬프트 — 서버의 것인지 확인할 수 있다. */\n systemPrompt: string\n /** 첫 턴에 도구를 강제했는가. */\n requiredToolOnFirstTurn: boolean\n reply: string\n}\n\n/** 시험용 번역기 — 키를 그대로 돌려준다. 문장이 나와야 할 자리에 키가 나오면 눈에 띈다. */\nexport const keyEchoTranslate: Translate = key => key\n\n/**\n * 등록된 모드로 한 턴을 돌린다 — 프롬프트도 도구도 **등록에서** 나온다.\n *\n * 시험이 자기 값을 넘기면 그 시험은 선언을 안 본 것이다. 그래서 여기서는 `kind` 만 받는다.\n */\nexport async function runRegisteredMode(\n kind: string,\n options: { client: any; hostContext?: any; state?: any; toolAccess?: any; reply?: string; screen?: TurnScreen } = {} as any\n): Promise<ModeRunResult> {\n const settings = resolveModeSettings({ kind }, keyEchoTranslate)\n let requiredToolOnFirstTurn = false\n\n const result = await registryAssistantChat({\n client: options.client,\n messages: [{ role: 'user', content: '한 턴' }] as any,\n systemPrompt: settings.systemPrompt,\n toolCategories: settings.toolCategories,\n /* What the door puts in front on that screen (ADR-0061 decision 9) - a mode too large for one call is offered as a person meets it. */\n front: { categories: modeFrontCategories(getAssistantMode(kind) ?? {}, options.screen ?? {}) },\n requireToolOnFirstTurn: (requiredToolOnFirstTurn = Boolean(settings.requireToolOnFirstTurn)),\n temperature: settings.temperature,\n maxTokens: settings.maxTokens,\n hostContext: options.hostContext,\n /*\n * This harness asks about **the mode** — \"what does this mode open\", not \"does this person pass the\n * doors\". Since the gate moved onto `doors` (ADR-0071 decision 2), passing no check closes every tool\n * that names a door, and an app's conformance test would quietly lose **exactly the tools that read\n * real data**. So the default is someone who passes every door; an app that wants the other case\n * writes `toolAccess`. The permission axis has its own tests (`tool-access.test.ts` ·\n * `tool-scope.test.ts`).\n */\n toolAccess: options.toolAccess ?? (async () => true),\n state: options.state ?? { domain: { id: 'dom-1' }, user: { id: 'u-1' } }\n })\n\n return {\n offeredTools: [...result.offeredTools],\n openableTools: [...result.openableTools],\n systemPrompt: settings.systemPrompt,\n requiredToolOnFirstTurn,\n reply: result.reply\n }\n}\n\nexport interface ModeWording {\n placeholder?: string\n /** 빈 대화의 제목과 한 줄 — 없으면 공용 대화창이 보드 제품의 글을 낸다. */\n intro?: { title: string; subtitle: string }\n /** 빈 대화의 예시 — 이 대화가 실제로 할 수 있는 말. */\n examples?: Array<{ label: string; items: string[] }>\n slash: { name: string; description: string; template: string }[]\n footerNotice?: string\n stagedNotice?: string\n}\n\n/** 화면에 낼 말 — 주어진 사전으로 옮긴 결과. 키가 그대로 남으면 사전에 없는 것이다. */\nexport function modeWording(kind: string, dictionary: Record<string, string>): ModeWording {\n const view = assistantModeView(kind, key => dictionary[key] ?? key)\n return {\n placeholder: view.placeholder,\n intro: view.intro,\n examples: view.examples,\n slash: view.slashTemplates,\n footerNotice: view.footerNotice,\n stagedNotice: view.stagedNotice\n }\n}\n\n/** 사전에 없어서 키가 그대로 화면에 나갈 말들. 비어 있어야 한다. */\nexport function missingWording(kind: string, dictionary: Record<string, string>): string[] {\n const missing: string[] = []\n const spec = getAssistantMode(kind)\n if (!spec) return [`mode:${kind}`]\n\n for (const key of [spec.placeholderKey, spec.footerNoticeKey, spec.stagedNoticeKey]) {\n if (key && !(key in dictionary)) missing.push(key)\n }\n /* 빈 대화의 말도 사전에 있어야 한다 — 없으면 화면에 키가 그대로 찍힌다. */\n for (const key of [spec.introKeys?.title, spec.introKeys?.subtitle]) {\n if (key && !(key in dictionary)) missing.push(key)\n }\n for (const group of spec.exampleKeys ?? []) {\n for (const key of [group.labelKey, ...group.itemKeys]) {\n if (!(key in dictionary)) missing.push(key)\n }\n }\n for (const slash of spec.slashTemplates ?? []) {\n for (const key of [slash.descriptionKey, slash.templateKey]) {\n if (!(key in dictionary)) missing.push(key)\n }\n }\n return missing\n}\n\n/** 이 모드가 자기 도구로 못 하는 일을 제안하는가 — 부팅 보고와 같은 답. */\nexport function modePromisesItCannotKeep(kind: string): string[] {\n return getAssistantModeWarnings()\n .filter(warning => warning.params.kind === kind)\n .map(warning => `${warning.code}:${warning.params.slash}→${warning.params.tool}`)\n}\n\n/**\n * What an app's prompt carries that every conversation already gets (design/ai/screen-pattern.md §6 · §7).\n *\n * The absence rule is added by the door to every conversation. A copy in an app's prompt says it twice, and the\n * copies drift - five apps had five spellings of it.\n */\nexport function modeCarriesSharedRules(kind: string): string[] {\n const prompt = String(getAssistantMode(kind)?.systemPrompt ?? '')\n const found: string[] = []\n if (/name our plumbing|do not describe our machinery/i.test(prompt)) found.push('absence-rule-copy')\n if (/^\\s*Next:/im.test(prompt) || /end the reply with one last line/i.test(prompt)) found.push('next-request-rule-copy')\n return found\n}\n\n/** Subjects that more than one conversation names - a screen has one (design/ai/screen-pattern.md §3). */\nexport function subjectsClaimedTwice(): string[] {\n return getAssistantModeWarnings()\n .filter(warning => warning.code === 'assistant-mode-subject-claimed-twice')\n .map(warning => String(warning.params.subject))\n}\n\n/** The line the model reads for an open subject - an app's test checks its describer names its own record. */\nexport function openSubjectLineFor(anchorType: string, anchorId: string, domainId: string, state?: any): Promise<string | undefined> {\n return openSubjectLine({ anchorType, anchorId, domain: { id: domainId }, state })\n}\n"]}
1
+ {"version":3,"file":"mode-conformance.js","sourceRoot":"","sources":["../../../server/service/testing/mode-conformance.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 등록된 모드가 **선언한 대로 도는가** — 글자가 아니라 문을 지나서 본다.\n *\n * 모드 선언은 소스에 적힌 값이고, 사람이 만나는 것은 그 값이 문을 지나 만들어 낸 것이다. 그 둘이\n * 어긋나는 자리가 오늘 하루에만 여럿 나왔다: 범주에 없는 도구를 프롬프트가 부르라고 했고, 키는 있는데\n * 번역이 없었고, 부품에 규칙이 있는데 부르는 쪽이 없었다. 전부 「선언은 맞는데 돌지 않는」 모양이다.\n *\n * 그래서 앱의 시험이 이것으로 **자기 모드를 실제로 돌려 본다**:\n * · 그 이름으로 모드가 서는가\n * · 그 모드가 여는 도구가 무엇인가 — 모델에게 실제로 건네지는 목록\n * · 첫 턴 도구 호출을 강제하는가\n * · 화면에 낼 말이 **키가 아니라 문장**으로 나오는가\n * · 슬래시가 가리키는 도구가 그 모드 안에 있는가\n */\nimport { openSubjectLine, registryAssistantChat } from '@things-factory/ai-client-base'\n\nimport { getAssistantMode, getAssistantModeWarnings } from '../assistant-mode/registry.js'\nimport { assistantModeView, resolveModeSettings, type Translate } from '../assistant-mode/resolve.js'\nimport { modeFrontCategories, type TurnScreen } from '../assistant-mode/mode-categories.js'\n\nexport interface ModeRunResult {\n /** 이 모드가 모델에게 실제로 건넨 도구 이름. */\n offeredTools: string[]\n /** 건네지 않았지만 모델이 openTools 로 꺼낼 수 있던 도구 — 둘을 합치면 이 모드로 쓸 수 있는 전부(ADR-0061 결정 9). */\n openableTools: string[]\n /** 모델이 받은 시스템 프롬프트 — 서버의 것인지 확인할 수 있다. */\n systemPrompt: string\n /** 첫 턴에 도구를 강제했는가. */\n requiredToolOnFirstTurn: boolean\n reply: string\n}\n\n/** 시험용 번역기 — 키를 그대로 돌려준다. 문장이 나와야 할 자리에 키가 나오면 눈에 띈다. */\nexport const keyEchoTranslate: Translate = key => key\n\n/**\n * 등록된 모드로 한 턴을 돌린다 — 프롬프트도 도구도 **등록에서** 나온다.\n *\n * 시험이 자기 값을 넘기면 그 시험은 선언을 안 본 것이다. 그래서 여기서는 `kind` 만 받는다.\n */\nexport async function runRegisteredMode(\n kind: string,\n options: { client: any; hostContext?: any; state?: any; toolAccess?: any; reply?: string; screen?: TurnScreen } = {} as any\n): Promise<ModeRunResult> {\n const settings = resolveModeSettings({ kind }, keyEchoTranslate)\n let requiredToolOnFirstTurn = false\n\n const result = await registryAssistantChat({\n client: options.client,\n messages: [{ role: 'user', content: '한 턴' }] as any,\n systemPrompt: settings.systemPrompt,\n toolCategories: settings.toolCategories,\n /* What the door puts in front on that screen (ADR-0061 decision 9) - a mode too large for one call is offered as a person meets it. */\n front: { categories: modeFrontCategories(getAssistantMode(kind) ?? {}, options.screen ?? {}) },\n requireToolOnFirstTurn: (requiredToolOnFirstTurn = Boolean(settings.requireToolOnFirstTurn)),\n temperature: settings.temperature,\n maxTokens: settings.maxTokens,\n hostContext: options.hostContext,\n /*\n * This harness asks about **the mode** — \"what does this mode open\", not \"does this person pass the\n * doors\". Since the gate moved onto `doors` (ADR-0071 decision 2), passing no check closes every tool\n * that names a door, and an app's conformance test would quietly lose **exactly the tools that read\n * real data**. So the default is someone who passes every door; an app that wants the other case\n * writes `toolAccess`. The permission axis has its own tests (`tool-access.test.ts` ·\n * `tool-scope.test.ts`).\n */\n toolAccess: options.toolAccess ?? (async () => true),\n state: options.state ?? { domain: { id: 'dom-1' }, user: { id: 'u-1' } }\n })\n\n return {\n offeredTools: [...result.offeredTools],\n openableTools: [...result.openableTools],\n systemPrompt: settings.systemPrompt,\n requiredToolOnFirstTurn,\n reply: result.reply\n }\n}\n\nexport interface ModeWording {\n placeholder?: string\n /** 빈 대화의 제목과 한 줄 — 없으면 공용 대화창이 보드 제품의 글을 낸다. */\n intro?: { title: string; subtitle: string }\n /** 빈 대화의 예시 — 이 대화가 실제로 할 수 있는 말. */\n examples?: Array<{ label: string; items: string[] }>\n slash: { name: string; description: string; template: string }[]\n footerNotice?: string\n stagedNotice?: string\n}\n\n/** 화면에 낼 말 — 주어진 사전으로 옮긴 결과. 키가 그대로 남으면 사전에 없는 것이다. */\nexport function modeWording(kind: string, dictionary: Record<string, string>): ModeWording {\n const view = assistantModeView(kind, key => dictionary[key] ?? key)\n return {\n placeholder: view.placeholder,\n intro: view.intro,\n examples: view.examples,\n slash: view.slashTemplates,\n footerNotice: view.footerNotice,\n stagedNotice: view.stagedNotice\n }\n}\n\n/** 사전에 없어서 키가 그대로 화면에 나갈 말들. 비어 있어야 한다. */\nexport function missingWording(kind: string, dictionary: Record<string, string>): string[] {\n const missing: string[] = []\n const spec = getAssistantMode(kind)\n if (!spec) return [`mode:${kind}`]\n\n for (const key of [spec.placeholderKey, spec.footerNoticeKey, spec.stagedNoticeKey]) {\n if (key && !(key in dictionary)) missing.push(key)\n }\n /* 빈 대화의 말도 사전에 있어야 한다 — 없으면 화면에 키가 그대로 찍힌다. */\n for (const key of [spec.introKeys?.title, spec.introKeys?.subtitle]) {\n if (key && !(key in dictionary)) missing.push(key)\n }\n for (const group of spec.exampleKeys ?? []) {\n for (const key of [group.labelKey, ...group.itemKeys]) {\n if (!(key in dictionary)) missing.push(key)\n }\n }\n for (const slash of spec.slashTemplates ?? []) {\n for (const key of [slash.descriptionKey, slash.templateKey]) {\n if (!(key in dictionary)) missing.push(key)\n }\n }\n return missing\n}\n\n/** 이 모드가 자기 도구로 못 하는 일을 제안하는가 — 부팅 보고와 같은 답. */\nexport function modePromisesItCannotKeep(kind: string): string[] {\n return getAssistantModeWarnings()\n .filter(warning => warning.params.kind === kind)\n .map(warning => `${warning.code}:${warning.params.slash}→${warning.params.tool}`)\n}\n\n/**\n * What an app's prompt carries that every conversation already gets (design/ai/screen-pattern.md §6 · §7).\n *\n * The absence rule is added by the door to every conversation. A copy in an app's prompt says it twice, and the\n * copies drift - five apps had five spellings of it.\n */\nexport function modeCarriesSharedRules(kind: string): string[] {\n const prompt = String(getAssistantMode(kind)?.systemPrompt ?? '')\n const found: string[] = []\n if (/name our plumbing|do not describe our machinery/i.test(prompt)) found.push('absence-rule-copy')\n if (/^\\s*Next:/im.test(prompt) || /end the reply with one last line/i.test(prompt)) found.push('next-request-rule-copy')\n if (/finish what was asked in this turn|never ask whether to propose/i.test(prompt)) found.push('finish-rule-copy')\n if (/\\[writer → addressee\\]/.test(prompt)) found.push('speaker-rule-copy')\n return found\n}\n\n/** Subjects that more than one conversation names - a screen has one (design/ai/screen-pattern.md §3). */\nexport function subjectsClaimedTwice(): string[] {\n return getAssistantModeWarnings()\n .filter(warning => warning.code === 'assistant-mode-subject-claimed-twice')\n .map(warning => String(warning.params.subject))\n}\n\n/** The line the model reads for an open subject - an app's test checks its describer names its own record. */\nexport function openSubjectLineFor(anchorType: string, anchorId: string, domainId: string, state?: any): Promise<string | undefined> {\n return openSubjectLine({ anchorType, anchorId, domain: { id: domainId }, state })\n}\n"]}