opengate 0.2.11 → 0.2.12

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.
Files changed (2) hide show
  1. package/dist/index.js +137 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -168,6 +168,52 @@ If you receive a \`task.comment_mention\` notification:
168
168
 
169
169
  Now begin. Start with Phase 1: claim the task.`;
170
170
  }
171
+ function buildMentionPrompt(taskId, commentBody, commentAuthor, openGateUrl, apiKey, project, projectsDir) {
172
+ let workspacePath = null;
173
+ if (project?.repo_url) {
174
+ const repoName = repoNameFromUrl(project.repo_url);
175
+ if (repoName && projectsDir) {
176
+ workspacePath = `${projectsDir}/${repoName}`;
177
+ }
178
+ }
179
+ const workspaceInstruction = workspacePath ? `Your project workspace is at: ${workspacePath}
180
+ Change into this directory before any file operations.` : "";
181
+ return `You are an AI agent responding to an @-mention in a task comment.
182
+
183
+ ## API Access
184
+ - Base URL: ${openGateUrl}
185
+ - API Key: ${apiKey} (pass as Authorization: Bearer header)
186
+
187
+ ## Task
188
+ - Task ID: ${taskId}
189
+ ${workspaceInstruction}
190
+
191
+ ## The Comment That Mentioned You
192
+ **Author:** ${commentAuthor}
193
+ **Content:**
194
+ ${commentBody}
195
+
196
+ ## Instructions
197
+
198
+ 1. Fetch the full task context: GET ${openGateUrl}/api/tasks/${taskId} (with Authorization header)
199
+ 2. Read the task description, status, and recent activity to understand context
200
+ 3. Reason about what the comment author is asking:
201
+ - If it's a **question** \u2192 post a reply comment with your answer
202
+ - If it's a **request for changes** \u2192 assess what's needed, make the changes if possible, and post a comment describing what you did
203
+ - If it's a **simple acknowledgment or FYI** \u2192 post a brief confirmation comment
204
+ 4. Post your reply: POST ${openGateUrl}/api/tasks/${taskId}/activity
205
+ Body: {"content": "<your reply>", "activity_type": "comment"}
206
+ 5. If you made code changes, post a summary of what changed
207
+
208
+ ## Rules
209
+ - ALWAYS reply with a comment so the author knows you've seen the mention
210
+ - Do NOT change the task status unless explicitly asked to
211
+ - Do NOT try to claim or complete the task \u2014 you are just responding to a comment
212
+ - Keep your response focused and concise
213
+ - If you need to make code changes, work on a feature branch
214
+
215
+ Begin by fetching the task context.`;
216
+ }
171
217
 
172
218
  // src/spawner.ts
173
219
  async function spawnTaskSession(taskId, message, pluginCfg, openclawCfg) {
@@ -304,6 +350,34 @@ async function fetchInbox(url, apiKey) {
304
350
  inProgressTasks: body.in_progress_tasks ?? []
305
351
  };
306
352
  }
353
+ async function fetchNotifications(url, apiKey) {
354
+ const resp = await fetch(`${url}/api/agents/me/notifications?unread=true`, {
355
+ headers: { Authorization: `Bearer ${apiKey}` },
356
+ signal: AbortSignal.timeout(1e4)
357
+ });
358
+ if (!resp.ok) return [];
359
+ return await resp.json();
360
+ }
361
+ async function ackNotification(url, apiKey, notifId) {
362
+ await fetch(`${url}/api/agents/me/notifications/${notifId}/ack`, {
363
+ method: "POST",
364
+ headers: { Authorization: `Bearer ${apiKey}` },
365
+ signal: AbortSignal.timeout(1e4)
366
+ }).catch(() => {
367
+ });
368
+ }
369
+ async function fetchTaskById(url, apiKey, taskId) {
370
+ try {
371
+ const resp = await fetch(`${url}/api/tasks/${taskId}`, {
372
+ headers: { Authorization: `Bearer ${apiKey}` },
373
+ signal: AbortSignal.timeout(1e4)
374
+ });
375
+ if (!resp.ok) return null;
376
+ return await resp.json();
377
+ } catch {
378
+ return null;
379
+ }
380
+ }
307
381
  var OpenGatePoller = class {
308
382
  constructor(pluginCfg, openclawCfg, logger, stateDir) {
309
383
  this.pluginCfg = pluginCfg;
@@ -362,6 +436,7 @@ var OpenGatePoller = class {
362
436
  await this.releaseTask(task.id);
363
437
  }
364
438
  }
439
+ await this.handleNotifications();
365
440
  const maxConcurrent = this.pluginCfg.maxConcurrent ?? 3;
366
441
  const active = this.state.activeCount();
367
442
  if (active >= maxConcurrent) {
@@ -414,6 +489,68 @@ var OpenGatePoller = class {
414
489
  );
415
490
  }
416
491
  }
492
+ async handleNotifications() {
493
+ let notifications;
494
+ try {
495
+ notifications = await fetchNotifications(this.pluginCfg.url, this.pluginCfg.apiKey);
496
+ } catch {
497
+ return;
498
+ }
499
+ const mentionNotifs = notifications.filter(
500
+ (n) => n.event_type === "task.comment_mention" && n.task_id
501
+ );
502
+ for (const notif of mentionNotifs) {
503
+ if (!this.running) break;
504
+ const maxConcurrent = this.pluginCfg.maxConcurrent ?? 3;
505
+ if (this.state.activeCount() >= maxConcurrent) {
506
+ this.logger.info("[opengate] At capacity \u2014 deferring mention notifications");
507
+ break;
508
+ }
509
+ const mentionKey = `mention:${notif.id}`;
510
+ if (this.state.isSpawned(mentionKey)) continue;
511
+ await this.spawnMentionSession(notif);
512
+ await ackNotification(this.pluginCfg.url, this.pluginCfg.apiKey, notif.id);
513
+ }
514
+ }
515
+ async spawnMentionSession(notif) {
516
+ const taskId = notif.task_id;
517
+ this.logger.info(
518
+ `[opengate] Handling @-mention notification ${notif.id} on task ${taskId}`
519
+ );
520
+ const bodyText = notif.body ?? "";
521
+ const colonIdx = bodyText.indexOf(": ");
522
+ const author = colonIdx > 0 ? bodyText.slice(0, colonIdx) : "Someone";
523
+ const commentBody = colonIdx > 0 ? bodyText.slice(colonIdx + 2) : bodyText;
524
+ const task = await fetchTaskById(this.pluginCfg.url, this.pluginCfg.apiKey, taskId);
525
+ let project = null;
526
+ if (task?.project_id) {
527
+ project = await this.resolveProject(task.project_id);
528
+ }
529
+ const prompt = buildMentionPrompt(
530
+ taskId,
531
+ commentBody,
532
+ author,
533
+ this.pluginCfg.url,
534
+ this.pluginCfg.apiKey,
535
+ project,
536
+ this.pluginCfg.projectsDir
537
+ );
538
+ const result = await spawnTaskSession(
539
+ `mention-${notif.id}`,
540
+ prompt,
541
+ this.pluginCfg,
542
+ this.openclawCfg
543
+ );
544
+ if (!result.ok) {
545
+ this.logger.error(result.error);
546
+ return;
547
+ }
548
+ const mentionKey = `mention:${notif.id}`;
549
+ this.state.markSpawned(mentionKey, result.sessionKey);
550
+ this.logger.info(
551
+ `[opengate] Mention session spawned for notification ${notif.id} \u2192 ${result.sessionKey}`
552
+ );
553
+ }
417
554
  async spawnTask(task) {
418
555
  this.logger.info(`[opengate] Spawning session for task: "${task.title}" (${task.id})`);
419
556
  let project = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opengate",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
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",