@mindstudio-ai/remy 0.1.278 → 0.1.280

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
@@ -994,25 +994,18 @@ var init_updatePlanStatus = __esm({
994
994
  }
995
995
  });
996
996
 
997
- // src/tools/common/setProjectOnboardingState.ts
998
- var setProjectOnboardingStateTool;
999
- var init_setProjectOnboardingState = __esm({
1000
- "src/tools/common/setProjectOnboardingState.ts"() {
997
+ // src/tools/common/markBuildComplete.ts
998
+ var markBuildCompleteTool;
999
+ var init_markBuildComplete = __esm({
1000
+ "src/tools/common/markBuildComplete.ts"() {
1001
1001
  "use strict";
1002
- setProjectOnboardingStateTool = {
1002
+ markBuildCompleteTool = {
1003
1003
  definition: {
1004
- name: "setProjectOnboardingState",
1005
- description: "Advance the project onboarding state. Forward-only: building \u2192 buildComplete \u2192 onboardingFinished. Normally driven by automated actions \u2014 don't call this out of order during a normal build, or you'll skip stages the user hasn't experienced. Exception: if the project has been in `building` for a while, the build is clearly done (the user is iterating on a working app, deploying, etc.), and the user reports the editor seems stuck \u2014 disabled Preview/Spec/Code tabs, no reveal, etc. \u2014 call `setProjectOnboardingState({ state: 'buildComplete' })` to unstick them. `onboardingFinished` is always set by the frontend after the user dismisses the reveal; never call it yourself.",
1004
+ name: "markBuildComplete",
1005
+ description: 'Mark the initial build as finished. The frontend lands the user on the Build Overview, fully unlocks the editor, and finishes onboarding; the platform sends the one-time "your first build is ready" email. Normally called as the final step of the automated post-build flow \u2014 don\'t call it early, or you\'ll cut the build experience short. Exception: if the project has been in `building` for a while, the build is clearly done (the user is iterating on a working app, deploying, etc.), and the user reports the editor seems stuck \u2014 disabled Preview/Spec/Code tabs \u2014 call this to unstick them. Refused during intake: the user starts the build via "Start Building", never you.',
1006
1006
  inputSchema: {
1007
1007
  type: "object",
1008
- properties: {
1009
- state: {
1010
- type: "string",
1011
- enum: ["building", "buildComplete", "onboardingFinished"],
1012
- description: "The onboarding state to advance to."
1013
- }
1014
- },
1015
- required: ["state"]
1008
+ properties: {}
1016
1009
  }
1017
1010
  },
1018
1011
  async execute() {
@@ -4146,6 +4139,41 @@ var init_runMethod = __esm({
4146
4139
  }
4147
4140
  });
4148
4141
 
4142
+ // src/tools/code/testJewel.ts
4143
+ var testJewelTool;
4144
+ var init_testJewel = __esm({
4145
+ "src/tools/code/testJewel.ts"() {
4146
+ "use strict";
4147
+ testJewelTool = {
4148
+ definition: {
4149
+ name: "testJewel",
4150
+ description: "Run a method's jewel directly against a test input and return its pair record. Use after writing or modifying a jewel to verify the subject projection, the proposal, and the grading \u2014 the method itself is NOT executed (no data changes) and nothing reaches the pair ledger. Pass `humanInput` (the exact input a human would have submitted) for a full graded run: the jewel derives the subject from it, proposes, and grades its proposal against it as ground truth \u2014 the record carries a verdict (agree/disagree/skip). Or pass `subject` for an ungraded eval run (propose only). Returns the full pair record (subject, proposed, reasoning, actual, verdict, notes, error) plus captured console output and duration. A `pair.error` means your jewel code threw in that phase \u2014 fix and re-run. Seed realistic data first (runScenario) so the jewel's reads have a world to look at.",
4151
+ inputSchema: {
4152
+ type: "object",
4153
+ properties: {
4154
+ method: {
4155
+ type: "string",
4156
+ description: 'The shadowed method\'s export name (camelCase, e.g. "updateIssue"). Its manifest entry must have a `jewel` pointer.'
4157
+ },
4158
+ humanInput: {
4159
+ type: "object",
4160
+ description: "Shadow-style run: the exact method input a human would have submitted. The jewel derives its subject from this, proposes, and grades against it \u2014 the record gets a verdict. Use realistic inputs; this is how you verify agreement."
4161
+ },
4162
+ subject: {
4163
+ type: "object",
4164
+ description: "Eval run: a subject to propose against with no ground truth (record is ungraded). Use to probe the jewel on cases where you don't know the right answer yet. Pass exactly one of humanInput or subject."
4165
+ }
4166
+ },
4167
+ required: ["method"]
4168
+ }
4169
+ },
4170
+ async execute() {
4171
+ return "ok";
4172
+ }
4173
+ };
4174
+ }
4175
+ });
4176
+
4149
4177
  // src/tools/code/queryDatabase.ts
4150
4178
  var queryDatabaseTool;
4151
4179
  var init_queryDatabase = __esm({
@@ -6704,6 +6732,155 @@ var init_loadSkill2 = __esm({
6704
6732
  }
6705
6733
  });
6706
6734
 
6735
+ // src/subagents/designExpert/tools/createWireframe.ts
6736
+ var createWireframe_exports = {};
6737
+ __export(createWireframe_exports, {
6738
+ WIREFRAMES_DIR: () => WIREFRAMES_DIR,
6739
+ definition: () => definition10,
6740
+ execute: () => execute10
6741
+ });
6742
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
6743
+ import { join as join2 } from "path";
6744
+ function slugify(name) {
6745
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40).replace(/-+$/, "") || "wireframe";
6746
+ }
6747
+ function singleLine(value) {
6748
+ return value.replace(/\s+/g, " ").trim();
6749
+ }
6750
+ function validate(name, html) {
6751
+ const warnings = [];
6752
+ if (!name.trim()) {
6753
+ return { error: "Error: name is required.", warnings };
6754
+ }
6755
+ if (!html.trim()) {
6756
+ return { error: "Error: html is required.", warnings };
6757
+ }
6758
+ if (/^\s*---/.test(html)) {
6759
+ return {
6760
+ error: "Error: html must not include frontmatter \u2014 pass name and description as parameters and the frontmatter is added for you.",
6761
+ warnings
6762
+ };
6763
+ }
6764
+ if (/<img[\s>]/i.test(html)) {
6765
+ return {
6766
+ error: "Error: wireframes must not contain <img> tags. Use skeleton placeholders (grey boxes, gradients) instead of images.",
6767
+ warnings
6768
+ };
6769
+ }
6770
+ if (!/<style[\s>]/i.test(html) && !/style=/i.test(html)) {
6771
+ return {
6772
+ error: "Error: no CSS found. Wireframes are self-contained HTML+CSS \u2014 style the component container (not the body) so it reads against the transparent background.",
6773
+ warnings
6774
+ };
6775
+ }
6776
+ const lines = html.split("\n").length;
6777
+ if (lines > 120) {
6778
+ warnings.push(
6779
+ `The wireframe is ${lines} lines \u2014 past the ~100-line budget. Wireframes isolate one small piece; consider whether this is building too much.`
6780
+ );
6781
+ }
6782
+ return { warnings };
6783
+ }
6784
+ async function uploadMirror(context, slug, content) {
6785
+ const { apiConfig } = context;
6786
+ if (!apiConfig?.appId) {
6787
+ return { ok: false, note: "no app id in this session" };
6788
+ }
6789
+ try {
6790
+ const url = `${apiConfig.baseUrl}/_internal/v2/apps/${apiConfig.appId}/dev/wireframes`;
6791
+ const res = await fetch(url, {
6792
+ method: "POST",
6793
+ headers: {
6794
+ "Content-Type": "application/json",
6795
+ Authorization: `Bearer ${apiConfig.apiKey}`
6796
+ },
6797
+ body: JSON.stringify({ slug, content }),
6798
+ signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS2)
6799
+ });
6800
+ if (!res.ok) {
6801
+ return { ok: false, note: `HTTP ${res.status}` };
6802
+ }
6803
+ return { ok: true };
6804
+ } catch (err) {
6805
+ return { ok: false, note: err.message };
6806
+ }
6807
+ }
6808
+ async function execute10(input, onLog, context) {
6809
+ if (!context) {
6810
+ return "Error: createWireframe requires execution context";
6811
+ }
6812
+ const name = String(input.name ?? "");
6813
+ const description = String(input.description ?? "");
6814
+ const html = String(input.html ?? "");
6815
+ const { error, warnings } = validate(name, html);
6816
+ if (error) {
6817
+ return error;
6818
+ }
6819
+ const slug = `${slugify(name)}-${Math.random().toString(36).slice(2, 8)}`;
6820
+ const relPath = `${WIREFRAMES_DIR}/${slug}.html`;
6821
+ const content = [
6822
+ "---",
6823
+ `name: ${singleLine(name)}`,
6824
+ `description: ${singleLine(description)}`,
6825
+ "---",
6826
+ html.trim(),
6827
+ ""
6828
+ ].join("\n");
6829
+ await mkdir2(join2(PROJECT_ROOT, WIREFRAMES_DIR), { recursive: true });
6830
+ await writeFile2(join2(PROJECT_ROOT, relPath), content, "utf8");
6831
+ onLog?.(`Wrote ${relPath}, mirroring for preview...`);
6832
+ const mirror = await uploadMirror(context, slug, content);
6833
+ if (!mirror.ok) {
6834
+ log10.warn("Wireframe mirror upload failed", { slug, note: mirror.note });
6835
+ }
6836
+ const lines = [
6837
+ `Created wireframe "${singleLine(name)}" at ${relPath}.`,
6838
+ `Reference it in your response and in specs with exactly: ![${singleLine(name)}](${relPath})`
6839
+ ];
6840
+ if (!mirror.ok) {
6841
+ lines.push(
6842
+ `Note: the preview mirror upload failed (${mirror.note}) \u2014 the file is intact and usable by the developer, but the wireframe may not render visually in the dashboard. Do not retry; mention the layout in prose as well.`
6843
+ );
6844
+ }
6845
+ if (warnings.length > 0) {
6846
+ lines.push(...warnings.map((w) => `Note: ${w}`));
6847
+ }
6848
+ return lines.join("\n");
6849
+ }
6850
+ var log10, WIREFRAMES_DIR, UPLOAD_TIMEOUT_MS2, definition10;
6851
+ var init_createWireframe = __esm({
6852
+ "src/subagents/designExpert/tools/createWireframe.ts"() {
6853
+ "use strict";
6854
+ init_projectRoot();
6855
+ init_logger();
6856
+ log10 = createLogger("createWireframe");
6857
+ WIREFRAMES_DIR = "src/.wireframes";
6858
+ UPLOAD_TIMEOUT_MS2 = 3e4;
6859
+ definition10 = {
6860
+ name: "createWireframe",
6861
+ description: "Create a wireframe from self-contained HTML+CSS. Returns a markdown reference line \u2014 include that exact line in your response wherever the wireframe belongs, with your notes in the surrounding prose. To revise a wireframe, create a new one (read the old file first if you are iterating on it).",
6862
+ inputSchema: {
6863
+ type: "object",
6864
+ properties: {
6865
+ name: {
6866
+ type: "string",
6867
+ description: 'Short display name, e.g. "Feed Post Card". Becomes the caption and the filename slug.'
6868
+ },
6869
+ description: {
6870
+ type: "string",
6871
+ description: "One line on what the wireframe shows \u2014 layout, states, motion."
6872
+ },
6873
+ html: {
6874
+ type: "string",
6875
+ description: "The complete HTML document (<html>\u2026</html>), self-contained vanilla HTML/CSS/JS. No frontmatter \u2014 it is added for you."
6876
+ }
6877
+ },
6878
+ required: ["name", "description", "html"]
6879
+ }
6880
+ };
6881
+ }
6882
+ });
6883
+
6707
6884
  // src/subagents/designExpert/tools/index.ts
6708
6885
  async function executeDesignExpertTool(name, input, context, toolCallId, onLog) {
6709
6886
  const tool = tools[name];
@@ -6728,6 +6905,7 @@ var init_tools4 = __esm({
6728
6905
  init_renderImage();
6729
6906
  init_polishCopy();
6730
6907
  init_loadSkill2();
6908
+ init_createWireframe();
6731
6909
  init_screenshot2();
6732
6910
  tools = {
6733
6911
  searchGoogle: searchGoogle_exports,
@@ -6742,7 +6920,9 @@ var init_tools4 = __esm({
6742
6920
  editImages: editImages_exports,
6743
6921
  renderImage: renderImage_exports,
6744
6922
  polishCopy: polishCopy_exports,
6745
- loadSkill: loadSkill_exports
6923
+ loadSkill: loadSkill_exports,
6924
+ // Appended last: tool order is part of the subagent's prompt-cache prefix.
6925
+ createWireframe: createWireframe_exports
6746
6926
  };
6747
6927
  DESIGN_EXPERT_TOOLS = [
6748
6928
  ...COMMON_READ_TOOLS,
@@ -6757,7 +6937,7 @@ async function initOrgContext(config) {
6757
6937
  cached = await fetchRemyContext(config);
6758
6938
  const orgDefaultModels2 = filterModelPicks(cached?.defaultModels);
6759
6939
  setOrgDefaultModels(orgDefaultModels2);
6760
- log10.debug("org context loaded", {
6940
+ log11.debug("org context loaded", {
6761
6941
  delegatedAvailable: cached?.auth?.delegatedAvailable ?? false,
6762
6942
  requireDelegatedOnly: cached?.auth?.requireDelegatedOnly ?? false,
6763
6943
  hasOrgName: !!cached?.org?.name,
@@ -6766,7 +6946,7 @@ async function initOrgContext(config) {
6766
6946
  } catch (err) {
6767
6947
  cached = null;
6768
6948
  setOrgDefaultModels({});
6769
- log10.debug("org context init failed", { error: err.message });
6949
+ log11.debug("org context init failed", { error: err.message });
6770
6950
  }
6771
6951
  }
6772
6952
  function getOrgContext() {
@@ -6802,14 +6982,14 @@ function renderOrgContextBlock() {
6802
6982
  lines.push("</org_context>");
6803
6983
  return lines.join("\n");
6804
6984
  }
6805
- var log10, cached;
6985
+ var log11, cached;
6806
6986
  var init_orgContext = __esm({
6807
6987
  "src/orgContext.ts"() {
6808
6988
  "use strict";
6809
6989
  init_api();
6810
6990
  init_surfaces();
6811
6991
  init_logger();
6812
- log10 = createLogger("orgContext");
6992
+ log11 = createLogger("orgContext");
6813
6993
  cached = null;
6814
6994
  }
6815
6995
  });
@@ -7966,7 +8146,7 @@ var init_tools8 = __esm({
7966
8146
  init_presentPublishPlan();
7967
8147
  init_writePlan();
7968
8148
  init_updatePlanStatus();
7969
- init_setProjectOnboardingState();
8149
+ init_markBuildComplete();
7970
8150
  init_promptUser();
7971
8151
  init_confirmDestructiveAction();
7972
8152
  init_sdkConsultant();
@@ -7986,6 +8166,7 @@ var init_tools8 = __esm({
7986
8166
  init_restartProcess();
7987
8167
  init_runScenario();
7988
8168
  init_runMethod();
8169
+ init_testJewel();
7989
8170
  init_queryDatabase();
7990
8171
  init_screenshot2();
7991
8172
  init_browserAutomation();
@@ -7998,7 +8179,7 @@ var init_tools8 = __esm({
7998
8179
  init_writeBuildOverview();
7999
8180
  ALL_TOOLS = [
8000
8181
  // Common
8001
- setProjectOnboardingStateTool,
8182
+ markBuildCompleteTool,
8002
8183
  promptUserTool,
8003
8184
  confirmDestructiveActionTool,
8004
8185
  askMindStudioSdkTool,
@@ -8041,7 +8222,8 @@ var init_tools8 = __esm({
8041
8222
  restartProcessTool,
8042
8223
  // Appended rather than grouped: position is part of the cache prefix, so a
8043
8224
  // new tool goes at the end to leave every existing session's prefix intact.
8044
- loadSkillTool
8225
+ loadSkillTool,
8226
+ testJewelTool
8045
8227
  ];
8046
8228
  SUBAGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
8047
8229
  "visualDesignExpert",
@@ -8336,17 +8518,17 @@ async function runExtraction(apiConfig, model) {
8336
8518
  const inputHash = computeInputHash();
8337
8519
  const cached3 = readCache();
8338
8520
  if (cached3 && cached3.inputHash === inputHash) {
8339
- log11.debug("Brand inputs unchanged \u2014 skipping extraction", { inputHash });
8521
+ log12.debug("Brand inputs unchanged \u2014 skipping extraction", { inputHash });
8340
8522
  return null;
8341
8523
  }
8342
- log11.info("Extracting brand", { inputHash });
8524
+ log12.info("Extracting brand", { inputHash });
8343
8525
  const brand = await extractBrand(apiConfig, model);
8344
8526
  if (!brand) {
8345
- log11.warn("Brand extraction failed \u2014 leaving cache untouched");
8527
+ log12.warn("Brand extraction failed \u2014 leaving cache untouched");
8346
8528
  return null;
8347
8529
  }
8348
8530
  persistBrand(brand, inputHash);
8349
- log11.info("Brand persisted", { inputHash });
8531
+ log12.info("Brand persisted", { inputHash });
8350
8532
  return brand;
8351
8533
  }
8352
8534
  function isDedicatedBrandFile(filePath) {
@@ -8432,7 +8614,7 @@ function parseFrontmatter3(filePath) {
8432
8614
  async function extractBrand(apiConfig, model) {
8433
8615
  const corpus = buildCorpus();
8434
8616
  if (!corpus.trim()) {
8435
- log11.debug("No spec corpus \u2014 emitting empty brand");
8617
+ log12.debug("No spec corpus \u2014 emitting empty brand");
8436
8618
  return { version: 1 };
8437
8619
  }
8438
8620
  let responseText = "";
@@ -8466,17 +8648,17 @@ async function extractBrand(apiConfig, model) {
8466
8648
  toolNames: []
8467
8649
  });
8468
8650
  } else if (event.type === "error") {
8469
- log11.error("Brand extraction stream error", { error: event.error });
8651
+ log12.error("Brand extraction stream error", { error: event.error });
8470
8652
  return null;
8471
8653
  }
8472
8654
  }
8473
8655
  } catch (err) {
8474
- log11.error("Brand extraction threw", { error: err?.message });
8656
+ log12.error("Brand extraction threw", { error: err?.message });
8475
8657
  return null;
8476
8658
  }
8477
8659
  const parsed = parseJsonResponse(responseText);
8478
8660
  if (!parsed) {
8479
- log11.warn("Brand extraction returned unparseable JSON", {
8661
+ log12.warn("Brand extraction returned unparseable JSON", {
8480
8662
  preview: responseText.slice(0, 200)
8481
8663
  });
8482
8664
  return null;
@@ -8643,7 +8825,7 @@ function readCache() {
8643
8825
  return null;
8644
8826
  }
8645
8827
  }
8646
- var log11, EXTRACT_PROMPT, BRAND_FILE, CACHE_FILE, HEAD_SLICE_CHARS, BRAND_CORPUS_CHAR_LIMIT;
8828
+ var log12, EXTRACT_PROMPT, BRAND_FILE, CACHE_FILE, HEAD_SLICE_CHARS, BRAND_CORPUS_CHAR_LIMIT;
8647
8829
  var init_brandExtraction = __esm({
8648
8830
  "src/brandExtraction/index.ts"() {
8649
8831
  "use strict";
@@ -8652,7 +8834,7 @@ var init_brandExtraction = __esm({
8652
8834
  init_atomicWrite();
8653
8835
  init_logger();
8654
8836
  init_usageLedger();
8655
- log11 = createLogger("brandExtraction");
8837
+ log12 = createLogger("brandExtraction");
8656
8838
  EXTRACT_PROMPT = readAsset("brandExtraction", "extract.md");
8657
8839
  BRAND_FILE = ".remy-brand.json";
8658
8840
  CACHE_FILE = ".remy-brand.cache.json";
@@ -8669,7 +8851,7 @@ function triggerBrandExtraction(apiConfig, model) {
8669
8851
  }
8670
8852
  inflight = true;
8671
8853
  void runExtraction(apiConfig, model).catch((err) => {
8672
- log12.error("Brand extraction failed", { error: err?.message });
8854
+ log13.error("Brand extraction failed", { error: err?.message });
8673
8855
  }).finally(() => {
8674
8856
  inflight = false;
8675
8857
  if (dirty) {
@@ -8678,13 +8860,13 @@ function triggerBrandExtraction(apiConfig, model) {
8678
8860
  }
8679
8861
  });
8680
8862
  }
8681
- var log12, inflight, dirty;
8863
+ var log13, inflight, dirty;
8682
8864
  var init_trigger2 = __esm({
8683
8865
  "src/brandExtraction/trigger.ts"() {
8684
8866
  "use strict";
8685
8867
  init_brandExtraction();
8686
8868
  init_logger();
8687
- log12 = createLogger("brandExtraction:trigger");
8869
+ log13 = createLogger("brandExtraction:trigger");
8688
8870
  inflight = false;
8689
8871
  dirty = false;
8690
8872
  }
@@ -8733,7 +8915,7 @@ async function runTurn(params) {
8733
8915
  (n, e) => n + (e.attachments?.length ?? 0),
8734
8916
  0
8735
8917
  );
8736
- log13.info("Turn started", {
8918
+ log14.info("Turn started", {
8737
8919
  requestId,
8738
8920
  model,
8739
8921
  buildModel: buildModelOverride,
@@ -8782,7 +8964,7 @@ async function runTurn(params) {
8782
8964
  }
8783
8965
  const isFirstMessage = state.messages.filter((m) => m.role === "user").length === 1;
8784
8966
  const STATUS_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
8785
- "setProjectOnboardingState",
8967
+ "markBuildComplete",
8786
8968
  "setProjectMetadata",
8787
8969
  "editsFinished"
8788
8970
  ]);
@@ -9028,7 +9210,7 @@ async function runTurn(params) {
9028
9210
  const acc = toolInputAccumulators.get(event.id);
9029
9211
  const wasStreamed = acc?.started ?? false;
9030
9212
  const isInputStreaming = !!tool?.streaming?.partialInput;
9031
- log13.info("Tool received", {
9213
+ log14.info("Tool received", {
9032
9214
  requestId,
9033
9215
  toolCallId: event.id,
9034
9216
  name: event.name
@@ -9147,7 +9329,7 @@ async function runTurn(params) {
9147
9329
  const toolCalls = getToolCalls(contentBlocks);
9148
9330
  if (toolCalls.length === 0 && (stopReason === "repetition" || stopReason === "max_tokens") && abnormalStopRecoveries < MAX_ABNORMAL_STOP_RECOVERIES && !signal?.aborted) {
9149
9331
  abnormalStopRecoveries++;
9150
- log13.warn("Abnormal stop \u2014 nudging model to continue", {
9332
+ log14.warn("Abnormal stop \u2014 nudging model to continue", {
9151
9333
  requestId,
9152
9334
  stopReason,
9153
9335
  attempt: abnormalStopRecoveries
@@ -9175,7 +9357,7 @@ async function runTurn(params) {
9175
9357
  });
9176
9358
  return;
9177
9359
  }
9178
- log13.info("Tools executing", {
9360
+ log14.info("Tools executing", {
9179
9361
  requestId,
9180
9362
  count: toolCalls.length,
9181
9363
  tools: toolCalls.map((tc) => tc.name)
@@ -9222,7 +9404,7 @@ async function runTurn(params) {
9222
9404
  let result;
9223
9405
  if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
9224
9406
  saveSession(state);
9225
- log13.info("Waiting for external tool result", {
9407
+ log14.info("Waiting for external tool result", {
9226
9408
  requestId,
9227
9409
  toolCallId: tc.id,
9228
9410
  name: tc.name
@@ -9290,7 +9472,7 @@ async function runTurn(params) {
9290
9472
  if (!isBackgroundCall(tc)) {
9291
9473
  toolRegistry?.unregister(tc.id);
9292
9474
  }
9293
- log13.info("Tool completed", {
9475
+ log14.info("Tool completed", {
9294
9476
  requestId,
9295
9477
  toolCallId: tc.id,
9296
9478
  name: tc.name,
@@ -9364,7 +9546,7 @@ async function runTurn(params) {
9364
9546
  statusWatcher.stop();
9365
9547
  }
9366
9548
  }
9367
- var log13, BRAND_TRIGGERING_TOOLS, EXTERNAL_TOOLS, USER_BLOCKING_EXTERNAL_TOOLS;
9549
+ var log14, BRAND_TRIGGERING_TOOLS, EXTERNAL_TOOLS, USER_BLOCKING_EXTERNAL_TOOLS;
9368
9550
  var init_agent = __esm({
9369
9551
  "src/agent.ts"() {
9370
9552
  "use strict";
@@ -9383,7 +9565,7 @@ var init_agent = __esm({
9383
9565
  init_surfaces();
9384
9566
  init_toolRegistry();
9385
9567
  init_historyLimits();
9386
- log13 = createLogger("agent");
9568
+ log14 = createLogger("agent");
9387
9569
  BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set([
9388
9570
  "writeSpec",
9389
9571
  "editSpec",
@@ -9391,11 +9573,12 @@ var init_agent = __esm({
9391
9573
  ]);
9392
9574
  EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
9393
9575
  "promptUser",
9394
- "setProjectOnboardingState",
9576
+ "markBuildComplete",
9395
9577
  "presentPublishPlan",
9396
9578
  "confirmDestructiveAction",
9397
9579
  "runScenario",
9398
9580
  "runMethod",
9581
+ "testJewel",
9399
9582
  "queryDatabase",
9400
9583
  "browserCommand",
9401
9584
  "setProjectMetadata"
@@ -9507,6 +9690,10 @@ function buildSystemPrompt(onboardingState) {
9507
9690
  {{compiled/auth.md}}
9508
9691
  </auth>
9509
9692
 
9693
+ <jewels>
9694
+ {{compiled/jewels.md}}
9695
+ </jewels>
9696
+
9510
9697
  <dev_and_deploy>
9511
9698
  {{compiled/dev-and-deploy.md}}
9512
9699
  </dev_and_deploy>
@@ -9561,7 +9748,7 @@ New projects progress through four onboarding states. The user might skip this e
9561
9748
 
9562
9749
  - **intake**: Gathering requirements. The project has scaffold code (a "hello world" starter) but it's not the user's app yet. Focus on understanding what they want to build, not on the existing code. Intake ends with a plan proposal via writePlan.
9563
9750
  - **building**: The user approved the initial plan. The agent is writing the spec and building the app. This can take a while and involves heavy tool use (spec authoring, design expert consultation, code generation, verification, polishing).
9564
- - **buildComplete**: The build is done. The frontend is showing the user a reveal experience (pitch deck, app preview). The agent does not need to do anything in this state; the frontend advances to onboardingFinished when the user is ready.
9751
+ - **buildComplete**: The build is done \u2014 entered via markBuildComplete at the end of the automated post-build flow. Transient: the frontend lands the user on the Build Overview, fully unlocks the editor, and immediately advances to onboardingFinished on its own. The agent does not need to do anything in this state.
9565
9752
  - **onboardingFinished**: The project is built and ready. Full development mode with all tools available. From here on, keep spec and code in sync as changes are made.
9566
9753
  </project_onboarding>
9567
9754
 
@@ -9602,10 +9789,10 @@ import os from "os";
9602
9789
  function loadConfigFile() {
9603
9790
  try {
9604
9791
  const raw = fs22.readFileSync(CONFIG_PATH, "utf-8");
9605
- log14.debug("Loaded config file", { path: CONFIG_PATH });
9792
+ log15.debug("Loaded config file", { path: CONFIG_PATH });
9606
9793
  return JSON.parse(raw);
9607
9794
  } catch (err) {
9608
- log14.debug("No config file found", {
9795
+ log15.debug("No config file found", {
9609
9796
  path: CONFIG_PATH,
9610
9797
  error: err.message
9611
9798
  });
@@ -9620,13 +9807,13 @@ function resolveConfig(flags2) {
9620
9807
  const baseUrl2 = flags2?.baseUrl || process.env.MINDSTUDIO_BASE_URL || env?.apiBaseUrl || DEFAULT_BASE_URL;
9621
9808
  const appId = process.env.MINDSTUDIO_APP_ID || void 0;
9622
9809
  if (!apiKey) {
9623
- log14.error("No API key found");
9810
+ log15.error("No API key found");
9624
9811
  throw new Error(
9625
9812
  "No API key found. Set MINDSTUDIO_API_KEY or configure ~/.mindstudio-local-tunnel/config.json."
9626
9813
  );
9627
9814
  }
9628
9815
  const keySource = flags2?.apiKey ? "cli flag" : process.env.MINDSTUDIO_API_KEY ? "env var" : "config file";
9629
- log14.info("Config resolved", {
9816
+ log15.info("Config resolved", {
9630
9817
  baseUrl: baseUrl2,
9631
9818
  keySource,
9632
9819
  environment: activeEnv,
@@ -9634,12 +9821,12 @@ function resolveConfig(flags2) {
9634
9821
  });
9635
9822
  return { apiKey, baseUrl: baseUrl2, appId };
9636
9823
  }
9637
- var log14, CONFIG_PATH, DEFAULT_BASE_URL;
9824
+ var log15, CONFIG_PATH, DEFAULT_BASE_URL;
9638
9825
  var init_config = __esm({
9639
9826
  "src/config.ts"() {
9640
9827
  "use strict";
9641
9828
  init_logger();
9642
- log14 = createLogger("config");
9829
+ log15 = createLogger("config");
9643
9830
  CONFIG_PATH = path12.join(
9644
9831
  os.homedir(),
9645
9832
  ".mindstudio-local-tunnel",
@@ -9651,8 +9838,8 @@ var init_config = __esm({
9651
9838
 
9652
9839
  // src/headless/attachments.ts
9653
9840
  import { mkdirSync, existsSync } from "fs";
9654
- import { writeFile as writeFile2 } from "fs/promises";
9655
- import { basename as basename2, join as join2, extname as extname2 } from "path";
9841
+ import { writeFile as writeFile3 } from "fs/promises";
9842
+ import { basename as basename2, join as join3, extname as extname2 } from "path";
9656
9843
  function filenameFromUrl(url) {
9657
9844
  try {
9658
9845
  const pathname = new URL(url).pathname;
@@ -9663,7 +9850,7 @@ function filenameFromUrl(url) {
9663
9850
  }
9664
9851
  }
9665
9852
  function resolveUniqueFilename(name, claimed) {
9666
- const isFree = (candidate) => !claimed.has(candidate) && !existsSync(join2(UPLOADS_DIR, candidate));
9853
+ const isFree = (candidate) => !claimed.has(candidate) && !existsSync(join3(UPLOADS_DIR, candidate));
9667
9854
  if (isFree(name)) {
9668
9855
  return name;
9669
9856
  }
@@ -9697,7 +9884,7 @@ async function persistAttachments(attachments) {
9697
9884
  const results = await Promise.allSettled(
9698
9885
  nonVoice.map(async (att, i) => {
9699
9886
  const name = names[i];
9700
- const localPath = join2(UPLOADS_DIR, name);
9887
+ const localPath = join3(UPLOADS_DIR, name);
9701
9888
  const res = await fetch(att.url, {
9702
9889
  signal: AbortSignal.timeout(3e4)
9703
9890
  });
@@ -9705,8 +9892,8 @@ async function persistAttachments(attachments) {
9705
9892
  throw new Error(`HTTP ${res.status} downloading ${att.url}`);
9706
9893
  }
9707
9894
  const buffer = Buffer.from(await res.arrayBuffer());
9708
- await writeFile2(localPath, buffer);
9709
- log15.info("Attachment saved", {
9895
+ await writeFile3(localPath, buffer);
9896
+ log16.info("Attachment saved", {
9710
9897
  filename: name,
9711
9898
  path: localPath,
9712
9899
  bytes: buffer.length
@@ -9719,8 +9906,8 @@ async function persistAttachments(attachments) {
9719
9906
  });
9720
9907
  if (textRes.ok) {
9721
9908
  extractedTextPath = `${localPath}.txt`;
9722
- await writeFile2(extractedTextPath, await textRes.text(), "utf-8");
9723
- log15.info("Extracted text saved", { path: extractedTextPath });
9909
+ await writeFile3(extractedTextPath, await textRes.text(), "utf-8");
9910
+ log16.info("Extracted text saved", { path: extractedTextPath });
9724
9911
  }
9725
9912
  } catch {
9726
9913
  }
@@ -9764,12 +9951,12 @@ function buildUploadHeader(documents, images) {
9764
9951
  return `[Uploaded files]
9765
9952
  ${lines.join("\n")}`;
9766
9953
  }
9767
- var log15, UPLOADS_DIR, IMAGE_EXTENSIONS;
9954
+ var log16, UPLOADS_DIR, IMAGE_EXTENSIONS;
9768
9955
  var init_attachments = __esm({
9769
9956
  "src/headless/attachments.ts"() {
9770
9957
  "use strict";
9771
9958
  init_logger();
9772
- log15 = createLogger("headless:attachments");
9959
+ log16 = createLogger("headless:attachments");
9773
9960
  UPLOADS_DIR = "src/.user-uploads";
9774
9961
  IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
9775
9962
  }
@@ -9961,7 +10148,7 @@ var headless_exports = {};
9961
10148
  __export(headless_exports, {
9962
10149
  HeadlessSession: () => HeadlessSession
9963
10150
  });
9964
- var log16, EXTERNAL_TOOL_TIMEOUT_MS, USER_FACING_TOOLS, FORCED_COMPACTION_THRESHOLD_TOKENS, HeadlessSession;
10151
+ var log17, EXTERNAL_TOOL_TIMEOUT_MS, LONG_RUNNING_TOOLS, LONG_RUNNING_TOOL_TIMEOUT_MS, USER_FACING_TOOLS, FORCED_COMPACTION_THRESHOLD_TOKENS, HeadlessSession;
9965
10152
  var init_headless = __esm({
9966
10153
  "src/headless/index.ts"() {
9967
10154
  "use strict";
@@ -9983,8 +10170,10 @@ var init_headless = __esm({
9983
10170
  init_messageQueue();
9984
10171
  init_resolve();
9985
10172
  init_sentinel();
9986
- log16 = createLogger("headless");
10173
+ log17 = createLogger("headless");
9987
10174
  EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
10175
+ LONG_RUNNING_TOOLS = /* @__PURE__ */ new Set(["runMethod", "testJewel"]);
10176
+ LONG_RUNNING_TOOL_TIMEOUT_MS = 18e5;
9988
10177
  USER_FACING_TOOLS = /* @__PURE__ */ new Set([
9989
10178
  "promptUser",
9990
10179
  "confirmDestructiveAction",
@@ -10206,7 +10395,7 @@ var init_headless = __esm({
10206
10395
  }
10207
10396
  const line = JSON.stringify(payload) + "\n";
10208
10397
  if (event === "history") {
10209
- log16.info("Wrote history event to stdout", {
10398
+ log17.info("Wrote history event to stdout", {
10210
10399
  requestId,
10211
10400
  bytes: line.length
10212
10401
  });
@@ -10292,7 +10481,7 @@ var init_headless = __esm({
10292
10481
  if (this.sessionStats.lastContextSize <= FORCED_COMPACTION_THRESHOLD_TOKENS) {
10293
10482
  return;
10294
10483
  }
10295
- log16.info("Forced compaction gate triggered", {
10484
+ log17.info("Forced compaction gate triggered", {
10296
10485
  contextSize: this.sessionStats.lastContextSize,
10297
10486
  threshold: FORCED_COMPACTION_THRESHOLD_TOKENS,
10298
10487
  requestId
@@ -10311,7 +10500,7 @@ var init_headless = __esm({
10311
10500
  onBackgroundComplete = (toolCallId, name, result, subAgentMessages) => {
10312
10501
  const notify = getToolByName(name)?.backgroundNotify ?? "wake";
10313
10502
  this.pendingBlockUpdates.push({ toolCallId, result, subAgentMessages });
10314
- log16.info("Background complete", {
10503
+ log17.info("Background complete", {
10315
10504
  toolCallId,
10316
10505
  name,
10317
10506
  notify,
@@ -10363,13 +10552,14 @@ var init_headless = __esm({
10363
10552
  return Promise.resolve(early);
10364
10553
  }
10365
10554
  const shouldTimeout = !USER_FACING_TOOLS.has(name);
10555
+ const timeoutMs = LONG_RUNNING_TOOLS.has(name) ? LONG_RUNNING_TOOL_TIMEOUT_MS : EXTERNAL_TOOL_TIMEOUT_MS;
10366
10556
  return new Promise((resolve4) => {
10367
10557
  const timeout = shouldTimeout ? setTimeout(() => {
10368
10558
  this.pendingTools.delete(id);
10369
10559
  resolve4(
10370
- "Error: Tool timed out \u2014 no response from the app environment after 5 minutes."
10560
+ `Error: Tool timed out \u2014 no response from the app environment after ${Math.round(timeoutMs / 6e4)} minutes.`
10371
10561
  );
10372
- }, EXTERNAL_TOOL_TIMEOUT_MS) : void 0;
10562
+ }, timeoutMs) : void 0;
10373
10563
  this.pendingTools.set(id, {
10374
10564
  resolve: (result) => {
10375
10565
  clearTimeout(timeout);
@@ -10573,7 +10763,7 @@ var init_headless = __esm({
10573
10763
  const { documents, images } = await persistAttachments(attachments);
10574
10764
  return buildUploadHeader(documents, images) || void 0;
10575
10765
  } catch (err) {
10576
- log16.warn("Attachment persistence failed", { error: err.message });
10766
+ log17.warn("Attachment persistence failed", { error: err.message });
10577
10767
  return void 0;
10578
10768
  }
10579
10769
  }
@@ -10587,7 +10777,7 @@ var init_headless = __esm({
10587
10777
  async runSingleTurn(parsed, requestId, fromChain = false, queued = false) {
10588
10778
  const attachments = parsed.attachments;
10589
10779
  if (attachments?.length) {
10590
- log16.info("Message has attachments", {
10780
+ log17.info("Message has attachments", {
10591
10781
  count: attachments.length,
10592
10782
  urls: attachments.map((a) => a.url)
10593
10783
  });
@@ -10775,7 +10965,7 @@ var init_headless = __esm({
10775
10965
  error: "Turn ended unexpectedly"
10776
10966
  });
10777
10967
  }
10778
- log16.info("Turn complete", {
10968
+ log17.info("Turn complete", {
10779
10969
  requestId,
10780
10970
  durationMs: Date.now() - this.turnStart
10781
10971
  });
@@ -10787,7 +10977,7 @@ var init_headless = __esm({
10787
10977
  error: err.message
10788
10978
  });
10789
10979
  }
10790
- log16.warn("Command failed", {
10980
+ log17.warn("Command failed", {
10791
10981
  action: "message",
10792
10982
  requestId,
10793
10983
  error: err.message
@@ -10997,7 +11187,7 @@ var init_headless = __esm({
10997
11187
  try {
10998
11188
  parsed = JSON.parse(line);
10999
11189
  } catch (err) {
11000
- log16.warn("Invalid JSON on stdin", {
11190
+ log17.warn("Invalid JSON on stdin", {
11001
11191
  error: err.message,
11002
11192
  lineLength: line.length,
11003
11193
  preview: line.slice(0, 200)
@@ -11006,7 +11196,7 @@ var init_headless = __esm({
11006
11196
  return;
11007
11197
  }
11008
11198
  const { action, requestId } = parsed;
11009
- log16.info("Command received", { action, requestId });
11199
+ log17.info("Command received", { action, requestId });
11010
11200
  if (action === "tool_result" && parsed.id) {
11011
11201
  const id = parsed.id;
11012
11202
  const result = parsed.result ?? "";
@@ -11015,7 +11205,7 @@ var init_headless = __esm({
11015
11205
  this.pendingTools.delete(id);
11016
11206
  pending2.resolve(result);
11017
11207
  } else if (!this.running) {
11018
- log16.info("Late tool_result while idle, dismissing", { id });
11208
+ log17.info("Late tool_result while idle, dismissing", { id });
11019
11209
  this.emit("completed", { success: true }, requestId);
11020
11210
  } else {
11021
11211
  this.earlyResults.set(id, result);
@@ -11028,7 +11218,7 @@ var init_headless = __esm({
11028
11218
  ...typeof parsed.before === "number" ? { before: parsed.before } : {},
11029
11219
  ...typeof parsed.limit === "number" ? { limit: parsed.limit } : {}
11030
11220
  });
11031
- log16.info("History response", {
11221
+ log17.info("History response", {
11032
11222
  requestId,
11033
11223
  startIndex: page.startIndex,
11034
11224
  endIndex: page.endIndex,