@mindstudio-ai/remy 0.1.40 → 0.1.42

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
@@ -44,11 +44,6 @@ function readJsonAsset(fallback, ...segments) {
44
44
  }
45
45
  }
46
46
 
47
- // src/config.ts
48
- import fs3 from "fs";
49
- import path2 from "path";
50
- import os from "os";
51
-
52
47
  // src/logger.ts
53
48
  import fs2 from "fs";
54
49
  var LEVELS = {
@@ -60,9 +55,6 @@ var LEVELS = {
60
55
  var currentLevel = LEVELS.error;
61
56
  var writeFn = () => {
62
57
  };
63
- function timestamp() {
64
- return (/* @__PURE__ */ new Date()).toISOString();
65
- }
66
58
  var MAX_VALUE_LENGTH = 200;
67
59
  function truncateValues(obj) {
68
60
  const result = {};
@@ -77,32 +69,35 @@ function truncateValues(obj) {
77
69
  }
78
70
  return result;
79
71
  }
80
- function write(level, msg, data) {
72
+ function write(level, module, msg, data) {
81
73
  if (LEVELS[level] > currentLevel) {
82
74
  return;
83
75
  }
84
- const parts = [`[${timestamp()}]`, level.toUpperCase().padEnd(5), msg];
76
+ const entry = {
77
+ ts: Date.now(),
78
+ level,
79
+ module,
80
+ msg
81
+ };
85
82
  if (data) {
86
- parts.push(JSON.stringify(truncateValues(data)));
83
+ Object.assign(entry, truncateValues(data));
87
84
  }
88
- writeFn(parts.join(" "));
85
+ writeFn(JSON.stringify(entry));
86
+ }
87
+ function createLogger(module) {
88
+ return {
89
+ error: (msg, data) => write("error", module, msg, data),
90
+ warn: (msg, data) => write("warn", module, msg, data),
91
+ info: (msg, data) => write("info", module, msg, data),
92
+ debug: (msg, data) => write("debug", module, msg, data)
93
+ };
89
94
  }
90
- var log = {
91
- error(msg, data) {
92
- write("error", msg, data);
93
- },
94
- warn(msg, data) {
95
- write("warn", msg, data);
96
- },
97
- info(msg, data) {
98
- write("info", msg, data);
99
- },
100
- debug(msg, data) {
101
- write("debug", msg, data);
102
- }
103
- };
104
95
 
105
96
  // src/config.ts
97
+ import fs3 from "fs";
98
+ import path2 from "path";
99
+ import os from "os";
100
+ var log = createLogger("config");
106
101
  var CONFIG_PATH = path2.join(
107
102
  os.homedir(),
108
103
  ".mindstudio-local-tunnel",
@@ -144,10 +139,11 @@ function resolveConfig(flags) {
144
139
  }
145
140
 
146
141
  // src/tools/_helpers/sidecar.ts
142
+ var log2 = createLogger("sidecar");
147
143
  var baseUrl = null;
148
144
  function setSidecarBaseUrl(url) {
149
145
  baseUrl = url;
150
- log.info("Sidecar configured", { url });
146
+ log2.info("Configured", { url });
151
147
  }
152
148
  function isSidecarConfigured() {
153
149
  return baseUrl !== null;
@@ -157,7 +153,6 @@ async function sidecarRequest(endpoint, body = {}, options) {
157
153
  throw new Error("Sidecar not available");
158
154
  }
159
155
  const url = `${baseUrl}${endpoint}`;
160
- log.debug("Sidecar request", { endpoint, body });
161
156
  try {
162
157
  const res = await fetch(url, {
163
158
  method: "POST",
@@ -166,7 +161,7 @@ async function sidecarRequest(endpoint, body = {}, options) {
166
161
  signal: options?.timeout ? AbortSignal.timeout(options.timeout) : void 0
167
162
  });
168
163
  if (!res.ok) {
169
- log.error("Sidecar error", { endpoint, status: res.status });
164
+ log2.error("Sidecar error", { endpoint, status: res.status });
170
165
  throw new Error(`Sidecar error: ${res.status}`);
171
166
  }
172
167
  return res.json();
@@ -174,7 +169,7 @@ async function sidecarRequest(endpoint, body = {}, options) {
174
169
  if (err.message.startsWith("Sidecar error")) {
175
170
  throw err;
176
171
  }
177
- log.error("Sidecar connection error", { endpoint, error: err.message });
172
+ log2.error("Sidecar connection error", { endpoint, error: err.message });
178
173
  throw new Error(`Sidecar connection error: ${err.message}`);
179
174
  }
180
175
  }
@@ -429,25 +424,19 @@ ${viewContext?.activeFile ? `Active file: ${viewContext.activeFile}` : ""}
429
424
  }
430
425
 
431
426
  // src/api.ts
427
+ var log3 = createLogger("api");
432
428
  async function* streamChat(params) {
433
- const { baseUrl: baseUrl2, apiKey, signal, ...body } = params;
429
+ const { baseUrl: baseUrl2, apiKey, signal, requestId, ...body } = params;
434
430
  const url = `${baseUrl2}/_internal/v2/agent/remy/chat`;
435
431
  const startTime = Date.now();
436
432
  const messagesWithAttachments = body.messages.filter(
437
433
  (m) => m.attachments && m.attachments.length > 0
438
434
  );
439
- log.info("POST agent/chat", {
440
- url,
435
+ log3.info("API request", {
436
+ requestId,
441
437
  model: body.model,
442
438
  messageCount: body.messages.length,
443
- toolCount: body.tools.length,
444
- ...messagesWithAttachments.length > 0 && {
445
- attachments: messagesWithAttachments.map((m) => ({
446
- role: m.role,
447
- attachmentCount: m.attachments.length,
448
- urls: m.attachments.map((a) => a.url)
449
- }))
450
- }
439
+ toolCount: body.tools.length
451
440
  });
452
441
  let res;
453
442
  try {
@@ -462,15 +451,15 @@ async function* streamChat(params) {
462
451
  });
463
452
  } catch (err) {
464
453
  if (signal?.aborted) {
465
- log.info("Request aborted by signal");
454
+ log3.warn("Request aborted", { requestId });
466
455
  throw err;
467
456
  }
468
- log.error("Network error", { error: err.message });
457
+ log3.error("Network error", { requestId, error: err.message });
469
458
  yield { type: "error", error: `Network error: ${err.message}` };
470
459
  return;
471
460
  }
472
461
  const ttfb = Date.now() - startTime;
473
- log.info(`Response ${res.status}`, { ttfb: `${ttfb}ms` });
462
+ log3.info("API response", { requestId, status: res.status, ttfbMs: ttfb });
474
463
  if (!res.ok) {
475
464
  let errorMessage = `HTTP ${res.status}`;
476
465
  try {
@@ -483,7 +472,11 @@ async function* streamChat(params) {
483
472
  }
484
473
  } catch {
485
474
  }
486
- log.error("API error", { status: res.status, error: errorMessage });
475
+ log3.error("API error", {
476
+ requestId,
477
+ status: res.status,
478
+ error: errorMessage
479
+ });
487
480
  yield { type: "error", error: errorMessage };
488
481
  return;
489
482
  }
@@ -508,7 +501,10 @@ async function* streamChat(params) {
508
501
  } catch {
509
502
  clearTimeout(stallTimer);
510
503
  await reader.cancel();
511
- log.error("Stream stalled", { elapsed: `${Date.now() - startTime}ms` });
504
+ log3.error("Stream stalled", {
505
+ requestId,
506
+ durationMs: Date.now() - startTime
507
+ });
512
508
  yield {
513
509
  type: "error",
514
510
  error: "Stream stalled \u2014 no data received for 5 minutes"
@@ -530,8 +526,9 @@ async function* streamChat(params) {
530
526
  const event = JSON.parse(line.slice(6));
531
527
  if (event.type === "done") {
532
528
  const elapsed = Date.now() - startTime;
533
- log.info("Stream complete", {
534
- elapsed: `${elapsed}ms`,
529
+ log3.info("Stream complete", {
530
+ requestId,
531
+ durationMs: elapsed,
535
532
  stopReason: event.stopReason,
536
533
  inputTokens: event.usage.inputTokens,
537
534
  outputTokens: event.usage.outputTokens
@@ -564,11 +561,6 @@ async function* streamChatWithRetry(params, options) {
564
561
  for await (const event of streamChat(params)) {
565
562
  if (event.type === "error") {
566
563
  if (isRetryableError(event.error) && attempt < MAX_RETRIES - 1) {
567
- log.warn("Retryable error, will retry", {
568
- attempt: attempt + 1,
569
- maxRetries: MAX_RETRIES,
570
- error: event.error
571
- });
572
564
  options?.onRetry?.(attempt, event.error);
573
565
  retryableFailure = true;
574
566
  break;
@@ -583,7 +575,12 @@ async function* streamChatWithRetry(params, options) {
583
575
  return;
584
576
  }
585
577
  const backoff = INITIAL_BACKOFF_MS * 2 ** attempt;
586
- log.info("Retrying after backoff", { backoffMs: backoff });
578
+ log3.warn("Retrying", {
579
+ requestId: params.requestId,
580
+ attempt: attempt + 1,
581
+ maxRetries: MAX_RETRIES,
582
+ backoffMs: backoff
583
+ });
587
584
  await sleep(backoff);
588
585
  continue;
589
586
  }
@@ -2144,6 +2141,17 @@ var runMethodTool = {
2144
2141
  }
2145
2142
  };
2146
2143
 
2144
+ // src/subagents/common/analyzeImage.ts
2145
+ var VISION_MODEL = "gemini-3-flash";
2146
+ var VISION_MODEL_OVERRIDE = JSON.stringify({ model: VISION_MODEL });
2147
+ async function analyzeImage(params) {
2148
+ const { prompt, imageUrl, timeout = 2e5, onLog } = params;
2149
+ return runCli(
2150
+ `mindstudio analyze-image --prompt ${JSON.stringify(prompt)} --image-url ${JSON.stringify(imageUrl)} --vision-model-override ${JSON.stringify(VISION_MODEL_OVERRIDE)} --output-key analysis --no-meta`,
2151
+ { timeout, onLog }
2152
+ );
2153
+ }
2154
+
2147
2155
  // src/tools/_helpers/screenshot.ts
2148
2156
  var SCREENSHOT_ANALYSIS_PROMPT = "Describe everything visible on screen from top to bottom \u2014 every element, its position, its size relative to the viewport, its colors, its content. Be comprehensive, 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.";
2149
2157
  async function captureAndAnalyzeScreenshot(promptOrOptions) {
@@ -2158,7 +2166,6 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
2158
2166
  const ssResult = await sidecarRequest("/screenshot-full-page", void 0, {
2159
2167
  timeout: 12e4
2160
2168
  });
2161
- log.debug("Screenshot response", { ssResult });
2162
2169
  const url = ssResult?.url || ssResult?.screenshotUrl;
2163
2170
  if (!url) {
2164
2171
  throw new Error(
@@ -2169,10 +2176,11 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
2169
2176
  return url;
2170
2177
  }
2171
2178
  const analysisPrompt = prompt || SCREENSHOT_ANALYSIS_PROMPT;
2172
- const analysis = await runCli(
2173
- `mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`,
2174
- { timeout: 2e5, onLog }
2175
- );
2179
+ const analysis = await analyzeImage({
2180
+ prompt: analysisPrompt,
2181
+ imageUrl: url,
2182
+ onLog
2183
+ });
2176
2184
  return JSON.stringify({ url, analysis });
2177
2185
  }
2178
2186
 
@@ -2218,13 +2226,8 @@ function startStatusWatcher(config) {
2218
2226
  try {
2219
2227
  const ctx = getContext();
2220
2228
  if (!ctx.assistantText && !ctx.lastToolName) {
2221
- log.debug("Status watcher: no context, skipping");
2222
2229
  return;
2223
2230
  }
2224
- log.debug("Status watcher: requesting label", {
2225
- textLength: ctx.assistantText.length,
2226
- lastToolName: ctx.lastToolName
2227
- });
2228
2231
  const res = await fetch(url, {
2229
2232
  method: "POST",
2230
2233
  headers: {
@@ -2241,30 +2244,18 @@ function startStatusWatcher(config) {
2241
2244
  signal
2242
2245
  });
2243
2246
  if (!res.ok) {
2244
- log.debug("Status watcher: endpoint returned non-ok", {
2245
- status: res.status
2246
- });
2247
2247
  return;
2248
2248
  }
2249
2249
  const data = await res.json();
2250
- if (!data.label) {
2251
- log.debug("Status watcher: no label in response");
2252
- return;
2253
- }
2254
- if (data.label === lastLabel) {
2255
- log.debug("Status watcher: duplicate label, skipping", {
2256
- label: data.label
2257
- });
2250
+ if (!data.label || data.label === lastLabel) {
2258
2251
  return;
2259
2252
  }
2260
2253
  lastLabel = data.label;
2261
2254
  if (stopped) {
2262
2255
  return;
2263
2256
  }
2264
- log.debug("Status watcher: emitting", { label: data.label });
2265
2257
  onStatus(data.label);
2266
- } catch (err) {
2267
- log.debug("Status watcher: error", { error: err?.message ?? "unknown" });
2258
+ } catch {
2268
2259
  } finally {
2269
2260
  inflight = false;
2270
2261
  }
@@ -2272,12 +2263,10 @@ function startStatusWatcher(config) {
2272
2263
  const timer = setInterval(tick, interval);
2273
2264
  tick().catch(() => {
2274
2265
  });
2275
- log.debug("Status watcher started", { interval });
2276
2266
  return {
2277
2267
  stop() {
2278
2268
  stopped = true;
2279
2269
  clearInterval(timer);
2280
- log.debug("Status watcher stopped");
2281
2270
  }
2282
2271
  };
2283
2272
  }
@@ -2318,6 +2307,7 @@ function cleanMessagesForApi(messages) {
2318
2307
  }
2319
2308
 
2320
2309
  // src/subagents/runner.ts
2310
+ var log4 = createLogger("sub-agent");
2321
2311
  async function runSubAgent(config) {
2322
2312
  const {
2323
2313
  system,
@@ -2333,14 +2323,19 @@ async function runSubAgent(config) {
2333
2323
  onEvent,
2334
2324
  resolveExternalTool,
2335
2325
  toolRegistry,
2326
+ requestId,
2336
2327
  background,
2337
2328
  onBackgroundComplete
2338
2329
  } = config;
2339
2330
  const bgAbort = background ? new AbortController() : null;
2340
2331
  const signal = background ? bgAbort.signal : parentSignal;
2332
+ const agentName = subAgentId || "sub-agent";
2333
+ const runStart = Date.now();
2334
+ log4.info("Sub-agent started", { requestId, parentToolId, agentName });
2341
2335
  const emit2 = (e) => {
2342
2336
  onEvent({ ...e, parentToolId });
2343
2337
  };
2338
+ let turns = 0;
2344
2339
  const run = async () => {
2345
2340
  const messages = [{ role: "user", content: task }];
2346
2341
  function getPartialText(blocks) {
@@ -2360,6 +2355,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
2360
2355
  }
2361
2356
  let lastToolResult = "";
2362
2357
  while (true) {
2358
+ turns++;
2363
2359
  if (signal?.aborted) {
2364
2360
  return abortResult([]);
2365
2361
  }
@@ -2385,6 +2381,7 @@ Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " "
2385
2381
  {
2386
2382
  ...apiConfig,
2387
2383
  model,
2384
+ requestId,
2388
2385
  subAgentId,
2389
2386
  system: fullSystem,
2390
2387
  messages: cleanMessagesForApi(messages),
@@ -2475,7 +2472,8 @@ Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " "
2475
2472
  const text = getPartialText(contentBlocks);
2476
2473
  return { text, messages };
2477
2474
  }
2478
- log.info("Sub-agent executing tools", {
2475
+ log4.info("Tools executing", {
2476
+ requestId,
2479
2477
  parentToolId,
2480
2478
  count: toolCalls.length,
2481
2479
  tools: toolCalls.map((tc) => tc.name)
@@ -2574,15 +2572,37 @@ Current date/time: ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " "
2574
2572
  }
2575
2573
  }
2576
2574
  };
2575
+ const wrapRun = async () => {
2576
+ try {
2577
+ const result = await run();
2578
+ log4.info("Sub-agent complete", {
2579
+ requestId,
2580
+ parentToolId,
2581
+ agentName,
2582
+ durationMs: Date.now() - runStart,
2583
+ turns
2584
+ });
2585
+ return result;
2586
+ } catch (err) {
2587
+ log4.warn("Sub-agent error", {
2588
+ requestId,
2589
+ parentToolId,
2590
+ agentName,
2591
+ error: err.message
2592
+ });
2593
+ throw err;
2594
+ }
2595
+ };
2577
2596
  if (!background) {
2578
- return run();
2597
+ return wrapRun();
2579
2598
  }
2599
+ log4.info("Sub-agent backgrounded", { requestId, parentToolId, agentName });
2580
2600
  const ack = await generateBackgroundAck({
2581
2601
  apiConfig,
2582
2602
  agentName: subAgentId || "agent",
2583
2603
  task
2584
2604
  });
2585
- run().then((finalResult) => onBackgroundComplete?.(finalResult)).catch(
2605
+ wrapRun().then((finalResult) => onBackgroundComplete?.(finalResult)).catch(
2586
2606
  (err) => onBackgroundComplete?.({ text: `Error: ${err.message}`, messages: [] })
2587
2607
  );
2588
2608
  return { text: ack, messages: [], backgrounded: true };
@@ -2705,6 +2725,7 @@ ${appSpec}
2705
2725
  }
2706
2726
 
2707
2727
  // src/subagents/browserAutomation/index.ts
2728
+ var log5 = createLogger("browser-automation");
2708
2729
  var browserAutomationTool = {
2709
2730
  definition: {
2710
2731
  name: "runAutomatedBrowserTest",
@@ -2764,6 +2785,7 @@ var browserAutomationTool = {
2764
2785
  subAgentId: "browserAutomation",
2765
2786
  signal: context.signal,
2766
2787
  parentToolId: context.toolCallId,
2788
+ requestId: context.requestId,
2767
2789
  onEvent: context.onEvent,
2768
2790
  resolveExternalTool: async (id, name, input2) => {
2769
2791
  if (!context.resolveExternalTool) {
@@ -2798,7 +2820,7 @@ var browserAutomationTool = {
2798
2820
  }
2799
2821
  }
2800
2822
  } catch {
2801
- log.debug("Failed to parse batch analysis result", {
2823
+ log5.debug("Failed to parse batch analysis result", {
2802
2824
  batchResult
2803
2825
  });
2804
2826
  }
@@ -2885,6 +2907,8 @@ You are analyzing a screenshot of a real website or app for a designer's persona
2885
2907
 
2886
2908
  Analyze the image and think about what makes the site or app special and unique. What is it doing that is unique, different, original, and creative? What makes it special? What isn't working? What doesn't look or feel good?
2887
2909
 
2910
+ Important: First, look at the screenshot and use your judgement to identify what the user wants notes about. If the screenshot is of a website for design case studies, or a blog post writeup about a new product or design, assume that the user is interested in a reference for the site/app/product being talked about - it is unlikely they are interested in a design audit of dribble.com, for example.
2911
+
2888
2912
  Then, provide the following analysis:
2889
2913
 
2890
2914
  ## Context
@@ -2934,10 +2958,11 @@ async function execute3(input, onLog) {
2934
2958
  }
2935
2959
  imageUrl = ssUrl;
2936
2960
  }
2937
- const analysis = await runCli(
2938
- `mindstudio analyze-image --prompt ${JSON.stringify(analysisPrompt)} --image-url ${JSON.stringify(imageUrl)} --output-key analysis --no-meta`,
2939
- { timeout: 2e5, onLog }
2940
- );
2961
+ const analysis = await analyzeImage({
2962
+ prompt: analysisPrompt,
2963
+ imageUrl,
2964
+ onLog
2965
+ });
2941
2966
  return JSON.stringify({ url: imageUrl, analysis });
2942
2967
  }
2943
2968
 
@@ -2969,10 +2994,11 @@ var definition4 = {
2969
2994
  async function execute4(input, onLog) {
2970
2995
  const imageUrl = input.imageUrl;
2971
2996
  const prompt = input.prompt || DEFAULT_PROMPT;
2972
- const analysis = await runCli(
2973
- `mindstudio analyze-image --prompt ${JSON.stringify(prompt)} --image-url ${JSON.stringify(imageUrl)} --output-key analysis --no-meta`,
2974
- { timeout: 2e5, onLog }
2975
- );
2997
+ const analysis = await analyzeImage({
2998
+ prompt,
2999
+ imageUrl,
3000
+ onLog
3001
+ });
2976
3002
  return JSON.stringify({ url: imageUrl, analysis });
2977
3003
  }
2978
3004
 
@@ -3006,29 +3032,68 @@ async function execute5(input, onLog) {
3006
3032
  }
3007
3033
  }
3008
3034
 
3009
- // src/subagents/designExpert/tools/generateImages.ts
3035
+ // src/subagents/designExpert/tools/images/generateImages.ts
3010
3036
  var generateImages_exports = {};
3011
3037
  __export(generateImages_exports, {
3012
3038
  definition: () => definition6,
3013
3039
  execute: () => execute6
3014
3040
  });
3015
3041
 
3016
- // src/subagents/designExpert/tools/_seedream.ts
3017
- var ANALYZE_PROMPT = "You are reviewing this image for a visual designer sourcing assets for a project. Describe: what the image depicts, the mood and color palette, how the lighting and composition work, whether there are any issues (unwanted text, artifacts, distortions), and how it could be used in a layout (hero background, feature section, card texture, etc). Be concise and practical. Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.";
3018
- async function seedreamGenerate(opts) {
3019
- const { prompts, sourceImages, transparentBackground, onLog } = opts;
3020
- const width = opts.width || 2048;
3021
- const height = opts.height || 2048;
3022
- const config = { width, height };
3023
- if (sourceImages?.length) {
3024
- config.images = sourceImages;
3042
+ // src/subagents/designExpert/tools/images/enhancePrompt.ts
3043
+ var SYSTEM_PROMPT = readAsset(
3044
+ "subagents/designExpert/tools/images/enhance-image-prompt.md"
3045
+ );
3046
+ async function enhanceImagePrompt(params) {
3047
+ const { brief, aspectRatio, transparentBackground, onLog } = params;
3048
+ const orientation = aspectRatio === "1:1" ? "square" : ["16:9", "4:3", "3:2"].includes(aspectRatio) ? "landscape" : "portrait";
3049
+ const contextParts = [
3050
+ `Aspect ratio: ${aspectRatio} (${orientation})`
3051
+ ];
3052
+ if (transparentBackground) {
3053
+ contextParts.push(
3054
+ "Transparent background: yes \u2014 the background will be removed. Focus on the subject as an isolated element."
3055
+ );
3025
3056
  }
3057
+ const message = `<context>
3058
+ ${contextParts.join("\n")}
3059
+ </context>
3060
+
3061
+ <brief>
3062
+ ${brief}
3063
+ </brief>`;
3064
+ const enhanced = await runCli(
3065
+ `mindstudio generate-text --prompt ${JSON.stringify(SYSTEM_PROMPT)} --message ${JSON.stringify(message)} --output-key enhanced --no-meta`,
3066
+ { timeout: 6e4, onLog }
3067
+ );
3068
+ return enhanced.trim();
3069
+ }
3070
+
3071
+ // src/subagents/designExpert/tools/images/imageGenerator.ts
3072
+ var ANALYZE_PROMPT = "You are reviewing this image for a visual designer sourcing assets for a project. Describe: what the image depicts, the mood and color palette, how the lighting and composition work, any text present in the image, whether there are any issues (artifacts, distortions), and how it could be used in a layout for an app or website. Be concise and practical. Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.";
3073
+ async function generateImageAssets(opts) {
3074
+ const { prompts, sourceImages, transparentBackground, onLog } = opts;
3075
+ const aspectRatio = opts.aspectRatio || "1:1";
3076
+ const config = {
3077
+ aspect_ratio: aspectRatio,
3078
+ ...sourceImages?.length && { source_images: sourceImages }
3079
+ };
3080
+ const isEdit = !!sourceImages?.length;
3081
+ const enhancedPrompts = isEdit ? prompts : await Promise.all(
3082
+ prompts.map(
3083
+ (brief) => enhanceImagePrompt({
3084
+ brief,
3085
+ aspectRatio,
3086
+ transparentBackground,
3087
+ onLog
3088
+ })
3089
+ )
3090
+ );
3026
3091
  let imageUrls;
3027
- if (prompts.length === 1) {
3092
+ if (enhancedPrompts.length === 1) {
3028
3093
  const step = JSON.stringify({
3029
- prompt: prompts[0],
3094
+ prompt: enhancedPrompts[0],
3030
3095
  imageModelOverride: {
3031
- model: "seedream-4.5",
3096
+ model: "gemini-3.1-flash-image",
3032
3097
  config
3033
3098
  }
3034
3099
  });
@@ -3038,12 +3103,12 @@ async function seedreamGenerate(opts) {
3038
3103
  );
3039
3104
  imageUrls = [url];
3040
3105
  } else {
3041
- const steps = prompts.map((prompt) => ({
3106
+ const steps = enhancedPrompts.map((prompt) => ({
3042
3107
  stepType: "generateImage",
3043
3108
  step: {
3044
3109
  prompt,
3045
3110
  imageModelOverride: {
3046
- model: "seedream-4.5",
3111
+ model: "gemini-3.1-flash-image",
3047
3112
  config
3048
3113
  }
3049
3114
  }
@@ -3080,22 +3145,33 @@ async function seedreamGenerate(opts) {
3080
3145
  const images = await Promise.all(
3081
3146
  imageUrls.map(async (url, i) => {
3082
3147
  if (url.startsWith("Error")) {
3083
- return { prompt: prompts[i], error: url };
3148
+ return {
3149
+ prompt: prompts[i],
3150
+ ...!isEdit && { enhancedPrompt: enhancedPrompts[i] },
3151
+ error: url
3152
+ };
3084
3153
  }
3085
- const analysis = await runCli(
3086
- `mindstudio analyze-image --prompt ${JSON.stringify(ANALYZE_PROMPT)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`,
3087
- { timeout: 2e5, onLog }
3088
- );
3089
- return { url, prompt: prompts[i], analysis, width, height };
3154
+ const analysis = await analyzeImage({
3155
+ prompt: ANALYZE_PROMPT,
3156
+ imageUrl: url,
3157
+ onLog
3158
+ });
3159
+ return {
3160
+ url,
3161
+ prompt: prompts[i],
3162
+ ...!isEdit && { enhancedPrompt: enhancedPrompts[i] },
3163
+ analysis,
3164
+ aspectRatio
3165
+ };
3090
3166
  })
3091
3167
  );
3092
3168
  return JSON.stringify({ images });
3093
3169
  }
3094
3170
 
3095
- // src/subagents/designExpert/tools/generateImages.ts
3171
+ // src/subagents/designExpert/tools/images/generateImages.ts
3096
3172
  var definition6 = {
3097
3173
  name: "generateImages",
3098
- description: "Generate images using AI. Returns CDN URLs with a quality analysis for each image. Produces high-quality results for everything from photorealistic images and abstract/creative visuals. Pass multiple prompts to generate in parallel. No need to analyze images separately after generating \u2014 the analysis is included.",
3174
+ description: "Generate images. Returns CDN URLs with a quality analysis for each image. Produces high-quality results for everything from photorealistic images and abstract/creative visuals. Pass multiple prompts to generate in parallel. No need to analyze images separately after generating \u2014 the analysis is included.",
3099
3175
  inputSchema: {
3100
3176
  type: "object",
3101
3177
  properties: {
@@ -3104,15 +3180,12 @@ var definition6 = {
3104
3180
  items: {
3105
3181
  type: "string"
3106
3182
  },
3107
- description: "One or more image generation prompts. Be detailed: describe style, mood, composition, colors. Multiple prompts run in parallel."
3183
+ description: "One or more image briefs describing what you want. Focus on subject, mood, style, and intended use \u2014 the tool optimizes your brief into a model-ready prompt automatically. Multiple briefs run in parallel."
3108
3184
  },
3109
- width: {
3110
- type: "number",
3111
- description: "Image width in pixels. Default 2048. Range: 2048-4096."
3112
- },
3113
- height: {
3114
- type: "number",
3115
- description: "Image height in pixels. Default 2048. Range: 2048-4096."
3185
+ aspectRatio: {
3186
+ type: "string",
3187
+ enum: ["1:1", "16:9", "9:16", "3:4", "4:3", "2:3", "3:2"],
3188
+ description: "Aspect ratio. Default 1:1."
3116
3189
  },
3117
3190
  transparentBackground: {
3118
3191
  type: "boolean",
@@ -3123,16 +3196,15 @@ var definition6 = {
3123
3196
  }
3124
3197
  };
3125
3198
  async function execute6(input, onLog) {
3126
- return seedreamGenerate({
3199
+ return generateImageAssets({
3127
3200
  prompts: input.prompts,
3128
- width: input.width,
3129
- height: input.height,
3201
+ aspectRatio: input.aspectRatio,
3130
3202
  transparentBackground: input.transparentBackground,
3131
3203
  onLog
3132
3204
  });
3133
3205
  }
3134
3206
 
3135
- // src/subagents/designExpert/tools/editImages.ts
3207
+ // src/subagents/designExpert/tools/images/editImages.ts
3136
3208
  var editImages_exports = {};
3137
3209
  __export(editImages_exports, {
3138
3210
  definition: () => definition7,
@@ -3140,7 +3212,7 @@ __export(editImages_exports, {
3140
3212
  });
3141
3213
  var definition7 = {
3142
3214
  name: "editImages",
3143
- description: "Edit or transform existing images using AI. Provide one or more source image URLs as reference and a prompt describing the desired edit. Use for compositing, style transfer, subject transformation, blending multiple references, or incorporating one or more ferences into something new. Returns CDN URLs with analysis.",
3215
+ description: "Edit or transform existing images. Provide one or more source image URLs as reference and a prompt describing the desired edit. Use for compositing, style transfer, subject transformation, blending multiple references, or incorporating one or more references into something new. Returns CDN URLs with analysis.",
3144
3216
  inputSchema: {
3145
3217
  type: "object",
3146
3218
  properties: {
@@ -3149,7 +3221,7 @@ var definition7 = {
3149
3221
  items: {
3150
3222
  type: "string"
3151
3223
  },
3152
- description: "One or more edit prompts describing how to transform the source images. Multiple prompts run in parallel, each using the same source images."
3224
+ description: "One or more edit briefs describing the desired transformation. Focus on what to change relative to the source material. Multiple briefs run in parallel, each using the same source images."
3153
3225
  },
3154
3226
  sourceImages: {
3155
3227
  type: "array",
@@ -3158,13 +3230,10 @@ var definition7 = {
3158
3230
  },
3159
3231
  description: "One or more source/reference image URLs. These are used as the basis for the edit \u2014 the AI will use them as reference for style, subject, or composition."
3160
3232
  },
3161
- width: {
3162
- type: "number",
3163
- description: "Output width in pixels. Default 2048. Range: 2048-4096."
3164
- },
3165
- height: {
3166
- type: "number",
3167
- description: "Output height in pixels. Default 2048. Range: 2048-4096."
3233
+ aspectRatio: {
3234
+ type: "string",
3235
+ enum: ["1:1", "16:9", "9:16", "3:4", "4:3", "2:3", "3:2"],
3236
+ description: "Output aspect ratio. Default 1:1."
3168
3237
  },
3169
3238
  transparentBackground: {
3170
3239
  type: "boolean",
@@ -3175,11 +3244,10 @@ var definition7 = {
3175
3244
  }
3176
3245
  };
3177
3246
  async function execute7(input, onLog) {
3178
- return seedreamGenerate({
3247
+ return generateImageAssets({
3179
3248
  prompts: input.prompts,
3180
3249
  sourceImages: input.sourceImages,
3181
- width: input.width,
3182
- height: input.height,
3250
+ aspectRatio: input.aspectRatio,
3183
3251
  transparentBackground: input.transparentBackground,
3184
3252
  onLog
3185
3253
  });
@@ -3345,15 +3413,11 @@ function getFontLibrarySample() {
3345
3413
  return `
3346
3414
  ## Font Library
3347
3415
 
3348
- A random sample from a curated font library. Use these as starting points for font selection.
3416
+ This is your personal library of fonts you love. Use it as a starting point when thinking about anything related to typography.
3349
3417
 
3350
3418
  ### Fonts
3351
3419
 
3352
- ${fontList}
3353
-
3354
- ### Pairings
3355
-
3356
- ${pairingList}`.trim();
3420
+ ${fontList}`.trim();
3357
3421
  }
3358
3422
 
3359
3423
  // src/subagents/designExpert/data/getDesignReferencesSample.ts
@@ -3373,16 +3437,16 @@ function sample2(arr, n) {
3373
3437
  return copy.slice(0, n);
3374
3438
  }
3375
3439
  function getDesignReferencesSample() {
3376
- const images = sample2(inspirationImages, 30);
3440
+ const images = sample2(inspirationImages, 25);
3377
3441
  if (!images.length) {
3378
3442
  return "";
3379
3443
  }
3380
3444
  const imageList = images.map((img, i) => `### Reference ${i + 1}
3381
3445
  ${img.analysis}`).join("\n\n");
3382
3446
  return `
3383
- ## Design References
3447
+ ## Visual Design References
3384
3448
 
3385
- This is what the bar looks like. These are real sites that made it onto curated design galleries because they did something bold, intentional, and memorable. Use them as inspiration and let the takeaways guide your work. Your designs should feel like they belong in this company.
3449
+ This is your personal reference library of visual design you love. The apps and sites featured within made it into your library because they did something bold, intentional, and memorable. Use them as reference, inspiration, and let the takeaways guide your work.
3386
3450
 
3387
3451
  ${imageList}`.trim();
3388
3452
  }
@@ -3404,16 +3468,16 @@ function sample3(arr, n) {
3404
3468
  return copy.slice(0, n);
3405
3469
  }
3406
3470
  function getUiInspirationSample() {
3407
- const screens = sample3(uiScreens, 20);
3471
+ const screens = sample3(uiScreens, 25);
3408
3472
  if (!screens.length) {
3409
3473
  return "";
3410
3474
  }
3411
3475
  const screenList = screens.map((s, i) => `### Screen ${i + 1}
3412
3476
  ${s.analysis}`).join("\n\n");
3413
3477
  return `
3414
- ## UI Pattern References
3478
+ ## UI Case Studies
3415
3479
 
3416
- There are real app screens from well-designed products, sourced and curated by hand as a reference by a desigher. Use them as inspiration and let the takeaways guide your work. Your designs should feel like they belong in this company.
3480
+ These are your personal notes, collected over the years, about UI patterns you've encountered in the wild that you love. You re-use aspects of them liberally in your work, reference them as ground truths, as well as use them to synthesize new ideas and refine your sense of what good UI feels and looks like. The work you do must always feel like it belongs in this company.
3417
3481
 
3418
3482
  ${screenList}`.trim();
3419
3483
  }
@@ -3422,8 +3486,8 @@ ${screenList}`.trim();
3422
3486
  var SUBAGENT = "subagents/designExpert";
3423
3487
  var RUNTIME_PLACEHOLDERS = /* @__PURE__ */ new Set([
3424
3488
  "font_library",
3425
- "design_references",
3426
- "ui_patterns"
3489
+ "visual_design_references",
3490
+ "ui_case_studies"
3427
3491
  ]);
3428
3492
  var PROMPT_TEMPLATE = readAsset(SUBAGENT, "prompt.md").replace(/\{\{([^}]+)\}\}/g, (match, key) => {
3429
3493
  const k = key.trim();
@@ -3434,7 +3498,7 @@ function getDesignExpertPrompt() {
3434
3498
  let prompt = PROMPT_TEMPLATE.replace(
3435
3499
  "{{font_library}}",
3436
3500
  getFontLibrarySample()
3437
- ).replace("{{design_references}}", getDesignReferencesSample()).replace("{{ui_patterns}}", getUiInspirationSample());
3501
+ ).replace("{{visual_design_references}}", getDesignReferencesSample()).replace("{{ui_case_studies}}", getUiInspirationSample());
3438
3502
  if (specContext) {
3439
3503
  prompt += `
3440
3504
 
@@ -3449,7 +3513,7 @@ ${specContext}`;
3449
3513
 
3450
3514
  // src/subagents/designExpert/index.ts
3451
3515
  var DESCRIPTION = `
3452
- Visual design expert. Describe the situation and what you need \u2014 the agent decides what to deliver. It reads the spec files automatically. Include relevant user requirements and context it can't get from the spec, but do not list specific deliverables or tell it how to do its job.
3516
+ Visual design expert. Describe the situation and what you need \u2014 the agent decides what to deliver. It reads the spec files automatically. Include relevant user requirements and context it can't get from the spec, but do not list specific deliverables or tell it how to do its job. Do not suggest implementation details or ideas - only relay what is needed.
3453
3517
  `.trim();
3454
3518
  var designExpertTool = {
3455
3519
  definition: {
@@ -3485,6 +3549,7 @@ var designExpertTool = {
3485
3549
  subAgentId: "visualDesignExpert",
3486
3550
  signal: context.signal,
3487
3551
  parentToolId: context.toolCallId,
3552
+ requestId: context.requestId,
3488
3553
  onEvent: context.onEvent,
3489
3554
  resolveExternalTool: context.resolveExternalTool,
3490
3555
  toolRegistry: context.toolRegistry,
@@ -3781,6 +3846,7 @@ var productVisionTool = {
3781
3846
  subAgentId: "productVision",
3782
3847
  signal: context.signal,
3783
3848
  parentToolId: context.toolCallId,
3849
+ requestId: context.requestId,
3784
3850
  onEvent: context.onEvent,
3785
3851
  resolveExternalTool: context.resolveExternalTool,
3786
3852
  toolRegistry: context.toolRegistry,
@@ -3928,6 +3994,7 @@ var codeSanityCheckTool = {
3928
3994
  subAgentId: "codeSanityCheck",
3929
3995
  signal: context.signal,
3930
3996
  parentToolId: context.toolCallId,
3997
+ requestId: context.requestId,
3931
3998
  onEvent: context.onEvent,
3932
3999
  resolveExternalTool: context.resolveExternalTool,
3933
4000
  toolRegistry: context.toolRegistry
@@ -4020,6 +4087,7 @@ function executeTool(name, input, context) {
4020
4087
 
4021
4088
  // src/session.ts
4022
4089
  import fs17 from "fs";
4090
+ var log6 = createLogger("session");
4023
4091
  var SESSION_FILE = ".remy-session.json";
4024
4092
  function loadSession(state) {
4025
4093
  try {
@@ -4027,6 +4095,7 @@ function loadSession(state) {
4027
4095
  const data = JSON.parse(raw);
4028
4096
  if (Array.isArray(data.messages) && data.messages.length > 0) {
4029
4097
  state.messages = sanitizeMessages(data.messages);
4098
+ log6.info("Session loaded", { messageCount: state.messages.length });
4030
4099
  return true;
4031
4100
  }
4032
4101
  } catch {
@@ -4076,7 +4145,9 @@ function saveSession(state) {
4076
4145
  JSON.stringify({ messages: state.messages }, null, 2),
4077
4146
  "utf-8"
4078
4147
  );
4079
- } catch {
4148
+ log6.info("Session saved", { messageCount: state.messages.length });
4149
+ } catch (err) {
4150
+ log6.warn("Session save failed", { error: err.message });
4080
4151
  }
4081
4152
  }
4082
4153
  function clearSession(state) {
@@ -4277,6 +4348,7 @@ function friendlyError(raw) {
4277
4348
  }
4278
4349
 
4279
4350
  // src/agent.ts
4351
+ var log7 = createLogger("agent");
4280
4352
  function getTextContent(blocks) {
4281
4353
  return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
4282
4354
  }
@@ -4314,17 +4386,17 @@ async function runTurn(params) {
4314
4386
  onEvent,
4315
4387
  resolveExternalTool,
4316
4388
  hidden,
4389
+ requestId,
4317
4390
  toolRegistry,
4318
4391
  onBackgroundComplete
4319
4392
  } = params;
4320
4393
  const tools2 = getToolDefinitions(onboardingState);
4321
- log.info("Turn started", {
4322
- messageLength: userMessage.length,
4394
+ log7.info("Turn started", {
4395
+ requestId,
4396
+ model,
4323
4397
  toolCount: tools2.length,
4324
- tools: tools2.map((t) => t.name),
4325
4398
  ...attachments && attachments.length > 0 && {
4326
- attachmentCount: attachments.length,
4327
- attachmentUrls: attachments.map((a) => a.url)
4399
+ attachmentCount: attachments.length
4328
4400
  }
4329
4401
  });
4330
4402
  onEvent({ type: "turn_started" });
@@ -4334,10 +4406,6 @@ async function runTurn(params) {
4334
4406
  }
4335
4407
  if (attachments && attachments.length > 0) {
4336
4408
  userMsg.attachments = attachments;
4337
- log.debug("Attachments added to user message", {
4338
- count: attachments.length,
4339
- urls: attachments.map((a) => a.url)
4340
- });
4341
4409
  }
4342
4410
  state.messages.push(userMsg);
4343
4411
  const STATUS_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
@@ -4398,11 +4466,6 @@ async function runTurn(params) {
4398
4466
  }
4399
4467
  acc.lastEmittedCount = result.emittedCount;
4400
4468
  acc.started = true;
4401
- log.debug("Streaming partial tool_start", {
4402
- id,
4403
- name,
4404
- emittedCount: result.emittedCount
4405
- });
4406
4469
  onEvent({
4407
4470
  type: "tool_start",
4408
4471
  id,
@@ -4418,10 +4481,6 @@ async function runTurn(params) {
4418
4481
  }
4419
4482
  if (!acc.started) {
4420
4483
  acc.started = true;
4421
- log.debug("Streaming content tool: emitting early tool_start", {
4422
- id,
4423
- name
4424
- });
4425
4484
  onEvent({ type: "tool_start", id, name, input: partial });
4426
4485
  }
4427
4486
  if (transform) {
@@ -4429,18 +4488,8 @@ async function runTurn(params) {
4429
4488
  if (result === null) {
4430
4489
  return;
4431
4490
  }
4432
- log.debug("Streaming content tool: emitting tool_input_delta", {
4433
- id,
4434
- name,
4435
- resultLength: result.length
4436
- });
4437
4491
  onEvent({ type: "tool_input_delta", id, name, result });
4438
4492
  } else {
4439
- log.debug("Streaming content tool: emitting tool_input_delta", {
4440
- id,
4441
- name,
4442
- contentLength: content.length
4443
- });
4444
4493
  onEvent({ type: "tool_input_delta", id, name, result: content });
4445
4494
  }
4446
4495
  }
@@ -4449,6 +4498,7 @@ async function runTurn(params) {
4449
4498
  {
4450
4499
  ...apiConfig,
4451
4500
  model,
4501
+ requestId,
4452
4502
  system,
4453
4503
  messages: cleanMessagesForApi(state.messages),
4454
4504
  tools: tools2,
@@ -4500,12 +4550,6 @@ async function runTurn(params) {
4500
4550
  case "tool_input_delta": {
4501
4551
  const acc = getOrCreateAccumulator2(event.id, event.name);
4502
4552
  acc.json += event.delta;
4503
- log.debug("Received tool_input_delta", {
4504
- id: event.id,
4505
- name: event.name,
4506
- deltaLength: event.delta.length,
4507
- accumulatedLength: acc.json.length
4508
- });
4509
4553
  try {
4510
4554
  const partial = parsePartialJson(acc.json);
4511
4555
  await handlePartialInput(acc, event.id, event.name, partial);
@@ -4515,11 +4559,6 @@ async function runTurn(params) {
4515
4559
  }
4516
4560
  case "tool_input_args": {
4517
4561
  const acc = getOrCreateAccumulator2(event.id, event.name);
4518
- log.debug("Received tool_input_args", {
4519
- id: event.id,
4520
- name: event.name,
4521
- keys: Object.keys(event.args)
4522
- });
4523
4562
  await handlePartialInput(acc, event.id, event.name, event.args);
4524
4563
  break;
4525
4564
  }
@@ -4536,11 +4575,10 @@ async function runTurn(params) {
4536
4575
  const tool = getToolByName(event.name);
4537
4576
  const wasStreamed = acc?.started ?? false;
4538
4577
  const isInputStreaming = !!tool?.streaming?.partialInput;
4539
- log.info("Tool call received", {
4540
- id: event.id,
4541
- name: event.name,
4542
- wasStreamed,
4543
- isInputStreaming
4578
+ log7.info("Tool received", {
4579
+ requestId,
4580
+ toolCallId: event.id,
4581
+ name: event.name
4544
4582
  });
4545
4583
  if (!wasStreamed || isInputStreaming) {
4546
4584
  onEvent({
@@ -4594,7 +4632,8 @@ async function runTurn(params) {
4594
4632
  onEvent({ type: "turn_done" });
4595
4633
  return;
4596
4634
  }
4597
- log.info("Executing tools", {
4635
+ log7.info("Tools executing", {
4636
+ requestId,
4598
4637
  count: toolCalls.length,
4599
4638
  tools: toolCalls.map((tc) => tc.name)
4600
4639
  });
@@ -4637,9 +4676,10 @@ async function runTurn(params) {
4637
4676
  let result;
4638
4677
  if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
4639
4678
  saveSession(state);
4640
- log.info("Waiting for external tool result", {
4641
- name: tc.name,
4642
- id: tc.id
4679
+ log7.info("Waiting for external tool result", {
4680
+ requestId,
4681
+ toolCallId: tc.id,
4682
+ name: tc.name
4643
4683
  });
4644
4684
  result = await resolveExternalTool(tc.id, tc.name, input);
4645
4685
  } else {
@@ -4650,6 +4690,7 @@ async function runTurn(params) {
4650
4690
  onEvent: wrappedOnEvent,
4651
4691
  resolveExternalTool,
4652
4692
  toolCallId: tc.id,
4693
+ requestId,
4653
4694
  subAgentMessages,
4654
4695
  toolRegistry,
4655
4696
  onBackgroundComplete,
@@ -4688,11 +4729,12 @@ async function runTurn(params) {
4688
4729
  run(tc.input);
4689
4730
  const r = await resultPromise;
4690
4731
  toolRegistry?.unregister(tc.id);
4691
- log.info("Tool completed", {
4732
+ log7.info("Tool completed", {
4733
+ requestId,
4734
+ toolCallId: tc.id,
4692
4735
  name: tc.name,
4693
- elapsed: `${Date.now() - toolStart}ms`,
4694
- isError: r.isError,
4695
- resultLength: r.result.length
4736
+ durationMs: Date.now() - toolStart,
4737
+ isError: r.isError
4696
4738
  });
4697
4739
  onEvent({
4698
4740
  type: "tool_done",
@@ -4738,6 +4780,7 @@ async function runTurn(params) {
4738
4780
  }
4739
4781
 
4740
4782
  // src/toolRegistry.ts
4783
+ var log8 = createLogger("tool-registry");
4741
4784
  var ToolRegistry = class {
4742
4785
  entries = /* @__PURE__ */ new Map();
4743
4786
  onEvent;
@@ -4763,6 +4806,7 @@ var ToolRegistry = class {
4763
4806
  if (!entry) {
4764
4807
  return false;
4765
4808
  }
4809
+ log8.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
4766
4810
  entry.abortController.abort(mode);
4767
4811
  if (mode === "graceful") {
4768
4812
  const partial = entry.getPartialResult?.() ?? "";
@@ -4795,6 +4839,7 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
4795
4839
  if (!entry) {
4796
4840
  return false;
4797
4841
  }
4842
+ log8.info("Tool restarted", { toolCallId: id, name: entry.name });
4798
4843
  entry.abortController.abort("restart");
4799
4844
  const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
4800
4845
  this.onEvent?.({
@@ -4810,6 +4855,7 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
4810
4855
  };
4811
4856
 
4812
4857
  // src/headless.ts
4858
+ var log9 = createLogger("headless");
4813
4859
  function loadActionPrompt(name) {
4814
4860
  return readAsset("prompt", "actions", `${name}.md`);
4815
4861
  }
@@ -4908,6 +4954,11 @@ ${xmlParts}
4908
4954
  }
4909
4955
  }
4910
4956
  }
4957
+ log9.info("Background complete", {
4958
+ toolCallId,
4959
+ name,
4960
+ requestId: currentRequestId
4961
+ });
4911
4962
  onEvent({
4912
4963
  type: "tool_background_complete",
4913
4964
  id: toolCallId,
@@ -5100,6 +5151,7 @@ ${xmlParts}
5100
5151
  currentRequestId = requestId;
5101
5152
  currentAbort = new AbortController();
5102
5153
  completedEmitted = false;
5154
+ const turnStart = Date.now();
5103
5155
  const attachments = parsed.attachments;
5104
5156
  if (attachments?.length) {
5105
5157
  console.warn(
@@ -5131,6 +5183,7 @@ ${xmlParts}
5131
5183
  system,
5132
5184
  model: opts.model,
5133
5185
  onboardingState,
5186
+ requestId,
5134
5187
  signal: currentAbort.signal,
5135
5188
  onEvent,
5136
5189
  resolveExternalTool,
@@ -5145,11 +5198,20 @@ ${xmlParts}
5145
5198
  requestId
5146
5199
  );
5147
5200
  }
5201
+ log9.info("Turn complete", {
5202
+ requestId,
5203
+ durationMs: Date.now() - turnStart
5204
+ });
5148
5205
  } catch (err) {
5149
5206
  if (!completedEmitted) {
5150
5207
  emit("error", { error: err.message }, requestId);
5151
5208
  emit("completed", { success: false, error: err.message }, requestId);
5152
5209
  }
5210
+ log9.warn("Command failed", {
5211
+ action: "message",
5212
+ requestId,
5213
+ error: err.message
5214
+ });
5153
5215
  }
5154
5216
  currentAbort = null;
5155
5217
  currentRequestId = void 0;
@@ -5165,6 +5227,7 @@ ${xmlParts}
5165
5227
  return;
5166
5228
  }
5167
5229
  const { action, requestId } = parsed;
5230
+ log9.info("Command received", { action, requestId });
5168
5231
  if (action === "tool_result" && parsed.id) {
5169
5232
  const id = parsed.id;
5170
5233
  const result = parsed.result ?? "";