mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-10 17:29:41 +02:00
fix(doctor): resolve CLIs via searchDirs, single-flight runs, admin-gate the group (#536 review)
- doctor probes each CLI's discovery.searchDirs when which misses and runs --version on the resolved path, so a service with a minimal PATH no longer reports installed CLIs as missing - GET /api/doctor shares one in-flight run per category - Diagnostics group hidden from non-admins in multi-user mode (_applyDoctorAdminGate) - 500 uses INTERNAL_ERROR; a killed child reports 'timed out after 30 s' - browser test blocks service workers so page.route() is reliable - wiki: Diagnostics sentence Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JrzFKEdBLwVfu6ev2ZscJS
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
1b89d7a387
commit
db9a39405b
@@ -422,6 +422,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
this._mcpSyncSavedOn = settings.mcpSyncEnabled === true;
|
||||
document.getElementById('appSettingsMcpSync').checked = this._mcpSyncSavedOn;
|
||||
this.applyMcpSyncVisibility();
|
||||
this._applyDoctorAdminGate();
|
||||
this.loadWebhook();
|
||||
// Read My Mind: synced, default OFF (opt-in; capture + prediction cost real tokens).
|
||||
document.getElementById('appSettingsReadMyMind').checked = settings.readMyMindEnabled === true;
|
||||
@@ -1192,6 +1193,18 @@ Object.assign(CodemanApp.prototype, {
|
||||
group.style.display = me.multiUser && me.role !== 'admin' ? 'none' : '';
|
||||
},
|
||||
|
||||
/**
|
||||
* GET /api/doctor is admin-only in multi-user mode (it names install paths on the host), so a
|
||||
* non-admin gets no Diagnostics group instead of a button that can only answer 403. Also
|
||||
* wired to `codeman:me` for the same late-resolving role as the groups above.
|
||||
*/
|
||||
_applyDoctorAdminGate() {
|
||||
const group = document.getElementById('doctorGroup');
|
||||
if (!group) return;
|
||||
const me = window.__codemanUser || {};
|
||||
group.style.display = me.multiUser && me.role !== 'admin' ? 'none' : '';
|
||||
},
|
||||
|
||||
/** Preview (apply=false) or run (apply=true) the MCP server sync across enabled CLIs. */
|
||||
async mcpSync(apply) {
|
||||
const out = this.$('mcpSyncResult');
|
||||
@@ -4434,4 +4447,5 @@ document.addEventListener?.('codeman:me', () => {
|
||||
window.app?._applyCustomModelAdminGate?.();
|
||||
window.app?._applyCliManagementAdminGate?.();
|
||||
window.app?._applyMcpSyncAdminGate?.();
|
||||
window.app?._applyDoctorAdminGate?.();
|
||||
});
|
||||
|
||||
@@ -47,6 +47,9 @@ export const defaultDoctorRunner: DoctorRunner = (category) =>
|
||||
args,
|
||||
{ timeout: DOCTOR_TIMEOUT_MS, maxBuffer: 1024 * 1024, env: process.env },
|
||||
(err, stdout) => {
|
||||
if (err && (err as { killed?: boolean }).killed) {
|
||||
return reject(new Error(`timed out after ${DOCTOR_TIMEOUT_MS / 1000} s`));
|
||||
}
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(stdout);
|
||||
if (isReport(parsed)) return resolve(parsed);
|
||||
@@ -59,6 +62,18 @@ export const defaultDoctorRunner: DoctorRunner = (category) =>
|
||||
});
|
||||
|
||||
export function registerDoctorRoutes(app: FastifyInstance, runner: DoctorRunner = defaultDoctorRunner): void {
|
||||
// Each run forks a full Node process, so two tabs or a script must not stack them: callers
|
||||
// asking for the same category while one is in flight share its promise.
|
||||
const inFlight = new Map<string, Promise<DependencyReportJson>>();
|
||||
const runShared = (category?: string): Promise<DependencyReportJson> => {
|
||||
const key = category ?? '';
|
||||
let running = inFlight.get(key);
|
||||
if (!running) {
|
||||
running = runner(category).finally(() => inFlight.delete(key));
|
||||
inFlight.set(key, running);
|
||||
}
|
||||
return running;
|
||||
};
|
||||
app.get(
|
||||
'/api/doctor',
|
||||
async (req: FastifyRequest, reply: FastifyReply): Promise<ApiResponse<DependencyReportJson>> => {
|
||||
@@ -75,10 +90,10 @@ export function registerDoctorRoutes(app: FastifyInstance, runner: DoctorRunner
|
||||
);
|
||||
}
|
||||
try {
|
||||
return { success: true, data: await runner(category) };
|
||||
return { success: true, data: await runShared(category) };
|
||||
} catch (err) {
|
||||
reply.code(500);
|
||||
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, `doctor failed: ${getErrorMessage(err)}`);
|
||||
return createErrorResponse(ApiErrorCode.INTERNAL_ERROR, `doctor failed: ${getErrorMessage(err)}`);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user