opencrowd 0.13.2 → 0.13.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.
@@ -57861,8 +57861,13 @@ async function recordReviewSubmitted(session, purchaseId, rating) {
57861
57861
  submitted_at: (/* @__PURE__ */ new Date()).toISOString()
57862
57862
  });
57863
57863
  }
57864
- async function recordReviewFailed(session, purchaseId) {
57865
- await appendPurchaseEvent(session, { type: "review_failed", purchase_id: purchaseId, attempted_at: (/* @__PURE__ */ new Date()).toISOString() });
57864
+ async function recordReviewFailed(session, purchaseId, options = {}) {
57865
+ await appendPurchaseEvent(session, {
57866
+ type: "review_failed",
57867
+ purchase_id: purchaseId,
57868
+ attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
57869
+ ...options.permanent ? { permanent: true } : {}
57870
+ });
57866
57871
  }
57867
57872
  async function skipPendingReviews(session) {
57868
57873
  for (const state of await pendingRequiredReviews(session))
@@ -57907,7 +57912,7 @@ async function listPurchases(session) {
57907
57912
  } else if (event.type === "review_failed") {
57908
57913
  const state = states.get(event.purchase_id);
57909
57914
  if (state)
57910
- state.reviewAttempts = (state.reviewAttempts ?? 0) + 1;
57915
+ state.reviewAttempts = event.permanent ? MAX_REVIEW_ATTEMPTS : (state.reviewAttempts ?? 0) + 1;
57911
57916
  } else if (event.type === "review_submitted") {
57912
57917
  const state = states.get(event.purchase_id);
57913
57918
  if (state) {
@@ -58036,7 +58041,7 @@ function numberArg(value) {
58036
58041
  function slugUrl(url3) {
58037
58042
  return url3.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "").slice(0, 80) || "service";
58038
58043
  }
58039
- var GATEWAY_TOOL_NAMES, VERIFIED_RAILS, EconomyGateway, GATEWAY_TOOL_DEFINITIONS;
58044
+ var GATEWAY_TOOL_NAMES, VERIFIED_RAILS, PERMANENT_REVIEW_REJECTION, isPermanentReviewRejection, EconomyGateway, GATEWAY_TOOL_DEFINITIONS;
58040
58045
  var init_gateway = __esm({
58041
58046
  "../../packages/economy/dist/gateway.js"() {
58042
58047
  "use strict";
@@ -58061,6 +58066,8 @@ var init_gateway = __esm({
58061
58066
  "bridge_usdc"
58062
58067
  ];
58063
58068
  VERIFIED_RAILS = ["x402-base", "x402-solana", "x402-monad", "mppx"];
58069
+ PERMANENT_REVIEW_REJECTION = /service identity conflict|payment target is shared by multiple services|service not found|unknown purchase/i;
58070
+ isPermanentReviewRejection = (error53) => !!error53 && PERMANENT_REVIEW_REJECTION.test(error53);
58064
58071
  EconomyGateway = class {
58065
58072
  options;
58066
58073
  /**
@@ -58343,6 +58350,8 @@ var init_gateway = __esm({
58343
58350
  // Confirmed paid successes AND paid failures require reviews; free,
58344
58351
  // SIWX, and unknown outcomes create no paid receipt to review.
58345
58352
  review_required: paid && supportedRail && await this.crowdcodeEnabled(),
58353
+ // Our own failure, not the seller's: recorded, never reviewable.
58354
+ ...result.callerError ? { caller_error: true } : {},
58346
58355
  // x402 Solana receipts carry no payee; CrowdCode needs it to verify a new
58347
58356
  // service, so use the inspected offer's when the payment settled on that rail.
58348
58357
  evidence: result.payment && !result.payment.payTo && inspection.payTo && railUsed === inspection.rail ? { ...result.payment, payTo: inspection.payTo } : result.payment,
@@ -58483,6 +58492,12 @@ var init_gateway = __esm({
58483
58492
  if (state.reviewStatus === "submitted") {
58484
58493
  return { ok: false, error: `purchase ${purchaseId} is already reviewed` };
58485
58494
  }
58495
+ if (state.record.caller_error) {
58496
+ return {
58497
+ ok: false,
58498
+ error: `purchase ${purchaseId} never reached ${state.record.endpoint}: it was refused before the request was sent (${state.record.notes ?? "no payment was made"}). That is not something the service did, so there is nothing to review. Fix the cause and call the service again.`
58499
+ };
58500
+ }
58486
58501
  const evidence = state.record.evidence;
58487
58502
  const result = await this.options.crowdcode.reviewService({
58488
58503
  rating,
@@ -58498,7 +58513,14 @@ var init_gateway = __esm({
58498
58513
  taskContext: stringArg(args.task_context)
58499
58514
  });
58500
58515
  if (!result.ok) {
58501
- await recordReviewFailed(this.options.session, purchaseId);
58516
+ const permanent = isPermanentReviewRejection(result.error);
58517
+ await recordReviewFailed(this.options.session, purchaseId, { permanent });
58518
+ if (permanent) {
58519
+ return {
58520
+ ok: false,
58521
+ error: `CrowdCode could not attach this review to a registered service (${result.error}). This will not change on a retry, so the review is recorded as unsendable; it no longer blocks later purchases. Do not call review_paid_service for ${purchaseId} again.`
58522
+ };
58523
+ }
58502
58524
  const deferred = (state.reviewAttempts ?? 0) + 1 >= MAX_REVIEW_ATTEMPTS;
58503
58525
  return { ok: false, error: `CrowdCode review failed (${result.error}); the review stays pending${deferred ? " for manual retry, without blocking completion or later purchases" : " \u2014 retry review_paid_service"}` };
58504
58526
  }
@@ -58850,7 +58872,10 @@ var init_mocks = __esm({
58850
58872
  }
58851
58873
  async reviewService(review) {
58852
58874
  if (this.options.failReviews) {
58853
- return { ok: false, error: "mock review submission failure" };
58875
+ return {
58876
+ ok: false,
58877
+ error: typeof this.options.failReviews === "string" ? this.options.failReviews : "mock review submission failure"
58878
+ };
58854
58879
  }
58855
58880
  this.reviews.push(review);
58856
58881
  return { ok: true, raw: { accepted: true } };
@@ -81463,7 +81488,18 @@ var init_hosted_economy = __esm({
81463
81488
  return this.discovery.inspect(input2);
81464
81489
  }
81465
81490
  async fetch(request2) {
81466
- const refuse = (error53) => ({ ok: false, ambiguous: false, data: void 0, error: error53 });
81491
+ const theirs = /* @__PURE__ */ new Set([
81492
+ // They answered 402 and sent no payment challenge at all. Nothing to
81493
+ // misparse, and nothing we could have got wrong.
81494
+ "payment_required_header_missing"
81495
+ ]);
81496
+ const refuse = (error53, code) => ({
81497
+ ok: false,
81498
+ ambiguous: false,
81499
+ data: void 0,
81500
+ error: error53,
81501
+ ...code && theirs.has(code) ? {} : { callerError: true }
81502
+ });
81467
81503
  if (!HOSTED_RAILS.includes(request2.rail)) {
81468
81504
  return refuse(`this service settles on a payment network hosted agents cannot pay; hosted agents pay ${describeRails(HOSTED_RAILS)}`);
81469
81505
  }
@@ -81540,7 +81576,7 @@ var init_hosted_economy = __esm({
81540
81576
  error: timedOut ? `the paid call did not answer within ${Math.round(timeoutMs / 1e3)}s and its stored purchase has no final result yet` : reply.error ?? "the supervisor could not determine whether the payment settled"
81541
81577
  };
81542
81578
  }
81543
- return refuse(reply.code ? `${reply.error ?? "payment refused"} (${reply.code})` : reply.error ?? "payment refused");
81579
+ return refuse(reply.code ? `${reply.error ?? "payment refused"} (${reply.code})` : reply.error ?? "payment refused", reply.code);
81544
81580
  }
81545
81581
  /**
81546
81582
  * Re-post the same purchase once. A finished attempt replays from the ledger;
@@ -82509,7 +82545,7 @@ async function runAgentTaskDetailed(session, task, options = {}) {
82509
82545
  if (options.hosted)
82510
82546
  systemPromptParts.push("Files the owner sends you: an attached file is saved in this session under uploads/ before its message reaches you, and the message names each one with its path, size and type. Read a text file with read_file at uploads/<name>, and any file with run_shell at artifacts/uploads/<name> from the session directory. Those files are already the owner's and already in their file list: work from them, and never copy or re-save one under artifacts/ to hand it back. Write your own outputs elsewhere under artifacts/; uploads/ is the owner's directory and nothing you write there is delivered. If a file the message names is missing, say so and ask the owner to send it again rather than guessing at its contents.", "Files you produce: only the artifacts/ directory of this session reaches the owner. save_file writes there; anything you write elsewhere is scratch they never see. The owner sees each file at its path without that prefix, so artifacts/generated/report.pdf is generated/report.pdf to them. Name a produced file in your final message by exactly that path as plain text and nothing else \u2014 the owner's interface turns it into a working download and preview link. Never invent a download URL, a sandbox: link, a /mnt/data path, or any other path the owner cannot open, and never claim a file exists before you have verified it.");
82511
82547
  if (options.dynamicTools) {
82512
- systemPromptParts.push("When the local computer is not the right environment, or after one clear local capability failure, buy external capability: find_paid_service to discover, inspect_paid_service to see the exact schema/price/reputation, call_paid_service to execute through the enforced purchase lifecycle, and review_paid_service for the required review after every confirmed paid call (success or failure). The same review tool accepts unpaid experiences; review relevant free results or failed attempts without inventing payment evidence. Distinguish provider faults from local errors and uncertain failures. A platform judge, not you, writes the public score and text of a paid review from the platform's record of the call; your rating and notes are evidence it reads, so report accurately and never try to talk it into a rating.", `Choosing a service: find_paid_service with a short capability query (for example "web search" or "object storage", not the task's subject) searches every catalog (AgentCash, Coinbase's x402 catalog, the MPP catalog and CrowdCode's reviews) across Base, Solana and Tempo and returns services you can pay, ranked best first by CrowdCode: quality (score and verified reviews), then effective price on the service's best network, including about $0.03 to move USDC between your own networks when that network's balance is short, then your balance there. Prefer the highest-ranked service that fits the task, whatever its network; a better or cheaper service on Solana or Tempo beats a Base default. Each result also lists \`networks\`, the networks it can be paid on; between services that fit the task equally well, one you can pay from a balance you already hold beats one that needs a transfer first, because a move costs about $0.03. When the chosen service is marked transfer_needed, move USDC between your own networks first if you have a tool for that; otherwise tell the user which network needs funds. When the ranking is unavailable, compare candidates yourself with inspect_paid_service (reputation, price and network) against your per-network balances.`, ...options.hosted ? [
82548
+ systemPromptParts.push("When the local computer is not the right environment, or after one clear local capability failure, buy external capability: find_paid_service to discover, inspect_paid_service to see the exact schema/price/reputation, call_paid_service to execute through the enforced purchase lifecycle, and review_paid_service for the required review after every confirmed paid call (success or failure). The same review tool accepts unpaid experiences; review relevant free results or failed attempts without inventing payment evidence. Distinguish provider faults from local errors and uncertain failures: a call that never reached the seller is never the seller's fault. funding_required means your USDC is on the wrong network, a cap or a declined approval means the owner's policy stopped you, and an unpayable rail means you could not pay at all \u2014 in every one of those the service was never asked, so it gets no review and certainly not a low rating. Fix the cause and call it again instead; the gateway refuses those reviews, and trying anyway wastes a turn. A platform judge, not you, writes the public score and text of a paid review from the platform's record of the call; your rating and notes are evidence it reads, so report accurately and never try to talk it into a rating.", `Choosing a service: find_paid_service with a short capability query (for example "web search" or "object storage", not the task's subject) searches every catalog (AgentCash, Coinbase's x402 catalog, the MPP catalog and CrowdCode's reviews) across Base, Solana and Tempo and returns services you can pay, ranked best first by CrowdCode: quality (score and verified reviews), then effective price on the service's best network, including about $0.03 to move USDC between your own networks when that network's balance is short, then your balance there. Prefer the highest-ranked service that fits the task, whatever its network; a better or cheaper service on Solana or Tempo beats a Base default. Each result also lists \`networks\`, the networks it can be paid on; between services that fit the task equally well, one you can pay from a balance you already hold beats one that needs a transfer first, because a move costs about $0.03. When the chosen service is marked transfer_needed, move USDC between your own networks first if you have a tool for that; otherwise tell the user which network needs funds. When the ranking is unavailable, compare candidates yourself with inspect_paid_service (reputation, price and network) against your per-network balances.`, ...options.hosted ? [
82513
82549
  "You can use new services without prior CrowdCode reviews. Payable: x402 USDC on Base or Solana and MPP USDC.e on Tempo; the hosted gateway pays the cheapest offered network your balance on that network covers. Other networks, including Monad, are not payable here. Payments stay within the user's configured limits; over-limit quotes request approval. Use read_service for authenticated job polling and never resubmit a pending generation job.",
82514
82550
  "funding_required means your USDC is on the wrong network, not that you are out of money, and a wallet with money in it finishes the job. First go back to find_paid_service and look for an equivalent service sold on a network you already hold USDC on \u2014 every result lists its `networks`, and paying from an existing balance costs no transfer fee. If none fits, move funds with bridge_usdc and retry the same call: aim for about $1 so one fee funds many later calls, and when the wallet holds less than that, move what the call needs plus the fee, as long as the fee stays under about a fifth of what moves (roughly $0.15 and up at a $0.03 fee). A move inside that rule is ordinary work, not a decision for the owner: make it and retry, exactly as you would pay for the call itself. The spending guard applies the owner's approval mode to the fee by itself and pauses the run if it has to, so you never need permission before calling bridge_usdc. Stopping to ask whether you may spend three cents moving the owner's own money to the network a seller wants is the dead end this rule exists to prevent. Only below that threshold is a move not worth making on your own: quote_only it, then ask. When the owner asks you to move funds, quote first, state the fee, then execute and report both transaction ids and the amount received. Moves follow the owner's approval mode like payments; never move funds without a reason.",
82515
82551
  "When money is what blocks a task, say exactly that and nothing else: which network the service is sold on, what you hold where, and the single thing you need \u2014 permission for a move you have quoted, or USDC added on that network. Never dress a funding problem up as the service being unsuitable or unavailable, never imply your wallet is empty when it is not, and never offer a recorded request_service as though it were the answer the owner asked for."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencrowd",
3
- "version": "0.13.2",
3
+ "version": "0.13.4",
4
4
  "description": "A CLI agent with its own wallet — discovers x402 services, pays in USDC, and completes tasks under a local budget",
5
5
  "type": "module",
6
6
  "license": "MIT",