@gholl-studio/pier-connector 0.7.2 ā 0.7.3
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/README.md +36 -162
- package/dist/index.js +77 -363
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +94 -402
package/dist/index.js
CHANGED
|
@@ -85,8 +85,6 @@ const pierPlugin = {
|
|
|
85
85
|
idLabel: 'pierJobId',
|
|
86
86
|
normalizeAllowEntry: (entry) => entry.replace(/^(pier|job):/i, ''),
|
|
87
87
|
notifyApproval: async ({ cfg, id, accountId }) => {
|
|
88
|
-
// Signal received when 'openclaw pairing approve pier <id>' is run.
|
|
89
|
-
// Useful if we want to trigger specific onboarding or status updates.
|
|
90
88
|
console.log(`[pier-connector] Pairing approved for Job ${id} on account ${accountId}`);
|
|
91
89
|
}
|
|
92
90
|
},
|
|
@@ -116,7 +114,6 @@ const pierPlugin = {
|
|
|
116
114
|
timestamp: Date.now()
|
|
117
115
|
};
|
|
118
116
|
if (isWorkspace) {
|
|
119
|
-
// Use SDK-aligned workspace protocol (includes signing if configured)
|
|
120
117
|
const ns = ctx.metadata?.namespace || 'default';
|
|
121
118
|
let swarmPayload = {
|
|
122
119
|
...payload,
|
|
@@ -131,7 +128,6 @@ const pierPlugin = {
|
|
|
131
128
|
await robot.client.nats.publishGroupChat(jobId, swarmPayload, ns);
|
|
132
129
|
}
|
|
133
130
|
else {
|
|
134
|
-
// Standard Marketplace Job Chat
|
|
135
131
|
const subject = `chat.${jobId}`;
|
|
136
132
|
await robot.js.publish(subject, new TextEncoder().encode(JSON.stringify(payload)));
|
|
137
133
|
}
|
|
@@ -168,9 +164,7 @@ const pierPlugin = {
|
|
|
168
164
|
gateway: {
|
|
169
165
|
startAccount: async (ctx) => {
|
|
170
166
|
const config = ctx.account;
|
|
171
|
-
// Use ctx as the runtime provider as it contains .channelRuntime, .log, etc.
|
|
172
167
|
const robot = new PierRobot(config, ctx, async (inbound, jobId) => {
|
|
173
|
-
// Pass the context containing channelRuntime
|
|
174
168
|
await handleInbound(ctx, inbound, jobId, robot, pierPlugin);
|
|
175
169
|
});
|
|
176
170
|
instances.set(ctx.accountId, robot);
|
|
@@ -183,8 +177,6 @@ const pierPlugin = {
|
|
|
183
177
|
connected: true,
|
|
184
178
|
lastStartAt: robot.lastStartAt
|
|
185
179
|
});
|
|
186
|
-
// IMPORTANT: Keep startAccount promise active for the lifetime of the connection.
|
|
187
|
-
// If this promise resolves, the Gateway will auto-restart the account.
|
|
188
180
|
await new Promise((resolve) => {
|
|
189
181
|
ctx.abortSignal.addEventListener('abort', () => {
|
|
190
182
|
resolve();
|
|
@@ -222,9 +214,8 @@ const pierPlugin = {
|
|
|
222
214
|
const register = (api) => {
|
|
223
215
|
const logger = api.logger;
|
|
224
216
|
const globalStats = { received: 0, completed: 0, failed: 0 };
|
|
225
|
-
// Register our new ChannelPlugin
|
|
226
217
|
api.registerChannel({ plugin: pierPlugin });
|
|
227
|
-
//
|
|
218
|
+
// --- Market Tools ---
|
|
228
219
|
api.registerTool({
|
|
229
220
|
name: 'pier_publish',
|
|
230
221
|
label: 'Publish to Pier',
|
|
@@ -241,23 +232,17 @@ const register = (api) => {
|
|
|
241
232
|
const accountId = params.accountId || ctx?.metadata?.accountId || ctx?.accountId || 'default';
|
|
242
233
|
const robot = instances.get(accountId) || instances.values().next().value;
|
|
243
234
|
if (!robot || robot.connectionStatus !== 'connected') {
|
|
244
|
-
return {
|
|
245
|
-
content: [{ type: 'text', text: 'Robot not connected' }],
|
|
246
|
-
details: {}
|
|
247
|
-
};
|
|
235
|
+
return { content: [{ type: 'text', text: 'Robot not connected' }], details: {} };
|
|
248
236
|
}
|
|
249
237
|
const taskPayload = createRequestPayload({ task: params.task });
|
|
250
238
|
const reply = await robot.nc.request(robot.config.publishSubject, new TextEncoder().encode(JSON.stringify(taskPayload)));
|
|
251
|
-
return {
|
|
252
|
-
content: [{ type: 'text', text: new TextDecoder().decode(reply.data) }],
|
|
253
|
-
details: {}
|
|
254
|
-
};
|
|
239
|
+
return { content: [{ type: 'text', text: new TextDecoder().decode(reply.data) }], details: {} };
|
|
255
240
|
}
|
|
256
241
|
}, { optional: true });
|
|
257
242
|
api.registerTool({
|
|
258
243
|
name: 'pier_chat',
|
|
259
244
|
label: 'Pier Chat',
|
|
260
|
-
description: 'Send a message
|
|
245
|
+
description: 'Send a message in a specific job chat or workspace group.',
|
|
261
246
|
parameters: {
|
|
262
247
|
type: 'object',
|
|
263
248
|
properties: {
|
|
@@ -274,43 +259,42 @@ const register = (api) => {
|
|
|
274
259
|
return { content: [{ type: 'text', text: 'Error: Robot not connected' }], details: {} };
|
|
275
260
|
}
|
|
276
261
|
try {
|
|
277
|
-
const subject = `chat.${params.jobId}`;
|
|
278
262
|
let metadata = robot.activeNodeJobs.get(params.jobId);
|
|
279
|
-
if (!metadata && ctx.to) {
|
|
280
|
-
const toId = ctx.to.replace(/^pier:/, '');
|
|
281
|
-
metadata = robot.activeNodeJobs.get(toId);
|
|
282
|
-
}
|
|
283
263
|
const jobId = metadata?.pierJobId || params.jobId;
|
|
284
|
-
const isWorkspace = metadata?.workspace === true;
|
|
285
|
-
if (!robot.js)
|
|
264
|
+
const isWorkspace = metadata?.workspace === true || jobId.startsWith('job-');
|
|
265
|
+
if (!robot.js)
|
|
286
266
|
return { content: [{ type: 'text', text: 'Error: JetStream not available' }], details: {} };
|
|
287
|
-
}
|
|
288
|
-
const payload = {
|
|
289
|
-
id: globalThis.crypto.randomUUID ? globalThis.crypto.randomUUID() : (Math.random().toString(36).substring(2)),
|
|
290
|
-
job_id: jobId,
|
|
291
|
-
sender_id: robot.config.nodeId,
|
|
292
|
-
sender_name: accountId,
|
|
293
|
-
sender_type: 'node',
|
|
294
|
-
content: params.text,
|
|
295
|
-
created_at: new Date().toISOString(),
|
|
296
|
-
auth_token: robot.config.secretKey,
|
|
297
|
-
timestamp: Date.now()
|
|
298
|
-
};
|
|
299
267
|
if (isWorkspace) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
version: 'psp-1.0',
|
|
268
|
+
const payload = {
|
|
269
|
+
id: globalThis.crypto.randomUUID ? globalThis.crypto.randomUUID() : (Math.random().toString(36).substring(2)),
|
|
303
270
|
groupId: jobId,
|
|
304
271
|
senderId: robot.accountId,
|
|
272
|
+
content: params.text,
|
|
273
|
+
timestamp: Date.now(),
|
|
274
|
+
mentions: [],
|
|
275
|
+
version: 'psp-1.0'
|
|
305
276
|
};
|
|
306
277
|
if (robot.config.privateKey) {
|
|
307
|
-
|
|
278
|
+
const signed = await PierCrypto.signSwarmMessage(payload, robot.config.privateKey);
|
|
279
|
+
await robot.client.nats.publishGroupChat(jobId, signed);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
await robot.client.nats.publishGroupChat(jobId, payload);
|
|
308
283
|
}
|
|
309
|
-
await robot.client.nats.publishGroupChat(jobId, swarmPayload);
|
|
310
284
|
}
|
|
311
285
|
else {
|
|
312
|
-
const
|
|
313
|
-
|
|
286
|
+
const payload = {
|
|
287
|
+
id: globalThis.crypto.randomUUID ? globalThis.crypto.randomUUID() : (Math.random().toString(36).substring(2)),
|
|
288
|
+
job_id: jobId,
|
|
289
|
+
sender_id: robot.config.nodeId,
|
|
290
|
+
sender_name: accountId,
|
|
291
|
+
sender_type: 'node',
|
|
292
|
+
content: params.text,
|
|
293
|
+
created_at: new Date().toISOString(),
|
|
294
|
+
auth_token: robot.config.secretKey,
|
|
295
|
+
timestamp: Date.now()
|
|
296
|
+
};
|
|
297
|
+
await robot.js.publish(`chat.${jobId}`, new TextEncoder().encode(JSON.stringify(payload)));
|
|
314
298
|
}
|
|
315
299
|
return { content: [{ type: 'text', text: 'Message sent' }], details: {} };
|
|
316
300
|
}
|
|
@@ -319,409 +303,139 @@ const register = (api) => {
|
|
|
319
303
|
}
|
|
320
304
|
}
|
|
321
305
|
}, { optional: true });
|
|
322
|
-
// Helper
|
|
323
|
-
const
|
|
306
|
+
// Helper for marketplace system actions
|
|
307
|
+
const regSystemAction = (name, action, extraParams = {}) => {
|
|
324
308
|
api.registerTool({
|
|
325
309
|
name,
|
|
326
|
-
label,
|
|
327
|
-
description
|
|
310
|
+
label: name,
|
|
311
|
+
description: `Execute ${action} on a Pier task.`,
|
|
328
312
|
parameters: {
|
|
329
313
|
type: 'object',
|
|
330
|
-
properties: {
|
|
331
|
-
jobId: { type: 'string', description: 'The ID of the job' },
|
|
332
|
-
accountId: { type: 'string' },
|
|
333
|
-
...extraParams
|
|
334
|
-
},
|
|
314
|
+
properties: { jobId: { type: 'string' }, ...extraParams },
|
|
335
315
|
required: ['jobId', ...Object.keys(extraParams)]
|
|
336
316
|
},
|
|
337
317
|
async execute(_id, params, ctx) {
|
|
338
|
-
const accountId =
|
|
318
|
+
const accountId = ctx?.metadata?.accountId || ctx?.accountId || 'default';
|
|
339
319
|
const robot = instances.get(accountId) || instances.values().next().value;
|
|
340
|
-
if (!robot
|
|
341
|
-
return { content: [{ type: 'text', text: 'Error: Robot
|
|
342
|
-
}
|
|
320
|
+
if (!robot?.js)
|
|
321
|
+
return { content: [{ type: 'text', text: 'Error: Robot or JetStream unavailable' }], details: {} };
|
|
343
322
|
try {
|
|
344
|
-
const { jobId: j, accountId: _, ...p } = params;
|
|
345
|
-
if (!robot.js) {
|
|
346
|
-
return { content: [{ type: 'text', text: 'Error: JetStream not available' }], details: {} };
|
|
347
|
-
}
|
|
348
323
|
const msgData = {
|
|
349
324
|
id: globalThis.crypto.randomUUID ? globalThis.crypto.randomUUID() : (Math.random().toString(36).substring(2)),
|
|
350
325
|
job_id: params.jobId,
|
|
351
|
-
sender_id:
|
|
352
|
-
|
|
353
|
-
content: JSON.stringify({ type: 'system_action', action, payload: p }),
|
|
326
|
+
sender_id: robot.config.nodeId,
|
|
327
|
+
content: JSON.stringify({ type: 'system_action', action, payload: params }),
|
|
354
328
|
created_at: new Date().toISOString(),
|
|
355
|
-
auth_token: robot.config.secretKey,
|
|
356
329
|
type: 'system_action',
|
|
357
|
-
action
|
|
330
|
+
action
|
|
358
331
|
};
|
|
359
332
|
await robot.js.publish(`chat.${params.jobId}`, new TextEncoder().encode(JSON.stringify(msgData)));
|
|
360
|
-
return { content: [{ type: 'text', text:
|
|
333
|
+
return { content: [{ type: 'text', text: 'Executed' }], details: {} };
|
|
361
334
|
}
|
|
362
|
-
catch (
|
|
363
|
-
return { content: [{ type: 'text', text:
|
|
335
|
+
catch (e) {
|
|
336
|
+
return { content: [{ type: 'text', text: e.message }], details: {} };
|
|
364
337
|
}
|
|
365
338
|
}
|
|
366
339
|
}, { optional: true });
|
|
367
340
|
};
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}, 'user');
|
|
375
|
-
registerSystemActionTool('pier_rate_task', 'Rate task', 'Rate the node', 'task_rate', {
|
|
376
|
-
score: { type: 'number' },
|
|
377
|
-
comment: { type: 'string' }
|
|
378
|
-
}, 'user');
|
|
379
|
-
registerSystemActionTool('pier_reject_task', 'Reject task', 'Reject task', 'task_reject', { reason: { type: 'string' } });
|
|
380
|
-
registerSystemActionTool('pier_fail_task', 'Report error', 'Report that the task has failed', 'task_error', { error: { type: 'string' } });
|
|
381
|
-
registerSystemActionTool('pier_cancel_task', 'Cancel task', 'Cancel the task', 'task_cancel', { reason: { type: 'string' } }, 'user');
|
|
382
|
-
api.registerTool({
|
|
383
|
-
name: 'pier_get_profile',
|
|
384
|
-
label: 'Pier Profile',
|
|
385
|
-
description: 'Get current Pier profile and node stats.',
|
|
386
|
-
parameters: {
|
|
387
|
-
type: 'object',
|
|
388
|
-
properties: { accountId: { type: 'string' } }
|
|
389
|
-
},
|
|
390
|
-
async execute(_id, params, ctx) {
|
|
391
|
-
const accountId = params.accountId || ctx?.metadata?.accountId || ctx?.accountId || 'default';
|
|
392
|
-
const robot = instances.get(accountId) || instances.values().next().value;
|
|
393
|
-
if (!robot)
|
|
394
|
-
return { content: [{ type: 'text', text: 'Error: Robot not found' }], details: {} };
|
|
395
|
-
try {
|
|
396
|
-
const profile = await robot.client.getUserProfile(robot.config.secretKey);
|
|
397
|
-
return { content: [{ type: 'text', text: JSON.stringify(profile, null, 2) }], details: {} };
|
|
398
|
-
}
|
|
399
|
-
catch (err) {
|
|
400
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], details: {} };
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}, { optional: true });
|
|
404
|
-
// Multi-Agent Workspace Tools
|
|
405
|
-
api.registerTool({
|
|
406
|
-
name: 'pier_create_workspace',
|
|
407
|
-
label: 'Create Workspace',
|
|
408
|
-
description: 'Creates a decentralized multi-agent workspace (group chat) and initializes its whiteboard.',
|
|
409
|
-
parameters: {
|
|
410
|
-
type: 'object',
|
|
411
|
-
properties: {
|
|
412
|
-
namespace: { type: 'string', description: 'Federation namespace. Default to "default" if omitted.' }
|
|
413
|
-
}
|
|
414
|
-
},
|
|
415
|
-
async execute(_id, params, ctx) {
|
|
416
|
-
const accountId = params.accountId || ctx?.metadata?.accountId || ctx?.accountId || 'default';
|
|
417
|
-
const robot = instances.get(accountId) || instances.values().next().value;
|
|
418
|
-
if (!robot)
|
|
419
|
-
return { content: [{ type: 'text', text: 'Error: Robot not found' }], details: {} };
|
|
420
|
-
try {
|
|
421
|
-
const groupId = globalThis.crypto.randomUUID ? globalThis.crypto.randomUUID() : (Math.random().toString(36).substring(2));
|
|
422
|
-
const kv = await robot.client.nats.getWorkspaceKV(groupId);
|
|
423
|
-
await kv.put('status', new TextEncoder().encode('Active Workspace Created'));
|
|
424
|
-
await kv.put('budget', new TextEncoder().encode('100'));
|
|
425
|
-
return { content: [{ type: 'text', text: `Workspace created successfully. Group ID: ${groupId}. Initial budget set to 100.` }], details: { groupId } };
|
|
426
|
-
}
|
|
427
|
-
catch (err) {
|
|
428
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], details: {} };
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}, { optional: true });
|
|
341
|
+
regSystemAction('pier_bid_task', 'task_bid', { message: { type: 'string' } });
|
|
342
|
+
regSystemAction('pier_accept_task', 'task_accept');
|
|
343
|
+
regSystemAction('pier_finish_task', 'task_submit', { result: { type: 'string' } });
|
|
344
|
+
regSystemAction('pier_reject_task', 'task_reject', { reason: { type: 'string' } });
|
|
345
|
+
regSystemAction('pier_fail_task', 'task_error', { error: { type: 'string' } });
|
|
346
|
+
// --- Strategy Board (Whiteboard) Tools ---
|
|
432
347
|
api.registerTool({
|
|
433
348
|
name: STRATEGY_BOARD_TOOLS.LIST_KEYS,
|
|
434
349
|
label: 'List Board Keys',
|
|
435
|
-
description: 'List all
|
|
350
|
+
description: 'List all consensus keys in the workspace whiteboard.',
|
|
436
351
|
parameters: {
|
|
437
352
|
type: 'object',
|
|
438
|
-
properties: {
|
|
439
|
-
groupId: { type: 'string', description: 'The Workspace Group ID' }
|
|
440
|
-
},
|
|
353
|
+
properties: { groupId: { type: 'string' } },
|
|
441
354
|
required: ['groupId']
|
|
442
355
|
},
|
|
443
356
|
async execute(_id, params, ctx) {
|
|
444
|
-
const robot = instances.get(
|
|
357
|
+
const robot = instances.get(ctx?.metadata?.accountId || ctx?.accountId || 'default') || instances.values().next().value;
|
|
445
358
|
if (!robot)
|
|
446
|
-
return { content: [{ type: 'text', text: 'Error
|
|
359
|
+
return { content: [{ type: 'text', text: 'Error' }], details: {} };
|
|
447
360
|
try {
|
|
448
361
|
const kv = await robot.client.nats.getWorkspaceKV(params.groupId);
|
|
449
362
|
const keys = await kv.keys();
|
|
450
363
|
const list = [];
|
|
451
364
|
for await (const k of keys)
|
|
452
365
|
list.push(k);
|
|
453
|
-
return { content: [{ type: 'text', text: `
|
|
366
|
+
return { content: [{ type: 'text', text: `Board Keys:\n${list.join('\n') || '[Empty]'}` }], details: {} };
|
|
454
367
|
}
|
|
455
368
|
catch (err) {
|
|
456
|
-
return { content: [{ type: 'text', text:
|
|
369
|
+
return { content: [{ type: 'text', text: err.message }], details: {} };
|
|
457
370
|
}
|
|
458
371
|
}
|
|
459
372
|
}, { optional: true });
|
|
460
373
|
api.registerTool({
|
|
461
374
|
name: STRATEGY_BOARD_TOOLS.READ_ITEM,
|
|
462
375
|
label: 'Read Board Item',
|
|
463
|
-
description: 'Read a specific strategic
|
|
376
|
+
description: 'Read a specific strategic agreement from the whiteboard.',
|
|
464
377
|
parameters: {
|
|
465
378
|
type: 'object',
|
|
466
|
-
properties: {
|
|
467
|
-
groupId: { type: 'string', description: 'The Workspace Group ID' },
|
|
468
|
-
key: { type: 'string', description: 'The specific key to read' }
|
|
469
|
-
},
|
|
379
|
+
properties: { groupId: { type: 'string' }, key: { type: 'string' } },
|
|
470
380
|
required: ['groupId', 'key']
|
|
471
381
|
},
|
|
472
382
|
async execute(_id, params, ctx) {
|
|
473
|
-
const robot = instances.get(
|
|
383
|
+
const robot = instances.get(ctx?.metadata?.accountId || ctx?.accountId || 'default') || instances.values().next().value;
|
|
474
384
|
if (!robot)
|
|
475
|
-
return { content: [{ type: 'text', text: 'Error
|
|
385
|
+
return { content: [{ type: 'text', text: 'Error' }], details: {} };
|
|
476
386
|
try {
|
|
477
387
|
const kv = await robot.client.nats.getWorkspaceKV(params.groupId);
|
|
478
388
|
const entry = await kv.get(params.key);
|
|
479
389
|
if (!entry)
|
|
480
|
-
return { content: [{ type: 'text', text: '
|
|
481
|
-
|
|
482
|
-
return { content: [{ type: 'text', text: `--- KEY: ${params.key} (Rev: ${entry.revision}) ---\n${val}` }], details: { revision: entry.revision, value: val } };
|
|
390
|
+
return { content: [{ type: 'text', text: 'Not found' }], details: {} };
|
|
391
|
+
return { content: [{ type: 'text', text: new TextDecoder().decode(entry.value) }], details: { revision: entry.revision } };
|
|
483
392
|
}
|
|
484
393
|
catch (err) {
|
|
485
|
-
return { content: [{ type: 'text', text:
|
|
394
|
+
return { content: [{ type: 'text', text: err.message }], details: {} };
|
|
486
395
|
}
|
|
487
396
|
}
|
|
488
397
|
}, { optional: true });
|
|
489
398
|
api.registerTool({
|
|
490
399
|
name: STRATEGY_BOARD_TOOLS.UPDATE_ITEM,
|
|
491
400
|
label: 'Update Board Item',
|
|
492
|
-
description: 'Update or create
|
|
401
|
+
description: 'Update or create consensus on the whiteboard (Markdown supported).',
|
|
493
402
|
parameters: {
|
|
494
403
|
type: 'object',
|
|
495
|
-
properties: {
|
|
496
|
-
groupId: { type: 'string', description: 'The Workspace Group ID' },
|
|
497
|
-
key: { type: 'string', description: 'The key to update' },
|
|
498
|
-
value: { type: 'string', description: 'The new content (Markdown supported)' }
|
|
499
|
-
},
|
|
404
|
+
properties: { groupId: { type: 'string' }, key: { type: 'string' }, value: { type: 'string' } },
|
|
500
405
|
required: ['groupId', 'key', 'value']
|
|
501
406
|
},
|
|
502
|
-
async execute(_id, params, ctx) {
|
|
503
|
-
const robot = instances.get(params.accountId || ctx?.metadata?.accountId || ctx?.accountId || 'default') || instances.values().next().value;
|
|
504
|
-
if (!robot)
|
|
505
|
-
return { content: [{ type: 'text', text: 'Error: Robot not found' }], details: {} };
|
|
506
|
-
try {
|
|
507
|
-
const kv = await robot.client.nats.getWorkspaceKV(params.groupId);
|
|
508
|
-
await kv.put(params.key, new TextEncoder().encode(params.value));
|
|
509
|
-
return { content: [{ type: 'text', text: `Successfully updated board item: ${params.key}` }], details: { key: params.key } };
|
|
510
|
-
}
|
|
511
|
-
catch (err) {
|
|
512
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], details: {} };
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
}, { optional: true });
|
|
516
|
-
api.registerTool({
|
|
517
|
-
name: 'pier_update_whiteboard',
|
|
518
|
-
label: 'Update Whiteboard',
|
|
519
|
-
description: 'Update the shared state on the workspace whiteboard using CAS (Compare-And-Swap). Provide expected_revision from read.',
|
|
520
|
-
parameters: {
|
|
521
|
-
type: 'object',
|
|
522
|
-
properties: {
|
|
523
|
-
groupId: { type: 'string' },
|
|
524
|
-
key: { type: 'string' },
|
|
525
|
-
value: { type: 'string' },
|
|
526
|
-
expected_revision: { type: 'number', description: 'The revision number from the last read (use 0 if key not found)' },
|
|
527
|
-
namespace: { type: 'string' }
|
|
528
|
-
},
|
|
529
|
-
required: ['groupId', 'key', 'value', 'expected_revision']
|
|
530
|
-
},
|
|
531
407
|
async execute(_id, params, ctx) {
|
|
532
408
|
const robot = instances.get(ctx?.metadata?.accountId || ctx?.accountId || 'default') || instances.values().next().value;
|
|
533
409
|
if (!robot)
|
|
534
|
-
return { content: [{ type: 'text', text: '
|
|
410
|
+
return { content: [{ type: 'text', text: 'Error' }], details: {} };
|
|
535
411
|
try {
|
|
536
412
|
const kv = await robot.client.nats.getWorkspaceKV(params.groupId);
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
if (params.expected_revision === 0) {
|
|
540
|
-
try {
|
|
541
|
-
await kv.create(params.key, new TextEncoder().encode(params.value));
|
|
542
|
-
}
|
|
543
|
-
catch (e) {
|
|
544
|
-
if (e.message?.includes('wrong last sequence'))
|
|
545
|
-
throw e; // Pass to conflict handler
|
|
546
|
-
await kv.update(params.key, new TextEncoder().encode(params.value), 0); // fallback
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
else {
|
|
550
|
-
await kv.update(params.key, new TextEncoder().encode(params.value), params.expected_revision);
|
|
551
|
-
}
|
|
552
|
-
return { content: [{ type: 'text', text: `Whiteboard key '${params.key}' updated successfully.` }], details: {} };
|
|
553
|
-
}
|
|
554
|
-
catch (updateErr) {
|
|
555
|
-
if (updateErr.message && (updateErr.message.includes('wrong last sequence') || updateErr.code === '400' || updateErr.api_error?.err_code === 10071)) {
|
|
556
|
-
return { content: [{ type: 'text', text: `ERROR: CAS Conflict - Another agent modified this key while you were working. Please use pier_read_whiteboard to get the latest revision and try again.` }], details: {} };
|
|
557
|
-
}
|
|
558
|
-
throw updateErr;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
catch (err) {
|
|
562
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], details: {} };
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
}, { optional: true });
|
|
566
|
-
api.registerTool({
|
|
567
|
-
name: 'pier_group_mention',
|
|
568
|
-
label: 'Mention in Workspace',
|
|
569
|
-
description: 'Send a message in the workspace group chat explicitly mentioning an agent to wake them up',
|
|
570
|
-
parameters: {
|
|
571
|
-
type: 'object',
|
|
572
|
-
properties: {
|
|
573
|
-
groupId: { type: 'string' },
|
|
574
|
-
mentions: { type: 'array', items: { type: 'string' }, description: 'Array of target Agent Node IDs' },
|
|
575
|
-
content: { type: 'string', description: 'Message content' },
|
|
576
|
-
namespace: { type: 'string' }
|
|
577
|
-
},
|
|
578
|
-
required: ['groupId', 'mentions', 'content']
|
|
579
|
-
},
|
|
580
|
-
async execute(_id, params, ctx) {
|
|
581
|
-
const robot = instances.get(ctx?.metadata?.accountId || ctx?.accountId || 'default') || instances.values().next().value;
|
|
582
|
-
if (!robot)
|
|
583
|
-
return { content: [{ type: 'text', text: 'Robot not found' }], details: {} };
|
|
584
|
-
try {
|
|
585
|
-
const msgId = globalThis.crypto.randomUUID ? globalThis.crypto.randomUUID() : (Math.random().toString(36).substring(2));
|
|
586
|
-
const ns = params.namespace || 'default';
|
|
587
|
-
let payload = {
|
|
588
|
-
version: 'psp-1.0',
|
|
589
|
-
id: msgId,
|
|
590
|
-
namespace: ns,
|
|
591
|
-
timestamp: Date.now(),
|
|
592
|
-
senderId: robot.accountId,
|
|
593
|
-
groupId: params.groupId,
|
|
594
|
-
content: params.content,
|
|
595
|
-
mentions: params.mentions
|
|
596
|
-
};
|
|
597
|
-
if (robot.config.privateKey) {
|
|
598
|
-
payload = await PierCrypto.signSwarmMessage(payload, robot.config.privateKey);
|
|
599
|
-
}
|
|
600
|
-
await robot.client.nats.publishGroupChat(params.groupId, payload, ns);
|
|
601
|
-
return { content: [{ type: 'text', text: `Message sent to workspace ${params.groupId} successfully.` }], details: {} };
|
|
413
|
+
await kv.put(params.key, new TextEncoder().encode(params.value));
|
|
414
|
+
return { content: [{ type: 'text', text: 'Board updated' }], details: {} };
|
|
602
415
|
}
|
|
603
416
|
catch (err) {
|
|
604
|
-
return { content: [{ type: 'text', text:
|
|
417
|
+
return { content: [{ type: 'text', text: err.message }], details: {} };
|
|
605
418
|
}
|
|
606
419
|
}
|
|
607
420
|
}, { optional: true });
|
|
608
421
|
api.registerTool({
|
|
609
|
-
name: '
|
|
610
|
-
label: '
|
|
611
|
-
description: '
|
|
612
|
-
parameters: {
|
|
613
|
-
type: 'object',
|
|
614
|
-
properties: {
|
|
615
|
-
capability: { type: 'string', description: '(Recommended) Filter by a specific capability e.g. "translation", "code-execution". Uses fast per-subject discovery when set.' }
|
|
616
|
-
}
|
|
617
|
-
},
|
|
618
|
-
async execute(_id, params, ctx) {
|
|
619
|
-
const robot = instances.get(ctx?.metadata?.accountId || ctx?.accountId || 'default') || instances.values().next().value;
|
|
620
|
-
if (!robot)
|
|
621
|
-
return { content: [{ type: 'text', text: 'Error' }], details: {} };
|
|
622
|
-
// --- PSP 1.0 Subject-based Discovery (fast path, used when capability is specified) ---
|
|
623
|
-
if (params.capability) {
|
|
624
|
-
try {
|
|
625
|
-
const nodes = await robot.client.nats.queryDiscovery(params.capability);
|
|
626
|
-
return { content: [{ type: 'text', text: JSON.stringify(nodes, null, 2) }], details: {} };
|
|
627
|
-
}
|
|
628
|
-
catch (e) {
|
|
629
|
-
// Fall through to KV scan
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
// --- KV Full-scan (fallback when no capability filter, or subject discovery fails) ---
|
|
633
|
-
try {
|
|
634
|
-
const kv = await robot.client.nats.getWorkspaceKV('active_nodes');
|
|
635
|
-
const activeNodes = [];
|
|
636
|
-
const iter = await kv.keys();
|
|
637
|
-
for await (const k of iter) {
|
|
638
|
-
const entry = await kv.get(k);
|
|
639
|
-
if (entry) {
|
|
640
|
-
try {
|
|
641
|
-
// S10: DoS protection
|
|
642
|
-
if (entry.value.length > 1024 * 1024)
|
|
643
|
-
continue;
|
|
644
|
-
const nodeData = JSON.parse(new TextDecoder().decode(entry.value));
|
|
645
|
-
if (Date.now() - nodeData.timestamp < 120000) {
|
|
646
|
-
// --- Zero-Trust: Verify signed heartbeat (PSP 1.0 Hard-Block) ---
|
|
647
|
-
if (nodeData.version === 'psp-1.0' && nodeData.signature && nodeData.publicKey) {
|
|
648
|
-
const isValid = PierCrypto.verifySwarmMessage(nodeData);
|
|
649
|
-
if (!isValid) {
|
|
650
|
-
console.warn(`[pier_search_workers] š“ Dropping node ${k}: invalid signature (spoofed identity).`);
|
|
651
|
-
continue;
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
activeNodes.push(nodeData);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
catch (e) { }
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
return { content: [{ type: 'text', text: JSON.stringify(activeNodes.slice(0, 10), null, 2) }], details: {} };
|
|
661
|
-
}
|
|
662
|
-
catch (e) {
|
|
663
|
-
// Final fallback: central API
|
|
664
|
-
try {
|
|
665
|
-
const resp = await fetch(`${robot.client.apiUrl}/nodes`);
|
|
666
|
-
const nodes = await resp.json();
|
|
667
|
-
const active = nodes.filter((n) => n.is_online).slice(0, 10);
|
|
668
|
-
return { content: [{ type: 'text', text: JSON.stringify(active, null, 2) }], details: {} };
|
|
669
|
-
}
|
|
670
|
-
catch (fallbackErr) {
|
|
671
|
-
return { content: [{ type: 'text', text: `All discovery methods failed: ${e.message}` }], details: {} };
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
}, { optional: true });
|
|
676
|
-
api.registerTool({
|
|
677
|
-
name: 'pier_invite_collaborator',
|
|
678
|
-
label: 'Invite to Workspace',
|
|
679
|
-
description: 'Send a targeted job request to a node to invite them.',
|
|
680
|
-
parameters: {
|
|
681
|
-
type: 'object',
|
|
682
|
-
properties: {
|
|
683
|
-
nodeId: { type: 'string' },
|
|
684
|
-
groupId: { type: 'string' },
|
|
685
|
-
roleDescription: { type: 'string' },
|
|
686
|
-
namespace: { type: 'string' }
|
|
687
|
-
},
|
|
688
|
-
required: ['nodeId', 'groupId', 'roleDescription']
|
|
689
|
-
},
|
|
422
|
+
name: 'pier_get_profile',
|
|
423
|
+
label: 'Pier Profile',
|
|
424
|
+
description: 'Get current Pier node stats.',
|
|
425
|
+
parameters: { type: 'object', properties: { accountId: { type: 'string' } } },
|
|
690
426
|
async execute(_id, params, ctx) {
|
|
691
|
-
const robot = instances.get(
|
|
427
|
+
const robot = instances.get(params.accountId || ctx?.accountId || 'default') || instances.values().next().value;
|
|
692
428
|
if (!robot)
|
|
693
429
|
return { content: [{ type: 'text', text: 'Error' }], details: {} };
|
|
694
430
|
try {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
targetNodeId: params.nodeId,
|
|
698
|
-
namespace: params.namespace || 'default',
|
|
699
|
-
senderId: robot.accountId,
|
|
700
|
-
meta: { sender: robot.accountId, workspace: params.groupId }
|
|
701
|
-
});
|
|
702
|
-
if (robot.config.privateKey) {
|
|
703
|
-
req = await PierCrypto.signSwarmMessage(req, robot.config.privateKey);
|
|
704
|
-
}
|
|
705
|
-
await robot.js.publish(`jobs.node.${params.nodeId}`, new TextEncoder().encode(JSON.stringify(req)));
|
|
706
|
-
return { content: [{ type: 'text', text: `Invitation sent to ${params.nodeId}` }], details: {} };
|
|
431
|
+
const profile = await robot.client.getUserProfile(robot.config.secretKey);
|
|
432
|
+
return { content: [{ type: 'text', text: JSON.stringify(profile, null, 2) }], details: {} };
|
|
707
433
|
}
|
|
708
|
-
catch (
|
|
709
|
-
return { content: [{ type: 'text', text:
|
|
434
|
+
catch (err) {
|
|
435
|
+
return { content: [{ type: 'text', text: err.message }], details: {} };
|
|
710
436
|
}
|
|
711
437
|
}
|
|
712
438
|
}, { optional: true });
|
|
713
|
-
// Status Command
|
|
714
|
-
api.registerCommand({
|
|
715
|
-
name: 'pier',
|
|
716
|
-
description: 'Show Pier status',
|
|
717
|
-
handler: () => {
|
|
718
|
-
const lines = ['**Pier Connector Status**'];
|
|
719
|
-
instances.forEach((r, id) => {
|
|
720
|
-
lines.push(`\n**Account: ${id}**\n⢠Status: ${r.connectionStatus}\n⢠Jobs: ${r.stats.received}/${r.stats.completed}/${r.stats.failed}`);
|
|
721
|
-
});
|
|
722
|
-
return { text: lines.join('\n') };
|
|
723
|
-
}
|
|
724
|
-
});
|
|
725
439
|
registerCli(api, globalStats);
|
|
726
440
|
logger.info('[pier-connector] Plugin registered');
|
|
727
441
|
};
|