union-ark-web 1.0.1

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 (122) hide show
  1. package/PROJECT_OVERVIEW.md +405 -0
  2. package/README.md +177 -0
  3. package/ark-control/pom.xml +142 -0
  4. package/ark-control/src/local-mock/java/com/epcc/commons/securityproxy/api/SecurityResult.java +14 -0
  5. package/ark-control/src/local-mock/java/com/epcc/commons/securityproxy/api/SymmetricalSecurityService.java +11 -0
  6. package/ark-control/src/local-mock/java/com/epcc/dubbo/result/Result.java +29 -0
  7. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/annotation/EnDecryptField.java +16 -0
  8. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/annotation/EnDecryptFieldLong.java +17 -0
  9. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/annotation/EnDecryptFieldWithTag.java +16 -0
  10. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/exception/BaseDataErrorCode.java +26 -0
  11. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/exception/CheckException.java +33 -0
  12. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/exception/ErrorCode.java +9 -0
  13. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/redis/RedisCacheService.java +131 -0
  14. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/util/Constant.java +24 -0
  15. package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/util/ResultUtil.java +9 -0
  16. package/ark-control/src/local-mock/java/com/union/control/local/sensitive/LocalMockSensitiveProxy.java +40 -0
  17. package/ark-control/src/local-mock/java/com/union/control/local/sensitive/LocalRedisCacheService.java +45 -0
  18. package/ark-control/src/local-mock/java/com/union/control/local/sensitive/LocalSensitiveHostConfiguration.java +27 -0
  19. package/ark-control/src/local-mock/java/com/union/control/local/sensitive/LocalSensitiveProxyConfiguration.java +16 -0
  20. package/ark-control/src/main/java/com/union/control/ArkControlApplication.java +17 -0
  21. package/ark-control/src/main/java/com/union/control/dubbo/ProviderAccessLogFilter.java +45 -0
  22. package/ark-control/src/main/java/com/union/control/mapper/AgentExecutionMapper.java +35 -0
  23. package/ark-control/src/main/java/com/union/control/mapper/ConversationMapper.java +44 -0
  24. package/ark-control/src/main/java/com/union/control/mapper/MemoryStoreMapper.java +41 -0
  25. package/ark-control/src/main/java/com/union/control/mapper/ScheduledTaskMapper.java +96 -0
  26. package/ark-control/src/main/java/com/union/control/mapper/SensitiveAddressBookDemo.java +31 -0
  27. package/ark-control/src/main/java/com/union/control/mapper/SensitiveDataDemoMapper.java +24 -0
  28. package/ark-control/src/main/java/com/union/control/mapper/interceptor/AESInterceptor.java +265 -0
  29. package/ark-control/src/main/java/com/union/control/mapper/interceptor/AddressBookHandler.java +105 -0
  30. package/ark-control/src/main/java/com/union/control/schedule/ScheduledExecutionToken.java +55 -0
  31. package/ark-control/src/main/java/com/union/control/schedule/ScheduledTaskScheduler.java +206 -0
  32. package/ark-control/src/main/java/com/union/control/service/impl/AgentExecutionServiceImpl.java +212 -0
  33. package/ark-control/src/main/java/com/union/control/service/impl/AgentProxyServiceImpl.java +143 -0
  34. package/ark-control/src/main/java/com/union/control/service/impl/ConversationServiceImpl.java +245 -0
  35. package/ark-control/src/main/java/com/union/control/service/impl/MemoryStoreServiceImpl.java +252 -0
  36. package/ark-control/src/main/java/com/union/control/service/impl/RunningAnalysisMockServiceImpl.java +301 -0
  37. package/ark-control/src/main/java/com/union/control/service/impl/ScheduledTaskServiceImpl.java +576 -0
  38. package/ark-control/src/main/java/com/union/control/service/impl/SensitiveDataDemoServiceImpl.java +144 -0
  39. package/ark-control/src/main/java/com/union/control/service/sensitive/RedisRevealTokenStore.java +90 -0
  40. package/ark-control/src/main/java/com/union/control/service/sensitive/SensitiveRevealProcessor.java +319 -0
  41. package/ark-control/src/main/java/com/union/control/service/sensitive/SensitiveRevealServiceImpl.java +72 -0
  42. package/ark-control/src/main/java/com/union/control/utils/AgentSupport.java +103 -0
  43. package/ark-control/src/main/java/com/union/control/utils/security/SymmetricalSecurityUtils.java +160 -0
  44. package/ark-control/src/main/resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter +1 -0
  45. package/ark-control/src/main/resources/application.properties +4 -0
  46. package/ark-control/src/main/resources/application.yml +32 -0
  47. package/ark-control/src/main/resources/dubbo-provider.xml +20 -0
  48. package/ark-control/src/main/resources/mapper/AgentExecutionMapper.xml +75 -0
  49. package/ark-control/src/main/resources/mapper/ConversationMapper.xml +110 -0
  50. package/ark-control/src/main/resources/mapper/MemoryStoreMapper.xml +74 -0
  51. package/ark-control/src/main/resources/mapper/ScheduledTaskMapper.xml +286 -0
  52. package/ark-control/src/main/resources/mapper/SensitiveDataDemoMapper.xml +55 -0
  53. package/ark-control/src/main/resources/schema.sql +146 -0
  54. package/ark-control/src/test/java/com/union/control/AgentExecutionServiceTest.java +170 -0
  55. package/ark-control/src/test/java/com/union/control/ArkControlStructureContractTest.java +107 -0
  56. package/ark-control/src/test/java/com/union/control/ConversationServiceTest.java +89 -0
  57. package/ark-control/src/test/java/com/union/control/MemoryStoreServiceTest.java +26 -0
  58. package/ark-control/src/test/java/com/union/control/PydanticAiControlContractTest.java +52 -0
  59. package/ark-control/src/test/java/com/union/control/TestJson.java +45 -0
  60. package/ark-control/src/test/java/com/union/control/dubbo/ProviderAccessLogFilterTest.java +53 -0
  61. package/ark-control/src/test/java/com/union/control/local/sensitive/LocalSensitiveProxyConfigurationTest.java +23 -0
  62. package/ark-control/src/test/java/com/union/control/mapper/interceptor/AESInterceptorCompatibilityTest.java +505 -0
  63. package/ark-control/src/test/java/com/union/control/schedule/ScheduledExecutionTokenTest.java +27 -0
  64. package/ark-control/src/test/java/com/union/control/schedule/ScheduledTaskSchedulerTest.java +51 -0
  65. package/ark-control/src/test/java/com/union/control/service/AgentProxyServiceTest.java +111 -0
  66. package/ark-control/src/test/java/com/union/control/service/ScheduledTaskServiceTest.java +483 -0
  67. package/ark-control/src/test/java/com/union/control/service/impl/SensitiveDataDemoServiceImplTest.java +104 -0
  68. package/ark-control/src/test/java/com/union/control/service/sensitive/RedisRevealTokenStoreTest.java +65 -0
  69. package/ark-control/src/test/java/com/union/control/service/sensitive/SensitiveRevealBoundaryTest.java +99 -0
  70. package/ark-control/src/test/java/com/union/control/service/sensitive/SensitiveRevealProcessorCodecTest.java +111 -0
  71. package/ark-control/src/test/java/com/union/control/service/sensitive/SensitiveRevealProcessorTest.java +111 -0
  72. package/ark-control/src/test/java/com/union/control/utils/security/SymmetricalSecurityUtilsCompatibilityTest.java +87 -0
  73. package/ark-control-facade/pom.xml +16 -0
  74. package/ark-control-facade/src/main/java/com/union/control/service/AgentExecutionService.java +8 -0
  75. package/ark-control-facade/src/main/java/com/union/control/service/AgentProxyService.java +7 -0
  76. package/ark-control-facade/src/main/java/com/union/control/service/AgentResponse.java +22 -0
  77. package/ark-control-facade/src/main/java/com/union/control/service/ConversationService.java +12 -0
  78. package/ark-control-facade/src/main/java/com/union/control/service/MemoryStoreService.java +12 -0
  79. package/ark-control-facade/src/main/java/com/union/control/service/RunningAnalysisMockService.java +10 -0
  80. package/ark-control-facade/src/main/java/com/union/control/service/ScheduledTaskService.java +40 -0
  81. package/ark-control-facade/src/main/java/com/union/control/service/SensitiveDataDemoService.java +13 -0
  82. package/ark-control-facade/src/main/java/com/union/control/service/sensitive/SensitiveRevealService.java +18 -0
  83. package/ark-web/README.md +80 -0
  84. package/ark-web/pom.xml +105 -0
  85. package/ark-web/src/local-mock/java/com/epcc/arkweb/mock/ArkAuthServiceMock.java +105 -0
  86. package/ark-web/src/local-mock/java/com/epcc/arkweb/mock/LocalKaptchaConfiguration.java +26 -0
  87. package/ark-web/src/main/java/com/epcc/arkweb/Application.java +13 -0
  88. package/ark-web/src/main/java/com/epcc/arkweb/config/InterceptorConfig.java +20 -0
  89. package/ark-web/src/main/java/com/epcc/arkweb/config/Realm.java +64 -0
  90. package/ark-web/src/main/java/com/epcc/arkweb/config/ShiroConfig.java +129 -0
  91. package/ark-web/src/main/java/com/epcc/arkweb/filter/LoginFormFilter.java +43 -0
  92. package/ark-web/src/main/java/com/epcc/arkweb/helper/AuthContextHolder.java +39 -0
  93. package/ark-web/src/main/java/com/epcc/arkweb/helper/AuthenticatedRequest.java +91 -0
  94. package/ark-web/src/main/java/com/epcc/arkweb/model/ShiroUser.java +21 -0
  95. package/ark-web/src/main/java/com/epcc/arkweb/utils/ResultMsg.java +19 -0
  96. package/ark-web/src/main/java/com/epcc/arkweb/vo/llm/ScheduledTaskCommandVO.java +30 -0
  97. package/ark-web/src/main/java/com/epcc/arkweb/vo/llm/ScheduledTaskQueryVO.java +20 -0
  98. package/ark-web/src/main/java/com/epcc/arkweb/web/CommonController.java +25 -0
  99. package/ark-web/src/main/java/com/epcc/arkweb/web/llm/AgentAuthorizationInterceptor.java +229 -0
  100. package/ark-web/src/main/java/com/epcc/arkweb/web/llm/AgentController.java +165 -0
  101. package/ark-web/src/main/java/com/epcc/arkweb/web/llm/AgentPermission.java +13 -0
  102. package/ark-web/src/main/java/com/epcc/arkweb/web/llm/LlmController.java +209 -0
  103. package/ark-web/src/main/java/com/epcc/arkweb/web/llm/ScheduledTaskController.java +160 -0
  104. package/ark-web/src/main/java/com/epcc/arkweb/web/sensitive/reveal/SensitiveCaptchaService.java +146 -0
  105. package/ark-web/src/main/java/com/epcc/arkweb/web/sensitive/reveal/SensitiveRevealController.java +163 -0
  106. package/ark-web/src/main/resources/application.properties +1 -0
  107. package/ark-web/src/main/resources/application.yml +23 -0
  108. package/ark-web/src/main/resources/dubbo-consumer.xml +19 -0
  109. package/ark-web/src/production-overlay/java/com/epcc/arkweb/config/CasShiroConfig.java +229 -0
  110. package/ark-web/src/production-overlay/java/com/epcc/arkweb/config/InterceptorConfig.java +35 -0
  111. package/ark-web/src/production-overlay/java/com/epcc/arkweb/config/ShiroConfig.java +161 -0
  112. package/ark-web/src/test/java/com/epcc/arkweb/ArkWebStructureContractTest.java +290 -0
  113. package/ark-web/src/test/java/com/epcc/arkweb/ShiroTestSupport.java +32 -0
  114. package/ark-web/src/test/java/com/epcc/arkweb/config/LocalShiroIntegrationTest.java +121 -0
  115. package/ark-web/src/test/java/com/epcc/arkweb/helper/AuthenticatedRequestTest.java +131 -0
  116. package/ark-web/src/test/java/com/epcc/arkweb/local/LocalArkAuthServiceMockTest.java +17 -0
  117. package/ark-web/src/test/java/com/epcc/arkweb/web/llm/AgentAuthorizationInterceptorTest.java +134 -0
  118. package/ark-web/src/test/java/com/epcc/arkweb/web/llm/AgentControllerAuthorizationTest.java +97 -0
  119. package/ark-web/src/test/java/com/epcc/arkweb/web/llm/LlmControllerTest.java +113 -0
  120. package/ark-web/src/test/java/com/epcc/arkweb/web/sensitive/reveal/SensitiveRevealControllerTest.java +273 -0
  121. package/package.json +18 -0
  122. package/pom.xml +16 -0
@@ -0,0 +1,286 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <!DOCTYPE mapper
3
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
4
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
5
+ <mapper namespace="com.union.control.mapper.ScheduledTaskMapper">
6
+
7
+ <sql id="taskFields">
8
+ t.id,t.title,t.prompt,t.schedule_type AS scheduleType,
9
+ DATE_FORMAT(t.run_at,'%Y-%m-%dT%H:%i:%s.%fZ') AS runAt,
10
+ t.cron_expression AS cronExpression,t.interval_seconds AS intervalSeconds,
11
+ t.timezone,t.status,
12
+ DATE_FORMAT(t.next_run_at,'%Y-%m-%dT%H:%i:%s.%fZ') AS nextRunAt,
13
+ DATE_FORMAT(t.created_at,'%Y-%m-%dT%H:%i:%s.%fZ') AS createdAt,
14
+ DATE_FORMAT(t.updated_at,'%Y-%m-%dT%H:%i:%s.%fZ') AS updatedAt
15
+ </sql>
16
+
17
+ <sql id="runSummaryFields">
18
+ r.id,r.task_id AS taskId,
19
+ DATE_FORMAT(r.scheduled_at,'%Y-%m-%dT%H:%i:%s.%fZ') AS scheduledAt,
20
+ r.status,r.result_conversation_id AS resultConversationId,r.read_flag AS readFlag,
21
+ r.error_code AS errorCode,r.error_message AS errorMessage,
22
+ DATE_FORMAT(r.started_at,'%Y-%m-%dT%H:%i:%s.%fZ') AS startedAt,
23
+ DATE_FORMAT(r.finished_at,'%Y-%m-%dT%H:%i:%s.%fZ') AS finishedAt,
24
+ DATE_FORMAT(r.created_at,'%Y-%m-%dT%H:%i:%s.%fZ') AS createdAt
25
+ </sql>
26
+
27
+ <sql id="taskFilter">
28
+ WHERE t.user_id=#{userId} AND t.delete_flag=1
29
+ <if test="keyword != null">
30
+ AND (t.title LIKE #{keyword} ESCAPE '!' OR t.prompt LIKE #{keyword} ESCAPE '!')
31
+ </if>
32
+ <if test="status != null">AND t.status=#{status}</if>
33
+ </sql>
34
+
35
+ <select id="lockUserTasks" resultType="long">
36
+ SELECT id FROM agent_scheduled_task
37
+ WHERE user_id=#{userId} AND delete_flag=1 FOR UPDATE
38
+ </select>
39
+
40
+ <select id="countRunnableTasks" resultType="int">
41
+ SELECT COUNT(*) FROM agent_scheduled_task
42
+ WHERE user_id=#{userId} AND delete_flag=1 AND status IN ('ACTIVE','PAUSED')
43
+ </select>
44
+
45
+ <insert id="insertTask" parameterType="map" useGeneratedKeys="true" keyProperty="id">
46
+ INSERT INTO agent_scheduled_task
47
+ (user_id,org_code,role_id,title,prompt,schedule_type,run_at,cron_expression,interval_seconds,
48
+ timezone,status,next_run_at,delete_flag,created_at,updated_at)
49
+ VALUES
50
+ (#{userId},#{orgCode},#{roleId},#{title},#{prompt},#{scheduleType},#{runAt},#{cronExpression},
51
+ #{intervalSeconds},#{timezone},'ACTIVE',#{nextRunAt},1,
52
+ UTC_TIMESTAMP(3),UTC_TIMESTAMP(3))
53
+ </insert>
54
+
55
+ <update id="updateTask" parameterType="map">
56
+ UPDATE agent_scheduled_task
57
+ SET org_code=#{orgCode},role_id=#{roleId},title=#{title},prompt=#{prompt},schedule_type=#{scheduleType},run_at=#{runAt},
58
+ cron_expression=#{cronExpression},interval_seconds=#{intervalSeconds},timezone=#{timezone},
59
+ next_run_at=CASE WHEN status='ACTIVE' THEN #{nextRunAt} ELSE NULL END,
60
+ updated_at=UTC_TIMESTAMP(3)
61
+ WHERE id=#{taskId} AND user_id=#{userId} AND delete_flag=1
62
+ </update>
63
+
64
+ <select id="countTasks" resultType="long">
65
+ SELECT COUNT(*) FROM agent_scheduled_task t
66
+ <include refid="taskFilter"/>
67
+ </select>
68
+
69
+ <select id="findTasks" resultType="map">
70
+ SELECT <include refid="taskFields"/>,
71
+ (SELECT r.status FROM agent_scheduled_task_run r
72
+ WHERE r.task_id=t.id AND r.delete_flag=1
73
+ ORDER BY r.scheduled_at DESC,r.id DESC LIMIT 1) AS latestRunStatus
74
+ FROM agent_scheduled_task t
75
+ <include refid="taskFilter"/>
76
+ ORDER BY t.updated_at DESC,t.id DESC LIMIT #{limit} OFFSET #{offset}
77
+ </select>
78
+
79
+ <select id="findTaskDetail" resultType="map">
80
+ SELECT <include refid="taskFields"/>,
81
+ (SELECT r.status FROM agent_scheduled_task_run r
82
+ WHERE r.task_id=t.id AND r.delete_flag=1
83
+ ORDER BY r.scheduled_at DESC,r.id DESC LIMIT 1) AS latestRunStatus
84
+ FROM agent_scheduled_task t
85
+ WHERE t.id=#{taskId} AND t.user_id=#{userId} AND t.delete_flag=1
86
+ </select>
87
+
88
+ <select id="findTaskOwner" resultType="map">
89
+ SELECT id,schedule_type AS scheduleType,run_at AS runAt,
90
+ cron_expression AS cronExpression,interval_seconds AS intervalSeconds,
91
+ timezone,status
92
+ FROM agent_scheduled_task
93
+ WHERE id=#{taskId} AND user_id=#{userId} AND delete_flag=1
94
+ <if test="lock">FOR UPDATE</if>
95
+ </select>
96
+
97
+ <select id="countRuns" resultType="long">
98
+ SELECT COUNT(*)
99
+ FROM agent_scheduled_task_run r
100
+ JOIN agent_scheduled_task t ON t.id=r.task_id
101
+ WHERE r.task_id=#{taskId} AND r.delete_flag=1
102
+ AND t.user_id=#{userId} AND t.delete_flag=1
103
+ </select>
104
+
105
+ <select id="findRuns" resultType="map">
106
+ SELECT <include refid="runSummaryFields"/>
107
+ FROM agent_scheduled_task_run r
108
+ JOIN agent_scheduled_task t ON t.id=r.task_id
109
+ WHERE r.task_id=#{taskId} AND r.delete_flag=1
110
+ AND t.user_id=#{userId} AND t.delete_flag=1
111
+ ORDER BY r.scheduled_at DESC,r.id DESC LIMIT #{limit} OFFSET #{offset}
112
+ </select>
113
+
114
+ <select id="findOwnedRun" resultType="map">
115
+ SELECT <include refid="runSummaryFields"/>,r.result_payload AS resultPayload,
116
+ t.title,t.prompt,t.user_id AS userId
117
+ FROM agent_scheduled_task_run r
118
+ JOIN agent_scheduled_task t ON t.id=r.task_id
119
+ WHERE r.id=#{runId} AND r.delete_flag=1
120
+ AND t.user_id=#{userId} AND t.delete_flag=1
121
+ <if test="lock">FOR UPDATE</if>
122
+ </select>
123
+
124
+ <select id="countUnread" resultType="long">
125
+ SELECT COUNT(*)
126
+ FROM agent_scheduled_task_run r
127
+ JOIN agent_scheduled_task t ON t.id=r.task_id
128
+ WHERE t.user_id=#{userId} AND t.delete_flag=1 AND r.delete_flag=1
129
+ AND r.status IN ('SUCCEEDED','FAILED') AND r.read_flag=0
130
+ </select>
131
+
132
+ <select id="findUnread" resultType="map">
133
+ SELECT <include refid="runSummaryFields"/>,t.title
134
+ FROM agent_scheduled_task_run r
135
+ JOIN agent_scheduled_task t ON t.id=r.task_id
136
+ WHERE t.user_id=#{userId} AND t.delete_flag=1 AND r.delete_flag=1
137
+ AND r.status IN ('SUCCEEDED','FAILED') AND r.read_flag=0
138
+ ORDER BY r.finished_at DESC,r.id DESC LIMIT 100
139
+ </select>
140
+
141
+ <update id="activateTask">
142
+ UPDATE agent_scheduled_task
143
+ SET status='ACTIVE',next_run_at=#{nextRunAt},updated_at=UTC_TIMESTAMP(3)
144
+ WHERE id=#{taskId} AND user_id=#{userId} AND delete_flag=1 AND status='PAUSED'
145
+ </update>
146
+
147
+ <update id="pauseTask">
148
+ UPDATE agent_scheduled_task
149
+ SET status='PAUSED',next_run_at=NULL,updated_at=UTC_TIMESTAMP(3)
150
+ WHERE id=#{taskId} AND user_id=#{userId} AND delete_flag=1 AND status='ACTIVE'
151
+ </update>
152
+
153
+ <update id="discardTask">
154
+ UPDATE agent_scheduled_task t
155
+ LEFT JOIN agent_scheduled_task_run r
156
+ ON r.task_id=t.id AND r.delete_flag=1 AND r.status IN ('PENDING','RUNNING')
157
+ SET t.delete_flag=0,t.next_run_at=NULL,t.updated_at=UTC_TIMESTAMP(3),
158
+ r.status='FAILED',r.error_code='scheduled_task_discarded',
159
+ r.error_message='定时任务已删除',r.finished_at=UTC_TIMESTAMP(3),
160
+ r.updated_at=UTC_TIMESTAMP(3),r.execution_token_hash=NULL,
161
+ r.execution_token_expires_at=NULL
162
+ WHERE t.id=#{taskId} AND t.user_id=#{userId} AND t.delete_flag=1
163
+ </update>
164
+
165
+ <select id="findDueTask" resultType="map">
166
+ SELECT id,schedule_type AS scheduleType,run_at AS runAt,
167
+ cron_expression AS cronExpression,interval_seconds AS intervalSeconds,
168
+ timezone,next_run_at AS nextRunAt
169
+ FROM agent_scheduled_task
170
+ WHERE delete_flag=1 AND status='ACTIVE' AND next_run_at&lt;=UTC_TIMESTAMP(3)
171
+ ORDER BY next_run_at,id LIMIT 1 FOR UPDATE SKIP LOCKED
172
+ </select>
173
+
174
+ <insert id="insertRun" parameterType="map" useGeneratedKeys="true" keyProperty="id">
175
+ INSERT INTO agent_scheduled_task_run
176
+ (task_id,scheduled_at,status,read_flag,delete_flag,created_at,updated_at)
177
+ VALUES (#{taskId},#{scheduledAt},'PENDING',0,1,UTC_TIMESTAMP(3),UTC_TIMESTAMP(3))
178
+ </insert>
179
+
180
+ <update id="completeOnceTask">
181
+ UPDATE agent_scheduled_task
182
+ SET status='COMPLETED',next_run_at=NULL,updated_at=UTC_TIMESTAMP(3)
183
+ WHERE id=#{taskId} AND schedule_type='ONCE' AND delete_flag=1
184
+ </update>
185
+
186
+ <update id="updateNextRun">
187
+ UPDATE agent_scheduled_task
188
+ SET next_run_at=#{nextRunAt},updated_at=UTC_TIMESTAMP(3)
189
+ WHERE id=#{taskId}
190
+ </update>
191
+
192
+ <select id="findPendingRunIds" resultType="long">
193
+ SELECT id FROM agent_scheduled_task_run
194
+ WHERE delete_flag=1 AND status='PENDING'
195
+ ORDER BY created_at,id LIMIT #{limit}
196
+ </select>
197
+
198
+ <update id="beginRun">
199
+ UPDATE agent_scheduled_task_run
200
+ SET status='RUNNING',started_at=UTC_TIMESTAMP(3),updated_at=UTC_TIMESTAMP(3),
201
+ execution_token_hash=#{tokenHash},
202
+ execution_token_expires_at=#{tokenExpiresAt}
203
+ WHERE id=#{runId} AND delete_flag=1 AND status='PENDING'
204
+ AND EXISTS (SELECT 1 FROM agent_scheduled_task t
205
+ WHERE t.id=agent_scheduled_task_run.task_id AND t.delete_flag=1)
206
+ </update>
207
+
208
+ <select id="findPendingRunContext" resultType="map">
209
+ SELECT r.id AS runId,r.task_id AS taskId,t.user_id AS userId,t.org_code AS orgCode,
210
+ t.role_id AS roleId
211
+ FROM agent_scheduled_task_run r
212
+ JOIN agent_scheduled_task t ON t.id=r.task_id
213
+ WHERE r.id=#{runId} AND r.delete_flag=1 AND r.status='PENDING' AND t.delete_flag=1
214
+ </select>
215
+
216
+ <select id="resolveScheduledToken" resultType="map">
217
+ SELECT r.id AS runId,r.task_id AS taskId,t.user_id AS userId,t.org_code AS orgCode,
218
+ t.role_id AS roleId,t.prompt,t.timezone,
219
+ DATE_FORMAT(r.scheduled_at,'%Y-%m-%dT%H:%i:%s.%fZ') AS scheduledAt,
220
+ DATE_FORMAT(r.execution_token_expires_at,'%Y-%m-%dT%H:%i:%s.%fZ') AS expiresAt,
221
+ r.execution_token_hash AS tokenHash
222
+ FROM agent_scheduled_task_run r
223
+ JOIN agent_scheduled_task t ON t.id=r.task_id
224
+ WHERE r.execution_token_hash=#{tokenHash}
225
+ AND r.delete_flag=1 AND r.status='RUNNING'
226
+ AND r.execution_token_expires_at&gt;UTC_TIMESTAMP(3)
227
+ AND t.delete_flag=1 AND t.user_id IS NOT NULL AND t.user_id&lt;&gt;''
228
+ AND t.org_code IS NOT NULL AND t.org_code&lt;&gt;''
229
+ AND t.role_id IS NOT NULL AND t.role_id&lt;&gt;''
230
+ </select>
231
+
232
+ <select id="findRunForUpdate" resultType="map">
233
+ SELECT id,task_id AS taskId,status
234
+ FROM agent_scheduled_task_run
235
+ WHERE id=#{runId} AND delete_flag=1 FOR UPDATE
236
+ </select>
237
+
238
+ <update id="completeRun">
239
+ UPDATE agent_scheduled_task_run
240
+ SET status='SUCCEEDED',result_payload=#{resultPayload},
241
+ finished_at=UTC_TIMESTAMP(3),updated_at=UTC_TIMESTAMP(3),
242
+ execution_token_hash=NULL,execution_token_expires_at=NULL
243
+ WHERE id=#{runId} AND delete_flag=1 AND status='RUNNING'
244
+ </update>
245
+
246
+ <update id="failRun">
247
+ UPDATE agent_scheduled_task_run
248
+ SET status='FAILED',error_code=#{errorCode},error_message=#{errorMessage},
249
+ finished_at=UTC_TIMESTAMP(3),updated_at=UTC_TIMESTAMP(3),
250
+ execution_token_hash=NULL,execution_token_expires_at=NULL
251
+ WHERE id=#{runId} AND delete_flag=1 AND status IN ('PENDING','RUNNING')
252
+ </update>
253
+
254
+ <update id="failStaleRuns">
255
+ UPDATE agent_scheduled_task_run r
256
+ JOIN agent_scheduled_task t ON t.id=r.task_id
257
+ SET r.status='FAILED',r.error_code='scheduled_run_timeout',
258
+ r.error_message='定时任务执行超时',r.finished_at=UTC_TIMESTAMP(3),
259
+ r.updated_at=UTC_TIMESTAMP(3),r.execution_token_hash=NULL,
260
+ r.execution_token_expires_at=NULL,
261
+ t.status=CASE WHEN t.schedule_type='ONCE' THEN 'COMPLETED' ELSE t.status END,
262
+ t.updated_at=UTC_TIMESTAMP(3)
263
+ WHERE r.delete_flag=1 AND r.status='RUNNING'
264
+ AND r.started_at&lt;DATE_SUB(UTC_TIMESTAMP(3),INTERVAL #{maxRunSeconds} SECOND)
265
+ </update>
266
+
267
+ <update id="markRunRead">
268
+ UPDATE agent_scheduled_task_run
269
+ SET read_flag=1,updated_at=UTC_TIMESTAMP(3) WHERE id=#{runId}
270
+ </update>
271
+
272
+ <update id="attachConversation">
273
+ UPDATE agent_scheduled_task_run
274
+ SET result_conversation_id=#{conversationId},read_flag=1,updated_at=UTC_TIMESTAMP(3)
275
+ WHERE id=#{runId} AND result_conversation_id IS NULL
276
+ </update>
277
+
278
+ <update id="softDeleteByConversation">
279
+ UPDATE agent_scheduled_task_run r
280
+ JOIN agent_scheduled_task t ON t.id=r.task_id
281
+ SET r.delete_flag=0,r.updated_at=UTC_TIMESTAMP(3)
282
+ WHERE r.result_conversation_id=#{conversationId} AND r.delete_flag=1
283
+ AND t.user_id=#{userId} AND t.delete_flag=1
284
+ </update>
285
+
286
+ </mapper>
@@ -0,0 +1,55 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <!DOCTYPE mapper
3
+ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
4
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
5
+ <mapper namespace="com.union.control.mapper.SensitiveDataDemoMapper">
6
+ <insert id="insert">
7
+ INSERT INTO sensitive_data_demo.sensitive_data_demo (phone_number)
8
+ VALUES (#{phoneNumber})
9
+ </insert>
10
+
11
+ <select id="query" resultType="map">
12
+ SELECT id, phone_number AS phoneNumber,
13
+ DATE_FORMAT(created_at, '%Y-%m-%dT%H:%i:%s') AS createdAt
14
+ FROM sensitive_data_demo.sensitive_data_demo
15
+ ORDER BY id DESC
16
+ </select>
17
+
18
+ <insert id="insertAddressBook" useGeneratedKeys="true" keyProperty="id">
19
+ INSERT INTO t_m_announce_address_book
20
+ (name, role, email, telephone, mobile_number)
21
+ VALUES
22
+ (#{name}, #{role}, #{email}, #{telephone}, #{mobileNumber})
23
+ </insert>
24
+
25
+ <select id="queryAddressBook"
26
+ resultType="com.union.control.mapper.SensitiveAddressBookDemo">
27
+ SELECT id, name, role, email, telephone,
28
+ mobile_number AS mobileNumber,
29
+ DATE_FORMAT(created_at, '%Y-%m-%dT%H:%i:%s') AS createdAt
30
+ FROM t_m_announce_address_book
31
+ ORDER BY id DESC
32
+ </select>
33
+ <insert id="insertSaved" useGeneratedKeys="true" keyProperty="id">
34
+ INSERT INTO sensitive_data_demo.sensitive_data_demo (phone_number) VALUES (#{phoneNumber})
35
+ </insert>
36
+ <update id="update">
37
+ UPDATE sensitive_data_demo.sensitive_data_demo SET phone_number = #{phoneNumber} WHERE id = #{id}
38
+ </update>
39
+ <select id="queryById" resultType="map">
40
+ SELECT id, phone_number AS phoneNumber,
41
+ DATE_FORMAT(created_at, '%Y-%m-%dT%H:%i:%s') AS createdAt
42
+ FROM sensitive_data_demo.sensitive_data_demo WHERE id = #{id}
43
+ </select>
44
+ <update id="updateAddressBook">
45
+ UPDATE t_m_announce_address_book
46
+ SET name = #{name}, role = #{role}, email = #{email},
47
+ telephone = #{telephone}, mobile_number = #{mobileNumber}
48
+ WHERE id = #{id}
49
+ </update>
50
+ <select id="queryAddressBookById" resultType="com.union.control.mapper.SensitiveAddressBookDemo">
51
+ SELECT id, name, role, email, telephone, mobile_number AS mobileNumber,
52
+ DATE_FORMAT(created_at, '%Y-%m-%dT%H:%i:%s') AS createdAt
53
+ FROM t_m_announce_address_book WHERE id = #{id}
54
+ </select>
55
+ </mapper>
@@ -0,0 +1,146 @@
1
+ CREATE TABLE IF NOT EXISTS `ai_conversation` (
2
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '数据库自增主键',
3
+ `conversation_id` VARCHAR(64) COLLATE utf8mb4_bin NOT NULL COMMENT '业务会话标识,对应 AG-UI threadId',
4
+ `user_id` VARCHAR(64) COLLATE utf8mb4_bin NOT NULL COMMENT '会话所属用户标识',
5
+ `title` VARCHAR(255) DEFAULT NULL COMMENT '会话标题',
6
+ `status` VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '会话状态:active-活跃,archived-已归档',
7
+ `is_pinned` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否置顶:1-是,0-否',
8
+ `delete_flag` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '删除标记:1-有效,0-已删除',
9
+ `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
10
+ `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
11
+ PRIMARY KEY (`id`),
12
+ UNIQUE KEY `uk_conversation_id` (`conversation_id`),
13
+ KEY `idx_conversation_user_list` (`user_id`, `delete_flag`, `status`, `updated_at`)
14
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
15
+
16
+ CREATE TABLE IF NOT EXISTS `ai_agent_execution` (
17
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '数据库自增主键',
18
+ `run_id` VARCHAR(64) COLLATE utf8mb4_bin NOT NULL COMMENT 'Agent 执行业务标识',
19
+ `conversation_id` VARCHAR(64) COLLATE utf8mb4_bin NOT NULL COMMENT '所属业务会话标识',
20
+ `user_id` VARCHAR(64) COLLATE utf8mb4_bin NOT NULL COMMENT '执行所属用户标识',
21
+ `parent_execution_id` BIGINT DEFAULT NULL COMMENT '父执行数据库主键,根执行为空',
22
+ `agent_name` VARCHAR(128) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'py-app 最终提交的 Agent 名称',
23
+ `delegation_tool_call_id` VARCHAR(128) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '父执行发起委派的工具调用标识',
24
+ `task` VARCHAR(1000) DEFAULT NULL COMMENT '委派给 Agent 的任务说明',
25
+ `status` VARCHAR(32) NOT NULL COMMENT 'py-app 提交的执行状态',
26
+ `finished_at` DATETIME DEFAULT NULL COMMENT '执行结束时间',
27
+ `error_code` VARCHAR(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '执行失败错误码',
28
+ `delete_flag` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '删除标记:1-有效,0-已删除',
29
+ `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
30
+ `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
31
+ PRIMARY KEY (`id`),
32
+ UNIQUE KEY `uk_agent_execution_run` (`run_id`),
33
+ UNIQUE KEY `uk_parent_delegation_call` (`parent_execution_id`, `delegation_tool_call_id`),
34
+ KEY `idx_execution_conversation` (`conversation_id`, `delete_flag`, `created_at`),
35
+ KEY `idx_execution_parent` (`parent_execution_id`, `delete_flag`, `created_at`),
36
+ KEY `idx_execution_user_status` (`user_id`, `delete_flag`, `status`),
37
+ CONSTRAINT `fk_execution_conversation` FOREIGN KEY (`conversation_id`)
38
+ REFERENCES `ai_conversation` (`conversation_id`),
39
+ CONSTRAINT `fk_execution_parent` FOREIGN KEY (`parent_execution_id`)
40
+ REFERENCES `ai_agent_execution` (`id`)
41
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
42
+
43
+ CREATE TABLE IF NOT EXISTS `ai_conversation_message` (
44
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '数据库自增主键',
45
+ `message_id` VARCHAR(128) COLLATE utf8mb4_bin NOT NULL COMMENT '消息业务标识',
46
+ `conversation_id` VARCHAR(64) COLLATE utf8mb4_bin NOT NULL COMMENT '所属业务会话标识',
47
+ `user_id` VARCHAR(64) COLLATE utf8mb4_bin NOT NULL COMMENT '消息所属用户标识',
48
+ `agent_execution_id` BIGINT NOT NULL COMMENT '消息所属执行数据库主键',
49
+ `role` VARCHAR(32) COLLATE utf8mb4_bin NOT NULL COMMENT '消息角色:user-用户,assistant-助手,tool-工具结果,system-系统,developer-开发者,reasoning-推理',
50
+ `sequence_no` BIGINT NOT NULL COMMENT '会话范围内的消息顺序号',
51
+ `payload` JSON NOT NULL COMMENT '消息角色特有字段 JSON',
52
+ `delete_flag` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '删除标记:1-有效,0-已删除',
53
+ `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
54
+ `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
55
+ PRIMARY KEY (`id`),
56
+ UNIQUE KEY `uk_conversation_message` (`conversation_id`, `message_id`),
57
+ UNIQUE KEY `uk_conversation_sequence` (`conversation_id`, `sequence_no`),
58
+ KEY `idx_message_history` (`conversation_id`, `user_id`, `delete_flag`, `sequence_no`),
59
+ KEY `idx_message_execution` (`agent_execution_id`, `delete_flag`, `sequence_no`),
60
+ CONSTRAINT `fk_message_conversation` FOREIGN KEY (`conversation_id`)
61
+ REFERENCES `ai_conversation` (`conversation_id`),
62
+ CONSTRAINT `fk_message_execution` FOREIGN KEY (`agent_execution_id`)
63
+ REFERENCES `ai_agent_execution` (`id`)
64
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
65
+
66
+ CREATE TABLE IF NOT EXISTS `ai_memory_file` (
67
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '数据库自增主键',
68
+ `user_id` VARCHAR(64) COLLATE utf8mb4_bin NOT NULL COMMENT '记忆文件所属用户标识',
69
+ `path` VARCHAR(512) COLLATE utf8mb4_bin NOT NULL COMMENT '用户命名空间内的文件路径',
70
+ `content` MEDIUMTEXT NOT NULL COMMENT '记忆文件内容',
71
+ `version` BIGINT NOT NULL COMMENT '文件内容版本号',
72
+ `last_operation_id` VARCHAR(128) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '最后一次修改文件的幂等操作标识',
73
+ `delete_flag` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '删除标记:1-有效,0-已删除',
74
+ `active_path` VARCHAR(512) COLLATE utf8mb4_bin GENERATED ALWAYS AS (
75
+ CASE WHEN `delete_flag` = 1 THEN `path` ELSE NULL END
76
+ ) STORED COMMENT '有效文件路径,用于实现软删除后的路径唯一约束',
77
+ `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
78
+ `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
79
+ PRIMARY KEY (`id`),
80
+ UNIQUE KEY `uk_memory_file_active_path` (`user_id`, `active_path`),
81
+ KEY `idx_memory_file_prefix` (`user_id`, `delete_flag`, `path`(191))
82
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
83
+
84
+ CREATE TABLE IF NOT EXISTS `ai_memory_operation` (
85
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '数据库自增主键',
86
+ `user_id` VARCHAR(64) COLLATE utf8mb4_bin NOT NULL COMMENT '操作所属用户标识',
87
+ `operation_id` VARCHAR(128) COLLATE utf8mb4_bin NOT NULL COMMENT '客户端幂等操作标识',
88
+ `fingerprint` VARCHAR(128) COLLATE utf8mb4_bin NOT NULL COMMENT '操作请求内容指纹',
89
+ `result_version` BIGINT DEFAULT NULL COMMENT '操作结果版本号,删除操作为空',
90
+ `existed` TINYINT(1) NOT NULL COMMENT '操作前文件是否存在:1-存在,0-不存在',
91
+ `delete_flag` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '删除标记:1-有效,0-已删除',
92
+ `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
93
+ `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
94
+ PRIMARY KEY (`id`),
95
+ UNIQUE KEY `uk_memory_operation` (`user_id`, `operation_id`)
96
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
97
+
98
+ CREATE TABLE IF NOT EXISTS `agent_scheduled_task` (
99
+ `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '数据库自增主键',
100
+ `user_id` VARCHAR(64) COLLATE utf8mb4_bin NOT NULL COMMENT '任务所属用户标识',
101
+ `org_code` VARCHAR(64) COLLATE utf8mb4_bin NOT NULL COMMENT '任务创建时的机构快照',
102
+ `role_id` VARCHAR(64) COLLATE utf8mb4_bin NOT NULL COMMENT '任务创建或编辑时的Ark系统角色快照',
103
+ `title` VARCHAR(255) NOT NULL COMMENT '任务标题',
104
+ `prompt` TEXT NOT NULL COMMENT '每次执行使用的自然语言任务提示',
105
+ `schedule_type` VARCHAR(16) COLLATE utf8mb4_bin NOT NULL COMMENT '调度类型:ONCE、CRON、INTERVAL',
106
+ `run_at` DATETIME(3) DEFAULT NULL COMMENT '单次执行时间或间隔锚点,UTC',
107
+ `cron_expression` VARCHAR(128) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'Spring 六字段 cron 表达式',
108
+ `interval_seconds` BIGINT UNSIGNED DEFAULT NULL COMMENT '固定执行间隔秒数',
109
+ `timezone` VARCHAR(64) COLLATE utf8mb4_bin NOT NULL COMMENT '任务 IANA 时区',
110
+ `status` VARCHAR(16) COLLATE utf8mb4_bin NOT NULL COMMENT '任务状态:ACTIVE、PAUSED、COMPLETED',
111
+ `next_run_at` DATETIME(3) DEFAULT NULL COMMENT '下一次计划执行时间,UTC',
112
+ `delete_flag` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '删除标记:1-有效,0-已删除',
113
+ `created_at` DATETIME(3) NOT NULL COMMENT '创建时间,UTC',
114
+ `updated_at` DATETIME(3) NOT NULL COMMENT '最后更新时间,UTC',
115
+ PRIMARY KEY (`id`),
116
+ KEY `idx_scheduled_task_due` (`delete_flag`, `status`, `next_run_at`),
117
+ KEY `idx_scheduled_task_user` (`user_id`, `delete_flag`, `updated_at`)
118
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
119
+
120
+ CREATE TABLE IF NOT EXISTS `agent_scheduled_task_run` (
121
+ `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '数据库自增主键',
122
+ `task_id` BIGINT UNSIGNED NOT NULL COMMENT '所属定时任务主键',
123
+ `scheduled_at` DATETIME(3) NOT NULL COMMENT '本次计划执行时间,UTC',
124
+ `status` VARCHAR(16) COLLATE utf8mb4_bin NOT NULL COMMENT '运行状态:PENDING、RUNNING、SUCCEEDED、FAILED',
125
+ `result_payload` LONGTEXT DEFAULT NULL COMMENT 'py-app 返回的标准结果 JSON',
126
+ `result_conversation_id` VARCHAR(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用户打开结果后创建的会话标识',
127
+ `read_flag` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '结果读取标记:1-已打开,0-未打开',
128
+ `error_code` VARCHAR(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '稳定错误码',
129
+ `error_message` VARCHAR(512) DEFAULT NULL COMMENT '用户安全错误信息',
130
+ `execution_token_hash` CHAR(64) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL COMMENT '单次运行 token 的 SHA-256 小写十六进制哈希',
131
+ `execution_token_expires_at` DATETIME(3) DEFAULT NULL COMMENT '单次运行 token 的失效时间,UTC',
132
+ `started_at` DATETIME(3) DEFAULT NULL COMMENT '实际开始时间,UTC',
133
+ `finished_at` DATETIME(3) DEFAULT NULL COMMENT '实际结束时间,UTC',
134
+ `delete_flag` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '删除标记:1-有效,0-已删除',
135
+ `created_at` DATETIME(3) NOT NULL COMMENT '创建时间,UTC',
136
+ `updated_at` DATETIME(3) NOT NULL COMMENT '最后更新时间,UTC',
137
+ PRIMARY KEY (`id`),
138
+ UNIQUE KEY `uk_scheduled_run_occurrence` (`task_id`, `scheduled_at`),
139
+ UNIQUE KEY `uk_scheduled_run_token_hash` (`execution_token_hash`),
140
+ KEY `idx_scheduled_run_task` (`task_id`, `delete_flag`, `scheduled_at`),
141
+ KEY `idx_scheduled_run_status` (`delete_flag`, `status`, `created_at`),
142
+ KEY `idx_scheduled_run_unread` (`delete_flag`, `status`, `read_flag`, `finished_at`),
143
+ KEY `idx_scheduled_run_conversation` (`result_conversation_id`),
144
+ CONSTRAINT `fk_scheduled_run_task` FOREIGN KEY (`task_id`)
145
+ REFERENCES `agent_scheduled_task` (`id`)
146
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
@@ -0,0 +1,170 @@
1
+ package com.union.control;
2
+
3
+ import com.fasterxml.jackson.databind.ObjectMapper;
4
+ import com.union.control.mapper.AgentExecutionMapper;
5
+ import com.union.control.mapper.ConversationMapper;
6
+ import com.union.control.service.impl.AgentExecutionServiceImpl;
7
+ import org.junit.Before;
8
+ import org.junit.Test;
9
+ import org.mockito.InOrder;
10
+
11
+ import java.util.ArrayList;
12
+ import java.util.Arrays;
13
+ import java.util.Collections;
14
+ import java.util.LinkedHashMap;
15
+ import java.util.List;
16
+ import java.util.Map;
17
+
18
+ import static org.mockito.Mockito.mock;
19
+ import static org.mockito.Mockito.inOrder;
20
+ import static org.mockito.Mockito.never;
21
+ import static org.mockito.Mockito.verify;
22
+ import static org.mockito.Mockito.when;
23
+ import static org.assertj.core.api.Assertions.assertThatThrownBy;
24
+
25
+ public class AgentExecutionServiceTest {
26
+ private AgentExecutionServiceImpl service;
27
+ private AgentExecutionMapper executions;
28
+ private ConversationMapper conversations;
29
+
30
+ @Before
31
+ public void setUp() {
32
+ executions = mock(AgentExecutionMapper.class);
33
+ conversations = mock(ConversationMapper.class);
34
+ service = new AgentExecutionServiceImpl(executions, conversations, new ObjectMapper());
35
+ when(conversations.requireOwned("thread-1", TestJson.USER_ID, true)).thenReturn(1L);
36
+ }
37
+
38
+ @Test
39
+ public void aguiRunLeavesFrontendFeatureValidationToPyApp() {
40
+ String body = "{\"threadId\":\"thread-1\",\"runId\":\"run-1\"," +
41
+ "\"messages\":[{\"id\":\"m1\",\"role\":\"user\",\"content\":\"hello\"}]," +
42
+ "\"state\":{},\"context\":[],\"forwardedProps\":{}," +
43
+ "\"tools\":[{\"name\":\"unsafe\"}]}";
44
+ when(conversations.requireOwnedActive("thread-1", TestJson.USER_ID)).thenReturn(1L);
45
+ when(executions.findExecution(TestJson.USER_ID, "thread-1", "run-1", false))
46
+ .thenReturn(Collections.singletonList(root("running", null)));
47
+
48
+ service.claimAguiRun(TestJson.request(body));
49
+
50
+ verify(executions).insertRootExecution("run-1", "thread-1", TestJson.USER_ID);
51
+ }
52
+
53
+ @Test
54
+ public void aguiRunRejectsAnExistingConversationOwnedByAnotherUser() {
55
+ String body = "{\"threadId\":\"foreign-thread\",\"runId\":\"run-1\"," +
56
+ "\"messages\":[],\"state\":{},\"context\":[],\"forwardedProps\":{}}";
57
+ when(conversations.requireOwnedActive("foreign-thread", TestJson.USER_ID))
58
+ .thenReturn(null);
59
+
60
+ assertThatThrownBy(() -> service.claimAguiRun(TestJson.request(body)))
61
+ .isInstanceOf(java.util.NoSuchElementException.class);
62
+ verify(executions, never()).insertRootExecution(
63
+ "run-1", "foreign-thread", TestJson.USER_ID);
64
+ }
65
+
66
+ @Test
67
+ public void completionPersistsTheStatusChosenByPyApp() {
68
+ when(executions.findExecution(TestJson.USER_ID, "thread-1", "run-1", true))
69
+ .thenReturn(Collections.singletonList(root("running", null)));
70
+ when(executions.finishExecution(7L, "sdk_terminal", null)).thenReturn(1);
71
+ Map<String, Object> rootExecution =
72
+ execution("run-1", null, "UnionCoordinatorAgent", null, null);
73
+ rootExecution.put("status", "sdk_terminal");
74
+ Map<String, Object> payload = completion(rootExecution);
75
+
76
+ service.completeRun(TestJson.request(payload));
77
+
78
+ verify(executions).finishExecution(7L, "sdk_terminal", null);
79
+ }
80
+
81
+ @Test
82
+ public void completionLocksTheConversationBeforeAllocatingMessageSequences() {
83
+ when(executions.findExecution(TestJson.USER_ID, "thread-1", "run-1", true))
84
+ .thenReturn(Collections.singletonList(root("running", null)));
85
+ when(executions.finishExecution(7L, "completed", null)).thenReturn(1);
86
+
87
+ service.completeRun(TestJson.request(completion(
88
+ execution("run-1", null, "KnowledgeAgent", null, null))));
89
+
90
+ InOrder order = inOrder(conversations, executions);
91
+ order.verify(conversations).requireOwned("thread-1", TestJson.USER_ID, true);
92
+ order.verify(executions).findExecution(
93
+ TestJson.USER_ID, "thread-1", "run-1", true);
94
+ order.verify(conversations).currentMessageSequence("thread-1");
95
+ }
96
+
97
+ @Test
98
+ public void completionSelectsTheRootAgentWithoutAnExtraCallback() {
99
+ when(executions.findExecution(TestJson.USER_ID, "thread-1", "run-1", true))
100
+ .thenReturn(Collections.singletonList(root("running", null)));
101
+ when(executions.finishExecution(7L, "completed", null)).thenReturn(1);
102
+ Map<String, Object> payload = completion(
103
+ execution("run-1", null, "KnowledgeAgent", null, null));
104
+
105
+ service.completeRun(TestJson.request(payload));
106
+
107
+ verify(executions).updateRootAgent(7L, "KnowledgeAgent");
108
+ verify(executions).finishExecution(7L, "completed", null);
109
+ }
110
+
111
+ @Test
112
+ public void completionDoesNotLimitMessageCount() {
113
+ when(executions.findExecution(TestJson.USER_ID, "thread-1", "run-1", true))
114
+ .thenReturn(Collections.singletonList(root("running", null)));
115
+ when(executions.finishExecution(7L, "completed", null)).thenReturn(1);
116
+ Map<String, Object> payload = completion(
117
+ execution("run-1", null, "UnionCoordinatorAgent", null, null));
118
+ List<Map<String, Object>> messages = new ArrayList<>();
119
+ for (int index = 0; index < 1001; index++) {
120
+ Map<String, Object> message = new LinkedHashMap<>();
121
+ message.put("id", "message-" + index);
122
+ message.put("role", "assistant");
123
+ message.put("content", "value");
124
+ Map<String, Object> envelope = new LinkedHashMap<>();
125
+ envelope.put("runId", "run-1");
126
+ envelope.put("message", message);
127
+ messages.add(envelope);
128
+ }
129
+ payload.put("messages", messages);
130
+
131
+ service.completeRun(TestJson.request(payload));
132
+
133
+ verify(conversations).touchConversation("thread-1", TestJson.USER_ID);
134
+ }
135
+
136
+ private static Map<String, Object> completion(Map<String, Object>... executions) {
137
+ Map<String, Object> payload = new LinkedHashMap<>();
138
+ payload.put("conversationId", "thread-1");
139
+ payload.put("runId", "run-1");
140
+ payload.put("executions", Arrays.asList(executions));
141
+ payload.put("messages", Collections.emptyList());
142
+ return payload;
143
+ }
144
+
145
+ private static Map<String, Object> execution(
146
+ String runId, String parentRunId, String agentName,
147
+ String callId, String task) {
148
+ Map<String, Object> execution = new LinkedHashMap<>();
149
+ execution.put("runId", runId);
150
+ execution.put("parentRunId", parentRunId);
151
+ execution.put("agentName", agentName);
152
+ execution.put("delegationToolCallId", callId);
153
+ execution.put("task", task);
154
+ execution.put("status", "completed");
155
+ execution.put("errorCode", null);
156
+ return execution;
157
+ }
158
+
159
+ private static Map<String, Object> root(String status, String errorCode) {
160
+ Map<String, Object> root = new LinkedHashMap<>();
161
+ root.put("id", 7L);
162
+ root.put("runId", "run-1");
163
+ root.put("conversationId", "thread-1");
164
+ root.put("parentExecutionId", null);
165
+ root.put("agentName", null);
166
+ root.put("status", status);
167
+ root.put("errorCode", errorCode);
168
+ return root;
169
+ }
170
+ }