refactor: remove glabPath and ghPath options from providers and update related code

This commit is contained in:
Lauren ten Hoor
2026-02-09 22:33:46 +08:00
parent 7a29da4c83
commit 3a2e739a62
12 changed files with 40 additions and 75 deletions

View File

@@ -29,14 +29,6 @@ const plugin = {
qa: { type: "string", description: "QA engineer model" }, qa: { type: "string", description: "QA engineer model" },
}, },
}, },
glabPath: {
type: "string",
description: "Path to glab CLI binary. Defaults to 'glab' on PATH.",
},
ghPath: {
type: "string",
description: "Path to gh CLI binary. Defaults to 'gh' on PATH.",
},
}, },
}, },

View File

@@ -25,7 +25,6 @@ const STATE_LABELS = [
export type StateLabel = (typeof STATE_LABELS)[number]; export type StateLabel = (typeof STATE_LABELS)[number];
type GlabOptions = { type GlabOptions = {
glabPath?: string;
repoPath: string; repoPath: string;
}; };
@@ -33,8 +32,7 @@ async function glab(
args: string[], args: string[],
opts: GlabOptions, opts: GlabOptions,
): Promise<string> { ): Promise<string> {
const bin = opts.glabPath ?? "glab"; const { stdout } = await execFileAsync("glab", args, {
const { stdout } = await execFileAsync(bin, args, {
cwd: opts.repoPath, cwd: opts.repoPath,
timeout: 30_000, timeout: 30_000,
}); });

View File

@@ -24,7 +24,6 @@ import {
const execFileAsync = promisify(execFile); const execFileAsync = promisify(execFile);
export type GitHubProviderOptions = { export type GitHubProviderOptions = {
ghPath?: string;
repoPath: string; repoPath: string;
}; };
@@ -50,16 +49,14 @@ function toIssue(gh: GhIssue): Issue {
} }
export class GitHubProvider implements IssueProvider { export class GitHubProvider implements IssueProvider {
private ghPath: string;
private repoPath: string; private repoPath: string;
constructor(opts: GitHubProviderOptions) { constructor(opts: GitHubProviderOptions) {
this.ghPath = opts.ghPath ?? "gh";
this.repoPath = opts.repoPath; this.repoPath = opts.repoPath;
} }
private async gh(args: string[]): Promise<string> { private async gh(args: string[]): Promise<string> {
const { stdout } = await execFileAsync(this.ghPath, args, { const { stdout } = await execFileAsync("gh", args, {
cwd: this.repoPath, cwd: this.repoPath,
timeout: 30_000, timeout: 30_000,
}); });

View File

@@ -20,21 +20,18 @@ import {
const execFileAsync = promisify(execFile); const execFileAsync = promisify(execFile);
export type GitLabProviderOptions = { export type GitLabProviderOptions = {
glabPath?: string;
repoPath: string; repoPath: string;
}; };
export class GitLabProvider implements IssueProvider { export class GitLabProvider implements IssueProvider {
private glabPath: string;
private repoPath: string; private repoPath: string;
constructor(opts: GitLabProviderOptions) { constructor(opts: GitLabProviderOptions) {
this.glabPath = opts.glabPath ?? "glab";
this.repoPath = opts.repoPath; this.repoPath = opts.repoPath;
} }
private async glab(args: string[]): Promise<string> { private async glab(args: string[]): Promise<string> {
const { stdout } = await execFileAsync(this.glabPath, args, { const { stdout } = await execFileAsync("glab", args, {
cwd: this.repoPath, cwd: this.repoPath,
timeout: 30_000, timeout: 30_000,
}); });
@@ -76,7 +73,7 @@ export class GitLabProvider implements IssueProvider {
const { promisify } = await import("node:util"); const { promisify } = await import("node:util");
const execAsync = promisify(exec); const execAsync = promisify(exec);
let cmd = `${this.glabPath} issue create --title "${title.replace(/"/g, '\\"')}" --description "$(cat ${tempFile})" --label "${label}" --output json`; let cmd = `glab issue create --title "${title.replace(/"/g, '\\"')}" --description "$(cat ${tempFile})" --label "${label}" --output json`;
if (assignees && assignees.length > 0) { if (assignees && assignees.length > 0) {
cmd += ` --assignee "${assignees.join(",")}"`; cmd += ` --assignee "${assignees.join(",")}"`;
} }

View File

@@ -11,12 +11,12 @@ import { execFileSync } from "node:child_process";
import type { IssueProvider } from "../issue-provider.js"; import type { IssueProvider } from "../issue-provider.js";
import { GitLabProvider } from "./gitlab.js"; import { GitLabProvider } from "./gitlab.js";
import { GitHubProvider } from "./github.js"; import { GitHubProvider } from "./github.js";
import { resolveRepoPath } from "../utils.js";
export type ProviderOptions = { export type ProviderOptions = {
provider?: "gitlab" | "github"; provider?: "gitlab" | "github";
glabPath?: string; repo?: string;
ghPath?: string; repoPath?: string;
repoPath: string;
}; };
function detectProvider(repoPath: string): "gitlab" | "github" { function detectProvider(repoPath: string): "gitlab" | "github" {
@@ -39,16 +39,21 @@ export type ProviderWithType = {
}; };
export function createProvider(opts: ProviderOptions): ProviderWithType { export function createProvider(opts: ProviderOptions): ProviderWithType {
const type = opts.provider ?? detectProvider(opts.repoPath); const repoPath = opts.repoPath ?? (opts.repo ? resolveRepoPath(opts.repo) : null);
if (!repoPath) {
throw new Error("Either repoPath or repo must be provided to createProvider");
}
const type = opts.provider ?? detectProvider(repoPath);
if (type === "github") { if (type === "github") {
return { return {
provider: new GitHubProvider({ ghPath: opts.ghPath, repoPath: opts.repoPath }), provider: new GitHubProvider({ repoPath }),
type: "github", type: "github",
}; };
} }
return { return {
provider: new GitLabProvider({ glabPath: opts.glabPath, repoPath: opts.repoPath }), provider: new GitLabProvider({ repoPath }),
type: "gitlab", type: "gitlab",
}; };
} }

View File

@@ -170,9 +170,7 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) {
const repoPath = resolveRepoPath(repo); const repoPath = resolveRepoPath(repo);
// 3. Create provider and verify it works // 3. Create provider and verify it works
const glabPath = (api.pluginConfig as Record<string, unknown>)?.glabPath as string | undefined; const { provider, type: providerType } = createProvider({ repo });
const ghPath = (api.pluginConfig as Record<string, unknown>)?.ghPath as string | undefined;
const { provider, type: providerType } = createProvider({ glabPath, ghPath, repoPath });
const healthy = await provider.healthCheck(); const healthy = await provider.healthCheck();
if (!healthy) { if (!healthy) {

View File

@@ -11,7 +11,6 @@ import { type StateLabel } from "../issue-provider.js";
import { createProvider } from "../providers/index.js"; import { createProvider } from "../providers/index.js";
import { log as auditLog } from "../audit.js"; import { log as auditLog } from "../audit.js";
import { detectContext, generateGuardrails } from "../context-guard.js"; import { detectContext, generateGuardrails } from "../context-guard.js";
import { resolveRepoPath } from "../utils.js";
export function createQueueStatusTool(api: OpenClawPluginApi) { export function createQueueStatusTool(api: OpenClawPluginApi) {
return (ctx: ToolContext) => ({ return (ctx: ToolContext) => ({
@@ -69,11 +68,8 @@ export function createQueueStatusTool(api: OpenClawPluginApi) {
const project = getProject(data, pid); const project = getProject(data, pid);
if (!project) continue; if (!project) continue;
const repoPath = resolveRepoPath(project.repo);
const { provider } = createProvider({ const { provider } = createProvider({
glabPath: (api.pluginConfig as Record<string, unknown>)?.glabPath as string | undefined, repo: project.repo,
ghPath: (api.pluginConfig as Record<string, unknown>)?.ghPath as string | undefined,
repoPath,
}); });
// Fetch queue counts from issue tracker // Fetch queue counts from issue tracker

View File

@@ -11,7 +11,6 @@ import { readProjects, updateWorker, getSessionForModel } from "../projects.js";
import { type StateLabel } from "../issue-provider.js"; import { type StateLabel } from "../issue-provider.js";
import { createProvider } from "../providers/index.js"; import { createProvider } from "../providers/index.js";
import { log as auditLog } from "../audit.js"; import { log as auditLog } from "../audit.js";
import { resolveRepoPath } from "../utils.js";
export function createSessionHealthTool(api: OpenClawPluginApi) { export function createSessionHealthTool(api: OpenClawPluginApi) {
return (ctx: ToolContext) => ({ return (ctx: ToolContext) => ({
@@ -48,11 +47,8 @@ export function createSessionHealthTool(api: OpenClawPluginApi) {
let fixesApplied = 0; let fixesApplied = 0;
for (const [groupId, project] of Object.entries(data.projects)) { for (const [groupId, project] of Object.entries(data.projects)) {
const repoPath = resolveRepoPath(project.repo);
const { provider } = createProvider({ const { provider } = createProvider({
glabPath: (api.pluginConfig as Record<string, unknown>)?.glabPath as string | undefined, repo: project.repo,
ghPath: (api.pluginConfig as Record<string, unknown>)?.ghPath as string | undefined,
repoPath,
}); });
for (const role of ["dev", "qa"] as const) { for (const role of ["dev", "qa"] as const) {

View File

@@ -16,6 +16,7 @@ import { log as auditLog } from "../audit.js";
import { dispatchTask } from "../dispatch.js"; import { dispatchTask } from "../dispatch.js";
import { type StateLabel } from "../issue-provider.js"; import { type StateLabel } from "../issue-provider.js";
import { createProvider } from "../providers/index.js"; import { createProvider } from "../providers/index.js";
import { resolveRepoPath } from "../utils.js";
import { import {
deactivateWorker, deactivateWorker,
getProject, getProject,
@@ -24,7 +25,6 @@ import {
readProjects, readProjects,
} from "../projects.js"; } from "../projects.js";
import type { ToolContext } from "../types.js"; import type { ToolContext } from "../types.js";
import { resolveRepoPath } from "../utils.js";
const execFileAsync = promisify(execFile); const execFileAsync = promisify(execFile);
@@ -105,17 +105,12 @@ export function createTaskCompleteTool(api: OpenClawPluginApi) {
); );
} }
const repoPath = resolveRepoPath(project.repo);
const { provider } = createProvider({ const { provider } = createProvider({
glabPath: (api.pluginConfig as Record<string, unknown>)?.glabPath as repo: project.repo,
| string
| undefined,
ghPath: (api.pluginConfig as Record<string, unknown>)?.ghPath as
| string
| undefined,
repoPath,
}); });
const repoPath = resolveRepoPath(project.repo);
const output: Record<string, unknown> = { const output: Record<string, unknown> = {
success: true, success: true,
project: project.name, project: project.name,

View File

@@ -13,7 +13,6 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { jsonResult } from "openclaw/plugin-sdk"; import { jsonResult } from "openclaw/plugin-sdk";
import type { ToolContext } from "../types.js"; import type { ToolContext } from "../types.js";
import { readProjects } from "../projects.js"; import { readProjects } from "../projects.js";
import { resolveRepoPath } from "../utils.js";
import { createProvider } from "../providers/index.js"; import { createProvider } from "../providers/index.js";
import { log as auditLog } from "../audit.js"; import { log as auditLog } from "../audit.js";
import type { StateLabel } from "../issue-provider.js"; import type { StateLabel } from "../issue-provider.js";
@@ -95,12 +94,8 @@ The issue is created with a state label (defaults to "Planning"). Returns the cr
} }
// 2. Create provider // 2. Create provider
const repoPath = resolveRepoPath(project.repo);
const config = api.pluginConfig as Record<string, unknown> | undefined;
const { provider, type: providerType } = createProvider({ const { provider, type: providerType } = createProvider({
glabPath: config?.glabPath as string | undefined, repo: project.repo,
ghPath: config?.ghPath as string | undefined,
repoPath,
}); });
// 3. Create the issue // 3. Create the issue

View File

@@ -17,7 +17,6 @@ import { selectModel } from "../model-selector.js";
import { getProject, getWorker, readProjects } from "../projects.js"; import { getProject, getWorker, readProjects } from "../projects.js";
import type { ToolContext } from "../types.js"; import type { ToolContext } from "../types.js";
import { detectContext, generateGuardrails } from "../context-guard.js"; import { detectContext, generateGuardrails } from "../context-guard.js";
import { resolveRepoPath } from "../utils.js";
export function createTaskPickupTool(api: OpenClawPluginApi) { export function createTaskPickupTool(api: OpenClawPluginApi) {
return (ctx: ToolContext) => ({ return (ctx: ToolContext) => ({
@@ -96,15 +95,8 @@ export function createTaskPickupTool(api: OpenClawPluginApi) {
} }
// 3. Fetch issue and verify state // 3. Fetch issue and verify state
const repoPath = resolveRepoPath(project.repo);
const { provider } = createProvider({ const { provider } = createProvider({
glabPath: (api.pluginConfig as Record<string, unknown>)?.glabPath as repo: project.repo,
| string
| undefined,
ghPath: (api.pluginConfig as Record<string, unknown>)?.ghPath as
| string
| undefined,
repoPath,
}); });
const issue = await provider.getIssue(issueId); const issue = await provider.getIssue(issueId);

View File

@@ -9,19 +9,23 @@
"type": "object", "type": "object",
"description": "Model mapping per developer tier (junior, medior, senior, qa)", "description": "Model mapping per developer tier (junior, medior, senior, qa)",
"properties": { "properties": {
"junior": { "type": "string", "description": "Junior dev model (default: anthropic/claude-haiku-4-5)" }, "junior": {
"medior": { "type": "string", "description": "Medior dev model (default: anthropic/claude-sonnet-4-5)" },
"senior": { "type": "string", "description": "Senior dev model (default: anthropic/claude-opus-4-5)" },
"qa": { "type": "string", "description": "QA engineer model (default: anthropic/claude-sonnet-4-5)" }
}
},
"glabPath": {
"type": "string", "type": "string",
"description": "Path to glab CLI binary. Defaults to 'glab' on PATH." "description": "Junior dev model (default: anthropic/claude-haiku-4-5)"
}, },
"ghPath": { "medior": {
"type": "string", "type": "string",
"description": "Path to gh CLI binary. Defaults to 'gh' on PATH." "description": "Medior dev model (default: anthropic/claude-sonnet-4-5)"
},
"senior": {
"type": "string",
"description": "Senior dev model (default: anthropic/claude-opus-4-5)"
},
"qa": {
"type": "string",
"description": "QA engineer model (default: anthropic/claude-sonnet-4-5)"
}
}
} }
} }
} }