@things-factory/board-ai 10.1.52 → 10.1.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/board-ai",
3
- "version": "10.1.52",
3
+ "version": "10.1.53",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/client/index.js",
6
6
  "things-factory": true,
@@ -32,10 +32,10 @@
32
32
  "@operato/i18n": "^10.0.0",
33
33
  "@operato/scene-ops": "^10.14.1",
34
34
  "@operato/styles": "^10.0.0",
35
- "@things-factory/ai-assistant": "^10.1.52",
36
- "@things-factory/ai-client-base": "^10.1.39",
35
+ "@things-factory/ai-assistant": "^10.1.53",
36
+ "@things-factory/ai-client-base": "^10.1.53",
37
37
  "@things-factory/auth-base": "^10.1.47",
38
- "@things-factory/board-import": "^10.1.47",
38
+ "@things-factory/board-import": "^10.1.53",
39
39
  "@things-factory/env": "^10.1.20",
40
40
  "@things-factory/shell": "^10.1.47",
41
41
  "dompurify": "^3.0.0",
@@ -47,5 +47,5 @@
47
47
  "copyfiles": "^2.4.1",
48
48
  "rimraf": "^5.0.0"
49
49
  },
50
- "gitHead": "b3ec4f0a38f0759d396f49b1f47c547bff4657be"
50
+ "gitHead": "1bdb0c604614735fd5a5467aee1e56f53be6b89e"
51
51
  }
@@ -118,6 +118,18 @@ export interface BoardAIAssistantOptions {
118
118
  categories?: ComponentCategory[]
119
119
  }
120
120
 
121
+ /**
122
+ * Said to the model when the person's rights turned the editing tools off (`boardToolsWithheld`). The editing
123
+ * guide above still describes the tools, so without this the model calls them anyway, fails on each, and
124
+ * writes the calls out as its answer.
125
+ */
126
+ export const BOARD_EDIT_WITHHELD =
127
+ '==== BOARD EDITING IS OFF FOR THIS PERSON ====\n' +
128
+ 'This person has no permission to change boards (board-ai mutation), so this conversation has no editing tool - ' +
129
+ 'the editing instructions above do not apply. Never call an editing tool and never write a tool call out as text. ' +
130
+ 'When asked to change the board, say it cannot be changed from this conversation because changing boards needs the ' +
131
+ 'board edit permission, which an administrator can grant. You can still answer questions about the board.'
132
+
121
133
  export class DefaultBoardAIAssistant implements BoardAIAssistant {
122
134
  readonly id: string
123
135
  constructor(private base: AIClient, private options: BoardAIAssistantOptions = {}) {
@@ -183,7 +195,8 @@ export class DefaultBoardAIAssistant implements BoardAIAssistant {
183
195
  const systemPrompt =
184
196
  this.buildSystemPromptForTools(knownTypes, categories, schemas) +
185
197
  (surface ? `\n\n==== THIS SURFACE (declared by the app) ====\n${surface}` : '') +
186
- (guidance.length ? `\n\n==== TOOL USE RULES (from the tools exposed to this conversation) ====\n${guidance.join('\n')}` : '')
198
+ (guidance.length ? `\n\n==== TOOL USE RULES (from the tools exposed to this conversation) ====\n${guidance.join('\n')}` : '') +
199
+ (!gate.coreOn && options.boardToolsWithheld ? `\n\n${BOARD_EDIT_WITHHELD}` : '')
187
200
 
188
201
  const selectedRefids = (options.selectedRefids ?? []).filter(
189
202
  (n): n is number => typeof n === 'number' && Number.isFinite(n)
@@ -439,13 +439,18 @@ export class BoardAIChatResolver {
439
439
  }))
440
440
  }
441
441
 
442
+ /* The surface wanted board editing and the person's rights took it away - the model is told, so it says so. */
443
+ const boardTools = await effectiveBoardTools(input.boardTools, context)
444
+ const boardToolsWithheld = input.boardTools !== false && boardTools === false
445
+
442
446
  const r = await assistant.chat(llmMessages, input.currentBoard ?? undefined, {
443
447
  scopes: input.scopes,
444
448
  hostContext: input.hostContext ?? undefined,
445
449
  surfacePrompt: modeSettings?.systemPrompt,
446
450
  /* 대화면별 도구 노출 — 모드가 있으면 서버 선언(빈 목록 = 외부 도구 없음), 없으면 호출자 값(미지정 = 전부). */
447
451
  toolCategories: modeSettings ? modeSettings.toolCategories ?? [] : input.toolCategories ?? undefined,
448
- boardTools: await effectiveBoardTools(input.boardTools, context),
452
+ boardTools,
453
+ boardToolsWithheld,
449
454
  requireGroundingTools: modeSettings ? modeSettings.requireToolOnFirstTurn : input.requireGroundingTools ?? undefined,
450
455
  knownTypes: input.knownTypes,
451
456
  categories: input.categories as ComponentCategory[] | undefined,
@@ -16,7 +16,7 @@
16
16
  import type { AIClient } from '@things-factory/ai-client-base'
17
17
  import { registerToolCategory, clearToolRegistry } from '@things-factory/ai-client-base'
18
18
 
19
- import { DefaultBoardAIAssistant } from './assistant'
19
+ import { BOARD_EDIT_WITHHELD, DefaultBoardAIAssistant } from './assistant'
20
20
 
21
21
  /** 스크립트된 턴을 돌려주는 가짜 provider — 호출 옵션도 기록한다(toolChoice 확인용). */
22
22
  function mockClient(turns: Array<{ text?: string; toolCalls: any[] }>) {
@@ -306,3 +306,28 @@ describe('도크 계약 — 도구 사용 규율이 프롬프트에 실린다',
306
306
  expect(calls[0].options.systemPrompt).not.toContain('OTHER-RULE')
307
307
  })
308
308
  })
309
+
310
+ describe('dock contract - board editing withheld by rights', () => {
311
+ it('the model is told editing is off when the rights took the tools away', async () => {
312
+ const { client, calls } = mockClient([{ text: 'I cannot change the board here.', toolCalls: [] }])
313
+ await new DefaultBoardAIAssistant(client).chat([{ role: 'user', content: 'add a title' }], { components: [] } as any, {
314
+ boardTools: false,
315
+ boardToolsWithheld: true
316
+ })
317
+ expect(calls[0].options.systemPrompt).toContain(BOARD_EDIT_WITHHELD)
318
+ })
319
+
320
+ it('a surface that turned the tools off itself is not told about rights', async () => {
321
+ const { client, calls } = mockClient([{ text: 'ok', toolCalls: [] }])
322
+ await new DefaultBoardAIAssistant(client).chat([{ role: 'user', content: 'status?' }], undefined, { boardTools: false })
323
+ expect(calls[0].options.systemPrompt).not.toContain(BOARD_EDIT_WITHHELD)
324
+ })
325
+
326
+ it('with the tools on, nothing is said', async () => {
327
+ const { client, calls } = mockClient([{ text: 'ok', toolCalls: [] }])
328
+ await new DefaultBoardAIAssistant(client).chat([{ role: 'user', content: 'add a title' }], { components: [] } as any, {
329
+ boardToolsWithheld: true
330
+ })
331
+ expect(calls[0].options.systemPrompt).not.toContain(BOARD_EDIT_WITHHELD)
332
+ })
333
+ })
@@ -37,6 +37,12 @@ export interface ChatOptions {
37
37
  * 되돌릴 수 있는 편집이 한 대화에 섞이지 않게 한다.
38
38
  */
39
39
  boardTools?: boolean
40
+ /**
41
+ * The surface asked for board editing but the person has no board-ai:mutation, so the editing tools are off.
42
+ * The model is told in so many words - otherwise the editing guide in the prompt leads it to call tools that
43
+ * are not there and to write the calls out as text.
44
+ */
45
+ boardToolsWithheld?: boolean
40
46
  /**
41
47
  * **첫 턴에 도구 호출을 강제**한다. 라이브 상태를 다루는 대화면(운영 협의)에서 켠다.
42
48
  *