@gholl-studio/pier-connector 0.2.13 → 0.2.17
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 +108 -25
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.17",
|
|
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:
|
|
209
|
+
SessionKey: dynamicSessionKey,
|
|
198
210
|
AccountId: route.accountId,
|
|
199
211
|
ChatType: 'direct',
|
|
200
212
|
SenderId: inbound.senderId,
|
|
@@ -204,24 +216,34 @@ export default function register(api) {
|
|
|
204
216
|
OriginatingTo: `pier:${jobId}`,
|
|
205
217
|
WasMentioned: true,
|
|
206
218
|
CommandAuthorized: true,
|
|
207
|
-
SystemPrompt:
|
|
219
|
+
SystemPrompt: injectedPrompt,
|
|
208
220
|
MessageId: inbound.messageId || jobId
|
|
209
221
|
});
|
|
210
222
|
|
|
223
|
+
let collectedResult = '';
|
|
224
|
+
|
|
211
225
|
// Create a dispatcher to handle the reply lifecycle (fixes sendFinalReply error)
|
|
212
226
|
const { dispatcher, markDispatchIdle } = api.runtime.channel.reply.createReplyDispatcherWithTyping({
|
|
213
227
|
cfg: api.config,
|
|
214
228
|
agentId: route.agentId,
|
|
215
229
|
onIdle: () => {
|
|
216
|
-
logger.debug(`[pier-connector] Dispatcher idle for session ${
|
|
230
|
+
logger.debug(`[pier-connector] Dispatcher idle for session ${dynamicSessionKey}`);
|
|
217
231
|
},
|
|
218
232
|
deliver: async (payload) => {
|
|
219
|
-
|
|
220
|
-
|
|
233
|
+
const metadata = activeNodeJobs.get(jobId);
|
|
234
|
+
const isRealtime = metadata?.isRealtimeMsg;
|
|
235
|
+
|
|
236
|
+
if (!isRealtime) {
|
|
237
|
+
collectedResult += payload.text;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Route Agent's reply to outbound.sendText
|
|
241
|
+
// For marketplace jobs, sendText will now just log (content is buffered in collectedResult)
|
|
242
|
+
// For realtime chat, it will publish to the chat subject immediately.
|
|
221
243
|
await pierChannel.outbound.sendText({
|
|
222
244
|
text: payload.text,
|
|
223
245
|
to: `pier:${jobId}`,
|
|
224
|
-
metadata
|
|
246
|
+
metadata,
|
|
225
247
|
});
|
|
226
248
|
}
|
|
227
249
|
});
|
|
@@ -229,10 +251,10 @@ export default function register(api) {
|
|
|
229
251
|
// Record session meta
|
|
230
252
|
if (api.runtime.channel.session?.recordSessionMetaFromInbound) {
|
|
231
253
|
try {
|
|
232
|
-
const storePath = api.runtime.channel.session.resolveStorePath(api.config,
|
|
254
|
+
const storePath = api.runtime.channel.session.resolveStorePath(api.config, dynamicSessionKey);
|
|
233
255
|
await api.runtime.channel.session.recordSessionMetaFromInbound({
|
|
234
256
|
storePath,
|
|
235
|
-
sessionKey:
|
|
257
|
+
sessionKey: dynamicSessionKey,
|
|
236
258
|
ctx: ctxPayload
|
|
237
259
|
});
|
|
238
260
|
} catch (err) {
|
|
@@ -241,14 +263,14 @@ export default function register(api) {
|
|
|
241
263
|
}
|
|
242
264
|
|
|
243
265
|
try {
|
|
244
|
-
logger.info(`[pier-connector] 🧠 Triggering agent for session ${
|
|
266
|
+
logger.info(`[pier-connector] 🧠 Triggering agent for session ${dynamicSessionKey}...`);
|
|
245
267
|
// Dispatch reply — this will trigger outbound: sendText in pierChannel
|
|
246
268
|
await api.runtime.channel.reply.dispatchReplyFromConfig({
|
|
247
269
|
ctx: ctxPayload,
|
|
248
270
|
cfg: api.config,
|
|
249
271
|
dispatcher
|
|
250
272
|
});
|
|
251
|
-
logger.info(`[pier-connector] 🧠 Agent dispatch completed for ${
|
|
273
|
+
logger.info(`[pier-connector] 🧠 Agent dispatch completed for ${dynamicSessionKey}`);
|
|
252
274
|
} finally {
|
|
253
275
|
markDispatchIdle();
|
|
254
276
|
}
|
|
@@ -362,17 +384,9 @@ export default function register(api) {
|
|
|
362
384
|
logger.error(`[pier-connector] Failed to publish realtime reply to NATS: ${err.message}`);
|
|
363
385
|
}
|
|
364
386
|
} else if (js) {
|
|
365
|
-
//
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
jobsCompleted++;
|
|
369
|
-
logger.info(
|
|
370
|
-
`[pier-connector] ✔ Job ${jobId} result published to jobs.submit` +
|
|
371
|
-
(elapsed ? ` — latency: ${elapsed}ms` : ''),
|
|
372
|
-
);
|
|
373
|
-
} catch (err) {
|
|
374
|
-
logger.error(`[pier-connector] ❌ Failed to publish result for job ${jobId}: ${err.message}`);
|
|
375
|
-
}
|
|
387
|
+
// BUG-44: Marketplace results are now buffered in receiveIncoming and submitted ONCE at the end.
|
|
388
|
+
// sendText only logs the progress here.
|
|
389
|
+
logger.debug(`[pier-connector] ⏳ Marketplace response buffered: "${truncate(text, 40)}"`);
|
|
376
390
|
}
|
|
377
391
|
|
|
378
392
|
// Delayed stop for job-specific message listener
|
|
@@ -544,9 +558,11 @@ export default function register(api) {
|
|
|
544
558
|
logger.warn(`[pier-connector] ⚠️ Failed to restore active jobs: ${err.message}`);
|
|
545
559
|
}
|
|
546
560
|
|
|
547
|
-
// Public pool with
|
|
561
|
+
// Public pool with broadcast via JetStream Durable Consumer
|
|
548
562
|
if (publicSubject) {
|
|
549
|
-
|
|
563
|
+
// For Bidding, every node needs to see the marketplace requests to evaluate them,
|
|
564
|
+
// so we do not use the shared queueGroup. We use a unique durable per node.
|
|
565
|
+
const durableName = `pier_market_${config.nodeId.replace(/-/g, '_')}`;
|
|
550
566
|
const streamName = 'PIER_JOBS';
|
|
551
567
|
logger.info(`[pier-connector] 👂 Listening to Marketplace (JetStream): ${publicSubject} (Durable: ${durableName})`);
|
|
552
568
|
|
|
@@ -565,7 +581,7 @@ export default function register(api) {
|
|
|
565
581
|
filter_subject: publicSubject,
|
|
566
582
|
deliver_policy: DeliverPolicy.New, // SDK-3: Only new messages
|
|
567
583
|
ack_policy: AckPolicy.Explicit,
|
|
568
|
-
deliver_group
|
|
584
|
+
// Removed deliver_group to ensure it's a broadcast to all independent nodes
|
|
569
585
|
});
|
|
570
586
|
} catch (addErr) {
|
|
571
587
|
// SDK-5: Config mismatch — delete old and recreate
|
|
@@ -576,7 +592,6 @@ export default function register(api) {
|
|
|
576
592
|
filter_subject: publicSubject,
|
|
577
593
|
deliver_policy: DeliverPolicy.New,
|
|
578
594
|
ack_policy: AckPolicy.Explicit,
|
|
579
|
-
deliver_group: config.queueGroup
|
|
580
595
|
});
|
|
581
596
|
}
|
|
582
597
|
consumer = await js.consumers.get(streamName, durableName);
|
|
@@ -807,6 +822,7 @@ export default function register(api) {
|
|
|
807
822
|
|
|
808
823
|
// Anti-Interference: If busy, ignore new marketplace jobs (BUG-34)
|
|
809
824
|
if (isBusy) {
|
|
825
|
+
logger.debug(`[pier-connector] 🚫 Busy. Ignoring new job payload: ${rawData.substring(0, 50)}...`);
|
|
810
826
|
// NAK so others in the queue group can take it
|
|
811
827
|
msg.nak();
|
|
812
828
|
return;
|
|
@@ -1088,6 +1104,73 @@ export default function register(api) {
|
|
|
1088
1104
|
{ optional: true }
|
|
1089
1105
|
);
|
|
1090
1106
|
|
|
1107
|
+
// ── V2 System Action Tools ────────────────────────────────────────
|
|
1108
|
+
|
|
1109
|
+
const registerSystemActionTool = (name, description, action, extraParams, userRole = 'node') => {
|
|
1110
|
+
api.registerTool({
|
|
1111
|
+
name,
|
|
1112
|
+
description,
|
|
1113
|
+
parameters: {
|
|
1114
|
+
type: 'object',
|
|
1115
|
+
properties: {
|
|
1116
|
+
jobId: { type: 'string', description: 'The ID of the job/chat session' },
|
|
1117
|
+
...extraParams
|
|
1118
|
+
},
|
|
1119
|
+
required: ['jobId', ...Object.keys(extraParams)]
|
|
1120
|
+
},
|
|
1121
|
+
async execute(_id, params) {
|
|
1122
|
+
if (!nc || connectionStatus !== 'connected') {
|
|
1123
|
+
return { content: [{ type: 'text', text: 'Error: NATS not connected' }] };
|
|
1124
|
+
}
|
|
1125
|
+
try {
|
|
1126
|
+
const config = getActiveConfig();
|
|
1127
|
+
const subject = `jobs.job.${params.jobId}.msg`;
|
|
1128
|
+
|
|
1129
|
+
const { jobId, ...payload } = params;
|
|
1130
|
+
|
|
1131
|
+
// If acting as User (Employer), we need the user API key ideally, but for demo we just pass node ID or secret.
|
|
1132
|
+
// The Backend handles sender_type="node" vs "user".
|
|
1133
|
+
const msgData = {
|
|
1134
|
+
id: ethers.hexlify(ethers.randomBytes(16)),
|
|
1135
|
+
job_id: params.jobId,
|
|
1136
|
+
sender_id: userRole === 'user' ? 'user_' + config.nodeId : config.nodeId,
|
|
1137
|
+
sender_type: userRole,
|
|
1138
|
+
content: JSON.stringify({
|
|
1139
|
+
type: 'system_action',
|
|
1140
|
+
action,
|
|
1141
|
+
payload
|
|
1142
|
+
}),
|
|
1143
|
+
timestamp: new Date().toISOString(),
|
|
1144
|
+
auth_token: config.secretKey, // auth handle in DB needs updating for user role simulation
|
|
1145
|
+
type: 'system_action',
|
|
1146
|
+
action: action
|
|
1147
|
+
};
|
|
1148
|
+
|
|
1149
|
+
await js.publish(subject, new TextEncoder().encode(JSON.stringify(msgData)));
|
|
1150
|
+
return { content: [{ type: 'text', text: `${action} executed successfully` }] };
|
|
1151
|
+
} catch (err) {
|
|
1152
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }] };
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}, { optional: true });
|
|
1156
|
+
};
|
|
1157
|
+
|
|
1158
|
+
// For Node (Worker)
|
|
1159
|
+
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' } });
|
|
1160
|
+
registerSystemActionTool('pier_accept_task', 'Accept an offered task from the employer in the current chat', 'task_accept', {});
|
|
1161
|
+
registerSystemActionTool('pier_finish_task', 'Submit the final result for a task', 'task_submit', { result: { type: 'string', description: 'The final result or file links' } });
|
|
1162
|
+
|
|
1163
|
+
// For User (Employer Robot) - Spoofed identity using Node's config for A2A testing
|
|
1164
|
+
registerSystemActionTool('pier_propose_task', 'Offer a task to a node with a specific price', 'task_offer', {
|
|
1165
|
+
price: { type: 'number', description: 'The price in PIER tokens to offer' },
|
|
1166
|
+
description: { type: 'string', description: 'The formal task description' }
|
|
1167
|
+
}, 'user');
|
|
1168
|
+
|
|
1169
|
+
registerSystemActionTool('pier_rate_task', 'Rate the node after completion', 'task_rate', {
|
|
1170
|
+
score: { type: 'number', description: 'Rating from 1 to 5' },
|
|
1171
|
+
comment: { type: 'string', description: 'A short review' }
|
|
1172
|
+
}, 'user');
|
|
1173
|
+
|
|
1091
1174
|
// ── 4. Register /pier status command ───────────────────────────────
|
|
1092
1175
|
|
|
1093
1176
|
api.registerCommand({
|