@things-factory/board-ai 10.1.159 → 10.1.161
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/dist-client/server/service/assistant.js +32 -29
- package/dist-client/server/service/assistant.js.map +1 -1
- package/dist-client/server/service/types.d.ts +6 -0
- package/dist-client/server/service/types.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/assistant.js +32 -29
- package/dist-server/service/assistant.js.map +1 -1
- package/dist-server/service/board-ai-resolver.d.ts +1 -0
- package/dist-server/service/board-ai-resolver.js +25 -2
- package/dist-server/service/board-ai-resolver.js.map +1 -1
- package/dist-server/service/types.d.ts +6 -0
- package/dist-server/service/types.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/server/service/assistant.test.ts +28 -14
- package/server/service/assistant.ts +35 -31
- package/server/service/board-ai-resolver.ts +28 -4
- package/server/service/board-context-path.test.ts +32 -0
- package/server/service/types.ts +6 -0
|
@@ -121,32 +121,24 @@ class DefaultBoardAIAssistant {
|
|
|
121
121
|
* 모아 붙이기만 한다(도메인 지식이 board-ai 프롬프트로 새지 않는다). */
|
|
122
122
|
const guidance = (0, ai_client_base_2.getCategoryGuidance)(options.toolCategories);
|
|
123
123
|
const surface = (options.surfacePrompt ?? '').trim();
|
|
124
|
+
const openSubject = (options.openSubject ?? '').trim();
|
|
124
125
|
const systemPrompt = this.buildSystemPromptForTools(knownTypes, categories, schemas) +
|
|
125
126
|
(surface ? `\n\n==== THIS SURFACE (declared by the app) ====\n${surface}` : '') +
|
|
127
|
+
/* Which board is open - the server's line from the stored anchor, the one way the model learns it (screen-pattern §2-1). */
|
|
128
|
+
(openSubject ? `\n\n==== OPEN SUBJECT ====\n${openSubject}` : '') +
|
|
129
|
+
(gate.coreOn ? `\n\n${READ_THE_BOARD}` : '') +
|
|
126
130
|
(guidance.length ? `\n\n==== TOOL USE RULES (from the tools exposed to this conversation) ====\n${guidance.join('\n')}` : '') +
|
|
127
131
|
(!gate.coreOn && options.boardToolsWithheld ? `\n\n${exports.BOARD_EDIT_WITHHELD}` : '');
|
|
128
132
|
const selectedRefids = (options.selectedRefids ?? []).filter((n) => typeof n === 'number' && Number.isFinite(n));
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
`clarify or call \`getSelection\` to confirm.`;
|
|
139
|
-
const boardContext = currentBoard
|
|
140
|
-
? `Current board state (JSON, summarized; full details via \`getComponent\` / \`findComponents\`):\n` +
|
|
141
|
-
`${JSON.stringify(summarizeBoard(currentBoard), null, 2)}` + selectionContext
|
|
142
|
-
: 'No current board (empty start).' + selectionContext;
|
|
143
|
-
let conversation = [
|
|
144
|
-
{ role: 'user', content: boardContext },
|
|
145
|
-
...messages.map(m => ({
|
|
146
|
-
role: m.role === 'assistant' ? 'assistant' : 'user',
|
|
147
|
-
content: m.content
|
|
148
|
-
}))
|
|
149
|
-
];
|
|
133
|
+
/*
|
|
134
|
+
* The board and the selection are not pasted into the request any more (screen-pattern §2-2, 2026-10-13): the
|
|
135
|
+
* whole board went into every turn as a first user message, a second way of knowing the open board beside the
|
|
136
|
+
* server's line. The model reads what it needs with getBoardOutline · getSelection · findComponents.
|
|
137
|
+
*/
|
|
138
|
+
let conversation = messages.map(m => ({
|
|
139
|
+
role: m.role === 'assistant' ? 'assistant' : 'user',
|
|
140
|
+
content: m.content
|
|
141
|
+
}));
|
|
150
142
|
// multi-turn agentic loop — LLM 이 read 호출 → 서버 실행 → 결과 회신 → LLM 추론 반복.
|
|
151
143
|
// write tool 은 누적해 client 에 patch 로 전달. 본체는 ./agentic-loop 에 분리.
|
|
152
144
|
//
|
|
@@ -231,8 +223,7 @@ class DefaultBoardAIAssistant {
|
|
|
231
223
|
* 근거집합의 정의와 사정거리는 ./grounding 머리말 참조(식별자 없는 창작 서술은 잡지 못한다). */
|
|
232
224
|
const warnings = (0, grounding_1.checkReplyGrounding)(baseReply, {
|
|
233
225
|
systemPrompt,
|
|
234
|
-
/*
|
|
235
|
-
hostContext: boardContext,
|
|
226
|
+
/* 보드는 이제 도구로만 모델에게 닿는다 — 읽은 보드는 toolResults 안에 있다(screen-pattern §2-2). */
|
|
236
227
|
messages: messages.map(m => (typeof m.content === 'string' ? m.content : '')),
|
|
237
228
|
toolResults: loopResult.groundedTexts
|
|
238
229
|
});
|
|
@@ -244,7 +235,7 @@ class DefaultBoardAIAssistant {
|
|
|
244
235
|
confidence: { level: warnings.length ? 'low' : 'medium', reasons: warnings.length ? ['grounding-warning'] : ['tool-or-board-context'] },
|
|
245
236
|
limitations: warnings.map(code => ({ code })),
|
|
246
237
|
claims: [{ code: patch ? 'board.patch-proposed' : 'board.context-reviewed', severity: 'info', evidence: { toolCalls: toolUsages.length, componentCount: components.length }, method: 'board-agentic-loop', assumptions: ['Board context is current for this turn.'] }],
|
|
247
|
-
facts: { board:
|
|
238
|
+
facts: { board: summarizeBoard(currentBoard), toolUsages },
|
|
248
239
|
...(patch ? { proposal: patch } : accumulatedActions.length ? { proposal: accumulatedActions } : {}),
|
|
249
240
|
approvalRequired: true,
|
|
250
241
|
approval: { required: true, mode: 'confirm', reason: patch || accumulatedActions.length ? 'Board or scene change requires user confirmation.' : 'Analysis is advisory.' },
|
|
@@ -346,8 +337,8 @@ Rules:
|
|
|
346
337
|
배열에 자식을 가질 수 있다. 자식이 또 group 일 수 있어 임의 깊이로 중첩 가능.
|
|
347
338
|
|
|
348
339
|
가시성:
|
|
349
|
-
- \`
|
|
350
|
-
|
|
340
|
+
- \`getBoardOutline\` 은 *평면 list* 로 모든 컴포넌트를 돌려준다 — top-level 뿐 아니라
|
|
341
|
+
group/container 안 자식도 모두 포함.
|
|
351
342
|
- 자식은 \`_parentRefid\` (부모 group refid) 와 \`_depth\` (중첩 깊이, 0=root) 메타
|
|
352
343
|
가 첨부되어 어느 group 안에 있는지 식별 가능.
|
|
353
344
|
- \`findComponents\` 는 전체 트리를 search — group 안 자식까지 매칭. 특정 group
|
|
@@ -537,7 +528,7 @@ Behavioral rules:
|
|
|
537
528
|
- If the user is asking a question rather than commanding, REPLY in TEXT WITHOUT calling any tool.
|
|
538
529
|
- If the user's request is ambiguous, REPLY in TEXT (asking for clarification) WITHOUT calling any tool.
|
|
539
530
|
- For multiple components, call addComponent multiple times in parallel.
|
|
540
|
-
- The
|
|
531
|
+
- The board is not in the conversation: read it with \`getBoardOutline\` (all components, flat) and the other read tools before you describe or change it.
|
|
541
532
|
|
|
542
533
|
Reply formatting rules (chat surface):
|
|
543
534
|
- Reply ONCE with ONE form only. NEVER restate the same answer in a second form (e.g. do NOT add a prose summary after a bullet list, or vice versa).
|
|
@@ -605,7 +596,7 @@ function buildBoardEditTools(knownTypes, exposure = {}) {
|
|
|
605
596
|
},
|
|
606
597
|
{
|
|
607
598
|
name: 'removeComponentByRefid',
|
|
608
|
-
description: 'Remove an existing component by its `refid` — the universal numeric handle that things-scene auto-assigns to every component (
|
|
599
|
+
description: 'Remove an existing component by its `refid` — the universal numeric handle that things-scene auto-assigns to every component (each component in `getBoardOutline` carries it as `refid`).',
|
|
609
600
|
parameters: {
|
|
610
601
|
type: 'object',
|
|
611
602
|
properties: {
|
|
@@ -696,6 +687,11 @@ function buildBoardEditTools(knownTypes, exposure = {}) {
|
|
|
696
687
|
}
|
|
697
688
|
},
|
|
698
689
|
// ── Read tools (서버측 즉시 실행, 결과를 LLM 에 회신) ─────────
|
|
690
|
+
{
|
|
691
|
+
name: 'getBoardOutline',
|
|
692
|
+
description: 'The open board as it stands now, saved or not: its own attributes (size, background, ...) and every component in a flat list - group and container children included, each with `refid`, `type`, its key attributes and, for a child, `_parentRefid` / `_depth`. Call this before describing or changing the board. Full details of one component: getComponentByRefid.',
|
|
693
|
+
parameters: { type: 'object', properties: {} }
|
|
694
|
+
},
|
|
699
695
|
{
|
|
700
696
|
name: 'getSelection',
|
|
701
697
|
description: 'Get full details of components currently selected by the user in the modeller. Call this when the user asks "what is selected?" / "선택된 게 뭐냐?" / refers to "the selected component" without specifying ids. Returns array of full component objects (no truncation).',
|
|
@@ -703,7 +699,7 @@ function buildBoardEditTools(knownTypes, exposure = {}) {
|
|
|
703
699
|
},
|
|
704
700
|
{
|
|
705
701
|
name: 'getComponentByRefid',
|
|
706
|
-
description: 'Get full details of a single component by its `refid` (the things-scene-assigned universal numeric handle). Use this to inspect properties
|
|
702
|
+
description: 'Get full details of a single component by its `refid` (the things-scene-assigned universal numeric handle). Use this to inspect properties `getBoardOutline` leaves out.',
|
|
707
703
|
parameters: {
|
|
708
704
|
type: 'object',
|
|
709
705
|
properties: {
|
|
@@ -1055,7 +1051,11 @@ function buildBoardEditTools(knownTypes, exposure = {}) {
|
|
|
1055
1051
|
return tools;
|
|
1056
1052
|
}
|
|
1057
1053
|
// ── Read tool 분류 + 실행기 ────────────────────────────────────────
|
|
1054
|
+
/** How the model learns the board and the selection - by reading, every time it needs to (screen-pattern §2-2). */
|
|
1055
|
+
const READ_THE_BOARD = 'The board and the selection are not in the conversation. Read the board with `getBoardOutline` before you describe or change it, ' +
|
|
1056
|
+
'and the selection with `getSelection` when the person says "selected", "this" or "these" without naming components; then work only on those.';
|
|
1058
1057
|
const READ_TOOL_NAMES = new Set([
|
|
1058
|
+
'getBoardOutline',
|
|
1059
1059
|
'getSelection',
|
|
1060
1060
|
'getComponentByRefid',
|
|
1061
1061
|
'getComponentStyle',
|
|
@@ -1121,6 +1121,9 @@ function executeReadTool(call, currentBoard, selectedRefids) {
|
|
|
1121
1121
|
// 컴포넌트 인지 가능. (top-level 만 보던 옛 동작 → 자식 누락 사고 방지)
|
|
1122
1122
|
const flatComponents = (0, component_tree_1.collectAllComponents)(board).map(f => f.component);
|
|
1123
1123
|
switch (call.name) {
|
|
1124
|
+
case 'getBoardOutline':
|
|
1125
|
+
/* The summary that used to be pasted into every request - now read when the question needs it. */
|
|
1126
|
+
return board ? summarizeBoard(board) : { empty: true, message: 'No board is open in this conversation.' };
|
|
1124
1127
|
case 'getSelection': {
|
|
1125
1128
|
if (selectedRefids.length === 0) {
|
|
1126
1129
|
return { selected: [], message: 'No component is currently selected.' };
|