@gholl-studio/pier-connector 0.2.12 → 0.2.16

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +83 -14
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.12",
4
+ "version": "0.2.16",
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",
package/src/index.js CHANGED
@@ -145,6 +145,7 @@ export default function register(api) {
145
145
  headers: {
146
146
  'Content-Type': 'application/json',
147
147
  'Authorization': `Bearer ${config.secretKey}`,
148
+ 'X-Node-Id': config.nodeId // BUG-40: Explicit Node ID for backend
148
149
  }
149
150
  });
150
151
 
@@ -207,6 +208,8 @@ export default function register(api) {
207
208
  MessageId: inbound.messageId || jobId
208
209
  });
209
210
 
211
+ let collectedResult = '';
212
+
210
213
  // Create a dispatcher to handle the reply lifecycle (fixes sendFinalReply error)
211
214
  const { dispatcher, markDispatchIdle } = api.runtime.channel.reply.createReplyDispatcherWithTyping({
212
215
  cfg: api.config,
@@ -215,12 +218,20 @@ export default function register(api) {
215
218
  logger.debug(`[pier-connector] Dispatcher idle for session ${route.sessionKey}`);
216
219
  },
217
220
  deliver: async (payload) => {
218
- // Route Agent's reply to outbound.sendText so it reaches NATS
219
- logger.debug(`[pier-connector] deliver() called with text: "${truncate(payload.text, 60)}"`);
221
+ const metadata = activeNodeJobs.get(jobId);
222
+ const isRealtime = metadata?.isRealtimeMsg;
223
+
224
+ if (!isRealtime) {
225
+ collectedResult += payload.text;
226
+ }
227
+
228
+ // Route Agent's reply to outbound.sendText
229
+ // For marketplace jobs, sendText will now just log (content is buffered in collectedResult)
230
+ // For realtime chat, it will publish to the chat subject immediately.
220
231
  await pierChannel.outbound.sendText({
221
232
  text: payload.text,
222
233
  to: `pier:${jobId}`,
223
- metadata: activeNodeJobs.get(jobId),
234
+ metadata,
224
235
  });
225
236
  }
226
237
  });
@@ -361,17 +372,9 @@ export default function register(api) {
361
372
  logger.error(`[pier-connector] Failed to publish realtime reply to NATS: ${err.message}`);
362
373
  }
363
374
  } else if (js) {
364
- // Publish result to jobs.submit via JetStream (not msg.respond)
365
- try {
366
- await js.publish('jobs.submit', new TextEncoder().encode(JSON.stringify(responsePayload)));
367
- jobsCompleted++;
368
- logger.info(
369
- `[pier-connector] ✔ Job ${jobId} result published to jobs.submit` +
370
- (elapsed ? ` — latency: ${elapsed}ms` : ''),
371
- );
372
- } catch (err) {
373
- logger.error(`[pier-connector] ❌ Failed to publish result for job ${jobId}: ${err.message}`);
374
- }
375
+ // BUG-44: Marketplace results are now buffered in receiveIncoming and submitted ONCE at the end.
376
+ // sendText only logs the progress here.
377
+ logger.debug(`[pier-connector] Marketplace response buffered: "${truncate(text, 40)}"`);
375
378
  }
376
379
 
377
380
  // Delayed stop for job-specific message listener
@@ -1087,6 +1090,72 @@ export default function register(api) {
1087
1090
  { optional: true }
1088
1091
  );
1089
1092
 
1093
+ // ── V2 System Action Tools ────────────────────────────────────────
1094
+
1095
+ const registerSystemActionTool = (name, description, action, extraParams, userRole = 'node') => {
1096
+ api.registerTool({
1097
+ name,
1098
+ description,
1099
+ parameters: {
1100
+ type: 'object',
1101
+ properties: {
1102
+ jobId: { type: 'string', description: 'The ID of the job/chat session' },
1103
+ ...extraParams
1104
+ },
1105
+ required: ['jobId', ...Object.keys(extraParams)]
1106
+ },
1107
+ async execute(_id, params) {
1108
+ if (!nc || connectionStatus !== 'connected') {
1109
+ return { content: [{ type: 'text', text: 'Error: NATS not connected' }] };
1110
+ }
1111
+ try {
1112
+ const config = getActiveConfig();
1113
+ const subject = `jobs.job.${params.jobId}.msg`;
1114
+
1115
+ const { jobId, ...payload } = params;
1116
+
1117
+ // If acting as User (Employer), we need the user API key ideally, but for demo we just pass node ID or secret.
1118
+ // The Backend handles sender_type="node" vs "user".
1119
+ const msgData = {
1120
+ id: ethers.hexlify(ethers.randomBytes(16)),
1121
+ job_id: params.jobId,
1122
+ sender_id: userRole === 'user' ? 'user_' + config.nodeId : config.nodeId,
1123
+ sender_type: userRole,
1124
+ content: JSON.stringify({
1125
+ type: 'system_action',
1126
+ action,
1127
+ payload
1128
+ }),
1129
+ timestamp: new Date().toISOString(),
1130
+ auth_token: config.secretKey, // auth handle in DB needs updating for user role simulation
1131
+ type: 'system_action',
1132
+ action: action
1133
+ };
1134
+
1135
+ await js.publish(subject, new TextEncoder().encode(JSON.stringify(msgData)));
1136
+ return { content: [{ type: 'text', text: `${action} executed successfully` }] };
1137
+ } catch (err) {
1138
+ return { content: [{ type: 'text', text: `Error: ${err.message}` }] };
1139
+ }
1140
+ }
1141
+ }, { optional: true });
1142
+ };
1143
+
1144
+ // For Node (Worker)
1145
+ registerSystemActionTool('pier_accept_task', 'Accept an offered task from the employer in the current chat', 'task_accept', {});
1146
+ registerSystemActionTool('pier_finish_task', 'Submit the final result for a task', 'task_submit', { result: { type: 'string', description: 'The final result or file links' } });
1147
+
1148
+ // For User (Employer Robot) - Spoofed identity using Node's config for A2A testing
1149
+ registerSystemActionTool('pier_propose_task', 'Offer a task to a node with a specific price', 'task_offer', {
1150
+ price: { type: 'number', description: 'The price in PIER tokens to offer' },
1151
+ description: { type: 'string', description: 'The formal task description' }
1152
+ }, 'user');
1153
+
1154
+ registerSystemActionTool('pier_rate_task', 'Rate the node after completion', 'task_rate', {
1155
+ score: { type: 'number', description: 'Rating from 1 to 5' },
1156
+ comment: { type: 'string', description: 'A short review' }
1157
+ }, 'user');
1158
+
1090
1159
  // ── 4. Register /pier status command ───────────────────────────────
1091
1160
 
1092
1161
  api.registerCommand({