@mindstudio-ai/remy 0.1.158 → 0.1.160
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 +322 -39
- package/dist/index.js +351 -49
- 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,
|
|
@@ -5572,6 +5848,7 @@ async function runTurn(params) {
|
|
|
5572
5848
|
const contentBlocks = [];
|
|
5573
5849
|
const thinkingBlockStartTimes = [];
|
|
5574
5850
|
let thinkingCompleteCount = 0;
|
|
5851
|
+
let textBlockOpen = false;
|
|
5575
5852
|
const toolInputAccumulators = /* @__PURE__ */ new Map();
|
|
5576
5853
|
let stopReason = "end_turn";
|
|
5577
5854
|
let subAgentText = "";
|
|
@@ -5682,7 +5959,7 @@ async function runTurn(params) {
|
|
|
5682
5959
|
switch (event.type) {
|
|
5683
5960
|
case "text": {
|
|
5684
5961
|
const lastBlock = contentBlocks.at(-1);
|
|
5685
|
-
if (lastBlock?.type === "text") {
|
|
5962
|
+
if (lastBlock?.type === "text" && textBlockOpen) {
|
|
5686
5963
|
lastBlock.text += event.text;
|
|
5687
5964
|
} else {
|
|
5688
5965
|
contentBlocks.push({
|
|
@@ -5691,12 +5968,14 @@ async function runTurn(params) {
|
|
|
5691
5968
|
startedAt: event.ts
|
|
5692
5969
|
});
|
|
5693
5970
|
}
|
|
5971
|
+
textBlockOpen = true;
|
|
5694
5972
|
onEvent({ type: "text", text: event.text });
|
|
5695
5973
|
break;
|
|
5696
5974
|
}
|
|
5697
5975
|
case "thinking":
|
|
5698
5976
|
if (event.text === "") {
|
|
5699
5977
|
thinkingBlockStartTimes.push(event.ts);
|
|
5978
|
+
textBlockOpen = false;
|
|
5700
5979
|
}
|
|
5701
5980
|
onEvent({ type: "thinking", text: event.text });
|
|
5702
5981
|
break;
|
|
@@ -5738,7 +6017,7 @@ async function runTurn(params) {
|
|
|
5738
6017
|
const tool = getToolByName(event.name);
|
|
5739
6018
|
const wasStreamed = acc?.started ?? false;
|
|
5740
6019
|
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
5741
|
-
|
|
6020
|
+
log11.info("Tool received", {
|
|
5742
6021
|
requestId,
|
|
5743
6022
|
toolCallId: event.id,
|
|
5744
6023
|
name: event.name
|
|
@@ -5831,7 +6110,7 @@ async function runTurn(params) {
|
|
|
5831
6110
|
});
|
|
5832
6111
|
return;
|
|
5833
6112
|
}
|
|
5834
|
-
|
|
6113
|
+
log11.info("Tools executing", {
|
|
5835
6114
|
requestId,
|
|
5836
6115
|
count: toolCalls.length,
|
|
5837
6116
|
tools: toolCalls.map((tc) => tc.name)
|
|
@@ -5878,7 +6157,7 @@ async function runTurn(params) {
|
|
|
5878
6157
|
let result;
|
|
5879
6158
|
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
5880
6159
|
saveSession(state);
|
|
5881
|
-
|
|
6160
|
+
log11.info("Waiting for external tool result", {
|
|
5882
6161
|
requestId,
|
|
5883
6162
|
toolCallId: tc.id,
|
|
5884
6163
|
name: tc.name
|
|
@@ -5945,7 +6224,7 @@ async function runTurn(params) {
|
|
|
5945
6224
|
if (!tc.input.background) {
|
|
5946
6225
|
toolRegistry?.unregister(tc.id);
|
|
5947
6226
|
}
|
|
5948
|
-
|
|
6227
|
+
log11.info("Tool completed", {
|
|
5949
6228
|
requestId,
|
|
5950
6229
|
toolCallId: tc.id,
|
|
5951
6230
|
name: tc.name,
|
|
@@ -5959,6 +6238,9 @@ async function runTurn(params) {
|
|
|
5959
6238
|
result: r.result,
|
|
5960
6239
|
isError: r.isError
|
|
5961
6240
|
});
|
|
6241
|
+
if (!r.isError && BRAND_TRIGGERING_TOOLS.has(tc.name)) {
|
|
6242
|
+
triggerBrandExtraction(apiConfig);
|
|
6243
|
+
}
|
|
5962
6244
|
return r;
|
|
5963
6245
|
})
|
|
5964
6246
|
);
|
|
@@ -6000,7 +6282,7 @@ async function runTurn(params) {
|
|
|
6000
6282
|
}
|
|
6001
6283
|
|
|
6002
6284
|
// src/toolRegistry.ts
|
|
6003
|
-
var
|
|
6285
|
+
var log12 = createLogger("tool-registry");
|
|
6004
6286
|
var ToolRegistry = class {
|
|
6005
6287
|
entries = /* @__PURE__ */ new Map();
|
|
6006
6288
|
onEvent;
|
|
@@ -6026,7 +6308,7 @@ var ToolRegistry = class {
|
|
|
6026
6308
|
if (!entry) {
|
|
6027
6309
|
return false;
|
|
6028
6310
|
}
|
|
6029
|
-
|
|
6311
|
+
log12.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
|
|
6030
6312
|
entry.abortController.abort(mode);
|
|
6031
6313
|
if (mode === "graceful") {
|
|
6032
6314
|
const partial = entry.getPartialResult?.() ?? "";
|
|
@@ -6059,7 +6341,7 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
|
6059
6341
|
if (!entry) {
|
|
6060
6342
|
return false;
|
|
6061
6343
|
}
|
|
6062
|
-
|
|
6344
|
+
log12.info("Tool restarted", { toolCallId: id, name: entry.name });
|
|
6063
6345
|
entry.abortController.abort("restart");
|
|
6064
6346
|
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
6065
6347
|
this.onEvent?.({
|
|
@@ -6078,7 +6360,7 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
|
6078
6360
|
import { mkdirSync, existsSync } from "fs";
|
|
6079
6361
|
import { writeFile } from "fs/promises";
|
|
6080
6362
|
import { basename, join, extname } from "path";
|
|
6081
|
-
var
|
|
6363
|
+
var log13 = createLogger("headless:attachments");
|
|
6082
6364
|
var UPLOADS_DIR = "src/.user-uploads";
|
|
6083
6365
|
function filenameFromUrl(url) {
|
|
6084
6366
|
try {
|
|
@@ -6126,7 +6408,7 @@ async function persistAttachments(attachments) {
|
|
|
6126
6408
|
}
|
|
6127
6409
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
6128
6410
|
await writeFile(localPath, buffer);
|
|
6129
|
-
|
|
6411
|
+
log13.info("Attachment saved", {
|
|
6130
6412
|
filename: name,
|
|
6131
6413
|
path: localPath,
|
|
6132
6414
|
bytes: buffer.length
|
|
@@ -6140,7 +6422,7 @@ async function persistAttachments(attachments) {
|
|
|
6140
6422
|
if (textRes.ok) {
|
|
6141
6423
|
extractedTextPath = `${localPath}.txt`;
|
|
6142
6424
|
await writeFile(extractedTextPath, await textRes.text(), "utf-8");
|
|
6143
|
-
|
|
6425
|
+
log13.info("Extracted text saved", { path: extractedTextPath });
|
|
6144
6426
|
}
|
|
6145
6427
|
} catch {
|
|
6146
6428
|
}
|
|
@@ -6327,7 +6609,7 @@ function resolveAction(text) {
|
|
|
6327
6609
|
}
|
|
6328
6610
|
|
|
6329
6611
|
// src/headless/index.ts
|
|
6330
|
-
var
|
|
6612
|
+
var log14 = createLogger("headless");
|
|
6331
6613
|
var EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
|
|
6332
6614
|
var USER_FACING_TOOLS = /* @__PURE__ */ new Set([
|
|
6333
6615
|
"promptUser",
|
|
@@ -6399,6 +6681,7 @@ var HeadlessSession = class {
|
|
|
6399
6681
|
...this.queueFields()
|
|
6400
6682
|
});
|
|
6401
6683
|
}
|
|
6684
|
+
triggerBrandExtraction(this.config);
|
|
6402
6685
|
this.toolRegistry.onEvent = this.onEvent;
|
|
6403
6686
|
this.readline = createInterface({ input: process.stdin });
|
|
6404
6687
|
this.readline.on("line", this.handleStdinLine);
|
|
@@ -6498,7 +6781,7 @@ var HeadlessSession = class {
|
|
|
6498
6781
|
}
|
|
6499
6782
|
onBackgroundComplete = (toolCallId, name, result, subAgentMessages) => {
|
|
6500
6783
|
this.pendingBlockUpdates.push({ toolCallId, result, subAgentMessages });
|
|
6501
|
-
|
|
6784
|
+
log14.info("Background complete", {
|
|
6502
6785
|
toolCallId,
|
|
6503
6786
|
name,
|
|
6504
6787
|
requestId: this.currentRequestId
|
|
@@ -6719,7 +7002,7 @@ var HeadlessSession = class {
|
|
|
6719
7002
|
this.turnStart = Date.now();
|
|
6720
7003
|
const attachments = parsed.attachments;
|
|
6721
7004
|
if (attachments?.length) {
|
|
6722
|
-
|
|
7005
|
+
log14.info("Message has attachments", {
|
|
6723
7006
|
count: attachments.length,
|
|
6724
7007
|
urls: attachments.map((a) => a.url)
|
|
6725
7008
|
});
|
|
@@ -6736,7 +7019,7 @@ var HeadlessSession = class {
|
|
|
6736
7019
|
${userMessage}` : header;
|
|
6737
7020
|
}
|
|
6738
7021
|
} catch (err) {
|
|
6739
|
-
|
|
7022
|
+
log14.warn("Attachment persistence failed", { error: err.message });
|
|
6740
7023
|
}
|
|
6741
7024
|
}
|
|
6742
7025
|
let resolved = null;
|
|
@@ -6794,7 +7077,7 @@ ${userMessage}` : header;
|
|
|
6794
7077
|
error: "Turn ended unexpectedly"
|
|
6795
7078
|
});
|
|
6796
7079
|
}
|
|
6797
|
-
|
|
7080
|
+
log14.info("Turn complete", {
|
|
6798
7081
|
requestId,
|
|
6799
7082
|
durationMs: Date.now() - this.turnStart
|
|
6800
7083
|
});
|
|
@@ -6806,7 +7089,7 @@ ${userMessage}` : header;
|
|
|
6806
7089
|
error: err.message
|
|
6807
7090
|
});
|
|
6808
7091
|
}
|
|
6809
|
-
|
|
7092
|
+
log14.warn("Command failed", {
|
|
6810
7093
|
action: "message",
|
|
6811
7094
|
requestId,
|
|
6812
7095
|
error: err.message
|
|
@@ -6941,7 +7224,7 @@ ${userMessage}` : header;
|
|
|
6941
7224
|
return;
|
|
6942
7225
|
}
|
|
6943
7226
|
const { action, requestId } = parsed;
|
|
6944
|
-
|
|
7227
|
+
log14.info("Command received", { action, requestId });
|
|
6945
7228
|
if (action === "tool_result" && parsed.id) {
|
|
6946
7229
|
const id = parsed.id;
|
|
6947
7230
|
const result = parsed.result ?? "";
|
|
@@ -6950,7 +7233,7 @@ ${userMessage}` : header;
|
|
|
6950
7233
|
this.pendingTools.delete(id);
|
|
6951
7234
|
pending.resolve(result);
|
|
6952
7235
|
} else if (!this.running) {
|
|
6953
|
-
|
|
7236
|
+
log14.info("Late tool_result while idle, dismissing", { id });
|
|
6954
7237
|
this.emit("completed", { success: true }, requestId);
|
|
6955
7238
|
} else {
|
|
6956
7239
|
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,
|
|
@@ -6155,6 +6447,7 @@ async function runTurn(params) {
|
|
|
6155
6447
|
const contentBlocks = [];
|
|
6156
6448
|
const thinkingBlockStartTimes = [];
|
|
6157
6449
|
let thinkingCompleteCount = 0;
|
|
6450
|
+
let textBlockOpen = false;
|
|
6158
6451
|
const toolInputAccumulators = /* @__PURE__ */ new Map();
|
|
6159
6452
|
let stopReason = "end_turn";
|
|
6160
6453
|
let subAgentText = "";
|
|
@@ -6265,7 +6558,7 @@ async function runTurn(params) {
|
|
|
6265
6558
|
switch (event.type) {
|
|
6266
6559
|
case "text": {
|
|
6267
6560
|
const lastBlock = contentBlocks.at(-1);
|
|
6268
|
-
if (lastBlock?.type === "text") {
|
|
6561
|
+
if (lastBlock?.type === "text" && textBlockOpen) {
|
|
6269
6562
|
lastBlock.text += event.text;
|
|
6270
6563
|
} else {
|
|
6271
6564
|
contentBlocks.push({
|
|
@@ -6274,12 +6567,14 @@ async function runTurn(params) {
|
|
|
6274
6567
|
startedAt: event.ts
|
|
6275
6568
|
});
|
|
6276
6569
|
}
|
|
6570
|
+
textBlockOpen = true;
|
|
6277
6571
|
onEvent({ type: "text", text: event.text });
|
|
6278
6572
|
break;
|
|
6279
6573
|
}
|
|
6280
6574
|
case "thinking":
|
|
6281
6575
|
if (event.text === "") {
|
|
6282
6576
|
thinkingBlockStartTimes.push(event.ts);
|
|
6577
|
+
textBlockOpen = false;
|
|
6283
6578
|
}
|
|
6284
6579
|
onEvent({ type: "thinking", text: event.text });
|
|
6285
6580
|
break;
|
|
@@ -6321,7 +6616,7 @@ async function runTurn(params) {
|
|
|
6321
6616
|
const tool = getToolByName(event.name);
|
|
6322
6617
|
const wasStreamed = acc?.started ?? false;
|
|
6323
6618
|
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
6324
|
-
|
|
6619
|
+
log10.info("Tool received", {
|
|
6325
6620
|
requestId,
|
|
6326
6621
|
toolCallId: event.id,
|
|
6327
6622
|
name: event.name
|
|
@@ -6414,7 +6709,7 @@ async function runTurn(params) {
|
|
|
6414
6709
|
});
|
|
6415
6710
|
return;
|
|
6416
6711
|
}
|
|
6417
|
-
|
|
6712
|
+
log10.info("Tools executing", {
|
|
6418
6713
|
requestId,
|
|
6419
6714
|
count: toolCalls.length,
|
|
6420
6715
|
tools: toolCalls.map((tc) => tc.name)
|
|
@@ -6461,7 +6756,7 @@ async function runTurn(params) {
|
|
|
6461
6756
|
let result;
|
|
6462
6757
|
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
6463
6758
|
saveSession(state);
|
|
6464
|
-
|
|
6759
|
+
log10.info("Waiting for external tool result", {
|
|
6465
6760
|
requestId,
|
|
6466
6761
|
toolCallId: tc.id,
|
|
6467
6762
|
name: tc.name
|
|
@@ -6528,7 +6823,7 @@ async function runTurn(params) {
|
|
|
6528
6823
|
if (!tc.input.background) {
|
|
6529
6824
|
toolRegistry?.unregister(tc.id);
|
|
6530
6825
|
}
|
|
6531
|
-
|
|
6826
|
+
log10.info("Tool completed", {
|
|
6532
6827
|
requestId,
|
|
6533
6828
|
toolCallId: tc.id,
|
|
6534
6829
|
name: tc.name,
|
|
@@ -6542,6 +6837,9 @@ async function runTurn(params) {
|
|
|
6542
6837
|
result: r.result,
|
|
6543
6838
|
isError: r.isError
|
|
6544
6839
|
});
|
|
6840
|
+
if (!r.isError && BRAND_TRIGGERING_TOOLS.has(tc.name)) {
|
|
6841
|
+
triggerBrandExtraction(apiConfig);
|
|
6842
|
+
}
|
|
6545
6843
|
return r;
|
|
6546
6844
|
})
|
|
6547
6845
|
);
|
|
@@ -6581,7 +6879,7 @@ async function runTurn(params) {
|
|
|
6581
6879
|
}
|
|
6582
6880
|
}
|
|
6583
6881
|
}
|
|
6584
|
-
var
|
|
6882
|
+
var log10, BRAND_TRIGGERING_TOOLS, EXTERNAL_TOOLS, USER_BLOCKING_EXTERNAL_TOOLS;
|
|
6585
6883
|
var init_agent = __esm({
|
|
6586
6884
|
"src/agent.ts"() {
|
|
6587
6885
|
"use strict";
|
|
@@ -6595,7 +6893,9 @@ var init_agent = __esm({
|
|
|
6595
6893
|
init_cleanMessages();
|
|
6596
6894
|
init_tools6();
|
|
6597
6895
|
init_sentinel();
|
|
6598
|
-
|
|
6896
|
+
init_trigger2();
|
|
6897
|
+
log10 = createLogger("agent");
|
|
6898
|
+
BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set(["writeSpec", "editSpec"]);
|
|
6599
6899
|
EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
6600
6900
|
"promptUser",
|
|
6601
6901
|
"setProjectOnboardingState",
|
|
@@ -6616,16 +6916,16 @@ var init_agent = __esm({
|
|
|
6616
6916
|
});
|
|
6617
6917
|
|
|
6618
6918
|
// src/config.ts
|
|
6619
|
-
import
|
|
6620
|
-
import
|
|
6919
|
+
import fs20 from "fs";
|
|
6920
|
+
import path10 from "path";
|
|
6621
6921
|
import os from "os";
|
|
6622
6922
|
function loadConfigFile() {
|
|
6623
6923
|
try {
|
|
6624
|
-
const raw =
|
|
6625
|
-
|
|
6924
|
+
const raw = fs20.readFileSync(CONFIG_PATH, "utf-8");
|
|
6925
|
+
log11.debug("Loaded config file", { path: CONFIG_PATH });
|
|
6626
6926
|
return JSON.parse(raw);
|
|
6627
6927
|
} catch (err) {
|
|
6628
|
-
|
|
6928
|
+
log11.debug("No config file found", {
|
|
6629
6929
|
path: CONFIG_PATH,
|
|
6630
6930
|
error: err.message
|
|
6631
6931
|
});
|
|
@@ -6639,26 +6939,26 @@ function resolveConfig(flags2) {
|
|
|
6639
6939
|
const apiKey = flags2?.apiKey || process.env.MINDSTUDIO_API_KEY || env?.apiKey || "";
|
|
6640
6940
|
const baseUrl2 = flags2?.baseUrl || process.env.MINDSTUDIO_BASE_URL || env?.apiBaseUrl || DEFAULT_BASE_URL;
|
|
6641
6941
|
if (!apiKey) {
|
|
6642
|
-
|
|
6942
|
+
log11.error("No API key found");
|
|
6643
6943
|
throw new Error(
|
|
6644
6944
|
"No API key found. Set MINDSTUDIO_API_KEY or configure ~/.mindstudio-local-tunnel/config.json."
|
|
6645
6945
|
);
|
|
6646
6946
|
}
|
|
6647
6947
|
const keySource = flags2?.apiKey ? "cli flag" : process.env.MINDSTUDIO_API_KEY ? "env var" : "config file";
|
|
6648
|
-
|
|
6948
|
+
log11.info("Config resolved", {
|
|
6649
6949
|
baseUrl: baseUrl2,
|
|
6650
6950
|
keySource,
|
|
6651
6951
|
environment: activeEnv
|
|
6652
6952
|
});
|
|
6653
6953
|
return { apiKey, baseUrl: baseUrl2 };
|
|
6654
6954
|
}
|
|
6655
|
-
var
|
|
6955
|
+
var log11, CONFIG_PATH, DEFAULT_BASE_URL;
|
|
6656
6956
|
var init_config = __esm({
|
|
6657
6957
|
"src/config.ts"() {
|
|
6658
6958
|
"use strict";
|
|
6659
6959
|
init_logger();
|
|
6660
|
-
|
|
6661
|
-
CONFIG_PATH =
|
|
6960
|
+
log11 = createLogger("config");
|
|
6961
|
+
CONFIG_PATH = path10.join(
|
|
6662
6962
|
os.homedir(),
|
|
6663
6963
|
".mindstudio-local-tunnel",
|
|
6664
6964
|
"config.json"
|
|
@@ -6668,12 +6968,12 @@ var init_config = __esm({
|
|
|
6668
6968
|
});
|
|
6669
6969
|
|
|
6670
6970
|
// src/toolRegistry.ts
|
|
6671
|
-
var
|
|
6971
|
+
var log12, ToolRegistry;
|
|
6672
6972
|
var init_toolRegistry = __esm({
|
|
6673
6973
|
"src/toolRegistry.ts"() {
|
|
6674
6974
|
"use strict";
|
|
6675
6975
|
init_logger();
|
|
6676
|
-
|
|
6976
|
+
log12 = createLogger("tool-registry");
|
|
6677
6977
|
ToolRegistry = class {
|
|
6678
6978
|
entries = /* @__PURE__ */ new Map();
|
|
6679
6979
|
onEvent;
|
|
@@ -6699,7 +6999,7 @@ var init_toolRegistry = __esm({
|
|
|
6699
6999
|
if (!entry) {
|
|
6700
7000
|
return false;
|
|
6701
7001
|
}
|
|
6702
|
-
|
|
7002
|
+
log12.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
|
|
6703
7003
|
entry.abortController.abort(mode);
|
|
6704
7004
|
if (mode === "graceful") {
|
|
6705
7005
|
const partial = entry.getPartialResult?.() ?? "";
|
|
@@ -6732,7 +7032,7 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
|
6732
7032
|
if (!entry) {
|
|
6733
7033
|
return false;
|
|
6734
7034
|
}
|
|
6735
|
-
|
|
7035
|
+
log12.info("Tool restarted", { toolCallId: id, name: entry.name });
|
|
6736
7036
|
entry.abortController.abort("restart");
|
|
6737
7037
|
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
6738
7038
|
this.onEvent?.({
|
|
@@ -6798,7 +7098,7 @@ async function persistAttachments(attachments) {
|
|
|
6798
7098
|
}
|
|
6799
7099
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
6800
7100
|
await writeFile(localPath, buffer);
|
|
6801
|
-
|
|
7101
|
+
log13.info("Attachment saved", {
|
|
6802
7102
|
filename: name,
|
|
6803
7103
|
path: localPath,
|
|
6804
7104
|
bytes: buffer.length
|
|
@@ -6812,7 +7112,7 @@ async function persistAttachments(attachments) {
|
|
|
6812
7112
|
if (textRes.ok) {
|
|
6813
7113
|
extractedTextPath = `${localPath}.txt`;
|
|
6814
7114
|
await writeFile(extractedTextPath, await textRes.text(), "utf-8");
|
|
6815
|
-
|
|
7115
|
+
log13.info("Extracted text saved", { path: extractedTextPath });
|
|
6816
7116
|
}
|
|
6817
7117
|
} catch {
|
|
6818
7118
|
}
|
|
@@ -6857,12 +7157,12 @@ function buildUploadHeader(results) {
|
|
|
6857
7157
|
return `[Uploaded files]
|
|
6858
7158
|
${lines.join("\n")}`;
|
|
6859
7159
|
}
|
|
6860
|
-
var
|
|
7160
|
+
var log13, UPLOADS_DIR, IMAGE_EXTENSIONS;
|
|
6861
7161
|
var init_attachments = __esm({
|
|
6862
7162
|
"src/headless/attachments.ts"() {
|
|
6863
7163
|
"use strict";
|
|
6864
7164
|
init_logger();
|
|
6865
|
-
|
|
7165
|
+
log13 = createLogger("headless:attachments");
|
|
6866
7166
|
UPLOADS_DIR = "src/.user-uploads";
|
|
6867
7167
|
IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
|
|
6868
7168
|
}
|
|
@@ -7041,7 +7341,7 @@ __export(headless_exports, {
|
|
|
7041
7341
|
HeadlessSession: () => HeadlessSession
|
|
7042
7342
|
});
|
|
7043
7343
|
import { createInterface } from "readline";
|
|
7044
|
-
var
|
|
7344
|
+
var log14, EXTERNAL_TOOL_TIMEOUT_MS, USER_FACING_TOOLS, HeadlessSession;
|
|
7045
7345
|
var init_headless = __esm({
|
|
7046
7346
|
"src/headless/index.ts"() {
|
|
7047
7347
|
"use strict";
|
|
@@ -7050,6 +7350,7 @@ var init_headless = __esm({
|
|
|
7050
7350
|
init_prompt();
|
|
7051
7351
|
init_trigger();
|
|
7052
7352
|
init_compaction();
|
|
7353
|
+
init_trigger2();
|
|
7053
7354
|
init_lsp();
|
|
7054
7355
|
init_agent();
|
|
7055
7356
|
init_session();
|
|
@@ -7060,7 +7361,7 @@ var init_headless = __esm({
|
|
|
7060
7361
|
init_messageQueue();
|
|
7061
7362
|
init_resolve();
|
|
7062
7363
|
init_sentinel();
|
|
7063
|
-
|
|
7364
|
+
log14 = createLogger("headless");
|
|
7064
7365
|
EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
|
|
7065
7366
|
USER_FACING_TOOLS = /* @__PURE__ */ new Set([
|
|
7066
7367
|
"promptUser",
|
|
@@ -7132,6 +7433,7 @@ var init_headless = __esm({
|
|
|
7132
7433
|
...this.queueFields()
|
|
7133
7434
|
});
|
|
7134
7435
|
}
|
|
7436
|
+
triggerBrandExtraction(this.config);
|
|
7135
7437
|
this.toolRegistry.onEvent = this.onEvent;
|
|
7136
7438
|
this.readline = createInterface({ input: process.stdin });
|
|
7137
7439
|
this.readline.on("line", this.handleStdinLine);
|
|
@@ -7231,7 +7533,7 @@ var init_headless = __esm({
|
|
|
7231
7533
|
}
|
|
7232
7534
|
onBackgroundComplete = (toolCallId, name, result, subAgentMessages) => {
|
|
7233
7535
|
this.pendingBlockUpdates.push({ toolCallId, result, subAgentMessages });
|
|
7234
|
-
|
|
7536
|
+
log14.info("Background complete", {
|
|
7235
7537
|
toolCallId,
|
|
7236
7538
|
name,
|
|
7237
7539
|
requestId: this.currentRequestId
|
|
@@ -7452,7 +7754,7 @@ var init_headless = __esm({
|
|
|
7452
7754
|
this.turnStart = Date.now();
|
|
7453
7755
|
const attachments = parsed.attachments;
|
|
7454
7756
|
if (attachments?.length) {
|
|
7455
|
-
|
|
7757
|
+
log14.info("Message has attachments", {
|
|
7456
7758
|
count: attachments.length,
|
|
7457
7759
|
urls: attachments.map((a) => a.url)
|
|
7458
7760
|
});
|
|
@@ -7469,7 +7771,7 @@ var init_headless = __esm({
|
|
|
7469
7771
|
${userMessage}` : header;
|
|
7470
7772
|
}
|
|
7471
7773
|
} catch (err) {
|
|
7472
|
-
|
|
7774
|
+
log14.warn("Attachment persistence failed", { error: err.message });
|
|
7473
7775
|
}
|
|
7474
7776
|
}
|
|
7475
7777
|
let resolved = null;
|
|
@@ -7527,7 +7829,7 @@ ${userMessage}` : header;
|
|
|
7527
7829
|
error: "Turn ended unexpectedly"
|
|
7528
7830
|
});
|
|
7529
7831
|
}
|
|
7530
|
-
|
|
7832
|
+
log14.info("Turn complete", {
|
|
7531
7833
|
requestId,
|
|
7532
7834
|
durationMs: Date.now() - this.turnStart
|
|
7533
7835
|
});
|
|
@@ -7539,7 +7841,7 @@ ${userMessage}` : header;
|
|
|
7539
7841
|
error: err.message
|
|
7540
7842
|
});
|
|
7541
7843
|
}
|
|
7542
|
-
|
|
7844
|
+
log14.warn("Command failed", {
|
|
7543
7845
|
action: "message",
|
|
7544
7846
|
requestId,
|
|
7545
7847
|
error: err.message
|
|
@@ -7674,7 +7976,7 @@ ${userMessage}` : header;
|
|
|
7674
7976
|
return;
|
|
7675
7977
|
}
|
|
7676
7978
|
const { action, requestId } = parsed;
|
|
7677
|
-
|
|
7979
|
+
log14.info("Command received", { action, requestId });
|
|
7678
7980
|
if (action === "tool_result" && parsed.id) {
|
|
7679
7981
|
const id = parsed.id;
|
|
7680
7982
|
const result = parsed.result ?? "";
|
|
@@ -7683,7 +7985,7 @@ ${userMessage}` : header;
|
|
|
7683
7985
|
this.pendingTools.delete(id);
|
|
7684
7986
|
pending.resolve(result);
|
|
7685
7987
|
} else if (!this.running) {
|
|
7686
|
-
|
|
7988
|
+
log14.info("Late tool_result while idle, dismissing", { id });
|
|
7687
7989
|
this.emit("completed", { success: true }, requestId);
|
|
7688
7990
|
} else {
|
|
7689
7991
|
this.earlyResults.set(id, result);
|
|
@@ -7811,8 +8113,8 @@ ${userMessage}` : header;
|
|
|
7811
8113
|
// src/index.tsx
|
|
7812
8114
|
import { render } from "ink";
|
|
7813
8115
|
import os2 from "os";
|
|
7814
|
-
import
|
|
7815
|
-
import
|
|
8116
|
+
import fs21 from "fs";
|
|
8117
|
+
import path11 from "path";
|
|
7816
8118
|
|
|
7817
8119
|
// src/tui/App.tsx
|
|
7818
8120
|
import { useState as useState2, useCallback, useRef } from "react";
|
|
@@ -8130,8 +8432,8 @@ for (let i = 0; i < args.length; i++) {
|
|
|
8130
8432
|
var startupLog = createLogger("startup");
|
|
8131
8433
|
function printDebugInfo(config) {
|
|
8132
8434
|
const pkg = JSON.parse(
|
|
8133
|
-
|
|
8134
|
-
|
|
8435
|
+
fs21.readFileSync(
|
|
8436
|
+
path11.join(import.meta.dirname, "..", "package.json"),
|
|
8135
8437
|
"utf-8"
|
|
8136
8438
|
)
|
|
8137
8439
|
);
|