opencrowd 0.16.0 → 0.16.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.
Files changed (2) hide show
  1. package/bundle/opencrowd.js +133 -60
  2. package/package.json +1 -1
@@ -16311,6 +16311,26 @@ var init_financial_state = __esm({
16311
16311
  }
16312
16312
  });
16313
16313
 
16314
+ // ../../packages/protocol/dist/reasoning.js
16315
+ function stripReasoning(text) {
16316
+ let rest = text;
16317
+ for (; ; ) {
16318
+ const lead = /^\s*<think>[\s\S]*?<\/think>/.exec(rest);
16319
+ if (!lead)
16320
+ break;
16321
+ rest = rest.slice(lead[0].length);
16322
+ }
16323
+ const close = rest.indexOf("</think>");
16324
+ if (close !== -1 && !rest.slice(0, close).includes("<think>"))
16325
+ rest = rest.slice(close + "</think>".length);
16326
+ return rest === text ? text : rest.trim();
16327
+ }
16328
+ var init_reasoning = __esm({
16329
+ "../../packages/protocol/dist/reasoning.js"() {
16330
+ "use strict";
16331
+ }
16332
+ });
16333
+
16314
16334
  // ../../packages/protocol/dist/index.js
16315
16335
  var init_dist = __esm({
16316
16336
  "../../packages/protocol/dist/index.js"() {
@@ -16329,6 +16349,7 @@ var init_dist = __esm({
16329
16349
  init_deadlines();
16330
16350
  init_spending();
16331
16351
  init_financial_state();
16352
+ init_reasoning();
16332
16353
  }
16333
16354
  });
16334
16355
 
@@ -59759,6 +59780,16 @@ var init_dist4 = __esm({
59759
59780
  // ../../packages/agent-runtime/dist/context.js
59760
59781
  import { createHash as createHash3 } from "node:crypto";
59761
59782
  import { relative as relative3, join as join13 } from "node:path";
59783
+ function requestMessages(messages, state) {
59784
+ if (state.trailingMessage)
59785
+ return [...messages, state.trailingMessage];
59786
+ const last = messages.at(-1);
59787
+ if (!state.note || last?.role !== "user")
59788
+ return messages;
59789
+ return [...messages.slice(0, -1), { ...last, content: `${state.note}
59790
+
59791
+ ${last.content}` }];
59792
+ }
59762
59793
  async function completeWithContext(session, messages, options) {
59763
59794
  for (; ; ) {
59764
59795
  options.signal?.throwIfAborted();
@@ -59771,8 +59802,7 @@ async function completeWithContext(session, messages, options) {
59771
59802
  await options.checkpoint?.();
59772
59803
  try {
59773
59804
  const retry = options.state.retry ?? 0;
59774
- const trailing = options.state.trailingMessage;
59775
- const request2 = trailing ? [...messages, trailing] : messages;
59805
+ const request2 = requestMessages(messages, options.state);
59776
59806
  const response = await options.provider.complete(request2, {
59777
59807
  signal: options.signal,
59778
59808
  operationId: options.operationId && `${options.operationId}${retry ? `:context:${retry}` : ""}`
@@ -59784,6 +59814,7 @@ async function completeWithContext(session, messages, options) {
59784
59814
  options.state.cachedFraction = typeof cached3 === "number" && Number.isFinite(cached3) && cached3 >= 0 ? Math.min(1, cached3 / used) : 0;
59785
59815
  }
59786
59816
  options.state.prepared = false;
59817
+ options.state.note = void 0;
59787
59818
  options.state.trailingMessage = void 0;
59788
59819
  options.state.retry = 0;
59789
59820
  return response;
@@ -59842,9 +59873,9 @@ async function compactContext(session, messages, options) {
59842
59873
  }
59843
59874
  return saved;
59844
59875
  };
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);
59876
+ const extra = options.state.trailingMessage ?? options.state.note;
59877
+ const toolTokens = estimateContextTokens(options.tools) + 32 + (extra ? estimateContextTokens(extra) : 0);
59878
+ const toolBytes = Buffer.byteLength(JSON.stringify(options.tools)) + 32 + (extra ? Buffer.byteLength(JSON.stringify(extra)) + 1 : 0);
59848
59879
  const count = (list2) => Math.ceil(list2.reduce((total, m) => total + measure(m).tokens, toolTokens) * scale);
59849
59880
  const tokensBefore = count(messages);
59850
59881
  const retry = options.state.retry ?? 0;
@@ -60150,16 +60181,11 @@ async function readSseCompletion(response, onTextDelta, startedAtMs, onActivity)
60150
60181
  throw new Error("LLM stream terminated before a finish reason or [DONE] marker (unexpected EOF)");
60151
60182
  }
60152
60183
  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
- }
60184
+ const parsed = parseToolArguments(entry.argumentsText);
60159
60185
  return {
60160
60186
  id: entry.id ?? `call-${index2}`,
60161
60187
  name: entry.name ?? "",
60162
- arguments: parsedArguments
60188
+ ...parsed
60163
60189
  };
60164
60190
  }).filter((toolCall) => toolCall.name !== "");
60165
60191
  return { content, toolCalls, usage, finishReason, firstTokenMs };
@@ -60340,20 +60366,24 @@ function toParsedToolCall(value) {
60340
60366
  if (!name) {
60341
60367
  return null;
60342
60368
  }
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
- }
60369
+ const parsed = typeof fn.arguments === "string" ? parseToolArguments(fn.arguments) : { arguments: objectValue4(fn.arguments) ?? {} };
60351
60370
  return {
60352
60371
  id: stringValue3(object3.id) ?? `call-${Date.now()}`,
60353
60372
  name,
60354
- arguments: parsedArguments
60373
+ ...parsed
60355
60374
  };
60356
60375
  }
60376
+ function parseToolArguments(text) {
60377
+ if (!text?.trim())
60378
+ return { arguments: {} };
60379
+ try {
60380
+ const parsed = JSON.parse(text);
60381
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
60382
+ return { arguments: parsed };
60383
+ } catch {
60384
+ }
60385
+ return { arguments: {}, invalidArguments: text.slice(0, 2e3) };
60386
+ }
60357
60387
  function messageContent(value) {
60358
60388
  if (typeof value === "string") {
60359
60389
  return value;
@@ -87313,6 +87343,8 @@ function createHostedProvider(options) {
87313
87343
  for (const call2 of result.response.toolCalls) {
87314
87344
  if (typeof call2.id !== "string" || !call2.id || typeof call2.name !== "string" || !call2.arguments || typeof call2.arguments !== "object" || Array.isArray(call2.arguments))
87315
87345
  throw new Error("Invalid hosted tool call");
87346
+ if (call2.invalidArguments !== void 0 && typeof call2.invalidArguments !== "string")
87347
+ delete call2.invalidArguments;
87316
87348
  }
87317
87349
  return result.response;
87318
87350
  }
@@ -88505,10 +88537,11 @@ var init_worker = __esm({
88505
88537
  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
88538
  },
88507
88539
  onMessage: async (message) => {
88508
- if (message.role === "assistant" && message.content)
88540
+ const content = message.role === "assistant" && !message.housekeeping ? stripReasoning(message.content) : "";
88541
+ if (content.trim())
88509
88542
  await this.emit("assistant.message", {
88510
88543
  messageId: randomUUID8(),
88511
- content: message.content
88544
+ content
88512
88545
  }, runId, run.commandId);
88513
88546
  },
88514
88547
  toolExecutor: (name, args, context) => record3(name, args, () => hosted && HOSTED_ONLY_TOOL_NAMES.includes(name) ? hosted(name, args, context) : local(name, args, context))
@@ -88843,7 +88876,7 @@ async function runAgentTaskDetailed(session, task, options = {}) {
88843
88876
  "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
88877
  ];
88845
88878
  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.");
88879
+ 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
88880
  if (options.hosted)
88848
88881
  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
88882
  if (options.dynamicTools) {
@@ -88889,6 +88922,7 @@ async function runAgentTaskDetailed(session, task, options = {}) {
88889
88922
  };
88890
88923
  const currentBudget = () => options.financialState ? financial : budgetStatus(session);
88891
88924
  const financialPrefix = "Current hosted financial snapshot: ";
88925
+ const financialNote = "[Automatic note from CrowdCode, not written by the user; do not reply to it. ";
88892
88926
  if (!options.resume)
88893
88927
  await options.onMessage?.({ role: "user", content: task });
88894
88928
  const maxTurns = options.maxTurns ?? 100;
@@ -88938,7 +88972,8 @@ async function runAgentTaskDetailed(session, task, options = {}) {
88938
88972
  await options.inbox?.delivered(pending.map((m) => m.id));
88939
88973
  return fresh.length > 0;
88940
88974
  };
88941
- const recordFinalMessage = (text) => {
88975
+ const recordFinalMessage = (raw) => {
88976
+ const text = raw === void 0 ? void 0 : stripReasoning(raw);
88942
88977
  if (!text?.trim())
88943
88978
  return;
88944
88979
  if (!finalAnswerHeld)
@@ -88948,6 +88983,14 @@ async function runAgentTaskDetailed(session, task, options = {}) {
88948
88983
  };
88949
88984
  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}`;
88950
88985
  const withReflection = (summary2) => supersededFinalMessage ? { ...summary2, reflection_note: supersededFinalMessage } : summary2;
88986
+ const holding = () => finalAnswerHeld && !!pendingFinalMessage?.trim() && !options.signal?.aborted;
88987
+ const deliverHeld = async (turn, problem) => {
88988
+ options.onProgress?.({ type: "complete", message: `Delivered the saved answer; housekeeping did not finish: ${problem}` });
88989
+ const summary2 = { ...withReflection(await completeSession(session, pendingFinalMessage)), housekeeping_error: problem };
88990
+ if (backgroundSubagents.size > 0)
88991
+ summary2.background_subagents = await drainBackground();
88992
+ return { outcome: "completed", summary: summary2, turns: turn + 1 };
88993
+ };
88951
88994
  let subagentCount = 0;
88952
88995
  const backgroundSubagents = /* @__PURE__ */ new Map();
88953
88996
  const subagentLimiter = createLimiter(options.subagent?.maxParallel ?? 4);
@@ -88974,7 +89017,7 @@ async function runAgentTaskDetailed(session, task, options = {}) {
88974
89017
  const legacy = messages.findIndex((m) => m.role === "system" && m.content.startsWith(financialPrefix));
88975
89018
  if (legacy !== -1)
88976
89019
  messages.splice(legacy, 1);
88977
- contextState.trailingMessage = { role: "user", content: financialPrefix + JSON.stringify(financial) };
89020
+ contextState.note = `${financialNote}${financialPrefix}${JSON.stringify(financial)}]`;
88978
89021
  }
88979
89022
  response = restored?.response ?? await completeWithContext(session, messages, {
88980
89023
  provider: provider instanceof MockLlmProvider ? { complete: (input2) => completeMockLlmCall(session, provider, input2, turn + 1) } : provider,
@@ -88995,6 +89038,8 @@ async function runAgentTaskDetailed(session, task, options = {}) {
88995
89038
  options.signal?.throwIfAborted();
88996
89039
  await refreshFinancialState();
88997
89040
  } catch (error53) {
89041
+ if (holding())
89042
+ return deliverHeld(turn, `the housekeeping model call failed: ${error53.message}`);
88998
89043
  if (error53 instanceof BudgetExhaustedError) {
88999
89044
  const summary2 = await completeSession(session, `Stopped: the session budget is exhausted (${error53.message}).`);
89000
89045
  return { outcome: options.hosted ? "budget_exhausted" : "stopped", summary: summary2, turns: turn + 1 };
@@ -89004,12 +89049,14 @@ async function runAgentTaskDetailed(session, task, options = {}) {
89004
89049
  if (!restored?.response && (response.content || response.toolCalls.length > 0)) {
89005
89050
  const assistantMessage = assistantMessageFromResponse(isOutputLimitFinishReason(response.finishReason) ? { ...response, toolCalls: [] } : response);
89006
89051
  messages.push(assistantMessage);
89007
- await options.onMessage?.(assistantMessage);
89052
+ await options.onMessage?.(finalAnswerHeld ? { ...assistantMessage, housekeeping: true } : assistantMessage);
89008
89053
  }
89009
89054
  await checkpoint(turn, response, completedTools);
89010
89055
  if (isOutputLimitFinishReason(response.finishReason)) {
89011
89056
  outputContinuationPrefix += response.content;
89012
89057
  if (outputContinuationNudges >= 1) {
89058
+ if (holding())
89059
+ return deliverHeld(turn, "the housekeeping turn hit the output limit twice");
89013
89060
  const summary2 = await completeSession(session, `Stopped: the LLM hit its output limit twice. Partial response:
89014
89061
 
89015
89062
  ${outputContinuationPrefix}`);
@@ -89032,6 +89079,8 @@ ${outputContinuationPrefix}`);
89032
89079
  const blocker = await options.completionGate?.(completionNudges > 0);
89033
89080
  if (blocker) {
89034
89081
  if (completionNudges >= 1) {
89082
+ if (holding())
89083
+ return deliverHeld(turn, `the completion gate still blocks: ${blocker}`);
89035
89084
  const summary3 = await completeSession(session, `Stopped: ${blocker}`);
89036
89085
  return { outcome: "stopped", summary: summary3, turns: turn + 1 };
89037
89086
  }
@@ -89090,42 +89139,52 @@ ${outputContinuationPrefix}`);
89090
89139
  data: { tool: call2.name, arguments: call2.arguments }
89091
89140
  });
89092
89141
  const budgetBeforeToolCall = currentBudget();
89142
+ const salvaged = call2.invalidArguments === void 0 || call2.name !== "complete_session" ? void 0 : response.content.trim() ? response.content : holding() ? pendingFinalMessage : void 0;
89143
+ const baseArguments = salvaged === void 0 ? call2.arguments : { final_message: salvaged };
89093
89144
  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;
89145
+ ...baseArguments,
89146
+ final_message: outputContinuationPrefix + (typeof baseArguments.final_message === "string" ? baseArguments.final_message : "")
89147
+ } : baseArguments;
89097
89148
  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() } };
89149
+ try {
89150
+ if (completedTools[call2.id]) {
89151
+ result = completedTools[call2.id];
89152
+ } else if (call2.invalidArguments !== void 0 && salvaged === void 0) {
89153
+ result = { ok: false, error: invalidArgumentsError(call2.name, call2.invalidArguments) };
89154
+ } else if (call2.name === "spawn_subagent") {
89155
+ try {
89156
+ 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" };
89157
+ } catch (error53) {
89158
+ await Promise.allSettled(spawnedThisTurn.values());
89159
+ throw error53;
89160
+ }
89161
+ } else if (call2.name === "check_subagents") {
89162
+ if (call2.arguments.wait === true) {
89163
+ result = { ok: true, data: { subagents: await drainBackground() } };
89164
+ } else {
89165
+ result = {
89166
+ ok: true,
89167
+ data: {
89168
+ subagents: [...backgroundSubagents.values()].map((entry) => ({
89169
+ subagent_index: entry.index,
89170
+ task: entry.task,
89171
+ status: entry.status,
89172
+ ...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 } : {}
89173
+ }))
89174
+ }
89175
+ };
89176
+ }
89177
+ } else if (TOOL_NAMES.includes(call2.name)) {
89178
+ result = await toolExecutor(call2.name, callArguments, { session, onProgress: options.onProgress, signal: options.signal });
89179
+ } else if (options.dynamicTools) {
89180
+ result = await options.dynamicTools.execute(call2.name, callArguments);
89110
89181
  } 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
- };
89182
+ result = { ok: false, error: `unknown tool: ${call2.name}` };
89122
89183
  }
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}` };
89184
+ } catch (error53) {
89185
+ if (holding())
89186
+ return deliverHeld(turn, `the housekeeping tool ${call2.name} failed: ${error53.message}`);
89187
+ throw error53;
89129
89188
  }
89130
89189
  result = capToolResult(result);
89131
89190
  await refreshFinancialState();
@@ -89161,6 +89220,8 @@ ${outputContinuationPrefix}`);
89161
89220
  if (call2.name === "call_paid_service") {
89162
89221
  serviceCallFailures += 1;
89163
89222
  if (serviceCallFailures >= 3) {
89223
+ if (holding())
89224
+ return deliverHeld(turn, `paid service calls failed ${serviceCallFailures} times: ${result.error}`);
89164
89225
  const summary2 = await completeSession(session, `Stopped after ${serviceCallFailures} service call failures. Last error: ${result.error}`);
89165
89226
  return { outcome: "stopped", summary: summary2, turns: turn + 1 };
89166
89227
  }
@@ -89169,6 +89230,8 @@ ${outputContinuationPrefix}`);
89169
89230
  const count = (repeatedFailures.get(key) ?? 0) + 1;
89170
89231
  repeatedFailures.set(key, count);
89171
89232
  if (count >= 2) {
89233
+ if (holding())
89234
+ return deliverHeld(turn, `${call2.name} failed repeatedly: ${result.error}`);
89172
89235
  const summary2 = await completeSession(session, `Stopped because ${call2.name} failed repeatedly: ${result.error}`);
89173
89236
  return { outcome: "stopped", summary: summary2, turns: turn + 1 };
89174
89237
  }
@@ -89188,6 +89251,8 @@ ${outputContinuationPrefix}`);
89188
89251
  const blocker = await options.completionGate?.(completionNudges > 0);
89189
89252
  if (blocker) {
89190
89253
  if (completionNudges >= 1) {
89254
+ if (holding())
89255
+ return deliverHeld(turn, `the completion gate still blocks: ${blocker}`);
89191
89256
  const summary3 = await completeSession(session, `Stopped: ${blocker}`);
89192
89257
  return { outcome: "stopped", summary: summary3, turns: turn + 1 };
89193
89258
  }
@@ -89220,6 +89285,8 @@ ${outputContinuationPrefix}`);
89220
89285
  return { outcome: "completed", summary: summary2, turns: turn + 1 };
89221
89286
  }
89222
89287
  }
89288
+ if (holding())
89289
+ return deliverHeld(maxTurns - 1, "the run reached its maximum turns during housekeeping");
89223
89290
  if (backgroundSubagents.size > 0) {
89224
89291
  await drainBackground();
89225
89292
  }
@@ -89453,9 +89520,15 @@ function assistantMessageFromResponse(response) {
89453
89520
  return {
89454
89521
  role: "assistant",
89455
89522
  content: response.content,
89456
- toolCalls: response.toolCalls
89523
+ // The raw text of a malformed call is for this runtime's error, not a
89524
+ // field a provider request may carry.
89525
+ toolCalls: response.toolCalls.map(({ invalidArguments: _invalid, ...call2 }) => call2)
89457
89526
  };
89458
89527
  }
89528
+ function invalidArgumentsError(name, raw) {
89529
+ const shown = raw.length > 300 ? `${raw.slice(0, 300)}\u2026` : raw;
89530
+ 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.`;
89531
+ }
89459
89532
  async function completeMockLlmCall(session, provider, messages, turn) {
89460
89533
  const costCents = 1;
89461
89534
  const reservation = await reserveBudget(session, costCents);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencrowd",
3
- "version": "0.16.0",
3
+ "version": "0.16.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",