interaqt 0.8.3 → 0.8.5
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/agent/.claude/agents/code-generation-handler.md +31 -17
- package/agent/.claude/agents/error-check-handler.md +17 -0
- package/agent/.claude/agents/frontend-generation-handler.md +51 -1
- package/agent/.claude/agents/implement-integration-handler.md +278 -25
- package/agent/.claude/agents/requirements-analysis-handler.md +90 -0
- package/agent/.claude/settings.local.json +3 -1
- package/package.json +1 -1
- package/agent/agentspace/prompt/integration_sub_agent_refactor.md +0 -19
- package/agent/agentspace/prompt/requirement_analysis_refactor.md +0 -34
|
@@ -162,27 +162,41 @@ git commit -m "feat: Task 3.1.2 - Complete entity and relation implementation"
|
|
|
162
162
|
- [ ] Ensure all payloads match the documented fields
|
|
163
163
|
- [ ] **🔴 CRITICAL: For query interactions (action: GetAction):**
|
|
164
164
|
- **MUST declare `data` field** - specify the Entity or Relation to query
|
|
165
|
-
- **SHOULD declare `
|
|
166
|
-
-
|
|
165
|
+
- **SHOULD declare `dataPolicy` field** if there are predefined filters/fields or access restrictions
|
|
166
|
+
- **⚠️ IMPORTANT: Data Access Scope vs Business Rules**
|
|
167
|
+
- If `dataConstraints` express **data access scope restrictions** (e.g., "can only view own entities", "can only view specific fields"), use `dataPolicy` NOT `condition`
|
|
168
|
+
- `dataPolicy` controls what data can be accessed AFTER the operation is permitted
|
|
169
|
+
- `condition` controls WHETHER the operation can execute (permissions/business rules)
|
|
170
|
+
- Example of data policy: Restricting visible fields, filtering by ownership
|
|
171
|
+
- Example with dynamic data policy (user-based filtering):
|
|
172
|
+
```typescript
|
|
173
|
+
const ViewMyOrders = Interaction.create({
|
|
174
|
+
name: 'ViewMyOrders',
|
|
175
|
+
action: GetAction,
|
|
176
|
+
data: Order,
|
|
177
|
+
dataPolicy: DataPolicy.create({
|
|
178
|
+
match: function(this: Controller, event: any) {
|
|
179
|
+
// Only show user's own orders
|
|
180
|
+
return MatchExp.atom({key: 'owner.id', value:['=', event.user.id]})
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
```
|
|
185
|
+
- Example with combined data policy (filtering + field restrictions + pagination):
|
|
167
186
|
```typescript
|
|
168
187
|
const ViewMyFollowers = Interaction.create({
|
|
169
|
-
name: '
|
|
188
|
+
name: 'ViewMyFollowers',
|
|
170
189
|
action: GetAction,
|
|
171
190
|
data: User, // REQUIRED: specify what to query
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
value: function(this:Controller, event:any) {
|
|
182
|
-
return MatchExp.atom({key: 'follow.id', value:['=', event.user.id]})
|
|
183
|
-
}
|
|
184
|
-
})
|
|
185
|
-
]
|
|
191
|
+
dataPolicy: DataPolicy.create({
|
|
192
|
+
// Dynamic filter: only users who follow the current user
|
|
193
|
+
match: function(this: Controller, event: any) {
|
|
194
|
+
return MatchExp.atom({key: 'following.id', value:['=', event.user.id]})
|
|
195
|
+
},
|
|
196
|
+
// Field restrictions: only expose specific fields
|
|
197
|
+
attributeQuery: ['id', 'name', 'email'],
|
|
198
|
+
// Default pagination
|
|
199
|
+
modifier: { limit: 20, orderBy: { name: 'asc' } }
|
|
186
200
|
})
|
|
187
201
|
})
|
|
188
202
|
```
|
|
@@ -1015,6 +1015,23 @@ const GetUserDonations = Interaction.create({
|
|
|
1015
1015
|
]
|
|
1016
1016
|
})
|
|
1017
1017
|
})
|
|
1018
|
+
|
|
1019
|
+
// Query interaction with dataPolicy for access control
|
|
1020
|
+
const GetMyDonations = Interaction.create({
|
|
1021
|
+
name: 'GetMyDonations',
|
|
1022
|
+
action: GetAction,
|
|
1023
|
+
data: Donation, // ✅ Entity reference
|
|
1024
|
+
dataPolicy: DataPolicy.create({
|
|
1025
|
+
// ✅ Dynamic filter: users can only see their own donations
|
|
1026
|
+
match: function(this: Controller, event: any) {
|
|
1027
|
+
return MatchExp.atom({key: 'donor.id', value: ['=', event.user.id]})
|
|
1028
|
+
},
|
|
1029
|
+
// ✅ Field restrictions: limit exposed fields
|
|
1030
|
+
attributeQuery: ['id', 'amount', 'createdAt', 'status'],
|
|
1031
|
+
// ✅ Default pagination
|
|
1032
|
+
modifier: { limit: 20, orderBy: { createdAt: 'desc' } }
|
|
1033
|
+
})
|
|
1034
|
+
})
|
|
1018
1035
|
```
|
|
1019
1036
|
|
|
1020
1037
|
### Pattern 5: Test Error Checking
|
|
@@ -205,6 +205,7 @@ npm run generate-frontend-api
|
|
|
205
205
|
- **Framework**: React + TypeScript
|
|
206
206
|
- **Build Tool**: Vite
|
|
207
207
|
- **Styling**: Tailwind CSS
|
|
208
|
+
- **Routing**: React Router
|
|
208
209
|
- **State Management**: React Context (for API client and global state)
|
|
209
210
|
|
|
210
211
|
**Project Structure:**
|
|
@@ -231,7 +232,40 @@ npm run generate-frontend-api
|
|
|
231
232
|
}
|
|
232
233
|
```
|
|
233
234
|
|
|
234
|
-
2. **
|
|
235
|
+
2. **Query Interactions Always Return Arrays:**
|
|
236
|
+
- Backend query-type interactions always return arrays in `response.data`
|
|
237
|
+
- To query a specific entity/relation, use the `match` field in the query options (2nd parameter)
|
|
238
|
+
- Do NOT put match criteria in the payload (1st parameter)
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
// ✅ Correct: Use match in query options
|
|
242
|
+
const response = await apiClient.ViewVideoGenerationStatus(
|
|
243
|
+
{ videoGenerationRequestId: videoId }, // payload
|
|
244
|
+
{
|
|
245
|
+
attributeQuery: ['id', 'status', 'videoUrl'],
|
|
246
|
+
match: {
|
|
247
|
+
key: 'id',
|
|
248
|
+
value: ['=', videoId] // Match condition here
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
const item = response.data[0]; // Extract first item from array
|
|
253
|
+
|
|
254
|
+
// ❌ Wrong: Don't rely on payload for filtering
|
|
255
|
+
const response = await apiClient.ViewVideoGenerationStatus(
|
|
256
|
+
{ videoGenerationRequestId: videoId } // This won't filter results
|
|
257
|
+
);
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
3. **Handling Asynchronous External System Tasks:**
|
|
261
|
+
- For asynchronous tasks that call external systems, backend typically does NOT implement polling unless explicitly specified in requirements
|
|
262
|
+
- Backend usually provides a separate API endpoint to trigger status updates
|
|
263
|
+
- Frontend can call this API to trigger backend status updates
|
|
264
|
+
- Frontend implementation options:
|
|
265
|
+
- **Manual trigger**: Add a button in the component for users to manually trigger the status update API
|
|
266
|
+
- **Automatic polling**: Implement polling in the component (on mount) until the task reaches a completion state
|
|
267
|
+
|
|
268
|
+
4. **Reference Existing Components for Patterns:**
|
|
235
269
|
- Look at `frontend/src/components/*.tsx` files
|
|
236
270
|
- Follow the same patterns for API client usage
|
|
237
271
|
- Check how error handling is implemented
|
|
@@ -274,6 +308,22 @@ npm run generate-frontend-api
|
|
|
274
308
|
- Test on different screen sizes
|
|
275
309
|
- Ensure mobile-friendly layouts
|
|
276
310
|
|
|
311
|
+
7. **Modular Navigation:**
|
|
312
|
+
- **Module Entry Points**: Create a main navigation menu listing all modules
|
|
313
|
+
- **Route Organization**: Structure routes with module prefixes (e.g., `/donate/*`, `/livestream/*`)
|
|
314
|
+
- **Active Module Indicator**: Highlight current module in navigation menu
|
|
315
|
+
- **Breadcrumb Navigation**: Show module name → page hierarchy
|
|
316
|
+
- **Module Switching**: Enable seamless navigation between modules
|
|
317
|
+
- **Example Structure**:
|
|
318
|
+
```typescript
|
|
319
|
+
// Main App Router
|
|
320
|
+
<Routes>
|
|
321
|
+
<Route path="/" element={<ModuleSelector />} />
|
|
322
|
+
<Route path="/donate/*" element={<DonateModule />} />
|
|
323
|
+
<Route path="/livestream/*" element={<LivestreamModule />} />
|
|
324
|
+
</Routes>
|
|
325
|
+
```
|
|
326
|
+
|
|
277
327
|
**🔴 CRITICAL: Completeness Check**
|
|
278
328
|
|
|
279
329
|
Before marking Step 4 complete, verify:
|
|
@@ -45,6 +45,27 @@ This applies to BOTH async APIs (with task IDs) and sync APIs (immediate results
|
|
|
45
45
|
- All integration documentation files MUST be prefixed with current module name from `.currentmodule`
|
|
46
46
|
- Format: `docs/{module}.{integration-name}.integration-design.md`
|
|
47
47
|
|
|
48
|
+
**🔴 CRITICAL PRINCIPLE: Status Polling Strategy**
|
|
49
|
+
|
|
50
|
+
**Default Approach: Frontend Polling with Manual Query API**
|
|
51
|
+
|
|
52
|
+
Backend polling consumes significant server resources. Follow this priority order:
|
|
53
|
+
|
|
54
|
+
1. **Default (ALWAYS implement)**: Provide manual query API for frontend
|
|
55
|
+
- Create API endpoint to query external status
|
|
56
|
+
- Frontend can poll this API at its own pace
|
|
57
|
+
- Even if polling is needed, frontend handles it unless explicitly stated otherwise
|
|
58
|
+
|
|
59
|
+
2. **Backend Polling (ONLY if explicitly required)**: Implement server-side polling
|
|
60
|
+
- ONLY implement if user explicitly requests "backend polling" in requirements
|
|
61
|
+
- Use with caution due to resource consumption
|
|
62
|
+
- Example: volcjmeng integration (only because explicitly required)
|
|
63
|
+
|
|
64
|
+
3. **Webhook (ONLY if both conditions met)**: Implement webhook endpoint
|
|
65
|
+
- ONLY if external service supports webhook registration
|
|
66
|
+
- AND user can register webhook themselves
|
|
67
|
+
- Requires exposing public endpoint for external callbacks
|
|
68
|
+
|
|
48
69
|
# Core Concepts
|
|
49
70
|
|
|
50
71
|
## Interaqt Framework
|
|
@@ -281,6 +302,7 @@ interface IIntegration {
|
|
|
281
302
|
setup?(controller: Controller): Promise<any> // Setup phase with controller access
|
|
282
303
|
createSideEffects(): RecordMutationSideEffect[] // Listen to data mutations and create events
|
|
283
304
|
createAPIs?(): APIs // Expose custom APIs (e.g., webhook endpoints)
|
|
305
|
+
createMiddlewares?(): MiddlewareHandler[] // Optional: Create HTTP middleware for request processing
|
|
284
306
|
}
|
|
285
307
|
```
|
|
286
308
|
|
|
@@ -292,6 +314,63 @@ interface IIntegration {
|
|
|
292
314
|
1. Webhook endpoints to receive external system callbacks
|
|
293
315
|
2. Manual trigger/query APIs for status checks and retries
|
|
294
316
|
3. Frontend support APIs (e.g., pre-signed URLs for uploads)
|
|
317
|
+
- **createMiddlewares()**: Optional method to create HTTP middleware for request processing (e.g., authentication, authorization, request validation)
|
|
318
|
+
|
|
319
|
+
**🔴 CRITICAL: Separation Between API Layer and Integration Layer**
|
|
320
|
+
|
|
321
|
+
**API File Responsibilities** (`integrations/{name}/externalApi.ts` or `integrations/{name}/volcApi.ts`):
|
|
322
|
+
- Construct HTTP requests according to external API documentation
|
|
323
|
+
- Call external APIs and return raw responses
|
|
324
|
+
- **NO data transformation** - return data as-is from external system
|
|
325
|
+
- Define **strict TypeScript types** based on official API documentation:
|
|
326
|
+
- Input parameter types (exactly matching API requirements)
|
|
327
|
+
- Output response types (exactly matching API responses)
|
|
328
|
+
- Handle only HTTP-level errors (network failures, status codes)
|
|
329
|
+
|
|
330
|
+
**Integration File Responsibilities** (`integrations/{name}/index.ts`):
|
|
331
|
+
- Call API file methods to interact with external system
|
|
332
|
+
- **Transform external API responses** into internal event format
|
|
333
|
+
- Map external data structures to business event entity fields
|
|
334
|
+
- Create integration events following unified sequence
|
|
335
|
+
- Handle business-level error scenarios
|
|
336
|
+
|
|
337
|
+
**Example:**
|
|
338
|
+
```typescript
|
|
339
|
+
// ❌ WRONG: API file transforms data
|
|
340
|
+
// integrations/tts/externalApi.ts
|
|
341
|
+
export async function callTTSApi(params: TTSParams): Promise<{ audioUrl: string }> {
|
|
342
|
+
const response = await fetch(...)
|
|
343
|
+
const data = await response.json()
|
|
344
|
+
return { audioUrl: data.result.url } // ❌ Transformation in API file
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ✅ CORRECT: API file returns raw response
|
|
348
|
+
// integrations/tts/externalApi.ts
|
|
349
|
+
export type TTSApiResponse = {
|
|
350
|
+
taskId: string
|
|
351
|
+
status: string
|
|
352
|
+
result?: {
|
|
353
|
+
url: string
|
|
354
|
+
duration: number
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export async function callTTSApi(params: TTSParams): Promise<TTSApiResponse> {
|
|
359
|
+
const response = await fetch(...)
|
|
360
|
+
return await response.json() // ✅ Raw response with strict types
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ✅ CORRECT: Integration file transforms data
|
|
364
|
+
// integrations/tts/index.ts
|
|
365
|
+
const apiResponse = await callTTSApi(requestParams)
|
|
366
|
+
|
|
367
|
+
// Transform to internal event format
|
|
368
|
+
await this.createIntegrationEvent(controller, apiCall.id, apiResponse.taskId, 'initialized', {
|
|
369
|
+
taskId: apiResponse.taskId, // Map to event fields
|
|
370
|
+
status: apiResponse.status,
|
|
371
|
+
audioUrl: apiResponse.result?.url // Extract what business needs
|
|
372
|
+
}, null)
|
|
373
|
+
```
|
|
295
374
|
|
|
296
375
|
# Task 4: Integration Implementation
|
|
297
376
|
|
|
@@ -810,14 +889,26 @@ mkdir -p integrations/{integration-name}
|
|
|
810
889
|
|
|
811
890
|
### 4.4.2 Create External API Wrapper (if no SDK)
|
|
812
891
|
|
|
892
|
+
**🔴 CRITICAL: API File Responsibilities**
|
|
893
|
+
|
|
894
|
+
This file MUST:
|
|
895
|
+
- Return raw API responses without transformation
|
|
896
|
+
- Define strict TypeScript types matching official API documentation
|
|
897
|
+
- Handle only HTTP-level errors
|
|
898
|
+
|
|
899
|
+
This file MUST NOT:
|
|
900
|
+
- Transform data to internal event format (that's integration file's job)
|
|
901
|
+
- Create any integration events
|
|
902
|
+
- Handle business logic
|
|
903
|
+
|
|
813
904
|
Create `integrations/{integration-name}/externalApi.ts`:
|
|
814
905
|
|
|
815
906
|
```typescript
|
|
816
907
|
/**
|
|
817
908
|
* External API wrapper for {System Name}
|
|
818
909
|
*
|
|
819
|
-
* This
|
|
820
|
-
*
|
|
910
|
+
* CRITICAL: This file returns raw API responses with strict types.
|
|
911
|
+
* NO data transformation - integration file handles that.
|
|
821
912
|
*/
|
|
822
913
|
|
|
823
914
|
export type ExternalApiConfig = {
|
|
@@ -825,16 +916,37 @@ export type ExternalApiConfig = {
|
|
|
825
916
|
baseUrl?: string
|
|
826
917
|
}
|
|
827
918
|
|
|
919
|
+
/**
|
|
920
|
+
* Request parameters - MUST match external API documentation exactly
|
|
921
|
+
*/
|
|
828
922
|
export type RequestParams = {
|
|
829
|
-
// Define
|
|
923
|
+
// Define according to official API docs
|
|
924
|
+
param1: string
|
|
925
|
+
param2: number
|
|
926
|
+
// ... more parameters
|
|
830
927
|
}
|
|
831
928
|
|
|
929
|
+
/**
|
|
930
|
+
* Response data - MUST match external API response exactly
|
|
931
|
+
*/
|
|
832
932
|
export type ResponseData = {
|
|
833
|
-
// Define
|
|
933
|
+
// Define according to official API docs
|
|
934
|
+
taskId: string
|
|
935
|
+
status: 'pending' | 'processing' | 'completed' | 'failed'
|
|
936
|
+
result?: {
|
|
937
|
+
// Define result structure from API docs
|
|
938
|
+
data: any
|
|
939
|
+
}
|
|
940
|
+
error?: {
|
|
941
|
+
code: string
|
|
942
|
+
message: string
|
|
943
|
+
}
|
|
834
944
|
}
|
|
835
945
|
|
|
836
946
|
/**
|
|
837
947
|
* Call external API
|
|
948
|
+
*
|
|
949
|
+
* Returns raw API response - NO transformation
|
|
838
950
|
*/
|
|
839
951
|
export async function callExternalApi(
|
|
840
952
|
params: RequestParams,
|
|
@@ -861,7 +973,8 @@ export async function callExternalApi(
|
|
|
861
973
|
throw new Error(`API call failed: ${response.statusText}`)
|
|
862
974
|
}
|
|
863
975
|
|
|
864
|
-
|
|
976
|
+
// Return raw response - integration file will transform it
|
|
977
|
+
const data: ResponseData = await response.json()
|
|
865
978
|
return data
|
|
866
979
|
} catch (error: any) {
|
|
867
980
|
console.error('[ExternalAPI] Call failed:', error.message)
|
|
@@ -871,12 +984,26 @@ export async function callExternalApi(
|
|
|
871
984
|
|
|
872
985
|
/**
|
|
873
986
|
* Query external status
|
|
987
|
+
*
|
|
988
|
+
* Returns raw status response - NO transformation
|
|
874
989
|
*/
|
|
875
990
|
export async function queryExternalStatus(
|
|
876
991
|
taskId: string,
|
|
877
992
|
config?: ExternalApiConfig
|
|
878
993
|
): Promise<ResponseData> {
|
|
879
|
-
// Similar implementation
|
|
994
|
+
// Similar implementation - return raw response
|
|
995
|
+
const apiKey = config?.apiKey
|
|
996
|
+
const baseUrl = config?.baseUrl
|
|
997
|
+
|
|
998
|
+
const response = await fetch(`${baseUrl}/v1/status/${taskId}`, {
|
|
999
|
+
method: 'GET',
|
|
1000
|
+
headers: {
|
|
1001
|
+
'Authorization': `Bearer ${apiKey}`
|
|
1002
|
+
}
|
|
1003
|
+
})
|
|
1004
|
+
|
|
1005
|
+
const data: ResponseData = await response.json()
|
|
1006
|
+
return data // Raw response
|
|
880
1007
|
}
|
|
881
1008
|
```
|
|
882
1009
|
|
|
@@ -891,8 +1018,9 @@ Create `integrations/{integration-name}/index.ts`:
|
|
|
891
1018
|
* Purpose: {Brief description}
|
|
892
1019
|
*
|
|
893
1020
|
* Features:
|
|
894
|
-
* - Listen to
|
|
895
|
-
* -
|
|
1021
|
+
* - Listen to APICall entity creation and trigger external API calls
|
|
1022
|
+
* - Transform external API responses to internal event format
|
|
1023
|
+
* - Create integration events following unified sequence
|
|
896
1024
|
* - Provide manual status refresh API
|
|
897
1025
|
* - Factory function pattern for configuration flexibility
|
|
898
1026
|
*/
|
|
@@ -1086,31 +1214,42 @@ export function create{IntegrationName}Integration(config: {IntegrationName}Conf
|
|
|
1086
1214
|
requestParams
|
|
1087
1215
|
})
|
|
1088
1216
|
|
|
1089
|
-
// Step 2: Call external API
|
|
1217
|
+
// Step 2: Call external API (returns raw response with strict types)
|
|
1090
1218
|
try {
|
|
1091
|
-
const
|
|
1219
|
+
const apiResponse = await callExternalApi(requestParams)
|
|
1220
|
+
// apiResponse has type ResponseData from API file
|
|
1092
1221
|
|
|
1222
|
+
// Step 3: Transform external response to internal event format
|
|
1093
1223
|
// Determine externalId: use API's task ID or generate one
|
|
1094
|
-
const externalId =
|
|
1224
|
+
const externalId = apiResponse.taskId
|
|
1095
1225
|
|
|
1096
1226
|
console.log('[{IntegrationName}] External API called', {
|
|
1097
1227
|
apiCallId: apiCall.id,
|
|
1098
1228
|
externalId,
|
|
1099
|
-
hasTaskId: !!(
|
|
1229
|
+
hasTaskId: !!(apiResponse.taskId)
|
|
1100
1230
|
})
|
|
1101
1231
|
|
|
1232
|
+
// Step 4: Transform and create 'initialized' event
|
|
1233
|
+
// Map external fields to internal event format
|
|
1234
|
+
const eventData = {
|
|
1235
|
+
taskId: apiResponse.taskId,
|
|
1236
|
+
status: apiResponse.status,
|
|
1237
|
+
// Map other fields as needed for business logic
|
|
1238
|
+
rawResponse: apiResponse // Keep raw response if needed
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1102
1241
|
// ALWAYS create 'initialized' event with both entityId and externalId
|
|
1103
1242
|
await self.createIntegrationEvent(
|
|
1104
1243
|
this,
|
|
1105
1244
|
apiCall.id, // entityId - APICall's id
|
|
1106
1245
|
externalId, // externalId - task ID or generated UUID
|
|
1107
1246
|
'initialized',
|
|
1108
|
-
|
|
1247
|
+
eventData, // Transformed data, not raw response
|
|
1109
1248
|
null
|
|
1110
1249
|
)
|
|
1111
1250
|
|
|
1112
1251
|
// For sync APIs (no task ID): immediately create processing and completed events
|
|
1113
|
-
if (!
|
|
1252
|
+
if (!apiResponse.taskId) {
|
|
1114
1253
|
// Immediately create processing event
|
|
1115
1254
|
await self.createIntegrationEvent(
|
|
1116
1255
|
this,
|
|
@@ -1121,19 +1260,26 @@ export function create{IntegrationName}Integration(config: {IntegrationName}Conf
|
|
|
1121
1260
|
null
|
|
1122
1261
|
)
|
|
1123
1262
|
|
|
1124
|
-
//
|
|
1263
|
+
// Transform completion data
|
|
1264
|
+
const completedData = {
|
|
1265
|
+
status: 'completed',
|
|
1266
|
+
result: apiResponse.result,
|
|
1267
|
+
// Map other completion fields
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// Immediately create completed event with transformed data
|
|
1125
1271
|
await self.createIntegrationEvent(
|
|
1126
1272
|
this,
|
|
1127
1273
|
null,
|
|
1128
1274
|
externalId,
|
|
1129
1275
|
'completed',
|
|
1130
|
-
|
|
1276
|
+
completedData, // Transformed data
|
|
1131
1277
|
null
|
|
1132
1278
|
)
|
|
1133
1279
|
|
|
1134
1280
|
console.log('[{IntegrationName}] Sync API completed with unified event sequence')
|
|
1135
1281
|
}
|
|
1136
|
-
// For async APIs:
|
|
1282
|
+
// For async APIs: status will come later via webhook or polling
|
|
1137
1283
|
|
|
1138
1284
|
} catch (error: any) {
|
|
1139
1285
|
console.error('[{IntegrationName}] External API call failed', {
|
|
@@ -1191,10 +1337,11 @@ export function create{IntegrationName}Integration(config: {IntegrationName}Conf
|
|
|
1191
1337
|
apiCallId: string
|
|
1192
1338
|
}) {
|
|
1193
1339
|
try {
|
|
1194
|
-
await self.checkAndUpdateStatus(params.apiCallId)
|
|
1340
|
+
const apiResponse = await self.checkAndUpdateStatus(params.apiCallId)
|
|
1195
1341
|
return {
|
|
1196
1342
|
success: true,
|
|
1197
|
-
message: 'Status check triggered, integration event created'
|
|
1343
|
+
message: 'Status check triggered, integration event created',
|
|
1344
|
+
response: apiResponse
|
|
1198
1345
|
}
|
|
1199
1346
|
} catch (error: any) {
|
|
1200
1347
|
console.error('[{IntegrationName}] Failed to query status', {
|
|
@@ -1322,25 +1469,36 @@ export function create{IntegrationName}Integration(config: {IntegrationName}Conf
|
|
|
1322
1469
|
throw new Error(`No external ID found for APICall: ${apiCallId}`)
|
|
1323
1470
|
}
|
|
1324
1471
|
|
|
1325
|
-
// Query external system
|
|
1326
|
-
const
|
|
1472
|
+
// Query external system (returns raw response)
|
|
1473
|
+
const apiResponse = await queryExternalStatus(externalId)
|
|
1327
1474
|
|
|
1328
1475
|
console.log('[{IntegrationName}] Status checked', {
|
|
1329
1476
|
apiCallId,
|
|
1330
1477
|
externalId,
|
|
1331
|
-
status:
|
|
1478
|
+
status: apiResponse.status
|
|
1332
1479
|
})
|
|
1333
1480
|
|
|
1481
|
+
// Transform external response to internal event format
|
|
1482
|
+
const eventType = apiResponse.status // Map external status to event type
|
|
1483
|
+
const eventData = apiResponse.result ? {
|
|
1484
|
+
status: apiResponse.status,
|
|
1485
|
+
result: apiResponse.result,
|
|
1486
|
+
// Map other fields as needed
|
|
1487
|
+
} : null
|
|
1488
|
+
const errorMessage = apiResponse.error?.message || null
|
|
1489
|
+
|
|
1334
1490
|
// Create integration event based on status
|
|
1335
1491
|
// entityId is null because APICall already exists (not 'initialized' event)
|
|
1336
1492
|
await this.createIntegrationEvent(
|
|
1337
1493
|
this.controller,
|
|
1338
1494
|
null, // entityId - not needed for status updates
|
|
1339
1495
|
externalId, // externalId - to match with existing APICall
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1496
|
+
eventType, // eventType - 'processing' | 'completed' | 'failed'
|
|
1497
|
+
eventData, // Transformed data, not raw response
|
|
1498
|
+
errorMessage // Extracted error message
|
|
1343
1499
|
)
|
|
1500
|
+
|
|
1501
|
+
return apiResponse
|
|
1344
1502
|
}
|
|
1345
1503
|
}
|
|
1346
1504
|
}
|
|
@@ -1666,6 +1824,101 @@ createAPIs() {
|
|
|
1666
1824
|
}
|
|
1667
1825
|
```
|
|
1668
1826
|
|
|
1827
|
+
## Integration Middleware
|
|
1828
|
+
|
|
1829
|
+
**🔴 CRITICAL: Middleware for Request Processing**
|
|
1830
|
+
|
|
1831
|
+
Integrations can provide HTTP middleware to handle cross-cutting concerns like authentication, authorization, request validation, logging, etc.
|
|
1832
|
+
|
|
1833
|
+
**When to use middleware:**
|
|
1834
|
+
- Authentication and authorization (e.g., JWT verification)
|
|
1835
|
+
- Request/response transformation
|
|
1836
|
+
- Logging and monitoring
|
|
1837
|
+
- Rate limiting
|
|
1838
|
+
- CORS handling
|
|
1839
|
+
- Custom header processing
|
|
1840
|
+
|
|
1841
|
+
**Middleware execution:**
|
|
1842
|
+
- Middleware runs BEFORE API handlers
|
|
1843
|
+
- Can access and modify request context
|
|
1844
|
+
- Can short-circuit request processing
|
|
1845
|
+
- Can inject context data for API handlers
|
|
1846
|
+
|
|
1847
|
+
**Example: Authentication Middleware**
|
|
1848
|
+
|
|
1849
|
+
```typescript
|
|
1850
|
+
export function createAuthIntegration(config: AuthIntegrationConfig) {
|
|
1851
|
+
return class AuthIntegration implements IIntegration {
|
|
1852
|
+
createMiddlewares(): MiddlewareHandler[] {
|
|
1853
|
+
return [
|
|
1854
|
+
async (c, next) => {
|
|
1855
|
+
// Extract token from multiple sources
|
|
1856
|
+
let token: string | undefined
|
|
1857
|
+
const authToken = c.req.query('authToken')
|
|
1858
|
+
const authHeader = c.req.header('authorization')
|
|
1859
|
+
const cookieHeader = c.req.header('cookie')
|
|
1860
|
+
|
|
1861
|
+
if (authToken) {
|
|
1862
|
+
token = authToken
|
|
1863
|
+
} else if (authHeader?.startsWith('Bearer ')) {
|
|
1864
|
+
token = authHeader.substring(7)
|
|
1865
|
+
} else if (cookieHeader) {
|
|
1866
|
+
const allCookies: Record<string, string> = {}
|
|
1867
|
+
cookieHeader.split(';').forEach((cookie) => {
|
|
1868
|
+
const [name, value] = cookie.trim().split('=')
|
|
1869
|
+
if (name && value) {
|
|
1870
|
+
allCookies[name] = decodeURIComponent(value)
|
|
1871
|
+
}
|
|
1872
|
+
})
|
|
1873
|
+
token = allCookies.token
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
// Verify token and inject userId into context
|
|
1877
|
+
if (token) {
|
|
1878
|
+
try {
|
|
1879
|
+
const decoded = await verify(token, jwtSecret) as any
|
|
1880
|
+
c.set('userId', decoded.userId)
|
|
1881
|
+
} catch (err) {
|
|
1882
|
+
console.log('Invalid token', err)
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
await next()
|
|
1887
|
+
}
|
|
1888
|
+
]
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
createAPIs() {
|
|
1892
|
+
return {
|
|
1893
|
+
login: createAPI(
|
|
1894
|
+
async function(context, params: { identifier: string; password: string }) {
|
|
1895
|
+
// Authenticate user
|
|
1896
|
+
// Generate JWT token
|
|
1897
|
+
// Return token to client
|
|
1898
|
+
},
|
|
1899
|
+
{ allowAnonymous: true }
|
|
1900
|
+
)
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
```
|
|
1906
|
+
|
|
1907
|
+
**Key principles:**
|
|
1908
|
+
- **Middleware is optional**: Only use when needed for cross-cutting concerns
|
|
1909
|
+
- **Order matters**: Middleware executes in the order returned from createMiddlewares()
|
|
1910
|
+
- **Context injection**: Use `c.set()` to inject data into context for API handlers
|
|
1911
|
+
- **Non-blocking**: Always call `await next()` to continue the middleware chain
|
|
1912
|
+
- **Error handling**: Middleware can catch and handle errors before they reach API handlers
|
|
1913
|
+
|
|
1914
|
+
**Common use cases:**
|
|
1915
|
+
1. **Authentication** (`integrations/auth/index.ts`): JWT verification, session management
|
|
1916
|
+
2. **Authorization**: Role-based access control, permission checks
|
|
1917
|
+
3. **Request validation**: Schema validation, sanitization
|
|
1918
|
+
4. **Logging**: Request/response logging, performance monitoring
|
|
1919
|
+
5. **Rate limiting**: Throttle requests per user/IP
|
|
1920
|
+
6. **CORS**: Handle cross-origin requests
|
|
1921
|
+
|
|
1669
1922
|
## Example: Stripe Payment Integration
|
|
1670
1923
|
|
|
1671
1924
|
```typescript
|
|
@@ -553,6 +553,80 @@ git commit -m "feat: Task 1.2 - Complete functional requirements analysis"
|
|
|
553
553
|
|
|
554
554
|
**External side-effects requiring third-party APIs must be identified separately.**
|
|
555
555
|
|
|
556
|
+
### Special Case: Authentication Integration
|
|
557
|
+
|
|
558
|
+
**⚠️ CRITICAL: Authentication for Basic Module**
|
|
559
|
+
|
|
560
|
+
If the current module is "basic", authentication (login/registration) requirements must be handled as an integration named **"auth"**.
|
|
561
|
+
|
|
562
|
+
**Authentication vs Business Logic:**
|
|
563
|
+
1. **Authentication (Integration)**:
|
|
564
|
+
- User login with credentials
|
|
565
|
+
- User registration/signup
|
|
566
|
+
- Password reset/recovery
|
|
567
|
+
- OAuth/social login
|
|
568
|
+
- These are NOT part of business logic - treat as integration "auth"
|
|
569
|
+
|
|
570
|
+
2. **User Management (Business Logic)**:
|
|
571
|
+
- Administrator creates user accounts
|
|
572
|
+
- Administrator updates user information
|
|
573
|
+
- Administrator deactivates users
|
|
574
|
+
- These ARE business data operations - design as regular interactions
|
|
575
|
+
|
|
576
|
+
**Handling Authentication Requirements:**
|
|
577
|
+
|
|
578
|
+
**Case 1: User explicitly described login/registration requirements**
|
|
579
|
+
- Extract ALL authentication-related requirements into integration "auth"
|
|
580
|
+
- Do NOT create interactions for login/registration in Task 1.5
|
|
581
|
+
- Document the specific authentication flow in integration.json
|
|
582
|
+
|
|
583
|
+
**Case 2: User did NOT mention login/registration**
|
|
584
|
+
- For "basic" module, assume authentication is needed
|
|
585
|
+
- Design a simple password-based authentication integration
|
|
586
|
+
- Use default specification (see example below)
|
|
587
|
+
|
|
588
|
+
**Default Password Authentication Integration:**
|
|
589
|
+
```json
|
|
590
|
+
{
|
|
591
|
+
"id": "INT_AUTH",
|
|
592
|
+
"name": "auth",
|
|
593
|
+
"type": "stateful-system",
|
|
594
|
+
"type_explanation": "Authentication system maintains session state and user credentials. Requires bidirectional communication for login, registration, and session management.",
|
|
595
|
+
"external_system": "Authentication Service",
|
|
596
|
+
"purpose": "Handle user authentication and registration for the system",
|
|
597
|
+
"related_requirements": ["User login", "User registration"],
|
|
598
|
+
"flow_description": "For registration: User provides username/email and password in current system. System sends registration request (username, password) to authentication service. Authentication service validates uniqueness, hashes password, creates auth record, and returns user credentials. Current system receives response and creates User entity with returned user ID. For login: User provides credentials in current system. System sends login request to authentication service. Authentication service validates credentials and returns session token. Current system stores session and grants access.",
|
|
599
|
+
"user_interactions": {
|
|
600
|
+
"in_current_system": [
|
|
601
|
+
"User enters username/email and password for registration",
|
|
602
|
+
"User enters credentials for login",
|
|
603
|
+
"User views login success/failure messages"
|
|
604
|
+
],
|
|
605
|
+
"in_external_system": [
|
|
606
|
+
"Authentication service validates credential format",
|
|
607
|
+
"Authentication service checks username/email uniqueness",
|
|
608
|
+
"Authentication service hashes password securely",
|
|
609
|
+
"Authentication service generates session tokens"
|
|
610
|
+
]
|
|
611
|
+
},
|
|
612
|
+
"current_system_data": [
|
|
613
|
+
{
|
|
614
|
+
"entity": "User",
|
|
615
|
+
"properties": ["id", "username", "email"],
|
|
616
|
+
"usage": "Created after successful registration using ID returned from authentication service. Read during login to fetch user profile."
|
|
617
|
+
}
|
|
618
|
+
],
|
|
619
|
+
"notes": "This is a default password-based authentication. If user requirements specify OAuth, biometric, or other authentication methods, adjust accordingly."
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
**Important Notes:**
|
|
624
|
+
- Authentication integration should ALWAYS be named "auth" for consistency
|
|
625
|
+
- Do NOT create "User" entity properties for password, passwordHash, or session tokens - these belong to the auth integration
|
|
626
|
+
- User entity should only contain business-related properties (username, email, profile info)
|
|
627
|
+
- In Task 1.4, do NOT create entities for authentication (no AuthSession, no Credentials entities)
|
|
628
|
+
- In Task 1.5, do NOT create login/registration interactions - these are handled by the auth integration
|
|
629
|
+
|
|
556
630
|
### What is NOT an Integration
|
|
557
631
|
|
|
558
632
|
**⚠️ CRITICAL: Intra-Project Module Access**
|
|
@@ -1280,6 +1354,22 @@ git commit -m "feat: Task 1.4 - Complete data concept extraction"
|
|
|
1280
1354
|
- Interaction IDs must be semantic names (e.g., "BorrowBook", "ViewAvailableBooks") not codes (e.g., "I001")
|
|
1281
1355
|
- ❌ If a requirement has role="System", it was incorrectly created - SKIP it, do NOT create interaction
|
|
1282
1356
|
|
|
1357
|
+
**⚠️ IMPORTANT: Distinguishing Data Access Constraints**
|
|
1358
|
+
|
|
1359
|
+
For read-type interaction requirements with access restrictions, distinguish between:
|
|
1360
|
+
|
|
1361
|
+
1. **Business Rules** - Constraints on whether the read operation can execute
|
|
1362
|
+
- Example: "Only administrators can view XXX entity"
|
|
1363
|
+
- Controls who can perform the action
|
|
1364
|
+
- Should be specified in the `conditions` field
|
|
1365
|
+
|
|
1366
|
+
2. **Data Policy** - Constraints on the scope of data returned
|
|
1367
|
+
- Example: "Can only view own XXX entities" or "Can only view YYY fields of XXX entity"
|
|
1368
|
+
- Controls what data is accessible after the operation is permitted
|
|
1369
|
+
- Should be specified in the `dataConstraints` field
|
|
1370
|
+
|
|
1371
|
+
These must be separated as they are implemented differently in subsequent phases.
|
|
1372
|
+
|
|
1283
1373
|
**⚠️ IMPORTANT: External Integration Interactions**
|
|
1284
1374
|
|
|
1285
1375
|
If Task 1.4 includes API Call entities, design error handling interactions:
|
|
@@ -73,7 +73,9 @@
|
|
|
73
73
|
"Bash(else echo \"VOLC_ACCESS_KEY_ID is set\")",
|
|
74
74
|
"Bash(fi:*)",
|
|
75
75
|
"Bash(if [ -z \"$VOLC_SECRET_ACCESS_KEY\" ])",
|
|
76
|
-
"Bash(then echo \"VOLC_SECRET_ACCESS_KEY is empty or not set\")"
|
|
76
|
+
"Bash(then echo \"VOLC_SECRET_ACCESS_KEY is empty or not set\")",
|
|
77
|
+
"Bash(npm run)",
|
|
78
|
+
"Bash(chmod:*)"
|
|
77
79
|
],
|
|
78
80
|
"deny": [],
|
|
79
81
|
"ask": []
|
package/package.json
CHANGED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
我们的项目所使用的框架是一个叫做 Interaqt 的后端响应式数据框架,它会自动根据应用中的数据变化及响应式数据的定义执行相应的数据变化。它只负责处理一般的业务逻辑中表达的数据逻辑,例如一般业务逻辑中会用到 平均/综合 等计算,还有常见的基于状态机等业务逻辑表达等。对于一些非一般的能力需求,例如 大模型生图、大模型生视频、tts、发送邮件、发送信息、完整支付系统等。它需要借助外部系统/api 来完成。
|
|
2
|
-
我们设计了一个叫做 integration 的概念,专门用来对接当前系统和外部的api/系统。它通过 interaqt 框架提供的数据观察机制,来观察数据变化,根据数据变化来决定如何调用外部的 api。同时通过 webhook 等机制来接受外部的事件,将外部事件同步回系统中,触发响应式的业务逻辑。
|
|
3
|
-
|
|
4
|
-
我们将 integration 需要集成的功能分成了三种类别:
|
|
5
|
-
1. 调用外部的 api,为了获得一个具体的返回。例如 tts,大模型生图等。
|
|
6
|
-
2. 执行某种副作用,例如发送邮件、发送 im 消息等。
|
|
7
|
-
3. 对接其他有状态的系统,例如支付系统等。
|
|
8
|
-
|
|
9
|
-
现在我们需要指导 claude code 的 sub agent 合理地识别需要的外部服务以及如何自己实现 integration。
|
|
10
|
-
1. 指导 `.claude/agents/requirements-analysis-handler.md` 在需求分析阶段,正确分析出 integration 的类型。并在相应的输出的文档中,设计一个字段来表达 integration 的类型。
|
|
11
|
-
2. 指导 `.claude/agents/implement-design-handler.md` 在设计数据的时候,根据如下原则进行设计:
|
|
12
|
-
2.1. 不管是哪种类型,都会涉及到对外部 api 的调用,例如执行副作用,也会有副作用 api 的调用。所以我们应该对每一个 api 的调用都设计一个 `{xxx}APICall` 的 entity,它负责记录这次 api 调用的参数、状态、返回值、调用时间等。
|
|
13
|
-
2.2. 同时设计一个相应的 integraion event entity,当我们通过 webhook 或者自己通过接口查询到 api 调用状态的变化时,在系统内创建相应的 api call result event 事件。并且将上一步创建的 `{xxx}APICall` entity 的 status 和 data 字段写成基于 integration event entity 的 computation,这样就完整符合了框架的响应式范式。也记录了所有应该记录的数据,增强了系统的健壮性。
|
|
14
|
-
2.3. 如果当前场景是希望基于这个 integration 获得具体的返回值,那么意味着我们系统内的业务数据对这个 `{xxx}APICall` 的 entity 是有依赖的,应该写成基于 `{xxx}APICall` 的 computation。例如我们的有一个 `Greeting` entity,其中有个 `voiceUrl` property 是需要利用外部 tts 能力将文本转化为语音。那么 `Greeting.voiceUrl` 就应该表达为基于 `{ttsAPICall}` entity 的 computation。如果是纯副作用类型等的调用,就不需要了。注意,这种情况下,还需要建立相应的 entity 和 api call entity 之间的关系,才能查找到正确的数据。
|
|
15
|
-
2.4. `.claude/agents/implement-design-handler.md` 在做 data-design 的时候,应该明确表达出来:1. 设计的哪些实体是 api call 类型的 entity,哪些实体是 api call result event 实体。2. 系统内的业务数据如果需要 api 的返回结果,那么应该依赖正确的 api call entity。
|
|
16
|
-
3. 指导 `.claude/agents/code-generation-handler.md` 在实现阶段,在写测试用例时,完全可以通过创建正确的 api call result event 来模拟 api 的调用,完整验证系统的内部逻辑的正确性。不需要等到 integration 的真实实现。
|
|
17
|
-
4. 指导 `.claude/agents/error-check-handler.md` 在合适的阶段对 integration 相关的设计做错误检查。
|
|
18
|
-
|
|
19
|
-
你充分理解上面的所有思路,并且修改相应的 sub agent 文件来达成目标。
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
在需求分析的过程中,我们已经在 `.claude/agents/requirements-analysis-handle.md` 中提出了一种以最终"读需求"为起点,反向衍生出 创建/修改/删除 需求的方法。
|
|
2
|
-
在实际的探索中发现:用户可能会在输入阶段就按照自己的真实需要描述了一部分流程了,这个流程里面包含了
|
|
3
|
-
|
|
4
|
-
- 进行增删改查交互的步骤,通常是有依赖顺序的
|
|
5
|
-
- 进行交互的角色
|
|
6
|
-
- 交互对数据产生的具体增删改变化
|
|
7
|
-
例子:在 `requirements/requirements.md` 中,用户直接在描述中叙述如何创建版本 rollback 时数据应该如何变化。这里面就已经包含了:"创建版本-回退版本"的具体流程。
|
|
8
|
-
|
|
9
|
-
这个流程本身也是用户的一种需求,我们需要严格遵照他的指示实现。
|
|
10
|
-
现在,你来将 `.claude/agents/requirements-analysis-handle.md` 完全重写。要求:
|
|
11
|
-
1. 先将 `.claude/agents/requirements-analysis-handle.md` 关于目标补充、数据分析、交互动作定义的步骤、设计的数据结构完全提取出来,这些部分已经很稳定,可以留作复用。
|
|
12
|
-
|
|
13
|
-
用下面的这些步骤作为重写的分析步骤:
|
|
14
|
-
1. 对用户的输入进行分析,识别出其中的:
|
|
15
|
-
- 目标。通常是和具体软件功能无关,和现实中真实目标相关的描述。例如“管理图书”,“管理员工”,“和好友实时交流”等。
|
|
16
|
-
- 流程。例如 "发出好友申请-收到申请后同意/收到申请后拒绝"。
|
|
17
|
-
- 数据概念。(使用原文档里数据概念)。
|
|
18
|
-
2. 进行常见的目标补充(使用原文档里的方法)。
|
|
19
|
-
3. 进行完整的数据概念设计(复用原文档中的方法)。
|
|
20
|
-
4. 进行流程和交互动作设计。
|
|
21
|
-
3.1. 优先整合用户描述中的流程。并且看流程达到了哪些目标。
|
|
22
|
-
3.2. 为没有达到的目标设计流程。
|
|
23
|
-
3.2.1. 流程是一系列有顺序的交互的总和,其中可以包含可能的分支或者循环。交互的具体定义仍然采用原文档中的定义。
|
|
24
|
-
3.2.2. 流程的最后仍然应该是一个"读需求"作为结尾,来真正满足目标。但前面需要补充常见的创建等逻辑。
|
|
25
|
-
3.3. 注意,流程中的每一步应该都是一个交互动作,交互动作要使用原文档中 interactions-design.json 中的数据结构表示。要包含完整 `data.creates`/`data.updates`/`data.deletes`。从用户的输入中直接提取的出来的流程也要完善这些信息。
|
|
26
|
-
5. 设计完流程后。从数据的角度来补充用户可能需要的其他增删改查需求:
|
|
27
|
-
4.1. 为每一个修改和删除的交互考虑用户作为人类,是否需要经过查看/搜索再进行决策的需求。
|
|
28
|
-
4.2. 为每一个创建的交互动作考虑,数据在现实中是否允许修改/删除。如果允许就应该增加相应的需求。
|
|
29
|
-
6. 最终产出的 interactions-design.json 仍然使用原文档里的 interaction 数据结构,但以流程为组来组织。
|
|
30
|
-
|
|
31
|
-
注意,在每一个步骤中,都要给出清晰的数据结构定义,用 json 来写。
|
|
32
|
-
整体用简洁的英语完成文档重写。注意原本文档中的在关键步骤 update docs/{module}.status.json 仍然按照原文档的方式写。
|
|
33
|
-
|
|
34
|
-
|