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