opengate 0.2.3 → 0.2.4

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/dist/index.js CHANGED
@@ -4,18 +4,29 @@ function resolveConfig(raw) {
4
4
  const apiKey = typeof raw.apiKey === "string" ? raw.apiKey : "";
5
5
  if (!url) throw new Error("[opengate] plugin config missing: url");
6
6
  if (!apiKey) throw new Error("[opengate] plugin config missing: apiKey");
7
+ const home = process.env.HOME ?? "/home/unknown";
7
8
  return {
8
9
  url,
9
10
  apiKey,
10
11
  agentId: typeof raw.agentId === "string" ? raw.agentId : "main",
11
12
  model: typeof raw.model === "string" ? raw.model : void 0,
12
13
  pollIntervalMs: typeof raw.pollIntervalMs === "number" ? raw.pollIntervalMs : 3e4,
13
- maxConcurrent: typeof raw.maxConcurrent === "number" ? raw.maxConcurrent : 3
14
+ maxConcurrent: typeof raw.maxConcurrent === "number" ? raw.maxConcurrent : 3,
15
+ projectsDir: typeof raw.projectsDir === "string" ? raw.projectsDir : `${home}/Projects`
14
16
  };
15
17
  }
16
18
 
17
19
  // src/bootstrap.ts
18
- function buildBootstrapPrompt(task, openGateUrl, apiKey) {
20
+ function repoNameFromUrl(repoUrl) {
21
+ try {
22
+ const url = new URL(repoUrl);
23
+ const segments = url.pathname.replace(/\.git$/, "").split("/").filter(Boolean);
24
+ return segments.length > 0 ? segments[segments.length - 1] : null;
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+ function buildBootstrapPrompt(task, openGateUrl, apiKey, project, projectsDir) {
19
30
  const tags = Array.isArray(task.tags) && task.tags.length > 0 ? task.tags.join(", ") : "none";
20
31
  const contextBlock = task.context && Object.keys(task.context).length > 0 ? `
21
32
  ## Task Context
@@ -24,6 +35,26 @@ ${JSON.stringify(task.context, null, 2)}
24
35
  \`\`\`
25
36
  ` : "";
26
37
  const projectId = task.project_id ?? "unknown";
38
+ const defaultBranch = project?.default_branch ?? "main";
39
+ let workspacePath = null;
40
+ if (project?.repo_url) {
41
+ const repoName = repoNameFromUrl(project.repo_url);
42
+ if (repoName && projectsDir) {
43
+ workspacePath = `${projectsDir}/${repoName}`;
44
+ }
45
+ }
46
+ const workspaceBlock = workspacePath ? `### Phase 4: Workspace Setup
47
+ 6. **Set up your workspace:**
48
+ - \`cd ${workspacePath}\`
49
+ - Pull latest: \`git fetch origin && git checkout ${defaultBranch} && git pull origin ${defaultBranch}\`
50
+ - Create a feature branch: \`git checkout -b <branch-name>\`
51
+ - Branch naming: use the task title slugified, e.g. \`feat/add-user-auth\` or \`fix/null-pointer-in-parser\`
52
+ - If \`${workspacePath}\` does not exist, clone it: \`git clone ${project.repo_url} ${workspacePath}\`` : `### Phase 4: Workspace Setup
53
+ 6. **Set up your workspace:**
54
+ - Fetch project info: \`GET /api/projects/${projectId}\` \u2014 read \`repo_url\` and \`default_branch\`
55
+ - Derive the local path from the repo name (under ~/Projects/)
56
+ - If the directory doesn't exist, clone it
57
+ - Create a feature branch from the default branch`;
27
58
  return `You are an autonomous coding agent assigned a task via OpenGate.
28
59
 
29
60
  ## Your Task (Summary)
@@ -61,19 +92,12 @@ Before writing any code, gather all available context:
61
92
  - Read any returned entries \u2014 they contain architecture decisions, patterns, gotchas, and conventions for this project
62
93
  - Follow these conventions in your implementation
63
94
 
64
- 4. **Check project info** \u2014 \`GET /api/projects/${projectId}\`
65
- - Note the \`repo_url\` and \`default_branch\` \u2014 use these for your workspace setup
66
-
67
95
  ### Phase 3: Plan & Announce
68
- 5. **Post starting comment** \u2014 \`POST /api/tasks/${task.id}/activity\`
96
+ 4. **Post starting comment** \u2014 \`POST /api/tasks/${task.id}/activity\`
69
97
  Body: \`{"content": "Starting work. Plan: <your plan informed by the context you gathered, 2-4 sentences>"}\`
70
98
  - Your plan should reflect what you learned from the knowledge base, existing comments, and dependencies
71
99
 
72
- ### Phase 4: Workspace Setup
73
- 6. **Set up your workspace:**
74
- - Navigate to the project workspace (based on \`repo_url\` from the project info)
75
- - Create a feature branch from the default branch: \`git checkout -b <branch-name>\`
76
- - Branch naming: use the task title slugified, e.g. \`feat/add-user-auth\` or \`fix/null-pointer-in-parser\`
100
+ ${workspaceBlock}
77
101
 
78
102
  ### Phase 5: Do the Work
79
103
  7. **Implement the solution:**
@@ -90,8 +114,8 @@ Before writing any code, gather all available context:
90
114
  Body: \`{"title": "...", "content": "...", "tags": [...], "category": "<architecture|pattern|gotcha|decision|reference>"}\`
91
115
  - Write entries for: architectural decisions you made, gotchas you encountered, patterns you established
92
116
 
93
- 10. **Complete** \u2014 \`POST /api/tasks/${task.id}/complete\`
94
- Body: \`{"summary": "<what was done>", "output": {"branch": "...", "commits": [...]}}\`
117
+ 10. **Complete the task** \u2014 \`POST /api/tasks/${task.id}/complete\`
118
+ Body: \`{"summary": "<what was done>", "output": {"branch": "<branch-name>", "commits": ["<hash>"]}}\`
95
119
 
96
120
  ## Handling Blockers
97
121
  If you encounter a blocker that requires human input:
@@ -217,6 +241,19 @@ var TaskState = class {
217
241
  };
218
242
 
219
243
  // src/poller.ts
244
+ async function fetchProject(url, apiKey, projectId) {
245
+ try {
246
+ const resp = await fetch(`${url}/api/projects/${projectId}`, {
247
+ headers: { Authorization: `Bearer ${apiKey}` },
248
+ signal: AbortSignal.timeout(1e4)
249
+ });
250
+ if (!resp.ok) return null;
251
+ const body = await resp.json();
252
+ return body.project ?? body;
253
+ } catch {
254
+ return null;
255
+ }
256
+ }
220
257
  async function fetchInbox(url, apiKey) {
221
258
  const resp = await fetch(`${url}/api/agents/me/inbox`, {
222
259
  headers: { Authorization: `Bearer ${apiKey}` },
@@ -238,6 +275,7 @@ var OpenGatePoller = class {
238
275
  timer = null;
239
276
  state;
240
277
  running = false;
278
+ projectCache = /* @__PURE__ */ new Map();
241
279
  start() {
242
280
  if (this.running) return;
243
281
  this.running = true;
@@ -291,9 +329,26 @@ var OpenGatePoller = class {
291
329
  await this.spawnTask(task);
292
330
  }
293
331
  }
332
+ async resolveProject(projectId) {
333
+ const cached = this.projectCache.get(projectId);
334
+ if (cached) return cached;
335
+ const project = await fetchProject(this.pluginCfg.url, this.pluginCfg.apiKey, projectId);
336
+ if (project) this.projectCache.set(projectId, project);
337
+ return project;
338
+ }
294
339
  async spawnTask(task) {
295
340
  this.logger.info(`[opengate] Spawning session for task: "${task.title}" (${task.id})`);
296
- const prompt = buildBootstrapPrompt(task, this.pluginCfg.url, this.pluginCfg.apiKey);
341
+ let project = null;
342
+ if (task.project_id) {
343
+ project = await this.resolveProject(task.project_id);
344
+ }
345
+ const prompt = buildBootstrapPrompt(
346
+ task,
347
+ this.pluginCfg.url,
348
+ this.pluginCfg.apiKey,
349
+ project,
350
+ this.pluginCfg.projectsDir
351
+ );
297
352
  const result = await spawnTaskSession(
298
353
  task.id,
299
354
  prompt,
@@ -2,7 +2,7 @@
2
2
  "id": "opengate",
3
3
  "name": "OpenGate",
4
4
  "description": "Polls OpenGate for assigned tasks and spawns isolated OpenClaw sessions to execute them. Turns OpenGate into the orchestrator.",
5
- "version": "0.2.3",
5
+ "version": "0.2.4",
6
6
  "skills": ["./skills/opengate"],
7
7
  "configSchema": {
8
8
  "type": "object",
@@ -32,6 +32,10 @@
32
32
  "maxConcurrent": {
33
33
  "type": "number",
34
34
  "description": "Max concurrent task sessions (default: 3)"
35
+ },
36
+ "projectsDir": {
37
+ "type": "string",
38
+ "description": "Base directory where project repos live on disk (default: ~/Projects)"
35
39
  }
36
40
  }
37
41
  },
@@ -41,6 +45,7 @@
41
45
  "agentId": { "label": "OpenClaw Agent ID", "placeholder": "main" },
42
46
  "model": { "label": "Model Override", "placeholder": "anthropic/claude-sonnet-4-6" },
43
47
  "pollIntervalMs": { "label": "Poll Interval (ms)", "advanced": true },
44
- "maxConcurrent": { "label": "Max Concurrent Tasks", "advanced": true }
48
+ "maxConcurrent": { "label": "Max Concurrent Tasks", "advanced": true },
49
+ "projectsDir": { "label": "Projects Directory", "placeholder": "~/Projects", "advanced": true }
45
50
  }
46
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opengate",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "OpenGate task executor plugin for OpenClaw — polls assigned tasks and spawns isolated agent sessions",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",