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 +69 -14
- package/openclaw.plugin.json +7 -2
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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
|
-
|
|
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": "
|
|
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
|
-
|
|
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,
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
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
|
}
|