@mindstudio-ai/remy 0.1.36 → 0.1.37

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/index.js CHANGED
@@ -250,13 +250,38 @@ async function* streamChatWithRetry(params, options) {
250
250
  return;
251
251
  }
252
252
  }
253
- var MAX_RETRIES, INITIAL_BACKOFF_MS;
253
+ async function generateBackgroundAck(params) {
254
+ const url = `${params.apiConfig.baseUrl}/_internal/v2/agent/remy/generate-ack`;
255
+ try {
256
+ const res = await fetch(url, {
257
+ method: "POST",
258
+ headers: {
259
+ "Content-Type": "application/json",
260
+ Authorization: `Bearer ${params.apiConfig.apiKey}`
261
+ },
262
+ body: JSON.stringify({
263
+ agentName: params.agentName,
264
+ task: params.task
265
+ }),
266
+ signal: AbortSignal.timeout(2e4)
267
+ });
268
+ if (!res.ok) {
269
+ return FALLBACK_ACK;
270
+ }
271
+ const data = await res.json();
272
+ return data.message || FALLBACK_ACK;
273
+ } catch (err) {
274
+ return FALLBACK_ACK;
275
+ }
276
+ }
277
+ var MAX_RETRIES, INITIAL_BACKOFF_MS, FALLBACK_ACK;
254
278
  var init_api = __esm({
255
279
  "src/api.ts"() {
256
280
  "use strict";
257
281
  init_logger();
258
282
  MAX_RETRIES = 3;
259
283
  INITIAL_BACKOFF_MS = 1e3;
284
+ FALLBACK_ACK = "[Message sent to agent. Agent is working in the background and will report back with its results when finished.]";
260
285
  }
261
286
  });
262
287
 
@@ -1020,8 +1045,12 @@ function runCli(cmd, options) {
1020
1045
  const maxBuffer = options?.maxBuffer ?? 1024 * 1024;
1021
1046
  const cmdWithLogs = options?.jsonLogs && !cmd.includes("--json-logs") ? cmd.replace(/^(mindstudio\s+\S+)/, "$1 --json-logs") : cmd;
1022
1047
  const child = spawn("sh", ["-c", cmdWithLogs], {
1023
- stdio: ["ignore", "pipe", "pipe"]
1048
+ stdio: [options?.stdin ? "pipe" : "ignore", "pipe", "pipe"]
1024
1049
  });
1050
+ if (options?.stdin) {
1051
+ child.stdin.write(options.stdin);
1052
+ child.stdin.end();
1053
+ }
1025
1054
  const logs = [];
1026
1055
  let stdout = "";
1027
1056
  let stderr = "";
@@ -1201,24 +1230,31 @@ var init_searchGoogle = __esm({
1201
1230
  }
1202
1231
  });
1203
1232
 
1204
- // src/tools/common/setProjectName.ts
1205
- var setProjectNameTool;
1206
- var init_setProjectName = __esm({
1207
- "src/tools/common/setProjectName.ts"() {
1233
+ // src/tools/common/setProjectMetadata.ts
1234
+ var setProjectMetadataTool;
1235
+ var init_setProjectMetadata = __esm({
1236
+ "src/tools/common/setProjectMetadata.ts"() {
1208
1237
  "use strict";
1209
- setProjectNameTool = {
1238
+ setProjectMetadataTool = {
1210
1239
  definition: {
1211
- name: "setProjectName",
1212
- 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").`,
1240
+ name: "setProjectMetadata",
1241
+ 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.",
1213
1242
  inputSchema: {
1214
1243
  type: "object",
1215
1244
  properties: {
1216
1245
  name: {
1217
1246
  type: "string",
1218
- description: "The project name."
1247
+ 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."
1248
+ },
1249
+ iconUrl: {
1250
+ type: "string",
1251
+ description: "URL for the app icon (square."
1252
+ },
1253
+ openGraphShareImageUrl: {
1254
+ type: "string",
1255
+ description: "URL for the Open Graph share image (1200x630)."
1219
1256
  }
1220
- },
1221
- required: ["name"]
1257
+ }
1222
1258
  }
1223
1259
  },
1224
1260
  async execute() {
@@ -1972,7 +2008,7 @@ var init_runScenario = __esm({
1972
2008
  runScenarioTool = {
1973
2009
  definition: {
1974
2010
  name: "runScenario",
1975
- 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.",
2011
+ 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.",
1976
2012
  inputSchema: {
1977
2013
  type: "object",
1978
2014
  properties: {
@@ -1999,7 +2035,7 @@ var init_runMethod = __esm({
1999
2035
  runMethodTool = {
2000
2036
  definition: {
2001
2037
  name: "runMethod",
2002
- 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.",
2038
+ 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.",
2003
2039
  inputSchema: {
2004
2040
  type: "object",
2005
2041
  properties: {
@@ -2185,6 +2221,12 @@ var init_statusWatcher = __esm({
2185
2221
  // src/subagents/common/cleanMessages.ts
2186
2222
  function cleanMessagesForApi(messages) {
2187
2223
  return messages.map((msg) => {
2224
+ if (msg.role === "user" && typeof msg.content === "string" && msg.content.startsWith("@@automated::")) {
2225
+ return {
2226
+ ...msg,
2227
+ content: msg.content.replace(/^@@automated::[^@]*@@\n?/, "")
2228
+ };
2229
+ }
2188
2230
  if (!Array.isArray(msg.content)) {
2189
2231
  return msg;
2190
2232
  }
@@ -2227,238 +2269,264 @@ async function runSubAgent(config) {
2227
2269
  apiConfig,
2228
2270
  model,
2229
2271
  subAgentId,
2230
- signal,
2272
+ signal: parentSignal,
2231
2273
  parentToolId,
2232
2274
  onEvent,
2233
2275
  resolveExternalTool,
2234
- toolRegistry
2276
+ toolRegistry,
2277
+ background,
2278
+ onBackgroundComplete
2235
2279
  } = config;
2280
+ const bgAbort = background ? new AbortController() : null;
2281
+ const signal = background ? bgAbort.signal : parentSignal;
2236
2282
  const emit2 = (e) => {
2237
2283
  onEvent({ ...e, parentToolId });
2238
2284
  };
2239
- const messages = [{ role: "user", content: task }];
2240
- function getPartialText(blocks) {
2241
- return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
2242
- }
2243
- function abortResult(blocks) {
2244
- if (signal?.reason === "graceful") {
2245
- const partial = getPartialText(blocks);
2246
- return {
2247
- text: partial ? `[INTERRUPTED]
2248
-
2249
- ${partial}` : "[INTERRUPTED] Sub-agent was interrupted before producing output.",
2250
- messages
2251
- };
2285
+ const run = async () => {
2286
+ const messages = [{ role: "user", content: task }];
2287
+ function getPartialText(blocks) {
2288
+ return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
2252
2289
  }
2253
- return { text: "Error: cancelled", messages };
2254
- }
2255
- let lastToolResult = "";
2256
- while (true) {
2257
- if (signal?.aborted) {
2258
- return abortResult([]);
2290
+ function abortResult(blocks) {
2291
+ if (signal?.reason === "graceful") {
2292
+ const partial = getPartialText(blocks);
2293
+ return {
2294
+ 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.
2295
+
2296
+ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
2297
+ messages
2298
+ };
2299
+ }
2300
+ return { text: "Error: cancelled", messages };
2259
2301
  }
2260
- const contentBlocks = [];
2261
- let thinkingStartedAt = 0;
2262
- let stopReason = "end_turn";
2263
- let currentToolNames = "";
2264
- const statusWatcher = startStatusWatcher({
2265
- apiConfig,
2266
- getContext: () => ({
2267
- assistantText: getPartialText(contentBlocks),
2268
- lastToolName: currentToolNames || void 0,
2269
- lastToolResult: lastToolResult || void 0
2270
- }),
2271
- onStatus: (label) => emit2({ type: "status", message: label }),
2272
- signal
2273
- });
2274
- const fullSystem = `${system}
2302
+ let lastToolResult = "";
2303
+ while (true) {
2304
+ if (signal?.aborted) {
2305
+ return abortResult([]);
2306
+ }
2307
+ const contentBlocks = [];
2308
+ let thinkingStartedAt = 0;
2309
+ let stopReason = "end_turn";
2310
+ let currentToolNames = "";
2311
+ const statusWatcher = startStatusWatcher({
2312
+ apiConfig,
2313
+ getContext: () => ({
2314
+ assistantText: getPartialText(contentBlocks),
2315
+ lastToolName: currentToolNames || void 0,
2316
+ lastToolResult: lastToolResult || void 0
2317
+ }),
2318
+ onStatus: (label) => emit2({ type: "status", message: label }),
2319
+ signal
2320
+ });
2321
+ const fullSystem = `${system}
2275
2322
 
2276
2323
  Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC")}`;
2277
- try {
2278
- for await (const event of streamChatWithRetry({
2279
- ...apiConfig,
2280
- model,
2281
- subAgentId,
2282
- system: fullSystem,
2283
- messages: cleanMessagesForApi(messages),
2284
- tools: tools2,
2285
- signal
2286
- })) {
2287
- if (signal?.aborted) {
2288
- break;
2289
- }
2290
- switch (event.type) {
2291
- case "text": {
2292
- const lastBlock = contentBlocks.at(-1);
2293
- if (lastBlock?.type === "text") {
2294
- lastBlock.text += event.text;
2295
- } else {
2296
- contentBlocks.push({
2297
- type: "text",
2298
- text: event.text,
2299
- startedAt: event.ts
2300
- });
2301
- }
2302
- emit2({ type: "text", text: event.text });
2324
+ try {
2325
+ for await (const event of streamChatWithRetry(
2326
+ {
2327
+ ...apiConfig,
2328
+ model,
2329
+ subAgentId,
2330
+ system: fullSystem,
2331
+ messages: cleanMessagesForApi(messages),
2332
+ tools: tools2,
2333
+ signal
2334
+ },
2335
+ {
2336
+ onRetry: (attempt) => emit2({
2337
+ type: "status",
2338
+ message: `Lost connection, retrying (attempt ${attempt + 2} of 3)...`
2339
+ })
2340
+ }
2341
+ )) {
2342
+ if (signal?.aborted) {
2303
2343
  break;
2304
2344
  }
2305
- case "thinking":
2306
- if (!thinkingStartedAt) {
2307
- thinkingStartedAt = event.ts;
2345
+ switch (event.type) {
2346
+ case "text": {
2347
+ const lastBlock = contentBlocks.at(-1);
2348
+ if (lastBlock?.type === "text") {
2349
+ lastBlock.text += event.text;
2350
+ } else {
2351
+ contentBlocks.push({
2352
+ type: "text",
2353
+ text: event.text,
2354
+ startedAt: event.ts
2355
+ });
2356
+ }
2357
+ emit2({ type: "text", text: event.text });
2358
+ break;
2308
2359
  }
2309
- emit2({ type: "thinking", text: event.text });
2310
- break;
2311
- case "thinking_complete":
2312
- contentBlocks.push({
2313
- type: "thinking",
2314
- thinking: event.thinking,
2315
- signature: event.signature,
2316
- startedAt: thinkingStartedAt,
2317
- completedAt: event.ts
2318
- });
2319
- thinkingStartedAt = 0;
2320
- break;
2321
- case "tool_use":
2322
- contentBlocks.push({
2323
- type: "tool",
2324
- id: event.id,
2325
- name: event.name,
2326
- input: event.input,
2327
- startedAt: Date.now()
2328
- });
2329
- emit2({
2330
- type: "tool_start",
2331
- id: event.id,
2332
- name: event.name,
2333
- input: event.input
2334
- });
2335
- break;
2336
- case "done":
2337
- stopReason = event.stopReason;
2338
- break;
2339
- case "error":
2340
- return { text: `Error: ${event.error}`, messages };
2360
+ case "thinking":
2361
+ if (!thinkingStartedAt) {
2362
+ thinkingStartedAt = event.ts;
2363
+ }
2364
+ emit2({ type: "thinking", text: event.text });
2365
+ break;
2366
+ case "thinking_complete":
2367
+ contentBlocks.push({
2368
+ type: "thinking",
2369
+ thinking: event.thinking,
2370
+ signature: event.signature,
2371
+ startedAt: thinkingStartedAt,
2372
+ completedAt: event.ts
2373
+ });
2374
+ thinkingStartedAt = 0;
2375
+ break;
2376
+ case "tool_use":
2377
+ contentBlocks.push({
2378
+ type: "tool",
2379
+ id: event.id,
2380
+ name: event.name,
2381
+ input: event.input,
2382
+ startedAt: Date.now()
2383
+ });
2384
+ emit2({
2385
+ type: "tool_start",
2386
+ id: event.id,
2387
+ name: event.name,
2388
+ input: event.input
2389
+ });
2390
+ break;
2391
+ case "done":
2392
+ stopReason = event.stopReason;
2393
+ break;
2394
+ case "error":
2395
+ return { text: `Error: ${event.error}`, messages };
2396
+ }
2397
+ }
2398
+ } catch (err) {
2399
+ if (!signal?.aborted) {
2400
+ throw err;
2341
2401
  }
2342
2402
  }
2343
- } catch (err) {
2344
- if (!signal?.aborted) {
2345
- throw err;
2403
+ if (signal?.aborted) {
2404
+ statusWatcher.stop();
2405
+ return abortResult(contentBlocks);
2346
2406
  }
2347
- }
2348
- if (signal?.aborted) {
2349
- statusWatcher.stop();
2350
- return abortResult(contentBlocks);
2351
- }
2352
- messages.push({
2353
- role: "assistant",
2354
- content: contentBlocks
2355
- });
2356
- const toolCalls = contentBlocks.filter(
2357
- (b) => b.type === "tool"
2358
- );
2359
- if (stopReason !== "tool_use" || toolCalls.length === 0) {
2360
- statusWatcher.stop();
2361
- const text = contentBlocks.filter((b) => b.type === "text").map((b) => b.text).join("");
2362
- return { text, messages };
2363
- }
2364
- log.info("Sub-agent executing tools", {
2365
- parentToolId,
2366
- count: toolCalls.length,
2367
- tools: toolCalls.map((tc) => tc.name)
2368
- });
2369
- currentToolNames = toolCalls.map((tc) => tc.name).join(", ");
2370
- const results = await Promise.all(
2371
- toolCalls.map(async (tc) => {
2372
- if (signal?.aborted) {
2373
- return { id: tc.id, result: "Error: cancelled", isError: true };
2374
- }
2375
- let settle;
2376
- const resultPromise = new Promise((res) => {
2377
- settle = (result, isError) => res({ id: tc.id, result, isError });
2378
- });
2379
- let toolAbort = new AbortController();
2380
- const cascadeAbort = () => toolAbort.abort();
2381
- signal?.addEventListener("abort", cascadeAbort, { once: true });
2382
- let settled = false;
2383
- const safeSettle = (result, isError) => {
2384
- if (settled) {
2385
- return;
2407
+ messages.push({
2408
+ role: "assistant",
2409
+ content: contentBlocks
2410
+ });
2411
+ const toolCalls = contentBlocks.filter(
2412
+ (b) => b.type === "tool"
2413
+ );
2414
+ if (stopReason !== "tool_use" || toolCalls.length === 0) {
2415
+ statusWatcher.stop();
2416
+ const text = getPartialText(contentBlocks);
2417
+ return { text, messages };
2418
+ }
2419
+ log.info("Sub-agent executing tools", {
2420
+ parentToolId,
2421
+ count: toolCalls.length,
2422
+ tools: toolCalls.map((tc) => tc.name)
2423
+ });
2424
+ currentToolNames = toolCalls.map((tc) => tc.name).join(", ");
2425
+ const results = await Promise.all(
2426
+ toolCalls.map(async (tc) => {
2427
+ if (signal?.aborted) {
2428
+ return { id: tc.id, result: "Error: cancelled", isError: true };
2386
2429
  }
2387
- settled = true;
2388
- signal?.removeEventListener("abort", cascadeAbort);
2389
- settle(result, isError);
2390
- };
2391
- const run = async (input) => {
2392
- try {
2393
- let result;
2394
- if (externalTools.has(tc.name) && resolveExternalTool) {
2395
- result = await resolveExternalTool(tc.id, tc.name, input);
2396
- } else {
2397
- const onLog = (line) => emit2({
2398
- type: "tool_input_delta",
2399
- id: tc.id,
2400
- name: tc.name,
2401
- result: line
2430
+ let settle;
2431
+ const resultPromise = new Promise((res) => {
2432
+ settle = (result, isError) => res({ id: tc.id, result, isError });
2433
+ });
2434
+ let toolAbort = new AbortController();
2435
+ const cascadeAbort = () => toolAbort.abort();
2436
+ signal?.addEventListener("abort", cascadeAbort, { once: true });
2437
+ let settled = false;
2438
+ const safeSettle = (result, isError) => {
2439
+ if (settled) {
2440
+ return;
2441
+ }
2442
+ settled = true;
2443
+ signal?.removeEventListener("abort", cascadeAbort);
2444
+ settle(result, isError);
2445
+ };
2446
+ const run2 = async (input) => {
2447
+ try {
2448
+ let result;
2449
+ if (externalTools.has(tc.name) && resolveExternalTool) {
2450
+ result = await resolveExternalTool(tc.id, tc.name, input);
2451
+ } else {
2452
+ const onLog = (line) => emit2({
2453
+ type: "tool_input_delta",
2454
+ id: tc.id,
2455
+ name: tc.name,
2456
+ result: line
2457
+ });
2458
+ result = await executeTool2(tc.name, input, tc.id, onLog);
2459
+ }
2460
+ safeSettle(result, result.startsWith("Error"));
2461
+ } catch (err) {
2462
+ safeSettle(`Error: ${err.message}`, true);
2463
+ }
2464
+ };
2465
+ const entry = {
2466
+ id: tc.id,
2467
+ name: tc.name,
2468
+ input: tc.input,
2469
+ parentToolId,
2470
+ abortController: toolAbort,
2471
+ startedAt: Date.now(),
2472
+ settle: safeSettle,
2473
+ rerun: (newInput) => {
2474
+ settled = false;
2475
+ toolAbort = new AbortController();
2476
+ signal?.addEventListener("abort", () => toolAbort.abort(), {
2477
+ once: true
2402
2478
  });
2403
- result = await executeTool2(tc.name, input, tc.id, onLog);
2479
+ entry.abortController = toolAbort;
2480
+ entry.input = newInput;
2481
+ run2(newInput);
2404
2482
  }
2405
- safeSettle(result, result.startsWith("Error"));
2406
- } catch (err) {
2407
- safeSettle(`Error: ${err.message}`, true);
2408
- }
2409
- };
2410
- const entry = {
2411
- id: tc.id,
2412
- name: tc.name,
2413
- input: tc.input,
2414
- parentToolId,
2415
- abortController: toolAbort,
2416
- startedAt: Date.now(),
2417
- settle: safeSettle,
2418
- rerun: (newInput) => {
2419
- settled = false;
2420
- toolAbort = new AbortController();
2421
- signal?.addEventListener("abort", () => toolAbort.abort(), {
2422
- once: true
2423
- });
2424
- entry.abortController = toolAbort;
2425
- entry.input = newInput;
2426
- run(newInput);
2427
- }
2428
- };
2429
- toolRegistry?.register(entry);
2430
- run(tc.input);
2431
- const r = await resultPromise;
2432
- toolRegistry?.unregister(tc.id);
2433
- emit2({
2434
- type: "tool_done",
2435
- id: tc.id,
2436
- name: tc.name,
2437
- result: r.result,
2438
- isError: r.isError
2439
- });
2440
- return r;
2441
- })
2442
- );
2443
- statusWatcher.stop();
2444
- lastToolResult = results.at(-1)?.result ?? "";
2445
- for (const r of results) {
2446
- const block = contentBlocks.find(
2447
- (b) => b.type === "tool" && b.id === r.id
2483
+ };
2484
+ toolRegistry?.register(entry);
2485
+ run2(tc.input);
2486
+ const r = await resultPromise;
2487
+ toolRegistry?.unregister(tc.id);
2488
+ emit2({
2489
+ type: "tool_done",
2490
+ id: tc.id,
2491
+ name: tc.name,
2492
+ result: r.result,
2493
+ isError: r.isError
2494
+ });
2495
+ return r;
2496
+ })
2448
2497
  );
2449
- if (block?.type === "tool") {
2450
- block.result = r.result;
2451
- block.isError = r.isError;
2452
- block.completedAt = Date.now();
2498
+ statusWatcher.stop();
2499
+ lastToolResult = results.at(-1)?.result ?? "";
2500
+ for (const r of results) {
2501
+ const block = contentBlocks.find(
2502
+ (b) => b.type === "tool" && b.id === r.id
2503
+ );
2504
+ if (block?.type === "tool") {
2505
+ block.result = r.result;
2506
+ block.isError = r.isError;
2507
+ block.completedAt = Date.now();
2508
+ }
2509
+ messages.push({
2510
+ role: "user",
2511
+ content: r.result,
2512
+ toolCallId: r.id,
2513
+ isToolError: r.isError
2514
+ });
2453
2515
  }
2454
- messages.push({
2455
- role: "user",
2456
- content: r.result,
2457
- toolCallId: r.id,
2458
- isToolError: r.isError
2459
- });
2460
2516
  }
2517
+ };
2518
+ if (!background) {
2519
+ return run();
2461
2520
  }
2521
+ const ack = await generateBackgroundAck({
2522
+ apiConfig,
2523
+ agentName: subAgentId || "agent",
2524
+ task
2525
+ });
2526
+ run().then((finalResult) => onBackgroundComplete?.(finalResult)).catch(
2527
+ (err) => onBackgroundComplete?.({ text: `Error: ${err.message}`, messages: [] })
2528
+ );
2529
+ return { text: ack, messages: [], backgrounded: true };
2462
2530
  }
2463
2531
  var init_runner = __esm({
2464
2532
  "src/subagents/runner.ts"() {
@@ -2864,9 +2932,7 @@ async function execute3(input, onLog) {
2864
2932
  `mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(imageUrl)} --output-key analysis --no-meta`,
2865
2933
  { timeout: 2e5, onLog }
2866
2934
  );
2867
- return isImageUrl ? analysis : `Screenshot: ${imageUrl}
2868
-
2869
- ${analysis}`;
2935
+ return JSON.stringify({ url: imageUrl, analysis });
2870
2936
  }
2871
2937
  var DESIGN_REFERENCE_PROMPT, definition3;
2872
2938
  var init_analyzeDesign = __esm({
@@ -2938,7 +3004,7 @@ var init_analyzeImage = __esm({
2938
3004
  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.";
2939
3005
  definition4 = {
2940
3006
  name: "analyzeImage",
2941
- 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.",
3007
+ 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.",
2942
3008
  inputSchema: {
2943
3009
  type: "object",
2944
3010
  properties: {
@@ -3028,10 +3094,12 @@ async function seedreamGenerate(opts) {
3028
3094
  }
3029
3095
  }
3030
3096
  }));
3031
- const batchResult = await runCli(
3032
- `mindstudio batch ${JSON.stringify(JSON.stringify(steps))} --no-meta`,
3033
- { jsonLogs: true, timeout: 2e5, onLog }
3034
- );
3097
+ const batchResult = await runCli(`mindstudio batch --no-meta`, {
3098
+ jsonLogs: true,
3099
+ timeout: 2e5,
3100
+ onLog,
3101
+ stdin: JSON.stringify(steps)
3102
+ });
3035
3103
  try {
3036
3104
  const parsed = JSON.parse(batchResult);
3037
3105
  imageUrls = parsed.map(
@@ -3478,6 +3546,10 @@ Visual design expert. Describe the situation and what you need \u2014 the agent
3478
3546
  task: {
3479
3547
  type: "string",
3480
3548
  description: "What you need, in natural language. Include context about the project when relevant."
3549
+ },
3550
+ background: {
3551
+ type: "boolean",
3552
+ description: "Run in background \u2014 returns immediately and doesn't block while continuing to do work in the background. Reports results when finished working."
3481
3553
  }
3482
3554
  },
3483
3555
  required: ["task"]
@@ -3500,7 +3572,16 @@ Visual design expert. Describe the situation and what you need \u2014 the agent
3500
3572
  parentToolId: context.toolCallId,
3501
3573
  onEvent: context.onEvent,
3502
3574
  resolveExternalTool: context.resolveExternalTool,
3503
- toolRegistry: context.toolRegistry
3575
+ toolRegistry: context.toolRegistry,
3576
+ background: input.background,
3577
+ onBackgroundComplete: input.background ? (bgResult) => {
3578
+ context.onBackgroundComplete?.(
3579
+ context.toolCallId,
3580
+ "visualDesignExpert",
3581
+ bgResult.text,
3582
+ bgResult.messages
3583
+ );
3584
+ } : void 0
3504
3585
  });
3505
3586
  context.subAgentMessages?.set(context.toolCallId, result.messages);
3506
3587
  return result.text;
@@ -3794,6 +3875,10 @@ var init_productVision = __esm({
3794
3875
  task: {
3795
3876
  type: "string",
3796
3877
  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."
3878
+ },
3879
+ background: {
3880
+ type: "boolean",
3881
+ description: "Run in background \u2014 returns immediately and doesn't block while continuing to do work in the background. Reports results when finished working."
3797
3882
  }
3798
3883
  },
3799
3884
  required: ["task"]
@@ -3816,7 +3901,16 @@ var init_productVision = __esm({
3816
3901
  parentToolId: context.toolCallId,
3817
3902
  onEvent: context.onEvent,
3818
3903
  resolveExternalTool: context.resolveExternalTool,
3819
- toolRegistry: context.toolRegistry
3904
+ toolRegistry: context.toolRegistry,
3905
+ background: input.background,
3906
+ onBackgroundComplete: input.background ? (bgResult) => {
3907
+ context.onBackgroundComplete?.(
3908
+ context.toolCallId,
3909
+ "productVision",
3910
+ bgResult.text,
3911
+ bgResult.messages
3912
+ );
3913
+ } : void 0
3820
3914
  });
3821
3915
  context.subAgentMessages?.set(context.toolCallId, result.messages);
3822
3916
  return result.text;
@@ -4012,7 +4106,7 @@ function getCommonTools() {
4012
4106
  askMindStudioSdkTool,
4013
4107
  fetchUrlTool,
4014
4108
  searchGoogleTool,
4015
- setProjectNameTool,
4109
+ setProjectMetadataTool,
4016
4110
  designExpertTool,
4017
4111
  productVisionTool,
4018
4112
  codeSanityCheckTool
@@ -4077,7 +4171,7 @@ var init_tools5 = __esm({
4077
4171
  init_sdkConsultant();
4078
4172
  init_fetchUrl();
4079
4173
  init_searchGoogle();
4080
- init_setProjectName();
4174
+ init_setProjectMetadata();
4081
4175
  init_readFile();
4082
4176
  init_writeFile();
4083
4177
  init_editFile();
@@ -4400,7 +4494,8 @@ async function runTurn(params) {
4400
4494
  onEvent,
4401
4495
  resolveExternalTool,
4402
4496
  hidden,
4403
- toolRegistry
4497
+ toolRegistry,
4498
+ onBackgroundComplete
4404
4499
  } = params;
4405
4500
  const tools2 = getToolDefinitions(onboardingState);
4406
4501
  log.info("Turn started", {
@@ -4413,8 +4508,7 @@ async function runTurn(params) {
4413
4508
  }
4414
4509
  });
4415
4510
  onEvent({ type: "turn_started" });
4416
- const cleanMessage = userMessage.replace(/^@@automated::[^@]*@@/, "");
4417
- const userMsg = { role: "user", content: cleanMessage };
4511
+ const userMsg = { role: "user", content: userMessage };
4418
4512
  if (hidden) {
4419
4513
  userMsg.hidden = true;
4420
4514
  }
@@ -4428,7 +4522,7 @@ async function runTurn(params) {
4428
4522
  state.messages.push(userMsg);
4429
4523
  const STATUS_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
4430
4524
  "setProjectOnboardingState",
4431
- "setProjectName",
4525
+ "setProjectMetadata",
4432
4526
  "clearSyncStatus",
4433
4527
  "editsFinished"
4434
4528
  ]);
@@ -4615,7 +4709,8 @@ async function runTurn(params) {
4615
4709
  id: event.id,
4616
4710
  name: event.name,
4617
4711
  input: event.input,
4618
- startedAt: event.ts
4712
+ startedAt: event.ts,
4713
+ ...event.input.background && { background: true }
4619
4714
  });
4620
4715
  const acc = toolInputAccumulators.get(event.id);
4621
4716
  const tool = getToolByName(event.name);
@@ -4737,6 +4832,7 @@ async function runTurn(params) {
4737
4832
  toolCallId: tc.id,
4738
4833
  subAgentMessages,
4739
4834
  toolRegistry,
4835
+ onBackgroundComplete,
4740
4836
  onLog: (line) => wrappedOnEvent({
4741
4837
  type: "tool_input_delta",
4742
4838
  id: tc.id,
@@ -4843,7 +4939,7 @@ var init_agent = __esm({
4843
4939
  "runScenario",
4844
4940
  "runMethod",
4845
4941
  "browserCommand",
4846
- "setProjectName"
4942
+ "setProjectMetadata"
4847
4943
  ]);
4848
4944
  }
4849
4945
  });
@@ -5305,6 +5401,54 @@ async function startHeadless(opts = {}) {
5305
5401
  const pendingTools = /* @__PURE__ */ new Map();
5306
5402
  const earlyResults = /* @__PURE__ */ new Map();
5307
5403
  const toolRegistry = new ToolRegistry();
5404
+ const backgroundQueue = [];
5405
+ function flushBackgroundQueue() {
5406
+ if (backgroundQueue.length === 0) {
5407
+ return;
5408
+ }
5409
+ const results = backgroundQueue.splice(0);
5410
+ const xmlParts = results.map(
5411
+ (r) => `<tool_result id="${r.toolCallId}" name="${r.name}">
5412
+ ${r.result}
5413
+ </tool_result>`
5414
+ ).join("\n\n");
5415
+ const message = `@@automated::background_results@@
5416
+ <background_results>
5417
+ ${xmlParts}
5418
+ </background_results>`;
5419
+ handleMessage({ action: "message", text: message }, void 0);
5420
+ }
5421
+ function onBackgroundComplete(toolCallId, name, result, subAgentMessages) {
5422
+ for (const msg of state.messages) {
5423
+ if (!Array.isArray(msg.content)) {
5424
+ continue;
5425
+ }
5426
+ for (const block of msg.content) {
5427
+ if (block.type === "tool" && block.id === toolCallId) {
5428
+ block.backgroundResult = result;
5429
+ block.completedAt = Date.now();
5430
+ if (subAgentMessages) {
5431
+ block.subAgentMessages = subAgentMessages;
5432
+ }
5433
+ }
5434
+ }
5435
+ }
5436
+ onEvent({
5437
+ type: "tool_background_complete",
5438
+ id: toolCallId,
5439
+ name,
5440
+ result
5441
+ });
5442
+ backgroundQueue.push({
5443
+ toolCallId,
5444
+ name,
5445
+ result,
5446
+ completedAt: Date.now()
5447
+ });
5448
+ if (!running) {
5449
+ flushBackgroundQueue();
5450
+ }
5451
+ }
5308
5452
  const USER_FACING_TOOLS = /* @__PURE__ */ new Set([
5309
5453
  "promptUser",
5310
5454
  "confirmDestructiveAction",
@@ -5345,6 +5489,7 @@ async function startHeadless(opts = {}) {
5345
5489
  case "turn_done":
5346
5490
  completedEmitted = true;
5347
5491
  emit("completed", { success: true }, rid);
5492
+ setTimeout(() => flushBackgroundQueue(), 0);
5348
5493
  return;
5349
5494
  case "turn_cancelled":
5350
5495
  completedEmitted = true;
@@ -5391,6 +5536,7 @@ async function startHeadless(opts = {}) {
5391
5536
  name: e.name,
5392
5537
  input: e.input,
5393
5538
  ...e.partial && { partial: true },
5539
+ ...e.background && { background: true },
5394
5540
  ...e.parentToolId && { parentToolId: e.parentToolId }
5395
5541
  },
5396
5542
  rid
@@ -5409,6 +5555,18 @@ async function startHeadless(opts = {}) {
5409
5555
  rid
5410
5556
  );
5411
5557
  return;
5558
+ case "tool_background_complete":
5559
+ emit(
5560
+ "tool_background_complete",
5561
+ {
5562
+ id: e.id,
5563
+ name: e.name,
5564
+ result: e.result,
5565
+ ...e.parentToolId && { parentToolId: e.parentToolId }
5566
+ },
5567
+ rid
5568
+ );
5569
+ return;
5412
5570
  case "tool_stopped":
5413
5571
  emit(
5414
5572
  "tool_stopped",
@@ -5476,6 +5634,7 @@ async function startHeadless(opts = {}) {
5476
5634
  }
5477
5635
  let userMessage = parsed.text ?? "";
5478
5636
  const isCommand = !!parsed.runCommand;
5637
+ const isHidden = isCommand || !!parsed.hidden;
5479
5638
  if (parsed.runCommand === "sync") {
5480
5639
  userMessage = loadActionPrompt("sync");
5481
5640
  } else if (parsed.runCommand === "publish") {
@@ -5500,8 +5659,9 @@ async function startHeadless(opts = {}) {
5500
5659
  signal: currentAbort.signal,
5501
5660
  onEvent,
5502
5661
  resolveExternalTool,
5503
- hidden: isCommand,
5504
- toolRegistry
5662
+ hidden: isHidden,
5663
+ toolRegistry,
5664
+ onBackgroundComplete
5505
5665
  });
5506
5666
  if (!completedEmitted) {
5507
5667
  emit(