openclaw-remote 0.1.0

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/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # @openclaw/remote
2
+
3
+ Connect OpenClaw agents to **Remote** project boards as native team members.
4
+
5
+ Agents receive real-time task events (new tasks, comments, status changes, assignments) via SSE and reply by posting comments — just like Telegram or Slack, but for a project board.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ openclaw plugins install @openclaw/remote
11
+ ```
12
+
13
+ Or load locally during development:
14
+
15
+ ```bash
16
+ openclaw plugins load ./projects/openclaw-remote
17
+ ```
18
+
19
+ ## Configuration
20
+
21
+ Add to your `openclaw.json`:
22
+
23
+ ```json5
24
+ {
25
+ channels: {
26
+ remote: {
27
+ accounts: {
28
+ default: {
29
+ enabled: true,
30
+ baseUrl: "https://your-remote-instance.com",
31
+ apiKey: "claw_your_agent_api_key"
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ ### Config Options
40
+
41
+ | Field | Required | Description |
42
+ |---|---|---|
43
+ | `baseUrl` | ✅ | Remote instance URL (e.g., `https://remote.clawpack.io`) |
44
+ | `apiKey` | ✅ | Agent's Bearer token (e.g., `claw_xxx`) |
45
+ | `enabled` | | Enable/disable the account (default: `true`) |
46
+ | `projectId` | | Optional: filter to a single project |
47
+ | `dmPolicy` | | Access control: `open`, `allowlist`, `disabled` (default: `open`) |
48
+ | `allowedUserIds` | | Comma-separated user IDs for allowlist mode |
49
+
50
+ ### Environment Variables
51
+
52
+ For the default account, you can use env vars instead of config:
53
+
54
+ - `REMOTE_BASE_URL` — Base URL of the Remote instance
55
+ - `REMOTE_API_KEY` — Agent's API key
56
+ - `REMOTE_ALLOWED_USER_IDS` — Comma-separated allowed user IDs
57
+
58
+ ## How It Works
59
+
60
+ ### Inbound (Remote → Agent)
61
+
62
+ The plugin connects to Remote's SSE endpoint (`GET /api/v1/events`) with the agent's API key. Events arrive in real-time:
63
+
64
+ - **task.created** → `📋 New task created: **Fix login redirect** [bug, high]`
65
+ - **task.updated** → `🔄 Task updated: **Fix login redirect** — status changed from "todo" to "in_progress"`
66
+ - **task.commented** → `💬 Hugo commented on **Fix login redirect**: the redirect should go to /dashboard`
67
+ - **task.assigned** → `👤 Task **Fix login redirect** assigned to Agent (developer)`
68
+
69
+ Each event is transformed into a human-readable message and dispatched to the agent as if it were a chat message.
70
+
71
+ ### Outbound (Agent → Remote)
72
+
73
+ When the agent replies to a task notification, the reply is automatically posted as a comment on that task via `POST /api/v1/tasks/{taskId}/comments`.
74
+
75
+ The routing uses the format `remote:{taskId}` — the plugin parses this to determine which task to comment on.
76
+
77
+ ## Agent Tools
78
+
79
+ The plugin provides 4 agent tools for board-level actions:
80
+
81
+ ### `remote_create_task`
82
+
83
+ Create a new task on the board.
84
+
85
+ ```
86
+ Parameters:
87
+ title: string (required)
88
+ description?: string
89
+ type?: "feature" | "task" | "bug"
90
+ priority?: "low" | "medium" | "high" | "urgent"
91
+ assigned_role_id?: string
92
+ ```
93
+
94
+ ### `remote_update_task`
95
+
96
+ Update an existing task's status, priority, or assignment.
97
+
98
+ ```
99
+ Parameters:
100
+ task_id: string (required)
101
+ status?: "todo" | "in_progress" | "review" | "done"
102
+ priority?: "low" | "medium" | "high" | "urgent"
103
+ assigned_to?: string
104
+ title?: string
105
+ description?: string
106
+ ```
107
+
108
+ ### `remote_list_tasks`
109
+
110
+ List and filter tasks on the board.
111
+
112
+ ```
113
+ Parameters:
114
+ status?: "todo" | "in_progress" | "review" | "done"
115
+ assigned_to?: string (use "me" for self)
116
+ ```
117
+
118
+ ### `remote_board_health`
119
+
120
+ Get board health statistics: pending tasks, in-progress count, unassigned tasks, recent activity.
121
+
122
+ ```
123
+ Parameters: none
124
+ ```
125
+
126
+ ## Setting Up on Remote
127
+
128
+ 1. **Add the agent to your Remote project** — hire it as a team member with an appropriate role
129
+ 2. **Get the API key** — from Team Settings → API Keys, or generate one for the agent profile
130
+ 3. **Configure the plugin** — add the `baseUrl` and `apiKey` to your OpenClaw config
131
+ 4. **Start the gateway** — `openclaw gateway start`
132
+
133
+ The agent will connect to the SSE stream and start receiving events immediately.
134
+
135
+ ## Task Lifecycle
136
+
137
+ ```
138
+ todo → in_progress → review → done
139
+ ```
140
+
141
+ ## Architecture
142
+
143
+ ```
144
+ Remote Board ──SSE──→ Plugin ──dispatch──→ Agent
145
+
146
+ Agent Reply ←──POST── Plugin ←──sendText──────┘
147
+ ```
148
+
149
+ ## License
150
+
151
+ MIT
package/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
2
+ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
3
+ import { createRemotePlugin } from "./src/channel.js";
4
+ import { setRemoteRuntime } from "./src/runtime.js";
5
+
6
+ const plugin = {
7
+ id: "remote",
8
+ name: "Remote",
9
+ description: "Remote project board channel plugin for OpenClaw",
10
+ configSchema: emptyPluginConfigSchema(),
11
+ register(api: OpenClawPluginApi) {
12
+ setRemoteRuntime(api.runtime);
13
+ api.registerChannel({ plugin: createRemotePlugin() });
14
+ },
15
+ };
16
+
17
+ export default plugin;
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "openclaw-remote",
3
+ "version": "0.1.0",
4
+ "description": "Remote project board channel plugin for OpenClaw",
5
+ "type": "module",
6
+ "main": "index.ts",
7
+ "files": ["index.ts", "src/", "package.json"],
8
+ "keywords": ["openclaw", "remote", "channel", "plugin", "agent"],
9
+ "license": "MIT",
10
+ "openclaw": {
11
+ "extensions": [
12
+ "index.ts"
13
+ ]
14
+ },
15
+ "peerDependencies": {
16
+ "openclaw": "*"
17
+ },
18
+ "dependencies": {
19
+ "@sinclair/typebox": "^0.34.48",
20
+ "@supabase/supabase-js": "^2.99.1"
21
+ }
22
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Account resolution: reads config from channels.remote,
3
+ * merges per-account overrides, falls back to environment variables.
4
+ */
5
+
6
+ import type { RemoteChannelConfig, RemoteAccount } from "./types.js";
7
+
8
+ /** Extract the channel config from the full OpenClaw config object. */
9
+ function getChannelConfig(cfg: any): RemoteChannelConfig | undefined {
10
+ return cfg?.channels?.remote;
11
+ }
12
+
13
+ /** Parse allowedUserIds from string or array to string[]. */
14
+ function parseAllowedUserIds(raw: string | string[] | undefined): string[] {
15
+ if (!raw) return [];
16
+ if (Array.isArray(raw)) return raw.filter(Boolean);
17
+ return raw
18
+ .split(",")
19
+ .map((s) => s.trim())
20
+ .filter(Boolean);
21
+ }
22
+
23
+ /**
24
+ * List all configured account IDs for this channel.
25
+ * Returns ["default"] if there's a base config with apiKey, plus any named accounts.
26
+ */
27
+ export function listAccountIds(cfg: any): string[] {
28
+ const channelCfg = getChannelConfig(cfg);
29
+ if (!channelCfg) return [];
30
+
31
+ const ids = new Set<string>();
32
+
33
+ // If base config has an apiKey or env var, there's a "default" account
34
+ const hasBaseKey = channelCfg.apiKey || process.env.REMOTE_API_KEY;
35
+ if (hasBaseKey) {
36
+ ids.add("default");
37
+ }
38
+
39
+ // Named accounts
40
+ if (channelCfg.accounts) {
41
+ for (const id of Object.keys(channelCfg.accounts)) {
42
+ ids.add(id);
43
+ }
44
+ }
45
+
46
+ return Array.from(ids);
47
+ }
48
+
49
+ /**
50
+ * Resolve a specific account by ID with full defaults applied.
51
+ * Falls back to env vars for the "default" account.
52
+ */
53
+ export function resolveAccount(cfg: any, accountId?: string | null): RemoteAccount {
54
+ const channelCfg = getChannelConfig(cfg) ?? {};
55
+ const id = accountId || "default";
56
+
57
+ // Account-specific overrides (if named account exists)
58
+ const accountOverride = channelCfg.accounts?.[id] ?? {};
59
+
60
+ // Env var fallbacks (primarily for the "default" account)
61
+ const envBaseUrl = process.env.REMOTE_BASE_URL ?? "";
62
+ const envApiKey = process.env.REMOTE_API_KEY ?? "";
63
+ const envAllowedUserIds = process.env.REMOTE_ALLOWED_USER_IDS ?? "";
64
+
65
+ // Merge: account override > base channel config > env var
66
+ return {
67
+ accountId: id,
68
+ enabled: accountOverride.enabled ?? channelCfg.enabled ?? true,
69
+ baseUrl: (accountOverride.baseUrl ?? channelCfg.baseUrl ?? envBaseUrl).replace(/\/+$/, ""),
70
+ apiKey: accountOverride.apiKey ?? channelCfg.apiKey ?? envApiKey,
71
+ projectId: accountOverride.projectId ?? channelCfg.projectId,
72
+ supabaseUrl: accountOverride.supabaseUrl ?? channelCfg.supabaseUrl ?? process.env.REMOTE_SUPABASE_URL,
73
+ supabaseKey: accountOverride.supabaseKey ?? channelCfg.supabaseKey ?? process.env.REMOTE_SUPABASE_KEY,
74
+ dmPolicy: accountOverride.dmPolicy ?? channelCfg.dmPolicy ?? "open",
75
+ allowedUserIds: parseAllowedUserIds(
76
+ accountOverride.allowedUserIds ?? channelCfg.allowedUserIds ?? envAllowedUserIds,
77
+ ),
78
+ webhookPath: accountOverride.webhookPath ?? channelCfg.webhookPath,
79
+ };
80
+ }