@things-factory/board-ai 10.0.2 → 10.0.4

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 (60) hide show
  1. package/client/components/board-ai-chat.ts +458 -19
  2. package/client/components/chat-echo-dedup.test.ts +59 -3
  3. package/client/components/chat-echo-dedup.ts +29 -0
  4. package/dist-client/client/components/board-ai-chat.d.ts +63 -0
  5. package/dist-client/client/components/board-ai-chat.js +438 -16
  6. package/dist-client/client/components/board-ai-chat.js.map +1 -1
  7. package/dist-client/client/components/chat-echo-dedup.js +28 -0
  8. package/dist-client/client/components/chat-echo-dedup.js.map +1 -1
  9. package/dist-client/client/components/chat-echo-dedup.test.js +53 -3
  10. package/dist-client/client/components/chat-echo-dedup.test.js.map +1 -1
  11. package/dist-client/server/service/agentic-loop.d.ts +15 -0
  12. package/dist-client/server/service/agentic-loop.js +70 -9
  13. package/dist-client/server/service/agentic-loop.js.map +1 -1
  14. package/dist-client/server/service/assistant.js +18 -3
  15. package/dist-client/server/service/assistant.js.map +1 -1
  16. package/dist-client/server/service/types.d.ts +23 -0
  17. package/dist-client/server/service/types.js.map +1 -1
  18. package/dist-client/tsconfig.tsbuildinfo +1 -1
  19. package/dist-server/service/agentic-loop.d.ts +15 -0
  20. package/dist-server/service/agentic-loop.js +71 -9
  21. package/dist-server/service/agentic-loop.js.map +1 -1
  22. package/dist-server/service/assistant.js +17 -2
  23. package/dist-server/service/assistant.js.map +1 -1
  24. package/dist-server/service/board-ai-resolver.d.ts +13 -0
  25. package/dist-server/service/board-ai-resolver.js +99 -1
  26. package/dist-server/service/board-ai-resolver.js.map +1 -1
  27. package/dist-server/service/chat-message/fold-history.d.ts +30 -0
  28. package/dist-server/service/chat-message/fold-history.js +29 -0
  29. package/dist-server/service/chat-message/fold-history.js.map +1 -0
  30. package/dist-server/service/chat-message/history-summary.d.ts +43 -0
  31. package/dist-server/service/chat-message/history-summary.js +77 -0
  32. package/dist-server/service/chat-message/history-summary.js.map +1 -0
  33. package/dist-server/service/chat-message/llm-history.d.ts +19 -0
  34. package/dist-server/service/chat-message/llm-history.js +31 -1
  35. package/dist-server/service/chat-message/llm-history.js.map +1 -1
  36. package/dist-server/service/chat-session/chat-session.d.ts +8 -0
  37. package/dist-server/service/chat-session/chat-session.js +5 -0
  38. package/dist-server/service/chat-session/chat-session.js.map +1 -1
  39. package/dist-server/service/types.d.ts +23 -0
  40. package/dist-server/service/types.js.map +1 -1
  41. package/dist-server/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +6 -6
  43. package/server/service/agentic-loop.test.ts +91 -0
  44. package/server/service/agentic-loop.ts +80 -9
  45. package/server/service/assistant.ts +21 -3
  46. package/server/service/board-ai-resolver.ts +108 -1
  47. package/server/service/chat-message/fold-history.test.ts +98 -0
  48. package/server/service/chat-message/fold-history.ts +60 -0
  49. package/server/service/chat-message/history-summary.test.ts +127 -0
  50. package/server/service/chat-message/history-summary.ts +100 -0
  51. package/server/service/chat-message/llm-history.test.ts +65 -0
  52. package/server/service/chat-message/llm-history.ts +48 -1
  53. package/server/service/chat-session/chat-session.ts +11 -0
  54. package/server/service/dock-contract.test.ts +305 -0
  55. package/server/service/types.ts +23 -0
  56. package/translations/en.json +14 -1
  57. package/translations/ja.json +14 -1
  58. package/translations/ko.json +13 -0
  59. package/translations/ms.json +14 -1
  60. package/translations/zh.json +14 -1
@@ -0,0 +1,305 @@
1
+ /**
2
+ * 협의 도크 계약 — **어시스턴트 경계의 배선**을 못박는다.
3
+ *
4
+ * 왜 이 파일이 따로 필요한가: 루프의 거동(도구 강제·근거 수집·제안 수집)은 agentic-loop.test 가,
5
+ * 접지 판정 규칙은 grounding.test 가 이미 잠근다. 비어 있던 것은 **그 둘을 잇는 한 줄들**이었고,
6
+ * 그 줄들이 끊어질 때 나타나는 증상이 실제로 겪은 실패와 1:1 로 대응한다:
7
+ *
8
+ * · hostContext → ctx.state.host 끊기면 → AI 가 사용자에게 "어느 트윈이냐" 를 되묻는다
9
+ * · requireGroundingTools → 루프 끊기면 → 도구 없이 상태를 단언한다("바쁘게 움직이고 있다")
10
+ * · 접지 근거 4갈래 끊기면 → 정상 답에 경고가 붙거나(오탐) 없는 대상이 통과한다
11
+ * · proposals → 응답 끊기면 → 실행 카드가 사라지고 제안이 도구 추적에 묻힌다
12
+ *
13
+ * 여기서 검증하지 **않는** 것: 실제 모델이 어느 도구를 고르는지. 그건 가짜 LLM 으로 알 수 없고,
14
+ * 도크의 도구 사용 패널이 보여준다. 이 파일의 값은 "배선이 살아 있다" 를 영구히 지키는 것이다.
15
+ */
16
+ import type { AIClient } from '@things-factory/ai-client-base'
17
+ import { registerToolCategory, clearToolRegistry } from '@things-factory/ai-client-base'
18
+
19
+ import { DefaultBoardAIAssistant } from './assistant'
20
+
21
+ /** 스크립트된 턴을 돌려주는 가짜 provider — 호출 옵션도 기록한다(toolChoice 확인용). */
22
+ function mockClient(turns: Array<{ text?: string; toolCalls: any[] }>) {
23
+ let i = 0
24
+ const calls: any[] = []
25
+ const client: AIClient = {
26
+ id: 'mock:dock',
27
+ chat: async () => '',
28
+ generateJSON: async () => ({}),
29
+ chatWithTools: async (messages: any[], _tools: any[], options: any) => {
30
+ calls.push({ messages, options })
31
+ const turn = turns[i++] ?? { toolCalls: [] }
32
+ return {
33
+ text: turn.text,
34
+ toolCalls: turn.toolCalls,
35
+ stopReason: turn.toolCalls.length > 0 ? 'tool_use' : 'end_turn'
36
+ }
37
+ }
38
+ } as any
39
+ return { client, calls }
40
+ }
41
+
42
+ /** 트윈 도구 대역 — 실제 twin 카테고리와 같은 모양(대상을 문맥에서 해소하고 결과를 돌려준다). */
43
+ function registerFakeTwinTools(seen: { host?: any; args?: any }) {
44
+ registerToolCategory({
45
+ name: 'twin-ops-fake',
46
+ description: 'fake ops tools for contract test',
47
+ specs: [
48
+ {
49
+ kind: 'read',
50
+ name: 'getSpaceStatus',
51
+ description: 'status of the twins running in the space this surface is looking at',
52
+ schema: { type: 'object', properties: { instanceId: { type: 'string' } } },
53
+ builder: async (args: any, ctx: any) => {
54
+ /* 대상을 **문맥에서** 정한다 — 사용자에게 되묻지 않는 경로가 이것이다. */
55
+ seen.host = ctx?.state?.host
56
+ seen.args = args
57
+ const target = args?.instanceId || ctx?.state?.host?.spaceId
58
+ return { instanceId: 'busan-wms', spaceId: target, nodes: [{ id: 'dock-1', occupancy: 0 }] }
59
+ }
60
+ } as any,
61
+ {
62
+ kind: 'read',
63
+ name: 'proposeHold',
64
+ description: 'record a proposed hold (never executes it)',
65
+ schema: { type: 'object', properties: { orderId: { type: 'string' } } },
66
+ builder: async (args: any) => ({
67
+ proposed: true,
68
+ command: 'order.hold',
69
+ args: { orderId: args?.orderId },
70
+ label: `${args?.orderId} 보류`,
71
+ instanceId: 'busan-wms'
72
+ })
73
+ } as any
74
+ ]
75
+ })
76
+ }
77
+
78
+ const TC = (name: string, args: any = {}, id = name) => ({ id, name, arguments: args })
79
+
80
+ beforeEach(() => clearToolRegistry())
81
+ afterEach(() => clearToolRegistry())
82
+
83
+ describe('도크 계약 — 대화면 문맥(hostContext)', () => {
84
+ it('hostContext 가 도구의 ctx.state.host 로 도달한다 — 이게 끊기면 AI 가 식별자를 되묻는다', async () => {
85
+ const seen: { host?: any; args?: any } = {}
86
+ registerFakeTwinTools(seen)
87
+ const { client } = mockClient([{ toolCalls: [TC('getSpaceStatus', {})] }, { text: '여유롭습니다.', toolCalls: [] }])
88
+
89
+ await new DefaultBoardAIAssistant(client).chat([{ role: 'user', content: '지금 상태 어때?' }], undefined, {
90
+ toolCategories: ['twin-ops-fake'],
91
+ boardTools: false,
92
+ hostContext: { spaceId: 'busan' },
93
+ toolCallContext: { domain: { id: 'dom-1' } }
94
+ })
95
+
96
+ expect(seen.host).toEqual({ spaceId: 'busan' })
97
+ })
98
+
99
+ it('서버 문맥(domain)과 호스트 문맥이 함께 전달된다 — 권한은 서버 것, 범위는 호스트 것', async () => {
100
+ let state: any
101
+ registerToolCategory({
102
+ name: 'twin-ops-fake',
103
+ description: 'x',
104
+ specs: [
105
+ {
106
+ kind: 'read',
107
+ name: 'peek',
108
+ description: 'peek',
109
+ schema: { type: 'object', properties: {} },
110
+ builder: async (_a: any, ctx: any) => {
111
+ state = ctx?.state
112
+ return { ok: true }
113
+ }
114
+ } as any
115
+ ]
116
+ })
117
+ const { client } = mockClient([{ toolCalls: [TC('peek')] }, { text: 'ok', toolCalls: [] }])
118
+ await new DefaultBoardAIAssistant(client).chat([{ role: 'user', content: 'x' }], undefined, {
119
+ toolCategories: ['twin-ops-fake'],
120
+ boardTools: false,
121
+ hostContext: { spaceId: 'busan' },
122
+ toolCallContext: { domain: { id: 'dom-1' }, user: { id: 'u1' } }
123
+ })
124
+ expect(state.domain).toEqual({ id: 'dom-1' })
125
+ expect(state.host).toEqual({ spaceId: 'busan' })
126
+ })
127
+ })
128
+
129
+ describe('도크 계약 — 첫 턴 도구 강제', () => {
130
+ it('requireGroundingTools 를 켜면 첫 호출이 required, 이후 auto', async () => {
131
+ const seen: any = {}
132
+ registerFakeTwinTools(seen)
133
+ const { client, calls } = mockClient([
134
+ { toolCalls: [TC('getSpaceStatus', {})] },
135
+ { text: 'dock-1 은 여유롭습니다.', toolCalls: [] }
136
+ ])
137
+ await new DefaultBoardAIAssistant(client).chat([{ role: 'user', content: '상태?' }], undefined, {
138
+ toolCategories: ['twin-ops-fake'],
139
+ boardTools: false,
140
+ requireGroundingTools: true,
141
+ hostContext: { spaceId: 'busan' },
142
+ toolCallContext: { domain: { id: 'dom-1' } }
143
+ })
144
+ expect(calls.map(c => c.options.toolChoice)).toEqual(['required', 'auto'])
145
+ })
146
+
147
+ it('끄면 첫 호출도 auto — 라이브 상태를 다루지 않는 대화면은 강제하지 않는다', async () => {
148
+ const seen: any = {}
149
+ registerFakeTwinTools(seen)
150
+ const { client, calls } = mockClient([{ text: '네', toolCalls: [] }])
151
+ await new DefaultBoardAIAssistant(client).chat([{ role: 'user', content: '고마워' }], undefined, {
152
+ toolCategories: ['twin-ops-fake'],
153
+ boardTools: false,
154
+ toolCallContext: { domain: { id: 'dom-1' } }
155
+ })
156
+ expect(calls[0].options.toolChoice).toBe('auto')
157
+ })
158
+ })
159
+
160
+ describe('도크 계약 — 접지 경고', () => {
161
+ it('도구가 돌려준 식별자로 답하면 경고가 없다 — 도구 결과가 근거집합에 들어간다', async () => {
162
+ const seen: any = {}
163
+ registerFakeTwinTools(seen)
164
+ const { client } = mockClient([
165
+ { toolCalls: [TC('getSpaceStatus', {})] },
166
+ { text: 'dock-1 은 여유롭고 busan-wms 는 정상입니다.', toolCalls: [] }
167
+ ])
168
+ const r = await new DefaultBoardAIAssistant(client).chat([{ role: 'user', content: '상태?' }], undefined, {
169
+ toolCategories: ['twin-ops-fake'],
170
+ boardTools: false,
171
+ hostContext: { spaceId: 'busan' },
172
+ toolCallContext: { domain: { id: 'dom-1' } }
173
+ })
174
+ expect(r.groundingWarnings).toBeUndefined()
175
+ })
176
+
177
+ it('근거 밖의 대상을 지목하면 경고가 붙는다 — 답은 그대로 둔다(비파괴)', async () => {
178
+ const seen: any = {}
179
+ registerFakeTwinTools(seen)
180
+ const { client } = mockClient([
181
+ { toolCalls: [TC('getSpaceStatus', {})] },
182
+ { text: 'dock-9 에 적체가 있습니다.', toolCalls: [] }
183
+ ])
184
+ const r = await new DefaultBoardAIAssistant(client).chat([{ role: 'user', content: '상태?' }], undefined, {
185
+ toolCategories: ['twin-ops-fake'],
186
+ boardTools: false,
187
+ hostContext: { spaceId: 'busan' },
188
+ toolCallContext: { domain: { id: 'dom-1' } }
189
+ })
190
+ expect(r.groundingWarnings).toEqual(['dock-9'])
191
+ expect(r.reply).toContain('dock-9') // 경고는 붙이지만 답을 고치지 않는다
192
+ })
193
+
194
+ it('사용자가 말한 식별자를 되받는 것은 근거 있는 것이다 — 이력도 근거집합', async () => {
195
+ const seen: any = {}
196
+ registerFakeTwinTools(seen)
197
+ const { client } = mockClient([
198
+ { toolCalls: [TC('getSpaceStatus', {})] },
199
+ { text: 'rack-77 은 확인이 필요합니다.', toolCalls: [] }
200
+ ])
201
+ const r = await new DefaultBoardAIAssistant(client).chat(
202
+ [{ role: 'user', content: 'rack-77 어때?' }],
203
+ undefined,
204
+ {
205
+ toolCategories: ['twin-ops-fake'],
206
+ boardTools: false,
207
+ hostContext: { spaceId: 'busan' },
208
+ toolCallContext: { domain: { id: 'dom-1' } }
209
+ }
210
+ )
211
+ expect(r.groundingWarnings).toBeUndefined()
212
+ })
213
+ })
214
+
215
+ describe('도크 계약 — 조치 제안', () => {
216
+ it('proposed:true 도구 결과가 응답의 proposals 로 올라온다 — 실행 카드의 근거', async () => {
217
+ const seen: any = {}
218
+ registerFakeTwinTools(seen)
219
+ const { client } = mockClient([
220
+ { toolCalls: [TC('proposeHold', { orderId: 'o7' })] },
221
+ { text: '아래에서 확인해 주세요.', toolCalls: [] }
222
+ ])
223
+ const r = await new DefaultBoardAIAssistant(client).chat([{ role: 'user', content: 'o7 보류해줘' }], undefined, {
224
+ toolCategories: ['twin-ops-fake'],
225
+ boardTools: false,
226
+ hostContext: { spaceId: 'busan' },
227
+ toolCallContext: { domain: { id: 'dom-1' } }
228
+ })
229
+ expect(r.proposals).toHaveLength(1)
230
+ expect(r.proposals![0]).toMatchObject({ tool: 'proposeHold', command: 'order.hold', instanceId: 'busan-wms' })
231
+ /* 제안은 실행이 아니다 — 보드 패치도 액션도 만들어지지 않는다. */
232
+ expect(r.patch).toBeUndefined()
233
+ expect(r.actions).toBeUndefined()
234
+ })
235
+
236
+ it('조회만 한 응답에는 proposals 가 없다 — 규약은 플래그 하나뿐', async () => {
237
+ const seen: any = {}
238
+ registerFakeTwinTools(seen)
239
+ const { client } = mockClient([{ toolCalls: [TC('getSpaceStatus', {})] }, { text: '정상입니다.', toolCalls: [] }])
240
+ const r = await new DefaultBoardAIAssistant(client).chat([{ role: 'user', content: '상태?' }], undefined, {
241
+ toolCategories: ['twin-ops-fake'],
242
+ boardTools: false,
243
+ hostContext: { spaceId: 'busan' },
244
+ toolCallContext: { domain: { id: 'dom-1' } }
245
+ })
246
+ expect(r.proposals).toBeUndefined()
247
+ })
248
+ })
249
+
250
+ describe('도크 계약 — 능력 경계', () => {
251
+ it('보드 편집 도구를 끈 대화에서는 보드 편집이 실행되지 않는다 — 되돌릴 수 없는 면의 안전선', async () => {
252
+ const seen: any = {}
253
+ registerFakeTwinTools(seen)
254
+ const { client } = mockClient([
255
+ { toolCalls: [TC('addComponent', { type: 'rect', left: 0, top: 0, width: 10, height: 10 })] },
256
+ { text: '...', toolCalls: [] }
257
+ ])
258
+ const r = await new DefaultBoardAIAssistant(client).chat([{ role: 'user', content: 'rect 추가' }], undefined, {
259
+ toolCategories: ['twin-ops-fake'],
260
+ boardTools: false,
261
+ knownTypes: ['rect'],
262
+ toolCallContext: { domain: { id: 'dom-1' } }
263
+ })
264
+ expect(r.patch).toBeUndefined()
265
+ })
266
+ })
267
+
268
+ describe('도크 계약 — 도구 사용 규율이 프롬프트에 실린다', () => {
269
+ it('노출된 카테고리의 규율이 시스템 프롬프트에 붙는다 — 목록만 보내면 "언제 불러야 하는지" 가 빠진다', async () => {
270
+ registerToolCategory({
271
+ name: 'twin-ops-fake',
272
+ description: 'x',
273
+ guidance: '- 조치 요청에는 반드시 proposeHold 를 호출한다. 호출 없이 제안했다고 쓰지 말 것.',
274
+ specs: [
275
+ {
276
+ kind: 'read',
277
+ name: 'proposeHold',
278
+ description: 'propose',
279
+ schema: { type: 'object', properties: {} },
280
+ builder: async () => ({ proposed: true, command: 'order.hold' })
281
+ } as any
282
+ ]
283
+ })
284
+ const { client, calls } = mockClient([{ text: '네', toolCalls: [] }])
285
+ await new DefaultBoardAIAssistant(client).chat([{ role: 'user', content: 'o7 보류' }], undefined, {
286
+ toolCategories: ['twin-ops-fake'],
287
+ boardTools: false,
288
+ toolCallContext: { domain: { id: 'dom-1' } }
289
+ })
290
+ expect(calls[0].options.systemPrompt).toContain('반드시 proposeHold 를 호출한다')
291
+ })
292
+
293
+ it('노출하지 않은 카테고리의 규율은 새지 않는다 — 대화면마다 능력과 규율이 함께 좁혀진다', async () => {
294
+ registerToolCategory({ name: 'twin-ops-fake', guidance: 'OPS-RULE', specs: [] })
295
+ registerToolCategory({ name: 'other-fake', guidance: 'OTHER-RULE', specs: [] })
296
+ const { client, calls } = mockClient([{ text: '네', toolCalls: [] }])
297
+ await new DefaultBoardAIAssistant(client).chat([{ role: 'user', content: 'x' }], undefined, {
298
+ toolCategories: ['twin-ops-fake'],
299
+ boardTools: false,
300
+ toolCallContext: { domain: { id: 'dom-1' } }
301
+ })
302
+ expect(calls[0].options.systemPrompt).toContain('OPS-RULE')
303
+ expect(calls[0].options.systemPrompt).not.toContain('OTHER-RULE')
304
+ })
305
+ })
@@ -126,6 +126,22 @@ export interface ToolUsage {
126
126
  result: any
127
127
  /** read tool / write tool 분류 */
128
128
  kind: 'read' | 'write' | 'unknown'
129
+ /**
130
+ * 몇 번째 LLM 턴에서 불렀는가(0부터). 판단 과정을 단계로 읽을 수 있게 한다 —
131
+ * "조회 → 거절 → 정정 → 제안" 이 한 턴 안의 일인지 여러 턴에 걸친 일인지가 진단의 절반이다.
132
+ */
133
+ iter?: number
134
+ /**
135
+ * 이 호출이 **어떻게 끝났는가** — 화면이 배지로 보여줄 판정.
136
+ * ok 조회·실행이 정상 완료
137
+ * rejected 도구가 거절(인자 누락·대상 없음 등). 무엇이 빠졌는지는 result 에 있다.
138
+ * queued 보드 편집처럼 클라이언트에서 적용될 예정
139
+ * proposed 조치 제안이 만들어졌다(실행 아님 — 사용자가 버튼을 누른다)
140
+ * folded 같은 효과의 제안이 이미 있어 접혔다. **조용히 버리지 않는다** — 접힌 사실이 보여야
141
+ * "왜 카드가 하나뿐인가" 를 추적할 수 있다.
142
+ * error 알 수 없는 도구·검증 실패
143
+ */
144
+ outcome?: 'ok' | 'rejected' | 'queued' | 'proposed' | 'folded' | 'error'
129
145
  }
130
146
 
131
147
  export interface ChatResponse {
@@ -142,6 +158,13 @@ export interface ChatResponse {
142
158
  followUp?: string
143
159
  /** AI 가 응답 만드는 과정에서 호출한 도구들 (시간순). UI fold-able 박스용. */
144
160
  toolUsages?: ToolUsage[]
161
+ /**
162
+ * **조치 제안** — 도구가 실행하지 않고 제안만 한 것들. 실행은 사용자가 버튼으로 한다.
163
+ *
164
+ * 규약: 도구 결과에 `proposed: true` 가 있으면 제안이다(도메인 무관 — board-ai 는 내용을 해석하지
165
+ * 않고 그대로 전달한다). 되돌릴 수 없는 조치를 AI 가 직접 실행하지 않게 하는 경계가 이 채널이다.
166
+ */
167
+ proposals?: any[]
145
168
  /**
146
169
  * 접지 경고 — 답이 언급했으나 모델이 받은 근거(프롬프트·보드 문맥·이력·도구 결과)에 없는 식별자.
147
170
  *
@@ -46,6 +46,19 @@
46
46
  "board-ai.text.mention-not-found": "#{token} not found",
47
47
  "board-ai.text.session-tab-label": "Session {n}",
48
48
  "board-ai.text.tools-used": "Tools used",
49
+ "board-ai.text.tool-turn": "decision {n}",
50
+ "board-ai.text.tool-rejected-count": "rejected {count}",
51
+ "board-ai.text.tool-folded-count": "folded {count}",
52
+ "board-ai.text.outcome-ok": "read",
53
+ "board-ai.text.outcome-rejected": "rejected",
54
+ "board-ai.text.outcome-queued": "to apply",
55
+ "board-ai.text.outcome-proposed": "proposed",
56
+ "board-ai.text.outcome-folded": "duplicate",
57
+ "board-ai.text.outcome-error": "error",
49
58
  "board-ai.text.ungrounded-mention": "Mentions an unverified target — not present in the actual data:",
50
- "board-ai.text.toggle-system-note": "click to expand"
59
+ "board-ai.text.toggle-system-note": "click to expand",
60
+ "board-ai.button.run-proposal": "run",
61
+ "board-ai.text.proposal-sent": "execution requested",
62
+ "board-ai.text.history-folded": "this conversation grew long — the earlier {count} messages were left out. a new conversation is better for a different topic.",
63
+ "board-ai.text.history-folded-summarized": "this conversation grew long — the earlier {count} messages were folded into a summary. a new conversation is better for a different topic."
51
64
  }
@@ -45,6 +45,19 @@
45
45
  "board-ai.text.mention-not-found": "#{token} が見つかりません",
46
46
  "board-ai.text.session-tab-label": "セッション {n}",
47
47
  "board-ai.text.tools-used": "使用したツール",
48
+ "board-ai.text.tool-turn": "{n} 回目の判断",
49
+ "board-ai.text.tool-rejected-count": "却下 {count}",
50
+ "board-ai.text.tool-folded-count": "重複 {count}",
51
+ "board-ai.text.outcome-ok": "照会",
52
+ "board-ai.text.outcome-rejected": "却下",
53
+ "board-ai.text.outcome-queued": "適用予定",
54
+ "board-ai.text.outcome-proposed": "提案",
55
+ "board-ai.text.outcome-folded": "重複",
56
+ "board-ai.text.outcome-error": "エラー",
48
57
  "board-ai.text.ungrounded-mention": "確認できない対象に言及しています — 実データにありません:",
49
- "board-ai.text.toggle-system-note": "クリックで展開"
58
+ "board-ai.text.toggle-system-note": "クリックで展開",
59
+ "board-ai.button.run-proposal": "実行",
60
+ "board-ai.text.proposal-sent": "実行を依頼しました",
61
+ "board-ai.text.history-folded": "会話が長くなり、前半の {count} 件は省略されました。別の話題なら新しい会話のほうが適しています。",
62
+ "board-ai.text.history-folded-summarized": "会話が長くなり、前半の {count} 件は要約にまとめられました。別の話題なら新しい会話のほうが適しています。"
50
63
  }
@@ -23,8 +23,21 @@
23
23
  "board-ai.text.failed-to-load-history": "이력 로드 실패",
24
24
  "board-ai.text.empty-response": "응답이 비어있습니다.",
25
25
  "board-ai.text.tools-used": "도구 사용",
26
+ "board-ai.text.tool-turn": "{n}번째 판단",
27
+ "board-ai.text.tool-rejected-count": "거절 {count}",
28
+ "board-ai.text.tool-folded-count": "접힘 {count}",
29
+ "board-ai.text.outcome-ok": "조회",
30
+ "board-ai.text.outcome-rejected": "거절",
31
+ "board-ai.text.outcome-queued": "적용 예정",
32
+ "board-ai.text.outcome-proposed": "제안",
33
+ "board-ai.text.outcome-folded": "중복 접힘",
34
+ "board-ai.text.outcome-error": "오류",
26
35
  "board-ai.text.ungrounded-mention": "확인되지 않은 대상을 언급했습니다 — 실제 데이터에 없습니다:",
27
36
  "board-ai.text.toggle-system-note": "눌러서 펼치기",
37
+ "board-ai.button.run-proposal": "실행",
38
+ "board-ai.text.proposal-sent": "실행 요청됨",
39
+ "board-ai.text.history-folded": "대화가 길어져 앞부분 {count}개가 생략되었습니다. 다른 주제라면 새 대화가 낫습니다.",
40
+ "board-ai.text.history-folded-summarized": "대화가 길어져 앞부분 {count}개가 요약으로 접혔습니다. 다른 주제라면 새 대화가 낫습니다.",
28
41
 
29
42
  "board-ai.example.create-monitoring-dashboard": "가동률·온도·압력 3개 게이지와 24시간 라인 차트로 모니터링 대시보드",
30
43
  "board-ai.example.create-welcome-screen": "큰 'OPERATO' 제목과 시계, 부제로 환영 화면",
@@ -45,6 +45,19 @@
45
45
  "board-ai.text.mention-not-found": "#{token} tidak dijumpai",
46
46
  "board-ai.text.session-tab-label": "Sesi {n}",
47
47
  "board-ai.text.tools-used": "Alat yang digunakan",
48
+ "board-ai.text.tool-turn": "keputusan {n}",
49
+ "board-ai.text.tool-rejected-count": "ditolak {count}",
50
+ "board-ai.text.tool-folded-count": "pendua {count}",
51
+ "board-ai.text.outcome-ok": "baca",
52
+ "board-ai.text.outcome-rejected": "ditolak",
53
+ "board-ai.text.outcome-queued": "akan digunakan",
54
+ "board-ai.text.outcome-proposed": "dicadangkan",
55
+ "board-ai.text.outcome-folded": "pendua",
56
+ "board-ai.text.outcome-error": "ralat",
48
57
  "board-ai.text.ungrounded-mention": "Menyebut sasaran yang tidak disahkan — tiada dalam data sebenar:",
49
- "board-ai.text.toggle-system-note": "klik untuk kembangkan"
58
+ "board-ai.text.toggle-system-note": "klik untuk kembangkan",
59
+ "board-ai.button.run-proposal": "jalankan",
60
+ "board-ai.text.proposal-sent": "pelaksanaan diminta",
61
+ "board-ai.text.history-folded": "perbualan ini menjadi panjang — {count} mesej terawal ditinggalkan. perbualan baharu lebih sesuai untuk topik lain.",
62
+ "board-ai.text.history-folded-summarized": "perbualan ini menjadi panjang — {count} mesej terawal diringkaskan. perbualan baharu lebih sesuai untuk topik lain."
50
63
  }
@@ -45,6 +45,19 @@
45
45
  "board-ai.text.mention-not-found": "未找到 #{token}",
46
46
  "board-ai.text.session-tab-label": "会话 {n}",
47
47
  "board-ai.text.tools-used": "使用的工具",
48
+ "board-ai.text.tool-turn": "第 {n} 次判断",
49
+ "board-ai.text.tool-rejected-count": "拒绝 {count}",
50
+ "board-ai.text.tool-folded-count": "折叠 {count}",
51
+ "board-ai.text.outcome-ok": "查询",
52
+ "board-ai.text.outcome-rejected": "拒绝",
53
+ "board-ai.text.outcome-queued": "待应用",
54
+ "board-ai.text.outcome-proposed": "提案",
55
+ "board-ai.text.outcome-folded": "重复",
56
+ "board-ai.text.outcome-error": "错误",
48
57
  "board-ai.text.ungrounded-mention": "提到了未经确认的对象 — 实际数据中不存在:",
49
- "board-ai.text.toggle-system-note": "点击展开"
58
+ "board-ai.text.toggle-system-note": "点击展开",
59
+ "board-ai.button.run-proposal": "执行",
60
+ "board-ai.text.proposal-sent": "已请求执行",
61
+ "board-ai.text.history-folded": "对话变长,前面 {count} 条已省略。若是另一个话题,新建对话更合适。",
62
+ "board-ai.text.history-folded-summarized": "对话变长,前面 {count} 条已折叠为摘要。若是另一个话题,新建对话更合适。"
50
63
  }