opengate 0.2.9 → 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.
- package/dist/index.js +198 -16
- package/package.json +1 -1
- package/skills/opengate/SKILL.md +54 -14
package/dist/index.js
CHANGED
|
@@ -95,9 +95,11 @@ Before writing any code, gather all available context:
|
|
|
95
95
|
- Read any returned entries \u2014 they contain architecture decisions, patterns, gotchas, and conventions for this project
|
|
96
96
|
- Follow these conventions in your implementation
|
|
97
97
|
|
|
98
|
-
### Phase 3: Plan & Announce
|
|
98
|
+
### Phase 3: Plan & Announce (REQUIRED)
|
|
99
99
|
4. **Post starting comment** \u2014 \`POST /api/tasks/${task.id}/activity\`
|
|
100
|
-
Body: \`{"content": "Starting
|
|
100
|
+
Body: \`{"content": "## Starting Work\\n\\n**Plan:**\\n- <step 1>\\n- <step 2>\\n\\n**Context from KB:** <what you learned>\\n\\n**Concerns:** <any blockers or risks>"}\`
|
|
101
|
+
|
|
102
|
+
This comment is REQUIRED. Do not skip it.
|
|
101
103
|
- Your plan should reflect what you learned from the knowledge base, existing comments, and dependencies
|
|
102
104
|
|
|
103
105
|
${workspaceBlock}
|
|
@@ -109,15 +111,32 @@ ${workspaceBlock}
|
|
|
109
111
|
- Run the project's test suite and fix any failures
|
|
110
112
|
- Commit your changes with a descriptive message referencing the task
|
|
111
113
|
|
|
112
|
-
### Phase
|
|
114
|
+
### Phase 5.5: Knowledge Review (MANDATORY)
|
|
115
|
+
Before completing the task, review your work and ask yourself:
|
|
116
|
+
- Did I make an architecture or design decision? \u2192 Write it (category: "decision" or "architecture")
|
|
117
|
+
- Did I encounter a non-obvious gotcha or bug? \u2192 Write it (category: "gotcha")
|
|
118
|
+
- Did I establish or follow a pattern others should know? \u2192 Write it (category: "pattern")
|
|
119
|
+
- Did I discover project structure or conventions? \u2192 Write it (category: "reference")
|
|
120
|
+
|
|
121
|
+
If ANY of the above apply, write knowledge entries BEFORE completing:
|
|
122
|
+
\`PUT /api/projects/${projectId}/knowledge/<descriptive-key>\`
|
|
123
|
+
Body: \`{"title": "...", "content": "...", "tags": [...], "category": "..."}\`
|
|
124
|
+
|
|
125
|
+
Key naming: use kebab-case descriptors, e.g. "auth-middleware-pattern", "db-migration-gotcha"
|
|
126
|
+
|
|
127
|
+
IMPORTANT: Only write PROJECT-SCOPED knowledge. Do NOT write task-specific details.
|
|
128
|
+
Good: "The API uses tower middleware for auth, not axum extractors"
|
|
129
|
+
Bad: "Task T-123 required adding a new endpoint"
|
|
130
|
+
|
|
131
|
+
If nothing new was discovered, that's fine \u2014 skip this phase.
|
|
132
|
+
|
|
133
|
+
### Phase 6: Report & Complete (REQUIRED)
|
|
113
134
|
8. **Post results comment** \u2014 \`POST /api/tasks/${task.id}/activity\`
|
|
114
|
-
Body: \`{"content": "<
|
|
135
|
+
Body: \`{"content": "## Results\\n\\n**Changes:**\\n- <file1>: <what changed>\\n- <file2>: <what changed>\\n\\n**Approach:** <brief explanation>\\n\\n**Tests:** <pass/fail status>\\n\\n**Commits:** <hash(es)>"}\`
|
|
115
136
|
|
|
116
|
-
|
|
117
|
-
Body: \`{"title": "...", "content": "...", "tags": [...], "category": "<architecture|pattern|gotcha|decision|reference>"}\`
|
|
118
|
-
- Write entries for: architectural decisions you made, gotchas you encountered, patterns you established
|
|
137
|
+
This comment is REQUIRED. Do not skip it.
|
|
119
138
|
|
|
120
|
-
|
|
139
|
+
9. **Complete the task** \u2014 \`POST /api/tasks/${task.id}/complete\`
|
|
121
140
|
Body: \`{"summary": "<what was done>", "output": {"branch": "<branch-name>", "commits": ["<hash>"]}}\`
|
|
122
141
|
|
|
123
142
|
## Handling Blockers
|
|
@@ -137,8 +156,64 @@ If a dependency is not yet done:
|
|
|
137
156
|
- Respect existing patterns found in the knowledge base
|
|
138
157
|
- Keep commits atomic and descriptive
|
|
139
158
|
|
|
159
|
+
## Handling @-Mentions
|
|
160
|
+
If you receive a \`task.comment_mention\` notification:
|
|
161
|
+
1. Read the comment content from the notification body
|
|
162
|
+
2. Fetch the full task context: \`GET /api/tasks/{taskId}\`
|
|
163
|
+
3. Reason about what's being asked:
|
|
164
|
+
- If it's a question \u2192 post a reply comment with your answer
|
|
165
|
+
- If it's a request for changes \u2192 update task status to in_progress, make the changes, then re-complete
|
|
166
|
+
- If it's a simple acknowledgment \u2192 post a brief confirmation comment
|
|
167
|
+
4. Always reply with a comment so the human knows you've seen it
|
|
168
|
+
|
|
140
169
|
Now begin. Start with Phase 1: claim the task.`;
|
|
141
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
|
+
}
|
|
142
217
|
|
|
143
218
|
// src/spawner.ts
|
|
144
219
|
async function spawnTaskSession(taskId, message, pluginCfg, openclawCfg) {
|
|
@@ -241,6 +316,10 @@ var TaskState = class {
|
|
|
241
316
|
activeCount() {
|
|
242
317
|
return Object.keys(this.data.spawned).length;
|
|
243
318
|
}
|
|
319
|
+
/** Returns all task IDs currently in the spawned set. */
|
|
320
|
+
spawnedIds() {
|
|
321
|
+
return Object.keys(this.data.spawned);
|
|
322
|
+
}
|
|
244
323
|
};
|
|
245
324
|
|
|
246
325
|
// src/poller.ts
|
|
@@ -271,6 +350,34 @@ async function fetchInbox(url, apiKey) {
|
|
|
271
350
|
inProgressTasks: body.in_progress_tasks ?? []
|
|
272
351
|
};
|
|
273
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
|
+
}
|
|
274
381
|
var OpenGatePoller = class {
|
|
275
382
|
constructor(pluginCfg, openclawCfg, logger, stateDir) {
|
|
276
383
|
this.pluginCfg = pluginCfg;
|
|
@@ -300,14 +407,6 @@ var OpenGatePoller = class {
|
|
|
300
407
|
}
|
|
301
408
|
async poll() {
|
|
302
409
|
if (!this.running) return;
|
|
303
|
-
const maxConcurrent = this.pluginCfg.maxConcurrent ?? 3;
|
|
304
|
-
const active = this.state.activeCount();
|
|
305
|
-
if (active >= maxConcurrent) {
|
|
306
|
-
this.logger.info(
|
|
307
|
-
`[opengate] At capacity (${active}/${maxConcurrent} active) \u2014 skipping poll`
|
|
308
|
-
);
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
410
|
let inbox;
|
|
312
411
|
try {
|
|
313
412
|
inbox = await fetchInbox(this.pluginCfg.url, this.pluginCfg.apiKey);
|
|
@@ -317,6 +416,18 @@ var OpenGatePoller = class {
|
|
|
317
416
|
);
|
|
318
417
|
return;
|
|
319
418
|
}
|
|
419
|
+
const inboxTaskIds = /* @__PURE__ */ new Set([
|
|
420
|
+
...inbox.todoTasks.map((t) => t.id),
|
|
421
|
+
...inbox.inProgressTasks.map((t) => t.id)
|
|
422
|
+
]);
|
|
423
|
+
for (const spawnedId of this.state.spawnedIds()) {
|
|
424
|
+
if (!inboxTaskIds.has(spawnedId)) {
|
|
425
|
+
this.logger.info(
|
|
426
|
+
`[opengate] Task ${spawnedId} no longer in inbox (completed/cancelled) \u2014 freeing slot`
|
|
427
|
+
);
|
|
428
|
+
this.state.remove(spawnedId);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
320
431
|
for (const task of inbox.inProgressTasks) {
|
|
321
432
|
if (!this.state.isSpawned(task.id)) {
|
|
322
433
|
this.logger.warn(
|
|
@@ -325,6 +436,15 @@ var OpenGatePoller = class {
|
|
|
325
436
|
await this.releaseTask(task.id);
|
|
326
437
|
}
|
|
327
438
|
}
|
|
439
|
+
await this.handleNotifications();
|
|
440
|
+
const maxConcurrent = this.pluginCfg.maxConcurrent ?? 3;
|
|
441
|
+
const active = this.state.activeCount();
|
|
442
|
+
if (active >= maxConcurrent) {
|
|
443
|
+
this.logger.info(
|
|
444
|
+
`[opengate] At capacity (${active}/${maxConcurrent} active) \u2014 skipping poll`
|
|
445
|
+
);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
328
448
|
if (inbox.todoTasks.length === 0) return;
|
|
329
449
|
this.logger.info(`[opengate] Found ${inbox.todoTasks.length} todo task(s)`);
|
|
330
450
|
for (const task of inbox.todoTasks) {
|
|
@@ -369,6 +489,68 @@ var OpenGatePoller = class {
|
|
|
369
489
|
);
|
|
370
490
|
}
|
|
371
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
|
+
}
|
|
372
554
|
async spawnTask(task) {
|
|
373
555
|
this.logger.info(`[opengate] Spawning session for task: "${task.title}" (${task.id})`);
|
|
374
556
|
let project = null;
|
package/package.json
CHANGED
package/skills/opengate/SKILL.md
CHANGED
|
@@ -95,14 +95,24 @@ Specifically look for:
|
|
|
95
95
|
|
|
96
96
|
This enforces capacity limits and dependency checks. If claiming fails, read the error — you may have too many tasks in progress or a dependency is incomplete.
|
|
97
97
|
|
|
98
|
-
### 5. Comment: Starting Work
|
|
98
|
+
### 5. Comment: Starting Work (REQUIRED)
|
|
99
99
|
|
|
100
100
|
`post_comment(task_id, content)` → Post a comment before you start
|
|
101
101
|
|
|
102
|
-
Your starting comment
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
Your starting comment MUST use this structure:
|
|
103
|
+
```
|
|
104
|
+
## Starting Work
|
|
105
|
+
|
|
106
|
+
**Plan:**
|
|
107
|
+
- <step 1>
|
|
108
|
+
- <step 2>
|
|
109
|
+
|
|
110
|
+
**Context from KB:** <what you learned>
|
|
111
|
+
|
|
112
|
+
**Concerns:** <any blockers or risks>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Do NOT skip this comment. It is how the team tracks work and maintains visibility.
|
|
106
116
|
|
|
107
117
|
### 6. Do the Work
|
|
108
118
|
|
|
@@ -125,7 +135,28 @@ Store useful artifacts like:
|
|
|
125
135
|
- Links to PRs, commits, or external resources
|
|
126
136
|
- Feature branch name (e.g. `task/<task_id_short>`) for workspace continuity
|
|
127
137
|
|
|
128
|
-
### 9.
|
|
138
|
+
### 9. Post Results Comment (REQUIRED)
|
|
139
|
+
|
|
140
|
+
`post_comment(task_id, content)` → Post a final results comment
|
|
141
|
+
|
|
142
|
+
Your results comment MUST use this structure:
|
|
143
|
+
```
|
|
144
|
+
## Results
|
|
145
|
+
|
|
146
|
+
**Changes:**
|
|
147
|
+
- <file1>: <what changed>
|
|
148
|
+
- <file2>: <what changed>
|
|
149
|
+
|
|
150
|
+
**Approach:** <brief explanation>
|
|
151
|
+
|
|
152
|
+
**Tests:** <pass/fail status>
|
|
153
|
+
|
|
154
|
+
**Commits:** <hash(es)>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Do NOT skip this comment. It is critical for reviewers and future reference.
|
|
158
|
+
|
|
159
|
+
### 10. Complete the Task
|
|
129
160
|
|
|
130
161
|
`complete_task(task_id, summary, output)` → Finish the task with a summary and structured output
|
|
131
162
|
|
|
@@ -181,19 +212,28 @@ If a dependency is no longer needed:
|
|
|
181
212
|
- **References** — links, docs, external resources
|
|
182
213
|
- **General** — anything else worth knowing
|
|
183
214
|
|
|
184
|
-
### Writing Knowledge
|
|
215
|
+
### Writing Knowledge (MANDATORY Review)
|
|
185
216
|
|
|
186
|
-
|
|
217
|
+
Before completing any task, review your work with this checklist:
|
|
218
|
+
|
|
219
|
+
- [ ] Did I make an architecture or design decision? → Write it (category: `decision` or `architecture`)
|
|
220
|
+
- [ ] Did I encounter a non-obvious gotcha or bug? → Write it (category: `gotcha`)
|
|
221
|
+
- [ ] Did I establish or follow a pattern others should know? → Write it (category: `pattern`)
|
|
222
|
+
- [ ] Did I discover project structure or conventions? → Write it (category: `reference`)
|
|
223
|
+
|
|
224
|
+
If ANY apply, write knowledge entries BEFORE completing:
|
|
187
225
|
|
|
188
226
|
`set_knowledge(project_id, key, title, content, tags, category)`
|
|
189
227
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
-
|
|
194
|
-
-
|
|
228
|
+
Key naming: use kebab-case descriptors, e.g. `auth-middleware-pattern`, `db-migration-gotcha`
|
|
229
|
+
|
|
230
|
+
**IMPORTANT: Only write PROJECT-SCOPED knowledge.** Do NOT write task-specific details.
|
|
231
|
+
- Good: "The API uses tower middleware for auth, not axum extractors"
|
|
232
|
+
- Bad: "Task T-123 required adding a new endpoint"
|
|
233
|
+
|
|
234
|
+
If nothing new was discovered, that's fine — skip writing.
|
|
195
235
|
|
|
196
|
-
Categories: `architecture`, `
|
|
236
|
+
Categories: `architecture`, `pattern`, `gotcha`, `decision`, `reference`
|
|
197
237
|
|
|
198
238
|
## Agent Profile
|
|
199
239
|
|