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 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 function register(api: OpenClawPluginApi): void;
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 { register as default };
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 buildBootstrapPrompt(task, openGateUrl, apiKey) {
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
- 5. **Post starting comment** \u2014 \`POST /api/tasks/${task.id}/activity\`
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
- ### 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\`
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": "...", "commits": [...]}}\`
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
- const prompt = buildBootstrapPrompt(task, this.pluginCfg.url, this.pluginCfg.apiKey);
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
- function register(api) {
316
- let poller = null;
317
- let pluginCfg;
318
- try {
319
- pluginCfg = resolveConfig(api.pluginConfig ?? {});
320
- } catch (e) {
321
- api.logger.error(e instanceof Error ? e.message : String(e));
322
- return;
323
- }
324
- const hooksToken = api.config?.hooks?.token;
325
- if (!hooksToken) {
326
- api.logger.error(
327
- '[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:"] }'
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
- register as default
408
+ index_default as default
345
409
  };
@@ -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.3",
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",
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"