@agrentingai/paperclip-adapter 0.3.0 → 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,9 +33,12 @@ __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,
38
40
  cancel: () => cancel,
41
+ cancelHiring: () => cancelHiring,
39
42
  cancelTask: () => cancelTask,
40
43
  checkBalance: () => checkBalance,
41
44
  createServerAdapter: () => createServerAdapter,
@@ -43,6 +46,7 @@ __export(index_exports, {
43
46
  deregisterWebhook: () => deregisterWebhook,
44
47
  detectModel: () => detectModel,
45
48
  execute: () => execute,
49
+ executePaperclip: () => executePaperclip,
46
50
  executeWithRetry: () => executeWithRetry,
47
51
  formatAgentResponse: () => formatAgentResponse,
48
52
  formatInsufficientBalanceComment: () => formatInsufficientBalanceComment,
@@ -54,14 +58,17 @@ __export(index_exports, {
54
58
  getConfigSchema: () => getConfigSchema,
55
59
  getHiring: () => getHiring,
56
60
  getHiringMessages: () => getHiringMessages,
61
+ getPaperclipConfigSchema: () => getPaperclipConfigSchema,
57
62
  getTaskMessages: () => getTaskMessages,
58
63
  getTaskProgress: () => getTaskProgress,
59
64
  getWebhookGracePeriodMs: () => getWebhookGracePeriodMs,
60
65
  hireAgent: () => hireAgent,
61
66
  invoke: () => invoke,
67
+ label: () => label,
62
68
  listCapabilities: () => listCapabilities,
63
69
  listHirings: () => listHirings,
64
70
  listSkills: () => listSkills,
71
+ paperclipSessionCodec: () => paperclipSessionCodec,
65
72
  pollTaskUntilDone: () => pollTaskUntilDone,
66
73
  processIncomingMessage: () => processIncomingMessage,
67
74
  reassignTask: () => reassignTask,
@@ -74,6 +81,8 @@ __export(index_exports, {
74
81
  status: () => status,
75
82
  syncSkills: () => syncSkills,
76
83
  testEnvironment: () => testEnvironment,
84
+ testPaperclipEnvironment: () => testPaperclipEnvironment,
85
+ type: () => type,
77
86
  unregisterTaskMapping: () => unregisterTaskMapping,
78
87
  verifyWebhookSignature: () => verifyWebhookSignature
79
88
  });
@@ -82,6 +91,12 @@ module.exports = __toCommonJS(index_exports);
82
91
  // server/src/client.ts
83
92
  var DEFAULT_TIMEOUT_MS = 3e4;
84
93
  var MAX_RETRIES = 3;
94
+ var NonRetryableAgrentingError = class extends Error {
95
+ constructor(message) {
96
+ super(message);
97
+ this.name = "NonRetryableAgrentingError";
98
+ }
99
+ };
85
100
  var AgrentingClient = class {
86
101
  baseUrl;
87
102
  apiKey;
@@ -128,17 +143,22 @@ var AgrentingClient = class {
128
143
  await new Promise((resolve) => setTimeout(resolve, delayMs));
129
144
  continue;
130
145
  }
131
- throw new Error(
146
+ throw new NonRetryableAgrentingError(
132
147
  `Agrenting API ${response.status}: ${text.slice(0, 500)}`
133
148
  );
134
149
  }
135
150
  const envelope = await response.json();
136
151
  if (Array.isArray(envelope.errors) && envelope.errors.length) {
137
- throw new Error(`API errors: ${envelope.errors.join(", ")}`);
152
+ throw new NonRetryableAgrentingError(
153
+ `API errors: ${envelope.errors.join(", ")}`
154
+ );
138
155
  }
139
156
  return envelope.data ?? envelope;
140
157
  } catch (err) {
141
158
  lastError = err instanceof Error ? err : new Error(String(err));
159
+ if (lastError instanceof NonRetryableAgrentingError) {
160
+ throw lastError;
161
+ }
142
162
  if (attempt < MAX_RETRIES) {
143
163
  clearTimeout(timer);
144
164
  const delayMs = Math.min(1e3 * 2 ** attempt, 3e4);
@@ -324,12 +344,23 @@ var AgrentingClient = class {
324
344
  async getAgentProfile(agentDid) {
325
345
  return this.request("GET", `/api/v1/agents/${encodeURIComponent(agentDid)}`);
326
346
  }
327
- /** Hire/bind an agent to your account.
328
- * 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.
329
349
  */
330
- async hireAgent(agentDid, options = {}) {
331
- const body = {};
332
- 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;
333
364
  return this.request(
334
365
  "POST",
335
366
  `/api/v1/agents/${encodeURIComponent(agentDid)}/hire`,
@@ -388,13 +419,11 @@ var AgrentingClient = class {
388
419
  );
389
420
  }
390
421
  /** Get messages for a hiring.
391
- * GET /api/v1/hirings/:id/messages
422
+ * The canonical API includes recent messages in GET /api/v1/hirings/:id.
392
423
  */
393
424
  async getHiringMessages(hiringId) {
394
- return this.request(
395
- "GET",
396
- `/api/v1/hirings/${hiringId}/messages`
397
- );
425
+ const hiring = await this.getHiring(hiringId);
426
+ return hiring.messages ?? [];
398
427
  }
399
428
  /** Retry a failed hiring.
400
429
  * POST /api/v1/hirings/:id/retry
@@ -416,6 +445,15 @@ var AgrentingClient = class {
416
445
  async getHiring(hiringId) {
417
446
  return this.request("GET", `/api/v1/hirings/${hiringId}`);
418
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
+ }
419
457
  /** List hirings for the authenticated agent.
420
458
  * GET /api/v1/hirings
421
459
  */
@@ -423,9 +461,16 @@ var AgrentingClient = class {
423
461
  const params = new URLSearchParams();
424
462
  if (options.status) params.set("status", options.status);
425
463
  if (options.limit) params.set("limit", String(options.limit));
426
- 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
+ }
427
468
  const query = params.toString() ? `?${params}` : "";
428
- 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;
429
474
  }
430
475
  /** List agents filtered by capability for auto-select.
431
476
  * GET /api/v1/agents?capability=X
@@ -438,6 +483,493 @@ var AgrentingClient = class {
438
483
  }
439
484
  };
440
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
+
441
973
  // server/src/crypto.ts
442
974
  async function verifyWebhookSignature(rawBody, signature, secret) {
443
975
  const crypto = await import("crypto");
@@ -1173,9 +1705,9 @@ async function cancelTask(config, taskId) {
1173
1705
  var TASK_MAX_RETRIES = 2;
1174
1706
  var TASK_RETRY_BASE_DELAY_MS = 1e3;
1175
1707
  var TASK_RETRY_MAX_DELAY_MS = 3e4;
1176
- async function hireAgent(config, agentDid) {
1708
+ async function hireAgent(config, agentDid, options) {
1177
1709
  const client = new AgrentingClient(config);
1178
- return client.hireAgent(agentDid);
1710
+ return client.hireAgent(agentDid, options);
1179
1711
  }
1180
1712
  async function getAgentProfile(config, agentDid) {
1181
1713
  const client = new AgrentingClient(config);
@@ -1217,6 +1749,10 @@ async function listHirings(config, options) {
1217
1749
  const client = new AgrentingClient(config);
1218
1750
  return client.listHirings(options);
1219
1751
  }
1752
+ async function cancelHiring(config, hiringId) {
1753
+ const client = new AgrentingClient(config);
1754
+ return client.cancelHiring(hiringId);
1755
+ }
1220
1756
  async function autoSelectAgent(config, options) {
1221
1757
  const client = new AgrentingClient(config);
1222
1758
  const capabilities = await client.listCapabilities();
@@ -1242,7 +1778,8 @@ async function autoSelectAgent(config, options) {
1242
1778
  const minRep = options.minReputation;
1243
1779
  filtered = filtered.filter((a) => {
1244
1780
  if (!a.reputation_score) return false;
1245
- return a.reputation_score >= minRep;
1781
+ const reputation = Number(a.reputation_score);
1782
+ return Number.isFinite(reputation) && reputation >= minRep;
1246
1783
  });
1247
1784
  }
1248
1785
  if (filtered.length === 0) {
@@ -1253,15 +1790,17 @@ async function autoSelectAgent(config, options) {
1253
1790
  const sortBy = options.sortBy ?? "reputation_score";
1254
1791
  if (options.preferAvailable ?? true) {
1255
1792
  filtered.sort((a, b) => {
1256
- const aAvail = a.availability_status === "available" ? 0 : 1;
1257
- 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;
1258
1795
  if (aAvail !== bAvail) return aAvail - bAvail;
1259
1796
  return 0;
1260
1797
  });
1261
1798
  }
1262
1799
  filtered.sort((a, b) => {
1263
1800
  if (sortBy === "reputation_score") {
1264
- 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);
1265
1804
  }
1266
1805
  if (sortBy === "base_price") {
1267
1806
  const aPrice = parseFloat(a.base_price ?? "999999");
@@ -1269,14 +1808,19 @@ async function autoSelectAgent(config, options) {
1269
1808
  return aPrice - bPrice;
1270
1809
  }
1271
1810
  if (sortBy === "availability") {
1272
- const aAvail = a.availability_status === "available" ? 0 : 1;
1273
- 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;
1274
1813
  return aAvail - bAvail;
1275
1814
  }
1276
1815
  return 0;
1277
1816
  });
1278
1817
  const selectedAgent = filtered[0];
1279
- 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
+ });
1280
1824
  return {
1281
1825
  ...hireResult,
1282
1826
  selectedAgent
@@ -1372,19 +1916,7 @@ async function syncSkills(config, existing) {
1372
1916
  removed: existing.filter((s) => !remoteIds.has(s.id))
1373
1917
  };
1374
1918
  }
1375
- var sessionCodec = {
1376
- encode(state) {
1377
- return JSON.stringify(state ?? null);
1378
- },
1379
- decode(blob) {
1380
- if (!blob) return null;
1381
- try {
1382
- return JSON.parse(blob);
1383
- } catch {
1384
- return null;
1385
- }
1386
- }
1387
- };
1919
+ var sessionCodec = paperclipSessionCodec;
1388
1920
  async function invoke(config, params) {
1389
1921
  return execute(config, params);
1390
1922
  }
@@ -1403,11 +1935,15 @@ async function cancel(config, taskId) {
1403
1935
  }
1404
1936
  }
1405
1937
  function createServerAdapter() {
1938
+ const canonical = createCanonicalServerAdapter();
1406
1939
  return {
1940
+ ...canonical,
1407
1941
  name: "agrenting",
1408
- execute,
1409
- testEnvironment,
1410
- 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,
1411
1947
  startWebhookListener,
1412
1948
  stopWebhookListener,
1413
1949
  registerWebhook,
@@ -1425,6 +1961,7 @@ function createServerAdapter() {
1425
1961
  sendMessageToHiring,
1426
1962
  getHiringMessages,
1427
1963
  retryHiring,
1964
+ cancelHiring,
1428
1965
  getHiring,
1429
1966
  listHirings,
1430
1967
  autoSelectAgent,
@@ -1435,24 +1972,56 @@ function createServerAdapter() {
1435
1972
  getTransactions,
1436
1973
  deposit,
1437
1974
  withdraw,
1438
- // Canonical contract additions:
1975
+ // Legacy pre-0.4 contract additions:
1439
1976
  invoke,
1440
1977
  status,
1441
1978
  cancel,
1442
- detectModel,
1443
- listSkills,
1444
- syncSkills,
1445
- sessionCodec
1979
+ legacyDetectModel: detectModel,
1980
+ legacyListSkills: listSkills,
1981
+ legacySyncSkills: syncSkills
1446
1982
  };
1447
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
+ };
1448
2014
  // Annotate the CommonJS export names for ESM import in node:
1449
2015
  0 && (module.exports = {
1450
2016
  AgrentingClient,
1451
2017
  MAX_POLLS,
1452
2018
  POLL_INTERVALS_MS,
2019
+ agentConfigurationDoc,
2020
+ agrentingAppGalleryEntry,
1453
2021
  autoSelectAgent,
1454
2022
  canSubmitTask,
1455
2023
  cancel,
2024
+ cancelHiring,
1456
2025
  cancelTask,
1457
2026
  checkBalance,
1458
2027
  createServerAdapter,
@@ -1460,6 +2029,7 @@ function createServerAdapter() {
1460
2029
  deregisterWebhook,
1461
2030
  detectModel,
1462
2031
  execute,
2032
+ executePaperclip,
1463
2033
  executeWithRetry,
1464
2034
  formatAgentResponse,
1465
2035
  formatInsufficientBalanceComment,
@@ -1471,14 +2041,17 @@ function createServerAdapter() {
1471
2041
  getConfigSchema,
1472
2042
  getHiring,
1473
2043
  getHiringMessages,
2044
+ getPaperclipConfigSchema,
1474
2045
  getTaskMessages,
1475
2046
  getTaskProgress,
1476
2047
  getWebhookGracePeriodMs,
1477
2048
  hireAgent,
1478
2049
  invoke,
2050
+ label,
1479
2051
  listCapabilities,
1480
2052
  listHirings,
1481
2053
  listSkills,
2054
+ paperclipSessionCodec,
1482
2055
  pollTaskUntilDone,
1483
2056
  processIncomingMessage,
1484
2057
  reassignTask,
@@ -1491,6 +2064,8 @@ function createServerAdapter() {
1491
2064
  status,
1492
2065
  syncSkills,
1493
2066
  testEnvironment,
2067
+ testPaperclipEnvironment,
2068
+ type,
1494
2069
  unregisterTaskMapping,
1495
2070
  verifyWebhookSignature
1496
2071
  });