opengate 0.2.3 → 0.2.8
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.d.ts +9 -2
- package/dist/index.js +106 -42
- package/openclaw.plugin.json +7 -3
- package/package.json +6 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import * as openclaw_plugin_sdk from 'openclaw/plugin-sdk';
|
|
1
2
|
import { OpenClawPluginApi } from 'openclaw/plugin-sdk';
|
|
2
3
|
|
|
3
|
-
declare
|
|
4
|
+
declare const _default: {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
configSchema: openclaw_plugin_sdk.OpenClawPluginConfigSchema;
|
|
9
|
+
register(api: OpenClawPluginApi): void;
|
|
10
|
+
};
|
|
4
11
|
|
|
5
|
-
export {
|
|
12
|
+
export { _default as default };
|
package/dist/index.js
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
|
3
|
+
|
|
1
4
|
// src/config.ts
|
|
2
5
|
function resolveConfig(raw) {
|
|
3
6
|
const url = typeof raw.url === "string" ? raw.url.replace(/\/$/, "") : "";
|
|
4
7
|
const apiKey = typeof raw.apiKey === "string" ? raw.apiKey : "";
|
|
5
8
|
if (!url) throw new Error("[opengate] plugin config missing: url");
|
|
6
9
|
if (!apiKey) throw new Error("[opengate] plugin config missing: apiKey");
|
|
10
|
+
const home = process.env.HOME ?? "/home/unknown";
|
|
7
11
|
return {
|
|
8
12
|
url,
|
|
9
13
|
apiKey,
|
|
10
14
|
agentId: typeof raw.agentId === "string" ? raw.agentId : "main",
|
|
11
15
|
model: typeof raw.model === "string" ? raw.model : void 0,
|
|
12
16
|
pollIntervalMs: typeof raw.pollIntervalMs === "number" ? raw.pollIntervalMs : 3e4,
|
|
13
|
-
maxConcurrent: typeof raw.maxConcurrent === "number" ? raw.maxConcurrent : 3
|
|
17
|
+
maxConcurrent: typeof raw.maxConcurrent === "number" ? raw.maxConcurrent : 3,
|
|
18
|
+
projectsDir: typeof raw.projectsDir === "string" ? raw.projectsDir : `${home}/Projects`
|
|
14
19
|
};
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
// src/bootstrap.ts
|
|
18
|
-
function
|
|
23
|
+
function repoNameFromUrl(repoUrl) {
|
|
24
|
+
try {
|
|
25
|
+
const url = new URL(repoUrl);
|
|
26
|
+
const segments = url.pathname.replace(/\.git$/, "").split("/").filter(Boolean);
|
|
27
|
+
return segments.length > 0 ? segments[segments.length - 1] : null;
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function buildBootstrapPrompt(task, openGateUrl, apiKey, project, projectsDir) {
|
|
19
33
|
const tags = Array.isArray(task.tags) && task.tags.length > 0 ? task.tags.join(", ") : "none";
|
|
20
34
|
const contextBlock = task.context && Object.keys(task.context).length > 0 ? `
|
|
21
35
|
## Task Context
|
|
@@ -24,6 +38,26 @@ ${JSON.stringify(task.context, null, 2)}
|
|
|
24
38
|
\`\`\`
|
|
25
39
|
` : "";
|
|
26
40
|
const projectId = task.project_id ?? "unknown";
|
|
41
|
+
const defaultBranch = project?.default_branch ?? "main";
|
|
42
|
+
let workspacePath = null;
|
|
43
|
+
if (project?.repo_url) {
|
|
44
|
+
const repoName = repoNameFromUrl(project.repo_url);
|
|
45
|
+
if (repoName && projectsDir) {
|
|
46
|
+
workspacePath = `${projectsDir}/${repoName}`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const workspaceBlock = workspacePath ? `### Phase 4: Workspace Setup
|
|
50
|
+
6. **Set up your workspace:**
|
|
51
|
+
- \`cd ${workspacePath}\`
|
|
52
|
+
- Pull latest: \`git fetch origin && git checkout ${defaultBranch} && git pull origin ${defaultBranch}\`
|
|
53
|
+
- Create a feature branch: \`git checkout -b <branch-name>\`
|
|
54
|
+
- Branch naming: use the task title slugified, e.g. \`feat/add-user-auth\` or \`fix/null-pointer-in-parser\`
|
|
55
|
+
- If \`${workspacePath}\` does not exist, clone it: \`git clone ${project.repo_url} ${workspacePath}\`` : `### Phase 4: Workspace Setup
|
|
56
|
+
6. **Set up your workspace:**
|
|
57
|
+
- Fetch project info: \`GET /api/projects/${projectId}\` \u2014 read \`repo_url\` and \`default_branch\`
|
|
58
|
+
- Derive the local path from the repo name (under ~/Projects/)
|
|
59
|
+
- If the directory doesn't exist, clone it
|
|
60
|
+
- Create a feature branch from the default branch`;
|
|
27
61
|
return `You are an autonomous coding agent assigned a task via OpenGate.
|
|
28
62
|
|
|
29
63
|
## Your Task (Summary)
|
|
@@ -61,19 +95,12 @@ Before writing any code, gather all available context:
|
|
|
61
95
|
- Read any returned entries \u2014 they contain architecture decisions, patterns, gotchas, and conventions for this project
|
|
62
96
|
- Follow these conventions in your implementation
|
|
63
97
|
|
|
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
98
|
### Phase 3: Plan & Announce
|
|
68
|
-
|
|
99
|
+
4. **Post starting comment** \u2014 \`POST /api/tasks/${task.id}/activity\`
|
|
69
100
|
Body: \`{"content": "Starting work. Plan: <your plan informed by the context you gathered, 2-4 sentences>"}\`
|
|
70
101
|
- Your plan should reflect what you learned from the knowledge base, existing comments, and dependencies
|
|
71
102
|
|
|
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\`
|
|
103
|
+
${workspaceBlock}
|
|
77
104
|
|
|
78
105
|
### Phase 5: Do the Work
|
|
79
106
|
7. **Implement the solution:**
|
|
@@ -90,8 +117,8 @@ Before writing any code, gather all available context:
|
|
|
90
117
|
Body: \`{"title": "...", "content": "...", "tags": [...], "category": "<architecture|pattern|gotcha|decision|reference>"}\`
|
|
91
118
|
- Write entries for: architectural decisions you made, gotchas you encountered, patterns you established
|
|
92
119
|
|
|
93
|
-
10. **Complete** \u2014 \`POST /api/tasks/${task.id}/complete\`
|
|
94
|
-
Body: \`{"summary": "<what was done>", "output": {"branch": "
|
|
120
|
+
10. **Complete the task** \u2014 \`POST /api/tasks/${task.id}/complete\`
|
|
121
|
+
Body: \`{"summary": "<what was done>", "output": {"branch": "<branch-name>", "commits": ["<hash>"]}}\`
|
|
95
122
|
|
|
96
123
|
## Handling Blockers
|
|
97
124
|
If you encounter a blocker that requires human input:
|
|
@@ -217,6 +244,19 @@ var TaskState = class {
|
|
|
217
244
|
};
|
|
218
245
|
|
|
219
246
|
// src/poller.ts
|
|
247
|
+
async function fetchProject(url, apiKey, projectId) {
|
|
248
|
+
try {
|
|
249
|
+
const resp = await fetch(`${url}/api/projects/${projectId}`, {
|
|
250
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
251
|
+
signal: AbortSignal.timeout(1e4)
|
|
252
|
+
});
|
|
253
|
+
if (!resp.ok) return null;
|
|
254
|
+
const body = await resp.json();
|
|
255
|
+
return body.project ?? body;
|
|
256
|
+
} catch {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
220
260
|
async function fetchInbox(url, apiKey) {
|
|
221
261
|
const resp = await fetch(`${url}/api/agents/me/inbox`, {
|
|
222
262
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
@@ -238,6 +278,7 @@ var OpenGatePoller = class {
|
|
|
238
278
|
timer = null;
|
|
239
279
|
state;
|
|
240
280
|
running = false;
|
|
281
|
+
projectCache = /* @__PURE__ */ new Map();
|
|
241
282
|
start() {
|
|
242
283
|
if (this.running) return;
|
|
243
284
|
this.running = true;
|
|
@@ -291,9 +332,26 @@ var OpenGatePoller = class {
|
|
|
291
332
|
await this.spawnTask(task);
|
|
292
333
|
}
|
|
293
334
|
}
|
|
335
|
+
async resolveProject(projectId) {
|
|
336
|
+
const cached = this.projectCache.get(projectId);
|
|
337
|
+
if (cached) return cached;
|
|
338
|
+
const project = await fetchProject(this.pluginCfg.url, this.pluginCfg.apiKey, projectId);
|
|
339
|
+
if (project) this.projectCache.set(projectId, project);
|
|
340
|
+
return project;
|
|
341
|
+
}
|
|
294
342
|
async spawnTask(task) {
|
|
295
343
|
this.logger.info(`[opengate] Spawning session for task: "${task.title}" (${task.id})`);
|
|
296
|
-
|
|
344
|
+
let project = null;
|
|
345
|
+
if (task.project_id) {
|
|
346
|
+
project = await this.resolveProject(task.project_id);
|
|
347
|
+
}
|
|
348
|
+
const prompt = buildBootstrapPrompt(
|
|
349
|
+
task,
|
|
350
|
+
this.pluginCfg.url,
|
|
351
|
+
this.pluginCfg.apiKey,
|
|
352
|
+
project,
|
|
353
|
+
this.pluginCfg.projectsDir
|
|
354
|
+
);
|
|
297
355
|
const result = await spawnTaskSession(
|
|
298
356
|
task.id,
|
|
299
357
|
prompt,
|
|
@@ -312,34 +370,40 @@ var OpenGatePoller = class {
|
|
|
312
370
|
};
|
|
313
371
|
|
|
314
372
|
// src/index.ts
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
);
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
api.registerService({
|
|
332
|
-
id: "opengate-poller",
|
|
333
|
-
start(ctx) {
|
|
334
|
-
poller = new OpenGatePoller(pluginCfg, api.config, ctx.logger, ctx.stateDir);
|
|
335
|
-
poller.start();
|
|
336
|
-
},
|
|
337
|
-
stop(ctx) {
|
|
338
|
-
poller?.stop();
|
|
339
|
-
poller = null;
|
|
373
|
+
var index_default = {
|
|
374
|
+
id: "opengate",
|
|
375
|
+
name: "OpenGate",
|
|
376
|
+
description: "Polls OpenGate for assigned tasks and spawns isolated OpenClaw sessions to execute them. Turns OpenGate into the orchestrator.",
|
|
377
|
+
configSchema: emptyPluginConfigSchema(),
|
|
378
|
+
register(api) {
|
|
379
|
+
let poller = null;
|
|
380
|
+
let pluginCfg;
|
|
381
|
+
try {
|
|
382
|
+
pluginCfg = resolveConfig(api.pluginConfig ?? {});
|
|
383
|
+
} catch (e) {
|
|
384
|
+
api.logger.error(e instanceof Error ? e.message : String(e));
|
|
385
|
+
return;
|
|
340
386
|
}
|
|
341
|
-
|
|
342
|
-
|
|
387
|
+
const hooksToken = api.config?.hooks?.token;
|
|
388
|
+
if (!hooksToken) {
|
|
389
|
+
api.logger.error(
|
|
390
|
+
'[opengate] hooks.token is not configured. Add the following to your OpenClaw config to enable task spawning:\n "hooks": { "enabled": true, "token": "<your-secret>", "allowRequestSessionKey": true, "allowedSessionKeyPrefixes": ["opengate-task:"] }'
|
|
391
|
+
);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
api.registerService({
|
|
395
|
+
id: "opengate-poller",
|
|
396
|
+
start(ctx) {
|
|
397
|
+
poller = new OpenGatePoller(pluginCfg, api.config, ctx.logger, ctx.stateDir);
|
|
398
|
+
poller.start();
|
|
399
|
+
},
|
|
400
|
+
stop(ctx) {
|
|
401
|
+
poller?.stop();
|
|
402
|
+
poller = null;
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
};
|
|
343
407
|
export {
|
|
344
|
-
|
|
408
|
+
index_default as default
|
|
345
409
|
};
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
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.8",
|
|
6
6
|
"skills": ["./skills/opengate"],
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
|
9
9
|
"additionalProperties": false,
|
|
10
|
-
"required": ["url", "apiKey"],
|
|
11
10
|
"properties": {
|
|
12
11
|
"url": {
|
|
13
12
|
"type": "string",
|
|
@@ -32,6 +31,10 @@
|
|
|
32
31
|
"maxConcurrent": {
|
|
33
32
|
"type": "number",
|
|
34
33
|
"description": "Max concurrent task sessions (default: 3)"
|
|
34
|
+
},
|
|
35
|
+
"projectsDir": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "Base directory where project repos live on disk (default: ~/Projects)"
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
},
|
|
@@ -41,6 +44,7 @@
|
|
|
41
44
|
"agentId": { "label": "OpenClaw Agent ID", "placeholder": "main" },
|
|
42
45
|
"model": { "label": "Model Override", "placeholder": "anthropic/claude-sonnet-4-6" },
|
|
43
46
|
"pollIntervalMs": { "label": "Poll Interval (ms)", "advanced": true },
|
|
44
|
-
"maxConcurrent": { "label": "Max Concurrent Tasks", "advanced": true }
|
|
47
|
+
"maxConcurrent": { "label": "Max Concurrent Tasks", "advanced": true },
|
|
48
|
+
"projectsDir": { "label": "Projects Directory", "placeholder": "~/Projects", "advanced": true }
|
|
45
49
|
}
|
|
46
50
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opengate",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
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",
|
|
@@ -26,7 +26,11 @@
|
|
|
26
26
|
"openclaw": {
|
|
27
27
|
"extensions": [
|
|
28
28
|
"./dist/index.js"
|
|
29
|
-
]
|
|
29
|
+
],
|
|
30
|
+
"install": {
|
|
31
|
+
"npmSpec": "opengate",
|
|
32
|
+
"defaultChoice": "npm"
|
|
33
|
+
}
|
|
30
34
|
},
|
|
31
35
|
"publishConfig": {
|
|
32
36
|
"access": "public"
|