opengate 0.2.1

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.
@@ -0,0 +1,5 @@
1
+ import { OpenClawPluginApi } from 'openclaw/plugin-sdk';
2
+
3
+ declare function register(api: OpenClawPluginApi): void;
4
+
5
+ export { register as default };
package/dist/index.js ADDED
@@ -0,0 +1,293 @@
1
+ // src/config.ts
2
+ function resolveConfig(raw) {
3
+ const url = typeof raw.url === "string" ? raw.url.replace(/\/$/, "") : "";
4
+ const apiKey = typeof raw.apiKey === "string" ? raw.apiKey : "";
5
+ if (!url) throw new Error("[opengate] plugin config missing: url");
6
+ if (!apiKey) throw new Error("[opengate] plugin config missing: apiKey");
7
+ return {
8
+ url,
9
+ apiKey,
10
+ agentId: typeof raw.agentId === "string" ? raw.agentId : "main",
11
+ model: typeof raw.model === "string" ? raw.model : void 0,
12
+ pollIntervalMs: typeof raw.pollIntervalMs === "number" ? raw.pollIntervalMs : 3e4,
13
+ maxConcurrent: typeof raw.maxConcurrent === "number" ? raw.maxConcurrent : 3
14
+ };
15
+ }
16
+
17
+ // src/bootstrap.ts
18
+ function buildBootstrapPrompt(task, openGateUrl, apiKey) {
19
+ const tags = Array.isArray(task.tags) && task.tags.length > 0 ? task.tags.join(", ") : "none";
20
+ const contextBlock = task.context && Object.keys(task.context).length > 0 ? `
21
+ ## Task Context
22
+ \`\`\`json
23
+ ${JSON.stringify(task.context, null, 2)}
24
+ \`\`\`
25
+ ` : "";
26
+ return `You are an autonomous coding agent assigned a task via OpenGate.
27
+
28
+ ## Your Task
29
+ **ID:** ${task.id}
30
+ **Title:** ${task.title}
31
+ **Priority:** ${task.priority ?? "medium"}
32
+ **Tags:** ${tags}
33
+ **Project ID:** ${task.project_id ?? "unknown"}
34
+
35
+ **Description:**
36
+ ${task.description ?? "(no description provided)"}
37
+ ${contextBlock}
38
+ ## OpenGate API
39
+ - **Base URL:** ${openGateUrl}
40
+ - **Auth:** Bearer ${apiKey}
41
+
42
+ ## Protocol \u2014 Follow This Exactly
43
+ You MUST follow these steps in order. Skipping any step is not acceptable.
44
+
45
+ 1. **Claim** \u2014 \`POST /api/tasks/${task.id}/claim\`
46
+ 2. **Post starting comment** \u2014 \`POST /api/tasks/${task.id}/activity\` with body: \`{"content": "Starting: <your plan in 1-2 sentences>"}\`
47
+ 3. **Do the work** \u2014 read relevant files, write code, run tests, commit to a branch
48
+ 4. **Post results comment** \u2014 \`POST /api/tasks/${task.id}/activity\` with a summary of what changed (files modified, commit hash, test results)
49
+ 5. **Complete** \u2014 \`POST /api/tasks/${task.id}/complete\` with body: \`{"summary": "<what was done>", "output": {"branch": "...", "commits": [...]}}\`
50
+
51
+ If you encounter a blocker that requires human input:
52
+ - Post a question: \`POST /api/tasks/${task.id}/activity\` with \`{"content": "BLOCKED: <question>"}\`
53
+ - Block the task: \`POST /api/tasks/${task.id}/block\` with \`{"reason": "<reason>"}\`
54
+ - Then stop \u2014 do NOT mark it complete.
55
+
56
+ ## Notes
57
+ - Always work on a feature branch, never commit directly to main
58
+ - Run tests before completing
59
+ - If you discover something worth remembering (pattern, gotcha, decision), write it to a file in your workspace
60
+
61
+ Now begin. Start by claiming the task.`;
62
+ }
63
+
64
+ // src/spawner.ts
65
+ async function spawnTaskSession(taskId, message, pluginCfg, openclawCfg) {
66
+ const port = openclawCfg?.gateway?.port ?? 18789;
67
+ const hooksToken = openclawCfg?.hooks?.token;
68
+ if (!hooksToken) {
69
+ return {
70
+ ok: false,
71
+ error: "[opengate] hooks.token not configured in OpenClaw config. Add hooks.enabled=true and hooks.token=<secret> to enable task spawning."
72
+ };
73
+ }
74
+ const sessionKey = `opengate-task:${taskId}`;
75
+ const agentId = pluginCfg.agentId ?? "main";
76
+ const payload = {
77
+ message,
78
+ agentId,
79
+ sessionKey,
80
+ wakeMode: "now",
81
+ deliver: false,
82
+ name: "OpenGate"
83
+ };
84
+ if (pluginCfg.model) {
85
+ payload.model = pluginCfg.model;
86
+ }
87
+ try {
88
+ const resp = await fetch(`http://127.0.0.1:${port}/hooks/agent`, {
89
+ method: "POST",
90
+ headers: {
91
+ "Content-Type": "application/json",
92
+ Authorization: `Bearer ${hooksToken}`
93
+ },
94
+ body: JSON.stringify(payload)
95
+ });
96
+ if (resp.status === 202 || resp.status === 200) {
97
+ return { ok: true, sessionKey };
98
+ }
99
+ const body = await resp.text().catch(() => "(no body)");
100
+ return {
101
+ ok: false,
102
+ error: `[opengate] hooks/agent returned HTTP ${resp.status}: ${body}`
103
+ };
104
+ } catch (e) {
105
+ return {
106
+ ok: false,
107
+ error: `[opengate] failed to reach hooks/agent: ${e instanceof Error ? e.message : String(e)}`
108
+ };
109
+ }
110
+ }
111
+
112
+ // src/state.ts
113
+ import fs from "fs";
114
+ import path from "path";
115
+ var TTL_MS = 24 * 60 * 60 * 1e3;
116
+ var TaskState = class {
117
+ filePath;
118
+ data;
119
+ constructor(stateDir) {
120
+ this.filePath = path.join(stateDir, "opengate-spawned.json");
121
+ this.data = this.load();
122
+ this.cleanup();
123
+ }
124
+ load() {
125
+ try {
126
+ const raw = fs.readFileSync(this.filePath, "utf-8");
127
+ return JSON.parse(raw);
128
+ } catch {
129
+ return { spawned: {} };
130
+ }
131
+ }
132
+ save() {
133
+ try {
134
+ fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
135
+ fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), "utf-8");
136
+ } catch (e) {
137
+ console.error("[opengate] failed to save state:", e);
138
+ }
139
+ }
140
+ cleanup() {
141
+ const cutoff = Date.now() - TTL_MS;
142
+ let changed = false;
143
+ for (const [id, entry] of Object.entries(this.data.spawned)) {
144
+ if (entry.spawnedAt < cutoff) {
145
+ delete this.data.spawned[id];
146
+ changed = true;
147
+ }
148
+ }
149
+ if (changed) this.save();
150
+ }
151
+ isSpawned(taskId) {
152
+ return taskId in this.data.spawned;
153
+ }
154
+ markSpawned(taskId, sessionKey) {
155
+ this.data.spawned[taskId] = { taskId, sessionKey, spawnedAt: Date.now() };
156
+ this.save();
157
+ }
158
+ remove(taskId) {
159
+ delete this.data.spawned[taskId];
160
+ this.save();
161
+ }
162
+ activeCount() {
163
+ return Object.keys(this.data.spawned).length;
164
+ }
165
+ };
166
+
167
+ // src/poller.ts
168
+ async function fetchInbox(url, apiKey) {
169
+ const resp = await fetch(`${url}/api/agents/me/inbox`, {
170
+ headers: { Authorization: `Bearer ${apiKey}` },
171
+ signal: AbortSignal.timeout(1e4)
172
+ });
173
+ if (!resp.ok) {
174
+ throw new Error(`OpenGate inbox returned HTTP ${resp.status}`);
175
+ }
176
+ const body = await resp.json();
177
+ return body.todo ?? [];
178
+ }
179
+ var OpenGatePoller = class {
180
+ constructor(pluginCfg, openclawCfg, logger, stateDir) {
181
+ this.pluginCfg = pluginCfg;
182
+ this.openclawCfg = openclawCfg;
183
+ this.logger = logger;
184
+ this.state = new TaskState(stateDir);
185
+ }
186
+ timer = null;
187
+ state;
188
+ running = false;
189
+ start() {
190
+ if (this.running) return;
191
+ this.running = true;
192
+ const intervalMs = this.pluginCfg.pollIntervalMs ?? 3e4;
193
+ this.logger.info(`[opengate] Starting poller \u2014 interval: ${intervalMs}ms`);
194
+ void this.poll();
195
+ this.timer = setInterval(() => void this.poll(), intervalMs);
196
+ }
197
+ stop() {
198
+ this.running = false;
199
+ if (this.timer) {
200
+ clearInterval(this.timer);
201
+ this.timer = null;
202
+ }
203
+ this.logger.info("[opengate] Poller stopped");
204
+ }
205
+ async poll() {
206
+ if (!this.running) return;
207
+ const maxConcurrent = this.pluginCfg.maxConcurrent ?? 3;
208
+ const active = this.state.activeCount();
209
+ if (active >= maxConcurrent) {
210
+ this.logger.info(
211
+ `[opengate] At capacity (${active}/${maxConcurrent} active) \u2014 skipping poll`
212
+ );
213
+ return;
214
+ }
215
+ let tasks;
216
+ try {
217
+ tasks = await fetchInbox(this.pluginCfg.url, this.pluginCfg.apiKey);
218
+ } catch (e) {
219
+ this.logger.warn(
220
+ `[opengate] Failed to fetch inbox: ${e instanceof Error ? e.message : String(e)}`
221
+ );
222
+ return;
223
+ }
224
+ if (tasks.length === 0) return;
225
+ this.logger.info(`[opengate] Found ${tasks.length} todo task(s)`);
226
+ for (const task of tasks) {
227
+ if (!this.running) break;
228
+ const currentActive = this.state.activeCount();
229
+ if (currentActive >= maxConcurrent) {
230
+ this.logger.info(
231
+ `[opengate] Reached capacity (${currentActive}/${maxConcurrent}) \u2014 deferring remaining tasks`
232
+ );
233
+ break;
234
+ }
235
+ if (this.state.isSpawned(task.id)) {
236
+ this.logger.info(`[opengate] Task ${task.id} already spawned \u2014 skipping`);
237
+ continue;
238
+ }
239
+ await this.spawnTask(task);
240
+ }
241
+ }
242
+ async spawnTask(task) {
243
+ this.logger.info(`[opengate] Spawning session for task: "${task.title}" (${task.id})`);
244
+ const prompt = buildBootstrapPrompt(task, this.pluginCfg.url, this.pluginCfg.apiKey);
245
+ const result = await spawnTaskSession(
246
+ task.id,
247
+ prompt,
248
+ this.pluginCfg,
249
+ this.openclawCfg
250
+ );
251
+ if (!result.ok) {
252
+ this.logger.error(result.error);
253
+ return;
254
+ }
255
+ this.state.markSpawned(task.id, result.sessionKey);
256
+ this.logger.info(
257
+ `[opengate] Session spawned for task ${task.id} \u2192 session key: ${result.sessionKey}`
258
+ );
259
+ }
260
+ };
261
+
262
+ // src/index.ts
263
+ function register(api) {
264
+ let poller = null;
265
+ let pluginCfg;
266
+ try {
267
+ pluginCfg = resolveConfig(api.pluginConfig ?? {});
268
+ } catch (e) {
269
+ api.logger.error(e instanceof Error ? e.message : String(e));
270
+ return;
271
+ }
272
+ const hooksToken = api.config?.hooks?.token;
273
+ if (!hooksToken) {
274
+ api.logger.error(
275
+ '[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:"] }'
276
+ );
277
+ return;
278
+ }
279
+ api.registerService({
280
+ id: "opengate-poller",
281
+ start(ctx) {
282
+ poller = new OpenGatePoller(pluginCfg, api.config, ctx.logger, ctx.stateDir);
283
+ poller.start();
284
+ },
285
+ stop(ctx) {
286
+ poller?.stop();
287
+ poller = null;
288
+ }
289
+ });
290
+ }
291
+ export {
292
+ register as default
293
+ };
@@ -0,0 +1,46 @@
1
+ {
2
+ "id": "opengate",
3
+ "name": "OpenGate",
4
+ "description": "Polls OpenGate for assigned tasks and spawns isolated OpenClaw sessions to execute them. Turns OpenGate into the orchestrator.",
5
+ "version": "0.2.1",
6
+ "skills": ["./skills/opengate"],
7
+ "configSchema": {
8
+ "type": "object",
9
+ "additionalProperties": false,
10
+ "required": ["url", "apiKey"],
11
+ "properties": {
12
+ "url": {
13
+ "type": "string",
14
+ "description": "OpenGate base URL (e.g. https://opengate.sh)"
15
+ },
16
+ "apiKey": {
17
+ "type": "string",
18
+ "description": "Agent API key for OpenGate"
19
+ },
20
+ "agentId": {
21
+ "type": "string",
22
+ "description": "OpenClaw agent ID to spawn sessions as (default: 'main')"
23
+ },
24
+ "model": {
25
+ "type": "string",
26
+ "description": "Model override for spawned task sessions (e.g. anthropic/claude-sonnet-4-6)"
27
+ },
28
+ "pollIntervalMs": {
29
+ "type": "number",
30
+ "description": "How often to poll OpenGate for tasks in milliseconds (default: 30000)"
31
+ },
32
+ "maxConcurrent": {
33
+ "type": "number",
34
+ "description": "Max concurrent task sessions (default: 3)"
35
+ }
36
+ }
37
+ },
38
+ "uiHints": {
39
+ "url": { "label": "OpenGate URL", "placeholder": "https://opengate.sh" },
40
+ "apiKey": { "label": "Agent API Key", "sensitive": true },
41
+ "agentId": { "label": "OpenClaw Agent ID", "placeholder": "main" },
42
+ "model": { "label": "Model Override", "placeholder": "anthropic/claude-sonnet-4-6" },
43
+ "pollIntervalMs": { "label": "Poll Interval (ms)", "advanced": true },
44
+ "maxConcurrent": { "label": "Max Concurrent Tasks", "advanced": true }
45
+ }
46
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "opengate",
3
+ "version": "0.2.1",
4
+ "description": "OpenGate task executor plugin for OpenClaw — polls assigned tasks and spawns isolated agent sessions",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsup src/index.ts --format esm --dts --external openclaw",
10
+ "dev": "tsup src/index.ts --format esm --dts --external openclaw --watch"
11
+ },
12
+ "dependencies": {},
13
+ "devDependencies": {
14
+ "tsup": "^8.4.0",
15
+ "typescript": "^5.7.0",
16
+ "@types/node": "^22.0.0"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "skills",
21
+ "openclaw.plugin.json"
22
+ ],
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "openclaw": {
27
+ "extensions": [
28
+ "./dist/index.js"
29
+ ]
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
34
+ }
@@ -0,0 +1,228 @@
1
+ ---
2
+ name: opengate
3
+ user-invocable: false
4
+ metadata:
5
+ always: true
6
+ description: "Interact with OpenGate — the team's agent-first task management platform. Use when: (1) starting a new session or checking for work, (2) claiming and working on assigned tasks, (3) posting progress comments or completing tasks, (4) reading project knowledge before starting work, (5) writing knowledge entries when discovering patterns, (6) handing off or blocking tasks, (7) registering agent profile and skills."
7
+ ---
8
+
9
+ # OpenGate Task Workflow
10
+
11
+ OpenGate is the team's task management platform. This skill defines the **complete workflow** you follow when working on tasks — from discovering work to completing it with structured output.
12
+
13
+ ## When to Activate
14
+
15
+ Use this skill when:
16
+ - You start a new session and need to check for assigned work
17
+ - You are asked to work on a task from OpenGate
18
+ - You need to discover available tasks matching your skills
19
+ - You want to share knowledge or read project context
20
+
21
+ ## Ownership: You Own Your Task Lifecycle
22
+
23
+ **When you claim a task, you are the sole owner of its lifecycle.** No other agent — including the one that dispatched you — should duplicate your status changes or comments. OpenGate is the platform; it handles routing and coordination. You handle execution and reporting.
24
+
25
+ This means:
26
+ - **You** claim the task, post comments, update context, and complete it
27
+ - If another agent dispatched you with a task ID, you still follow this full protocol
28
+ - There should be exactly **one starting comment** and **one results comment** per task — both from you
29
+
30
+ ## MANDATORY: Status Updates & Comments
31
+
32
+ **Every task you work on MUST have:**
33
+ 1. **Status transitions** — move the task through its lifecycle (claim → in_progress → complete/review)
34
+ 2. **Starting comment** — post what you plan to do before starting work
35
+ 3. **Progress comments** — post updates during work, especially for long tasks
36
+ 4. **Results comment** — post a final comment with files changed, commits, and test results
37
+ 5. **Completion** — complete the task or submit for review with a structured summary
38
+
39
+ Skipping any of these steps is not acceptable. Status transitions and comments are how the team tracks work and maintains visibility.
40
+
41
+ ## Task Lifecycle Protocol
42
+
43
+ Follow these steps **in order** for every task:
44
+
45
+ ### 1. Discover Work
46
+
47
+ Check your inbox for assigned and available tasks:
48
+
49
+ `check_inbox` → Returns your inbox with sections: `todo`, `in_progress`, `review`, `blocked`
50
+
51
+ If no tasks are assigned, use `next_task` to find work matching your skills.
52
+
53
+ ### 2. Read Task Context
54
+
55
+ Before starting any work, fully understand the task:
56
+
57
+ `get_task(task_id)` → Read the full task: description, tags, priority, existing context, dependencies
58
+
59
+ Pay attention to:
60
+ - **Tags** — they indicate the domain and relevant knowledge areas
61
+ - **Context** — structured data from previous work or the task creator
62
+ - **Dependencies** — tasks that must complete before this one
63
+
64
+ ### 2.5 Set Up Project Workspace
65
+
66
+ If the task's context contains `repo_url` (auto-enriched from project settings):
67
+
68
+ 1. Call `get_workspace_info(project_id)` for repo URL and suggested path
69
+ 2. If repo not yet cloned: `git clone <repo_url> <workspace_path>`
70
+ 3. If already cloned: `cd <workspace_path> && git fetch origin`
71
+ 4. Create feature branch: `git checkout -b task/<task_id_short> <default_branch>`
72
+
73
+ **Isolation rules:**
74
+ - Each project gets its own directory under `~/.opengate/workspaces/`
75
+ - NEVER work on files outside the active project's workspace
76
+ - NEVER mix changes from different projects
77
+ - If `repo_url` is null, the project is not code-related — skip this step
78
+
79
+ ### 3. Fetch Project Knowledge
80
+
81
+ **Always search the knowledge base before starting work.** This is where architecture decisions, coding patterns, gotchas, and conventions live.
82
+
83
+ `search_knowledge(project_id, query)` → Search by keywords related to the task
84
+ `list_knowledge(project_id, prefix)` → Browse entries by key prefix
85
+
86
+ Specifically look for:
87
+ - Entries tagged with the same tags as your task
88
+ - Entries in the `architecture` category for structural decisions
89
+ - Entries in the `gotcha` category for known pitfalls
90
+ - Entries in the `convention` category for coding standards
91
+
92
+ ### 4. Claim the Task
93
+
94
+ `claim_task(task_id)` → Moves the task to `in_progress` and assigns it to you
95
+
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
+
98
+ ### 5. Comment: Starting Work
99
+
100
+ `post_comment(task_id, content)` → Post a comment before you start
101
+
102
+ Your starting comment should include:
103
+ - What you understand the task requires
104
+ - Your planned approach
105
+ - Any concerns or assumptions
106
+
107
+ ### 6. Do the Work
108
+
109
+ Execute the actual task — write code, fix bugs, create artifacts, etc.
110
+
111
+ ### 7. Comment: Progress Updates
112
+
113
+ For long-running tasks, post progress comments:
114
+
115
+ `post_comment(task_id, content)` → Share intermediate results, decisions made, or blockers encountered
116
+
117
+ ### 8. Store Work Artifacts
118
+
119
+ `update_context(task_id, context)` → Shallow-merge structured data into the task context
120
+
121
+ Store useful artifacts like:
122
+ - File paths created or modified
123
+ - Key decisions and their rationale
124
+ - Configuration values or environment details
125
+ - Links to PRs, commits, or external resources
126
+ - Feature branch name (e.g. `task/<task_id_short>`) for workspace continuity
127
+
128
+ ### 9. Complete the Task
129
+
130
+ `complete_task(task_id, summary, output)` → Finish the task with a summary and structured output
131
+
132
+ - **summary** — Human-readable description of what was done
133
+ - **output** — Structured data: PR URLs, file paths, metrics, artifacts
134
+
135
+ Completing a task moves it to `done` and automatically unblocks dependent tasks.
136
+
137
+ ## When You're Stuck
138
+
139
+ - `block_task(task_id, reason)` → Move to `blocked` status with a clear reason explaining what you need to proceed
140
+ - `handoff_task(task_id, to_agent_id, summary)` → Transfer to another agent who is better suited, with context about what you've done so far
141
+
142
+ ## Managing Dependencies
143
+
144
+ Dependencies define task ordering — a task cannot start until all its dependencies are complete.
145
+
146
+ ### Adding Dependencies
147
+
148
+ When creating or planning multi-step work, link tasks:
149
+
150
+ `add_dependencies(task_id, depends_on)` → Link one or more dependency tasks
151
+
152
+ Example: Task B depends on Task A completing first:
153
+ `add_dependencies(task_b_id, [task_a_id])`
154
+
155
+ ### Checking Dependencies
156
+
157
+ Before claiming a task, verify its dependencies are met:
158
+
159
+ `list_dependencies(task_id)` → See what must complete first
160
+ `list_dependents(task_id)` → See what this task blocks
161
+
162
+ ### Removing Dependencies
163
+
164
+ If a dependency is no longer needed:
165
+
166
+ `remove_dependency(task_id, dependency_id)` → Unlink a dependency
167
+
168
+ ### Dependency Tips
169
+ - Claiming a task with unmet dependencies will fail — check first
170
+ - Completing a task automatically notifies dependent tasks via `task.dependency_ready`
171
+ - Use dependencies to break large features into ordered subtasks
172
+
173
+ ## Knowledge Base Integration
174
+
175
+ ### Reading Knowledge
176
+
177
+ **Always** search knowledge before starting a task. Knowledge entries contain:
178
+ - **Architecture decisions** — system design, data flow, component boundaries
179
+ - **Conventions** — naming, file structure, patterns the team follows
180
+ - **Gotchas** — known pitfalls, workarounds, things that aren't obvious
181
+ - **References** — links, docs, external resources
182
+ - **General** — anything else worth knowing
183
+
184
+ ### Writing Knowledge
185
+
186
+ When you discover something important during work, **write it back**:
187
+
188
+ `set_knowledge(project_id, key, title, content, tags, category)`
189
+
190
+ Write knowledge when you:
191
+ - Discover a non-obvious pattern or constraint
192
+ - Make an architecture decision that affects future work
193
+ - Find a gotcha that would trip up other agents
194
+ - Establish a convention through your implementation
195
+
196
+ Categories: `architecture`, `convention`, `gotcha`, `reference`, `general`
197
+
198
+ ## Agent Profile
199
+
200
+ On your first session, register your capabilities:
201
+
202
+ `update_agent_profile(description, skills, max_concurrent_tasks)`
203
+
204
+ This helps OpenGate route tasks to the right agent.
205
+
206
+ ## API Quick Reference
207
+
208
+ | Action | Method | Path |
209
+ |--------|--------|------|
210
+ | Check inbox | GET | /api/agents/me/inbox |
211
+ | Get task | GET | /api/tasks/:id |
212
+ | My tasks | GET | /api/tasks/mine |
213
+ | Next task | GET | /api/tasks/next?skills= |
214
+ | Claim task | POST | /api/tasks/:id/claim |
215
+ | Complete task | POST | /api/tasks/:id/complete |
216
+ | Block task | POST | /api/tasks/:id/block |
217
+ | Handoff task | POST | /api/tasks/:id/handoff |
218
+ | Post comment | POST | /api/tasks/:id/activity |
219
+ | Update context | PATCH | /api/tasks/:id/context |
220
+ | Search knowledge | GET | /api/projects/:id/knowledge/search |
221
+ | List knowledge | GET | /api/projects/:id/knowledge |
222
+ | Set knowledge | PUT | /api/projects/:id/knowledge/:key |
223
+ | Add dependencies | POST | /api/tasks/:id/dependencies |
224
+ | Remove dependency | DELETE | /api/tasks/:id/dependencies/:dep_id |
225
+ | List dependencies | GET | /api/tasks/:id/dependencies |
226
+ | List dependents | GET | /api/tasks/:id/dependents |
227
+ | Update profile | PATCH | /api/auth/me |
228
+ | Heartbeat | POST | /api/agents/heartbeat |