@agent-platform/ui 0.0.6 → 0.0.7

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 (67) hide show
  1. package/README.md +5 -0
  2. package/dist/components/agent/agent-container-state.d.ts +2 -0
  3. package/dist/components/agent/agent-container-state.js +52 -0
  4. package/dist/components/agent/agent-container-view.d.ts +3 -0
  5. package/dist/components/agent/agent-container-view.js +22 -0
  6. package/dist/components/agent/agent-container-view.test.d.ts +1 -0
  7. package/dist/components/agent/agent-container-view.test.js +16 -0
  8. package/dist/components/agent/agent-container.d.ts +1 -1
  9. package/dist/components/agent/agent-container.js +6 -44
  10. package/dist/components/agent/agent-greeting.d.ts +1 -1
  11. package/dist/components/agent/agent-greeting.js +4 -4
  12. package/dist/components/agent/agent-header.d.ts +1 -1
  13. package/dist/components/agent/agent-header.js +6 -6
  14. package/dist/components/agent/agent-home-cards.d.ts +2 -2
  15. package/dist/components/agent/agent-home-cards.js +8 -6
  16. package/dist/components/agent/agent-screen.d.ts +1 -1
  17. package/dist/components/agent/agent-screen.js +5 -2
  18. package/dist/components/agent/approval-ui-model.d.ts +15 -0
  19. package/dist/components/agent/approval-ui-model.js +27 -0
  20. package/dist/components/agent/approval-ui-model.test.d.ts +1 -0
  21. package/dist/components/agent/approval-ui-model.test.js +39 -0
  22. package/dist/components/agent/defaults.d.ts +0 -6
  23. package/dist/components/agent/defaults.js +0 -11
  24. package/dist/components/agent/index.d.ts +0 -2
  25. package/dist/components/agent/index.js +0 -1
  26. package/dist/components/agent/input-mode.d.ts +5 -0
  27. package/dist/components/agent/input-mode.js +9 -0
  28. package/dist/components/agent/message/index.d.ts +1 -1
  29. package/dist/components/agent/message/index.js +1 -1
  30. package/dist/components/agent/message/message-item.js +3 -9
  31. package/dist/components/agent/message/message-list.js +6 -11
  32. package/dist/components/agent/message/message-loading.d.ts +0 -6
  33. package/dist/components/agent/message/message-loading.js +0 -6
  34. package/dist/components/agent/message/tool-call-card.js +2 -0
  35. package/dist/components/agent/message/utils.d.ts +8 -0
  36. package/dist/components/agent/message/utils.js +21 -0
  37. package/dist/components/agent/provider/agent-context.d.ts +1 -0
  38. package/dist/components/agent/provider/agent-context.js +3 -0
  39. package/dist/components/agent/provider/agent-provider.d.ts +1 -1
  40. package/dist/components/agent/provider/agent-provider.js +19 -5
  41. package/dist/components/agent/provider/index.d.ts +1 -1
  42. package/dist/components/agent/provider/runtime-config.js +14 -5
  43. package/dist/components/agent/provider/types.d.ts +17 -15
  44. package/dist/components/agent/types.d.ts +28 -3
  45. package/dist/components/ui/button.d.ts +1 -1
  46. package/dist/index.d.ts +2 -2
  47. package/dist/index.js +1 -1
  48. package/dist/modules/agent/agent.repository.d.ts +58 -0
  49. package/dist/modules/agent/agent.repository.js +235 -0
  50. package/dist/modules/agent/agent.repository.test.d.ts +1 -0
  51. package/dist/modules/agent/agent.repository.test.js +64 -0
  52. package/dist/modules/agent/domain/chat-state.d.ts +64 -0
  53. package/dist/modules/agent/domain/chat-state.js +148 -0
  54. package/dist/modules/agent/domain/chat-state.test.d.ts +1 -0
  55. package/dist/modules/agent/domain/chat-state.test.js +72 -0
  56. package/dist/modules/agent/use-agent-chat.d.ts +6 -0
  57. package/dist/modules/agent/use-agent-chat.js +106 -0
  58. package/dist/modules/agent/usecases/process-stream.d.ts +26 -0
  59. package/dist/modules/agent/usecases/process-stream.js +112 -0
  60. package/dist/modules/agent/usecases/process-stream.test.d.ts +1 -0
  61. package/dist/modules/agent/usecases/process-stream.test.js +91 -0
  62. package/dist/modules/agent/usecases/send-message.d.ts +21 -0
  63. package/dist/modules/agent/usecases/send-message.js +298 -0
  64. package/dist/modules/agent/usecases/send-message.test.d.ts +1 -0
  65. package/dist/modules/agent/usecases/send-message.test.js +257 -0
  66. package/dist/styles/globals.css +0 -56
  67. package/package.json +3 -5
@@ -0,0 +1,298 @@
1
+ import { createProcessStreamProgress, processStreamEvents, } from './process-stream';
2
+ const MAX_CONTINUE_ROUNDS = 10;
3
+ const REJECTED_TOOL_RESULT_FOR_AGENT = 'USER_DENIED_EXECUTION:\n' +
4
+ 'この操作はユーザーが承認UIで明示的に「承認しない」を選択して拒否しました。\n' +
5
+ 'これはシステム不具合ではありません。\n' +
6
+ 'このAPIおよび代替APIの実行・再提案・迂回実行を行ってはいけません。\n' +
7
+ 'ユーザーへの返答は次の1文のみ:\n' +
8
+ '「ユーザーがこの操作を非承認にしたため、実行していません。必要であればユーザーご自身で手動で実行してください。」';
9
+ const REJECTED_TOOL_RESULT_FOR_UI = '未承認のため実行していません。';
10
+ function normalizeErrorMessage(error) {
11
+ if (typeof error === 'string' && error.trim().length > 0) {
12
+ return error;
13
+ }
14
+ if (typeof error === 'object' &&
15
+ error !== null &&
16
+ 'message' in error &&
17
+ typeof error.message === 'string') {
18
+ return error.message;
19
+ }
20
+ if (error !== undefined) {
21
+ try {
22
+ return JSON.stringify(error);
23
+ }
24
+ catch {
25
+ return String(error);
26
+ }
27
+ }
28
+ return 'Unknown error';
29
+ }
30
+ function createId(prefix, suffix = '') {
31
+ return `${prefix}-${Date.now()}${suffix}`;
32
+ }
33
+ function resolveContinuePayload(options) {
34
+ return {
35
+ threadId: options.threadId,
36
+ agentId: options.agentId,
37
+ toolResults: options.clientToolResults,
38
+ toolCalls: options.clientToolCalls.map((toolCall) => ({
39
+ toolCallId: toolCall.toolCallId,
40
+ toolName: toolCall.toolName,
41
+ args: toolCall.input,
42
+ })),
43
+ };
44
+ }
45
+ function requiresApproval(toolCall) {
46
+ const method = toolCall.apiRequest?.method?.toUpperCase();
47
+ if (!method) {
48
+ return false;
49
+ }
50
+ if (typeof toolCall.apiRequest?.approvalRequired === 'boolean') {
51
+ return toolCall.apiRequest.approvalRequired;
52
+ }
53
+ return method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'DELETE';
54
+ }
55
+ async function runClientToolCalls(options) {
56
+ const { toolCalls, repository, dispatch, apiBaseUrl, requestToolApproval } = options;
57
+ if (toolCalls.length === 0) {
58
+ return { toolResults: [] };
59
+ }
60
+ const authHeaders = await repository.buildToolApiHeaders();
61
+ const toolResults = [];
62
+ for (const toolCall of toolCalls) {
63
+ if (requiresApproval(toolCall)) {
64
+ dispatch({
65
+ type: 'UPSERT_TOOL_CALL_STATE',
66
+ payload: {
67
+ toolCall,
68
+ status: 'awaiting-approval',
69
+ },
70
+ });
71
+ const approved = requestToolApproval ? await requestToolApproval(toolCall) : true;
72
+ if (!approved) {
73
+ dispatch({
74
+ type: 'UPSERT_TOOL_CALL_STATE',
75
+ payload: {
76
+ toolCall,
77
+ status: 'error',
78
+ result: REJECTED_TOOL_RESULT_FOR_UI,
79
+ error: REJECTED_TOOL_RESULT_FOR_UI,
80
+ },
81
+ });
82
+ toolResults.push({
83
+ toolCallId: toolCall.toolCallId,
84
+ result: REJECTED_TOOL_RESULT_FOR_AGENT,
85
+ isError: true,
86
+ });
87
+ continue;
88
+ }
89
+ }
90
+ dispatch({
91
+ type: 'UPSERT_TOOL_CALL_STATE',
92
+ payload: {
93
+ toolCall,
94
+ status: 'executing',
95
+ },
96
+ });
97
+ try {
98
+ const executionResult = await repository.executeToolCall({
99
+ toolCall,
100
+ apiBaseUrl,
101
+ authHeaders,
102
+ });
103
+ const isError = executionResult.isError ?? false;
104
+ const error = isError ? normalizeErrorMessage(executionResult.output) : undefined;
105
+ dispatch({
106
+ type: 'UPSERT_TOOL_CALL_STATE',
107
+ payload: {
108
+ toolCall,
109
+ status: isError ? 'error' : 'completed',
110
+ result: executionResult.output,
111
+ error,
112
+ },
113
+ });
114
+ toolResults.push({
115
+ toolCallId: toolCall.toolCallId,
116
+ result: executionResult.output,
117
+ isError,
118
+ });
119
+ }
120
+ catch (error) {
121
+ const errorMessage = normalizeErrorMessage(error);
122
+ dispatch({
123
+ type: 'UPSERT_TOOL_CALL_STATE',
124
+ payload: {
125
+ toolCall,
126
+ status: 'error',
127
+ error: errorMessage,
128
+ },
129
+ });
130
+ toolResults.push({
131
+ toolCallId: toolCall.toolCallId,
132
+ result: errorMessage,
133
+ isError: true,
134
+ });
135
+ }
136
+ }
137
+ return { toolResults };
138
+ }
139
+ async function collectAndProcessStream(options) {
140
+ let progress = createProcessStreamProgress();
141
+ let latestResult = {
142
+ clientToolCalls: [],
143
+ clientToolResults: [],
144
+ progress,
145
+ };
146
+ let processedBatchCount = 0;
147
+ const processEvents = (events) => {
148
+ if (events.length === 0) {
149
+ return;
150
+ }
151
+ processedBatchCount += 1;
152
+ const result = processStreamEvents({
153
+ events,
154
+ assistantMessageId: options.assistantMessageId,
155
+ baseState: options.getState(),
156
+ dispatch: options.dispatch,
157
+ onError: options.onError,
158
+ progress,
159
+ });
160
+ progress = result.progress;
161
+ latestResult = result;
162
+ for (const event of events) {
163
+ if (event.type === 'thread-id') {
164
+ options.onThreadId(event.payload.threadId);
165
+ }
166
+ }
167
+ };
168
+ const allEvents = await options.repository.collectStreamEvents(options.response, {
169
+ onEvents: processEvents,
170
+ });
171
+ if (allEvents.length > 0 && processedBatchCount === 0) {
172
+ processEvents(allEvents);
173
+ }
174
+ return {
175
+ clientToolCalls: latestResult.clientToolCalls,
176
+ };
177
+ }
178
+ export async function sendMessageUseCase(input) {
179
+ const state = input.getState();
180
+ const trimmedMessage = input.message.trim();
181
+ if (!trimmedMessage || state.isLoading) {
182
+ return;
183
+ }
184
+ const userMessageId = createId('user');
185
+ const assistantMessageId = createId('assistant');
186
+ const agentId = input.config.agentId ?? 'assistant';
187
+ let activeThreadId = state.threadId;
188
+ const handleError = (error) => {
189
+ const errorMessage = normalizeErrorMessage(error);
190
+ input.dispatch({ type: 'SET_ERROR', payload: { error: errorMessage } });
191
+ input.onError?.(errorMessage);
192
+ };
193
+ input.dispatch({
194
+ type: 'START_CHAT',
195
+ payload: {
196
+ userMessageId,
197
+ text: trimmedMessage,
198
+ assistantMessageId,
199
+ },
200
+ });
201
+ try {
202
+ const headers = await input.repository.buildAgentHeaders(input.config.getAgentHeaders);
203
+ const response = await input.repository.sendChat({
204
+ endpoint: input.config.endpoint,
205
+ threadId: state.threadId,
206
+ message: trimmedMessage,
207
+ agentId,
208
+ headers,
209
+ signal: input.signal,
210
+ });
211
+ if (!response.ok) {
212
+ const errorMessage = await input.repository.parseErrorResponse(response, 'Failed to send message');
213
+ throw new Error(errorMessage);
214
+ }
215
+ const firstResult = await collectAndProcessStream({
216
+ response,
217
+ repository: input.repository,
218
+ assistantMessageId,
219
+ getState: input.getState,
220
+ dispatch: input.dispatch,
221
+ onError: input.onError,
222
+ onThreadId: (threadId) => {
223
+ activeThreadId = threadId;
224
+ },
225
+ });
226
+ let currentToolCalls = firstResult.clientToolCalls;
227
+ let { toolResults } = await runClientToolCalls({
228
+ toolCalls: currentToolCalls,
229
+ repository: input.repository,
230
+ dispatch: input.dispatch,
231
+ apiBaseUrl: input.config.apiBaseUrl,
232
+ requestToolApproval: input.requestToolApproval,
233
+ });
234
+ let continueRound = 0;
235
+ let shouldContinue = toolResults.length > 0;
236
+ while (shouldContinue) {
237
+ continueRound += 1;
238
+ if (continueRound > MAX_CONTINUE_ROUNDS) {
239
+ handleError(`Tool execution loop exceeded maximum rounds (${MAX_CONTINUE_ROUNDS}).`);
240
+ break;
241
+ }
242
+ const continueMessageId = createId('assistant', `-${continueRound}`);
243
+ input.dispatch({
244
+ type: 'START_ASSISTANT_MESSAGE',
245
+ payload: { assistantMessageId: continueMessageId },
246
+ });
247
+ const continueHeaders = await input.repository.buildAgentHeaders(input.config.getAgentHeaders);
248
+ const continuePayload = resolveContinuePayload({
249
+ threadId: activeThreadId,
250
+ agentId,
251
+ clientToolResults: toolResults,
252
+ clientToolCalls: currentToolCalls,
253
+ });
254
+ const continueResponse = await input.repository.continueChat({
255
+ endpoint: input.config.endpoint,
256
+ payload: continuePayload,
257
+ headers: continueHeaders,
258
+ signal: input.signal,
259
+ });
260
+ if (!continueResponse.ok) {
261
+ const continueErrorText = await continueResponse
262
+ .text()
263
+ .catch(() => continueResponse.statusText);
264
+ throw new Error(`Failed to continue agent after tool execution: ${continueErrorText}`);
265
+ }
266
+ const continueResult = await collectAndProcessStream({
267
+ response: continueResponse,
268
+ repository: input.repository,
269
+ assistantMessageId: continueMessageId,
270
+ getState: input.getState,
271
+ dispatch: input.dispatch,
272
+ onError: input.onError,
273
+ onThreadId: (threadId) => {
274
+ activeThreadId = threadId;
275
+ },
276
+ });
277
+ const toolExecution = await runClientToolCalls({
278
+ toolCalls: continueResult.clientToolCalls,
279
+ repository: input.repository,
280
+ dispatch: input.dispatch,
281
+ apiBaseUrl: input.config.apiBaseUrl,
282
+ requestToolApproval: input.requestToolApproval,
283
+ });
284
+ currentToolCalls = continueResult.clientToolCalls;
285
+ toolResults = toolExecution.toolResults;
286
+ shouldContinue = toolResults.length > 0;
287
+ }
288
+ }
289
+ catch (error) {
290
+ if (error instanceof Error && error.name === 'AbortError') {
291
+ return;
292
+ }
293
+ handleError(error);
294
+ }
295
+ finally {
296
+ input.dispatch({ type: 'SET_LOADING', payload: { isLoading: false } });
297
+ }
298
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,257 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { createInitialChatState } from '../domain/chat-state';
3
+ import { sendMessageUseCase } from './send-message';
4
+ function buildToolCall(id) {
5
+ return {
6
+ toolCallId: id,
7
+ toolName: 'searchCandidates',
8
+ input: { keyword: 'alice' },
9
+ apiRequest: {
10
+ method: 'GET',
11
+ path: '/candidates',
12
+ pathParams: [],
13
+ queryParams: ['keyword'],
14
+ bodyFields: [],
15
+ requiresAuth: true,
16
+ approvalRequired: false,
17
+ riskLevel: 'read',
18
+ actionLabel: '候補者検索',
19
+ },
20
+ };
21
+ }
22
+ function buildCreateScoutToolCall(id) {
23
+ return {
24
+ toolCallId: id,
25
+ toolName: 'createScout',
26
+ input: { candidateId: 'c-1', subject: 'ご提案', body: 'ご興味ありますか?' },
27
+ apiRequest: {
28
+ method: 'POST',
29
+ path: '/scouts',
30
+ pathParams: [],
31
+ queryParams: [],
32
+ bodyFields: ['candidateId', 'subject', 'body'],
33
+ requiresAuth: true,
34
+ approvalRequired: true,
35
+ riskLevel: 'write',
36
+ actionLabel: 'スカウト送信',
37
+ },
38
+ };
39
+ }
40
+ describe('sendMessageUseCase', () => {
41
+ it('MAX_CONTINUE_ROUNDSを超えるとonErrorを呼ぶ', async () => {
42
+ const toolCall = buildToolCall('tc-1');
43
+ const firstEvents = [
44
+ { type: 'tool-call', payload: toolCall },
45
+ { type: 'finish', payload: {} },
46
+ ];
47
+ const continueEvents = [
48
+ { type: 'tool-call', payload: toolCall },
49
+ { type: 'finish', payload: {} },
50
+ ];
51
+ const repository = {
52
+ sendChat: vi.fn().mockResolvedValue(new Response('ok')),
53
+ continueChat: vi.fn().mockResolvedValue(new Response('ok')),
54
+ collectStreamEvents: vi
55
+ .fn()
56
+ .mockResolvedValueOnce(firstEvents)
57
+ .mockResolvedValue(continueEvents),
58
+ executeToolCall: vi.fn().mockResolvedValue({ output: { ok: true } }),
59
+ parseErrorResponse: vi.fn().mockResolvedValue('error'),
60
+ buildAgentHeaders: vi.fn().mockResolvedValue({ 'Content-Type': 'application/json' }),
61
+ buildToolApiHeaders: vi.fn().mockReturnValue({}),
62
+ };
63
+ const onError = vi.fn();
64
+ const dispatch = vi.fn();
65
+ await sendMessageUseCase({
66
+ message: 'hello',
67
+ repository,
68
+ dispatch,
69
+ getState: () => createInitialChatState(),
70
+ config: {
71
+ endpoint: 'http://localhost:3002/api/agent',
72
+ apiBaseUrl: 'http://localhost:3001/api',
73
+ agentId: 'assistant',
74
+ disableToolApiAuthHeader: false,
75
+ },
76
+ onError,
77
+ });
78
+ expect(onError).toHaveBeenCalledWith(expect.stringContaining('Tool execution loop exceeded maximum rounds'));
79
+ });
80
+ it('collectStreamEvents完了前でも受信イベントをdispatchして進捗反映する', async () => {
81
+ const progressEvent = { type: 'text-delta', payload: { text: 'hi' } };
82
+ const finishEvent = { type: 'finish', payload: {} };
83
+ let resolveCollect = null;
84
+ const repository = {
85
+ sendChat: vi.fn().mockResolvedValue(new Response('ok')),
86
+ continueChat: vi.fn(),
87
+ collectStreamEvents: vi.fn().mockImplementation((_response, options) => {
88
+ options?.onEvents?.([progressEvent]);
89
+ return new Promise((resolve) => {
90
+ resolveCollect = () => {
91
+ options?.onEvents?.([finishEvent]);
92
+ resolve([progressEvent, finishEvent]);
93
+ };
94
+ });
95
+ }),
96
+ executeToolCall: vi.fn(),
97
+ parseErrorResponse: vi.fn(),
98
+ buildAgentHeaders: vi.fn().mockResolvedValue({ 'Content-Type': 'application/json' }),
99
+ buildToolApiHeaders: vi.fn().mockReturnValue({}),
100
+ };
101
+ const dispatch = vi.fn();
102
+ const runPromise = sendMessageUseCase({
103
+ message: 'hello',
104
+ repository,
105
+ dispatch,
106
+ getState: () => createInitialChatState(),
107
+ config: {
108
+ endpoint: 'http://localhost:3002/api/agent',
109
+ apiBaseUrl: 'http://localhost:3001/api',
110
+ },
111
+ });
112
+ await new Promise((resolve) => setTimeout(resolve, 0));
113
+ const progressAction = dispatch.mock.calls
114
+ .map(([action]) => action)
115
+ .find((action) => action.type === 'UPDATE_ASSISTANT_PROGRESS');
116
+ expect(progressAction).toBeDefined();
117
+ if (!progressAction) {
118
+ throw new Error('Expected UPDATE_ASSISTANT_PROGRESS action');
119
+ }
120
+ expect(progressAction.payload.text).toBe('hi');
121
+ resolveCollect?.();
122
+ await runPromise;
123
+ });
124
+ it('変更系tool-callは承認後に実行される', async () => {
125
+ const toolCall = buildCreateScoutToolCall('tc-approval-1');
126
+ const firstEvents = [
127
+ { type: 'tool-call', payload: toolCall },
128
+ { type: 'finish', payload: {} },
129
+ ];
130
+ const continueEvents = [{ type: 'finish', payload: {} }];
131
+ const repository = {
132
+ sendChat: vi.fn().mockResolvedValue(new Response('ok')),
133
+ continueChat: vi.fn().mockResolvedValue(new Response('ok')),
134
+ collectStreamEvents: vi
135
+ .fn()
136
+ .mockResolvedValueOnce(firstEvents)
137
+ .mockResolvedValueOnce(continueEvents),
138
+ executeToolCall: vi.fn().mockResolvedValue({ output: { ok: true } }),
139
+ parseErrorResponse: vi.fn().mockResolvedValue('error'),
140
+ buildAgentHeaders: vi.fn().mockResolvedValue({ 'Content-Type': 'application/json' }),
141
+ buildToolApiHeaders: vi.fn().mockReturnValue({}),
142
+ };
143
+ const requestToolApproval = vi.fn().mockResolvedValue(true);
144
+ await sendMessageUseCase({
145
+ message: '送って',
146
+ repository,
147
+ dispatch: vi.fn(),
148
+ getState: () => createInitialChatState(),
149
+ config: {
150
+ endpoint: 'http://localhost:3002/api/agent',
151
+ apiBaseUrl: 'http://localhost:3001/api',
152
+ },
153
+ requestToolApproval,
154
+ });
155
+ expect(requestToolApproval).toHaveBeenCalledWith(toolCall);
156
+ expect(repository.executeToolCall).toHaveBeenCalledTimes(1);
157
+ });
158
+ it('変更系tool-callを拒否した場合は未実行で固定文言を返す', async () => {
159
+ const toolCall = buildCreateScoutToolCall('tc-approval-2');
160
+ const firstEvents = [
161
+ { type: 'tool-call', payload: toolCall },
162
+ { type: 'finish', payload: {} },
163
+ ];
164
+ const continueEvents = [{ type: 'finish', payload: {} }];
165
+ const continueChat = vi.fn().mockResolvedValue(new Response('ok'));
166
+ const repository = {
167
+ sendChat: vi.fn().mockResolvedValue(new Response('ok')),
168
+ continueChat,
169
+ collectStreamEvents: vi
170
+ .fn()
171
+ .mockResolvedValueOnce(firstEvents)
172
+ .mockResolvedValueOnce(continueEvents),
173
+ executeToolCall: vi.fn().mockResolvedValue({ output: { ok: true } }),
174
+ parseErrorResponse: vi.fn().mockResolvedValue('error'),
175
+ buildAgentHeaders: vi.fn().mockResolvedValue({ 'Content-Type': 'application/json' }),
176
+ buildToolApiHeaders: vi.fn().mockReturnValue({}),
177
+ };
178
+ await sendMessageUseCase({
179
+ message: '送って',
180
+ repository,
181
+ dispatch: vi.fn(),
182
+ getState: () => createInitialChatState(),
183
+ config: {
184
+ endpoint: 'http://localhost:3002/api/agent',
185
+ apiBaseUrl: 'http://localhost:3001/api',
186
+ },
187
+ requestToolApproval: vi.fn().mockResolvedValue(false),
188
+ });
189
+ expect(repository.executeToolCall).not.toHaveBeenCalled();
190
+ expect(continueChat).toHaveBeenCalledWith(expect.objectContaining({
191
+ payload: expect.objectContaining({
192
+ toolResults: [
193
+ expect.objectContaining({
194
+ toolCallId: 'tc-approval-2',
195
+ isError: true,
196
+ result: expect.stringContaining('USER_DENIED_EXECUTION'),
197
+ }),
198
+ ],
199
+ }),
200
+ }));
201
+ });
202
+ it('同一ラウンドで一部拒否されても他のtool-callは続行する', async () => {
203
+ const rejectToolCall = buildCreateScoutToolCall('tc-approval-3');
204
+ const approveToolCall = buildCreateScoutToolCall('tc-approval-4');
205
+ approveToolCall.input = { candidateId: 'c-2', subject: '件名2', body: '本文2' };
206
+ const firstEvents = [
207
+ { type: 'tool-call', payload: rejectToolCall },
208
+ { type: 'tool-call', payload: approveToolCall },
209
+ { type: 'finish', payload: {} },
210
+ ];
211
+ const continueEvents = [{ type: 'finish', payload: {} }];
212
+ const continueChat = vi.fn().mockResolvedValue(new Response('ok'));
213
+ const executeToolCall = vi.fn().mockResolvedValue({ output: { ok: true } });
214
+ const repository = {
215
+ sendChat: vi.fn().mockResolvedValue(new Response('ok')),
216
+ continueChat,
217
+ collectStreamEvents: vi
218
+ .fn()
219
+ .mockResolvedValueOnce(firstEvents)
220
+ .mockResolvedValueOnce(continueEvents),
221
+ executeToolCall,
222
+ parseErrorResponse: vi.fn().mockResolvedValue('error'),
223
+ buildAgentHeaders: vi.fn().mockResolvedValue({ 'Content-Type': 'application/json' }),
224
+ buildToolApiHeaders: vi.fn().mockReturnValue({}),
225
+ };
226
+ const requestToolApproval = vi
227
+ .fn()
228
+ .mockResolvedValueOnce(false)
229
+ .mockResolvedValueOnce(true);
230
+ await sendMessageUseCase({
231
+ message: '送って',
232
+ repository,
233
+ dispatch: vi.fn(),
234
+ getState: () => createInitialChatState(),
235
+ config: {
236
+ endpoint: 'http://localhost:3002/api/agent',
237
+ apiBaseUrl: 'http://localhost:3001/api',
238
+ },
239
+ requestToolApproval,
240
+ });
241
+ expect(executeToolCall).toHaveBeenCalledTimes(1);
242
+ expect(continueChat).toHaveBeenCalledWith(expect.objectContaining({
243
+ payload: expect.objectContaining({
244
+ toolResults: expect.arrayContaining([
245
+ expect.objectContaining({
246
+ toolCallId: 'tc-approval-3',
247
+ isError: true,
248
+ }),
249
+ expect.objectContaining({
250
+ toolCallId: 'tc-approval-4',
251
+ isError: false,
252
+ }),
253
+ ]),
254
+ }),
255
+ }));
256
+ });
257
+ });
@@ -3,53 +3,6 @@
3
3
  * 消費側で @import "@agent-platform/ui/styles/globals.css" として使用
4
4
  * 注意: @import "tailwindcss" は消費側で行う
5
5
  */
6
-
7
- @custom-variant dark (&:is(.dark *));
8
-
9
- @theme inline {
10
- --color-background: var(--background);
11
- --color-foreground: var(--foreground);
12
- --font-sans: var(--font-sans);
13
- --font-mono: var(--font-geist-mono);
14
- --color-sidebar-ring: var(--sidebar-ring);
15
- --color-sidebar-border: var(--sidebar-border);
16
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
17
- --color-sidebar-accent: var(--sidebar-accent);
18
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
19
- --color-sidebar-primary: var(--sidebar-primary);
20
- --color-sidebar-foreground: var(--sidebar-foreground);
21
- --color-sidebar: var(--sidebar);
22
- --color-chart-5: var(--chart-5);
23
- --color-chart-4: var(--chart-4);
24
- --color-chart-3: var(--chart-3);
25
- --color-chart-2: var(--chart-2);
26
- --color-chart-1: var(--chart-1);
27
- --color-ring: var(--ring);
28
- --color-input: var(--input);
29
- --color-border: var(--border);
30
- --color-destructive: var(--destructive);
31
- --color-accent-foreground: var(--accent-foreground);
32
- --color-accent: var(--accent);
33
- --color-muted-foreground: var(--muted-foreground);
34
- --color-muted: var(--muted);
35
- --color-placeholder: var(--placeholder);
36
- --color-secondary-foreground: var(--secondary-foreground);
37
- --color-secondary: var(--secondary);
38
- --color-primary-foreground: var(--primary-foreground);
39
- --color-primary: var(--primary);
40
- --color-popover-foreground: var(--popover-foreground);
41
- --color-popover: var(--popover);
42
- --color-card-foreground: var(--card-foreground);
43
- --color-card: var(--card);
44
- --radius-sm: calc(var(--radius) - 4px);
45
- --radius-md: calc(var(--radius) - 2px);
46
- --radius-lg: var(--radius);
47
- --radius-xl: calc(var(--radius) + 4px);
48
- --radius-2xl: calc(var(--radius) + 8px);
49
- --radius-3xl: calc(var(--radius) + 12px);
50
- --radius-4xl: calc(var(--radius) + 16px);
51
- }
52
-
53
6
  :root {
54
7
  --background: oklch(1 0 0);
55
8
  --foreground: oklch(0.145 0 0);
@@ -121,15 +74,6 @@
121
74
  --sidebar-ring: oklch(0.556 0 0);
122
75
  }
123
76
 
124
- @layer base {
125
- * {
126
- @apply border-border outline-ring/50;
127
- }
128
- body {
129
- @apply bg-background text-foreground;
130
- }
131
- }
132
-
133
77
  /* Custom animations for agent loading states */
134
78
  @layer utilities {
135
79
  @keyframes dot-fade {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-platform/ui",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -26,14 +26,12 @@
26
26
  "react-dom": "^19.0.0"
27
27
  },
28
28
  "dependencies": {
29
- "@base-ui/react": "^1.0.0",
30
29
  "class-variance-authority": "^0.7.1",
31
30
  "clsx": "^2.1.1",
32
31
  "radix-ui": "^1.4.3",
33
32
  "react-markdown": "^10.1.0",
34
33
  "remark-gfm": "^4.0.1",
35
- "tailwind-merge": "^3.4.0",
36
- "tw-animate-css": "^1.0.0"
34
+ "tailwind-merge": "^3.4.0"
37
35
  },
38
36
  "devDependencies": {
39
37
  "@phosphor-icons/react": "^2.1.10",
@@ -41,7 +39,6 @@
41
39
  "@types/react-dom": "^19",
42
40
  "react": "19.2.3",
43
41
  "react-dom": "19.2.3",
44
- "shadcn": "^2.0.0",
45
42
  "typescript": "^5",
46
43
  "@agent-platform/server": "0.0.1"
47
44
  },
@@ -49,6 +46,7 @@
49
46
  "build": "pnpm run build:types && pnpm run build:styles",
50
47
  "build:types": "tsc -p tsconfig.build.json",
51
48
  "build:styles": "mkdir -p dist/styles && cp src/styles/globals.css dist/styles/globals.css",
49
+ "test": "pnpm -C ../server exec vitest --root ../ui",
52
50
  "typecheck": "tsc --noEmit",
53
51
  "pack:check": "pnpm pack",
54
52
  "publish:check-auth": "node ./scripts/check-publish-auth.mjs",