@tuturuuu/ai 0.13.0 → 0.15.0

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 (56) hide show
  1. package/package.json +41 -41
  2. package/src/chat/google/assistant-message-parts.ts +105 -0
  3. package/src/chat/google/chat-tools.ts +7 -0
  4. package/src/chat/google/default-system-instruction.ts +5 -194
  5. package/src/chat/google/google-search-usage.ts +62 -0
  6. package/src/chat/google/message-file-processing.ts +8 -2
  7. package/src/chat/google/mira-step-preparation.ts +37 -16
  8. package/src/chat/google/route-chat-resolution.ts +11 -18
  9. package/src/chat/google/route-credits.ts +6 -7
  10. package/src/chat/google/route-message-preparation.ts +33 -0
  11. package/src/chat/google/route-mira-runtime.ts +56 -32
  12. package/src/chat/google/route.ts +32 -77
  13. package/src/chat/google/stream-finish-persistence.ts +39 -150
  14. package/src/chat/mira-artifact-intent.ts +38 -0
  15. package/src/chat/mira-prompt.ts +29 -0
  16. package/src/chat/mira-render-ui-policy.ts +65 -20
  17. package/src/chat/mira-system-instruction.ts +10 -172
  18. package/src/chat/mira-tool-loop-guard.ts +84 -0
  19. package/src/credits/cap-output-tokens.ts +48 -15
  20. package/src/credits/check-credits.ts +14 -3
  21. package/src/credits/constants.ts +3 -0
  22. package/src/credits/index.ts +1 -0
  23. package/src/generate/route.ts +6 -3
  24. package/src/meetings/audio-contract.ts +8 -0
  25. package/src/meetings/audio-transcripts.ts +25 -0
  26. package/src/meetings/chat-source-footer.ts +1 -0
  27. package/src/meetings/chat.ts +30 -7
  28. package/src/meetings/gemini.ts +84 -23
  29. package/src/meetings/public-chat-search.ts +4 -1
  30. package/src/meetings/transcript.ts +63 -0
  31. package/src/meetings/workspace-tools.ts +3 -1
  32. package/src/studio/metering.ts +5 -1
  33. package/src/tools/definitions/calendar-automation.ts +43 -0
  34. package/src/tools/definitions/calendar.ts +27 -1
  35. package/src/tools/definitions/meta.ts +17 -1
  36. package/src/tools/definitions/tasks.ts +82 -31
  37. package/src/tools/definitions/workspace-user-chat.ts +24 -0
  38. package/src/tools/executors/calendar-automation.ts +207 -0
  39. package/src/tools/executors/calendar.ts +30 -1
  40. package/src/tools/executors/helpers/encryption.ts +12 -9
  41. package/src/tools/executors/search.ts +40 -9
  42. package/src/tools/executors/task-create.ts +232 -0
  43. package/src/tools/executors/task-dashboard.ts +59 -0
  44. package/src/tools/executors/task-read.ts +76 -0
  45. package/src/tools/executors/tasks.ts +50 -205
  46. package/src/tools/executors/theme.ts +48 -0
  47. package/src/tools/google-search-events.ts +4 -0
  48. package/src/tools/mira-sdk-tool-search.ts +73 -0
  49. package/src/tools/mira-task-write-guard.ts +61 -0
  50. package/src/tools/mira-tool-discovery.ts +138 -0
  51. package/src/tools/mira-tool-dispatcher.ts +33 -1
  52. package/src/tools/mira-tool-metadata.ts +29 -1
  53. package/src/tools/mira-tool-names.ts +13 -0
  54. package/src/tools/mira-tool-types.ts +7 -0
  55. package/src/tools/mira-tools.ts +70 -2
  56. package/src/workspace-artifacts.ts +58 -0
@@ -0,0 +1,138 @@
1
+ import { DEV_MODE } from '@tuturuuu/utils/constants';
2
+ import { MIRA_TOOL_DIRECTORY } from './mira-tool-metadata';
3
+ import type { MiraToolName } from './mira-tool-names';
4
+
5
+ const normalize = (value: string) =>
6
+ value
7
+ .normalize('NFD')
8
+ .replace(/\p{Diacritic}/gu, '')
9
+ .toLowerCase()
10
+ .replace(/_/g, ' ');
11
+ const matchesTerm = (word: string, term: string) =>
12
+ word.startsWith(term) || term === `${word}s`;
13
+ const domains = {
14
+ tasks: {
15
+ keywords:
16
+ 'task tasks board list label project assignee assign priority deadline cong viec nhiem vu nhan du an',
17
+ matches: /task|board|project/,
18
+ guidance:
19
+ 'Discover board/list and member/label/project IDs before writes. Tasks require a list. Read a task before editing ambiguous fields. Use assignment tools for labels, projects, and people. Dates use the user timezone. Keep operations in the requested workspace; report the workspace and resulting task ID after creation.',
20
+ },
21
+ calendar: {
22
+ keywords:
23
+ 'calendar event schedule scheduling google microsoft sync reclaim lock availability habit focus meeting lich dong bo su kien',
24
+ matches: /calendar|event|schedule|e2ee/,
25
+ guidance:
26
+ 'Use existing connected accounts; an unconnected Google account needs user OAuth consent. Inspect sync health before retrying. Locked events must stay fixed. Preview scheduling before applying it; preserve hard commitments and explain conflicts. Use actual priorities, deadlines, availability, and durations, never invented free time.',
27
+ },
28
+ finance: {
29
+ keywords:
30
+ 'finance wallet transaction money expense income currency category tag tai chinh vi giao dich',
31
+ matches: /wallet|transaction|spending|currency/,
32
+ guidance:
33
+ 'Discover wallets and categories before writes. Positive amounts are income, negative amounts expenses. Keep currencies separate. Report actual tool outcomes and never infer a successful financial write from a visualization.',
34
+ },
35
+ time: {
36
+ keywords: 'time timer tracking goal hours duration thoi gian theo doi',
37
+ matches: /timer|time_track/,
38
+ guidance:
39
+ 'Discover categories and sessions before edits. Respect the user timezone and running session state. Use history pagination and date filters to keep results small.',
40
+ },
41
+ memory: {
42
+ keywords:
43
+ 'memory remember recall preference personality name settings nho so thich',
44
+ matches: /memor|recall|remember|settings|user_name/,
45
+ guidance:
46
+ 'Recall relevant memories before personalization. Store durable preferences, not transient tool output. Behavior settings use update_my_settings; user naming uses update_user_name. Avoid duplicate memories.',
47
+ },
48
+ workspace: {
49
+ keywords:
50
+ 'workspace member context sidebar artifact panel theme layout screen khong gian thanh ben giao dien',
51
+ matches: /workspace|sidebar|theme|immersive/,
52
+ guidance:
53
+ 'Resolve named workspaces before operations. Opening artifacts fetches data in the UI, not in the model. Keep explanations in chat. Make each UI change once and finish after success. Replace a panel with close_artifact plus show_workspace_artifact; focus_artifact closes all other panels.',
54
+ },
55
+ creative: {
56
+ keywords:
57
+ 'image qr file document convert markdown visualization render hinh anh tep tai lieu',
58
+ matches: /image|qr_code|convert_file|render_ui/,
59
+ guidance:
60
+ 'Use Markdown for code, math, diagrams, and tables. Convert attached documents when their contents are needed. Google models receive YouTube URLs as video input; do not send YouTube links to file conversion. Use image generation only for requested images.',
61
+ },
62
+ research: {
63
+ keywords:
64
+ 'search web current news weather research verify tim kiem tin tuc thoi tiet',
65
+ matches: /google_search|parallel_checks/,
66
+ guidance:
67
+ 'Use web search for current external facts and cite sources. Use parallel checks for explicit deeper verification; summarize the results rather than dumping raw output.',
68
+ },
69
+ } as const;
70
+
71
+ export function searchMiraTools(
72
+ args: { query: string; limit?: number },
73
+ allowed?: ReadonlySet<string>
74
+ ) {
75
+ const query = normalize(args.query);
76
+ const terms = query
77
+ .split(/[^\p{L}\p{N}]+/u)
78
+ .filter((term) => term.length > 2);
79
+ const matchingDomains = Object.entries(domains).filter(([, domain]) =>
80
+ terms.some((term) =>
81
+ normalize(domain.keywords)
82
+ .split(' ')
83
+ .some((word) => word === term || term === `${word}s`)
84
+ )
85
+ );
86
+ const matches = (
87
+ Object.entries(MIRA_TOOL_DIRECTORY) as [MiraToolName, string][]
88
+ )
89
+ .filter(
90
+ ([name]) =>
91
+ !['search_tools', 'select_tools', 'no_action_needed'].includes(name) &&
92
+ (DEV_MODE || name !== 'render_ui') &&
93
+ (!allowed || allowed.has(name))
94
+ )
95
+ .map(([name, description]) => {
96
+ const words = normalize(`${name} ${description}`).split(
97
+ /[^\p{L}\p{N}]+/u
98
+ );
99
+ const nameWords = normalize(name).split(' ');
100
+ const score =
101
+ (normalize(name) === query ? 100 : 0) +
102
+ terms.reduce(
103
+ (total, term) =>
104
+ total +
105
+ (nameWords.some((word) => matchesTerm(word, term))
106
+ ? 6
107
+ : words.some((word) => matchesTerm(word, term))
108
+ ? 2
109
+ : 0),
110
+ 0
111
+ ) +
112
+ matchingDomains.reduce(
113
+ (total, [, domain]) => total + (domain.matches.test(name) ? 1 : 0),
114
+ 0
115
+ );
116
+ return { name, description, score };
117
+ })
118
+ .filter((entry) => entry.score > 0)
119
+ .sort((a, b) => b.score - a.score || a.name.localeCompare(b.name))
120
+ .slice(0, Math.min(8, Math.max(1, args.limit ?? 6)));
121
+ return {
122
+ ok: true,
123
+ selectedTools: matches.map(({ name }) => name),
124
+ matches: matches.map(({ name, description }) => ({ name, description })),
125
+ guidance: matchingDomains.map(([domain, { guidance }]) => ({
126
+ domain,
127
+ guidance,
128
+ })),
129
+ ...(matches.length
130
+ ? {
131
+ next: 'Use the activated tools. Search again only for a different operation or missing capability.',
132
+ }
133
+ : {
134
+ next: 'No matching authorized tools. Try a specific app or operation.',
135
+ domains: Object.keys(domains),
136
+ }),
137
+ };
138
+ }
@@ -6,6 +6,7 @@ import {
6
6
  executeGetUpcomingEvents,
7
7
  executeUpdateEvent,
8
8
  } from './executors/calendar';
9
+ import { executeCalendarAutomation } from './executors/calendar-automation';
9
10
  import { executeSetImmersiveMode } from './executors/chat';
10
11
  import {
11
12
  executeCreateTransactionCategory,
@@ -41,6 +42,7 @@ import { executeParallelChecks } from './executors/parallel-checks';
41
42
  import { executeCreateQrCode } from './executors/qr';
42
43
  import { executeGoogleSearch } from './executors/search';
43
44
  import { executeUpdateMySettings } from './executors/settings';
45
+ import { executeGetTask, executeSearchTasks } from './executors/task-read';
44
46
  import {
45
47
  executeAddTaskAssignee,
46
48
  executeAddTaskLabels,
@@ -70,7 +72,12 @@ import {
70
72
  executeUpdateTaskLabel,
71
73
  executeUpdateTaskList,
72
74
  } from './executors/tasks';
73
- import { executeSetTheme } from './executors/theme';
75
+ import {
76
+ executeManageWorkspace,
77
+ executeSetSidebar,
78
+ executeSetTheme,
79
+ executeShowWorkspaceArtifact,
80
+ } from './executors/theme';
74
81
  import {
75
82
  executeCreateTimeTrackerGoal,
76
83
  executeCreateTimeTrackingCategory,
@@ -98,6 +105,7 @@ import {
98
105
  executeSetWorkspaceContext,
99
106
  } from './executors/workspace';
100
107
  import type { DefinedMiraToolName } from './mira-tool-definitions';
108
+ import { searchMiraTools } from './mira-tool-discovery';
101
109
  import {
102
110
  buildRenderUiRecoverySpec,
103
111
  isRenderableRenderUiSpec,
@@ -111,12 +119,19 @@ type ToolHandler = (
111
119
  ) => Promise<unknown> | unknown;
112
120
 
113
121
  const toolHandlers = {
122
+ search_tools: (args) =>
123
+ searchMiraTools({
124
+ query: String(args.query ?? ''),
125
+ limit: typeof args.limit === 'number' ? args.limit : undefined,
126
+ }),
114
127
  select_tools: (args) => ({ ok: true, selectedTools: args.tools }),
115
128
  no_action_needed: () => ({ ok: true }),
116
129
  google_search: executeGoogleSearch,
117
130
  run_parallel_checks: (args, ctx, options) =>
118
131
  executeParallelChecks(args, ctx, options),
119
132
 
133
+ get_task: executeGetTask,
134
+ search_tasks: executeSearchTasks,
120
135
  get_my_tasks: executeGetMyTasks,
121
136
  create_task: executeCreateTask,
122
137
  complete_task: executeCompleteTask,
@@ -145,6 +160,20 @@ const toolHandlers = {
145
160
  add_task_assignee: executeAddTaskAssignee,
146
161
  remove_task_assignee: executeRemoveTaskAssignee,
147
162
 
163
+ get_calendar_connections: (args, ctx) =>
164
+ executeCalendarAutomation('get_calendar_connections', args, ctx),
165
+ connect_google_calendar: (args, ctx) =>
166
+ executeCalendarAutomation('connect_google_calendar', args, ctx),
167
+ sync_calendar: (args, ctx) =>
168
+ executeCalendarAutomation('sync_calendar', args, ctx),
169
+ set_event_locked: (args, ctx) =>
170
+ executeCalendarAutomation('set_event_locked', args, ctx),
171
+ preview_calendar_schedule: (args, ctx) =>
172
+ executeCalendarAutomation('preview_calendar_schedule', args, ctx),
173
+ apply_calendar_schedule: (args, ctx) =>
174
+ executeCalendarAutomation('apply_calendar_schedule', args, ctx),
175
+ get_schedulable_tasks: (args, ctx) =>
176
+ executeCalendarAutomation('get_schedulable_tasks', args, ctx),
148
177
  get_upcoming_events: executeGetUpcomingEvents,
149
178
  create_event: executeCreateEvent,
150
179
  update_event: executeUpdateEvent,
@@ -205,6 +234,9 @@ const toolHandlers = {
205
234
  update_my_settings: executeUpdateMySettings,
206
235
  set_default_currency: executeSetDefaultCurrency,
207
236
  set_theme: executeSetTheme,
237
+ set_sidebar: executeSetSidebar,
238
+ manage_workspace: executeManageWorkspace,
239
+ show_workspace_artifact: executeShowWorkspaceArtifact,
208
240
  list_workspace_members: executeListWorkspaceMembers,
209
241
  update_user_name: executeUpdateUserName,
210
242
  set_immersive_mode: executeSetImmersiveMode,
@@ -2,7 +2,10 @@ import type { PermissionId } from '@tuturuuu/types';
2
2
  import type { MiraToolName } from './mira-tool-names';
3
3
 
4
4
  export const MIRA_TOOL_DIRECTORY: Record<MiraToolName, string> = {
5
- select_tools: 'Present user with tool selection options',
5
+ select_tools: 'Activate a small working set of tools',
6
+ search_tools: 'Discover tools and domain guidance for an operation',
7
+ get_task: 'Read saved task details and location by ID',
8
+ search_tasks: 'Search tasks by title with pagination',
6
9
  get_my_tasks: 'Get your tasks by status (overdue, today, upcoming)',
7
10
  create_task: 'Create a new task',
8
11
  complete_task: 'Mark a task as completed',
@@ -30,6 +33,20 @@ export const MIRA_TOOL_DIRECTORY: Record<MiraToolName, string> = {
30
33
  remove_task_from_project: 'Unlink a task from a project',
31
34
  add_task_assignee: 'Assign a user to a task',
32
35
  remove_task_assignee: 'Remove a user from a task',
36
+ get_calendar_connections:
37
+ 'List linked external calendar accounts and their sync status',
38
+ connect_google_calendar:
39
+ 'Start the Google Calendar OAuth connection flow for this workspace',
40
+ sync_calendar:
41
+ 'Synchronize events with a connected external calendar in a given direction',
42
+ set_event_locked:
43
+ 'Lock or unlock a calendar event so automatic scheduling cannot move it',
44
+ preview_calendar_schedule:
45
+ 'Preview a proposed schedule for tasks without writing events',
46
+ apply_calendar_schedule:
47
+ 'Apply a previously previewed schedule and create calendar events',
48
+ get_schedulable_tasks:
49
+ 'List tasks eligible for automatic calendar scheduling',
33
50
  get_upcoming_events: 'Get calendar events for the next N days',
34
51
  create_event: 'Create a calendar event',
35
52
  update_event: 'Update a calendar event',
@@ -102,6 +119,10 @@ export const MIRA_TOOL_DIRECTORY: Record<MiraToolName, string> = {
102
119
  set_workspace_context:
103
120
  'Switch the current workspace context for task, calendar, and finance tools',
104
121
  update_my_settings: "Update the assistant's personality settings",
122
+ manage_workspace: 'Close, focus, or arrange the conversation workspace',
123
+ set_sidebar: 'Control navigation sidebar visibility and hover expansion',
124
+ show_workspace_artifact:
125
+ 'Open tasks, calendar, finance, or meetings beside the chat',
105
126
  set_theme: 'Switch dark mode, light mode, or system theme',
106
127
  render_ui:
107
128
  'Generate an interactive, actionable UI component or widget instead of plain text when it significantly improves user experience (e.g. for forms, dashboards, blog-like insight layouts, or data visualization).',
@@ -136,6 +157,13 @@ export const MIRA_TOOL_PERMISSIONS: Partial<
136
157
  remove_task_from_project: 'manage_projects',
137
158
  add_task_assignee: 'manage_projects',
138
159
  remove_task_assignee: 'manage_projects',
160
+ get_calendar_connections: 'manage_calendar',
161
+ connect_google_calendar: 'manage_calendar',
162
+ sync_calendar: 'manage_calendar',
163
+ set_event_locked: 'manage_calendar',
164
+ preview_calendar_schedule: 'manage_calendar',
165
+ apply_calendar_schedule: 'manage_calendar',
166
+ get_schedulable_tasks: 'manage_calendar',
139
167
  get_upcoming_events: 'manage_calendar',
140
168
  create_event: 'manage_calendar',
141
169
  update_event: 'manage_calendar',
@@ -1,5 +1,8 @@
1
1
  export const MIRA_TOOL_NAMES = [
2
2
  'select_tools',
3
+ 'search_tools',
4
+ 'get_task',
5
+ 'search_tasks',
3
6
  'get_my_tasks',
4
7
  'create_task',
5
8
  'complete_task',
@@ -27,6 +30,13 @@ export const MIRA_TOOL_NAMES = [
27
30
  'remove_task_from_project',
28
31
  'add_task_assignee',
29
32
  'remove_task_assignee',
33
+ 'get_calendar_connections',
34
+ 'connect_google_calendar',
35
+ 'sync_calendar',
36
+ 'set_event_locked',
37
+ 'preview_calendar_schedule',
38
+ 'apply_calendar_schedule',
39
+ 'get_schedulable_tasks',
30
40
  'get_upcoming_events',
31
41
  'create_event',
32
42
  'update_event',
@@ -84,6 +94,9 @@ export const MIRA_TOOL_NAMES = [
84
94
  'set_workspace_context',
85
95
  'update_my_settings',
86
96
  'set_theme',
97
+ 'set_sidebar',
98
+ 'show_workspace_artifact',
99
+ 'manage_workspace',
87
100
  'render_ui',
88
101
  'list_workspace_members',
89
102
  'update_user_name',
@@ -1,4 +1,5 @@
1
1
  import type { TypedSupabaseClient } from '@tuturuuu/supabase/next/client';
2
+ import type { PermissionId } from '@tuturuuu/types';
2
3
  import type { MiraWorkspaceContextState } from './workspace-context';
3
4
 
4
5
  export interface MiraToolContext {
@@ -9,6 +10,12 @@ export interface MiraToolContext {
9
10
  chatId?: string;
10
11
  supabase: TypedSupabaseClient;
11
12
  timezone?: string;
13
+ requestHeaders?: Pick<Headers, 'get'>;
14
+ executionState?: object;
15
+ authorizeWorkspaceTools?: (
16
+ wsId: string,
17
+ permissions: PermissionId[]
18
+ ) => Promise<boolean>;
12
19
  canReadUserGroupStorage?: (input: {
13
20
  groupId: string;
14
21
  storagePath: string;
@@ -2,7 +2,9 @@ import type { PermissionId } from '@tuturuuu/types';
2
2
  import { DEV_MODE } from '@tuturuuu/utils/constants';
3
3
  import type { Tool, ToolSet } from 'ai';
4
4
  import { createStreamRenderUiTool } from './definitions/render-ui';
5
+ import { createTaskWriteGuard } from './mira-task-write-guard';
5
6
  import { miraToolDefinitions } from './mira-tool-definitions';
7
+ import { searchMiraTools } from './mira-tool-discovery';
6
8
  import { executeMiraTool } from './mira-tool-dispatcher';
7
9
  import {
8
10
  MIRA_TOOL_DIRECTORY,
@@ -14,6 +16,7 @@ import {
14
16
  isRenderableRenderUiSpec,
15
17
  } from './mira-tool-render-ui';
16
18
  import type { MiraToolContext } from './mira-tool-types';
19
+ import { getWorkspaceContextWorkspaceId } from './workspace-context';
17
20
 
18
21
  export type { MiraToolContext } from './mira-tool-types';
19
22
  export type { MiraToolName };
@@ -31,6 +34,9 @@ export function createMiraStreamTools(
31
34
  ): ToolSet {
32
35
  const tools: ToolSet = {};
33
36
  let renderUiInvalidAttempts = 0;
37
+ const permittedNames = new Set<string>();
38
+ const guardTaskWrite = createTaskWriteGuard();
39
+ ctx.executionState ??= {};
34
40
 
35
41
  // Create a per-stream render_ui tool with stateful preprocessor.
36
42
  // The preprocessor auto-populates a context-aware fallback on the first
@@ -63,7 +69,38 @@ export function createMiraStreamTools(
63
69
  }
64
70
  }
65
71
 
66
- if (isMissingPermission) {
72
+ if (!isMissingPermission) permittedNames.add(name);
73
+
74
+ if (name === 'search_tools') {
75
+ tools[name] = {
76
+ ...def,
77
+ execute: async (args: { query: string; limit?: number }) => {
78
+ const workspaceId = getWorkspaceContextWorkspaceId(ctx);
79
+ const allowed = new Set<string>();
80
+ for (const candidate of Object.keys(
81
+ miraToolDefinitions
82
+ ) as MiraToolName[]) {
83
+ const permission = MIRA_TOOL_PERMISSIONS[candidate];
84
+ const required = permission
85
+ ? Array.isArray(permission)
86
+ ? permission
87
+ : [permission]
88
+ : [];
89
+ if (
90
+ ctx.authorizeWorkspaceTools
91
+ ? !required.length ||
92
+ (await ctx.authorizeWorkspaceTools(workspaceId, required))
93
+ : permittedNames.has(candidate)
94
+ )
95
+ allowed.add(candidate);
96
+ }
97
+ return searchMiraTools(args, allowed);
98
+ },
99
+ } as Tool;
100
+ continue;
101
+ }
102
+
103
+ if (isMissingPermission && !ctx.authorizeWorkspaceTools) {
67
104
  tools[name] = {
68
105
  ...def,
69
106
  execute: async () => ({
@@ -159,7 +196,38 @@ export function createMiraStreamTools(
159
196
  execute: async (
160
197
  args: Record<string, unknown>,
161
198
  options?: { abortSignal?: AbortSignal }
162
- ) => executeMiraTool(name, args, ctx, options),
199
+ ) => {
200
+ const executionCtx =
201
+ name === 'set_workspace_context'
202
+ ? ctx
203
+ : {
204
+ ...ctx,
205
+ workspaceContext: ctx.workspaceContext
206
+ ? { ...ctx.workspaceContext }
207
+ : undefined,
208
+ };
209
+ const targetWsId = getWorkspaceContextWorkspaceId(executionCtx);
210
+ const required = requiredPerm
211
+ ? Array.isArray(requiredPerm)
212
+ ? requiredPerm
213
+ : [requiredPerm]
214
+ : [];
215
+ if (
216
+ required.length &&
217
+ ctx.authorizeWorkspaceTools &&
218
+ !(await ctx.authorizeWorkspaceTools(targetWsId, required))
219
+ )
220
+ return {
221
+ success: false,
222
+ error:
223
+ 'The current workspace does not grant permission for this operation.',
224
+ };
225
+ return name === 'create_task'
226
+ ? guardTaskWrite(targetWsId, args, () =>
227
+ executeMiraTool(name, args, executionCtx, options)
228
+ )
229
+ : executeMiraTool(name, args, executionCtx, options);
230
+ },
163
231
  } as Tool;
164
232
  }
165
233
 
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod';
2
+
3
+ export const artifactKindSchema = z.enum([
4
+ 'tasks',
5
+ 'calendar',
6
+ 'finance',
7
+ 'meetings',
8
+ ]);
9
+ export const artifactLayoutSchema = z.enum([
10
+ 'auto',
11
+ 'horizontal',
12
+ 'vertical',
13
+ 'grid',
14
+ ]);
15
+ export const artifactPresentationSchema = z.object({
16
+ title: z.string().trim().min(1).max(80).optional(),
17
+ description: z.string().trim().max(240).optional(),
18
+ highlights: z.array(z.string().trim().min(1).max(160)).max(3).optional(),
19
+ search: z.string().trim().max(120).optional(),
20
+ taskStatus: z.enum(['all', 'overdue', 'today', 'upcoming']).optional(),
21
+ currency: z
22
+ .string()
23
+ .trim()
24
+ .max(8)
25
+ .toUpperCase()
26
+ .optional()
27
+ .describe('Finance only: ISO currency code'),
28
+ date: z.iso
29
+ .date()
30
+ .optional()
31
+ .describe('Calendar only: start the seven-day view on this local date'),
32
+ itemIds: z.array(z.string().min(1).max(80)).max(50).optional(),
33
+ });
34
+ export type ArtifactPresentation = z.infer<typeof artifactPresentationSchema>;
35
+ export const showWorkspaceArtifactSchema = z.object({
36
+ kind: artifactKindSchema,
37
+ layout: artifactLayoutSchema.optional(),
38
+ presentation: artifactPresentationSchema.optional(),
39
+ });
40
+ export const manageWorkspaceSchema = z
41
+ .object({
42
+ operation: z.enum([
43
+ 'close_artifact',
44
+ 'close_all',
45
+ 'focus_artifact',
46
+ 'set_layout',
47
+ ]),
48
+ kind: artifactKindSchema.optional(),
49
+ layout: artifactLayoutSchema.optional(),
50
+ })
51
+ .refine(
52
+ (value) =>
53
+ value.operation === 'close_all' ||
54
+ (value.operation === 'set_layout' ? !!value.layout : !!value.kind),
55
+ {
56
+ message: 'Provide kind for artifact operations or layout for set_layout.',
57
+ }
58
+ );