pulsemcp-cms-admin-mcp-server 0.7.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ This server is built and tested on macOS with Claude Desktop. It should work wit
|
|
|
94
94
|
| `cleanup_good_jobs` | good_jobs | write | Clean up old background jobs by status and age. |
|
|
95
95
|
| `run_exam_for_mirror` | proctor | write | Run proctor exams against unofficial mirrors via Fly Machines. Returns truncated summary with `result_id`. |
|
|
96
96
|
| `get_exam_result` | proctor | read | Retrieve full untruncated exam results by `result_id`, with optional section/mirror filtering. |
|
|
97
|
-
| `save_results_for_mirror` | proctor | write | Save proctor exam results
|
|
97
|
+
| `save_results_for_mirror` | proctor | write | Save proctor exam results via `result_id` from `run_exam_for_mirror`. |
|
|
98
98
|
|
|
99
99
|
# Tool Groups
|
|
100
100
|
|
|
@@ -3,38 +3,19 @@ import { examResultStore, extractExamId, extractStatus } from '../exam-result-st
|
|
|
3
3
|
const PARAM_DESCRIPTIONS = {
|
|
4
4
|
mirror_id: 'The ID of the unofficial mirror to save results for',
|
|
5
5
|
runtime_id: 'The runtime ID that was used to run the exams',
|
|
6
|
-
result_id: 'The UUID returned by run_exam_for_mirror.
|
|
7
|
-
results: 'Array of exam results to save. Each result must include exam_id, status, and optional data. Only needed if result_id is not provided.',
|
|
8
|
-
exam_id: 'The exam identifier (e.g., "auth-check", "init-tools-list")',
|
|
9
|
-
status: 'The result status (e.g., "pass", "fail", "error", "skip")',
|
|
10
|
-
data: 'Optional detailed result data. Sensitive fields (tokens, secrets, passwords) will be automatically redacted before storage.',
|
|
6
|
+
result_id: 'The UUID returned by run_exam_for_mirror. The server retrieves the full result from the local file store automatically.',
|
|
11
7
|
};
|
|
12
|
-
const
|
|
13
|
-
exam_id: z.string().describe(PARAM_DESCRIPTIONS.exam_id),
|
|
14
|
-
status: z.string().describe(PARAM_DESCRIPTIONS.status),
|
|
15
|
-
data: z.record(z.unknown()).optional().describe(PARAM_DESCRIPTIONS.data),
|
|
16
|
-
});
|
|
17
|
-
const SaveResultsForMirrorSchema = z
|
|
18
|
-
.object({
|
|
8
|
+
const SaveResultsForMirrorSchema = z.object({
|
|
19
9
|
mirror_id: z.number().describe(PARAM_DESCRIPTIONS.mirror_id),
|
|
20
10
|
runtime_id: z.string().optional().describe(PARAM_DESCRIPTIONS.runtime_id),
|
|
21
|
-
result_id: z.string().uuid().
|
|
22
|
-
results: z.array(ResultSchema).optional().describe(PARAM_DESCRIPTIONS.results),
|
|
23
|
-
})
|
|
24
|
-
.refine((data) => data.result_id || (data.results && data.results.length > 0), {
|
|
25
|
-
message: 'Either result_id or a non-empty results array must be provided',
|
|
26
|
-
})
|
|
27
|
-
.refine((data) => !(data.result_id && data.results && data.results.length > 0), {
|
|
28
|
-
message: 'Provide either result_id or results, not both. Use result_id (preferred) to retrieve from the store, or results for direct submission.',
|
|
11
|
+
result_id: z.string().uuid().describe(PARAM_DESCRIPTIONS.result_id),
|
|
29
12
|
});
|
|
30
13
|
export function saveResultsForMirror(_server, clientFactory) {
|
|
31
14
|
return {
|
|
32
15
|
name: 'save_results_for_mirror',
|
|
33
16
|
description: `Save proctor exam results for an unofficial mirror.
|
|
34
17
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
**Fallback**: Pass results directly (as before) if result_id is not available.
|
|
18
|
+
Pass the \`result_id\` returned by \`run_exam_for_mirror\`. The full result is retrieved from the local file store server-side — no need to pass the large results payload through the LLM context.
|
|
38
19
|
|
|
39
20
|
Results are sanitized server-side to redact sensitive data (OAuth tokens, client secrets, passwords, etc.) before being persisted.
|
|
40
21
|
|
|
@@ -54,94 +35,85 @@ Typical workflow:
|
|
|
54
35
|
format: 'uuid',
|
|
55
36
|
description: PARAM_DESCRIPTIONS.result_id,
|
|
56
37
|
},
|
|
57
|
-
results: {
|
|
58
|
-
type: 'array',
|
|
59
|
-
items: {
|
|
60
|
-
type: 'object',
|
|
61
|
-
properties: {
|
|
62
|
-
exam_id: { type: 'string', description: PARAM_DESCRIPTIONS.exam_id },
|
|
63
|
-
status: { type: 'string', description: PARAM_DESCRIPTIONS.status },
|
|
64
|
-
data: {
|
|
65
|
-
type: 'object',
|
|
66
|
-
additionalProperties: true,
|
|
67
|
-
description: PARAM_DESCRIPTIONS.data,
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
required: ['exam_id', 'status'],
|
|
71
|
-
},
|
|
72
|
-
description: PARAM_DESCRIPTIONS.results,
|
|
73
|
-
},
|
|
74
38
|
},
|
|
75
|
-
required: ['mirror_id'],
|
|
39
|
+
required: ['mirror_id', 'result_id'],
|
|
76
40
|
},
|
|
77
41
|
handler: async (args) => {
|
|
78
42
|
const validatedArgs = SaveResultsForMirrorSchema.parse(args);
|
|
79
43
|
const client = clientFactory();
|
|
80
44
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
isError: true,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
// Extract exam_result lines from stored data.
|
|
98
|
-
// The exam_id may live at the top level of the stream line OR inside
|
|
99
|
-
// line.data (the actual result payload). Prefer the data payload to
|
|
100
|
-
// avoid reading from potentially incomplete display metadata.
|
|
101
|
-
//
|
|
102
|
-
// The real proctor API returns line.data as a metadata wrapper:
|
|
103
|
-
// { mirror_id, exam_id, status, result: { status, output: {...} } }
|
|
104
|
-
// The actual output lives inside line.data.result. When we pass the
|
|
105
|
-
// entire line.data as `data`, the output ends up nested too deeply
|
|
106
|
-
// (result.data.result.output) and the backend saves empty output.
|
|
107
|
-
// Use line.data.result when present so that `output` is at the
|
|
108
|
-
// expected depth (result.data.output).
|
|
109
|
-
results = stored.lines
|
|
110
|
-
.filter((line) => line.type === 'exam_result')
|
|
111
|
-
.map((line) => {
|
|
112
|
-
const data = line.data;
|
|
113
|
-
// Prefer the nested result object (contains output, input, etc.)
|
|
114
|
-
// over the full data wrapper (contains metadata like mirror_id)
|
|
115
|
-
const resultData = data?.result && typeof data.result === 'object' && !Array.isArray(data.result)
|
|
116
|
-
? data.result
|
|
117
|
-
: data;
|
|
118
|
-
return {
|
|
119
|
-
exam_id: extractExamId(line),
|
|
120
|
-
status: extractStatus(line),
|
|
121
|
-
...(resultData ? { data: resultData } : {}),
|
|
122
|
-
};
|
|
123
|
-
});
|
|
124
|
-
if (!runtimeId) {
|
|
125
|
-
runtimeId = stored.runtime_id;
|
|
126
|
-
}
|
|
45
|
+
const stored = examResultStore.get(validatedArgs.result_id);
|
|
46
|
+
if (!stored) {
|
|
47
|
+
return {
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: 'text',
|
|
51
|
+
text: `No stored result found for result_id "${validatedArgs.result_id}". The result file may have been cleaned up or the /tmp directory cleared. Re-run run_exam_for_mirror to generate a new result.`,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
isError: true,
|
|
55
|
+
};
|
|
127
56
|
}
|
|
128
|
-
|
|
57
|
+
// Extract exam_result lines from stored data.
|
|
58
|
+
//
|
|
59
|
+
// The real proctor API returns a deeply nested structure:
|
|
60
|
+
// line.data = {
|
|
61
|
+
// mirror_id, server_slug, exam_id, ...,
|
|
62
|
+
// result: { ← envelope
|
|
63
|
+
// exam_id, machine_id, status,
|
|
64
|
+
// result: { ← actual payload
|
|
65
|
+
// input: {...}, output: {...}, processedBy: {...}
|
|
66
|
+
// },
|
|
67
|
+
// error, logs
|
|
68
|
+
// }
|
|
69
|
+
// }
|
|
70
|
+
//
|
|
71
|
+
// The PulseMCP API expects the actual payload { input, output,
|
|
72
|
+
// processedBy } at the top level of the saved results column.
|
|
73
|
+
// We must unwrap through data.result.result to reach it.
|
|
74
|
+
const results = stored.lines
|
|
75
|
+
.filter((line) => line.type === 'exam_result')
|
|
76
|
+
.map((line) => {
|
|
77
|
+
const data = line.data;
|
|
78
|
+
// Unwrap nested result objects to find the exam payload
|
|
79
|
+
// containing { input, output, processedBy }.
|
|
80
|
+
let resultData = data;
|
|
81
|
+
// Level 1: data.result (envelope with exam_id, machine_id, logs, etc.)
|
|
82
|
+
if (resultData?.result &&
|
|
83
|
+
typeof resultData.result === 'object' &&
|
|
84
|
+
!Array.isArray(resultData.result)) {
|
|
85
|
+
resultData = resultData.result;
|
|
86
|
+
// Level 2: data.result.result (actual payload with input, output, processedBy)
|
|
87
|
+
if (resultData.result &&
|
|
88
|
+
typeof resultData.result === 'object' &&
|
|
89
|
+
!Array.isArray(resultData.result)) {
|
|
90
|
+
resultData = resultData.result;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
exam_id: extractExamId(line),
|
|
95
|
+
status: extractStatus(line),
|
|
96
|
+
...(resultData ? { data: resultData } : {}),
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
if (results.length === 0) {
|
|
129
100
|
return {
|
|
130
101
|
content: [
|
|
131
102
|
{
|
|
132
103
|
type: 'text',
|
|
133
|
-
text: 'No exam results
|
|
104
|
+
text: 'No exam results found in the stored result. The stored data may not contain any exam_result lines.',
|
|
134
105
|
},
|
|
135
106
|
],
|
|
136
107
|
isError: true,
|
|
137
108
|
};
|
|
138
109
|
}
|
|
110
|
+
const runtimeId = validatedArgs.runtime_id || stored.runtime_id;
|
|
139
111
|
if (!runtimeId) {
|
|
140
112
|
return {
|
|
141
113
|
content: [
|
|
142
114
|
{
|
|
143
115
|
type: 'text',
|
|
144
|
-
text: 'runtime_id is required. Provide it directly or
|
|
116
|
+
text: 'runtime_id is required. Provide it directly or ensure the stored result includes it.',
|
|
145
117
|
},
|
|
146
118
|
],
|
|
147
119
|
isError: true,
|
|
@@ -155,9 +127,7 @@ Typical workflow:
|
|
|
155
127
|
let content = `**Proctor Results Saved**\n\n`;
|
|
156
128
|
content += `Mirror ID: ${validatedArgs.mirror_id}\n`;
|
|
157
129
|
content += `Runtime: ${runtimeId}\n`;
|
|
158
|
-
|
|
159
|
-
content += `Result ID: ${validatedArgs.result_id}\n`;
|
|
160
|
-
}
|
|
130
|
+
content += `Result ID: ${validatedArgs.result_id}\n`;
|
|
161
131
|
content += '\n';
|
|
162
132
|
if (response.saved.length > 0) {
|
|
163
133
|
content += `**Successfully Saved (${response.saved.length}):**\n`;
|
|
@@ -178,7 +148,7 @@ Typical workflow:
|
|
|
178
148
|
}
|
|
179
149
|
}
|
|
180
150
|
// Clean up stored result after successful save (all results persisted)
|
|
181
|
-
if (
|
|
151
|
+
if (response.errors.length === 0) {
|
|
182
152
|
examResultStore.delete(validatedArgs.result_id);
|
|
183
153
|
}
|
|
184
154
|
return { content: [{ type: 'text', text: content.trim() }] };
|
package/package.json
CHANGED
|
@@ -17,30 +17,7 @@ export declare function saveResultsForMirror(_server: Server, clientFactory: Cli
|
|
|
17
17
|
result_id: {
|
|
18
18
|
type: string;
|
|
19
19
|
format: string;
|
|
20
|
-
description: "The UUID returned by run_exam_for_mirror.
|
|
21
|
-
};
|
|
22
|
-
results: {
|
|
23
|
-
type: string;
|
|
24
|
-
items: {
|
|
25
|
-
type: string;
|
|
26
|
-
properties: {
|
|
27
|
-
exam_id: {
|
|
28
|
-
type: string;
|
|
29
|
-
description: "The exam identifier (e.g., \"auth-check\", \"init-tools-list\")";
|
|
30
|
-
};
|
|
31
|
-
status: {
|
|
32
|
-
type: string;
|
|
33
|
-
description: "The result status (e.g., \"pass\", \"fail\", \"error\", \"skip\")";
|
|
34
|
-
};
|
|
35
|
-
data: {
|
|
36
|
-
type: string;
|
|
37
|
-
additionalProperties: boolean;
|
|
38
|
-
description: "Optional detailed result data. Sensitive fields (tokens, secrets, passwords) will be automatically redacted before storage.";
|
|
39
|
-
};
|
|
40
|
-
};
|
|
41
|
-
required: string[];
|
|
42
|
-
};
|
|
43
|
-
description: "Array of exam results to save. Each result must include exam_id, status, and optional data. Only needed if result_id is not provided.";
|
|
20
|
+
description: "The UUID returned by run_exam_for_mirror. The server retrieves the full result from the local file store automatically.";
|
|
44
21
|
};
|
|
45
22
|
};
|
|
46
23
|
required: string[];
|
|
@@ -3,38 +3,19 @@ import { examResultStore, extractExamId, extractStatus } from '../exam-result-st
|
|
|
3
3
|
const PARAM_DESCRIPTIONS = {
|
|
4
4
|
mirror_id: 'The ID of the unofficial mirror to save results for',
|
|
5
5
|
runtime_id: 'The runtime ID that was used to run the exams',
|
|
6
|
-
result_id: 'The UUID returned by run_exam_for_mirror.
|
|
7
|
-
results: 'Array of exam results to save. Each result must include exam_id, status, and optional data. Only needed if result_id is not provided.',
|
|
8
|
-
exam_id: 'The exam identifier (e.g., "auth-check", "init-tools-list")',
|
|
9
|
-
status: 'The result status (e.g., "pass", "fail", "error", "skip")',
|
|
10
|
-
data: 'Optional detailed result data. Sensitive fields (tokens, secrets, passwords) will be automatically redacted before storage.',
|
|
6
|
+
result_id: 'The UUID returned by run_exam_for_mirror. The server retrieves the full result from the local file store automatically.',
|
|
11
7
|
};
|
|
12
|
-
const
|
|
13
|
-
exam_id: z.string().describe(PARAM_DESCRIPTIONS.exam_id),
|
|
14
|
-
status: z.string().describe(PARAM_DESCRIPTIONS.status),
|
|
15
|
-
data: z.record(z.unknown()).optional().describe(PARAM_DESCRIPTIONS.data),
|
|
16
|
-
});
|
|
17
|
-
const SaveResultsForMirrorSchema = z
|
|
18
|
-
.object({
|
|
8
|
+
const SaveResultsForMirrorSchema = z.object({
|
|
19
9
|
mirror_id: z.number().describe(PARAM_DESCRIPTIONS.mirror_id),
|
|
20
10
|
runtime_id: z.string().optional().describe(PARAM_DESCRIPTIONS.runtime_id),
|
|
21
|
-
result_id: z.string().uuid().
|
|
22
|
-
results: z.array(ResultSchema).optional().describe(PARAM_DESCRIPTIONS.results),
|
|
23
|
-
})
|
|
24
|
-
.refine((data) => data.result_id || (data.results && data.results.length > 0), {
|
|
25
|
-
message: 'Either result_id or a non-empty results array must be provided',
|
|
26
|
-
})
|
|
27
|
-
.refine((data) => !(data.result_id && data.results && data.results.length > 0), {
|
|
28
|
-
message: 'Provide either result_id or results, not both. Use result_id (preferred) to retrieve from the store, or results for direct submission.',
|
|
11
|
+
result_id: z.string().uuid().describe(PARAM_DESCRIPTIONS.result_id),
|
|
29
12
|
});
|
|
30
13
|
export function saveResultsForMirror(_server, clientFactory) {
|
|
31
14
|
return {
|
|
32
15
|
name: 'save_results_for_mirror',
|
|
33
16
|
description: `Save proctor exam results for an unofficial mirror.
|
|
34
17
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
**Fallback**: Pass results directly (as before) if result_id is not available.
|
|
18
|
+
Pass the \`result_id\` returned by \`run_exam_for_mirror\`. The full result is retrieved from the local file store server-side — no need to pass the large results payload through the LLM context.
|
|
38
19
|
|
|
39
20
|
Results are sanitized server-side to redact sensitive data (OAuth tokens, client secrets, passwords, etc.) before being persisted.
|
|
40
21
|
|
|
@@ -54,94 +35,85 @@ Typical workflow:
|
|
|
54
35
|
format: 'uuid',
|
|
55
36
|
description: PARAM_DESCRIPTIONS.result_id,
|
|
56
37
|
},
|
|
57
|
-
results: {
|
|
58
|
-
type: 'array',
|
|
59
|
-
items: {
|
|
60
|
-
type: 'object',
|
|
61
|
-
properties: {
|
|
62
|
-
exam_id: { type: 'string', description: PARAM_DESCRIPTIONS.exam_id },
|
|
63
|
-
status: { type: 'string', description: PARAM_DESCRIPTIONS.status },
|
|
64
|
-
data: {
|
|
65
|
-
type: 'object',
|
|
66
|
-
additionalProperties: true,
|
|
67
|
-
description: PARAM_DESCRIPTIONS.data,
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
required: ['exam_id', 'status'],
|
|
71
|
-
},
|
|
72
|
-
description: PARAM_DESCRIPTIONS.results,
|
|
73
|
-
},
|
|
74
38
|
},
|
|
75
|
-
required: ['mirror_id'],
|
|
39
|
+
required: ['mirror_id', 'result_id'],
|
|
76
40
|
},
|
|
77
41
|
handler: async (args) => {
|
|
78
42
|
const validatedArgs = SaveResultsForMirrorSchema.parse(args);
|
|
79
43
|
const client = clientFactory();
|
|
80
44
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
isError: true,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
// Extract exam_result lines from stored data.
|
|
98
|
-
// The exam_id may live at the top level of the stream line OR inside
|
|
99
|
-
// line.data (the actual result payload). Prefer the data payload to
|
|
100
|
-
// avoid reading from potentially incomplete display metadata.
|
|
101
|
-
//
|
|
102
|
-
// The real proctor API returns line.data as a metadata wrapper:
|
|
103
|
-
// { mirror_id, exam_id, status, result: { status, output: {...} } }
|
|
104
|
-
// The actual output lives inside line.data.result. When we pass the
|
|
105
|
-
// entire line.data as `data`, the output ends up nested too deeply
|
|
106
|
-
// (result.data.result.output) and the backend saves empty output.
|
|
107
|
-
// Use line.data.result when present so that `output` is at the
|
|
108
|
-
// expected depth (result.data.output).
|
|
109
|
-
results = stored.lines
|
|
110
|
-
.filter((line) => line.type === 'exam_result')
|
|
111
|
-
.map((line) => {
|
|
112
|
-
const data = line.data;
|
|
113
|
-
// Prefer the nested result object (contains output, input, etc.)
|
|
114
|
-
// over the full data wrapper (contains metadata like mirror_id)
|
|
115
|
-
const resultData = data?.result && typeof data.result === 'object' && !Array.isArray(data.result)
|
|
116
|
-
? data.result
|
|
117
|
-
: data;
|
|
118
|
-
return {
|
|
119
|
-
exam_id: extractExamId(line),
|
|
120
|
-
status: extractStatus(line),
|
|
121
|
-
...(resultData ? { data: resultData } : {}),
|
|
122
|
-
};
|
|
123
|
-
});
|
|
124
|
-
if (!runtimeId) {
|
|
125
|
-
runtimeId = stored.runtime_id;
|
|
126
|
-
}
|
|
45
|
+
const stored = examResultStore.get(validatedArgs.result_id);
|
|
46
|
+
if (!stored) {
|
|
47
|
+
return {
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: 'text',
|
|
51
|
+
text: `No stored result found for result_id "${validatedArgs.result_id}". The result file may have been cleaned up or the /tmp directory cleared. Re-run run_exam_for_mirror to generate a new result.`,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
isError: true,
|
|
55
|
+
};
|
|
127
56
|
}
|
|
128
|
-
|
|
57
|
+
// Extract exam_result lines from stored data.
|
|
58
|
+
//
|
|
59
|
+
// The real proctor API returns a deeply nested structure:
|
|
60
|
+
// line.data = {
|
|
61
|
+
// mirror_id, server_slug, exam_id, ...,
|
|
62
|
+
// result: { ← envelope
|
|
63
|
+
// exam_id, machine_id, status,
|
|
64
|
+
// result: { ← actual payload
|
|
65
|
+
// input: {...}, output: {...}, processedBy: {...}
|
|
66
|
+
// },
|
|
67
|
+
// error, logs
|
|
68
|
+
// }
|
|
69
|
+
// }
|
|
70
|
+
//
|
|
71
|
+
// The PulseMCP API expects the actual payload { input, output,
|
|
72
|
+
// processedBy } at the top level of the saved results column.
|
|
73
|
+
// We must unwrap through data.result.result to reach it.
|
|
74
|
+
const results = stored.lines
|
|
75
|
+
.filter((line) => line.type === 'exam_result')
|
|
76
|
+
.map((line) => {
|
|
77
|
+
const data = line.data;
|
|
78
|
+
// Unwrap nested result objects to find the exam payload
|
|
79
|
+
// containing { input, output, processedBy }.
|
|
80
|
+
let resultData = data;
|
|
81
|
+
// Level 1: data.result (envelope with exam_id, machine_id, logs, etc.)
|
|
82
|
+
if (resultData?.result &&
|
|
83
|
+
typeof resultData.result === 'object' &&
|
|
84
|
+
!Array.isArray(resultData.result)) {
|
|
85
|
+
resultData = resultData.result;
|
|
86
|
+
// Level 2: data.result.result (actual payload with input, output, processedBy)
|
|
87
|
+
if (resultData.result &&
|
|
88
|
+
typeof resultData.result === 'object' &&
|
|
89
|
+
!Array.isArray(resultData.result)) {
|
|
90
|
+
resultData = resultData.result;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
exam_id: extractExamId(line),
|
|
95
|
+
status: extractStatus(line),
|
|
96
|
+
...(resultData ? { data: resultData } : {}),
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
if (results.length === 0) {
|
|
129
100
|
return {
|
|
130
101
|
content: [
|
|
131
102
|
{
|
|
132
103
|
type: 'text',
|
|
133
|
-
text: 'No exam results
|
|
104
|
+
text: 'No exam results found in the stored result. The stored data may not contain any exam_result lines.',
|
|
134
105
|
},
|
|
135
106
|
],
|
|
136
107
|
isError: true,
|
|
137
108
|
};
|
|
138
109
|
}
|
|
110
|
+
const runtimeId = validatedArgs.runtime_id || stored.runtime_id;
|
|
139
111
|
if (!runtimeId) {
|
|
140
112
|
return {
|
|
141
113
|
content: [
|
|
142
114
|
{
|
|
143
115
|
type: 'text',
|
|
144
|
-
text: 'runtime_id is required. Provide it directly or
|
|
116
|
+
text: 'runtime_id is required. Provide it directly or ensure the stored result includes it.',
|
|
145
117
|
},
|
|
146
118
|
],
|
|
147
119
|
isError: true,
|
|
@@ -155,9 +127,7 @@ Typical workflow:
|
|
|
155
127
|
let content = `**Proctor Results Saved**\n\n`;
|
|
156
128
|
content += `Mirror ID: ${validatedArgs.mirror_id}\n`;
|
|
157
129
|
content += `Runtime: ${runtimeId}\n`;
|
|
158
|
-
|
|
159
|
-
content += `Result ID: ${validatedArgs.result_id}\n`;
|
|
160
|
-
}
|
|
130
|
+
content += `Result ID: ${validatedArgs.result_id}\n`;
|
|
161
131
|
content += '\n';
|
|
162
132
|
if (response.saved.length > 0) {
|
|
163
133
|
content += `**Successfully Saved (${response.saved.length}):**\n`;
|
|
@@ -178,7 +148,7 @@ Typical workflow:
|
|
|
178
148
|
}
|
|
179
149
|
}
|
|
180
150
|
// Clean up stored result after successful save (all results persisted)
|
|
181
|
-
if (
|
|
151
|
+
if (response.errors.length === 0) {
|
|
182
152
|
examResultStore.delete(validatedArgs.result_id);
|
|
183
153
|
}
|
|
184
154
|
return { content: [{ type: 'text', text: content.trim() }] };
|