@tuturuuu/ai 0.0.10
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/README.md +76 -0
- package/package.json +106 -0
- package/src/api-key-hash.ts +28 -0
- package/src/calendar/events.ts +34 -0
- package/src/calendar/route.ts +114 -0
- package/src/chat/credit-source.ts +1 -0
- package/src/chat/google/chat-request-schema.ts +150 -0
- package/src/chat/google/default-system-instruction.ts +198 -0
- package/src/chat/google/message-file-processing.ts +212 -0
- package/src/chat/google/mira-step-preparation.ts +221 -0
- package/src/chat/google/new/route.ts +368 -0
- package/src/chat/google/route-auth.ts +81 -0
- package/src/chat/google/route-chat-resolution.ts +98 -0
- package/src/chat/google/route-credits.ts +61 -0
- package/src/chat/google/route-message-preparation.ts +331 -0
- package/src/chat/google/route-mira-runtime.ts +206 -0
- package/src/chat/google/route.ts +632 -0
- package/src/chat/google/stream-finish-persistence.ts +722 -0
- package/src/chat/google/summary/route.ts +153 -0
- package/src/chat/mira-render-ui-policy.ts +540 -0
- package/src/chat/mira-system-instruction.ts +484 -0
- package/src/chat-sdk/adapters.ts +389 -0
- package/src/chat-sdk/registry.ts +197 -0
- package/src/chat-sdk.ts +33 -0
- package/src/core.ts +3 -0
- package/src/credits/cap-output-tokens.ts +90 -0
- package/src/credits/check-credits.ts +232 -0
- package/src/credits/constants.ts +30 -0
- package/src/credits/index.ts +46 -0
- package/src/credits/model-mapping.ts +92 -0
- package/src/credits/reservations.ts +514 -0
- package/src/credits/resolve-plan-model.ts +219 -0
- package/src/credits/sync-gateway-models.ts +351 -0
- package/src/credits/types.ts +109 -0
- package/src/credits/use-ai-credits.ts +3 -0
- package/src/embeddings/metered.ts +283 -0
- package/src/executions/route.ts +137 -0
- package/src/generate/route.ts +411 -0
- package/src/hooks.ts +7 -0
- package/src/meetings/summary/route.ts +7 -0
- package/src/meetings/transcription/route.ts +134 -0
- package/src/memory/client.ts +158 -0
- package/src/memory/config.ts +38 -0
- package/src/memory/index.ts +32 -0
- package/src/memory/ingest.ts +51 -0
- package/src/memory/middleware.ts +35 -0
- package/src/memory/operations.ts +480 -0
- package/src/memory/scope.ts +102 -0
- package/src/memory/settings.ts +121 -0
- package/src/memory/types.ts +101 -0
- package/src/memory/workspace.ts +36 -0
- package/src/memory.ts +1 -0
- package/src/mind/patch.ts +146 -0
- package/src/mind/route.ts +687 -0
- package/src/mind/tools.ts +1500 -0
- package/src/mind/types.ts +20 -0
- package/src/object/core.ts +3 -0
- package/src/object/flashcards/route.ts +140 -0
- package/src/object/quizzes/explanation/route.ts +145 -0
- package/src/object/quizzes/route.ts +142 -0
- package/src/object/types.ts +187 -0
- package/src/object/year-plan/route.ts +196 -0
- package/src/react.ts +1 -0
- package/src/scheduling/algorithm.ts +791 -0
- package/src/scheduling/default.ts +36 -0
- package/src/scheduling/duration-optimizer.ts +689 -0
- package/src/scheduling/index.ts +79 -0
- package/src/scheduling/priority-calculator.ts +187 -0
- package/src/scheduling/recurrence-calculator.ts +621 -0
- package/src/scheduling/templates.ts +892 -0
- package/src/scheduling/types.ts +136 -0
- package/src/scheduling/web-adapter.ts +308 -0
- package/src/scheduling.ts +6 -0
- package/src/supported-actions.ts +1 -0
- package/src/supported-providers.ts +6 -0
- package/src/tools/context-builder.ts +372 -0
- package/src/tools/core.ts +1 -0
- package/src/tools/definitions/calendar.ts +106 -0
- package/src/tools/definitions/finance.ts +197 -0
- package/src/tools/definitions/image.ts +74 -0
- package/src/tools/definitions/memory.ts +83 -0
- package/src/tools/definitions/meta.ts +154 -0
- package/src/tools/definitions/render-ui.ts +81 -0
- package/src/tools/definitions/tasks.ts +343 -0
- package/src/tools/definitions/time-tracking.ts +381 -0
- package/src/tools/definitions/workspace-context.ts +45 -0
- package/src/tools/definitions/workspace-user-chat.ts +111 -0
- package/src/tools/executors/calendar.ts +371 -0
- package/src/tools/executors/chat.ts +15 -0
- package/src/tools/executors/finance.ts +638 -0
- package/src/tools/executors/helpers/encryption.ts +107 -0
- package/src/tools/executors/image.ts +247 -0
- package/src/tools/executors/markitdown.ts +684 -0
- package/src/tools/executors/memory.ts +277 -0
- package/src/tools/executors/parallel-checks.ts +176 -0
- package/src/tools/executors/qr.ts +170 -0
- package/src/tools/executors/scope-helpers.ts +192 -0
- package/src/tools/executors/search.ts +149 -0
- package/src/tools/executors/settings.ts +40 -0
- package/src/tools/executors/tasks.ts +1087 -0
- package/src/tools/executors/theme.ts +23 -0
- package/src/tools/executors/timer/timer-categories-executor.ts +110 -0
- package/src/tools/executors/timer/timer-category-mutations.ts +240 -0
- package/src/tools/executors/timer/timer-goal-mutations.ts +323 -0
- package/src/tools/executors/timer/timer-goals-executor.ts +272 -0
- package/src/tools/executors/timer/timer-helpers.ts +372 -0
- package/src/tools/executors/timer/timer-mutation-schemas.ts +160 -0
- package/src/tools/executors/timer/timer-mutation-types.ts +212 -0
- package/src/tools/executors/timer/timer-mutations.ts +19 -0
- package/src/tools/executors/timer/timer-queries.ts +18 -0
- package/src/tools/executors/timer/timer-session-lifecycle.ts +299 -0
- package/src/tools/executors/timer/timer-session-mutations.ts +10 -0
- package/src/tools/executors/timer/timer-session-queries.ts +153 -0
- package/src/tools/executors/timer/timer-session-updates.ts +200 -0
- package/src/tools/executors/timer/timer-sessions-executor.ts +91 -0
- package/src/tools/executors/timer/timer-stats-executor.ts +157 -0
- package/src/tools/executors/timer.ts +22 -0
- package/src/tools/executors/user.ts +60 -0
- package/src/tools/executors/workspace.ts +135 -0
- package/src/tools/json-render-catalog.ts +875 -0
- package/src/tools/mira-tool-definitions.ts +55 -0
- package/src/tools/mira-tool-dispatcher.ts +265 -0
- package/src/tools/mira-tool-metadata.ts +164 -0
- package/src/tools/mira-tool-names.ts +95 -0
- package/src/tools/mira-tool-render-ui.ts +54 -0
- package/src/tools/mira-tool-types.ts +17 -0
- package/src/tools/mira-tools.ts +167 -0
- package/src/tools/normalize-render-ui-input.ts +321 -0
- package/src/tools/workspace-context.ts +233 -0
- package/src/types.ts +38 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export {
|
|
2
|
+
executeCreateTimeTrackingEntry,
|
|
3
|
+
executeStartTimer,
|
|
4
|
+
executeStopTimer,
|
|
5
|
+
} from './timer-session-lifecycle';
|
|
6
|
+
export { executeMoveTimeTrackingSession } from './timer-session-queries';
|
|
7
|
+
export {
|
|
8
|
+
executeDeleteTimeTrackingSession,
|
|
9
|
+
executeUpdateTimeTrackingSession,
|
|
10
|
+
} from './timer-session-updates';
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { verifyWorkspaceMembershipType } from '@tuturuuu/utils/workspace-helper';
|
|
2
|
+
import type { MiraToolContext } from '../../mira-tools';
|
|
3
|
+
import { getWorkspaceContextWorkspaceId } from '../../workspace-context';
|
|
4
|
+
import { coerceOptionalString } from './timer-helpers';
|
|
5
|
+
import {
|
|
6
|
+
getZodErrorMessage,
|
|
7
|
+
type MoveTimeTrackingSessionArgs,
|
|
8
|
+
moveTimeTrackingSessionArgsSchema,
|
|
9
|
+
} from './timer-mutation-schemas';
|
|
10
|
+
import {
|
|
11
|
+
type MoveTimeTrackingSessionResult,
|
|
12
|
+
toTimerSession,
|
|
13
|
+
} from './timer-mutation-types';
|
|
14
|
+
|
|
15
|
+
export async function executeMoveTimeTrackingSession(
|
|
16
|
+
args: Record<string, unknown>,
|
|
17
|
+
ctx: MiraToolContext
|
|
18
|
+
): Promise<MoveTimeTrackingSessionResult> {
|
|
19
|
+
let parsedArgs: MoveTimeTrackingSessionArgs;
|
|
20
|
+
try {
|
|
21
|
+
parsedArgs = moveTimeTrackingSessionArgsSchema.parse(args);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
return { error: getZodErrorMessage(error) };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const workspaceId = getWorkspaceContextWorkspaceId(ctx);
|
|
27
|
+
const sessionId =
|
|
28
|
+
coerceOptionalString(parsedArgs.sessionId) ??
|
|
29
|
+
coerceOptionalString(parsedArgs.id);
|
|
30
|
+
const targetWorkspaceId = coerceOptionalString(parsedArgs.targetWorkspaceId);
|
|
31
|
+
|
|
32
|
+
if (!sessionId) return { error: 'sessionId is required' };
|
|
33
|
+
if (!targetWorkspaceId) return { error: 'targetWorkspaceId is required' };
|
|
34
|
+
|
|
35
|
+
const [sourceCheck, targetCheck] = await Promise.all([
|
|
36
|
+
verifyWorkspaceMembershipType({
|
|
37
|
+
wsId: workspaceId,
|
|
38
|
+
userId: ctx.userId,
|
|
39
|
+
supabase: ctx.supabase,
|
|
40
|
+
requiredType: 'MEMBER',
|
|
41
|
+
}),
|
|
42
|
+
verifyWorkspaceMembershipType({
|
|
43
|
+
wsId: targetWorkspaceId,
|
|
44
|
+
userId: ctx.userId,
|
|
45
|
+
supabase: ctx.supabase,
|
|
46
|
+
requiredType: 'MEMBER',
|
|
47
|
+
}),
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
if (
|
|
51
|
+
sourceCheck.error === 'membership_lookup_failed' ||
|
|
52
|
+
targetCheck.error === 'membership_lookup_failed'
|
|
53
|
+
) {
|
|
54
|
+
return { error: 'Could not verify workspace access' };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!sourceCheck.ok) {
|
|
58
|
+
return { error: 'Source workspace access denied' };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!targetCheck.ok) {
|
|
62
|
+
return { error: 'Target workspace access denied' };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { data: session, error: sessionError } = await ctx.supabase
|
|
66
|
+
.from('time_tracking_sessions')
|
|
67
|
+
.select(
|
|
68
|
+
`
|
|
69
|
+
*,
|
|
70
|
+
category:time_tracking_categories(id, name),
|
|
71
|
+
task:tasks(id, name)
|
|
72
|
+
`
|
|
73
|
+
)
|
|
74
|
+
.eq('id', sessionId)
|
|
75
|
+
.eq('ws_id', workspaceId)
|
|
76
|
+
.eq('user_id', ctx.userId)
|
|
77
|
+
.maybeSingle();
|
|
78
|
+
|
|
79
|
+
if (sessionError) return { error: sessionError.message };
|
|
80
|
+
if (!session) return { error: 'Session not found' };
|
|
81
|
+
if (session.is_running) {
|
|
82
|
+
return {
|
|
83
|
+
error: 'Cannot move running sessions. Please stop the session first.',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let targetCategoryId: string | null = null;
|
|
88
|
+
if (session.category?.name) {
|
|
89
|
+
const { data: targetCategory, error: targetCategoryError } =
|
|
90
|
+
await ctx.supabase
|
|
91
|
+
.from('time_tracking_categories')
|
|
92
|
+
.select('id')
|
|
93
|
+
.eq('ws_id', targetWorkspaceId)
|
|
94
|
+
.eq('name', session.category.name)
|
|
95
|
+
.limit(1)
|
|
96
|
+
.maybeSingle();
|
|
97
|
+
|
|
98
|
+
if (targetCategoryError) {
|
|
99
|
+
return {
|
|
100
|
+
error: `Failed to resolve matching category in target workspace: ${targetCategoryError.message}`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
targetCategoryId = targetCategory?.id ?? null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let targetTaskId: string | null = null;
|
|
108
|
+
if (session.task?.name) {
|
|
109
|
+
const { data: targetTask, error: targetTaskError } = await ctx.supabase
|
|
110
|
+
.from('tasks')
|
|
111
|
+
.select('id, list:task_lists!inner(board:workspace_boards!inner(ws_id))')
|
|
112
|
+
.eq('list.board.ws_id', targetWorkspaceId)
|
|
113
|
+
.eq('name', session.task.name)
|
|
114
|
+
.limit(1)
|
|
115
|
+
.maybeSingle();
|
|
116
|
+
|
|
117
|
+
if (targetTaskError) {
|
|
118
|
+
return {
|
|
119
|
+
error: `Failed to resolve matching task in target workspace: ${targetTaskError.message}`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
targetTaskId = targetTask?.id ?? null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const { data: movedSession, error: moveError } = await ctx.supabase
|
|
127
|
+
.from('time_tracking_sessions')
|
|
128
|
+
.update({
|
|
129
|
+
ws_id: targetWorkspaceId,
|
|
130
|
+
category_id: targetCategoryId,
|
|
131
|
+
task_id: targetTaskId,
|
|
132
|
+
updated_at: new Date().toISOString(),
|
|
133
|
+
})
|
|
134
|
+
.eq('id', sessionId)
|
|
135
|
+
.eq('ws_id', workspaceId)
|
|
136
|
+
.eq('user_id', ctx.userId)
|
|
137
|
+
.select(
|
|
138
|
+
`
|
|
139
|
+
*,
|
|
140
|
+
category:time_tracking_categories(*),
|
|
141
|
+
task:tasks(*)
|
|
142
|
+
`
|
|
143
|
+
)
|
|
144
|
+
.single();
|
|
145
|
+
|
|
146
|
+
if (moveError) return { error: moveError.message };
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
success: true,
|
|
150
|
+
message: 'Session moved successfully',
|
|
151
|
+
session: toTimerSession(movedSession as Record<string, unknown>),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { TablesUpdate } from '@tuturuuu/types';
|
|
2
|
+
import type { MiraToolContext } from '../../mira-tools';
|
|
3
|
+
import { getWorkspaceContextWorkspaceId } from '../../workspace-context';
|
|
4
|
+
import { hasTaskAccess, hasTimeTrackingCategoryAccess } from '../scope-helpers';
|
|
5
|
+
import {
|
|
6
|
+
coerceOptionalString,
|
|
7
|
+
MIN_DURATION_SECONDS,
|
|
8
|
+
parseFlexibleDateTime,
|
|
9
|
+
} from './timer-helpers';
|
|
10
|
+
import {
|
|
11
|
+
type DeleteTimeTrackingSessionArgs,
|
|
12
|
+
deleteTimeTrackingSessionArgsSchema,
|
|
13
|
+
getZodErrorMessage,
|
|
14
|
+
type UpdateTimeTrackingSessionArgs,
|
|
15
|
+
updateTimeTrackingSessionArgsSchema,
|
|
16
|
+
} from './timer-mutation-schemas';
|
|
17
|
+
import {
|
|
18
|
+
type DeleteTimeTrackingSessionResult,
|
|
19
|
+
toTimerSession,
|
|
20
|
+
type UpdateTimeTrackingSessionResult,
|
|
21
|
+
} from './timer-mutation-types';
|
|
22
|
+
|
|
23
|
+
export async function executeUpdateTimeTrackingSession(
|
|
24
|
+
args: Record<string, unknown>,
|
|
25
|
+
ctx: MiraToolContext
|
|
26
|
+
): Promise<UpdateTimeTrackingSessionResult> {
|
|
27
|
+
let parsedArgs: UpdateTimeTrackingSessionArgs;
|
|
28
|
+
try {
|
|
29
|
+
parsedArgs = updateTimeTrackingSessionArgsSchema.parse(args);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
return { error: getZodErrorMessage(error) };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const workspaceId = getWorkspaceContextWorkspaceId(ctx);
|
|
35
|
+
const sessionId =
|
|
36
|
+
coerceOptionalString(parsedArgs.sessionId) ??
|
|
37
|
+
coerceOptionalString(parsedArgs.id);
|
|
38
|
+
if (!sessionId) return { error: 'sessionId is required' };
|
|
39
|
+
|
|
40
|
+
const { data: existing, error: existingError } = await ctx.supabase
|
|
41
|
+
.from('time_tracking_sessions')
|
|
42
|
+
.select('*')
|
|
43
|
+
.eq('id', sessionId)
|
|
44
|
+
.eq('ws_id', workspaceId)
|
|
45
|
+
.eq('user_id', ctx.userId)
|
|
46
|
+
.maybeSingle();
|
|
47
|
+
|
|
48
|
+
if (existingError) return { error: existingError.message };
|
|
49
|
+
if (!existing) return { error: 'Session not found' };
|
|
50
|
+
|
|
51
|
+
const updates: TablesUpdate<'time_tracking_sessions'> = {};
|
|
52
|
+
if (parsedArgs.title !== undefined) {
|
|
53
|
+
if (typeof parsedArgs.title !== 'string') {
|
|
54
|
+
return { error: 'title must be a string' };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const normalizedTitle = parsedArgs.title.trim();
|
|
58
|
+
if (normalizedTitle.length === 0) {
|
|
59
|
+
return { error: 'title cannot be blank' };
|
|
60
|
+
}
|
|
61
|
+
updates.title = normalizedTitle;
|
|
62
|
+
}
|
|
63
|
+
if (parsedArgs.description !== undefined) {
|
|
64
|
+
updates.description = coerceOptionalString(parsedArgs.description);
|
|
65
|
+
}
|
|
66
|
+
if (parsedArgs.categoryId !== undefined) {
|
|
67
|
+
const categoryId = coerceOptionalString(parsedArgs.categoryId);
|
|
68
|
+
|
|
69
|
+
if (categoryId) {
|
|
70
|
+
try {
|
|
71
|
+
if (!(await hasTimeTrackingCategoryAccess(ctx, categoryId))) {
|
|
72
|
+
return { error: 'Category not found in current workspace' };
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return {
|
|
76
|
+
error:
|
|
77
|
+
error instanceof Error ? error.message : 'Category lookup failed',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
updates.category_id = categoryId;
|
|
83
|
+
}
|
|
84
|
+
if (parsedArgs.taskId !== undefined) {
|
|
85
|
+
const taskId = coerceOptionalString(parsedArgs.taskId);
|
|
86
|
+
|
|
87
|
+
if (taskId) {
|
|
88
|
+
try {
|
|
89
|
+
if (!(await hasTaskAccess(ctx, taskId))) {
|
|
90
|
+
return { error: 'Task not found in current workspace' };
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
return {
|
|
94
|
+
error: error instanceof Error ? error.message : 'Task lookup failed',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
updates.task_id = taskId;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let nextStartTime = existing.start_time;
|
|
103
|
+
let nextEndTime = existing.end_time;
|
|
104
|
+
|
|
105
|
+
if (parsedArgs.startTime !== undefined) {
|
|
106
|
+
const parsed = parseFlexibleDateTime(parsedArgs.startTime, 'startTime', {
|
|
107
|
+
date: parsedArgs.date,
|
|
108
|
+
timezone: ctx.timezone,
|
|
109
|
+
});
|
|
110
|
+
if (!parsed.ok) return { error: parsed.error };
|
|
111
|
+
nextStartTime = parsed.value.toISOString();
|
|
112
|
+
updates.start_time = nextStartTime;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (parsedArgs.endTime !== undefined) {
|
|
116
|
+
const parsed = parseFlexibleDateTime(parsedArgs.endTime, 'endTime', {
|
|
117
|
+
date: parsedArgs.date,
|
|
118
|
+
timezone: ctx.timezone,
|
|
119
|
+
});
|
|
120
|
+
if (!parsed.ok) return { error: parsed.error };
|
|
121
|
+
nextEndTime = parsed.value.toISOString();
|
|
122
|
+
updates.end_time = nextEndTime;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (parsedArgs.startTime !== undefined || parsedArgs.endTime !== undefined) {
|
|
126
|
+
if (!nextEndTime) {
|
|
127
|
+
return { error: 'Cannot compute duration without endTime' };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const start = new Date(nextStartTime);
|
|
131
|
+
const end = new Date(nextEndTime);
|
|
132
|
+
if (end <= start) {
|
|
133
|
+
return { error: 'endTime must be after startTime' };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const durationSeconds = Math.floor(
|
|
137
|
+
(end.getTime() - start.getTime()) / 1000
|
|
138
|
+
);
|
|
139
|
+
if (durationSeconds < MIN_DURATION_SECONDS) {
|
|
140
|
+
return { error: 'Session must be at least 1 minute long' };
|
|
141
|
+
}
|
|
142
|
+
updates.duration_seconds = durationSeconds;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (Object.keys(updates).length === 0) {
|
|
146
|
+
return { success: true, message: 'No fields to update' };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const { data, error } = await ctx.supabase
|
|
150
|
+
.from('time_tracking_sessions')
|
|
151
|
+
.update(updates)
|
|
152
|
+
.eq('id', sessionId)
|
|
153
|
+
.eq('ws_id', workspaceId)
|
|
154
|
+
.eq('user_id', ctx.userId)
|
|
155
|
+
.select(
|
|
156
|
+
`
|
|
157
|
+
*,
|
|
158
|
+
category:time_tracking_categories(*),
|
|
159
|
+
task:tasks(*)
|
|
160
|
+
`
|
|
161
|
+
)
|
|
162
|
+
.single();
|
|
163
|
+
|
|
164
|
+
if (error) return { error: error.message };
|
|
165
|
+
return {
|
|
166
|
+
success: true,
|
|
167
|
+
message: 'Session updated',
|
|
168
|
+
session: toTimerSession(data as Record<string, unknown>),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export async function executeDeleteTimeTrackingSession(
|
|
173
|
+
args: Record<string, unknown>,
|
|
174
|
+
ctx: MiraToolContext
|
|
175
|
+
): Promise<DeleteTimeTrackingSessionResult> {
|
|
176
|
+
let parsedArgs: DeleteTimeTrackingSessionArgs;
|
|
177
|
+
try {
|
|
178
|
+
parsedArgs = deleteTimeTrackingSessionArgsSchema.parse(args);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
return { error: getZodErrorMessage(error) };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const workspaceId = getWorkspaceContextWorkspaceId(ctx);
|
|
184
|
+
const sessionId =
|
|
185
|
+
coerceOptionalString(parsedArgs.sessionId) ??
|
|
186
|
+
coerceOptionalString(parsedArgs.id);
|
|
187
|
+
if (!sessionId) return { error: 'sessionId is required' };
|
|
188
|
+
|
|
189
|
+
const { data, error } = await ctx.supabase
|
|
190
|
+
.from('time_tracking_sessions')
|
|
191
|
+
.delete()
|
|
192
|
+
.eq('id', sessionId)
|
|
193
|
+
.eq('ws_id', workspaceId)
|
|
194
|
+
.eq('user_id', ctx.userId)
|
|
195
|
+
.select('id');
|
|
196
|
+
|
|
197
|
+
if (error) return { error: error.message };
|
|
198
|
+
if (!data?.length) return { error: 'Session not found' };
|
|
199
|
+
return { success: true, message: 'Session deleted' };
|
|
200
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { MiraToolContext } from '../../mira-tools';
|
|
2
|
+
import { getWorkspaceContextWorkspaceId } from '../../workspace-context';
|
|
3
|
+
import { coerceOptionalString, normalizeCursor } from './timer-helpers';
|
|
4
|
+
|
|
5
|
+
export async function executeListTimeTrackingSessions(
|
|
6
|
+
args: Record<string, unknown>,
|
|
7
|
+
ctx: MiraToolContext
|
|
8
|
+
) {
|
|
9
|
+
const includePending = Boolean(args.includePending);
|
|
10
|
+
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 50);
|
|
11
|
+
const cursor = args.cursor;
|
|
12
|
+
const workspaceId = getWorkspaceContextWorkspaceId(ctx);
|
|
13
|
+
|
|
14
|
+
let query = ctx.supabase
|
|
15
|
+
.from('time_tracking_sessions')
|
|
16
|
+
.select(
|
|
17
|
+
`
|
|
18
|
+
id, title, description, start_time, end_time, duration_seconds,
|
|
19
|
+
is_running, category_id, task_id, pending_approval, ws_id,
|
|
20
|
+
category:time_tracking_categories(id, name, color),
|
|
21
|
+
task:tasks(id, name)
|
|
22
|
+
`
|
|
23
|
+
)
|
|
24
|
+
.eq('ws_id', workspaceId)
|
|
25
|
+
.eq('user_id', ctx.userId)
|
|
26
|
+
.order('start_time', { ascending: false })
|
|
27
|
+
.order('id', { ascending: false })
|
|
28
|
+
.limit(limit + 1);
|
|
29
|
+
|
|
30
|
+
if (!includePending) {
|
|
31
|
+
query = query.eq('pending_approval', false);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (cursor !== undefined) {
|
|
35
|
+
const normalized = normalizeCursor(cursor);
|
|
36
|
+
if (!normalized.ok) return { error: normalized.error };
|
|
37
|
+
|
|
38
|
+
const esc = (value: string) =>
|
|
39
|
+
value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
40
|
+
|
|
41
|
+
query = query.or(
|
|
42
|
+
`start_time.lt."${esc(normalized.lastStartTime)}",and(start_time.eq."${esc(normalized.lastStartTime)}",id.lt."${esc(normalized.lastId)}")`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const { data, error } = await query;
|
|
47
|
+
if (error) return { error: error.message };
|
|
48
|
+
|
|
49
|
+
const rows = data ?? [];
|
|
50
|
+
const hasMore = rows.length > limit;
|
|
51
|
+
const sessions = hasMore ? rows.slice(0, limit) : rows;
|
|
52
|
+
const last = sessions[sessions.length - 1];
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
success: true,
|
|
56
|
+
count: sessions.length,
|
|
57
|
+
sessions,
|
|
58
|
+
hasMore,
|
|
59
|
+
nextCursor: last ? `${last.start_time}|${last.id}` : null,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function executeGetTimeTrackingSession(
|
|
64
|
+
args: Record<string, unknown>,
|
|
65
|
+
ctx: MiraToolContext
|
|
66
|
+
) {
|
|
67
|
+
const sessionIdNormalized = coerceOptionalString(args.sessionId);
|
|
68
|
+
const idNormalized = coerceOptionalString(args.id);
|
|
69
|
+
const sessionId = sessionIdNormalized ?? idNormalized;
|
|
70
|
+
const workspaceId = getWorkspaceContextWorkspaceId(ctx);
|
|
71
|
+
if (!sessionId) return { error: 'sessionId is required' };
|
|
72
|
+
|
|
73
|
+
const { data, error } = await ctx.supabase
|
|
74
|
+
.from('time_tracking_sessions')
|
|
75
|
+
.select(
|
|
76
|
+
`
|
|
77
|
+
*,
|
|
78
|
+
category:time_tracking_categories(*),
|
|
79
|
+
task:tasks(*)
|
|
80
|
+
`
|
|
81
|
+
)
|
|
82
|
+
.eq('id', sessionId)
|
|
83
|
+
.eq('ws_id', workspaceId)
|
|
84
|
+
.eq('user_id', ctx.userId)
|
|
85
|
+
.maybeSingle();
|
|
86
|
+
|
|
87
|
+
if (error) return { error: error.message };
|
|
88
|
+
if (!data) return { error: 'Session not found' };
|
|
89
|
+
|
|
90
|
+
return { success: true, session: data };
|
|
91
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { MiraToolContext } from '../../mira-tools';
|
|
2
|
+
import { getWorkspaceContextWorkspaceId } from '../../workspace-context';
|
|
3
|
+
import {
|
|
4
|
+
buildToolFailure,
|
|
5
|
+
resolveTimezone,
|
|
6
|
+
toFiniteNumber,
|
|
7
|
+
} from './timer-helpers';
|
|
8
|
+
|
|
9
|
+
export type TimeTrackerStatsRow = {
|
|
10
|
+
today_time?: number | null;
|
|
11
|
+
week_time?: number | null;
|
|
12
|
+
month_time?: number | null;
|
|
13
|
+
streak?: number | null;
|
|
14
|
+
daily_activity?: Array<{
|
|
15
|
+
date?: string | null;
|
|
16
|
+
duration?: number | null;
|
|
17
|
+
sessions?: number | null;
|
|
18
|
+
}> | null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export async function fetchTimeTrackerStats(
|
|
22
|
+
ctx: MiraToolContext,
|
|
23
|
+
options?: {
|
|
24
|
+
timezone?: string;
|
|
25
|
+
summaryOnly?: boolean;
|
|
26
|
+
daysBack?: number;
|
|
27
|
+
}
|
|
28
|
+
) {
|
|
29
|
+
const summaryOnly = options?.summaryOnly ?? true;
|
|
30
|
+
const daysBack = Math.max(0, Math.min(options?.daysBack ?? 365, 365));
|
|
31
|
+
const appliedDaysBack = summaryOnly ? 0 : daysBack;
|
|
32
|
+
const timezoneResolution = resolveTimezone(options?.timezone, ctx.timezone);
|
|
33
|
+
const workspaceId = getWorkspaceContextWorkspaceId(ctx);
|
|
34
|
+
|
|
35
|
+
const { data, error } = await ctx.supabase.rpc('get_time_tracker_stats', {
|
|
36
|
+
p_user_id: ctx.userId,
|
|
37
|
+
p_ws_id: workspaceId,
|
|
38
|
+
p_is_personal: ctx.workspaceContext?.personal ?? false,
|
|
39
|
+
p_timezone: timezoneResolution.resolved,
|
|
40
|
+
p_days_back: appliedDaysBack,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (error) {
|
|
44
|
+
return buildToolFailure('TT_STATS_RPC_FAILED', error.message, true);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const row = ((Array.isArray(data) ? data[0] : null) ??
|
|
48
|
+
{}) as TimeTrackerStatsRow;
|
|
49
|
+
const dailyActivity = Array.isArray(row.daily_activity)
|
|
50
|
+
? row.daily_activity.map((entry) => ({
|
|
51
|
+
date: entry.date ?? '',
|
|
52
|
+
duration: toFiniteNumber(entry.duration),
|
|
53
|
+
sessions: toFiniteNumber(entry.sessions),
|
|
54
|
+
}))
|
|
55
|
+
: [];
|
|
56
|
+
|
|
57
|
+
const todayTime = toFiniteNumber(row.today_time);
|
|
58
|
+
const weekTime = toFiniteNumber(row.week_time);
|
|
59
|
+
const monthTime = toFiniteNumber(row.month_time);
|
|
60
|
+
const streak = toFiniteNumber(row.streak);
|
|
61
|
+
const totalDuration = dailyActivity.reduce(
|
|
62
|
+
(sum, entry) => sum + toFiniteNumber(entry.duration),
|
|
63
|
+
0
|
|
64
|
+
);
|
|
65
|
+
const totalSessions = dailyActivity.reduce(
|
|
66
|
+
(sum, entry) => sum + toFiniteNumber(entry.sessions),
|
|
67
|
+
0
|
|
68
|
+
);
|
|
69
|
+
const averageDailyDuration =
|
|
70
|
+
dailyActivity.length > 0 ? totalDuration / dailyActivity.length : 0;
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
success: true,
|
|
74
|
+
workspaceId,
|
|
75
|
+
timezone: timezoneResolution.resolved,
|
|
76
|
+
timezoneResolution,
|
|
77
|
+
summaryOnly,
|
|
78
|
+
daysBack: appliedDaysBack,
|
|
79
|
+
todayTime,
|
|
80
|
+
weekTime,
|
|
81
|
+
monthTime,
|
|
82
|
+
streak,
|
|
83
|
+
dailyActivity,
|
|
84
|
+
insights: {
|
|
85
|
+
totalDuration,
|
|
86
|
+
totalSessions,
|
|
87
|
+
averageDailyDuration,
|
|
88
|
+
averageSessionDuration:
|
|
89
|
+
totalSessions > 0 ? Math.round(totalDuration / totalSessions) : 0,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function executeGetTimeTrackerStats(
|
|
95
|
+
args: Record<string, unknown>,
|
|
96
|
+
ctx: MiraToolContext
|
|
97
|
+
) {
|
|
98
|
+
const summaryOnly =
|
|
99
|
+
typeof args.summaryOnly === 'boolean' ? args.summaryOnly : true;
|
|
100
|
+
const daysBackRaw = Number(args.daysBack);
|
|
101
|
+
const daysBack =
|
|
102
|
+
Number.isFinite(daysBackRaw) && daysBackRaw > 0
|
|
103
|
+
? Math.min(Math.floor(daysBackRaw), 3650)
|
|
104
|
+
: 365;
|
|
105
|
+
const timezoneArg =
|
|
106
|
+
typeof args.timezone === 'string' ? args.timezone : undefined;
|
|
107
|
+
|
|
108
|
+
const result = await fetchTimeTrackerStats(ctx, {
|
|
109
|
+
timezone: timezoneArg,
|
|
110
|
+
summaryOnly,
|
|
111
|
+
daysBack,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (!result.success) return result;
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
success: true,
|
|
118
|
+
todayTime: result.todayTime,
|
|
119
|
+
weekTime: result.weekTime,
|
|
120
|
+
monthTime: result.monthTime,
|
|
121
|
+
streak: result.streak,
|
|
122
|
+
dailyActivity: result.dailyActivity,
|
|
123
|
+
stats: {
|
|
124
|
+
todayTimeSeconds: result.todayTime,
|
|
125
|
+
weekTimeSeconds: result.weekTime,
|
|
126
|
+
monthTimeSeconds: result.monthTime,
|
|
127
|
+
streakDays: result.streak,
|
|
128
|
+
dailyActivity: result.dailyActivity.map((entry) => ({
|
|
129
|
+
date: entry.date,
|
|
130
|
+
durationSeconds: entry.duration,
|
|
131
|
+
sessionCount: entry.sessions,
|
|
132
|
+
})),
|
|
133
|
+
},
|
|
134
|
+
insights: result.insights,
|
|
135
|
+
meta: {
|
|
136
|
+
workspaceId: result.workspaceId,
|
|
137
|
+
workspaceContextId: ctx.workspaceContext?.workspaceContextId ?? ctx.wsId,
|
|
138
|
+
isPersonalContext: ctx.workspaceContext?.personal ?? false,
|
|
139
|
+
filtersApplied: {
|
|
140
|
+
summaryOnly: result.summaryOnly,
|
|
141
|
+
daysBack: result.daysBack,
|
|
142
|
+
},
|
|
143
|
+
timezone: {
|
|
144
|
+
requested: result.timezoneResolution.requested,
|
|
145
|
+
resolved: result.timezoneResolution.resolved,
|
|
146
|
+
validRequested: result.timezoneResolution.validRequested,
|
|
147
|
+
usedFallback: result.timezoneResolution.usedFallback,
|
|
148
|
+
},
|
|
149
|
+
units: {
|
|
150
|
+
durations: 'seconds',
|
|
151
|
+
streak: 'days',
|
|
152
|
+
goalTargets: 'minutes',
|
|
153
|
+
progress: 'percent',
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export { executeListTimeTrackingCategories } from './timer/timer-categories-executor';
|
|
2
|
+
export { executeGetTimeTrackerGoals } from './timer/timer-goals-executor';
|
|
3
|
+
export { parseFlexibleDateTime } from './timer/timer-helpers';
|
|
4
|
+
export {
|
|
5
|
+
executeCreateTimeTrackerGoal,
|
|
6
|
+
executeCreateTimeTrackingCategory,
|
|
7
|
+
executeCreateTimeTrackingEntry,
|
|
8
|
+
executeDeleteTimeTrackerGoal,
|
|
9
|
+
executeDeleteTimeTrackingCategory,
|
|
10
|
+
executeDeleteTimeTrackingSession,
|
|
11
|
+
executeMoveTimeTrackingSession,
|
|
12
|
+
executeStartTimer,
|
|
13
|
+
executeStopTimer,
|
|
14
|
+
executeUpdateTimeTrackerGoal,
|
|
15
|
+
executeUpdateTimeTrackingCategory,
|
|
16
|
+
executeUpdateTimeTrackingSession,
|
|
17
|
+
} from './timer/timer-mutations';
|
|
18
|
+
export {
|
|
19
|
+
executeGetTimeTrackingSession,
|
|
20
|
+
executeListTimeTrackingSessions,
|
|
21
|
+
} from './timer/timer-sessions-executor';
|
|
22
|
+
export { executeGetTimeTrackerStats } from './timer/timer-stats-executor';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { MiraToolContext } from '../mira-tools';
|
|
2
|
+
|
|
3
|
+
export async function executeUpdateUserName(
|
|
4
|
+
args: Record<string, unknown>,
|
|
5
|
+
ctx: MiraToolContext
|
|
6
|
+
) {
|
|
7
|
+
const displayName = args.displayName as string | undefined;
|
|
8
|
+
const fullName = args.fullName as string | undefined;
|
|
9
|
+
|
|
10
|
+
if (!displayName && !fullName) {
|
|
11
|
+
return {
|
|
12
|
+
success: false,
|
|
13
|
+
error: 'No name fields provided to update',
|
|
14
|
+
message: 'You must provide at least one of displayName or fullName.',
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const results: Record<string, any> = {};
|
|
19
|
+
|
|
20
|
+
if (displayName) {
|
|
21
|
+
const { error } = await ctx.supabase
|
|
22
|
+
.from('users')
|
|
23
|
+
.update({ display_name: displayName })
|
|
24
|
+
.eq('id', ctx.userId);
|
|
25
|
+
|
|
26
|
+
if (error) {
|
|
27
|
+
results.displayNameError = error.message;
|
|
28
|
+
} else {
|
|
29
|
+
results.displayNameUpdated = true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (fullName) {
|
|
34
|
+
const { error } = await ctx.supabase
|
|
35
|
+
.from('user_private_details')
|
|
36
|
+
.update({ full_name: fullName })
|
|
37
|
+
.eq('user_id', ctx.userId);
|
|
38
|
+
|
|
39
|
+
if (error) {
|
|
40
|
+
results.fullNameError = error.message;
|
|
41
|
+
} else {
|
|
42
|
+
results.fullNameUpdated = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (results.displayNameError || results.fullNameError) {
|
|
47
|
+
return {
|
|
48
|
+
success: false,
|
|
49
|
+
error: 'Partial or complete failure',
|
|
50
|
+
message: 'Failed to update some or all name fields',
|
|
51
|
+
details: results,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
success: true,
|
|
57
|
+
message: 'User name updated successfully',
|
|
58
|
+
details: results,
|
|
59
|
+
};
|
|
60
|
+
}
|