@mindstudio-ai/remy 0.1.244 → 0.1.246
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/headless.d.ts +4 -1
- package/dist/headless.js +190 -36
- package/dist/index.js +193 -39
- package/dist/prompt/static/team.md +1 -1
- package/dist/subagents/designExpert/prompts/images.md +3 -1
- package/dist/subagents/designExpert/tools/images/enhance-image-prompt.md +0 -6
- package/package.json +1 -1
package/dist/headless.d.ts
CHANGED
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
* page size (default 500, hard cap 2000). Response: {event:"history",
|
|
20
20
|
* messages, startIndex, endIndex, totalMessageCount, ...}. Walk backward by
|
|
21
21
|
* passing the previous response's `startIndex` as the next `before`. When
|
|
22
|
-
* `startIndex === 0`, no older messages remain.
|
|
22
|
+
* `startIndex === 0`, no older messages remain. Indices are GLOBAL — they span
|
|
23
|
+
* the sealed session archives followed by the live tail (see getHistoryPage in
|
|
24
|
+
* session.ts), so scrollback continues past a rotation to the conversation
|
|
25
|
+
* start, not just to the start of the live (post-rotation) array.
|
|
23
26
|
*/
|
|
24
27
|
interface HeadlessOptions {
|
|
25
28
|
apiKey?: string;
|
package/dist/headless.js
CHANGED
|
@@ -1881,11 +1881,11 @@ var setProjectMetadataTool = {
|
|
|
1881
1881
|
},
|
|
1882
1882
|
iconUrl: {
|
|
1883
1883
|
type: "string",
|
|
1884
|
-
description: "URL for the app icon (square."
|
|
1884
|
+
description: "URL for the app icon (square)."
|
|
1885
1885
|
},
|
|
1886
1886
|
openGraphShareImageUrl: {
|
|
1887
1887
|
type: "string",
|
|
1888
|
-
description: "URL for the Open Graph share image (
|
|
1888
|
+
description: "URL for the Open Graph share image (1200\xD7630 PNG)."
|
|
1889
1889
|
}
|
|
1890
1890
|
}
|
|
1891
1891
|
}
|
|
@@ -2819,9 +2819,7 @@ async function analyzeImage(params) {
|
|
|
2819
2819
|
|
|
2820
2820
|
// src/tools/_helpers/screenshot.ts
|
|
2821
2821
|
var SCREENSHOT_ANALYSIS_PROMPT = `Describe everything visible on screen from top to bottom \u2014 every element, its position, its size relative to the viewport, its colors, its content. Be comprehensive, thorough, and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components).`;
|
|
2822
|
-
var
|
|
2823
|
-
|
|
2824
|
-
Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.`;
|
|
2822
|
+
var ANALYSIS_RESPONSE_FORMAT = `Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.`;
|
|
2825
2823
|
function buildScreenshotAnalysisPrompt(opts) {
|
|
2826
2824
|
let p = opts?.prompt || SCREENSHOT_ANALYSIS_PROMPT;
|
|
2827
2825
|
if (opts?.styleMap) {
|
|
@@ -2835,7 +2833,7 @@ ${opts.styleMap}
|
|
|
2835
2833
|
}
|
|
2836
2834
|
p += `
|
|
2837
2835
|
|
|
2838
|
-
${
|
|
2836
|
+
${ANALYSIS_RESPONSE_FORMAT}`;
|
|
2839
2837
|
return p;
|
|
2840
2838
|
}
|
|
2841
2839
|
async function streamScreenshotAnalysis(opts) {
|
|
@@ -2861,6 +2859,9 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
2861
2859
|
let model;
|
|
2862
2860
|
let path13;
|
|
2863
2861
|
let fullPage = true;
|
|
2862
|
+
let width;
|
|
2863
|
+
let height;
|
|
2864
|
+
let format;
|
|
2864
2865
|
if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
|
|
2865
2866
|
prompt = promptOrOptions.prompt;
|
|
2866
2867
|
existingUrl = promptOrOptions.imageUrl;
|
|
@@ -2868,11 +2869,17 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
2868
2869
|
if (promptOrOptions.fullPage !== void 0) {
|
|
2869
2870
|
fullPage = promptOrOptions.fullPage;
|
|
2870
2871
|
}
|
|
2872
|
+
width = promptOrOptions.width;
|
|
2873
|
+
height = promptOrOptions.height;
|
|
2874
|
+
format = promptOrOptions.format;
|
|
2871
2875
|
onLog = promptOrOptions.onLog;
|
|
2872
2876
|
model = promptOrOptions.model;
|
|
2873
2877
|
} else {
|
|
2874
2878
|
prompt = promptOrOptions;
|
|
2875
2879
|
}
|
|
2880
|
+
if (width != null && height != null) {
|
|
2881
|
+
fullPage = false;
|
|
2882
|
+
}
|
|
2876
2883
|
let url;
|
|
2877
2884
|
let styleMap;
|
|
2878
2885
|
if (existingUrl) {
|
|
@@ -2880,7 +2887,12 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
2880
2887
|
} else {
|
|
2881
2888
|
const ssResult = await sidecarRequest(
|
|
2882
2889
|
fullPage ? "/screenshot-full-page" : "/screenshot-viewport",
|
|
2883
|
-
|
|
2890
|
+
{
|
|
2891
|
+
...path13 ? { path: path13 } : {},
|
|
2892
|
+
...width != null ? { width } : {},
|
|
2893
|
+
...height != null ? { height } : {},
|
|
2894
|
+
...format ? { format } : {}
|
|
2895
|
+
},
|
|
2884
2896
|
{ timeout: fullPage ? 12e4 : 3e4 }
|
|
2885
2897
|
);
|
|
2886
2898
|
url = ssResult?.url || ssResult?.screenshotUrl;
|
|
@@ -4009,7 +4021,7 @@ var screenshotTool = {
|
|
|
4009
4021
|
clearable: true,
|
|
4010
4022
|
definition: {
|
|
4011
4023
|
name: "screenshot",
|
|
4012
|
-
description: "Capture a screenshot of the app preview and get a description of what's on screen. Choose `fullPage`: `false` captures just the visible viewport (fast \u2014 for a specific section the page is scrolled to), `true` captures the entire page top-to-bottom (slower \u2014 for overall composition or content past the fold). Captures the settled page state \u2014 it cannot catch animations, transitions, or transient state. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. To ask additional questions about a screenshot you have already captured, pass its URL as imageUrl to skip recapture. If the screenshot requires interaction first (logging in, clicking a tab, dismissing a modal, scrolling to a section), use the instructions param to describe the steps.",
|
|
4024
|
+
description: "Capture a screenshot of the app preview and get a description of what's on screen. Choose `fullPage`: `false` captures just the visible viewport (fast \u2014 for a specific section the page is scrolled to), `true` captures the entire page top-to-bottom (slower \u2014 for overall composition or content past the fold). Captures the settled page state \u2014 it cannot catch animations, transitions, or transient state. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. To ask additional questions about a screenshot you have already captured, pass its URL as imageUrl to skip recapture. If the screenshot requires interaction first (logging in, clicking a tab, dismissing a modal, scrolling to a section), use the instructions param to describe the steps. To render a fixed-size image such as an Open Graph share card, set `width` and `height` (e.g. 1200 \xD7 630) and `format: 'png'`: the tool navigates to `path`, clips to exactly those pixel dimensions, and returns the image URL.",
|
|
4013
4025
|
inputSchema: {
|
|
4014
4026
|
type: "object",
|
|
4015
4027
|
properties: {
|
|
@@ -4029,6 +4041,19 @@ var screenshotTool = {
|
|
|
4029
4041
|
type: "string",
|
|
4030
4042
|
description: 'Navigate to this path before capturing (e.g. "/settings", "/dashboard"). If omitted, screenshots the current page.'
|
|
4031
4043
|
},
|
|
4044
|
+
width: {
|
|
4045
|
+
type: "number",
|
|
4046
|
+
description: "Exact capture width in pixels. Set together with `height` to render a fixed-size image; clips to exactly this viewport instead of the default preview size."
|
|
4047
|
+
},
|
|
4048
|
+
height: {
|
|
4049
|
+
type: "number",
|
|
4050
|
+
description: "Exact capture height in pixels. Set together with `width`."
|
|
4051
|
+
},
|
|
4052
|
+
format: {
|
|
4053
|
+
type: "string",
|
|
4054
|
+
enum: ["png", "jpeg"],
|
|
4055
|
+
description: "Output image format. Defaults to 'jpeg'. Use 'png' for crisp flat graphics like share cards, where JPEG artifacts show on sharp type and edges."
|
|
4056
|
+
},
|
|
4032
4057
|
instructions: {
|
|
4033
4058
|
type: "string",
|
|
4034
4059
|
description: "If the screenshot you need requires interaction first (dismissing a modal, clicking a tab, filling out a form, navigating a flow, scrolling to a section, getting through a login/auth checkpoint), describe the steps to get there. A browser automation agent will follow these instructions, then capture per your `fullPage` choice \u2014 so with `fullPage: false` you can scroll to a section and capture just that viewport. It can bypass auth and get right to where it needs to be if you tell it to authenticate as a test user and give it the path/screen to start its test at. Never describe what names or values to use when applying the instructions - the browser automation agent must use its own values for it to work properly. If a specific auth role is required to access the content, be sure to note that - it can automatically assume it for the purpose of testing. Use only when interaction is required to *reach* the state you want to capture \u2014 log in, dismiss a modal, switch a tab, follow a route, scroll to a section. If your steps are exercising the app's functionality across multiple states (running flows, asserting behavior under interaction, multi-step QA), use `runAutomatedBrowserTest` instead."
|
|
@@ -4071,6 +4096,9 @@ var screenshotTool = {
|
|
|
4071
4096
|
prompt: input.prompt,
|
|
4072
4097
|
path: input.path,
|
|
4073
4098
|
fullPage,
|
|
4099
|
+
width: input.width,
|
|
4100
|
+
height: input.height,
|
|
4101
|
+
format: input.format,
|
|
4074
4102
|
onLog: context?.onLog,
|
|
4075
4103
|
model: resolveModel("imageAnalysis", context?.models, context?.model)
|
|
4076
4104
|
});
|
|
@@ -6281,6 +6309,14 @@ var ARCHIVE_DIR = ".logs/sessions";
|
|
|
6281
6309
|
var ROTATE_THRESHOLD_BYTES = 32 * 1024 * 1024;
|
|
6282
6310
|
var RETAIN_TAIL_BYTES = 16 * 1024 * 1024;
|
|
6283
6311
|
var ARCHIVE_RETENTION_BYTES = 64 * 1024 * 1024;
|
|
6312
|
+
var ARCHIVE_NAME_RE = /^(cleared|rotated)-.*\.json$/;
|
|
6313
|
+
var archiveSortKey = (name) => name.replace(/^(cleared|rotated)-/, "");
|
|
6314
|
+
var ARCHIVE_COUNT_RE = /\.c(\d+)\.json$/;
|
|
6315
|
+
var HISTORY_DEFAULT_LIMIT = 500;
|
|
6316
|
+
var HISTORY_MAX_LIMIT = 2e3;
|
|
6317
|
+
var archiveCountCache = /* @__PURE__ */ new Map();
|
|
6318
|
+
var archiveMsgCache = /* @__PURE__ */ new Map();
|
|
6319
|
+
var ARCHIVE_MSG_CACHE_MAX = 3;
|
|
6284
6320
|
function loadSession(state) {
|
|
6285
6321
|
pruneArchives();
|
|
6286
6322
|
try {
|
|
@@ -6359,31 +6395,34 @@ function buildPayload(state) {
|
|
|
6359
6395
|
function archiveMessages(messages, label, models) {
|
|
6360
6396
|
fs21.mkdirSync(ARCHIVE_DIR, { recursive: true });
|
|
6361
6397
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6362
|
-
|
|
6398
|
+
const count = messages.length;
|
|
6399
|
+
let dest = path11.join(ARCHIVE_DIR, `${label}-${ts}.c${count}.json`);
|
|
6363
6400
|
let n = 1;
|
|
6364
6401
|
while (fs21.existsSync(dest)) {
|
|
6365
|
-
dest = path11.join(ARCHIVE_DIR, `${label}-${ts}-${n++}.json`);
|
|
6402
|
+
dest = path11.join(ARCHIVE_DIR, `${label}-${ts}-${n++}.c${count}.json`);
|
|
6366
6403
|
}
|
|
6367
6404
|
const payload = { messages };
|
|
6368
6405
|
if (models && Object.keys(models).length > 0) {
|
|
6369
6406
|
payload.models = models;
|
|
6370
6407
|
}
|
|
6371
6408
|
fs21.writeFileSync(dest, JSON.stringify(payload), "utf-8");
|
|
6372
|
-
|
|
6409
|
+
archiveCountCache.set(path11.basename(dest), count);
|
|
6410
|
+
log9.info("Session archived", { label, dest, messageCount: count });
|
|
6373
6411
|
pruneArchives();
|
|
6374
6412
|
return dest;
|
|
6375
6413
|
}
|
|
6376
6414
|
function pruneArchives() {
|
|
6377
6415
|
try {
|
|
6378
|
-
const entries = fs21.readdirSync(ARCHIVE_DIR).filter((name) =>
|
|
6416
|
+
const entries = fs21.readdirSync(ARCHIVE_DIR).filter((name) => ARCHIVE_NAME_RE.test(name));
|
|
6379
6417
|
if (entries.length <= 1) {
|
|
6380
6418
|
return;
|
|
6381
6419
|
}
|
|
6382
|
-
const sortKey = (name) => name.replace(/^(cleared|rotated)-/, "");
|
|
6383
6420
|
const archives = entries.map((name) => ({
|
|
6384
6421
|
name,
|
|
6385
6422
|
size: fs21.statSync(path11.join(ARCHIVE_DIR, name)).size
|
|
6386
|
-
})).sort(
|
|
6423
|
+
})).sort(
|
|
6424
|
+
(a, b) => archiveSortKey(b.name).localeCompare(archiveSortKey(a.name))
|
|
6425
|
+
);
|
|
6387
6426
|
let kept = 0;
|
|
6388
6427
|
let cut = archives.length;
|
|
6389
6428
|
for (let i = 0; i < archives.length; i++) {
|
|
@@ -6414,6 +6453,129 @@ function pruneArchives() {
|
|
|
6414
6453
|
} catch {
|
|
6415
6454
|
}
|
|
6416
6455
|
}
|
|
6456
|
+
function parseArchive(name) {
|
|
6457
|
+
const cached3 = archiveMsgCache.get(name);
|
|
6458
|
+
if (cached3) {
|
|
6459
|
+
archiveMsgCache.delete(name);
|
|
6460
|
+
archiveMsgCache.set(name, cached3);
|
|
6461
|
+
return cached3;
|
|
6462
|
+
}
|
|
6463
|
+
try {
|
|
6464
|
+
const raw = fs21.readFileSync(path11.join(ARCHIVE_DIR, name), "utf-8");
|
|
6465
|
+
const data = JSON.parse(raw);
|
|
6466
|
+
const messages = Array.isArray(data?.messages) ? data.messages : [];
|
|
6467
|
+
archiveCountCache.set(name, messages.length);
|
|
6468
|
+
archiveMsgCache.set(name, messages);
|
|
6469
|
+
while (archiveMsgCache.size > ARCHIVE_MSG_CACHE_MAX) {
|
|
6470
|
+
const oldest = archiveMsgCache.keys().next().value;
|
|
6471
|
+
if (oldest === void 0) {
|
|
6472
|
+
break;
|
|
6473
|
+
}
|
|
6474
|
+
archiveMsgCache.delete(oldest);
|
|
6475
|
+
}
|
|
6476
|
+
return messages;
|
|
6477
|
+
} catch (err) {
|
|
6478
|
+
log9.warn("Session archive unreadable", { name, error: err?.message });
|
|
6479
|
+
return null;
|
|
6480
|
+
}
|
|
6481
|
+
}
|
|
6482
|
+
function archiveCount(name) {
|
|
6483
|
+
const cached3 = archiveCountCache.get(name);
|
|
6484
|
+
if (cached3 !== void 0) {
|
|
6485
|
+
return cached3;
|
|
6486
|
+
}
|
|
6487
|
+
const m = ARCHIVE_COUNT_RE.exec(name);
|
|
6488
|
+
if (m) {
|
|
6489
|
+
const n = Number(m[1]);
|
|
6490
|
+
archiveCountCache.set(name, n);
|
|
6491
|
+
return n;
|
|
6492
|
+
}
|
|
6493
|
+
const msgs = parseArchive(name);
|
|
6494
|
+
return msgs ? msgs.length : null;
|
|
6495
|
+
}
|
|
6496
|
+
function readArchiveMessages(name) {
|
|
6497
|
+
return parseArchive(name) ?? [];
|
|
6498
|
+
}
|
|
6499
|
+
function listConversationArchives() {
|
|
6500
|
+
let names;
|
|
6501
|
+
try {
|
|
6502
|
+
names = fs21.readdirSync(ARCHIVE_DIR).filter((n) => ARCHIVE_NAME_RE.test(n));
|
|
6503
|
+
} catch {
|
|
6504
|
+
return { slots: [], archivedCount: 0 };
|
|
6505
|
+
}
|
|
6506
|
+
let maxClearedKey = null;
|
|
6507
|
+
for (const name of names) {
|
|
6508
|
+
if (name.startsWith("cleared-")) {
|
|
6509
|
+
const key = archiveSortKey(name);
|
|
6510
|
+
if (maxClearedKey === null || key > maxClearedKey) {
|
|
6511
|
+
maxClearedKey = key;
|
|
6512
|
+
}
|
|
6513
|
+
}
|
|
6514
|
+
}
|
|
6515
|
+
const rotated = names.filter((n) => n.startsWith("rotated-")).filter((n) => maxClearedKey === null || archiveSortKey(n) > maxClearedKey).sort((a, b) => archiveSortKey(a).localeCompare(archiveSortKey(b)));
|
|
6516
|
+
const slots = [];
|
|
6517
|
+
let offset = 0;
|
|
6518
|
+
for (const name of rotated) {
|
|
6519
|
+
const count = archiveCount(name);
|
|
6520
|
+
if (count === null || count <= 0) {
|
|
6521
|
+
continue;
|
|
6522
|
+
}
|
|
6523
|
+
slots.push({ name, count, offset });
|
|
6524
|
+
offset += count;
|
|
6525
|
+
}
|
|
6526
|
+
return { slots, archivedCount: offset };
|
|
6527
|
+
}
|
|
6528
|
+
function getHistoryPage(state, opts) {
|
|
6529
|
+
const { slots, archivedCount } = listConversationArchives();
|
|
6530
|
+
const liveLen = state.messages.length;
|
|
6531
|
+
const total = archivedCount + liveLen;
|
|
6532
|
+
const rawLimit = opts?.limit;
|
|
6533
|
+
const limit = typeof rawLimit === "number" && Number.isFinite(rawLimit) ? Math.min(Math.max(1, rawLimit | 0), HISTORY_MAX_LIMIT) : HISTORY_DEFAULT_LIMIT;
|
|
6534
|
+
const rawBefore = opts?.before;
|
|
6535
|
+
const before = typeof rawBefore === "number" && Number.isFinite(rawBefore) ? Math.max(0, Math.min(rawBefore | 0, total)) : total;
|
|
6536
|
+
const peekGlobal = (i) => {
|
|
6537
|
+
if (i >= archivedCount) {
|
|
6538
|
+
return state.messages[i - archivedCount];
|
|
6539
|
+
}
|
|
6540
|
+
for (const slot of slots) {
|
|
6541
|
+
if (i < slot.offset + slot.count) {
|
|
6542
|
+
return readArchiveMessages(slot.name)[i - slot.offset];
|
|
6543
|
+
}
|
|
6544
|
+
}
|
|
6545
|
+
return void 0;
|
|
6546
|
+
};
|
|
6547
|
+
let startIndex = Math.max(0, before - limit);
|
|
6548
|
+
while (startIndex > 0) {
|
|
6549
|
+
const msg = peekGlobal(startIndex);
|
|
6550
|
+
if (msg && msg.role === "user" && msg.toolCallId) {
|
|
6551
|
+
startIndex--;
|
|
6552
|
+
} else {
|
|
6553
|
+
break;
|
|
6554
|
+
}
|
|
6555
|
+
}
|
|
6556
|
+
const endIndex = before;
|
|
6557
|
+
const messages = [];
|
|
6558
|
+
for (const slot of slots) {
|
|
6559
|
+
const slotEnd = slot.offset + slot.count;
|
|
6560
|
+
if (slotEnd <= startIndex || slot.offset >= endIndex) {
|
|
6561
|
+
continue;
|
|
6562
|
+
}
|
|
6563
|
+
const from = Math.max(startIndex, slot.offset) - slot.offset;
|
|
6564
|
+
const to = Math.min(endIndex, slotEnd) - slot.offset;
|
|
6565
|
+
const msgs = readArchiveMessages(slot.name);
|
|
6566
|
+
for (let i = from; i < to; i++) {
|
|
6567
|
+
messages.push(msgs[i]);
|
|
6568
|
+
}
|
|
6569
|
+
}
|
|
6570
|
+
if (endIndex > archivedCount) {
|
|
6571
|
+
const from = Math.max(startIndex, archivedCount) - archivedCount;
|
|
6572
|
+
const to = endIndex - archivedCount;
|
|
6573
|
+
for (let i = from; i < to; i++) {
|
|
6574
|
+
messages.push(state.messages[i]);
|
|
6575
|
+
}
|
|
6576
|
+
}
|
|
6577
|
+
return { messages, startIndex, endIndex, totalMessageCount: total };
|
|
6578
|
+
}
|
|
6417
6579
|
function rotate(state) {
|
|
6418
6580
|
const messages = state.messages;
|
|
6419
6581
|
if (messages.length === 0) {
|
|
@@ -7951,8 +8113,6 @@ var USER_FACING_TOOLS = /* @__PURE__ */ new Set([
|
|
|
7951
8113
|
"presentPublishPlan"
|
|
7952
8114
|
]);
|
|
7953
8115
|
var FORCED_COMPACTION_THRESHOLD_TOKENS = 85e4;
|
|
7954
|
-
var HISTORY_DEFAULT_LIMIT = 500;
|
|
7955
|
-
var HISTORY_MAX_LIMIT = 2e3;
|
|
7956
8116
|
var HeadlessSession = class {
|
|
7957
8117
|
// Configuration
|
|
7958
8118
|
opts;
|
|
@@ -8698,30 +8858,24 @@ var HeadlessSession = class {
|
|
|
8698
8858
|
}
|
|
8699
8859
|
if (action === "get_history") {
|
|
8700
8860
|
this.applyPendingBlockUpdates();
|
|
8701
|
-
const
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
const before = typeof rawBefore === "number" && Number.isFinite(rawBefore) ? Math.max(0, Math.min(rawBefore | 0, total)) : total;
|
|
8706
|
-
let startIndex = Math.max(0, before - limit);
|
|
8707
|
-
while (startIndex > 0 && this.state.messages[startIndex].role === "user" && this.state.messages[startIndex].toolCallId) {
|
|
8708
|
-
startIndex--;
|
|
8709
|
-
}
|
|
8710
|
-
const endIndex = before;
|
|
8861
|
+
const page = getHistoryPage(this.state, {
|
|
8862
|
+
...typeof parsed.before === "number" ? { before: parsed.before } : {},
|
|
8863
|
+
...typeof parsed.limit === "number" ? { limit: parsed.limit } : {}
|
|
8864
|
+
});
|
|
8711
8865
|
log15.info("History response", {
|
|
8712
8866
|
requestId,
|
|
8713
|
-
startIndex,
|
|
8714
|
-
endIndex,
|
|
8715
|
-
count: endIndex - startIndex,
|
|
8716
|
-
totalMessageCount:
|
|
8717
|
-
beforeParam:
|
|
8718
|
-
limitParam:
|
|
8867
|
+
startIndex: page.startIndex,
|
|
8868
|
+
endIndex: page.endIndex,
|
|
8869
|
+
count: page.endIndex - page.startIndex,
|
|
8870
|
+
totalMessageCount: page.totalMessageCount,
|
|
8871
|
+
beforeParam: parsed.before,
|
|
8872
|
+
limitParam: parsed.limit
|
|
8719
8873
|
});
|
|
8720
8874
|
this.dispatchSimple(requestId, "history", () => ({
|
|
8721
|
-
messages:
|
|
8722
|
-
startIndex,
|
|
8723
|
-
endIndex,
|
|
8724
|
-
totalMessageCount:
|
|
8875
|
+
messages: page.messages,
|
|
8876
|
+
startIndex: page.startIndex,
|
|
8877
|
+
endIndex: page.endIndex,
|
|
8878
|
+
totalMessageCount: page.totalMessageCount,
|
|
8725
8879
|
running: this.running,
|
|
8726
8880
|
...this.running && this.currentRequestId ? { currentRequestId: this.currentRequestId } : {},
|
|
8727
8881
|
...this.state.models && { models: this.state.models },
|
package/dist/index.js
CHANGED
|
@@ -1462,11 +1462,11 @@ var init_setProjectMetadata = __esm({
|
|
|
1462
1462
|
},
|
|
1463
1463
|
iconUrl: {
|
|
1464
1464
|
type: "string",
|
|
1465
|
-
description: "URL for the app icon (square."
|
|
1465
|
+
description: "URL for the app icon (square)."
|
|
1466
1466
|
},
|
|
1467
1467
|
openGraphShareImageUrl: {
|
|
1468
1468
|
type: "string",
|
|
1469
|
-
description: "URL for the Open Graph share image (
|
|
1469
|
+
description: "URL for the Open Graph share image (1200\xD7630 PNG)."
|
|
1470
1470
|
}
|
|
1471
1471
|
}
|
|
1472
1472
|
}
|
|
@@ -2314,31 +2314,34 @@ function buildPayload(state) {
|
|
|
2314
2314
|
function archiveMessages(messages, label, models) {
|
|
2315
2315
|
fs10.mkdirSync(ARCHIVE_DIR, { recursive: true });
|
|
2316
2316
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2317
|
-
|
|
2317
|
+
const count = messages.length;
|
|
2318
|
+
let dest = path4.join(ARCHIVE_DIR, `${label}-${ts}.c${count}.json`);
|
|
2318
2319
|
let n = 1;
|
|
2319
2320
|
while (fs10.existsSync(dest)) {
|
|
2320
|
-
dest = path4.join(ARCHIVE_DIR, `${label}-${ts}-${n++}.json`);
|
|
2321
|
+
dest = path4.join(ARCHIVE_DIR, `${label}-${ts}-${n++}.c${count}.json`);
|
|
2321
2322
|
}
|
|
2322
2323
|
const payload = { messages };
|
|
2323
2324
|
if (models && Object.keys(models).length > 0) {
|
|
2324
2325
|
payload.models = models;
|
|
2325
2326
|
}
|
|
2326
2327
|
fs10.writeFileSync(dest, JSON.stringify(payload), "utf-8");
|
|
2327
|
-
|
|
2328
|
+
archiveCountCache.set(path4.basename(dest), count);
|
|
2329
|
+
log3.info("Session archived", { label, dest, messageCount: count });
|
|
2328
2330
|
pruneArchives();
|
|
2329
2331
|
return dest;
|
|
2330
2332
|
}
|
|
2331
2333
|
function pruneArchives() {
|
|
2332
2334
|
try {
|
|
2333
|
-
const entries = fs10.readdirSync(ARCHIVE_DIR).filter((name) =>
|
|
2335
|
+
const entries = fs10.readdirSync(ARCHIVE_DIR).filter((name) => ARCHIVE_NAME_RE.test(name));
|
|
2334
2336
|
if (entries.length <= 1) {
|
|
2335
2337
|
return;
|
|
2336
2338
|
}
|
|
2337
|
-
const sortKey = (name) => name.replace(/^(cleared|rotated)-/, "");
|
|
2338
2339
|
const archives = entries.map((name) => ({
|
|
2339
2340
|
name,
|
|
2340
2341
|
size: fs10.statSync(path4.join(ARCHIVE_DIR, name)).size
|
|
2341
|
-
})).sort(
|
|
2342
|
+
})).sort(
|
|
2343
|
+
(a, b) => archiveSortKey(b.name).localeCompare(archiveSortKey(a.name))
|
|
2344
|
+
);
|
|
2342
2345
|
let kept = 0;
|
|
2343
2346
|
let cut = archives.length;
|
|
2344
2347
|
for (let i = 0; i < archives.length; i++) {
|
|
@@ -2369,6 +2372,129 @@ function pruneArchives() {
|
|
|
2369
2372
|
} catch {
|
|
2370
2373
|
}
|
|
2371
2374
|
}
|
|
2375
|
+
function parseArchive(name) {
|
|
2376
|
+
const cached3 = archiveMsgCache.get(name);
|
|
2377
|
+
if (cached3) {
|
|
2378
|
+
archiveMsgCache.delete(name);
|
|
2379
|
+
archiveMsgCache.set(name, cached3);
|
|
2380
|
+
return cached3;
|
|
2381
|
+
}
|
|
2382
|
+
try {
|
|
2383
|
+
const raw = fs10.readFileSync(path4.join(ARCHIVE_DIR, name), "utf-8");
|
|
2384
|
+
const data = JSON.parse(raw);
|
|
2385
|
+
const messages = Array.isArray(data?.messages) ? data.messages : [];
|
|
2386
|
+
archiveCountCache.set(name, messages.length);
|
|
2387
|
+
archiveMsgCache.set(name, messages);
|
|
2388
|
+
while (archiveMsgCache.size > ARCHIVE_MSG_CACHE_MAX) {
|
|
2389
|
+
const oldest = archiveMsgCache.keys().next().value;
|
|
2390
|
+
if (oldest === void 0) {
|
|
2391
|
+
break;
|
|
2392
|
+
}
|
|
2393
|
+
archiveMsgCache.delete(oldest);
|
|
2394
|
+
}
|
|
2395
|
+
return messages;
|
|
2396
|
+
} catch (err) {
|
|
2397
|
+
log3.warn("Session archive unreadable", { name, error: err?.message });
|
|
2398
|
+
return null;
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
function archiveCount(name) {
|
|
2402
|
+
const cached3 = archiveCountCache.get(name);
|
|
2403
|
+
if (cached3 !== void 0) {
|
|
2404
|
+
return cached3;
|
|
2405
|
+
}
|
|
2406
|
+
const m = ARCHIVE_COUNT_RE.exec(name);
|
|
2407
|
+
if (m) {
|
|
2408
|
+
const n = Number(m[1]);
|
|
2409
|
+
archiveCountCache.set(name, n);
|
|
2410
|
+
return n;
|
|
2411
|
+
}
|
|
2412
|
+
const msgs = parseArchive(name);
|
|
2413
|
+
return msgs ? msgs.length : null;
|
|
2414
|
+
}
|
|
2415
|
+
function readArchiveMessages(name) {
|
|
2416
|
+
return parseArchive(name) ?? [];
|
|
2417
|
+
}
|
|
2418
|
+
function listConversationArchives() {
|
|
2419
|
+
let names;
|
|
2420
|
+
try {
|
|
2421
|
+
names = fs10.readdirSync(ARCHIVE_DIR).filter((n) => ARCHIVE_NAME_RE.test(n));
|
|
2422
|
+
} catch {
|
|
2423
|
+
return { slots: [], archivedCount: 0 };
|
|
2424
|
+
}
|
|
2425
|
+
let maxClearedKey = null;
|
|
2426
|
+
for (const name of names) {
|
|
2427
|
+
if (name.startsWith("cleared-")) {
|
|
2428
|
+
const key = archiveSortKey(name);
|
|
2429
|
+
if (maxClearedKey === null || key > maxClearedKey) {
|
|
2430
|
+
maxClearedKey = key;
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
const rotated = names.filter((n) => n.startsWith("rotated-")).filter((n) => maxClearedKey === null || archiveSortKey(n) > maxClearedKey).sort((a, b) => archiveSortKey(a).localeCompare(archiveSortKey(b)));
|
|
2435
|
+
const slots = [];
|
|
2436
|
+
let offset = 0;
|
|
2437
|
+
for (const name of rotated) {
|
|
2438
|
+
const count = archiveCount(name);
|
|
2439
|
+
if (count === null || count <= 0) {
|
|
2440
|
+
continue;
|
|
2441
|
+
}
|
|
2442
|
+
slots.push({ name, count, offset });
|
|
2443
|
+
offset += count;
|
|
2444
|
+
}
|
|
2445
|
+
return { slots, archivedCount: offset };
|
|
2446
|
+
}
|
|
2447
|
+
function getHistoryPage(state, opts) {
|
|
2448
|
+
const { slots, archivedCount } = listConversationArchives();
|
|
2449
|
+
const liveLen = state.messages.length;
|
|
2450
|
+
const total = archivedCount + liveLen;
|
|
2451
|
+
const rawLimit = opts?.limit;
|
|
2452
|
+
const limit = typeof rawLimit === "number" && Number.isFinite(rawLimit) ? Math.min(Math.max(1, rawLimit | 0), HISTORY_MAX_LIMIT) : HISTORY_DEFAULT_LIMIT;
|
|
2453
|
+
const rawBefore = opts?.before;
|
|
2454
|
+
const before = typeof rawBefore === "number" && Number.isFinite(rawBefore) ? Math.max(0, Math.min(rawBefore | 0, total)) : total;
|
|
2455
|
+
const peekGlobal = (i) => {
|
|
2456
|
+
if (i >= archivedCount) {
|
|
2457
|
+
return state.messages[i - archivedCount];
|
|
2458
|
+
}
|
|
2459
|
+
for (const slot of slots) {
|
|
2460
|
+
if (i < slot.offset + slot.count) {
|
|
2461
|
+
return readArchiveMessages(slot.name)[i - slot.offset];
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
return void 0;
|
|
2465
|
+
};
|
|
2466
|
+
let startIndex = Math.max(0, before - limit);
|
|
2467
|
+
while (startIndex > 0) {
|
|
2468
|
+
const msg = peekGlobal(startIndex);
|
|
2469
|
+
if (msg && msg.role === "user" && msg.toolCallId) {
|
|
2470
|
+
startIndex--;
|
|
2471
|
+
} else {
|
|
2472
|
+
break;
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
const endIndex = before;
|
|
2476
|
+
const messages = [];
|
|
2477
|
+
for (const slot of slots) {
|
|
2478
|
+
const slotEnd = slot.offset + slot.count;
|
|
2479
|
+
if (slotEnd <= startIndex || slot.offset >= endIndex) {
|
|
2480
|
+
continue;
|
|
2481
|
+
}
|
|
2482
|
+
const from = Math.max(startIndex, slot.offset) - slot.offset;
|
|
2483
|
+
const to = Math.min(endIndex, slotEnd) - slot.offset;
|
|
2484
|
+
const msgs = readArchiveMessages(slot.name);
|
|
2485
|
+
for (let i = from; i < to; i++) {
|
|
2486
|
+
messages.push(msgs[i]);
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
if (endIndex > archivedCount) {
|
|
2490
|
+
const from = Math.max(startIndex, archivedCount) - archivedCount;
|
|
2491
|
+
const to = endIndex - archivedCount;
|
|
2492
|
+
for (let i = from; i < to; i++) {
|
|
2493
|
+
messages.push(state.messages[i]);
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
return { messages, startIndex, endIndex, totalMessageCount: total };
|
|
2497
|
+
}
|
|
2372
2498
|
function rotate(state) {
|
|
2373
2499
|
const messages = state.messages;
|
|
2374
2500
|
if (messages.length === 0) {
|
|
@@ -2428,7 +2554,7 @@ function clearSession(state) {
|
|
|
2428
2554
|
});
|
|
2429
2555
|
}
|
|
2430
2556
|
}
|
|
2431
|
-
var log3, SESSION_FILE, ARCHIVE_DIR, ROTATE_THRESHOLD_BYTES, RETAIN_TAIL_BYTES, ARCHIVE_RETENTION_BYTES;
|
|
2557
|
+
var log3, SESSION_FILE, ARCHIVE_DIR, ROTATE_THRESHOLD_BYTES, RETAIN_TAIL_BYTES, ARCHIVE_RETENTION_BYTES, ARCHIVE_NAME_RE, archiveSortKey, ARCHIVE_COUNT_RE, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT, archiveCountCache, archiveMsgCache, ARCHIVE_MSG_CACHE_MAX;
|
|
2432
2558
|
var init_session = __esm({
|
|
2433
2559
|
"src/session.ts"() {
|
|
2434
2560
|
"use strict";
|
|
@@ -2442,6 +2568,14 @@ var init_session = __esm({
|
|
|
2442
2568
|
ROTATE_THRESHOLD_BYTES = 32 * 1024 * 1024;
|
|
2443
2569
|
RETAIN_TAIL_BYTES = 16 * 1024 * 1024;
|
|
2444
2570
|
ARCHIVE_RETENTION_BYTES = 64 * 1024 * 1024;
|
|
2571
|
+
ARCHIVE_NAME_RE = /^(cleared|rotated)-.*\.json$/;
|
|
2572
|
+
archiveSortKey = (name) => name.replace(/^(cleared|rotated)-/, "");
|
|
2573
|
+
ARCHIVE_COUNT_RE = /\.c(\d+)\.json$/;
|
|
2574
|
+
HISTORY_DEFAULT_LIMIT = 500;
|
|
2575
|
+
HISTORY_MAX_LIMIT = 2e3;
|
|
2576
|
+
archiveCountCache = /* @__PURE__ */ new Map();
|
|
2577
|
+
archiveMsgCache = /* @__PURE__ */ new Map();
|
|
2578
|
+
ARCHIVE_MSG_CACHE_MAX = 3;
|
|
2445
2579
|
}
|
|
2446
2580
|
});
|
|
2447
2581
|
|
|
@@ -3555,7 +3689,7 @@ ${opts.styleMap}
|
|
|
3555
3689
|
}
|
|
3556
3690
|
p += `
|
|
3557
3691
|
|
|
3558
|
-
${
|
|
3692
|
+
${ANALYSIS_RESPONSE_FORMAT}`;
|
|
3559
3693
|
return p;
|
|
3560
3694
|
}
|
|
3561
3695
|
async function streamScreenshotAnalysis(opts) {
|
|
@@ -3581,6 +3715,9 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
3581
3715
|
let model;
|
|
3582
3716
|
let path14;
|
|
3583
3717
|
let fullPage = true;
|
|
3718
|
+
let width;
|
|
3719
|
+
let height;
|
|
3720
|
+
let format;
|
|
3584
3721
|
if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
|
|
3585
3722
|
prompt = promptOrOptions.prompt;
|
|
3586
3723
|
existingUrl = promptOrOptions.imageUrl;
|
|
@@ -3588,11 +3725,17 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
3588
3725
|
if (promptOrOptions.fullPage !== void 0) {
|
|
3589
3726
|
fullPage = promptOrOptions.fullPage;
|
|
3590
3727
|
}
|
|
3728
|
+
width = promptOrOptions.width;
|
|
3729
|
+
height = promptOrOptions.height;
|
|
3730
|
+
format = promptOrOptions.format;
|
|
3591
3731
|
onLog = promptOrOptions.onLog;
|
|
3592
3732
|
model = promptOrOptions.model;
|
|
3593
3733
|
} else {
|
|
3594
3734
|
prompt = promptOrOptions;
|
|
3595
3735
|
}
|
|
3736
|
+
if (width != null && height != null) {
|
|
3737
|
+
fullPage = false;
|
|
3738
|
+
}
|
|
3596
3739
|
let url;
|
|
3597
3740
|
let styleMap;
|
|
3598
3741
|
if (existingUrl) {
|
|
@@ -3600,7 +3743,12 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
3600
3743
|
} else {
|
|
3601
3744
|
const ssResult = await sidecarRequest(
|
|
3602
3745
|
fullPage ? "/screenshot-full-page" : "/screenshot-viewport",
|
|
3603
|
-
|
|
3746
|
+
{
|
|
3747
|
+
...path14 ? { path: path14 } : {},
|
|
3748
|
+
...width != null ? { width } : {},
|
|
3749
|
+
...height != null ? { height } : {},
|
|
3750
|
+
...format ? { format } : {}
|
|
3751
|
+
},
|
|
3604
3752
|
{ timeout: fullPage ? 12e4 : 3e4 }
|
|
3605
3753
|
);
|
|
3606
3754
|
url = ssResult?.url || ssResult?.screenshotUrl;
|
|
@@ -3627,16 +3775,14 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
|
|
|
3627
3775
|
model
|
|
3628
3776
|
});
|
|
3629
3777
|
}
|
|
3630
|
-
var SCREENSHOT_ANALYSIS_PROMPT,
|
|
3778
|
+
var SCREENSHOT_ANALYSIS_PROMPT, ANALYSIS_RESPONSE_FORMAT;
|
|
3631
3779
|
var init_screenshot = __esm({
|
|
3632
3780
|
"src/tools/_helpers/screenshot.ts"() {
|
|
3633
3781
|
"use strict";
|
|
3634
3782
|
init_sidecar();
|
|
3635
3783
|
init_analyzeImage();
|
|
3636
3784
|
SCREENSHOT_ANALYSIS_PROMPT = `Describe everything visible on screen from top to bottom \u2014 every element, its position, its size relative to the viewport, its colors, its content. Be comprehensive, thorough, and spatial. After the inventory, note anything that looks visually broken (overlapping elements, clipped text, misaligned components).`;
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.`;
|
|
3785
|
+
ANALYSIS_RESPONSE_FORMAT = `Respond only with your analysis as Markdown and absolutely no other text. Do not use emojis - use unicode if you need symbols.`;
|
|
3640
3786
|
}
|
|
3641
3787
|
});
|
|
3642
3788
|
|
|
@@ -4622,7 +4768,7 @@ var init_screenshot2 = __esm({
|
|
|
4622
4768
|
clearable: true,
|
|
4623
4769
|
definition: {
|
|
4624
4770
|
name: "screenshot",
|
|
4625
|
-
description: "Capture a screenshot of the app preview and get a description of what's on screen. Choose `fullPage`: `false` captures just the visible viewport (fast \u2014 for a specific section the page is scrolled to), `true` captures the entire page top-to-bottom (slower \u2014 for overall composition or content past the fold). Captures the settled page state \u2014 it cannot catch animations, transitions, or transient state. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. To ask additional questions about a screenshot you have already captured, pass its URL as imageUrl to skip recapture. If the screenshot requires interaction first (logging in, clicking a tab, dismissing a modal, scrolling to a section), use the instructions param to describe the steps.",
|
|
4771
|
+
description: "Capture a screenshot of the app preview and get a description of what's on screen. Choose `fullPage`: `false` captures just the visible viewport (fast \u2014 for a specific section the page is scrolled to), `true` captures the entire page top-to-bottom (slower \u2014 for overall composition or content past the fold). Captures the settled page state \u2014 it cannot catch animations, transitions, or transient state. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. To ask additional questions about a screenshot you have already captured, pass its URL as imageUrl to skip recapture. If the screenshot requires interaction first (logging in, clicking a tab, dismissing a modal, scrolling to a section), use the instructions param to describe the steps. To render a fixed-size image such as an Open Graph share card, set `width` and `height` (e.g. 1200 \xD7 630) and `format: 'png'`: the tool navigates to `path`, clips to exactly those pixel dimensions, and returns the image URL.",
|
|
4626
4772
|
inputSchema: {
|
|
4627
4773
|
type: "object",
|
|
4628
4774
|
properties: {
|
|
@@ -4642,6 +4788,19 @@ var init_screenshot2 = __esm({
|
|
|
4642
4788
|
type: "string",
|
|
4643
4789
|
description: 'Navigate to this path before capturing (e.g. "/settings", "/dashboard"). If omitted, screenshots the current page.'
|
|
4644
4790
|
},
|
|
4791
|
+
width: {
|
|
4792
|
+
type: "number",
|
|
4793
|
+
description: "Exact capture width in pixels. Set together with `height` to render a fixed-size image; clips to exactly this viewport instead of the default preview size."
|
|
4794
|
+
},
|
|
4795
|
+
height: {
|
|
4796
|
+
type: "number",
|
|
4797
|
+
description: "Exact capture height in pixels. Set together with `width`."
|
|
4798
|
+
},
|
|
4799
|
+
format: {
|
|
4800
|
+
type: "string",
|
|
4801
|
+
enum: ["png", "jpeg"],
|
|
4802
|
+
description: "Output image format. Defaults to 'jpeg'. Use 'png' for crisp flat graphics like share cards, where JPEG artifacts show on sharp type and edges."
|
|
4803
|
+
},
|
|
4645
4804
|
instructions: {
|
|
4646
4805
|
type: "string",
|
|
4647
4806
|
description: "If the screenshot you need requires interaction first (dismissing a modal, clicking a tab, filling out a form, navigating a flow, scrolling to a section, getting through a login/auth checkpoint), describe the steps to get there. A browser automation agent will follow these instructions, then capture per your `fullPage` choice \u2014 so with `fullPage: false` you can scroll to a section and capture just that viewport. It can bypass auth and get right to where it needs to be if you tell it to authenticate as a test user and give it the path/screen to start its test at. Never describe what names or values to use when applying the instructions - the browser automation agent must use its own values for it to work properly. If a specific auth role is required to access the content, be sure to note that - it can automatically assume it for the purpose of testing. Use only when interaction is required to *reach* the state you want to capture \u2014 log in, dismiss a modal, switch a tab, follow a route, scroll to a section. If your steps are exercising the app's functionality across multiple states (running flows, asserting behavior under interaction, multi-step QA), use `runAutomatedBrowserTest` instead."
|
|
@@ -4684,6 +4843,9 @@ var init_screenshot2 = __esm({
|
|
|
4684
4843
|
prompt: input.prompt,
|
|
4685
4844
|
path: input.path,
|
|
4686
4845
|
fullPage,
|
|
4846
|
+
width: input.width,
|
|
4847
|
+
height: input.height,
|
|
4848
|
+
format: input.format,
|
|
4687
4849
|
onLog: context?.onLog,
|
|
4688
4850
|
model: resolveModel("imageAnalysis", context?.models, context?.model)
|
|
4689
4851
|
});
|
|
@@ -8811,7 +8973,7 @@ var headless_exports = {};
|
|
|
8811
8973
|
__export(headless_exports, {
|
|
8812
8974
|
HeadlessSession: () => HeadlessSession
|
|
8813
8975
|
});
|
|
8814
|
-
var log15, EXTERNAL_TOOL_TIMEOUT_MS, USER_FACING_TOOLS, FORCED_COMPACTION_THRESHOLD_TOKENS,
|
|
8976
|
+
var log15, EXTERNAL_TOOL_TIMEOUT_MS, USER_FACING_TOOLS, FORCED_COMPACTION_THRESHOLD_TOKENS, HeadlessSession;
|
|
8815
8977
|
var init_headless = __esm({
|
|
8816
8978
|
"src/headless/index.ts"() {
|
|
8817
8979
|
"use strict";
|
|
@@ -8840,8 +9002,6 @@ var init_headless = __esm({
|
|
|
8840
9002
|
"presentPublishPlan"
|
|
8841
9003
|
]);
|
|
8842
9004
|
FORCED_COMPACTION_THRESHOLD_TOKENS = 85e4;
|
|
8843
|
-
HISTORY_DEFAULT_LIMIT = 500;
|
|
8844
|
-
HISTORY_MAX_LIMIT = 2e3;
|
|
8845
9005
|
HeadlessSession = class {
|
|
8846
9006
|
// Configuration
|
|
8847
9007
|
opts;
|
|
@@ -9587,30 +9747,24 @@ var init_headless = __esm({
|
|
|
9587
9747
|
}
|
|
9588
9748
|
if (action === "get_history") {
|
|
9589
9749
|
this.applyPendingBlockUpdates();
|
|
9590
|
-
const
|
|
9591
|
-
|
|
9592
|
-
|
|
9593
|
-
|
|
9594
|
-
const before = typeof rawBefore === "number" && Number.isFinite(rawBefore) ? Math.max(0, Math.min(rawBefore | 0, total)) : total;
|
|
9595
|
-
let startIndex = Math.max(0, before - limit);
|
|
9596
|
-
while (startIndex > 0 && this.state.messages[startIndex].role === "user" && this.state.messages[startIndex].toolCallId) {
|
|
9597
|
-
startIndex--;
|
|
9598
|
-
}
|
|
9599
|
-
const endIndex = before;
|
|
9750
|
+
const page = getHistoryPage(this.state, {
|
|
9751
|
+
...typeof parsed.before === "number" ? { before: parsed.before } : {},
|
|
9752
|
+
...typeof parsed.limit === "number" ? { limit: parsed.limit } : {}
|
|
9753
|
+
});
|
|
9600
9754
|
log15.info("History response", {
|
|
9601
9755
|
requestId,
|
|
9602
|
-
startIndex,
|
|
9603
|
-
endIndex,
|
|
9604
|
-
count: endIndex - startIndex,
|
|
9605
|
-
totalMessageCount:
|
|
9606
|
-
beforeParam:
|
|
9607
|
-
limitParam:
|
|
9756
|
+
startIndex: page.startIndex,
|
|
9757
|
+
endIndex: page.endIndex,
|
|
9758
|
+
count: page.endIndex - page.startIndex,
|
|
9759
|
+
totalMessageCount: page.totalMessageCount,
|
|
9760
|
+
beforeParam: parsed.before,
|
|
9761
|
+
limitParam: parsed.limit
|
|
9608
9762
|
});
|
|
9609
9763
|
this.dispatchSimple(requestId, "history", () => ({
|
|
9610
|
-
messages:
|
|
9611
|
-
startIndex,
|
|
9612
|
-
endIndex,
|
|
9613
|
-
totalMessageCount:
|
|
9764
|
+
messages: page.messages,
|
|
9765
|
+
startIndex: page.startIndex,
|
|
9766
|
+
endIndex: page.endIndex,
|
|
9767
|
+
totalMessageCount: page.totalMessageCount,
|
|
9614
9768
|
running: this.running,
|
|
9615
9769
|
...this.running && this.currentRequestId ? { currentRequestId: this.currentRequestId } : {},
|
|
9616
9770
|
...this.state.models && { models: this.state.models },
|
|
@@ -76,6 +76,6 @@ When you receive background results:
|
|
|
76
76
|
|
|
77
77
|
You can only background the following two tasks, unless the user specifically asks you to do work in the background:
|
|
78
78
|
- `productVision` seeding the intiial roadmap after writing the spec for the first time or updating the roadmap after large work sessions. This task takes a while and we can allow the user to continue building while it happens in the background.
|
|
79
|
-
- After writing the spec, once you have finalized the shape of the app, ask `visualDesignExpert` to create an icon and an
|
|
79
|
+
- After writing the spec, once you have finalized the shape of the app, ask `visualDesignExpert` to create an icon and to design an Open Graph share image, then set them with `setProjectMetadata` alongside the app's name and short description. The icon is a generated asset. The share image should be composed as a self-contained HTML card (real lockup, brand fonts, exact type) and captured — not generated: serve the card HTML from `dist/interfaces/web/public/`, screenshot it with the `screenshot` tool at `width: 1200, height: 630, format: 'png'`, and save the resulting PNG into the app's public assets so the deployed site hosts it.
|
|
80
80
|
|
|
81
81
|
Do not background any other tasks. Be aware that sometimes tools like specSync will background on their own - this is not something that is within your control.
|
|
@@ -77,7 +77,9 @@ Keep logos and icons consistent - if you already have a logo, use `editImages` t
|
|
|
77
77
|
|
|
78
78
|
#### Open Graph Sharing Images
|
|
79
79
|
|
|
80
|
-
OG images show up in iMessage, Slack, Twitter, etc. at small sizes. They're a mood piece, not a messaging opportunity. Keep text minimal: the app name and at most a short tagline (three to five words). Think App Store feature card — one beautiful composition that makes someone want to tap. The text should feel integrated into the scene, not pasted on a background.
|
|
80
|
+
OG images show up in iMessage, Slack, Twitter, etc. at small sizes. They're a mood piece, not a messaging opportunity. Keep text minimal: the app name and at most a short tagline (three to five words). Think App Store feature card — one beautiful composition that makes someone want to tap. The text should feel integrated into the scene, not pasted on a background.
|
|
81
|
+
|
|
82
|
+
A share card is a wordmark, a short line, and a logo on a brand field — **compose it as HTML, don't generate it with the image model.** A generated image gives you odd letterforms and no brand fidelity; HTML gives you the real SVG lockup, the actual brand fonts, exact colors, and pixel-perfect spacing. Author a self-contained HTML document sized to 1200 × 630 — the lockup inline as SVG, the brand fonts inlined as base64 (so nothing loads late and captures as a fallback), the palette and type exact — then capture it with the `screenshot` tool at `width: 1200, height: 630, format: 'png'`, a faithful real-browser render. Don't route it through a document/HTML-to-image "openGraph" render mode; that pipeline strips CSS backgrounds. The same compose-and-capture approach beats generation for any precise brand graphic where letterforms and spacing carry the design.
|
|
81
83
|
|
|
82
84
|
### When to use images
|
|
83
85
|
|
|
@@ -69,12 +69,6 @@ For app icons and logos, the goal is something that reads clearly at phone home
|
|
|
69
69
|
- You must specify that the image is full bleed - never say anything about rounded corners or there is a high likelihood that the image will come back as a rounded rectangle on a white background!
|
|
70
70
|
- Apply the same material/lighting/color density as photography prompts, just to a single object. Describe the surface finish ("high-gloss lacquered finish with clean specular highlights," "soft matte ceramic with subtle surface texture"), the lighting behavior ("warm directional light from upper left producing a bright highlight streak across the curved surface and a soft shadow beneath"), and color as relationships ("deep coral body graduating to warm peach at the highlight edge, with a cream accent on the lens element"). Generic descriptors like "clean surfaces, soft lighting" produce generic icons.
|
|
71
71
|
|
|
72
|
-
#### Open Graph Sharing Images
|
|
73
|
-
|
|
74
|
-
OG images are often a user's first impression of the app — they show up in iMessage, Slack, Twitter, etc. at small sizes. Keep text minimal: the app name and at most a short tagline (three to five words). This is a mood piece, not a messaging opportunity. Think App Store feature card — one beautiful composition that makes someone want to tap.
|
|
75
|
-
|
|
76
|
-
Apply the same material/lighting/color density as photography prompts. The text should feel integrated into the scene — typeset within the composition, not pasted on top. Describe the typography treatment (weight, size, color, position) as part of the overall image, and describe how the background interacts with the text (glow, depth, contrast). The whole image should read as one cohesive graphic, not layers.
|
|
77
|
-
|
|
78
72
|
## Output
|
|
79
73
|
|
|
80
74
|
Respond with ONLY the enhanced prompt. Be detailed and specific — a good prompt is a dense paragraph of 80-150 words that paints a complete picture: style, subject, materials, lighting behavior, color relationships, atmosphere, and composition. Terse prompts produce generic images.
|