@things-factory/ai-assistant 10.1.24 → 10.1.26

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 (48) hide show
  1. package/client/components/assistant-chat.ts +90 -18
  2. package/client/components/assistant-mode.test.ts +97 -0
  3. package/client/components/assistant-mode.ts +130 -0
  4. package/client/index.ts +1 -1
  5. package/dist-client/components/assistant-chat.d.ts +15 -4
  6. package/dist-client/components/assistant-chat.js +73 -18
  7. package/dist-client/components/assistant-chat.js.map +1 -1
  8. package/dist-client/components/assistant-mode.d.ts +97 -0
  9. package/dist-client/components/assistant-mode.js +36 -0
  10. package/dist-client/components/assistant-mode.js.map +1 -0
  11. package/dist-client/components/assistant-mode.test.d.ts +1 -0
  12. package/dist-client/components/assistant-mode.test.js +85 -0
  13. package/dist-client/components/assistant-mode.test.js.map +1 -0
  14. package/dist-client/index.d.ts +1 -1
  15. package/dist-client/index.js +1 -1
  16. package/dist-client/index.js.map +1 -1
  17. package/dist-client/tsconfig.tsbuildinfo +1 -1
  18. package/dist-server/index.d.ts +1 -0
  19. package/dist-server/index.js +1 -0
  20. package/dist-server/index.js.map +1 -1
  21. package/dist-server/service/assistant-chat-resolver.js +3 -1
  22. package/dist-server/service/assistant-chat-resolver.js.map +1 -1
  23. package/dist-server/service/index.d.ts +1 -0
  24. package/dist-server/service/index.js +1 -0
  25. package/dist-server/service/index.js.map +1 -1
  26. package/dist-server/service/tool-access.d.ts +4 -0
  27. package/dist-server/service/tool-access.js +23 -0
  28. package/dist-server/service/tool-access.js.map +1 -0
  29. package/dist-server/tool-registry-boot-report.d.ts +3 -0
  30. package/dist-server/tool-registry-boot-report.js +29 -0
  31. package/dist-server/tool-registry-boot-report.js.map +1 -0
  32. package/dist-server/tsconfig.tsbuildinfo +1 -1
  33. package/docs/session-controller.md +1 -1
  34. package/package.json +5 -5
  35. package/server/index.ts +1 -0
  36. package/server/service/assistant-chat-resolver.ts +3 -1
  37. package/server/service/index.ts +1 -0
  38. package/server/service/tool-access.test.ts +48 -0
  39. package/server/service/tool-access.ts +27 -0
  40. package/server/tool-registry-boot-report.test.ts +18 -0
  41. package/server/tool-registry-boot-report.ts +26 -0
  42. package/test/assistant-request-context.test.ts +2 -2
  43. package/test/assistant-session-controller.test.ts +0 -1
  44. package/test/assistant-session-toolbar.test.ts +2 -2
  45. package/client/utils/board-edit-patch.ts +0 -374
  46. package/dist-client/utils/board-edit-patch.d.ts +0 -157
  47. package/dist-client/utils/board-edit-patch.js +0 -284
  48. package/dist-client/utils/board-edit-patch.js.map +0 -1
@@ -0,0 +1,97 @@
1
+ /**
2
+ * A mode is what a conversation *is*: who the assistant is, what it can do, what the talk is
3
+ * about, what it offers to start with, and when it is over.
4
+ *
5
+ * Those five used to be four separate properties on the chat element plus one global registry,
6
+ * and every host wired them one at a time. Separate means they can disagree, and they did: a
7
+ * screen whose `systemPrompt` said "you do not edit boards here" offered `/add-monitor` in the
8
+ * same panel, because the prompt was a property and the slash list was global.
9
+ *
10
+ * So a host declares one value and hands it over. What cannot be split cannot fall out of step.
11
+ *
12
+ * ```ts
13
+ * const SPACE_MODE = (spaceId: string): AssistantMode => ({
14
+ * id: `twin:space:${spaceId}`,
15
+ * systemPrompt: '...',
16
+ * toolCategories: ['twin'],
17
+ * hostContext: { spaceId },
18
+ * slashTemplates: TWIN_SLASH_TEMPLATES
19
+ * })
20
+ *
21
+ * html`<ox-assistant-chat .mode=${SPACE_MODE(this.spaceId)}></ox-assistant-chat>`
22
+ * ```
23
+ */
24
+ export interface AssistantMode {
25
+ /**
26
+ * What this conversation is about, as a string the host can compare.
27
+ *
28
+ * **The conversation's lifetime hangs on this.** When it changes, the chat closes what is on
29
+ * screen and starts again — because a conversation about station 3 is not a conversation
30
+ * about station 7, and leaving the old one up means the user reads an answer about a screen
31
+ * they are no longer looking at.
32
+ *
33
+ * Hosts built this by hand before, each in their own way: plant kept a module-global registry
34
+ * with subscribers and re-checked the context on every send, twin closed its session when the
35
+ * anchor moved. Both were writing the same guard around the same absence.
36
+ *
37
+ * Include whatever makes the subject specific — `twin:space:S-12`, `plant:station:3`.
38
+ */
39
+ id: string;
40
+ /** Who the assistant is here, and what it must not claim to do. */
41
+ systemPrompt?: string;
42
+ /** Which tool categories the server may expose for this conversation. */
43
+ toolCategories?: string[];
44
+ /** What the host knows about the subject; travels with each request. */
45
+ hostContext?: any;
46
+ /** Which server-side chat entry point answers. */
47
+ chatEndpoint?: 'boardAIChat' | 'assistantChat';
48
+ /** What `@` offers. */
49
+ catalogEntries?: any[];
50
+ /**
51
+ * What `/` offers.
52
+ *
53
+ * These are sentences a person could have typed, not tool names — "이 공간의 지금 상태를
54
+ * 요약해줘" cannot be generated from `summarizeStatus`. But **a mode must not offer work it
55
+ * cannot do**: everything here should be answerable by the tools `toolCategories` opens.
56
+ */
57
+ slashTemplates?: any[];
58
+ /** The i18n key for the line under an empty conversation. */
59
+ footerNoticeKey?: string;
60
+ /** Follow-up chips for a proposal that arrived without its own. */
61
+ proposalChoices?: any[];
62
+ /** The i18n key for the line shown once the host reports a proposal staged. */
63
+ stagedNoticeKey?: string;
64
+ }
65
+ /** True when these two describe different conversations, so the old one should be closed. */
66
+ export declare function isDifferentConversation(a?: AssistantMode, b?: AssistantMode): boolean;
67
+ /** Everything a conversation needs, after the mode, the host's own properties and the
68
+ * registered defaults have been reconciled. */
69
+ export interface ConversationSettings {
70
+ systemPrompt?: string;
71
+ toolCategories?: string[];
72
+ hostContext?: any;
73
+ chatEndpoint: 'boardAIChat' | 'assistantChat';
74
+ catalogEntries: any[];
75
+ slashTemplates: any[];
76
+ footerNoticeKey?: string;
77
+ proposalChoices: any[];
78
+ stagedNoticeKey?: string;
79
+ }
80
+ /**
81
+ * Reconcile the places a setting can come from.
82
+ *
83
+ * **A host that declares a mode gets the mode and nothing else.** A field the mode leaves out is
84
+ * empty: it is not filled from the element's own properties, and it is not filled from the
85
+ * defaults another package registered. Filling it back in is how the defect this type removes
86
+ * returned — board-ai registers board authoring's `/add-monitor`, its component `@` catalog and
87
+ * its "previewed on the board" line globally, and plant's factory mode, which declares no slash
88
+ * list, offered them on a factory screen once the board modeller had been loaded. A mode that
89
+ * wants those declares them.
90
+ *
91
+ * A host that declares no mode gets exactly what it got before modes existed — its own
92
+ * properties, falling back to the registered defaults.
93
+ *
94
+ * `chatEndpoint` still falls back to `boardAIChat` when nothing names one; that default is a
95
+ * separate question (standard A2).
96
+ */
97
+ export declare function resolveConversation(mode: AssistantMode | undefined, own: Partial<ConversationSettings>, defaults: Partial<ConversationSettings>): ConversationSettings;
@@ -0,0 +1,36 @@
1
+ /** True when these two describe different conversations, so the old one should be closed. */
2
+ export function isDifferentConversation(a, b) {
3
+ return (a?.id ?? '') !== (b?.id ?? '');
4
+ }
5
+ /**
6
+ * Reconcile the places a setting can come from.
7
+ *
8
+ * **A host that declares a mode gets the mode and nothing else.** A field the mode leaves out is
9
+ * empty: it is not filled from the element's own properties, and it is not filled from the
10
+ * defaults another package registered. Filling it back in is how the defect this type removes
11
+ * returned — board-ai registers board authoring's `/add-monitor`, its component `@` catalog and
12
+ * its "previewed on the board" line globally, and plant's factory mode, which declares no slash
13
+ * list, offered them on a factory screen once the board modeller had been loaded. A mode that
14
+ * wants those declares them.
15
+ *
16
+ * A host that declares no mode gets exactly what it got before modes existed — its own
17
+ * properties, falling back to the registered defaults.
18
+ *
19
+ * `chatEndpoint` still falls back to `boardAIChat` when nothing names one; that default is a
20
+ * separate question (standard A2).
21
+ */
22
+ export function resolveConversation(mode, own, defaults) {
23
+ const pick = (key) => mode ? mode[key] : (own[key] ?? defaults[key]);
24
+ return {
25
+ systemPrompt: pick('systemPrompt'),
26
+ toolCategories: pick('toolCategories'),
27
+ hostContext: pick('hostContext'),
28
+ chatEndpoint: pick('chatEndpoint') ?? 'boardAIChat',
29
+ catalogEntries: pick('catalogEntries') ?? [],
30
+ slashTemplates: pick('slashTemplates') ?? [],
31
+ footerNoticeKey: pick('footerNoticeKey'),
32
+ proposalChoices: pick('proposalChoices') ?? [],
33
+ stagedNoticeKey: pick('stagedNoticeKey')
34
+ };
35
+ }
36
+ //# sourceMappingURL=assistant-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-mode.js","sourceRoot":"","sources":["../../client/components/assistant-mode.ts"],"names":[],"mappings":"AA0EA,6FAA6F;AAC7F,MAAM,UAAU,uBAAuB,CAAC,CAAiB,EAAE,CAAiB;IAC1E,OAAO,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;AACxC,CAAC;AAgBD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAA+B,EAC/B,GAAkC,EAClC,QAAuC;IAEvC,MAAM,IAAI,GAAG,CAAuC,GAAM,EAA2B,EAAE,CACrF,IAAI,CAAC,CAAC,CAAE,IAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAE,GAAW,CAAC,GAAG,CAAC,IAAK,QAAgB,CAAC,GAAG,CAAC,CAAC,CAAA;IAE3E,OAAO;QACL,YAAY,EAAE,IAAI,CAAC,cAAc,CAAC;QAClC,cAAc,EAAE,IAAI,CAAC,gBAAgB,CAAC;QACtC,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC;QAChC,YAAY,EAAE,IAAI,CAAC,cAAc,CAAC,IAAI,aAAa;QACnD,cAAc,EAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE;QAC5C,cAAc,EAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE;QAC5C,eAAe,EAAE,IAAI,CAAC,iBAAiB,CAAC;QACxC,eAAe,EAAE,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE;QAC9C,eAAe,EAAE,IAAI,CAAC,iBAAiB,CAAC;KACzC,CAAA;AACH,CAAC","sourcesContent":["/**\n * A mode is what a conversation *is*: who the assistant is, what it can do, what the talk is\n * about, what it offers to start with, and when it is over.\n *\n * Those five used to be four separate properties on the chat element plus one global registry,\n * and every host wired them one at a time. Separate means they can disagree, and they did: a\n * screen whose `systemPrompt` said \"you do not edit boards here\" offered `/add-monitor` in the\n * same panel, because the prompt was a property and the slash list was global.\n *\n * So a host declares one value and hands it over. What cannot be split cannot fall out of step.\n *\n * ```ts\n * const SPACE_MODE = (spaceId: string): AssistantMode => ({\n * id: `twin:space:${spaceId}`,\n * systemPrompt: '...',\n * toolCategories: ['twin'],\n * hostContext: { spaceId },\n * slashTemplates: TWIN_SLASH_TEMPLATES\n * })\n *\n * html`<ox-assistant-chat .mode=${SPACE_MODE(this.spaceId)}></ox-assistant-chat>`\n * ```\n */\nexport interface AssistantMode {\n /**\n * What this conversation is about, as a string the host can compare.\n *\n * **The conversation's lifetime hangs on this.** When it changes, the chat closes what is on\n * screen and starts again — because a conversation about station 3 is not a conversation\n * about station 7, and leaving the old one up means the user reads an answer about a screen\n * they are no longer looking at.\n *\n * Hosts built this by hand before, each in their own way: plant kept a module-global registry\n * with subscribers and re-checked the context on every send, twin closed its session when the\n * anchor moved. Both were writing the same guard around the same absence.\n *\n * Include whatever makes the subject specific — `twin:space:S-12`, `plant:station:3`.\n */\n id: string\n\n /** Who the assistant is here, and what it must not claim to do. */\n systemPrompt?: string\n\n /** Which tool categories the server may expose for this conversation. */\n toolCategories?: string[]\n\n /** What the host knows about the subject; travels with each request. */\n hostContext?: any\n\n /** Which server-side chat entry point answers. */\n chatEndpoint?: 'boardAIChat' | 'assistantChat'\n\n /** What `@` offers. */\n catalogEntries?: any[]\n\n /**\n * What `/` offers.\n *\n * These are sentences a person could have typed, not tool names — \"이 공간의 지금 상태를\n * 요약해줘\" cannot be generated from `summarizeStatus`. But **a mode must not offer work it\n * cannot do**: everything here should be answerable by the tools `toolCategories` opens.\n */\n slashTemplates?: any[]\n\n /** The i18n key for the line under an empty conversation. */\n footerNoticeKey?: string\n\n /** Follow-up chips for a proposal that arrived without its own. */\n proposalChoices?: any[]\n\n /** The i18n key for the line shown once the host reports a proposal staged. */\n stagedNoticeKey?: string\n}\n\n/** True when these two describe different conversations, so the old one should be closed. */\nexport function isDifferentConversation(a?: AssistantMode, b?: AssistantMode): boolean {\n return (a?.id ?? '') !== (b?.id ?? '')\n}\n\n/** Everything a conversation needs, after the mode, the host's own properties and the\n * registered defaults have been reconciled. */\nexport interface ConversationSettings {\n systemPrompt?: string\n toolCategories?: string[]\n hostContext?: any\n chatEndpoint: 'boardAIChat' | 'assistantChat'\n catalogEntries: any[]\n slashTemplates: any[]\n footerNoticeKey?: string\n proposalChoices: any[]\n stagedNoticeKey?: string\n}\n\n/**\n * Reconcile the places a setting can come from.\n *\n * **A host that declares a mode gets the mode and nothing else.** A field the mode leaves out is\n * empty: it is not filled from the element's own properties, and it is not filled from the\n * defaults another package registered. Filling it back in is how the defect this type removes\n * returned — board-ai registers board authoring's `/add-monitor`, its component `@` catalog and\n * its \"previewed on the board\" line globally, and plant's factory mode, which declares no slash\n * list, offered them on a factory screen once the board modeller had been loaded. A mode that\n * wants those declares them.\n *\n * A host that declares no mode gets exactly what it got before modes existed — its own\n * properties, falling back to the registered defaults.\n *\n * `chatEndpoint` still falls back to `boardAIChat` when nothing names one; that default is a\n * separate question (standard A2).\n */\nexport function resolveConversation(\n mode: AssistantMode | undefined,\n own: Partial<ConversationSettings>,\n defaults: Partial<ConversationSettings>\n): ConversationSettings {\n const pick = <K extends keyof ConversationSettings>(key: K): ConversationSettings[K] =>\n mode ? (mode as any)[key] : ((own as any)[key] ?? (defaults as any)[key])\n\n return {\n systemPrompt: pick('systemPrompt'),\n toolCategories: pick('toolCategories'),\n hostContext: pick('hostContext'),\n chatEndpoint: pick('chatEndpoint') ?? 'boardAIChat',\n catalogEntries: pick('catalogEntries') ?? [],\n slashTemplates: pick('slashTemplates') ?? [],\n footerNoticeKey: pick('footerNoticeKey'),\n proposalChoices: pick('proposalChoices') ?? [],\n stagedNoticeKey: pick('stagedNoticeKey')\n }\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,85 @@
1
+ /*
2
+ * A mode is one value, and the point of it is that the parts of a conversation cannot
3
+ * disagree with each other.
4
+ *
5
+ * The defect this guards against shipped: a screen whose systemPrompt said "you do not edit
6
+ * boards here" offered `/add-monitor` in the same panel, because the prompt was an element
7
+ * property and the slash list was a module global. Any resolution that lets a mode supply one
8
+ * of them while something else supplies the other brings it back.
9
+ */
10
+ import { isDifferentConversation, resolveConversation } from './assistant-mode';
11
+ const MODE = {
12
+ id: 'twin:space:S-12',
13
+ systemPrompt: 'You are looking at one space. You do not edit boards here.',
14
+ toolCategories: ['twin'],
15
+ hostContext: { spaceId: 'S-12' },
16
+ slashTemplates: [{ name: 'status', template: '이 공간의 지금 상태를 요약해줘' }]
17
+ };
18
+ const HOST_PROPERTIES = {
19
+ systemPrompt: 'a prompt the host set the old way',
20
+ toolCategories: ['board-ai'],
21
+ hostContext: { boardId: 'B-1' },
22
+ chatEndpoint: 'boardAIChat'
23
+ };
24
+ const GLOBAL_DEFAULTS = {
25
+ catalogEntries: [{ type: 'rect' }],
26
+ slashTemplates: [{ name: 'add-monitor', template: '모니터를 추가해줘' }],
27
+ footerNoticeKey: 'text.previewed-on-the-board'
28
+ };
29
+ describe('resolveConversation', () => {
30
+ test('a mode answers before the host properties it overlaps', () => {
31
+ const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS);
32
+ expect(c.systemPrompt).toBe(MODE.systemPrompt);
33
+ expect(c.toolCategories).toEqual(['twin']);
34
+ expect(c.hostContext).toEqual({ spaceId: 'S-12' });
35
+ });
36
+ test('a mode answers before the global defaults — the prompt and the slash list cannot disagree', () => {
37
+ const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS);
38
+ expect(c.slashTemplates).toEqual(MODE.slashTemplates);
39
+ /* A mode that says it does not edit boards must not offer a board edit. */
40
+ expect(JSON.stringify(c.slashTemplates)).not.toContain('add-monitor');
41
+ });
42
+ test('★ what a mode leaves out stays out — the defaults another package registered do not fill it', () => {
43
+ const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS);
44
+ /* The board's `@` catalog and its "previewed on the board" line belong to board authoring. */
45
+ expect(c.catalogEntries).toEqual([]);
46
+ expect(c.footerNoticeKey).toBeUndefined();
47
+ });
48
+ test('★ a mode that declares no slash list offers none, even when a board package registered its own', () => {
49
+ /* plant's factory mode declares no `slashTemplates`; board-ai registers `/add-monitor` globally. */
50
+ const { slashTemplates, ...withoutSlash } = MODE;
51
+ const c = resolveConversation({ ...withoutSlash, id: 'plant:factory:D-1' }, HOST_PROPERTIES, GLOBAL_DEFAULTS);
52
+ expect(c.slashTemplates).toEqual([]);
53
+ });
54
+ test('★ a mode that leaves out a field the host also set as a property does not take the property either', () => {
55
+ const c = resolveConversation({ id: 'twin:none' }, HOST_PROPERTIES, GLOBAL_DEFAULTS);
56
+ expect(c.systemPrompt).toBeUndefined();
57
+ expect(c.toolCategories).toBeUndefined();
58
+ expect(c.hostContext).toBeUndefined();
59
+ });
60
+ test('a host with no mode gets exactly what it had before modes existed', () => {
61
+ const c = resolveConversation(undefined, HOST_PROPERTIES, GLOBAL_DEFAULTS);
62
+ expect(c.systemPrompt).toBe(HOST_PROPERTIES.systemPrompt);
63
+ expect(c.toolCategories).toEqual(HOST_PROPERTIES.toolCategories);
64
+ expect(c.slashTemplates).toEqual(GLOBAL_DEFAULTS.slashTemplates);
65
+ });
66
+ test('nothing anywhere resolves to empty lists and the board endpoint, never undefined', () => {
67
+ const c = resolveConversation(undefined, {}, {});
68
+ expect(c.catalogEntries).toEqual([]);
69
+ expect(c.slashTemplates).toEqual([]);
70
+ expect(c.proposalChoices).toEqual([]);
71
+ expect(c.chatEndpoint).toBe('boardAIChat');
72
+ });
73
+ });
74
+ describe('isDifferentConversation', () => {
75
+ const station3 = { id: 'plant:station:3' };
76
+ test('a different subject ends the conversation', () => {
77
+ expect(isDifferentConversation(station3, { id: 'plant:station:7' })).toBe(true);
78
+ expect(isDifferentConversation(undefined, station3)).toBe(true);
79
+ });
80
+ test('rewording the same subject does not', () => {
81
+ expect(isDifferentConversation(station3, { ...station3, systemPrompt: 'reworded' })).toBe(false);
82
+ expect(isDifferentConversation(undefined, undefined)).toBe(false);
83
+ });
84
+ });
85
+ //# sourceMappingURL=assistant-mode.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-mode.test.js","sourceRoot":"","sources":["../../client/components/assistant-mode.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * A mode is one value, and the point of it is that the parts of a conversation cannot\n * disagree with each other.\n *\n * The defect this guards against shipped: a screen whose systemPrompt said \"you do not edit\n * boards here\" offered `/add-monitor` in the same panel, because the prompt was an element\n * property and the slash list was a module global. Any resolution that lets a mode supply one\n * of them while something else supplies the other brings it back.\n */\nimport { isDifferentConversation, resolveConversation, type AssistantMode } from './assistant-mode'\n\nconst MODE: AssistantMode = {\n id: 'twin:space:S-12',\n systemPrompt: 'You are looking at one space. You do not edit boards here.',\n toolCategories: ['twin'],\n hostContext: { spaceId: 'S-12' },\n slashTemplates: [{ name: 'status', template: '이 공간의 지금 상태를 요약해줘' }]\n}\n\nconst HOST_PROPERTIES = {\n systemPrompt: 'a prompt the host set the old way',\n toolCategories: ['board-ai'],\n hostContext: { boardId: 'B-1' },\n chatEndpoint: 'boardAIChat' as const\n}\n\nconst GLOBAL_DEFAULTS = {\n catalogEntries: [{ type: 'rect' }],\n slashTemplates: [{ name: 'add-monitor', template: '모니터를 추가해줘' }],\n footerNoticeKey: 'text.previewed-on-the-board'\n}\n\ndescribe('resolveConversation', () => {\n test('a mode answers before the host properties it overlaps', () => {\n const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS)\n expect(c.systemPrompt).toBe(MODE.systemPrompt)\n expect(c.toolCategories).toEqual(['twin'])\n expect(c.hostContext).toEqual({ spaceId: 'S-12' })\n })\n\n test('a mode answers before the global defaults — the prompt and the slash list cannot disagree', () => {\n const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS)\n expect(c.slashTemplates).toEqual(MODE.slashTemplates)\n /* A mode that says it does not edit boards must not offer a board edit. */\n expect(JSON.stringify(c.slashTemplates)).not.toContain('add-monitor')\n })\n\n test('★ what a mode leaves out stays out — the defaults another package registered do not fill it', () => {\n const c = resolveConversation(MODE, HOST_PROPERTIES, GLOBAL_DEFAULTS)\n /* The board's `@` catalog and its \"previewed on the board\" line belong to board authoring. */\n expect(c.catalogEntries).toEqual([])\n expect(c.footerNoticeKey).toBeUndefined()\n })\n\n test('★ a mode that declares no slash list offers none, even when a board package registered its own', () => {\n /* plant's factory mode declares no `slashTemplates`; board-ai registers `/add-monitor` globally. */\n const { slashTemplates, ...withoutSlash } = MODE\n const c = resolveConversation({ ...withoutSlash, id: 'plant:factory:D-1' }, HOST_PROPERTIES, GLOBAL_DEFAULTS)\n expect(c.slashTemplates).toEqual([])\n })\n\n test('★ a mode that leaves out a field the host also set as a property does not take the property either', () => {\n const c = resolveConversation({ id: 'twin:none' }, HOST_PROPERTIES, GLOBAL_DEFAULTS)\n expect(c.systemPrompt).toBeUndefined()\n expect(c.toolCategories).toBeUndefined()\n expect(c.hostContext).toBeUndefined()\n })\n\n test('a host with no mode gets exactly what it had before modes existed', () => {\n const c = resolveConversation(undefined, HOST_PROPERTIES, GLOBAL_DEFAULTS)\n expect(c.systemPrompt).toBe(HOST_PROPERTIES.systemPrompt)\n expect(c.toolCategories).toEqual(HOST_PROPERTIES.toolCategories)\n expect(c.slashTemplates).toEqual(GLOBAL_DEFAULTS.slashTemplates)\n })\n\n test('nothing anywhere resolves to empty lists and the board endpoint, never undefined', () => {\n const c = resolveConversation(undefined, {}, {})\n expect(c.catalogEntries).toEqual([])\n expect(c.slashTemplates).toEqual([])\n expect(c.proposalChoices).toEqual([])\n expect(c.chatEndpoint).toBe('boardAIChat')\n })\n})\n\ndescribe('isDifferentConversation', () => {\n const station3: AssistantMode = { id: 'plant:station:3' }\n\n test('a different subject ends the conversation', () => {\n expect(isDifferentConversation(station3, { id: 'plant:station:7' })).toBe(true)\n expect(isDifferentConversation(undefined, station3)).toBe(true)\n })\n\n test('rewording the same subject does not', () => {\n expect(isDifferentConversation(station3, { ...station3, systemPrompt: 'reworded' })).toBe(false)\n expect(isDifferentConversation(undefined, undefined)).toBe(false)\n })\n})\n"]}
@@ -5,12 +5,12 @@
5
5
  */
6
6
  export * from './components/assistant-chat.js';
7
7
  export * from './components/chat-defaults.js';
8
+ export * from './components/assistant-mode.js';
8
9
  export * from './components/markdown.js';
9
10
  export * from './components/chat-echo-dedup.js';
10
11
  export * from './components/chat-input-builder.js';
11
12
  export * from './components/mention-popup.js';
12
13
  export * from './components/mention-popup-helpers.js';
13
- export * from './utils/board-edit-patch.js';
14
14
  export * from './utils/assistant-session-controller.js';
15
15
  export * from './utils/assistant-session-transport.js';
16
16
  export * from './components/assistant-session-toolbar.js';
@@ -5,12 +5,12 @@
5
5
  */
6
6
  export * from './components/assistant-chat.js';
7
7
  export * from './components/chat-defaults.js';
8
+ export * from './components/assistant-mode.js';
8
9
  export * from './components/markdown.js';
9
10
  export * from './components/chat-echo-dedup.js';
10
11
  export * from './components/chat-input-builder.js';
11
12
  export * from './components/mention-popup.js';
12
13
  export * from './components/mention-popup-helpers.js';
13
- export * from './utils/board-edit-patch.js';
14
14
  export * from './utils/assistant-session-controller.js';
15
15
  export * from './utils/assistant-session-transport.js';
16
16
  export * from './components/assistant-session-toolbar.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,gCAAgC,CAAA;AAC9C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,0BAA0B,CAAA;AACxC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,oCAAoC,CAAA;AAClD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uCAAuC,CAAA;AACrD,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yCAAyC,CAAA;AACvD,cAAc,wCAAwC,CAAA;AACtD,cAAc,2CAA2C,CAAA;AACzD,cAAc,sCAAsC,CAAA","sourcesContent":["/**\n * `@things-factory/ai-assistant` — 대화면.\n *\n * 표준 컴포넌트는 `<ox-assistant-chat>` 이다.\n */\nexport * from './components/assistant-chat.js'\nexport * from './components/chat-defaults.js'\nexport * from './components/markdown.js'\nexport * from './components/chat-echo-dedup.js'\nexport * from './components/chat-input-builder.js'\nexport * from './components/mention-popup.js'\nexport * from './components/mention-popup-helpers.js'\nexport * from './utils/board-edit-patch.js'\nexport * from './utils/assistant-session-controller.js'\nexport * from './utils/assistant-session-transport.js'\nexport * from './components/assistant-session-toolbar.js'\nexport * from './utils/assistant-request-context.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,gCAAgC,CAAA;AAC9C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,0BAA0B,CAAA;AACxC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,oCAAoC,CAAA;AAClD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uCAAuC,CAAA;AACrD,cAAc,yCAAyC,CAAA;AACvD,cAAc,wCAAwC,CAAA;AACtD,cAAc,2CAA2C,CAAA;AACzD,cAAc,sCAAsC,CAAA","sourcesContent":["/**\n * `@things-factory/ai-assistant` — 대화면.\n *\n * 표준 컴포넌트는 `<ox-assistant-chat>` 이다.\n */\nexport * from './components/assistant-chat.js'\nexport * from './components/chat-defaults.js'\nexport * from './components/assistant-mode.js'\nexport * from './components/markdown.js'\nexport * from './components/chat-echo-dedup.js'\nexport * from './components/chat-input-builder.js'\nexport * from './components/mention-popup.js'\nexport * from './components/mention-popup-helpers.js'\nexport * from './utils/assistant-session-controller.js'\nexport * from './utils/assistant-session-transport.js'\nexport * from './components/assistant-session-toolbar.js'\nexport * from './utils/assistant-request-context.js'\n"]}