@mindstudio-ai/remy 0.1.158 → 0.1.159
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/automatedActions/buildFromInitialSpec.md +2 -2
- package/dist/brandExtraction/extract.md +51 -0
- package/dist/headless.js +318 -38
- package/dist/index.js +347 -48
- package/package.json +1 -1
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
This is the code generation phase. The spec is written. Build everything now in three phases: planning, coding, and verifying. Execute each phase in order in a single turn.
|
|
7
7
|
|
|
8
8
|
## Planning
|
|
9
|
-
|
|
9
|
+
Get a quick architecture sanity check from `codeSanityCheck` to make sure your approach holds up.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Then bring in `visualDesignExpert` before writing any frontend code. Walk it through the screens and the key interactions — the moments where the user does something or sees something land — and ask for direction on the specifics that make a real product feel alive: motion, micro-interactions, hover and focus states, empty and loading states, the components and moments that need extra texture. The spec defines the brand and the rough layout. The designer fills in the texture between them, and that texture is what separates an app that lands from one that feels generic. This pass is the highest-leverage thing you do before writing code.
|
|
12
12
|
|
|
13
13
|
Use your remy-notes.md file to make a checklist of the work that needs to be done. Don't store implementation details in it - it is soley for keeping track of tasks.
|
|
14
14
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
You extract a structured `AppBrand` JSON object from the spec files of a MindStudio app project.
|
|
2
|
+
|
|
3
|
+
Your output is read by a frontend renderer that uses the brand to style internal documents (implementation plans, sync plans, publish plans) with a "letterhead" treatment — a wordmark, accent color, paper-tone background, and branded fonts. Every field is optional. The renderer falls back to generic styling when a field is missing or invalid.
|
|
4
|
+
|
|
5
|
+
## Output format
|
|
6
|
+
|
|
7
|
+
Reply with **exactly one** fenced ```json block. No prose before or after.
|
|
8
|
+
|
|
9
|
+
The object must match this shape:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
type AppBrand = {
|
|
13
|
+
version: 1;
|
|
14
|
+
name?: string;
|
|
15
|
+
tagline?: string;
|
|
16
|
+
logoUrl?: string;
|
|
17
|
+
colors?: {
|
|
18
|
+
background?: string;
|
|
19
|
+
text?: string;
|
|
20
|
+
heading?: string;
|
|
21
|
+
accent?: string;
|
|
22
|
+
muted?: string;
|
|
23
|
+
};
|
|
24
|
+
typography?: {
|
|
25
|
+
body?: { family: string; stylesheet?: string; fileUrl?: string };
|
|
26
|
+
heading?: { family: string; stylesheet?: string; fileUrl?: string };
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Hex (`#RRGGBB`, `#RGB`) is preferred for colors; any valid CSS color string is acceptable.
|
|
32
|
+
|
|
33
|
+
## Rules
|
|
34
|
+
|
|
35
|
+
- **Omit any field you can't extract confidently.** Partial output is correct. Do not invent.
|
|
36
|
+
- **`version` is always `1`.**
|
|
37
|
+
- **`name`**: the wordmark text — the app's display name as it would appear at the top of a document. Pull from the manifest, the main spec, or wherever the app's name is stated. Do NOT guess from the file paths.
|
|
38
|
+
- **`tagline`**: a short subtitle if the spec has one. One sentence or a short phrase. Omit if there isn't one — do not invent a tagline from the description.
|
|
39
|
+
- **`logoUrl`**: the app's logo image URL if the spec has one. Omit if there isn't one.
|
|
40
|
+
- **`colors.background`** is **paper**, not brand. It tints the page behind body text. If the brand only defines saturated/vivid colors and no calm surface tone, **omit this field** so the renderer falls back to neutral. A 3-5% saturation tint of a brand color is acceptable; a fully saturated brand color is not.
|
|
41
|
+
- **`colors.text`** is body text — usually near-black or near-white depending on background. **`colors.heading`** is often the same as text. **`colors.accent`** is for links and small flair. **`colors.muted`** is for captions and secondary text — omit if the spec doesn't define it; the renderer derives it from `text` automatically.
|
|
42
|
+
- **`typography.body`**: applied to paragraphs, lists, tables. **Body type must stay readable.** If the brand font is decorative, display-only, or script (e.g., a hand-lettered logo font), **omit `body`** and only set `heading`.
|
|
43
|
+
- **`typography.heading`**: applied to h1-h6 only. Decorative fonts are fine here.
|
|
44
|
+
- **Do NOT include a `mono` field.** Code blocks stay generic mono for legibility.
|
|
45
|
+
- For `BrandFont.stylesheet` and `BrandFont.fileUrl`: only emit a URL that **appears verbatim in the spec content** you were given. Do not fabricate Google Fonts URLs from a family name. If neither URL is present in the spec, omit both — the renderer assumes the family is a system font or already loaded.
|
|
46
|
+
|
|
47
|
+
If the project has no spec files yet, or the spec contains no brand information, output:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{ "version": 1 }
|
|
51
|
+
```
|
package/dist/headless.js
CHANGED
|
@@ -2695,11 +2695,11 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
2695
2695
|
let prompt;
|
|
2696
2696
|
let existingUrl;
|
|
2697
2697
|
let onLog;
|
|
2698
|
-
let
|
|
2698
|
+
let path11;
|
|
2699
2699
|
if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
|
|
2700
2700
|
prompt = promptOrOptions.prompt;
|
|
2701
2701
|
existingUrl = promptOrOptions.imageUrl;
|
|
2702
|
-
|
|
2702
|
+
path11 = promptOrOptions.path;
|
|
2703
2703
|
onLog = promptOrOptions.onLog;
|
|
2704
2704
|
} else {
|
|
2705
2705
|
prompt = promptOrOptions;
|
|
@@ -2711,7 +2711,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
2711
2711
|
} else {
|
|
2712
2712
|
const ssResult = await sidecarRequest(
|
|
2713
2713
|
"/screenshot-full-page",
|
|
2714
|
-
|
|
2714
|
+
path11 ? { path: path11 } : void 0,
|
|
2715
2715
|
{ timeout: 12e4 }
|
|
2716
2716
|
);
|
|
2717
2717
|
url = ssResult?.url || ssResult?.screenshotUrl;
|
|
@@ -2748,15 +2748,15 @@ function acquireBrowserLock() {
|
|
|
2748
2748
|
// src/statusWatcher.ts
|
|
2749
2749
|
function startStatusWatcher(config) {
|
|
2750
2750
|
const { apiConfig, getContext, onStatus, interval = 5e3, signal } = config;
|
|
2751
|
-
let
|
|
2751
|
+
let inflight2 = false;
|
|
2752
2752
|
let stopped = false;
|
|
2753
2753
|
let pauseCount = 0;
|
|
2754
2754
|
const url = `${apiConfig.baseUrl}/_internal/v2/agent/remy/generate-status`;
|
|
2755
2755
|
async function tick() {
|
|
2756
|
-
if (stopped || signal?.aborted ||
|
|
2756
|
+
if (stopped || signal?.aborted || inflight2 || pauseCount > 0) {
|
|
2757
2757
|
return;
|
|
2758
2758
|
}
|
|
2759
|
-
|
|
2759
|
+
inflight2 = true;
|
|
2760
2760
|
try {
|
|
2761
2761
|
const context = getContext();
|
|
2762
2762
|
if (!context) {
|
|
@@ -2784,7 +2784,7 @@ function startStatusWatcher(config) {
|
|
|
2784
2784
|
onStatus(data.label);
|
|
2785
2785
|
} catch {
|
|
2786
2786
|
} finally {
|
|
2787
|
-
|
|
2787
|
+
inflight2 = false;
|
|
2788
2788
|
}
|
|
2789
2789
|
}
|
|
2790
2790
|
const timer = setInterval(tick, interval);
|
|
@@ -4497,15 +4497,15 @@ function getSampleIndices(pools, sizes) {
|
|
|
4497
4497
|
}
|
|
4498
4498
|
const loaded = load();
|
|
4499
4499
|
if (loaded) {
|
|
4500
|
-
let
|
|
4500
|
+
let dirty2 = false;
|
|
4501
4501
|
for (const key of ["uiInspiration", "designReferences", "fonts"]) {
|
|
4502
4502
|
const before = loaded[key].length;
|
|
4503
4503
|
loaded[key] = loaded[key].filter((i) => i < pools[key]);
|
|
4504
4504
|
if (loaded[key].length < before) {
|
|
4505
|
-
|
|
4505
|
+
dirty2 = true;
|
|
4506
4506
|
}
|
|
4507
4507
|
}
|
|
4508
|
-
if (
|
|
4508
|
+
if (dirty2) {
|
|
4509
4509
|
save(loaded);
|
|
4510
4510
|
}
|
|
4511
4511
|
cached = loaded;
|
|
@@ -5199,17 +5199,292 @@ function triggerCompaction(state, apiConfig, callbacks) {
|
|
|
5199
5199
|
});
|
|
5200
5200
|
}
|
|
5201
5201
|
|
|
5202
|
-
// src/
|
|
5202
|
+
// src/brandExtraction/index.ts
|
|
5203
5203
|
import fs19 from "fs";
|
|
5204
|
-
|
|
5204
|
+
import path10 from "path";
|
|
5205
|
+
import { createHash } from "crypto";
|
|
5206
|
+
var log8 = createLogger("brandExtraction");
|
|
5207
|
+
var EXTRACT_PROMPT = readAsset("brandExtraction", "extract.md");
|
|
5208
|
+
var BRAND_FILE = ".remy-brand.json";
|
|
5209
|
+
var CACHE_FILE = ".remy-brand.cache.json";
|
|
5210
|
+
async function runExtraction(apiConfig) {
|
|
5211
|
+
const inputHash = computeInputHash();
|
|
5212
|
+
const cached2 = readCache();
|
|
5213
|
+
if (cached2 && cached2.inputHash === inputHash) {
|
|
5214
|
+
log8.debug("Brand inputs unchanged \u2014 skipping extraction", { inputHash });
|
|
5215
|
+
return null;
|
|
5216
|
+
}
|
|
5217
|
+
log8.info("Extracting brand", { inputHash });
|
|
5218
|
+
const brand = await extractBrand(apiConfig);
|
|
5219
|
+
if (!brand) {
|
|
5220
|
+
log8.warn("Brand extraction failed \u2014 leaving cache untouched");
|
|
5221
|
+
return null;
|
|
5222
|
+
}
|
|
5223
|
+
persistBrand(brand, inputHash);
|
|
5224
|
+
log8.info("Brand persisted", { inputHash });
|
|
5225
|
+
return brand;
|
|
5226
|
+
}
|
|
5227
|
+
function computeInputHash() {
|
|
5228
|
+
const entries = [];
|
|
5229
|
+
for (const filePath of walkMdFiles3("src")) {
|
|
5230
|
+
if (filePath === path10.join("src", "app.md")) {
|
|
5231
|
+
entries.push({ path: filePath, content: readSafe(filePath) });
|
|
5232
|
+
continue;
|
|
5233
|
+
}
|
|
5234
|
+
const fm = parseFrontmatter3(filePath);
|
|
5235
|
+
if (fm.type.startsWith("design/color") || fm.type.startsWith("design/typography")) {
|
|
5236
|
+
entries.push({ path: filePath, content: readSafe(filePath) });
|
|
5237
|
+
}
|
|
5238
|
+
}
|
|
5239
|
+
const manifest = readSafe("mindstudio.json");
|
|
5240
|
+
if (manifest) {
|
|
5241
|
+
entries.push({ path: "mindstudio.json", content: manifest });
|
|
5242
|
+
}
|
|
5243
|
+
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
5244
|
+
const fingerprint = entries.map((e) => `${e.path}:${sha256(e.content)}`).join("\n");
|
|
5245
|
+
return sha256(fingerprint);
|
|
5246
|
+
}
|
|
5247
|
+
function sha256(input) {
|
|
5248
|
+
return createHash("sha256").update(input).digest("hex");
|
|
5249
|
+
}
|
|
5250
|
+
function readSafe(filePath) {
|
|
5251
|
+
try {
|
|
5252
|
+
return fs19.readFileSync(filePath, "utf-8");
|
|
5253
|
+
} catch {
|
|
5254
|
+
return "";
|
|
5255
|
+
}
|
|
5256
|
+
}
|
|
5257
|
+
function walkMdFiles3(dir) {
|
|
5258
|
+
const results = [];
|
|
5259
|
+
try {
|
|
5260
|
+
const entries = fs19.readdirSync(dir, { withFileTypes: true });
|
|
5261
|
+
for (const entry of entries) {
|
|
5262
|
+
const full = path10.join(dir, entry.name);
|
|
5263
|
+
if (entry.isDirectory()) {
|
|
5264
|
+
results.push(...walkMdFiles3(full));
|
|
5265
|
+
} else if (entry.name.endsWith(".md")) {
|
|
5266
|
+
results.push(full);
|
|
5267
|
+
}
|
|
5268
|
+
}
|
|
5269
|
+
} catch {
|
|
5270
|
+
}
|
|
5271
|
+
return results.sort();
|
|
5272
|
+
}
|
|
5273
|
+
function parseFrontmatter3(filePath) {
|
|
5274
|
+
try {
|
|
5275
|
+
const content = fs19.readFileSync(filePath, "utf-8");
|
|
5276
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
5277
|
+
if (!match) {
|
|
5278
|
+
return { type: "" };
|
|
5279
|
+
}
|
|
5280
|
+
const fm = match[1];
|
|
5281
|
+
const type = fm.match(/^type:\s*(.+)$/m)?.[1]?.trim() ?? "";
|
|
5282
|
+
return { type };
|
|
5283
|
+
} catch {
|
|
5284
|
+
return { type: "" };
|
|
5285
|
+
}
|
|
5286
|
+
}
|
|
5287
|
+
async function extractBrand(apiConfig) {
|
|
5288
|
+
const corpus = buildCorpus();
|
|
5289
|
+
if (!corpus.trim()) {
|
|
5290
|
+
log8.debug("No spec corpus \u2014 emitting empty brand");
|
|
5291
|
+
return { version: 1 };
|
|
5292
|
+
}
|
|
5293
|
+
let responseText = "";
|
|
5294
|
+
try {
|
|
5295
|
+
for await (const event of streamChat({
|
|
5296
|
+
...apiConfig,
|
|
5297
|
+
subAgentId: "brandExtractor",
|
|
5298
|
+
system: EXTRACT_PROMPT,
|
|
5299
|
+
messages: [{ role: "user", content: corpus }],
|
|
5300
|
+
tools: []
|
|
5301
|
+
})) {
|
|
5302
|
+
if (event.type === "text") {
|
|
5303
|
+
responseText += event.text;
|
|
5304
|
+
} else if (event.type === "error") {
|
|
5305
|
+
log8.error("Brand extraction stream error", { error: event.error });
|
|
5306
|
+
return null;
|
|
5307
|
+
}
|
|
5308
|
+
}
|
|
5309
|
+
} catch (err) {
|
|
5310
|
+
log8.error("Brand extraction threw", { error: err?.message });
|
|
5311
|
+
return null;
|
|
5312
|
+
}
|
|
5313
|
+
const parsed = parseJsonResponse(responseText);
|
|
5314
|
+
if (!parsed) {
|
|
5315
|
+
log8.warn("Brand extraction returned unparseable JSON", {
|
|
5316
|
+
preview: responseText.slice(0, 200)
|
|
5317
|
+
});
|
|
5318
|
+
return null;
|
|
5319
|
+
}
|
|
5320
|
+
return validateBrand(parsed);
|
|
5321
|
+
}
|
|
5322
|
+
function buildCorpus() {
|
|
5323
|
+
const sections = [];
|
|
5324
|
+
const manifest = readSafe("mindstudio.json");
|
|
5325
|
+
if (manifest) {
|
|
5326
|
+
sections.push(`## File: mindstudio.json
|
|
5327
|
+
|
|
5328
|
+
${manifest}`);
|
|
5329
|
+
}
|
|
5330
|
+
for (const filePath of walkMdFiles3("src")) {
|
|
5331
|
+
const content = readSafe(filePath);
|
|
5332
|
+
if (content) {
|
|
5333
|
+
sections.push(`## File: ${filePath}
|
|
5334
|
+
|
|
5335
|
+
${content}`);
|
|
5336
|
+
}
|
|
5337
|
+
}
|
|
5338
|
+
return sections.join("\n\n---\n\n");
|
|
5339
|
+
}
|
|
5340
|
+
function parseJsonResponse(text) {
|
|
5341
|
+
const trimmed = text.trim();
|
|
5342
|
+
const fenceMatch = trimmed.match(/^```(?:json)?\s*\n([\s\S]*?)\n```\s*$/);
|
|
5343
|
+
const candidate = fenceMatch ? fenceMatch[1] : trimmed;
|
|
5344
|
+
try {
|
|
5345
|
+
return JSON.parse(candidate);
|
|
5346
|
+
} catch {
|
|
5347
|
+
const braceMatch = candidate.match(/\{[\s\S]*\}/);
|
|
5348
|
+
if (braceMatch) {
|
|
5349
|
+
try {
|
|
5350
|
+
return JSON.parse(braceMatch[0]);
|
|
5351
|
+
} catch {
|
|
5352
|
+
return null;
|
|
5353
|
+
}
|
|
5354
|
+
}
|
|
5355
|
+
return null;
|
|
5356
|
+
}
|
|
5357
|
+
}
|
|
5358
|
+
function validateBrand(raw) {
|
|
5359
|
+
if (!raw || typeof raw !== "object") {
|
|
5360
|
+
return null;
|
|
5361
|
+
}
|
|
5362
|
+
const obj = raw;
|
|
5363
|
+
const out = { version: 1 };
|
|
5364
|
+
if (typeof obj.name === "string" && obj.name.trim()) {
|
|
5365
|
+
out.name = obj.name.trim();
|
|
5366
|
+
}
|
|
5367
|
+
if (typeof obj.tagline === "string" && obj.tagline.trim()) {
|
|
5368
|
+
out.tagline = obj.tagline.trim();
|
|
5369
|
+
}
|
|
5370
|
+
if (typeof obj.logoUrl === "string" && obj.logoUrl.trim()) {
|
|
5371
|
+
out.logoUrl = obj.logoUrl.trim();
|
|
5372
|
+
}
|
|
5373
|
+
const colors = pickColors(obj.colors);
|
|
5374
|
+
if (colors) {
|
|
5375
|
+
out.colors = colors;
|
|
5376
|
+
}
|
|
5377
|
+
const typography = pickTypography(obj.typography);
|
|
5378
|
+
if (typography) {
|
|
5379
|
+
out.typography = typography;
|
|
5380
|
+
}
|
|
5381
|
+
return out;
|
|
5382
|
+
}
|
|
5383
|
+
function pickColors(raw) {
|
|
5384
|
+
if (!raw || typeof raw !== "object") {
|
|
5385
|
+
return void 0;
|
|
5386
|
+
}
|
|
5387
|
+
const c = raw;
|
|
5388
|
+
const out = {};
|
|
5389
|
+
for (const key of [
|
|
5390
|
+
"background",
|
|
5391
|
+
"text",
|
|
5392
|
+
"heading",
|
|
5393
|
+
"accent",
|
|
5394
|
+
"muted"
|
|
5395
|
+
]) {
|
|
5396
|
+
const v = c[key];
|
|
5397
|
+
if (typeof v === "string" && v.trim()) {
|
|
5398
|
+
out[key] = v.trim();
|
|
5399
|
+
}
|
|
5400
|
+
}
|
|
5401
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
5402
|
+
}
|
|
5403
|
+
function pickTypography(raw) {
|
|
5404
|
+
if (!raw || typeof raw !== "object") {
|
|
5405
|
+
return void 0;
|
|
5406
|
+
}
|
|
5407
|
+
const t = raw;
|
|
5408
|
+
const out = {};
|
|
5409
|
+
const body = pickFont(t.body);
|
|
5410
|
+
if (body) {
|
|
5411
|
+
out.body = body;
|
|
5412
|
+
}
|
|
5413
|
+
const heading = pickFont(t.heading);
|
|
5414
|
+
if (heading) {
|
|
5415
|
+
out.heading = heading;
|
|
5416
|
+
}
|
|
5417
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
5418
|
+
}
|
|
5419
|
+
function pickFont(raw) {
|
|
5420
|
+
if (!raw || typeof raw !== "object") {
|
|
5421
|
+
return void 0;
|
|
5422
|
+
}
|
|
5423
|
+
const f = raw;
|
|
5424
|
+
if (typeof f.family !== "string" || !f.family.trim()) {
|
|
5425
|
+
return void 0;
|
|
5426
|
+
}
|
|
5427
|
+
const out = { family: f.family.trim() };
|
|
5428
|
+
if (typeof f.stylesheet === "string" && f.stylesheet.trim()) {
|
|
5429
|
+
out.stylesheet = f.stylesheet.trim();
|
|
5430
|
+
}
|
|
5431
|
+
if (typeof f.fileUrl === "string" && f.fileUrl.trim()) {
|
|
5432
|
+
out.fileUrl = f.fileUrl.trim();
|
|
5433
|
+
}
|
|
5434
|
+
return out;
|
|
5435
|
+
}
|
|
5436
|
+
function persistBrand(brand, inputHash) {
|
|
5437
|
+
const tmp = `${BRAND_FILE}.tmp`;
|
|
5438
|
+
fs19.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
|
|
5439
|
+
fs19.renameSync(tmp, BRAND_FILE);
|
|
5440
|
+
const cache = { inputHash, generatedAt: Date.now() };
|
|
5441
|
+
fs19.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
5442
|
+
}
|
|
5443
|
+
function readCache() {
|
|
5444
|
+
try {
|
|
5445
|
+
const raw = fs19.readFileSync(CACHE_FILE, "utf-8");
|
|
5446
|
+
const parsed = JSON.parse(raw);
|
|
5447
|
+
if (parsed && typeof parsed.inputHash === "string" && typeof parsed.generatedAt === "number") {
|
|
5448
|
+
return parsed;
|
|
5449
|
+
}
|
|
5450
|
+
return null;
|
|
5451
|
+
} catch {
|
|
5452
|
+
return null;
|
|
5453
|
+
}
|
|
5454
|
+
}
|
|
5455
|
+
|
|
5456
|
+
// src/brandExtraction/trigger.ts
|
|
5457
|
+
var log9 = createLogger("brandExtraction:trigger");
|
|
5458
|
+
var inflight = false;
|
|
5459
|
+
var dirty = false;
|
|
5460
|
+
function triggerBrandExtraction(apiConfig) {
|
|
5461
|
+
if (inflight) {
|
|
5462
|
+
dirty = true;
|
|
5463
|
+
return;
|
|
5464
|
+
}
|
|
5465
|
+
inflight = true;
|
|
5466
|
+
void runExtraction(apiConfig).catch((err) => {
|
|
5467
|
+
log9.error("Brand extraction failed", { error: err?.message });
|
|
5468
|
+
}).finally(() => {
|
|
5469
|
+
inflight = false;
|
|
5470
|
+
if (dirty) {
|
|
5471
|
+
dirty = false;
|
|
5472
|
+
triggerBrandExtraction(apiConfig);
|
|
5473
|
+
}
|
|
5474
|
+
});
|
|
5475
|
+
}
|
|
5476
|
+
|
|
5477
|
+
// src/session.ts
|
|
5478
|
+
import fs20 from "fs";
|
|
5479
|
+
var log10 = createLogger("session");
|
|
5205
5480
|
var SESSION_FILE = ".remy-session.json";
|
|
5206
5481
|
function loadSession(state) {
|
|
5207
5482
|
try {
|
|
5208
|
-
const raw =
|
|
5483
|
+
const raw = fs20.readFileSync(SESSION_FILE, "utf-8");
|
|
5209
5484
|
const data = JSON.parse(raw);
|
|
5210
5485
|
if (Array.isArray(data.messages) && data.messages.length > 0) {
|
|
5211
5486
|
state.messages = sanitizeMessages(data.messages);
|
|
5212
|
-
|
|
5487
|
+
log10.info("Session loaded", { messageCount: state.messages.length });
|
|
5213
5488
|
return true;
|
|
5214
5489
|
}
|
|
5215
5490
|
} catch {
|
|
@@ -5254,20 +5529,20 @@ function sanitizeMessages(messages) {
|
|
|
5254
5529
|
}
|
|
5255
5530
|
function saveSession(state) {
|
|
5256
5531
|
try {
|
|
5257
|
-
|
|
5532
|
+
fs20.writeFileSync(
|
|
5258
5533
|
SESSION_FILE,
|
|
5259
5534
|
JSON.stringify({ messages: state.messages }, null, 2),
|
|
5260
5535
|
"utf-8"
|
|
5261
5536
|
);
|
|
5262
|
-
|
|
5537
|
+
log10.info("Session saved", { messageCount: state.messages.length });
|
|
5263
5538
|
} catch (err) {
|
|
5264
|
-
|
|
5539
|
+
log10.warn("Session save failed", { error: err.message });
|
|
5265
5540
|
}
|
|
5266
5541
|
}
|
|
5267
5542
|
function clearSession(state) {
|
|
5268
5543
|
state.messages = [];
|
|
5269
5544
|
try {
|
|
5270
|
-
|
|
5545
|
+
fs20.unlinkSync(SESSION_FILE);
|
|
5271
5546
|
} catch {
|
|
5272
5547
|
}
|
|
5273
5548
|
}
|
|
@@ -5462,7 +5737,8 @@ function friendlyError(raw) {
|
|
|
5462
5737
|
}
|
|
5463
5738
|
|
|
5464
5739
|
// src/agent.ts
|
|
5465
|
-
var
|
|
5740
|
+
var log11 = createLogger("agent");
|
|
5741
|
+
var BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set(["writeSpec", "editSpec"]);
|
|
5466
5742
|
function getTextContent(blocks) {
|
|
5467
5743
|
return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
5468
5744
|
}
|
|
@@ -5509,7 +5785,7 @@ async function runTurn(params) {
|
|
|
5509
5785
|
} = params;
|
|
5510
5786
|
const tools2 = getToolDefinitions(onboardingState);
|
|
5511
5787
|
const excludeToolsFromClearing = tools2.filter((t) => !CLEARABLE_TOOLS.has(t.name)).map((t) => t.name);
|
|
5512
|
-
|
|
5788
|
+
log11.info("Turn started", {
|
|
5513
5789
|
requestId,
|
|
5514
5790
|
model,
|
|
5515
5791
|
toolCount: tools2.length,
|
|
@@ -5738,7 +6014,7 @@ async function runTurn(params) {
|
|
|
5738
6014
|
const tool = getToolByName(event.name);
|
|
5739
6015
|
const wasStreamed = acc?.started ?? false;
|
|
5740
6016
|
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
5741
|
-
|
|
6017
|
+
log11.info("Tool received", {
|
|
5742
6018
|
requestId,
|
|
5743
6019
|
toolCallId: event.id,
|
|
5744
6020
|
name: event.name
|
|
@@ -5831,7 +6107,7 @@ async function runTurn(params) {
|
|
|
5831
6107
|
});
|
|
5832
6108
|
return;
|
|
5833
6109
|
}
|
|
5834
|
-
|
|
6110
|
+
log11.info("Tools executing", {
|
|
5835
6111
|
requestId,
|
|
5836
6112
|
count: toolCalls.length,
|
|
5837
6113
|
tools: toolCalls.map((tc) => tc.name)
|
|
@@ -5878,7 +6154,7 @@ async function runTurn(params) {
|
|
|
5878
6154
|
let result;
|
|
5879
6155
|
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
5880
6156
|
saveSession(state);
|
|
5881
|
-
|
|
6157
|
+
log11.info("Waiting for external tool result", {
|
|
5882
6158
|
requestId,
|
|
5883
6159
|
toolCallId: tc.id,
|
|
5884
6160
|
name: tc.name
|
|
@@ -5945,7 +6221,7 @@ async function runTurn(params) {
|
|
|
5945
6221
|
if (!tc.input.background) {
|
|
5946
6222
|
toolRegistry?.unregister(tc.id);
|
|
5947
6223
|
}
|
|
5948
|
-
|
|
6224
|
+
log11.info("Tool completed", {
|
|
5949
6225
|
requestId,
|
|
5950
6226
|
toolCallId: tc.id,
|
|
5951
6227
|
name: tc.name,
|
|
@@ -5959,6 +6235,9 @@ async function runTurn(params) {
|
|
|
5959
6235
|
result: r.result,
|
|
5960
6236
|
isError: r.isError
|
|
5961
6237
|
});
|
|
6238
|
+
if (!r.isError && BRAND_TRIGGERING_TOOLS.has(tc.name)) {
|
|
6239
|
+
triggerBrandExtraction(apiConfig);
|
|
6240
|
+
}
|
|
5962
6241
|
return r;
|
|
5963
6242
|
})
|
|
5964
6243
|
);
|
|
@@ -6000,7 +6279,7 @@ async function runTurn(params) {
|
|
|
6000
6279
|
}
|
|
6001
6280
|
|
|
6002
6281
|
// src/toolRegistry.ts
|
|
6003
|
-
var
|
|
6282
|
+
var log12 = createLogger("tool-registry");
|
|
6004
6283
|
var ToolRegistry = class {
|
|
6005
6284
|
entries = /* @__PURE__ */ new Map();
|
|
6006
6285
|
onEvent;
|
|
@@ -6026,7 +6305,7 @@ var ToolRegistry = class {
|
|
|
6026
6305
|
if (!entry) {
|
|
6027
6306
|
return false;
|
|
6028
6307
|
}
|
|
6029
|
-
|
|
6308
|
+
log12.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
|
|
6030
6309
|
entry.abortController.abort(mode);
|
|
6031
6310
|
if (mode === "graceful") {
|
|
6032
6311
|
const partial = entry.getPartialResult?.() ?? "";
|
|
@@ -6059,7 +6338,7 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
|
6059
6338
|
if (!entry) {
|
|
6060
6339
|
return false;
|
|
6061
6340
|
}
|
|
6062
|
-
|
|
6341
|
+
log12.info("Tool restarted", { toolCallId: id, name: entry.name });
|
|
6063
6342
|
entry.abortController.abort("restart");
|
|
6064
6343
|
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
6065
6344
|
this.onEvent?.({
|
|
@@ -6078,7 +6357,7 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
|
6078
6357
|
import { mkdirSync, existsSync } from "fs";
|
|
6079
6358
|
import { writeFile } from "fs/promises";
|
|
6080
6359
|
import { basename, join, extname } from "path";
|
|
6081
|
-
var
|
|
6360
|
+
var log13 = createLogger("headless:attachments");
|
|
6082
6361
|
var UPLOADS_DIR = "src/.user-uploads";
|
|
6083
6362
|
function filenameFromUrl(url) {
|
|
6084
6363
|
try {
|
|
@@ -6126,7 +6405,7 @@ async function persistAttachments(attachments) {
|
|
|
6126
6405
|
}
|
|
6127
6406
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
6128
6407
|
await writeFile(localPath, buffer);
|
|
6129
|
-
|
|
6408
|
+
log13.info("Attachment saved", {
|
|
6130
6409
|
filename: name,
|
|
6131
6410
|
path: localPath,
|
|
6132
6411
|
bytes: buffer.length
|
|
@@ -6140,7 +6419,7 @@ async function persistAttachments(attachments) {
|
|
|
6140
6419
|
if (textRes.ok) {
|
|
6141
6420
|
extractedTextPath = `${localPath}.txt`;
|
|
6142
6421
|
await writeFile(extractedTextPath, await textRes.text(), "utf-8");
|
|
6143
|
-
|
|
6422
|
+
log13.info("Extracted text saved", { path: extractedTextPath });
|
|
6144
6423
|
}
|
|
6145
6424
|
} catch {
|
|
6146
6425
|
}
|
|
@@ -6327,7 +6606,7 @@ function resolveAction(text) {
|
|
|
6327
6606
|
}
|
|
6328
6607
|
|
|
6329
6608
|
// src/headless/index.ts
|
|
6330
|
-
var
|
|
6609
|
+
var log14 = createLogger("headless");
|
|
6331
6610
|
var EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
|
|
6332
6611
|
var USER_FACING_TOOLS = /* @__PURE__ */ new Set([
|
|
6333
6612
|
"promptUser",
|
|
@@ -6399,6 +6678,7 @@ var HeadlessSession = class {
|
|
|
6399
6678
|
...this.queueFields()
|
|
6400
6679
|
});
|
|
6401
6680
|
}
|
|
6681
|
+
triggerBrandExtraction(this.config);
|
|
6402
6682
|
this.toolRegistry.onEvent = this.onEvent;
|
|
6403
6683
|
this.readline = createInterface({ input: process.stdin });
|
|
6404
6684
|
this.readline.on("line", this.handleStdinLine);
|
|
@@ -6498,7 +6778,7 @@ var HeadlessSession = class {
|
|
|
6498
6778
|
}
|
|
6499
6779
|
onBackgroundComplete = (toolCallId, name, result, subAgentMessages) => {
|
|
6500
6780
|
this.pendingBlockUpdates.push({ toolCallId, result, subAgentMessages });
|
|
6501
|
-
|
|
6781
|
+
log14.info("Background complete", {
|
|
6502
6782
|
toolCallId,
|
|
6503
6783
|
name,
|
|
6504
6784
|
requestId: this.currentRequestId
|
|
@@ -6719,7 +6999,7 @@ var HeadlessSession = class {
|
|
|
6719
6999
|
this.turnStart = Date.now();
|
|
6720
7000
|
const attachments = parsed.attachments;
|
|
6721
7001
|
if (attachments?.length) {
|
|
6722
|
-
|
|
7002
|
+
log14.info("Message has attachments", {
|
|
6723
7003
|
count: attachments.length,
|
|
6724
7004
|
urls: attachments.map((a) => a.url)
|
|
6725
7005
|
});
|
|
@@ -6736,7 +7016,7 @@ var HeadlessSession = class {
|
|
|
6736
7016
|
${userMessage}` : header;
|
|
6737
7017
|
}
|
|
6738
7018
|
} catch (err) {
|
|
6739
|
-
|
|
7019
|
+
log14.warn("Attachment persistence failed", { error: err.message });
|
|
6740
7020
|
}
|
|
6741
7021
|
}
|
|
6742
7022
|
let resolved = null;
|
|
@@ -6794,7 +7074,7 @@ ${userMessage}` : header;
|
|
|
6794
7074
|
error: "Turn ended unexpectedly"
|
|
6795
7075
|
});
|
|
6796
7076
|
}
|
|
6797
|
-
|
|
7077
|
+
log14.info("Turn complete", {
|
|
6798
7078
|
requestId,
|
|
6799
7079
|
durationMs: Date.now() - this.turnStart
|
|
6800
7080
|
});
|
|
@@ -6806,7 +7086,7 @@ ${userMessage}` : header;
|
|
|
6806
7086
|
error: err.message
|
|
6807
7087
|
});
|
|
6808
7088
|
}
|
|
6809
|
-
|
|
7089
|
+
log14.warn("Command failed", {
|
|
6810
7090
|
action: "message",
|
|
6811
7091
|
requestId,
|
|
6812
7092
|
error: err.message
|
|
@@ -6941,7 +7221,7 @@ ${userMessage}` : header;
|
|
|
6941
7221
|
return;
|
|
6942
7222
|
}
|
|
6943
7223
|
const { action, requestId } = parsed;
|
|
6944
|
-
|
|
7224
|
+
log14.info("Command received", { action, requestId });
|
|
6945
7225
|
if (action === "tool_result" && parsed.id) {
|
|
6946
7226
|
const id = parsed.id;
|
|
6947
7227
|
const result = parsed.result ?? "";
|
|
@@ -6950,7 +7230,7 @@ ${userMessage}` : header;
|
|
|
6950
7230
|
this.pendingTools.delete(id);
|
|
6951
7231
|
pending.resolve(result);
|
|
6952
7232
|
} else if (!this.running) {
|
|
6953
|
-
|
|
7233
|
+
log14.info("Late tool_result while idle, dismissing", { id });
|
|
6954
7234
|
this.emit("completed", { success: true }, requestId);
|
|
6955
7235
|
} else {
|
|
6956
7236
|
this.earlyResults.set(id, result);
|
package/dist/index.js
CHANGED
|
@@ -2962,11 +2962,11 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
2962
2962
|
let prompt;
|
|
2963
2963
|
let existingUrl;
|
|
2964
2964
|
let onLog;
|
|
2965
|
-
let
|
|
2965
|
+
let path12;
|
|
2966
2966
|
if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
|
|
2967
2967
|
prompt = promptOrOptions.prompt;
|
|
2968
2968
|
existingUrl = promptOrOptions.imageUrl;
|
|
2969
|
-
|
|
2969
|
+
path12 = promptOrOptions.path;
|
|
2970
2970
|
onLog = promptOrOptions.onLog;
|
|
2971
2971
|
} else {
|
|
2972
2972
|
prompt = promptOrOptions;
|
|
@@ -2978,7 +2978,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
2978
2978
|
} else {
|
|
2979
2979
|
const ssResult = await sidecarRequest(
|
|
2980
2980
|
"/screenshot-full-page",
|
|
2981
|
-
|
|
2981
|
+
path12 ? { path: path12 } : void 0,
|
|
2982
2982
|
{ timeout: 12e4 }
|
|
2983
2983
|
);
|
|
2984
2984
|
url = ssResult?.url || ssResult?.screenshotUrl;
|
|
@@ -3033,15 +3033,15 @@ var init_browserLock = __esm({
|
|
|
3033
3033
|
// src/statusWatcher.ts
|
|
3034
3034
|
function startStatusWatcher(config) {
|
|
3035
3035
|
const { apiConfig, getContext, onStatus, interval = 5e3, signal } = config;
|
|
3036
|
-
let
|
|
3036
|
+
let inflight2 = false;
|
|
3037
3037
|
let stopped = false;
|
|
3038
3038
|
let pauseCount = 0;
|
|
3039
3039
|
const url = `${apiConfig.baseUrl}/_internal/v2/agent/remy/generate-status`;
|
|
3040
3040
|
async function tick() {
|
|
3041
|
-
if (stopped || signal?.aborted ||
|
|
3041
|
+
if (stopped || signal?.aborted || inflight2 || pauseCount > 0) {
|
|
3042
3042
|
return;
|
|
3043
3043
|
}
|
|
3044
|
-
|
|
3044
|
+
inflight2 = true;
|
|
3045
3045
|
try {
|
|
3046
3046
|
const context = getContext();
|
|
3047
3047
|
if (!context) {
|
|
@@ -3069,7 +3069,7 @@ function startStatusWatcher(config) {
|
|
|
3069
3069
|
onStatus(data.label);
|
|
3070
3070
|
} catch {
|
|
3071
3071
|
} finally {
|
|
3072
|
-
|
|
3072
|
+
inflight2 = false;
|
|
3073
3073
|
}
|
|
3074
3074
|
}
|
|
3075
3075
|
const timer = setInterval(tick, interval);
|
|
@@ -4936,15 +4936,15 @@ function getSampleIndices(pools, sizes) {
|
|
|
4936
4936
|
}
|
|
4937
4937
|
const loaded = load();
|
|
4938
4938
|
if (loaded) {
|
|
4939
|
-
let
|
|
4939
|
+
let dirty2 = false;
|
|
4940
4940
|
for (const key of ["uiInspiration", "designReferences", "fonts"]) {
|
|
4941
4941
|
const before = loaded[key].length;
|
|
4942
4942
|
loaded[key] = loaded[key].filter((i) => i < pools[key]);
|
|
4943
4943
|
if (loaded[key].length < before) {
|
|
4944
|
-
|
|
4944
|
+
dirty2 = true;
|
|
4945
4945
|
}
|
|
4946
4946
|
}
|
|
4947
|
-
if (
|
|
4947
|
+
if (dirty2) {
|
|
4948
4948
|
save(loaded);
|
|
4949
4949
|
}
|
|
4950
4950
|
cached = loaded;
|
|
@@ -6061,6 +6061,298 @@ var init_errors = __esm({
|
|
|
6061
6061
|
}
|
|
6062
6062
|
});
|
|
6063
6063
|
|
|
6064
|
+
// src/brandExtraction/index.ts
|
|
6065
|
+
import fs19 from "fs";
|
|
6066
|
+
import path9 from "path";
|
|
6067
|
+
import { createHash } from "crypto";
|
|
6068
|
+
async function runExtraction(apiConfig) {
|
|
6069
|
+
const inputHash = computeInputHash();
|
|
6070
|
+
const cached2 = readCache();
|
|
6071
|
+
if (cached2 && cached2.inputHash === inputHash) {
|
|
6072
|
+
log8.debug("Brand inputs unchanged \u2014 skipping extraction", { inputHash });
|
|
6073
|
+
return null;
|
|
6074
|
+
}
|
|
6075
|
+
log8.info("Extracting brand", { inputHash });
|
|
6076
|
+
const brand = await extractBrand(apiConfig);
|
|
6077
|
+
if (!brand) {
|
|
6078
|
+
log8.warn("Brand extraction failed \u2014 leaving cache untouched");
|
|
6079
|
+
return null;
|
|
6080
|
+
}
|
|
6081
|
+
persistBrand(brand, inputHash);
|
|
6082
|
+
log8.info("Brand persisted", { inputHash });
|
|
6083
|
+
return brand;
|
|
6084
|
+
}
|
|
6085
|
+
function computeInputHash() {
|
|
6086
|
+
const entries = [];
|
|
6087
|
+
for (const filePath of walkMdFiles3("src")) {
|
|
6088
|
+
if (filePath === path9.join("src", "app.md")) {
|
|
6089
|
+
entries.push({ path: filePath, content: readSafe(filePath) });
|
|
6090
|
+
continue;
|
|
6091
|
+
}
|
|
6092
|
+
const fm = parseFrontmatter3(filePath);
|
|
6093
|
+
if (fm.type.startsWith("design/color") || fm.type.startsWith("design/typography")) {
|
|
6094
|
+
entries.push({ path: filePath, content: readSafe(filePath) });
|
|
6095
|
+
}
|
|
6096
|
+
}
|
|
6097
|
+
const manifest = readSafe("mindstudio.json");
|
|
6098
|
+
if (manifest) {
|
|
6099
|
+
entries.push({ path: "mindstudio.json", content: manifest });
|
|
6100
|
+
}
|
|
6101
|
+
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
6102
|
+
const fingerprint = entries.map((e) => `${e.path}:${sha256(e.content)}`).join("\n");
|
|
6103
|
+
return sha256(fingerprint);
|
|
6104
|
+
}
|
|
6105
|
+
function sha256(input) {
|
|
6106
|
+
return createHash("sha256").update(input).digest("hex");
|
|
6107
|
+
}
|
|
6108
|
+
function readSafe(filePath) {
|
|
6109
|
+
try {
|
|
6110
|
+
return fs19.readFileSync(filePath, "utf-8");
|
|
6111
|
+
} catch {
|
|
6112
|
+
return "";
|
|
6113
|
+
}
|
|
6114
|
+
}
|
|
6115
|
+
function walkMdFiles3(dir) {
|
|
6116
|
+
const results = [];
|
|
6117
|
+
try {
|
|
6118
|
+
const entries = fs19.readdirSync(dir, { withFileTypes: true });
|
|
6119
|
+
for (const entry of entries) {
|
|
6120
|
+
const full = path9.join(dir, entry.name);
|
|
6121
|
+
if (entry.isDirectory()) {
|
|
6122
|
+
results.push(...walkMdFiles3(full));
|
|
6123
|
+
} else if (entry.name.endsWith(".md")) {
|
|
6124
|
+
results.push(full);
|
|
6125
|
+
}
|
|
6126
|
+
}
|
|
6127
|
+
} catch {
|
|
6128
|
+
}
|
|
6129
|
+
return results.sort();
|
|
6130
|
+
}
|
|
6131
|
+
function parseFrontmatter3(filePath) {
|
|
6132
|
+
try {
|
|
6133
|
+
const content = fs19.readFileSync(filePath, "utf-8");
|
|
6134
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
6135
|
+
if (!match) {
|
|
6136
|
+
return { type: "" };
|
|
6137
|
+
}
|
|
6138
|
+
const fm = match[1];
|
|
6139
|
+
const type = fm.match(/^type:\s*(.+)$/m)?.[1]?.trim() ?? "";
|
|
6140
|
+
return { type };
|
|
6141
|
+
} catch {
|
|
6142
|
+
return { type: "" };
|
|
6143
|
+
}
|
|
6144
|
+
}
|
|
6145
|
+
async function extractBrand(apiConfig) {
|
|
6146
|
+
const corpus = buildCorpus();
|
|
6147
|
+
if (!corpus.trim()) {
|
|
6148
|
+
log8.debug("No spec corpus \u2014 emitting empty brand");
|
|
6149
|
+
return { version: 1 };
|
|
6150
|
+
}
|
|
6151
|
+
let responseText = "";
|
|
6152
|
+
try {
|
|
6153
|
+
for await (const event of streamChat({
|
|
6154
|
+
...apiConfig,
|
|
6155
|
+
subAgentId: "brandExtractor",
|
|
6156
|
+
system: EXTRACT_PROMPT,
|
|
6157
|
+
messages: [{ role: "user", content: corpus }],
|
|
6158
|
+
tools: []
|
|
6159
|
+
})) {
|
|
6160
|
+
if (event.type === "text") {
|
|
6161
|
+
responseText += event.text;
|
|
6162
|
+
} else if (event.type === "error") {
|
|
6163
|
+
log8.error("Brand extraction stream error", { error: event.error });
|
|
6164
|
+
return null;
|
|
6165
|
+
}
|
|
6166
|
+
}
|
|
6167
|
+
} catch (err) {
|
|
6168
|
+
log8.error("Brand extraction threw", { error: err?.message });
|
|
6169
|
+
return null;
|
|
6170
|
+
}
|
|
6171
|
+
const parsed = parseJsonResponse(responseText);
|
|
6172
|
+
if (!parsed) {
|
|
6173
|
+
log8.warn("Brand extraction returned unparseable JSON", {
|
|
6174
|
+
preview: responseText.slice(0, 200)
|
|
6175
|
+
});
|
|
6176
|
+
return null;
|
|
6177
|
+
}
|
|
6178
|
+
return validateBrand(parsed);
|
|
6179
|
+
}
|
|
6180
|
+
function buildCorpus() {
|
|
6181
|
+
const sections = [];
|
|
6182
|
+
const manifest = readSafe("mindstudio.json");
|
|
6183
|
+
if (manifest) {
|
|
6184
|
+
sections.push(`## File: mindstudio.json
|
|
6185
|
+
|
|
6186
|
+
${manifest}`);
|
|
6187
|
+
}
|
|
6188
|
+
for (const filePath of walkMdFiles3("src")) {
|
|
6189
|
+
const content = readSafe(filePath);
|
|
6190
|
+
if (content) {
|
|
6191
|
+
sections.push(`## File: ${filePath}
|
|
6192
|
+
|
|
6193
|
+
${content}`);
|
|
6194
|
+
}
|
|
6195
|
+
}
|
|
6196
|
+
return sections.join("\n\n---\n\n");
|
|
6197
|
+
}
|
|
6198
|
+
function parseJsonResponse(text) {
|
|
6199
|
+
const trimmed = text.trim();
|
|
6200
|
+
const fenceMatch = trimmed.match(/^```(?:json)?\s*\n([\s\S]*?)\n```\s*$/);
|
|
6201
|
+
const candidate = fenceMatch ? fenceMatch[1] : trimmed;
|
|
6202
|
+
try {
|
|
6203
|
+
return JSON.parse(candidate);
|
|
6204
|
+
} catch {
|
|
6205
|
+
const braceMatch = candidate.match(/\{[\s\S]*\}/);
|
|
6206
|
+
if (braceMatch) {
|
|
6207
|
+
try {
|
|
6208
|
+
return JSON.parse(braceMatch[0]);
|
|
6209
|
+
} catch {
|
|
6210
|
+
return null;
|
|
6211
|
+
}
|
|
6212
|
+
}
|
|
6213
|
+
return null;
|
|
6214
|
+
}
|
|
6215
|
+
}
|
|
6216
|
+
function validateBrand(raw) {
|
|
6217
|
+
if (!raw || typeof raw !== "object") {
|
|
6218
|
+
return null;
|
|
6219
|
+
}
|
|
6220
|
+
const obj = raw;
|
|
6221
|
+
const out = { version: 1 };
|
|
6222
|
+
if (typeof obj.name === "string" && obj.name.trim()) {
|
|
6223
|
+
out.name = obj.name.trim();
|
|
6224
|
+
}
|
|
6225
|
+
if (typeof obj.tagline === "string" && obj.tagline.trim()) {
|
|
6226
|
+
out.tagline = obj.tagline.trim();
|
|
6227
|
+
}
|
|
6228
|
+
if (typeof obj.logoUrl === "string" && obj.logoUrl.trim()) {
|
|
6229
|
+
out.logoUrl = obj.logoUrl.trim();
|
|
6230
|
+
}
|
|
6231
|
+
const colors = pickColors(obj.colors);
|
|
6232
|
+
if (colors) {
|
|
6233
|
+
out.colors = colors;
|
|
6234
|
+
}
|
|
6235
|
+
const typography = pickTypography(obj.typography);
|
|
6236
|
+
if (typography) {
|
|
6237
|
+
out.typography = typography;
|
|
6238
|
+
}
|
|
6239
|
+
return out;
|
|
6240
|
+
}
|
|
6241
|
+
function pickColors(raw) {
|
|
6242
|
+
if (!raw || typeof raw !== "object") {
|
|
6243
|
+
return void 0;
|
|
6244
|
+
}
|
|
6245
|
+
const c = raw;
|
|
6246
|
+
const out = {};
|
|
6247
|
+
for (const key of [
|
|
6248
|
+
"background",
|
|
6249
|
+
"text",
|
|
6250
|
+
"heading",
|
|
6251
|
+
"accent",
|
|
6252
|
+
"muted"
|
|
6253
|
+
]) {
|
|
6254
|
+
const v = c[key];
|
|
6255
|
+
if (typeof v === "string" && v.trim()) {
|
|
6256
|
+
out[key] = v.trim();
|
|
6257
|
+
}
|
|
6258
|
+
}
|
|
6259
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
6260
|
+
}
|
|
6261
|
+
function pickTypography(raw) {
|
|
6262
|
+
if (!raw || typeof raw !== "object") {
|
|
6263
|
+
return void 0;
|
|
6264
|
+
}
|
|
6265
|
+
const t = raw;
|
|
6266
|
+
const out = {};
|
|
6267
|
+
const body = pickFont(t.body);
|
|
6268
|
+
if (body) {
|
|
6269
|
+
out.body = body;
|
|
6270
|
+
}
|
|
6271
|
+
const heading = pickFont(t.heading);
|
|
6272
|
+
if (heading) {
|
|
6273
|
+
out.heading = heading;
|
|
6274
|
+
}
|
|
6275
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
6276
|
+
}
|
|
6277
|
+
function pickFont(raw) {
|
|
6278
|
+
if (!raw || typeof raw !== "object") {
|
|
6279
|
+
return void 0;
|
|
6280
|
+
}
|
|
6281
|
+
const f = raw;
|
|
6282
|
+
if (typeof f.family !== "string" || !f.family.trim()) {
|
|
6283
|
+
return void 0;
|
|
6284
|
+
}
|
|
6285
|
+
const out = { family: f.family.trim() };
|
|
6286
|
+
if (typeof f.stylesheet === "string" && f.stylesheet.trim()) {
|
|
6287
|
+
out.stylesheet = f.stylesheet.trim();
|
|
6288
|
+
}
|
|
6289
|
+
if (typeof f.fileUrl === "string" && f.fileUrl.trim()) {
|
|
6290
|
+
out.fileUrl = f.fileUrl.trim();
|
|
6291
|
+
}
|
|
6292
|
+
return out;
|
|
6293
|
+
}
|
|
6294
|
+
function persistBrand(brand, inputHash) {
|
|
6295
|
+
const tmp = `${BRAND_FILE}.tmp`;
|
|
6296
|
+
fs19.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
|
|
6297
|
+
fs19.renameSync(tmp, BRAND_FILE);
|
|
6298
|
+
const cache = { inputHash, generatedAt: Date.now() };
|
|
6299
|
+
fs19.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
6300
|
+
}
|
|
6301
|
+
function readCache() {
|
|
6302
|
+
try {
|
|
6303
|
+
const raw = fs19.readFileSync(CACHE_FILE, "utf-8");
|
|
6304
|
+
const parsed = JSON.parse(raw);
|
|
6305
|
+
if (parsed && typeof parsed.inputHash === "string" && typeof parsed.generatedAt === "number") {
|
|
6306
|
+
return parsed;
|
|
6307
|
+
}
|
|
6308
|
+
return null;
|
|
6309
|
+
} catch {
|
|
6310
|
+
return null;
|
|
6311
|
+
}
|
|
6312
|
+
}
|
|
6313
|
+
var log8, EXTRACT_PROMPT, BRAND_FILE, CACHE_FILE;
|
|
6314
|
+
var init_brandExtraction = __esm({
|
|
6315
|
+
"src/brandExtraction/index.ts"() {
|
|
6316
|
+
"use strict";
|
|
6317
|
+
init_api();
|
|
6318
|
+
init_assets();
|
|
6319
|
+
init_logger();
|
|
6320
|
+
log8 = createLogger("brandExtraction");
|
|
6321
|
+
EXTRACT_PROMPT = readAsset("brandExtraction", "extract.md");
|
|
6322
|
+
BRAND_FILE = ".remy-brand.json";
|
|
6323
|
+
CACHE_FILE = ".remy-brand.cache.json";
|
|
6324
|
+
}
|
|
6325
|
+
});
|
|
6326
|
+
|
|
6327
|
+
// src/brandExtraction/trigger.ts
|
|
6328
|
+
function triggerBrandExtraction(apiConfig) {
|
|
6329
|
+
if (inflight) {
|
|
6330
|
+
dirty = true;
|
|
6331
|
+
return;
|
|
6332
|
+
}
|
|
6333
|
+
inflight = true;
|
|
6334
|
+
void runExtraction(apiConfig).catch((err) => {
|
|
6335
|
+
log9.error("Brand extraction failed", { error: err?.message });
|
|
6336
|
+
}).finally(() => {
|
|
6337
|
+
inflight = false;
|
|
6338
|
+
if (dirty) {
|
|
6339
|
+
dirty = false;
|
|
6340
|
+
triggerBrandExtraction(apiConfig);
|
|
6341
|
+
}
|
|
6342
|
+
});
|
|
6343
|
+
}
|
|
6344
|
+
var log9, inflight, dirty;
|
|
6345
|
+
var init_trigger2 = __esm({
|
|
6346
|
+
"src/brandExtraction/trigger.ts"() {
|
|
6347
|
+
"use strict";
|
|
6348
|
+
init_brandExtraction();
|
|
6349
|
+
init_logger();
|
|
6350
|
+
log9 = createLogger("brandExtraction:trigger");
|
|
6351
|
+
inflight = false;
|
|
6352
|
+
dirty = false;
|
|
6353
|
+
}
|
|
6354
|
+
});
|
|
6355
|
+
|
|
6064
6356
|
// src/agent.ts
|
|
6065
6357
|
function getTextContent(blocks) {
|
|
6066
6358
|
return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
@@ -6092,7 +6384,7 @@ async function runTurn(params) {
|
|
|
6092
6384
|
} = params;
|
|
6093
6385
|
const tools2 = getToolDefinitions(onboardingState);
|
|
6094
6386
|
const excludeToolsFromClearing = tools2.filter((t) => !CLEARABLE_TOOLS.has(t.name)).map((t) => t.name);
|
|
6095
|
-
|
|
6387
|
+
log10.info("Turn started", {
|
|
6096
6388
|
requestId,
|
|
6097
6389
|
model,
|
|
6098
6390
|
toolCount: tools2.length,
|
|
@@ -6321,7 +6613,7 @@ async function runTurn(params) {
|
|
|
6321
6613
|
const tool = getToolByName(event.name);
|
|
6322
6614
|
const wasStreamed = acc?.started ?? false;
|
|
6323
6615
|
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
6324
|
-
|
|
6616
|
+
log10.info("Tool received", {
|
|
6325
6617
|
requestId,
|
|
6326
6618
|
toolCallId: event.id,
|
|
6327
6619
|
name: event.name
|
|
@@ -6414,7 +6706,7 @@ async function runTurn(params) {
|
|
|
6414
6706
|
});
|
|
6415
6707
|
return;
|
|
6416
6708
|
}
|
|
6417
|
-
|
|
6709
|
+
log10.info("Tools executing", {
|
|
6418
6710
|
requestId,
|
|
6419
6711
|
count: toolCalls.length,
|
|
6420
6712
|
tools: toolCalls.map((tc) => tc.name)
|
|
@@ -6461,7 +6753,7 @@ async function runTurn(params) {
|
|
|
6461
6753
|
let result;
|
|
6462
6754
|
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
6463
6755
|
saveSession(state);
|
|
6464
|
-
|
|
6756
|
+
log10.info("Waiting for external tool result", {
|
|
6465
6757
|
requestId,
|
|
6466
6758
|
toolCallId: tc.id,
|
|
6467
6759
|
name: tc.name
|
|
@@ -6528,7 +6820,7 @@ async function runTurn(params) {
|
|
|
6528
6820
|
if (!tc.input.background) {
|
|
6529
6821
|
toolRegistry?.unregister(tc.id);
|
|
6530
6822
|
}
|
|
6531
|
-
|
|
6823
|
+
log10.info("Tool completed", {
|
|
6532
6824
|
requestId,
|
|
6533
6825
|
toolCallId: tc.id,
|
|
6534
6826
|
name: tc.name,
|
|
@@ -6542,6 +6834,9 @@ async function runTurn(params) {
|
|
|
6542
6834
|
result: r.result,
|
|
6543
6835
|
isError: r.isError
|
|
6544
6836
|
});
|
|
6837
|
+
if (!r.isError && BRAND_TRIGGERING_TOOLS.has(tc.name)) {
|
|
6838
|
+
triggerBrandExtraction(apiConfig);
|
|
6839
|
+
}
|
|
6545
6840
|
return r;
|
|
6546
6841
|
})
|
|
6547
6842
|
);
|
|
@@ -6581,7 +6876,7 @@ async function runTurn(params) {
|
|
|
6581
6876
|
}
|
|
6582
6877
|
}
|
|
6583
6878
|
}
|
|
6584
|
-
var
|
|
6879
|
+
var log10, BRAND_TRIGGERING_TOOLS, EXTERNAL_TOOLS, USER_BLOCKING_EXTERNAL_TOOLS;
|
|
6585
6880
|
var init_agent = __esm({
|
|
6586
6881
|
"src/agent.ts"() {
|
|
6587
6882
|
"use strict";
|
|
@@ -6595,7 +6890,9 @@ var init_agent = __esm({
|
|
|
6595
6890
|
init_cleanMessages();
|
|
6596
6891
|
init_tools6();
|
|
6597
6892
|
init_sentinel();
|
|
6598
|
-
|
|
6893
|
+
init_trigger2();
|
|
6894
|
+
log10 = createLogger("agent");
|
|
6895
|
+
BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set(["writeSpec", "editSpec"]);
|
|
6599
6896
|
EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
6600
6897
|
"promptUser",
|
|
6601
6898
|
"setProjectOnboardingState",
|
|
@@ -6616,16 +6913,16 @@ var init_agent = __esm({
|
|
|
6616
6913
|
});
|
|
6617
6914
|
|
|
6618
6915
|
// src/config.ts
|
|
6619
|
-
import
|
|
6620
|
-
import
|
|
6916
|
+
import fs20 from "fs";
|
|
6917
|
+
import path10 from "path";
|
|
6621
6918
|
import os from "os";
|
|
6622
6919
|
function loadConfigFile() {
|
|
6623
6920
|
try {
|
|
6624
|
-
const raw =
|
|
6625
|
-
|
|
6921
|
+
const raw = fs20.readFileSync(CONFIG_PATH, "utf-8");
|
|
6922
|
+
log11.debug("Loaded config file", { path: CONFIG_PATH });
|
|
6626
6923
|
return JSON.parse(raw);
|
|
6627
6924
|
} catch (err) {
|
|
6628
|
-
|
|
6925
|
+
log11.debug("No config file found", {
|
|
6629
6926
|
path: CONFIG_PATH,
|
|
6630
6927
|
error: err.message
|
|
6631
6928
|
});
|
|
@@ -6639,26 +6936,26 @@ function resolveConfig(flags2) {
|
|
|
6639
6936
|
const apiKey = flags2?.apiKey || process.env.MINDSTUDIO_API_KEY || env?.apiKey || "";
|
|
6640
6937
|
const baseUrl2 = flags2?.baseUrl || process.env.MINDSTUDIO_BASE_URL || env?.apiBaseUrl || DEFAULT_BASE_URL;
|
|
6641
6938
|
if (!apiKey) {
|
|
6642
|
-
|
|
6939
|
+
log11.error("No API key found");
|
|
6643
6940
|
throw new Error(
|
|
6644
6941
|
"No API key found. Set MINDSTUDIO_API_KEY or configure ~/.mindstudio-local-tunnel/config.json."
|
|
6645
6942
|
);
|
|
6646
6943
|
}
|
|
6647
6944
|
const keySource = flags2?.apiKey ? "cli flag" : process.env.MINDSTUDIO_API_KEY ? "env var" : "config file";
|
|
6648
|
-
|
|
6945
|
+
log11.info("Config resolved", {
|
|
6649
6946
|
baseUrl: baseUrl2,
|
|
6650
6947
|
keySource,
|
|
6651
6948
|
environment: activeEnv
|
|
6652
6949
|
});
|
|
6653
6950
|
return { apiKey, baseUrl: baseUrl2 };
|
|
6654
6951
|
}
|
|
6655
|
-
var
|
|
6952
|
+
var log11, CONFIG_PATH, DEFAULT_BASE_URL;
|
|
6656
6953
|
var init_config = __esm({
|
|
6657
6954
|
"src/config.ts"() {
|
|
6658
6955
|
"use strict";
|
|
6659
6956
|
init_logger();
|
|
6660
|
-
|
|
6661
|
-
CONFIG_PATH =
|
|
6957
|
+
log11 = createLogger("config");
|
|
6958
|
+
CONFIG_PATH = path10.join(
|
|
6662
6959
|
os.homedir(),
|
|
6663
6960
|
".mindstudio-local-tunnel",
|
|
6664
6961
|
"config.json"
|
|
@@ -6668,12 +6965,12 @@ var init_config = __esm({
|
|
|
6668
6965
|
});
|
|
6669
6966
|
|
|
6670
6967
|
// src/toolRegistry.ts
|
|
6671
|
-
var
|
|
6968
|
+
var log12, ToolRegistry;
|
|
6672
6969
|
var init_toolRegistry = __esm({
|
|
6673
6970
|
"src/toolRegistry.ts"() {
|
|
6674
6971
|
"use strict";
|
|
6675
6972
|
init_logger();
|
|
6676
|
-
|
|
6973
|
+
log12 = createLogger("tool-registry");
|
|
6677
6974
|
ToolRegistry = class {
|
|
6678
6975
|
entries = /* @__PURE__ */ new Map();
|
|
6679
6976
|
onEvent;
|
|
@@ -6699,7 +6996,7 @@ var init_toolRegistry = __esm({
|
|
|
6699
6996
|
if (!entry) {
|
|
6700
6997
|
return false;
|
|
6701
6998
|
}
|
|
6702
|
-
|
|
6999
|
+
log12.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
|
|
6703
7000
|
entry.abortController.abort(mode);
|
|
6704
7001
|
if (mode === "graceful") {
|
|
6705
7002
|
const partial = entry.getPartialResult?.() ?? "";
|
|
@@ -6732,7 +7029,7 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
|
6732
7029
|
if (!entry) {
|
|
6733
7030
|
return false;
|
|
6734
7031
|
}
|
|
6735
|
-
|
|
7032
|
+
log12.info("Tool restarted", { toolCallId: id, name: entry.name });
|
|
6736
7033
|
entry.abortController.abort("restart");
|
|
6737
7034
|
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
6738
7035
|
this.onEvent?.({
|
|
@@ -6798,7 +7095,7 @@ async function persistAttachments(attachments) {
|
|
|
6798
7095
|
}
|
|
6799
7096
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
6800
7097
|
await writeFile(localPath, buffer);
|
|
6801
|
-
|
|
7098
|
+
log13.info("Attachment saved", {
|
|
6802
7099
|
filename: name,
|
|
6803
7100
|
path: localPath,
|
|
6804
7101
|
bytes: buffer.length
|
|
@@ -6812,7 +7109,7 @@ async function persistAttachments(attachments) {
|
|
|
6812
7109
|
if (textRes.ok) {
|
|
6813
7110
|
extractedTextPath = `${localPath}.txt`;
|
|
6814
7111
|
await writeFile(extractedTextPath, await textRes.text(), "utf-8");
|
|
6815
|
-
|
|
7112
|
+
log13.info("Extracted text saved", { path: extractedTextPath });
|
|
6816
7113
|
}
|
|
6817
7114
|
} catch {
|
|
6818
7115
|
}
|
|
@@ -6857,12 +7154,12 @@ function buildUploadHeader(results) {
|
|
|
6857
7154
|
return `[Uploaded files]
|
|
6858
7155
|
${lines.join("\n")}`;
|
|
6859
7156
|
}
|
|
6860
|
-
var
|
|
7157
|
+
var log13, UPLOADS_DIR, IMAGE_EXTENSIONS;
|
|
6861
7158
|
var init_attachments = __esm({
|
|
6862
7159
|
"src/headless/attachments.ts"() {
|
|
6863
7160
|
"use strict";
|
|
6864
7161
|
init_logger();
|
|
6865
|
-
|
|
7162
|
+
log13 = createLogger("headless:attachments");
|
|
6866
7163
|
UPLOADS_DIR = "src/.user-uploads";
|
|
6867
7164
|
IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
|
|
6868
7165
|
}
|
|
@@ -7041,7 +7338,7 @@ __export(headless_exports, {
|
|
|
7041
7338
|
HeadlessSession: () => HeadlessSession
|
|
7042
7339
|
});
|
|
7043
7340
|
import { createInterface } from "readline";
|
|
7044
|
-
var
|
|
7341
|
+
var log14, EXTERNAL_TOOL_TIMEOUT_MS, USER_FACING_TOOLS, HeadlessSession;
|
|
7045
7342
|
var init_headless = __esm({
|
|
7046
7343
|
"src/headless/index.ts"() {
|
|
7047
7344
|
"use strict";
|
|
@@ -7050,6 +7347,7 @@ var init_headless = __esm({
|
|
|
7050
7347
|
init_prompt();
|
|
7051
7348
|
init_trigger();
|
|
7052
7349
|
init_compaction();
|
|
7350
|
+
init_trigger2();
|
|
7053
7351
|
init_lsp();
|
|
7054
7352
|
init_agent();
|
|
7055
7353
|
init_session();
|
|
@@ -7060,7 +7358,7 @@ var init_headless = __esm({
|
|
|
7060
7358
|
init_messageQueue();
|
|
7061
7359
|
init_resolve();
|
|
7062
7360
|
init_sentinel();
|
|
7063
|
-
|
|
7361
|
+
log14 = createLogger("headless");
|
|
7064
7362
|
EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
|
|
7065
7363
|
USER_FACING_TOOLS = /* @__PURE__ */ new Set([
|
|
7066
7364
|
"promptUser",
|
|
@@ -7132,6 +7430,7 @@ var init_headless = __esm({
|
|
|
7132
7430
|
...this.queueFields()
|
|
7133
7431
|
});
|
|
7134
7432
|
}
|
|
7433
|
+
triggerBrandExtraction(this.config);
|
|
7135
7434
|
this.toolRegistry.onEvent = this.onEvent;
|
|
7136
7435
|
this.readline = createInterface({ input: process.stdin });
|
|
7137
7436
|
this.readline.on("line", this.handleStdinLine);
|
|
@@ -7231,7 +7530,7 @@ var init_headless = __esm({
|
|
|
7231
7530
|
}
|
|
7232
7531
|
onBackgroundComplete = (toolCallId, name, result, subAgentMessages) => {
|
|
7233
7532
|
this.pendingBlockUpdates.push({ toolCallId, result, subAgentMessages });
|
|
7234
|
-
|
|
7533
|
+
log14.info("Background complete", {
|
|
7235
7534
|
toolCallId,
|
|
7236
7535
|
name,
|
|
7237
7536
|
requestId: this.currentRequestId
|
|
@@ -7452,7 +7751,7 @@ var init_headless = __esm({
|
|
|
7452
7751
|
this.turnStart = Date.now();
|
|
7453
7752
|
const attachments = parsed.attachments;
|
|
7454
7753
|
if (attachments?.length) {
|
|
7455
|
-
|
|
7754
|
+
log14.info("Message has attachments", {
|
|
7456
7755
|
count: attachments.length,
|
|
7457
7756
|
urls: attachments.map((a) => a.url)
|
|
7458
7757
|
});
|
|
@@ -7469,7 +7768,7 @@ var init_headless = __esm({
|
|
|
7469
7768
|
${userMessage}` : header;
|
|
7470
7769
|
}
|
|
7471
7770
|
} catch (err) {
|
|
7472
|
-
|
|
7771
|
+
log14.warn("Attachment persistence failed", { error: err.message });
|
|
7473
7772
|
}
|
|
7474
7773
|
}
|
|
7475
7774
|
let resolved = null;
|
|
@@ -7527,7 +7826,7 @@ ${userMessage}` : header;
|
|
|
7527
7826
|
error: "Turn ended unexpectedly"
|
|
7528
7827
|
});
|
|
7529
7828
|
}
|
|
7530
|
-
|
|
7829
|
+
log14.info("Turn complete", {
|
|
7531
7830
|
requestId,
|
|
7532
7831
|
durationMs: Date.now() - this.turnStart
|
|
7533
7832
|
});
|
|
@@ -7539,7 +7838,7 @@ ${userMessage}` : header;
|
|
|
7539
7838
|
error: err.message
|
|
7540
7839
|
});
|
|
7541
7840
|
}
|
|
7542
|
-
|
|
7841
|
+
log14.warn("Command failed", {
|
|
7543
7842
|
action: "message",
|
|
7544
7843
|
requestId,
|
|
7545
7844
|
error: err.message
|
|
@@ -7674,7 +7973,7 @@ ${userMessage}` : header;
|
|
|
7674
7973
|
return;
|
|
7675
7974
|
}
|
|
7676
7975
|
const { action, requestId } = parsed;
|
|
7677
|
-
|
|
7976
|
+
log14.info("Command received", { action, requestId });
|
|
7678
7977
|
if (action === "tool_result" && parsed.id) {
|
|
7679
7978
|
const id = parsed.id;
|
|
7680
7979
|
const result = parsed.result ?? "";
|
|
@@ -7683,7 +7982,7 @@ ${userMessage}` : header;
|
|
|
7683
7982
|
this.pendingTools.delete(id);
|
|
7684
7983
|
pending.resolve(result);
|
|
7685
7984
|
} else if (!this.running) {
|
|
7686
|
-
|
|
7985
|
+
log14.info("Late tool_result while idle, dismissing", { id });
|
|
7687
7986
|
this.emit("completed", { success: true }, requestId);
|
|
7688
7987
|
} else {
|
|
7689
7988
|
this.earlyResults.set(id, result);
|
|
@@ -7811,8 +8110,8 @@ ${userMessage}` : header;
|
|
|
7811
8110
|
// src/index.tsx
|
|
7812
8111
|
import { render } from "ink";
|
|
7813
8112
|
import os2 from "os";
|
|
7814
|
-
import
|
|
7815
|
-
import
|
|
8113
|
+
import fs21 from "fs";
|
|
8114
|
+
import path11 from "path";
|
|
7816
8115
|
|
|
7817
8116
|
// src/tui/App.tsx
|
|
7818
8117
|
import { useState as useState2, useCallback, useRef } from "react";
|
|
@@ -8130,8 +8429,8 @@ for (let i = 0; i < args.length; i++) {
|
|
|
8130
8429
|
var startupLog = createLogger("startup");
|
|
8131
8430
|
function printDebugInfo(config) {
|
|
8132
8431
|
const pkg = JSON.parse(
|
|
8133
|
-
|
|
8134
|
-
|
|
8432
|
+
fs21.readFileSync(
|
|
8433
|
+
path11.join(import.meta.dirname, "..", "package.json"),
|
|
8135
8434
|
"utf-8"
|
|
8136
8435
|
)
|
|
8137
8436
|
);
|