@agrentingai/paperclip-adapter 0.2.2 → 0.4.0

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.
@@ -33,12 +33,20 @@ __export(index_exports, {
33
33
  AgrentingClient: () => AgrentingClient,
34
34
  MAX_POLLS: () => MAX_POLLS,
35
35
  POLL_INTERVALS_MS: () => POLL_INTERVALS_MS,
36
+ agentConfigurationDoc: () => agentConfigurationDoc,
37
+ agrentingAppGalleryEntry: () => agrentingAppGalleryEntry,
36
38
  autoSelectAgent: () => autoSelectAgent,
37
39
  canSubmitTask: () => canSubmitTask,
40
+ cancel: () => cancel,
41
+ cancelHiring: () => cancelHiring,
42
+ cancelTask: () => cancelTask,
38
43
  checkBalance: () => checkBalance,
39
44
  createServerAdapter: () => createServerAdapter,
40
45
  createWebhookHandler: () => createWebhookHandler,
41
46
  deregisterWebhook: () => deregisterWebhook,
47
+ detectModel: () => detectModel,
48
+ execute: () => execute,
49
+ executePaperclip: () => executePaperclip,
42
50
  executeWithRetry: () => executeWithRetry,
43
51
  formatAgentResponse: () => formatAgentResponse,
44
52
  formatInsufficientBalanceComment: () => formatInsufficientBalanceComment,
@@ -47,13 +55,20 @@ __export(index_exports, {
47
55
  getActiveTaskMappings: () => getActiveTaskMappings,
48
56
  getAgentProfile: () => getAgentProfile,
49
57
  getBackoffMs: () => getBackoffMs,
58
+ getConfigSchema: () => getConfigSchema,
50
59
  getHiring: () => getHiring,
51
60
  getHiringMessages: () => getHiringMessages,
61
+ getPaperclipConfigSchema: () => getPaperclipConfigSchema,
52
62
  getTaskMessages: () => getTaskMessages,
63
+ getTaskProgress: () => getTaskProgress,
53
64
  getWebhookGracePeriodMs: () => getWebhookGracePeriodMs,
54
65
  hireAgent: () => hireAgent,
66
+ invoke: () => invoke,
67
+ label: () => label,
55
68
  listCapabilities: () => listCapabilities,
56
69
  listHirings: () => listHirings,
70
+ listSkills: () => listSkills,
71
+ paperclipSessionCodec: () => paperclipSessionCodec,
57
72
  pollTaskUntilDone: () => pollTaskUntilDone,
58
73
  processIncomingMessage: () => processIncomingMessage,
59
74
  reassignTask: () => reassignTask,
@@ -62,6 +77,12 @@ __export(index_exports, {
62
77
  retryHiring: () => retryHiring,
63
78
  sendMessageToHiring: () => sendMessageToHiring,
64
79
  sendMessageToTask: () => sendMessageToTask,
80
+ sessionCodec: () => sessionCodec,
81
+ status: () => status,
82
+ syncSkills: () => syncSkills,
83
+ testEnvironment: () => testEnvironment,
84
+ testPaperclipEnvironment: () => testPaperclipEnvironment,
85
+ type: () => type,
65
86
  unregisterTaskMapping: () => unregisterTaskMapping,
66
87
  verifyWebhookSignature: () => verifyWebhookSignature
67
88
  });
@@ -70,6 +91,12 @@ module.exports = __toCommonJS(index_exports);
70
91
  // server/src/client.ts
71
92
  var DEFAULT_TIMEOUT_MS = 3e4;
72
93
  var MAX_RETRIES = 3;
94
+ var NonRetryableAgrentingError = class extends Error {
95
+ constructor(message) {
96
+ super(message);
97
+ this.name = "NonRetryableAgrentingError";
98
+ }
99
+ };
73
100
  var AgrentingClient = class {
74
101
  baseUrl;
75
102
  apiKey;
@@ -116,17 +143,22 @@ var AgrentingClient = class {
116
143
  await new Promise((resolve) => setTimeout(resolve, delayMs));
117
144
  continue;
118
145
  }
119
- throw new Error(
146
+ throw new NonRetryableAgrentingError(
120
147
  `Agrenting API ${response.status}: ${text.slice(0, 500)}`
121
148
  );
122
149
  }
123
150
  const envelope = await response.json();
124
151
  if (Array.isArray(envelope.errors) && envelope.errors.length) {
125
- throw new Error(`API errors: ${envelope.errors.join(", ")}`);
152
+ throw new NonRetryableAgrentingError(
153
+ `API errors: ${envelope.errors.join(", ")}`
154
+ );
126
155
  }
127
156
  return envelope.data ?? envelope;
128
157
  } catch (err) {
129
158
  lastError = err instanceof Error ? err : new Error(String(err));
159
+ if (lastError instanceof NonRetryableAgrentingError) {
160
+ throw lastError;
161
+ }
130
162
  if (attempt < MAX_RETRIES) {
131
163
  clearTimeout(timer);
132
164
  const delayMs = Math.min(1e3 * 2 ** attempt, 3e4);
@@ -312,12 +344,23 @@ var AgrentingClient = class {
312
344
  async getAgentProfile(agentDid) {
313
345
  return this.request("GET", `/api/v1/agents/${encodeURIComponent(agentDid)}`);
314
346
  }
315
- /** Hire/bind an agent to your account.
316
- * Returns adapter config so Paperclip can auto-provision the agent.
347
+ /** Create a paid hiring for an agent.
348
+ * Agrenting requires a concrete task, capability, and offered price.
317
349
  */
318
- async hireAgent(agentDid, options = {}) {
319
- const body = {};
320
- if (options.pricingModel) body.pricing_model = options.pricingModel;
350
+ async hireAgent(agentDid, options) {
351
+ const body = {
352
+ task_description: options.taskDescription,
353
+ capability_requested: options.capabilityRequested,
354
+ price: String(options.price),
355
+ delivery_mode: options.deliveryMode ?? "output"
356
+ };
357
+ if (options.repoUrl) body.repo_url = options.repoUrl;
358
+ if (options.repoAccessToken) body.repo_access_token = options.repoAccessToken;
359
+ if (options.clientIdempotencyKey) {
360
+ body.client_idempotency_key = options.clientIdempotencyKey;
361
+ }
362
+ if (options.taskInput) body.task_input = options.taskInput;
363
+ if (options.clientMessage) body.client_message = options.clientMessage;
321
364
  return this.request(
322
365
  "POST",
323
366
  `/api/v1/agents/${encodeURIComponent(agentDid)}/hire`,
@@ -376,13 +419,11 @@ var AgrentingClient = class {
376
419
  );
377
420
  }
378
421
  /** Get messages for a hiring.
379
- * GET /api/v1/hirings/:id/messages
422
+ * The canonical API includes recent messages in GET /api/v1/hirings/:id.
380
423
  */
381
424
  async getHiringMessages(hiringId) {
382
- return this.request(
383
- "GET",
384
- `/api/v1/hirings/${hiringId}/messages`
385
- );
425
+ const hiring = await this.getHiring(hiringId);
426
+ return hiring.messages ?? [];
386
427
  }
387
428
  /** Retry a failed hiring.
388
429
  * POST /api/v1/hirings/:id/retry
@@ -404,6 +445,15 @@ var AgrentingClient = class {
404
445
  async getHiring(hiringId) {
405
446
  return this.request("GET", `/api/v1/hirings/${hiringId}`);
406
447
  }
448
+ /** Cancel an active hiring.
449
+ * POST /api/v1/hirings/:id/cancel
450
+ */
451
+ async cancelHiring(hiringId) {
452
+ return this.request(
453
+ "POST",
454
+ `/api/v1/hirings/${hiringId}/cancel`
455
+ );
456
+ }
407
457
  /** List hirings for the authenticated agent.
408
458
  * GET /api/v1/hirings
409
459
  */
@@ -411,9 +461,16 @@ var AgrentingClient = class {
411
461
  const params = new URLSearchParams();
412
462
  if (options.status) params.set("status", options.status);
413
463
  if (options.limit) params.set("limit", String(options.limit));
414
- if (options.offset) params.set("offset", String(options.offset));
464
+ if (options.offset) {
465
+ const perPage = options.limit ?? 20;
466
+ params.set("page", String(Math.floor(options.offset / perPage) + 1));
467
+ }
415
468
  const query = params.toString() ? `?${params}` : "";
416
- return this.request("GET", `/api/v1/hirings${query}`);
469
+ const result = await this.request(
470
+ "GET",
471
+ `/api/v1/hirings${query}`
472
+ );
473
+ return result.hirings;
417
474
  }
418
475
  /** List agents filtered by capability for auto-select.
419
476
  * GET /api/v1/agents?capability=X
@@ -426,6 +483,493 @@ var AgrentingClient = class {
426
483
  }
427
484
  };
428
485
 
486
+ // server/src/paperclip.ts
487
+ var type = "agrenting";
488
+ var label = "Agrenting";
489
+ var agentConfigurationDoc = `# agrenting agent configuration
490
+
491
+ Adapter: agrenting
492
+
493
+ Use when:
494
+ - A Paperclip agent should delegate each heartbeat to a remote agent hired from Agrenting.
495
+ - The operator has an Agrenting user API token and sufficient ledger balance.
496
+
497
+ Core fields:
498
+ - agrentingUrl (required): Agrenting base URL, normally https://agrenting.com
499
+ - apiKey (required, secret): Agrenting user API token (ap_...)
500
+ - agentDid (required): DID of the marketplace agent to hire
501
+ - capabilityRequested (optional): defaults to the first capability on the agent profile
502
+ - price (optional): defaults to the agent's current base price
503
+ - timeoutSec (optional): maximum time to poll a hiring, default 600
504
+ - pollIntervalMs (optional): hiring status poll interval, default 2000
505
+ - deliveryMode (optional): output by default; push requires repository access
506
+ - repoUrl (optional): repository URL used only when deliveryMode is push
507
+
508
+ Recommended API-key scopes:
509
+ - agents:discover, agents:read, hire:create, hirings:read, hirings:cancel
510
+ - add balance:read and artifacts:read when sharing the key with Apps/Claude
511
+ - deposits:create and account:read/account:write are optional elevated scopes
512
+ - set max_price_per_hire on the key to cap paid actions
513
+
514
+ Execution:
515
+ - Each Paperclip run creates one canonical Agrenting hiring.
516
+ - The Paperclip run id is sent as client_idempotency_key so safe retries deduplicate.
517
+ - Paperclip polls the hiring until it completes, fails, is cancelled, or times out.
518
+ - Push delivery can use a repository URL from Paperclip and an Agrenting-stored
519
+ GitHub token. The adapter never stores a repository token in agent config.
520
+ `;
521
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
522
+ "completed",
523
+ "failed",
524
+ "cancelled",
525
+ "disputed",
526
+ "refunded"
527
+ ]);
528
+ var DEFAULT_TIMEOUT_SEC = 600;
529
+ var DEFAULT_POLL_INTERVAL_MS = 2e3;
530
+ var MAX_TASK_DESCRIPTION_LENGTH = 5e3;
531
+ function asRecord(value) {
532
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
533
+ return null;
534
+ }
535
+ return value;
536
+ }
537
+ function nonEmpty(value) {
538
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
539
+ }
540
+ function positiveNumber(value, fallback) {
541
+ const parsed = typeof value === "number" ? value : Number(value);
542
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
543
+ }
544
+ function configFrom(raw) {
545
+ return {
546
+ agrentingUrl: nonEmpty(raw.agrentingUrl) ?? "https://agrenting.com",
547
+ apiKey: nonEmpty(raw.apiKey) ?? "",
548
+ agentDid: nonEmpty(raw.agentDid) ?? "",
549
+ capabilityRequested: nonEmpty(raw.capabilityRequested) ?? void 0,
550
+ price: nonEmpty(raw.price) ?? void 0,
551
+ timeoutSec: positiveNumber(raw.timeoutSec, DEFAULT_TIMEOUT_SEC),
552
+ pollIntervalMs: positiveNumber(
553
+ raw.pollIntervalMs,
554
+ DEFAULT_POLL_INTERVAL_MS
555
+ ),
556
+ deliveryMode: raw.deliveryMode === "push" ? "push" : "output",
557
+ repoUrl: nonEmpty(raw.repoUrl) ?? void 0
558
+ };
559
+ }
560
+ function taskDescriptionFrom(ctx) {
561
+ const context = ctx.context;
562
+ const title = nonEmpty(context.taskTitle) ?? nonEmpty(context.issueTitle) ?? nonEmpty(context.title);
563
+ const body = nonEmpty(context.paperclipTaskMarkdown) ?? nonEmpty(context.taskBody) ?? nonEmpty(context.taskDescription) ?? nonEmpty(context.issueDescription) ?? nonEmpty(context.prompt) ?? nonEmpty(context.input);
564
+ let description = [title, body].filter(Boolean).join("\n\n").trim();
565
+ if (!description) {
566
+ description = `Continue the assigned Paperclip work for ${ctx.agent.name}.`;
567
+ }
568
+ if (description.length <= MAX_TASK_DESCRIPTION_LENGTH) return description;
569
+ return `${description.slice(0, MAX_TASK_DESCRIPTION_LENGTH - 32)}
570
+
571
+ [truncated by Paperclip adapter]`;
572
+ }
573
+ function capabilityFrom(config, context, profile) {
574
+ return nonEmpty(config.capabilityRequested) ?? nonEmpty(context.capabilityRequested) ?? nonEmpty(context.capability) ?? profile.capabilities.find((capability) => capability.trim().length > 0) ?? null;
575
+ }
576
+ function priceFrom(config, context, profile) {
577
+ return nonEmpty(config.price) ?? nonEmpty(context.price) ?? nonEmpty(context.maxPrice) ?? nonEmpty(profile.base_price);
578
+ }
579
+ function repoUrlFrom(ctx) {
580
+ const workspace = asRecord(ctx.context.paperclipWorkspace);
581
+ return nonEmpty(configFrom(ctx.config).repoUrl) ?? nonEmpty(workspace?.repoUrl) ?? void 0;
582
+ }
583
+ function taskInputFrom(ctx) {
584
+ const input = {
585
+ paperclip_run_id: ctx.runId,
586
+ paperclip_agent_id: ctx.agent.id,
587
+ paperclip_company_id: ctx.agent.companyId
588
+ };
589
+ const mappings = [
590
+ ["paperclip_issue_id", ctx.context.issueId ?? ctx.context.taskId],
591
+ ["paperclip_project_id", ctx.context.projectId],
592
+ ["paperclip_wake_reason", ctx.context.wakeReason]
593
+ ];
594
+ for (const [key, value] of mappings) {
595
+ const normalized = nonEmpty(value);
596
+ if (normalized) input[key] = normalized;
597
+ }
598
+ const wake = asRecord(ctx.context.paperclipWake);
599
+ if (wake) input.paperclip_wake = wake;
600
+ return input;
601
+ }
602
+ function outputText(hiring) {
603
+ const output = hiring.task_output;
604
+ if (typeof output === "string" && output.trim()) return output;
605
+ const record = asRecord(output);
606
+ if (record) {
607
+ for (const key of ["result", "output", "text", "summary"]) {
608
+ const direct = nonEmpty(record[key]);
609
+ if (direct) return direct;
610
+ }
611
+ return JSON.stringify(record, null, 2);
612
+ }
613
+ return `Agrenting hiring ${hiring.id} completed.`;
614
+ }
615
+ function firstLine(value) {
616
+ return value.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "Agrenting hiring completed";
617
+ }
618
+ function sleep(ms) {
619
+ return new Promise((resolve) => setTimeout(resolve, ms));
620
+ }
621
+ function resultForFailure(hiring, message) {
622
+ return {
623
+ exitCode: 1,
624
+ signal: null,
625
+ timedOut: false,
626
+ errorMessage: message,
627
+ errorCode: `agrenting_hiring_${hiring.status}`,
628
+ provider: "agrenting",
629
+ biller: "agrenting",
630
+ sessionParams: { hiringId: hiring.id },
631
+ sessionDisplayId: hiring.id,
632
+ resultJson: {
633
+ hiringId: hiring.id,
634
+ status: hiring.status,
635
+ failedReason: hiring.failed_reason ?? null
636
+ }
637
+ };
638
+ }
639
+ async function executePaperclip(ctx) {
640
+ const config = configFrom(ctx.config);
641
+ if (!config.apiKey || !config.agentDid) {
642
+ return {
643
+ exitCode: 1,
644
+ signal: null,
645
+ timedOut: false,
646
+ errorCode: "agrenting_config_invalid",
647
+ errorMessage: "Agrenting requires apiKey and agentDid."
648
+ };
649
+ }
650
+ const client = new AgrentingClient(config);
651
+ try {
652
+ const profile = await client.getAgentProfile(config.agentDid);
653
+ const capability = capabilityFrom(config, ctx.context, profile);
654
+ const price = priceFrom(config, ctx.context, profile);
655
+ if (!capability) {
656
+ throw new Error(
657
+ "No capabilityRequested was configured and the Agrenting agent profile has no capabilities."
658
+ );
659
+ }
660
+ if (!price) {
661
+ throw new Error(
662
+ "No price was configured and the Agrenting agent profile has no base_price."
663
+ );
664
+ }
665
+ const taskDescription = taskDescriptionFrom(ctx);
666
+ await ctx.onLog(
667
+ "stdout",
668
+ `[agrenting] Hiring ${config.agentDid} for ${capability} at ${price}
669
+ `
670
+ );
671
+ const created = await client.hireAgent(config.agentDid, {
672
+ taskDescription,
673
+ capabilityRequested: capability,
674
+ price,
675
+ deliveryMode: config.deliveryMode,
676
+ clientIdempotencyKey: ctx.runId,
677
+ taskInput: taskInputFrom(ctx),
678
+ repoUrl: repoUrlFrom(ctx)
679
+ });
680
+ const hiringId = created.hiring.id;
681
+ await ctx.onMeta?.({
682
+ adapterType: type,
683
+ command: "POST /api/v1/agents/:did/hire",
684
+ context: { hiringId, agentDid: config.agentDid, capability, price }
685
+ });
686
+ await ctx.onLog(
687
+ "stdout",
688
+ `[agrenting] Hiring ${hiringId} created with status ${created.hiring.status}
689
+ `
690
+ );
691
+ const timeoutMs = (config.timeoutSec ?? DEFAULT_TIMEOUT_SEC) * 1e3;
692
+ const pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
693
+ const deadline = Date.now() + timeoutMs;
694
+ let hiring = created.hiring;
695
+ let lastStatus = hiring.status;
696
+ while (!TERMINAL_STATUSES.has(hiring.status) && Date.now() < deadline) {
697
+ await sleep(Math.min(pollIntervalMs, Math.max(1, deadline - Date.now())));
698
+ hiring = await client.getHiring(hiringId);
699
+ if (hiring.status !== lastStatus) {
700
+ lastStatus = hiring.status;
701
+ await ctx.onLog(
702
+ "stdout",
703
+ `[agrenting] Hiring ${hiringId} is ${hiring.status}
704
+ `
705
+ );
706
+ }
707
+ }
708
+ if (!TERMINAL_STATUSES.has(hiring.status)) {
709
+ try {
710
+ await client.cancelHiring(hiringId);
711
+ await ctx.onLog(
712
+ "stderr",
713
+ `[agrenting] Hiring ${hiringId} timed out and was cancelled
714
+ `
715
+ );
716
+ } catch (cancelError) {
717
+ await ctx.onLog(
718
+ "stderr",
719
+ `[agrenting] Hiring ${hiringId} timed out; cancellation failed: ${cancelError instanceof Error ? cancelError.message : String(cancelError)}
720
+ `
721
+ );
722
+ }
723
+ return {
724
+ exitCode: null,
725
+ signal: null,
726
+ timedOut: true,
727
+ errorCode: "agrenting_hiring_timeout",
728
+ errorMessage: `Agrenting hiring ${hiringId} timed out after ${config.timeoutSec ?? DEFAULT_TIMEOUT_SEC}s.`,
729
+ provider: "agrenting",
730
+ biller: "agrenting",
731
+ sessionParams: { hiringId },
732
+ sessionDisplayId: hiringId
733
+ };
734
+ }
735
+ if (hiring.status !== "completed") {
736
+ return resultForFailure(
737
+ hiring,
738
+ hiring.failed_reason ?? `Agrenting hiring ended with status ${hiring.status}.`
739
+ );
740
+ }
741
+ const output = outputText(hiring);
742
+ await ctx.onLog("stdout", `${output}
743
+ `);
744
+ const numericPrice = Number(hiring.price ?? price);
745
+ return {
746
+ exitCode: 0,
747
+ signal: null,
748
+ timedOut: false,
749
+ provider: "agrenting",
750
+ biller: "agrenting",
751
+ billingType: "fixed",
752
+ costUsd: Number.isFinite(numericPrice) ? numericPrice : null,
753
+ sessionParams: { hiringId },
754
+ sessionDisplayId: hiringId,
755
+ summary: firstLine(output),
756
+ resultJson: {
757
+ hiringId,
758
+ status: hiring.status,
759
+ taskOutput: hiring.task_output ?? null
760
+ }
761
+ };
762
+ } catch (error) {
763
+ const message = error instanceof Error ? error.message : String(error);
764
+ const transient = /\b(429|5\d\d)\b|rate.?limit|temporar|timeout/i.test(message);
765
+ await ctx.onLog("stderr", `[agrenting] ${message}
766
+ `);
767
+ return {
768
+ exitCode: 1,
769
+ signal: null,
770
+ timedOut: false,
771
+ errorCode: "agrenting_hiring_failed",
772
+ errorFamily: transient ? "transient_upstream" : null,
773
+ errorMessage: message,
774
+ provider: "agrenting",
775
+ biller: "agrenting"
776
+ };
777
+ }
778
+ }
779
+ function summarizeChecks(checks) {
780
+ if (checks.some((check) => check.level === "error")) return "fail";
781
+ if (checks.some((check) => check.level === "warn")) return "warn";
782
+ return "pass";
783
+ }
784
+ async function testPaperclipEnvironment(ctx) {
785
+ const config = configFrom(ctx.config);
786
+ const checks = [];
787
+ let parsedUrl = null;
788
+ try {
789
+ parsedUrl = new URL(config.agrentingUrl);
790
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
791
+ parsedUrl = null;
792
+ }
793
+ } catch {
794
+ parsedUrl = null;
795
+ }
796
+ if (!parsedUrl) {
797
+ checks.push({
798
+ code: "agrenting_url_invalid",
799
+ level: "error",
800
+ message: "agrentingUrl must be an http:// or https:// URL."
801
+ });
802
+ }
803
+ if (!config.apiKey) {
804
+ checks.push({
805
+ code: "agrenting_api_key_missing",
806
+ level: "error",
807
+ message: "Agrenting requires a user API token.",
808
+ hint: "Create an ap_... token in Agrenting and store it as a Paperclip secret."
809
+ });
810
+ }
811
+ if (!config.agentDid) {
812
+ checks.push({
813
+ code: "agrenting_agent_did_missing",
814
+ level: "error",
815
+ message: "Agrenting requires agentDid."
816
+ });
817
+ }
818
+ if (!checks.some((check) => check.level === "error")) {
819
+ const client = new AgrentingClient(config);
820
+ let connectionOk = false;
821
+ try {
822
+ await client.listHirings({ limit: 1 });
823
+ connectionOk = true;
824
+ checks.push({
825
+ code: "agrenting_connection_ok",
826
+ level: "info",
827
+ message: "Connected to the authenticated Agrenting hiring API."
828
+ });
829
+ } catch (error) {
830
+ checks.push({
831
+ code: "agrenting_connection_failed",
832
+ level: "error",
833
+ message: "Could not access the authenticated Agrenting hiring API.",
834
+ detail: error instanceof Error ? error.message : String(error)
835
+ });
836
+ }
837
+ if (connectionOk) {
838
+ try {
839
+ const profile = await client.getAgentProfile(config.agentDid);
840
+ checks.push({
841
+ code: "agrenting_agent_profile_ok",
842
+ level: "info",
843
+ message: `Found Agrenting agent ${profile.name} (${profile.did}).`,
844
+ detail: `${profile.capabilities.length} capabilities; base price ${profile.base_price ?? "not reported"}.`
845
+ });
846
+ } catch (error) {
847
+ checks.push({
848
+ code: "agrenting_agent_profile_failed",
849
+ level: "error",
850
+ message: `Could not load Agrenting agent ${config.agentDid}.`,
851
+ detail: error instanceof Error ? error.message : String(error)
852
+ });
853
+ }
854
+ }
855
+ }
856
+ return {
857
+ adapterType: ctx.adapterType,
858
+ status: summarizeChecks(checks),
859
+ checks,
860
+ testedAt: (/* @__PURE__ */ new Date()).toISOString()
861
+ };
862
+ }
863
+ function getPaperclipConfigSchema() {
864
+ return {
865
+ fields: [
866
+ {
867
+ key: "agrentingUrl",
868
+ label: "Agrenting URL",
869
+ type: "text",
870
+ required: true,
871
+ default: "https://agrenting.com",
872
+ hint: "Base URL of the Agrenting platform."
873
+ },
874
+ {
875
+ key: "apiKey",
876
+ label: "API token",
877
+ type: "text",
878
+ required: true,
879
+ hint: "Agrenting user API token (ap_...). Stored as a Paperclip secret.",
880
+ meta: { secret: true }
881
+ },
882
+ {
883
+ key: "agentDid",
884
+ label: "Agent DID",
885
+ type: "text",
886
+ required: true,
887
+ hint: "Marketplace agent DID, for example did:agrenting:code-reviewer."
888
+ },
889
+ {
890
+ key: "capabilityRequested",
891
+ label: "Capability",
892
+ type: "text",
893
+ hint: "Optional default. Falls back to the first capability on the agent profile."
894
+ },
895
+ {
896
+ key: "price",
897
+ label: "Price per hiring (USD)",
898
+ type: "text",
899
+ hint: "Optional. Falls back to the agent's current base price."
900
+ },
901
+ {
902
+ key: "timeoutSec",
903
+ label: "Timeout seconds",
904
+ type: "number",
905
+ default: DEFAULT_TIMEOUT_SEC
906
+ },
907
+ {
908
+ key: "pollIntervalMs",
909
+ label: "Poll interval milliseconds",
910
+ type: "number",
911
+ default: DEFAULT_POLL_INTERVAL_MS
912
+ },
913
+ {
914
+ key: "repoUrl",
915
+ label: "Repository URL",
916
+ type: "text",
917
+ hint: "Optional push target. Push delivery uses a GitHub token already stored in Agrenting."
918
+ },
919
+ {
920
+ key: "deliveryMode",
921
+ label: "Delivery mode",
922
+ type: "select",
923
+ default: "output",
924
+ options: [
925
+ { value: "output", label: "Return output" },
926
+ { value: "push", label: "Push to repository" }
927
+ ],
928
+ hint: "Use output unless the hiring also has repository credentials."
929
+ }
930
+ ]
931
+ };
932
+ }
933
+ var paperclipSessionCodec = {
934
+ deserialize(raw) {
935
+ if (typeof raw === "string") {
936
+ try {
937
+ return asRecord(JSON.parse(raw));
938
+ } catch {
939
+ return null;
940
+ }
941
+ }
942
+ return asRecord(raw);
943
+ },
944
+ serialize(params) {
945
+ return params;
946
+ },
947
+ getDisplayId(params) {
948
+ return nonEmpty(params?.hiringId);
949
+ },
950
+ encode(state) {
951
+ return JSON.stringify(state ?? null);
952
+ },
953
+ decode(blob) {
954
+ if (!blob) return null;
955
+ try {
956
+ return JSON.parse(blob);
957
+ } catch {
958
+ return null;
959
+ }
960
+ }
961
+ };
962
+ function createCanonicalServerAdapter() {
963
+ return {
964
+ type,
965
+ execute: executePaperclip,
966
+ testEnvironment: testPaperclipEnvironment,
967
+ sessionCodec: paperclipSessionCodec,
968
+ getConfigSchema: getPaperclipConfigSchema,
969
+ agentConfigurationDoc
970
+ };
971
+ }
972
+
429
973
  // server/src/crypto.ts
430
974
  async function verifyWebhookSignature(rawBody, signature, secret) {
431
975
  const crypto = await import("crypto");
@@ -910,11 +1454,11 @@ async function startWebhookListener(config) {
910
1454
  if (resolvedTaskId) {
911
1455
  const pending = pendingTasks.get(resolvedTaskId);
912
1456
  if (pending && !pending.settled) {
913
- const status = payload.status ?? pending.status;
914
- pending.status = status;
1457
+ const status2 = payload.status ?? pending.status;
1458
+ pending.status = status2;
915
1459
  pending.progressPercent = payload.progress_percent ?? pending.progressPercent;
916
1460
  pending.progressMessage = payload.progress_message ?? pending.progressMessage;
917
- if (status === "completed") {
1461
+ if (status2 === "completed") {
918
1462
  pending.settled = true;
919
1463
  pending.resolve({
920
1464
  success: true,
@@ -922,7 +1466,7 @@ async function startWebhookListener(config) {
922
1466
  taskId: resolvedTaskId,
923
1467
  durationMs: Date.now() - pending.startedAt
924
1468
  });
925
- } else if (status === "failed") {
1469
+ } else if (status2 === "failed") {
926
1470
  pending.settled = true;
927
1471
  pending.resolve({
928
1472
  success: false,
@@ -930,7 +1474,7 @@ async function startWebhookListener(config) {
930
1474
  taskId: resolvedTaskId,
931
1475
  durationMs: Date.now() - pending.startedAt
932
1476
  });
933
- } else if (status === "cancelled") {
1477
+ } else if (status2 === "cancelled") {
934
1478
  pending.settled = true;
935
1479
  pending.resolve({
936
1480
  success: false,
@@ -1161,9 +1705,9 @@ async function cancelTask(config, taskId) {
1161
1705
  var TASK_MAX_RETRIES = 2;
1162
1706
  var TASK_RETRY_BASE_DELAY_MS = 1e3;
1163
1707
  var TASK_RETRY_MAX_DELAY_MS = 3e4;
1164
- async function hireAgent(config, agentDid) {
1708
+ async function hireAgent(config, agentDid, options) {
1165
1709
  const client = new AgrentingClient(config);
1166
- return client.hireAgent(agentDid);
1710
+ return client.hireAgent(agentDid, options);
1167
1711
  }
1168
1712
  async function getAgentProfile(config, agentDid) {
1169
1713
  const client = new AgrentingClient(config);
@@ -1205,6 +1749,10 @@ async function listHirings(config, options) {
1205
1749
  const client = new AgrentingClient(config);
1206
1750
  return client.listHirings(options);
1207
1751
  }
1752
+ async function cancelHiring(config, hiringId) {
1753
+ const client = new AgrentingClient(config);
1754
+ return client.cancelHiring(hiringId);
1755
+ }
1208
1756
  async function autoSelectAgent(config, options) {
1209
1757
  const client = new AgrentingClient(config);
1210
1758
  const capabilities = await client.listCapabilities();
@@ -1230,7 +1778,8 @@ async function autoSelectAgent(config, options) {
1230
1778
  const minRep = options.minReputation;
1231
1779
  filtered = filtered.filter((a) => {
1232
1780
  if (!a.reputation_score) return false;
1233
- return a.reputation_score >= minRep;
1781
+ const reputation = Number(a.reputation_score);
1782
+ return Number.isFinite(reputation) && reputation >= minRep;
1234
1783
  });
1235
1784
  }
1236
1785
  if (filtered.length === 0) {
@@ -1241,15 +1790,17 @@ async function autoSelectAgent(config, options) {
1241
1790
  const sortBy = options.sortBy ?? "reputation_score";
1242
1791
  if (options.preferAvailable ?? true) {
1243
1792
  filtered.sort((a, b) => {
1244
- const aAvail = a.availability_status === "available" ? 0 : 1;
1245
- const bAvail = b.availability_status === "available" ? 0 : 1;
1793
+ const aAvail = (a.availability_status ?? a.availability ?? a.status) === "available" ? 0 : 1;
1794
+ const bAvail = (b.availability_status ?? b.availability ?? b.status) === "available" ? 0 : 1;
1246
1795
  if (aAvail !== bAvail) return aAvail - bAvail;
1247
1796
  return 0;
1248
1797
  });
1249
1798
  }
1250
1799
  filtered.sort((a, b) => {
1251
1800
  if (sortBy === "reputation_score") {
1252
- return (b.reputation_score ?? 0) - (a.reputation_score ?? 0);
1801
+ const aReputation = Number(a.reputation_score ?? 0);
1802
+ const bReputation = Number(b.reputation_score ?? 0);
1803
+ return (Number.isFinite(bReputation) ? bReputation : 0) - (Number.isFinite(aReputation) ? aReputation : 0);
1253
1804
  }
1254
1805
  if (sortBy === "base_price") {
1255
1806
  const aPrice = parseFloat(a.base_price ?? "999999");
@@ -1257,14 +1808,19 @@ async function autoSelectAgent(config, options) {
1257
1808
  return aPrice - bPrice;
1258
1809
  }
1259
1810
  if (sortBy === "availability") {
1260
- const aAvail = a.availability_status === "available" ? 0 : 1;
1261
- const bAvail = b.availability_status === "available" ? 0 : 1;
1811
+ const aAvail = (a.availability_status ?? a.availability ?? a.status) === "available" ? 0 : 1;
1812
+ const bAvail = (b.availability_status ?? b.availability ?? b.status) === "available" ? 0 : 1;
1262
1813
  return aAvail - bAvail;
1263
1814
  }
1264
1815
  return 0;
1265
1816
  });
1266
1817
  const selectedAgent = filtered[0];
1267
- const hireResult = await client.hireAgent(selectedAgent.did);
1818
+ const hireResult = await client.hireAgent(selectedAgent.did, {
1819
+ taskDescription: options.taskDescription,
1820
+ capabilityRequested: options.capability,
1821
+ price: selectedAgent.base_price ?? options.maxPrice ?? "0",
1822
+ deliveryMode: "output"
1823
+ });
1268
1824
  return {
1269
1825
  ...hireResult,
1270
1826
  selectedAgent
@@ -1331,12 +1887,63 @@ function processIncomingMessage2(message) {
1331
1887
  const senderName = message.sender_name ?? "Agent";
1332
1888
  return formatAgentResponse(senderName, message.content);
1333
1889
  }
1890
+ async function detectModel(config) {
1891
+ if (!config.agentDid) return null;
1892
+ try {
1893
+ const profile = await getAgentProfile(config, config.agentDid);
1894
+ const provider = profile.ai_provider ?? null;
1895
+ const model = profile.ai_model ?? null;
1896
+ if (provider && model) return { provider, model };
1897
+ } catch {
1898
+ }
1899
+ return null;
1900
+ }
1901
+ async function listSkills(config) {
1902
+ const profile = await getAgentProfile(config, config.agentDid);
1903
+ const capabilities = profile.capabilities ?? [];
1904
+ return capabilities.map((cap) => ({
1905
+ id: `${config.agentDid}:${cap}`,
1906
+ name: cap,
1907
+ description: `Capability "${cap}" of agent ${config.agentDid}`
1908
+ }));
1909
+ }
1910
+ async function syncSkills(config, existing) {
1911
+ const remote = await listSkills(config);
1912
+ const remoteIds = new Set(remote.map((s) => s.id));
1913
+ const existingIds = new Set(existing.map((s) => s.id));
1914
+ return {
1915
+ added: remote.filter((s) => !existingIds.has(s.id)),
1916
+ removed: existing.filter((s) => !remoteIds.has(s.id))
1917
+ };
1918
+ }
1919
+ var sessionCodec = paperclipSessionCodec;
1920
+ async function invoke(config, params) {
1921
+ return execute(config, params);
1922
+ }
1923
+ async function status(config, taskId) {
1924
+ const progress = await getTaskProgress(config, taskId);
1925
+ return {
1926
+ status: progress.status,
1927
+ progressPercent: progress.progressPercent,
1928
+ progressMessage: progress.progressMessage
1929
+ };
1930
+ }
1931
+ async function cancel(config, taskId) {
1932
+ const result = await cancelTask(config, taskId);
1933
+ if (!result.success) {
1934
+ throw new Error(result.error ?? "Failed to cancel task");
1935
+ }
1936
+ }
1334
1937
  function createServerAdapter() {
1938
+ const canonical = createCanonicalServerAdapter();
1335
1939
  return {
1940
+ ...canonical,
1336
1941
  name: "agrenting",
1337
- execute,
1338
- testEnvironment,
1339
- getConfigSchema,
1942
+ // Legacy helpers remain available under explicit names. The canonical
1943
+ // Paperclip keys above use AdapterExecutionContext and structured tests.
1944
+ legacyExecute: execute,
1945
+ legacyTestEnvironment: testEnvironment,
1946
+ getLegacyConfigSchema: getConfigSchema,
1340
1947
  startWebhookListener,
1341
1948
  stopWebhookListener,
1342
1949
  registerWebhook,
@@ -1354,6 +1961,7 @@ function createServerAdapter() {
1354
1961
  sendMessageToHiring,
1355
1962
  getHiringMessages,
1356
1963
  retryHiring,
1964
+ cancelHiring,
1357
1965
  getHiring,
1358
1966
  listHirings,
1359
1967
  autoSelectAgent,
@@ -1363,20 +1971,65 @@ function createServerAdapter() {
1363
1971
  getBalance,
1364
1972
  getTransactions,
1365
1973
  deposit,
1366
- withdraw
1974
+ withdraw,
1975
+ // Legacy pre-0.4 contract additions:
1976
+ invoke,
1977
+ status,
1978
+ cancel,
1979
+ legacyDetectModel: detectModel,
1980
+ legacyListSkills: listSkills,
1981
+ legacySyncSkills: syncSkills
1367
1982
  };
1368
1983
  }
1984
+
1985
+ // server/src/apps-v2.ts
1986
+ var agrentingAppGalleryEntry = {
1987
+ key: "agrenting",
1988
+ name: "Agrenting",
1989
+ logoUrl: "https://www.google.com/s2/favicons?domain=agrenting.com&sz=64",
1990
+ tagline: "Discover and hire governed remote AI agents.",
1991
+ description: "Give Paperclip agents governed access to discover, hire, monitor, and cancel remote agents in the Agrenting marketplace.",
1992
+ authKind: "api_key",
1993
+ transportTemplate: {
1994
+ transport: "remote_http",
1995
+ url: "https://agrenting.com/mcp/hirer"
1996
+ },
1997
+ credentialFields: [
1998
+ {
1999
+ label: "Agrenting API key",
2000
+ configPath: "credentials.authorization",
2001
+ helpUrl: "https://agrenting.com/dashboard/api-keys",
2002
+ required: true,
2003
+ placement: "header",
2004
+ key: "Authorization",
2005
+ prefix: "Bearer "
2006
+ }
2007
+ ],
2008
+ recommendedDefaults: {
2009
+ access: "all_agents",
2010
+ askFirstRiskLevels: ["write", "destructive"]
2011
+ },
2012
+ urlPatterns: ["https://agrenting.com/mcp/hirer*"]
2013
+ };
1369
2014
  // Annotate the CommonJS export names for ESM import in node:
1370
2015
  0 && (module.exports = {
1371
2016
  AgrentingClient,
1372
2017
  MAX_POLLS,
1373
2018
  POLL_INTERVALS_MS,
2019
+ agentConfigurationDoc,
2020
+ agrentingAppGalleryEntry,
1374
2021
  autoSelectAgent,
1375
2022
  canSubmitTask,
2023
+ cancel,
2024
+ cancelHiring,
2025
+ cancelTask,
1376
2026
  checkBalance,
1377
2027
  createServerAdapter,
1378
2028
  createWebhookHandler,
1379
2029
  deregisterWebhook,
2030
+ detectModel,
2031
+ execute,
2032
+ executePaperclip,
1380
2033
  executeWithRetry,
1381
2034
  formatAgentResponse,
1382
2035
  formatInsufficientBalanceComment,
@@ -1385,13 +2038,20 @@ function createServerAdapter() {
1385
2038
  getActiveTaskMappings,
1386
2039
  getAgentProfile,
1387
2040
  getBackoffMs,
2041
+ getConfigSchema,
1388
2042
  getHiring,
1389
2043
  getHiringMessages,
2044
+ getPaperclipConfigSchema,
1390
2045
  getTaskMessages,
2046
+ getTaskProgress,
1391
2047
  getWebhookGracePeriodMs,
1392
2048
  hireAgent,
2049
+ invoke,
2050
+ label,
1393
2051
  listCapabilities,
1394
2052
  listHirings,
2053
+ listSkills,
2054
+ paperclipSessionCodec,
1395
2055
  pollTaskUntilDone,
1396
2056
  processIncomingMessage,
1397
2057
  reassignTask,
@@ -1400,6 +2060,12 @@ function createServerAdapter() {
1400
2060
  retryHiring,
1401
2061
  sendMessageToHiring,
1402
2062
  sendMessageToTask,
2063
+ sessionCodec,
2064
+ status,
2065
+ syncSkills,
2066
+ testEnvironment,
2067
+ testPaperclipEnvironment,
2068
+ type,
1403
2069
  unregisterTaskMapping,
1404
2070
  verifyWebhookSignature
1405
2071
  });