@open-mercato/ai-assistant 0.6.2-develop.3406.1.2b403f40da → 0.6.2-develop.3446.1.bd060c6017
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +8 -1
- package/build.mjs +1 -0
- package/dist/frontend/components/AiChatButton.js +1 -1
- package/dist/frontend/components/AiChatButton.js.map +2 -2
- package/dist/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.js +16 -5
- package/dist/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.js.map +2 -2
- package/dist/modules/ai_assistant/ai-tools/meta-pack.js +58 -1
- package/dist/modules/ai_assistant/ai-tools/meta-pack.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/agents/route.js +2 -1
- package/dist/modules/ai_assistant/api/ai/agents/route.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/i18n/de.json +7 -1
- package/dist/modules/ai_assistant/i18n/en.json +7 -1
- package/dist/modules/ai_assistant/i18n/es.json +7 -1
- package/dist/modules/ai_assistant/i18n/pl.json +7 -1
- package/dist/modules/ai_assistant/lib/agent-registry.js +26 -6
- package/dist/modules/ai_assistant/lib/agent-registry.js.map +2 -2
- package/dist/modules/ai_assistant/lib/agent-runtime.js +21 -8
- package/dist/modules/ai_assistant/lib/agent-runtime.js.map +3 -3
- package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +2 -2
- package/dist/modules/ai_assistant/lib/pending-action-types.js.map +2 -2
- package/dist/modules/ai_assistant/lib/prepare-mutation.js +16 -6
- package/dist/modules/ai_assistant/lib/prepare-mutation.js.map +2 -2
- package/dist/modules/ai_assistant/lib/task-plan-labels.js +95 -0
- package/dist/modules/ai_assistant/lib/task-plan-labels.js.map +7 -0
- package/dist/modules/ai_assistant/lib/task-plan-stream.js +349 -0
- package/dist/modules/ai_assistant/lib/task-plan-stream.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-test-fixtures.js +3 -0
- package/dist/modules/ai_assistant/lib/tool-test-fixtures.js.map +2 -2
- package/package.json +6 -6
- package/src/frontend/components/AiChatButton.tsx +1 -1
- package/src/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.ts +20 -8
- package/src/modules/ai_assistant/ai-tools/__tests__/meta-pack.test.ts +60 -4
- package/src/modules/ai_assistant/ai-tools/meta-pack.ts +79 -2
- package/src/modules/ai_assistant/api/ai/agents/route.ts +2 -1
- package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +1 -0
- package/src/modules/ai_assistant/i18n/de.json +7 -1
- package/src/modules/ai_assistant/i18n/en.json +7 -1
- package/src/modules/ai_assistant/i18n/es.json +7 -1
- package/src/modules/ai_assistant/i18n/pl.json +7 -1
- package/src/modules/ai_assistant/lib/__tests__/agent-registry.test.ts +60 -0
- package/src/modules/ai_assistant/lib/__tests__/ai-agent-definition.test.ts +4 -0
- package/src/modules/ai_assistant/lib/__tests__/prepare-mutation.test.ts +43 -0
- package/src/modules/ai_assistant/lib/__tests__/task-plan-stream.test.ts +375 -0
- package/src/modules/ai_assistant/lib/agent-registry.ts +36 -5
- package/src/modules/ai_assistant/lib/agent-runtime.ts +26 -8
- package/src/modules/ai_assistant/lib/ai-agent-definition.ts +14 -0
- package/src/modules/ai_assistant/lib/pending-action-types.ts +4 -1
- package/src/modules/ai_assistant/lib/prepare-mutation.ts +17 -5
- package/src/modules/ai_assistant/lib/task-plan-labels.ts +112 -0
- package/src/modules/ai_assistant/lib/task-plan-stream.ts +463 -0
- package/src/modules/ai_assistant/lib/tool-test-fixtures.ts +3 -0
- package/src/modules/ai_assistant/lib/types.ts +16 -0
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Covers `list_agents` empty-registry graceful case, RBAC filtering,
|
|
5
5
|
* super-admin bypass, `describe_agent` not-found / forbidden / happy,
|
|
6
|
-
* and the `output.schema` JSON-Schema
|
|
6
|
+
* `update_task_plan` sanitization, and the `output.schema` JSON-Schema
|
|
7
|
+
* fallback.
|
|
7
8
|
*/
|
|
8
9
|
import { z } from 'zod'
|
|
9
10
|
import type { AiAgentDefinition } from '../../lib/ai-agent-definition'
|
|
@@ -148,6 +149,7 @@ describe('meta.describe_agent', () => {
|
|
|
148
149
|
readOnly: true,
|
|
149
150
|
mutationPolicy: 'read-only',
|
|
150
151
|
acceptedMediaTypes: ['image', 'pdf'],
|
|
152
|
+
taskPlan: { enabled: true },
|
|
151
153
|
maxSteps: 6,
|
|
152
154
|
output: { schemaName: 'MerchProposal', schema, mode: 'generate' },
|
|
153
155
|
keywords: ['catalog', 'merch'],
|
|
@@ -159,9 +161,14 @@ describe('meta.describe_agent', () => {
|
|
|
159
161
|
const agent = result.agent as Record<string, unknown>
|
|
160
162
|
expect(agent.id).toBe('catalog.merch')
|
|
161
163
|
expect(agent.executionMode).toBe('object')
|
|
162
|
-
expect(agent.allowedTools).toEqual([
|
|
164
|
+
expect(agent.allowedTools).toEqual([
|
|
165
|
+
'search.hybrid_search',
|
|
166
|
+
'catalog.get_product_bundle',
|
|
167
|
+
'meta.update_task_plan',
|
|
168
|
+
])
|
|
163
169
|
expect(agent.readOnly).toBe(true)
|
|
164
170
|
expect(agent.acceptedMediaTypes).toEqual(['image', 'pdf'])
|
|
171
|
+
expect(agent.taskPlan).toEqual({ enabled: true })
|
|
165
172
|
const output = agent.output as Record<string, unknown>
|
|
166
173
|
expect(output.schemaName).toBe('MerchProposal')
|
|
167
174
|
expect(output.jsonSchema).toBeDefined()
|
|
@@ -206,10 +213,59 @@ describe('meta.describe_agent', () => {
|
|
|
206
213
|
})
|
|
207
214
|
})
|
|
208
215
|
|
|
216
|
+
describe('meta.update_task_plan', () => {
|
|
217
|
+
const tool = findTool('meta.update_task_plan')
|
|
218
|
+
|
|
219
|
+
it('returns sanitized user-visible task labels', async () => {
|
|
220
|
+
const ctx = makeCtx()
|
|
221
|
+
const result = (await tool.handler(
|
|
222
|
+
{
|
|
223
|
+
tasks: [
|
|
224
|
+
{
|
|
225
|
+
id: ' step one ',
|
|
226
|
+
label: ' Search catalog products ',
|
|
227
|
+
detail: ' Use product search ',
|
|
228
|
+
toolName: 'catalog__search_products',
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
ctx as any,
|
|
233
|
+
)) as Record<string, unknown>
|
|
234
|
+
|
|
235
|
+
expect(result.ok).toBe(true)
|
|
236
|
+
expect(result.accepted).toBe(1)
|
|
237
|
+
expect(result.tasks).toEqual([
|
|
238
|
+
{
|
|
239
|
+
id: 'step-one',
|
|
240
|
+
label: 'Search catalog products',
|
|
241
|
+
detail: 'Use product search',
|
|
242
|
+
toolName: 'catalog.search_products',
|
|
243
|
+
},
|
|
244
|
+
])
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('rejects hidden-reasoning-like labels at schema validation', () => {
|
|
248
|
+
expect(() =>
|
|
249
|
+
tool.inputSchema.parse({
|
|
250
|
+
tasks: [
|
|
251
|
+
{
|
|
252
|
+
label: '<thinking>First I will inspect private chain of thought</thinking>',
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
}),
|
|
256
|
+
).toThrow(/private reasoning|Task-plan labels/i)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('does not expose mutation capability', () => {
|
|
260
|
+
expect(tool.isMutation).not.toBe(true)
|
|
261
|
+
expect(tool.requiredFeatures).toEqual(['ai_assistant.view'])
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
209
265
|
describe('meta-pack tool surface', () => {
|
|
210
|
-
it('exports the
|
|
266
|
+
it('exports the read-only meta tools', () => {
|
|
211
267
|
const names = metaAiTools.map((tool) => tool.name)
|
|
212
|
-
expect(names).toEqual(['meta.list_agents', 'meta.describe_agent'])
|
|
268
|
+
expect(names).toEqual(['meta.list_agents', 'meta.describe_agent', 'meta.update_task_plan'])
|
|
213
269
|
for (const tool of metaAiTools) {
|
|
214
270
|
expect(tool.isMutation).not.toBe(true)
|
|
215
271
|
expect(tool.requiredFeatures).toEqual(['ai_assistant.view'])
|
|
@@ -12,7 +12,15 @@ import type { AiAgentDefinition } from '../lib/ai-agent-definition'
|
|
|
12
12
|
import { listAgents, getAgent } from '../lib/agent-registry'
|
|
13
13
|
import { hasRequiredFeatures } from '../lib/auth'
|
|
14
14
|
import { defineAiTool } from '../lib/ai-tool-definition'
|
|
15
|
-
import
|
|
15
|
+
import {
|
|
16
|
+
TASK_PLAN_DETAIL_MAX_CHARS,
|
|
17
|
+
TASK_PLAN_ID_MAX_CHARS,
|
|
18
|
+
TASK_PLAN_LABEL_MAX_CHARS,
|
|
19
|
+
TASK_PLAN_MAX_TASKS,
|
|
20
|
+
TASK_PLAN_TOOL_NAME_MAX_CHARS,
|
|
21
|
+
looksLikeHiddenReasoning,
|
|
22
|
+
sanitizeAgentTaskPlanInput,
|
|
23
|
+
} from '../lib/task-plan-labels'
|
|
16
24
|
|
|
17
25
|
function summarizeAgent(agent: AiAgentDefinition): Record<string, unknown> {
|
|
18
26
|
return {
|
|
@@ -30,6 +38,7 @@ function summarizeAgent(agent: AiAgentDefinition): Record<string, unknown> {
|
|
|
30
38
|
domain: agent.domain ?? null,
|
|
31
39
|
keywords: agent.keywords ?? [],
|
|
32
40
|
suggestions: agent.suggestions ?? [],
|
|
41
|
+
taskPlan: agent.taskPlan ?? { enabled: false },
|
|
33
42
|
dataCapabilities: agent.dataCapabilities ?? null,
|
|
34
43
|
hasOutputSchema: Boolean(agent.output),
|
|
35
44
|
hasPageContextResolver: typeof agent.resolvePageContext === 'function',
|
|
@@ -135,6 +144,74 @@ const describeAgentTool = defineAiTool({
|
|
|
135
144
|
},
|
|
136
145
|
})
|
|
137
146
|
|
|
138
|
-
|
|
147
|
+
const visibleTaskLabel = z
|
|
148
|
+
.string()
|
|
149
|
+
.min(1)
|
|
150
|
+
.max(TASK_PLAN_LABEL_MAX_CHARS)
|
|
151
|
+
.superRefine((value, ctx) => {
|
|
152
|
+
if (!looksLikeHiddenReasoning(value)) return
|
|
153
|
+
ctx.addIssue({
|
|
154
|
+
code: 'custom',
|
|
155
|
+
message: 'Task-plan labels are user-visible UI copy and must not include private reasoning.',
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
const visibleTaskDetail = z
|
|
160
|
+
.string()
|
|
161
|
+
.max(TASK_PLAN_DETAIL_MAX_CHARS)
|
|
162
|
+
.optional()
|
|
163
|
+
.superRefine((value, ctx) => {
|
|
164
|
+
if (!value || !looksLikeHiddenReasoning(value)) return
|
|
165
|
+
ctx.addIssue({
|
|
166
|
+
code: 'custom',
|
|
167
|
+
message: 'Task-plan details must not include private reasoning.',
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const updateTaskPlanInput = z.object({
|
|
172
|
+
tasks: z
|
|
173
|
+
.array(
|
|
174
|
+
z.object({
|
|
175
|
+
id: z.string().min(1).max(TASK_PLAN_ID_MAX_CHARS).optional(),
|
|
176
|
+
label: visibleTaskLabel.describe('Concise user-visible step label. Do not include private reasoning.'),
|
|
177
|
+
detail: visibleTaskDetail.describe('Optional short visible detail, not private reasoning.'),
|
|
178
|
+
toolName: z
|
|
179
|
+
.string()
|
|
180
|
+
.min(1)
|
|
181
|
+
.max(TASK_PLAN_TOOL_NAME_MAX_CHARS)
|
|
182
|
+
.optional()
|
|
183
|
+
.describe('Optional whitelisted tool name that this planned step maps to.'),
|
|
184
|
+
}),
|
|
185
|
+
)
|
|
186
|
+
.min(1)
|
|
187
|
+
.max(TASK_PLAN_MAX_TASKS),
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
const updateTaskPlanTool = defineAiTool({
|
|
191
|
+
name: 'meta.update_task_plan',
|
|
192
|
+
displayName: 'Update task plan',
|
|
193
|
+
description:
|
|
194
|
+
'Set the concise user-visible task plan for this assistant turn before calling domain tools. Labels are progress UI, not hidden reasoning.',
|
|
195
|
+
inputSchema: updateTaskPlanInput,
|
|
196
|
+
requiredFeatures: ['ai_assistant.view'],
|
|
197
|
+
tags: ['read', 'meta', 'task-plan'],
|
|
198
|
+
isMutation: false,
|
|
199
|
+
handler: async (rawInput) => {
|
|
200
|
+
const input = updateTaskPlanInput.parse(rawInput)
|
|
201
|
+
const sanitized = sanitizeAgentTaskPlanInput(input)
|
|
202
|
+
return {
|
|
203
|
+
ok: sanitized.tasks.length > 0,
|
|
204
|
+
tasks: sanitized.tasks,
|
|
205
|
+
accepted: sanitized.tasks.length,
|
|
206
|
+
dropped: input.tasks.length - sanitized.tasks.length,
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
export const metaAiTools = [
|
|
212
|
+
listAgentsTool,
|
|
213
|
+
describeAgentTool,
|
|
214
|
+
updateTaskPlanTool,
|
|
215
|
+
]
|
|
139
216
|
|
|
140
217
|
export default metaAiTools
|
|
@@ -4,7 +4,7 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
|
4
4
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
5
|
import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
|
|
6
6
|
import { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'
|
|
7
|
-
import { listAgents, loadAgentRegistry } from '../../../lib/agent-registry'
|
|
7
|
+
import { isAgentTaskPlanEnabled, listAgents, loadAgentRegistry } from '../../../lib/agent-registry'
|
|
8
8
|
import { hasRequiredFeatures } from '../../../lib/auth'
|
|
9
9
|
import { toolRegistry } from '../../../lib/tool-registry'
|
|
10
10
|
import type { AiToolDefinition } from '../../../lib/types'
|
|
@@ -90,6 +90,7 @@ export async function GET(req: NextRequest) {
|
|
|
90
90
|
mutationPolicy: agent.mutationPolicy ?? 'read-only',
|
|
91
91
|
readOnly: Boolean(agent.readOnly),
|
|
92
92
|
maxSteps: agent.maxSteps ?? null,
|
|
93
|
+
taskPlan: { enabled: isAgentTaskPlanEnabled(agent) },
|
|
93
94
|
allowedTools: agent.allowedTools,
|
|
94
95
|
tools,
|
|
95
96
|
requiredFeatures: agent.requiredFeatures ?? [],
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
"ai_assistant.allowlist.save.success": "Liste gespeichert.",
|
|
147
147
|
"ai_assistant.allowlist.subtitle": "Schränke Anbieter und Modelle ein, die für diesen Mandanten verwendet werden dürfen. Die ENV-Liste ist die äußere Beschränkung — die Mandantenauswahl engt sie weiter ein.",
|
|
148
148
|
"ai_assistant.allowlist.title": "Anbieter- & Modell-Allowlist",
|
|
149
|
-
"ai_assistant.chat.agentTasksTitle": "
|
|
149
|
+
"ai_assistant.chat.agentTasksTitle": "Tool-Aufrufe",
|
|
150
150
|
"ai_assistant.chat.assistantRoleLabel": "Assistent",
|
|
151
151
|
"ai_assistant.chat.attachFile": "Attach file",
|
|
152
152
|
"ai_assistant.chat.betaChip": "beta",
|
|
@@ -257,6 +257,12 @@
|
|
|
257
257
|
"ai_assistant.chat.tabs.noSessions": "Keine Sitzungen",
|
|
258
258
|
"ai_assistant.chat.tabs.recentSessions": "Letzte Sitzungen",
|
|
259
259
|
"ai_assistant.chat.tabs.rename": "Umbenennen",
|
|
260
|
+
"ai_assistant.chat.taskDone": "fertig",
|
|
261
|
+
"ai_assistant.chat.taskFailed": "fehlgeschlagen",
|
|
262
|
+
"ai_assistant.chat.taskPending": "ausstehend",
|
|
263
|
+
"ai_assistant.chat.taskPlanTitle": "Plan",
|
|
264
|
+
"ai_assistant.chat.taskRunning": "läuft…",
|
|
265
|
+
"ai_assistant.chat.taskSkipped": "übersprungen",
|
|
260
266
|
"ai_assistant.chat.thinking": "Denkt nach...",
|
|
261
267
|
"ai_assistant.chat.toolDone": "done",
|
|
262
268
|
"ai_assistant.chat.toolError": "failed",
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
"ai_assistant.allowlist.save.success": "Allowlist saved.",
|
|
147
147
|
"ai_assistant.allowlist.subtitle": "Limit which providers and models the runtime, settings, and chat picker may use for this tenant. The env allowlist is the outer constraint — tenant picks narrow it further.",
|
|
148
148
|
"ai_assistant.allowlist.title": "AI provider & model allowlist",
|
|
149
|
-
"ai_assistant.chat.agentTasksTitle": "
|
|
149
|
+
"ai_assistant.chat.agentTasksTitle": "Tool calls",
|
|
150
150
|
"ai_assistant.chat.assistantRoleLabel": "Assistant",
|
|
151
151
|
"ai_assistant.chat.attachFile": "Attach file",
|
|
152
152
|
"ai_assistant.chat.betaChip": "beta",
|
|
@@ -257,6 +257,12 @@
|
|
|
257
257
|
"ai_assistant.chat.tabs.noSessions": "No sessions",
|
|
258
258
|
"ai_assistant.chat.tabs.recentSessions": "Recent sessions",
|
|
259
259
|
"ai_assistant.chat.tabs.rename": "Rename",
|
|
260
|
+
"ai_assistant.chat.taskDone": "done",
|
|
261
|
+
"ai_assistant.chat.taskFailed": "failed",
|
|
262
|
+
"ai_assistant.chat.taskPending": "pending",
|
|
263
|
+
"ai_assistant.chat.taskPlanTitle": "Plan",
|
|
264
|
+
"ai_assistant.chat.taskRunning": "running…",
|
|
265
|
+
"ai_assistant.chat.taskSkipped": "skipped",
|
|
260
266
|
"ai_assistant.chat.thinking": "Thinking...",
|
|
261
267
|
"ai_assistant.chat.toolDone": "done",
|
|
262
268
|
"ai_assistant.chat.toolError": "failed",
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
"ai_assistant.allowlist.save.success": "Lista guardada.",
|
|
147
147
|
"ai_assistant.allowlist.subtitle": "Limita los proveedores y modelos que el runtime, los ajustes y el selector de chat pueden usar para este inquilino. La lista ENV es la restricción externa — las elecciones del inquilino la reducen.",
|
|
148
148
|
"ai_assistant.allowlist.title": "Lista de proveedores y modelos AI",
|
|
149
|
-
"ai_assistant.chat.agentTasksTitle": "
|
|
149
|
+
"ai_assistant.chat.agentTasksTitle": "Llamadas a herramientas",
|
|
150
150
|
"ai_assistant.chat.assistantRoleLabel": "Asistente",
|
|
151
151
|
"ai_assistant.chat.attachFile": "Attach file",
|
|
152
152
|
"ai_assistant.chat.betaChip": "beta",
|
|
@@ -257,6 +257,12 @@
|
|
|
257
257
|
"ai_assistant.chat.tabs.noSessions": "Sin sesiones",
|
|
258
258
|
"ai_assistant.chat.tabs.recentSessions": "Sesiones recientes",
|
|
259
259
|
"ai_assistant.chat.tabs.rename": "Renombrar",
|
|
260
|
+
"ai_assistant.chat.taskDone": "hecho",
|
|
261
|
+
"ai_assistant.chat.taskFailed": "fallido",
|
|
262
|
+
"ai_assistant.chat.taskPending": "pendiente",
|
|
263
|
+
"ai_assistant.chat.taskPlanTitle": "Plan",
|
|
264
|
+
"ai_assistant.chat.taskRunning": "en curso…",
|
|
265
|
+
"ai_assistant.chat.taskSkipped": "omitido",
|
|
260
266
|
"ai_assistant.chat.thinking": "Pensando...",
|
|
261
267
|
"ai_assistant.chat.toolDone": "done",
|
|
262
268
|
"ai_assistant.chat.toolError": "failed",
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
"ai_assistant.allowlist.save.success": "Lista zapisana.",
|
|
147
147
|
"ai_assistant.allowlist.subtitle": "Ogranicz dostawców i modele dostępne dla tego najemcy. Lista ENV jest zewnętrznym ograniczeniem — wybory najemcy je zawężają.",
|
|
148
148
|
"ai_assistant.allowlist.title": "Lista dozwolonych dostawców i modeli AI",
|
|
149
|
-
"ai_assistant.chat.agentTasksTitle": "
|
|
149
|
+
"ai_assistant.chat.agentTasksTitle": "Wywołania narzędzi",
|
|
150
150
|
"ai_assistant.chat.assistantRoleLabel": "Asystent",
|
|
151
151
|
"ai_assistant.chat.attachFile": "Attach file",
|
|
152
152
|
"ai_assistant.chat.betaChip": "beta",
|
|
@@ -257,6 +257,12 @@
|
|
|
257
257
|
"ai_assistant.chat.tabs.noSessions": "Brak sesji",
|
|
258
258
|
"ai_assistant.chat.tabs.recentSessions": "Ostatnie sesje",
|
|
259
259
|
"ai_assistant.chat.tabs.rename": "Zmień nazwę",
|
|
260
|
+
"ai_assistant.chat.taskDone": "ukończono",
|
|
261
|
+
"ai_assistant.chat.taskFailed": "niepowodzenie",
|
|
262
|
+
"ai_assistant.chat.taskPending": "oczekuje",
|
|
263
|
+
"ai_assistant.chat.taskPlanTitle": "Plan",
|
|
264
|
+
"ai_assistant.chat.taskRunning": "w toku…",
|
|
265
|
+
"ai_assistant.chat.taskSkipped": "pominięto",
|
|
260
266
|
"ai_assistant.chat.thinking": "Myślenie...",
|
|
261
267
|
"ai_assistant.chat.toolDone": "done",
|
|
262
268
|
"ai_assistant.chat.toolError": "failed",
|
|
@@ -187,6 +187,66 @@ describe('agent-registry', () => {
|
|
|
187
187
|
})
|
|
188
188
|
})
|
|
189
189
|
|
|
190
|
+
it('adds the internal task-plan tool only when taskPlan is enabled', () => {
|
|
191
|
+
seedAgentRegistryForTests([
|
|
192
|
+
makeAgent({
|
|
193
|
+
id: 'customers.account_assistant',
|
|
194
|
+
moduleId: 'customers',
|
|
195
|
+
allowedTools: ['customers.list_people'],
|
|
196
|
+
taskPlan: { enabled: true },
|
|
197
|
+
}),
|
|
198
|
+
makeAgent({
|
|
199
|
+
id: 'catalog.catalog_assistant',
|
|
200
|
+
moduleId: 'catalog',
|
|
201
|
+
allowedTools: ['catalog.list_products', 'meta.update_task_plan'],
|
|
202
|
+
}),
|
|
203
|
+
])
|
|
204
|
+
|
|
205
|
+
expect(getAgent('customers.account_assistant')).toMatchObject({
|
|
206
|
+
taskPlan: { enabled: true },
|
|
207
|
+
allowedTools: ['customers.list_people', 'meta.update_task_plan'],
|
|
208
|
+
})
|
|
209
|
+
const catalogAgent = getAgent('catalog.catalog_assistant')
|
|
210
|
+
expect(catalogAgent?.taskPlan).toBeUndefined()
|
|
211
|
+
expect(catalogAgent?.allowedTools).toEqual(['catalog.list_products'])
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('lets extensions opt an existing agent in or out of visible task planning', () => {
|
|
215
|
+
seedAgentRegistryForTests([
|
|
216
|
+
makeAgent({
|
|
217
|
+
id: 'catalog.catalog_assistant',
|
|
218
|
+
moduleId: 'catalog',
|
|
219
|
+
allowedTools: ['catalog.list_products'],
|
|
220
|
+
}),
|
|
221
|
+
makeAgent({
|
|
222
|
+
id: 'customers.account_assistant',
|
|
223
|
+
moduleId: 'customers',
|
|
224
|
+
allowedTools: ['customers.list_people'],
|
|
225
|
+
taskPlan: { enabled: true },
|
|
226
|
+
}),
|
|
227
|
+
])
|
|
228
|
+
|
|
229
|
+
applyAgentExtensionEntriesForTests([
|
|
230
|
+
{
|
|
231
|
+
targetAgentId: 'catalog.catalog_assistant',
|
|
232
|
+
taskPlan: { enabled: true },
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
targetAgentId: 'customers.account_assistant',
|
|
236
|
+
taskPlan: { enabled: false },
|
|
237
|
+
},
|
|
238
|
+
])
|
|
239
|
+
|
|
240
|
+
expect(getAgent('catalog.catalog_assistant')).toMatchObject({
|
|
241
|
+
taskPlan: { enabled: true },
|
|
242
|
+
allowedTools: ['catalog.list_products', 'meta.update_task_plan'],
|
|
243
|
+
})
|
|
244
|
+
expect(getAgent('customers.account_assistant')).toMatchObject({
|
|
245
|
+
taskPlan: { enabled: false },
|
|
246
|
+
allowedTools: ['customers.list_people'],
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
|
|
190
250
|
it('resetAgentRegistryForTests clears the cache so a subsequent seed sees fresh fixtures', () => {
|
|
191
251
|
seedAgentRegistryForTests([
|
|
192
252
|
makeAgent({ id: 'catalog.merchandiser', moduleId: 'catalog' }),
|
|
@@ -71,6 +71,7 @@ describe('defineAiAgentExtension', () => {
|
|
|
71
71
|
replaceAllowedTools: ['catalog.list_products'],
|
|
72
72
|
deleteAllowedTools: ['catalog.old_tool'],
|
|
73
73
|
appendAllowedTools: ['example.catalog_stats'],
|
|
74
|
+
taskPlan: { enabled: true },
|
|
74
75
|
replaceSystemPrompt: 'Replacement prompt.',
|
|
75
76
|
appendSystemPrompt: 'Use example.catalog_stats for app-level catalog metrics.',
|
|
76
77
|
replaceSuggestions: [
|
|
@@ -86,6 +87,7 @@ describe('defineAiAgentExtension', () => {
|
|
|
86
87
|
expect(extension.replaceAllowedTools).toEqual(['catalog.list_products'])
|
|
87
88
|
expect(extension.deleteAllowedTools).toEqual(['catalog.old_tool'])
|
|
88
89
|
expect(extension.appendAllowedTools).toEqual(['example.catalog_stats'])
|
|
90
|
+
expect(extension.taskPlan).toEqual({ enabled: true })
|
|
89
91
|
expect(extension.replaceSystemPrompt).toBe('Replacement prompt.')
|
|
90
92
|
expect(extension.deleteSuggestions).toEqual(['Old prompt'])
|
|
91
93
|
expect(extension.appendSuggestions).toEqual([
|
|
@@ -128,6 +130,7 @@ describe('defineAiAgent', () => {
|
|
|
128
130
|
defaultModel: 'gpt-4o',
|
|
129
131
|
acceptedMediaTypes: ['image', 'pdf', 'file'],
|
|
130
132
|
requiredFeatures: ['catalog.products.view'],
|
|
133
|
+
taskPlan: { enabled: true },
|
|
131
134
|
uiParts: ['mutation-preview-card'],
|
|
132
135
|
readOnly: true,
|
|
133
136
|
mutationPolicy: 'read-only',
|
|
@@ -163,6 +166,7 @@ describe('defineAiAgent', () => {
|
|
|
163
166
|
expect(agent.domain).toBe('catalog')
|
|
164
167
|
expect(agent.acceptedMediaTypes).toEqual(['image', 'pdf', 'file'])
|
|
165
168
|
expect(agent.requiredFeatures).toEqual(['catalog.products.view'])
|
|
169
|
+
expect(agent.taskPlan).toEqual({ enabled: true })
|
|
166
170
|
expect(agent.uiParts).toEqual(['mutation-preview-card'])
|
|
167
171
|
expect(agent.readOnly).toBe(true)
|
|
168
172
|
expect(agent.defaultModel).toBe('gpt-4o')
|
|
@@ -267,6 +267,49 @@ describe('prepareMutation', () => {
|
|
|
267
267
|
expect(pendingAction.organizationId).toBe('org-alpha')
|
|
268
268
|
})
|
|
269
269
|
|
|
270
|
+
it('uses resolver-provided after snapshots and display labels for operator previews', async () => {
|
|
271
|
+
const em = mockEm()
|
|
272
|
+
const container = makeContainer(em)
|
|
273
|
+
const tool = makeTool({
|
|
274
|
+
name: 'customers.update_deal_stage',
|
|
275
|
+
isMutation: true,
|
|
276
|
+
loadBeforeRecord: async () => ({
|
|
277
|
+
recordId: 'deal-1',
|
|
278
|
+
entityType: 'customers.deal',
|
|
279
|
+
recordVersion: 'v-1',
|
|
280
|
+
before: { pipelineStageId: 'stage-old' },
|
|
281
|
+
after: { pipelineStageId: 'stage-new' },
|
|
282
|
+
display: {
|
|
283
|
+
fieldLabels: { pipelineStageId: 'Pipeline stage' },
|
|
284
|
+
before: { pipelineStageId: 'Offering' },
|
|
285
|
+
after: { pipelineStageId: 'Lost' },
|
|
286
|
+
},
|
|
287
|
+
}),
|
|
288
|
+
})
|
|
289
|
+
const agent = makeAgent({ id: 'customers.deal_analyzer' })
|
|
290
|
+
|
|
291
|
+
const { uiPart, pendingAction } = await prepareMutation(
|
|
292
|
+
{
|
|
293
|
+
agent,
|
|
294
|
+
tool,
|
|
295
|
+
toolCallArgs: { dealId: 'deal-1', toPipelineStageId: 'stage-new' },
|
|
296
|
+
},
|
|
297
|
+
{ ...baseCtx, container },
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
expect(uiPart.props.fieldDiff).toEqual([
|
|
301
|
+
{
|
|
302
|
+
field: 'pipelineStageId',
|
|
303
|
+
fieldLabel: 'Pipeline stage',
|
|
304
|
+
before: 'stage-old',
|
|
305
|
+
after: 'stage-new',
|
|
306
|
+
beforeDisplay: 'Offering',
|
|
307
|
+
afterDisplay: 'Lost',
|
|
308
|
+
},
|
|
309
|
+
])
|
|
310
|
+
expect(pendingAction.fieldDiff).toEqual(uiPart.props.fieldDiff)
|
|
311
|
+
})
|
|
312
|
+
|
|
270
313
|
it('batch happy path: populates records[] with per-record diffs (fieldDiff stays []) when isBulk=true', async () => {
|
|
271
314
|
const em = mockEm()
|
|
272
315
|
const container = makeContainer(em)
|