opencrowd 0.16.4 → 0.16.6

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 +157 -36
  2. package/package.json +1 -1
@@ -262,7 +262,7 @@ var init_paths = __esm({
262
262
  });
263
263
 
264
264
  // ../../packages/core/dist/artifacts.js
265
- import { mkdir as mkdir3, readFile as readFile3 } from "node:fs/promises";
265
+ import { mkdir as mkdir3, readFile as readFile3, readdir, stat } from "node:fs/promises";
266
266
  import { dirname as dirname4, join as join3, relative as relative2 } from "node:path";
267
267
  async function saveArtifact(state, path3, content, metadata) {
268
268
  const target = await safeArtifactPath(state, path3);
@@ -283,21 +283,75 @@ async function readArtifact(state, path3) {
283
283
  return readFile3(await safeArtifactPath(state, path3), "utf8");
284
284
  }
285
285
  async function listArtifacts(state, prefix = "") {
286
- const { readdir: readdir3 } = await import("node:fs/promises");
287
286
  const start = await safeArtifactPath(state, prefix || ".");
288
287
  const files = [];
289
- await walk4(start, files, state.artifactsDir);
288
+ await walkFiles(start, state.artifactsDir, files);
290
289
  return files.sort();
291
- async function walk4(dir, out, root) {
292
- for (const entry of await readdir3(dir, { withFileTypes: true })) {
293
- const full = join3(dir, entry.name);
294
- if (entry.isDirectory()) {
295
- await walk4(full, out, root);
296
- } else if (entry.isFile()) {
297
- out.push(relative2(root, full));
298
- }
290
+ }
291
+ async function listOtherChatArtifacts(state) {
292
+ const sessionsDir = dirname4(state.sessionDir);
293
+ let entries;
294
+ try {
295
+ entries = await readdir(sessionsDir, { withFileTypes: true });
296
+ } catch {
297
+ return { chats: [], truncated: false };
298
+ }
299
+ const others = [];
300
+ for (const entry of entries) {
301
+ if (!entry.isDirectory() || entry.name === state.sessionId)
302
+ continue;
303
+ try {
304
+ assertPathId(entry.name);
305
+ } catch {
306
+ continue;
307
+ }
308
+ const dir = join3(sessionsDir, entry.name, "artifacts");
309
+ try {
310
+ const info = await stat(join3(sessionsDir, entry.name));
311
+ others.push({ id: entry.name, dir, mtime: info.mtimeMs });
312
+ } catch {
299
313
  }
300
314
  }
315
+ others.sort((a, b) => b.mtime - a.mtime);
316
+ const chats = [];
317
+ let remaining = OTHER_CHAT_FILE_LIMIT, truncated = false;
318
+ for (const other of others) {
319
+ if (remaining <= 0) {
320
+ truncated = true;
321
+ break;
322
+ }
323
+ let root;
324
+ try {
325
+ root = await containedPath(sessionsDir, join3(other.id, "artifacts"));
326
+ } catch {
327
+ continue;
328
+ }
329
+ const files = [];
330
+ try {
331
+ await walkFiles(root, root, files);
332
+ } catch {
333
+ continue;
334
+ }
335
+ if (!files.length)
336
+ continue;
337
+ files.sort();
338
+ if (files.length > remaining) {
339
+ truncated = true;
340
+ files.length = remaining;
341
+ }
342
+ remaining -= files.length;
343
+ chats.push({ session_id: other.id, shell_dir: `../${other.id}/artifacts`, files });
344
+ }
345
+ return { chats, truncated };
346
+ }
347
+ async function walkFiles(dir, root, out) {
348
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
349
+ const full = join3(dir, entry.name);
350
+ if (entry.isDirectory())
351
+ await walkFiles(full, root, out);
352
+ else if (entry.isFile())
353
+ out.push(relative2(root, full));
354
+ }
301
355
  }
302
356
  function safeArtifactPath(state, requestedPath) {
303
357
  const normalizedRequest = requestedPath.replace(/^artifacts\//, "");
@@ -305,11 +359,13 @@ function safeArtifactPath(state, requestedPath) {
305
359
  throw new Error(`artifact path: ${error53.message}`);
306
360
  });
307
361
  }
362
+ var OTHER_CHAT_FILE_LIMIT;
308
363
  var init_artifacts = __esm({
309
364
  "../../packages/core/dist/artifacts.js"() {
310
365
  "use strict";
311
366
  init_ledger();
312
367
  init_paths();
368
+ OTHER_CHAT_FILE_LIMIT = 300;
313
369
  }
314
370
  });
315
371
 
@@ -15429,6 +15485,12 @@ var init_primitives = __esm({
15429
15485
  */
15430
15486
  inputPriceUsdPerMillion: external_exports.number().nonnegative().max(1e4).optional(),
15431
15487
  cachedInputPriceUsdPerMillion: external_exports.number().nonnegative().max(1e4).optional(),
15488
+ /**
15489
+ * The host offers this model for conversation only: the run advertises no
15490
+ * tools, sends no tool-use instructions, and executes no tool call. For a
15491
+ * model that writes its tool calls as text and makes up their results.
15492
+ */
15493
+ chatOnly: external_exports.boolean().optional(),
15432
15494
  submodel: external_exports.string().min(1).optional()
15433
15495
  });
15434
15496
  BudgetSchema = external_exports.looseObject({
@@ -17017,7 +17079,7 @@ function toolDescription(name) {
17017
17079
  case "read_file":
17018
17080
  return "Read a session artifact.";
17019
17081
  case "list_files":
17020
- return "List session artifacts.";
17082
+ return `List this chat's artifacts. Each chat with the owner has its own artifacts/ directory, but the owner's Files view shows every chat's files together: set scope="all" to also list files from your other chats before you say a file the owner mentions does not exist.`;
17021
17083
  case "run_shell":
17022
17084
  return "Run a gated local shell command only when shell access is enabled for the session.";
17023
17085
  case "spawn_subagent":
@@ -17041,7 +17103,10 @@ function toolParameters(name) {
17041
17103
  case "get_budget_status":
17042
17104
  return objectSchema({});
17043
17105
  case "list_files":
17044
- return objectSchema({ prefix: stringSchema("Optional artifact path prefix.") });
17106
+ return objectSchema({
17107
+ prefix: stringSchema("Optional artifact path prefix (this chat only)."),
17108
+ scope: { type: "string", enum: ["chat", "all"], description: "chat (default): this chat's artifacts. all: also every other chat's files, each with the shell_dir to reach it from run_shell." }
17109
+ });
17045
17110
  case "save_file":
17046
17111
  return objectSchema({
17047
17112
  path: stringSchema("Relative artifact path."),
@@ -17239,8 +17304,21 @@ async function executeTool(name, args, context) {
17239
17304
  return ok(await saveArtifact(context.session, requiredString(args.path, "path"), requiredString(args.content, "content"), objectValue(args.metadata)));
17240
17305
  case "read_file":
17241
17306
  return ok({ content: await readArtifact(context.session, requiredString(args.path, "path")) });
17242
- case "list_files":
17243
- return ok(await listArtifacts(context.session, optionalString(args.prefix, "prefix") ?? ""));
17307
+ case "list_files": {
17308
+ const files = await listArtifacts(context.session, optionalString(args.prefix, "prefix") ?? "");
17309
+ const scope = optionalString(args.scope, "scope") ?? "chat";
17310
+ if (scope !== "chat" && scope !== "all")
17311
+ throw new Error("scope must be chat or all");
17312
+ if (scope === "chat")
17313
+ return ok(files);
17314
+ const other = await listOtherChatArtifacts(context.session);
17315
+ return ok({
17316
+ files,
17317
+ other_chats: other.chats,
17318
+ ...other.truncated ? { truncated: true } : {},
17319
+ note: other.chats.length ? "Files from other chats are already the owner's and already in their Files view. Read one with run_shell at <shell_dir>/<file> (run_shell starts in this chat's session directory). To attach or upload one from this chat, copy it into artifacts/ first; never re-buy or regenerate a file you already hold." : "No other chat of this agent has files."
17320
+ });
17321
+ }
17244
17322
  case "run_shell": {
17245
17323
  context.onProgress?.({ type: "running_shell", message: "Running gated shell command" });
17246
17324
  const result = await runShell(context.session, requiredString(args.command, "command"), optionalString(args.cwd, "cwd") ?? context.session.sessionDir, shellTimeoutMs(args.timeout_ms), { signal: context.signal });
@@ -87491,19 +87569,47 @@ var init_runtime2 = __esm({
87491
87569
  }
87492
87570
  });
87493
87571
 
87572
+ // ../../packages/agent-runtime/dist/chat-only.js
87573
+ function chatOnlyMessages(messages) {
87574
+ return messages.flatMap((message) => {
87575
+ if (message.role === "tool")
87576
+ return [];
87577
+ if (message.role !== "assistant" || !message.toolCalls?.length)
87578
+ return [message];
87579
+ if (!message.content.trim())
87580
+ return [];
87581
+ const { toolCalls: _calls, ...rest } = message;
87582
+ return [rest];
87583
+ });
87584
+ }
87585
+ var CHAT_ONLY_SYSTEM_PROMPT;
87586
+ var init_chat_only = __esm({
87587
+ "../../packages/agent-runtime/dist/chat-only.js"() {
87588
+ "use strict";
87589
+ CHAT_ONLY_SYSTEM_PROMPT = [
87590
+ "You are a hosted CrowdCode agent, talking with your owner.",
87591
+ "This agent runs on a chat-only model: in this chat you have no tools. You cannot run commands or code, read, write or send files, browse or search the web, buy or call paid services, send email or messages, schedule reminders or recurring tasks, or move money.",
87592
+ "Talk with the owner: answer questions, explain, write, brainstorm, and take part in role-play or creative writing when they ask for it, from your own knowledge.",
87593
+ "When the owner asks for something that needs one of those abilities, say plainly that this agent is on a chat-only model and cannot do it, suggest switching this agent to a model that can use tools in the model picker, and offer what you can do in words instead.",
87594
+ "Never pretend to have done such a thing. Do not write out a tool or function call, and never invent command output, search results, file contents, prices, or payment, booking or delivery confirmations.",
87595
+ "You cannot check anything live, so say so when an answer depends on current facts."
87596
+ ].join(" ");
87597
+ }
87598
+ });
87599
+
87494
87600
  // ../../packages/agent-runtime/dist/hosted-provider.js
87495
87601
  import { request } from "node:http";
87496
87602
  import { isAbsolute as isAbsolute3 } from "node:path";
87497
87603
  function createHostedProvider(options) {
87498
87604
  if (!isAbsolute3(options.socketPath))
87499
87605
  throw new Error("Hosted bridge socket must be absolute");
87500
- const tools = [...OPEN_CROWD_TOOLS.filter((tool) => !["spawn_subagent", "check_subagents"].includes(tool.name)).map((tool) => tool.name === "get_budget_status" ? { ...tool, description: "Read authoritative hosted finances: live USDC wallet balance, settled inference and service spending, pending holds, and remaining authorized budget for this run." } : tool), ...options.extraTools ?? []];
87606
+ const tools = options.chatOnly ? [] : [...OPEN_CROWD_TOOLS.filter((tool) => !["spawn_subagent", "check_subagents"].includes(tool.name)).map((tool) => tool.name === "get_budget_status" ? { ...tool, description: "Read authoritative hosted finances: live USDC wallet balance, settled inference and service spending, pending holds, and remaining authorized budget for this run." } : tool), ...options.extraTools ?? []];
87501
87607
  return {
87502
87608
  async complete(messages, context) {
87503
87609
  const operationId = context?.operationId;
87504
87610
  if (!operationId?.startsWith(`${options.runId}:llm:`))
87505
87611
  throw new Error("Hosted model operation scope is missing");
87506
- const body = JSON.stringify({ runId: options.runId, sessionId: options.sessionId, operationId, messages: messages.map(({ contextArchive: _archive, ...message }) => message), tools });
87612
+ const body = JSON.stringify({ runId: options.runId, sessionId: options.sessionId, operationId, messages: (options.chatOnly ? chatOnlyMessages(messages) : messages).map(({ contextArchive: _archive, ...message }) => message), tools });
87507
87613
  if (Buffer.byteLength(body) > MODEL_SELLER_REQUEST_MAX_BYTES)
87508
87614
  throw new ContextWindowExceeded(`Hosted request is ${Buffer.byteLength(body)} bytes, above the ${MODEL_SELLER_REQUEST_MAX_BYTES} a seller accepts`);
87509
87615
  const result = await new Promise((resolve9, reject) => {
@@ -87682,6 +87788,7 @@ var init_hosted_provider = __esm({
87682
87788
  init_dist2();
87683
87789
  init_dist4();
87684
87790
  init_worker();
87791
+ init_chat_only();
87685
87792
  HostedRequestError = class extends Error {
87686
87793
  code;
87687
87794
  status;
@@ -88833,11 +88940,12 @@ var init_worker = __esm({
88833
88940
  await beginQuery(session, runId);
88834
88941
  await this.emit("run.state", { sessionId: session.sessionId, state: "running" }, runId, run.commandId);
88835
88942
  const local = this.options.toolExecutor ?? executeTool;
88836
- const hosted = this.options.hostedTools?.(run.start, session);
88837
- const economy = this.options.economy?.(run.start, session);
88943
+ const chatOnly = run.start.payload.modelPolicy.chatOnly === true;
88944
+ const hosted = chatOnly ? void 0 : this.options.hostedTools?.(run.start, session);
88945
+ const economy = chatOnly ? void 0 : this.options.economy?.(run.start, session);
88838
88946
  const dynamicTools = economy ? hostedDynamicTools(economy) : void 0;
88839
88947
  let latestFinancialState = { status: "unavailable", reason: "Hosted financial state has not been read." };
88840
- const financialState = this.options.financialState ? async () => {
88948
+ const financialState = this.options.financialState && !chatOnly ? async () => {
88841
88949
  try {
88842
88950
  latestFinancialState = await this.options.financialState(run.start, session);
88843
88951
  } catch {
@@ -88978,6 +89086,7 @@ var init_worker = __esm({
88978
89086
  signal,
88979
89087
  resume: run.checkpoint,
88980
89088
  maxTurns: run.start.payload.maxTurns,
89089
+ ...chatOnly ? { chatOnly: true } : {},
88981
89090
  contextWindowTokens: run.start.payload.modelPolicy.contextWindowTokens,
88982
89091
  maxOutputTokens: run.start.payload.modelPolicy.maxOutputTokens,
88983
89092
  prices: {
@@ -89181,8 +89290,10 @@ function createMockToolExecutor() {
89181
89290
  });
89182
89291
  case "read_file":
89183
89292
  return ok3({ content: `Mock file content for ${stringValue6(args.path) ?? "unknown path"}.` });
89184
- case "list_files":
89185
- return ok3(["mock-output.txt"].filter((path3) => path3.startsWith(stringValue6(args.prefix) ?? "")));
89293
+ case "list_files": {
89294
+ const files = ["mock-output.txt"].filter((path3) => path3.startsWith(stringValue6(args.prefix) ?? ""));
89295
+ return ok3(args.scope === "all" ? { files, other_chats: [], note: "No other chat of this agent has files." } : files);
89296
+ }
89186
89297
  case "run_shell":
89187
89298
  context.onProgress?.({ type: "running_shell", message: "Mock running gated shell command" });
89188
89299
  return ok3({
@@ -89326,8 +89437,8 @@ function wireToolDefinitions(names, extraTools) {
89326
89437
  ];
89327
89438
  }
89328
89439
  async function runAgentTaskDetailed(session, task, options = {}) {
89329
- const enabledTools = options.tools ?? (options.hosted ? TOOL_NAMES.filter((name) => !["spawn_subagent", "check_subagents", "get_budget_status"].includes(name)) : TOOL_NAMES.filter((name) => !HOSTED_ONLY_TOOL_NAMES.includes(name) && (options.subagent || name !== "spawn_subagent")));
89330
- const dynamicDefinitions = options.dynamicTools?.definitions ?? [];
89440
+ const enabledTools = options.chatOnly ? [] : options.tools ?? (options.hosted ? TOOL_NAMES.filter((name) => !["spawn_subagent", "check_subagents", "get_budget_status"].includes(name)) : TOOL_NAMES.filter((name) => !HOSTED_ONLY_TOOL_NAMES.includes(name) && (options.subagent || name !== "spawn_subagent")));
89441
+ const dynamicDefinitions = options.chatOnly ? [] : options.dynamicTools?.definitions ?? [];
89331
89442
  const model = options.llm?.catalog?.find((m) => m.id === options.llm?.model);
89332
89443
  const limits = contextLimits(options.contextWindowTokens ?? model?.contextWindowTokens, options.maxOutputTokens ?? model?.maxOutputTokens);
89333
89444
  const contextState = { ...options.resume?.context };
@@ -89348,7 +89459,7 @@ async function runAgentTaskDetailed(session, task, options = {}) {
89348
89459
  throw new Error("no LLM provider configured: pass an explicit provider (tests/demo) or a typed llm runtime");
89349
89460
  }
89350
89461
  const toolExecutor = options.toolExecutor ?? executeTool;
89351
- const knowledge = options.capabilityIndex === void 0 && options.knowledge !== false && options.dynamicTools ? await loadKnowledgeTree(session, options.knowledge ?? {}) : void 0;
89462
+ const knowledge = !options.chatOnly && options.capabilityIndex === void 0 && options.knowledge !== false && options.dynamicTools ? await loadKnowledgeTree(session, options.knowledge ?? {}) : void 0;
89352
89463
  const capabilityIndex = options.capabilityIndex ?? (knowledge ? renderCapabilityIndex(knowledge.l0) : void 0);
89353
89464
  const systemPromptParts = [
89354
89465
  options.hosted ? "You are a hosted OpenCrowd agent running in the cloud with your own USDC wallets on Base, Solana and Tempo." : "You are the local OpenCrowd agent, a CLI agent with a USDC wallet running on the user's personal machine.",
@@ -89358,7 +89469,7 @@ async function runAgentTaskDetailed(session, task, options = {}) {
89358
89469
  if (options.hosted)
89359
89470
  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.");
89360
89471
  if (options.hosted)
89361
- 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.");
89472
+ 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.", `Files from your other chats: each chat with the owner is its own session with its own artifacts/, so list_files and read_file see only this one, while the owner's Files view shows every chat's files together. When the owner mentions a file, PDF or report you do not see here, call list_files with scope="all" before you say it does not exist; it lists each other chat's files with the shell_dir that run_shell reaches them at.`);
89362
89473
  if (options.dynamicTools) {
89363
89474
  systemPromptParts.push("When the local computer is not the right environment, or after one clear local capability failure, buy external capability: find_paid_service to discover, inspect_paid_service to see the exact schema/price/reputation, call_paid_service to execute through the enforced purchase lifecycle, and review_paid_service for the required review after every confirmed paid call (success or failure). The same review tool accepts unpaid experiences; review relevant free results or failed attempts without inventing payment evidence. Distinguish provider faults from local errors and uncertain failures: a call that never reached the seller is never the seller's fault. funding_required means your USDC is on the wrong network, a cap or a declined approval means the owner's policy stopped you, and an unpayable rail means you could not pay at all \u2014 in every one of those the service was never asked, so it gets no review and certainly not a low rating. Fix the cause and call it again instead; the gateway refuses those reviews, and trying anyway wastes a turn. A platform judge, not you, writes the public score and text of a paid review from the platform's record of the call; your rating and notes are evidence it reads, so report accurately and never try to talk it into a rating.", `Choosing a service: find_paid_service with a short capability query (for example "web search" or "object storage", not the task's subject) searches every catalog (AgentCash, Coinbase's x402 catalog, the MPP catalog and CrowdCode's reviews) across Base, Solana and Tempo and returns services you can pay, ranked best first by CrowdCode: quality (score and verified reviews), then effective price on the service's best network, including about $0.03 to move USDC between your own networks when that network's balance is short, then your balance there. Prefer the highest-ranked service that fits the task, whatever its network; a better or cheaper service on Solana or Tempo beats a Base default. Each result also lists \`networks\`, the networks it can be paid on; between services that fit the task equally well, one you can pay from a balance you already hold beats one that needs a transfer first, because a move costs about $0.03. When the chosen service is marked transfer_needed, move USDC between your own networks first if you have a tool for that; otherwise tell the user which network needs funds. When the ranking is unavailable, compare candidates yourself with inspect_paid_service (reputation, price and network) against your per-network balances.`, ...options.hosted ? [
89364
89475
  "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.",
@@ -89387,7 +89498,7 @@ async function runAgentTaskDetailed(session, task, options = {}) {
89387
89498
  systemPromptParts.push(`Delegation: spawn_subagent runs a cheaper, faster model (${options.subagent.model}${options.subagent.costHint ? `, ${options.subagent.costHint}` : ""}) with local file and shell tools only.`, "Delegate ONLY when you can spawn two or more INDEPENDENT subagents in the same reply \u2014 parallel fan-out is the entire benefit; multiple spawn_subagent calls in one reply run concurrently (2-4 is the sweet spot).", "If the work is one sequential thread where each step depends on the previous result, do it yourself in this main loop: a chain of single subagents is strictly slower than working directly and adds spawn/read overhead on top.", "Give each subagent an objective, expected output format, and clear boundaries so parallel subagents cannot make conflicting decisions; they see none of this conversation, so pass explicit context (file paths, constraints).", "Each subagent's files land under artifacts/subagents/<n>/ and its completion lists what it wrote.", "Suitable subagent work: summarizing, extracting, mechanical edits, or reading specific already-known URLs (subagents can curl free URLs via run_shell); keep planning, paid-service decisions, and final answers in this main loop.", "For web research: first buy one paid web search here in the main loop, then fan the returned URLs out to parallel subagents in a single reply. Never send a subagent off to guess URLs with curl.");
89388
89499
  }
89389
89500
  const messages = options.resume?.messages ?? [
89390
- { role: "system", content: systemPromptParts.join(" ") },
89501
+ { role: "system", content: options.chatOnly ? CHAT_ONLY_SYSTEM_PROMPT : systemPromptParts.join(" ") },
89391
89502
  ...options.history ?? [],
89392
89503
  { role: "user", content: task }
89393
89504
  ];
@@ -89540,6 +89651,8 @@ async function runAgentTaskDetailed(session, task, options = {}) {
89540
89651
  }
89541
89652
  throw error53;
89542
89653
  }
89654
+ if (options.chatOnly && response.toolCalls.length)
89655
+ response = { ...response, toolCalls: [] };
89543
89656
  if (!restored?.response && (response.content || response.toolCalls.length > 0)) {
89544
89657
  const assistantMessage = assistantMessageFromResponse(isOutputLimitFinishReason(response.finishReason) ? { ...response, toolCalls: [] } : response);
89545
89658
  messages.push(assistantMessage);
@@ -89559,7 +89672,7 @@ ${outputContinuationPrefix}`);
89559
89672
  outputContinuationNudges += 1;
89560
89673
  const nudge = {
89561
89674
  role: "user",
89562
- content: "Your previous response hit the provider output limit. Any tool calls in that response were discarded and did not execute. If you were calling a tool, issue a complete, shorter tool call. Otherwise continue the answer exactly where it stopped. Put only the remaining suffix in complete_session.final_message; OpenCrowd will prepend the saved prefix. Do not repeat completed material."
89675
+ content: options.chatOnly ? "Your previous response hit the provider output limit. Continue the answer exactly where it stopped, without repeating what you already wrote." : "Your previous response hit the provider output limit. Any tool calls in that response were discarded and did not execute. If you were calling a tool, issue a complete, shorter tool call. Otherwise continue the answer exactly where it stopped. Put only the remaining suffix in complete_session.final_message; OpenCrowd will prepend the saved prefix. Do not repeat completed material."
89563
89676
  };
89564
89677
  messages.push(nudge);
89565
89678
  await options.onMessage?.(nudge);
@@ -89570,7 +89683,7 @@ ${outputContinuationPrefix}`);
89570
89683
  continue;
89571
89684
  const finalMessage = response.content.trim() ? outputContinuationPrefix + response.content : pendingFinalMessage ?? outputContinuationPrefix;
89572
89685
  recordFinalMessage(finalMessage);
89573
- const blocker = await options.completionGate?.(completionNudges > 0);
89686
+ const blocker = options.chatOnly ? void 0 : await options.completionGate?.(completionNudges > 0);
89574
89687
  const unanswered = answerNudges < 1 && placeholder();
89575
89688
  if (blocker) {
89576
89689
  if (completionNudges >= 1) {
@@ -90341,6 +90454,8 @@ var init_dist6 = __esm({
90341
90454
  init_hosted_economy();
90342
90455
  init_inbox();
90343
90456
  init_x402_proxy();
90457
+ init_chat_only();
90458
+ init_chat_only();
90344
90459
  init_knowledge();
90345
90460
  init_context2();
90346
90461
  init_tool_deadlines();
@@ -90591,7 +90706,7 @@ var init_dist6 = __esm({
90591
90706
  });
90592
90707
 
90593
90708
  // src/shared.ts
90594
- import { readdir } from "node:fs/promises";
90709
+ import { readdir as readdir2 } from "node:fs/promises";
90595
90710
  import { join as join24, resolve as resolve5 } from "node:path";
90596
90711
  import { stdout as output } from "node:process";
90597
90712
  function renderKeyValues(value) {
@@ -90716,7 +90831,7 @@ function isConsumedOption(args, index2, options) {
90716
90831
  async function latestSessionId(workspaceRoot) {
90717
90832
  const sessionsDir = join24(resolve5(workspaceRoot), "sessions");
90718
90833
  try {
90719
- const entries = await readdir(sessionsDir, { withFileTypes: true });
90834
+ const entries = await readdir2(sessionsDir, { withFileTypes: true });
90720
90835
  return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort().at(-1);
90721
90836
  } catch (error53) {
90722
90837
  if (error53.code === "ENOENT") {
@@ -90744,7 +90859,13 @@ async function workerCommand(args) {
90744
90859
  if (args.includes("--demo") === !!socketPath) throw new Error("worker requires exactly one of --demo or --bridge-socket <path>");
90745
90860
  const worker = new MachineWorker({
90746
90861
  agentHome: home,
90747
- provider: (run, session, extraTools) => socketPath ? createHostedProvider({ socketPath, runId: run.runId, sessionId: session.sessionId, extraTools }) : createWorkerDemoProvider(),
90862
+ provider: (run, session, extraTools) => socketPath ? createHostedProvider({
90863
+ socketPath,
90864
+ runId: run.runId,
90865
+ sessionId: session.sessionId,
90866
+ extraTools,
90867
+ chatOnly: run.payload.modelPolicy.chatOnly === true
90868
+ }) : createWorkerDemoProvider(),
90748
90869
  hostedTools: socketPath ? (run, session) => createHostedToolExecutor({ socketPath, runId: run.runId, sessionId: session.sessionId }) : void 0,
90749
90870
  economy: socketPath ? (run, session) => createHostedEconomy({ socketPath, runId: run.runId, sessionId: session.sessionId, session }) : void 0,
90750
90871
  financialState: socketPath ? (run, session) => readHostedFinancialState({ socketPath, runId: run.runId, sessionId: session.sessionId }) : void 0,
@@ -91899,7 +92020,7 @@ var init_tasks = __esm({
91899
92020
  });
91900
92021
 
91901
92022
  // ../../packages/evals/dist/scoring.js
91902
- import { readFile as readFile21, stat } from "node:fs/promises";
92023
+ import { readFile as readFile21, stat as stat2 } from "node:fs/promises";
91903
92024
  import { resolve as resolveDns } from "node:dns/promises";
91904
92025
  import { join as join30 } from "node:path";
91905
92026
  async function scoreTask(task, finalMessage, context = {}) {
@@ -91949,7 +92070,7 @@ async function scoreTask(task, finalMessage, context = {}) {
91949
92070
  const path3 = join30(context.artifactsDir, name);
91950
92071
  let size5;
91951
92072
  try {
91952
- size5 = (await stat(path3)).size;
92073
+ size5 = (await stat2(path3)).size;
91953
92074
  } catch {
91954
92075
  continue;
91955
92076
  }
@@ -92547,7 +92668,7 @@ var init_evidence = __esm({
92547
92668
 
92548
92669
  // ../../packages/evals/dist/knowledge.js
92549
92670
  import { createHash as createHash8 } from "node:crypto";
92550
- import { mkdir as mkdir18, readdir as readdir2, readFile as readFile23, writeFile as writeFile12 } from "node:fs/promises";
92671
+ import { mkdir as mkdir18, readdir as readdir3, readFile as readFile23, writeFile as writeFile12 } from "node:fs/promises";
92551
92672
  import { dirname as dirname15, join as join32, relative as relative4 } from "node:path";
92552
92673
  function approxTokens(text) {
92553
92674
  return Math.ceil(text.length / 4);
@@ -92650,7 +92771,7 @@ async function writeTree(dir, tree, meta4) {
92650
92771
  async function readTree(dir) {
92651
92772
  const tree = {};
92652
92773
  const walk4 = async (current) => {
92653
- for (const entry of await readdir2(current, { withFileTypes: true })) {
92774
+ for (const entry of await readdir3(current, { withFileTypes: true })) {
92654
92775
  const path3 = join32(current, entry.name);
92655
92776
  if (entry.isDirectory()) {
92656
92777
  await walk4(path3);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencrowd",
3
- "version": "0.16.4",
3
+ "version": "0.16.6",
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",