@open-mercato/ai-assistant 0.6.2-develop.3439.1.94b9d9733c → 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
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
[build:ai-assistant] found
|
|
1
|
+
[build:ai-assistant] found 182 entry points
|
|
2
2
|
[build:ai-assistant] built successfully
|
package/AGENTS.md
CHANGED
|
@@ -105,7 +105,7 @@ Typed AI agents live in each module's root `ai-agents.ts`. The generator auto-di
|
|
|
105
105
|
1. Create `<module>/ai-agents.ts` and export `aiAgents: AiAgentDefinition[]` (default export optional).
|
|
106
106
|
2. Declare the agent with `defineAiAgent({ ... })` from `@open-mercato/ai-assistant`. Required fields: `id`, `moduleId`, `label`, `description`, `systemPrompt`, `allowedTools`. Useful optional fields: `executionMode` (`'chat'` — default — or `'object'`), `executionEngine` (`'stream-text'` — default — or `'tool-loop-agent'`; see §"Loop controls and execution engines" below), `defaultProvider` (registered provider id the agent prefers; when paired with `defaultModel`, the pair fails closed if the provider is unconfigured; Phase 1 of `2026-04-27-ai-agents-provider-model-baseurl-overrides`), `defaultModel` (plain model id or slash-qualified `<provider>/<model>` shorthand, e.g. `openai/gpt-5-mini`), `acceptedMediaTypes`, `requiredFeatures`, `uiParts`, `readOnly`, `mutationPolicy` (`'read-only'` | `'confirm-required'` | `'destructive-confirm-required'`), `maxSteps`, `loop` (Phase 0–5 of spec `2026-04-28-ai-agents-agentic-loop-controls`), `output` (Zod schema for `'object'` mode), `resolvePageContext`, `keywords`, `suggestions`, `domain`, `dataCapabilities`.
|
|
107
107
|
3. Add the feature(s) you list in `requiredFeatures` to the module's `acl.ts` and grant them in `setup.ts` `defaultRoleFeatures`.
|
|
108
|
-
4. Put the agent's tool allowlist behind the narrowest set possible. Start from the general-purpose packs (`search.hybrid_search`, `search.get_record_context`, `attachments.list_record_attachments`, `attachments.read_attachment`, `meta.describe_agent`) and add your module's own `defineAiTool`-registered tools.
|
|
108
|
+
4. Put the agent's tool allowlist behind the narrowest set possible. Start from the general-purpose packs (`search.hybrid_search`, `search.get_record_context`, `attachments.list_record_attachments`, `attachments.read_attachment`, `meta.describe_agent`) and add your module's own `defineAiTool`-registered tools. Do not list `meta.update_task_plan` manually; set `taskPlan: { enabled: true }` when the agent should expose the visible planning helper.
|
|
109
109
|
5. For mutation-capable agents, keep `readOnly: true` + `mutationPolicy: 'read-only'` on the agent and light up writes only via the per-tenant mutation-policy override table (spec Phase 3 WS-C §5.4). The runtime filters out any `isMutation: true` tool when the override is still read-only.
|
|
110
110
|
6. Run `yarn generate` so the agent shows up in the registry. Smoke-test via `/backend/config/ai-assistant/playground` (see `/framework/ai-assistant/playground`), then embed `<AiChat agent="<module>.<agent>" />` in the page where you want the operator UI.
|
|
111
111
|
|
|
@@ -138,10 +138,17 @@ MUST rules:
|
|
|
138
138
|
- MUST use Zod for `inputSchema` — never raw JSON Schema.
|
|
139
139
|
- MUST set `isMutation: true` on write tools. The policy gate strips these from read-only agents and from read-only tenant overrides.
|
|
140
140
|
- MUST route every mutation tool through `prepareMutation(...)` (see the Mutation Approvals guide at `/framework/ai-assistant/mutation-approvals`). Writing directly inside the handler bypasses the approval gate — the runtime fails closed and refuses to return a result to the operator.
|
|
141
|
+
- Mutation preview resolvers SHOULD return a normalized `after` snapshot and display hints when tool inputs contain dictionary IDs or other opaque values. Use `display.fieldLabels`, `display.before`, and `display.after` so `field-diff-card` shows operator-friendly names while `field`, `before`, and `after` keep the raw execution values.
|
|
141
142
|
- MUST expose tools to an agent by listing the tool name in the agent's `allowedTools`. Tools not on the whitelist never reach the model.
|
|
142
143
|
|
|
143
144
|
Run `yarn generate` after adding/changing tool definitions so the typed tool registry picks them up.
|
|
144
145
|
|
|
146
|
+
### Visible Task Plans
|
|
147
|
+
|
|
148
|
+
Operator-facing chat agents can enable visible plans with `taskPlan: { enabled: true }`. CRM/customer agents enable it by default; other agents stay quiet unless their definition or an `AiAgentExtension` opts in. When enabled, the registry exposes the read-only `meta.update_task_plan` helper and the runtime injects prompt guidance to call it before domain tools on tool-using turns. Labels are user-visible progress copy: keep them short, include `toolName` when a step maps to a known tool, and never include private reasoning, chain-of-thought, scratchpad notes, or XML thinking tags. The runtime sanitizes and rejects unsafe labels before streaming `data-agent-task-plan` / `data-agent-task-update` chunks.
|
|
149
|
+
|
|
150
|
+
`<AiChat>` renders runtime tool lifecycle rows under **Tool calls**, not as Plan rows. When a task-plan chunk carries a label for a `toolCallId`, the Tool calls row uses it as a friendly caption while still showing the raw tool id in parentheses.
|
|
151
|
+
|
|
145
152
|
### How to Add a UI Part (record cards / custom inline widgets)
|
|
146
153
|
|
|
147
154
|
UI parts are typed inline widgets the agent streams into the chat. Two paths — pick the cheapest one that fits.
|
package/build.mjs
CHANGED
|
@@ -19,7 +19,7 @@ function AiChatButton({ onClick, className }) {
|
|
|
19
19
|
onClick: handleClick,
|
|
20
20
|
className,
|
|
21
21
|
"aria-label": "Open AI Assistant",
|
|
22
|
-
children: /* @__PURE__ */ jsx(AiIcon, { className: "h-5 w-5" })
|
|
22
|
+
children: /* @__PURE__ */ jsx(AiIcon, { className: "h-5 w-5 text-foreground" })
|
|
23
23
|
}
|
|
24
24
|
) }),
|
|
25
25
|
/* @__PURE__ */ jsx(TooltipContent, { side: "bottom", children: /* @__PURE__ */ jsxs("p", { children: [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/frontend/components/AiChatButton.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { AiIcon } from '@open-mercato/ui/ai/AiIcon'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from '@open-mercato/ui/primitives/tooltip'\n\ninterface AiChatButtonProps {\n onClick?: () => void\n className?: string\n}\n\nexport function AiChatButton({ onClick, className }: AiChatButtonProps) {\n const handleClick = (e: React.MouseEvent) => {\n e.preventDefault()\n onClick?.()\n }\n\n const isMac = typeof navigator !== 'undefined' && navigator.platform?.toUpperCase().indexOf('MAC') >= 0\n\n return (\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={handleClick}\n className={className}\n aria-label=\"Open AI Assistant\"\n >\n <AiIcon className=\"h-5 w-5\" />\n </Button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">\n <p>AI Assistant ({isMac ? '\u2318' : 'Ctrl+'}J)</p>\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n )\n}\n"],
|
|
5
|
-
"mappings": ";AAgCY,cAIF,YAJE;AA7BZ,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,SAAS,gBAAgB,gBAAgB,uBAAuB;AAOlE,SAAS,aAAa,EAAE,SAAS,UAAU,GAAsB;AACtE,QAAM,cAAc,CAAC,MAAwB;AAC3C,MAAE,eAAe;AACjB,cAAU;AAAA,EACZ;AAEA,QAAM,QAAQ,OAAO,cAAc,eAAe,UAAU,UAAU,YAAY,EAAE,QAAQ,KAAK,KAAK;AAEtG,SACE,oBAAC,mBACC,+BAAC,WACC;AAAA,wBAAC,kBAAe,SAAO,MACrB;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,SAAS;AAAA,QACT;AAAA,QACA,cAAW;AAAA,QAEX,8BAAC,UAAO,WAAU,
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { AiIcon } from '@open-mercato/ui/ai/AiIcon'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from '@open-mercato/ui/primitives/tooltip'\n\ninterface AiChatButtonProps {\n onClick?: () => void\n className?: string\n}\n\nexport function AiChatButton({ onClick, className }: AiChatButtonProps) {\n const handleClick = (e: React.MouseEvent) => {\n e.preventDefault()\n onClick?.()\n }\n\n const isMac = typeof navigator !== 'undefined' && navigator.platform?.toUpperCase().indexOf('MAC') >= 0\n\n return (\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={handleClick}\n className={className}\n aria-label=\"Open AI Assistant\"\n >\n <AiIcon className=\"h-5 w-5 text-foreground\" />\n </Button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">\n <p>AI Assistant ({isMac ? '\u2318' : 'Ctrl+'}J)</p>\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAgCY,cAIF,YAJE;AA7BZ,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,SAAS,gBAAgB,gBAAgB,uBAAuB;AAOlE,SAAS,aAAa,EAAE,SAAS,UAAU,GAAsB;AACtE,QAAM,cAAc,CAAC,MAAwB;AAC3C,MAAE,eAAe;AACjB,cAAU;AAAA,EACZ;AAEA,QAAM,QAAQ,OAAO,cAAc,eAAe,UAAU,UAAU,YAAY,EAAE,QAAQ,KAAK,KAAK;AAEtG,SACE,oBAAC,mBACC,+BAAC,WACC;AAAA,wBAAC,kBAAe,SAAO,MACrB;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,SAAS;AAAA,QACT;AAAA,QACA,cAAW;AAAA,QAEX,8BAAC,UAAO,WAAU,2BAA0B;AAAA;AAAA,IAC9C,GACF;AAAA,IACA,oBAAC,kBAAe,MAAK,UACnB,+BAAC,OAAE;AAAA;AAAA,MAAe,QAAQ,WAAM;AAAA,MAAQ;AAAA,OAAE,GAC5C;AAAA,KACF,GACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -79,9 +79,7 @@ test.describe("TC-AI-TOKEN-USAGE-001\u2013005: token usage stats page", () => {
|
|
|
79
79
|
});
|
|
80
80
|
test("TC-AI-TOKEN-USAGE-002: apply filter triggers re-fetch with new date params", async ({ page }) => {
|
|
81
81
|
await login(page, "superadmin");
|
|
82
|
-
const fetchedUrls = [];
|
|
83
82
|
await page.route("**/api/ai_assistant/usage/daily**", async (route) => {
|
|
84
|
-
fetchedUrls.push(route.request().url());
|
|
85
83
|
await route.fulfill({
|
|
86
84
|
status: 200,
|
|
87
85
|
contentType: "application/json",
|
|
@@ -95,17 +93,30 @@ test.describe("TC-AI-TOKEN-USAGE-001\u2013005: token usage stats page", () => {
|
|
|
95
93
|
body: JSON.stringify(EMPTY_SESSIONS_PAYLOAD)
|
|
96
94
|
});
|
|
97
95
|
});
|
|
96
|
+
const initialDailyRequest = page.waitForResponse(
|
|
97
|
+
(response) => response.url().includes("/api/ai_assistant/usage/daily") && response.status() === 200,
|
|
98
|
+
{ timeout: 15e3 }
|
|
99
|
+
);
|
|
100
|
+
const initialSessionsRequest = page.waitForResponse(
|
|
101
|
+
(response) => response.url().includes("/api/ai_assistant/usage/sessions") && response.status() === 200,
|
|
102
|
+
{ timeout: 15e3 }
|
|
103
|
+
);
|
|
98
104
|
await page.goto(USAGE_PAGE, { waitUntil: "domcontentloaded" });
|
|
105
|
+
await Promise.all([initialDailyRequest, initialSessionsRequest]);
|
|
99
106
|
const fromInput = page.locator("#usage-from");
|
|
100
107
|
const toInput = page.locator("#usage-to");
|
|
101
108
|
const applyButton = page.getByRole("button", { name: /apply/i });
|
|
102
109
|
await expect(fromInput).toBeVisible({ timeout: 1e4 });
|
|
103
110
|
await fromInput.fill("2026-04-01");
|
|
104
111
|
await toInput.fill("2026-04-30");
|
|
112
|
+
await expect(fromInput).toHaveValue("2026-04-01");
|
|
113
|
+
await expect(toInput).toHaveValue("2026-04-30");
|
|
114
|
+
const updatedDailyRequest = page.waitForResponse(
|
|
115
|
+
(response) => response.url().includes("/api/ai_assistant/usage/daily") && response.url().includes("from=2026-04-01") && response.url().includes("to=2026-04-30") && response.status() === 200,
|
|
116
|
+
{ timeout: 1e4 }
|
|
117
|
+
);
|
|
105
118
|
await applyButton.click();
|
|
106
|
-
await
|
|
107
|
-
const hasNewDates = fetchedUrls.some((url) => url.includes("from=2026-04-01"));
|
|
108
|
-
expect(hasNewDates).toBe(true);
|
|
119
|
+
await updatedDailyRequest;
|
|
109
120
|
});
|
|
110
121
|
test("TC-AI-TOKEN-USAGE-003: sessions list renders rows when API returns sessions", async ({ page }) => {
|
|
111
122
|
await login(page, "superadmin");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.ts"],
|
|
4
|
-
"sourcesContent": ["import { test, expect } from '@playwright/test';\nimport { login } from '@open-mercato/core/modules/core/__integration__/helpers/auth';\n\n/**\n * TC-AI-TOKEN-USAGE-001 through TC-AI-TOKEN-USAGE-005\n *\n * Integration coverage for Phase 6 (Token Usage Tracking & Stats Page) of\n * spec `2026-04-28-ai-agents-agentic-loop-controls`.\n *\n * TC-AI-TOKEN-USAGE-001 \u2014 Usage page loads and renders summary tiles (ACL gate).\n * TC-AI-TOKEN-USAGE-002 \u2014 Date filter apply re-fetches with updated params.\n * TC-AI-TOKEN-USAGE-003 \u2014 Sessions list renders when API returns session rows.\n * TC-AI-TOKEN-USAGE-004 \u2014 Clicking a session row opens the detail dialog.\n * TC-AI-TOKEN-USAGE-005 \u2014 Unauthenticated visit to usage page redirects to login.\n *\n * All API calls are intercepted via page.route() stubs \u2014 no real DB needed.\n */\n\nconst USAGE_PAGE = '/backend/config/ai-assistant/usage';\n\nconst EMPTY_DAILY_PAYLOAD = { rows: [], total: 0 };\nconst EMPTY_SESSIONS_PAYLOAD = { sessions: [], total: 0, limit: 50, offset: 0 };\n\nconst DAILY_ROW = {\n id: 'row-1',\n tenantId: 'tenant-1',\n organizationId: null,\n day: '2026-05-01',\n agentId: 'catalog.assistant',\n modelId: 'claude-haiku-4-5',\n providerId: 'anthropic',\n inputTokens: '1000',\n outputTokens: '500',\n cachedInputTokens: '0',\n reasoningTokens: '0',\n stepCount: '5',\n turnCount: '3',\n sessionCount: '2',\n createdAt: '2026-05-01T12:00:00.000Z',\n updatedAt: '2026-05-01T12:00:00.000Z',\n};\n\nconst SESSION_ROW = {\n sessionId: '00000000-0000-0000-0000-000000000001',\n agentId: 'catalog.assistant',\n moduleId: 'catalog',\n userId: 'user-1',\n startedAt: '2026-05-01T10:00:00.000Z',\n lastEventAt: '2026-05-01T10:05:00.000Z',\n stepCount: 5,\n turnCount: 3,\n inputTokens: 1000,\n outputTokens: 500,\n cachedInputTokens: 0,\n reasoningTokens: 0,\n};\n\nconst STEP_EVENT = {\n id: 'evt-1',\n tenantId: 'tenant-1',\n organizationId: null,\n userId: 'user-1',\n agentId: 'catalog.assistant',\n moduleId: 'catalog',\n sessionId: '00000000-0000-0000-0000-000000000001',\n turnId: '00000000-0000-0000-0000-000000000002',\n stepIndex: 0,\n providerId: 'anthropic',\n modelId: 'claude-haiku-4-5',\n inputTokens: 1000,\n outputTokens: 500,\n cachedInputTokens: null,\n reasoningTokens: null,\n finishReason: 'stop',\n loopAbortReason: null,\n createdAt: '2026-05-01T10:00:00.000Z',\n updatedAt: '2026-05-01T10:00:00.000Z',\n};\n\ntest.describe('TC-AI-TOKEN-USAGE-001\u2013005: token usage stats page', () => {\n test('TC-AI-TOKEN-USAGE-001: usage page renders summary tiles for superadmin', async ({ page }) => {\n await login(page, 'superadmin');\n\n await page.route('**/api/ai_assistant/usage/daily**', async (route) => {\n await route.fulfill({\n status: 200,\n contentType: 'application/json',\n body: JSON.stringify({ rows: [DAILY_ROW], total: 1 }),\n });\n });\n\n await page.route('**/api/ai_assistant/usage/sessions**', async (route) => {\n await route.fulfill({\n status: 200,\n contentType: 'application/json',\n body: JSON.stringify(EMPTY_SESSIONS_PAYLOAD),\n });\n });\n\n await page.goto(USAGE_PAGE, { waitUntil: 'domcontentloaded' });\n\n const summaryTile = page.locator('p.font-semibold.text-xl', { hasText: /^(1,000|500)$/ }).first();\n await expect(summaryTile).toBeVisible({ timeout: 15_000 });\n });\n\n test('TC-AI-TOKEN-USAGE-002: apply filter triggers re-fetch with new date params', async ({ page }) => {\n await login(page, 'superadmin');\n\n
|
|
5
|
-
"mappings": "AAAA,SAAS,MAAM,cAAc;AAC7B,SAAS,aAAa;AAiBtB,MAAM,aAAa;AAEnB,MAAM,sBAAsB,EAAE,MAAM,CAAC,GAAG,OAAO,EAAE;AACjD,MAAM,yBAAyB,EAAE,UAAU,CAAC,GAAG,OAAO,GAAG,OAAO,IAAI,QAAQ,EAAE;AAE9E,MAAM,YAAY;AAAA,EAChB,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,gBAAgB;AAAA,EAChB,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,iBAAiB;AAAA,EACjB,WAAW;AAAA,EACX,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,WAAW;AACb;AAEA,MAAM,cAAc;AAAA,EAClB,WAAW;AAAA,EACX,SAAS;AAAA,EACT,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,aAAa;AAAA,EACb,WAAW;AAAA,EACX,WAAW;AAAA,EACX,aAAa;AAAA,EACb,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,iBAAiB;AACnB;AAEA,MAAM,aAAa;AAAA,EACjB,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,gBAAgB;AAAA,EAChB,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,aAAa;AAAA,EACb,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,iBAAiB;AAAA,EACjB,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,WAAW;AAAA,EACX,WAAW;AACb;AAEA,KAAK,SAAS,0DAAqD,MAAM;AACvE,OAAK,0EAA0E,OAAO,EAAE,KAAK,MAAM;AACjG,UAAM,MAAM,MAAM,YAAY;AAE9B,UAAM,KAAK,MAAM,qCAAqC,OAAO,UAAU;AACrE,YAAM,MAAM,QAAQ;AAAA,QAClB,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,MAAM,KAAK,UAAU,EAAE,MAAM,CAAC,SAAS,GAAG,OAAO,EAAE,CAAC;AAAA,MACtD,CAAC;AAAA,IACH,CAAC;AAED,UAAM,KAAK,MAAM,wCAAwC,OAAO,UAAU;AACxE,YAAM,MAAM,QAAQ;AAAA,QAClB,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,MAAM,KAAK,UAAU,sBAAsB;AAAA,MAC7C,CAAC;AAAA,IACH,CAAC;AAED,UAAM,KAAK,KAAK,YAAY,EAAE,WAAW,mBAAmB,CAAC;AAE7D,UAAM,cAAc,KAAK,QAAQ,2BAA2B,EAAE,SAAS,gBAAgB,CAAC,EAAE,MAAM;AAChG,UAAM,OAAO,WAAW,EAAE,YAAY,EAAE,SAAS,KAAO,CAAC;AAAA,EAC3D,CAAC;AAED,OAAK,8EAA8E,OAAO,EAAE,KAAK,MAAM;AACrG,UAAM,MAAM,MAAM,YAAY;AAE9B,UAAM,
|
|
4
|
+
"sourcesContent": ["import { test, expect } from '@playwright/test';\nimport { login } from '@open-mercato/core/modules/core/__integration__/helpers/auth';\n\n/**\n * TC-AI-TOKEN-USAGE-001 through TC-AI-TOKEN-USAGE-005\n *\n * Integration coverage for Phase 6 (Token Usage Tracking & Stats Page) of\n * spec `2026-04-28-ai-agents-agentic-loop-controls`.\n *\n * TC-AI-TOKEN-USAGE-001 \u2014 Usage page loads and renders summary tiles (ACL gate).\n * TC-AI-TOKEN-USAGE-002 \u2014 Date filter apply re-fetches with updated params.\n * TC-AI-TOKEN-USAGE-003 \u2014 Sessions list renders when API returns session rows.\n * TC-AI-TOKEN-USAGE-004 \u2014 Clicking a session row opens the detail dialog.\n * TC-AI-TOKEN-USAGE-005 \u2014 Unauthenticated visit to usage page redirects to login.\n *\n * All API calls are intercepted via page.route() stubs \u2014 no real DB needed.\n */\n\nconst USAGE_PAGE = '/backend/config/ai-assistant/usage';\n\nconst EMPTY_DAILY_PAYLOAD = { rows: [], total: 0 };\nconst EMPTY_SESSIONS_PAYLOAD = { sessions: [], total: 0, limit: 50, offset: 0 };\n\nconst DAILY_ROW = {\n id: 'row-1',\n tenantId: 'tenant-1',\n organizationId: null,\n day: '2026-05-01',\n agentId: 'catalog.assistant',\n modelId: 'claude-haiku-4-5',\n providerId: 'anthropic',\n inputTokens: '1000',\n outputTokens: '500',\n cachedInputTokens: '0',\n reasoningTokens: '0',\n stepCount: '5',\n turnCount: '3',\n sessionCount: '2',\n createdAt: '2026-05-01T12:00:00.000Z',\n updatedAt: '2026-05-01T12:00:00.000Z',\n};\n\nconst SESSION_ROW = {\n sessionId: '00000000-0000-0000-0000-000000000001',\n agentId: 'catalog.assistant',\n moduleId: 'catalog',\n userId: 'user-1',\n startedAt: '2026-05-01T10:00:00.000Z',\n lastEventAt: '2026-05-01T10:05:00.000Z',\n stepCount: 5,\n turnCount: 3,\n inputTokens: 1000,\n outputTokens: 500,\n cachedInputTokens: 0,\n reasoningTokens: 0,\n};\n\nconst STEP_EVENT = {\n id: 'evt-1',\n tenantId: 'tenant-1',\n organizationId: null,\n userId: 'user-1',\n agentId: 'catalog.assistant',\n moduleId: 'catalog',\n sessionId: '00000000-0000-0000-0000-000000000001',\n turnId: '00000000-0000-0000-0000-000000000002',\n stepIndex: 0,\n providerId: 'anthropic',\n modelId: 'claude-haiku-4-5',\n inputTokens: 1000,\n outputTokens: 500,\n cachedInputTokens: null,\n reasoningTokens: null,\n finishReason: 'stop',\n loopAbortReason: null,\n createdAt: '2026-05-01T10:00:00.000Z',\n updatedAt: '2026-05-01T10:00:00.000Z',\n};\n\ntest.describe('TC-AI-TOKEN-USAGE-001\u2013005: token usage stats page', () => {\n test('TC-AI-TOKEN-USAGE-001: usage page renders summary tiles for superadmin', async ({ page }) => {\n await login(page, 'superadmin');\n\n await page.route('**/api/ai_assistant/usage/daily**', async (route) => {\n await route.fulfill({\n status: 200,\n contentType: 'application/json',\n body: JSON.stringify({ rows: [DAILY_ROW], total: 1 }),\n });\n });\n\n await page.route('**/api/ai_assistant/usage/sessions**', async (route) => {\n await route.fulfill({\n status: 200,\n contentType: 'application/json',\n body: JSON.stringify(EMPTY_SESSIONS_PAYLOAD),\n });\n });\n\n await page.goto(USAGE_PAGE, { waitUntil: 'domcontentloaded' });\n\n const summaryTile = page.locator('p.font-semibold.text-xl', { hasText: /^(1,000|500)$/ }).first();\n await expect(summaryTile).toBeVisible({ timeout: 15_000 });\n });\n\n test('TC-AI-TOKEN-USAGE-002: apply filter triggers re-fetch with new date params', async ({ page }) => {\n await login(page, 'superadmin');\n\n await page.route('**/api/ai_assistant/usage/daily**', async (route) => {\n await route.fulfill({\n status: 200,\n contentType: 'application/json',\n body: JSON.stringify(EMPTY_DAILY_PAYLOAD),\n });\n });\n\n await page.route('**/api/ai_assistant/usage/sessions**', async (route) => {\n await route.fulfill({\n status: 200,\n contentType: 'application/json',\n body: JSON.stringify(EMPTY_SESSIONS_PAYLOAD),\n });\n });\n\n const initialDailyRequest = page.waitForResponse(\n (response) => response.url().includes('/api/ai_assistant/usage/daily') && response.status() === 200,\n { timeout: 15_000 },\n );\n const initialSessionsRequest = page.waitForResponse(\n (response) => response.url().includes('/api/ai_assistant/usage/sessions') && response.status() === 200,\n { timeout: 15_000 },\n );\n await page.goto(USAGE_PAGE, { waitUntil: 'domcontentloaded' });\n await Promise.all([initialDailyRequest, initialSessionsRequest]);\n\n const fromInput = page.locator('#usage-from');\n const toInput = page.locator('#usage-to');\n const applyButton = page.getByRole('button', { name: /apply/i });\n\n await expect(fromInput).toBeVisible({ timeout: 10_000 });\n\n await fromInput.fill('2026-04-01');\n await toInput.fill('2026-04-30');\n await expect(fromInput).toHaveValue('2026-04-01');\n await expect(toInput).toHaveValue('2026-04-30');\n const updatedDailyRequest = page.waitForResponse(\n (response) =>\n response.url().includes('/api/ai_assistant/usage/daily') &&\n response.url().includes('from=2026-04-01') &&\n response.url().includes('to=2026-04-30') &&\n response.status() === 200,\n { timeout: 10_000 },\n );\n await applyButton.click();\n await updatedDailyRequest;\n });\n\n test('TC-AI-TOKEN-USAGE-003: sessions list renders rows when API returns sessions', async ({ page }) => {\n await login(page, 'superadmin');\n\n await page.route('**/api/ai_assistant/usage/daily**', async (route) => {\n await route.fulfill({\n status: 200,\n contentType: 'application/json',\n body: JSON.stringify(EMPTY_DAILY_PAYLOAD),\n });\n });\n\n await page.route('**/api/ai_assistant/usage/sessions**', async (route, request) => {\n if (request.url().includes('/sessions/')) {\n await route.continue();\n return;\n }\n await route.fulfill({\n status: 200,\n contentType: 'application/json',\n body: JSON.stringify({ sessions: [SESSION_ROW], total: 1, limit: 50, offset: 0 }),\n });\n });\n\n await page.goto(USAGE_PAGE, { waitUntil: 'domcontentloaded' });\n\n const sessionCell = page.getByText('00000000').first();\n await expect(sessionCell).toBeVisible({ timeout: 15_000 });\n });\n\n test('TC-AI-TOKEN-USAGE-004: clicking a session row opens the detail dialog', async ({ page }) => {\n await login(page, 'superadmin');\n\n await page.route('**/api/ai_assistant/usage/daily**', async (route) => {\n await route.fulfill({\n status: 200,\n contentType: 'application/json',\n body: JSON.stringify(EMPTY_DAILY_PAYLOAD),\n });\n });\n\n await page.route('**/api/ai_assistant/usage/sessions/00000000-0000-0000-0000-000000000001', async (route) => {\n await route.fulfill({\n status: 200,\n contentType: 'application/json',\n body: JSON.stringify({ events: [STEP_EVENT], total: 1, sessionId: SESSION_ROW.sessionId }),\n });\n });\n\n await page.route('**/api/ai_assistant/usage/sessions**', async (route, request) => {\n if (request.url().includes('/00000000-0000-0000-0000-000000000001')) {\n // Fall back to the previously registered (more specific) handler.\n await route.fallback();\n return;\n }\n await route.fulfill({\n status: 200,\n contentType: 'application/json',\n body: JSON.stringify({ sessions: [SESSION_ROW], total: 1, limit: 50, offset: 0 }),\n });\n });\n\n await page.goto(USAGE_PAGE, { waitUntil: 'domcontentloaded' });\n\n const sessionCell = page.getByText('00000000').first();\n await expect(sessionCell).toBeVisible({ timeout: 15_000 });\n await sessionCell.click();\n\n const dialogTitle = page.getByRole('dialog');\n await expect(dialogTitle).toBeVisible({ timeout: 10_000 });\n\n const modelCell = page.getByText('claude-haiku-4-5').first();\n await expect(modelCell).toBeVisible({ timeout: 5_000 });\n });\n\n test('TC-AI-TOKEN-USAGE-005: unauthenticated visit redirects to login', async ({ browser }) => {\n const context = await browser.newContext();\n const page = await context.newPage();\n try {\n await page.goto(USAGE_PAGE, { waitUntil: 'domcontentloaded' });\n await page.waitForURL(/\\/login/, { timeout: 15_000 });\n expect(page.url()).toMatch(/\\/login/);\n } finally {\n await context.close();\n }\n });\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,6 +2,15 @@ import { z } from "zod";
|
|
|
2
2
|
import { listAgents, getAgent } from "../lib/agent-registry.js";
|
|
3
3
|
import { hasRequiredFeatures } from "../lib/auth.js";
|
|
4
4
|
import { defineAiTool } from "../lib/ai-tool-definition.js";
|
|
5
|
+
import {
|
|
6
|
+
TASK_PLAN_DETAIL_MAX_CHARS,
|
|
7
|
+
TASK_PLAN_ID_MAX_CHARS,
|
|
8
|
+
TASK_PLAN_LABEL_MAX_CHARS,
|
|
9
|
+
TASK_PLAN_MAX_TASKS,
|
|
10
|
+
TASK_PLAN_TOOL_NAME_MAX_CHARS,
|
|
11
|
+
looksLikeHiddenReasoning,
|
|
12
|
+
sanitizeAgentTaskPlanInput
|
|
13
|
+
} from "../lib/task-plan-labels.js";
|
|
5
14
|
function summarizeAgent(agent) {
|
|
6
15
|
return {
|
|
7
16
|
id: agent.id,
|
|
@@ -18,6 +27,7 @@ function summarizeAgent(agent) {
|
|
|
18
27
|
domain: agent.domain ?? null,
|
|
19
28
|
keywords: agent.keywords ?? [],
|
|
20
29
|
suggestions: agent.suggestions ?? [],
|
|
30
|
+
taskPlan: agent.taskPlan ?? { enabled: false },
|
|
21
31
|
dataCapabilities: agent.dataCapabilities ?? null,
|
|
22
32
|
hasOutputSchema: Boolean(agent.output),
|
|
23
33
|
hasPageContextResolver: typeof agent.resolvePageContext === "function"
|
|
@@ -112,7 +122,54 @@ const describeAgentTool = defineAiTool({
|
|
|
112
122
|
};
|
|
113
123
|
}
|
|
114
124
|
});
|
|
115
|
-
const
|
|
125
|
+
const visibleTaskLabel = z.string().min(1).max(TASK_PLAN_LABEL_MAX_CHARS).superRefine((value, ctx) => {
|
|
126
|
+
if (!looksLikeHiddenReasoning(value)) return;
|
|
127
|
+
ctx.addIssue({
|
|
128
|
+
code: "custom",
|
|
129
|
+
message: "Task-plan labels are user-visible UI copy and must not include private reasoning."
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
const visibleTaskDetail = z.string().max(TASK_PLAN_DETAIL_MAX_CHARS).optional().superRefine((value, ctx) => {
|
|
133
|
+
if (!value || !looksLikeHiddenReasoning(value)) return;
|
|
134
|
+
ctx.addIssue({
|
|
135
|
+
code: "custom",
|
|
136
|
+
message: "Task-plan details must not include private reasoning."
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
const updateTaskPlanInput = z.object({
|
|
140
|
+
tasks: z.array(
|
|
141
|
+
z.object({
|
|
142
|
+
id: z.string().min(1).max(TASK_PLAN_ID_MAX_CHARS).optional(),
|
|
143
|
+
label: visibleTaskLabel.describe("Concise user-visible step label. Do not include private reasoning."),
|
|
144
|
+
detail: visibleTaskDetail.describe("Optional short visible detail, not private reasoning."),
|
|
145
|
+
toolName: z.string().min(1).max(TASK_PLAN_TOOL_NAME_MAX_CHARS).optional().describe("Optional whitelisted tool name that this planned step maps to.")
|
|
146
|
+
})
|
|
147
|
+
).min(1).max(TASK_PLAN_MAX_TASKS)
|
|
148
|
+
});
|
|
149
|
+
const updateTaskPlanTool = defineAiTool({
|
|
150
|
+
name: "meta.update_task_plan",
|
|
151
|
+
displayName: "Update task plan",
|
|
152
|
+
description: "Set the concise user-visible task plan for this assistant turn before calling domain tools. Labels are progress UI, not hidden reasoning.",
|
|
153
|
+
inputSchema: updateTaskPlanInput,
|
|
154
|
+
requiredFeatures: ["ai_assistant.view"],
|
|
155
|
+
tags: ["read", "meta", "task-plan"],
|
|
156
|
+
isMutation: false,
|
|
157
|
+
handler: async (rawInput) => {
|
|
158
|
+
const input = updateTaskPlanInput.parse(rawInput);
|
|
159
|
+
const sanitized = sanitizeAgentTaskPlanInput(input);
|
|
160
|
+
return {
|
|
161
|
+
ok: sanitized.tasks.length > 0,
|
|
162
|
+
tasks: sanitized.tasks,
|
|
163
|
+
accepted: sanitized.tasks.length,
|
|
164
|
+
dropped: input.tasks.length - sanitized.tasks.length
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
const metaAiTools = [
|
|
169
|
+
listAgentsTool,
|
|
170
|
+
describeAgentTool,
|
|
171
|
+
updateTaskPlanTool
|
|
172
|
+
];
|
|
116
173
|
var meta_pack_default = metaAiTools;
|
|
117
174
|
export {
|
|
118
175
|
meta_pack_default as default,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/ai-tools/meta-pack.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * General-purpose `meta.*` tool pack (Phase 1 WS-C, Step 3.8).\n *\n * `list_agents` enumerates agents the caller can invoke; `describe_agent`\n * returns a serializable description (Zod output schema \u2192 JSON-Schema when\n * possible, otherwise a `non-serializable` marker). Both tools treat a\n * missing agent registry as an empty list \u2014 the chat runtime must not\n * crash if `ai-agents.generated.ts` has not been emitted yet.\n */\nimport { z } from 'zod'\nimport type { AiAgentDefinition } from '../lib/ai-agent-definition'\nimport { listAgents, getAgent } from '../lib/agent-registry'\nimport { hasRequiredFeatures } from '../lib/auth'\nimport { defineAiTool } from '../lib/ai-tool-definition'\nimport
|
|
5
|
-
"mappings": "AASA,SAAS,SAAS;AAElB,SAAS,YAAY,gBAAgB;AACrC,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;
|
|
4
|
+
"sourcesContent": ["/**\n * General-purpose `meta.*` tool pack (Phase 1 WS-C, Step 3.8).\n *\n * `list_agents` enumerates agents the caller can invoke; `describe_agent`\n * returns a serializable description (Zod output schema \u2192 JSON-Schema when\n * possible, otherwise a `non-serializable` marker). Both tools treat a\n * missing agent registry as an empty list \u2014 the chat runtime must not\n * crash if `ai-agents.generated.ts` has not been emitted yet.\n */\nimport { z } from 'zod'\nimport type { AiAgentDefinition } from '../lib/ai-agent-definition'\nimport { listAgents, getAgent } from '../lib/agent-registry'\nimport { hasRequiredFeatures } from '../lib/auth'\nimport { defineAiTool } from '../lib/ai-tool-definition'\nimport {\n TASK_PLAN_DETAIL_MAX_CHARS,\n TASK_PLAN_ID_MAX_CHARS,\n TASK_PLAN_LABEL_MAX_CHARS,\n TASK_PLAN_MAX_TASKS,\n TASK_PLAN_TOOL_NAME_MAX_CHARS,\n looksLikeHiddenReasoning,\n sanitizeAgentTaskPlanInput,\n} from '../lib/task-plan-labels'\n\nfunction summarizeAgent(agent: AiAgentDefinition): Record<string, unknown> {\n return {\n id: agent.id,\n moduleId: agent.moduleId,\n label: agent.label,\n description: agent.description,\n requiredFeatures: agent.requiredFeatures ?? [],\n allowedTools: agent.allowedTools,\n executionMode: agent.executionMode ?? 'chat',\n mutationPolicy: agent.mutationPolicy ?? 'read-only',\n readOnly: typeof agent.readOnly === 'boolean' ? agent.readOnly : true,\n maxSteps: agent.maxSteps ?? null,\n acceptedMediaTypes: agent.acceptedMediaTypes ?? [],\n domain: agent.domain ?? null,\n keywords: agent.keywords ?? [],\n suggestions: agent.suggestions ?? [],\n taskPlan: agent.taskPlan ?? { enabled: false },\n dataCapabilities: agent.dataCapabilities ?? null,\n hasOutputSchema: Boolean(agent.output),\n hasPageContextResolver: typeof agent.resolvePageContext === 'function',\n }\n}\n\nfunction serializeStructuredOutput(\n output: AiAgentDefinition['output'],\n): Record<string, unknown> | null {\n if (!output) return null\n try {\n const jsonSchema = z.toJSONSchema(output.schema as unknown as z.ZodType, {\n unrepresentable: 'any',\n })\n return {\n schemaName: output.schemaName,\n mode: output.mode ?? 'generate',\n jsonSchema,\n }\n } catch (error) {\n return {\n schemaName: output.schemaName,\n mode: output.mode ?? 'generate',\n note: 'non-serializable',\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\nfunction serializePrompt(agent: AiAgentDefinition): Record<string, unknown> {\n return {\n systemPrompt: agent.systemPrompt,\n hasDynamicPageContext: typeof agent.resolvePageContext === 'function',\n }\n}\n\nconst listAgentsInput = z.object({\n moduleId: z.string().optional().describe('Restrict results to one module id.'),\n})\n\nconst listAgentsTool = defineAiTool({\n name: 'meta.list_agents',\n displayName: 'List agents',\n description:\n 'List registered AI agents the caller can invoke. Filters by requiredFeatures RBAC; returns { agents: [] } if the registry is empty.',\n inputSchema: listAgentsInput,\n requiredFeatures: ['ai_assistant.view'],\n tags: ['read', 'meta'],\n handler: async (rawInput, ctx) => {\n const input = listAgentsInput.parse(rawInput)\n let all: AiAgentDefinition[] = []\n try {\n all = listAgents()\n } catch {\n all = []\n }\n const filtered = all.filter((agent) => {\n if (input.moduleId && agent.moduleId !== input.moduleId) return false\n const features = agent.requiredFeatures ?? []\n return hasRequiredFeatures(features, ctx.userFeatures, ctx.isSuperAdmin)\n })\n return {\n agents: filtered.map(summarizeAgent),\n total: filtered.length,\n }\n },\n})\n\nconst describeAgentInput = z.object({\n agentId: z.string().min(1).describe('Agent id (e.g. \"catalog.merchandising_assistant\").'),\n})\n\nconst describeAgentTool = defineAiTool({\n name: 'meta.describe_agent',\n displayName: 'Describe agent',\n description:\n 'Return metadata, RBAC, allowed tools, execution mode, output schema (JSON-Schema when representable), and prompt shape for a single agent. Never throws \u2014 returns { agent: null, reason } if missing or forbidden.',\n inputSchema: describeAgentInput,\n requiredFeatures: ['ai_assistant.view'],\n tags: ['read', 'meta'],\n handler: async (rawInput, ctx) => {\n const input = describeAgentInput.parse(rawInput)\n let agent: AiAgentDefinition | undefined\n try {\n agent = getAgent(input.agentId)\n } catch {\n agent = undefined\n }\n if (!agent) {\n return { agent: null, reason: 'not_found' as const }\n }\n const features = agent.requiredFeatures ?? []\n if (!hasRequiredFeatures(features, ctx.userFeatures, ctx.isSuperAdmin)) {\n return { agent: null, reason: 'forbidden' as const }\n }\n return {\n agent: {\n ...summarizeAgent(agent),\n output: serializeStructuredOutput(agent.output),\n prompt: serializePrompt(agent),\n },\n }\n },\n})\n\nconst visibleTaskLabel = z\n .string()\n .min(1)\n .max(TASK_PLAN_LABEL_MAX_CHARS)\n .superRefine((value, ctx) => {\n if (!looksLikeHiddenReasoning(value)) return\n ctx.addIssue({\n code: 'custom',\n message: 'Task-plan labels are user-visible UI copy and must not include private reasoning.',\n })\n })\n\nconst visibleTaskDetail = z\n .string()\n .max(TASK_PLAN_DETAIL_MAX_CHARS)\n .optional()\n .superRefine((value, ctx) => {\n if (!value || !looksLikeHiddenReasoning(value)) return\n ctx.addIssue({\n code: 'custom',\n message: 'Task-plan details must not include private reasoning.',\n })\n })\n\nconst updateTaskPlanInput = z.object({\n tasks: z\n .array(\n z.object({\n id: z.string().min(1).max(TASK_PLAN_ID_MAX_CHARS).optional(),\n label: visibleTaskLabel.describe('Concise user-visible step label. Do not include private reasoning.'),\n detail: visibleTaskDetail.describe('Optional short visible detail, not private reasoning.'),\n toolName: z\n .string()\n .min(1)\n .max(TASK_PLAN_TOOL_NAME_MAX_CHARS)\n .optional()\n .describe('Optional whitelisted tool name that this planned step maps to.'),\n }),\n )\n .min(1)\n .max(TASK_PLAN_MAX_TASKS),\n})\n\nconst updateTaskPlanTool = defineAiTool({\n name: 'meta.update_task_plan',\n displayName: 'Update task plan',\n description:\n 'Set the concise user-visible task plan for this assistant turn before calling domain tools. Labels are progress UI, not hidden reasoning.',\n inputSchema: updateTaskPlanInput,\n requiredFeatures: ['ai_assistant.view'],\n tags: ['read', 'meta', 'task-plan'],\n isMutation: false,\n handler: async (rawInput) => {\n const input = updateTaskPlanInput.parse(rawInput)\n const sanitized = sanitizeAgentTaskPlanInput(input)\n return {\n ok: sanitized.tasks.length > 0,\n tasks: sanitized.tasks,\n accepted: sanitized.tasks.length,\n dropped: input.tasks.length - sanitized.tasks.length,\n }\n },\n})\n\nexport const metaAiTools = [\n listAgentsTool,\n describeAgentTool,\n updateTaskPlanTool,\n]\n\nexport default metaAiTools\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
3
3
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
4
|
import { llmProviderRegistry } from "@open-mercato/shared/lib/ai/llm-provider-registry";
|
|
5
|
-
import { listAgents, loadAgentRegistry } from "../../../lib/agent-registry.js";
|
|
5
|
+
import { isAgentTaskPlanEnabled, listAgents, loadAgentRegistry } from "../../../lib/agent-registry.js";
|
|
6
6
|
import { hasRequiredFeatures } from "../../../lib/auth.js";
|
|
7
7
|
import { toolRegistry } from "../../../lib/tool-registry.js";
|
|
8
8
|
const openApi = {
|
|
@@ -72,6 +72,7 @@ async function GET(req) {
|
|
|
72
72
|
mutationPolicy: agent.mutationPolicy ?? "read-only",
|
|
73
73
|
readOnly: Boolean(agent.readOnly),
|
|
74
74
|
maxSteps: agent.maxSteps ?? null,
|
|
75
|
+
taskPlan: { enabled: isAgentTaskPlanEnabled(agent) },
|
|
75
76
|
allowedTools: agent.allowedTools,
|
|
76
77
|
tools,
|
|
77
78
|
requiredFeatures: agent.requiredFeatures ?? [],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/ai_assistant/api/ai/agents/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport { listAgents, loadAgentRegistry } from '../../../lib/agent-registry'\nimport { hasRequiredFeatures } from '../../../lib/auth'\nimport { toolRegistry } from '../../../lib/tool-registry'\nimport type { AiToolDefinition } from '../../../lib/types'\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'List AI agents the caller can invoke',\n methods: {\n GET: {\n operationId: 'aiAssistantListAgents',\n summary: 'List registered AI agents, filtered by the caller\\'s features.',\n description:\n 'Returns `{ agents: [...] }` \u2014 the subset of agents from `ai-agents.generated.ts` that the ' +\n 'authenticated caller can invoke based on each agent\\'s `requiredFeatures`. Mirrors the ' +\n '`meta.list_agents` tool handler so backoffice pages (e.g. the playground) can render an ' +\n 'agent picker without going through the MCP tool transport.',\n responses: [\n {\n status: 200,\n description: 'Accessible agent summaries.',\n mediaType: 'application/json',\n },\n ],\n errors: [\n { status: 401, description: 'Unauthenticated caller.' },\n { status: 500, description: 'Internal failure while loading the agent registry.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\nexport async function GET(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized', code: 'unauthenticated' }, { status: 401 })\n }\n\n try {\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n\n // No LLM provider configured (no API keys set). The launcher uses the\n // `aiConfigured` flag to show a setup prompt; explicit `<AiChat>` mounts\n // and playground pages still see the full registry so they can show their\n // own configuration prompts instead of silently disappearing.\n const aiConfigured = llmProviderRegistry.resolveFirstConfigured() != null\n\n await loadAgentRegistry()\n const all = listAgents()\n const accessible = all.filter((agent) =>\n hasRequiredFeatures(agent.requiredFeatures, acl.features, acl.isSuperAdmin, rbacService),\n )\n\n const agents = accessible.map((agent) => {\n const tools = agent.allowedTools.map((toolName) => {\n const tool = toolRegistry.getTool(toolName) as AiToolDefinition | undefined\n return {\n name: toolName,\n displayName: tool?.displayName ?? toolName,\n isMutation: Boolean(tool?.isMutation),\n registered: Boolean(tool),\n }\n })\n return {\n id: agent.id,\n moduleId: agent.moduleId,\n label: agent.label,\n description: agent.description,\n systemPrompt: agent.systemPrompt,\n executionMode: agent.executionMode ?? 'chat',\n defaultProvider: agent.defaultProvider ?? null,\n defaultModel: agent.defaultModel ?? null,\n defaultBaseUrl: agent.defaultBaseUrl ?? null,\n allowRuntimeModelOverride: agent.allowRuntimeModelOverride !== false,\n mutationPolicy: agent.mutationPolicy ?? 'read-only',\n readOnly: Boolean(agent.readOnly),\n maxSteps: agent.maxSteps ?? null,\n allowedTools: agent.allowedTools,\n tools,\n requiredFeatures: agent.requiredFeatures ?? [],\n acceptedMediaTypes: agent.acceptedMediaTypes ?? [],\n keywords: agent.keywords ?? [],\n suggestions: agent.suggestions ?? [],\n hasOutputSchema: Boolean(agent.output),\n }\n })\n\n return NextResponse.json({ agents, total: agents.length, aiConfigured })\n } catch (error) {\n console.error('[AI Agents] Failed to list agents:', error)\n return NextResponse.json(\n { error: 'Failed to list agents', code: 'internal_error' },\n { status: 500 },\n )\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAsC;AAE/C,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAEvC,SAAS,2BAA2B;AACpC,SAAS,YAAY,yBAAyB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport { isAgentTaskPlanEnabled, listAgents, loadAgentRegistry } from '../../../lib/agent-registry'\nimport { hasRequiredFeatures } from '../../../lib/auth'\nimport { toolRegistry } from '../../../lib/tool-registry'\nimport type { AiToolDefinition } from '../../../lib/types'\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'List AI agents the caller can invoke',\n methods: {\n GET: {\n operationId: 'aiAssistantListAgents',\n summary: 'List registered AI agents, filtered by the caller\\'s features.',\n description:\n 'Returns `{ agents: [...] }` \u2014 the subset of agents from `ai-agents.generated.ts` that the ' +\n 'authenticated caller can invoke based on each agent\\'s `requiredFeatures`. Mirrors the ' +\n '`meta.list_agents` tool handler so backoffice pages (e.g. the playground) can render an ' +\n 'agent picker without going through the MCP tool transport.',\n responses: [\n {\n status: 200,\n description: 'Accessible agent summaries.',\n mediaType: 'application/json',\n },\n ],\n errors: [\n { status: 401, description: 'Unauthenticated caller.' },\n { status: 500, description: 'Internal failure while loading the agent registry.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\nexport async function GET(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized', code: 'unauthenticated' }, { status: 401 })\n }\n\n try {\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n\n // No LLM provider configured (no API keys set). The launcher uses the\n // `aiConfigured` flag to show a setup prompt; explicit `<AiChat>` mounts\n // and playground pages still see the full registry so they can show their\n // own configuration prompts instead of silently disappearing.\n const aiConfigured = llmProviderRegistry.resolveFirstConfigured() != null\n\n await loadAgentRegistry()\n const all = listAgents()\n const accessible = all.filter((agent) =>\n hasRequiredFeatures(agent.requiredFeatures, acl.features, acl.isSuperAdmin, rbacService),\n )\n\n const agents = accessible.map((agent) => {\n const tools = agent.allowedTools.map((toolName) => {\n const tool = toolRegistry.getTool(toolName) as AiToolDefinition | undefined\n return {\n name: toolName,\n displayName: tool?.displayName ?? toolName,\n isMutation: Boolean(tool?.isMutation),\n registered: Boolean(tool),\n }\n })\n return {\n id: agent.id,\n moduleId: agent.moduleId,\n label: agent.label,\n description: agent.description,\n systemPrompt: agent.systemPrompt,\n executionMode: agent.executionMode ?? 'chat',\n defaultProvider: agent.defaultProvider ?? null,\n defaultModel: agent.defaultModel ?? null,\n defaultBaseUrl: agent.defaultBaseUrl ?? null,\n allowRuntimeModelOverride: agent.allowRuntimeModelOverride !== false,\n mutationPolicy: agent.mutationPolicy ?? 'read-only',\n readOnly: Boolean(agent.readOnly),\n maxSteps: agent.maxSteps ?? null,\n taskPlan: { enabled: isAgentTaskPlanEnabled(agent) },\n allowedTools: agent.allowedTools,\n tools,\n requiredFeatures: agent.requiredFeatures ?? [],\n acceptedMediaTypes: agent.acceptedMediaTypes ?? [],\n keywords: agent.keywords ?? [],\n suggestions: agent.suggestions ?? [],\n hasOutputSchema: Boolean(agent.output),\n }\n })\n\n return NextResponse.json({ agents, total: agents.length, aiConfigured })\n } catch (error) {\n console.error('[AI Agents] Failed to list agents:', error)\n return NextResponse.json(\n { error: 'Failed to list agents', code: 'internal_error' },\n { status: 500 },\n )\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|