opencrowd 0.9.0 → 0.10.1

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/README.md CHANGED
@@ -88,9 +88,16 @@ Paid services settle in USDC on the network you prefer when they offer it:
88
88
  x402 on Base (the default) or Solana, MPP on Tempo, and x402 on Monad when
89
89
  you choose it. Solana uses AgentCash's separate Solana address
90
90
  (`~/.agentcash/solana-wallet.json`); `opencrowd wallet accounts` lists every
91
- network's address and balance. When a service does not offer your preferred
92
- network, the first of Base, Solana and Tempo whose balance covers the price is
93
- used.
91
+ network's address and balance. When your preferred network is not offered or
92
+ its balance does not cover the price, the first of Base, Solana and Tempo whose
93
+ balance covers it is used.
94
+
95
+ `find_paid_service` searches across all of these networks. With CrowdCode on,
96
+ CrowdCode merges AgentCash search with Coinbase's x402 catalog, the mpp.dev MPP
97
+ catalog and its reviewed services, and ranks the results: quality first, then
98
+ the effective price on each service's best network (including a transfer
99
+ between your own networks when that network's balance is short), then your
100
+ balance there. With CrowdCode off, AgentCash's order is used.
94
101
 
95
102
  ```sh
96
103
  opencrowd config set payment-network solana # or --payment-network solana per run
@@ -16497,7 +16497,7 @@ function normalizeConfig(value) {
16497
16497
  const proxyDefaults = providerDefaults(raw["openrouter-x402-proxy"] ?? raw.x402, DEFAULT_CONFIG["openrouter-x402-proxy"]);
16498
16498
  const mcpServers = recordValue(raw.mcpServers);
16499
16499
  const servers = { ...DEFAULT_CONFIG.mcpServers, ...mcpServers };
16500
- if (servers.crowdcode?.command === "npx" && ["0.5", "0.5.2", "0.5.3", "0.6.0", "0.7.0"].some((version6) => JSON.stringify(servers.crowdcode.args) === JSON.stringify(["--yes", `crowdcode-mcp@${version6}`]))) {
16500
+ if (servers.crowdcode?.command === "npx" && ["0.5", "0.5.2", "0.5.3", "0.6.0", "0.7.0", "0.8.0", "0.9.0"].some((version6) => JSON.stringify(servers.crowdcode.args) === JSON.stringify(["--yes", `crowdcode-mcp@${version6}`]))) {
16501
16501
  servers.crowdcode = { ...servers.crowdcode, args: [...DEFAULT_CONFIG.mcpServers.crowdcode.args] };
16502
16502
  }
16503
16503
  return {
@@ -16569,7 +16569,7 @@ var init_config = __esm({
16569
16569
  configVersion: 3,
16570
16570
  mcpServers: {
16571
16571
  agentcash: { command: "npx", args: ["--yes", "agentcash@0.17"] },
16572
- crowdcode: { command: "npx", args: ["--yes", "crowdcode-mcp@0.8.0"] }
16572
+ crowdcode: { command: "npx", args: ["--yes", "crowdcode-mcp@0.9.1"] }
16573
16573
  },
16574
16574
  // GAIA provider benchmark (2026-08-25): same-model BlockRun matched the
16575
16575
  // current route's accuracy and completed the sample 6.7x faster. The
@@ -57094,9 +57094,6 @@ function selectOffer(offers, selection) {
57094
57094
  const payable = offers.filter((offer) => offer.rail !== "unsupported" && selection.rails.includes(offer.rail));
57095
57095
  const cheapest = (name) => payable.filter((offer) => offer.network_name === name).sort((a, b) => (a.price_usd ?? Infinity) - (b.price_usd ?? Infinity))[0];
57096
57096
  const order = [preferred, ...FALLBACK_ORDER.filter((name) => name !== preferred)];
57097
- const first = cheapest(preferred);
57098
- if (first)
57099
- return first;
57100
57097
  const candidates = order.map(cheapest).filter((offer) => offer !== void 0);
57101
57098
  const balances = selection.balances;
57102
57099
  return balances && candidates.find((offer) => (balances[offer.network_name] ?? 0) >= (offer.price_usd ?? 0)) || candidates[0];
@@ -57135,12 +57132,54 @@ var init_offers = __esm({
57135
57132
  });
57136
57133
 
57137
57134
  // ../../packages/economy/dist/discovery.js
57138
- var BaseServiceDiscovery;
57135
+ function searchResults(raw) {
57136
+ const body = raw?.results?.results ? raw.results : raw?.data?.results ? raw.data : raw;
57137
+ return Array.isArray(body?.results) ? body.results : Array.isArray(body?.services) ? body.services : [];
57138
+ }
57139
+ function agentCashCandidate(result) {
57140
+ try {
57141
+ const endpoint = result.endpoint ?? new URL(result.path, typeof result.origin === "string" ? result.origin : result.origin?.url).href;
57142
+ const usage = result.signals?.originUsage?.uniqueUserCount;
57143
+ return {
57144
+ endpoint,
57145
+ method: result.method,
57146
+ name: result.summary ?? result.name,
57147
+ source: "agentcash",
57148
+ ...result.price !== void 0 ? { price_usd: Number.parseFloat(String(result.price)) || void 0 } : {},
57149
+ ...Array.isArray(result.origin?.protocols) ? { protocols: result.origin.protocols } : {},
57150
+ ...typeof usage === "number" ? { unique_payers: usage } : {}
57151
+ };
57152
+ } catch {
57153
+ return void 0;
57154
+ }
57155
+ }
57156
+ function ranked(ranking, schema) {
57157
+ const best = ranking.best ?? {};
57158
+ const offered = Array.isArray(schema.offers) && schema.offers.some((offer) => offer.payable && offer.network === best.network);
57159
+ const quality = ranking.quality ?? {};
57160
+ return {
57161
+ rank: ranking.rank,
57162
+ ...offered ? { best_network: best.network } : {},
57163
+ ...typeof best.effective_price_usd === "number" ? { effective_price_usd: best.effective_price_usd } : {},
57164
+ ...best.transfer_needed ? { transfer_needed: true } : {},
57165
+ quality: {
57166
+ score: quality.score,
57167
+ n_eff: quality.n_eff,
57168
+ reviews: quality.num_reviews,
57169
+ verified_reviews: quality.num_verified_reviews,
57170
+ ...quality.unique_payers_30d != null ? { payers_30d: quality.unique_payers_30d } : {},
57171
+ ...quality.below_floor ? { below_floor: true } : {}
57172
+ },
57173
+ sources: ranking.sources
57174
+ };
57175
+ }
57176
+ var RANKED_NOTE, BaseServiceDiscovery;
57139
57177
  var init_discovery = __esm({
57140
57178
  "../../packages/economy/dist/discovery.js"() {
57141
57179
  "use strict";
57142
57180
  init_dist3();
57143
57181
  init_offers();
57182
+ RANKED_NOTE = "Ranked by CrowdCode across Base, Solana and Tempo (AgentCash, Coinbase's x402 catalog, MPP catalog and CrowdCode reviews): quality first, then effective price on 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. transfer_needed means your balance on best_network is short: move USDC between your own networks first if you have a tool for that; otherwise say which network needs funds. New/unreviewed services are allowed. Inspect the chosen endpoint before paying.";
57144
57183
  BaseServiceDiscovery = class {
57145
57184
  searchApi;
57146
57185
  options;
@@ -57197,11 +57236,11 @@ var init_discovery = __esm({
57197
57236
  this.cache.set(key, { until: Date.now() + 6e4, data });
57198
57237
  return { ok: true, data };
57199
57238
  }
57200
- /** Balances are read only when they can change the choice: the preferred network is absent and several are payable. */
57239
+ /** Balances are read only when they can change the choice: several networks are payable. */
57201
57240
  async select(offers) {
57202
57241
  const selection = { preferred: this.options.paymentNetwork, rails: this.rails };
57203
57242
  const networks = payableNetworks(offers, this.rails);
57204
- const needsBalances = !this.options.negotiated && this.options.balances && networks.length > 1 && !networks.includes(this.options.paymentNetwork ?? "base");
57243
+ const needsBalances = !this.options.negotiated && this.options.balances && networks.length > 1;
57205
57244
  const balances = needsBalances ? await this.options.balances().catch(() => void 0) : void 0;
57206
57245
  return selectOffer(offers, { ...selection, balances });
57207
57246
  }
@@ -57216,25 +57255,33 @@ var init_discovery = __esm({
57216
57255
  return `The hosted gateway pays the cheapest of ${networks.join(", ")} that your balance on that network covers; price_usd is the ceiling across them. ${quote}`;
57217
57256
  }
57218
57257
  const preferred = this.options.paymentNetwork ?? "base";
57219
- return `Pays ${networkLabel(offer)}${offer.network_name === preferred ? "" : ` (${preferred} is not offered)`}. ${quote}`;
57258
+ const reason = payableNetworks(offers, this.rails).includes(preferred) ? `your ${preferred} balance does not cover it` : `${preferred} is not offered`;
57259
+ return `Pays ${networkLabel(offer)}${offer.network_name === preferred ? "" : ` (${reason})`}. ${quote}`;
57220
57260
  }
57261
+ /** Probe candidates in order and keep the first `limit` this payer can settle. */
57221
57262
  async verified(candidates, limit) {
57222
57263
  const services = [];
57223
57264
  for (let i = 0; i < candidates.length && services.length < limit; i += 4) {
57224
57265
  const batch = await Promise.all(candidates.slice(i, i + 4).map(async (c) => {
57225
57266
  try {
57226
57267
  const endpoint = c.endpoint ?? new URL(c.path, typeof c.origin === "string" ? c.origin : c.origin?.url).href;
57227
- const inspection = await this.inspect({ url: endpoint, method: c.method });
57268
+ let method = c.method ?? "POST";
57269
+ let inspection = await this.inspect({ url: endpoint, method });
57270
+ if (!inspection.ok && !c.method)
57271
+ inspection = await this.inspect({ url: endpoint, method: method = "GET" });
57228
57272
  const schema = inspection.data;
57229
- return inspection.ok && schema?.payable ? {
57273
+ if (!inspection.ok || !schema?.payable)
57274
+ return void 0;
57275
+ const service = {
57230
57276
  endpoint,
57231
- method: c.method ?? "POST",
57277
+ method,
57232
57278
  name: c.summary ?? c.name,
57233
57279
  price_usd: schema.price_usd,
57234
57280
  payment_provider: schema.rail === "mppx" ? "mppx" : "x402",
57235
57281
  network: schema.network ?? BASE_NETWORK,
57236
57282
  payable: true
57237
- } : void 0;
57283
+ };
57284
+ return c.ranking ? { ...service, ...ranked(c.ranking, schema) } : service;
57238
57285
  } catch {
57239
57286
  return void 0;
57240
57287
  }
@@ -57243,17 +57290,43 @@ var init_discovery = __esm({
57243
57290
  }
57244
57291
  return services.slice(0, limit);
57245
57292
  }
57293
+ /** The networks this payer settles, as CrowdCode names them. */
57294
+ networks() {
57295
+ return [...new Set(this.rails.filter((rail) => rail !== "unsupported").map((rail) => RAIL_NETWORK[rail]))];
57296
+ }
57297
+ /**
57298
+ * AgentCash search, ranked across chains by CrowdCode when a ranker is
57299
+ * supplied: CrowdCode adds Coinbase's x402 catalog, the MPP catalog and its
57300
+ * reviewed services, then orders everything by quality, effective price and
57301
+ * balance. Without a ranker, or if ranking fails, AgentCash's order stands.
57302
+ */
57246
57303
  async search(query, options = {}) {
57247
57304
  const limit = Math.min(20, Math.max(1, options.limit ?? 8));
57248
- const result = await this.searchApi(query, { ...options, limit: Math.min(50, limit * 3) });
57305
+ const { rank, ...searchOptions } = options;
57306
+ const [result, balances] = await Promise.all([
57307
+ this.searchApi(query, { ...searchOptions, limit: Math.min(50, limit * 3) }).catch((error53) => ({ ok: false, error: error53.message })),
57308
+ rank && this.options.balances ? this.options.balances().catch(() => void 0) : Promise.resolve(void 0)
57309
+ ]);
57310
+ const candidates = result.ok ? searchResults(result.data) : [];
57311
+ if (rank) {
57312
+ const ranking = await rank({
57313
+ query,
57314
+ networks: this.networks(),
57315
+ ...balances ? { balances } : {},
57316
+ candidates: candidates.map(agentCashCandidate).filter((candidate) => candidate !== void 0),
57317
+ limit: Math.min(25, limit * 2)
57318
+ }).catch((error53) => ({ ok: false, error: error53.message }));
57319
+ const services = ranking.data?.services;
57320
+ if (ranking.ok && Array.isArray(services)) {
57321
+ const ordered = services.filter((service) => typeof service?.endpoint === "string").map((service) => ({ endpoint: service.endpoint, method: service.method ?? void 0, name: service.name, ranking: service }));
57322
+ return { ok: true, data: { services: await this.verified(ordered, limit), note: RANKED_NOTE } };
57323
+ }
57324
+ }
57249
57325
  if (!result.ok)
57250
57326
  return result;
57251
- const raw = result.data;
57252
- const body = raw?.results?.results ? raw.results : raw?.data?.results ? raw.data : raw;
57253
- const candidates = Array.isArray(body?.results) ? body.results : Array.isArray(body?.services) ? body.services : [];
57254
57327
  return { ok: true, data: {
57255
57328
  services: await this.verified(candidates, limit),
57256
- note: `AgentCash discovery, verified ${describeRails(this.rails)}. New/unreviewed services are allowed. Inspect the chosen endpoint before paying.`
57329
+ note: `AgentCash discovery${rank ? " (CrowdCode ranking unavailable)" : ""}, verified ${describeRails(this.rails)}. New/unreviewed services are allowed. Inspect the chosen endpoint before paying.`
57257
57330
  } };
57258
57331
  }
57259
57332
  async discoverEndpoints(origin) {
@@ -57653,6 +57726,9 @@ var init_crowdcode = __esm({
57653
57726
  async manage(name, args) {
57654
57727
  return this.connection.call(name, args);
57655
57728
  }
57729
+ async findServices(search) {
57730
+ return this.connection.call("find_service", { ...search });
57731
+ }
57656
57732
  async getServiceScore(query) {
57657
57733
  const result = await this.connection.call("get_service_score", {
57658
57734
  ...query.apiEndpoint ? { api_endpoint: query.apiEndpoint } : {},
@@ -57872,6 +57948,38 @@ var init_purchases = __esm({
57872
57948
  // ../../packages/economy/dist/gateway.js
57873
57949
  import { readFile as readFile10, writeFile as writeFile5 } from "node:fs/promises";
57874
57950
  import { join as join12 } from "node:path";
57951
+ function requestFor(endpoint, method, body) {
57952
+ if (method !== "GET" && method !== "HEAD")
57953
+ return { url: endpoint, body };
57954
+ let fields = body;
57955
+ if (typeof fields === "string") {
57956
+ try {
57957
+ fields = JSON.parse(fields);
57958
+ } catch {
57959
+ return { url: endpoint, body };
57960
+ }
57961
+ }
57962
+ if (fields === void 0 || fields === null)
57963
+ return { url: endpoint, body: void 0 };
57964
+ if (typeof fields !== "object" || Array.isArray(fields))
57965
+ return { url: endpoint, body };
57966
+ let url3;
57967
+ try {
57968
+ url3 = new URL(endpoint);
57969
+ } catch {
57970
+ return { url: endpoint, body };
57971
+ }
57972
+ for (const [key, value] of Object.entries(fields)) {
57973
+ for (const item of Array.isArray(value) ? value : [value]) {
57974
+ if (item === void 0 || item === null)
57975
+ continue;
57976
+ if (typeof item === "object")
57977
+ return { url: endpoint, body };
57978
+ url3.searchParams.append(key, String(item));
57979
+ }
57980
+ }
57981
+ return { url: url3.href, body: void 0 };
57982
+ }
57875
57983
  function inspectionKey(endpoint, method) {
57876
57984
  return `${method} ${endpoint}`;
57877
57985
  }
@@ -58074,7 +58182,9 @@ var init_gateway = __esm({
58074
58182
  return result.ok ? { ok: true, data: result.data } : { ok: false, error: result.error };
58075
58183
  }
58076
58184
  if (query) {
58077
- const result = await this.options.agentcash.search(query, { limit: intArg(args.limit) });
58185
+ const findServices = this.options.crowdcode.findServices?.bind(this.options.crowdcode);
58186
+ const rank = findServices && await this.crowdcodeEnabled().catch(() => false) ? findServices : void 0;
58187
+ const result = await this.options.agentcash.search(query, { limit: intArg(args.limit), ...rank ? { rank } : {} });
58078
58188
  return result.ok ? { ok: true, data: result.data } : { ok: false, error: result.error };
58079
58189
  }
58080
58190
  return { ok: false, error: "find_paid_service requires `origin` (known origin) or `query` (capability search)" };
@@ -58144,6 +58254,7 @@ var init_gateway = __esm({
58144
58254
  return { ok: false, error: "call_paid_service requires `url`" };
58145
58255
  }
58146
58256
  const method = (stringArg(args.method) ?? "POST").toUpperCase();
58257
+ const { url: target, body } = requestFor(endpoint, method, args.body);
58147
58258
  const inspection = (await this.loadInspections()).get(inspectionKey(endpoint, method));
58148
58259
  if (!inspection) {
58149
58260
  return { ok: false, error: `inspect_paid_service must run for ${method} ${endpoint} before call_paid_service` };
@@ -58159,7 +58270,7 @@ var init_gateway = __esm({
58159
58270
  let quotedCostCents = requestedCostCents;
58160
58271
  let quoteIdentity;
58161
58272
  if (session.query && !this.options.hostedSpending) {
58162
- const fresh = await this.options.agentcash.checkEndpointSchema({ url: endpoint, method, body: args.body });
58273
+ const fresh = await this.options.agentcash.checkEndpointSchema({ url: target, method, body });
58163
58274
  if (!fresh.ok)
58164
58275
  return { ok: false, error: fresh.error };
58165
58276
  if (railFromSchema(fresh.data) !== inspection.rail)
@@ -58191,15 +58302,15 @@ var init_gateway = __esm({
58191
58302
  return { ok: false, error: approval.error };
58192
58303
  }
58193
58304
  this.options.onProgress?.({ type: "reserving_spend", message: `Reserving ${quotedCostCents} cents` });
58194
- const reservation = this.options.hostedSpending ? { id: "hosted", amountCents: 0 } : await reserveBudget(session, quotedCostCents, { description: `Paid tool \xB7 ${endpoint}`, identity: JSON.stringify({ operationId, endpoint, method, body: args.body, quoteIdentity }), forceApproval: requestedCostCents !== void 0 && quotedCostCents > requestedCostCents });
58305
+ const reservation = this.options.hostedSpending ? { id: "hosted", amountCents: 0 } : await reserveBudget(session, quotedCostCents, { description: `Paid tool \xB7 ${endpoint}`, identity: JSON.stringify({ operationId, endpoint: target, method, body, quoteIdentity }), forceApproval: requestedCostCents !== void 0 && quotedCostCents > requestedCostCents });
58195
58306
  let result;
58196
58307
  try {
58197
58308
  this.options.onProgress?.({ type: "calling_service", message: `Calling ${endpoint}` });
58198
58309
  result = await this.options.agentcash.fetch({
58199
- url: endpoint,
58310
+ url: target,
58200
58311
  operationId,
58201
58312
  method,
58202
- body: args.body,
58313
+ body,
58203
58314
  maxAmountUsd: quotedCostCents / 100,
58204
58315
  rail: inspection.rail
58205
58316
  });
@@ -58488,12 +58599,12 @@ var init_gateway = __esm({
58488
58599
  },
58489
58600
  {
58490
58601
  name: "find_paid_service",
58491
- description: "Discover paid capability: pass `origin` to list a known origin's endpoints, or `query` to search for an unknown capability.",
58602
+ description: "Discover paid capability across Base, Solana and Tempo. `query` searches every catalog (AgentCash, Coinbase's x402 catalog, MPP) and returns services you can pay, ranked best first by CrowdCode: quality, then effective price on your best network (including a transfer between your own networks when its balance is short), then your balance there. `origin` lists a known origin's endpoints.",
58492
58603
  parameters: {
58493
58604
  type: "object",
58494
58605
  properties: {
58495
58606
  origin: { type: "string", description: "Known API origin, e.g. https://stableenrich.dev" },
58496
- query: { type: "string", description: "Natural-language capability search when no origin is known" },
58607
+ query: { type: "string", description: `The capability in a few words, for example "web search", "object storage" or "solana token risk"; not the task's subject` },
58497
58608
  limit: { type: "integer", minimum: 1, description: "Maximum search results" }
58498
58609
  },
58499
58610
  additionalProperties: false
@@ -58521,7 +58632,7 @@ var init_gateway = __esm({
58521
58632
  properties: {
58522
58633
  url: { type: "string", description: "Exact endpoint URL from inspect_paid_service" },
58523
58634
  method: { type: "string", description: "HTTP method (default POST)" },
58524
- body: { description: "Request body matching the inspected schema" },
58635
+ body: { description: "Request inputs matching the inspected schema; for GET they are sent as query parameters" },
58525
58636
  max_cost_cents: { type: "integer", minimum: 1, description: "Price ceiling in cents; defaults to the inspected price" }
58526
58637
  },
58527
58638
  required: ["url"],
@@ -61558,7 +61669,7 @@ ${value.slice(-(scanLimit - prefixLength))}`;
61558
61669
  "anthropic/claude-opus-4.8",
61559
61670
  "openai/gpt-5.3-codex"
61560
61671
  ];
61561
- const ranked = features.agentDomain === "web_research" ? [
61672
+ const ranked2 = features.agentDomain === "web_research" ? [
61562
61673
  ...scoredModels,
61563
61674
  ...webResearchFallbackOrder.filter(
61564
61675
  (model2) => eligibleCandidates.includes(model2) && !scoredModels.includes(model2)
@@ -61573,16 +61684,16 @@ ${value.slice(-(scanLimit - prefixLength))}`;
61573
61684
  ...scoredModels,
61574
61685
  ...eligibleCandidates.filter((model2) => !scoredModels.includes(model2))
61575
61686
  ];
61576
- const model = ranked[0] ?? base2.model;
61687
+ const model = ranked2[0] ?? base2.model;
61577
61688
  const selectedTierConfigs = {
61578
61689
  ...tierConfigs,
61579
- [targetTier]: { primary: model, fallback: ranked.slice(1) }
61690
+ [targetTier]: { primary: model, fallback: ranked2.slice(1) }
61580
61691
  };
61581
61692
  const decision = selectModel(
61582
61693
  targetTier,
61583
61694
  base2.confidence,
61584
61695
  "portfolio",
61585
- `${base2.reasoning} | v3 task=${features.taskType} agentRisk=${features.agentRisk} deepWebResearch=${features.deepWebResearch} terminalCode=${features.implicitTerminalCode} terminalSafety=${features.terminalSafetySensitive} candidates=${ranked.length}`,
61696
+ `${base2.reasoning} | v3 task=${features.taskType} agentRisk=${features.agentRisk} deepWebResearch=${features.deepWebResearch} terminalCode=${features.implicitTerminalCode} terminalSafety=${features.terminalSafetySensitive} candidates=${ranked2.length}`,
61586
61697
  selectedTierConfigs,
61587
61698
  options.modelPricing,
61588
61699
  features.estimatedInputTokens,
@@ -61594,7 +61705,7 @@ ${value.slice(-(scanLimit - prefixLength))}`;
61594
61705
  ...decision,
61595
61706
  tierConfigs: selectedTierConfigs,
61596
61707
  profile: base2.profile,
61597
- candidates: ranked,
61708
+ candidates: ranked2,
61598
61709
  candidateScores: rankedEntries.map(({ model: model2, score, quality, cost, speed, reliability }) => ({
61599
61710
  model: model2,
61600
61711
  score,
@@ -80783,7 +80894,7 @@ function economyTools(economy, options = {}) {
80783
80894
  const status = await economy.execute("crowdcode_status", {});
80784
80895
  if (!status.ok || !status.data?.enabled)
80785
80896
  return review || void 0;
80786
- return [review, "Before your final answer, reflect once: What concrete services would have been worth paying for to solve this task? For each observed failure, poor result, excessive cost or avoidable detour with a reusable paid remedy, call request_service. Specify exact inputs, the paid deliverable, acceptance criteria, the actual use case and why paying would help. A prior purchase or spending permission is not required. Do not invent budgets. Skip generic local computation, ordinary agent mistakes without a sellable remedy, and services such as web search that already worked well. If everything worked well and cheaply, submit nothing. Then finish the user's task."].filter(Boolean).join(" ");
80897
+ return [review, "Before this run ends, reflect once: What concrete services would have been worth paying for to solve this task? For each observed failure, poor result, excessive cost or avoidable detour with a reusable paid remedy, call request_service. Specify exact inputs, the paid deliverable, acceptance criteria, the actual use case and why paying would help. A prior purchase or spending permission is not required. Do not invent budgets. Skip generic local computation, ordinary agent mistakes without a sellable remedy, and services such as web search that already worked well. If everything worked well and cheaply, submit nothing. Then call complete_session again to finish."].filter(Boolean).join(" ");
80787
80898
  }
80788
80899
  };
80789
80900
  }
@@ -81195,7 +81306,7 @@ function numberValue6(value) {
81195
81306
  }
81196
81307
  return void 0;
81197
81308
  }
81198
- var DEFAULT_CROWDCODE_BASE, LIST_CACHE_MS, PAY_TIMEOUT_MS, RECONCILE_DELAY_MS, NOT_SENT_CODES, FREE_FETCH_TIMEOUT_MS, USDC_ATOMIC_PER_USD, TRANSFER_WAIT_MS, TRANSFER_POLL_MS, MIN_PRACTICAL_MOVE_USD, RAIL_NETWORK2, ServiceDirectory, HostedAgentCashAdapter, usd, isPending, HostedCrowdCodeAdapter, HOSTED_BRIDGE_TOOL;
81309
+ var DEFAULT_CROWDCODE_BASE, LIST_CACHE_MS, FIND_TIMEOUT_MS, PAY_TIMEOUT_MS, RECONCILE_DELAY_MS, NOT_SENT_CODES, FREE_FETCH_TIMEOUT_MS, USDC_ATOMIC_PER_USD, TRANSFER_WAIT_MS, TRANSFER_POLL_MS, MIN_PRACTICAL_MOVE_USD, RAIL_NETWORK2, ServiceDirectory, HostedAgentCashAdapter, usd, isPending, HostedCrowdCodeAdapter, HOSTED_BRIDGE_TOOL;
81199
81310
  var init_hosted_economy = __esm({
81200
81311
  "../../packages/agent-runtime/dist/hosted-economy.js"() {
81201
81312
  "use strict";
@@ -81208,6 +81319,7 @@ var init_hosted_economy = __esm({
81208
81319
  init_economy_tools();
81209
81320
  DEFAULT_CROWDCODE_BASE = "https://crowdcode-backend.onrender.com";
81210
81321
  LIST_CACHE_MS = 6e4;
81322
+ FIND_TIMEOUT_MS = 2e4;
81211
81323
  PAY_TIMEOUT_MS = SERVICE_GATEWAY_TIMEOUT_MS + 3e4;
81212
81324
  RECONCILE_DELAY_MS = 2e3;
81213
81325
  NOT_SENT_CODES = /* @__PURE__ */ new Set(["ENOENT", "ECONNREFUSED", "EACCES", "ENOTDIR"]);
@@ -81281,7 +81393,7 @@ var init_hosted_economy = __esm({
81281
81393
  if (!reply.ok || result?.status !== 200)
81282
81394
  return { ok: false, error: reply.error ?? "AgentCash search unavailable" };
81283
81395
  return { ok: true, data: JSON.parse(result.body) };
81284
- }, { rails: HOSTED_RAILS, negotiated: true });
81396
+ }, { rails: HOSTED_RAILS, negotiated: true, balances: () => this.networkBalances() });
81285
81397
  }
81286
81398
  discovery;
81287
81399
  async read(url3) {
@@ -81303,6 +81415,20 @@ var init_hosted_economy = __esm({
81303
81415
  return { ok: false, error: `wallet status unavailable: ${error53.message}` };
81304
81416
  }
81305
81417
  }
81418
+ /** Available USDC per network (balance minus open holds), for ranking search results. */
81419
+ async networkBalances() {
81420
+ const reply = await postHostedTool(this.socket, "economy.balance", {});
81421
+ const networks = reply.ok ? objectValue5(reply.data)?.networks : void 0;
81422
+ const balances = {};
81423
+ for (const entry of Array.isArray(networks) ? networks : []) {
81424
+ const row = objectValue5(entry);
81425
+ const name = networkName(row?.network) ?? networkName(row?.caip2);
81426
+ const available = Number(row?.available_usd ?? row?.balance_usd);
81427
+ if (name && row?.available_usd != null && Number.isFinite(available))
81428
+ balances[name] = available;
81429
+ }
81430
+ return balances;
81431
+ }
81306
81432
  discoverEndpoints(origin) {
81307
81433
  return this.discovery.discoverEndpoints(origin);
81308
81434
  }
@@ -81530,6 +81656,21 @@ var init_hosted_economy = __esm({
81530
81656
  this.directory = directory;
81531
81657
  this.socket = socket;
81532
81658
  }
81659
+ /** CrowdCode's cross-chain search and ranking over its public API. */
81660
+ async findServices(search) {
81661
+ try {
81662
+ const response = await this.directory.fetcher(`${this.directory.base}/api/services/find`, {
81663
+ method: "POST",
81664
+ signal: AbortSignal.timeout(FIND_TIMEOUT_MS),
81665
+ headers: { accept: "application/json", "content-type": "application/json" },
81666
+ body: JSON.stringify(search)
81667
+ });
81668
+ const body = objectValue5(await response.json().catch(() => void 0));
81669
+ return response.ok && body?.ok !== false ? { ok: true, data: body } : { ok: false, error: `CrowdCode search failed (HTTP ${response.status})` };
81670
+ } catch (error53) {
81671
+ return { ok: false, error: `CrowdCode search unavailable: ${error53.message}` };
81672
+ }
81673
+ }
81533
81674
  async getServiceScore(query) {
81534
81675
  const unreviewed = { ok: true, unproven: true, summary: "not yet reviewed on CrowdCode" };
81535
81676
  let serviceId = query.serviceId;
@@ -82334,9 +82475,11 @@ async function runAgentTaskDetailed(session, task, options = {}) {
82334
82475
  ];
82335
82476
  if (options.hosted)
82336
82477
  systemPromptParts.push("You operate this service for its owner. Inference and paid tools debit its USDC wallet. When authorized to build or sell a service, take concrete steps within the owner's configured scope and budget. Ask only for missing information or permissions that materially block progress. Report actual costs and outcomes. Financial sustainability is the owner's business objective; respect their stop controls.", "You are a continuous autonomous agent, not a turn-based assistant. Maintain unfinished goals, promised follow-ups, recurring work, and conditions worth monitoring. When future work would clearly help, proactively create a persistent automation instead of waiting for the owner to say schedule. Use a one-time automation for a future reminder or follow-up and a cron automation for monitoring, recurring reports, or maintenance. It runs in this same conversation. Never create duplicates or noisy schedules, and always tell the owner exactly what you created, its timezone, and next run.", "Before finishing a substantive task, consider whether a follow-up, reminder, monitoring loop, recurring report, or maintenance check would materially advance the owner's goal; create one when the need is clear, but do not schedule ordinary one-off work.", "Financial state: the current hosted financial snapshot is authoritative; historical local session budget figures are not wallet balances and may omit inference spending. Wallet balance, pending payment holds, settled inference spend, settled service spend, and remaining run budget are separate. When the snapshot lists wallet networks, each network's balance is separate: a payment on a network needs that network's available balance. Report the wallet to the owner as one total, mentioning the per-network split only when it matters. Spending totals cover only the identified current run. A budget limit does not supply funds. An unavailable balance is unknown, not zero. The snapshot before a model call excludes that call's eventual charge; never claim a response was free or that you spent nothing merely because no paid tool ran. Use get_budget_status or get_wallet_status for refreshed financial state. Respect approval mode and per-call limits; over-limit requests require the owner's approval.");
82478
+ if (options.hosted)
82479
+ systemPromptParts.push("Files you produce: every file in this session's workspace, whether written with save_file or by a shell command, is published to the owner as a downloadable, previewable file. To point at one in your final message, write its exact workspace-relative path as plain text, for example generated/report.pdf \u2014 the owner's interface turns that path 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.");
82337
82480
  if (options.dynamicTools) {
82338
- 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.", ...options.hosted ? [
82339
- "Discover services through AgentCash search and provider catalogs. 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.",
82481
+ 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.", `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. 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 ? [
82482
+ "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.",
82340
82483
  "Moving funds: when a paid call fails with funding_required and another of your networks holds enough, move funds to a network the service accepts with bridge_usdc (at least $1, or what the task needs) and retry the same call; do not switch services just because of where funds sit. 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."
82341
82484
  ] : [], capabilityIndex ?? DEFAULT_CAPABILITY_INDEX, "When a task needs current web facts, search results, or unfamiliar page content, make one paid web search your FIRST move \u2014 do not serially guess URLs with curl; one paid search replaces minutes of blind fetching and costs less than the LLM turns it saves.", "Approval, budget, reputation, and payment rails are enforced in code \u2014 you cannot bypass them, so state costs plainly and never invent payment details.", "When the user says CrowdCode on/off, call set_crowdcode_enabled; default to session scope unless they ask for a persistent default. Use list_my_reviews and delete_my_review for user-directed review management, including while off. Before finishing a substantive task, consider concrete paid services that would fix real shortcomings and submit qualifying demand with request_service, unless CrowdCode is off. Never invent a purchase or demand.", "Each tool result includes the budget before and after that tool call. Never ask for wallet private keys or secrets.", ...options.promptSections ?? []);
82342
82485
  } else {
@@ -82382,6 +82525,8 @@ async function runAgentTaskDetailed(session, task, options = {}) {
82382
82525
  const repeatedFailures = new Map(options.resume?.repeatedFailures);
82383
82526
  let completionNudges = options.resume?.completionNudges ?? 0;
82384
82527
  let pendingFinalMessage = options.resume?.pendingFinalMessage;
82528
+ let finalAnswerHeld = options.resume?.finalAnswerHeld ?? false;
82529
+ let supersededFinalMessage;
82385
82530
  let outputContinuationNudges = options.resume?.outputContinuationNudges ?? 0;
82386
82531
  let outputContinuationPrefix = options.resume?.outputContinuationPrefix ?? "";
82387
82532
  let serviceCallFailures = options.resume?.serviceCallFailures ?? 0;
@@ -82396,6 +82541,7 @@ async function runAgentTaskDetailed(session, task, options = {}) {
82396
82541
  deliveredMessageIds: [...deliveredMessageIds],
82397
82542
  completionNudges,
82398
82543
  pendingFinalMessage,
82544
+ finalAnswerHeld,
82399
82545
  outputContinuationNudges,
82400
82546
  outputContinuationPrefix,
82401
82547
  serviceCallFailures,
@@ -82411,6 +82557,8 @@ async function runAgentTaskDetailed(session, task, options = {}) {
82411
82557
  }
82412
82558
  if (fresh.length) {
82413
82559
  pendingFinalMessage = void 0;
82560
+ finalAnswerHeld = false;
82561
+ supersededFinalMessage = void 0;
82414
82562
  outputContinuationPrefix = "";
82415
82563
  await checkpoint(turn);
82416
82564
  }
@@ -82420,6 +82568,16 @@ async function runAgentTaskDetailed(session, task, options = {}) {
82420
82568
  await options.inbox?.delivered(pending.map((m) => m.id));
82421
82569
  return fresh.length > 0;
82422
82570
  };
82571
+ const recordFinalMessage = (text) => {
82572
+ if (!text?.trim())
82573
+ return;
82574
+ if (!finalAnswerHeld)
82575
+ pendingFinalMessage = text;
82576
+ else if (text.trim() !== pendingFinalMessage?.trim())
82577
+ supersededFinalMessage = text;
82578
+ };
82579
+ const gateNudge = (blocker) => finalAnswerHeld ? `Your final answer is already saved and will be delivered to the user exactly as written; nothing you write now can replace it. Before this run ends: ${blocker} Then call complete_session again to finish; a one-line acknowledgement is enough, and do not restate the answer.` : `You cannot finish yet: ${blocker}`;
82580
+ const withReflection = (summary2) => supersededFinalMessage ? { ...summary2, reflection_note: supersededFinalMessage } : summary2;
82423
82581
  let subagentCount = 0;
82424
82582
  const backgroundSubagents = /* @__PURE__ */ new Map();
82425
82583
  const subagentLimiter = createLimiter(options.subagent?.maxParallel ?? 4);
@@ -82501,8 +82659,7 @@ ${outputContinuationPrefix}`);
82501
82659
  if (await injectMessages(turn + 1))
82502
82660
  continue;
82503
82661
  const finalMessage = response.content.trim() ? outputContinuationPrefix + response.content : pendingFinalMessage ?? outputContinuationPrefix;
82504
- if (finalMessage.trim())
82505
- pendingFinalMessage = finalMessage;
82662
+ recordFinalMessage(finalMessage);
82506
82663
  const blocker = await options.completionGate?.(completionNudges > 0);
82507
82664
  if (blocker) {
82508
82665
  if (completionNudges >= 1) {
@@ -82510,7 +82667,9 @@ ${outputContinuationPrefix}`);
82510
82667
  return { outcome: "stopped", summary: summary3, turns: turn + 1 };
82511
82668
  }
82512
82669
  completionNudges += 1;
82513
- const nudge = { role: "user", content: `You cannot finish yet: ${blocker}` };
82670
+ if (pendingFinalMessage?.trim())
82671
+ finalAnswerHeld = true;
82672
+ const nudge = { role: "user", content: gateNudge(blocker) };
82514
82673
  messages.push(nudge);
82515
82674
  await options.onMessage?.(nudge);
82516
82675
  await checkpoint(turn + 1);
@@ -82518,7 +82677,7 @@ ${outputContinuationPrefix}`);
82518
82677
  }
82519
82678
  if (!pendingFinalMessage?.trim())
82520
82679
  throw new Error("The model ended without an answer.");
82521
- const summary2 = await completeSession(session, pendingFinalMessage);
82680
+ const summary2 = withReflection(await completeSession(session, pendingFinalMessage));
82522
82681
  return { outcome: "completed", summary: summary2, turns: turn + 1 };
82523
82682
  }
82524
82683
  const spawnedThisTurn = /* @__PURE__ */ new Map();
@@ -82654,8 +82813,8 @@ ${outputContinuationPrefix}`);
82654
82813
  if (!result.ok)
82655
82814
  continue;
82656
82815
  const finalMessage = result.data && typeof result.data === "object" ? result.data.final_message : void 0;
82657
- if (typeof finalMessage === "string" && finalMessage.trim())
82658
- pendingFinalMessage = finalMessage;
82816
+ if (typeof finalMessage === "string")
82817
+ recordFinalMessage(finalMessage);
82659
82818
  const blocker = await options.completionGate?.(completionNudges > 0);
82660
82819
  if (blocker) {
82661
82820
  if (completionNudges >= 1) {
@@ -82663,9 +82822,11 @@ ${outputContinuationPrefix}`);
82663
82822
  return { outcome: "stopped", summary: summary3, turns: turn + 1 };
82664
82823
  }
82665
82824
  completionNudges += 1;
82825
+ if (pendingFinalMessage?.trim())
82826
+ finalAnswerHeld = true;
82666
82827
  const gateMessage = {
82667
82828
  role: "user",
82668
- content: `You cannot finish yet: ${blocker}`
82829
+ content: gateNudge(blocker)
82669
82830
  };
82670
82831
  messages.push(gateMessage);
82671
82832
  await options.onMessage?.(gateMessage);
@@ -82676,13 +82837,13 @@ ${outputContinuationPrefix}`);
82676
82837
  throw new Error("The model ended without an answer.");
82677
82838
  const backgroundResults = backgroundSubagents.size > 0 ? await drainBackground() : void 0;
82678
82839
  if (result.data && typeof result.data === "object") {
82679
- const summary3 = { ...result.data, final_message: pendingFinalMessage };
82840
+ const summary3 = withReflection({ ...result.data, final_message: pendingFinalMessage });
82680
82841
  if (backgroundResults) {
82681
82842
  summary3.background_subagents = backgroundResults;
82682
82843
  }
82683
82844
  return { outcome: "completed", summary: summary3, turns: turn + 1 };
82684
82845
  }
82685
- const summary2 = await completeSession(session, pendingFinalMessage);
82846
+ const summary2 = withReflection(await completeSession(session, pendingFinalMessage));
82686
82847
  if (backgroundResults) {
82687
82848
  summary2.background_subagents = backgroundResults;
82688
82849
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencrowd",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
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",