@things-factory/ai-assistant 10.1.46 → 10.1.47

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 (37) hide show
  1. package/client/components/assistant-chat.ts +3 -1
  2. package/client/components/assistant-mode.ts +27 -0
  3. package/client/components/chat-input-builder.ts +8 -1
  4. package/client/index.ts +2 -0
  5. package/client/utils/assistant-capability-read.ts +24 -0
  6. package/client/utils/assistant-capability.test.ts +17 -0
  7. package/client/utils/assistant-capability.ts +19 -0
  8. package/dist-client/components/assistant-chat.js +3 -1
  9. package/dist-client/components/assistant-chat.js.map +1 -1
  10. package/dist-client/components/assistant-mode.js +24 -0
  11. package/dist-client/components/assistant-mode.js.map +1 -1
  12. package/dist-client/components/chat-input-builder.d.ts +9 -0
  13. package/dist-client/components/chat-input-builder.js +2 -1
  14. package/dist-client/components/chat-input-builder.js.map +1 -1
  15. package/dist-client/index.d.ts +2 -0
  16. package/dist-client/index.js +2 -0
  17. package/dist-client/index.js.map +1 -1
  18. package/dist-client/tsconfig.tsbuildinfo +1 -1
  19. package/dist-client/utils/assistant-capability-read.d.ts +6 -0
  20. package/dist-client/utils/assistant-capability-read.js +23 -0
  21. package/dist-client/utils/assistant-capability-read.js.map +1 -0
  22. package/dist-client/utils/assistant-capability.d.ts +4 -0
  23. package/dist-client/utils/assistant-capability.js +17 -0
  24. package/dist-client/utils/assistant-capability.js.map +1 -0
  25. package/dist-client/utils/assistant-capability.test.d.ts +1 -0
  26. package/dist-client/utils/assistant-capability.test.js +16 -0
  27. package/dist-client/utils/assistant-capability.test.js.map +1 -0
  28. package/dist-server/service/assistant-chat-resolver.js +1 -1
  29. package/dist-server/service/assistant-chat-resolver.js.map +1 -1
  30. package/dist-server/service/index.d.ts +1 -0
  31. package/dist-server/service/index.js +3 -1
  32. package/dist-server/service/index.js.map +1 -1
  33. package/dist-server/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +4 -4
  35. package/server/service/assistant-chat-resolver.ts +1 -1
  36. package/server/service/chat-message/fold-session-history.test.ts +18 -0
  37. package/server/service/index.ts +1 -0
@@ -4529,7 +4529,9 @@ export class OxAssistantChat extends LitElement {
4529
4529
  categories: this.categories,
4530
4530
  componentSchemas: this.componentSchemas,
4531
4531
  selectedRefids: this.selectedRefids ?? [],
4532
- mentions
4532
+ mentions,
4533
+ /* With a declared mode the server owns persona and tools; the board door still carries the board. */
4534
+ mode: this.conversation.kind ? { kind: this.conversation.kind, target: this.conversation.target } : undefined
4533
4535
  })
4534
4536
  }
4535
4537
  })
@@ -103,6 +103,33 @@ export function resolveConversation(
103
103
  defaults: Partial<ConversationSettings>,
104
104
  view?: AssistantModeView
105
105
  ): ConversationSettings {
106
+ if (mode && own.chatEndpoint === 'boardAIChat') {
107
+ /*
108
+ * The board door with a declared mode. The mode decides persona and tools on the server; the
109
+ * door stays `boardAIChat` because only it carries the board document and returns patches -
110
+ * `assistantChat` has no document. What the panel offers comes from the mode where the mode
111
+ * declares it, and from the board's own defaults (the # component catalog, the slash
112
+ * templates) where it does not, so a mode that declares nothing does not empty the panel.
113
+ */
114
+ const offered = view && view.kind === mode.kind ? view : undefined
115
+ const pickOwn = <K extends keyof ConversationSettings>(key: K): ConversationSettings[K] =>
116
+ (own as any)[key] ?? (defaults as any)[key]
117
+ const orOwn = (list: any[] | undefined, key: 'catalogEntries' | 'slashTemplates' | 'proposalChoices') =>
118
+ list && list.length > 0 ? list : (pickOwn(key) as any[]) ?? []
119
+ return {
120
+ kind: mode.kind,
121
+ target: mode.target,
122
+ hostContext: mode.hostContext ?? pickOwn('hostContext'),
123
+ chatEndpoint: 'boardAIChat',
124
+ catalogEntries: orOwn(offered?.catalogEntries, 'catalogEntries'),
125
+ slashTemplates: orOwn(offered?.slashTemplates, 'slashTemplates'),
126
+ proposalChoices: orOwn(offered?.proposalChoices, 'proposalChoices'),
127
+ placeholder: offered?.placeholder ?? pickOwn('placeholder'),
128
+ footerNotice: offered?.footerNotice ?? pickOwn('footerNotice'),
129
+ stagedNotice: offered?.stagedNotice ?? pickOwn('stagedNotice')
130
+ }
131
+ }
132
+
106
133
  if (mode) {
107
134
  /* A view for another kind is the previous conversation's answer arriving late. */
108
135
  const offered = view && view.kind === mode.kind ? view : undefined
@@ -40,6 +40,12 @@ export interface ChatMutationInputArgs {
40
40
  * server 가 mention map 으로 LLM 에 정확한 refid 정보 첨부 (사용자 노출 0).
41
41
  */
42
42
  mentions?: Array<{ token: string; refid: number }>
43
+ /**
44
+ * The registered conversation this board chat is (ADR-0061 decision 7). When sent, the server
45
+ * takes the persona, tool categories, model and limits from that mode and ignores the per-request
46
+ * ones above. Omitted for a surface that declares no mode.
47
+ */
48
+ mode?: { kind: string; target?: string }
43
49
  }
44
50
 
45
51
  /**
@@ -64,7 +70,8 @@ export function buildChatMutationInput(args: ChatMutationInputArgs): Record<stri
64
70
  categories: args.categories,
65
71
  componentSchemas: args.componentSchemas,
66
72
  selectedRefids: args.selectedRefids,
67
- ...(args.mentions && args.mentions.length > 0 && { mentions: args.mentions })
73
+ ...(args.mentions && args.mentions.length > 0 && { mentions: args.mentions }),
74
+ ...(args.mode?.kind ? { mode: { kind: args.mode.kind, ...(args.mode.target ? { target: args.mode.target } : {}) } } : {})
68
75
  }
69
76
  }
70
77
 
package/client/index.ts CHANGED
@@ -15,3 +15,5 @@ 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'
17
17
  export * from './utils/assistant-request-context.js'
18
+ export * from './utils/assistant-capability.js'
19
+ export * from './utils/assistant-capability-read.js'
@@ -0,0 +1,24 @@
1
+ import { client } from '@operato/graphql'
2
+ import gql from 'graphql-tag'
3
+
4
+ import { capabilityOf, type AiCapability } from './assistant-capability.js'
5
+
6
+ /**
7
+ * Asks `aiCapability` (standard A7-a). A GraphQL error is thrown for the caller to show; the caller keeps
8
+ * `unknown` meanwhile and asks again — it does not remember a failed read as an answer.
9
+ */
10
+ export async function readAiCapability(): Promise<AiCapability> {
11
+ const result = await client.query({
12
+ query: gql`
13
+ query {
14
+ aiCapability {
15
+ state
16
+ }
17
+ }
18
+ `,
19
+ fetchPolicy: 'no-cache'
20
+ })
21
+
22
+ if (result.errors?.length) throw new Error(result.errors[0].message)
23
+ return capabilityOf(result.data?.aiCapability)
24
+ }
@@ -0,0 +1,17 @@
1
+ /*
2
+ * A7-a — the third state is never read as the first.
3
+ */
4
+ import { capabilityOf } from './assistant-capability'
5
+
6
+ describe('capabilityOf', () => {
7
+ it('passes the two answers the door gives', () => {
8
+ expect(capabilityOf({ state: 'configured' })).toBe('configured')
9
+ expect(capabilityOf({ state: 'not-configured' })).toBe('not-configured')
10
+ })
11
+
12
+ it('★ not asked yet, and an answer it cannot read, are unknown — never configured', () => {
13
+ for (const answer of [undefined, null, {}, { state: null }, { state: 'yes' }, { state: 'CONFIGURED' }]) {
14
+ expect(capabilityOf(answer as any)).toBe('unknown')
15
+ }
16
+ })
17
+ })
@@ -0,0 +1,19 @@
1
+ /*
2
+ * Whether this deployment has an AI to answer — the rule every dock reads before offering a conversation.
3
+ *
4
+ * AI standard A7-a: a screen does not find out by failing. Without this a dock takes the question, sends
5
+ * it, and only then shows the provider's error. `aiCapability` answers first.
6
+ *
7
+ * Three states, and the third is the screen's own: not asked yet, or an answer that could not be read.
8
+ * It is never folded into `configured` — that is where the question started (a button that was always on).
9
+ *
10
+ * Pure, so it is tested without GraphQL. The read is `readAiCapability` (`assistant-capability-read.ts`).
11
+ * Warehouse and twin each held a copy of this until it moved here (2026-09-24).
12
+ */
13
+
14
+ export type AiCapability = 'configured' | 'not-configured' | 'unknown'
15
+
16
+ export function capabilityOf(answer: { state?: string | null } | null | undefined): AiCapability {
17
+ const state = answer?.state
18
+ return state === 'configured' || state === 'not-configured' ? state : 'unknown'
19
+ }
@@ -4039,7 +4039,9 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
4039
4039
  categories: this.categories,
4040
4040
  componentSchemas: this.componentSchemas,
4041
4041
  selectedRefids: this.selectedRefids ?? [],
4042
- mentions
4042
+ mentions,
4043
+ /* With a declared mode the server owns persona and tools; the board door still carries the board. */
4044
+ mode: this.conversation.kind ? { kind: this.conversation.kind, target: this.conversation.target } : undefined
4043
4045
  })
4044
4046
  }
4045
4047
  });