@gholl-studio/pier-connector 0.2.16 → 0.2.18

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 +53 -51
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.16",
4
+ "version": "0.2.18",
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
@@ -188,13 +188,25 @@ export default function register(api) {
188
188
  }
189
189
  });
190
190
 
191
+ const dynamicSessionKey = `pier-job-${jobId}`;
192
+ const injectedPrompt = [
193
+ "=========================================",
194
+ "CRITICAL DIRECTIVE - PIER TRADING NETWORK",
195
+ "=========================================",
196
+ "You are an autonomous AI worker on Pier.",
197
+ "1. You MUST USE the `pier_finish_task` tool to submit the final result when your work is complete.",
198
+ "2. If you need to negotiate or clarify requirements with the employer, use `pier_chat`.",
199
+ "3. Do not just wait in silence. Always officially finish the task if the employer's goal is met.",
200
+ inbound.systemPrompt ? `\nEmployer Guidelines:\n${inbound.systemPrompt}` : ""
201
+ ].join('\n');
202
+
191
203
  const ctxPayload = api.runtime.channel.reply.finalizeInboundContext({
192
204
  Body: inbound.text,
193
205
  BodyForAgent: inbound.text,
194
206
  RawBody: inbound.text,
195
207
  From: inbound.senderId,
196
208
  To: `pier:${jobId}`,
197
- SessionKey: route.sessionKey,
209
+ SessionKey: dynamicSessionKey,
198
210
  AccountId: route.accountId,
199
211
  ChatType: 'direct',
200
212
  SenderId: inbound.senderId,
@@ -204,7 +216,7 @@ export default function register(api) {
204
216
  OriginatingTo: `pier:${jobId}`,
205
217
  WasMentioned: true,
206
218
  CommandAuthorized: true,
207
- SystemPrompt: inbound.systemPrompt || undefined,
219
+ SystemPrompt: injectedPrompt,
208
220
  MessageId: inbound.messageId || jobId
209
221
  });
210
222
 
@@ -215,7 +227,7 @@ export default function register(api) {
215
227
  cfg: api.config,
216
228
  agentId: route.agentId,
217
229
  onIdle: () => {
218
- logger.debug(`[pier-connector] Dispatcher idle for session ${route.sessionKey}`);
230
+ logger.debug(`[pier-connector] Dispatcher idle for session ${dynamicSessionKey}`);
219
231
  },
220
232
  deliver: async (payload) => {
221
233
  const metadata = activeNodeJobs.get(jobId);
@@ -239,10 +251,10 @@ export default function register(api) {
239
251
  // Record session meta
240
252
  if (api.runtime.channel.session?.recordSessionMetaFromInbound) {
241
253
  try {
242
- const storePath = api.runtime.channel.session.resolveStorePath(api.config, route.sessionKey);
254
+ const storePath = api.runtime.channel.session.resolveStorePath(api.config, dynamicSessionKey);
243
255
  await api.runtime.channel.session.recordSessionMetaFromInbound({
244
256
  storePath,
245
- sessionKey: route.sessionKey,
257
+ sessionKey: dynamicSessionKey,
246
258
  ctx: ctxPayload
247
259
  });
248
260
  } catch (err) {
@@ -251,14 +263,14 @@ export default function register(api) {
251
263
  }
252
264
 
253
265
  try {
254
- logger.info(`[pier-connector] 🧠 Triggering agent for session ${route.sessionKey}...`);
266
+ logger.info(`[pier-connector] 🧠 Triggering agent for session ${dynamicSessionKey}...`);
255
267
  // Dispatch reply — this will trigger outbound: sendText in pierChannel
256
268
  await api.runtime.channel.reply.dispatchReplyFromConfig({
257
269
  ctx: ctxPayload,
258
270
  cfg: api.config,
259
271
  dispatcher
260
272
  });
261
- logger.info(`[pier-connector] 🧠 Agent dispatch completed for ${route.sessionKey}`);
273
+ logger.info(`[pier-connector] 🧠 Agent dispatch completed for ${dynamicSessionKey}`);
262
274
  } finally {
263
275
  markDispatchIdle();
264
276
  }
@@ -339,42 +351,26 @@ export default function register(api) {
339
351
  const msg = metadata?.pierNatsMsg;
340
352
  const isRealtimeMsg = metadata?.isRealtimeMsg;
341
353
 
342
- if (msg || isRealtimeMsg) {
343
- const elapsed = metadata?.pierStartTime
344
- ? (performance.now() - metadata.pierStartTime).toFixed(1)
345
- : null;
346
-
347
- const responsePayload = createResultPayload({
348
- id: jobId,
349
- reply: text,
350
- latencyMs: elapsed ? Number(elapsed) : undefined,
351
- workerId: getActiveConfig().nodeId,
352
- walletAddress: getActiveConfig().walletAddress,
353
- });
354
-
355
- if (isRealtimeMsg && js) {
356
- try {
357
- const config = getActiveConfig();
358
- const replySubject = `jobs.job.${jobId}.msg`;
359
- const replyPayload = {
360
- id: ethers.hexlify(ethers.randomBytes(16)),
361
- job_id: jobId,
362
- sender_id: config.nodeId,
363
- sender_type: 'node',
364
- content: text,
365
- timestamp: new Date().toISOString(),
366
- auth_token: config.secretKey // Secure token for backend validation
367
- };
368
-
369
- await js.publish(replySubject, new TextEncoder().encode(JSON.stringify(replyPayload)));
370
- logger.info(`[pier-connector] 💬 Agent reply published directly to NATS for job ${jobId}`);
371
- } catch (err) {
372
- logger.error(`[pier-connector] Failed to publish realtime reply to NATS: ${err.message}`);
373
- }
374
- } else if (js) {
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)}"`);
354
+ if (jobId && js) { // Ensure we have a jobId and NATS JetStream is available
355
+ try {
356
+ const config = getActiveConfig();
357
+ const replySubject = `jobs.job.${jobId}.msg`;
358
+
359
+ // Treat ALL agent text outputs as Chat Messages so the Employer sees them in real-time
360
+ const chatPayload = {
361
+ id: crypto.randomUUID ? crypto.randomUUID() : (Math.random().toString(36).substring(2)),
362
+ job_id: jobId,
363
+ sender_id: config.nodeId || 'anonymous',
364
+ sender_type: 'node',
365
+ content: text,
366
+ timestamp: new Date().toISOString(),
367
+ auth_token: config.secretKey // Secure token for backend validation
368
+ };
369
+
370
+ await js.publish(replySubject, new TextEncoder().encode(JSON.stringify(chatPayload)));
371
+ logger.info(`[pier-connector] 💬 Agent reply published directly to NATS chat for job ${jobId}`);
372
+ } catch (err) {
373
+ logger.error(`[pier-connector] Failed to publish realtime reply to NATS: ${err.message}`);
378
374
  }
379
375
 
380
376
  // Delayed stop for job-specific message listener
@@ -546,9 +542,11 @@ export default function register(api) {
546
542
  logger.warn(`[pier-connector] ⚠️ Failed to restore active jobs: ${err.message}`);
547
543
  }
548
544
 
549
- // Public pool with load balancing via JetStream Durable Consumer
545
+ // Public pool with broadcast via JetStream Durable Consumer
550
546
  if (publicSubject) {
551
- const durableName = `pier_marketplace_${config.queueGroup || 'default'}`;
547
+ // For Bidding, every node needs to see the marketplace requests to evaluate them,
548
+ // so we do not use the shared queueGroup. We use a unique durable per node.
549
+ const durableName = `pier_market_${config.nodeId.replace(/-/g, '_')}`;
552
550
  const streamName = 'PIER_JOBS';
553
551
  logger.info(`[pier-connector] 👂 Listening to Marketplace (JetStream): ${publicSubject} (Durable: ${durableName})`);
554
552
 
@@ -567,7 +565,7 @@ export default function register(api) {
567
565
  filter_subject: publicSubject,
568
566
  deliver_policy: DeliverPolicy.New, // SDK-3: Only new messages
569
567
  ack_policy: AckPolicy.Explicit,
570
- deliver_group: config.queueGroup
568
+ // Removed deliver_group to ensure it's a broadcast to all independent nodes
571
569
  });
572
570
  } catch (addErr) {
573
571
  // SDK-5: Config mismatch — delete old and recreate
@@ -578,7 +576,6 @@ export default function register(api) {
578
576
  filter_subject: publicSubject,
579
577
  deliver_policy: DeliverPolicy.New,
580
578
  ack_policy: AckPolicy.Explicit,
581
- deliver_group: config.queueGroup
582
579
  });
583
580
  }
584
581
  consumer = await js.consumers.get(streamName, durableName);
@@ -809,6 +806,7 @@ export default function register(api) {
809
806
 
810
807
  // Anti-Interference: If busy, ignore new marketplace jobs (BUG-34)
811
808
  if (isBusy) {
809
+ logger.debug(`[pier-connector] 🚫 Busy. Ignoring new job payload: ${rawData.substring(0, 50)}...`);
812
810
  // NAK so others in the queue group can take it
813
811
  msg.nak();
814
812
  return;
@@ -821,10 +819,12 @@ export default function register(api) {
821
819
  return;
822
820
  }
823
821
 
824
- // For Marketplace jobs (those without assigned_node_id, or even those with it),
825
- // we must ATOMICALLY CLAIM it from the backend before setting isBusy.
822
+ // For Marketplace jobs: if assigned_node_id IS NOT present, it's open!
823
+ // Do NOT claim it, let the agent evaluate and use pier_bid_task instead.
826
824
  const jobIdToClaim = payload.id;
827
- if (jobIdToClaim) {
825
+ const isTargeted = !!(payload.assigned_node_id || payload.targetNodeId || payload.TargetNodeID);
826
+
827
+ if (jobIdToClaim && isTargeted) {
828
828
  const claimResult = await claimJob(jobIdToClaim);
829
829
  if (!claimResult.ok) {
830
830
  if (claimResult.alreadyClaimed) {
@@ -838,7 +838,8 @@ export default function register(api) {
838
838
  }
839
839
  }
840
840
 
841
- // Claimed successfully! Set busy state
841
+ // For an open bidding job, we don't lock the DB but we do set the node `isBusy` temporarily
842
+ // while it thinks whether to bid or not.
842
843
  isBusy = true;
843
844
  msg.ack();
844
845
 
@@ -1142,6 +1143,7 @@ export default function register(api) {
1142
1143
  };
1143
1144
 
1144
1145
  // For Node (Worker)
1146
+ registerSystemActionTool('pier_bid_task', 'Bid on an open marketplace task to offer your services to the employer', 'task_bid', { message: { type: 'string', description: 'Your pitch explaining why you are a good fit for this task' } });
1145
1147
  registerSystemActionTool('pier_accept_task', 'Accept an offered task from the employer in the current chat', 'task_accept', {});
1146
1148
  registerSystemActionTool('pier_finish_task', 'Submit the final result for a task', 'task_submit', { result: { type: 'string', description: 'The final result or file links' } });
1147
1149