@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/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,140 @@ 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');
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 ---
347
+ // Standard names using STRATEGY_BOARD_TOOLS constant which already includes 'pier_' prefix
382
348
  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 });
432
- api.registerTool({
433
- name: STRATEGY_BOARD_TOOLS.LIST_KEYS,
349
+ name: STRATEGY_BOARD_TOOLS.LIST_KEYS, // 'pier_list_board_keys'
434
350
  label: 'List Board Keys',
435
- description: 'List all available strategic keys in the workspace whiteboard.',
351
+ description: 'List all consensus keys in the workspace whiteboard.',
436
352
  parameters: {
437
353
  type: 'object',
438
- properties: {
439
- groupId: { type: 'string', description: 'The Workspace Group ID' }
440
- },
354
+ properties: { groupId: { type: 'string' } },
441
355
  required: ['groupId']
442
356
  },
443
357
  async execute(_id, params, ctx) {
444
- const robot = instances.get(params.accountId || ctx?.metadata?.accountId || ctx?.accountId || 'default') || instances.values().next().value;
358
+ const robot = instances.get(ctx?.metadata?.accountId || ctx?.accountId || 'default') || instances.values().next().value;
445
359
  if (!robot)
446
- return { content: [{ type: 'text', text: 'Error: Robot not found' }], details: {} };
360
+ return { content: [{ type: 'text', text: 'Error' }], details: {} };
447
361
  try {
448
362
  const kv = await robot.client.nats.getWorkspaceKV(params.groupId);
449
363
  const keys = await kv.keys();
450
364
  const list = [];
451
365
  for await (const k of keys)
452
366
  list.push(k);
453
- return { content: [{ type: 'text', text: `Available keys in board:\n${list.join('\n') || '[Board is empty]'}` }], details: { keys: list } };
367
+ return { content: [{ type: 'text', text: `Board Keys:\n${list.join('\n') || '[Empty]'}` }], details: {} };
454
368
  }
455
369
  catch (err) {
456
- return { content: [{ type: 'text', text: `Error or Board Empty: ${err.message}` }], details: {} };
370
+ return { content: [{ type: 'text', text: err.message }], details: {} };
457
371
  }
458
372
  }
459
373
  }, { optional: true });
460
374
  api.registerTool({
461
- name: STRATEGY_BOARD_TOOLS.READ_ITEM,
375
+ name: STRATEGY_BOARD_TOOLS.READ_ITEM, // 'pier_read_board_item'
462
376
  label: 'Read Board Item',
463
- description: 'Read a specific strategic item from the whiteboard.',
377
+ description: 'Read a specific strategic agreement from the whiteboard.',
464
378
  parameters: {
465
379
  type: 'object',
466
- properties: {
467
- groupId: { type: 'string', description: 'The Workspace Group ID' },
468
- key: { type: 'string', description: 'The specific key to read' }
469
- },
380
+ properties: { groupId: { type: 'string' }, key: { type: 'string' } },
470
381
  required: ['groupId', 'key']
471
382
  },
472
383
  async execute(_id, params, ctx) {
473
- const robot = instances.get(params.accountId || ctx?.metadata?.accountId || ctx?.accountId || 'default') || instances.values().next().value;
384
+ const robot = instances.get(ctx?.metadata?.accountId || ctx?.accountId || 'default') || instances.values().next().value;
474
385
  if (!robot)
475
- return { content: [{ type: 'text', text: 'Error: Robot not found' }], details: {} };
386
+ return { content: [{ type: 'text', text: 'Error' }], details: {} };
476
387
  try {
477
388
  const kv = await robot.client.nats.getWorkspaceKV(params.groupId);
478
389
  const entry = await kv.get(params.key);
479
390
  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 } };
391
+ return { content: [{ type: 'text', text: 'Not found' }], details: {} };
392
+ return { content: [{ type: 'text', text: new TextDecoder().decode(entry.value) }], details: { revision: entry.revision } };
483
393
  }
484
394
  catch (err) {
485
- return { content: [{ type: 'text', text: `Error: ${err.message}` }], details: {} };
395
+ return { content: [{ type: 'text', text: err.message }], details: {} };
486
396
  }
487
397
  }
488
398
  }, { optional: true });
489
399
  api.registerTool({
490
- name: STRATEGY_BOARD_TOOLS.UPDATE_ITEM,
400
+ name: STRATEGY_BOARD_TOOLS.UPDATE_ITEM, // 'pier_update_board_item'
491
401
  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.',
402
+ description: 'Update or create consensus on the whiteboard (Markdown supported).',
493
403
  parameters: {
494
404
  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
- },
405
+ properties: { groupId: { type: 'string' }, key: { type: 'string' }, value: { type: 'string' } },
500
406
  required: ['groupId', 'key', 'value']
501
407
  },
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
408
  async execute(_id, params, ctx) {
532
409
  const robot = instances.get(ctx?.metadata?.accountId || ctx?.accountId || 'default') || instances.values().next().value;
533
410
  if (!robot)
534
- return { content: [{ type: 'text', text: 'Robot not found' }], details: {} };
411
+ return { content: [{ type: 'text', text: 'Error' }], details: {} };
535
412
  try {
536
413
  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: {} };
414
+ await kv.put(params.key, new TextEncoder().encode(params.value));
415
+ return { content: [{ type: 'text', text: 'Board updated' }], details: {} };
602
416
  }
603
417
  catch (err) {
604
- return { content: [{ type: 'text', text: `Error: ${err.message}` }], details: {} };
418
+ return { content: [{ type: 'text', text: err.message }], details: {} };
605
419
  }
606
420
  }
607
421
  }, { optional: true });
608
422
  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
- },
423
+ name: 'pier_get_profile',
424
+ label: 'Pier Profile',
425
+ description: 'Get current Pier node stats.',
426
+ parameters: { type: 'object', properties: { accountId: { type: 'string' } } },
690
427
  async execute(_id, params, ctx) {
691
- const robot = instances.get(ctx?.metadata?.accountId || ctx?.accountId || 'default') || instances.values().next().value;
428
+ const robot = instances.get(params.accountId || ctx?.accountId || 'default') || instances.values().next().value;
692
429
  if (!robot)
693
430
  return { content: [{ type: 'text', text: 'Error' }], details: {} };
694
431
  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: {} };
432
+ const profile = await robot.client.getUserProfile(robot.config.secretKey);
433
+ return { content: [{ type: 'text', text: JSON.stringify(profile, null, 2) }], details: {} };
707
434
  }
708
- catch (e) {
709
- return { content: [{ type: 'text', text: e.message }], details: {} };
435
+ catch (err) {
436
+ return { content: [{ type: 'text', text: err.message }], details: {} };
710
437
  }
711
438
  }
712
439
  }, { 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
440
  registerCli(api, globalStats);
726
441
  logger.info('[pier-connector] Plugin registered');
727
442
  };