@mindstudio-ai/remy 0.1.239 → 0.1.241

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
@@ -1522,7 +1522,7 @@ var init_assets = __esm({
1522
1522
  });
1523
1523
 
1524
1524
  // src/compaction/index.ts
1525
- async function compactConversation(messages, apiConfig, system, tools2, model) {
1525
+ async function compactConversation(messages, apiConfig, model) {
1526
1526
  const endIndex = findSafeInsertionPoint(messages);
1527
1527
  const summaries = [];
1528
1528
  const tasks = [];
@@ -1530,6 +1530,7 @@ async function compactConversation(messages, apiConfig, system, tools2, model) {
1530
1530
  messages,
1531
1531
  endIndex
1532
1532
  );
1533
+ let conversationFailed = false;
1533
1534
  if (conversationMessages.length > 0) {
1534
1535
  tasks.push(
1535
1536
  generateSummary(
@@ -1537,12 +1538,12 @@ async function compactConversation(messages, apiConfig, system, tools2, model) {
1537
1538
  "conversation",
1538
1539
  CONVERSATION_SUMMARY_PROMPT,
1539
1540
  conversationMessages,
1540
- system,
1541
- tools2,
1542
1541
  model
1543
1542
  ).then((text) => {
1544
1543
  if (text) {
1545
1544
  summaries.push({ name: "conversation", text });
1545
+ } else {
1546
+ conversationFailed = true;
1546
1547
  }
1547
1548
  })
1548
1549
  );
@@ -1560,18 +1561,25 @@ async function compactConversation(messages, apiConfig, system, tools2, model) {
1560
1561
  name,
1561
1562
  SUBAGENT_SUMMARY_PROMPT,
1562
1563
  subagentMessages,
1563
- system,
1564
- tools2,
1565
1564
  model
1566
1565
  ).then((text) => {
1567
1566
  if (text) {
1568
1567
  summaries.push({ name, text });
1568
+ } else {
1569
+ log2.warn("Subagent summary unusable \u2014 leaving its history intact", {
1570
+ name
1571
+ });
1569
1572
  }
1570
1573
  })
1571
1574
  );
1572
1575
  }
1573
1576
  }
1574
1577
  await Promise.all(tasks);
1578
+ if (conversationFailed) {
1579
+ throw new Error(
1580
+ "Could not summarize the conversation \u2014 the model did not return a usable summary. History left intact."
1581
+ );
1582
+ }
1575
1583
  const checkpointMessages = summaries.map((s) => ({
1576
1584
  role: "user",
1577
1585
  hidden: true,
@@ -1716,65 +1724,106 @@ function serializeForSummary(messages) {
1716
1724
  }
1717
1725
  return lines.join("\n\n");
1718
1726
  }
1719
- async function generateSummary(apiConfig, name, compactionPrompt, messagesToSummarize, mainSystem, mainTools, model) {
1727
+ async function generateSummary(apiConfig, name, compactionPrompt, messagesToSummarize, model, opts = {}) {
1720
1728
  const serialized = serializeForSummary(messagesToSummarize);
1721
1729
  if (!serialized.trim()) {
1722
1730
  return null;
1723
1731
  }
1724
- if (serialized.length > CHUNK_CHAR_LIMIT && messagesToSummarize.length > 1) {
1732
+ const splittable = messagesToSummarize.length > 1;
1733
+ const allowRetry = opts.allowRetry ?? true;
1734
+ if (splittable && (opts.forceChunk || serialized.length > CHUNK_CHAR_LIMIT)) {
1725
1735
  const mid = Math.floor(messagesToSummarize.length / 2);
1736
+ const halves = [
1737
+ messagesToSummarize.slice(0, mid),
1738
+ messagesToSummarize.slice(mid)
1739
+ ];
1726
1740
  log2.info("Chunking summary", {
1727
1741
  name,
1728
1742
  messageCount: messagesToSummarize.length,
1729
- serializedLength: serialized.length
1743
+ serializedLength: serialized.length,
1744
+ forced: !!opts.forceChunk
1730
1745
  });
1731
- const [first, second] = await Promise.all([
1732
- generateSummary(
1733
- apiConfig,
1734
- `${name} [pt1]`,
1735
- compactionPrompt,
1736
- messagesToSummarize.slice(0, mid),
1737
- mainSystem,
1738
- mainTools,
1739
- model
1740
- ),
1741
- generateSummary(
1742
- apiConfig,
1743
- `${name} [pt2]`,
1744
- compactionPrompt,
1745
- messagesToSummarize.slice(mid),
1746
- mainSystem,
1747
- mainTools,
1748
- model
1746
+ const results = await Promise.all(
1747
+ halves.map(
1748
+ (half, i) => generateSummary(
1749
+ apiConfig,
1750
+ `${name} [pt${i + 1}]`,
1751
+ compactionPrompt,
1752
+ half,
1753
+ model,
1754
+ // A split driven by size gets its children a retry of their own. A
1755
+ // split that IS the retry does not, or a model that will not
1756
+ // summarize at any size fans out call after call before giving up.
1757
+ { allowRetry: opts.forceChunk ? false : allowRetry }
1758
+ )
1749
1759
  )
1750
- ]);
1751
- const parts = [first, second].filter((p) => !!p);
1760
+ );
1761
+ const lost = results.some(
1762
+ (r, i) => r === null && serializeForSummary(halves[i]).trim()
1763
+ );
1764
+ if (lost) {
1765
+ return null;
1766
+ }
1767
+ const parts = results.filter((p) => p !== null);
1752
1768
  return parts.length > 0 ? parts.join("\n\n---\n\n") : null;
1753
1769
  }
1754
1770
  log2.info("Generating summary", {
1755
1771
  name,
1756
1772
  messageCount: messagesToSummarize.length,
1757
- cacheReuse: !!mainSystem
1773
+ serializedLength: serialized.length
1758
1774
  });
1759
- let summaryText = "";
1760
- const useMainCache = !!mainSystem;
1761
- const system = useMainCache ? mainSystem : compactionPrompt;
1762
- const tools2 = [];
1763
- const userContent = useMainCache ? `${compactionPrompt}
1775
+ const summaryText = await runSummaryCall(
1776
+ apiConfig,
1777
+ name,
1778
+ compactionPrompt,
1779
+ serialized,
1780
+ model
1781
+ );
1782
+ if (summaryText === null) {
1783
+ return null;
1784
+ }
1785
+ if (summaryText.length >= MIN_SUMMARY_CHARS) {
1786
+ log2.info("Summary generated", { name, summaryLength: summaryText.length });
1787
+ return summaryText;
1788
+ }
1789
+ log2.warn("Summary too short to be real", {
1790
+ name,
1791
+ summaryLength: summaryText.length,
1792
+ minimum: MIN_SUMMARY_CHARS,
1793
+ serializedLength: serialized.length,
1794
+ retrying: splittable && allowRetry
1795
+ });
1796
+ if (!splittable || !allowRetry) {
1797
+ return null;
1798
+ }
1799
+ return generateSummary(
1800
+ apiConfig,
1801
+ name,
1802
+ compactionPrompt,
1803
+ messagesToSummarize,
1804
+ model,
1805
+ { forceChunk: true, allowRetry: false }
1806
+ );
1807
+ }
1808
+ async function runSummaryCall(apiConfig, name, compactionPrompt, serialized, model) {
1809
+ const userContent = `Conversation to summarize:
1764
1810
 
1765
- ---
1811
+ ${serialized}
1766
1812
 
1767
- Conversation to summarize:
1813
+ ---
1768
1814
 
1769
- ${serialized}` : serialized;
1815
+ Write the summary of the conversation above, following your instructions.`;
1816
+ let summaryText = "";
1770
1817
  const iterStart = Date.now();
1771
- for await (const event of streamChat({
1818
+ for await (const event of streamChatWithRetry({
1772
1819
  ...apiConfig,
1773
1820
  model,
1774
1821
  subAgentId: "conversationSummarizer",
1775
- system,
1822
+ system: compactionPrompt,
1776
1823
  messages: [{ role: "user", content: userContent }],
1777
- tools: tools2
1824
+ // Always empty. With a toolset available the model picks `tool_use` over
1825
+ // producing a summary, leaving summaryText empty.
1826
+ tools: []
1778
1827
  })) {
1779
1828
  if (event.type === "text") {
1780
1829
  summaryText += event.text;
@@ -1801,10 +1850,9 @@ ${serialized}` : serialized;
1801
1850
  log2.warn("Empty summary generated", { name });
1802
1851
  return null;
1803
1852
  }
1804
- log2.info("Summary generated", { name, summaryLength: summaryText.length });
1805
1853
  return summaryText.trim();
1806
1854
  }
1807
- var log2, CONVERSATION_SUMMARY_PROMPT, SUBAGENT_SUMMARY_PROMPT, SUMMARIZABLE_SUBAGENTS, CHUNK_CHAR_LIMIT;
1855
+ var log2, CONVERSATION_SUMMARY_PROMPT, SUBAGENT_SUMMARY_PROMPT, SUMMARIZABLE_SUBAGENTS, CHUNK_CHAR_LIMIT, MIN_SUMMARY_CHARS;
1808
1856
  var init_compaction = __esm({
1809
1857
  "src/compaction/index.ts"() {
1810
1858
  "use strict";
@@ -1817,137 +1865,8 @@ var init_compaction = __esm({
1817
1865
  CONVERSATION_SUMMARY_PROMPT = readAsset("compaction", "conversation.md");
1818
1866
  SUBAGENT_SUMMARY_PROMPT = readAsset("compaction", "subagent.md");
1819
1867
  SUMMARIZABLE_SUBAGENTS = ["visualDesignExpert", "productVision"];
1820
- CHUNK_CHAR_LIMIT = 24e5;
1821
- }
1822
- });
1823
-
1824
- // src/prompt/static/projectContext.ts
1825
- import fs10 from "fs";
1826
- import path4 from "path";
1827
- function loadProjectManifest() {
1828
- try {
1829
- const manifest = fs10.readFileSync("mindstudio.json", "utf-8");
1830
- return `
1831
- ## Project Manifest (mindstudio.json)
1832
- \`\`\`json
1833
- ${manifest}
1834
- \`\`\``;
1835
- } catch {
1836
- return "";
1837
- }
1838
- }
1839
- function loadSpecFileMetadata() {
1840
- try {
1841
- const files = walkMdFiles("src");
1842
- if (files.length === 0) {
1843
- return "";
1844
- }
1845
- const entries = [];
1846
- for (const filePath of files) {
1847
- const { name, description, type } = parseFrontmatter(filePath);
1848
- let line = `- ${filePath}`;
1849
- if (name) {
1850
- line += ` \u2014 "${name}"`;
1851
- }
1852
- if (type) {
1853
- line += ` (${type})`;
1854
- }
1855
- if (description) {
1856
- line += ` \u2014 ${description}`;
1857
- }
1858
- entries.push(line);
1859
- }
1860
- return `
1861
- ## Spec Files
1862
- ${entries.join("\n")}`;
1863
- } catch {
1864
- return "";
1865
- }
1866
- }
1867
- function walkMdFiles(dir) {
1868
- const results = [];
1869
- try {
1870
- const entries = fs10.readdirSync(dir, { withFileTypes: true });
1871
- for (const entry of entries) {
1872
- const full = path4.join(dir, entry.name);
1873
- if (entry.isDirectory()) {
1874
- results.push(...walkMdFiles(full));
1875
- } else if (entry.name.endsWith(".md")) {
1876
- results.push(full);
1877
- }
1878
- }
1879
- } catch {
1880
- }
1881
- return results.sort();
1882
- }
1883
- function parseFrontmatter(filePath) {
1884
- try {
1885
- const content = fs10.readFileSync(filePath, "utf-8");
1886
- const match = content.match(/^---\n([\s\S]*?)\n---/);
1887
- if (!match) {
1888
- return { name: "", description: "", type: "" };
1889
- }
1890
- const fm = match[1];
1891
- const name = fm.match(/^name:\s*(.+)$/m)?.[1]?.trim() ?? "";
1892
- const description = fm.match(/^description:\s*(.+)$/m)?.[1]?.trim() ?? "";
1893
- const type = fm.match(/^type:\s*(.+)$/m)?.[1]?.trim() ?? "";
1894
- return { name, description, type };
1895
- } catch {
1896
- return { name: "", description: "", type: "" };
1897
- }
1898
- }
1899
- function loadPlanStatus(onboardingState) {
1900
- try {
1901
- const content = fs10.readFileSync(".remy-plan.md", "utf-8");
1902
- const match = content.match(/^---\n([\s\S]*?)\n---/);
1903
- const status = match?.[1]?.match(/^status:\s*(.+)$/m)?.[1]?.trim();
1904
- if (status === "pending" && onboardingState === "intake") {
1905
- return `
1906
- <pending_initial_plan>
1907
- This is your initial plan, proposed via writePlan and awaiting the user's decision. The user approves it by pressing "Start Building" \u2014 you'll get an approveInitialPlan message when they do, and it will tell you to begin. Until then, stay in intake: keep discussing, and revise with writePlan if the user asks. Don't start building on your own, even if the user says "looks good" in chat.
1908
- </pending_initial_plan>`;
1909
- }
1910
- if (status === "pending") {
1911
- return `
1912
- <pending_plan>
1913
- You have a pending implementation plan in .remy-plan.md awaiting user approval. Do NOT begin implementing the plan until the user approves it. You may continue chatting, answering questions, and revising the plan if asked. To revise, call writePlan again with updated content. When the user approves the plan (via chat or any other signal), call updatePlanStatus with status "approved" before beginning any implementation work.
1914
- </pending_plan>`;
1915
- }
1916
- if (status === "approved") {
1917
- return `
1918
- <approved_plan>
1919
- The user has approved your implementation plan in .remy-plan.md. You may reference it during implementation. Delete the file when you have finished all planned work.
1920
- </approved_plan>`;
1921
- }
1922
- return "";
1923
- } catch {
1924
- return "";
1925
- }
1926
- }
1927
- function loadProjectFileListing() {
1928
- try {
1929
- const entries = fs10.readdirSync(".", { withFileTypes: true });
1930
- const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
1931
- if (a.isDirectory() && !b.isDirectory()) {
1932
- return -1;
1933
- }
1934
- if (!a.isDirectory() && b.isDirectory()) {
1935
- return 1;
1936
- }
1937
- return a.name.localeCompare(b.name);
1938
- }).map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
1939
- return `
1940
- ## Project Files
1941
- \`\`\`
1942
- ${listing}
1943
- \`\`\``;
1944
- } catch {
1945
- return "";
1946
- }
1947
- }
1948
- var init_projectContext = __esm({
1949
- "src/prompt/static/projectContext.ts"() {
1950
- "use strict";
1868
+ CHUNK_CHAR_LIMIT = 2e5;
1869
+ MIN_SUMMARY_CHARS = 400;
1951
1870
  }
1952
1871
  });
1953
1872
 
@@ -2111,220 +2030,430 @@ var init_surfaces = __esm({
2111
2030
  }
2112
2031
  });
2113
2032
 
2114
- // src/orgContext.ts
2115
- async function initOrgContext(config) {
2116
- try {
2117
- cached = await fetchRemyContext(config);
2118
- const orgDefaultModels2 = filterModelPicks(cached?.defaultModels);
2119
- setOrgDefaultModels(orgDefaultModels2);
2120
- log3.debug("org context loaded", {
2121
- delegatedAvailable: cached?.auth?.delegatedAvailable ?? false,
2122
- requireDelegatedOnly: cached?.auth?.requireDelegatedOnly ?? false,
2123
- hasOrgName: !!cached?.org?.name,
2124
- orgDefaultModels: orgDefaultModels2
2125
- });
2126
- } catch (err) {
2127
- cached = null;
2128
- setOrgDefaultModels({});
2129
- log3.debug("org context init failed", { error: err.message });
2130
- }
2033
+ // src/automatedActions/sentinel.ts
2034
+ function sentinel(name) {
2035
+ return `@@automated::${name}@@`;
2131
2036
  }
2132
- function getOrgContext() {
2133
- return cached;
2037
+ function automatedMessage(name, body) {
2038
+ return body ? `${sentinel(name)}
2039
+ ${body}` : sentinel(name);
2134
2040
  }
2135
- function renderOrgContextBlock() {
2136
- const ctx = cached;
2137
- const auth = ctx?.auth;
2138
- if (!auth || !auth.delegatedAvailable && !auth.requireDelegatedOnly) {
2139
- return "";
2140
- }
2141
- const lines = ["<org_auth_context>"];
2142
- if (ctx?.org?.name && ctx?.org?.name !== "Personal Workspace") {
2143
- lines.push(`This app is owned by the organization "${ctx.org.name}".`);
2144
- }
2145
- if (auth.delegatedAvailable) {
2146
- lines.push(
2147
- '"Sign in with Remy" (platform-delegated sign-in) is an available auth type for this app: organization members can sign in without a verification code.'
2148
- );
2041
+ function hasSentinel(text, name) {
2042
+ return text.startsWith(sentinel(name));
2043
+ }
2044
+ function isAutomatedMessage(text) {
2045
+ return text.startsWith("@@automated::");
2046
+ }
2047
+ function parseSentinel(text) {
2048
+ const match = text.match(/^@@automated::(\w+)@@(.*)/s);
2049
+ if (!match) {
2050
+ return null;
2149
2051
  }
2150
- if (auth.requireDelegatedOnly) {
2151
- lines.push(
2152
- "This organization requires delegated sign-in: non-delegated human auth methods (email-code, sms-code) are blocked at the platform edge for its apps."
2153
- );
2052
+ return { name: match[1], remainder: match[2] };
2053
+ }
2054
+ function stripSentinelLine(text) {
2055
+ return text.replace(/^@@automated::[^@]*@@[^\n]*\n?/, "");
2056
+ }
2057
+ function buildBackgroundResultsMessage(results) {
2058
+ const xml = results.map(
2059
+ (r) => `<tool_result id="${r.toolCallId}" name="${r.name}">
2060
+ ${r.result}
2061
+ </tool_result>`
2062
+ ).join("\n\n");
2063
+ const plural = results.length > 1 ? "s" : "";
2064
+ const body = `This is an automated message containing the result${plural} of ${results.length > 1 ? "tool calls" : "a tool call"} that ${results.length > 1 ? "have" : "has"} been working in the background. This is not a direct message from the user.
2065
+ <background_results>
2066
+ ${xml}
2067
+ </background_results>`;
2068
+ return automatedMessage("background_results", body);
2069
+ }
2070
+ function mergeBackgroundResultsMessages(messages) {
2071
+ const results = [];
2072
+ const toolRe = /<tool_result id="([^"]+)" name="([^"]+)">\n([\s\S]*?)\n<\/tool_result>/g;
2073
+ for (const msg of messages) {
2074
+ for (const m of msg.matchAll(toolRe)) {
2075
+ results.push({ toolCallId: m[1], name: m[2], result: m[3] });
2076
+ }
2154
2077
  }
2155
- lines.push("</org_auth_context>");
2156
- return lines.join("\n");
2078
+ return buildBackgroundResultsMessage(results);
2157
2079
  }
2158
- var log3, cached;
2159
- var init_orgContext = __esm({
2160
- "src/orgContext.ts"() {
2080
+ var init_sentinel = __esm({
2081
+ "src/automatedActions/sentinel.ts"() {
2161
2082
  "use strict";
2162
- init_api();
2163
- init_surfaces();
2164
- init_logger();
2165
- log3 = createLogger("orgContext");
2166
- cached = null;
2167
2083
  }
2168
2084
  });
2169
2085
 
2170
- // src/prompt/index.ts
2171
- function resolveIncludes(template) {
2172
- const result = template.replace(
2173
- /\{\{([^}]+)\}\}/g,
2174
- (_, filePath) => readAsset("prompt", filePath.trim())
2175
- );
2176
- return result.replace(/\n{3,}/g, "\n\n").trim();
2086
+ // src/subagents/common/cleanMessages.ts
2087
+ function findLastSummaryCheckpoint(messages, name) {
2088
+ for (let i = messages.length - 1; i >= 0; i--) {
2089
+ const msg = messages[i];
2090
+ if (!Array.isArray(msg.content)) {
2091
+ continue;
2092
+ }
2093
+ for (const block of msg.content) {
2094
+ if (block.type === "summary" && block.name === name) {
2095
+ return i;
2096
+ }
2097
+ }
2098
+ }
2099
+ return -1;
2177
2100
  }
2178
- function buildSystemPrompt(onboardingState, viewContext) {
2179
- const projectContext = [
2180
- loadProjectManifest(),
2181
- loadSpecFileMetadata(),
2182
- loadProjectFileListing()
2183
- ].filter(Boolean).join("\n");
2184
- const now = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
2185
- month: "long",
2186
- day: "numeric",
2187
- year: "numeric"
2188
- });
2189
- const template = `
2190
- {{static/identity.md}}
2191
-
2192
- Current date: ${now}
2193
-
2194
- <platform_docs>
2195
- <platform>
2196
- {{compiled/platform.md}}
2197
- </platform>
2198
-
2199
- <manifest>
2200
- {{compiled/manifest.md}}
2201
- </manifest>
2202
-
2203
- <tables>
2204
- {{compiled/tables.md}}
2205
- </tables>
2206
-
2207
- <methods>
2208
- {{compiled/methods.md}}
2209
- </methods>
2210
-
2211
- <auth>
2212
- {{compiled/auth.md}}
2213
- </auth>
2214
-
2215
- <dev_and_deploy>
2216
- {{compiled/dev-and-deploy.md}}
2217
- </dev_and_deploy>
2218
-
2219
- <design>
2220
- {{compiled/design.md}}
2221
- </design>
2222
-
2223
- <building_agent_interfaces>
2224
- {{compiled/agent-interfaces.md}}
2225
- </building_agent_interfaces>
2226
-
2227
- <building_mcp_interfaces>
2228
- {{compiled/mcp-interfaces.md}}
2229
- </building_mcp_interfaces>
2230
-
2231
- <media_cdn>
2232
- {{compiled/media-cdn.md}}
2233
- </media_cdn>
2234
-
2235
- <interfaces>
2236
- {{compiled/interfaces.md}}
2237
- </interfaces>
2238
-
2239
- <scenarios>
2240
- {{compiled/scenarios.md}}
2241
- </scenarios>
2242
-
2243
- <secrets>
2244
- {{compiled/secrets.md}}
2245
- </secrets>
2246
- </platform_docs>
2247
-
2248
- <mindstudio_agent_sdk_docs>
2249
- {{compiled/sdk-actions.md}}
2250
-
2251
- {{compiled/task-agents.md}}
2252
- </mindstudio_agent_sdk_docs>
2253
-
2254
- <mindstudio_flavored_markdown_spec_docs>
2255
- {{compiled/msfm.md}}
2256
- </mindstudio_flavored_markdown_spec_docs>
2257
-
2258
- <intake_mode_instructions>
2259
- {{static/intake.md}}
2260
- </intake_mode_instructions>
2261
-
2262
- <spec_authoring_instructions>
2263
- {{static/authoring.md}}
2264
- </spec_authoring_instructions>
2265
-
2266
- <team>
2267
- {{static/team.md}}
2268
- </team>
2269
-
2270
- <code_authoring_instructions>
2271
- {{static/coding.md}}
2272
-
2273
- <typescript_lsp>
2274
- {{static/lsp.md}}
2275
- </typescript_lsp>
2276
- </code_authoring_instructions>
2277
-
2278
- <conversation_summaries>
2279
- Your conversation history may include <prior_conversation_summary> blocks in the user's messages. These are automated summaries of earlier messages that have been compacted to save context space. The user does not see this summary, they see the full conversation history in their UI. Treat the summary as ground truth for what happened before, but do not reference it directly to the user ("as mentioned in the summary..."). Just continue naturally as if you remember the prior work.
2280
-
2281
- Old tool results are periodically cleared from the conversation to save context space. This is automatic and expected \u2014 you don't need to note down or preserve information from tool results. If you need to reference something from an earlier tool call, just re-read the file or re-run the query.
2282
- </conversation_summaries>
2283
-
2284
- <project_onboarding>
2285
- New projects progress through three onboarding states. The user might skip this entirely and jump straight into working on the existing scaffold (which defaults to onboardingFinished), but ideally new projects move through each phase:
2286
-
2287
- - **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.
2288
- - **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).
2289
- - **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.
2290
- - **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.
2291
- </project_onboarding>
2292
-
2293
- {{static/instructions.md}}
2294
-
2295
- <!-- cache_breakpoint -->
2296
-
2297
- <current_project_onboarding_state>
2298
- ${onboardingState ?? "onboardingFinished"}
2299
- </current_project_onboarding_state>
2101
+ function fixOrphanedToolCalls(messages) {
2102
+ const toolResultIds = /* @__PURE__ */ new Set();
2103
+ for (const msg of messages) {
2104
+ if (msg.role === "user" && msg.toolCallId) {
2105
+ toolResultIds.add(msg.toolCallId);
2106
+ }
2107
+ }
2108
+ const result = [];
2109
+ for (const msg of messages) {
2110
+ result.push(msg);
2111
+ if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
2112
+ continue;
2113
+ }
2114
+ const toolBlocks = msg.content.filter(
2115
+ (b) => b.type === "tool"
2116
+ );
2117
+ const orphans = toolBlocks.filter((tc) => !toolResultIds.has(tc.id));
2118
+ for (const tc of orphans) {
2119
+ result.push({
2120
+ role: "user",
2121
+ content: "Error: tool result lost (session recovered)",
2122
+ toolCallId: tc.id,
2123
+ isToolError: true
2124
+ });
2125
+ toolResultIds.add(tc.id);
2126
+ }
2127
+ }
2128
+ return result;
2129
+ }
2130
+ function cleanMessagesForApi(messages) {
2131
+ const checkpointIdx = findLastSummaryCheckpoint(messages, "conversation");
2132
+ let startIdx = 0;
2133
+ const prefix = [];
2134
+ if (checkpointIdx !== -1) {
2135
+ const checkpointMsg = messages[checkpointIdx];
2136
+ const blocks = checkpointMsg.content;
2137
+ const summaryBlock = blocks.find(
2138
+ (b) => b.type === "summary" && b.name === "conversation"
2139
+ );
2140
+ if (summaryBlock && summaryBlock.type === "summary") {
2141
+ prefix.push({
2142
+ role: "user",
2143
+ content: `<conversation_summary>
2144
+ ${summaryBlock.text}
2145
+ </conversation_summary>`,
2146
+ hidden: true
2147
+ });
2148
+ }
2149
+ startIdx = checkpointIdx + 1;
2150
+ }
2151
+ const messagesToProcess = fixOrphanedToolCalls(messages.slice(startIdx));
2152
+ const toolUseIds = /* @__PURE__ */ new Set();
2153
+ for (const msg of messagesToProcess) {
2154
+ if (msg.role === "assistant" && Array.isArray(msg.content)) {
2155
+ for (const block of msg.content) {
2156
+ if (block.type === "tool") {
2157
+ toolUseIds.add(block.id);
2158
+ }
2159
+ }
2160
+ }
2161
+ }
2162
+ const cleaned = messagesToProcess.filter((msg) => {
2163
+ if (Array.isArray(msg.content)) {
2164
+ const blocks = msg.content;
2165
+ if (blocks.some((b) => b.type === "summary")) {
2166
+ return false;
2167
+ }
2168
+ }
2169
+ if (msg.role === "assistant" && Array.isArray(msg.content) && msg.content.length === 0) {
2170
+ return false;
2171
+ }
2172
+ if (msg.role === "user" && msg.toolCallId && !toolUseIds.has(msg.toolCallId)) {
2173
+ return false;
2174
+ }
2175
+ return true;
2176
+ }).map((msg) => {
2177
+ if (msg.role === "user" && typeof msg.content === "string") {
2178
+ const { attachmentHeader, ...rest } = msg;
2179
+ let content = isAutomatedMessage(msg.content) ? stripSentinelLine(msg.content) : msg.content;
2180
+ if (attachmentHeader) {
2181
+ content = content ? `${attachmentHeader}
2300
2182
 
2301
- <project_context>
2302
- ${projectContext}
2303
- </project_context>
2183
+ ${content}` : attachmentHeader;
2184
+ }
2185
+ return { ...rest, content };
2186
+ }
2187
+ if (!Array.isArray(msg.content)) {
2188
+ return msg;
2189
+ }
2190
+ const blocks = msg.content;
2191
+ const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
2192
+ const toolCalls = blocks.filter((b) => b.type === "tool").map((b) => ({ id: b.id, name: b.name, input: b.input }));
2193
+ const cleaned2 = {
2194
+ role: msg.role,
2195
+ content: text
2196
+ };
2197
+ if (toolCalls.length > 0) {
2198
+ cleaned2.toolCalls = toolCalls;
2199
+ }
2200
+ if (msg.providerMetadata) {
2201
+ cleaned2.providerMetadata = msg.providerMetadata;
2202
+ }
2203
+ if (msg.hidden) {
2204
+ cleaned2.hidden = true;
2205
+ }
2206
+ return cleaned2;
2207
+ });
2208
+ return [...prefix, ...cleaned];
2209
+ }
2210
+ var init_cleanMessages = __esm({
2211
+ "src/subagents/common/cleanMessages.ts"() {
2212
+ "use strict";
2213
+ init_sentinel();
2214
+ }
2215
+ });
2304
2216
 
2305
- ${renderOrgContextBlock()}
2217
+ // src/toolResultCap.ts
2218
+ function capToolResult(result) {
2219
+ const total = Buffer.byteLength(result, "utf-8");
2220
+ if (total <= MAX_TOOL_RESULT_BYTES) {
2221
+ return result;
2222
+ }
2223
+ const head = Buffer.from(result, "utf-8").subarray(0, MAX_TOOL_RESULT_BYTES).toString("utf-8");
2224
+ return head + `
2306
2225
 
2307
- <view_context>
2308
- The user is currently in ${viewContext?.mode ?? "code"} mode.
2309
- ${viewContext?.activeFile ? `Active file: ${viewContext.activeFile}` : ""}
2310
- </view_context>
2226
+ (tool result truncated at ${(MAX_TOOL_RESULT_BYTES / 1024).toFixed(0)}KB of ${(total / 1024).toFixed(0)}KB \u2014 too large to keep in context. Narrow the call (select fewer fields, paginate, or query a subset) instead of fetching everything.)`;
2227
+ }
2228
+ var MAX_TOOL_RESULT_BYTES;
2229
+ var init_toolResultCap = __esm({
2230
+ "src/toolResultCap.ts"() {
2231
+ "use strict";
2232
+ MAX_TOOL_RESULT_BYTES = 256 * 1024;
2233
+ }
2234
+ });
2311
2235
 
2312
- ${loadPlanStatus(onboardingState)}
2313
- `;
2314
- return resolveIncludes(template);
2236
+ // src/session.ts
2237
+ import fs10 from "fs";
2238
+ import path4 from "path";
2239
+ function loadSession(state) {
2240
+ pruneArchives();
2241
+ try {
2242
+ const raw = fs10.readFileSync(SESSION_FILE, "utf-8");
2243
+ const data = JSON.parse(raw);
2244
+ if (data.models && typeof data.models === "object") {
2245
+ state.models = data.models;
2246
+ }
2247
+ if (Array.isArray(data.messages) && data.messages.length > 0) {
2248
+ state.messages = sanitizeMessages(data.messages);
2249
+ log3.info("Session loaded", {
2250
+ messageCount: state.messages.length,
2251
+ ...state.models && { models: state.models }
2252
+ });
2253
+ return true;
2254
+ }
2255
+ } catch {
2256
+ }
2257
+ return false;
2258
+ }
2259
+ function capOversizedResults(msg) {
2260
+ if (msg.role === "assistant" && Array.isArray(msg.content)) {
2261
+ for (const block of msg.content) {
2262
+ if (block.type === "tool" && typeof block.result === "string") {
2263
+ block.result = capToolResult(block.result);
2264
+ }
2265
+ }
2266
+ } else if (msg.role === "user" && msg.toolCallId && typeof msg.content === "string") {
2267
+ msg.content = capToolResult(msg.content);
2268
+ }
2269
+ }
2270
+ function sanitizeMessages(messages) {
2271
+ const result = [];
2272
+ for (let i = 0; i < messages.length; i++) {
2273
+ const msg = messages[i];
2274
+ capOversizedResults(msg);
2275
+ result.push(msg);
2276
+ if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
2277
+ continue;
2278
+ }
2279
+ const toolBlocks = msg.content.filter(
2280
+ (b) => b.type === "tool"
2281
+ );
2282
+ if (toolBlocks.length === 0) {
2283
+ continue;
2284
+ }
2285
+ const resultIds = /* @__PURE__ */ new Set();
2286
+ for (let j = i + 1; j < messages.length; j++) {
2287
+ const next = messages[j];
2288
+ if (next.role === "user" && next.toolCallId) {
2289
+ resultIds.add(next.toolCallId);
2290
+ } else {
2291
+ break;
2292
+ }
2293
+ }
2294
+ for (const tc of toolBlocks) {
2295
+ if (!resultIds.has(tc.id)) {
2296
+ result.push({
2297
+ role: "user",
2298
+ content: "Error: tool result lost (session recovered)",
2299
+ toolCallId: tc.id,
2300
+ isToolError: true
2301
+ });
2302
+ }
2303
+ }
2304
+ }
2305
+ return result;
2306
+ }
2307
+ function buildPayload(state) {
2308
+ const payload = { messages: state.messages };
2309
+ if (state.models && Object.keys(state.models).length > 0) {
2310
+ payload.models = state.models;
2311
+ }
2312
+ return payload;
2313
+ }
2314
+ function archiveMessages(messages, label, models) {
2315
+ fs10.mkdirSync(ARCHIVE_DIR, { recursive: true });
2316
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2317
+ let dest = path4.join(ARCHIVE_DIR, `${label}-${ts}.json`);
2318
+ let n = 1;
2319
+ while (fs10.existsSync(dest)) {
2320
+ dest = path4.join(ARCHIVE_DIR, `${label}-${ts}-${n++}.json`);
2321
+ }
2322
+ const payload = { messages };
2323
+ if (models && Object.keys(models).length > 0) {
2324
+ payload.models = models;
2325
+ }
2326
+ fs10.writeFileSync(dest, JSON.stringify(payload), "utf-8");
2327
+ log3.info("Session archived", { label, dest, messageCount: messages.length });
2328
+ pruneArchives();
2329
+ return dest;
2330
+ }
2331
+ function pruneArchives() {
2332
+ try {
2333
+ const entries = fs10.readdirSync(ARCHIVE_DIR).filter((name) => /^(cleared|rotated)-.*\.json$/.test(name));
2334
+ if (entries.length <= 1) {
2335
+ return;
2336
+ }
2337
+ const sortKey = (name) => name.replace(/^(cleared|rotated)-/, "");
2338
+ const archives = entries.map((name) => ({
2339
+ name,
2340
+ size: fs10.statSync(path4.join(ARCHIVE_DIR, name)).size
2341
+ })).sort((a, b) => sortKey(b.name).localeCompare(sortKey(a.name)));
2342
+ let kept = 0;
2343
+ let cut = archives.length;
2344
+ for (let i = 0; i < archives.length; i++) {
2345
+ if (i === 0 || kept + archives[i].size <= ARCHIVE_RETENTION_BYTES) {
2346
+ kept += archives[i].size;
2347
+ } else {
2348
+ cut = i;
2349
+ break;
2350
+ }
2351
+ }
2352
+ let removed = 0;
2353
+ let freed = 0;
2354
+ for (let i = cut; i < archives.length; i++) {
2355
+ try {
2356
+ fs10.unlinkSync(path4.join(ARCHIVE_DIR, archives[i].name));
2357
+ freed += archives[i].size;
2358
+ removed++;
2359
+ } catch {
2360
+ }
2361
+ }
2362
+ if (removed > 0) {
2363
+ log3.info("Session archives pruned", {
2364
+ removed,
2365
+ freedBytes: freed,
2366
+ keptBytes: kept
2367
+ });
2368
+ }
2369
+ } catch {
2370
+ }
2371
+ }
2372
+ function rotate(state) {
2373
+ const messages = state.messages;
2374
+ if (messages.length === 0) {
2375
+ return false;
2376
+ }
2377
+ let tailBytes = 0;
2378
+ let scrollbackStart = 0;
2379
+ for (let i = messages.length - 1; i >= 0; i--) {
2380
+ tailBytes += Buffer.byteLength(JSON.stringify(messages[i]), "utf-8") + 1;
2381
+ if (tailBytes >= RETAIN_TAIL_BYTES) {
2382
+ scrollbackStart = i;
2383
+ break;
2384
+ }
2385
+ }
2386
+ const checkpointIdx = findLastSummaryCheckpoint(messages, "conversation");
2387
+ let cut = checkpointIdx === -1 ? scrollbackStart : Math.min(scrollbackStart, checkpointIdx);
2388
+ cut = findSafeInsertionPoint(messages, cut);
2389
+ if (cut <= 0) {
2390
+ return false;
2391
+ }
2392
+ archiveMessages(messages.slice(0, cut), "rotated", state.models);
2393
+ state.messages = messages.slice(cut);
2394
+ log3.info("Session rotated", {
2395
+ archived: cut,
2396
+ retained: state.messages.length
2397
+ });
2398
+ return true;
2315
2399
  }
2316
- var init_prompt = __esm({
2317
- "src/prompt/index.ts"() {
2400
+ function saveSession(state) {
2401
+ try {
2402
+ let serialized = JSON.stringify(buildPayload(state));
2403
+ if (Buffer.byteLength(serialized, "utf-8") > ROTATE_THRESHOLD_BYTES && rotate(state)) {
2404
+ serialized = JSON.stringify(buildPayload(state));
2405
+ }
2406
+ fs10.writeFileSync(SESSION_FILE, serialized, "utf-8");
2407
+ log3.info("Session saved", { messageCount: state.messages.length });
2408
+ } catch (err) {
2409
+ log3.warn("Session save failed", { error: err.message });
2410
+ }
2411
+ }
2412
+ function clearSession(state) {
2413
+ try {
2414
+ if (state.messages.length > 0) {
2415
+ archiveMessages(state.messages, "cleared", state.models);
2416
+ }
2417
+ } catch (err) {
2418
+ log3.warn("Session archive on clear failed", { error: err.message });
2419
+ }
2420
+ state.messages = [];
2421
+ try {
2422
+ if (fs10.existsSync(SESSION_FILE)) {
2423
+ fs10.unlinkSync(SESSION_FILE);
2424
+ }
2425
+ } catch (err) {
2426
+ log3.warn("Session clear: could not remove live file", {
2427
+ error: err.message
2428
+ });
2429
+ }
2430
+ }
2431
+ var log3, SESSION_FILE, ARCHIVE_DIR, ROTATE_THRESHOLD_BYTES, RETAIN_TAIL_BYTES, ARCHIVE_RETENTION_BYTES;
2432
+ var init_session = __esm({
2433
+ "src/session.ts"() {
2318
2434
  "use strict";
2319
- init_assets();
2320
- init_projectContext();
2321
- init_orgContext();
2435
+ init_logger();
2436
+ init_compaction();
2437
+ init_cleanMessages();
2438
+ init_toolResultCap();
2439
+ log3 = createLogger("session");
2440
+ SESSION_FILE = ".remy-session.json";
2441
+ ARCHIVE_DIR = ".logs/sessions";
2442
+ ROTATE_THRESHOLD_BYTES = 32 * 1024 * 1024;
2443
+ RETAIN_TAIL_BYTES = 16 * 1024 * 1024;
2444
+ ARCHIVE_RETENTION_BYTES = 64 * 1024 * 1024;
2322
2445
  }
2323
2446
  });
2324
2447
 
2325
2448
  // src/compaction/trigger.ts
2326
- function getPendingSummaries() {
2327
- return pendingSummaries.splice(0);
2449
+ function applyPendingSummaries(state) {
2450
+ const summaries = pendingSummaries.splice(0);
2451
+ if (summaries.length === 0) {
2452
+ return;
2453
+ }
2454
+ const idx = findSafeInsertionPoint(state.messages);
2455
+ state.messages.splice(idx, 0, ...summaries);
2456
+ saveSession(state);
2328
2457
  }
2329
2458
  function setCompactionListener(l) {
2330
2459
  listener = l;
@@ -2335,13 +2464,9 @@ function triggerCompaction(state, apiConfig, opts = {}) {
2335
2464
  }
2336
2465
  const { blocking = false, requestId, model } = opts;
2337
2466
  listener?.({ type: "started", blocking, requestId });
2338
- const system = buildSystemPrompt("onboardingFinished");
2339
- const tools2 = getToolDefinitions("onboardingFinished");
2340
2467
  inflightCompaction = compactConversation(
2341
2468
  state.messages,
2342
2469
  apiConfig,
2343
- system,
2344
- tools2,
2345
2470
  resolveModel("conversationSummarizer", state.models, model)
2346
2471
  ).then((summaries) => {
2347
2472
  pendingSummaries.push(...summaries);
@@ -2362,10 +2487,9 @@ var init_trigger = __esm({
2362
2487
  "src/compaction/trigger.ts"() {
2363
2488
  "use strict";
2364
2489
  init_compaction();
2365
- init_prompt();
2366
- init_tools8();
2367
2490
  init_logger();
2368
2491
  init_surfaces();
2492
+ init_session();
2369
2493
  log4 = createLogger("compaction:trigger");
2370
2494
  pendingSummaries = [];
2371
2495
  inflightCompaction = null;
@@ -2674,12 +2798,23 @@ ${unifiedDiff(input.path, content, updated)}`;
2674
2798
  }
2675
2799
  });
2676
2800
 
2801
+ // src/projectRoot.ts
2802
+ var PROJECT_ROOT;
2803
+ var init_projectRoot = __esm({
2804
+ "src/projectRoot.ts"() {
2805
+ "use strict";
2806
+ PROJECT_ROOT = process.cwd();
2807
+ }
2808
+ });
2809
+
2677
2810
  // src/tools/code/bash.ts
2678
2811
  import { spawn as spawn2 } from "child_process";
2812
+ import path6 from "path";
2679
2813
  var DEFAULT_TIMEOUT_MS, DEFAULT_MAX_LINES2, MAX_OUTPUT_BYTES, bashTool;
2680
2814
  var init_bash = __esm({
2681
2815
  "src/tools/code/bash.ts"() {
2682
2816
  "use strict";
2817
+ init_projectRoot();
2683
2818
  DEFAULT_TIMEOUT_MS = 12e4;
2684
2819
  DEFAULT_MAX_LINES2 = 500;
2685
2820
  MAX_OUTPUT_BYTES = 3e4;
@@ -2697,7 +2832,7 @@ var init_bash = __esm({
2697
2832
  },
2698
2833
  cwd: {
2699
2834
  type: "string",
2700
- description: "Working directory to run the command in. Defaults to the project root."
2835
+ description: "Working directory, relative to the project root. Defaults to the project root itself."
2701
2836
  },
2702
2837
  timeout: {
2703
2838
  type: "number",
@@ -2716,7 +2851,9 @@ var init_bash = __esm({
2716
2851
  const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
2717
2852
  return new Promise((resolve2) => {
2718
2853
  const child = spawn2("sh", ["-c", input.command], {
2719
- cwd: input.cwd || void 0,
2854
+ // Pinned rather than inherited. `undefined` here means "wherever the
2855
+ // process happens to be", which is the project root only by luck.
2856
+ cwd: input.cwd ? path6.resolve(PROJECT_ROOT, input.cwd) : PROJECT_ROOT,
2720
2857
  env: { ...process.env, FORCE_COLOR: "1" }
2721
2858
  });
2722
2859
  let output = "";
@@ -2976,7 +3113,7 @@ var init_glob = __esm({
2976
3113
 
2977
3114
  // src/tools/code/listDir.ts
2978
3115
  import fs14 from "fs/promises";
2979
- import path6 from "path";
3116
+ import path7 from "path";
2980
3117
  async function readAndSort(dirPath) {
2981
3118
  const entries = await fs14.readdir(dirPath, { withFileTypes: true });
2982
3119
  return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
@@ -2991,7 +3128,7 @@ async function readAndSort(dirPath) {
2991
3128
  }
2992
3129
  async function collapsePath(basePath, name) {
2993
3130
  let display = name;
2994
- let current = path6.join(basePath, name);
3131
+ let current = path7.join(basePath, name);
2995
3132
  for (; ; ) {
2996
3133
  let children;
2997
3134
  try {
@@ -3001,7 +3138,7 @@ async function collapsePath(basePath, name) {
3001
3138
  }
3002
3139
  if (children.length === 1 && children[0].isDirectory()) {
3003
3140
  display += "/" + children[0].name;
3004
- current = path6.join(current, children[0].name);
3141
+ current = path7.join(current, children[0].name);
3005
3142
  } else {
3006
3143
  break;
3007
3144
  }
@@ -3019,7 +3156,7 @@ function formatSize(bytes) {
3019
3156
  }
3020
3157
  async function formatFile(dirPath, name, indent) {
3021
3158
  try {
3022
- const stat = await fs14.stat(path6.join(dirPath, name));
3159
+ const stat = await fs14.stat(path7.join(dirPath, name));
3023
3160
  return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat.size)}`;
3024
3161
  } catch {
3025
3162
  return `${indent}${name}`;
@@ -3442,12 +3579,12 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
3442
3579
  let existingUrl;
3443
3580
  let onLog;
3444
3581
  let model;
3445
- let path13;
3582
+ let path14;
3446
3583
  let fullPage = true;
3447
3584
  if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
3448
3585
  prompt = promptOrOptions.prompt;
3449
3586
  existingUrl = promptOrOptions.imageUrl;
3450
- path13 = promptOrOptions.path;
3587
+ path14 = promptOrOptions.path;
3451
3588
  if (promptOrOptions.fullPage !== void 0) {
3452
3589
  fullPage = promptOrOptions.fullPage;
3453
3590
  }
@@ -3463,7 +3600,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
3463
3600
  } else {
3464
3601
  const ssResult = await sidecarRequest(
3465
3602
  fullPage ? "/screenshot-full-page" : "/screenshot-viewport",
3466
- path13 ? { path: path13 } : void 0,
3603
+ path14 ? { path: path14 } : void 0,
3467
3604
  { timeout: fullPage ? 12e4 : 3e4 }
3468
3605
  );
3469
3606
  url = ssResult?.url || ssResult?.screenshotUrl;
@@ -3604,25 +3741,6 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
3604
3741
  }
3605
3742
  });
3606
3743
 
3607
- // src/toolResultCap.ts
3608
- function capToolResult(result) {
3609
- const total = Buffer.byteLength(result, "utf-8");
3610
- if (total <= MAX_TOOL_RESULT_BYTES) {
3611
- return result;
3612
- }
3613
- const head = Buffer.from(result, "utf-8").subarray(0, MAX_TOOL_RESULT_BYTES).toString("utf-8");
3614
- return head + `
3615
-
3616
- (tool result truncated at ${(MAX_TOOL_RESULT_BYTES / 1024).toFixed(0)}KB of ${(total / 1024).toFixed(0)}KB \u2014 too large to keep in context. Narrow the call (select fewer fields, paginate, or query a subset) instead of fetching everything.)`;
3617
- }
3618
- var MAX_TOOL_RESULT_BYTES;
3619
- var init_toolResultCap = __esm({
3620
- "src/toolResultCap.ts"() {
3621
- "use strict";
3622
- MAX_TOOL_RESULT_BYTES = 256 * 1024;
3623
- }
3624
- });
3625
-
3626
3744
  // src/statusWatcher.ts
3627
3745
  function startStatusWatcher(config) {
3628
3746
  const { apiConfig, getContext, onStatus, interval = 5e3, signal } = config;
@@ -3679,195 +3797,11 @@ function startStatusWatcher(config) {
3679
3797
  resume() {
3680
3798
  pauseCount = Math.max(0, pauseCount - 1);
3681
3799
  }
3682
- };
3683
- }
3684
- var init_statusWatcher = __esm({
3685
- "src/statusWatcher.ts"() {
3686
- "use strict";
3687
- }
3688
- });
3689
-
3690
- // src/automatedActions/sentinel.ts
3691
- function sentinel(name) {
3692
- return `@@automated::${name}@@`;
3693
- }
3694
- function automatedMessage(name, body) {
3695
- return body ? `${sentinel(name)}
3696
- ${body}` : sentinel(name);
3697
- }
3698
- function hasSentinel(text, name) {
3699
- return text.startsWith(sentinel(name));
3700
- }
3701
- function isAutomatedMessage(text) {
3702
- return text.startsWith("@@automated::");
3703
- }
3704
- function parseSentinel(text) {
3705
- const match = text.match(/^@@automated::(\w+)@@(.*)/s);
3706
- if (!match) {
3707
- return null;
3708
- }
3709
- return { name: match[1], remainder: match[2] };
3710
- }
3711
- function stripSentinelLine(text) {
3712
- return text.replace(/^@@automated::[^@]*@@[^\n]*\n?/, "");
3713
- }
3714
- function buildBackgroundResultsMessage(results) {
3715
- const xml = results.map(
3716
- (r) => `<tool_result id="${r.toolCallId}" name="${r.name}">
3717
- ${r.result}
3718
- </tool_result>`
3719
- ).join("\n\n");
3720
- const plural = results.length > 1 ? "s" : "";
3721
- const body = `This is an automated message containing the result${plural} of ${results.length > 1 ? "tool calls" : "a tool call"} that ${results.length > 1 ? "have" : "has"} been working in the background. This is not a direct message from the user.
3722
- <background_results>
3723
- ${xml}
3724
- </background_results>`;
3725
- return automatedMessage("background_results", body);
3726
- }
3727
- function mergeBackgroundResultsMessages(messages) {
3728
- const results = [];
3729
- const toolRe = /<tool_result id="([^"]+)" name="([^"]+)">\n([\s\S]*?)\n<\/tool_result>/g;
3730
- for (const msg of messages) {
3731
- for (const m of msg.matchAll(toolRe)) {
3732
- results.push({ toolCallId: m[1], name: m[2], result: m[3] });
3733
- }
3734
- }
3735
- return buildBackgroundResultsMessage(results);
3736
- }
3737
- var init_sentinel = __esm({
3738
- "src/automatedActions/sentinel.ts"() {
3739
- "use strict";
3740
- }
3741
- });
3742
-
3743
- // src/subagents/common/cleanMessages.ts
3744
- function findLastSummaryCheckpoint(messages, name) {
3745
- for (let i = messages.length - 1; i >= 0; i--) {
3746
- const msg = messages[i];
3747
- if (!Array.isArray(msg.content)) {
3748
- continue;
3749
- }
3750
- for (const block of msg.content) {
3751
- if (block.type === "summary" && block.name === name) {
3752
- return i;
3753
- }
3754
- }
3755
- }
3756
- return -1;
3757
- }
3758
- function fixOrphanedToolCalls(messages) {
3759
- const toolResultIds = /* @__PURE__ */ new Set();
3760
- for (const msg of messages) {
3761
- if (msg.role === "user" && msg.toolCallId) {
3762
- toolResultIds.add(msg.toolCallId);
3763
- }
3764
- }
3765
- const result = [];
3766
- for (const msg of messages) {
3767
- result.push(msg);
3768
- if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
3769
- continue;
3770
- }
3771
- const toolBlocks = msg.content.filter(
3772
- (b) => b.type === "tool"
3773
- );
3774
- const orphans = toolBlocks.filter((tc) => !toolResultIds.has(tc.id));
3775
- for (const tc of orphans) {
3776
- result.push({
3777
- role: "user",
3778
- content: "Error: tool result lost (session recovered)",
3779
- toolCallId: tc.id,
3780
- isToolError: true
3781
- });
3782
- toolResultIds.add(tc.id);
3783
- }
3784
- }
3785
- return result;
3786
- }
3787
- function cleanMessagesForApi(messages) {
3788
- const checkpointIdx = findLastSummaryCheckpoint(messages, "conversation");
3789
- let startIdx = 0;
3790
- const prefix = [];
3791
- if (checkpointIdx !== -1) {
3792
- const checkpointMsg = messages[checkpointIdx];
3793
- const blocks = checkpointMsg.content;
3794
- const summaryBlock = blocks.find(
3795
- (b) => b.type === "summary" && b.name === "conversation"
3796
- );
3797
- if (summaryBlock && summaryBlock.type === "summary") {
3798
- prefix.push({
3799
- role: "user",
3800
- content: `<conversation_summary>
3801
- ${summaryBlock.text}
3802
- </conversation_summary>`,
3803
- hidden: true
3804
- });
3805
- }
3806
- startIdx = checkpointIdx + 1;
3807
- }
3808
- const messagesToProcess = fixOrphanedToolCalls(messages.slice(startIdx));
3809
- const toolUseIds = /* @__PURE__ */ new Set();
3810
- for (const msg of messagesToProcess) {
3811
- if (msg.role === "assistant" && Array.isArray(msg.content)) {
3812
- for (const block of msg.content) {
3813
- if (block.type === "tool") {
3814
- toolUseIds.add(block.id);
3815
- }
3816
- }
3817
- }
3818
- }
3819
- const cleaned = messagesToProcess.filter((msg) => {
3820
- if (Array.isArray(msg.content)) {
3821
- const blocks = msg.content;
3822
- if (blocks.some((b) => b.type === "summary")) {
3823
- return false;
3824
- }
3825
- }
3826
- if (msg.role === "assistant" && Array.isArray(msg.content) && msg.content.length === 0) {
3827
- return false;
3828
- }
3829
- if (msg.role === "user" && msg.toolCallId && !toolUseIds.has(msg.toolCallId)) {
3830
- return false;
3831
- }
3832
- return true;
3833
- }).map((msg) => {
3834
- if (msg.role === "user" && typeof msg.content === "string") {
3835
- const { attachmentHeader, ...rest } = msg;
3836
- let content = isAutomatedMessage(msg.content) ? stripSentinelLine(msg.content) : msg.content;
3837
- if (attachmentHeader) {
3838
- content = content ? `${attachmentHeader}
3839
-
3840
- ${content}` : attachmentHeader;
3841
- }
3842
- return { ...rest, content };
3843
- }
3844
- if (!Array.isArray(msg.content)) {
3845
- return msg;
3846
- }
3847
- const blocks = msg.content;
3848
- const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
3849
- const toolCalls = blocks.filter((b) => b.type === "tool").map((b) => ({ id: b.id, name: b.name, input: b.input }));
3850
- const cleaned2 = {
3851
- role: msg.role,
3852
- content: text
3853
- };
3854
- if (toolCalls.length > 0) {
3855
- cleaned2.toolCalls = toolCalls;
3856
- }
3857
- if (msg.providerMetadata) {
3858
- cleaned2.providerMetadata = msg.providerMetadata;
3859
- }
3860
- if (msg.hidden) {
3861
- cleaned2.hidden = true;
3862
- }
3863
- return cleaned2;
3864
- });
3865
- return [...prefix, ...cleaned];
3800
+ };
3866
3801
  }
3867
- var init_cleanMessages = __esm({
3868
- "src/subagents/common/cleanMessages.ts"() {
3802
+ var init_statusWatcher = __esm({
3803
+ "src/statusWatcher.ts"() {
3869
3804
  "use strict";
3870
- init_sentinel();
3871
3805
  }
3872
3806
  });
3873
3807
 
@@ -4485,7 +4419,7 @@ ${appSpec}
4485
4419
  }
4486
4420
  }
4487
4421
  var BASE_PROMPT;
4488
- var init_prompt2 = __esm({
4422
+ var init_prompt = __esm({
4489
4423
  "src/subagents/browserAutomation/prompt.ts"() {
4490
4424
  "use strict";
4491
4425
  init_assets();
@@ -4632,7 +4566,7 @@ var init_browserAutomation = __esm({
4632
4566
  "use strict";
4633
4567
  init_runner();
4634
4568
  init_tools();
4635
- init_prompt2();
4569
+ init_prompt();
4636
4570
  init_sidecar();
4637
4571
  init_browserLock();
4638
4572
  init_screenshot();
@@ -5442,15 +5376,15 @@ var init_editImages = __esm({
5442
5376
 
5443
5377
  // src/subagents/common/context.ts
5444
5378
  import fs16 from "fs";
5445
- import path7 from "path";
5446
- function walkMdFiles2(dir, skip) {
5379
+ import path8 from "path";
5380
+ function walkMdFiles(dir, skip) {
5447
5381
  const files = [];
5448
5382
  try {
5449
5383
  for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
5450
- const full = path7.join(dir, entry.name);
5384
+ const full = path8.join(dir, entry.name);
5451
5385
  if (entry.isDirectory()) {
5452
5386
  if (!skip?.has(entry.name)) {
5453
- files.push(...walkMdFiles2(full, skip));
5387
+ files.push(...walkMdFiles(full, skip));
5454
5388
  }
5455
5389
  } else if (entry.name.endsWith(".md")) {
5456
5390
  files.push(full);
@@ -5460,7 +5394,7 @@ function walkMdFiles2(dir, skip) {
5460
5394
  }
5461
5395
  return files.sort();
5462
5396
  }
5463
- function parseFrontmatter2(filePath) {
5397
+ function parseFrontmatter(filePath) {
5464
5398
  try {
5465
5399
  const content = fs16.readFileSync(filePath, "utf-8");
5466
5400
  const match = content.match(/^---\n([\s\S]*?)\n---/);
@@ -5482,12 +5416,12 @@ function parseFrontmatter2(filePath) {
5482
5416
  }
5483
5417
  }
5484
5418
  function loadSpecIndex() {
5485
- const files = walkMdFiles2("src", /* @__PURE__ */ new Set(["roadmap"]));
5419
+ const files = walkMdFiles("src", /* @__PURE__ */ new Set(["roadmap"]));
5486
5420
  if (files.length === 0) {
5487
5421
  return "";
5488
5422
  }
5489
5423
  const lines = files.map((f) => {
5490
- const fm = parseFrontmatter2(f);
5424
+ const fm = parseFrontmatter(f);
5491
5425
  let line = `- ${f}`;
5492
5426
  if (fm.name) {
5493
5427
  line += ` \u2014 "${fm.name}"`;
@@ -5525,10 +5459,10 @@ ${indexJson.standalone.map((s) => `- ${s}`).join("\n")}`
5525
5459
  }
5526
5460
  } catch {
5527
5461
  }
5528
- const files = walkMdFiles2("src/roadmap");
5462
+ const files = walkMdFiles("src/roadmap");
5529
5463
  if (files.length > 0) {
5530
5464
  const lines = files.map((f) => {
5531
- const fm = parseFrontmatter2(f);
5465
+ const fm = parseFrontmatter(f);
5532
5466
  let line = `- ${f}`;
5533
5467
  if (fm.name) {
5534
5468
  line += ` \u2014 "${fm.name}"`;
@@ -5757,6 +5691,62 @@ var init_tools4 = __esm({
5757
5691
  }
5758
5692
  });
5759
5693
 
5694
+ // src/orgContext.ts
5695
+ async function initOrgContext(config) {
5696
+ try {
5697
+ cached = await fetchRemyContext(config);
5698
+ const orgDefaultModels2 = filterModelPicks(cached?.defaultModels);
5699
+ setOrgDefaultModels(orgDefaultModels2);
5700
+ log9.debug("org context loaded", {
5701
+ delegatedAvailable: cached?.auth?.delegatedAvailable ?? false,
5702
+ requireDelegatedOnly: cached?.auth?.requireDelegatedOnly ?? false,
5703
+ hasOrgName: !!cached?.org?.name,
5704
+ orgDefaultModels: orgDefaultModels2
5705
+ });
5706
+ } catch (err) {
5707
+ cached = null;
5708
+ setOrgDefaultModels({});
5709
+ log9.debug("org context init failed", { error: err.message });
5710
+ }
5711
+ }
5712
+ function getOrgContext() {
5713
+ return cached;
5714
+ }
5715
+ function renderOrgContextBlock() {
5716
+ const ctx = cached;
5717
+ const auth = ctx?.auth;
5718
+ if (!auth || !auth.delegatedAvailable && !auth.requireDelegatedOnly) {
5719
+ return "";
5720
+ }
5721
+ const lines = ["<org_auth_context>"];
5722
+ if (ctx?.org?.name && ctx?.org?.name !== "Personal Workspace") {
5723
+ lines.push(`This app is owned by the organization "${ctx.org.name}".`);
5724
+ }
5725
+ if (auth.delegatedAvailable) {
5726
+ lines.push(
5727
+ '"Sign in with Remy" (platform-delegated sign-in) is an available auth type for this app: organization members can sign in without a verification code.'
5728
+ );
5729
+ }
5730
+ if (auth.requireDelegatedOnly) {
5731
+ lines.push(
5732
+ "This organization requires delegated sign-in: non-delegated human auth methods (email-code, sms-code) are blocked at the platform edge for its apps."
5733
+ );
5734
+ }
5735
+ lines.push("</org_auth_context>");
5736
+ return lines.join("\n");
5737
+ }
5738
+ var log9, cached;
5739
+ var init_orgContext = __esm({
5740
+ "src/orgContext.ts"() {
5741
+ "use strict";
5742
+ init_api();
5743
+ init_surfaces();
5744
+ init_logger();
5745
+ log9 = createLogger("orgContext");
5746
+ cached = null;
5747
+ }
5748
+ });
5749
+
5760
5750
  // src/subagents/designExpert/data/sampleCache.ts
5761
5751
  import fs17 from "fs";
5762
5752
  function generateIndices(poolSize, sampleSize) {
@@ -5978,7 +5968,7 @@ This project is in the "${state}" phase. The codebase is a placeholder scaffold
5978
5968
  return prompt;
5979
5969
  }
5980
5970
  var SUBAGENT, RUNTIME_PLACEHOLDERS, PROMPT_TEMPLATE;
5981
- var init_prompt3 = __esm({
5971
+ var init_prompt2 = __esm({
5982
5972
  "src/subagents/designExpert/prompt.ts"() {
5983
5973
  "use strict";
5984
5974
  init_assets();
@@ -6100,7 +6090,7 @@ var init_designExpert = __esm({
6100
6090
  init_runner();
6101
6091
  init_tools4();
6102
6092
  init_tools2();
6103
- init_prompt3();
6093
+ init_prompt2();
6104
6094
  init_history();
6105
6095
  init_surfaces();
6106
6096
  init_writeFile();
@@ -6212,9 +6202,9 @@ var init_tools5 = __esm({
6212
6202
 
6213
6203
  // src/subagents/productVision/executor.ts
6214
6204
  import fs18 from "fs";
6215
- import path8 from "path";
6205
+ import path9 from "path";
6216
6206
  function resolve(filePath) {
6217
- return path8.join(ROADMAP_DIR, filePath);
6207
+ return path9.join(ROADMAP_DIR, filePath);
6218
6208
  }
6219
6209
  async function executeVisionTool(name, input, context) {
6220
6210
  switch (name) {
@@ -6336,7 +6326,7 @@ function getProductVisionPrompt() {
6336
6326
  return parts.join("\n\n");
6337
6327
  }
6338
6328
  var BASE_PROMPT3;
6339
- var init_prompt4 = __esm({
6329
+ var init_prompt3 = __esm({
6340
6330
  "src/subagents/productVision/prompt.ts"() {
6341
6331
  "use strict";
6342
6332
  init_assets();
@@ -6356,7 +6346,7 @@ var init_productVision = __esm({
6356
6346
  init_tools5();
6357
6347
  init_tools2();
6358
6348
  init_executor();
6359
- init_prompt4();
6349
+ init_prompt3();
6360
6350
  init_history();
6361
6351
  init_surfaces();
6362
6352
  productVisionTool = {
@@ -6622,540 +6612,328 @@ ${readAsset(
6622
6612
  }
6623
6613
  const specIndex = loadSpecIndex();
6624
6614
  const parts = [
6625
- BASE_PROMPT5,
6626
- loadPlatformBrief(),
6627
- MSFM_DOCS,
6628
- "<!-- cache_breakpoint -->"
6629
- ];
6630
- if (specIndex) {
6631
- parts.push(specIndex);
6632
- }
6633
- const system = parts.join("\n\n");
6634
- const result = await runSubAgent({
6635
- system,
6636
- task: input.task,
6637
- tools: SPEC_SYNC_TOOLS,
6638
- externalTools: /* @__PURE__ */ new Set(),
6639
- executeTool: (name, toolInput) => executeTool(name, toolInput, context),
6640
- apiConfig: context.apiConfig,
6641
- model: resolveModel("specSync", context.models, context.model),
6642
- subAgentId: "specSync",
6643
- signal: context.signal,
6644
- parentToolId: context.toolCallId,
6645
- requestId: context.requestId,
6646
- onEvent: context.onEvent,
6647
- resolveExternalTool: context.resolveExternalTool,
6648
- toolRegistry: context.toolRegistry,
6649
- // Always background + serialized: never blocks Remy's turn, and two
6650
- // reconciliations queue (FIFO) instead of running at once.
6651
- background: true,
6652
- acquireLock: acquireSpecSyncLock,
6653
- onBackgroundComplete: (bgResult) => {
6654
- context.onBackgroundComplete?.(
6655
- context.toolCallId,
6656
- "specSync",
6657
- bgResult.text,
6658
- bgResult.messages
6659
- );
6660
- }
6661
- });
6662
- context.subAgentMessages?.set(context.toolCallId, result.messages);
6663
- return result.text;
6664
- }
6665
- };
6666
- }
6667
- });
6668
-
6669
- // src/tools/common/scrapeWebUrl.ts
6670
- var scrapeWebUrlTool;
6671
- var init_scrapeWebUrl2 = __esm({
6672
- "src/tools/common/scrapeWebUrl.ts"() {
6673
- "use strict";
6674
- init_runMindstudioCli();
6675
- scrapeWebUrlTool = {
6676
- clearable: false,
6677
- definition: {
6678
- name: "scrapeWebUrl",
6679
- description: "Scrape the content of a web page. Returns the HTML of the page as markdown text. Optionally capture a screenshot if you need see the visual design. Use this when you need to fetch or analyze content from a website",
6680
- inputSchema: {
6681
- type: "object",
6682
- properties: {
6683
- url: {
6684
- type: "string",
6685
- description: "The URL to fetch."
6686
- },
6687
- screenshot: {
6688
- type: "boolean",
6689
- description: "Capture a screenshot of the page in addition to the text content. Adds latency; only use when you need to see the visual design."
6690
- }
6691
- },
6692
- required: ["url"]
6693
- }
6694
- },
6695
- async execute(input, context) {
6696
- const url = input.url;
6697
- const screenshot = input.screenshot;
6698
- const pageOptions = { onlyMainContent: true };
6699
- if (screenshot) {
6700
- pageOptions.screenshot = true;
6701
- }
6702
- return runMindstudioCli(
6703
- [
6704
- "scrape-url",
6705
- "--url",
6706
- url,
6707
- "--page-options",
6708
- JSON.stringify(pageOptions)
6709
- ],
6710
- { onLog: context?.onLog }
6711
- );
6712
- }
6713
- };
6714
- }
6715
- });
6716
-
6717
- // src/tools/spec/writeBuildOverview.ts
6718
- import fs19 from "fs";
6719
- function initialDelivery() {
6720
- return `### Your deliverable
6721
- Write the complete Build Overview to \`${OVERVIEW_FILE}\`. The file does not exist yet \u2014 start from this scaffold, which carries the technical hygiene for the iframed render context (keep the viewport meta and the reset). Layout, sections, type, and styling are yours to compose.
6722
-
6723
- <overview_shell>
6724
- ${OVERVIEW_SHELL}
6725
- </overview_shell>
6726
-
6727
- Reply with a one-line summary of what you wrote \u2014 not the HTML.`;
6728
- }
6729
- function refreshDelivery() {
6730
- return `### Your deliverable
6731
- The Build Overview already exists at \`${OVERVIEW_FILE}\`. Read it, then update it to reflect <overview_copy>, preserving its established skin \u2014 change only what the copy changed.
6732
-
6733
- Reply with a one-line summary of what you changed \u2014 not the HTML.`;
6734
- }
6735
- var OVERVIEW_FILE, DESIGN_BRIEF, OVERVIEW_SHELL, buildOverviewTool;
6736
- var init_writeBuildOverview = __esm({
6737
- "src/tools/spec/writeBuildOverview.ts"() {
6738
- "use strict";
6739
- init_designExpert();
6740
- OVERVIEW_FILE = "src/overview.html";
6741
- DESIGN_BRIEF = `We are building the Build Overview for this app \u2014 the home page of its Spec tab. It is a calm, dense, one-page reference of everything the app actually contains, including the parts the user can't see. It renders flush inside the Spec tab's content panel (the IDE supplies the surrounding nav).
6742
-
6743
- Take the plain-language copy in <overview_copy> and lay it out and skin it into a single, beautiful, self-contained HTML document in the app's own brand.
6744
-
6745
- ### The single hard rule
6746
- The copy in <overview_copy> is final \u2014 it was authored and edited before it reached you. Treat it as locked content to typeset, not a draft to improve. Reproduce the words exactly: do not rewrite, rephrase, shorten, expand, reorder, or "polish" them, and do not run them through any copy tool. This is the opposite of your usual role \u2014 here you own layout, typography, and visual design only, and the words (every number, name, label, claim, and sentence) are fixed. A single changed word or wrong number breaks this document's purpose.
6747
-
6748
- ### What it is (and is not)
6749
- - A typeset reference dossier: composed, dense, a little cool \u2014 substantial at a glance, then readable. Density communicates substance; sparse and airy reads as "not much here."
6750
- - Brand-skinned: pull the palette, type system, and a single accent from the app's spec. The accent leads labels and figures; it is a mark, not a fill.
6751
- - NOT a slide deck \u2014 no theatrics, no building to a climax, no persuasion.
6752
- - NOT a docs site \u2014 no raised or shadowed cards, no hover-lift, no clickable-feeling surfaces, no sticky table of contents.
6753
- - NOT a sparse memo \u2014 a restrained single-column page under-sells the work.
6754
- - No header chrome and no footer: open straight on the app's logo and name so it sits flush with the IDE.
6755
-
6756
- ### Constraints
6757
- - A single self-contained HTML file. Fonts may load from a CDN; everything else (CSS, the logo SVG) is inline.
6758
- - Responsive: fills the embedded panel width and collapses gracefully at narrow widths.`;
6759
- OVERVIEW_SHELL = `<!DOCTYPE html>
6760
- <html lang="en">
6761
- <head>
6762
- <meta charset="utf-8">
6763
- <!-- This renders inside an iframe in the IDE. Keep it a simple, self-contained
6764
- static document \u2014 no scroll/zoom/accessibility scaffolding beyond this head.
6765
- The user-scalable=no viewport is intentional; leave it. -->
6766
- <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
6767
- <title>Build Overview</title>
6768
- <!-- SKIN: link the app's fonts here (CDN \u2014 e.g. Fontshare / Google Fonts). -->
6769
- <style>
6770
- *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
6771
- </style>
6772
- </head>
6773
- <body>
6774
- <!-- Compose the Build Overview here. Layout, sections, type, and styling are
6775
- yours \u2014 keep everything inline and self-contained, brand-skinned from the
6776
- app's spec. -->
6777
- </body>
6778
- </html>`;
6779
- buildOverviewTool = {
6780
- clearable: false,
6781
- definition: {
6782
- name: "writeBuildOverview",
6783
- description: "Generate or refresh the Build Overview \u2014 the project's home page in the Spec tab: a single-page, plain-language reference of everything the app actually contains, including the parts the user can't see (data stores, backend operations, access and roles, background jobs, seeded scenarios, the design system). You author the full copy: read the manifest and spec and state, plainly and exactly, what genuinely exists \u2014 real names and accurate counts \u2014 in calm, declarative, present-tense outcome language, with no persuasion or hype. Describe only what exists. Pass the complete copy as `content`; the design expert lays it out and skins it to the app's brand using your copy verbatim \u2014 it typesets your words, it does not rewrite them, so polish the copy before you pass it. Generate it at the end of a build and refresh it after meaningful work.",
6784
- inputSchema: {
6785
- type: "object",
6786
- properties: {
6787
- content: {
6788
- type: "string",
6789
- description: "The full Build Overview copy you authored: everything the app contains, in plain present-tense outcome language, with real names and exact counts. The design expert lays this out and skins it to the brand verbatim \u2014 it styles your words, it does not rewrite them \u2014 so this should be the final copy."
6790
- }
6791
- },
6792
- required: ["content"]
6793
- }
6794
- },
6795
- async execute(input, context) {
6796
- if (!context) {
6797
- return "Error: writeBuildOverview requires execution context for design expert delegation";
6798
- }
6799
- const content = (input.content ?? "").trim();
6800
- if (!content) {
6801
- return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
6615
+ BASE_PROMPT5,
6616
+ loadPlatformBrief(),
6617
+ MSFM_DOCS,
6618
+ "<!-- cache_breakpoint -->"
6619
+ ];
6620
+ if (specIndex) {
6621
+ parts.push(specIndex);
6802
6622
  }
6803
- const exists = fs19.existsSync(OVERVIEW_FILE);
6804
- const task = `<overview_copy>${content}</overview_copy>
6805
-
6806
- ${DESIGN_BRIEF}
6807
-
6808
- ${exists ? refreshDelivery() : initialDelivery()}`;
6809
- try {
6810
- if (exists) {
6811
- input.background = true;
6812
- const result2 = await runDesignExpertRender(
6813
- { task, background: true, reportingName: "writeBuildOverview" },
6814
- context
6623
+ const system = parts.join("\n\n");
6624
+ const result = await runSubAgent({
6625
+ system,
6626
+ task: input.task,
6627
+ tools: SPEC_SYNC_TOOLS,
6628
+ externalTools: /* @__PURE__ */ new Set(),
6629
+ executeTool: (name, toolInput) => executeTool(name, toolInput, context),
6630
+ apiConfig: context.apiConfig,
6631
+ model: resolveModel("specSync", context.models, context.model),
6632
+ subAgentId: "specSync",
6633
+ signal: context.signal,
6634
+ parentToolId: context.toolCallId,
6635
+ requestId: context.requestId,
6636
+ onEvent: context.onEvent,
6637
+ resolveExternalTool: context.resolveExternalTool,
6638
+ toolRegistry: context.toolRegistry,
6639
+ // Always background + serialized: never blocks Remy's turn, and two
6640
+ // reconciliations queue (FIFO) instead of running at once.
6641
+ background: true,
6642
+ acquireLock: acquireSpecSyncLock,
6643
+ onBackgroundComplete: (bgResult) => {
6644
+ context.onBackgroundComplete?.(
6645
+ context.toolCallId,
6646
+ "specSync",
6647
+ bgResult.text,
6648
+ bgResult.messages
6815
6649
  );
6816
- context.subAgentMessages?.set(context.toolCallId, result2.messages);
6817
- return result2.text;
6818
- }
6819
- const result = await runDesignExpertRender({ task }, context);
6820
- context.subAgentMessages?.set(context.toolCallId, result.messages);
6821
- if (!fs19.existsSync(OVERVIEW_FILE)) {
6822
- return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
6823
- ${result.text}`;
6824
6650
  }
6825
- return `Build overview written to ${OVERVIEW_FILE}. ${result.text}`;
6826
- } catch (err) {
6827
- return `Error generating build overview: ${err.message}`;
6828
- }
6651
+ });
6652
+ context.subAgentMessages?.set(context.toolCallId, result.messages);
6653
+ return result.text;
6829
6654
  }
6830
6655
  };
6831
6656
  }
6832
6657
  });
6833
6658
 
6834
- // src/tools/index.ts
6835
- function deriveContext(parent, toolCallId) {
6836
- return { ...parent, toolCallId };
6837
- }
6838
- function getToolDefinitions(_onboardingState) {
6839
- return ALL_TOOLS.map((t) => t.definition);
6840
- }
6841
- function getToolByName(name) {
6842
- return ALL_TOOLS.find((t) => t.definition.name === name);
6843
- }
6844
- function executeTool(name, input, context) {
6845
- const tool = getToolByName(name);
6846
- if (!tool) {
6847
- return Promise.resolve(`Error: Unknown tool "${name}"`);
6848
- }
6849
- return tool.execute(input, context);
6850
- }
6851
- var ALL_TOOLS, CLEARABLE_TOOLS, SUBAGENT_TOOL_NAMES;
6852
- var init_tools8 = __esm({
6853
- "src/tools/index.ts"() {
6659
+ // src/tools/common/scrapeWebUrl.ts
6660
+ var scrapeWebUrlTool;
6661
+ var init_scrapeWebUrl2 = __esm({
6662
+ "src/tools/common/scrapeWebUrl.ts"() {
6854
6663
  "use strict";
6855
- init_readSpec();
6856
- init_writeSpec();
6857
- init_editSpec();
6858
- init_listSpecFiles();
6859
- init_presentPublishPlan();
6860
- init_writePlan();
6861
- init_updatePlanStatus();
6862
- init_setProjectOnboardingState();
6863
- init_promptUser();
6864
- init_confirmDestructiveAction();
6865
- init_sdkConsultant();
6866
- init_searchGoogle();
6867
- init_setProjectMetadata();
6868
- init_compactConversation();
6869
- init_readFile();
6870
- init_writeFile();
6871
- init_editFile();
6872
- init_bash();
6873
- init_grep();
6874
- init_glob();
6875
- init_listDir();
6876
- init_editsFinished();
6877
- init_lspDiagnostics();
6878
- init_restartProcess();
6879
- init_runScenario();
6880
- init_runMethod();
6881
- init_queryDatabase();
6882
- init_screenshot2();
6883
- init_browserAutomation();
6884
- init_designExpert();
6885
- init_productVision();
6886
- init_codeSanityCheck();
6887
- init_copyEditor();
6888
- init_specSync();
6889
- init_scrapeWebUrl2();
6890
- init_writeBuildOverview();
6891
- ALL_TOOLS = [
6892
- // Common
6893
- setProjectOnboardingStateTool,
6894
- promptUserTool,
6895
- confirmDestructiveActionTool,
6896
- askMindStudioSdkTool,
6897
- scrapeWebUrlTool,
6898
- searchGoogleTool,
6899
- setProjectMetadataTool,
6900
- designExpertTool,
6901
- productVisionTool,
6902
- codeSanityCheckTool,
6903
- copyEditorTool,
6904
- specSyncTool,
6905
- // no-ops until onboardingFinished; kept here so the tool list stays cache-stable
6906
- buildOverviewTool,
6907
- compactConversationTool,
6908
- // Post-onboarding
6909
- presentPublishPlanTool,
6910
- writePlanTool,
6911
- updatePlanStatusTool,
6912
- // Spec
6913
- readSpecTool,
6914
- writeSpecTool,
6915
- editSpecTool,
6916
- listSpecFilesTool,
6917
- // Code
6918
- readFileTool,
6919
- writeFileTool,
6920
- editFileTool,
6921
- bashTool,
6922
- grepTool,
6923
- globTool,
6924
- listDirTool,
6925
- editsFinishedTool,
6926
- runScenarioTool,
6927
- runMethodTool,
6928
- queryDatabaseTool,
6929
- screenshotTool,
6930
- browserAutomationTool,
6931
- // LSP
6932
- lspDiagnosticsTool,
6933
- restartProcessTool
6934
- ];
6935
- CLEARABLE_TOOLS = new Set(
6936
- ALL_TOOLS.filter((t) => t.clearable).map((t) => t.definition.name)
6937
- );
6938
- SUBAGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
6939
- "visualDesignExpert",
6940
- "productVision",
6941
- "codeSanityCheck",
6942
- "copyEditor",
6943
- "specSync",
6944
- "runAutomatedBrowserTest",
6945
- "askMindStudioSdk"
6946
- ]);
6947
- }
6948
- });
6949
-
6950
- // src/session.ts
6951
- import fs20 from "fs";
6952
- import path9 from "path";
6953
- function loadSession(state) {
6954
- pruneArchives();
6955
- try {
6956
- const raw = fs20.readFileSync(SESSION_FILE, "utf-8");
6957
- const data = JSON.parse(raw);
6958
- if (data.models && typeof data.models === "object") {
6959
- state.models = data.models;
6960
- }
6961
- if (Array.isArray(data.messages) && data.messages.length > 0) {
6962
- state.messages = sanitizeMessages(data.messages);
6963
- log9.info("Session loaded", {
6964
- messageCount: state.messages.length,
6965
- ...state.models && { models: state.models }
6966
- });
6967
- return true;
6968
- }
6969
- } catch {
6970
- }
6971
- return false;
6972
- }
6973
- function capOversizedResults(msg) {
6974
- if (msg.role === "assistant" && Array.isArray(msg.content)) {
6975
- for (const block of msg.content) {
6976
- if (block.type === "tool" && typeof block.result === "string") {
6977
- block.result = capToolResult(block.result);
6978
- }
6979
- }
6980
- } else if (msg.role === "user" && msg.toolCallId && typeof msg.content === "string") {
6981
- msg.content = capToolResult(msg.content);
6982
- }
6983
- }
6984
- function sanitizeMessages(messages) {
6985
- const result = [];
6986
- for (let i = 0; i < messages.length; i++) {
6987
- const msg = messages[i];
6988
- capOversizedResults(msg);
6989
- result.push(msg);
6990
- if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
6991
- continue;
6992
- }
6993
- const toolBlocks = msg.content.filter(
6994
- (b) => b.type === "tool"
6995
- );
6996
- if (toolBlocks.length === 0) {
6997
- continue;
6998
- }
6999
- const resultIds = /* @__PURE__ */ new Set();
7000
- for (let j = i + 1; j < messages.length; j++) {
7001
- const next = messages[j];
7002
- if (next.role === "user" && next.toolCallId) {
7003
- resultIds.add(next.toolCallId);
7004
- } else {
7005
- break;
7006
- }
7007
- }
7008
- for (const tc of toolBlocks) {
7009
- if (!resultIds.has(tc.id)) {
7010
- result.push({
7011
- role: "user",
7012
- content: "Error: tool result lost (session recovered)",
7013
- toolCallId: tc.id,
7014
- isToolError: true
7015
- });
6664
+ init_runMindstudioCli();
6665
+ scrapeWebUrlTool = {
6666
+ clearable: false,
6667
+ definition: {
6668
+ name: "scrapeWebUrl",
6669
+ description: "Scrape the content of a web page. Returns the HTML of the page as markdown text. Optionally capture a screenshot if you need see the visual design. Use this when you need to fetch or analyze content from a website",
6670
+ inputSchema: {
6671
+ type: "object",
6672
+ properties: {
6673
+ url: {
6674
+ type: "string",
6675
+ description: "The URL to fetch."
6676
+ },
6677
+ screenshot: {
6678
+ type: "boolean",
6679
+ description: "Capture a screenshot of the page in addition to the text content. Adds latency; only use when you need to see the visual design."
6680
+ }
6681
+ },
6682
+ required: ["url"]
6683
+ }
6684
+ },
6685
+ async execute(input, context) {
6686
+ const url = input.url;
6687
+ const screenshot = input.screenshot;
6688
+ const pageOptions = { onlyMainContent: true };
6689
+ if (screenshot) {
6690
+ pageOptions.screenshot = true;
6691
+ }
6692
+ return runMindstudioCli(
6693
+ [
6694
+ "scrape-url",
6695
+ "--url",
6696
+ url,
6697
+ "--page-options",
6698
+ JSON.stringify(pageOptions)
6699
+ ],
6700
+ { onLog: context?.onLog }
6701
+ );
7016
6702
  }
7017
- }
7018
- }
7019
- return result;
7020
- }
7021
- function buildPayload(state) {
7022
- const payload = { messages: state.messages };
7023
- if (state.models && Object.keys(state.models).length > 0) {
7024
- payload.models = state.models;
6703
+ };
7025
6704
  }
7026
- return payload;
6705
+ });
6706
+
6707
+ // src/tools/spec/writeBuildOverview.ts
6708
+ import fs19 from "fs";
6709
+ function initialDelivery() {
6710
+ return `### Your deliverable
6711
+ Write the complete Build Overview to \`${OVERVIEW_FILE}\`. The file does not exist yet \u2014 start from this scaffold, which carries the technical hygiene for the iframed render context (keep the viewport meta and the reset). Layout, sections, type, and styling are yours to compose.
6712
+
6713
+ <overview_shell>
6714
+ ${OVERVIEW_SHELL}
6715
+ </overview_shell>
6716
+
6717
+ Reply with a one-line summary of what you wrote \u2014 not the HTML.`;
7027
6718
  }
7028
- function archiveMessages(messages, label, models) {
7029
- fs20.mkdirSync(ARCHIVE_DIR, { recursive: true });
7030
- const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7031
- let dest = path9.join(ARCHIVE_DIR, `${label}-${ts}.json`);
7032
- let n = 1;
7033
- while (fs20.existsSync(dest)) {
7034
- dest = path9.join(ARCHIVE_DIR, `${label}-${ts}-${n++}.json`);
7035
- }
7036
- const payload = { messages };
7037
- if (models && Object.keys(models).length > 0) {
7038
- payload.models = models;
7039
- }
7040
- fs20.writeFileSync(dest, JSON.stringify(payload), "utf-8");
7041
- log9.info("Session archived", { label, dest, messageCount: messages.length });
7042
- pruneArchives();
7043
- return dest;
6719
+ function refreshDelivery() {
6720
+ return `### Your deliverable
6721
+ The Build Overview already exists at \`${OVERVIEW_FILE}\`. Read it, then update it to reflect <overview_copy>, preserving its established skin \u2014 change only what the copy changed.
6722
+
6723
+ Reply with a one-line summary of what you changed \u2014 not the HTML.`;
7044
6724
  }
7045
- function pruneArchives() {
7046
- try {
7047
- const entries = fs20.readdirSync(ARCHIVE_DIR).filter((name) => /^(cleared|rotated)-.*\.json$/.test(name));
7048
- if (entries.length <= 1) {
7049
- return;
7050
- }
7051
- const sortKey = (name) => name.replace(/^(cleared|rotated)-/, "");
7052
- const archives = entries.map((name) => ({
7053
- name,
7054
- size: fs20.statSync(path9.join(ARCHIVE_DIR, name)).size
7055
- })).sort((a, b) => sortKey(b.name).localeCompare(sortKey(a.name)));
7056
- let kept = 0;
7057
- let cut = archives.length;
7058
- for (let i = 0; i < archives.length; i++) {
7059
- if (i === 0 || kept + archives[i].size <= ARCHIVE_RETENTION_BYTES) {
7060
- kept += archives[i].size;
7061
- } else {
7062
- cut = i;
7063
- break;
7064
- }
7065
- }
7066
- let removed = 0;
7067
- let freed = 0;
7068
- for (let i = cut; i < archives.length; i++) {
7069
- try {
7070
- fs20.unlinkSync(path9.join(ARCHIVE_DIR, archives[i].name));
7071
- freed += archives[i].size;
7072
- removed++;
7073
- } catch {
6725
+ var OVERVIEW_FILE, DESIGN_BRIEF, OVERVIEW_SHELL, buildOverviewTool;
6726
+ var init_writeBuildOverview = __esm({
6727
+ "src/tools/spec/writeBuildOverview.ts"() {
6728
+ "use strict";
6729
+ init_designExpert();
6730
+ OVERVIEW_FILE = "src/overview.html";
6731
+ DESIGN_BRIEF = `We are building the Build Overview for this app \u2014 the home page of its Spec tab. It is a calm, dense, one-page reference of everything the app actually contains, including the parts the user can't see. It renders flush inside the Spec tab's content panel (the IDE supplies the surrounding nav).
6732
+
6733
+ Take the plain-language copy in <overview_copy> and lay it out and skin it into a single, beautiful, self-contained HTML document in the app's own brand.
6734
+
6735
+ ### The single hard rule
6736
+ The copy in <overview_copy> is final \u2014 it was authored and edited before it reached you. Treat it as locked content to typeset, not a draft to improve. Reproduce the words exactly: do not rewrite, rephrase, shorten, expand, reorder, or "polish" them, and do not run them through any copy tool. This is the opposite of your usual role \u2014 here you own layout, typography, and visual design only, and the words (every number, name, label, claim, and sentence) are fixed. A single changed word or wrong number breaks this document's purpose.
6737
+
6738
+ ### What it is (and is not)
6739
+ - A typeset reference dossier: composed, dense, a little cool \u2014 substantial at a glance, then readable. Density communicates substance; sparse and airy reads as "not much here."
6740
+ - Brand-skinned: pull the palette, type system, and a single accent from the app's spec. The accent leads labels and figures; it is a mark, not a fill.
6741
+ - NOT a slide deck \u2014 no theatrics, no building to a climax, no persuasion.
6742
+ - NOT a docs site \u2014 no raised or shadowed cards, no hover-lift, no clickable-feeling surfaces, no sticky table of contents.
6743
+ - NOT a sparse memo \u2014 a restrained single-column page under-sells the work.
6744
+ - No header chrome and no footer: open straight on the app's logo and name so it sits flush with the IDE.
6745
+
6746
+ ### Constraints
6747
+ - A single self-contained HTML file. Fonts may load from a CDN; everything else (CSS, the logo SVG) is inline.
6748
+ - Responsive: fills the embedded panel width and collapses gracefully at narrow widths.`;
6749
+ OVERVIEW_SHELL = `<!DOCTYPE html>
6750
+ <html lang="en">
6751
+ <head>
6752
+ <meta charset="utf-8">
6753
+ <!-- This renders inside an iframe in the IDE. Keep it a simple, self-contained
6754
+ static document \u2014 no scroll/zoom/accessibility scaffolding beyond this head.
6755
+ The user-scalable=no viewport is intentional; leave it. -->
6756
+ <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
6757
+ <title>Build Overview</title>
6758
+ <!-- SKIN: link the app's fonts here (CDN \u2014 e.g. Fontshare / Google Fonts). -->
6759
+ <style>
6760
+ *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
6761
+ </style>
6762
+ </head>
6763
+ <body>
6764
+ <!-- Compose the Build Overview here. Layout, sections, type, and styling are
6765
+ yours \u2014 keep everything inline and self-contained, brand-skinned from the
6766
+ app's spec. -->
6767
+ </body>
6768
+ </html>`;
6769
+ buildOverviewTool = {
6770
+ clearable: false,
6771
+ definition: {
6772
+ name: "writeBuildOverview",
6773
+ description: "Generate or refresh the Build Overview \u2014 the project's home page in the Spec tab: a single-page, plain-language reference of everything the app actually contains, including the parts the user can't see (data stores, backend operations, access and roles, background jobs, seeded scenarios, the design system). You author the full copy: read the manifest and spec and state, plainly and exactly, what genuinely exists \u2014 real names and accurate counts \u2014 in calm, declarative, present-tense outcome language, with no persuasion or hype. Describe only what exists. Pass the complete copy as `content`; the design expert lays it out and skins it to the app's brand using your copy verbatim \u2014 it typesets your words, it does not rewrite them, so polish the copy before you pass it. Generate it at the end of a build and refresh it after meaningful work.",
6774
+ inputSchema: {
6775
+ type: "object",
6776
+ properties: {
6777
+ content: {
6778
+ type: "string",
6779
+ description: "The full Build Overview copy you authored: everything the app contains, in plain present-tense outcome language, with real names and exact counts. The design expert lays this out and skins it to the brand verbatim \u2014 it styles your words, it does not rewrite them \u2014 so this should be the final copy."
6780
+ }
6781
+ },
6782
+ required: ["content"]
6783
+ }
6784
+ },
6785
+ async execute(input, context) {
6786
+ if (!context) {
6787
+ return "Error: writeBuildOverview requires execution context for design expert delegation";
6788
+ }
6789
+ const content = (input.content ?? "").trim();
6790
+ if (!content) {
6791
+ return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
6792
+ }
6793
+ const exists = fs19.existsSync(OVERVIEW_FILE);
6794
+ const task = `<overview_copy>${content}</overview_copy>
6795
+
6796
+ ${DESIGN_BRIEF}
6797
+
6798
+ ${exists ? refreshDelivery() : initialDelivery()}`;
6799
+ try {
6800
+ if (exists) {
6801
+ input.background = true;
6802
+ const result2 = await runDesignExpertRender(
6803
+ { task, background: true, reportingName: "writeBuildOverview" },
6804
+ context
6805
+ );
6806
+ context.subAgentMessages?.set(context.toolCallId, result2.messages);
6807
+ return result2.text;
6808
+ }
6809
+ const result = await runDesignExpertRender({ task }, context);
6810
+ context.subAgentMessages?.set(context.toolCallId, result.messages);
6811
+ if (!fs19.existsSync(OVERVIEW_FILE)) {
6812
+ return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
6813
+ ${result.text}`;
6814
+ }
6815
+ return `Build overview written to ${OVERVIEW_FILE}. ${result.text}`;
6816
+ } catch (err) {
6817
+ return `Error generating build overview: ${err.message}`;
6818
+ }
7074
6819
  }
7075
- }
7076
- if (removed > 0) {
7077
- log9.info("Session archives pruned", {
7078
- removed,
7079
- freedBytes: freed,
7080
- keptBytes: kept
7081
- });
7082
- }
7083
- } catch {
6820
+ };
7084
6821
  }
6822
+ });
6823
+
6824
+ // src/tools/index.ts
6825
+ function deriveContext(parent, toolCallId) {
6826
+ return { ...parent, toolCallId };
7085
6827
  }
7086
- function rotate(state) {
7087
- const messages = state.messages;
7088
- if (messages.length === 0) {
7089
- return false;
7090
- }
7091
- let tailBytes = 0;
7092
- let scrollbackStart = 0;
7093
- for (let i = messages.length - 1; i >= 0; i--) {
7094
- tailBytes += Buffer.byteLength(JSON.stringify(messages[i]), "utf-8") + 1;
7095
- if (tailBytes >= RETAIN_TAIL_BYTES) {
7096
- scrollbackStart = i;
7097
- break;
7098
- }
7099
- }
7100
- const checkpointIdx = findLastSummaryCheckpoint(messages, "conversation");
7101
- let cut = checkpointIdx === -1 ? scrollbackStart : Math.min(scrollbackStart, checkpointIdx);
7102
- cut = findSafeInsertionPoint(messages, cut);
7103
- if (cut <= 0) {
7104
- return false;
7105
- }
7106
- archiveMessages(messages.slice(0, cut), "rotated", state.models);
7107
- state.messages = messages.slice(cut);
7108
- log9.info("Session rotated", {
7109
- archived: cut,
7110
- retained: state.messages.length
7111
- });
7112
- return true;
6828
+ function getToolDefinitions(_onboardingState) {
6829
+ return ALL_TOOLS.map((t) => t.definition);
7113
6830
  }
7114
- function saveSession(state) {
7115
- try {
7116
- let serialized = JSON.stringify(buildPayload(state));
7117
- if (Buffer.byteLength(serialized, "utf-8") > ROTATE_THRESHOLD_BYTES && rotate(state)) {
7118
- serialized = JSON.stringify(buildPayload(state));
7119
- }
7120
- fs20.writeFileSync(SESSION_FILE, serialized, "utf-8");
7121
- log9.info("Session saved", { messageCount: state.messages.length });
7122
- } catch (err) {
7123
- log9.warn("Session save failed", { error: err.message });
7124
- }
6831
+ function getToolByName(name) {
6832
+ return ALL_TOOLS.find((t) => t.definition.name === name);
7125
6833
  }
7126
- function clearSession(state) {
7127
- try {
7128
- if (state.messages.length > 0) {
7129
- archiveMessages(state.messages, "cleared", state.models);
7130
- }
7131
- } catch (err) {
7132
- log9.warn("Session archive on clear failed", { error: err.message });
7133
- }
7134
- state.messages = [];
7135
- try {
7136
- if (fs20.existsSync(SESSION_FILE)) {
7137
- fs20.unlinkSync(SESSION_FILE);
7138
- }
7139
- } catch (err) {
7140
- log9.warn("Session clear: could not remove live file", {
7141
- error: err.message
7142
- });
6834
+ function executeTool(name, input, context) {
6835
+ const tool = getToolByName(name);
6836
+ if (!tool) {
6837
+ return Promise.resolve(`Error: Unknown tool "${name}"`);
7143
6838
  }
7144
- }
7145
- var log9, SESSION_FILE, ARCHIVE_DIR, ROTATE_THRESHOLD_BYTES, RETAIN_TAIL_BYTES, ARCHIVE_RETENTION_BYTES;
7146
- var init_session = __esm({
7147
- "src/session.ts"() {
6839
+ return tool.execute(input, context);
6840
+ }
6841
+ var ALL_TOOLS, CLEARABLE_TOOLS, SUBAGENT_TOOL_NAMES;
6842
+ var init_tools8 = __esm({
6843
+ "src/tools/index.ts"() {
7148
6844
  "use strict";
7149
- init_logger();
7150
- init_compaction();
7151
- init_cleanMessages();
7152
- init_toolResultCap();
7153
- log9 = createLogger("session");
7154
- SESSION_FILE = ".remy-session.json";
7155
- ARCHIVE_DIR = ".logs/sessions";
7156
- ROTATE_THRESHOLD_BYTES = 32 * 1024 * 1024;
7157
- RETAIN_TAIL_BYTES = 16 * 1024 * 1024;
7158
- ARCHIVE_RETENTION_BYTES = 64 * 1024 * 1024;
6845
+ init_readSpec();
6846
+ init_writeSpec();
6847
+ init_editSpec();
6848
+ init_listSpecFiles();
6849
+ init_presentPublishPlan();
6850
+ init_writePlan();
6851
+ init_updatePlanStatus();
6852
+ init_setProjectOnboardingState();
6853
+ init_promptUser();
6854
+ init_confirmDestructiveAction();
6855
+ init_sdkConsultant();
6856
+ init_searchGoogle();
6857
+ init_setProjectMetadata();
6858
+ init_compactConversation();
6859
+ init_readFile();
6860
+ init_writeFile();
6861
+ init_editFile();
6862
+ init_bash();
6863
+ init_grep();
6864
+ init_glob();
6865
+ init_listDir();
6866
+ init_editsFinished();
6867
+ init_lspDiagnostics();
6868
+ init_restartProcess();
6869
+ init_runScenario();
6870
+ init_runMethod();
6871
+ init_queryDatabase();
6872
+ init_screenshot2();
6873
+ init_browserAutomation();
6874
+ init_designExpert();
6875
+ init_productVision();
6876
+ init_codeSanityCheck();
6877
+ init_copyEditor();
6878
+ init_specSync();
6879
+ init_scrapeWebUrl2();
6880
+ init_writeBuildOverview();
6881
+ ALL_TOOLS = [
6882
+ // Common
6883
+ setProjectOnboardingStateTool,
6884
+ promptUserTool,
6885
+ confirmDestructiveActionTool,
6886
+ askMindStudioSdkTool,
6887
+ scrapeWebUrlTool,
6888
+ searchGoogleTool,
6889
+ setProjectMetadataTool,
6890
+ designExpertTool,
6891
+ productVisionTool,
6892
+ codeSanityCheckTool,
6893
+ copyEditorTool,
6894
+ specSyncTool,
6895
+ // no-ops until onboardingFinished; kept here so the tool list stays cache-stable
6896
+ buildOverviewTool,
6897
+ compactConversationTool,
6898
+ // Post-onboarding
6899
+ presentPublishPlanTool,
6900
+ writePlanTool,
6901
+ updatePlanStatusTool,
6902
+ // Spec
6903
+ readSpecTool,
6904
+ writeSpecTool,
6905
+ editSpecTool,
6906
+ listSpecFilesTool,
6907
+ // Code
6908
+ readFileTool,
6909
+ writeFileTool,
6910
+ editFileTool,
6911
+ bashTool,
6912
+ grepTool,
6913
+ globTool,
6914
+ listDirTool,
6915
+ editsFinishedTool,
6916
+ runScenarioTool,
6917
+ runMethodTool,
6918
+ queryDatabaseTool,
6919
+ screenshotTool,
6920
+ browserAutomationTool,
6921
+ // LSP
6922
+ lspDiagnosticsTool,
6923
+ restartProcessTool
6924
+ ];
6925
+ CLEARABLE_TOOLS = new Set(
6926
+ ALL_TOOLS.filter((t) => t.clearable).map((t) => t.definition.name)
6927
+ );
6928
+ SUBAGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
6929
+ "visualDesignExpert",
6930
+ "productVision",
6931
+ "codeSanityCheck",
6932
+ "copyEditor",
6933
+ "specSync",
6934
+ "runAutomatedBrowserTest",
6935
+ "askMindStudioSdk"
6936
+ ]);
7159
6937
  }
7160
6938
  });
7161
6939
 
@@ -7369,7 +7147,7 @@ var init_errors = __esm({
7369
7147
  });
7370
7148
 
7371
7149
  // src/brandExtraction/index.ts
7372
- import fs21 from "fs";
7150
+ import fs20 from "fs";
7373
7151
  import path10 from "path";
7374
7152
  import { createHash } from "crypto";
7375
7153
  async function runExtraction(apiConfig, model) {
@@ -7393,12 +7171,12 @@ function isBrandRelevant(filePath) {
7393
7171
  if (filePath === path10.join("src", "app.md")) {
7394
7172
  return true;
7395
7173
  }
7396
- const { type } = parseFrontmatter3(filePath);
7174
+ const { type } = parseFrontmatter2(filePath);
7397
7175
  return type.startsWith("design/color") || type.startsWith("design/typography");
7398
7176
  }
7399
7177
  function computeInputHash() {
7400
7178
  const entries = [];
7401
- for (const filePath of walkMdFiles3("src")) {
7179
+ for (const filePath of walkMdFiles2("src")) {
7402
7180
  if (isBrandRelevant(filePath)) {
7403
7181
  entries.push({ path: filePath, content: readSafe(filePath) });
7404
7182
  }
@@ -7416,19 +7194,19 @@ function sha256(input) {
7416
7194
  }
7417
7195
  function readSafe(filePath) {
7418
7196
  try {
7419
- return fs21.readFileSync(filePath, "utf-8");
7197
+ return fs20.readFileSync(filePath, "utf-8");
7420
7198
  } catch {
7421
7199
  return "";
7422
7200
  }
7423
7201
  }
7424
- function walkMdFiles3(dir) {
7202
+ function walkMdFiles2(dir) {
7425
7203
  const results = [];
7426
7204
  try {
7427
- const entries = fs21.readdirSync(dir, { withFileTypes: true });
7205
+ const entries = fs20.readdirSync(dir, { withFileTypes: true });
7428
7206
  for (const entry of entries) {
7429
7207
  const full = path10.join(dir, entry.name);
7430
7208
  if (entry.isDirectory()) {
7431
- results.push(...walkMdFiles3(full));
7209
+ results.push(...walkMdFiles2(full));
7432
7210
  } else if (entry.name.endsWith(".md")) {
7433
7211
  results.push(full);
7434
7212
  }
@@ -7437,9 +7215,9 @@ function walkMdFiles3(dir) {
7437
7215
  }
7438
7216
  return results.sort();
7439
7217
  }
7440
- function parseFrontmatter3(filePath) {
7218
+ function parseFrontmatter2(filePath) {
7441
7219
  try {
7442
- const content = fs21.readFileSync(filePath, "utf-8");
7220
+ const content = fs20.readFileSync(filePath, "utf-8");
7443
7221
  const match = content.match(/^---\n([\s\S]*?)\n---/);
7444
7222
  if (!match) {
7445
7223
  return { type: "" };
@@ -7503,7 +7281,7 @@ async function extractBrand(apiConfig, model) {
7503
7281
  return validateBrand(parsed);
7504
7282
  }
7505
7283
  function buildCorpus() {
7506
- const all = walkMdFiles3("src");
7284
+ const all = walkMdFiles2("src");
7507
7285
  const ordered = [
7508
7286
  ...all.filter(isBrandRelevant),
7509
7287
  ...all.filter((f) => !isBrandRelevant(f))
@@ -7636,14 +7414,14 @@ function pickFont(raw) {
7636
7414
  }
7637
7415
  function persistBrand(brand, inputHash) {
7638
7416
  const tmp = `${BRAND_FILE}.tmp`;
7639
- fs21.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
7640
- fs21.renameSync(tmp, BRAND_FILE);
7417
+ fs20.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
7418
+ fs20.renameSync(tmp, BRAND_FILE);
7641
7419
  const cache = { inputHash, generatedAt: Date.now() };
7642
- fs21.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7420
+ fs20.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7643
7421
  }
7644
7422
  function readCache() {
7645
7423
  try {
7646
- const raw = fs21.readFileSync(CACHE_FILE, "utf-8");
7424
+ const raw = fs20.readFileSync(CACHE_FILE, "utf-8");
7647
7425
  const parsed = JSON.parse(raw);
7648
7426
  if (parsed && typeof parsed.inputHash === "string" && typeof parsed.generatedAt === "number") {
7649
7427
  return parsed;
@@ -8264,70 +8042,364 @@ async function runTurn(params) {
8264
8042
  }
8265
8043
  }
8266
8044
  }
8267
- const lastNonExcluded = toolCalls.filter(
8268
- (tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)
8269
- );
8270
- lastCompletedTools = lastNonExcluded.map((tc) => tc.name).join(", ");
8271
- lastCompletedInput = JSON.stringify(lastNonExcluded.at(-1)?.input ?? {});
8272
- lastCompletedResult = results.at(-1)?.result ?? "";
8273
- for (const r of results) {
8274
- state.messages.push({
8275
- role: "user",
8276
- content: r.result,
8277
- toolCallId: r.id,
8278
- isToolError: r.isError
8279
- });
8045
+ const lastNonExcluded = toolCalls.filter(
8046
+ (tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)
8047
+ );
8048
+ lastCompletedTools = lastNonExcluded.map((tc) => tc.name).join(", ");
8049
+ lastCompletedInput = JSON.stringify(lastNonExcluded.at(-1)?.input ?? {});
8050
+ lastCompletedResult = results.at(-1)?.result ?? "";
8051
+ for (const r of results) {
8052
+ state.messages.push({
8053
+ role: "user",
8054
+ content: r.result,
8055
+ toolCallId: r.id,
8056
+ isToolError: r.isError
8057
+ });
8058
+ }
8059
+ if (signal?.aborted) {
8060
+ onEvent({ type: "turn_cancelled" });
8061
+ saveSession(state);
8062
+ return;
8063
+ }
8064
+ }
8065
+ }
8066
+ var log12, BRAND_TRIGGERING_TOOLS, EXTERNAL_TOOLS, USER_BLOCKING_EXTERNAL_TOOLS;
8067
+ var init_agent = __esm({
8068
+ "src/agent.ts"() {
8069
+ "use strict";
8070
+ init_api();
8071
+ init_tools8();
8072
+ init_session();
8073
+ init_logger();
8074
+ init_usageLedger();
8075
+ init_parsePartialJson();
8076
+ init_statusWatcher();
8077
+ init_errors();
8078
+ init_cleanMessages();
8079
+ init_tools8();
8080
+ init_sentinel();
8081
+ init_trigger2();
8082
+ init_surfaces();
8083
+ init_toolRegistry();
8084
+ init_toolResultCap();
8085
+ log12 = createLogger("agent");
8086
+ BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set(["writeSpec", "editSpec"]);
8087
+ EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
8088
+ "promptUser",
8089
+ "setProjectOnboardingState",
8090
+ "presentPublishPlan",
8091
+ "confirmDestructiveAction",
8092
+ "runScenario",
8093
+ "runMethod",
8094
+ "queryDatabase",
8095
+ "browserCommand",
8096
+ "setProjectMetadata"
8097
+ ]);
8098
+ USER_BLOCKING_EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
8099
+ "promptUser",
8100
+ "presentPublishPlan",
8101
+ "confirmDestructiveAction"
8102
+ ]);
8103
+ }
8104
+ });
8105
+
8106
+ // src/prompt/static/projectContext.ts
8107
+ import fs21 from "fs";
8108
+ import path11 from "path";
8109
+ function loadProjectRoot() {
8110
+ return `
8111
+ ## Project Root
8112
+ \`${PROJECT_ROOT}\`
8113
+
8114
+ File paths are relative to this directory. Every tool operates here and bash commands run it it.`;
8115
+ }
8116
+ function loadProjectManifest() {
8117
+ try {
8118
+ const manifest = fs21.readFileSync("mindstudio.json", "utf-8");
8119
+ return `
8120
+ ## Project Manifest (mindstudio.json)
8121
+ \`\`\`json
8122
+ ${manifest}
8123
+ \`\`\``;
8124
+ } catch {
8125
+ return "";
8126
+ }
8127
+ }
8128
+ function loadSpecFileMetadata() {
8129
+ try {
8130
+ const files = walkMdFiles3("src");
8131
+ if (files.length === 0) {
8132
+ return "";
8133
+ }
8134
+ const entries = [];
8135
+ for (const filePath of files) {
8136
+ const { name, description, type } = parseFrontmatter3(filePath);
8137
+ let line = `- ${filePath}`;
8138
+ if (name) {
8139
+ line += ` \u2014 "${name}"`;
8140
+ }
8141
+ if (type) {
8142
+ line += ` (${type})`;
8143
+ }
8144
+ if (description) {
8145
+ line += ` \u2014 ${description}`;
8146
+ }
8147
+ entries.push(line);
8148
+ }
8149
+ return `
8150
+ ## Spec Files
8151
+ ${entries.join("\n")}`;
8152
+ } catch {
8153
+ return "";
8154
+ }
8155
+ }
8156
+ function walkMdFiles3(dir) {
8157
+ const results = [];
8158
+ try {
8159
+ const entries = fs21.readdirSync(dir, { withFileTypes: true });
8160
+ for (const entry of entries) {
8161
+ const full = path11.join(dir, entry.name);
8162
+ if (entry.isDirectory()) {
8163
+ results.push(...walkMdFiles3(full));
8164
+ } else if (entry.name.endsWith(".md")) {
8165
+ results.push(full);
8166
+ }
8167
+ }
8168
+ } catch {
8169
+ }
8170
+ return results.sort();
8171
+ }
8172
+ function parseFrontmatter3(filePath) {
8173
+ try {
8174
+ const content = fs21.readFileSync(filePath, "utf-8");
8175
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
8176
+ if (!match) {
8177
+ return { name: "", description: "", type: "" };
8178
+ }
8179
+ const fm = match[1];
8180
+ const name = fm.match(/^name:\s*(.+)$/m)?.[1]?.trim() ?? "";
8181
+ const description = fm.match(/^description:\s*(.+)$/m)?.[1]?.trim() ?? "";
8182
+ const type = fm.match(/^type:\s*(.+)$/m)?.[1]?.trim() ?? "";
8183
+ return { name, description, type };
8184
+ } catch {
8185
+ return { name: "", description: "", type: "" };
8186
+ }
8187
+ }
8188
+ function loadPlanStatus(onboardingState) {
8189
+ try {
8190
+ const content = fs21.readFileSync(".remy-plan.md", "utf-8");
8191
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
8192
+ const status = match?.[1]?.match(/^status:\s*(.+)$/m)?.[1]?.trim();
8193
+ if (status === "pending" && onboardingState === "intake") {
8194
+ return `
8195
+ <pending_initial_plan>
8196
+ This is your initial plan, proposed via writePlan and awaiting the user's decision. The user approves it by pressing "Start Building" \u2014 you'll get an approveInitialPlan message when they do, and it will tell you to begin. Until then, stay in intake: keep discussing, and revise with writePlan if the user asks. Don't start building on your own, even if the user says "looks good" in chat.
8197
+ </pending_initial_plan>`;
8198
+ }
8199
+ if (status === "pending") {
8200
+ return `
8201
+ <pending_plan>
8202
+ You have a pending implementation plan in .remy-plan.md awaiting user approval. Do NOT begin implementing the plan until the user approves it. You may continue chatting, answering questions, and revising the plan if asked. To revise, call writePlan again with updated content. When the user approves the plan (via chat or any other signal), call updatePlanStatus with status "approved" before beginning any implementation work.
8203
+ </pending_plan>`;
8280
8204
  }
8281
- if (signal?.aborted) {
8282
- onEvent({ type: "turn_cancelled" });
8283
- saveSession(state);
8284
- return;
8205
+ if (status === "approved") {
8206
+ return `
8207
+ <approved_plan>
8208
+ The user has approved your implementation plan in .remy-plan.md. You may reference it during implementation. Delete the file when you have finished all planned work.
8209
+ </approved_plan>`;
8285
8210
  }
8211
+ return "";
8212
+ } catch {
8213
+ return "";
8286
8214
  }
8287
8215
  }
8288
- var log12, BRAND_TRIGGERING_TOOLS, EXTERNAL_TOOLS, USER_BLOCKING_EXTERNAL_TOOLS;
8289
- var init_agent = __esm({
8290
- "src/agent.ts"() {
8216
+ function loadProjectFileListing() {
8217
+ try {
8218
+ const entries = fs21.readdirSync(".", { withFileTypes: true });
8219
+ const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
8220
+ if (a.isDirectory() && !b.isDirectory()) {
8221
+ return -1;
8222
+ }
8223
+ if (!a.isDirectory() && b.isDirectory()) {
8224
+ return 1;
8225
+ }
8226
+ return a.name.localeCompare(b.name);
8227
+ }).map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
8228
+ return `
8229
+ ## Project Files
8230
+ \`\`\`
8231
+ ${listing}
8232
+ \`\`\``;
8233
+ } catch {
8234
+ return "";
8235
+ }
8236
+ }
8237
+ var init_projectContext = __esm({
8238
+ "src/prompt/static/projectContext.ts"() {
8291
8239
  "use strict";
8292
- init_api();
8293
- init_tools8();
8294
- init_session();
8295
- init_logger();
8296
- init_usageLedger();
8297
- init_parsePartialJson();
8298
- init_statusWatcher();
8299
- init_errors();
8300
- init_cleanMessages();
8301
- init_tools8();
8302
- init_sentinel();
8303
- init_trigger2();
8304
- init_surfaces();
8305
- init_toolRegistry();
8306
- init_toolResultCap();
8307
- log12 = createLogger("agent");
8308
- BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set(["writeSpec", "editSpec"]);
8309
- EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
8310
- "promptUser",
8311
- "setProjectOnboardingState",
8312
- "presentPublishPlan",
8313
- "confirmDestructiveAction",
8314
- "runScenario",
8315
- "runMethod",
8316
- "queryDatabase",
8317
- "browserCommand",
8318
- "setProjectMetadata"
8319
- ]);
8320
- USER_BLOCKING_EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
8321
- "promptUser",
8322
- "presentPublishPlan",
8323
- "confirmDestructiveAction"
8324
- ]);
8240
+ init_projectRoot();
8241
+ }
8242
+ });
8243
+
8244
+ // src/prompt/index.ts
8245
+ function resolveIncludes(template) {
8246
+ const result = template.replace(
8247
+ /\{\{([^}]+)\}\}/g,
8248
+ (_, filePath) => readAsset("prompt", filePath.trim())
8249
+ );
8250
+ return result.replace(/\n{3,}/g, "\n\n").trim();
8251
+ }
8252
+ function buildSystemPrompt(onboardingState, viewContext) {
8253
+ const projectContext = [
8254
+ loadProjectRoot(),
8255
+ loadProjectManifest(),
8256
+ loadSpecFileMetadata(),
8257
+ loadProjectFileListing()
8258
+ ].filter(Boolean).join("\n");
8259
+ const now = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
8260
+ month: "long",
8261
+ day: "numeric",
8262
+ year: "numeric"
8263
+ });
8264
+ const template = `
8265
+ {{static/identity.md}}
8266
+
8267
+ Current date: ${now}
8268
+
8269
+ <platform_docs>
8270
+ <platform>
8271
+ {{compiled/platform.md}}
8272
+ </platform>
8273
+
8274
+ <manifest>
8275
+ {{compiled/manifest.md}}
8276
+ </manifest>
8277
+
8278
+ <tables>
8279
+ {{compiled/tables.md}}
8280
+ </tables>
8281
+
8282
+ <methods>
8283
+ {{compiled/methods.md}}
8284
+ </methods>
8285
+
8286
+ <auth>
8287
+ {{compiled/auth.md}}
8288
+ </auth>
8289
+
8290
+ <dev_and_deploy>
8291
+ {{compiled/dev-and-deploy.md}}
8292
+ </dev_and_deploy>
8293
+
8294
+ <design>
8295
+ {{compiled/design.md}}
8296
+ </design>
8297
+
8298
+ <building_agent_interfaces>
8299
+ {{compiled/agent-interfaces.md}}
8300
+ </building_agent_interfaces>
8301
+
8302
+ <building_mcp_interfaces>
8303
+ {{compiled/mcp-interfaces.md}}
8304
+ </building_mcp_interfaces>
8305
+
8306
+ <media_cdn>
8307
+ {{compiled/media-cdn.md}}
8308
+ </media_cdn>
8309
+
8310
+ <interfaces>
8311
+ {{compiled/interfaces.md}}
8312
+ </interfaces>
8313
+
8314
+ <scenarios>
8315
+ {{compiled/scenarios.md}}
8316
+ </scenarios>
8317
+
8318
+ <secrets>
8319
+ {{compiled/secrets.md}}
8320
+ </secrets>
8321
+ </platform_docs>
8322
+
8323
+ <mindstudio_agent_sdk_docs>
8324
+ {{compiled/sdk-actions.md}}
8325
+
8326
+ {{compiled/task-agents.md}}
8327
+ </mindstudio_agent_sdk_docs>
8328
+
8329
+ <mindstudio_flavored_markdown_spec_docs>
8330
+ {{compiled/msfm.md}}
8331
+ </mindstudio_flavored_markdown_spec_docs>
8332
+
8333
+ <intake_mode_instructions>
8334
+ {{static/intake.md}}
8335
+ </intake_mode_instructions>
8336
+
8337
+ <spec_authoring_instructions>
8338
+ {{static/authoring.md}}
8339
+ </spec_authoring_instructions>
8340
+
8341
+ <team>
8342
+ {{static/team.md}}
8343
+ </team>
8344
+
8345
+ <code_authoring_instructions>
8346
+ {{static/coding.md}}
8347
+
8348
+ <typescript_lsp>
8349
+ {{static/lsp.md}}
8350
+ </typescript_lsp>
8351
+ </code_authoring_instructions>
8352
+
8353
+ <conversation_summaries>
8354
+ Your conversation history may include <prior_conversation_summary> blocks in the user's messages. These are automated summaries of earlier messages that have been compacted to save context space. The user does not see this summary, they see the full conversation history in their UI. Treat the summary as ground truth for what happened before, but do not reference it directly to the user ("as mentioned in the summary..."). Just continue naturally as if you remember the prior work.
8355
+
8356
+ Old tool results are periodically cleared from the conversation to save context space. This is automatic and expected \u2014 you don't need to note down or preserve information from tool results. If you need to reference something from an earlier tool call, just re-read the file or re-run the query.
8357
+ </conversation_summaries>
8358
+
8359
+ <project_onboarding>
8360
+ New projects progress through three onboarding states. The user might skip this entirely and jump straight into working on the existing scaffold (which defaults to onboardingFinished), but ideally new projects move through each phase:
8361
+
8362
+ - **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.
8363
+ - **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).
8364
+ - **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.
8365
+ - **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.
8366
+ </project_onboarding>
8367
+
8368
+ {{static/instructions.md}}
8369
+
8370
+ <!-- cache_breakpoint -->
8371
+
8372
+ <current_project_onboarding_state>
8373
+ ${onboardingState ?? "onboardingFinished"}
8374
+ </current_project_onboarding_state>
8375
+
8376
+ <project_context>
8377
+ ${projectContext}
8378
+ </project_context>
8379
+
8380
+ ${renderOrgContextBlock()}
8381
+
8382
+ <view_context>
8383
+ The user is currently in ${viewContext?.mode ?? "code"} mode.
8384
+ ${viewContext?.activeFile ? `Active file: ${viewContext.activeFile}` : ""}
8385
+ </view_context>
8386
+
8387
+ ${loadPlanStatus(onboardingState)}
8388
+ `;
8389
+ return resolveIncludes(template);
8390
+ }
8391
+ var init_prompt4 = __esm({
8392
+ "src/prompt/index.ts"() {
8393
+ "use strict";
8394
+ init_assets();
8395
+ init_projectContext();
8396
+ init_orgContext();
8325
8397
  }
8326
8398
  });
8327
8399
 
8328
8400
  // src/config.ts
8329
8401
  import fs22 from "fs";
8330
- import path11 from "path";
8402
+ import path12 from "path";
8331
8403
  import os from "os";
8332
8404
  function loadConfigFile() {
8333
8405
  try {
@@ -8370,7 +8442,7 @@ var init_config = __esm({
8370
8442
  "use strict";
8371
8443
  init_logger();
8372
8444
  log13 = createLogger("config");
8373
- CONFIG_PATH = path11.join(
8445
+ CONFIG_PATH = path12.join(
8374
8446
  os.homedir(),
8375
8447
  ".mindstudio-local-tunnel",
8376
8448
  "config.json"
@@ -8721,9 +8793,8 @@ var init_headless = __esm({
8721
8793
  init_logger();
8722
8794
  init_config();
8723
8795
  init_orgContext();
8724
- init_prompt();
8796
+ init_prompt4();
8725
8797
  init_trigger();
8726
- init_compaction();
8727
8798
  init_trigger2();
8728
8799
  init_lsp();
8729
8800
  init_agent();
@@ -8835,7 +8906,9 @@ var init_headless = __esm({
8835
8906
  const data = event.error ? { error: event.error } : {};
8836
8907
  this.emit("compaction_complete", data, event.requestId);
8837
8908
  this.sessionStats.compactionInProgress = false;
8838
- this.sessionStats.lastContextSize = 0;
8909
+ if (!event.error) {
8910
+ this.sessionStats.lastContextSize = 0;
8911
+ }
8839
8912
  this.sessionStats.messageCount = this.state.messages.length;
8840
8913
  this.persistStats();
8841
8914
  }
@@ -8969,20 +9042,10 @@ var init_headless = __esm({
8969
9042
  requestId,
8970
9043
  model: this.opts.model
8971
9044
  });
8972
- this.applyPendingSummaries();
9045
+ applyPendingSummaries(this.state);
8973
9046
  } catch {
8974
9047
  }
8975
9048
  }
8976
- /** Drain pending compaction summaries and insert at a safe point. */
8977
- applyPendingSummaries() {
8978
- const summaries = getPendingSummaries();
8979
- if (summaries.length === 0) {
8980
- return;
8981
- }
8982
- const idx = findSafeInsertionPoint(this.state.messages);
8983
- this.state.messages.splice(idx, 0, ...summaries);
8984
- saveSession(this.state);
8985
- }
8986
9049
  onBackgroundComplete = (toolCallId, name, result, subAgentMessages) => {
8987
9050
  this.pendingBlockUpdates.push({ toolCallId, result, subAgentMessages });
8988
9051
  log15.info("Background complete", {
@@ -9311,7 +9374,7 @@ var init_headless = __esm({
9311
9374
  error: err.message
9312
9375
  });
9313
9376
  }
9314
- this.applyPendingSummaries();
9377
+ applyPendingSummaries(this.state);
9315
9378
  this.applyPendingBlockUpdates();
9316
9379
  }
9317
9380
  async handleMessage(parsed, requestId) {
@@ -9615,7 +9678,7 @@ var init_headless = __esm({
9615
9678
  model: this.opts.model
9616
9679
  });
9617
9680
  if (!this.running) {
9618
- this.applyPendingSummaries();
9681
+ applyPendingSummaries(this.state);
9619
9682
  }
9620
9683
  this.emit("completed", { success: true }, requestId);
9621
9684
  } catch (err) {
@@ -9660,7 +9723,7 @@ var init_headless = __esm({
9660
9723
  import { render } from "ink";
9661
9724
  import os2 from "os";
9662
9725
  import fs23 from "fs";
9663
- import path12 from "path";
9726
+ import path13 from "path";
9664
9727
 
9665
9728
  // src/tui/App.tsx
9666
9729
  import { useState as useState2, useCallback, useRef } from "react";
@@ -9795,7 +9858,8 @@ function MessageList({ turns }) {
9795
9858
 
9796
9859
  // src/tui/App.tsx
9797
9860
  init_agent();
9798
- init_prompt();
9861
+ init_trigger();
9862
+ init_prompt4();
9799
9863
  init_session();
9800
9864
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
9801
9865
  function App({ apiConfig, model }) {
@@ -9933,6 +9997,7 @@ Error: ${err.message}`,
9933
9997
  done: true
9934
9998
  }));
9935
9999
  }
10000
+ applyPendingSummaries(agentState);
9936
10001
  abortRef.current = null;
9937
10002
  setIsRunning(false);
9938
10003
  },
@@ -9980,7 +10045,7 @@ var startupLog = createLogger("startup");
9980
10045
  function printDebugInfo(config) {
9981
10046
  const pkg = JSON.parse(
9982
10047
  fs23.readFileSync(
9983
- path12.join(import.meta.dirname, "..", "package.json"),
10048
+ path13.join(import.meta.dirname, "..", "package.json"),
9984
10049
  "utf-8"
9985
10050
  )
9986
10051
  );