@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/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
- // Tools and CLI registration
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 to the employer regarding a specific job.',
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
- let swarmPayload = {
301
- ...payload,
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
- swarmPayload = await PierCrypto.signSwarmMessage(swarmPayload, robot.config.privateKey);
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 subject = `chat.${jobId}`;
313
- await robot.js.publish(subject, new TextEncoder().encode(JSON.stringify(payload)));
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 to register marketplace action tools
323
- const registerSystemActionTool = (name, label, description, action, extraParams, userRole = 'node') => {
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 = params.accountId || ctx?.metadata?.accountId || ctx?.accountId || 'default';
318
+ const accountId = ctx?.metadata?.accountId || ctx?.accountId || 'default';
339
319
  const robot = instances.get(accountId) || instances.values().next().value;
340
- if (!robot || robot.connectionStatus !== 'connected') {
341
- return { content: [{ type: 'text', text: 'Error: Robot not connected' }], details: {} };
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: userRole === 'user' ? 'user_' + robot.config.nodeId : robot.config.nodeId,
352
- sender_type: userRole,
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: 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: `${action} executed successfully` }], details: {} };
333
+ return { content: [{ type: 'text', text: 'Executed' }], details: {} };
361
334
  }
362
- catch (err) {
363
- return { content: [{ type: 'text', text: `Error: ${err.message}` }], details: {} };
335
+ catch (e) {
336
+ return { content: [{ type: 'text', text: e.message }], details: {} };
364
337
  }
365
338
  }
366
339
  }, { optional: true });
367
340
  };
368
- registerSystemActionTool('pier_bid_task', 'Bid on task', 'Bid on an marketplace task', 'task_bid', { message: { type: 'string', description: 'Your pitch' } });
369
- registerSystemActionTool('pier_accept_task', 'Accept task', 'Accept offered task', 'task_accept', {});
370
- registerSystemActionTool('pier_finish_task', 'Finish task', 'Submit final result', 'task_submit', { result: { type: 'string', description: 'Final result' } });
371
- registerSystemActionTool('pier_propose_task', 'Offer task', 'Offer task to a node', 'task_offer', {
372
- price: { type: 'number' },
373
- description: { type: 'string' }
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 available strategic keys in the workspace whiteboard.',
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(params.accountId || ctx?.metadata?.accountId || ctx?.accountId || 'default') || instances.values().next().value;
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: Robot not found' }], details: {} };
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: `Available keys in board:\n${list.join('\n') || '[Board is empty]'}` }], details: { keys: list } };
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: `Error or Board Empty: ${err.message}` }], details: {} };
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 item from the whiteboard.',
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(params.accountId || ctx?.metadata?.accountId || ctx?.accountId || 'default') || instances.values().next().value;
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: Robot not found' }], details: {} };
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: 'Key not found.' }], details: {} };
481
- const val = new TextDecoder().decode(entry.value);
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: `Error: ${err.message}` }], details: {} };
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 an item on the whiteboard. Use this to persist group consensus, API specs, or task progress.',
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: 'Robot not found' }], details: {} };
410
+ return { content: [{ type: 'text', text: 'Error' }], details: {} };
535
411
  try {
536
412
  const kv = await robot.client.nats.getWorkspaceKV(params.groupId);
537
- try {
538
- // if expected_revision is 0 it means we expect the key does not exist yet (or we use create)
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: `Error: ${err.message}` }], details: {} };
417
+ return { content: [{ type: 'text', text: err.message }], details: {} };
605
418
  }
606
419
  }
607
420
  }, { optional: true });
608
421
  api.registerTool({
609
- name: 'pier_search_workers',
610
- label: 'Search Workers',
611
- 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.',
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(ctx?.metadata?.accountId || ctx?.accountId || 'default') || instances.values().next().value;
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
- let req = createRequestPayload({
696
- task: `INVITATION: Join my workspace ${params.groupId}.\nRole: ${params.roleDescription}\nPlease use 'pier_group_mention' tool to reply in group ${params.groupId}.\nYou can also use 'pier_read_whiteboard' with groupId=${params.groupId} to view the plan.`,
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 (e) {
709
- return { content: [{ type: 'text', text: e.message }], details: {} };
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
  };