opencrowd 0.16.0 → 0.16.2

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.
Files changed (2) hide show
  1. package/bundle/opencrowd.js +204 -73
  2. package/package.json +1 -1
@@ -15448,12 +15448,13 @@ function upstreamRefusalSummary(error53) {
15448
15448
  const detail = typeof refusal.message === "string" ? refusal.message.trim() : "";
15449
15449
  return `The model provider refused this request before any payment, so nothing was charged${detail ? `: ${detail}` : "."} You can retry or send another message.`;
15450
15450
  }
15451
- var RUN_FAILURE_MESSAGE, RUN_OUTCOMES, RunOutcomeSchema, RESUME_CAUSES, ResumeCauseSchema, RunStateSchema;
15451
+ var RUN_FAILURE_MESSAGE, PAYMENT_RECONCILING_MESSAGE, RUN_OUTCOMES, RunOutcomeSchema, RESUME_CAUSES, ResumeCauseSchema, RunStateSchema;
15452
15452
  var init_outcomes = __esm({
15453
15453
  "../../packages/protocol/dist/outcomes.js"() {
15454
15454
  "use strict";
15455
15455
  init_zod();
15456
15456
  RUN_FAILURE_MESSAGE = "Something went wrong. You can retry or send another message.";
15457
+ PAYMENT_RECONCILING_MESSAGE = "The model call was interrupted while its payment was in flight. CrowdCode is confirming that payment and will continue this run automatically; nothing is charged twice. You can also send a new message.";
15457
15458
  RUN_OUTCOMES = [
15458
15459
  "completed",
15459
15460
  "idle",
@@ -16151,15 +16152,19 @@ function isContextWindowError(value) {
16151
16152
  return true;
16152
16153
  return error53.error !== value && !!error53.error && isContextWindowError(typeof error53.error === "string" ? { message: error53.error } : error53.error);
16153
16154
  }
16154
- var MODEL_REQUEST_MAX_BYTES, MODEL_SELLER_REQUEST_MAX_BYTES, MODEL_MAX_MESSAGES, MODEL_GATEWAY_TIMEOUT_MS, MODEL_BRIDGE_TIMEOUT_MS, SERVICE_GATEWAY_TIMEOUT_MS, SERVICE_BRIDGE_TIMEOUT_MS, HOSTED_TOOL_BRIDGE_TIMEOUT_MS, HOSTED_CONTROL_TOOL_TIMEOUT_MS, SERVICE_RESPONSE_MAX_BYTES, DEFAULT_CONTEXT_WINDOW, ContextWindowExceeded, CONTEXT_COST_BUDGET_USD, CONTEXT_MIN_HISTORY_TOKENS, centsPer1kToUsdPerMillion;
16155
+ var MODEL_REQUEST_MAX_BYTES, MODEL_SELLER_REQUEST_MAX_BYTES, MODEL_MAX_MESSAGES, MODEL_PROVIDER_MAX_TIMEOUT_MS, MODEL_QUOTE_MAX_TIMEOUT_MS, MIB, MODEL_GATEWAY_TIMEOUT_MS, MODEL_GATEWAY_RETRY_BUDGET_MS, MODEL_BRIDGE_TIMEOUT_MS, SERVICE_GATEWAY_TIMEOUT_MS, SERVICE_BRIDGE_TIMEOUT_MS, HOSTED_TOOL_BRIDGE_TIMEOUT_MS, HOSTED_CONTROL_TOOL_TIMEOUT_MS, SERVICE_RESPONSE_MAX_BYTES, DEFAULT_CONTEXT_WINDOW, ContextWindowExceeded, CONTEXT_COST_BUDGET_USD, CONTEXT_MIN_HISTORY_TOKENS, centsPer1kToUsdPerMillion;
16155
16156
  var init_context = __esm({
16156
16157
  "../../packages/protocol/dist/context.js"() {
16157
16158
  "use strict";
16158
16159
  MODEL_REQUEST_MAX_BYTES = 16 * 1024 * 1024;
16159
16160
  MODEL_SELLER_REQUEST_MAX_BYTES = 5 * 1024 * 1024;
16160
16161
  MODEL_MAX_MESSAGES = 1e3;
16161
- MODEL_GATEWAY_TIMEOUT_MS = 42e4;
16162
- MODEL_BRIDGE_TIMEOUT_MS = 2 * MODEL_GATEWAY_TIMEOUT_MS + 3e4;
16162
+ MODEL_PROVIDER_MAX_TIMEOUT_MS = 48e4;
16163
+ MODEL_QUOTE_MAX_TIMEOUT_MS = 45e3;
16164
+ MIB = 1024 * 1024;
16165
+ MODEL_GATEWAY_TIMEOUT_MS = MODEL_QUOTE_MAX_TIMEOUT_MS + MODEL_PROVIDER_MAX_TIMEOUT_MS + 75e3;
16166
+ MODEL_GATEWAY_RETRY_BUDGET_MS = 33e4;
16167
+ MODEL_BRIDGE_TIMEOUT_MS = 2 * MODEL_GATEWAY_TIMEOUT_MS + MODEL_GATEWAY_RETRY_BUDGET_MS + 3e4;
16163
16168
  SERVICE_GATEWAY_TIMEOUT_MS = 33e4;
16164
16169
  SERVICE_BRIDGE_TIMEOUT_MS = MODEL_GATEWAY_TIMEOUT_MS + SERVICE_GATEWAY_TIMEOUT_MS + 3e4;
16165
16170
  HOSTED_TOOL_BRIDGE_TIMEOUT_MS = 9e4;
@@ -16311,6 +16316,26 @@ var init_financial_state = __esm({
16311
16316
  }
16312
16317
  });
16313
16318
 
16319
+ // ../../packages/protocol/dist/reasoning.js
16320
+ function stripReasoning(text) {
16321
+ let rest = text;
16322
+ for (; ; ) {
16323
+ const lead = /^\s*<think>[\s\S]*?<\/think>/.exec(rest);
16324
+ if (!lead)
16325
+ break;
16326
+ rest = rest.slice(lead[0].length);
16327
+ }
16328
+ const close = rest.indexOf("</think>");
16329
+ if (close !== -1 && !rest.slice(0, close).includes("<think>"))
16330
+ rest = rest.slice(close + "</think>".length);
16331
+ return rest === text ? text : rest.trim();
16332
+ }
16333
+ var init_reasoning = __esm({
16334
+ "../../packages/protocol/dist/reasoning.js"() {
16335
+ "use strict";
16336
+ }
16337
+ });
16338
+
16314
16339
  // ../../packages/protocol/dist/index.js
16315
16340
  var init_dist = __esm({
16316
16341
  "../../packages/protocol/dist/index.js"() {
@@ -16329,6 +16354,7 @@ var init_dist = __esm({
16329
16354
  init_deadlines();
16330
16355
  init_spending();
16331
16356
  init_financial_state();
16357
+ init_reasoning();
16332
16358
  }
16333
16359
  });
16334
16360
 
@@ -59184,6 +59210,7 @@ var init_gateway = __esm({
59184
59210
  artifact_path: artifact?.path,
59185
59211
  review_required: record3.review_required,
59186
59212
  review_available: true,
59213
+ ...result.note ? { note: result.note } : {},
59187
59214
  data: result.data,
59188
59215
  ...result.ok ? {} : { error: result.error }
59189
59216
  },
@@ -59759,6 +59786,16 @@ var init_dist4 = __esm({
59759
59786
  // ../../packages/agent-runtime/dist/context.js
59760
59787
  import { createHash as createHash3 } from "node:crypto";
59761
59788
  import { relative as relative3, join as join13 } from "node:path";
59789
+ function requestMessages(messages, state) {
59790
+ if (state.trailingMessage)
59791
+ return [...messages, state.trailingMessage];
59792
+ const last = messages.at(-1);
59793
+ if (!state.note || last?.role !== "user")
59794
+ return messages;
59795
+ return [...messages.slice(0, -1), { ...last, content: `${state.note}
59796
+
59797
+ ${last.content}` }];
59798
+ }
59762
59799
  async function completeWithContext(session, messages, options) {
59763
59800
  for (; ; ) {
59764
59801
  options.signal?.throwIfAborted();
@@ -59771,8 +59808,7 @@ async function completeWithContext(session, messages, options) {
59771
59808
  await options.checkpoint?.();
59772
59809
  try {
59773
59810
  const retry = options.state.retry ?? 0;
59774
- const trailing = options.state.trailingMessage;
59775
- const request2 = trailing ? [...messages, trailing] : messages;
59811
+ const request2 = requestMessages(messages, options.state);
59776
59812
  const response = await options.provider.complete(request2, {
59777
59813
  signal: options.signal,
59778
59814
  operationId: options.operationId && `${options.operationId}${retry ? `:context:${retry}` : ""}`
@@ -59784,6 +59820,7 @@ async function completeWithContext(session, messages, options) {
59784
59820
  options.state.cachedFraction = typeof cached3 === "number" && Number.isFinite(cached3) && cached3 >= 0 ? Math.min(1, cached3 / used) : 0;
59785
59821
  }
59786
59822
  options.state.prepared = false;
59823
+ options.state.note = void 0;
59787
59824
  options.state.trailingMessage = void 0;
59788
59825
  options.state.retry = 0;
59789
59826
  return response;
@@ -59842,9 +59879,9 @@ async function compactContext(session, messages, options) {
59842
59879
  }
59843
59880
  return saved;
59844
59881
  };
59845
- const trailing = options.state.trailingMessage;
59846
- const toolTokens = estimateContextTokens(options.tools) + 32 + (trailing ? estimateContextTokens(trailing) : 0);
59847
- const toolBytes = Buffer.byteLength(JSON.stringify(options.tools)) + 32 + (trailing ? Buffer.byteLength(JSON.stringify(trailing)) + 1 : 0);
59882
+ const extra = options.state.trailingMessage ?? options.state.note;
59883
+ const toolTokens = estimateContextTokens(options.tools) + 32 + (extra ? estimateContextTokens(extra) : 0);
59884
+ const toolBytes = Buffer.byteLength(JSON.stringify(options.tools)) + 32 + (extra ? Buffer.byteLength(JSON.stringify(extra)) + 1 : 0);
59848
59885
  const count = (list2) => Math.ceil(list2.reduce((total, m) => total + measure(m).tokens, toolTokens) * scale);
59849
59886
  const tokensBefore = count(messages);
59850
59887
  const retry = options.state.retry ?? 0;
@@ -60150,16 +60187,11 @@ async function readSseCompletion(response, onTextDelta, startedAtMs, onActivity)
60150
60187
  throw new Error("LLM stream terminated before a finish reason or [DONE] marker (unexpected EOF)");
60151
60188
  }
60152
60189
  const toolCalls = [...toolCallsByIndex.entries()].sort(([left], [right]) => left - right).map(([index2, entry]) => {
60153
- let parsedArguments = {};
60154
- try {
60155
- parsedArguments = entry.argumentsText ? JSON.parse(entry.argumentsText) : {};
60156
- } catch {
60157
- parsedArguments = {};
60158
- }
60190
+ const parsed = parseToolArguments(entry.argumentsText);
60159
60191
  return {
60160
60192
  id: entry.id ?? `call-${index2}`,
60161
60193
  name: entry.name ?? "",
60162
- arguments: parsedArguments
60194
+ ...parsed
60163
60195
  };
60164
60196
  }).filter((toolCall) => toolCall.name !== "");
60165
60197
  return { content, toolCalls, usage, finishReason, firstTokenMs };
@@ -60340,20 +60372,24 @@ function toParsedToolCall(value) {
60340
60372
  if (!name) {
60341
60373
  return null;
60342
60374
  }
60343
- let parsedArguments = objectValue4(fn.arguments) ?? {};
60344
- if (typeof fn.arguments === "string") {
60345
- try {
60346
- parsedArguments = JSON.parse(fn.arguments);
60347
- } catch {
60348
- parsedArguments = {};
60349
- }
60350
- }
60375
+ const parsed = typeof fn.arguments === "string" ? parseToolArguments(fn.arguments) : { arguments: objectValue4(fn.arguments) ?? {} };
60351
60376
  return {
60352
60377
  id: stringValue3(object3.id) ?? `call-${Date.now()}`,
60353
60378
  name,
60354
- arguments: parsedArguments
60379
+ ...parsed
60355
60380
  };
60356
60381
  }
60382
+ function parseToolArguments(text) {
60383
+ if (!text?.trim())
60384
+ return { arguments: {} };
60385
+ try {
60386
+ const parsed = JSON.parse(text);
60387
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
60388
+ return { arguments: parsed };
60389
+ } catch {
60390
+ }
60391
+ return { arguments: {}, invalidArguments: text.slice(0, 2e3) };
60392
+ }
60357
60393
  function messageContent(value) {
60358
60394
  if (typeof value === "string") {
60359
60395
  return value;
@@ -69543,7 +69579,7 @@ var init_crowdcode_venice = __esm({
69543
69579
  init_http3();
69544
69580
  init_dist2();
69545
69581
  init_providers();
69546
- CROWDCODE_VENICE_URL = "https://crowdcode.app/s/venice-reseller/chat/completions";
69582
+ CROWDCODE_VENICE_URL = "https://crowdcode.app/s/venice-proxy/chat/completions";
69547
69583
  CROWDCODE_VENICE_PAYEE = "0xeEc135BDe3C69e48Ce6Cf5b9e4520d65baf90FC3";
69548
69584
  VENICE_MODELS_URL = "https://api.venice.ai/api/v1/models?type=text";
69549
69585
  BASE_USDC2 = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
@@ -87313,6 +87349,8 @@ function createHostedProvider(options) {
87313
87349
  for (const call2 of result.response.toolCalls) {
87314
87350
  if (typeof call2.id !== "string" || !call2.id || typeof call2.name !== "string" || !call2.arguments || typeof call2.arguments !== "object" || Array.isArray(call2.arguments))
87315
87351
  throw new Error("Invalid hosted tool call");
87352
+ if (call2.invalidArguments !== void 0 && typeof call2.invalidArguments !== "string")
87353
+ delete call2.invalidArguments;
87316
87354
  }
87317
87355
  return result.response;
87318
87356
  }
@@ -87772,6 +87810,18 @@ var init_hosted_economy = __esm({
87772
87810
  if (reply.ok) {
87773
87811
  const data = objectValue5(reply.data) ?? {};
87774
87812
  const status = numberValue6(data.status);
87813
+ if (data.outcome === "owner_test") {
87814
+ const ok4 = status !== void 0 && status < 400;
87815
+ return {
87816
+ ok: ok4,
87817
+ ambiguous: false,
87818
+ status,
87819
+ data: decodeBody(data.body, data.content_type),
87820
+ authMode: "free",
87821
+ error: ok4 ? void 0 : `your service answered HTTP ${status ?? "?"}`,
87822
+ note: "This is your own service, so CrowdCode ran this call as a free owner test: nothing was paid or earned. Buyers pay the listed price as usual."
87823
+ };
87824
+ }
87775
87825
  const outcome = data.outcome === "paid_success" ? "paid_success" : "paid_failure";
87776
87826
  const paidAtomic = numberValue6(data.amount_atomic);
87777
87827
  const network = stringValue5(data.network);
@@ -88505,10 +88555,11 @@ var init_worker = __esm({
88505
88555
  await atomicWrite(join21(session.sessionDir, "messages.jsonl"), checkpoint.messages.filter((m) => m.role !== "system").map((message) => JSON.stringify({ type: "message", timestamp: (/* @__PURE__ */ new Date()).toISOString(), message }) + "\n").join(""));
88506
88556
  },
88507
88557
  onMessage: async (message) => {
88508
- if (message.role === "assistant" && message.content)
88558
+ const content = message.role === "assistant" && !message.housekeeping ? stripReasoning(message.content) : "";
88559
+ if (content.trim())
88509
88560
  await this.emit("assistant.message", {
88510
88561
  messageId: randomUUID8(),
88511
- content: message.content
88562
+ content
88512
88563
  }, runId, run.commandId);
88513
88564
  },
88514
88565
  toolExecutor: (name, args, context) => record3(name, args, () => hosted && HOSTED_ONLY_TOOL_NAMES.includes(name) ? hosted(name, args, context) : local(name, args, context))
@@ -88528,7 +88579,7 @@ var init_worker = __esm({
88528
88579
  await this.mutate(() => {
88529
88580
  run.pendingOperationId = error53.operationId;
88530
88581
  });
88531
- await this.finish(runId, run, error53.outcome === "payment_unknown" ? "failed" : error53.outcome, error53.outcome === "payment_unknown" ? RUN_FAILURE_MESSAGE : error53.message);
88582
+ await this.finish(runId, run, error53.outcome, error53.outcome === "payment_unknown" ? PAYMENT_RECONCILING_MESSAGE : error53.message);
88532
88583
  } else if (error53 instanceof SpendingDeclined)
88533
88584
  await this.finish(runId, run, "user_stopped", error53.message);
88534
88585
  else
@@ -88642,6 +88693,23 @@ var init_inbox = __esm({
88642
88693
  }
88643
88694
  });
88644
88695
 
88696
+ // ../../packages/agent-runtime/dist/non-answer.js
88697
+ function isNonAnswer(text) {
88698
+ const trimmed = text.trim();
88699
+ if (!trimmed || trimmed.length > 160 || /https?:\/\/|\n\s*\n/.test(trimmed))
88700
+ return false;
88701
+ return ACKNOWLEDGEMENT.test(trimmed) || PROMISE.test(trimmed);
88702
+ }
88703
+ var ACKNOWLEDGEMENT, ME, PROMISE;
88704
+ var init_non_answer = __esm({
88705
+ "../../packages/agent-runtime/dist/non-answer.js"() {
88706
+ "use strict";
88707
+ ACKNOWLEDGEMENT = /^(?:ok(?:ay)?|acknowledged|noted|understood|done|got it|sure|will do|on it|roger(?: that)?|thanks?(?: you)?|all done|complete(?:d)?|finished)(?:[.!]|\s*(?:—|-|,)\s*(?:thanks?(?: you)?|done))*$/i;
88708
+ ME = String.raw`(?:i(?:['’]?m| am)|i(?:['’]ll| will)(?: now)?|we(?:['’]re| are)|we(?:['’]ll| will)(?: now)?|now i(?:['’]ll| will)|next,? i(?:['’]ll| will)|let me(?: now)?|now,? let me|next,? let me)`;
88709
+ PROMISE = new RegExp(String.raw`^(?:${ME}\s+(?:now\s+|just\s+|going to\s+|about to\s+|currently\s+)*(?:prepar|compil|put(?:ting)?\b|writ|draft|gather|finali[sz]|summari[sz]|pull|assembl|format|work(?:ing)? on|get(?:ting)? (?:the|your|that|this)|provid|present|shar|send|deliver|giv)|(?:one moment|stand by|please wait|hang on|working on it|preparing|compiling|drafting|finali[sz]ing)\b)`, "i");
88710
+ }
88711
+ });
88712
+
88645
88713
  // ../../packages/agent-runtime/dist/index.js
88646
88714
  import { createHash as createHash6 } from "node:crypto";
88647
88715
  import { appendFile as appendFile4, mkdir as mkdir12, readFile as readFile15 } from "node:fs/promises";
@@ -88843,7 +88911,7 @@ async function runAgentTaskDetailed(session, task, options = {}) {
88843
88911
  "Before repeatedly fighting the local environment, decide whether the task needs external capability such as hosting, persistent infrastructure, remote compute, specialized APIs, live data, or access the user's device does not have."
88844
88912
  ];
88845
88913
  if (options.hosted)
88846
- 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.");
88914
+ 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. It arrives as budget_after_tool_call in the latest tool result or, when no tool has just run, as an automatic CrowdCode note at the start of the latest user message; that note is context, not something the user said, so never reply to it or mention it unless the user asks about money. 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.");
88847
88915
  if (options.hosted)
88848
88916
  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.");
88849
88917
  if (options.dynamicTools) {
@@ -88889,6 +88957,7 @@ async function runAgentTaskDetailed(session, task, options = {}) {
88889
88957
  };
88890
88958
  const currentBudget = () => options.financialState ? financial : budgetStatus(session);
88891
88959
  const financialPrefix = "Current hosted financial snapshot: ";
88960
+ const financialNote = "[Automatic note from CrowdCode, not written by the user; do not reply to it. ";
88892
88961
  if (!options.resume)
88893
88962
  await options.onMessage?.({ role: "user", content: task });
88894
88963
  const maxTurns = options.maxTurns ?? 100;
@@ -88896,6 +88965,7 @@ async function runAgentTaskDetailed(session, task, options = {}) {
88896
88965
  let completionNudges = options.resume?.completionNudges ?? 0;
88897
88966
  let pendingFinalMessage = options.resume?.pendingFinalMessage;
88898
88967
  let finalAnswerHeld = options.resume?.finalAnswerHeld ?? false;
88968
+ let answerNudges = options.resume?.answerNudges ?? 0;
88899
88969
  let supersededFinalMessage;
88900
88970
  let outputContinuationNudges = options.resume?.outputContinuationNudges ?? 0;
88901
88971
  let outputContinuationPrefix = options.resume?.outputContinuationPrefix ?? "";
@@ -88912,6 +88982,7 @@ async function runAgentTaskDetailed(session, task, options = {}) {
88912
88982
  completionNudges,
88913
88983
  pendingFinalMessage,
88914
88984
  finalAnswerHeld,
88985
+ answerNudges,
88915
88986
  outputContinuationNudges,
88916
88987
  outputContinuationPrefix,
88917
88988
  serviceCallFailures,
@@ -88938,7 +89009,8 @@ async function runAgentTaskDetailed(session, task, options = {}) {
88938
89009
  await options.inbox?.delivered(pending.map((m) => m.id));
88939
89010
  return fresh.length > 0;
88940
89011
  };
88941
- const recordFinalMessage = (text) => {
89012
+ const recordFinalMessage = (raw) => {
89013
+ const text = raw === void 0 ? void 0 : stripReasoning(raw);
88942
89014
  if (!text?.trim())
88943
89015
  return;
88944
89016
  if (!finalAnswerHeld)
@@ -88946,8 +89018,23 @@ async function runAgentTaskDetailed(session, task, options = {}) {
88946
89018
  else if (text.trim() !== pendingFinalMessage?.trim())
88947
89019
  supersededFinalMessage = text;
88948
89020
  };
88949
- 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}`;
89021
+ const placeholder = () => !finalAnswerHeld && !!pendingFinalMessage && isNonAnswer(pendingFinalMessage) && messages.some((m) => m.role === "assistant" && m.toolCalls?.some((call2) => call2.name !== "complete_session"));
89022
+ const notAnAnswer = () => `Your final message so far ("${pendingFinalMessage.trim()}") is not an answer, and the user sees only your final message, never your tool results.`;
89023
+ const hold = () => {
89024
+ if (pendingFinalMessage?.trim() && !placeholder())
89025
+ finalAnswerHeld = true;
89026
+ };
89027
+ 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.` : placeholder() ? `You cannot finish yet: ${blocker} ${notAnAnswer()} When you call complete_session again, put the complete answer to the user's request in it.` : `You cannot finish yet: ${blocker}`;
89028
+ const answerNudge = () => `${notAnAnswer()} Call complete_session again with the complete answer to the user's request: what you found or did, with the URLs, figures or identifiers they need.`;
88950
89029
  const withReflection = (summary2) => supersededFinalMessage ? { ...summary2, reflection_note: supersededFinalMessage } : summary2;
89030
+ const holding = () => finalAnswerHeld && !!pendingFinalMessage?.trim() && !options.signal?.aborted;
89031
+ const deliverHeld = async (turn, problem) => {
89032
+ options.onProgress?.({ type: "complete", message: `Delivered the saved answer; housekeeping did not finish: ${problem}` });
89033
+ const summary2 = { ...withReflection(await completeSession(session, pendingFinalMessage)), housekeeping_error: problem };
89034
+ if (backgroundSubagents.size > 0)
89035
+ summary2.background_subagents = await drainBackground();
89036
+ return { outcome: "completed", summary: summary2, turns: turn + 1 };
89037
+ };
88951
89038
  let subagentCount = 0;
88952
89039
  const backgroundSubagents = /* @__PURE__ */ new Map();
88953
89040
  const subagentLimiter = createLimiter(options.subagent?.maxParallel ?? 4);
@@ -88974,7 +89061,7 @@ async function runAgentTaskDetailed(session, task, options = {}) {
88974
89061
  const legacy = messages.findIndex((m) => m.role === "system" && m.content.startsWith(financialPrefix));
88975
89062
  if (legacy !== -1)
88976
89063
  messages.splice(legacy, 1);
88977
- contextState.trailingMessage = { role: "user", content: financialPrefix + JSON.stringify(financial) };
89064
+ contextState.note = `${financialNote}${financialPrefix}${JSON.stringify(financial)}]`;
88978
89065
  }
88979
89066
  response = restored?.response ?? await completeWithContext(session, messages, {
88980
89067
  provider: provider instanceof MockLlmProvider ? { complete: (input2) => completeMockLlmCall(session, provider, input2, turn + 1) } : provider,
@@ -88995,6 +89082,8 @@ async function runAgentTaskDetailed(session, task, options = {}) {
88995
89082
  options.signal?.throwIfAborted();
88996
89083
  await refreshFinancialState();
88997
89084
  } catch (error53) {
89085
+ if (holding())
89086
+ return deliverHeld(turn, `the housekeeping model call failed: ${error53.message}`);
88998
89087
  if (error53 instanceof BudgetExhaustedError) {
88999
89088
  const summary2 = await completeSession(session, `Stopped: the session budget is exhausted (${error53.message}).`);
89000
89089
  return { outcome: options.hosted ? "budget_exhausted" : "stopped", summary: summary2, turns: turn + 1 };
@@ -89004,12 +89093,14 @@ async function runAgentTaskDetailed(session, task, options = {}) {
89004
89093
  if (!restored?.response && (response.content || response.toolCalls.length > 0)) {
89005
89094
  const assistantMessage = assistantMessageFromResponse(isOutputLimitFinishReason(response.finishReason) ? { ...response, toolCalls: [] } : response);
89006
89095
  messages.push(assistantMessage);
89007
- await options.onMessage?.(assistantMessage);
89096
+ await options.onMessage?.(finalAnswerHeld ? { ...assistantMessage, housekeeping: true } : assistantMessage);
89008
89097
  }
89009
89098
  await checkpoint(turn, response, completedTools);
89010
89099
  if (isOutputLimitFinishReason(response.finishReason)) {
89011
89100
  outputContinuationPrefix += response.content;
89012
89101
  if (outputContinuationNudges >= 1) {
89102
+ if (holding())
89103
+ return deliverHeld(turn, "the housekeeping turn hit the output limit twice");
89013
89104
  const summary2 = await completeSession(session, `Stopped: the LLM hit its output limit twice. Partial response:
89014
89105
 
89015
89106
  ${outputContinuationPrefix}`);
@@ -89030,15 +89121,23 @@ ${outputContinuationPrefix}`);
89030
89121
  const finalMessage = response.content.trim() ? outputContinuationPrefix + response.content : pendingFinalMessage ?? outputContinuationPrefix;
89031
89122
  recordFinalMessage(finalMessage);
89032
89123
  const blocker = await options.completionGate?.(completionNudges > 0);
89124
+ const unanswered = answerNudges < 1 && placeholder();
89033
89125
  if (blocker) {
89034
89126
  if (completionNudges >= 1) {
89127
+ hold();
89128
+ if (holding())
89129
+ return deliverHeld(turn, `the completion gate still blocks: ${blocker}`);
89035
89130
  const summary3 = await completeSession(session, `Stopped: ${blocker}`);
89036
89131
  return { outcome: "stopped", summary: summary3, turns: turn + 1 };
89037
89132
  }
89038
89133
  completionNudges += 1;
89039
- if (pendingFinalMessage?.trim())
89040
- finalAnswerHeld = true;
89041
- const nudge = { role: "user", content: gateNudge(blocker) };
89134
+ }
89135
+ if (blocker || unanswered) {
89136
+ if (unanswered)
89137
+ answerNudges += 1;
89138
+ hold();
89139
+ const content = blocker ? gateNudge(blocker) : answerNudge();
89140
+ const nudge = { role: "user", content };
89042
89141
  messages.push(nudge);
89043
89142
  await options.onMessage?.(nudge);
89044
89143
  await checkpoint(turn + 1);
@@ -89090,42 +89189,52 @@ ${outputContinuationPrefix}`);
89090
89189
  data: { tool: call2.name, arguments: call2.arguments }
89091
89190
  });
89092
89191
  const budgetBeforeToolCall = currentBudget();
89192
+ const salvaged = call2.invalidArguments === void 0 || call2.name !== "complete_session" ? void 0 : response.content.trim() ? response.content : holding() ? pendingFinalMessage : void 0;
89193
+ const baseArguments = salvaged === void 0 ? call2.arguments : { final_message: salvaged };
89093
89194
  const callArguments = call2.name === "complete_session" && outputContinuationPrefix ? {
89094
- ...call2.arguments,
89095
- final_message: outputContinuationPrefix + (typeof call2.arguments.final_message === "string" ? call2.arguments.final_message : "")
89096
- } : call2.arguments;
89195
+ ...baseArguments,
89196
+ final_message: outputContinuationPrefix + (typeof baseArguments.final_message === "string" ? baseArguments.final_message : "")
89197
+ } : baseArguments;
89097
89198
  let result;
89098
- if (completedTools[call2.id]) {
89099
- result = completedTools[call2.id];
89100
- } else if (call2.name === "spawn_subagent") {
89101
- try {
89102
- result = options.subagent ? await (spawnedThisTurn.get(call2.id) ?? Promise.resolve({ ok: false, error: "subagent launch failed" })) : { ok: false, error: "subagents are not enabled for this session" };
89103
- } catch (error53) {
89104
- await Promise.allSettled(spawnedThisTurn.values());
89105
- throw error53;
89106
- }
89107
- } else if (call2.name === "check_subagents") {
89108
- if (call2.arguments.wait === true) {
89109
- result = { ok: true, data: { subagents: await drainBackground() } };
89199
+ try {
89200
+ if (completedTools[call2.id]) {
89201
+ result = completedTools[call2.id];
89202
+ } else if (call2.invalidArguments !== void 0 && salvaged === void 0) {
89203
+ result = { ok: false, error: invalidArgumentsError(call2.name, call2.invalidArguments) };
89204
+ } else if (call2.name === "spawn_subagent") {
89205
+ try {
89206
+ result = options.subagent ? await (spawnedThisTurn.get(call2.id) ?? Promise.resolve({ ok: false, error: "subagent launch failed" })) : { ok: false, error: "subagents are not enabled for this session" };
89207
+ } catch (error53) {
89208
+ await Promise.allSettled(spawnedThisTurn.values());
89209
+ throw error53;
89210
+ }
89211
+ } else if (call2.name === "check_subagents") {
89212
+ if (call2.arguments.wait === true) {
89213
+ result = { ok: true, data: { subagents: await drainBackground() } };
89214
+ } else {
89215
+ result = {
89216
+ ok: true,
89217
+ data: {
89218
+ subagents: [...backgroundSubagents.values()].map((entry) => ({
89219
+ subagent_index: entry.index,
89220
+ task: entry.task,
89221
+ status: entry.status,
89222
+ ...entry.status === "finished" && entry.result?.ok && entry.result.data && typeof entry.result.data === "object" ? entry.result.data : entry.status === "finished" ? { outcome: "error", error: entry.result?.error } : {}
89223
+ }))
89224
+ }
89225
+ };
89226
+ }
89227
+ } else if (TOOL_NAMES.includes(call2.name)) {
89228
+ result = await toolExecutor(call2.name, callArguments, { session, onProgress: options.onProgress, signal: options.signal });
89229
+ } else if (options.dynamicTools) {
89230
+ result = await options.dynamicTools.execute(call2.name, callArguments);
89110
89231
  } else {
89111
- result = {
89112
- ok: true,
89113
- data: {
89114
- subagents: [...backgroundSubagents.values()].map((entry) => ({
89115
- subagent_index: entry.index,
89116
- task: entry.task,
89117
- status: entry.status,
89118
- ...entry.status === "finished" && entry.result?.ok && entry.result.data && typeof entry.result.data === "object" ? entry.result.data : entry.status === "finished" ? { outcome: "error", error: entry.result?.error } : {}
89119
- }))
89120
- }
89121
- };
89232
+ result = { ok: false, error: `unknown tool: ${call2.name}` };
89122
89233
  }
89123
- } else if (TOOL_NAMES.includes(call2.name)) {
89124
- result = await toolExecutor(call2.name, callArguments, { session, onProgress: options.onProgress, signal: options.signal });
89125
- } else if (options.dynamicTools) {
89126
- result = await options.dynamicTools.execute(call2.name, callArguments);
89127
- } else {
89128
- result = { ok: false, error: `unknown tool: ${call2.name}` };
89234
+ } catch (error53) {
89235
+ if (holding())
89236
+ return deliverHeld(turn, `the housekeeping tool ${call2.name} failed: ${error53.message}`);
89237
+ throw error53;
89129
89238
  }
89130
89239
  result = capToolResult(result);
89131
89240
  await refreshFinancialState();
@@ -89161,6 +89270,8 @@ ${outputContinuationPrefix}`);
89161
89270
  if (call2.name === "call_paid_service") {
89162
89271
  serviceCallFailures += 1;
89163
89272
  if (serviceCallFailures >= 3) {
89273
+ if (holding())
89274
+ return deliverHeld(turn, `paid service calls failed ${serviceCallFailures} times: ${result.error}`);
89164
89275
  const summary2 = await completeSession(session, `Stopped after ${serviceCallFailures} service call failures. Last error: ${result.error}`);
89165
89276
  return { outcome: "stopped", summary: summary2, turns: turn + 1 };
89166
89277
  }
@@ -89169,6 +89280,8 @@ ${outputContinuationPrefix}`);
89169
89280
  const count = (repeatedFailures.get(key) ?? 0) + 1;
89170
89281
  repeatedFailures.set(key, count);
89171
89282
  if (count >= 2) {
89283
+ if (holding())
89284
+ return deliverHeld(turn, `${call2.name} failed repeatedly: ${result.error}`);
89172
89285
  const summary2 = await completeSession(session, `Stopped because ${call2.name} failed repeatedly: ${result.error}`);
89173
89286
  return { outcome: "stopped", summary: summary2, turns: turn + 1 };
89174
89287
  }
@@ -89186,17 +89299,25 @@ ${outputContinuationPrefix}`);
89186
89299
  if (typeof finalMessage === "string")
89187
89300
  recordFinalMessage(finalMessage);
89188
89301
  const blocker = await options.completionGate?.(completionNudges > 0);
89302
+ const unanswered = answerNudges < 1 && placeholder();
89189
89303
  if (blocker) {
89190
89304
  if (completionNudges >= 1) {
89305
+ hold();
89306
+ if (holding())
89307
+ return deliverHeld(turn, `the completion gate still blocks: ${blocker}`);
89191
89308
  const summary3 = await completeSession(session, `Stopped: ${blocker}`);
89192
89309
  return { outcome: "stopped", summary: summary3, turns: turn + 1 };
89193
89310
  }
89194
89311
  completionNudges += 1;
89195
- if (pendingFinalMessage?.trim())
89196
- finalAnswerHeld = true;
89312
+ }
89313
+ if (blocker || unanswered) {
89314
+ if (unanswered)
89315
+ answerNudges += 1;
89316
+ hold();
89317
+ const content = blocker ? gateNudge(blocker) : answerNudge();
89197
89318
  const gateMessage = {
89198
89319
  role: "user",
89199
- content: gateNudge(blocker)
89320
+ content
89200
89321
  };
89201
89322
  messages.push(gateMessage);
89202
89323
  await options.onMessage?.(gateMessage);
@@ -89220,6 +89341,8 @@ ${outputContinuationPrefix}`);
89220
89341
  return { outcome: "completed", summary: summary2, turns: turn + 1 };
89221
89342
  }
89222
89343
  }
89344
+ if (holding())
89345
+ return deliverHeld(maxTurns - 1, "the run reached its maximum turns during housekeeping");
89223
89346
  if (backgroundSubagents.size > 0) {
89224
89347
  await drainBackground();
89225
89348
  }
@@ -89453,9 +89576,15 @@ function assistantMessageFromResponse(response) {
89453
89576
  return {
89454
89577
  role: "assistant",
89455
89578
  content: response.content,
89456
- toolCalls: response.toolCalls
89579
+ // The raw text of a malformed call is for this runtime's error, not a
89580
+ // field a provider request may carry.
89581
+ toolCalls: response.toolCalls.map(({ invalidArguments: _invalid, ...call2 }) => call2)
89457
89582
  };
89458
89583
  }
89584
+ function invalidArgumentsError(name, raw) {
89585
+ const shown = raw.length > 300 ? `${raw.slice(0, 300)}\u2026` : raw;
89586
+ return `Your ${name} call was not run: its arguments were not a valid JSON object (${JSON.stringify(shown)}). Call ${name} again with valid JSON arguments.`;
89587
+ }
89459
89588
  async function completeMockLlmCall(session, provider, messages, turn) {
89460
89589
  const costCents = 1;
89461
89590
  const reservation = await reserveBudget(session, costCents);
@@ -89765,6 +89894,8 @@ var init_dist6 = __esm({
89765
89894
  init_knowledge();
89766
89895
  init_context2();
89767
89896
  init_tool_deadlines();
89897
+ init_non_answer();
89898
+ init_non_answer();
89768
89899
  MockLlmProvider = class {
89769
89900
  random;
89770
89901
  endProbability;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencrowd",
3
- "version": "0.16.0",
3
+ "version": "0.16.2",
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",