@gholl-studio/pier-connector 0.2.29 → 0.2.31
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/openclaw.plugin.json +10 -0
- package/package.json +10 -3
- package/src/index.js +127 -117
- package/src/job-handler.js +2 -1
- package/src/compression.js +0 -12
- package/src/nats-client.js +0 -75
- package/src/protocol.js +0 -142
package/openclaw.plugin.json
CHANGED
|
@@ -59,6 +59,12 @@
|
|
|
59
59
|
"type": "string",
|
|
60
60
|
"default": "",
|
|
61
61
|
"description": "(Optional) Wallet address or account ID for receiving rewards"
|
|
62
|
+
},
|
|
63
|
+
"capabilities": {
|
|
64
|
+
"type": "array",
|
|
65
|
+
"items": { "type": "string" },
|
|
66
|
+
"default": ["translation", "code-execution", "reasoning", "vision"],
|
|
67
|
+
"description": "List of AI capabilities this node supports (e.g., translation, gpu:4090)"
|
|
62
68
|
}
|
|
63
69
|
}
|
|
64
70
|
},
|
|
@@ -102,6 +108,10 @@
|
|
|
102
108
|
"walletAddress": {
|
|
103
109
|
"label": "Wallet Address (Rewards)",
|
|
104
110
|
"placeholder": "0x..."
|
|
111
|
+
},
|
|
112
|
+
"capabilities": {
|
|
113
|
+
"label": "Capabilities",
|
|
114
|
+
"placeholder": "translation, reasoning, ..."
|
|
105
115
|
}
|
|
106
116
|
}
|
|
107
117
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gholl-studio/pier-connector",
|
|
3
3
|
"author": "gholl",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.31",
|
|
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",
|
|
@@ -19,8 +19,11 @@
|
|
|
19
19
|
]
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
|
-
"
|
|
23
|
-
"
|
|
22
|
+
"dev": "vite",
|
|
23
|
+
"build": "tsc -b && vite build",
|
|
24
|
+
"lint": "eslint .",
|
|
25
|
+
"preview": "vite preview",
|
|
26
|
+
"test:watch": "node --watch test-standalone.js"
|
|
24
27
|
},
|
|
25
28
|
"keywords": [
|
|
26
29
|
"openclaw",
|
|
@@ -31,6 +34,7 @@
|
|
|
31
34
|
],
|
|
32
35
|
"license": "MIT",
|
|
33
36
|
"dependencies": {
|
|
37
|
+
"pier-sdk": "file:../packages/pier-sdk",
|
|
34
38
|
"@nats-io/jetstream": "^3.3.1",
|
|
35
39
|
"@nats-io/transport-node": "^3.0.0",
|
|
36
40
|
"ethers": "^6.16.0",
|
|
@@ -44,5 +48,8 @@
|
|
|
44
48
|
"openclaw": {
|
|
45
49
|
"optional": true
|
|
46
50
|
}
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"dotenv": "^17.3.1"
|
|
47
54
|
}
|
|
48
55
|
}
|
package/src/index.js
CHANGED
|
@@ -8,13 +8,12 @@
|
|
|
8
8
|
* 4. A command ("/pier") for checking connection status
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { PierClient, protocol } from 'pier-sdk';
|
|
12
|
+
const { createRequestPayload, createResultPayload, createErrorPayload } = protocol;
|
|
12
13
|
import { parseJob, safeRespond, truncate } from './job-handler.js';
|
|
13
|
-
import { createRequestPayload, createResultPayload, createErrorPayload } from './protocol.js';
|
|
14
14
|
import { DEFAULTS } from './config.js';
|
|
15
15
|
import { jetstream, jetstreamManager, AckPolicy, DeliverPolicy } from '@nats-io/jetstream';
|
|
16
16
|
import inquirer from 'inquirer';
|
|
17
|
-
import { ethers } from 'ethers';
|
|
18
17
|
import fs from 'fs';
|
|
19
18
|
import path from 'path';
|
|
20
19
|
|
|
@@ -69,6 +68,7 @@ export default function register(api) {
|
|
|
69
68
|
queueGroup: merged.queueGroup || DEFAULTS.QUEUE_GROUP,
|
|
70
69
|
agentId: merged.agentId || DEFAULTS.AGENT_ID,
|
|
71
70
|
walletAddress: merged.walletAddress || DEFAULTS.WALLET_ADDRESS,
|
|
71
|
+
capabilities: merged.capabilities || ['translation', 'code-execution', 'reasoning', 'vision'],
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -80,6 +80,11 @@ export default function register(api) {
|
|
|
80
80
|
constructor(config) {
|
|
81
81
|
this.config = config;
|
|
82
82
|
this.accountId = config.accountId;
|
|
83
|
+
this.client = new PierClient({
|
|
84
|
+
apiUrl: config.pierApiUrl,
|
|
85
|
+
natsUrl: config.natsUrl,
|
|
86
|
+
logger
|
|
87
|
+
});
|
|
83
88
|
this.nc = null;
|
|
84
89
|
this.js = null;
|
|
85
90
|
this.jsm = null;
|
|
@@ -95,26 +100,37 @@ export default function register(api) {
|
|
|
95
100
|
this.connectedAt = null;
|
|
96
101
|
}
|
|
97
102
|
|
|
103
|
+
async init() {
|
|
104
|
+
try {
|
|
105
|
+
this.nc = await this.client.connectNats();
|
|
106
|
+
this.js = jetstream(this.nc);
|
|
107
|
+
this.jsm = await jetstreamManager(this.nc);
|
|
108
|
+
|
|
109
|
+
this.connectedAt = new Date();
|
|
110
|
+
this.connectionStatus = 'connected';
|
|
111
|
+
|
|
112
|
+
// Subscribe to tasks
|
|
113
|
+
await this.subscribeToTasks();
|
|
114
|
+
|
|
115
|
+
// Start heartbeat
|
|
116
|
+
this.startHeartbeat();
|
|
117
|
+
|
|
118
|
+
return true;
|
|
119
|
+
} catch (err) {
|
|
120
|
+
this.connectionStatus = 'error';
|
|
121
|
+
logger.error(`[pier-connector] Account ${this.accountId} failed to connect: ${err.message}`);
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
98
126
|
async heartbeat() {
|
|
99
127
|
if (!this.config.nodeId || !this.config.secretKey) return null;
|
|
100
128
|
try {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
secret_key: this.config.secretKey,
|
|
107
|
-
capabilities: ['translation', 'code-execution', 'reasoning', 'vision'],
|
|
108
|
-
description: `OpenClaw Node (${this.config.nodeId.substring(0, 8)}) [${this.accountId}]`,
|
|
109
|
-
}),
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
if (!resp.ok) {
|
|
113
|
-
const errText = await resp.text();
|
|
114
|
-
throw new Error(`Heartbeat failed (${resp.status}): ${errText}`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const data = await resp.json();
|
|
129
|
+
const data = await this.client.heartbeat(
|
|
130
|
+
this.config.nodeId,
|
|
131
|
+
this.config.secretKey,
|
|
132
|
+
this.config.capabilities
|
|
133
|
+
);
|
|
118
134
|
this.lastHeartbeatError = null;
|
|
119
135
|
return data.nats_config || null;
|
|
120
136
|
} catch (err) {
|
|
@@ -125,28 +141,7 @@ export default function register(api) {
|
|
|
125
141
|
}
|
|
126
142
|
|
|
127
143
|
async claimJob(jobId) {
|
|
128
|
-
|
|
129
|
-
const resp = await fetch(`${this.config.pierApiUrl}/jobs/${jobId}/claim`, {
|
|
130
|
-
method: 'POST',
|
|
131
|
-
headers: {
|
|
132
|
-
'Content-Type': 'application/json',
|
|
133
|
-
'Authorization': `Bearer ${this.config.secretKey}`,
|
|
134
|
-
'X-Node-Id': this.config.nodeId
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
if (!resp.ok) {
|
|
139
|
-
if (resp.status === 409) {
|
|
140
|
-
logger.warn(`[pier-connector][${this.accountId}] 🚫 Job ${jobId} already claimed.`);
|
|
141
|
-
return { ok: false, alreadyClaimed: true };
|
|
142
|
-
}
|
|
143
|
-
const errData = await resp.json().catch(() => ({}));
|
|
144
|
-
return { ok: false, error: errData.error || resp.statusText };
|
|
145
|
-
}
|
|
146
|
-
return { ok: true };
|
|
147
|
-
} catch (err) {
|
|
148
|
-
return { ok: false, error: err.message };
|
|
149
|
-
}
|
|
144
|
+
return await this.client.claimJob(jobId, this.config.nodeId, this.config.secretKey);
|
|
150
145
|
}
|
|
151
146
|
|
|
152
147
|
/**
|
|
@@ -416,15 +411,17 @@ export default function register(api) {
|
|
|
416
411
|
let finalText = job.task;
|
|
417
412
|
if (!isTargeted) {
|
|
418
413
|
finalText = `【PIER MARKETPLACE OPEN JOB】\nJob ID: ${job.id}\nTask: ${job.task}\n\n=== CRITICAL ===\nYou MUST use \`pier_bid_task\` to bid. Do not solve directly.`;
|
|
414
|
+
} else {
|
|
415
|
+
// Only subscribe to chat for targeted/assigned jobs
|
|
416
|
+
await this.subscribeToJobMessages(job.id);
|
|
419
417
|
}
|
|
420
418
|
|
|
421
|
-
await this.subscribeToJobMessages(job.id);
|
|
422
419
|
await this.receiveIncoming({
|
|
423
420
|
accountId: this.accountId,
|
|
424
421
|
senderId: `pier:${senderCore}`,
|
|
425
422
|
text: finalText,
|
|
426
423
|
}, job.id);
|
|
427
|
-
|
|
424
|
+
|
|
428
425
|
this.isBusy = false;
|
|
429
426
|
}
|
|
430
427
|
|
|
@@ -460,30 +457,10 @@ export default function register(api) {
|
|
|
460
457
|
}
|
|
461
458
|
|
|
462
459
|
async autoRegister() {
|
|
463
|
-
const wallet = new ethers.Wallet(this.config.privateKey);
|
|
464
|
-
const address = wallet.address;
|
|
465
|
-
|
|
466
|
-
const challengeRes = await fetch(`${this.config.pierApiUrl}/auth/challenge?wallet_address=${address}`);
|
|
467
|
-
const { challenge } = await challengeRes.json();
|
|
468
|
-
const signature = await wallet.signMessage(challenge);
|
|
469
|
-
|
|
470
|
-
const loginRes = await fetch(`${this.config.pierApiUrl}/auth/login`, {
|
|
471
|
-
method: 'POST',
|
|
472
|
-
headers: { 'Content-Type': 'application/json' },
|
|
473
|
-
body: JSON.stringify({ wallet_address: address, challenge, signature })
|
|
474
|
-
});
|
|
475
|
-
const { api_key } = await loginRes.json();
|
|
476
|
-
|
|
477
460
|
const hostName = `${api?.getRuntimeInfo?.()?.hostname ?? 'Auto'}-${this.accountId}`;
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
body: JSON.stringify({ wallet_address: address, name: hostName })
|
|
482
|
-
});
|
|
483
|
-
const { id, secret_key } = await regRes.json();
|
|
484
|
-
|
|
485
|
-
this.config.nodeId = id;
|
|
486
|
-
this.config.secretKey = secret_key;
|
|
461
|
+
const { nodeId, secretKey } = await this.client.autoRegister(this.config.privateKey, hostName);
|
|
462
|
+
this.config.nodeId = nodeId;
|
|
463
|
+
this.config.secretKey = secretKey;
|
|
487
464
|
}
|
|
488
465
|
|
|
489
466
|
async start() {
|
|
@@ -599,18 +576,24 @@ export default function register(api) {
|
|
|
599
576
|
try {
|
|
600
577
|
const replySubject = `chat.${jobId}`;
|
|
601
578
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
579
|
+
// Only publish replies for jobs assigned to this robot
|
|
580
|
+
if (!metadata?.isTargeted) {
|
|
581
|
+
logger.debug(`[pier-connector][${accountId}] 🫨 Suppressing reply for non-assigned job ${jobId}`);
|
|
582
|
+
} else {
|
|
583
|
+
const chatPayload = {
|
|
584
|
+
id: crypto.randomUUID ? crypto.randomUUID() : (Math.random().toString(36).substring(2)),
|
|
585
|
+
job_id: jobId,
|
|
586
|
+
sender_id: robot.config.nodeId || 'anonymous',
|
|
587
|
+
sender_name: accountId,
|
|
588
|
+
sender_type: 'node',
|
|
589
|
+
content: text,
|
|
590
|
+
created_at: new Date().toISOString(),
|
|
591
|
+
auth_token: robot.config.secretKey
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
await robot.js.publish(replySubject, new TextEncoder().encode(JSON.stringify(chatPayload)));
|
|
595
|
+
logger.info(`[pier-connector][${accountId}] 💬 Agent reply published to NATS for job ${jobId} (Subject: ${replySubject})`);
|
|
596
|
+
}
|
|
614
597
|
} catch (err) {
|
|
615
598
|
logger.error(`[pier-connector][${accountId}] Failed to publish reply: ${err.message}`);
|
|
616
599
|
}
|
|
@@ -745,8 +728,9 @@ export default function register(api) {
|
|
|
745
728
|
},
|
|
746
729
|
required: ['jobId', 'text']
|
|
747
730
|
},
|
|
748
|
-
async execute(_id, params) {
|
|
731
|
+
async execute(_id, params, ctx) {
|
|
749
732
|
const accountId = params.accountId || 'default';
|
|
733
|
+
logger.info(`[pier-connector] 🛠️ Tool called: pier_chat | jobId=${params.jobId} | accountId=${accountId}`);
|
|
750
734
|
const robot = instances.get(accountId) || instances.values().next().value;
|
|
751
735
|
if (!robot || robot.connectionStatus !== 'connected') {
|
|
752
736
|
return { content: [{ type: 'text', text: 'Error: Robot not connected' }] };
|
|
@@ -754,10 +738,20 @@ export default function register(api) {
|
|
|
754
738
|
|
|
755
739
|
try {
|
|
756
740
|
const subject = `chat.${params.jobId}`;
|
|
741
|
+
let metadata = robot.activeNodeJobs.get(params.jobId);
|
|
742
|
+
|
|
743
|
+
if (!metadata && ctx.to) {
|
|
744
|
+
const toId = ctx.to.replace(/^pier:/, '');
|
|
745
|
+
metadata = robot.activeNodeJobs.get(toId);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const jobId = metadata?.pierJobId || params.jobId;
|
|
749
|
+
|
|
757
750
|
const payload = {
|
|
758
751
|
id: crypto.randomUUID ? crypto.randomUUID() : (Math.random().toString(36).substring(2)),
|
|
759
|
-
job_id:
|
|
752
|
+
job_id: jobId,
|
|
760
753
|
sender_id: robot.config.nodeId,
|
|
754
|
+
sender_name: accountId,
|
|
761
755
|
sender_type: 'node',
|
|
762
756
|
content: params.text,
|
|
763
757
|
created_at: new Date().toISOString(),
|
|
@@ -837,6 +831,41 @@ export default function register(api) {
|
|
|
837
831
|
comment: { type: 'string', description: 'A short review' }
|
|
838
832
|
}, 'user');
|
|
839
833
|
|
|
834
|
+
registerSystemActionTool('pier_reject_task', 'Reject an offered task from the employer in the current chat', 'task_reject', {
|
|
835
|
+
reason: { type: 'string', description: 'Reason for rejection' }
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
registerSystemActionTool('pier_fail_task', 'Report that the task has failed during execution', 'task_error', {
|
|
839
|
+
error: { type: 'string', description: 'Description of the error' }
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
registerSystemActionTool('pier_cancel_task', 'Cancel a previously proposed task', 'task_cancel', {
|
|
843
|
+
reason: { type: 'string', description: 'Reason for cancellation' }
|
|
844
|
+
}, 'user');
|
|
845
|
+
|
|
846
|
+
api.registerTool({
|
|
847
|
+
name: 'pier_get_profile',
|
|
848
|
+
description: 'Get the current Pier profile, balance, and node stats.',
|
|
849
|
+
parameters: {
|
|
850
|
+
type: 'object',
|
|
851
|
+
properties: {
|
|
852
|
+
accountId: { type: 'string' }
|
|
853
|
+
}
|
|
854
|
+
},
|
|
855
|
+
async execute(_id, params) {
|
|
856
|
+
const accountId = params.accountId || 'default';
|
|
857
|
+
const robot = instances.get(accountId) || instances.values().next().value;
|
|
858
|
+
if (!robot) return { content: [{ type: 'text', text: 'Error: Robot not found' }] };
|
|
859
|
+
|
|
860
|
+
try {
|
|
861
|
+
const profile = await robot.client.getUserProfile(robot.config.secretKey);
|
|
862
|
+
return { content: [{ type: 'text', text: JSON.stringify(profile, null, 2) }] };
|
|
863
|
+
} catch (err) {
|
|
864
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }] };
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}, { optional: true });
|
|
868
|
+
|
|
840
869
|
// ── 4. Register /pier status command ───────────────────────────────
|
|
841
870
|
|
|
842
871
|
api.registerCommand({
|
|
@@ -920,55 +949,35 @@ export default function register(api) {
|
|
|
920
949
|
{ type: 'input', name: 'pierApiUrl', message: 'Pier API URL:', default: currentConfig.pierApiUrl },
|
|
921
950
|
{ type: 'input', name: 'nodeId', message: 'Bot Node ID (UUID):', default: finalNodeId },
|
|
922
951
|
{ type: 'password', name: 'secretKey', message: 'Bot Secret Key:', default: finalSecretKey },
|
|
923
|
-
{ type: 'input', name: 'walletAddress', message: 'Your Wallet Address (for payout):', default: finalWallet }
|
|
952
|
+
{ type: 'input', name: 'walletAddress', message: 'Your Wallet Address (for payout):', default: finalWallet },
|
|
953
|
+
{ type: 'input', name: 'capabilities', message: 'Capabilities (comma-separated):', default: (currentConfig.capabilities || []).join(', ') }
|
|
924
954
|
]);
|
|
925
955
|
finalNodeId = manualAnswers.nodeId;
|
|
926
956
|
finalSecretKey = manualAnswers.secretKey;
|
|
927
957
|
finalWallet = manualAnswers.walletAddress;
|
|
958
|
+
currentConfig.capabilities = manualAnswers.capabilities ? manualAnswers.capabilities.split(',').map(s => s.trim()) : currentConfig.capabilities;
|
|
928
959
|
currentConfig.pierApiUrl = manualAnswers.pierApiUrl;
|
|
929
960
|
} else if (setupMethod === 'auto') {
|
|
930
961
|
const autoAnswers = await inquirer.prompt([
|
|
931
962
|
{ type: 'input', name: 'pierApiUrl', message: 'Pier API URL:', default: currentConfig.pierApiUrl },
|
|
932
|
-
{ type: 'password', name: 'privateKey', message: 'Your Wallet Private Key (Hex, 0x...):', default: finalPrivateKey }
|
|
963
|
+
{ type: 'password', name: 'privateKey', message: 'Your Wallet Private Key (Hex, 0x...):', default: finalPrivateKey },
|
|
964
|
+
{ type: 'input', name: 'capabilities', message: 'Capabilities (comma-separated):', default: (currentConfig.capabilities || []).join(', ') }
|
|
933
965
|
]);
|
|
934
966
|
console.log('\n\x1b[36mRegistering your node...\x1b[0m');
|
|
935
967
|
try {
|
|
936
|
-
const
|
|
937
|
-
console.log(`\x1b[32m✔ Loaded wallet: ${wallet.address}\x1b[0m`);
|
|
938
|
-
finalWallet = finalWallet || wallet.address;
|
|
939
|
-
finalPrivateKey = autoAnswers.privateKey;
|
|
940
|
-
currentConfig.pierApiUrl = autoAnswers.pierApiUrl;
|
|
941
|
-
|
|
942
|
-
const challengeRes = await fetch(`${currentConfig.pierApiUrl}/auth/challenge?wallet_address=${wallet.address}`);
|
|
943
|
-
if (!challengeRes.ok) throw new Error("Failed to get challenge");
|
|
944
|
-
const { challenge } = await challengeRes.json();
|
|
945
|
-
|
|
946
|
-
const signature = await wallet.signMessage(challenge);
|
|
947
|
-
|
|
948
|
-
const loginRes = await fetch(`${currentConfig.pierApiUrl}/auth/login`, {
|
|
949
|
-
method: 'POST',
|
|
950
|
-
headers: { 'Content-Type': 'application/json' },
|
|
951
|
-
body: JSON.stringify({ wallet_address: wallet.address, challenge, signature })
|
|
952
|
-
});
|
|
953
|
-
if (!loginRes.ok) throw new Error("Failed to login");
|
|
954
|
-
const { api_key } = await loginRes.json();
|
|
955
|
-
|
|
968
|
+
const tempClient = new PierClient({ apiUrl: autoAnswers.pierApiUrl });
|
|
956
969
|
const hostName = api?.getRuntimeInfo?.()?.hostname ?? 'Auto-Node';
|
|
957
|
-
const regRes = await fetch(`${currentConfig.pierApiUrl}/nodes/register`, {
|
|
958
|
-
method: 'POST',
|
|
959
|
-
headers: {
|
|
960
|
-
'Content-Type': 'application/json',
|
|
961
|
-
'Authorization': `Bearer ${api_key}`
|
|
962
|
-
},
|
|
963
|
-
body: JSON.stringify({ wallet_address: wallet.address, name: hostName })
|
|
964
|
-
});
|
|
965
970
|
|
|
966
|
-
|
|
967
|
-
|
|
971
|
+
const { nodeId, secretKey, walletAddress } = await tempClient.autoRegister(autoAnswers.privateKey, hostName);
|
|
972
|
+
|
|
973
|
+
finalWallet = finalWallet || walletAddress;
|
|
974
|
+
finalPrivateKey = autoAnswers.privateKey;
|
|
975
|
+
currentConfig.pierApiUrl = autoAnswers.pierApiUrl;
|
|
976
|
+
currentConfig.capabilities = autoAnswers.capabilities ? autoAnswers.capabilities.split(',').map(s => s.trim()) : currentConfig.capabilities;
|
|
977
|
+
finalNodeId = nodeId;
|
|
978
|
+
finalSecretKey = secretKey;
|
|
968
979
|
|
|
969
|
-
|
|
970
|
-
finalSecretKey = secret_key;
|
|
971
|
-
console.log(`\x1b[32m✔ Node registered automatically! Node ID: ${id}\x1b[0m`);
|
|
980
|
+
console.log(`\x1b[32m✔ Node registered automatically! Node ID: ${nodeId}\x1b[0m`);
|
|
972
981
|
} catch (err) {
|
|
973
982
|
console.error(`\x1b[31m✖ Failed to auto-register: ${err.message}\x1b[0m`);
|
|
974
983
|
return; // Stop setup
|
|
@@ -1014,7 +1023,8 @@ export default function register(api) {
|
|
|
1014
1023
|
nodeId: finalNodeId,
|
|
1015
1024
|
secretKey: finalSecretKey,
|
|
1016
1025
|
walletAddress: finalWallet,
|
|
1017
|
-
agentId: finalAgentId
|
|
1026
|
+
agentId: finalAgentId,
|
|
1027
|
+
capabilities: currentConfig.capabilities
|
|
1018
1028
|
};
|
|
1019
1029
|
|
|
1020
1030
|
if (setupMethod === 'auto') {
|
package/src/job-handler.js
CHANGED
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
* @param {object} logger
|
|
21
21
|
* @returns {{ ok: true, job: object } | { ok: false, error: string }}
|
|
22
22
|
*/
|
|
23
|
-
import {
|
|
23
|
+
import { protocol } from 'pier-sdk';
|
|
24
|
+
const { normalizeInboundPayload } = protocol;
|
|
24
25
|
|
|
25
26
|
export function parseJob(msg, logger) {
|
|
26
27
|
let raw;
|
package/src/compression.js
DELETED
package/src/nats-client.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NATS WebSocket connection manager.
|
|
3
|
-
*
|
|
4
|
-
* Uses `wsconnect` from @nats-io/transport-node to establish a
|
|
5
|
-
* WebSocket connection to the NATS server.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { wsconnect } from '@nats-io/transport-node';
|
|
9
|
-
import { jetstream } from '@nats-io/jetstream';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Create and return a NATS connection over WebSocket.
|
|
13
|
-
*
|
|
14
|
-
* @param {string} url – WebSocket URL, e.g. "wss://pier.gholl.com/nexus"
|
|
15
|
-
* @param {object} logger – OpenClaw logger (api.logger)
|
|
16
|
-
* @returns {Promise<import('@nats-io/transport-node').NatsConnection>}
|
|
17
|
-
*/
|
|
18
|
-
export async function createNatsConnection(url, logger) {
|
|
19
|
-
logger.info(`[pier-connector] Connecting to NATS at ${url} …`);
|
|
20
|
-
|
|
21
|
-
const nc = await wsconnect({ servers: url });
|
|
22
|
-
|
|
23
|
-
logger.info('[pier-connector] ✔ NATS connected successfully');
|
|
24
|
-
|
|
25
|
-
// ---------- lifecycle event monitoring ----------
|
|
26
|
-
|
|
27
|
-
// Fired when the client is disconnected from the server
|
|
28
|
-
(async () => {
|
|
29
|
-
for await (const status of nc.status()) {
|
|
30
|
-
switch (status.type) {
|
|
31
|
-
case 'disconnect':
|
|
32
|
-
logger.warn(`[pier-connector] NATS disconnected: ${status.data}`);
|
|
33
|
-
break;
|
|
34
|
-
|
|
35
|
-
case 'reconnect':
|
|
36
|
-
logger.info(`[pier-connector] NATS reconnected to ${status.data}`);
|
|
37
|
-
break;
|
|
38
|
-
|
|
39
|
-
case 'reconnecting':
|
|
40
|
-
logger.warn('[pier-connector] NATS reconnecting …');
|
|
41
|
-
break;
|
|
42
|
-
|
|
43
|
-
case 'error':
|
|
44
|
-
logger.error(`[pier-connector] NATS error: ${status.data}`);
|
|
45
|
-
break;
|
|
46
|
-
|
|
47
|
-
case 'update':
|
|
48
|
-
logger.info(`[pier-connector] NATS cluster update: ${JSON.stringify(status.data)}`);
|
|
49
|
-
break;
|
|
50
|
-
|
|
51
|
-
default:
|
|
52
|
-
logger.info(`[pier-connector] NATS status: ${status.type}`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
})();
|
|
56
|
-
|
|
57
|
-
return nc;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Gracefully drain (flush pending + close) a NATS connection.
|
|
62
|
-
*
|
|
63
|
-
* @param {import('@nats-io/transport-node').NatsConnection} nc
|
|
64
|
-
* @param {object} logger
|
|
65
|
-
*/
|
|
66
|
-
export async function drainConnection(nc, logger) {
|
|
67
|
-
if (!nc) return;
|
|
68
|
-
try {
|
|
69
|
-
logger.info('[pier-connector] Draining NATS connection …');
|
|
70
|
-
await nc.drain();
|
|
71
|
-
logger.info('[pier-connector] ✔ NATS connection drained');
|
|
72
|
-
} catch (err) {
|
|
73
|
-
logger.error(`[pier-connector] Error draining NATS: ${err.message}`);
|
|
74
|
-
}
|
|
75
|
-
}
|
package/src/protocol.js
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Protocol Definitions for Pier Connector
|
|
3
|
-
*
|
|
4
|
-
* Standardizes outbound and inbound JSON boundaries for NATS.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export const PROTOCOL_VERSION = '1.1';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Creates a standard task request payload to send to Pier.
|
|
11
|
-
*
|
|
12
|
-
* @param {object} params
|
|
13
|
-
* @param {string} params.task
|
|
14
|
-
* @param {string} [params.systemPrompt]
|
|
15
|
-
* @param {number} [params.timeoutMs]
|
|
16
|
-
* @param {object} [params.meta]
|
|
17
|
-
* @param {string} [params.targetNodeId]
|
|
18
|
-
* @param {string} [params.parentJobId]
|
|
19
|
-
* @returns {object}
|
|
20
|
-
*/
|
|
21
|
-
export function createRequestPayload({ task, systemPrompt, timeoutMs, meta, targetNodeId, parentJobId }) {
|
|
22
|
-
return {
|
|
23
|
-
version: PROTOCOL_VERSION,
|
|
24
|
-
id: crypto.randomUUID(),
|
|
25
|
-
type: 'task', // Changed from task_request to task to align with backend models
|
|
26
|
-
task,
|
|
27
|
-
systemPrompt,
|
|
28
|
-
timeoutMs: timeoutMs || 60000,
|
|
29
|
-
meta: meta || {},
|
|
30
|
-
targetNodeId: targetNodeId || null,
|
|
31
|
-
parentJobId: parentJobId || null,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Creates a standard task result payload to respond to Pier.
|
|
37
|
-
*
|
|
38
|
-
* @param {object} params
|
|
39
|
-
* @param {string} params.id
|
|
40
|
-
* @param {string} params.reply
|
|
41
|
-
* @param {number} [params.latencyMs]
|
|
42
|
-
* @param {string} [params.workerId]
|
|
43
|
-
* @param {string} [params.walletAddress]
|
|
44
|
-
* @returns {object}
|
|
45
|
-
*/
|
|
46
|
-
export function createResultPayload({ id, reply, latencyMs, workerId, walletAddress }) {
|
|
47
|
-
return {
|
|
48
|
-
version: PROTOCOL_VERSION,
|
|
49
|
-
id,
|
|
50
|
-
type: 'task_result',
|
|
51
|
-
ok: true,
|
|
52
|
-
result: reply,
|
|
53
|
-
latencyMs,
|
|
54
|
-
worker: {
|
|
55
|
-
id: workerId || 'anonymous-worker',
|
|
56
|
-
wallet: walletAddress || null,
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Creates a standard task error payload to respond to Pier.
|
|
63
|
-
*
|
|
64
|
-
* @param {object} params
|
|
65
|
-
* @param {string} params.id
|
|
66
|
-
* @param {string} params.errorCode
|
|
67
|
-
* @param {string} params.errorMessage
|
|
68
|
-
* @param {string} [params.workerId]
|
|
69
|
-
* @param {string} [params.walletAddress]
|
|
70
|
-
* @returns {object}
|
|
71
|
-
*/
|
|
72
|
-
export function createErrorPayload({ id, errorCode, errorMessage, workerId, walletAddress }) {
|
|
73
|
-
return {
|
|
74
|
-
version: PROTOCOL_VERSION,
|
|
75
|
-
id,
|
|
76
|
-
type: 'task_error',
|
|
77
|
-
ok: false,
|
|
78
|
-
error: {
|
|
79
|
-
code: errorCode,
|
|
80
|
-
message: errorMessage,
|
|
81
|
-
},
|
|
82
|
-
worker: {
|
|
83
|
-
id: workerId || 'anonymous-worker',
|
|
84
|
-
wallet: walletAddress || null,
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Validate and normalize an incoming NATS payload to a standard job format.
|
|
91
|
-
*
|
|
92
|
-
* @param {object} payload
|
|
93
|
-
* @returns {{ ok: true, job: object } | { ok: false, error: string }}
|
|
94
|
-
*/
|
|
95
|
-
export function normalizeInboundPayload(payload) {
|
|
96
|
-
// If it's a strict v1.1+ protocol request
|
|
97
|
-
if (payload.type === 'task' || payload.type === 'task_request') {
|
|
98
|
-
if (!payload.task) {
|
|
99
|
-
return { ok: false, error: 'Missing "task" field in task payload' };
|
|
100
|
-
}
|
|
101
|
-
return {
|
|
102
|
-
ok: true,
|
|
103
|
-
job: {
|
|
104
|
-
id: payload.id || crypto.randomUUID(),
|
|
105
|
-
task: payload.task,
|
|
106
|
-
systemPrompt: payload.systemPrompt,
|
|
107
|
-
meta: payload.meta || {},
|
|
108
|
-
targetNodeId: payload.targetNodeId || null,
|
|
109
|
-
parentJobId: payload.parentJobId || null,
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Support for Join-Chat wakeup signals (BUG-26/29)
|
|
115
|
-
if (payload.type === 'wakeup') {
|
|
116
|
-
const jobId = payload.jobId || payload.id;
|
|
117
|
-
if (!jobId) return { ok: false, error: 'Missing jobId in wakeup payload' };
|
|
118
|
-
return {
|
|
119
|
-
ok: true,
|
|
120
|
-
job: {
|
|
121
|
-
id: jobId,
|
|
122
|
-
type: 'wakeup'
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Fallback for legacy / generic JSON structures
|
|
128
|
-
const task = payload.task ?? payload.prompt ?? payload.content ?? '';
|
|
129
|
-
if (!task) {
|
|
130
|
-
return { ok: false, error: 'Missing "task" or "prompt" field in payload' };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return {
|
|
134
|
-
ok: true,
|
|
135
|
-
job: {
|
|
136
|
-
id: payload.id || crypto.randomUUID(),
|
|
137
|
-
task,
|
|
138
|
-
systemPrompt: payload.systemPrompt,
|
|
139
|
-
meta: payload.meta || {},
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
}
|