agentweaver 0.1.12 → 0.1.13
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/LICENSE +21 -0
- package/README.md +6 -0
- package/dist/doctor/checks/agentweaver-home.js +57 -0
- package/dist/doctor/checks/category.js +9 -0
- package/dist/doctor/checks/cwd-context.js +69 -0
- package/dist/doctor/checks/env-diagnostics.js +171 -0
- package/dist/doctor/checks/executors.js +106 -0
- package/dist/doctor/checks/flow-readiness.js +305 -0
- package/dist/doctor/checks/node-version.js +79 -0
- package/dist/doctor/checks/register.js +18 -0
- package/dist/doctor/checks/system.js +91 -0
- package/dist/doctor/index.js +4 -0
- package/dist/doctor/orchestrator.js +78 -0
- package/dist/doctor/registry.js +50 -0
- package/dist/doctor/runner.js +89 -0
- package/dist/doctor/types.js +12 -0
- package/dist/index.js +17 -1
- package/package.json +4 -2
- package/dist/pipeline/flow-model-settings.js +0 -77
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 seko99
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -85,6 +85,7 @@ User-invokable built-in commands currently map to these flow specs:
|
|
|
85
85
|
- `run-go-linter-loop`
|
|
86
86
|
- `auto-golang`
|
|
87
87
|
- `auto-common`
|
|
88
|
+
- `doctor`
|
|
88
89
|
|
|
89
90
|
There are also built-in nested/helper flows that are loaded declaratively but are not direct top-level CLI commands, for example `review-project`.
|
|
90
91
|
|
|
@@ -215,6 +216,9 @@ agentweaver run-go-tests-loop DEMO-1234
|
|
|
215
216
|
agentweaver run-go-linter-loop DEMO-1234
|
|
216
217
|
agentweaver auto-golang DEMO-1234
|
|
217
218
|
agentweaver auto-common DEMO-1234
|
|
219
|
+
agentweaver doctor
|
|
220
|
+
agentweaver doctor --json
|
|
221
|
+
agentweaver doctor <category>|<check-id>
|
|
218
222
|
```
|
|
219
223
|
|
|
220
224
|
From a source checkout:
|
|
@@ -237,6 +241,8 @@ agentweaver auto-common --help-phases
|
|
|
237
241
|
agentweaver auto-golang --from <phase> DEMO-1234
|
|
238
242
|
agentweaver auto-status DEMO-1234
|
|
239
243
|
agentweaver auto-reset DEMO-1234
|
|
244
|
+
agentweaver doctor
|
|
245
|
+
agentweaver doctor --json
|
|
240
246
|
```
|
|
241
247
|
|
|
242
248
|
Notes:
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { accessSync, constants, existsSync } from "node:fs";
|
|
2
|
+
import { DoctorStatus } from "../types.js";
|
|
3
|
+
import { CATEGORY } from "./category.js";
|
|
4
|
+
import { agentweaverHome } from "../../runtime/agentweaver-home.js";
|
|
5
|
+
const PACKAGE_ROOT = process.cwd();
|
|
6
|
+
export const agentweaverHomeCheck = {
|
|
7
|
+
id: "agentweaver-home-01",
|
|
8
|
+
category: CATEGORY.AGENTWEAVER_HOME,
|
|
9
|
+
title: "agentweaver-home",
|
|
10
|
+
dependencies: [],
|
|
11
|
+
execute: async () => {
|
|
12
|
+
const homePath = agentweaverHome(PACKAGE_ROOT);
|
|
13
|
+
if (!existsSync(homePath)) {
|
|
14
|
+
return {
|
|
15
|
+
id: "agentweaver-home-01",
|
|
16
|
+
status: DoctorStatus.Fail,
|
|
17
|
+
title: "agentweaver-home",
|
|
18
|
+
message: `Directory does not exist: ${homePath}`,
|
|
19
|
+
hint: "Create the ~/.agentweaver directory or set AGENTWEAVER_HOME environment variable",
|
|
20
|
+
details: `resolved path: ${homePath}`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
accessSync(homePath, constants.R_OK | constants.W_OK);
|
|
25
|
+
return {
|
|
26
|
+
id: "agentweaver-home-01",
|
|
27
|
+
status: DoctorStatus.Ok,
|
|
28
|
+
title: "agentweaver-home",
|
|
29
|
+
message: `Directory accessible and writable: ${homePath}`,
|
|
30
|
+
details: `resolved path: ${homePath}`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
try {
|
|
35
|
+
accessSync(homePath, constants.R_OK);
|
|
36
|
+
return {
|
|
37
|
+
id: "agentweaver-home-01",
|
|
38
|
+
status: DoctorStatus.Warn,
|
|
39
|
+
title: "agentweaver-home",
|
|
40
|
+
message: `Directory exists but is not writable: ${homePath}`,
|
|
41
|
+
hint: "Check directory permissions; AgentWeaver needs write access to this directory",
|
|
42
|
+
details: `resolved path: ${homePath}`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return {
|
|
47
|
+
id: "agentweaver-home-01",
|
|
48
|
+
status: DoctorStatus.Fail,
|
|
49
|
+
title: "agentweaver-home",
|
|
50
|
+
message: `Directory exists but is not readable: ${homePath}`,
|
|
51
|
+
hint: "Check directory permissions",
|
|
52
|
+
details: `resolved path: ${homePath}`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { accessSync, constants, existsSync } from "node:fs";
|
|
2
|
+
import { DoctorStatus } from "../types.js";
|
|
3
|
+
import { CATEGORY } from "./category.js";
|
|
4
|
+
const SOFT_CHECK_ID = "cwd-context-01";
|
|
5
|
+
export const cwdContextCheck = {
|
|
6
|
+
id: "cwd-context-01",
|
|
7
|
+
category: CATEGORY.CWD_CONTEXT,
|
|
8
|
+
title: "cwd-context",
|
|
9
|
+
dependencies: [],
|
|
10
|
+
execute: async () => {
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
let permissionStatus;
|
|
13
|
+
let permissionMessage;
|
|
14
|
+
let permissionHint;
|
|
15
|
+
try {
|
|
16
|
+
accessSync(cwd, constants.R_OK | constants.W_OK);
|
|
17
|
+
permissionStatus = DoctorStatus.Ok;
|
|
18
|
+
permissionMessage = "Working directory is readable and writable";
|
|
19
|
+
permissionHint = undefined;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
try {
|
|
23
|
+
accessSync(cwd, constants.R_OK);
|
|
24
|
+
permissionStatus = DoctorStatus.Warn;
|
|
25
|
+
permissionMessage = "Working directory is readable but not writable";
|
|
26
|
+
permissionHint = "Some operations may fail if write access is required";
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
permissionStatus = DoctorStatus.Fail;
|
|
30
|
+
permissionMessage = "Working directory is not readable";
|
|
31
|
+
permissionHint = "Check directory permissions";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const gitRepoExists = existsSync(".git");
|
|
35
|
+
const gitStatusMessage = gitRepoExists ? "git repository detected" : "not a git repository";
|
|
36
|
+
const gitStatus = gitRepoExists ? DoctorStatus.Ok : DoctorStatus.Warn;
|
|
37
|
+
if (permissionStatus === DoctorStatus.Ok && gitStatus === DoctorStatus.Ok) {
|
|
38
|
+
return {
|
|
39
|
+
id: "cwd-context-01",
|
|
40
|
+
status: DoctorStatus.Ok,
|
|
41
|
+
title: "cwd-context",
|
|
42
|
+
message: `${permissionMessage}; ${gitStatusMessage}`,
|
|
43
|
+
details: `cwd: ${cwd}, git: ${gitStatusMessage}`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (permissionStatus === DoctorStatus.Ok && gitStatus === DoctorStatus.Warn) {
|
|
47
|
+
return {
|
|
48
|
+
id: "cwd-context-01",
|
|
49
|
+
status: DoctorStatus.Warn,
|
|
50
|
+
title: "cwd-context",
|
|
51
|
+
message: `${permissionMessage}; ${gitStatusMessage} (soft warning)`,
|
|
52
|
+
hint: "Git repository is recommended but not required",
|
|
53
|
+
details: `cwd: ${cwd}, git: ${gitStatusMessage}`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const result = {
|
|
57
|
+
id: "cwd-context-01",
|
|
58
|
+
status: permissionStatus,
|
|
59
|
+
title: "cwd-context",
|
|
60
|
+
message: permissionMessage,
|
|
61
|
+
details: `cwd: ${cwd}, git: ${gitStatusMessage}`,
|
|
62
|
+
};
|
|
63
|
+
if (permissionHint) {
|
|
64
|
+
result.hint = permissionHint;
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
export const SOFT_CHECK_IDS = [SOFT_CHECK_ID];
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { DoctorStatus } from "../types.js";
|
|
5
|
+
import { CATEGORY } from "./category.js";
|
|
6
|
+
const MONITORED_KEYS = [
|
|
7
|
+
"JIRA_API_KEY",
|
|
8
|
+
"JIRA_USERNAME",
|
|
9
|
+
"JIRA_AUTH_MODE",
|
|
10
|
+
"JIRA_BASE_URL",
|
|
11
|
+
"GITLAB_TOKEN",
|
|
12
|
+
"AGENTWEAVER_HOME",
|
|
13
|
+
"CODEX_BIN",
|
|
14
|
+
"CODEX_MODEL",
|
|
15
|
+
"OPENCODE_BIN",
|
|
16
|
+
"OPENCODE_MODEL",
|
|
17
|
+
];
|
|
18
|
+
const SECRET_KEYS = new Set(["JIRA_API_KEY", "GITLAB_TOKEN"]);
|
|
19
|
+
const JIRA_AUTH_MODE_ALLOWED_VALUES = ["auto", "basic", "bearer"];
|
|
20
|
+
function maskSecret(value) {
|
|
21
|
+
if (value.length <= 6) {
|
|
22
|
+
if (value.length <= 3) {
|
|
23
|
+
return "***";
|
|
24
|
+
}
|
|
25
|
+
const start = value.slice(0, Math.ceil(value.length / 2));
|
|
26
|
+
const end = value.slice(Math.ceil(value.length / 2));
|
|
27
|
+
return `${start}***${end}`;
|
|
28
|
+
}
|
|
29
|
+
const start = value.slice(0, 3);
|
|
30
|
+
const end = value.slice(-3);
|
|
31
|
+
return `${start}***${end}`;
|
|
32
|
+
}
|
|
33
|
+
function globalConfigDir() {
|
|
34
|
+
return path.join(os.homedir(), ".agentweaver");
|
|
35
|
+
}
|
|
36
|
+
function parseEnvFileRaw(envFilePath) {
|
|
37
|
+
const result = {};
|
|
38
|
+
if (!existsSync(envFilePath)) {
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
const lines = readFileSync(envFilePath, "utf8").split(/\r?\n/);
|
|
42
|
+
for (const rawLine of lines) {
|
|
43
|
+
let line = rawLine.trim();
|
|
44
|
+
if (!line || line.startsWith("#")) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (line.startsWith("export ")) {
|
|
48
|
+
line = line.slice(7).trim();
|
|
49
|
+
}
|
|
50
|
+
const separatorIndex = line.indexOf("=");
|
|
51
|
+
if (separatorIndex < 0) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const key = line.slice(0, separatorIndex).trim();
|
|
55
|
+
if (!key) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
let value = line.slice(separatorIndex + 1).trim();
|
|
59
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
60
|
+
value = value.slice(1, -1);
|
|
61
|
+
}
|
|
62
|
+
result[key] = value;
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
function getProjectEnvPath() {
|
|
67
|
+
return path.join(process.cwd(), ".agentweaver", ".env");
|
|
68
|
+
}
|
|
69
|
+
function getGlobalEnvPath() {
|
|
70
|
+
return path.join(globalConfigDir(), ".env");
|
|
71
|
+
}
|
|
72
|
+
function determineSource(key, shellSnapshot, currentValue) {
|
|
73
|
+
if (currentValue === null) {
|
|
74
|
+
return "missing";
|
|
75
|
+
}
|
|
76
|
+
const isInShellSnapshot = shellSnapshot.hasOwnProperty(key);
|
|
77
|
+
if (isInShellSnapshot) {
|
|
78
|
+
return "shell";
|
|
79
|
+
}
|
|
80
|
+
const globalEnv = parseEnvFileRaw(getGlobalEnvPath());
|
|
81
|
+
if (globalEnv.hasOwnProperty(key)) {
|
|
82
|
+
return "global";
|
|
83
|
+
}
|
|
84
|
+
const projectEnv = parseEnvFileRaw(getProjectEnvPath());
|
|
85
|
+
if (projectEnv.hasOwnProperty(key)) {
|
|
86
|
+
return "project-local";
|
|
87
|
+
}
|
|
88
|
+
return "shell";
|
|
89
|
+
}
|
|
90
|
+
function getDefaultValue(key) {
|
|
91
|
+
switch (key) {
|
|
92
|
+
case "AGENTWEAVER_HOME":
|
|
93
|
+
return path.join(os.homedir(), ".agentweaver");
|
|
94
|
+
default:
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function validateJiraAuthMode(value) {
|
|
99
|
+
if (value === null) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
return JIRA_AUTH_MODE_ALLOWED_VALUES.includes(value);
|
|
103
|
+
}
|
|
104
|
+
function checkEnvDiagnostics() {
|
|
105
|
+
const shellSnapshot = {};
|
|
106
|
+
for (const key of Object.keys(process.env)) {
|
|
107
|
+
shellSnapshot[key] = process.env[key];
|
|
108
|
+
}
|
|
109
|
+
const projectEnv = parseEnvFileRaw(getProjectEnvPath());
|
|
110
|
+
const globalEnv = parseEnvFileRaw(getGlobalEnvPath());
|
|
111
|
+
const keyInfos = [];
|
|
112
|
+
let hasWarnings = false;
|
|
113
|
+
for (const key of MONITORED_KEYS) {
|
|
114
|
+
const currentValue = process.env[key] ?? null;
|
|
115
|
+
const source = determineSource(key, shellSnapshot, currentValue);
|
|
116
|
+
const isSecret = SECRET_KEYS.has(key);
|
|
117
|
+
let maskedValue = null;
|
|
118
|
+
if (currentValue !== null && isSecret) {
|
|
119
|
+
maskedValue = maskSecret(currentValue);
|
|
120
|
+
}
|
|
121
|
+
else if (currentValue !== null) {
|
|
122
|
+
maskedValue = currentValue;
|
|
123
|
+
}
|
|
124
|
+
const keyInfo = {
|
|
125
|
+
key,
|
|
126
|
+
source,
|
|
127
|
+
value: currentValue,
|
|
128
|
+
maskedValue,
|
|
129
|
+
isSecret,
|
|
130
|
+
};
|
|
131
|
+
keyInfos.push(keyInfo);
|
|
132
|
+
if (source === "missing") {
|
|
133
|
+
hasWarnings = true;
|
|
134
|
+
}
|
|
135
|
+
if (key === "JIRA_AUTH_MODE" && currentValue !== null) {
|
|
136
|
+
if (!validateJiraAuthMode(currentValue)) {
|
|
137
|
+
hasWarnings = true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const status = hasWarnings ? DoctorStatus.Warn : DoctorStatus.Ok;
|
|
142
|
+
const keyCount = keyInfos.length;
|
|
143
|
+
const missingCount = keyInfos.filter(k => k.source === "missing").length;
|
|
144
|
+
const secretCount = keyInfos.filter(k => k.isSecret).length;
|
|
145
|
+
const summaryParts = [];
|
|
146
|
+
summaryParts.push(`${keyCount} keys checked`);
|
|
147
|
+
if (missingCount > 0) {
|
|
148
|
+
summaryParts.push(`${missingCount} missing`);
|
|
149
|
+
}
|
|
150
|
+
if (secretCount > 0) {
|
|
151
|
+
summaryParts.push(`${secretCount} secrets`);
|
|
152
|
+
}
|
|
153
|
+
const result = {
|
|
154
|
+
id: "env-diagnostics-01",
|
|
155
|
+
status,
|
|
156
|
+
title: "env-config",
|
|
157
|
+
message: summaryParts.join(", "),
|
|
158
|
+
...(missingCount > 0 ? { hint: `${missingCount} configuration keys are missing` } : {}),
|
|
159
|
+
details: JSON.stringify(keyInfos),
|
|
160
|
+
};
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
export const envDiagnosticsCheck = {
|
|
164
|
+
id: "env-diagnostics-01",
|
|
165
|
+
category: CATEGORY.ENV_DIAGNOSTICS,
|
|
166
|
+
title: "env-config",
|
|
167
|
+
dependencies: [],
|
|
168
|
+
execute: async () => {
|
|
169
|
+
return checkEnvDiagnostics();
|
|
170
|
+
},
|
|
171
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { DoctorStatus } from "../types.js";
|
|
3
|
+
import { CATEGORY } from "./category.js";
|
|
4
|
+
import { ALLOWED_MODELS_BY_EXECUTOR } from "../../pipeline/launch-profile-config.js";
|
|
5
|
+
import { findCmdPath, isExecutable } from "../../runtime/command-resolution.js";
|
|
6
|
+
function getEnvVarName(executorId) {
|
|
7
|
+
return executorId === "codex" ? "CODEX_BIN" : "OPENCODE_BIN";
|
|
8
|
+
}
|
|
9
|
+
function getCommandName(executorId) {
|
|
10
|
+
return executorId === "codex" ? "codex" : "opencode";
|
|
11
|
+
}
|
|
12
|
+
function resolveBinaryPath(executorId) {
|
|
13
|
+
const envVarName = getEnvVarName(executorId);
|
|
14
|
+
const commandName = getCommandName(executorId);
|
|
15
|
+
const configuredPath = process.env[envVarName];
|
|
16
|
+
if (configuredPath) {
|
|
17
|
+
if (isExecutable(configuredPath)) {
|
|
18
|
+
return { path: configuredPath, source: "env-override" };
|
|
19
|
+
}
|
|
20
|
+
return { path: null, source: "not-found" };
|
|
21
|
+
}
|
|
22
|
+
const foundPath = findCmdPath(commandName, "");
|
|
23
|
+
if (foundPath) {
|
|
24
|
+
return { path: foundPath, source: "PATH" };
|
|
25
|
+
}
|
|
26
|
+
return { path: null, source: "not-found" };
|
|
27
|
+
}
|
|
28
|
+
function runSmokeCheck(executorPath) {
|
|
29
|
+
const result = spawnSync(executorPath, ["--version"], { encoding: "utf8", stdio: "pipe" });
|
|
30
|
+
if (result.status === 0 && result.stdout) {
|
|
31
|
+
return result.stdout.trim();
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
function buildDetailsObj(executorId, resolution, versionOutput) {
|
|
36
|
+
return {
|
|
37
|
+
path: resolution.path,
|
|
38
|
+
source: resolution.source,
|
|
39
|
+
executable: resolution.path !== null && isExecutable(resolution.path),
|
|
40
|
+
versionOutput,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function createResult(executorId, status, message, hint, details, resolution, versionOutput) {
|
|
44
|
+
const result = {
|
|
45
|
+
id: `${executorId}-executor-01`,
|
|
46
|
+
status,
|
|
47
|
+
title: executorId,
|
|
48
|
+
message,
|
|
49
|
+
};
|
|
50
|
+
if (hint) {
|
|
51
|
+
result.hint = hint;
|
|
52
|
+
}
|
|
53
|
+
if (details) {
|
|
54
|
+
result.details = details;
|
|
55
|
+
}
|
|
56
|
+
result.detailsObj = buildDetailsObj(executorId, resolution, versionOutput);
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
function checkExecutor(executorId) {
|
|
60
|
+
const resolution = resolveBinaryPath(executorId);
|
|
61
|
+
if (resolution.path === null) {
|
|
62
|
+
const hint = executorId === "codex"
|
|
63
|
+
? "Set CODEX_BIN environment variable to point to the codex binary"
|
|
64
|
+
: "Set OPENCODE_BIN environment variable to point to the opencode binary";
|
|
65
|
+
return createResult(executorId, DoctorStatus.Fail, `${executorId} binary not found`, hint, `source: ${resolution.source}`, resolution, null);
|
|
66
|
+
}
|
|
67
|
+
if (!isExecutable(resolution.path)) {
|
|
68
|
+
const hint = `Binary at ${resolution.path} is not executable`;
|
|
69
|
+
return createResult(executorId, DoctorStatus.Fail, `${executorId} binary is not executable`, hint, `path: ${resolution.path}`, resolution, null);
|
|
70
|
+
}
|
|
71
|
+
const versionOutput = runSmokeCheck(resolution.path);
|
|
72
|
+
if (versionOutput === null) {
|
|
73
|
+
return createResult(executorId, DoctorStatus.Fail, `${executorId} --version check failed`, `${executorId} --version did not produce expected output`, `path: ${resolution.path}, source: ${resolution.source}`, resolution, null);
|
|
74
|
+
}
|
|
75
|
+
const allowedModels = ALLOWED_MODELS_BY_EXECUTOR[executorId];
|
|
76
|
+
const modelWarnings = [];
|
|
77
|
+
for (const model of allowedModels) {
|
|
78
|
+
const modelResult = spawnSync(resolution.path, ["--model", model, "--version"], { encoding: "utf8", stdio: "pipe" });
|
|
79
|
+
if (modelResult.status !== 0) {
|
|
80
|
+
modelWarnings.push(`Model '${model}' validation failed (exit code ${modelResult.status})`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const message = versionOutput;
|
|
84
|
+
if (modelWarnings.length > 0) {
|
|
85
|
+
return createResult(executorId, DoctorStatus.Warn, message, `Some models failed validation: ${modelWarnings.join("; ")}`, `source: ${resolution.source}, models validated: ${allowedModels.join(", ")}`, resolution, versionOutput);
|
|
86
|
+
}
|
|
87
|
+
return createResult(executorId, DoctorStatus.Ok, message, undefined, `source: ${resolution.source}`, resolution, versionOutput);
|
|
88
|
+
}
|
|
89
|
+
export const codexExecutorCheck = {
|
|
90
|
+
id: "codex-executor-01",
|
|
91
|
+
category: CATEGORY.EXECUTORS,
|
|
92
|
+
title: "codex",
|
|
93
|
+
dependencies: [],
|
|
94
|
+
execute: async () => {
|
|
95
|
+
return checkExecutor("codex");
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
export const opencodeExecutorCheck = {
|
|
99
|
+
id: "opencode-executor-01",
|
|
100
|
+
category: CATEGORY.EXECUTORS,
|
|
101
|
+
title: "opencode",
|
|
102
|
+
dependencies: [],
|
|
103
|
+
execute: async () => {
|
|
104
|
+
return checkExecutor("opencode");
|
|
105
|
+
},
|
|
106
|
+
};
|