@prmichaelsen/task-mcp 0.2.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 (142) hide show
  1. package/.env.example +19 -0
  2. package/AGENT.md +1165 -0
  3. package/CHANGELOG.md +72 -0
  4. package/agent/commands/acp.commit.md +511 -0
  5. package/agent/commands/acp.init.md +376 -0
  6. package/agent/commands/acp.package-install.md +347 -0
  7. package/agent/commands/acp.proceed.md +311 -0
  8. package/agent/commands/acp.report.md +392 -0
  9. package/agent/commands/acp.status.md +280 -0
  10. package/agent/commands/acp.sync.md +323 -0
  11. package/agent/commands/acp.update.md +301 -0
  12. package/agent/commands/acp.validate.md +385 -0
  13. package/agent/commands/acp.version-check-for-updates.md +275 -0
  14. package/agent/commands/acp.version-check.md +190 -0
  15. package/agent/commands/acp.version-update.md +288 -0
  16. package/agent/commands/command.template.md +273 -0
  17. package/agent/commands/git.commit.md +511 -0
  18. package/agent/commands/git.init.md +513 -0
  19. package/agent/design/.gitkeep +0 -0
  20. package/agent/design/acp-task-execution-requirements.md +555 -0
  21. package/agent/design/api-dto-design.md +394 -0
  22. package/agent/design/code-extraction-guide.md +827 -0
  23. package/agent/design/design.template.md +136 -0
  24. package/agent/design/requirements.template.md +387 -0
  25. package/agent/design/rest-api-integration.md +489 -0
  26. package/agent/design/sdk-export-requirements.md +549 -0
  27. package/agent/milestones/.gitkeep +0 -0
  28. package/agent/milestones/milestone-1-{title}.template.md +206 -0
  29. package/agent/milestones/milestone-2-task-infrastructure.md +232 -0
  30. package/agent/milestones/milestone-4-autonomous-execution.md +235 -0
  31. package/agent/patterns/.gitkeep +0 -0
  32. package/agent/patterns/bootstrap.md +1271 -0
  33. package/agent/patterns/bootstrap.template.md +1237 -0
  34. package/agent/patterns/pattern.template.md +364 -0
  35. package/agent/progress.template.yaml +158 -0
  36. package/agent/progress.yaml +375 -0
  37. package/agent/scripts/check-for-updates.sh +88 -0
  38. package/agent/scripts/install.sh +157 -0
  39. package/agent/scripts/uninstall.sh +75 -0
  40. package/agent/scripts/update.sh +139 -0
  41. package/agent/scripts/version.sh +35 -0
  42. package/agent/tasks/.gitkeep +0 -0
  43. package/agent/tasks/task-1-{title}.template.md +225 -0
  44. package/agent/tasks/task-86-task-data-model-schemas.md +143 -0
  45. package/agent/tasks/task-87-task-database-service.md +220 -0
  46. package/agent/tasks/task-88-firebase-client-wrapper.md +139 -0
  47. package/agent/tasks/task-88-task-execution-engine.md +277 -0
  48. package/agent/tasks/task-89-mcp-server-implementation.md +197 -0
  49. package/agent/tasks/task-90-build-configuration.md +146 -0
  50. package/agent/tasks/task-91-deployment-configuration.md +128 -0
  51. package/coverage/base.css +224 -0
  52. package/coverage/block-navigation.js +87 -0
  53. package/coverage/favicon.png +0 -0
  54. package/coverage/index.html +191 -0
  55. package/coverage/lcov-report/base.css +224 -0
  56. package/coverage/lcov-report/block-navigation.js +87 -0
  57. package/coverage/lcov-report/favicon.png +0 -0
  58. package/coverage/lcov-report/index.html +191 -0
  59. package/coverage/lcov-report/prettify.css +1 -0
  60. package/coverage/lcov-report/prettify.js +2 -0
  61. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  62. package/coverage/lcov-report/sorter.js +210 -0
  63. package/coverage/lcov-report/src/client.ts.html +1030 -0
  64. package/coverage/lcov-report/src/constant/collections.ts.html +469 -0
  65. package/coverage/lcov-report/src/constant/index.html +116 -0
  66. package/coverage/lcov-report/src/dto/index.html +116 -0
  67. package/coverage/lcov-report/src/dto/transformers.ts.html +568 -0
  68. package/coverage/lcov-report/src/index.html +146 -0
  69. package/coverage/lcov-report/src/schemas/index.html +116 -0
  70. package/coverage/lcov-report/src/schemas/task.ts.html +547 -0
  71. package/coverage/lcov-report/src/server-factory.ts.html +418 -0
  72. package/coverage/lcov-report/src/server.ts.html +289 -0
  73. package/coverage/lcov-report/src/services/index.html +116 -0
  74. package/coverage/lcov-report/src/services/task-database.service.ts.html +1495 -0
  75. package/coverage/lcov-report/src/tools/index.html +236 -0
  76. package/coverage/lcov-report/src/tools/index.ts.html +292 -0
  77. package/coverage/lcov-report/src/tools/task-add-message.ts.html +277 -0
  78. package/coverage/lcov-report/src/tools/task-complete-task-item.ts.html +343 -0
  79. package/coverage/lcov-report/src/tools/task-create-milestone.ts.html +286 -0
  80. package/coverage/lcov-report/src/tools/task-create-task-item.ts.html +358 -0
  81. package/coverage/lcov-report/src/tools/task-get-next-step.ts.html +460 -0
  82. package/coverage/lcov-report/src/tools/task-get-status.ts.html +316 -0
  83. package/coverage/lcov-report/src/tools/task-report-completion.ts.html +343 -0
  84. package/coverage/lcov-report/src/tools/task-update-progress.ts.html +232 -0
  85. package/coverage/lcov.info +974 -0
  86. package/coverage/prettify.css +1 -0
  87. package/coverage/prettify.js +2 -0
  88. package/coverage/sort-arrow-sprite.png +0 -0
  89. package/coverage/sorter.js +210 -0
  90. package/coverage/src/client.ts.html +1030 -0
  91. package/coverage/src/constant/collections.ts.html +469 -0
  92. package/coverage/src/constant/index.html +116 -0
  93. package/coverage/src/dto/index.html +116 -0
  94. package/coverage/src/dto/transformers.ts.html +568 -0
  95. package/coverage/src/index.html +146 -0
  96. package/coverage/src/schemas/index.html +116 -0
  97. package/coverage/src/schemas/task.ts.html +547 -0
  98. package/coverage/src/server-factory.ts.html +418 -0
  99. package/coverage/src/server.ts.html +289 -0
  100. package/coverage/src/services/index.html +116 -0
  101. package/coverage/src/services/task-database.service.ts.html +1495 -0
  102. package/coverage/src/tools/index.html +236 -0
  103. package/coverage/src/tools/index.ts.html +292 -0
  104. package/coverage/src/tools/task-add-message.ts.html +277 -0
  105. package/coverage/src/tools/task-complete-task-item.ts.html +343 -0
  106. package/coverage/src/tools/task-create-milestone.ts.html +286 -0
  107. package/coverage/src/tools/task-create-task-item.ts.html +358 -0
  108. package/coverage/src/tools/task-get-next-step.ts.html +460 -0
  109. package/coverage/src/tools/task-get-status.ts.html +316 -0
  110. package/coverage/src/tools/task-report-completion.ts.html +343 -0
  111. package/coverage/src/tools/task-update-progress.ts.html +232 -0
  112. package/firestore.rules +95 -0
  113. package/jest.config.js +31 -0
  114. package/package.json +67 -0
  115. package/src/client.spec.ts +199 -0
  116. package/src/client.ts +315 -0
  117. package/src/constant/collections.ts +128 -0
  118. package/src/dto/index.ts +47 -0
  119. package/src/dto/task-api.dto.ts +219 -0
  120. package/src/dto/transformers.spec.ts +462 -0
  121. package/src/dto/transformers.ts +161 -0
  122. package/src/schemas/task.ts +154 -0
  123. package/src/server-factory.spec.ts +70 -0
  124. package/src/server-factory.ts +111 -0
  125. package/src/server.ts +68 -0
  126. package/src/services/task-database.service.e2e.ts +116 -0
  127. package/src/services/task-database.service.spec.ts +479 -0
  128. package/src/services/task-database.service.ts +470 -0
  129. package/src/test-schemas.ts +161 -0
  130. package/src/tools/index.ts +69 -0
  131. package/src/tools/task-add-message.ts +64 -0
  132. package/src/tools/task-complete-task-item.ts +86 -0
  133. package/src/tools/task-create-milestone.ts +67 -0
  134. package/src/tools/task-create-task-item.ts +91 -0
  135. package/src/tools/task-get-next-step.spec.ts +136 -0
  136. package/src/tools/task-get-next-step.ts +125 -0
  137. package/src/tools/task-get-status.spec.ts +213 -0
  138. package/src/tools/task-get-status.ts +77 -0
  139. package/src/tools/task-report-completion.ts +86 -0
  140. package/src/tools/task-update-progress.ts +49 -0
  141. package/src/tools/tools.spec.ts +194 -0
  142. package/tsconfig.json +31 -0
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Task Data Model and Schemas
3
+ *
4
+ * Zod schemas and TypeScript interfaces for the task execution system.
5
+ * These schemas define the structure of Task documents in Firestore.
6
+ */
7
+
8
+ import { z } from 'zod'
9
+
10
+ /**
11
+ * Milestone Schema
12
+ * Represents a major phase in task execution with multiple sub-tasks
13
+ */
14
+ export const MilestoneSchema = z.object({
15
+ id: z.string(),
16
+ name: z.string(),
17
+ description: z.string(),
18
+ status: z.enum(['not_started', 'in_progress', 'completed']),
19
+ progress: z.number().min(0).max(100),
20
+ tasks_completed: z.number().min(0),
21
+ tasks_total: z.number().min(0),
22
+ started_at: z.string().optional(),
23
+ completed_at: z.string().optional()
24
+ })
25
+
26
+ /**
27
+ * Task Item Schema
28
+ * Represents a granular work item within a milestone
29
+ */
30
+ export const TaskItemSchema = z.object({
31
+ id: z.string(),
32
+ name: z.string(),
33
+ description: z.string(),
34
+ status: z.enum(['not_started', 'in_progress', 'completed']),
35
+ estimated_hours: z.number().optional(),
36
+ completed_at: z.string().optional(),
37
+ notes: z.string().optional()
38
+ })
39
+
40
+ /**
41
+ * Task Progress Schema
42
+ * Tracks overall progress and milestone/task completion
43
+ */
44
+ export const TaskProgressSchema = z.object({
45
+ current_milestone: z.string(),
46
+ current_task: z.string(),
47
+ overall_percentage: z.number().min(0).max(100),
48
+ milestones: z.array(MilestoneSchema),
49
+ tasks: z.record(z.string(), z.array(TaskItemSchema))
50
+ })
51
+
52
+ /**
53
+ * Task Execution Schema
54
+ * Stores execution state including messages and tool results
55
+ */
56
+ export const TaskExecutionSchema = z.object({
57
+ api_messages: z.array(z.any()),
58
+ task_messages: z.array(z.any()),
59
+ tool_results: z.array(z.any()),
60
+ error: z.string().optional(),
61
+ abort_reason: z.string().optional()
62
+ })
63
+
64
+ /**
65
+ * Task Configuration Schema
66
+ * Configuration for task execution behavior
67
+ * Note: Model is configured globally by the tenant platform, not per-task
68
+ */
69
+ export const TaskConfigSchema = z.object({
70
+ system_prompt: z.string(),
71
+ auto_approve: z.boolean(),
72
+ max_iterations: z.number().optional()
73
+ })
74
+
75
+ /**
76
+ * Task Metadata Schema
77
+ * Optional metadata for task organization and tracking
78
+ */
79
+ export const TaskMetadataSchema = z.object({
80
+ conversation_id: z.string().optional(),
81
+ parent_task_id: z.string().optional(),
82
+ tags: z.array(z.string()).optional()
83
+ }).optional()
84
+
85
+ /**
86
+ * Task Schema
87
+ * Complete task document structure
88
+ */
89
+ export const TaskSchema = z.object({
90
+ id: z.string(),
91
+ user_id: z.string(),
92
+ title: z.string(),
93
+ description: z.string(),
94
+ status: z.enum(['not_started', 'in_progress', 'paused', 'completed', 'failed']),
95
+ created_at: z.string(),
96
+ updated_at: z.string(),
97
+ started_at: z.string().optional(),
98
+ completed_at: z.string().optional(),
99
+
100
+ progress: TaskProgressSchema,
101
+ execution: TaskExecutionSchema,
102
+ config: TaskConfigSchema,
103
+ metadata: TaskMetadataSchema
104
+ })
105
+
106
+ /**
107
+ * Task Message Schema
108
+ * Messages in the task conversation thread
109
+ */
110
+ export const TaskMessageSchema = z.object({
111
+ id: z.string(),
112
+ task_id: z.string(),
113
+ role: z.enum(['user', 'assistant', 'system']),
114
+ content: z.string(),
115
+ timestamp: z.string(),
116
+ metadata: z.any().optional()
117
+ })
118
+
119
+ // Export TypeScript types inferred from Zod schemas
120
+ export type Milestone = z.infer<typeof MilestoneSchema>
121
+ export type TaskItem = z.infer<typeof TaskItemSchema>
122
+ export type TaskProgress = z.infer<typeof TaskProgressSchema>
123
+ export type TaskExecution = z.infer<typeof TaskExecutionSchema>
124
+ export type TaskConfig = z.infer<typeof TaskConfigSchema>
125
+ export type TaskMetadata = z.infer<typeof TaskMetadataSchema>
126
+ export type Task = z.infer<typeof TaskSchema>
127
+ export type TaskMessage = z.infer<typeof TaskMessageSchema>
128
+
129
+ // Export status enums for convenience
130
+ export const TaskStatus = {
131
+ NOT_STARTED: 'not_started',
132
+ IN_PROGRESS: 'in_progress',
133
+ PAUSED: 'paused',
134
+ COMPLETED: 'completed',
135
+ FAILED: 'failed'
136
+ } as const
137
+
138
+ export const MilestoneStatus = {
139
+ NOT_STARTED: 'not_started',
140
+ IN_PROGRESS: 'in_progress',
141
+ COMPLETED: 'completed'
142
+ } as const
143
+
144
+ export const TaskItemStatus = {
145
+ NOT_STARTED: 'not_started',
146
+ IN_PROGRESS: 'in_progress',
147
+ COMPLETED: 'completed'
148
+ } as const
149
+
150
+ export const MessageRole = {
151
+ USER: 'user',
152
+ ASSISTANT: 'assistant',
153
+ SYSTEM: 'system'
154
+ } as const
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Server Factory Tests
3
+ */
4
+
5
+ import { createServer } from './server-factory.js'
6
+ import { FirebaseClient } from './client.js'
7
+
8
+ // Mock FirebaseClient
9
+ jest.mock('./client.js', () => ({
10
+ FirebaseClient: jest.fn().mockImplementation(() => ({
11
+ connect: jest.fn().mockResolvedValue(undefined),
12
+ disconnect: jest.fn().mockResolvedValue(undefined)
13
+ }))
14
+ }))
15
+
16
+ describe('Server Factory', () => {
17
+ describe('Parameter Validation', () => {
18
+ it('should create server instance with valid userId', async () => {
19
+ const server = await createServer('test-token', 'test-user-123')
20
+ expect(server).toBeDefined()
21
+ expect(server).toHaveProperty('connect')
22
+ expect(server).toHaveProperty('setRequestHandler')
23
+ })
24
+
25
+ it('should throw error if userId is empty', async () => {
26
+ await expect(createServer('test-token', '')).rejects.toThrow('userId is required')
27
+ })
28
+
29
+ it('should accept custom options', async () => {
30
+ const server = await createServer('test-token', 'test-user-123', {
31
+ name: 'custom-task-mcp',
32
+ version: '1.0.0'
33
+ })
34
+ expect(server).toBeDefined()
35
+ })
36
+ })
37
+
38
+ describe('Server Isolation', () => {
39
+ it('should create separate instances for different users', async () => {
40
+ const server1 = await createServer('test-token', 'user-1')
41
+ const server2 = await createServer('test-token', 'user-2')
42
+
43
+ expect(server1).not.toBe(server2)
44
+ })
45
+
46
+ it('should scope operations to userId', async () => {
47
+ const server = await createServer('test-token', 'test-user-123')
48
+
49
+ // Server should be isolated to this user
50
+ expect(server).toBeDefined()
51
+ })
52
+ })
53
+
54
+ describe('Options', () => {
55
+ it('should use default name if not provided', async () => {
56
+ const server = await createServer('test-token', 'test-user-123')
57
+ expect(server).toBeDefined()
58
+ })
59
+
60
+ it('should use custom name if provided', async () => {
61
+ const server = await createServer('test-token', 'test-user-123', { name: 'custom-name' })
62
+ expect(server).toBeDefined()
63
+ })
64
+
65
+ it('should use custom version if provided', async () => {
66
+ const server = await createServer('test-token', 'test-user-123', { version: '2.0.0' })
67
+ expect(server).toBeDefined()
68
+ })
69
+ })
70
+ })
@@ -0,0 +1,111 @@
1
+ /**
2
+ * MCP Server Factory
3
+ *
4
+ * Creates isolated MCP server instances for multi-tenant usage.
5
+ * Each server instance is scoped to a specific userId.
6
+ */
7
+
8
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js'
9
+ import {
10
+ CallToolRequestSchema,
11
+ ListToolsRequestSchema,
12
+ ErrorCode,
13
+ McpError
14
+ } from '@modelcontextprotocol/sdk/types.js'
15
+ import { FirebaseClient } from './client.js'
16
+ import { allTools, getToolHandler } from './tools/index.js'
17
+
18
+ /**
19
+ * Server configuration options
20
+ */
21
+ export interface ServerOptions {
22
+ name?: string
23
+ version?: string
24
+ }
25
+
26
+ /**
27
+ * Create an MCP server instance for a specific user
28
+ *
29
+ * This function is compatible with mcp-auth wrapper which calls it with:
30
+ * createServer(accessToken, userId)
31
+ *
32
+ * @param accessToken - Access token (not used by task-mcp, but required by mcp-auth)
33
+ * @param userId - User ID to scope operations to
34
+ * @param options - Optional server configuration
35
+ * @returns Configured MCP Server instance
36
+ */
37
+ export async function createServer(
38
+ accessToken: string,
39
+ userId: string,
40
+ options: ServerOptions = {}
41
+ ): Promise<Server> {
42
+ if (!userId) {
43
+ throw new Error('userId is required')
44
+ }
45
+
46
+ // Initialize Firebase client for this user
47
+ const client = new FirebaseClient({ userId })
48
+ await client.connect()
49
+
50
+ // Create MCP server
51
+ const server = new Server(
52
+ {
53
+ name: options.name || 'task-mcp',
54
+ version: options.version || '0.1.0'
55
+ },
56
+ {
57
+ capabilities: {
58
+ tools: {}
59
+ }
60
+ }
61
+ )
62
+
63
+ // Register list_tools handler
64
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
65
+ return {
66
+ tools: allTools
67
+ }
68
+ })
69
+
70
+ // Register call_tool handler
71
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
72
+ const { name, arguments: args } = request.params
73
+
74
+ try {
75
+ // Get handler for this tool
76
+ const handler = getToolHandler(name)
77
+
78
+ if (!handler) {
79
+ throw new McpError(
80
+ ErrorCode.MethodNotFound,
81
+ `Unknown tool: ${name}`
82
+ )
83
+ }
84
+
85
+ // Execute tool handler
86
+ const result = await handler(client, args as any || {})
87
+
88
+ return {
89
+ content: [
90
+ {
91
+ type: 'text',
92
+ text: result
93
+ }
94
+ ]
95
+ }
96
+ } catch (error) {
97
+ // Re-throw MCP errors as-is
98
+ if (error instanceof McpError) {
99
+ throw error
100
+ }
101
+
102
+ // Wrap other errors in MCP error
103
+ throw new McpError(
104
+ ErrorCode.InternalError,
105
+ `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`
106
+ )
107
+ }
108
+ })
109
+
110
+ return server
111
+ }
package/src/server.ts ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * MCP Server - Standalone Entry Point
5
+ *
6
+ * Starts an MCP server with stdio transport for a single user.
7
+ * This is the main entry point for running task-mcp as a standalone server.
8
+ */
9
+
10
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
11
+ import { createServer } from './server-factory.js'
12
+
13
+ /**
14
+ * Initialize and start the MCP server
15
+ */
16
+ async function main(): Promise<void> {
17
+ // Get userId from environment variable (set by mcp-auth wrapper)
18
+ const userId = process.env.TASK_MCP_USER_ID
19
+
20
+ if (!userId) {
21
+ console.error('Error: TASK_MCP_USER_ID environment variable is required')
22
+ console.error('This server should be run through mcp-auth wrapper')
23
+ process.exit(1)
24
+ }
25
+
26
+ try {
27
+ // Create server instance
28
+ // Note: accessToken is not used by task-mcp but required by mcp-auth signature
29
+ const server = await createServer('', userId, {
30
+ name: 'task-mcp',
31
+ version: '0.1.0'
32
+ })
33
+
34
+ // Create stdio transport
35
+ const transport = new StdioServerTransport()
36
+
37
+ // Connect server to transport
38
+ await server.connect(transport)
39
+
40
+ // Log startup (to stderr to not interfere with stdio protocol)
41
+ console.error(`task-mcp server started for user: ${userId}`)
42
+ } catch (error) {
43
+ console.error('Failed to start server:', error)
44
+ process.exit(1)
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Setup graceful shutdown handlers
50
+ */
51
+ function setupShutdownHandlers(): void {
52
+ const shutdown = async (signal: string) => {
53
+ console.error(`\nReceived ${signal}, shutting down gracefully...`)
54
+ process.exit(0)
55
+ }
56
+
57
+ process.on('SIGINT', () => shutdown('SIGINT'))
58
+ process.on('SIGTERM', () => shutdown('SIGTERM'))
59
+ }
60
+
61
+ // Setup shutdown handlers
62
+ setupShutdownHandlers()
63
+
64
+ // Start server
65
+ main().catch((error) => {
66
+ console.error('Fatal error:', error)
67
+ process.exit(1)
68
+ })
@@ -0,0 +1,116 @@
1
+ /**
2
+ * E2E Tests for TaskDatabaseService
3
+ *
4
+ * Tests with real Firestore emulator for integration testing.
5
+ *
6
+ * To run these tests:
7
+ * 1. Start Firestore emulator: firebase emulators:start --only firestore
8
+ * 2. Run tests: npm run test:e2e
9
+ */
10
+
11
+ import { describe, it, expect, beforeAll, afterAll } from '@jest/globals'
12
+ import { initializeApp, cert, deleteApp } from 'firebase-admin/app'
13
+ import { getFirestore } from 'firebase-admin/firestore'
14
+ import { TaskDatabaseService } from '../../src/services/task-database.service.js'
15
+ import type { Task } from '../../src/schemas/task.js'
16
+
17
+ describe('TaskDatabaseService E2E', () => {
18
+ let app: any
19
+
20
+ beforeAll(async () => {
21
+ // Initialize Firebase Admin with emulator
22
+ process.env.FIRESTORE_EMULATOR_HOST = 'localhost:8080'
23
+
24
+ app = initializeApp({
25
+ projectId: 'test-project'
26
+ }, 'task-mcp-e2e-test')
27
+
28
+ const db = getFirestore(app)
29
+ TaskDatabaseService.initialize(db)
30
+ })
31
+
32
+ afterAll(async () => {
33
+ if (app) {
34
+ await deleteApp(app)
35
+ }
36
+ })
37
+
38
+ describe('Full Task Lifecycle', () => {
39
+ it('should create, read, update, and delete a task', async () => {
40
+ const userId = 'test-user-' + Date.now()
41
+
42
+ // Create
43
+ const task = await TaskDatabaseService.createTask(
44
+ userId,
45
+ 'E2E Test Task',
46
+ 'Testing full lifecycle'
47
+ )
48
+
49
+ expect(task.id).toBeDefined()
50
+ expect(task.title).toBe('E2E Test Task')
51
+
52
+ // Read
53
+ const retrieved = await TaskDatabaseService.getTask(userId, task.id)
54
+ expect(retrieved).not.toBeNull()
55
+ expect(retrieved?.title).toBe('E2E Test Task')
56
+
57
+ // Update status
58
+ await TaskDatabaseService.updateTaskStatus(userId, task.id, 'in_progress')
59
+ const updated = await TaskDatabaseService.getTask(userId, task.id)
60
+ expect(updated?.status).toBe('in_progress')
61
+ expect(updated?.started_at).toBeDefined()
62
+
63
+ // Delete
64
+ await TaskDatabaseService.deleteTask(userId, task.id)
65
+ const deleted = await TaskDatabaseService.getTask(userId, task.id)
66
+ expect(deleted).toBeNull()
67
+ })
68
+
69
+ it('should handle task messages', async () => {
70
+ const userId = 'test-user-' + Date.now()
71
+
72
+ const task = await TaskDatabaseService.createTask(
73
+ userId,
74
+ 'Message Test',
75
+ 'Testing messages'
76
+ )
77
+
78
+ // Add messages
79
+ const msg1 = await TaskDatabaseService.addMessage(userId, task.id, 'user', 'Hello')
80
+ const msg2 = await TaskDatabaseService.addMessage(userId, task.id, 'assistant', 'Hi there')
81
+
82
+ expect(msg1).toBeDefined()
83
+ expect(msg2).toBeDefined()
84
+
85
+ // Get messages
86
+ const messages = await TaskDatabaseService.getMessages(userId, task.id)
87
+ expect(messages).toHaveLength(2)
88
+ expect(messages[0].role).toBe('user')
89
+ expect(messages[1].role).toBe('assistant')
90
+
91
+ // Cleanup
92
+ await TaskDatabaseService.deleteTask(userId, task.id)
93
+ })
94
+ })
95
+
96
+ describe('Query Operations', () => {
97
+ it('should filter tasks by status', async () => {
98
+ const userId = 'test-user-' + Date.now()
99
+
100
+ // Create tasks with different statuses
101
+ const task1 = await TaskDatabaseService.createTask(userId, 'Task 1', 'Active')
102
+ await TaskDatabaseService.updateTaskStatus(userId, task1.id, 'in_progress')
103
+
104
+ const task2 = await TaskDatabaseService.createTask(userId, 'Task 2', 'Pending')
105
+
106
+ // Query active tasks
107
+ const activeTasks = await TaskDatabaseService.getActiveTasks(userId)
108
+ expect(activeTasks.length).toBeGreaterThanOrEqual(1)
109
+ expect(activeTasks.some(t => t.id === task1.id)).toBe(true)
110
+
111
+ // Cleanup
112
+ await TaskDatabaseService.deleteTask(userId, task1.id)
113
+ await TaskDatabaseService.deleteTask(userId, task2.id)
114
+ })
115
+ })
116
+ })