@gholl-studio/pier-connector 0.1.6 → 0.2.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +103 -90
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gholl-studio/pier-connector",
3
3
  "author": "gholl",
4
- "version": "0.1.6",
4
+ "version": "0.2.0",
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
@@ -162,7 +162,9 @@ export default function register(api) {
162
162
  OriginatingChannel: 'pier',
163
163
  OriginatingTo: `pier:${jobId}`,
164
164
  WasMentioned: true,
165
- CommandAuthorized: true
165
+ CommandAuthorized: true,
166
+ SystemPrompt: inbound.systemPrompt || undefined,
167
+ MessageId: inbound.messageId || jobId
166
168
  });
167
169
 
168
170
  // Create a dispatcher to handle the reply lifecycle (fixes sendFinalReply error)
@@ -173,9 +175,12 @@ export default function register(api) {
173
175
  logger.debug(`[pier-connector] Dispatcher idle for session ${route.sessionKey}`);
174
176
  },
175
177
  deliver: async (payload) => {
176
- // This triggers the outbound.sendText handler we defined in pierChannel
177
- await api.runtime.channel.reply.withReplyDispatcher(dispatcher, async () => {
178
- // withReplyDispatcher is sometimes needed by internal SDK methods
178
+ // Route Agent's reply to outbound.sendText so it reaches NATS
179
+ logger.debug(`[pier-connector] deliver() called with text: "${truncate(payload.text, 60)}"`);
180
+ await pierChannel.outbound.sendText({
181
+ text: payload.text,
182
+ to: `pier:${jobId}`,
183
+ metadata: activeNodeJobs.get(jobId),
179
184
  });
180
185
  }
181
186
  });
@@ -195,12 +200,14 @@ export default function register(api) {
195
200
  }
196
201
 
197
202
  try {
203
+ logger.info(`[pier-connector] 🧠 Triggering agent for session ${route.sessionKey}...`);
198
204
  // Dispatch reply — this will trigger outbound: sendText in pierChannel
199
205
  await api.runtime.channel.reply.dispatchReplyFromConfig({
200
206
  ctx: ctxPayload,
201
207
  cfg: api.config,
202
208
  dispatcher
203
209
  });
210
+ logger.info(`[pier-connector] 🧠 Agent dispatch completed for ${route.sessionKey}`);
204
211
  } finally {
205
212
  markDispatchIdle();
206
213
  }
@@ -269,11 +276,14 @@ export default function register(api) {
269
276
  const text = ctx.text;
270
277
  let metadata = ctx.metadata;
271
278
 
279
+ logger.info(`[pier-connector] 📤 Agent sending reply: "${truncate(text, 40)}" (To: ${ctx.to})`);
280
+
272
281
  if (!metadata && ctx.to) {
273
282
  const toId = ctx.to.replace(/^pier:/, '');
274
283
  metadata = activeNodeJobs.get(toId);
284
+ logger.debug(`[pier-connector] 📤 Resolved metadata for ${toId}: ${metadata ? 'Found' : 'Missing'}`);
275
285
  }
276
-
286
+
277
287
  const jobId = metadata?.pierJobId;
278
288
  const msg = metadata?.pierNatsMsg;
279
289
  const isRealtimeMsg = metadata?.isRealtimeMsg;
@@ -296,24 +306,32 @@ export default function register(api) {
296
306
  const config = getActiveConfig();
297
307
  const replySubject = `jobs.job.${jobId}.msg`;
298
308
  const replyPayload = {
299
- id: ethers.hexlify(ethers.randomBytes(16)), // Unique ID for frontend tracking
309
+ id: ethers.hexlify(ethers.randomBytes(16)),
310
+ job_id: jobId,
300
311
  sender_id: config.nodeId,
312
+ sender_type: 'node',
301
313
  content: text,
302
- created_at: new Date().toISOString()
314
+ timestamp: new Date().toISOString(),
315
+ auth_token: config.secretKey // Secure token for backend validation
303
316
  };
317
+
304
318
  await js.publish(replySubject, new TextEncoder().encode(JSON.stringify(replyPayload)));
305
- logger.info(`[pier-connector] 💬 Published agent reply to realtime chat for job ${jobId}`);
319
+ logger.info(`[pier-connector] 💬 Agent reply published directly to NATS for job ${jobId}`);
306
320
  } catch (err) {
307
- logger.error(`[pier-connector] Failed to send realtime reply: ${err.message}`);
321
+ logger.error(`[pier-connector] Failed to publish realtime reply to NATS: ${err.message}`);
322
+ }
323
+ } else if (js) {
324
+ // Publish result to jobs.submit via JetStream (not msg.respond)
325
+ try {
326
+ await js.publish('jobs.submit', new TextEncoder().encode(JSON.stringify(responsePayload)));
327
+ jobsCompleted++;
328
+ logger.info(
329
+ `[pier-connector] ✔ Job ${jobId} result published to jobs.submit` +
330
+ (elapsed ? ` — latency: ${elapsed}ms` : ''),
331
+ );
332
+ } catch (err) {
333
+ logger.error(`[pier-connector] ❌ Failed to publish result for job ${jobId}: ${err.message}`);
308
334
  }
309
- } else if (msg) {
310
- safeRespond(msg, responsePayload);
311
-
312
- jobsCompleted++;
313
- logger.info(
314
- `[pier-connector] ✔ Job ${jobId} completed` +
315
- (elapsed ? ` — latency: ${elapsed}ms` : ''),
316
- );
317
335
  }
318
336
 
319
337
  // Delayed stop for job-specific message listener
@@ -468,17 +486,35 @@ export default function register(api) {
468
486
 
469
487
  (async () => {
470
488
  try {
471
- let consumer = await js.consumers.get(streamName, durableName).catch(async () => {
489
+ // SDK-5: Try to get existing consumer; if config changed, delete and recreate
490
+ let consumer;
491
+ try {
492
+ consumer = await js.consumers.get(streamName, durableName);
493
+ } catch {
494
+ // Consumer doesn't exist yet, create it
472
495
  logger.info(`[pier-connector] Creating new Marketplace Consumer: ${durableName}`);
473
- await jsm.consumers.add(streamName, {
474
- durable_name: durableName,
475
- filter_subject: publicSubject,
476
- deliver_policy: DeliverPolicy.All,
477
- ack_policy: AckPolicy.Explicit,
478
- deliver_group: config.queueGroup
479
- });
480
- return await js.consumers.get(streamName, durableName);
481
- });
496
+ try {
497
+ await jsm.consumers.add(streamName, {
498
+ durable_name: durableName,
499
+ filter_subject: publicSubject,
500
+ deliver_policy: DeliverPolicy.New, // SDK-3: Only new messages
501
+ ack_policy: AckPolicy.Explicit,
502
+ deliver_group: config.queueGroup
503
+ });
504
+ } catch (addErr) {
505
+ // SDK-5: Config mismatch — delete old and recreate
506
+ logger.warn(`[pier-connector] Consumer config conflict, recreating: ${addErr.message}`);
507
+ try { await jsm.consumers.delete(streamName, durableName); } catch { /* ignore */ }
508
+ await jsm.consumers.add(streamName, {
509
+ durable_name: durableName,
510
+ filter_subject: publicSubject,
511
+ deliver_policy: DeliverPolicy.New,
512
+ ack_policy: AckPolicy.Explicit,
513
+ deliver_group: config.queueGroup
514
+ });
515
+ }
516
+ consumer = await js.consumers.get(streamName, durableName);
517
+ }
482
518
 
483
519
  const iter = await consumer.consume();
484
520
  for await (const msg of iter) {
@@ -502,16 +538,31 @@ export default function register(api) {
502
538
 
503
539
  (async () => {
504
540
  try {
505
- let consumer = await js.consumers.get(streamName, durableName).catch(async () => {
541
+ // SDK-5: Try to get existing consumer; if config changed, delete and recreate
542
+ let consumer;
543
+ try {
544
+ consumer = await js.consumers.get(streamName, durableName);
545
+ } catch {
506
546
  logger.info(`[pier-connector] Creating new Direct Consumer: ${durableName}`);
507
- await jsm.consumers.add(streamName, {
508
- durable_name: durableName,
509
- filter_subject: privateSubject,
510
- deliver_policy: DeliverPolicy.All,
511
- ack_policy: AckPolicy.Explicit
512
- });
513
- return await js.consumers.get(streamName, durableName);
514
- });
547
+ try {
548
+ await jsm.consumers.add(streamName, {
549
+ durable_name: durableName,
550
+ filter_subject: privateSubject,
551
+ deliver_policy: DeliverPolicy.New, // SDK-4: Only new messages
552
+ ack_policy: AckPolicy.Explicit
553
+ });
554
+ } catch (addErr) {
555
+ logger.warn(`[pier-connector] Consumer config conflict, recreating: ${addErr.message}`);
556
+ try { await jsm.consumers.delete(streamName, durableName); } catch { /* ignore */ }
557
+ await jsm.consumers.add(streamName, {
558
+ durable_name: durableName,
559
+ filter_subject: privateSubject,
560
+ deliver_policy: DeliverPolicy.New,
561
+ ack_policy: AckPolicy.Explicit
562
+ });
563
+ }
564
+ consumer = await js.consumers.get(streamName, durableName);
565
+ }
515
566
 
516
567
  const iter = await consumer.consume();
517
568
  for await (const msg of iter) {
@@ -546,9 +597,9 @@ export default function register(api) {
546
597
  const cInfo = await jsm.consumers.add(streamName, {
547
598
  durable_name: durableName,
548
599
  filter_subject: msgSubject,
549
- deliver_policy: DeliverPolicy.All,
600
+ deliver_policy: DeliverPolicy.New, // Only new messages to avoid replay spam
550
601
  ack_policy: AckPolicy.Explicit,
551
- ack_wait: 30000, // 30s ack wait
602
+ ack_wait: 30000,
552
603
  });
553
604
  const consumer = await js.consumers.get(streamName, cInfo.name);
554
605
 
@@ -556,31 +607,7 @@ export default function register(api) {
556
607
  jobSubscriptions.set(jobId, iter);
557
608
 
558
609
  (async () => {
559
- let historyBuffer = [];
560
- let isBursting = true;
561
-
562
- // Small delay to collect initial "DeliverPolicy.All" burst
563
- const burstTimeout = setTimeout(() => {
564
- if (isBursting && historyBuffer.length > 0) {
565
- isBursting = false;
566
- logger.info(`[pier-connector] 📦 Batch processing ${historyBuffer.length} historical messages for ${jobId}`);
567
-
568
- const combinedHistory = historyBuffer.map(m => `User: ${m.content}`).join('\n');
569
- const lastSenderId = historyBuffer[historyBuffer.length - 1].sender_id;
570
-
571
- receiveIncoming({
572
- accountId: config.accountId || 'default',
573
- senderId: `pier:${lastSenderId}`,
574
- text: `[Conversation History]\n${combinedHistory}\n\nPlease continue based on the above history.`,
575
- }, jobId).catch(err => {
576
- logger.error(`[pier-connector] Failed to process batch history: ${err.message}`);
577
- });
578
-
579
- historyBuffer = [];
580
- } else {
581
- isBursting = false;
582
- }
583
- }, 1000); // 1s burst window
610
+ logger.info(`[pier-connector] 👂 Monitoring new messages for job ${jobId}`);
584
611
 
585
612
  for await (const msg of iter) {
586
613
  try {
@@ -613,21 +640,15 @@ export default function register(api) {
613
640
  msg_id: msgPayload.id,
614
641
  reader_id: config.nodeId
615
642
  })));
616
- logger.info(`[pier-connector] 👁️ Sent read receipt for message ${msgPayload.id}`);
643
+ logger.debug(`[pier-connector] 👁️ Sent read receipt for message ${msgPayload.id}`);
617
644
  } catch (err) {
618
645
  logger.error(`[pier-connector] Failed to send read receipt: ${err.message}`);
619
646
  }
620
647
  }
621
648
 
622
649
  const content = msgPayload.content;
623
-
624
- if (isBursting) {
625
- historyBuffer.push(msgPayload);
626
- msg.ack();
627
- continue;
628
- }
629
-
630
- logger.info(`[pier-connector] 💬 Message for job ${jobId}: "${truncate(content, 40)}"`);
650
+ logger.info(`[pier-connector] 📥 Incoming message for ${jobId}: "${truncate(content, 40)}"`);
651
+
631
652
  const senderCore = msgPayload.sender_id;
632
653
 
633
654
  activeNodeJobs.set(jobId, {
@@ -635,7 +656,7 @@ export default function register(api) {
635
656
  isRealtimeMsg: true
636
657
  });
637
658
 
638
- // Wait for agent to finish before acking - this provides backpressure
659
+ // Trigger agent
639
660
  await receiveIncoming({
640
661
  accountId: config.accountId || 'default',
641
662
  senderId: `pier:${senderCore}`,
@@ -908,29 +929,21 @@ export default function register(api) {
908
929
  }
909
930
 
910
931
  try {
932
+
933
+ const config = getActiveConfig();
934
+ const subject = `jobs.job.${params.jobId}.msg`;
911
935
  const payload = {
912
- sender_id: getActiveConfig().nodeId,
936
+ id: ethers.hexlify(ethers.randomBytes(16)),
937
+ job_id: params.jobId,
938
+ sender_id: config.nodeId,
939
+ sender_type: 'node',
913
940
  content: params.text,
914
- created_at: new Date().toISOString()
941
+ timestamp: new Date().toISOString(),
942
+ auth_token: config.secretKey // Secure token for backend validation
915
943
  };
916
944
 
917
- const subject = `jobs.job.${params.jobId}.msg`;
918
945
  await js.publish(subject, new TextEncoder().encode(JSON.stringify(payload)));
919
946
 
920
- // Also SAVE to database for persistence
921
- const config = getActiveConfig();
922
- await fetch(`${config.pierApiUrl}/jobs/${params.jobId}/messages`, {
923
- method: 'POST',
924
- headers: {
925
- 'Content-Type': 'application/json',
926
- 'X-API-Key': config.secretKey // Assuming secretKey can be used as API key for node-specific actions
927
- },
928
- body: JSON.stringify({
929
- sender_id: config.nodeId,
930
- content: params.text
931
- })
932
- });
933
-
934
947
  return { content: [{ type: 'text', text: 'Message sent' }] };
935
948
  } catch (err) {
936
949
  return { content: [{ type: 'text', text: `Error: ${err.message}` }] };