glab-agent 0.2.1 → 0.2.3
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glab-agent",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Multi-agent GitLab To-Do watcher with YAML-defined agents, skills, and GitLab registry.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"access": "public"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
|
+
"prepare": "git config core.hooksPath scripts/hooks",
|
|
34
35
|
"build": "tsc -p tsconfig.build.json",
|
|
35
36
|
"test": "vitest run",
|
|
36
37
|
"agent": "tsx src/local-agent/cli.ts",
|
package/src/local-agent/cli.ts
CHANGED
|
@@ -713,6 +713,41 @@ Do not skip tests or use --no-verify to bypass hooks.
|
|
|
713
713
|
await writeFile(readmePath, readmeContent, "utf8");
|
|
714
714
|
console.log("Created README.md — product landing page for AI agents.");
|
|
715
715
|
}
|
|
716
|
+
|
|
717
|
+
// Auto-run wiki setup if remote and token are available
|
|
718
|
+
if (remote) {
|
|
719
|
+
const token = process.env[options?.tokenEnv ?? "GITLAB_TOKEN"];
|
|
720
|
+
if (token) {
|
|
721
|
+
console.log("\nPopulating GitLab Wiki with documentation...");
|
|
722
|
+
try {
|
|
723
|
+
const client = new GitlabGlabClient({ host: remote.host, token });
|
|
724
|
+
const { stdout } = await execFileAsync("glab", ["api", `projects/${encodeURIComponent(remote.projectPath)}`], {
|
|
725
|
+
encoding: "utf8",
|
|
726
|
+
env: { ...process.env, GITLAB_HOST: remote.host, GITLAB_TOKEN: token }
|
|
727
|
+
});
|
|
728
|
+
const projectId = JSON.parse(stdout).id as number;
|
|
729
|
+
const pages = buildWikiPages(remote.host, remote.projectPath);
|
|
730
|
+
const existingPages = await client.listWikiPages(projectId);
|
|
731
|
+
const existingSlugs = new Set(existingPages.map(p => p.slug));
|
|
732
|
+
let created = 0;
|
|
733
|
+
let updated = 0;
|
|
734
|
+
for (const page of pages) {
|
|
735
|
+
try {
|
|
736
|
+
if (existingSlugs.has(page.title)) {
|
|
737
|
+
await client.updateWikiPage(projectId, page.title, page.title, page.content);
|
|
738
|
+
updated++;
|
|
739
|
+
} else {
|
|
740
|
+
await client.createWikiPage(projectId, page.title, page.content);
|
|
741
|
+
created++;
|
|
742
|
+
}
|
|
743
|
+
} catch { /* skip failed pages */ }
|
|
744
|
+
}
|
|
745
|
+
console.log(`✅ Wiki: ${created} created, ${updated} updated. ${wikiUrl(remote.host, remote.projectPath, "home")}`);
|
|
746
|
+
} catch {
|
|
747
|
+
console.log(`Skipped wiki setup (run 'glab-agent wiki setup' manually after pushing to GitLab).`);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
716
751
|
}
|
|
717
752
|
|
|
718
753
|
// Also append to README.md for universal AI tool compatibility (non-repo mode)
|
|
@@ -50,6 +50,7 @@ const ACCEPTED_TODO_ACTIONS = new Set(["mentioned", "directly_addressed"]);
|
|
|
50
50
|
export interface LocalAgentConfig {
|
|
51
51
|
gitlabHost: string;
|
|
52
52
|
gitlabProjectId?: number;
|
|
53
|
+
gitlabProjectIdExplicit: boolean; // true when YAML explicitly sets project_id
|
|
53
54
|
gitlabToken: string;
|
|
54
55
|
agentProvider: AgentProvider;
|
|
55
56
|
agentRepoPath: string;
|
|
@@ -334,7 +335,7 @@ export async function runWatcherCycle(
|
|
|
334
335
|
|
|
335
336
|
await updateAgentUserStatus(dependencies, "idle", undefined, config);
|
|
336
337
|
|
|
337
|
-
const todos = config.gitlabProjectId
|
|
338
|
+
const todos = config.gitlabProjectIdExplicit && config.gitlabProjectId
|
|
338
339
|
? await dependencies.gitlabClient.listPendingTodos(config.gitlabProjectId)
|
|
339
340
|
: await dependencies.gitlabClient.listAllPendingTodos();
|
|
340
341
|
logger.info(`Polled ${todos.length} pending todo(s).`);
|
|
@@ -823,7 +824,7 @@ async function notifyPendingTodosWhileBusy(
|
|
|
823
824
|
let todos: GitlabTodoItem[];
|
|
824
825
|
|
|
825
826
|
try {
|
|
826
|
-
todos = config.gitlabProjectId
|
|
827
|
+
todos = (config.gitlabProjectIdExplicit && config.gitlabProjectId)
|
|
827
828
|
? await dependencies.gitlabClient.listMentionedIssueTodos(config.gitlabProjectId)
|
|
828
829
|
: await dependencies.gitlabClient.listAllPendingTodos();
|
|
829
830
|
} catch (error) {
|
|
@@ -980,7 +981,8 @@ function createDependencies(config: LocalAgentConfig, logger?: Logger): WatcherD
|
|
|
980
981
|
stateStore: new FileStateStore(config.statePath),
|
|
981
982
|
worktreeManager: new WorktreeManager({
|
|
982
983
|
repoPath: config.agentRepoPath,
|
|
983
|
-
worktreeRoot: config.agentWorktreeRoot
|
|
984
|
+
worktreeRoot: config.agentWorktreeRoot,
|
|
985
|
+
agentName: config.agentDefinition?.name
|
|
984
986
|
}),
|
|
985
987
|
agentRunner,
|
|
986
988
|
logger
|
|
@@ -1039,14 +1041,16 @@ export function loadConfigFromAgentDefinition(
|
|
|
1039
1041
|
throw new Error(`gitlab.host is required for agent "${def.name}" or must be inferable from git remote.`);
|
|
1040
1042
|
}
|
|
1041
1043
|
|
|
1044
|
+
const explicitProjectId = def.gitlab.project_id;
|
|
1042
1045
|
const gitlabProjectId =
|
|
1043
|
-
|
|
1046
|
+
explicitProjectId ??
|
|
1044
1047
|
inferGitlabProjectId(gitlabHost, gitlabToken, agentRepoPath, execFileSyncImpl) ??
|
|
1045
1048
|
undefined;
|
|
1046
1049
|
|
|
1047
1050
|
return {
|
|
1048
1051
|
gitlabHost,
|
|
1049
1052
|
gitlabProjectId,
|
|
1053
|
+
gitlabProjectIdExplicit: explicitProjectId != null,
|
|
1050
1054
|
gitlabToken,
|
|
1051
1055
|
agentProvider: def.provider,
|
|
1052
1056
|
agentRepoPath,
|
|
@@ -1181,7 +1185,7 @@ export async function main(argv: string[] = process.argv.slice(2)): Promise<void
|
|
|
1181
1185
|
process.exit(1);
|
|
1182
1186
|
}
|
|
1183
1187
|
|
|
1184
|
-
logger.info(`Watcher started. mode=${mode} provider=${config.agentProvider} host=${config.gitlabHost}${config.gitlabProjectId ? ` project=${config.gitlabProjectId}` : " project=auto (from todos)"}`);
|
|
1188
|
+
logger.info(`Watcher started. mode=${mode} provider=${config.agentProvider} host=${config.gitlabHost}${config.gitlabProjectId ? ` project=${config.gitlabProjectId}${config.gitlabProjectIdExplicit ? "" : " (inferred, todo polling unfiltered)"}` : " project=auto (from todos)"}`);
|
|
1185
1189
|
|
|
1186
1190
|
if (mode === "run-once") {
|
|
1187
1191
|
await runWatcherCycle(config, dependencies);
|
|
@@ -13,6 +13,7 @@ export interface WorktreeInfo {
|
|
|
13
13
|
interface WorktreeManagerOptions {
|
|
14
14
|
repoPath: string;
|
|
15
15
|
worktreeRoot: string;
|
|
16
|
+
agentName?: string;
|
|
16
17
|
execFileImpl?: typeof execFileAsync;
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -28,8 +29,9 @@ export function slugifyTitle(title: string): string {
|
|
|
28
29
|
return (slug || "task").slice(0, 48);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
export function branchNameForIssue(issueIid: number, title: string): string {
|
|
32
|
-
|
|
32
|
+
export function branchNameForIssue(issueIid: number, title: string, agentName?: string): string {
|
|
33
|
+
const prefix = agentName ?? "agent";
|
|
34
|
+
return `${prefix}/issue-${issueIid}-${slugifyTitle(title)}`;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export function worktreePathForIssue(worktreeRoot: string, issueIid: number, title: string): string {
|
|
@@ -41,16 +43,19 @@ export class WorktreeManager {
|
|
|
41
43
|
|
|
42
44
|
private readonly worktreeRoot: string;
|
|
43
45
|
|
|
46
|
+
private readonly agentName: string | undefined;
|
|
47
|
+
|
|
44
48
|
private readonly execFileImpl: typeof execFileAsync;
|
|
45
49
|
|
|
46
50
|
constructor(options: WorktreeManagerOptions) {
|
|
47
51
|
this.repoPath = options.repoPath;
|
|
48
52
|
this.worktreeRoot = options.worktreeRoot;
|
|
53
|
+
this.agentName = options.agentName;
|
|
49
54
|
this.execFileImpl = options.execFileImpl ?? execFileAsync;
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
async ensureWorktree(issueIid: number, title: string): Promise<WorktreeInfo> {
|
|
53
|
-
const branch = branchNameForIssue(issueIid, title);
|
|
58
|
+
const branch = branchNameForIssue(issueIid, title, this.agentName);
|
|
54
59
|
const worktreePath = worktreePathForIssue(this.worktreeRoot, issueIid, title);
|
|
55
60
|
|
|
56
61
|
await mkdir(this.worktreeRoot, { recursive: true });
|