@gholl-studio/pier-connector 0.0.1 ā 0.0.3
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 +30 -21
- package/openclaw.plugin.json +32 -14
- package/package.json +4 -3
- package/src/config.js +9 -3
- package/src/index.js +232 -116
- package/src/protocol.js +4 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
- **Pier Account**: A valid account with access to the job marketplace
|
|
18
18
|
- **Network**: Stable internet connection
|
|
19
19
|
|
|
20
|
-
> š” **Note**: The plugin handles
|
|
20
|
+
> š” **Note**: The plugin handles registration and heartbeat logic internally. You only need to configure your **Pier API URL/Key** in the settings. The plugin will automatically negotiate NATS connection details with the backend.
|
|
21
21
|
|
|
22
22
|
## Installation
|
|
23
23
|
|
|
@@ -34,9 +34,21 @@ openclaw plugins enable pier-connector
|
|
|
34
34
|
|
|
35
35
|
Restart the OpenClaw Gateway.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
Restart the OpenClaw Gateway.
|
|
38
|
+
|
|
39
|
+
## āļø How to Configure
|
|
40
|
+
|
|
41
|
+
There are two ways to configure the plugin:
|
|
42
|
+
|
|
43
|
+
### 1. Interactive CLI (Recommended)
|
|
44
|
+
Run the following command in your terminal:
|
|
45
|
+
```bash
|
|
46
|
+
openclaw pier setup
|
|
47
|
+
```
|
|
48
|
+
This will guide you through setting up your API URL, Key, and Wallet.
|
|
38
49
|
|
|
39
|
-
|
|
50
|
+
### 2. Manual Config
|
|
51
|
+
Add the following to your `openclaw.config.json` under `plugins.entries.pier-connector.config`:
|
|
40
52
|
|
|
41
53
|
```json
|
|
42
54
|
{
|
|
@@ -45,12 +57,9 @@ Configure via `plugins.entries.pier-connector.config`:
|
|
|
45
57
|
"pier-connector": {
|
|
46
58
|
"enabled": true,
|
|
47
59
|
"config": {
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"queueGroup": "openclaw-workers",
|
|
52
|
-
"workerId": "agent-001",
|
|
53
|
-
"walletAddress": "0xABC123"
|
|
60
|
+
"apiUrl": "https://pier.gholl.com",
|
|
61
|
+
"apiKey": "your-api-key-here",
|
|
62
|
+
"walletAddress": "0xYourWalletAddress"
|
|
54
63
|
}
|
|
55
64
|
}
|
|
56
65
|
}
|
|
@@ -58,14 +67,14 @@ Configure via `plugins.entries.pier-connector.config`:
|
|
|
58
67
|
}
|
|
59
68
|
```
|
|
60
69
|
|
|
61
|
-
| Option
|
|
62
|
-
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
70
|
+
| Option | Default | Description |
|
|
71
|
+
|-----------------|------------------------------|--------------------------------------------------|
|
|
72
|
+
| `apiUrl` | `https://pier.gholl.com` | Pier Backend API Base URL |
|
|
73
|
+
| `apiKey` | `""` | Your User API Key for platform authentication |
|
|
74
|
+
| `nodeId` | `""` | (Auto-generated) Unique UUID for this node |
|
|
75
|
+
| `secretKey` | `""` | (Auto-generated) Secret key for pulse verification |
|
|
76
|
+
| `walletAddress` | `""` | Wallet address for receiving job rewards |
|
|
77
|
+
| `natsUrl` | `""` | (Optional) Manual NATS override |
|
|
69
78
|
|
|
70
79
|
## How It Works
|
|
71
80
|
|
|
@@ -119,8 +128,8 @@ Use `/pier` in any OpenClaw channel to check connector status.
|
|
|
119
128
|
"result": "The AI's generated response",
|
|
120
129
|
"latencyMs": 1450,
|
|
121
130
|
"worker": {
|
|
122
|
-
"id": "
|
|
123
|
-
"wallet": "
|
|
131
|
+
"id": "uuid-node",
|
|
132
|
+
"wallet": "0xYourWalletAddress"
|
|
124
133
|
}
|
|
125
134
|
}
|
|
126
135
|
```
|
|
@@ -138,8 +147,8 @@ Use `/pier` in any OpenClaw channel to check connector status.
|
|
|
138
147
|
"message": "Error details"
|
|
139
148
|
},
|
|
140
149
|
"worker": {
|
|
141
|
-
"id": "
|
|
142
|
-
"wallet": "
|
|
150
|
+
"id": "uuid-node",
|
|
151
|
+
"wallet": "0xYourWalletAddress"
|
|
143
152
|
}
|
|
144
153
|
}
|
|
145
154
|
```
|
package/openclaw.plugin.json
CHANGED
|
@@ -10,10 +10,25 @@
|
|
|
10
10
|
"type": "object",
|
|
11
11
|
"additionalProperties": false,
|
|
12
12
|
"properties": {
|
|
13
|
+
"pierApiUrl": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"default": "https://pier-connector.gholl.com/api/v1",
|
|
16
|
+
"description": "Pier API Endpoint (from Website)"
|
|
17
|
+
},
|
|
18
|
+
"nodeId": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"default": "",
|
|
21
|
+
"description": "Bot Node ID (obtain from pier-connector.gholl.com)"
|
|
22
|
+
},
|
|
23
|
+
"secretKey": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"default": "",
|
|
26
|
+
"description": "Bot Secret Key (obtain from pier-connector.gholl.com)"
|
|
27
|
+
},
|
|
13
28
|
"natsUrl": {
|
|
14
29
|
"type": "string",
|
|
15
30
|
"default": "wss://pier.gholl.com/nexus",
|
|
16
|
-
"description": "NATS WebSocket server URL"
|
|
31
|
+
"description": "NATS WebSocket server URL (Overrides API dynamic config if set)"
|
|
17
32
|
},
|
|
18
33
|
"subject": {
|
|
19
34
|
"type": "string",
|
|
@@ -30,11 +45,6 @@
|
|
|
30
45
|
"default": "openclaw-workers",
|
|
31
46
|
"description": "NATS Queue Group to join for load balancing tasks"
|
|
32
47
|
},
|
|
33
|
-
"workerId": {
|
|
34
|
-
"type": "string",
|
|
35
|
-
"default": "",
|
|
36
|
-
"description": "(Optional) Unique identifier for this agent worker"
|
|
37
|
-
},
|
|
38
48
|
"walletAddress": {
|
|
39
49
|
"type": "string",
|
|
40
50
|
"default": "",
|
|
@@ -43,26 +53,34 @@
|
|
|
43
53
|
}
|
|
44
54
|
},
|
|
45
55
|
"uiHints": {
|
|
56
|
+
"pierApiUrl": {
|
|
57
|
+
"label": "Pier API URL",
|
|
58
|
+
"placeholder": "https://pier-connector.gholl.com/api/v1"
|
|
59
|
+
},
|
|
60
|
+
"nodeId": {
|
|
61
|
+
"label": "Bot Node ID",
|
|
62
|
+
"placeholder": "uuid-..."
|
|
63
|
+
},
|
|
64
|
+
"secretKey": {
|
|
65
|
+
"label": "Bot Secret Key (Secret)",
|
|
66
|
+
"placeholder": "sk_..."
|
|
67
|
+
},
|
|
46
68
|
"natsUrl": {
|
|
47
|
-
"label": "NATS WebSocket URL",
|
|
69
|
+
"label": "NATS WebSocket URL (Override)",
|
|
48
70
|
"placeholder": "wss://pier.gholl.com/nexus"
|
|
49
71
|
},
|
|
50
72
|
"subject": {
|
|
51
|
-
"label": "Subscribe Subject
|
|
73
|
+
"label": "Subscribe Subject",
|
|
52
74
|
"placeholder": "jobs.worker"
|
|
53
75
|
},
|
|
54
76
|
"publishSubject": {
|
|
55
|
-
"label": "Publish Subject
|
|
77
|
+
"label": "Publish Subject",
|
|
56
78
|
"placeholder": "jobs.submit"
|
|
57
79
|
},
|
|
58
80
|
"queueGroup": {
|
|
59
|
-
"label": "Queue Group
|
|
81
|
+
"label": "Queue Group",
|
|
60
82
|
"placeholder": "openclaw-workers"
|
|
61
83
|
},
|
|
62
|
-
"workerId": {
|
|
63
|
-
"label": "Worker ID",
|
|
64
|
-
"placeholder": "agent-001"
|
|
65
|
-
},
|
|
66
84
|
"walletAddress": {
|
|
67
85
|
"label": "Wallet Address (Rewards)",
|
|
68
86
|
"placeholder": "0x..."
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gholl-studio/pier-connector",
|
|
3
3
|
"author": "gholl",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.3",
|
|
5
5
|
"description": "OpenClaw plugin that connects to the Pier job marketplace. Automatically fetches, executes, and reports distributed tasks for rewards.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "src/index.js",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
],
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@nats-io/transport-node": "^3.0.0"
|
|
34
|
+
"@nats-io/transport-node": "^3.0.0",
|
|
35
|
+
"inquirer": "^13.3.0"
|
|
35
36
|
},
|
|
36
37
|
"peerDependencies": {
|
|
37
38
|
"openclaw": ">=2.0.0"
|
|
@@ -41,4 +42,4 @@
|
|
|
41
42
|
"optional": true
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
|
-
}
|
|
45
|
+
}
|
package/src/config.js
CHANGED
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
export const DEFAULTS = Object.freeze({
|
|
8
|
-
/**
|
|
8
|
+
/** Pier Backend API Base URL */
|
|
9
|
+
PIER_API_URL: 'https://pier-connector.gholl.com/api/v1',
|
|
10
|
+
|
|
11
|
+
/** NATS WebSocket server URL (usually provided by Heartbeat) */
|
|
9
12
|
NATS_URL: 'wss://pier.gholl.com/nexus',
|
|
10
13
|
|
|
11
14
|
/** NATS subject to subscribe for incoming jobs */
|
|
@@ -17,8 +20,11 @@ export const DEFAULTS = Object.freeze({
|
|
|
17
20
|
/** Default Queue Group for processing jobs (prevents duplicate work) */
|
|
18
21
|
QUEUE_GROUP: 'openclaw-workers',
|
|
19
22
|
|
|
20
|
-
/**
|
|
21
|
-
|
|
23
|
+
/** Unique identifier for this agent worker node (UUID) */
|
|
24
|
+
NODE_ID: '',
|
|
25
|
+
|
|
26
|
+
/** Secret key for node authentication and heartbeats */
|
|
27
|
+
SECRET_KEY: '',
|
|
22
28
|
|
|
23
29
|
/** Optional Wallet address to receive points/payments for completed tasks */
|
|
24
30
|
WALLET_ADDRESS: '',
|
package/src/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { createNatsConnection, drainConnection } from './nats-client.js';
|
|
|
12
12
|
import { parseJob, safeRespond, truncate } from './job-handler.js';
|
|
13
13
|
import { createRequestPayload, createResultPayload, createErrorPayload } from './protocol.js';
|
|
14
14
|
import { DEFAULTS } from './config.js';
|
|
15
|
+
import inquirer from 'inquirer';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* OpenClaw plugin register function.
|
|
@@ -41,15 +42,53 @@ export default function register(api) {
|
|
|
41
42
|
function resolveConfig() {
|
|
42
43
|
const cfg = api.config?.plugins?.entries?.['pier-connector']?.config ?? {};
|
|
43
44
|
return {
|
|
45
|
+
pierApiUrl: cfg.pierApiUrl || DEFAULTS.PIER_API_URL,
|
|
46
|
+
nodeId: cfg.nodeId || DEFAULTS.NODE_ID,
|
|
47
|
+
secretKey: cfg.secretKey || DEFAULTS.SECRET_KEY,
|
|
44
48
|
natsUrl: cfg.natsUrl || DEFAULTS.NATS_URL,
|
|
45
|
-
subject: cfg.subject || DEFAULTS.
|
|
49
|
+
subject: cfg.subject || DEFAULTS.PIER_SUBJECT,
|
|
46
50
|
publishSubject: cfg.publishSubject || DEFAULTS.PUBLISH_SUBJECT,
|
|
47
51
|
queueGroup: cfg.queueGroup || DEFAULTS.QUEUE_GROUP,
|
|
48
|
-
workerId: cfg.workerId || DEFAULTS.WORKER_ID,
|
|
49
52
|
walletAddress: cfg.walletAddress || DEFAULTS.WALLET_ADDRESS,
|
|
50
53
|
};
|
|
51
54
|
}
|
|
52
55
|
|
|
56
|
+
/** Heartbeat timer reference */
|
|
57
|
+
let heartbeatTimer = null;
|
|
58
|
+
|
|
59
|
+
// āā Lifecycle: Heartbeat āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Sends a heartbeat to the Pier Backend to stay active in the marketplace.
|
|
63
|
+
*/
|
|
64
|
+
async function heartbeatNode(config) {
|
|
65
|
+
if (!config.nodeId || !config.secretKey) return null;
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const resp = await fetch(`${config.pierApiUrl}/nodes/heartbeat`, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'Content-Type': 'application/json' },
|
|
71
|
+
body: JSON.stringify({
|
|
72
|
+
node_id: config.nodeId,
|
|
73
|
+
secret_key: config.secretKey,
|
|
74
|
+
capabilities: ['translation', 'code-execution', 'reasoning', 'vision'],
|
|
75
|
+
description: `OpenClaw Node (${config.nodeId.substring(0, 8)})`,
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (!resp.ok) {
|
|
80
|
+
const errText = await resp.text();
|
|
81
|
+
throw new Error(`Heartbeat failed (${resp.status}): ${errText}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const data = await resp.json();
|
|
85
|
+
return data.nats_config || null;
|
|
86
|
+
} catch (err) {
|
|
87
|
+
logger.warn(`[pier-connector] Heartbeat failed: ${err.message}`);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
53
92
|
// āā 1. Register messaging channel āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
54
93
|
|
|
55
94
|
const pierChannel = {
|
|
@@ -96,7 +135,7 @@ export default function register(api) {
|
|
|
96
135
|
id: jobId,
|
|
97
136
|
reply: text,
|
|
98
137
|
latencyMs: elapsed ? Number(elapsed) : undefined,
|
|
99
|
-
workerId: resolveConfig().
|
|
138
|
+
workerId: resolveConfig().nodeId,
|
|
100
139
|
walletAddress: resolveConfig().walletAddress,
|
|
101
140
|
});
|
|
102
141
|
|
|
@@ -123,141 +162,142 @@ export default function register(api) {
|
|
|
123
162
|
|
|
124
163
|
start: async () => {
|
|
125
164
|
const config = resolveConfig();
|
|
126
|
-
|
|
127
165
|
logger.info('[pier-connector] š Starting background service ā¦');
|
|
128
|
-
logger.info(`[pier-connector] NATS URL : ${config.natsUrl}`);
|
|
129
|
-
logger.info(`[pier-connector] Subscribe subject : ${config.subject}`);
|
|
130
|
-
logger.info(`[pier-connector] Queue Group : ${config.queueGroup}`);
|
|
131
|
-
logger.info(`[pier-connector] Publish subject : ${config.publishSubject}`);
|
|
132
|
-
if (config.workerId) logger.info(`[pier-connector] Worker ID : ${config.workerId}`);
|
|
133
|
-
if (config.walletAddress) logger.info(`[pier-connector] Wallet : ${config.walletAddress}`);
|
|
134
166
|
|
|
135
167
|
try {
|
|
136
|
-
//
|
|
137
|
-
|
|
168
|
+
// 1. Mandatory credentials check
|
|
169
|
+
if (!config.nodeId || !config.secretKey) {
|
|
170
|
+
logger.warn('[pier-connector] ā ļø Bot Node ID or Secret is missing.');
|
|
171
|
+
logger.warn('[pier-connector] Please run "openclaw pier setup" or go to pier-connector.gholl.com to create a bot.');
|
|
172
|
+
connectionStatus = 'error';
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
connectionStatus = 'connecting';
|
|
177
|
+
|
|
178
|
+
// 2. Initial Pulse & Config fetch
|
|
179
|
+
logger.info(`[pier-connector] š Sending initial pulse to ${config.pierApiUrl}...`);
|
|
180
|
+
const natsUpdate = await heartbeatNode(config);
|
|
181
|
+
const activeNatsUrl = natsUpdate?.url || config.natsUrl;
|
|
182
|
+
|
|
183
|
+
logger.info(`[pier-connector] Node ID : ${config.nodeId}`);
|
|
184
|
+
logger.info(`[pier-connector] NATS URL : ${activeNatsUrl}`);
|
|
185
|
+
|
|
186
|
+
// 3. Start Heartbeat Timer
|
|
187
|
+
const runHeartbeat = async () => {
|
|
188
|
+
const currentConfig = resolveConfig();
|
|
189
|
+
if (!currentConfig.nodeId) return;
|
|
190
|
+
await heartbeatNode(currentConfig);
|
|
191
|
+
};
|
|
192
|
+
heartbeatTimer = setInterval(runHeartbeat, 60000);
|
|
193
|
+
|
|
194
|
+
// 4. Connect to NATS
|
|
195
|
+
nc = await createNatsConnection(activeNatsUrl, logger);
|
|
138
196
|
connectionStatus = 'connected';
|
|
139
197
|
connectedAt = new Date();
|
|
140
198
|
|
|
141
|
-
// Subscribe to
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
`[pier-connector] ā Subscribed to "${config.subject}" (group: "${config.queueGroup}") ā waiting for jobs ā¦`,
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
// Async message processing loop
|
|
148
|
-
(async () => {
|
|
149
|
-
for await (const msg of subscription) {
|
|
150
|
-
jobsReceived++;
|
|
151
|
-
const startTime = performance.now();
|
|
152
|
-
|
|
153
|
-
logger.info(
|
|
154
|
-
`[pier-connector] šØ Job #${jobsReceived} received on "${msg.subject}"`,
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
// Parse the incoming job
|
|
158
|
-
const parsed = parseJob(msg, logger);
|
|
159
|
-
|
|
160
|
-
if (!parsed.ok) {
|
|
161
|
-
jobsFailed++;
|
|
162
|
-
safeRespond(
|
|
163
|
-
msg,
|
|
164
|
-
createErrorPayload({
|
|
165
|
-
id: msg.subject, // Fallback ID
|
|
166
|
-
errorCode: 'PARSE_ERROR',
|
|
167
|
-
errorMessage: parsed.error,
|
|
168
|
-
workerId: config.workerId,
|
|
169
|
-
walletAddress: config.walletAddress,
|
|
170
|
-
})
|
|
171
|
-
);
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
199
|
+
// 5. Subscribe to Subjects
|
|
200
|
+
const publicSubject = config.subject;
|
|
201
|
+
const privateSubject = `jobs.node.${config.nodeId}`;
|
|
174
202
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
`[pier-connector] š Job ${job.id}: "${truncate(job.task, 80)}"`,
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
// Route the job through OpenClaw's agent via the channel
|
|
181
|
-
try {
|
|
182
|
-
// Build an inbound message for the channel system
|
|
183
|
-
const inbound = {
|
|
184
|
-
channelId: 'pier',
|
|
185
|
-
accountId: 'default',
|
|
186
|
-
senderId: `pier:${job.meta?.sender ?? 'anonymous'}`,
|
|
187
|
-
text: job.task,
|
|
188
|
-
metadata: {
|
|
189
|
-
pierJobId: job.id,
|
|
190
|
-
pierNatsMsg: msg,
|
|
191
|
-
pierStartTime: startTime,
|
|
192
|
-
pierMeta: job.meta,
|
|
193
|
-
},
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
// If a system prompt was provided, attach it
|
|
197
|
-
if (job.systemPrompt) {
|
|
198
|
-
inbound.systemPrompt = job.systemPrompt;
|
|
199
|
-
}
|
|
203
|
+
logger.info(`[pier-connector] š Listening to Marketplace: ${publicSubject}`);
|
|
204
|
+
logger.info(`[pier-connector] š Listening to Direct Messages: ${privateSubject}`);
|
|
200
205
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const elapsed = (performance.now() - startTime).toFixed(1);
|
|
223
|
-
jobsFailed++;
|
|
224
|
-
logger.error(
|
|
225
|
-
`[pier-connector] ā Job ${job.id} failed after ${elapsed}ms ā ${err.message}`,
|
|
226
|
-
);
|
|
227
|
-
safeRespond(
|
|
228
|
-
msg,
|
|
229
|
-
createErrorPayload({
|
|
230
|
-
id: job.id,
|
|
231
|
-
errorCode: 'EXECUTION_FAILED',
|
|
232
|
-
errorMessage: err.message,
|
|
233
|
-
workerId: config.workerId,
|
|
234
|
-
walletAddress: config.walletAddress,
|
|
235
|
-
})
|
|
236
|
-
);
|
|
237
|
-
}
|
|
206
|
+
// Public pool with load balancing
|
|
207
|
+
nc.subscribe(publicSubject, {
|
|
208
|
+
queue: config.queueGroup,
|
|
209
|
+
callback: (err, msg) => handleMessage(msg)
|
|
210
|
+
});
|
|
211
|
+
// Private direct channel (no queue group for broadcast/direct)
|
|
212
|
+
nc.subscribe(privateSubject, {
|
|
213
|
+
callback: (err, msg) => handleMessage(msg)
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Unified Message Handler
|
|
217
|
+
async function handleMessage(msg) {
|
|
218
|
+
const startTime = performance.now();
|
|
219
|
+
const rawData = new TextDecoder().decode(msg.data);
|
|
220
|
+
|
|
221
|
+
let payload;
|
|
222
|
+
try {
|
|
223
|
+
payload = JSON.parse(rawData);
|
|
224
|
+
} catch (e) {
|
|
225
|
+
logger.error(`[pier-connector] Failed to parse JSON: ${rawData.substring(0, 100)}`);
|
|
226
|
+
return;
|
|
238
227
|
}
|
|
239
228
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
229
|
+
// V1.1 FEATURE: Task Poisoning / Poaching Protection
|
|
230
|
+
if (payload.assigned_node_id && payload.assigned_node_id !== config.nodeId) {
|
|
231
|
+
// Silent ignore - don't reply, don't log heavily
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
jobsReceived++;
|
|
236
|
+
const parsed = parseJob(msg, logger);
|
|
237
|
+
if (!parsed.ok) {
|
|
238
|
+
jobsFailed++;
|
|
239
|
+
logger.error(`[pier-connector] Job parse error: ${parsed.error}`);
|
|
240
|
+
safeRespond(msg, createErrorPayload({
|
|
241
|
+
id: 'unknown',
|
|
242
|
+
errorCode: 'PARSE_ERROR',
|
|
243
|
+
errorMessage: parsed.error,
|
|
244
|
+
workerId: config.nodeId,
|
|
245
|
+
walletAddress: config.walletAddress,
|
|
246
|
+
}));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const { job } = parsed;
|
|
251
|
+
logger.info(`[pier-connector] š„ Received job ${job.id}: "${truncate(job.task, 60)}"`);
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
const inbound = {
|
|
255
|
+
channelId: 'pier',
|
|
256
|
+
accountId: 'default',
|
|
257
|
+
senderId: `pier:${job.meta?.sender ?? 'anonymous'}`,
|
|
258
|
+
text: job.task,
|
|
259
|
+
metadata: {
|
|
260
|
+
pierJobId: job.id,
|
|
261
|
+
pierNatsMsg: msg,
|
|
262
|
+
pierStartTime: startTime,
|
|
263
|
+
pierMeta: job.meta,
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
if (job.systemPrompt) inbound.systemPrompt = job.systemPrompt;
|
|
268
|
+
|
|
269
|
+
if (api.runtime?.sendIncoming) {
|
|
270
|
+
await api.runtime.sendIncoming(inbound);
|
|
271
|
+
} else {
|
|
272
|
+
throw new Error('Agent runtime not available');
|
|
273
|
+
}
|
|
274
|
+
} catch (err) {
|
|
275
|
+
jobsFailed++;
|
|
276
|
+
safeRespond(msg, createErrorPayload({
|
|
277
|
+
id: job.id,
|
|
278
|
+
errorCode: 'EXECUTION_FAILED',
|
|
279
|
+
errorMessage: err.message,
|
|
280
|
+
workerId: config.nodeId,
|
|
281
|
+
walletAddress: config.walletAddress,
|
|
282
|
+
}));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
245
285
|
|
|
246
286
|
// Monitor connection closure
|
|
247
287
|
nc.closed().then((err) => {
|
|
248
288
|
connectionStatus = 'disconnected';
|
|
289
|
+
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
249
290
|
if (err) {
|
|
250
|
-
logger.error(
|
|
251
|
-
`[pier-connector] NATS connection closed with error: ${err.message}`,
|
|
252
|
-
);
|
|
291
|
+
logger.error(`[pier-connector] NATS connection closed with error: ${err.message}`);
|
|
253
292
|
} else {
|
|
254
293
|
logger.info('[pier-connector] NATS connection closed gracefully');
|
|
255
294
|
}
|
|
256
295
|
});
|
|
257
296
|
} catch (err) {
|
|
258
297
|
connectionStatus = 'error';
|
|
298
|
+
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
259
299
|
logger.error(`[pier-connector] ā Failed to start: ${err.message}`);
|
|
260
|
-
logger.error(
|
|
300
|
+
logger.error(err.stack);
|
|
261
301
|
}
|
|
262
302
|
},
|
|
263
303
|
|
|
@@ -393,6 +433,8 @@ export default function register(api) {
|
|
|
393
433
|
text: [
|
|
394
434
|
`**Pier Connector Status**`,
|
|
395
435
|
`⢠Connection: ${connectionStatus}`,
|
|
436
|
+
`⢠Node ID: ${config.nodeId || 'N/A'}`,
|
|
437
|
+
`⢠API URL: ${config.pierApiUrl}`,
|
|
396
438
|
`⢠NATS URL: ${config.natsUrl}`,
|
|
397
439
|
`⢠Subscribe: ${config.subject}`,
|
|
398
440
|
`⢠Publish: ${config.publishSubject}`,
|
|
@@ -403,5 +445,79 @@ export default function register(api) {
|
|
|
403
445
|
},
|
|
404
446
|
});
|
|
405
447
|
|
|
448
|
+
// āā 5. Register CLI Setup Command āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
449
|
+
|
|
450
|
+
api.registerCli(
|
|
451
|
+
({ program }) => {
|
|
452
|
+
// Find or create the 'pier' command namespace to avoid root conflicts
|
|
453
|
+
let pier = program.commands.find(c => c.name() === 'pier');
|
|
454
|
+
if (!pier) {
|
|
455
|
+
pier = program.command('pier').description('Pier connector commands');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
pier
|
|
459
|
+
.command('setup')
|
|
460
|
+
.description('Interactively configure the Pier connector settings')
|
|
461
|
+
.action(async () => {
|
|
462
|
+
const currentConfig = resolveConfig();
|
|
463
|
+
|
|
464
|
+
console.log('\nš¢ \x1b[1m\x1b[36mPier Connector Setup (V1.1)\x1b[0m');
|
|
465
|
+
console.log('Please register your bot at \x1b[1m\x1b[33mhttps://pier-connector.gholl.com\x1b[0m first.\n');
|
|
466
|
+
|
|
467
|
+
const answers = await inquirer.prompt([
|
|
468
|
+
{
|
|
469
|
+
type: 'input',
|
|
470
|
+
name: 'pierApiUrl',
|
|
471
|
+
message: 'Pier API URL:',
|
|
472
|
+
default: currentConfig.pierApiUrl,
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
type: 'input',
|
|
476
|
+
name: 'nodeId',
|
|
477
|
+
message: 'Bot Node ID (UUID):',
|
|
478
|
+
default: currentConfig.nodeId,
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
type: 'password',
|
|
482
|
+
name: 'secretKey',
|
|
483
|
+
message: 'Bot Secret Key:',
|
|
484
|
+
default: currentConfig.secretKey,
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
type: 'input',
|
|
488
|
+
name: 'walletAddress',
|
|
489
|
+
message: 'Your Wallet Address (for payout):',
|
|
490
|
+
default: currentConfig.walletAddress,
|
|
491
|
+
},
|
|
492
|
+
]);
|
|
493
|
+
|
|
494
|
+
// Determine how to save.
|
|
495
|
+
console.log('\nā
\x1b[32mConfiguration Captured!\x1b[0m\n');
|
|
496
|
+
|
|
497
|
+
const newConfigBlock = {
|
|
498
|
+
plugins: {
|
|
499
|
+
entries: {
|
|
500
|
+
"pier-connector": {
|
|
501
|
+
enabled: true,
|
|
502
|
+
config: {
|
|
503
|
+
pierApiUrl: answers.pierApiUrl,
|
|
504
|
+
nodeId: answers.nodeId,
|
|
505
|
+
secretKey: answers.secretKey,
|
|
506
|
+
walletAddress: answers.walletAddress
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
console.log('Currently, CLI automatic config writes depend on the framework host.');
|
|
514
|
+
console.log('Please ensure your \x1b[1m\x1b[33mopenclaw.config.json\x1b[0m includes the following block:\n');
|
|
515
|
+
console.log(JSON.stringify(newConfigBlock, null, 2));
|
|
516
|
+
console.log('\nRestart OpenClaw to apply changes.');
|
|
517
|
+
});
|
|
518
|
+
},
|
|
519
|
+
{ commands: ['pier'] }
|
|
520
|
+
);
|
|
521
|
+
|
|
406
522
|
logger.info('[pier-connector] Plugin registered');
|
|
407
523
|
}
|
package/src/protocol.js
CHANGED
|
@@ -14,9 +14,10 @@ export const PROTOCOL_VERSION = '1.0';
|
|
|
14
14
|
* @param {string} [params.systemPrompt]
|
|
15
15
|
* @param {number} [params.timeoutMs]
|
|
16
16
|
* @param {object} [params.meta]
|
|
17
|
+
* @param {string} [params.targetNodeId]
|
|
17
18
|
* @returns {object}
|
|
18
19
|
*/
|
|
19
|
-
export function createRequestPayload({ task, systemPrompt, timeoutMs, meta }) {
|
|
20
|
+
export function createRequestPayload({ task, systemPrompt, timeoutMs, meta, targetNodeId }) {
|
|
20
21
|
return {
|
|
21
22
|
version: PROTOCOL_VERSION,
|
|
22
23
|
id: crypto.randomUUID(),
|
|
@@ -25,6 +26,7 @@ export function createRequestPayload({ task, systemPrompt, timeoutMs, meta }) {
|
|
|
25
26
|
systemPrompt,
|
|
26
27
|
timeoutMs: timeoutMs || 60000,
|
|
27
28
|
meta: meta || {},
|
|
29
|
+
targetNodeId: targetNodeId || null,
|
|
28
30
|
};
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -101,6 +103,7 @@ export function normalizeInboundPayload(payload) {
|
|
|
101
103
|
task: payload.task,
|
|
102
104
|
systemPrompt: payload.systemPrompt,
|
|
103
105
|
meta: payload.meta || {},
|
|
106
|
+
targetNodeId: payload.targetNodeId || null,
|
|
104
107
|
},
|
|
105
108
|
};
|
|
106
109
|
}
|