@gholl-studio/pier-connector 0.1.2 → 0.1.4
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/package.json +1 -1
- package/src/index.js +145 -60
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.
|
|
4
|
+
"version": "0.1.4",
|
|
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
|
@@ -127,6 +127,65 @@ export default function register(api) {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Routes an incoming message from Pier to the OpenClaw agent.
|
|
132
|
+
* Replaces the non-existent api.runtime.sendIncoming.
|
|
133
|
+
*/
|
|
134
|
+
async function receiveIncoming(inbound, jobId) {
|
|
135
|
+
if (!api.runtime?.channel?.reply) {
|
|
136
|
+
logger.error(`[pier-connector] SDK Error: api.runtime.channel.reply is not available.`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const route = api.runtime.channel.routing.resolveAgentRoute({
|
|
141
|
+
cfg: api.config,
|
|
142
|
+
channel: 'pier',
|
|
143
|
+
accountId: inbound.accountId,
|
|
144
|
+
peer: {
|
|
145
|
+
kind: 'direct',
|
|
146
|
+
id: jobId
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const ctxPayload = api.runtime.channel.reply.finalizeInboundContext({
|
|
151
|
+
Body: inbound.text,
|
|
152
|
+
BodyForAgent: inbound.text,
|
|
153
|
+
RawBody: inbound.text,
|
|
154
|
+
From: inbound.senderId,
|
|
155
|
+
To: `pier:${jobId}`,
|
|
156
|
+
SessionKey: route.sessionKey,
|
|
157
|
+
AccountId: route.accountId,
|
|
158
|
+
ChatType: 'direct',
|
|
159
|
+
SenderId: inbound.senderId,
|
|
160
|
+
Provider: 'pier',
|
|
161
|
+
Surface: 'pier',
|
|
162
|
+
OriginatingChannel: 'pier',
|
|
163
|
+
OriginatingTo: `pier:${jobId}`,
|
|
164
|
+
WasMentioned: true,
|
|
165
|
+
CommandAuthorized: true
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Record session meta
|
|
169
|
+
if (api.runtime.channel.session?.recordSessionMetaFromInbound) {
|
|
170
|
+
try {
|
|
171
|
+
const storePath = api.runtime.channel.session.resolveStorePath(api.config, route.sessionKey);
|
|
172
|
+
await api.runtime.channel.session.recordSessionMetaFromInbound({
|
|
173
|
+
storePath,
|
|
174
|
+
sessionKey: route.sessionKey,
|
|
175
|
+
ctx: ctxPayload
|
|
176
|
+
});
|
|
177
|
+
} catch (err) {
|
|
178
|
+
logger.debug(`[pier-connector] Failed to record session meta: ${err.message}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Dispatch reply — this will trigger outbound: sendText in pierChannel
|
|
183
|
+
await api.runtime.channel.reply.dispatchReplyFromConfig({
|
|
184
|
+
ctx: ctxPayload,
|
|
185
|
+
cfg: api.config,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
130
189
|
// ── 1. Register messaging channel ──────────────────────────────────
|
|
131
190
|
|
|
132
191
|
const pierChannel = {
|
|
@@ -401,8 +460,9 @@ export default function register(api) {
|
|
|
401
460
|
|
|
402
461
|
const iter = await consumer.consume();
|
|
403
462
|
for await (const msg of iter) {
|
|
404
|
-
|
|
405
|
-
|
|
463
|
+
handleMessage(msg).catch(err => {
|
|
464
|
+
logger.error(`[pier-connector] Fatal handleMessage error: ${err.message}`);
|
|
465
|
+
});
|
|
406
466
|
}
|
|
407
467
|
} catch (err) {
|
|
408
468
|
logger.error(`[pier-connector] Marketplace JetStream error: ${err.message}`);
|
|
@@ -433,8 +493,9 @@ export default function register(api) {
|
|
|
433
493
|
|
|
434
494
|
const iter = await consumer.consume();
|
|
435
495
|
for await (const msg of iter) {
|
|
436
|
-
|
|
437
|
-
|
|
496
|
+
handleMessage(msg).catch(err => {
|
|
497
|
+
logger.error(`[pier-connector] Fatal handleMessage error: ${err.message}`);
|
|
498
|
+
});
|
|
438
499
|
}
|
|
439
500
|
} catch (err) {
|
|
440
501
|
logger.error(`[pier-connector] Direct JetStream error: ${err.message}`);
|
|
@@ -467,53 +528,73 @@ export default function register(api) {
|
|
|
467
528
|
const iter = await consumer.consume();
|
|
468
529
|
jobSubscriptions.set(jobId, iter);
|
|
469
530
|
|
|
470
|
-
|
|
471
|
-
msg
|
|
472
|
-
const rawMsg = new TextDecoder().decode(msg.data);
|
|
473
|
-
let msgPayload;
|
|
474
|
-
try {
|
|
475
|
-
msgPayload = JSON.parse(rawMsg);
|
|
476
|
-
} catch (e) { continue; }
|
|
477
|
-
|
|
478
|
-
// Ignore my own messages
|
|
479
|
-
if (msgPayload.sender_id === config.nodeId) continue;
|
|
480
|
-
|
|
481
|
-
if (msgPayload.type === 'receipt') continue;
|
|
482
|
-
|
|
483
|
-
// Send read receipt back
|
|
484
|
-
if (js && msgPayload.id) {
|
|
531
|
+
(async () => {
|
|
532
|
+
for await (const msg of iter) {
|
|
485
533
|
try {
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
534
|
+
const rawMsg = new TextDecoder().decode(msg.data);
|
|
535
|
+
let msgPayload;
|
|
536
|
+
try {
|
|
537
|
+
msgPayload = JSON.parse(rawMsg);
|
|
538
|
+
} catch (e) {
|
|
539
|
+
msg.ack();
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Ignore my own messages
|
|
544
|
+
if (msgPayload.sender_id === config.nodeId) {
|
|
545
|
+
msg.ack();
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (msgPayload.type === 'receipt') {
|
|
550
|
+
msg.ack();
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Send read receipt back
|
|
555
|
+
if (js && msgPayload.id) {
|
|
556
|
+
try {
|
|
557
|
+
const replySubject = `jobs.job.${jobId}.msg`;
|
|
558
|
+
await js.publish(replySubject, new TextEncoder().encode(JSON.stringify({
|
|
559
|
+
type: 'receipt',
|
|
560
|
+
msg_id: msgPayload.id,
|
|
561
|
+
reader_id: config.nodeId
|
|
562
|
+
})));
|
|
563
|
+
logger.info(`[pier-connector] 👁️ Sent read receipt for message ${msgPayload.id}`);
|
|
564
|
+
} catch (err) {
|
|
565
|
+
logger.error(`[pier-connector] Failed to send read receipt: ${err.message}`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const content = msgPayload.content;
|
|
570
|
+
logger.info(`[pier-connector] 💬 Message for job ${jobId}: "${truncate(content, 40)}"`);
|
|
571
|
+
|
|
572
|
+
const senderCore = msgPayload.sender_id;
|
|
573
|
+
|
|
574
|
+
activeNodeJobs.set(jobId, {
|
|
575
|
+
pierJobId: jobId,
|
|
576
|
+
isRealtimeMsg: true
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// Wait for agent to finish before acking - this provides backpressure
|
|
580
|
+
await receiveIncoming({
|
|
581
|
+
accountId: config.accountId || 'default',
|
|
582
|
+
senderId: `pier:${senderCore}`,
|
|
583
|
+
text: content,
|
|
584
|
+
}, jobId);
|
|
585
|
+
|
|
586
|
+
msg.ack();
|
|
493
587
|
} catch (err) {
|
|
494
|
-
logger.error(`[pier-connector]
|
|
588
|
+
logger.error(`[pier-connector] Chat message processing error for ${jobId}: ${err.message}`);
|
|
589
|
+
msg.nak();
|
|
495
590
|
}
|
|
496
591
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const senderCore = msgPayload.sender_id;
|
|
502
|
-
|
|
503
|
-
activeNodeJobs.set(senderCore, {
|
|
504
|
-
pierJobId: jobId,
|
|
505
|
-
isRealtimeMsg: true
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
await api.runtime.sendIncoming({
|
|
509
|
-
channelId: 'pier',
|
|
510
|
-
accountId: config.accountId || 'default',
|
|
511
|
-
senderId: `pier:${senderCore}`,
|
|
512
|
-
text: content,
|
|
513
|
-
});
|
|
514
|
-
}
|
|
592
|
+
})().catch(err => {
|
|
593
|
+
logger.error(`[pier-connector] Chat iteration died for ${jobId}: ${err.message}`);
|
|
594
|
+
jobSubscriptions.delete(jobId);
|
|
595
|
+
});
|
|
515
596
|
} catch (err) {
|
|
516
|
-
logger.error(`[pier-connector]
|
|
597
|
+
logger.error(`[pier-connector] Failed to setup chat consumer for ${jobId}: ${err.message}`);
|
|
517
598
|
}
|
|
518
599
|
}
|
|
519
600
|
|
|
@@ -527,6 +608,7 @@ export default function register(api) {
|
|
|
527
608
|
payload = JSON.parse(rawData);
|
|
528
609
|
} catch (e) {
|
|
529
610
|
logger.error(`[pier-connector] Failed to parse JSON: ${rawData.substring(0, 100)}`);
|
|
611
|
+
msg.ack();
|
|
530
612
|
return;
|
|
531
613
|
}
|
|
532
614
|
|
|
@@ -535,22 +617,30 @@ export default function register(api) {
|
|
|
535
617
|
const { jobId } = payload;
|
|
536
618
|
if (jobId) {
|
|
537
619
|
logger.info(`[pier-connector] ⏰ Received wakeup signal for job ${jobId}`);
|
|
620
|
+
msg.ack();
|
|
538
621
|
subscribeToJobMessages(jobId);
|
|
622
|
+
} else {
|
|
623
|
+
msg.ack();
|
|
539
624
|
}
|
|
540
625
|
return;
|
|
541
626
|
}
|
|
542
627
|
|
|
543
628
|
// V1.1 FEATURE: Task Poisoning / Poaching Protection
|
|
544
629
|
if (payload.assigned_node_id && payload.assigned_node_id !== config.nodeId) {
|
|
545
|
-
//
|
|
630
|
+
// Not for us, nak it so others can take it
|
|
631
|
+
msg.nak();
|
|
546
632
|
return;
|
|
547
633
|
}
|
|
548
634
|
|
|
635
|
+
// ACK the job early to 'claim' it and prevent redelivery while we process
|
|
636
|
+
msg.ack();
|
|
637
|
+
|
|
549
638
|
jobsReceived++;
|
|
550
639
|
const parsed = parseJob(msg, logger);
|
|
551
640
|
if (!parsed.ok) {
|
|
552
641
|
jobsFailed++;
|
|
553
642
|
logger.error(`[pier-connector] Job parse error: ${parsed.error}`);
|
|
643
|
+
// Since we already acked, we must send an error result back to Pier
|
|
554
644
|
safeRespond(msg, createErrorPayload({
|
|
555
645
|
id: 'unknown',
|
|
556
646
|
errorCode: 'PARSE_ERROR',
|
|
@@ -567,32 +657,27 @@ export default function register(api) {
|
|
|
567
657
|
try {
|
|
568
658
|
const senderCore = job.meta?.sender ?? 'anonymous';
|
|
569
659
|
|
|
570
|
-
activeNodeJobs.set(
|
|
660
|
+
activeNodeJobs.set(job.id, {
|
|
571
661
|
pierJobId: job.id,
|
|
572
662
|
pierNatsMsg: msg,
|
|
573
|
-
pierStartTime:
|
|
663
|
+
pierStartTime: performance.now(),
|
|
574
664
|
pierMeta: job.meta,
|
|
575
665
|
isRealtimeMsg: false
|
|
576
666
|
});
|
|
577
667
|
|
|
578
668
|
const inbound = {
|
|
579
|
-
channelId: 'pier',
|
|
580
669
|
accountId: config.accountId || 'default',
|
|
581
670
|
senderId: `pier:${senderCore}`,
|
|
582
671
|
text: job.task,
|
|
583
|
-
assignedAgentId: config.agentId || undefined,
|
|
584
672
|
};
|
|
585
673
|
|
|
586
|
-
|
|
674
|
+
// SUBSCRIBE to job-specific messages FIRST so we don't miss follow-ups during the agent run
|
|
675
|
+
// This handles the user's "can only receive one message" issue
|
|
676
|
+
subscribeToJobMessages(job.id);
|
|
587
677
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
// SUBSCRIBE to job-specific messages for real-time communication
|
|
592
|
-
subscribeToJobMessages(job.id);
|
|
593
|
-
} else {
|
|
594
|
-
throw new Error('Agent runtime not available');
|
|
595
|
-
}
|
|
678
|
+
// Trigger agent run
|
|
679
|
+
await receiveIncoming(inbound, job.id);
|
|
680
|
+
|
|
596
681
|
} catch (err) {
|
|
597
682
|
jobsFailed++;
|
|
598
683
|
safeRespond(msg, createErrorPayload({
|