@mindstudio-ai/remy 0.1.36 → 0.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/headless.js CHANGED
@@ -593,6 +593,31 @@ async function* streamChatWithRetry(params, options) {
593
593
  return;
594
594
  }
595
595
  }
596
+ var FALLBACK_ACK = "[Message sent to agent. Agent is working in the background and will report back with its results when finished.]";
597
+ async function generateBackgroundAck(params) {
598
+ const url = `${params.apiConfig.baseUrl}/_internal/v2/agent/remy/generate-ack`;
599
+ try {
600
+ const res = await fetch(url, {
601
+ method: "POST",
602
+ headers: {
603
+ "Content-Type": "application/json",
604
+ Authorization: `Bearer ${params.apiConfig.apiKey}`
605
+ },
606
+ body: JSON.stringify({
607
+ agentName: params.agentName,
608
+ task: params.task
609
+ }),
610
+ signal: AbortSignal.timeout(2e4)
611
+ });
612
+ if (!res.ok) {
613
+ return FALLBACK_ACK;
614
+ }
615
+ const data = await res.json();
616
+ return data.message || FALLBACK_ACK;
617
+ } catch (err) {
618
+ return FALLBACK_ACK;
619
+ }
620
+ }
596
621
 
597
622
  // src/tools/spec/readSpec.ts
598
623
  import fs5 from "fs/promises";
@@ -1275,8 +1300,12 @@ function runCli(cmd, options) {
1275
1300
  const maxBuffer = options?.maxBuffer ?? 1024 * 1024;
1276
1301
  const cmdWithLogs = options?.jsonLogs && !cmd.includes("--json-logs") ? cmd.replace(/^(mindstudio\s+\S+)/, "$1 --json-logs") : cmd;
1277
1302
  const child = spawn("sh", ["-c", cmdWithLogs], {
1278
- stdio: ["ignore", "pipe", "pipe"]
1303
+ stdio: [options?.stdin ? "pipe" : "ignore", "pipe", "pipe"]
1279
1304
  });
1305
+ if (options?.stdin) {
1306
+ child.stdin.write(options.stdin);
1307
+ child.stdin.end();
1308
+ }
1280
1309
  const logs = [];
1281
1310
  let stdout = "";
1282
1311
  let stderr = "";
@@ -1430,20 +1459,27 @@ var searchGoogleTool = {
1430
1459
  }
1431
1460
  };
1432
1461
 
1433
- // src/tools/common/setProjectName.ts
1434
- var setProjectNameTool = {
1462
+ // src/tools/common/setProjectMetadata.ts
1463
+ var setProjectMetadataTool = {
1435
1464
  definition: {
1436
- name: "setProjectName",
1437
- description: `Set the project display name. Call this after intake once you have enough context to give the project a clear, descriptive name. Keep it short (2-4 words). Use the app's actual name if the user mentioned one, otherwise pick something descriptive ("Vendor Procurement App", "Recipe Manager").`,
1465
+ name: "setProjectMetadata",
1466
+ description: "Set project metadata. Can update any combination of: display name, app icon, and Open Graph share image. Provide only the fields you want to change.",
1438
1467
  inputSchema: {
1439
1468
  type: "object",
1440
1469
  properties: {
1441
1470
  name: {
1442
1471
  type: "string",
1443
- description: "The project name."
1472
+ description: "Project display name. Keep it short (2-4 words). Use the app's actual name if the user mentioned one, otherwise pick something descriptive."
1473
+ },
1474
+ iconUrl: {
1475
+ type: "string",
1476
+ description: "URL for the app icon (square."
1477
+ },
1478
+ openGraphShareImageUrl: {
1479
+ type: "string",
1480
+ description: "URL for the Open Graph share image (1200x630)."
1444
1481
  }
1445
- },
1446
- required: ["name"]
1482
+ }
1447
1483
  }
1448
1484
  },
1449
1485
  async execute() {
@@ -2066,7 +2102,7 @@ var restartProcessTool = {
2066
2102
  var runScenarioTool = {
2067
2103
  definition: {
2068
2104
  name: "runScenario",
2069
- description: "Run a scenario to seed the dev database with test data. Truncates all tables first, then executes the seed function and impersonates the scenario roles. Blocks until complete. Scenario IDs are defined in mindstudio.json. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for details.",
2105
+ description: "Run a scenario to seed the dev database with test data. Truncates all tables first, then executes the seed function and impersonates the scenario roles. Blocks until complete. Scenario IDs are defined in mindstudio.json. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for details. Return synchronously - no need to sleep before checking results.",
2070
2106
  inputSchema: {
2071
2107
  type: "object",
2072
2108
  properties: {
@@ -2087,7 +2123,7 @@ var runScenarioTool = {
2087
2123
  var runMethodTool = {
2088
2124
  definition: {
2089
2125
  name: "runMethod",
2090
- description: "Run a method in the dev environment and return the result. Use for testing methods after writing or modifying them. Returns output, captured console output, errors with stack traces, and duration. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for more details.",
2126
+ description: "Run a method in the dev environment and return the result. Use for testing methods after writing or modifying them. Returns output, captured console output, errors with stack traces, and duration. If it fails, check .logs/tunnel.log or .logs/requests.ndjson for more details. Return synchronously - no need to sleep before checking results.",
2091
2127
  inputSchema: {
2092
2128
  type: "object",
2093
2129
  properties: {
@@ -2249,6 +2285,12 @@ function startStatusWatcher(config) {
2249
2285
  // src/subagents/common/cleanMessages.ts
2250
2286
  function cleanMessagesForApi(messages) {
2251
2287
  return messages.map((msg) => {
2288
+ if (msg.role === "user" && typeof msg.content === "string" && msg.content.startsWith("@@automated::")) {
2289
+ return {
2290
+ ...msg,
2291
+ content: msg.content.replace(/^@@automated::[^@]*@@\n?/, "")
2292
+ };
2293
+ }
2252
2294
  if (!Array.isArray(msg.content)) {
2253
2295
  return msg;
2254
2296
  }
@@ -2286,238 +2328,264 @@ async function runSubAgent(config) {
2286
2328
  apiConfig,
2287
2329
  model,
2288
2330
  subAgentId,
2289
- signal,
2331
+ signal: parentSignal,
2290
2332
  parentToolId,
2291
2333
  onEvent,
2292
2334
  resolveExternalTool,
2293
- toolRegistry
2335
+ toolRegistry,
2336
+ background,
2337
+ onBackgroundComplete
2294
2338
  } = config;
2339
+ const bgAbort = background ? new AbortController() : null;
2340
+ const signal = background ? bgAbort.signal : parentSignal;
2295
2341
  const emit2 = (e) => {
2296
2342
  onEvent({ ...e, parentToolId });
2297
2343
  };
2298
- const messages = [{ role: "user", content: task }];
2299
- function getPartialText(blocks) {
2300
- return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
2301
- }
2302
- function abortResult(blocks) {
2303
- if (signal?.reason === "graceful") {
2304
- const partial = getPartialText(blocks);
2305
- return {
2306
- text: partial ? `[INTERRUPTED]
2307
-
2308
- ${partial}` : "[INTERRUPTED] Sub-agent was interrupted before producing output.",
2309
- messages
2310
- };
2311
- }
2312
- return { text: "Error: cancelled", messages };
2313
- }
2314
- let lastToolResult = "";
2315
- while (true) {
2316
- if (signal?.aborted) {
2317
- return abortResult([]);
2344
+ const run = async () => {
2345
+ const messages = [{ role: "user", content: task }];
2346
+ function getPartialText(blocks) {
2347
+ return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
2348
+ }
2349
+ function abortResult(blocks) {
2350
+ if (signal?.reason === "graceful") {
2351
+ const partial = getPartialText(blocks);
2352
+ return {
2353
+ text: partial ? `[INTERRUPTED - PARTIAL OUTPUT RETRIEVED] Note that partial output may include thinking text or other unfinalized decisions. It is NOT an authoritative response from this agent.
2354
+
2355
+ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
2356
+ messages
2357
+ };
2358
+ }
2359
+ return { text: "Error: cancelled", messages };
2318
2360
  }
2319
- const contentBlocks = [];
2320
- let thinkingStartedAt = 0;
2321
- let stopReason = "end_turn";
2322
- let currentToolNames = "";
2323
- const statusWatcher = startStatusWatcher({
2324
- apiConfig,
2325
- getContext: () => ({
2326
- assistantText: getPartialText(contentBlocks),
2327
- lastToolName: currentToolNames || void 0,
2328
- lastToolResult: lastToolResult || void 0
2329
- }),
2330
- onStatus: (label) => emit2({ type: "status", message: label }),
2331
- signal
2332
- });
2333
- const fullSystem = `${system}
2361
+ let lastToolResult = "";
2362
+ while (true) {
2363
+ if (signal?.aborted) {
2364
+ return abortResult([]);
2365
+ }
2366
+ const contentBlocks = [];
2367
+ let thinkingStartedAt = 0;
2368
+ let stopReason = "end_turn";
2369
+ let currentToolNames = "";
2370
+ const statusWatcher = startStatusWatcher({
2371
+ apiConfig,
2372
+ getContext: () => ({
2373
+ assistantText: getPartialText(contentBlocks),
2374
+ lastToolName: currentToolNames || void 0,
2375
+ lastToolResult: lastToolResult || void 0
2376
+ }),
2377
+ onStatus: (label) => emit2({ type: "status", message: label }),
2378
+ signal
2379
+ });
2380
+ const fullSystem = `${system}
2334
2381
 
2335
2382
  Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC")}`;
2336
- try {
2337
- for await (const event of streamChatWithRetry({
2338
- ...apiConfig,
2339
- model,
2340
- subAgentId,
2341
- system: fullSystem,
2342
- messages: cleanMessagesForApi(messages),
2343
- tools: tools2,
2344
- signal
2345
- })) {
2346
- if (signal?.aborted) {
2347
- break;
2348
- }
2349
- switch (event.type) {
2350
- case "text": {
2351
- const lastBlock = contentBlocks.at(-1);
2352
- if (lastBlock?.type === "text") {
2353
- lastBlock.text += event.text;
2354
- } else {
2355
- contentBlocks.push({
2356
- type: "text",
2357
- text: event.text,
2358
- startedAt: event.ts
2359
- });
2360
- }
2361
- emit2({ type: "text", text: event.text });
2383
+ try {
2384
+ for await (const event of streamChatWithRetry(
2385
+ {
2386
+ ...apiConfig,
2387
+ model,
2388
+ subAgentId,
2389
+ system: fullSystem,
2390
+ messages: cleanMessagesForApi(messages),
2391
+ tools: tools2,
2392
+ signal
2393
+ },
2394
+ {
2395
+ onRetry: (attempt) => emit2({
2396
+ type: "status",
2397
+ message: `Lost connection, retrying (attempt ${attempt + 2} of 3)...`
2398
+ })
2399
+ }
2400
+ )) {
2401
+ if (signal?.aborted) {
2362
2402
  break;
2363
2403
  }
2364
- case "thinking":
2365
- if (!thinkingStartedAt) {
2366
- thinkingStartedAt = event.ts;
2404
+ switch (event.type) {
2405
+ case "text": {
2406
+ const lastBlock = contentBlocks.at(-1);
2407
+ if (lastBlock?.type === "text") {
2408
+ lastBlock.text += event.text;
2409
+ } else {
2410
+ contentBlocks.push({
2411
+ type: "text",
2412
+ text: event.text,
2413
+ startedAt: event.ts
2414
+ });
2415
+ }
2416
+ emit2({ type: "text", text: event.text });
2417
+ break;
2367
2418
  }
2368
- emit2({ type: "thinking", text: event.text });
2369
- break;
2370
- case "thinking_complete":
2371
- contentBlocks.push({
2372
- type: "thinking",
2373
- thinking: event.thinking,
2374
- signature: event.signature,
2375
- startedAt: thinkingStartedAt,
2376
- completedAt: event.ts
2377
- });
2378
- thinkingStartedAt = 0;
2379
- break;
2380
- case "tool_use":
2381
- contentBlocks.push({
2382
- type: "tool",
2383
- id: event.id,
2384
- name: event.name,
2385
- input: event.input,
2386
- startedAt: Date.now()
2387
- });
2388
- emit2({
2389
- type: "tool_start",
2390
- id: event.id,
2391
- name: event.name,
2392
- input: event.input
2393
- });
2394
- break;
2395
- case "done":
2396
- stopReason = event.stopReason;
2397
- break;
2398
- case "error":
2399
- return { text: `Error: ${event.error}`, messages };
2419
+ case "thinking":
2420
+ if (!thinkingStartedAt) {
2421
+ thinkingStartedAt = event.ts;
2422
+ }
2423
+ emit2({ type: "thinking", text: event.text });
2424
+ break;
2425
+ case "thinking_complete":
2426
+ contentBlocks.push({
2427
+ type: "thinking",
2428
+ thinking: event.thinking,
2429
+ signature: event.signature,
2430
+ startedAt: thinkingStartedAt,
2431
+ completedAt: event.ts
2432
+ });
2433
+ thinkingStartedAt = 0;
2434
+ break;
2435
+ case "tool_use":
2436
+ contentBlocks.push({
2437
+ type: "tool",
2438
+ id: event.id,
2439
+ name: event.name,
2440
+ input: event.input,
2441
+ startedAt: Date.now()
2442
+ });
2443
+ emit2({
2444
+ type: "tool_start",
2445
+ id: event.id,
2446
+ name: event.name,
2447
+ input: event.input
2448
+ });
2449
+ break;
2450
+ case "done":
2451
+ stopReason = event.stopReason;
2452
+ break;
2453
+ case "error":
2454
+ return { text: `Error: ${event.error}`, messages };
2455
+ }
2456
+ }
2457
+ } catch (err) {
2458
+ if (!signal?.aborted) {
2459
+ throw err;
2400
2460
  }
2401
2461
  }
2402
- } catch (err) {
2403
- if (!signal?.aborted) {
2404
- throw err;
2462
+ if (signal?.aborted) {
2463
+ statusWatcher.stop();
2464
+ return abortResult(contentBlocks);
2405
2465
  }
2406
- }
2407
- if (signal?.aborted) {
2408
- statusWatcher.stop();
2409
- return abortResult(contentBlocks);
2410
- }
2411
- messages.push({
2412
- role: "assistant",
2413
- content: contentBlocks
2414
- });
2415
- const toolCalls = contentBlocks.filter(
2416
- (b) => b.type === "tool"
2417
- );
2418
- if (stopReason !== "tool_use" || toolCalls.length === 0) {
2419
- statusWatcher.stop();
2420
- const text = contentBlocks.filter((b) => b.type === "text").map((b) => b.text).join("");
2421
- return { text, messages };
2422
- }
2423
- log.info("Sub-agent executing tools", {
2424
- parentToolId,
2425
- count: toolCalls.length,
2426
- tools: toolCalls.map((tc) => tc.name)
2427
- });
2428
- currentToolNames = toolCalls.map((tc) => tc.name).join(", ");
2429
- const results = await Promise.all(
2430
- toolCalls.map(async (tc) => {
2431
- if (signal?.aborted) {
2432
- return { id: tc.id, result: "Error: cancelled", isError: true };
2433
- }
2434
- let settle;
2435
- const resultPromise = new Promise((res) => {
2436
- settle = (result, isError) => res({ id: tc.id, result, isError });
2437
- });
2438
- let toolAbort = new AbortController();
2439
- const cascadeAbort = () => toolAbort.abort();
2440
- signal?.addEventListener("abort", cascadeAbort, { once: true });
2441
- let settled = false;
2442
- const safeSettle = (result, isError) => {
2443
- if (settled) {
2444
- return;
2466
+ messages.push({
2467
+ role: "assistant",
2468
+ content: contentBlocks
2469
+ });
2470
+ const toolCalls = contentBlocks.filter(
2471
+ (b) => b.type === "tool"
2472
+ );
2473
+ if (stopReason !== "tool_use" || toolCalls.length === 0) {
2474
+ statusWatcher.stop();
2475
+ const text = getPartialText(contentBlocks);
2476
+ return { text, messages };
2477
+ }
2478
+ log.info("Sub-agent executing tools", {
2479
+ parentToolId,
2480
+ count: toolCalls.length,
2481
+ tools: toolCalls.map((tc) => tc.name)
2482
+ });
2483
+ currentToolNames = toolCalls.map((tc) => tc.name).join(", ");
2484
+ const results = await Promise.all(
2485
+ toolCalls.map(async (tc) => {
2486
+ if (signal?.aborted) {
2487
+ return { id: tc.id, result: "Error: cancelled", isError: true };
2445
2488
  }
2446
- settled = true;
2447
- signal?.removeEventListener("abort", cascadeAbort);
2448
- settle(result, isError);
2449
- };
2450
- const run = async (input) => {
2451
- try {
2452
- let result;
2453
- if (externalTools.has(tc.name) && resolveExternalTool) {
2454
- result = await resolveExternalTool(tc.id, tc.name, input);
2455
- } else {
2456
- const onLog = (line) => emit2({
2457
- type: "tool_input_delta",
2458
- id: tc.id,
2459
- name: tc.name,
2460
- result: line
2489
+ let settle;
2490
+ const resultPromise = new Promise((res) => {
2491
+ settle = (result, isError) => res({ id: tc.id, result, isError });
2492
+ });
2493
+ let toolAbort = new AbortController();
2494
+ const cascadeAbort = () => toolAbort.abort();
2495
+ signal?.addEventListener("abort", cascadeAbort, { once: true });
2496
+ let settled = false;
2497
+ const safeSettle = (result, isError) => {
2498
+ if (settled) {
2499
+ return;
2500
+ }
2501
+ settled = true;
2502
+ signal?.removeEventListener("abort", cascadeAbort);
2503
+ settle(result, isError);
2504
+ };
2505
+ const run2 = async (input) => {
2506
+ try {
2507
+ let result;
2508
+ if (externalTools.has(tc.name) && resolveExternalTool) {
2509
+ result = await resolveExternalTool(tc.id, tc.name, input);
2510
+ } else {
2511
+ const onLog = (line) => emit2({
2512
+ type: "tool_input_delta",
2513
+ id: tc.id,
2514
+ name: tc.name,
2515
+ result: line
2516
+ });
2517
+ result = await executeTool2(tc.name, input, tc.id, onLog);
2518
+ }
2519
+ safeSettle(result, result.startsWith("Error"));
2520
+ } catch (err) {
2521
+ safeSettle(`Error: ${err.message}`, true);
2522
+ }
2523
+ };
2524
+ const entry = {
2525
+ id: tc.id,
2526
+ name: tc.name,
2527
+ input: tc.input,
2528
+ parentToolId,
2529
+ abortController: toolAbort,
2530
+ startedAt: Date.now(),
2531
+ settle: safeSettle,
2532
+ rerun: (newInput) => {
2533
+ settled = false;
2534
+ toolAbort = new AbortController();
2535
+ signal?.addEventListener("abort", () => toolAbort.abort(), {
2536
+ once: true
2461
2537
  });
2462
- result = await executeTool2(tc.name, input, tc.id, onLog);
2538
+ entry.abortController = toolAbort;
2539
+ entry.input = newInput;
2540
+ run2(newInput);
2463
2541
  }
2464
- safeSettle(result, result.startsWith("Error"));
2465
- } catch (err) {
2466
- safeSettle(`Error: ${err.message}`, true);
2467
- }
2468
- };
2469
- const entry = {
2470
- id: tc.id,
2471
- name: tc.name,
2472
- input: tc.input,
2473
- parentToolId,
2474
- abortController: toolAbort,
2475
- startedAt: Date.now(),
2476
- settle: safeSettle,
2477
- rerun: (newInput) => {
2478
- settled = false;
2479
- toolAbort = new AbortController();
2480
- signal?.addEventListener("abort", () => toolAbort.abort(), {
2481
- once: true
2482
- });
2483
- entry.abortController = toolAbort;
2484
- entry.input = newInput;
2485
- run(newInput);
2486
- }
2487
- };
2488
- toolRegistry?.register(entry);
2489
- run(tc.input);
2490
- const r = await resultPromise;
2491
- toolRegistry?.unregister(tc.id);
2492
- emit2({
2493
- type: "tool_done",
2494
- id: tc.id,
2495
- name: tc.name,
2496
- result: r.result,
2497
- isError: r.isError
2498
- });
2499
- return r;
2500
- })
2501
- );
2502
- statusWatcher.stop();
2503
- lastToolResult = results.at(-1)?.result ?? "";
2504
- for (const r of results) {
2505
- const block = contentBlocks.find(
2506
- (b) => b.type === "tool" && b.id === r.id
2542
+ };
2543
+ toolRegistry?.register(entry);
2544
+ run2(tc.input);
2545
+ const r = await resultPromise;
2546
+ toolRegistry?.unregister(tc.id);
2547
+ emit2({
2548
+ type: "tool_done",
2549
+ id: tc.id,
2550
+ name: tc.name,
2551
+ result: r.result,
2552
+ isError: r.isError
2553
+ });
2554
+ return r;
2555
+ })
2507
2556
  );
2508
- if (block?.type === "tool") {
2509
- block.result = r.result;
2510
- block.isError = r.isError;
2511
- block.completedAt = Date.now();
2557
+ statusWatcher.stop();
2558
+ lastToolResult = results.at(-1)?.result ?? "";
2559
+ for (const r of results) {
2560
+ const block = contentBlocks.find(
2561
+ (b) => b.type === "tool" && b.id === r.id
2562
+ );
2563
+ if (block?.type === "tool") {
2564
+ block.result = r.result;
2565
+ block.isError = r.isError;
2566
+ block.completedAt = Date.now();
2567
+ }
2568
+ messages.push({
2569
+ role: "user",
2570
+ content: r.result,
2571
+ toolCallId: r.id,
2572
+ isToolError: r.isError
2573
+ });
2512
2574
  }
2513
- messages.push({
2514
- role: "user",
2515
- content: r.result,
2516
- toolCallId: r.id,
2517
- isToolError: r.isError
2518
- });
2519
2575
  }
2576
+ };
2577
+ if (!background) {
2578
+ return run();
2520
2579
  }
2580
+ const ack = await generateBackgroundAck({
2581
+ apiConfig,
2582
+ agentName: subAgentId || "agent",
2583
+ task
2584
+ });
2585
+ run().then((finalResult) => onBackgroundComplete?.(finalResult)).catch(
2586
+ (err) => onBackgroundComplete?.({ text: `Error: ${err.message}`, messages: [] })
2587
+ );
2588
+ return { text: ack, messages: [], backgrounded: true };
2521
2589
  }
2522
2590
 
2523
2591
  // src/subagents/browserAutomation/tools.ts
@@ -2870,9 +2938,7 @@ async function execute3(input, onLog) {
2870
2938
  `mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(imageUrl)} --output-key analysis --no-meta`,
2871
2939
  { timeout: 2e5, onLog }
2872
2940
  );
2873
- return isImageUrl ? analysis : `Screenshot: ${imageUrl}
2874
-
2875
- ${analysis}`;
2941
+ return JSON.stringify({ url: imageUrl, analysis });
2876
2942
  }
2877
2943
 
2878
2944
  // src/subagents/designExpert/tools/analyzeImage.ts
@@ -2884,7 +2950,7 @@ __export(analyzeImage_exports, {
2884
2950
  var DEFAULT_PROMPT = "Describe everything visible in this image \u2014 every element, its position, its size relative to the frame, its colors, its content. Be comprhensive, thorough and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components). Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.";
2885
2951
  var definition4 = {
2886
2952
  name: "analyzeImage",
2887
- description: "Analyze an image by URL. Returns a detailed description of everything visible. Provide a custom prompt to ask a specific question instead of the default full description.",
2953
+ description: "Analyze an image by URL using a vision model. Returns an objective description of what is visible \u2014 shapes, colors, layout, text, artifacts. Use for factual inventory of image contents, not for subjective design judgment - the vision model providing the analysis has no sense of design. You are the design expert - use the analysis tool for factual inventory, then apply your own expertise for quality and suitability assessments.",
2888
2954
  inputSchema: {
2889
2955
  type: "object",
2890
2956
  properties: {
@@ -2982,10 +3048,12 @@ async function seedreamGenerate(opts) {
2982
3048
  }
2983
3049
  }
2984
3050
  }));
2985
- const batchResult = await runCli(
2986
- `mindstudio batch ${JSON.stringify(JSON.stringify(steps))} --no-meta`,
2987
- { jsonLogs: true, timeout: 2e5, onLog }
2988
- );
3051
+ const batchResult = await runCli(`mindstudio batch --no-meta`, {
3052
+ jsonLogs: true,
3053
+ timeout: 2e5,
3054
+ onLog,
3055
+ stdin: JSON.stringify(steps)
3056
+ });
2989
3057
  try {
2990
3058
  const parsed = JSON.parse(batchResult);
2991
3059
  imageUrls = parsed.map(
@@ -3358,6 +3426,10 @@ var designExpertTool = {
3358
3426
  task: {
3359
3427
  type: "string",
3360
3428
  description: "What you need, in natural language. Include context about the project when relevant."
3429
+ },
3430
+ background: {
3431
+ type: "boolean",
3432
+ description: "Run in background \u2014 returns immediately and doesn't block while continuing to do work in the background. Reports results when finished working."
3361
3433
  }
3362
3434
  },
3363
3435
  required: ["task"]
@@ -3380,7 +3452,16 @@ var designExpertTool = {
3380
3452
  parentToolId: context.toolCallId,
3381
3453
  onEvent: context.onEvent,
3382
3454
  resolveExternalTool: context.resolveExternalTool,
3383
- toolRegistry: context.toolRegistry
3455
+ toolRegistry: context.toolRegistry,
3456
+ background: input.background,
3457
+ onBackgroundComplete: input.background ? (bgResult) => {
3458
+ context.onBackgroundComplete?.(
3459
+ context.toolCallId,
3460
+ "visualDesignExpert",
3461
+ bgResult.text,
3462
+ bgResult.messages
3463
+ );
3464
+ } : void 0
3384
3465
  });
3385
3466
  context.subAgentMessages?.set(context.toolCallId, result.messages);
3386
3467
  return result.text;
@@ -3641,6 +3722,10 @@ var productVisionTool = {
3641
3722
  task: {
3642
3723
  type: "string",
3643
3724
  description: "Brief description of what happened or what is needed. Do not specify how many items to create, what topics to cover, or how to think. The agent reads the spec files and decides for itself."
3725
+ },
3726
+ background: {
3727
+ type: "boolean",
3728
+ description: "Run in background \u2014 returns immediately and doesn't block while continuing to do work in the background. Reports results when finished working."
3644
3729
  }
3645
3730
  },
3646
3731
  required: ["task"]
@@ -3663,7 +3748,16 @@ var productVisionTool = {
3663
3748
  parentToolId: context.toolCallId,
3664
3749
  onEvent: context.onEvent,
3665
3750
  resolveExternalTool: context.resolveExternalTool,
3666
- toolRegistry: context.toolRegistry
3751
+ toolRegistry: context.toolRegistry,
3752
+ background: input.background,
3753
+ onBackgroundComplete: input.background ? (bgResult) => {
3754
+ context.onBackgroundComplete?.(
3755
+ context.toolCallId,
3756
+ "productVision",
3757
+ bgResult.text,
3758
+ bgResult.messages
3759
+ );
3760
+ } : void 0
3667
3761
  });
3668
3762
  context.subAgentMessages?.set(context.toolCallId, result.messages);
3669
3763
  return result.text;
@@ -3840,7 +3934,7 @@ function getCommonTools() {
3840
3934
  askMindStudioSdkTool,
3841
3935
  fetchUrlTool,
3842
3936
  searchGoogleTool,
3843
- setProjectNameTool,
3937
+ setProjectMetadataTool,
3844
3938
  designExpertTool,
3845
3939
  productVisionTool,
3846
3940
  codeSanityCheckTool
@@ -4167,7 +4261,7 @@ var EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
4167
4261
  "runScenario",
4168
4262
  "runMethod",
4169
4263
  "browserCommand",
4170
- "setProjectName"
4264
+ "setProjectMetadata"
4171
4265
  ]);
4172
4266
  function createAgentState() {
4173
4267
  return { messages: [] };
@@ -4185,7 +4279,8 @@ async function runTurn(params) {
4185
4279
  onEvent,
4186
4280
  resolveExternalTool,
4187
4281
  hidden,
4188
- toolRegistry
4282
+ toolRegistry,
4283
+ onBackgroundComplete
4189
4284
  } = params;
4190
4285
  const tools2 = getToolDefinitions(onboardingState);
4191
4286
  log.info("Turn started", {
@@ -4198,8 +4293,7 @@ async function runTurn(params) {
4198
4293
  }
4199
4294
  });
4200
4295
  onEvent({ type: "turn_started" });
4201
- const cleanMessage = userMessage.replace(/^@@automated::[^@]*@@/, "");
4202
- const userMsg = { role: "user", content: cleanMessage };
4296
+ const userMsg = { role: "user", content: userMessage };
4203
4297
  if (hidden) {
4204
4298
  userMsg.hidden = true;
4205
4299
  }
@@ -4213,7 +4307,7 @@ async function runTurn(params) {
4213
4307
  state.messages.push(userMsg);
4214
4308
  const STATUS_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
4215
4309
  "setProjectOnboardingState",
4216
- "setProjectName",
4310
+ "setProjectMetadata",
4217
4311
  "clearSyncStatus",
4218
4312
  "editsFinished"
4219
4313
  ]);
@@ -4400,7 +4494,8 @@ async function runTurn(params) {
4400
4494
  id: event.id,
4401
4495
  name: event.name,
4402
4496
  input: event.input,
4403
- startedAt: event.ts
4497
+ startedAt: event.ts,
4498
+ ...event.input.background && { background: true }
4404
4499
  });
4405
4500
  const acc = toolInputAccumulators.get(event.id);
4406
4501
  const tool = getToolByName(event.name);
@@ -4522,6 +4617,7 @@ async function runTurn(params) {
4522
4617
  toolCallId: tc.id,
4523
4618
  subAgentMessages,
4524
4619
  toolRegistry,
4620
+ onBackgroundComplete,
4525
4621
  onLog: (line) => wrappedOnEvent({
4526
4622
  type: "tool_input_delta",
4527
4623
  id: tc.id,
@@ -4745,6 +4841,54 @@ async function startHeadless(opts = {}) {
4745
4841
  const pendingTools = /* @__PURE__ */ new Map();
4746
4842
  const earlyResults = /* @__PURE__ */ new Map();
4747
4843
  const toolRegistry = new ToolRegistry();
4844
+ const backgroundQueue = [];
4845
+ function flushBackgroundQueue() {
4846
+ if (backgroundQueue.length === 0) {
4847
+ return;
4848
+ }
4849
+ const results = backgroundQueue.splice(0);
4850
+ const xmlParts = results.map(
4851
+ (r) => `<tool_result id="${r.toolCallId}" name="${r.name}">
4852
+ ${r.result}
4853
+ </tool_result>`
4854
+ ).join("\n\n");
4855
+ const message = `@@automated::background_results@@
4856
+ <background_results>
4857
+ ${xmlParts}
4858
+ </background_results>`;
4859
+ handleMessage({ action: "message", text: message }, void 0);
4860
+ }
4861
+ function onBackgroundComplete(toolCallId, name, result, subAgentMessages) {
4862
+ for (const msg of state.messages) {
4863
+ if (!Array.isArray(msg.content)) {
4864
+ continue;
4865
+ }
4866
+ for (const block of msg.content) {
4867
+ if (block.type === "tool" && block.id === toolCallId) {
4868
+ block.backgroundResult = result;
4869
+ block.completedAt = Date.now();
4870
+ if (subAgentMessages) {
4871
+ block.subAgentMessages = subAgentMessages;
4872
+ }
4873
+ }
4874
+ }
4875
+ }
4876
+ onEvent({
4877
+ type: "tool_background_complete",
4878
+ id: toolCallId,
4879
+ name,
4880
+ result
4881
+ });
4882
+ backgroundQueue.push({
4883
+ toolCallId,
4884
+ name,
4885
+ result,
4886
+ completedAt: Date.now()
4887
+ });
4888
+ if (!running) {
4889
+ flushBackgroundQueue();
4890
+ }
4891
+ }
4748
4892
  const USER_FACING_TOOLS = /* @__PURE__ */ new Set([
4749
4893
  "promptUser",
4750
4894
  "confirmDestructiveAction",
@@ -4785,6 +4929,7 @@ async function startHeadless(opts = {}) {
4785
4929
  case "turn_done":
4786
4930
  completedEmitted = true;
4787
4931
  emit("completed", { success: true }, rid);
4932
+ setTimeout(() => flushBackgroundQueue(), 0);
4788
4933
  return;
4789
4934
  case "turn_cancelled":
4790
4935
  completedEmitted = true;
@@ -4831,6 +4976,7 @@ async function startHeadless(opts = {}) {
4831
4976
  name: e.name,
4832
4977
  input: e.input,
4833
4978
  ...e.partial && { partial: true },
4979
+ ...e.background && { background: true },
4834
4980
  ...e.parentToolId && { parentToolId: e.parentToolId }
4835
4981
  },
4836
4982
  rid
@@ -4849,6 +4995,18 @@ async function startHeadless(opts = {}) {
4849
4995
  rid
4850
4996
  );
4851
4997
  return;
4998
+ case "tool_background_complete":
4999
+ emit(
5000
+ "tool_background_complete",
5001
+ {
5002
+ id: e.id,
5003
+ name: e.name,
5004
+ result: e.result,
5005
+ ...e.parentToolId && { parentToolId: e.parentToolId }
5006
+ },
5007
+ rid
5008
+ );
5009
+ return;
4852
5010
  case "tool_stopped":
4853
5011
  emit(
4854
5012
  "tool_stopped",
@@ -4916,6 +5074,7 @@ async function startHeadless(opts = {}) {
4916
5074
  }
4917
5075
  let userMessage = parsed.text ?? "";
4918
5076
  const isCommand = !!parsed.runCommand;
5077
+ const isHidden = isCommand || !!parsed.hidden;
4919
5078
  if (parsed.runCommand === "sync") {
4920
5079
  userMessage = loadActionPrompt("sync");
4921
5080
  } else if (parsed.runCommand === "publish") {
@@ -4940,8 +5099,9 @@ async function startHeadless(opts = {}) {
4940
5099
  signal: currentAbort.signal,
4941
5100
  onEvent,
4942
5101
  resolveExternalTool,
4943
- hidden: isCommand,
4944
- toolRegistry
5102
+ hidden: isHidden,
5103
+ toolRegistry,
5104
+ onBackgroundComplete
4945
5105
  });
4946
5106
  if (!completedEmitted) {
4947
5107
  emit(