From ab1f5069adcb4aa7dda6be4831aa0b203a56ba68 Mon Sep 17 00:00:00 2001 From: mateaix <7333791@qq.com> Date: Tue, 15 Sep 2026 01:34:45 +0800 Subject: [PATCH] fix(goal): refresh terminal status in JSON requirements panel --- .../service/GoalJsonAcceptanceService.java | 4 +-- .../docs/en/managed-json-acceptance.md | 4 +-- .../docs/zh/managed-json-acceptance.md | 4 +-- .../GoalJsonHttpRuntimeIntegrationTest.java | 4 ++- mateclaw-ui/src/api/goalJsonAcceptance.ts | 2 +- .../goal/GoalJsonAcceptancePanel.vue | 8 +++-- .../__tests__/GoalJsonAcceptancePanel.test.ts | 31 ++++++++++++++----- 7 files changed, 38 insertions(+), 19 deletions(-) diff --git a/mateclaw-server/src/main/java/vip/mate/goal/service/GoalJsonAcceptanceService.java b/mateclaw-server/src/main/java/vip/mate/goal/service/GoalJsonAcceptanceService.java index 0247d822..b7028059 100644 --- a/mateclaw-server/src/main/java/vip/mate/goal/service/GoalJsonAcceptanceService.java +++ b/mateclaw-server/src/main/java/vip/mate/goal/service/GoalJsonAcceptanceService.java @@ -25,13 +25,13 @@ public class GoalJsonAcceptanceService { public record ConfigureRequest(Long expectedRevision, String artifactSlot, List requiredFields) { } public record Requirement(String criterionKey, String artifactSlot, long revision, List requiredFields, String configuredBy) { } - public record View(boolean required, List requirements) { } + public record View(boolean required, String status, List requirements) { } record GoalScope(long id, String conversationId, long workspaceId, long agentId, String status, boolean required) { } @Transactional public View get(Long goalId, String username) { GoalScope goal = authorizedGoal(goalId, username, true); - return new View(goal.required(), requirements(goalId)); + return new View(goal.required(), goal.status(), requirements(goalId)); } @Transactional diff --git a/mateclaw-server/src/main/resources/docs/en/managed-json-acceptance.md b/mateclaw-server/src/main/resources/docs/en/managed-json-acceptance.md index f94e9d18..9ab4e221 100644 --- a/mateclaw-server/src/main/resources/docs/en/managed-json-acceptance.md +++ b/mateclaw-server/src/main/resources/docs/en/managed-json-acceptance.md @@ -4,13 +4,13 @@ Open a conversation with an existing Goal, click the Goals button in its header, User configuration, independent managed versions, binding checks and the shared completion gate are connected. Every current requirement needs a matching valid binding before a selected goal can complete under its existing completion rules. Automatic evaluation, explicit completeGoal and retries share that gate. Unselected goals retain existing behavior. -The conversation Goals panel includes paused and terminal goals, loading 20 at a time with an option to load older records. Paused goals still allow requirement edits, publication and checks; terminal goals only expose existing requirements and content. Closing the panel, switching conversations or leaving the page clears its contents, and a failed refresh clears the old list. Reading history does not resume execution. +The conversation Goals panel includes paused and terminal goals, loading 20 at a time with an option to load older records. Paused goals still allow requirement edits, publication and checks; terminal goals only expose existing requirements and content. Closing the panel, switching conversations or leaving the page clears its contents, and a failed refresh clears the old list. Reading history does not resume execution. Reloading requirements also refreshes the Goal status, so a goal completed since the list was loaded becomes read-only in the acceptance panel. ## Managed version API Prefix: `/api/v1/goals/{goalId}/json-acceptance`. An enabled account with conversation-owner or administrator permission is required. Preserve IDs, revisions and generations as strings in clients. -- `GET /`: read required mode and requirements. +- `GET /`: read required mode, current Goal status and requirements. - `PUT /requirements/{criterionKey}`: send `expectedRevision`, `artifactSlot` and `requiredFields`; use revision `0` for a new requirement. - `GET /artifacts`: list required slots and current versions; an empty slot has generation `0`. - `POST /artifacts/{slot}`: send `expectedGeneration` and `jsonContent` (a string containing the original JSON body) to append a version and atomically advance the slot. diff --git a/mateclaw-server/src/main/resources/docs/zh/managed-json-acceptance.md b/mateclaw-server/src/main/resources/docs/zh/managed-json-acceptance.md index 24b9c94a..69576dc2 100644 --- a/mateclaw-server/src/main/resources/docs/zh/managed-json-acceptance.md +++ b/mateclaw-server/src/main/resources/docs/zh/managed-json-acceptance.md @@ -4,13 +4,13 @@ 当前已接通用户配置、独立受管版本、绑定检查及共享完成检查。选中模式后,所有当前要求必须具有匹配的有效绑定,才可在既有完成规则满足时完成;自动评估、显式 completeGoal 和重试均使用同一完成检查。未选中的 Goal 保持既有行为。 -当前会话的“目标”面板也保留暂停、已完成等历史目标,每次读取20项,可加载更早记录。暂停目标仍可修订要求、发布和检查;终态目标只能查看要求与已有正文。关闭面板、切换会话或离开页面后清除面板内容;刷新失败时清除旧列表。读取历史不会恢复目标运行。 +当前会话的“目标”面板也保留暂停、已完成等历史目标,每次读取20项,可加载更早记录。暂停目标仍可修订要求、发布和检查;终态目标只能查看要求与已有正文。关闭面板、切换会话或离开页面后清除面板内容;刷新失败时清除旧列表。读取历史不会恢复目标运行。刷新要求也会刷新 Goal 状态,因此列表加载后才完成的目标会在验收面板切换为只读。 ## 受管版本接口 接口前缀 `/api/v1/goals/{goalId}/json-acceptance`,需要启用账户及对话所有者或管理员权限。ID、revision 和 generation 在响应中使用字符串,客户端应原样保留。 -- `GET /`:读取启用状态和要求。 +- `GET /`:读取启用状态、Goal 当前状态和要求。 - `PUT /requirements/{criterionKey}`:提交 `expectedRevision`、`artifactSlot`、`requiredFields`。新要求的 revision 为 `0`。 - `GET /artifacts`:列出当前要求使用的槽及当前版本。空槽 generation 为 `0`。 - `POST /artifacts/{slot}`:提交 `expectedGeneration` 和 `jsonContent`(包含原始 JSON 正文的字符串),原子追加新版本并推进槽。 diff --git a/mateclaw-server/src/test/java/vip/mate/goal/GoalJsonHttpRuntimeIntegrationTest.java b/mateclaw-server/src/test/java/vip/mate/goal/GoalJsonHttpRuntimeIntegrationTest.java index 04e95ba0..7bc887f6 100644 --- a/mateclaw-server/src/test/java/vip/mate/goal/GoalJsonHttpRuntimeIntegrationTest.java +++ b/mateclaw-server/src/test/java/vip/mate/goal/GoalJsonHttpRuntimeIntegrationTest.java @@ -248,6 +248,8 @@ class GoalJsonHttpRuntimeIntegrationTest { assertTrue(calls.get() >= (accepted ? 6 : 2) && calls.get() <= (accepted ? 10 : 4), "Bounded offline model calls: " + calls.get()); if (!accepted) assertEquals(recovered ? 1 : 0, jdbc.queryForObject("SELECT COUNT(*) FROM mate_goal_json_artifact WHERE goal_id=?", Integer.class, goal.getId())); + JsonNode currentRequirements = request("GET", "/api/v1/goals/" + goal.getId() + "/json-acceptance", token, null); + assertEquals(accepted ? "completed" : "active", currentRequirements.path("data").path("status").asText()); verify(modelFactory, atLeastOnce()).buildFor(any(), any()); } @@ -273,7 +275,7 @@ class GoalJsonHttpRuntimeIntegrationTest { .timeout(Duration.ofSeconds(45)).header("Content-Type", "application/json").header("X-Workspace-Id", "1"); if (token != null) builder.header("Authorization", "Bearer " + token); var response = HttpClient.newHttpClient().send(builder.method(method, - HttpRequest.BodyPublishers.ofString(json.writeValueAsString(body))).build(), HttpResponse.BodyHandlers.ofString()); + (body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(json.writeValueAsString(body)))).build(), HttpResponse.BodyHandlers.ofString()); assertEquals(200, response.statusCode(), response.body()); return response.body(); } diff --git a/mateclaw-ui/src/api/goalJsonAcceptance.ts b/mateclaw-ui/src/api/goalJsonAcceptance.ts index 912145f9..7e58005b 100644 --- a/mateclaw-ui/src/api/goalJsonAcceptance.ts +++ b/mateclaw-ui/src/api/goalJsonAcceptance.ts @@ -7,7 +7,7 @@ export interface GoalJsonRequirement { requiredFields: string[] configuredBy: string } -export interface GoalJsonAcceptanceView { required: boolean; requirements: GoalJsonRequirement[] } +export interface GoalJsonAcceptanceView { required: boolean; status: string; requirements: GoalJsonRequirement[] } export interface ConfigureJsonRequirement { expectedRevision: string; artifactSlot: string; requiredFields: string[] } export interface ManagedJsonArtifact { artifactId: string; artifactSlot: string; generation: string; sha256: string; byteLength: number diff --git a/mateclaw-ui/src/components/goal/GoalJsonAcceptancePanel.vue b/mateclaw-ui/src/components/goal/GoalJsonAcceptancePanel.vue index 5e8840af..dbfc9db0 100644 --- a/mateclaw-ui/src/components/goal/GoalJsonAcceptancePanel.vue +++ b/mateclaw-ui/src/components/goal/GoalJsonAcceptancePanel.vue @@ -17,7 +17,8 @@ const slot = ref('') const fields = ref('') const revision = ref('0') let generation = 0 -const editable = computed(() => ['active', 'paused'].includes(props.status)) +const currentStatus = computed(() => ['active', 'paused'].includes(props.status) ? view.value?.status : props.status) +const editable = computed(() => !!view.value && ['active', 'paused'].includes(currentStatus.value ?? '')) const busy = computed(() => loading.value || saving.value) function resetForm() { key.value = ''; slot.value = ''; fields.value = ''; revision.value = '0'; conflict.value = false } @@ -76,7 +77,7 @@ async function save() { expectedRevision: revision.value, artifactSlot: slot.value, requiredFields: required, }) if (request !== generation || !view.value) return - view.value = { required: true, requirements: [...view.value.requirements.filter(r => r.criterionKey !== data.criterionKey), data] + view.value = { required: true, status: view.value.status, requirements: [...view.value.requirements.filter(r => r.criterionKey !== data.criterionKey), data] .sort((a, b) => a.criterionKey.localeCompare(b.criterionKey)) } resetForm() } catch (failure) { @@ -105,6 +106,7 @@ onBeforeUnmount(() => { generation++ })

{{ t(error) }}

{{ t('common.loading') }}