opengate 0.2.2 → 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 +124 -17
- 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
|
|
@@ -23,42 +34,107 @@ function buildBootstrapPrompt(task, openGateUrl, apiKey) {
|
|
|
23
34
|
${JSON.stringify(task.context, null, 2)}
|
|
24
35
|
\`\`\`
|
|
25
36
|
` : "";
|
|
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`;
|
|
26
58
|
return `You are an autonomous coding agent assigned a task via OpenGate.
|
|
27
59
|
|
|
28
|
-
## Your Task
|
|
60
|
+
## Your Task (Summary)
|
|
29
61
|
**ID:** ${task.id}
|
|
30
62
|
**Title:** ${task.title}
|
|
31
63
|
**Priority:** ${task.priority ?? "medium"}
|
|
32
64
|
**Tags:** ${tags}
|
|
33
|
-
**Project ID:** ${
|
|
65
|
+
**Project ID:** ${projectId}
|
|
34
66
|
|
|
35
67
|
**Description:**
|
|
36
68
|
${task.description ?? "(no description provided)"}
|
|
37
69
|
${contextBlock}
|
|
38
70
|
## OpenGate API
|
|
39
71
|
- **Base URL:** ${openGateUrl}
|
|
40
|
-
- **Auth:** Bearer ${apiKey}
|
|
72
|
+
- **Auth header:** Authorization: Bearer ${apiKey}
|
|
73
|
+
|
|
74
|
+
All API calls below use this base URL and auth header.
|
|
41
75
|
|
|
42
76
|
## Protocol \u2014 Follow This Exactly
|
|
43
|
-
You MUST follow these steps in order. Skipping any step is not acceptable.
|
|
44
77
|
|
|
45
|
-
1
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
78
|
+
### Phase 1: Claim
|
|
79
|
+
1. **Claim the task** \u2014 \`POST /api/tasks/${task.id}/claim\`
|
|
80
|
+
|
|
81
|
+
### Phase 2: Gather Context
|
|
82
|
+
Before writing any code, gather all available context:
|
|
83
|
+
|
|
84
|
+
2. **Fetch full task details** \u2014 \`GET /api/tasks/${task.id}\`
|
|
85
|
+
- Read the \`activities\` array for comments, instructions, and prior discussion
|
|
86
|
+
- Read the \`artifacts\` array for any attached files or links
|
|
87
|
+
- Read the \`dependencies\` array \u2014 if any dependency has status != "done", note it as a potential blocker
|
|
88
|
+
- Pay close attention to reviewer comments or change requests in activities
|
|
89
|
+
|
|
90
|
+
3. **Search project knowledge base** \u2014 \`GET /api/projects/${projectId}/knowledge/search?q=${task.title}\`
|
|
91
|
+
- Also search by tags if present: \`GET /api/projects/${projectId}/knowledge/search?tags=${tags}\`
|
|
92
|
+
- Read any returned entries \u2014 they contain architecture decisions, patterns, gotchas, and conventions for this project
|
|
93
|
+
- Follow these conventions in your implementation
|
|
94
|
+
|
|
95
|
+
### Phase 3: Plan & Announce
|
|
96
|
+
4. **Post starting comment** \u2014 \`POST /api/tasks/${task.id}/activity\`
|
|
97
|
+
Body: \`{"content": "Starting work. Plan: <your plan informed by the context you gathered, 2-4 sentences>"}\`
|
|
98
|
+
- Your plan should reflect what you learned from the knowledge base, existing comments, and dependencies
|
|
99
|
+
|
|
100
|
+
${workspaceBlock}
|
|
101
|
+
|
|
102
|
+
### Phase 5: Do the Work
|
|
103
|
+
7. **Implement the solution:**
|
|
104
|
+
- Follow patterns and conventions from the knowledge base
|
|
105
|
+
- Write clean, tested code
|
|
106
|
+
- Run the project's test suite and fix any failures
|
|
107
|
+
- Commit your changes with a descriptive message referencing the task
|
|
50
108
|
|
|
109
|
+
### Phase 6: Report & Complete
|
|
110
|
+
8. **Post results comment** \u2014 \`POST /api/tasks/${task.id}/activity\`
|
|
111
|
+
Body: \`{"content": "<summary of what changed: files modified, approach taken, test results, commit hash>"}\`
|
|
112
|
+
|
|
113
|
+
9. **Write knowledge** (if you discovered something worth sharing) \u2014 \`PUT /api/projects/${projectId}/knowledge/<key>\`
|
|
114
|
+
Body: \`{"title": "...", "content": "...", "tags": [...], "category": "<architecture|pattern|gotcha|decision|reference>"}\`
|
|
115
|
+
- Write entries for: architectural decisions you made, gotchas you encountered, patterns you established
|
|
116
|
+
|
|
117
|
+
10. **Complete the task** \u2014 \`POST /api/tasks/${task.id}/complete\`
|
|
118
|
+
Body: \`{"summary": "<what was done>", "output": {"branch": "<branch-name>", "commits": ["<hash>"]}}\`
|
|
119
|
+
|
|
120
|
+
## Handling Blockers
|
|
51
121
|
If you encounter a blocker that requires human input:
|
|
52
|
-
- Post a question: \`POST /api/tasks/${task.id}/activity\` with \`{"content": "BLOCKED: <
|
|
122
|
+
- Post a question: \`POST /api/tasks/${task.id}/activity\` with \`{"content": "BLOCKED: <describe the issue and what you need>"}\`
|
|
53
123
|
- Block the task: \`POST /api/tasks/${task.id}/block\` with \`{"reason": "<reason>"}\`
|
|
54
124
|
- Then stop \u2014 do NOT mark it complete.
|
|
55
125
|
|
|
56
|
-
|
|
126
|
+
If a dependency is not yet done:
|
|
127
|
+
- Post a comment noting which dependency is blocking you
|
|
128
|
+
- Block the task with the dependency info
|
|
129
|
+
- Stop and let the orchestrator handle sequencing
|
|
130
|
+
|
|
131
|
+
## Rules
|
|
57
132
|
- Always work on a feature branch, never commit directly to main
|
|
58
|
-
- Run tests before completing
|
|
59
|
-
-
|
|
133
|
+
- Run tests before completing \u2014 do not complete with failing tests
|
|
134
|
+
- Respect existing patterns found in the knowledge base
|
|
135
|
+
- Keep commits atomic and descriptive
|
|
60
136
|
|
|
61
|
-
Now begin. Start
|
|
137
|
+
Now begin. Start with Phase 1: claim the task.`;
|
|
62
138
|
}
|
|
63
139
|
|
|
64
140
|
// src/spawner.ts
|
|
@@ -165,6 +241,19 @@ var TaskState = class {
|
|
|
165
241
|
};
|
|
166
242
|
|
|
167
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
|
+
}
|
|
168
257
|
async function fetchInbox(url, apiKey) {
|
|
169
258
|
const resp = await fetch(`${url}/api/agents/me/inbox`, {
|
|
170
259
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
@@ -186,6 +275,7 @@ var OpenGatePoller = class {
|
|
|
186
275
|
timer = null;
|
|
187
276
|
state;
|
|
188
277
|
running = false;
|
|
278
|
+
projectCache = /* @__PURE__ */ new Map();
|
|
189
279
|
start() {
|
|
190
280
|
if (this.running) return;
|
|
191
281
|
this.running = true;
|
|
@@ -239,9 +329,26 @@ var OpenGatePoller = class {
|
|
|
239
329
|
await this.spawnTask(task);
|
|
240
330
|
}
|
|
241
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
|
+
}
|
|
242
339
|
async spawnTask(task) {
|
|
243
340
|
this.logger.info(`[opengate] Spawning session for task: "${task.title}" (${task.id})`);
|
|
244
|
-
|
|
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
|
+
);
|
|
245
352
|
const result = await spawnTaskSession(
|
|
246
353
|
task.id,
|
|
247
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
|
}
|