@things-factory/board-ai 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 (107) hide show
  1. package/PLAN-styling-tools.md +1 -1
  2. package/dist-client/server/service/assistant.d.ts +5 -4
  3. package/dist-client/server/service/assistant.js +35 -35
  4. package/dist-client/server/service/assistant.js.map +1 -1
  5. package/dist-client/server/service/styling/clear-tools.d.ts +2 -2
  6. package/dist-client/server/service/styling/clear-tools.js.map +1 -1
  7. package/dist-client/server/service/styling/copy-tools.d.ts +3 -3
  8. package/dist-client/server/service/styling/copy-tools.js +1 -1
  9. package/dist-client/server/service/styling/copy-tools.js.map +1 -1
  10. package/dist-client/server/service/styling/effect-tools.d.ts +2 -2
  11. package/dist-client/server/service/styling/effect-tools.js.map +1 -1
  12. package/dist-client/server/service/styling/fill-tools.d.ts +3 -3
  13. package/dist-client/server/service/styling/fill-tools.js +1 -1
  14. package/dist-client/server/service/styling/fill-tools.js.map +1 -1
  15. package/dist-client/server/service/styling/material-tools.d.ts +2 -2
  16. package/dist-client/server/service/styling/material-tools.js.map +1 -1
  17. package/dist-client/server/service/styling/registry.d.ts +6 -6
  18. package/dist-client/server/service/styling/registry.js +2 -2
  19. package/dist-client/server/service/styling/registry.js.map +1 -1
  20. package/dist-client/server/service/styling/stroke-tools.d.ts +2 -2
  21. package/dist-client/server/service/styling/stroke-tools.js.map +1 -1
  22. package/dist-client/server/service/styling/text-tools.d.ts +2 -2
  23. package/dist-client/server/service/styling/text-tools.js.map +1 -1
  24. package/dist-client/server/service/types.d.ts +6 -148
  25. package/dist-client/server/service/types.js.map +1 -1
  26. package/dist-client/server/service/validation/board-model-schema.d.ts +262 -262
  27. package/dist-client/server/service/validation/board-model-schema.js +4 -4
  28. package/dist-client/server/service/validation/board-model-schema.js.map +1 -1
  29. package/dist-client/server/service/validation/tool-validation.js +5 -5
  30. package/dist-client/server/service/validation/tool-validation.js.map +1 -1
  31. package/dist-client/tsconfig.tsbuildinfo +1 -1
  32. package/dist-server/index.d.ts +2 -2
  33. package/dist-server/index.js +2 -2
  34. package/dist-server/index.js.map +1 -1
  35. package/dist-server/service/assistant.d.ts +5 -4
  36. package/dist-server/service/assistant.js +37 -37
  37. package/dist-server/service/assistant.js.map +1 -1
  38. package/dist-server/service/board-ai-resolver.js +2 -2
  39. package/dist-server/service/board-ai-resolver.js.map +1 -1
  40. package/dist-server/service/index.d.ts +0 -1
  41. package/dist-server/service/index.js +0 -1
  42. package/dist-server/service/index.js.map +1 -1
  43. package/dist-server/service/patch-entry/board-patch-subscription.js +1 -1
  44. package/dist-server/service/patch-entry/board-patch-subscription.js.map +1 -1
  45. package/dist-server/service/patch-entry/patch-entry.d.ts +1 -1
  46. package/dist-server/service/patch-entry/patch-entry.js +1 -1
  47. package/dist-server/service/patch-entry/patch-entry.js.map +1 -1
  48. package/dist-server/service/styling/clear-tools.d.ts +2 -2
  49. package/dist-server/service/styling/clear-tools.js.map +1 -1
  50. package/dist-server/service/styling/copy-tools.d.ts +3 -3
  51. package/dist-server/service/styling/copy-tools.js +1 -1
  52. package/dist-server/service/styling/copy-tools.js.map +1 -1
  53. package/dist-server/service/styling/effect-tools.d.ts +2 -2
  54. package/dist-server/service/styling/effect-tools.js.map +1 -1
  55. package/dist-server/service/styling/fill-tools.d.ts +3 -3
  56. package/dist-server/service/styling/fill-tools.js +1 -1
  57. package/dist-server/service/styling/fill-tools.js.map +1 -1
  58. package/dist-server/service/styling/material-tools.d.ts +2 -2
  59. package/dist-server/service/styling/material-tools.js.map +1 -1
  60. package/dist-server/service/styling/registry.d.ts +6 -6
  61. package/dist-server/service/styling/registry.js +2 -2
  62. package/dist-server/service/styling/registry.js.map +1 -1
  63. package/dist-server/service/styling/stroke-tools.d.ts +2 -2
  64. package/dist-server/service/styling/stroke-tools.js.map +1 -1
  65. package/dist-server/service/styling/text-tools.d.ts +2 -2
  66. package/dist-server/service/styling/text-tools.js.map +1 -1
  67. package/dist-server/service/types.d.ts +6 -148
  68. package/dist-server/service/types.js.map +1 -1
  69. package/dist-server/service/validation/board-model-schema.d.ts +262 -262
  70. package/dist-server/service/validation/board-model-schema.js +4 -4
  71. package/dist-server/service/validation/board-model-schema.js.map +1 -1
  72. package/dist-server/service/validation/tool-validation.js +5 -5
  73. package/dist-server/service/validation/tool-validation.js.map +1 -1
  74. package/dist-server/tsconfig.tsbuildinfo +1 -1
  75. package/logs/.5e5d741d8b7784a2fbad65eedc0fd46946aaf6f2-audit.json +5 -0
  76. package/logs/connections-2026-09-17-16.log +0 -0
  77. package/package.json +8 -7
  78. package/server/index.ts +2 -2
  79. package/server/service/assistant-integration.test.ts +44 -44
  80. package/server/service/assistant-llm-smoke.test.ts +5 -5
  81. package/server/service/assistant.test.ts +86 -86
  82. package/server/service/assistant.ts +41 -43
  83. package/server/service/board-ai-resolver.ts +2 -2
  84. package/server/service/index.ts +0 -1
  85. package/server/service/patch-entry/board-patch-subscription.ts +1 -1
  86. package/server/service/patch-entry/patch-entry.ts +2 -2
  87. package/server/service/styling/clear-tools.ts +2 -2
  88. package/server/service/styling/copy-tools.ts +3 -3
  89. package/server/service/styling/effect-tools.ts +2 -2
  90. package/server/service/styling/fill-tools.test.ts +1 -1
  91. package/server/service/styling/fill-tools.ts +4 -4
  92. package/server/service/styling/material-tools.ts +2 -2
  93. package/server/service/styling/registry.ts +6 -6
  94. package/server/service/styling/stroke-tools.test.ts +1 -1
  95. package/server/service/styling/stroke-tools.ts +3 -3
  96. package/server/service/styling/text-tools.ts +2 -2
  97. package/server/service/tool-exposure.test.ts +7 -7
  98. package/server/service/types.ts +9 -110
  99. package/server/service/validation/board-model-schema.test.ts +3 -3
  100. package/server/service/validation/board-model-schema.ts +4 -4
  101. package/server/service/validation/tool-validation.ts +5 -5
  102. package/dist-server/service/apply-patch.d.ts +0 -47
  103. package/dist-server/service/apply-patch.js +0 -369
  104. package/dist-server/service/apply-patch.js.map +0 -1
  105. package/server/service/apply-patch-drift.test.ts +0 -831
  106. package/server/service/apply-patch.test.ts +0 -757
  107. package/server/service/apply-patch.ts +0 -411
@@ -16,8 +16,6 @@ import {
16
16
  } from '@things-factory/board-import'
17
17
  import type {
18
18
  BoardAIAssistant,
19
- BoardEditOp,
20
- BoardEditPatch,
21
19
  ChatOptions,
22
20
  ChatResponse,
23
21
  ComponentSchema,
@@ -49,7 +47,7 @@ import {
49
47
  type ToolGate as NeutralToolGate,
50
48
  type AgenticLoopResult
51
49
  } from '@things-factory/ai-client-base'
52
- import type { BoardActionOp } from './types.js'
50
+ import type { SceneActionOp, SceneEditOp, SceneEditPatch } from '@operato/scene-ops'
53
51
 
54
52
  /** 이 대화면의 도구가 알아야 하는 것 — 루프는 나르기만 한다. */
55
53
  type BoardToolContext = { board: BoardModel | undefined; selectedRefids: number[] }
@@ -246,7 +244,7 @@ export class DefaultBoardAIAssistant implements BoardAIAssistant {
246
244
  return undefined
247
245
  }
248
246
 
249
- const loopResult = await runAgenticLoop<BoardToolContext, BoardActionOp>({
247
+ const loopResult = await runAgenticLoop<BoardToolContext, SceneActionOp>({
250
248
  initialConversation: conversation,
251
249
  tools,
252
250
  options: {
@@ -266,7 +264,7 @@ export class DefaultBoardAIAssistant implements BoardAIAssistant {
266
264
  executeReadTool: (tc, ctx) => wrappedExecuteReadTool(tc, ctx.board, ctx.selectedRefids),
267
265
  /* 문서에서 부품을 꺼내는 일은 **여기서** 한다 — 루프는 board 의 모양을 모른다. */
268
266
  validateWriteToolCall: (tc, ctx) => validateWriteToolCall(tc, (ctx.board?.components ?? []) as any[]),
269
- toolCallToOp: toolCallToBoardActionOp,
267
+ toolCallToOp: toolCallToSceneActionOp,
270
268
  summarizeToolResult
271
269
  },
272
270
  context: { board: currentBoard, selectedRefids }
@@ -277,11 +275,11 @@ export class DefaultBoardAIAssistant implements BoardAIAssistant {
277
275
  const lastText = loopResult.lastText
278
276
  const stopReason = loopResult.stopReason
279
277
 
280
- // accumulated write calls → BoardEditPatch (tool 이 array 반환 가능 — flat)
278
+ // accumulated write calls → SceneEditPatch (tool 이 array 반환 가능 — flat)
281
279
  const components = (currentBoard?.components ?? []) as any[]
282
280
  const ops = accumulatedWriteCalls
283
281
  .flatMap(call => {
284
- const r = toolCallToBoardEditOp(call, components, gate)
282
+ const r = toolCallToSceneEditOp(call, components, gate)
285
283
  if (r === null) return []
286
284
  return Array.isArray(r) ? r : [r]
287
285
  })
@@ -291,7 +289,7 @@ export class DefaultBoardAIAssistant implements BoardAIAssistant {
291
289
  const baseReply = lastText || (ops.length > 0 ? '변경했습니다.' : '')
292
290
  const reply = baseReply + formatAbortNotice(loopResult.abortReason)
293
291
 
294
- let patch: BoardEditPatch | undefined
292
+ let patch: SceneEditPatch | undefined
295
293
  if (ops.length > 0) {
296
294
  patch = {
297
295
  ops,
@@ -390,7 +388,7 @@ export class DefaultBoardAIAssistant implements BoardAIAssistant {
390
388
  아무것도 안 바뀌었기 때문.
391
389
 
392
390
  룰:
393
- 1. 변경 요청 → write tool 호출 필수 (modifyBoard / setFill / setStroke / setMaterial /
391
+ 1. 변경 요청 → write tool 호출 필수 (modifyScene / setFill / setStroke / setMaterial /
394
392
  setShadow / setOpacity / setVisible / setFont / setText / setMaterial / addComponent /
395
393
  removeComponentByRefid / modifyComponentByRefid / align/distribute/group/zorder 등)
396
394
  2. 호출 + 짧은 reply 텍스트 동시 작성 OK — reply 는 *결과* 를 한국어로 한두 문장 요약
@@ -414,22 +412,22 @@ root-level properties separate from any child:
414
412
  - 3D 환경: \`sky\` / \`skyColor\` / \`exposure\` / \`hemi*\` / \`dirLight*\` / \`dirShadow*\` / \`camera*\` / \`autoRotate\`
415
413
  - 식별: \`id\`, \`refid\`, \`type\` ("model-layer")
416
414
  ⚠ 보드 \*\*name\*\* 은 root attribute 가 아님 — Board 엔티티의 DB 컬럼이지 scene
417
- MODEL 의 필드가 아님 (model-layer 가 무시). 보드 라벨 변경은 modifyBoard 영역 X.
415
+ MODEL 의 필드가 아님 (model-layer 가 무시). 보드 라벨 변경은 modifyScene 영역 X.
418
416
  • Children: an array of components inside the board (rect / circle / text /
419
417
  gauge / etc.). 컴포넌트들은 \`name\` (user label) 를 가질 수 있음 — 이는 child
420
418
  component 의 속성이지 board root 의 속성이 아님.
421
419
 
422
420
  Rules:
423
421
  - To change a board-level attribute (background, board size, 3D atmosphere)
424
- → call \`modifyBoard({ patch: { fillStyle: ..., width: ..., sky: ..., exposure: ..., dirLight*: ..., hemi*: ... } })\`.
422
+ → call \`modifyScene({ patch: { fillStyle: ..., width: ..., sky: ..., exposure: ..., dirLight*: ..., hemi*: ... } })\`.
425
423
  NOTE: 보드 라벨/제목 ("보드 이름") 은 scene model 이 아니라 Board *엔티티* 의 컬럼.
426
- things-scene 의 model-layer 는 \`name\` 을 인식하지 않음. modifyBoard 의 patch 에
424
+ things-scene 의 model-layer 는 \`name\` 을 인식하지 않음. modifyScene 의 patch 에
427
425
  \`name\` 을 넣지 말 것 — 죽은 필드로 박힐 뿐 효과 없음.
428
426
  - To change a CHILD COMPONENT's attribute → call \`modifyComponentByRefid\`.
429
- - DO NOT use modifyBoard to change a child. DO NOT use modifyComponentByRefid
427
+ - DO NOT use modifyScene to change a child. DO NOT use modifyComponentByRefid
430
428
  to change board background.
431
429
  - "보드 배경색", "board background", "테두리/캔버스 크기" 같은 요청은 root
432
- attribute 변경 → \`modifyBoard\`.
430
+ attribute 변경 → \`modifyScene\`.
433
431
 
434
432
  ==== COMPONENT TREE (NESTING) ====
435
433
  컴포넌트는 *재귀 구조*. group / container 같은 부모 컴포넌트는 자기 \`components\`
@@ -495,8 +493,8 @@ to that wording in your reply. Do not echo refid back to the user.
495
493
 
496
494
  ==== AVAILABLE TOOLS ====
497
495
  Board-level (root attributes):
498
- - modifyBoard: change board's own fillStyle / width / height / fitMode / translate / scale AND 3D atmosphere (sky / skyColor / exposure / hemiSkyColor / hemiGroundColor / hemiIntensity / dirLightEnabled / dirLightColor / dirLightIntensity / dirLightFollowCamera / dirLightAzimuth / dirLightElevation / dirShadow* / camera* / autoRotate / initialCameraView). ⚠ "name" 금지 — scene MODEL 이 아니라 Board 엔티티 컬럼.
499
- - replaceBoard: wholesale board reset (heavy — prefer modifyBoard for most changes)
496
+ - modifyScene: change board's own fillStyle / width / height / fitMode / translate / scale AND 3D atmosphere (sky / skyColor / exposure / hemiSkyColor / hemiGroundColor / hemiIntensity / dirLightEnabled / dirLightColor / dirLightIntensity / dirLightFollowCamera / dirLightAzimuth / dirLightElevation / dirShadow* / camera* / autoRotate / initialCameraView). ⚠ "name" 금지 — scene MODEL 이 아니라 Board 엔티티 컬럼.
497
+ - replaceBoard: wholesale board reset (heavy — prefer modifyScene for most changes)
500
498
  Child components — data:
501
499
  - addComponent: add a new component (parallel calls for multiple adds)
502
500
  - removeComponentByRefid: remove a child by refid
@@ -553,10 +551,10 @@ Modeller actions (ephemeral — does NOT modify the board model, no undo, no sav
553
551
  - fitToView: reset view so the board fits the canvas (mode: fit/ratio/width/height)
554
552
  - setSceneMode: switch modeller between 'edit' and 'view' modes
555
553
  Knowledge:
556
- - getAtmosphereGuide: 3D atmosphere synthesis primer — parameter physics, ranges, color theory, archetype recipes (time-of-day / indoor / surreal). Call BEFORE applying atmospheric/styling modifyBoard for any natural-language mood request.
554
+ - getAtmosphereGuide: 3D atmosphere synthesis primer — parameter physics, ranges, color theory, archetype recipes (time-of-day / indoor / surreal). Call BEFORE applying atmospheric/styling modifyScene for any natural-language mood request.
557
555
 
558
556
  ==== 3D ATMOSPHERE SYNTHESIS ====
559
- 3D scene atmosphere = combination of (1) sky preset / color (2) exposure (3) hemisphere ambient (4) directional sun/key (5) floor material. ALL of these are root-level board attributes — applied via a SINGLE modifyBoard call.
557
+ 3D scene atmosphere = combination of (1) sky preset / color (2) exposure (3) hemisphere ambient (4) directional sun/key (5) floor material. ALL of these are root-level board attributes — applied via a SINGLE modifyScene call.
560
558
 
561
559
  For ANY natural-language atmospheric request — "공장 분위기로", "심해 컬러", "황혼", "사이버펑크", "안개 자욱한 새벽", "1980 우주정거장", "따뜻하게", "차갑고 극적으로" — follow this workflow:
562
560
 
@@ -564,11 +562,11 @@ For ANY natural-language atmospheric request — "공장 분위기로", "심해
564
562
  2. From the guide's "archetypes" section, pick the closest base recipe (time-of-day / indoor / surreal). Interpolate if user combines multiple cues.
565
563
  3. Apply user's nuance via the guide's "modifiers" (warm/cool, drama, saturation, contrast).
566
564
  4. Sanity-check: avoid washout (all params maxed) or mud (all minned). Confirm hemi sky/ground colors and dir color are consistent with the mood.
567
- 5. Apply ALL keys in ONE modifyBoard({patch}) call (sky, skyColor?, exposure, hemiIntensity, hemiSkyColor, hemiGroundColor, dirLightEnabled, dirLightColor, dirLightIntensity, dirLightFollowCamera=false, dirLightAzimuth, dirLightElevation, fillStyle?). ONE patch — not multiple modifyBoard calls.
565
+ 5. Apply ALL keys in ONE modifyScene({patch}) call (sky, skyColor?, exposure, hemiIntensity, hemiSkyColor, hemiGroundColor, dirLightEnabled, dirLightColor, dirLightIntensity, dirLightFollowCamera=false, dirLightAzimuth, dirLightElevation, fillStyle?). ONE patch — not multiple modifyScene calls.
568
566
  6. In the reply, briefly state which archetype + modifier you composed.
569
567
 
570
568
  Avoid:
571
- - Calling modifyBoard with only "fillStyle" for atmospheric requests — that only changes 2D background and floor tint, NOT the 3D scene mood.
569
+ - Calling modifyScene with only "fillStyle" for atmospheric requests — that only changes 2D background and floor tint, NOT the 3D scene mood.
572
570
  - Mixing a sky preset with explicit skyColor (preset wins, skyColor ignored).
573
571
  - Setting dirLightAzimuth/Elevation while leaving dirLightFollowCamera=true (no effect — set follow=false first).
574
572
  - Using only canned presets when the user describes a custom mood — the guide enables synthesis beyond presets.
@@ -761,7 +759,7 @@ export function buildBoardEditTools(knownTypes: string[], exposure: ToolExposure
761
759
  }
762
760
  },
763
761
  {
764
- name: 'modifyBoard',
762
+ name: 'modifyScene',
765
763
  description:
766
764
  "Modify the BOARD's OWN root-level properties — the board itself is the topmost parent (model-layer) and has its own attributes (NOT child components). Valid root keys (things-scene 의 model-layer 가 실제 인식하는 것만):\n • Layout/Geometry: `width`, `height`, `fitMode` ('ratio'|'both'|'width'|'height'|'center'|'none'), `translate` ({x,y}), `scale` ({x,y})\n • 2D 배경: `fillStyle` (string color or object)\n • 3D Sky: `sky` (preset 이름), `skyColor` (preset 미지정 시 단색)\n • 3D Tone: `exposure` (toneMappingExposure, 기본 1.0~1.2)\n • 3D Hemisphere light: `hemiSkyColor`, `hemiGroundColor`, `hemiIntensity` (기본 4.0)\n • 3D Directional light: `dirLightEnabled` (bool), `dirLightColor`, `dirLightIntensity` (기본 2.0), `dirLightFollowCamera` (bool, 기본 true), `dirLightAzimuth` (deg, follow=false 일 때만), `dirLightElevation` (deg, follow=false 일 때만)\n • 3D Shadow: `dirShadowEnabled`, `dirShadowMapSize` (기본 2048), `dirShadowBias`\n • 3D Camera: `cameraX`, `cameraY`, `cameraZ`, `cameraAutoPlay` ('orbit' 등), `autoRotate` (bool), `initialCameraView`\n\n⚠ 절대 사용 금지: `name` — Board *엔티티* 의 컬럼이지 scene MODEL 의 필드가 아님. things-scene 이 인식 안 함. 보드 라벨 변경은 GraphQL boardPatch 의 영역.\n\nDo NOT use for child component edits (use modifyComponentByRefid for those). Do NOT pass a `components` array here — children are managed by add/remove/modify ops separately.",
767
765
  parameters: {
@@ -780,7 +778,7 @@ export function buildBoardEditTools(knownTypes: string[], exposure: ToolExposure
780
778
  {
781
779
  name: 'replaceBoard',
782
780
  description:
783
- 'Replace the entire board (use ONLY for fresh start or wholesale redesign — heavy operation that resets undo history). For just changing background or size, prefer modifyBoard. Provide width/height and the full components array.',
781
+ 'Replace the entire board (use ONLY for fresh start or wholesale redesign — heavy operation that resets undo history). For just changing background or size, prefer modifyScene. Provide width/height and the full components array.',
784
782
  parameters: {
785
783
  type: 'object',
786
784
  properties: {
@@ -856,7 +854,7 @@ export function buildBoardEditTools(knownTypes: string[], exposure: ToolExposure
856
854
  {
857
855
  name: 'getAtmosphereGuide',
858
856
  description:
859
- "Get a comprehensive 3D atmosphere synthesis guide — parameter physics (sky / exposure / hemisphere / directional / shadow / floor / camera), sane ranges, color theory, time-of-day archetypes, and indoor/surreal mood recipes. Call this BEFORE applying atmospheric/styling changes via modifyBoard whenever the user describes a mood / scene / time-of-day in natural language (e.g. '심해 컬러', '황혼', '공장 분위기', '사이버펑크', '안개 자욱한 새벽'). The guide enables you to synthesize concrete parameter values yourself, beyond canned presets.",
857
+ "Get a comprehensive 3D atmosphere synthesis guide — parameter physics (sky / exposure / hemisphere / directional / shadow / floor / camera), sane ranges, color theory, time-of-day archetypes, and indoor/surreal mood recipes. Call this BEFORE applying atmospheric/styling changes via modifyScene whenever the user describes a mood / scene / time-of-day in natural language (e.g. '심해 컬러', '황혼', '공장 분위기', '사이버펑크', '안개 자욱한 새벽'). The guide enables you to synthesize concrete parameter values yourself, beyond canned presets.",
860
858
  parameters: { type: 'object', properties: {} }
861
859
  },
862
860
  {
@@ -1215,7 +1213,7 @@ const WRITE_TOOL_NAMES = new Set([
1215
1213
  'setShadowProperties',
1216
1214
  'clearStyle',
1217
1215
  'copyStyle',
1218
- 'modifyBoard',
1216
+ 'modifyScene',
1219
1217
  'replaceBoard',
1220
1218
  // Phase 2 — model 차원 scene 조작 (좌표/구조/zorder)
1221
1219
  'alignComponents',
@@ -1232,7 +1230,7 @@ const WRITE_TOOL_NAMES = new Set([
1232
1230
  ])
1233
1231
  /**
1234
1232
  * C-1 — ephemeral scene 조작 (selection / view / mode). model 변경 X, undo 영향 X.
1235
- * 별도 ChatResponse.actions 채널로 전달 → 호스트가 board-action-execute 로 적용.
1233
+ * 별도 ChatResponse.actions 채널로 전달 → 호스트가 scene-action-execute 로 적용.
1236
1234
  */
1237
1235
  const ACTION_TOOL_NAMES = new Set([
1238
1236
  'selectComponents',
@@ -1377,14 +1375,14 @@ export function executeReadTool(
1377
1375
  *
1378
1376
  * 의도 — preset 한 단어로 끝나지 않는 사용자 요청 ("심해", "황혼", "1980 우주정거장",
1379
1377
  * "안개 새벽") 에 AI 가 구체적 파라미터 값을 직접 합성하도록 물리/색채/미학 지식
1380
- * 동시 제공. AI 는 이 가이드를 받은 뒤 modifyBoard 로 한 번에 적용.
1378
+ * 동시 제공. AI 는 이 가이드를 받은 뒤 modifyScene 로 한 번에 적용.
1381
1379
  *
1382
- * things-scene 의 board model 에 들어가는 root level 키만 다룸 (modifyBoard 로
1380
+ * things-scene 의 board model 에 들어가는 root level 키만 다룸 (modifyScene 로
1383
1381
  * 적용 가능한 것). camera position 같은 ephemeral 한 것은 별도 영역.
1384
1382
  */
1385
1383
  const ATMOSPHERE_GUIDE = {
1386
1384
  overview:
1387
- '3D 보드 분위기는 (1) sky preset 또는 단색 (2) tone mapping exposure (3) hemisphere ambient (4) directional sun/key (5) floor 의 조합. 모두 board root model 의 속성이라 modifyBoard 단일 op 로 한 번에 적용. 자연어 요청은 archetype 레시피를 base 로 잡고 modifier 로 fine-tune.',
1385
+ '3D 보드 분위기는 (1) sky preset 또는 단색 (2) tone mapping exposure (3) hemisphere ambient (4) directional sun/key (5) floor 의 조합. 모두 board root model 의 속성이라 modifyScene 단일 op 로 한 번에 적용. 자연어 요청은 archetype 레시피를 base 로 잡고 modifier 로 fine-tune.',
1388
1386
 
1389
1387
  parameters: {
1390
1388
  sky: {
@@ -1724,7 +1722,7 @@ const ATMOSPHERE_GUIDE = {
1724
1722
  '2. archetypes 에서 가장 가까운 base 레시피 선택.',
1725
1723
  '3. modifiers 로 사용자 요청 뉘앙스 fine-tune (warm/cool/drama/...).',
1726
1724
  '4. sanity check — exposure × hemi × dir 어느 한쪽 극단 아닌지, hemi sky/ground 색 모순 없는지.',
1727
- "5. modifyBoard({ patch: { ...combined keys... } }) 한 번에 적용. 사용자에게 어떤 archetype + modifier 조합으로 갔는지 한 줄로 설명."
1725
+ "5. modifyScene({ patch: { ...combined keys... } }) 한 번에 적용. 사용자에게 어떤 archetype + modifier 조합으로 갔는지 한 줄로 설명."
1728
1726
  ],
1729
1727
 
1730
1728
  pitfalls: [
@@ -1746,7 +1744,7 @@ const ATMOSPHERE_GUIDE = {
1746
1744
  * 사용 흐름:
1747
1745
  * 1. 사용자 의도 (산업 / 금융 / 의료 / SF / ...) 식별
1748
1746
  * 2. recommendations 에서 매칭 후보 1~2개 추림
1749
- * 3. modifyBoard 로 board.fillStyle 을 palette.base 로 설정
1747
+ * 3. modifyScene 로 board.fillStyle 을 palette.base 로 설정
1750
1748
  * 4. addPrefab / addComponent 시 palette 의 surface/accent/text 사용
1751
1749
  * 5. 일관된 룩 → 한 번 정한 팔레트 안에서만 색 선택
1752
1750
  *
@@ -1877,7 +1875,7 @@ const COLOR_PALETTES = {
1877
1875
  workflow: [
1878
1876
  '1. 사용자 도메인 / 분위기 키워드 식별 — 산업/금융/관제/의료/SF/프레젠테이션',
1879
1877
  '2. recommendations 에서 매칭 후보 추림. 사용자 모호하면 가장 흔한 industrial-steel 디폴트.',
1880
- "3. modifyBoard({ patch: { fillStyle: palette.base } }) 으로 보드 배경 set.",
1878
+ "3. modifyScene({ patch: { fillStyle: palette.base } }) 으로 보드 배경 set.",
1881
1879
  '4. 이후 모든 add / addPrefab 호출 시 palette.surface / accent / text / border 사용. 자유 색 X.',
1882
1880
  '5. status 표시는 success / warning / danger / info 4종에서만 — 자체 hex 만들지 마라.'
1883
1881
  ],
@@ -2109,13 +2107,13 @@ function buildExternalCategoryDefinitions(
2109
2107
  }
2110
2108
 
2111
2109
  /**
2112
- * Tool call → BoardActionOp 변환 (C-1 ephemeral scene 조작).
2110
+ * Tool call → SceneActionOp 변환 (C-1 ephemeral scene 조작).
2113
2111
  * 잘못된 인자는 null — agentic loop 가 Unknown tool 비슷하게 처리.
2114
2112
  */
2115
- export function toolCallToBoardActionOp(
2113
+ export function toolCallToSceneActionOp(
2116
2114
  tc: AIToolCall,
2117
2115
  gate: ToolGate = buildToolGate()
2118
- ): import('./types.js').BoardActionOp | null {
2116
+ ): import('@operato/scene-ops').SceneActionOp | null {
2119
2117
  const args = tc.arguments ?? {}
2120
2118
  /* 씬 조작 액션은 전부 코어(보드) 능력 — 노출 안 된 대화에서는 만들지 않는다.
2121
2119
  * loop 는 gate.isAction 으로 이미 걸러내지만, 다른 호출부의 오용도 막는다. */
@@ -2147,7 +2145,7 @@ export function toolCallToBoardActionOp(
2147
2145
  }
2148
2146
  }
2149
2147
 
2150
- // ── Tool call → BoardEditOp 변환 ───────────────────────────────────
2148
+ // ── Tool call → SceneEditOp 변환 ───────────────────────────────────
2151
2149
 
2152
2150
  /**
2153
2151
  * AIToolChatResult → ChatResponse.
@@ -2160,14 +2158,14 @@ export function toolCallToBoardActionOp(
2160
2158
  export function toolResultToChatResponse(result: AIToolChatResult): ChatResponse {
2161
2159
  const ops = result.toolCalls
2162
2160
  .flatMap(call => {
2163
- const r = toolCallToBoardEditOp(call)
2161
+ const r = toolCallToSceneEditOp(call)
2164
2162
  if (r === null) return []
2165
2163
  return Array.isArray(r) ? r : [r]
2166
2164
  })
2167
2165
 
2168
2166
  const reply = result.text || (ops.length > 0 ? '변경했습니다.' : '')
2169
2167
 
2170
- let patch: BoardEditPatch | undefined
2168
+ let patch: SceneEditPatch | undefined
2171
2169
  if (ops.length > 0) {
2172
2170
  patch = {
2173
2171
  ops,
@@ -2179,11 +2177,11 @@ export function toolResultToChatResponse(result: AIToolChatResult): ChatResponse
2179
2177
  return { reply, patch, followUp: undefined }
2180
2178
  }
2181
2179
 
2182
- export function toolCallToBoardEditOp(
2180
+ export function toolCallToSceneEditOp(
2183
2181
  tc: AIToolCall,
2184
2182
  components: ReadonlyArray<any> = [],
2185
2183
  gate: ToolGate = buildToolGate()
2186
- ): BoardEditOp | BoardEditOp[] | null {
2184
+ ): SceneEditOp | SceneEditOp[] | null {
2187
2185
  const args = tc.arguments ?? {}
2188
2186
  /* 노출되지 않은 코어(보드 편집) 도구는 op 를 만들지 않는다 — 정의를 숨겼어도 호출은 올 수 있다. */
2189
2187
  if (!gate.coreOn && !gate.spec(tc.name)) return null
@@ -2194,7 +2192,7 @@ export function toolCallToBoardEditOp(
2194
2192
  return dispatchStylingTool(tc.name, args, components)
2195
2193
  }
2196
2194
  // tool-registry plugin point — 외부 등록 'write' 카테고리 dispatch.
2197
- // builder 결과는 BoardEditOp[] | { ops, errors? } | null 셋 중 하나.
2195
+ // builder 결과는 SceneEditOp[] | { ops, errors? } | null 셋 중 하나.
2198
2196
  // (read/external 카테고리는 별도 dispatch path — agentic loop 의 executeReadTool 이 직접 처리.)
2199
2197
  const registrySpec = gate.spec(tc.name)
2200
2198
  if (registrySpec && registrySpec.kind === 'write') {
@@ -2220,15 +2218,15 @@ export function toolCallToBoardEditOp(
2220
2218
  // 모든 styling tool (setFill/setStroke/setText/setFont/setShadow/setOpacity/
2221
2219
  // setVisible/setMaterial/setShadowProperties/clearStyle/copyStyle) 은 registry
2222
2220
  // 가 처리 — 상단 isStylingTool 분기 참조
2223
- case 'modifyBoard': {
2221
+ case 'modifyScene': {
2224
2222
  if (!args.patch || typeof args.patch !== 'object' || Array.isArray(args.patch)) return null
2225
- return { op: 'modifyBoard', patch: args.patch as Partial<BoardModel> }
2223
+ return { op: 'modifyScene', patch: args.patch as Partial<BoardModel> }
2226
2224
  }
2227
2225
  case 'replaceBoard': {
2228
2226
  if (!Array.isArray(args.components)) return null
2229
2227
  return {
2230
2228
  op: 'replace',
2231
- board: {
2229
+ model: {
2232
2230
  width: typeof args.width === 'number' ? args.width : undefined,
2233
2231
  height: typeof args.height === 'number' ? args.height : undefined,
2234
2232
  fillStyle: typeof args.fillStyle === 'string' ? args.fillStyle : undefined,
@@ -190,7 +190,7 @@ class BoardAIChatOutput {
190
190
  @Field({ description: 'Conversational reply.' })
191
191
  reply: string
192
192
 
193
- @Field(() => GraphQLJSON, { nullable: true, description: 'BoardEditPatch.' })
193
+ @Field(() => GraphQLJSON, { nullable: true, description: 'SceneEditPatch.' })
194
194
  patch?: any
195
195
 
196
196
  @Field({ nullable: true, description: 'Clarifying question when input is ambiguous.' })
@@ -221,7 +221,7 @@ class BoardAIChatOutput {
221
221
  @Field(() => GraphQLJSON, {
222
222
  nullable: true,
223
223
  description:
224
- 'Ephemeral scene actions (selection/view/mode) — sequence of BoardActionOp. Applied via board-action-execute event on the host. Distinct from `patch` which carries persistent model changes.'
224
+ 'Ephemeral scene actions (selection/view/mode) — sequence of SceneActionOp. Applied via scene-action-execute event on the host. Distinct from `patch` which carries persistent model changes.'
225
225
  })
226
226
  actions?: any
227
227
 
@@ -16,7 +16,6 @@ import { entities as PatchEntryEntities } from './patch-entry/index.js'
16
16
 
17
17
  export * from './types.js'
18
18
  export * from './assistant.js'
19
- export * from './apply-patch.js'
20
19
  export * from './prefab-registry.js'
21
20
  export * from './mention-resolver.js'
22
21
  export * from './catalog-resolver.js'
@@ -2,7 +2,7 @@
2
2
  * BoardPatchSubscription (Phase Board-AI C1 — op-브로드캐스트 spine).
3
3
  *
4
4
  * PatchEntry(AI 또는 user-direct 편집) 가 생성될 때마다 같은 board 구독자에게 방송 →
5
- * 실시간 공동편집의 척추. 클라이언트(C2)가 구독해 원격 patch 를 applyBoardEditPatch 로 적용.
5
+ * 실시간 공동편집의 척추. 클라이언트(C2)가 구독해 원격 patch 를 applyScenePatch 로 적용.
6
6
  *
7
7
  * 전송/패턴은 기존 board-service/board-subscription.ts 복제 (PubSub + graphql-ws, domain 격리).
8
8
  * 순수 로직(토픽/필터)은 board-patch-broadcast.ts (테스트 가능). 여기엔 pubsub 의존만.
@@ -48,7 +48,7 @@ export class PatchEntry {
48
48
  @Field({ description: "'ai' | 'user-direct' | 'import'" })
49
49
  source!: string
50
50
 
51
- /** BoardEditOp[] JSON-stringified — column 으로는 long text. */
51
+ /** SceneEditOp[] JSON-stringified — column 으로는 long text. */
52
52
  @Column({
53
53
  type:
54
54
  DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
@@ -63,7 +63,7 @@ export class PatchEntry {
63
63
  ops!: string
64
64
 
65
65
  /** GraphQL 노출용 — JSON 객체로 변환. */
66
- @Field(type => GraphQLJSON, { description: 'BoardEditOp[] (parsed JSON array).' })
66
+ @Field(type => GraphQLJSON, { description: 'SceneEditOp[] (parsed JSON array).' })
67
67
  get opsJson(): any {
68
68
  try {
69
69
  return JSON.parse(this.ops || '[]')
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * preset 적용 전 / 데이터 바인딩 적용 전 깨끗한 상태로 돌리기 위한 helper.
5
5
  */
6
- import type { BoardEditOp } from '../types'
6
+ import type { SceneEditOp } from '@operato/scene-ops'
7
7
 
8
8
  export interface ClearStyleInput {
9
9
  refids: number[]
@@ -11,7 +11,7 @@ export interface ClearStyleInput {
11
11
  }
12
12
 
13
13
  export interface OpsResult {
14
- ops: BoardEditOp[]
14
+ ops: SceneEditOp[]
15
15
  errors: string[]
16
16
  }
17
17
 
@@ -9,7 +9,7 @@
9
9
  * 입력 — sourceRefid 의 실제 styling 값을 미리 알 필요 없음. server 가 components
10
10
  * 에서 source 찾아 styling 추출 후 target 들에 patch 생성.
11
11
  */
12
- import type { BoardEditOp } from '../types'
12
+ import type { SceneEditOp } from '@operato/scene-ops'
13
13
 
14
14
  export interface CopyStyleInput {
15
15
  sourceRefid: number
@@ -18,7 +18,7 @@ export interface CopyStyleInput {
18
18
  }
19
19
 
20
20
  export interface OpsResult {
21
- ops: BoardEditOp[]
21
+ ops: SceneEditOp[]
22
22
  errors: string[]
23
23
  }
24
24
 
@@ -52,7 +52,7 @@ const DEFAULT_ASPECTS: Array<keyof typeof ASPECT_FIELDS> = [
52
52
  ]
53
53
 
54
54
  /**
55
- * input + components 배열 → BoardEditOp[].
55
+ * input + components 배열 → SceneEditOp[].
56
56
  *
57
57
  * components 는 board 의 모든 컴포넌트 (caller 가 전달). source 찾아 styling 추출.
58
58
  */
@@ -5,7 +5,7 @@
5
5
  * setOpacity: state.alpha (컴포넌트 전체 알파, 0..1)
6
6
  * setVisible: state.hidden 토글 (true → 숨김)
7
7
  */
8
- import type { BoardEditOp } from '../types'
8
+ import type { SceneEditOp } from '@operato/scene-ops'
9
9
  import { normalizeColor, validateColor } from './color-utils'
10
10
 
11
11
  export interface SetShadowInput {
@@ -34,7 +34,7 @@ export interface SetVisibleInput {
34
34
  }
35
35
 
36
36
  export interface OpsResult {
37
- ops: BoardEditOp[]
37
+ ops: SceneEditOp[]
38
38
  errors: string[]
39
39
  }
40
40
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * fill-tools — setFill 입력 → BoardEditOp[] 변환 검증.
2
+ * fill-tools — setFill 입력 → SceneEditOp[] 변환 검증.
3
3
  *
4
4
  * 4 modes (none/solid/gradient/pattern) 별 동작 + 검증 + multi-target.
5
5
  *
@@ -17,7 +17,7 @@
17
17
  * 이전 2-op (null + new) 방식은 중간 null 상태가 onchange / 3D texture
18
18
  * 크래시 유발 → 제거. 정확한 colorStops 출력으로 stale 필드 문제는 자체 해결.
19
19
  */
20
- import type { BoardEditOp } from '../types'
20
+ import type { SceneEditOp } from '@operato/scene-ops'
21
21
  import { parseColor, normalizeColor, validateColor } from './color-utils'
22
22
 
23
23
  export interface SetFillInput {
@@ -59,12 +59,12 @@ export interface SetFillInput {
59
59
  }
60
60
 
61
61
  export interface SetFillResult {
62
- ops: BoardEditOp[]
62
+ ops: SceneEditOp[]
63
63
  errors: string[] // refid 별 에러 (성공한 것은 op 만 발사)
64
64
  }
65
65
 
66
66
  /**
67
- * setFill 입력 검증 + BoardEditOp[] 생성.
67
+ * setFill 입력 검증 + SceneEditOp[] 생성.
68
68
  *
69
69
  * 기본 정책:
70
70
  * - refids 비어있거나 mode 도 색도 없으면 에러
@@ -73,7 +73,7 @@ export interface SetFillResult {
73
73
  */
74
74
  export function buildSetFillOps(input: SetFillInput): SetFillResult {
75
75
  const errors: string[] = []
76
- const ops: BoardEditOp[] = []
76
+ const ops: SceneEditOp[] = []
77
77
 
78
78
  if (!Array.isArray(input.refids) || input.refids.length === 0) {
79
79
  return { ops: [], errors: ['refids must be a non-empty array of numbers'] }
@@ -4,7 +4,7 @@
4
4
  * Material3D (`things-scene/src/threed/material-3d.ts`) 와 1:1 매핑.
5
5
  * preset 만 줘도 변경 가능 (resolveMaterial3d 가 default 위 override 적용).
6
6
  */
7
- import type { BoardEditOp } from '../types'
7
+ import type { SceneEditOp } from '@operato/scene-ops'
8
8
  import { normalizeColor, validateColor } from './color-utils'
9
9
 
10
10
  const MATERIAL_PRESETS = ['default', 'metal', 'glass', 'plastic', 'wood', 'ceramic', 'rubber', 'custom'] as const
@@ -29,7 +29,7 @@ export interface SetShadowPropertiesInput {
29
29
  }
30
30
 
31
31
  export interface OpsResult {
32
- ops: BoardEditOp[]
32
+ ops: SceneEditOp[]
33
33
  errors: string[]
34
34
  }
35
35
 
@@ -12,10 +12,10 @@
12
12
  * STYLING_TOOLS 는 초기엔 비어있고, 한 tool 씩 점진 이전한다. 각 단계마다
13
13
  * 기존 hardcoded 정의/dispatch 와 byte-단위 동등성을 단위 테스트가 잠금.
14
14
  *
15
- * 비-styling write tool (addComponent / modifyBoard / alignComponents 등) 은
15
+ * 비-styling write tool (addComponent / modifyScene / alignComponents 등) 은
16
16
  * 본 registry 의 영역이 아니다 — 의미·구조가 styling 패턴과 달라 별도 dispatch.
17
17
  */
18
- import type { BoardEditOp } from '../types'
18
+ import type { SceneEditOp } from '@operato/scene-ops'
19
19
  import { buildSetFillOps } from './fill-tools'
20
20
  import { buildSetStrokeOps } from './stroke-tools'
21
21
  import { buildSetTextOps, buildSetFontOps } from './text-tools'
@@ -25,7 +25,7 @@ import { buildClearStyleOps } from './clear-tools'
25
25
  import { buildCopyStyleOps } from './copy-tools'
26
26
 
27
27
  export interface OpsResult {
28
- ops: BoardEditOp[]
28
+ ops: SceneEditOp[]
29
29
  errors: string[]
30
30
  }
31
31
 
@@ -393,12 +393,12 @@ export function buildStylingToolDefinitions(
393
393
  }
394
394
 
395
395
  /**
396
- * AIToolCall name 에 매칭되는 styling tool 의 builder 호출 → BoardEditOp[] | null.
396
+ * AIToolCall name 에 매칭되는 styling tool 의 builder 호출 → SceneEditOp[] | null.
397
397
  *
398
398
  * 반환:
399
399
  * - 매칭 spec 없음 → null (caller 가 다른 카테고리 dispatch 시도)
400
400
  * - 빈 ops (validation 실패 등) → null (assistant.ts 의 기존 동작과 동일)
401
- * - 정상 → BoardEditOp[]
401
+ * - 정상 → SceneEditOp[]
402
402
  *
403
403
  * components 인자는 copyStyle 같은 source-lookup 필요 tool 에만 의미 있음.
404
404
  */
@@ -407,7 +407,7 @@ export function dispatchStylingTool(
407
407
  args: any,
408
408
  components: ReadonlyArray<any> = [],
409
409
  registry: ReadonlyArray<StylingToolSpec> = STYLING_TOOLS
410
- ): BoardEditOp[] | null {
410
+ ): SceneEditOp[] | null {
411
411
  const spec = registry.find(s => s.name === name)
412
412
  if (!spec) return null
413
413
  const result = spec.builder(args, components)
@@ -1,5 +1,5 @@
1
1
  /**
2
- * stroke-tools — buildSetStrokeOps 입력 → BoardEditOp[] 변환 검증.
2
+ * stroke-tools — buildSetStrokeOps 입력 → SceneEditOp[] 변환 검증.
3
3
  *
4
4
  * things-scene canonical fields (모두 top-level): strokeStyle (string color), lineWidth,
5
5
  * lineDash (preset 이름 string), lineCap, lineJoin. tool 파라미터 이름이 engine 필드와
@@ -14,7 +14,7 @@
14
14
  * 입력 단순화 — AI 친화적 alias 도 받아 things-scene preset 으로 매핑:
15
15
  * AI 'dashed' → 'dash', 'dotted' → 'square-dot', 'dashdot' → 'dash-dot'.
16
16
  */
17
- import type { BoardEditOp } from '../types'
17
+ import type { SceneEditOp } from '@operato/scene-ops'
18
18
  import { normalizeColor, validateColor } from './color-utils'
19
19
 
20
20
  export interface SetStrokeInput {
@@ -44,7 +44,7 @@ export interface SetStrokeInput {
44
44
  }
45
45
 
46
46
  export interface SetStrokeResult {
47
- ops: BoardEditOp[]
47
+ ops: SceneEditOp[]
48
48
  errors: string[]
49
49
  }
50
50
 
@@ -140,6 +140,6 @@ export function buildSetStrokeOps(input: SetStrokeInput): SetStrokeResult {
140
140
  patch.lineWidth = 0
141
141
  }
142
142
 
143
- const ops: BoardEditOp[] = refids.map(refid => ({ op: 'modify', refid, patch }))
143
+ const ops: SceneEditOp[] = refids.map(refid => ({ op: 'modify', refid, patch }))
144
144
  return { ops, errors: [] }
145
145
  }
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * 두 tool 분리 — 의도가 다름. 내용 변경 vs 시각 스타일.
9
9
  */
10
- import type { BoardEditOp } from '../types'
10
+ import type { SceneEditOp } from '@operato/scene-ops'
11
11
  import { normalizeColor, validateColor } from './color-utils'
12
12
 
13
13
  export interface SetTextInput {
@@ -44,7 +44,7 @@ const VALID_TEXT_BASELINE = new Set([
44
44
  ])
45
45
 
46
46
  export interface OpsResult {
47
- ops: BoardEditOp[]
47
+ ops: SceneEditOp[]
48
48
  errors: string[]
49
49
  }
50
50
 
@@ -20,8 +20,8 @@ import {
20
20
  import {
21
21
  buildBoardEditTools,
22
22
  buildToolGate,
23
- toolCallToBoardEditOp,
24
- toolCallToBoardActionOp
23
+ toolCallToSceneEditOp,
24
+ toolCallToSceneActionOp
25
25
  } from './assistant'
26
26
 
27
27
  /* 두 개의 가짜 외부 카테고리 — 하나는 보드 저작 계열, 하나는 운영 계열. */
@@ -95,29 +95,29 @@ describe('도구 노출 게이트', () => {
95
95
  const gate = buildToolGate({ boardTools: false, toolCategories: ['test-ops'] })
96
96
  const components = [{ refid: 7, type: 'rect' }]
97
97
 
98
- const add = toolCallToBoardEditOp({ id: 'x', name: 'addComponent', arguments: { type: 'rect' } } as any, components, gate)
98
+ const add = toolCallToSceneEditOp({ id: 'x', name: 'addComponent', arguments: { type: 'rect' } } as any, components, gate)
99
99
  expect(add).toBeNull()
100
100
 
101
- const fill = toolCallToBoardEditOp(
101
+ const fill = toolCallToSceneEditOp(
102
102
  { id: 'x', name: 'setFill', arguments: { refids: [7], fillStyle: '#f00' } } as any,
103
103
  components,
104
104
  gate
105
105
  )
106
106
  expect(fill).toBeNull()
107
107
 
108
- const action = toolCallToBoardActionOp({ id: 'x', name: 'selectComponents', arguments: { refids: [7] } } as any, gate)
108
+ const action = toolCallToSceneActionOp({ id: 'x', name: 'selectComponents', arguments: { refids: [7] } } as any, gate)
109
109
  expect(action).toBeNull()
110
110
  })
111
111
 
112
112
  it('허용되지 않은 외부 카테고리의 write 도구도 실행되지 않는다', () => {
113
113
  const gate = buildToolGate({ toolCategories: ['test-ops'] })
114
- const r = toolCallToBoardEditOp({ id: 'x', name: 'testBindThing', arguments: { refid: 7 } } as any, [{ refid: 7 }], gate)
114
+ const r = toolCallToSceneEditOp({ id: 'x', name: 'testBindThing', arguments: { refid: 7 } } as any, [{ refid: 7 }], gate)
115
115
  expect(r).toBeNull()
116
116
  })
117
117
 
118
118
  it('허용된 카테고리의 write 도구는 정상 실행된다(게이트가 과잉 차단하지 않는다)', () => {
119
119
  const gate = buildToolGate({ toolCategories: ['test-authoring'] })
120
- const r: any = toolCallToBoardEditOp({ id: 'x', name: 'testBindThing', arguments: { refid: 7 } } as any, [{ refid: 7 }], gate)
120
+ const r: any = toolCallToSceneEditOp({ id: 'x', name: 'testBindThing', arguments: { refid: 7 } } as any, [{ refid: 7 }], gate)
121
121
  expect(r).toEqual([{ op: 'modify', refid: 7, patch: { tag: 'bound' } }])
122
122
  })
123
123