page-agent-sdk 4.5.0 → 4.7.0
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/README.md +27 -4
- package/README.zh-CN.md +25 -4
- package/dist/page-agent-sdk.headless.js +558 -389
- package/dist/page-agent-sdk.iife.js +2835 -66
- package/dist/page-agent-sdk.js +1961 -1796
- package/dist/page-agent-sdk.legacy.js +14064 -13899
- package/dist/page-agent-sdk.umd.cjs +2821 -52
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/style.css +1 -1
- package/types/headless.d.ts +7 -0
- package/types/index.d.ts +7 -0
|
@@ -3024,7 +3024,7 @@ function Sa(e) {
|
|
|
3024
3024
|
}
|
|
3025
3025
|
function Ca(e, t) {
|
|
3026
3026
|
let n = t || {};
|
|
3027
|
-
if (e === "query_data") return
|
|
3027
|
+
if (e === "query_data") return (Array.isArray(n.queries) && n.queries.length ? n.queries.map(String) : [String(n.expr || "")]).map((e) => Sa(e));
|
|
3028
3028
|
if (e === "search_data") return [pa];
|
|
3029
3029
|
let r = [];
|
|
3030
3030
|
if (typeof n.jsonPath == "string" && n.jsonPath && r.push(n.jsonPath), Array.isArray(n.jsonPaths)) for (let e of n.jsonPaths) typeof e == "string" && e && r.push(e);
|
|
@@ -3072,7 +3072,7 @@ function wa(e) {
|
|
|
3072
3072
|
}
|
|
3073
3073
|
function Ta(e, t, n, r) {
|
|
3074
3074
|
let i = e.map(ya).join("、"), a = [...new Set(n.flatMap((e) => e.paths))].map(ya).join("、"), o = [`⏱[过期快照] 此前读取 ${i} 的结果已失效(${r === void 0 ? `已写入 ${a}` : `第 ${r} 轮写入了 ${a}`})。`];
|
|
3075
|
-
return n.some((e) => e.hasPostValue) ? o.push(`该轮写入结果已含 ${a} 最新值与新 hash
|
|
3075
|
+
return n.some((e) => e.hasPostValue) ? o.push(`该轮写入结果已含 ${a} 最新值与新 hash,直接引用即可;原读取结果整体已过期,未提及路径需引用时按下方指引重读/重跑。`) : o.push("原读取结果整体已过期(含未触及的兄弟路径),需引用时按下方指引重读/重跑。"), t === "query_data" || t === "search_data" ? o.push(`需当前结果时重跑 ${t}(表达式/条件不变即可)。`) : o.push(`需当前精确值时再 read(建议窄读:${i})。`), o.join("\n");
|
|
3076
3076
|
}
|
|
3077
3077
|
function Ea(e, t, n = {}) {
|
|
3078
3078
|
let r = n.maxParallelTools ?? 1, i = t.map((e) => wa(e)).filter((e) => e !== null);
|
|
@@ -4777,17 +4777,18 @@ var ds = {
|
|
|
4777
4777
|
subagent: !1
|
|
4778
4778
|
} }
|
|
4779
4779
|
};
|
|
4780
|
-
function fs(e, t = "code") {
|
|
4781
|
-
let
|
|
4780
|
+
function fs(e, t = "code", n = !0) {
|
|
4781
|
+
let r = `use_${e}`;
|
|
4782
4782
|
return [
|
|
4783
|
-
`【先判意图(防长对话误路由)】先判断用户是「问」还是「要生成/改」:**提问类**(这是什么组件/怎么用/某字段什么意思/为什么这样/解释代码)→ 用 read / list_components / rag_component_docs **直接回答**,绝不委派 ${
|
|
4784
|
-
`【多方案征询=先文本】用户要「出几套/两套方案我来选」「给几个选择」「先出方案看看」→ **本轮只出文本方案**(每套一两句:布局/配色/交互差异),经 request_human_confirmation 的 options 让用户点选(或等用户回复选定);**选定后才**委派 ${
|
|
4785
|
-
`【执行纪律(最重要)】收到代码组件任务后:**新建类直接委派**(追加组件无需通读/翻页现有数组,子 agent 自会采样一个组件看结构;**新建组件由子 agent 全权创建** —— 它自己把整个组件(含 ${t})写进数组,你不需要也不能替它创建/追加/落地代码)→ 修改类才 read 定位(by name)→ 调 ${
|
|
4783
|
+
`【先判意图(防长对话误路由)】先判断用户是「问」还是「要生成/改」:**提问类**(这是什么组件/怎么用/某字段什么意思/为什么这样/解释代码)→ 用 read / list_components / rag_component_docs **直接回答**,绝不委派 ${r};**生成/修改类**(做一个/生成/改成/加一个 XX 组件)才按下面纪律委派 ${r}。长对话里上文全是生成记录时尤须警惕,不要把新问题当成延续旧任务。`,
|
|
4784
|
+
`【多方案征询=先文本】用户要「出几套/两套方案我来选」「给几个选择」「先出方案看看」→ **本轮只出文本方案**(每套一两句:布局/配色/交互差异),经 request_human_confirmation 的 options 让用户点选(或等用户回复选定);**选定后才**委派 ${r} 建所选方案。禁止先并发多个子 agent 把每套方案都生成出来再让用户挑(子 agent 产物当不了选项清单,且烧 token;真 LLM 实测踩过:「出两套方案」被路由成两个 use_${e} 委派)。`,
|
|
4785
|
+
`【执行纪律(最重要)】收到代码组件任务后:**新建类直接委派**(追加组件无需通读/翻页现有数组,子 agent 自会采样一个组件看结构;**新建组件由子 agent 全权创建** —— 它自己把整个组件(含 ${t})写进数组,你不需要也不能替它创建/追加/落地代码)→ 修改类才 read 定位(by name)→ 调 ${r} 委派 → read 核对 → 回复用户。**中间绝不输出过渡性文字** —— 「我先看看…」「稍后委派」「接下来我会…」这类计划性回复等于任务没做就结束了(回复即本轮终止);所有调研和委派在本轮内连续完成,完成后才回复总结。`,
|
|
4786
4786
|
"【产物形态】每个组件是完整、自包含的 HTML 页面(含 style/script,可独立成页);你只负责委派和收尾,不关心宿主如何渲染(v-html / iframe / SFC 是集成方的事)。",
|
|
4787
|
-
`【主 agent 职责边界(硬规则)】禁止直接 read/write 代码组件的 ${t} 字段(read 只得 <code Nkb> 摘要没用;write 绕过 vfs/verify 危险)—— 生成/修改/排查代码一律经 ${
|
|
4788
|
-
`【多组件委派(防上下文污染)】一次要多个组件:① write_todos 列出(每项 name + 要点)② **不同组件可在同一轮并行发多个 ${
|
|
4789
|
-
`【委派 task 规格化(收窄子 agent 决策,防开放任务致装饰穷举)】委派 ${
|
|
4790
|
-
`【委派失败重试】${
|
|
4787
|
+
`【主 agent 职责边界(硬规则)】禁止直接 read/write 代码组件的 ${t} 字段(read 只得 <code Nkb> 摘要没用;write 绕过 vfs/verify 危险)—— 生成/修改/排查代码一律经 ${r},收尾 read 核对。**委派返回即已落地**:${r} 的结论说「已创建组件 X」= 组件已在数组里,**不要再 write/append 一遍**(子 agent 已自己创建,你再写 = 造出重复组件,还得花轮次删;真 LLM 实测踩过:主 agent 把返回的 code 又追加了一次 → 索引 8/9 重复)。核对方式 = read 该数组尾部(确认新组件存在 + name 唯一),仅此而已。`,
|
|
4788
|
+
`【多组件委派(防上下文污染)】一次要多个组件:① write_todos 列出(每项 name + 要点)② **不同组件可在同一轮并行发多个 ${r} 委派**(每组件一次 ${r},task 只写该组件 name + 要点 + 主题/风格;每次委派是独立子 agent 实例,互不共享上下文);**同一组件同一时间只能有一个委派在途**,勿对同一组件同轮发两个委派 ③ 委派返回后逐个 read 核对(确认已生成 + 名称对)+ update_todo 标完成 ④ 主题/风格在每次 task 里转述(每个子 agent 全新上下文,不知其他组件)。一次 task 塞多个组件仍禁止(同一子 agent 共享上下文生成多个 → class/样式冲突污染);主 agent 自己的 write(普通组件属性)可与委派同轮发出,不必等委派返回。`,
|
|
4789
|
+
`【委派 task 规格化(收窄子 agent 决策,防开放任务致装饰穷举)】委派 ${r} 的 task 必须含:① 组件定位(by name)② 视觉风格(配色/质感/字体;**给 1-2 个具体视觉锚** —— 主色 hex **取自平台 UI/设计规范 skill 的定义值**(有规范类 skill 先 load 再引用其 hex,勿凭页面观察自造近似色;无规范才自定,如「金黄 #F7C948」)、主体占比(如「杯高约画布 60%」)或装饰密度(如「仅 2 类背景装饰」)—— 细节空间收窄,子 agent 不在装饰细节上展开推演)③ 内容(文案/数据/图)④ 交互意图(动效/状态/触发)⑤ 历史偏好(可选):聊天上下文中有与该组件相关的用户历史偏好/反馈(如「用户偏好深色系」「上轮嫌动画太快」),提炼一句附 task 末尾(新子 agent 无记忆,全靠 task);**不含技术实现**(SVG vs CSS / keyframes vs transition 归子 agent 选)。❌「生成啤酒杯动画」→ ✅「啤酒杯倒酒(beer):金黄啤酒 #F7C948 从上方倒入透明杯(杯高约画布 60%),深绿背景,液体循环下落 2s,hover 杯子放大」。规格简练(4 要素各半句),远省子 agent 思考 token。`,
|
|
4790
|
+
`【委派失败重试】${r} 返回乱码/内部标记(如 <|DSML|)/空结论或报错 = 子 agent 异常,**重新委派一次**(task 附「上次失败,这次先把代码写短些/分步」);连续两次失败才降级告知用户。**不要因此自己直接 write/edit ${t} 字段**(绕过 vfs/格式校验,且你拿不到规范全文)。`,
|
|
4791
|
+
...n ? ["【设计品味引导】子 agent 内置 web-design-engineer 设计 skill(设计系统先声明/反 AI 俗套/25 风格配方/5 维自评;**挂在子 agent 侧,你自己 load 不到也不必尝试**)。委派 task 的视觉锚可直接引用配方名 —— linear / apple-hig / muji / pentagram / bloomberg-terminal / aesop 等(如「Linear 风格:暖深色+发丝线」),子 agent 会按名取配方参数照做;用户未指定风格时勿写「随便/好看就行」,给一条具体方向(流派或参考品牌),子 agent 按其 skill 流程选配方并在结论里声明设计系统与假设。"] : [],
|
|
4791
4792
|
"【预算将尽暂停】组件很多、接近工具轮次上限不要硬扛:完成手头这个后报告「已生成 K/N,还剩 M 个」,等用户确认后从 todos 剩余项继续(勿重复已完成)。"
|
|
4792
4793
|
].join("\n");
|
|
4793
4794
|
}
|
|
@@ -8731,7 +8732,7 @@ function ad(e, n = {}) {
|
|
|
8731
8732
|
e.writeCapable = t;
|
|
8732
8733
|
}, oe = r(async () => [`说明: ${l}`, "格式: 写入值需为 JSON,且通过声明的 schema 校验(校验失败时 write 会返回结构化错误,含具体字段与期望类型)。"].join("\n"), {
|
|
8733
8734
|
name: "describe_data",
|
|
8734
|
-
description: "
|
|
8735
|
+
description: "获取主数据的说明与格式要求(等价于 read 不传 jsonPath;优先用 read 单一入口)。",
|
|
8735
8736
|
schema: t.object({})
|
|
8736
8737
|
}), L = r(async ({ id: e }, t) => {
|
|
8737
8738
|
let n = y(t);
|
|
@@ -8759,8 +8760,8 @@ function ad(e, n = {}) {
|
|
|
8759
8760
|
});
|
|
8760
8761
|
}, {
|
|
8761
8762
|
name: "restore_data",
|
|
8762
|
-
description: "把主数据回退到某个快照(
|
|
8763
|
-
schema: t.object({ id: t.number().int().optional().describe("
|
|
8763
|
+
description: "把主数据回退到某个快照(不传 id 回退最近一次)。快照列表用 history_data({list:true})。",
|
|
8764
|
+
schema: t.object({ id: t.number().int().optional().describe("快照序号;不传回退最近一次") })
|
|
8764
8765
|
});
|
|
8765
8766
|
I(L);
|
|
8766
8767
|
let se = r(async ({ id: e, jsonPath: t, list: n }) => {
|
|
@@ -8802,43 +8803,54 @@ function ad(e, n = {}) {
|
|
|
8802
8803
|
return `快照 #${r.id}[${r.op}]${r.label ? `(${r.label})` : ""}${a ? ` @ ${a}` : ""} = ${$(i)}`;
|
|
8803
8804
|
}, {
|
|
8804
8805
|
name: "history_data",
|
|
8805
|
-
description: "查看主数据快照(
|
|
8806
|
+
description: "查看主数据快照(只读不回退):list:true 列时间线;传 id 看某次内容(默认最近),jsonPath 可只看子路径。对比差异用 diff_data;回退用 restore_data。",
|
|
8806
8807
|
schema: t.object({
|
|
8807
|
-
id: t.number().int().optional().describe("
|
|
8808
|
-
jsonPath: t.string().optional().describe("只看快照的某子路径
|
|
8809
|
-
list: t.boolean().optional().describe("true
|
|
8808
|
+
id: t.number().int().optional().describe("快照序号;不传看最近一次(list:true 时忽略)"),
|
|
8809
|
+
jsonPath: t.string().optional().describe("只看快照的某子路径"),
|
|
8810
|
+
list: t.boolean().optional().describe("true 列出快照时间线(序号/操作/标签/时间/大小)")
|
|
8810
8811
|
})
|
|
8811
|
-
}), ce = r(async ({ expr: e,
|
|
8812
|
-
let
|
|
8812
|
+
}), ce = r(async ({ expr: e, queries: t, limit: n }, r) => {
|
|
8813
|
+
let i = y(r);
|
|
8813
8814
|
if (typeof c != "object" || !c) return Z({
|
|
8814
8815
|
code: "NOT_OBJECT",
|
|
8815
8816
|
message: `主数据不是对象/数组,无法查询(当前为 ${c === void 0 ? "undefined" : typeof c})`,
|
|
8816
8817
|
hint: "query 仅适用于对象/数组;叶子用 read 读"
|
|
8817
8818
|
});
|
|
8818
|
-
let
|
|
8819
|
-
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8819
|
+
let a = u ? Zo(c, s) : c, o = b(r), l = (e) => {
|
|
8820
|
+
let t;
|
|
8821
|
+
try {
|
|
8822
|
+
t = ca(a, e);
|
|
8823
|
+
} catch (t) {
|
|
8824
|
+
return Z({
|
|
8825
|
+
code: "JSONPATH_SYNTAX",
|
|
8826
|
+
message: `JSONPath 解析错误: ${t.message}`,
|
|
8827
|
+
hint: "语法子集:$ .key [n] [\"key\"] [*] [?(filter)] ..key ..*;filter:@.field op literal,&&/||/();对象根需先点出数组字段再过滤,如 $.components[?(@.x>1)]",
|
|
8828
|
+
details: { expr: e }
|
|
8829
|
+
});
|
|
8830
|
+
}
|
|
8831
|
+
let r = n ?? 50, s = t.slice(0, r), c = s.map((e) => `{"path":${JSON.stringify(e.path)},"index":${e.index === void 0 ? "null" : e.index},"value":${$(ed(e.value, i === "", re, j, {
|
|
8832
|
+
rootExempt: !1,
|
|
8833
|
+
rootPath: String(e.path ?? ""),
|
|
8834
|
+
fullTextPrefixes: o,
|
|
8835
|
+
onSummarized: ee
|
|
8836
|
+
}))}}`);
|
|
8837
|
+
return `{"matched":${t.length},"returned":${s.length},"truncated":${t.length > r},"results":[${c.join(",")}]}`;
|
|
8838
|
+
};
|
|
8839
|
+
return t && t.length ? `{"batch":true,"results":[${t.map((e) => {
|
|
8840
|
+
let t = l(e);
|
|
8841
|
+
return t.startsWith("ERROR:") ? `{"expr":${JSON.stringify(e)},"ok":false,"error":${JSON.stringify(t)}}` : `{"expr":${JSON.stringify(e)},"ok":true,${t.slice(1)}`;
|
|
8842
|
+
}).join(",")}]}` : e === void 0 ? Z({
|
|
8843
|
+
code: "SCHEMA_INVALID",
|
|
8844
|
+
message: "query_data 需传 expr(单条 JSONPath)或 queries(2-10 条批量),两者都没传",
|
|
8845
|
+
hint: "单表达式用 expr;多条件筛选一次取回用 queries"
|
|
8846
|
+
}) : l(e);
|
|
8836
8847
|
}, {
|
|
8837
8848
|
name: "query_data",
|
|
8838
|
-
description: "用 JSONPath 查询主数据(只读):$ 根/.key/[n]/[*]/[?(==/!=/</<=/>/>=,&&/||)]/..key 递归。返回匹配元素 path/index/value(path 可作 write patch 的 jsonPath)
|
|
8849
|
+
description: "用 JSONPath 查询主数据(只读):$ 根/.key/[n]/[*]/[?(==/!=/</<=/>/>=,&&/||)]/..key 递归。返回匹配元素 path/index/value(path 可作 write patch 的 jsonPath);大数组筛选定位用它;多条件筛选传 queries 数组一次取回。",
|
|
8839
8850
|
schema: t.object({
|
|
8840
|
-
expr: t.string().describe("JSONPath 表达式,如 $.components[?(@.type==\"card\" && @.price<100)] 或 $..title(递归找所有 title)"),
|
|
8841
|
-
|
|
8851
|
+
expr: t.string().optional().describe("单条 JSONPath 表达式,如 $.components[?(@.type==\"card\" && @.price<100)] 或 $..title(递归找所有 title)"),
|
|
8852
|
+
queries: t.array(t.string()).min(2).max(10).optional().describe("批量模式:2-10 条 JSONPath 一次取回(与 expr 同传按 queries);逐条独立返回,单条失败该项标 error 不整批"),
|
|
8853
|
+
limit: t.number().int().min(1).max(200).optional().describe("返回结果上限(批量时逐条适用),默认 50")
|
|
8842
8854
|
})
|
|
8843
8855
|
}), le = r(async ({ query: e, mode: t, fuzzyThreshold: n, matchKey: r, limit: i }) => {
|
|
8844
8856
|
if (c == null) return Z({
|
|
@@ -9015,11 +9027,11 @@ function ad(e, n = {}) {
|
|
|
9015
9027
|
});
|
|
9016
9028
|
}, {
|
|
9017
9029
|
name: "eval_script",
|
|
9018
|
-
description: "在 Worker 沙箱对主数据跑自定义 JS(
|
|
9030
|
+
description: "在 Worker 沙箱对主数据跑自定义 JS(无网络,超时 3s)。入参 data(深拷贝);mode:query 只读回给 LLM/transform 校验后落地(完整新值或 {patches:[…]} 增量);jsonPath 可限定子树。",
|
|
9019
9031
|
schema: t.object({
|
|
9020
|
-
script: t.string().describe("JS
|
|
9021
|
-
mode: t.enum(["query", "transform"]).optional().describe("query=只读返回结果(默认)
|
|
9022
|
-
jsonPath: t.string().optional().describe("
|
|
9032
|
+
script: t.string().describe("JS 脚本(入参 data;末尾表达式或 return 即返回值),如 data.filter(c=>c.stock>0)"),
|
|
9033
|
+
mode: t.enum(["query", "transform"]).optional().describe("query=只读返回结果(默认)/transform=校验后落地为新值"),
|
|
9034
|
+
jsonPath: t.string().optional().describe("子树模式:仅对该子树执行;transform 返回值作为子树新值")
|
|
9023
9035
|
})
|
|
9024
9036
|
});
|
|
9025
9037
|
I(ue, (e) => e?.mode === "transform");
|
|
@@ -9104,14 +9116,14 @@ function ad(e, n = {}) {
|
|
|
9104
9116
|
return v === void 0 ? `${x}主数据${h ? ` @ ${h}` : ""}${T} = (undefined) (hash=${p})` : `${x}主数据${h ? ` @ ${h}` : ""}${T} = ${$(v)} (hash=${p})`;
|
|
9105
9117
|
}, {
|
|
9106
9118
|
name: "read",
|
|
9107
|
-
description: "
|
|
9119
|
+
description: "读主数据(只读):不传 jsonPath 返回整体说明+格式;传 jsonPath 返回该路径当前值+hash;传 jsonPaths 一次读多个不相关路径。fields/depth 裁剪、offset/limit 数组分页减体积;详细规则见系统提示。",
|
|
9108
9120
|
schema: t.object({
|
|
9109
|
-
jsonPath: t.string().optional().describe("要读的子路径(
|
|
9110
|
-
jsonPaths: t.array(t.string()).optional().describe("
|
|
9111
|
-
fields: t.array(t.string()).optional().describe("
|
|
9112
|
-
depth: t.number().int().min(0).optional().describe("
|
|
9113
|
-
offset: t.number().int().min(0).optional().describe("数组分页起始偏移(
|
|
9114
|
-
limit: t.number().int().min(1).max(200).optional().describe("数组分页每页条数(默认 50,上限 200
|
|
9121
|
+
jsonPath: t.string().optional().describe("要读的子路径(如 components.0.text);不传读整体+说明"),
|
|
9122
|
+
jsonPaths: t.array(t.string()).optional().describe("多路径一次读(与 jsonPath 互斥,优先);非法路径单项标错不整批"),
|
|
9123
|
+
fields: t.array(t.string()).optional().describe("只返回指定字段(投影减体积),如 [\"id\",\"title\"]"),
|
|
9124
|
+
depth: t.number().int().min(0).optional().describe("嵌套深度限制(0=只根占位,逐层截断减体积)"),
|
|
9125
|
+
offset: t.number().int().min(0).optional().describe("数组分页起始偏移(仅数组目标,默认 0)"),
|
|
9126
|
+
limit: t.number().int().min(1).max(200).optional().describe("数组分页每页条数(默认 50,上限 200),返回切片+total/hasMore")
|
|
9115
9127
|
})
|
|
9116
9128
|
}), de = r(async ({ value: e, patch: t, patches: n, del: r, dryRun: a }, l) => {
|
|
9117
9129
|
let d = y(l), f = "set";
|
|
@@ -9230,9 +9242,9 @@ function ad(e, n = {}) {
|
|
|
9230
9242
|
}
|
|
9231
9243
|
}, {
|
|
9232
9244
|
name: "write",
|
|
9233
|
-
description: "
|
|
9245
|
+
description: "写主数据(唯一写入口;自动 schema 校验+乐观锁+快照)。四意图:整体替换 {value}/增量 {patch:{op,jsonPath,value}}(op set/remove/merge/append/move)/原子批 {patches:[…]}(任一失败回滚)/删子路径 {patch:{jsonPath},del:true};dryRun 预检;详细规则见系统提示。",
|
|
9234
9246
|
schema: t.object({
|
|
9235
|
-
value: t.unknown().optional().describe("JSON 对象(
|
|
9247
|
+
value: t.unknown().optional().describe("JSON 对象(推荐)或 JSON 字符串;set 整体或 patch 的 set/merge/append 必填"),
|
|
9236
9248
|
patch: t.object({
|
|
9237
9249
|
op: t.enum([
|
|
9238
9250
|
"set",
|
|
@@ -9240,10 +9252,10 @@ function ad(e, n = {}) {
|
|
|
9240
9252
|
"merge",
|
|
9241
9253
|
"append",
|
|
9242
9254
|
"move"
|
|
9243
|
-
]).optional().describe("
|
|
9244
|
-
jsonPath: t.string().optional().describe("相对主数据根的点号路径(如 components.0.text);set/remove 必填,merge/append
|
|
9245
|
-
value: t.unknown().optional().describe("该 patch 的值(JSON
|
|
9246
|
-
}).optional().describe("单个增量编辑(自带 value
|
|
9255
|
+
]).optional().describe("set 设值/remove 删/merge 合并对象/append 追加数组/move 移动元素(value=目标路径字符串);缺省 set"),
|
|
9256
|
+
jsonPath: t.string().optional().describe("相对主数据根的点号路径(如 components.0.text);set/remove 必填,merge/append 不填作用于根"),
|
|
9257
|
+
value: t.unknown().optional().describe("该 patch 的值(JSON 或 JSON 字符串);与顶层 value 二选一,优先 patch.value")
|
|
9258
|
+
}).optional().describe("单个增量编辑(自带 value)"),
|
|
9247
9259
|
patches: t.array(t.object({
|
|
9248
9260
|
op: t.enum([
|
|
9249
9261
|
"set",
|
|
@@ -9252,11 +9264,11 @@ function ad(e, n = {}) {
|
|
|
9252
9264
|
"append",
|
|
9253
9265
|
"move"
|
|
9254
9266
|
]),
|
|
9255
|
-
jsonPath: t.string().optional().describe("相对主数据根的点号路径;set/remove 必填,merge/append
|
|
9256
|
-
value: t.unknown().optional().describe("JSON 值(推荐直传)或 JSON 字符串;set/merge/append
|
|
9257
|
-
})).optional().describe("
|
|
9267
|
+
jsonPath: t.string().optional().describe("相对主数据根的点号路径;set/remove 必填,merge/append 不填作用于根"),
|
|
9268
|
+
value: t.unknown().optional().describe("JSON 值(推荐直传)或 JSON 字符串;set/merge/append 必填")
|
|
9269
|
+
})).optional().describe("批量原子编辑:一次提交多个 patch,任一失败整体不写入;适合一次改多处"),
|
|
9258
9270
|
del: t.boolean().optional().describe("true 则删除 patch.jsonPath 指定的子路径"),
|
|
9259
|
-
dryRun: t.boolean().optional().describe("
|
|
9271
|
+
dryRun: t.boolean().optional().describe("预检:走完整校验链但不落盘不入快照;冲突返回 VERSION_CONFLICT 不挂起")
|
|
9260
9272
|
})
|
|
9261
9273
|
});
|
|
9262
9274
|
I(de);
|
|
@@ -10236,9 +10248,166 @@ function Wd(e) {
|
|
|
10236
10248
|
}), t = JSON.stringify(e)), t;
|
|
10237
10249
|
}
|
|
10238
10250
|
//#endregion
|
|
10251
|
+
//#region src/core/sdk/designSkill/skillDoc.ts
|
|
10252
|
+
var Gd = "# Web Design Engineer\n\nThis skill positions the Agent as a top-tier design engineer who crafts elegant, refined Web artifacts using HTML/CSS/JavaScript/React. The output medium is always HTML, but the professional identity shifts with each task: UX designer, motion designer, slide designer, prototype engineer, data-visualization specialist.\n\nCore philosophy: **The bar is \"stunning,\" not \"functional.\" Every pixel is intentional, every interaction is deliberate. Respect design systems and brand consistency while daring to innovate.**\n\n---\n\n## Scope\n\n✅ **Applicable**: Visual front-end deliverables (pages / prototypes / slide decks / visualizations / animations / UI mockups / design systems)\n\n❌ **Not applicable**: Back-end APIs, CLI tools, data-processing scripts, pure logic development with no visual requirements, performance tuning, and other terminal tasks\n\n---\n\n## Host Environment (page-agent-sdk adaptation)\n\n> Read this first: it reinterprets every user-interaction instruction below. This guide was written for an interactive agent; you run as a **delegated sub-agent** inside page-agent-sdk. The adaptations:\n\n- **One-shot delegation**: you receive a spec'd `task`, run to completion, and return a single final report. You **cannot converse with the user mid-run**. Wherever this guide says \"ask the user\", \"wait for confirmation\", or \"checkpoint\" (Step 1 probing, Checkpoints 1–3, ambiguous feedback): pick the **conservative, defensible default**, note the assumption, keep building, and list all assumptions in your **closing report** so the main agent / user can course-correct on the next delegation.\n- **Facts**: no network tools. The delegation `task` is authoritative; see adapted Step 0.\n- **Output form**: each component is **complete, self-contained HTML** — inline `<style>` / `<script>` blocks (centralized, extractable), renderable standalone; external CDN JS/CSS only when the task calls for it. Code lands through your code-asset workflow (vfs working copy / write) and must pass `validate_code` before you finish.\n- **Scale**: usually **one component per delegation**, not a multi-file project. Treat file-management guidance below (descriptive filenames, v2 copies, `assets/` dirs, `brand-spec.md`) as moot; React patterns are kept from upstream for reference — **default to vanilla HTML/CSS/JS**, zero build steps.\n- **v0 / checkpoints**: \"show a v0 early\" translates to: state your design-system declaration (Step 3 block, condensed) in the closing report — not a mid-run pause.\n- **Verification**: you cannot open a browser console — the framework's `validate_code` / render check covers structural verification; run the Step-7 checklist mentally against the code you wrote.\n\n---\n\n## Workflow\n\n### Step 0: Verify Facts Before Anything Else\n\n> **SDK adaptation**: you have no network tools — skip the upstream WebSearch protocol. The delegation `task` is your source of facts. If it names a specific product / brand / version / dated event you cannot verify from the task or context, **do not assert specifics from training data**: implement the stated intent and flag the unverified spec in your closing report (e.g. \"assumed v2 pricing — unverified\"). Never fabricate specs, versions, release status, or stats.\n\n### Step 1: Understand the Requirements (judge the gap, then default deliberately)\n\nYou cannot ask the user — a delegation that stops to ask wastes the whole run. Instead, judge **how large the information gap** is, fill every gap with a **conservative, high-quality default**, and report the assumptions:\n\n- Task already spec'd (visual + content + interaction, like a good delegation task) → **no gap: follow it exactly**; don't re-design around it.\n- Turning an existing component / screenshot into a variant → small gap: only intent may be unclear — default conservatively, match the existing visual vocabulary.\n- \"Make it look great\" with no tone / audience / context → large gap: pick the school + recipe deliberately (Step 2 priority list below), state every assumption.\n- Genuinely vague (\"something nice\", \"I don't know what style\") → run the **Design Direction Advisor** below internally: pick 3 differentiated directions, implement the most conservative one that fits the task, mention the two alternatives in your closing report.\n\nKey areas to default consciously (mirror of upstream probing list):\n- **Product context**: What product? Target users? Existing design system / brand guidelines?\n- **Output type**: Web page / prototype / slide deck / animation / dashboard? Fidelity level?\n- **Variation dimensions**: Which dimensions should variants explore — layout, color, interaction, copy? How many?\n- **Constraints**: Responsive breakpoints? Dark/light mode? Accessibility? Fixed dimensions?\n\n### Step 2: Gather Design Context (by priority)\n\nGood design is rooted in existing context. **Never start from thin air.** Priority order:\n\n1. **Resources the user proactively provides** (screenshots / Figma / codebase / UI Kit / design system) → read them thoroughly and extract tokens\n2. **Existing pages of the user's product** → match their visual vocabulary (see \"When Adding to an Existing UI\" below)\n3. **Industry best practices** → ask which brands or products to use as reference\n4. **User names an anchor** (\"make it Linear-style\" / \"Aesop feeling\" / \"MUJI quietness\") → read the single recipe file at `references/style-recipes/<anchor>.md` (e.g., `references/style-recipes/linear.md`). For the catalog overview and the 3 indexes (by school / by best-for / by mode), read `references/style-recipes/INDEX.md` first.\n5. **Starting from scratch** → establish a temporary system based on industry best practices, or pick a recipe from `references/style-recipes/` (browse via `INDEX.md`) — and state the choice as an assumption in your closing report\n\nWhen analyzing reference materials, focus on: color system, typography scheme, spacing system, border-radius strategy, shadow hierarchy, motion style, component density, copywriting tone.\n\n> **Code ≫ Screenshots**: When the user provides both a codebase and screenshots, invest your effort in reading source code and extracting design tokens rather than guessing from screenshots — rebuilding/editing an interface from code yields far higher quality than from screenshots.\n\n#### When the Task Involves a Specific Brand — Asset Protocol\n\n**Asset > Spec.** A brand's identity is \"being recognized.\" Recognition is driven by assets in this order — **not by hex codes**:\n\n| Asset | Recognition contribution | When required |\n|---|---|---|\n| **Logo** (SVG / PNG, both light & dark variants if available) | Highest — any brand is identified by its logo | **Any brand task** — non-negotiable |\n| **Product imagery** (hero shots, detail, in-context) | Very high — physical products' \"main character\" *is* the product itself | **Physical products** (hardware, packaging, consumer goods) |\n| **UI screenshots** (latest version, real data scrubbed) | Very high — digital products' \"main character\" *is* the interface | **Digital products** (apps, SaaS, websites) |\n| Color tokens | Medium — auxiliary; without the assets above, brands collide | Auxiliary |\n| Typography | Low — needs the above to land | Auxiliary |\n\n**Hard rules**:\n\n- **Don't substitute CSS silhouettes / hand-drawn SVG for real product imagery** — the result is generic \"tech aesthetic\" any brand could wear (zero recognition value, the #1 way branded work fails)\n- **Logo is non-negotiable** — in delegation you cannot fetch one: if the task provides no logo asset, use an honest placeholder (see \"Placeholder Philosophy\") and flag the gap in your closing report — do **not** proceed with a colored rectangle as if it were a logo\n- **Color hex codes alone are not a brand** — they're the cheapest part of the identity\n- All available assets must be referenced via `<img src=\"…\">` as provided in the task / context map, not redrawn\n\n#### When Adding to an Existing UI\n\nThis is more common than designing from scratch. **Understand the visual vocabulary first, then act**:\n\n- **Color & tone**: The actual usage ratio of primary / neutral / accent colors? Does the copy feel engineer-oriented, marketing-oriented, or neutral?\n- **Interaction details**: The feedback style for hover / focus / active states (color shift / shadow / scale / translate)?\n- **Motion language**: Easing function preferences? Duration? Are transitions handled with CSS transition, CSS animation, or JS?\n- **Structural language**: How many elevation levels? Card density — sparse or dense? Border-radius uniform or hierarchical? Common layout patterns (split pane / cards / timeline / table)?\n- **Graphics & iconography**: Icon library in use? Illustration style? Image treatment?\n\nMatching the existing visual vocabulary is the prerequisite for seamless integration; newly added elements should be **indistinguishable from the originals**.\n\n### Step 3a: Position Four Questions Before Picking a System\n\n**Before listing color/typography/spacing tokens**, articulate four positioning questions for each artifact (or each slide / screen / scene):\n\n- **Narrative role**: Hero / transition / data / pull-quote / closing? (Each demands a different visual register.)\n- **Viewing distance**: 10cm phone / 1m laptop / 10m projector? (Drives type scale and information density.)\n- **Visual temperature**: Quiet / energized / authoritative / warm / somber / playful?\n- **Capacity check**: Mentally sketch the rough thumbnail — does the content fit the layout, or will it overflow / look too sparse?\n\nThe system that follows must serve these answers. Picking aesthetics in a vacuum is the root cause of generic output.\n\n### Step 3: Declare the Design System Before Writing Code\n\n**Before writing the first line of code**, articulate the design system in Markdown (in delegation: state it in your closing report — no mid-run pause):\n\n```markdown\nDesign Decisions:\n- Anchor / recipe (if any): [e.g., \"linear\" → `references/style-recipes/linear.md`, or \"custom\"]\n- Color palette: [primary / secondary / neutral / accent]\n- Typography: [heading font / body font / code font]\n- Spacing system: [base unit and multiples]\n- Border-radius strategy: [large / small / sharp]\n- Shadow hierarchy: [elevation 1–5]\n- Motion style: [easing curves / duration / trigger]\n```\n\n> If you picked a recipe from `references/style-recipes/`, paste its concrete palette / typography / spacing / radius / shadow / motion values straight into the block above — that catalog exists so you don't have to invent these on the fly, which is the leading cause of AI-default Inter + #3b82f6 mush. **Load only the one recipe file you're using**, not the whole catalog.\n\n### Step 4: Show a v0 Draft Early\n\n> **SDK adaptation**: in delegation this collapses into the closing report — include the (condensed) design-system declaration and the key structural choices there, so the main agent / user can course-correct on the next round. Build straight to the real thing; don't ship a placeholder skeleton as your final output.\n\nThe v0 mindset still applies *within* the build: lock the tone / layout direction first (design-system declaration), then flesh out content details, states, and motion.\n\n### Step 5: Full Build\n\nWrite full components, add states, and implement motion. Follow the technical specifications and design principles below.\n\n### Step 6: Verification\n\nWalk through the \"Pre-delivery Checklist\" item by item (mentally, against the code — you cannot open a browser console; `validate_code` / render check covers structure).\n\n### Step 7: Critique on Request (or as Self-Check Before Delivery)\n\nWhen the task asks \"review this\", \"is it good?\", \"score this\", \"好不好看\", or as a self-check before declaring done, run a **5-dimension critique**:\n\n| Dimension | What to evaluate |\n|---|---|\n| **Philosophy alignment** | Does every detail trace back to the chosen design direction? Or has it drifted into a generic mishmash? |\n| **Visual hierarchy** | Does the eye flow where intended? Squint test passes? Title/body ratio ≥ 2.5×? |\n| **Craft quality** | Pixel-level alignment, consistent spacing system (e.g., 8pt grid), controlled color count (≤ 4), font families ≤ 2 |\n| **Functionality** | Does each element earn its place? \"If I delete this, does the design get worse?\" If no → delete |\n| **Originality** | Avoids clichés while staying coherent? Any \"unexpected but right\" decisions, or pure template? |\n\nScore each 0–10. Output format:\n\n```markdown\n## Design Critique\n\n**Overall: X.X / 10** [Excellent (8+) / Good (6–7.9) / Needs work (4–5.9) / Failing (<4)]\n\n**By dimension**: Philosophy X / Hierarchy X / Craft X / Functionality X / Originality X\n\n### Keep\n- [Specific things done well, in design language]\n\n### Fix (sorted by severity)\n1. **[Issue name]** — ⚠️ Critical / ⚡ Important / 💡 Polish\n - Current: [what it looks like now]\n - Why: [why it's a problem]\n - Fix: [concrete change with values]\n\n### Quick Wins (top 3 if you only have 5 minutes)\n- [ ] [Highest-impact fix]\n- [ ] [Second]\n- [ ] [Third]\n```\n\n**Critique the design, not the designer.** For per-output-type weighting, common-issue catalog, and detailed scoring rubrics → see `references/critique-guide.md`.\n\n---\n\n## Fallback: Design Direction Advisor\n\n**When to trigger**:\n- The request is genuinely ambiguous (\"make something nice\", \"I don't know what style I want\", \"give me some directions\")\n- No design context exists, and no reference material is available\n- The task explicitly asks for \"a style\" / \"some directions\" / \"a vibe\"\n\n**When to skip**:\n- The task already provided brand / screenshot / style reference → go straight to the main workflow\n- The task stated a specific direction (\"make an Apple-Silicon-style launch animation\") → main workflow\n- Small tweaks → skip\n\n### Mechanism: 3 differentiated directions, not 10 questions\n\nDon't enumerate 10 generic taste questions. Instead, formulate **3 design directions** that come from clearly different schools — so the contrast is visible and the choice is meaningful. Each direction must include:\n\n- **A named designer or studio reference** (e.g., \"Pentagram-style information architecture\", not just \"minimalist\")\n- **2–3 lines of why this direction fits the task's context**\n- **Signature visual cues** (3–4 concrete details: color, typography, layout, motion)\n- **Optional**: one famous touchstone work\n\nIn delegation: implement the most conservative fitting direction, and list all three in the closing report as alternatives for the next round.\n\n### School library — pick 3 from different rows\n\n| School | Vibe | Sample anchors | Best for |\n|---|---|---|---|\n| **Information architecture** | Rational, data-driven, restrained | Pentagram, Edward Tufte, Massimo Vignelli, Bloomberg Terminal | Safe / professional / B2B / data products |\n| **Editorial / minimalist** | Whitespace, refined typography, quiet luxury | Kenya Hara (MUJI), Apple HIG, Dieter Rams, Aesop | Premium / high-end / quiet |\n| **Modern tool / Builder SaaS** | Hairline detail, warm dark, single accent, monospace chips | Linear, Vercel, Raycast, Notion | Developer tools / B2B SaaS / AI tools / infra |\n| **Motion / experimental** | Bold, generative, sensory | Field.io, Active Theory, Resn | Distinctive / launch films / brand moments |\n| **Brutalist / raw** | Anti-design, honest, unpolished | Balenciaga, Are.na, Bloomberg Businessweek covers | Differentiated / confident / counter-culture |\n| **Warm humanist** | Approachable, organic, hand-touched | Mailchimp (early), Stripe Press, Headspace | Lifestyle / education / approachable B2C / wellness |\n\n❌ **Hard rule**: never pick 3 from the same row — the contrast that makes the choice meaningful collapses.\n\n### After a direction is picked\n\nThe chosen direction becomes the design context for Step 2 onward. Surface 2–3 named recipes from that school by reading the matching files in `references/style-recipes/` (e.g., picked *Information Architecture* → read `references/style-recipes/pentagram.md`, `references/style-recipes/bloomberg-terminal.md`). Each recipe file brings concrete palette, typography, spacing, and signature moves you can paste into the Step 3 design-system declaration.\n\n> Extended philosophy library, per-school anchor tables, and AI-prompt templates → `references/design-directions.md`. Anchored recipe catalog → `references/style-recipes/INDEX.md` (catalog index + 3 indexes + cross-cutting anti-patterns) + 25 single-recipe files alongside it.\n\n---\n\n## Technical Specifications\n\n### React + Babel (Inline JSX)\n\nFor React prototypes, use **pinned-version** CDN scripts with `integrity` hashes — see the exact `<script>` tags in `references/advanced-patterns.md`. Do not change versions, do not add `type=\"module\"` (breaks the Babel transpilation pipeline). Import order: React → ReactDOM → Babel → your component files.\n\n> **SDK adaptation**: React remains upstream reference material; in page-agent-sdk components, **default to vanilla HTML/CSS/JS** (self-contained, zero build) unless the task explicitly demands React.\n\n#### Three Non-negotiable Hard Rules\n\n**1. Never use `const styles = { ... }`** — multiple component files with `styles` as a global object will silently overwrite each other. Always namespace: `const terminalStyles = { ... }`, `const headerStyles = { ... }`. Or use inline `style={{...}}` directly. **Never use `styles` as a variable name.**\n\n**2. Separate `<script type=\"text/babel\">` blocks do not share scope** — each Babel script is compiled independently. To share components across files, explicitly attach them to `window` at the end of each file: `Object.assign(window, { Terminal, Line });`\n\n**3. Do not use `scrollIntoView`** — in iframe-embedded preview environments, it disrupts outer-frame scrolling. Use `element.scrollTop = ...` or `window.scrollTo({...})` instead.\n\n### CSS Best Practices\n\n- Prefer CSS Grid + Flexbox for layout\n- Manage design tokens with CSS custom properties\n- **Prefer brand colors for palette**; when more colors are needed, derive harmonious variants using `oklch()` — **never invent new hues from scratch**\n- Use `text-wrap: pretty` for better line breaking\n- Use `clamp()` for fluid typography\n- Use `@container` queries for component-level responsiveness\n- Leverage `@media (prefers-color-scheme)` and `@media (prefers-reduced-motion)`\n\n### File Management\n\n> **SDK adaptation**: you have no filesystem and usually build one self-contained component per delegation — this section is upstream reference. Naming/versioning is handled by the host data model (component name / `__pgId`), not by you.\n\n---\n\n## Design Principles\n\n### Avoid AI-Style Clichés (the WHY matters)\n\nAnti-cliché is **not aesthetic snobbery** — it's protecting the user's brand recognition. The reasoning chain:\n\n1. The user wants their brand to be recognized\n2. AI defaults = average of training data = all brands averaged together = **no brand recognized**\n3. So AI-default output dilutes the user's identity into \"yet another AI-generated page\"\n\nThis is why the only legitimate exception to every anti-cliché rule below is **\"the brand spec uses it\"** — at that point it stops being slop and becomes a brand signature.\n\n| Pattern | Why it's slop | When it's actually fine |\n|---|---|---|\n| Aggressive purple → pink → blue gradient | The \"tech vibe\" formula AI training data converged on; on every SaaS / AI / web3 landing page | The brand itself uses it, or the task is satirizing this aesthetic |\n| Rounded card + colored left-border accent | Material/Tailwind era leftover; now visual noise in every dashboard | The user explicitly asks, or the brand spec preserves it |\n| Emoji as icon substitute | \"Not professional → slap emoji on it\" tic from training data | The brand uses emoji (Notion, Slack, early Linear), or audience is kids / casual |\n| SVG-drawn imagery (faces, scenes, objects) | AI-drawn SVG humans always have misaligned features and feel cheap | **Almost never** — use real images, AI-generated images, or honest placeholder |\n| CSS silhouette substituting for real product imagery | Generic \"tech aesthetic\" — same look across every brand | **Never** for branded work — go fetch the real product image |\n| Inter / Roboto / Arial / Fraunces / system-ui as display | Too common; reads as \"demo page\" rather than \"designed product\" | The brand spec specifies these (and usually with custom adjustments) |\n| Cyber-neon on `#0D1117` dark | GitHub-dark cosplay; baseline noise in dev-tool clones | The brand actually lives in this aesthetic |\n| Fabricated stats, fake logo walls, dummy testimonials | Damages credibility; users notice when numbers don't match reality | **Never** — use placeholders that say \"real data needed\" |\n\n### Emoji Rules\n\n**No emoji by default.** Only use emoji when the target design system/brand itself uses them (e.g., Notion, early Linear, certain consumer brands), and match their density and context precisely.\n\n- ❌ Using emoji as icon substitutes (\"I don't have an icon library, so I'll use 🚀 ⚡ ✨ as fillers\")\n- ❌ Using emoji as decorative filler (\"let's add an emoji before the heading to make it lively\")\n- ✅ No icon available → use a placeholder (see \"Placeholder Philosophy\" below) to signal that a real icon is needed\n- ✅ The brand itself uses emoji → follow the brand\n\n---\n\n### Placeholder Philosophy\n\n**When you lack icons, images, or components, a placeholder is more professional than a poorly drawn fake.**\n\n- Missing icon → square + label (e.g., `[icon]`, `▢`)\n- Missing avatar → initial-letter circle with a color fill\n- Missing image → a placeholder card with aspect-ratio info (e.g., `16:9 image`)\n- Missing data → use explicit \"real data needed\" placeholders; never fabricate\n- Missing logo → honest placeholder + flag in closing report (see Asset Protocol); never substitute \"brand name in a colored box\" for a logo on branded work\n\nA placeholder signals \"real material needed here.\" A fake signals \"I cut corners.\"\n\n### Aim to Stun\n\n- Play with proportion and whitespace to create visual rhythm\n- Bold type-size contrast (a 4–6× ratio between h1 and body text is normal)\n- Use color fills, textures, layering, and blend modes to create depth\n- Experiment with unconventional layouts, novel interaction metaphors, and thoughtful hover states\n- Use CSS animations + transitions for polished micro-interactions (button press, card hover, entry animations)\n- Use SVG filters, `backdrop-filter`, `mix-blend-mode`, `mask`, and other advanced CSS to create memorable moments\n\nCSS, HTML, JS, and SVG are far more capable than most people realize — **use them to astonish the user**.\n\n### Appropriate Scale\n\n| Context | Minimum Size |\n|---|---|\n| 1920×1080 presentations | Text ≥ 24px (ideally larger) |\n| Mobile mockups | Touch targets ≥ 44px |\n| Print documents | ≥ 12pt |\n| Web body text | Start at 16–18px |\n\n### Content Principles\n\n- **No filler content** — every element must earn its place\n- **Don't add sections unilaterally** — implement what the task specifies; if more content seems needed, note it in the closing report instead of padding\n- **Placeholders > fabricated data** — fake data damages credibility more than admitting a gap\n- **Less is more** — \"1,000 no's for every yes\"; whitespace is design\n- If the page looks empty → it's a layout problem, not a content problem. Solve it with composition, whitespace, and type-scale rhythm, not by stuffing content in\n\n---\n\n## Output Type Guidelines\n\n### Interactive Prototypes\n\n- **No title screen / cover page** — prototypes should center in the viewport or fill it (with sensible margins), letting the user see the product immediately\n- Use device frames (iPhone / Android / browser window) to enhance realism (see references file)\n- Implement key interaction paths so the user can click through them\n- Complete state coverage: default / hover / active / focus / disabled / loading / empty / error\n\n### HTML Slide Decks / Presentations\n\n- Fixed canvas at 1920×1080 (16:9), auto-fitted to any viewport via JS `transform: scale()`\n- Centered with letterbox bars; prev/next buttons placed **outside** the scaled container (to remain usable on small screens)\n- Keyboard navigation: ← → to change slides, Space for next\n- **Slide numbering is 1-indexed**: use labels like `01 Title`, `02 Agenda`, matching human speech (\"slide 5\" corresponds to label `05` — never use 0-indexed labels that cause off-by-one confusion)\n- Each slide should have a `data-screen-label` attribute for easy reference\n- Don't cram too much text — visuals lead, text supports; use at most 1–2 background colors per deck\n\n### Data Visualization Dashboards\n\n- Chart.js (simple) or D3.js (complex custom) — loaded via CDN\n- Responsive chart containers (`ResizeObserver`)\n- Provide dark/light mode toggle where it fits\n- Focus on **data-ink ratio**: remove unnecessary gridlines, 3D effects, and shadows; let the data speak\n- Color encoding should carry semantic meaning (up/down / category / time), not serve as decoration\n\n### Animation / Video Demos\n\nChoose animation approach by complexity, from simplest to heaviest — don't reach for a heavy library from the start:\n\n1. **CSS transitions / animations** — sufficient for 80% of micro-interactions (button press, card hover, fade-in entry, state toggle)\n2. **Simple state + setTimeout / requestAnimationFrame** — simple frame-by-frame or event-driven animations\n3. **Custom timeline engine** (full implementation in references) — timeline-driven video/demo scenes: scrubber, play/pause, multi-segment choreography\n4. **Fallback: Popmotion** (`https://unpkg.com/popmotion@11.0.5/dist/popmotion.min.js`) — only if the above three layers genuinely can't cover the use case\n\n> Avoid Framer Motion / GSAP / Lottie unless explicitly requested — bundle overhead and version conflicts. Always provide play/pause + scrubber, and skip \"title screen\" intros — go straight to content.\n\n### Static Visual Comparison vs. Full Flow\n\n- **Pure visual comparison** (button colors, typography, card styles) → use a design canvas to display options side by side\n- **Interactions, flows, multi-option scenarios** → build a full clickable prototype + expose options as Tweaks\n\n---\n\n## Variant Exploration Philosophy\n\nProviding multiple variants is about **exhausting possibilities so the user can mix and match**, not about delivering the perfect option.\n\nExplore \"atomic variants\" across at least these dimensions — mixing conservative, safe options with bold, novel ones:\n\n1. **Layout**: content organization (split pane / card grid / list / timeline)\n2. **Visual**: color palette, typography, texture, layering\n3. **Interaction**: motion, feedback, navigation patterns\n4. **Creative**: convention-breaking metaphors, novel UX, strong visual concepts\n\nStrategy: **Start the first few variants safely within the design system; then progressively push boundaries.** In delegation, prefer the safe-but-excellent end unless the task asks for boldness — radical exploration belongs in the reported alternatives, not in the delivered build.\n\n---\n\n## Tweaks Panel (Live Parameter Adjustment)\n\nLet users adjust design parameters in real time: theme color, font size, dark mode, spacing, component variants, content density, animation toggles, etc.\n\nDesign guidelines:\n- A floating panel in the bottom-right corner (see the reference implementation)\n- Title consistently labeled **\"Tweaks\"**\n- **Completely hidden** when closed, ensuring the design looks final during presentations\n- In multi-variant scenarios, expose variants as dropdowns/toggles within Tweaks instead of creating multiple files\n- Even if the task doesn't ask for tweaks, consider 1–2 creative ones (to expose interesting possibilities) — but skip entirely for small components where a floating panel would pollute the design\n\n---\n\n## Common CDN Resources\n\n**Default to hand-written CSS.** Only load a CDN when the scenario clearly calls for it — never include everything by default.\n\n| When clearly needed | Library |\n|---|---|\n| Charts (line / bar / pie) | Chart.js (`https://cdn.jsdelivr.net/npm/chart.js`) |\n| Complex custom visualizations | D3 v7 (`https://d3js.org/d3.v7.min.js`) |\n| Custom typography | Google Fonts (avoid Inter / Roboto / Arial / Fraunces / system-ui as display) |\n\n| Use only on explicit request or throwaway prototypes | Why |\n|---|---|\n| Tailwind CDN | Conflicts with the \"declare design tokens first\" workflow |\n| Lucide Icons CDN | Prefer placeholders over inserting icons \"to look complete\" when no icon library was specified |\n\n> React + Babel pinned CDN script tags → `references/advanced-patterns.md`. Do not change versions.\n\n---\n\n## Pre-delivery Checklist\n\nComplete the following before considering the work delivered (all items must pass):\n\n- [ ] **Facts**: any product/brand/version spec that couldn't be verified is flagged in the closing report — not asserted, not fabricated\n- [ ] **If the task is branded**: logo/product imagery come from task-provided assets via `<img src>` (or honest placeholder + flag), never CSS silhouettes or redrawn SVG for real imagery\n- [ ] Code passes `validate_code` (structure) — the framework's render check is the console-error proxy\n- [ ] Renders correctly on **target devices/viewports** (responsive web → mobile / tablet / desktop; slide decks/video with fixed dimensions → scaling container adapts without distortion)\n- [ ] **Interactive components** (buttons, links, inputs, cards, etc.) include states as appropriate: hover / focus / active / disabled / loading; empty/error states added where the scenario warrants them\n- [ ] No text overflow or truncation; `text-wrap: pretty` applied\n- [ ] All colors come from the design system declared in Step 3 — **no rogue hues introduced**\n- [ ] No use of `scrollIntoView`\n- [ ] No AI clichés (purple-pink gradients, emoji abuse, left-border accent cards, Inter/Roboto) — unless the brand spec explicitly uses them\n- [ ] No filler content, no fabricated data\n- [ ] Semantic naming, clean structure, easy to modify later\n- [ ] Visual quality at Dribbble / Behance showcase level\n- [ ] **Closing report**: condensed design-system declaration + assumptions/flags + (optionally) critique score and alternatives\n\n---\n\n## Collaborating with the User\n\n- **Report work-in-progress thinking**: your closing report carries the v0 role — design-system declaration, key decisions, assumptions\n- Explain decisions using **design language** (\"I tightened the spacing to create a tool-like feel\"), not technical language\n- When the task is ambiguous, state how you read it and what you'd do differently under another reading — the next delegation can pivot\n- Offer alternatives and creative options in the report so the user sees the boundaries of what's possible\n- When summarizing, **only mention important caveats and next steps** — don't recap what you did; the code speaks for itself\n\n---\n\n## References Routing\n\nRead on demand based on task type — don't preload everything (load via `load_skill('web-design-engineer', ref='<name>')`):\n\n| Task | Read |\n|---|---|\n| Slide engine, device frames, Tweaks panel, animation timeline, design canvas, dark mode, data viz, oklch color system, font recommendations | `references/advanced-patterns.md` |\n| Vague request → recommend 3 design directions; extended philosophy library + per-direction visual recipes + AI-prompt templates | `references/design-directions.md` |\n| Task names an anchor (\"Linear-style\" / \"Aesop feeling\") → load **only that one file** | `references/style-recipes/<anchor>.md` (e.g., `linear.md`, `aesop.md`) |\n| Browse the recipe catalog / compare options after Direction Advisor picks a school | `references/style-recipes/INDEX.md` (3 indexes + cross-cutting anti-patterns; then read 1–3 specific recipe files) |\n| Critique mode — detailed scoring rubrics, per-output-type weighting, common-issue catalog (top 10) | `references/critique-guide.md` |\n", Kd = [
|
|
10253
|
+
{
|
|
10254
|
+
name: "references/advanced-patterns.md",
|
|
10255
|
+
description: "Code templates: device frames, slide engine, animation timeline, Tweaks panel, design canvas, dark mode, data viz, oklch color system, font picks, pinned React+CDN tags",
|
|
10256
|
+
getContent: () => "\n# Advanced Reference: Component Patterns & Code Templates\n\nThis file contains advanced patterns and code templates to reference when implementing specific tasks.\n\n## Table of Contents\n\n1. [Responsive Slide Engine](#responsive-slide-engine)\n2. [Device Simulation Frames](#device-simulation-frames)\n3. [Tweaks Panel Implementation](#tweaks-panel-implementation)\n4. [Animation Timeline Engine](#animation-timeline-engine)\n5. [Design Canvas (Multi-option Comparison)](#design-canvas)\n6. [Dark Mode Toggle](#dark-mode-toggle)\n7. [Data Visualization Templates](#data-visualization-templates)\n\n---\n\n## Responsive Slide Engine\n\nFor building fixed-size presentations that auto-fit to any viewport.\n\n**Key conventions**:\n- Internal arrays use 0-indexed, **but numbers displayed to the user are always 1-indexed**\n- Each `<section class=\"slide\">` gets `data-screen-label=\"01 Title\"`, `data-screen-label=\"02 Agenda\"`, etc. for easy reference\n- Control buttons go **outside** the `.stage` scaled container to ensure usability on small screens\n\n```html\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { \n background: #000; \n display: flex; \n align-items: center; \n justify-content: center;\n height: 100vh;\n overflow: hidden;\n font-family: system-ui, sans-serif;\n }\n .stage {\n width: 1920px;\n height: 1080px;\n position: relative;\n transform-origin: center center;\n }\n .slide {\n position: absolute;\n inset: 0;\n display: none;\n padding: 80px;\n }\n .slide.active { display: flex; }\n .controls {\n position: fixed;\n bottom: 20px;\n left: 50%;\n transform: translateX(-50%);\n display: flex;\n gap: 12px;\n z-index: 1000;\n }\n .controls button {\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n background: rgba(255,255,255,0.15);\n color: white;\n cursor: pointer;\n font-size: 14px;\n }\n .slide-counter {\n position: fixed;\n bottom: 20px;\n right: 20px;\n color: rgba(255,255,255,0.6);\n font-size: 14px;\n }\n</style>\n\n<script>\n // Auto-fit scaling\n function scaleStage() {\n const stage = document.querySelector('.stage');\n const scaleX = window.innerWidth / 1920;\n const scaleY = window.innerHeight / 1080;\n const scale = Math.min(scaleX, scaleY);\n stage.style.transform = `scale(${scale})`;\n }\n window.addEventListener('resize', scaleStage);\n scaleStage();\n\n // Slide navigation\n let current = parseInt(localStorage.getItem('slideIndex') || '0');\n const slides = document.querySelectorAll('.slide');\n \n function showSlide(n) {\n current = Math.max(0, Math.min(n, slides.length - 1));\n slides.forEach((s, i) => s.classList.toggle('active', i === current));\n localStorage.setItem('slideIndex', current);\n // Display 1-indexed to user, store 0-indexed internally\n document.querySelector('.slide-counter').textContent = `${current + 1} / ${slides.length}`;\n }\n \n document.addEventListener('keydown', e => {\n if (e.key === 'ArrowRight' || e.key === ' ') showSlide(current + 1);\n if (e.key === 'ArrowLeft') showSlide(current - 1);\n });\n \n showSlide(current);\n<\/script>\n```\n\n---\n\n## Device Simulation Frames\n\n### iPhone Frame\n\n```jsx\nconst IPhoneFrame = ({ children, title = \"App\" }) => (\n <div style={{\n width: 390,\n height: 844,\n borderRadius: 48,\n border: '12px solid #1a1a1a',\n overflow: 'hidden',\n position: 'relative',\n boxShadow: '0 25px 50px -12px rgba(0,0,0,0.25)',\n background: '#fff'\n }}>\n {/* Status bar */}\n <div style={{\n height: 54,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '0 24px',\n fontSize: 14,\n fontWeight: 600\n }}>\n <span>9:41</span>\n <div style={{\n width: 126,\n height: 34,\n background: '#1a1a1a',\n borderRadius: 20,\n position: 'absolute',\n left: '50%',\n transform: 'translateX(-50%)',\n top: 8\n }} />\n <span>⚡ 📶</span>\n </div>\n {/* Content */}\n <div style={{ height: 'calc(100% - 54px)', overflow: 'auto' }}>\n {children}\n </div>\n {/* Home indicator */}\n <div style={{\n position: 'absolute',\n bottom: 8,\n left: '50%',\n transform: 'translateX(-50%)',\n width: 134,\n height: 5,\n background: '#1a1a1a',\n borderRadius: 3\n }} />\n </div>\n);\n```\n\n### Browser Window Frame\n\n```jsx\nconst BrowserFrame = ({ children, url = \"https://example.com\", title = \"Page\" }) => (\n <div style={{\n borderRadius: 12,\n overflow: 'hidden',\n boxShadow: '0 25px 50px -12px rgba(0,0,0,0.25)',\n border: '1px solid #e5e5e5'\n }}>\n {/* Title bar */}\n <div style={{\n background: '#f5f5f5',\n padding: '12px 16px',\n display: 'flex',\n alignItems: 'center',\n gap: 12,\n borderBottom: '1px solid #e5e5e5'\n }}>\n <div style={{ display: 'flex', gap: 8 }}>\n <div style={{ width: 12, height: 12, borderRadius: '50%', background: '#ff5f57' }} />\n <div style={{ width: 12, height: 12, borderRadius: '50%', background: '#febc2e' }} />\n <div style={{ width: 12, height: 12, borderRadius: '50%', background: '#28c840' }} />\n </div>\n <div style={{\n flex: 1,\n background: '#fff',\n borderRadius: 6,\n padding: '6px 12px',\n fontSize: 13,\n color: '#666',\n border: '1px solid #e0e0e0'\n }}>\n {url}\n </div>\n </div>\n {/* Content */}\n <div style={{ background: '#fff' }}>\n {children}\n </div>\n </div>\n);\n```\n\n---\n\n## Tweaks Panel Implementation\n\n```jsx\nconst TweaksPanel = ({ config, onChange, visible }) => {\n if (!visible) return null;\n \n return (\n <div style={{\n position: 'fixed',\n bottom: 20,\n right: 20,\n width: 280,\n background: 'rgba(24, 24, 27, 0.95)',\n backdropFilter: 'blur(12px)',\n borderRadius: 12,\n padding: 16,\n color: '#fff',\n fontSize: 13,\n zIndex: 9999,\n boxShadow: '0 20px 60px rgba(0,0,0,0.3)',\n border: '1px solid rgba(255,255,255,0.1)'\n }}>\n <div style={{ fontWeight: 600, marginBottom: 12, fontSize: 14 }}>Tweaks</div>\n \n {Object.entries(config).map(([key, value]) => (\n <div key={key} style={{ marginBottom: 12 }}>\n <label style={{ display: 'block', marginBottom: 4, opacity: 0.7 }}>\n {key}\n </label>\n {typeof value === 'boolean' ? (\n <input\n type=\"checkbox\"\n checked={value}\n onChange={e => onChange({ ...config, [key]: e.target.checked })}\n />\n ) : typeof value === 'number' ? (\n <input\n type=\"range\"\n min=\"0\"\n max=\"100\"\n value={value}\n onChange={e => onChange({ ...config, [key]: Number(e.target.value) })}\n style={{ width: '100%' }}\n />\n ) : value.startsWith('#') ? (\n <input\n type=\"color\"\n value={value}\n onChange={e => onChange({ ...config, [key]: e.target.value })}\n />\n ) : (\n <input\n type=\"text\"\n value={value}\n onChange={e => onChange({ ...config, [key]: e.target.value })}\n style={{\n width: '100%',\n background: 'rgba(255,255,255,0.1)',\n border: '1px solid rgba(255,255,255,0.2)',\n borderRadius: 4,\n padding: '4px 8px',\n color: '#fff'\n }}\n />\n )}\n </div>\n ))}\n </div>\n );\n};\n```\n\n---\n\n## Animation Timeline Engine\n\n```jsx\nconst useTime = (duration = 5000) => {\n const [time, setTime] = React.useState(0);\n const [playing, setPlaying] = React.useState(true);\n const frameRef = React.useRef();\n const startRef = React.useRef();\n \n React.useEffect(() => {\n if (!playing) return;\n const animate = (timestamp) => {\n if (!startRef.current) startRef.current = timestamp;\n const elapsed = (timestamp - startRef.current) % duration;\n setTime(elapsed / duration); // 0 to 1\n frameRef.current = requestAnimationFrame(animate);\n };\n frameRef.current = requestAnimationFrame(animate);\n return () => cancelAnimationFrame(frameRef.current);\n }, [playing, duration]);\n \n return { time, playing, setPlaying };\n};\n\nconst Easing = {\n linear: t => t,\n easeInOut: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,\n easeOut: t => 1 - Math.pow(1 - t, 3),\n easeIn: t => t * t * t,\n spring: t => 1 - Math.pow(Math.E, -6 * t) * Math.cos(8 * t)\n};\n\nconst interpolate = (t, from, to, easing = Easing.easeInOut) => {\n const progress = easing(Math.max(0, Math.min(1, t)));\n return from + (to - from) * progress;\n};\n\n// Usage example:\n// const { time } = useTime(3000);\n// const opacity = interpolate(time, 0, 1);\n// const x = interpolate(time, -100, 0, Easing.spring);\n```\n\n---\n\n## Design Canvas\n\nFor displaying multiple design options side by side for comparison:\n\n```jsx\nconst DesignCanvas = ({ options, columns = 3 }) => (\n <div style={{\n display: 'grid',\n gridTemplateColumns: `repeat(${columns}, 1fr)`,\n gap: 24,\n padding: 40,\n background: '#f8f9fa',\n minHeight: '100vh'\n }}>\n {options.map((option, i) => (\n <div key={i} style={{\n background: '#fff',\n borderRadius: 12,\n overflow: 'hidden',\n boxShadow: '0 1px 3px rgba(0,0,0,0.1)'\n }}>\n <div style={{\n padding: '12px 16px',\n borderBottom: '1px solid #eee',\n fontSize: 13,\n fontWeight: 600,\n color: '#666'\n }}>\n Option {String.fromCharCode(65 + i)}: {option.label}\n </div>\n <div style={{ padding: 16 }}>\n {option.content}\n </div>\n </div>\n ))}\n </div>\n);\n```\n\n---\n\n## Dark Mode Toggle\n\n```jsx\nconst ThemeProvider = ({ children }) => {\n const [dark, setDark] = React.useState(\n window.matchMedia('(prefers-color-scheme: dark)').matches\n );\n \n const theme = dark ? {\n bg: '#0a0a0b',\n surface: '#18181b',\n border: '#27272a',\n text: '#fafafa',\n textMuted: '#a1a1aa',\n primary: '#3b82f6'\n } : {\n bg: '#ffffff',\n surface: '#f4f4f5',\n border: '#e4e4e7',\n text: '#18181b',\n textMuted: '#71717a',\n primary: '#2563eb'\n };\n \n return (\n <ThemeContext.Provider value={{ theme, dark, setDark }}>\n <div style={{ background: theme.bg, color: theme.text, minHeight: '100vh' }}>\n {children}\n </div>\n </ThemeContext.Provider>\n );\n};\n```\n\n---\n\n## Data Visualization Templates\n\n### Chart.js Quick Start\n\n```html\n<canvas id=\"myChart\" width=\"800\" height=\"400\"></canvas>\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js\"><\/script>\n<script>\n const ctx = document.getElementById('myChart').getContext('2d');\n new Chart(ctx, {\n type: 'line', // bar, pie, doughnut, radar, etc.\n data: {\n labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],\n datasets: [{\n label: 'Revenue',\n data: [12, 19, 3, 5, 2, 3],\n borderColor: '#3b82f6',\n backgroundColor: 'rgba(59, 130, 246, 0.1)',\n tension: 0.4,\n fill: true\n }]\n },\n options: {\n responsive: true,\n plugins: {\n legend: { display: false }\n },\n scales: {\n y: { beginAtZero: true, grid: { color: '#f0f0f0' } },\n x: { grid: { display: false } }\n }\n }\n });\n<\/script>\n```\n\n---\n\n## Color System Best Practices\n\nUse oklch to define a harmonious color system:\n\n```css\n:root {\n /* oklch-based color system */\n --primary-h: 250; /* hue */\n --primary: oklch(0.55 0.25 var(--primary-h));\n --primary-light: oklch(0.75 0.15 var(--primary-h));\n --primary-dark: oklch(0.35 0.2 var(--primary-h));\n \n /* Neutrals */\n --gray-50: oklch(0.98 0.002 250);\n --gray-100: oklch(0.96 0.004 250);\n --gray-200: oklch(0.92 0.006 250);\n --gray-300: oklch(0.87 0.008 250);\n --gray-400: oklch(0.71 0.01 250);\n --gray-500: oklch(0.55 0.014 250);\n --gray-600: oklch(0.45 0.014 250);\n --gray-700: oklch(0.37 0.014 250);\n --gray-800: oklch(0.27 0.014 250);\n --gray-900: oklch(0.21 0.014 250);\n}\n```\n\n---\n\n## Font Recommendations (Non-default Choices)\n\n> ⚠️ **These are experience-based suggestions, not hard rules.**\n> - Always prefer fonts already specified by the brand or design system; only refer to this table when the user hasn't provided any font scheme.\n> - The only hard rule: **Avoid Inter / Roboto / Arial / Fraunces / system-ui — fonts overused by AI-generated content** that instantly signal \"this was assembled by AI.\"\n> - When choosing fonts, focus on \"personality fit\" rather than \"what's trendy.\" The table below lists common high-quality choices, not an exhaustive list.\n\n| Use Case | Recommendation | Google Fonts Name |\n|------|------|------------------|\n| Modern headings | Plus Jakarta Sans | Plus+Jakarta+Sans |\n| Elegant body text | Outfit | Outfit |\n| Technical feel | Space Grotesk | Space+Grotesk |\n| Premium brand | Sora | Sora |\n| Editorial feel | Newsreader | Newsreader |\n| Handwritten style | Caveat | Caveat |\n| Monospace / code | JetBrains Mono | JetBrains+Mono |\n\n```html\n<link href=\"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap\" rel=\"stylesheet\">\n```\n\n---\n\n## Color × Font Pairing Reference\n\n> ⚠️ **These are experience-based pairing suggestions, not hard rules.** When you have **absolutely no design context**, pick one as a starting point — it's far better than starting from Inter + #3b82f6.\n> Once the user provides a brand / design system / reference site, drop this table immediately and follow their materials.\n\nFor quickly establishing a visual system with personality:\n\n| Style | Primary Color (oklch) | Font Pairing | Best For |\n|---|---|---|---|\n| Modern tech | `oklch(0.55 0.25 250)` blue-violet | Space Grotesk + Inter | SaaS, dev tools, AI products |\n| Elegant editorial | `oklch(0.35 0.10 30)` warm brown | Newsreader + Outfit | Content platforms, blogs, editorial |\n| Premium brand | `oklch(0.20 0.02 250)` near-black | Sora + Plus Jakarta Sans | Luxury, consulting, finance |\n| Lively consumer | `oklch(0.70 0.20 30)` coral | Plus Jakarta Sans + Outfit | E-commerce, lifestyle, social |\n| Minimal professional | `oklch(0.50 0.15 200)` teal-blue | Outfit + Space Grotesk | Data products, dashboards, B2B |\n| Artisan warmth | `oklch(0.55 0.15 80)` caramel | Caveat (decorative) + Newsreader | Food & beverage, education, creative |\n\nAvoid these combos:\n- ❌ Inter + Roboto + blue buttons (peak AI aesthetic)\n- ❌ Fraunces + purple-pink gradients (overused)\n- ❌ More than three font families (visual chaos)\n\n"
|
|
10257
|
+
},
|
|
10258
|
+
{
|
|
10259
|
+
name: "references/critique-guide.md",
|
|
10260
|
+
description: "Critique scoring rubrics: per-output-type weighting, top-10 common issues, detailed 5-dimension scoring",
|
|
10261
|
+
getContent: () => "\n# Critique Mode — Detailed Reference\n\nRead this when running Step 7 of the workflow (user asked for review, or self-check before delivery). The main `SKILL.md` already covers the **5 dimensions and output format**. This file provides **scoring rubrics, per-output-type weighting, and the common-issue catalog**.\n\n**Critique the design, not the designer.** Be specific, actionable, and grounded in design language — not vague taste claims.\n\n---\n\n## The Five Dimensions — Detailed Rubrics\n\n### 1. Philosophy Alignment\n\nHow well does every detail trace back to the chosen design direction (Pentagram-style information architecture, Kenya Hara-style minimalism, etc.)?\n\n| Score | Standard |\n|---|---|\n| 9–10 | Every detail embodies the chosen philosophy; nothing reads as \"borrowed from elsewhere\" |\n| 7–8 | Direction is correct, signature traits land, 1–2 minor drift moments |\n| 5–6 | Intent visible, but mixed-in foreign elements dilute purity (e.g., \"minimalism\" with 6 cards per row) |\n| 3–4 | Surface mimicry only; the underlying values aren't understood |\n| 1–2 | No discernible relationship to any stated direction |\n\n**What to look for**:\n- Are signature moves of the chosen designer/studio actually present?\n- Do color, type, layout, motion all agree on the same philosophy?\n- Any \"self-contradicting\" elements? (Picked Kenya Hara then crammed the page full → contradiction)\n\n### 2. Visual Hierarchy\n\nDoes the eye flow where the designer intends?\n\n| Score | Standard |\n|---|---|\n| 9–10 | Eye flows naturally along the intended path; zero friction reading the information |\n| 7–8 | Primary/secondary clear; 1–2 spots where hierarchy is muddy |\n| 5–6 | Title vs. body distinguishable, but middle layers (subtitles, captions) collapse together |\n| 3–4 | Information sits flat with no clear entry point |\n| 1–2 | Chaotic — viewer doesn't know where to look first |\n\n**What to look for**:\n- Title vs. body size ratio ≥ 2.5× (ideally 4–6× for hero)?\n- Color / weight / size building 3–4 clear levels?\n- Whitespace actively guiding the eye?\n- **Squint test**: squint at the screen — is the hierarchy still legible?\n\n### 3. Craft Quality\n\nPixel-level execution: alignment, spacing, color discipline.\n\n| Score | Standard |\n|---|---|\n| 9–10 | Pixel-perfect; alignment, spacing, color all flawless |\n| 7–8 | Refined overall; 1–2 minor alignment or spacing issues |\n| 5–6 | Basically aligned, but spacing is inconsistent and color use is unsystematic |\n| 3–4 | Obvious alignment errors, chaotic spacing, too many colors |\n| 1–2 | Sloppy — looks like a draft |\n\n**What to look for**:\n- Consistent spacing system (8pt grid: 8 / 16 / 24 / 32 / 48 / 64)?\n- Same-class elements use identical spacing?\n- Color count controlled (typically ≤ 4 — primary + accent + neutral scale + 1 emphasis)?\n- Font families ≤ 2 (1 display + 1 body)?\n- Edges align precisely?\n\n### 4. Functionality\n\nDoes each element earn its place?\n\n| Score | Standard |\n|---|---|\n| 9–10 | Every element serves a goal; zero redundancy |\n| 7–8 | Function-led overall, with minor decoration that could be cut |\n| 5–6 | Usable, but obvious decorative elements compete for attention |\n| 3–4 | Form > function; users have to work to find information |\n| 1–2 | Decoration drowns the content's ability to communicate |\n\n**What to look for**:\n- The deletion test: \"If I delete this element, does the design get worse?\" If no → delete\n- Is the CTA / key information in the most prominent position?\n- Anything added \"because it looked good\"?\n- Is the information density appropriate for the medium? (PPT sparse; PDF can be denser; landing page conversion-focused)\n\n### 5. Originality\n\nAvoids clichés while staying coherent within the philosophy.\n\n| Score | Standard |\n|---|---|\n| 9–10 | Refreshing; finds a unique expression *within* the chosen philosophy |\n| 7–8 | Has its own ideas; not template-by-numbers |\n| 5–6 | Average; reads as a template execution |\n| 3–4 | Heavy use of clichés (gradient orbs for \"AI\", chat bubbles for \"conversation\") |\n| 1–2 | Pure template / stock-asset assembly |\n\n**What to look for**:\n- Has it avoided the AI-slop list (purple gradients, emoji icons, left-border accent cards, Inter as display)?\n- Is there at least one \"unexpected but right\" decision?\n- Any element that screams \"made by AI\"?\n\n---\n\n## Per-Output-Type Weighting\n\nDifferent outputs need different priorities. When scoring, weight these dimensions higher for each context:\n\n| Output type | Most important | Secondary | Can relax |\n|---|---|---|---|\n| Landing page / marketing site | Functionality, Visual hierarchy | Originality | — (must be all-around) |\n| Dashboard / data product | Functionality, Craft quality | Visual hierarchy | Originality (clarity wins) |\n| HTML slide deck | Visual hierarchy, Functionality | Craft | Originality (legibility wins) |\n| Mobile app prototype | Functionality, Craft | Visual hierarchy | Philosophy alignment (usability wins) |\n| Brand launch animation / hero film | Originality, Visual hierarchy | Philosophy | Functionality (it's the moment, not the form) |\n| Editorial / portfolio | Originality, Philosophy | Visual hierarchy | Functionality (vibe matters most) |\n| Documentation site | Functionality, Visual hierarchy | Craft | Originality (find-the-answer wins) |\n| Interactive prototype for user testing | Functionality, Visual hierarchy | Craft | Originality (testing the flow, not the look) |\n\n---\n\n## Common Issues — Top 10 Catalog\n\nUse these as a checklist when running a critique. Each entry has the issue, why it matters, and the fix.\n\n### 1. AI-tech cliché\n**Issue**: Gradient orbs, digital rain, blue circuit boards, robot faces\n**Why it's a problem**: Audience is exhausted by these — your product becomes indistinguishable\n**Fix**: Use abstract metaphors instead of literal symbols (e.g., a \"conversation\" metaphor instead of a chat bubble icon)\n\n### 2. Insufficient type-size hierarchy\n**Issue**: Title and body are too similar in size (< 2.5×)\n**Why**: Users can't find key information quickly\n**Fix**: Title at least 3× body (16px body → 48–64px title; for hero, 6× is normal)\n\n### 3. Too many colors\n**Issue**: 5+ colors in use without a clear primary/secondary structure\n**Why**: Visual chaos; weak brand identity\n**Fix**: Limit to 1 primary + 1 secondary + 1 accent + grayscale; everything else has to justify itself\n\n### 4. Inconsistent spacing\n**Issue**: Element spacing chosen ad-hoc with no system\n**Why**: Reads as unprofessional; visual rhythm broken\n**Fix**: Adopt an 8pt grid (only use spacing values from {8, 16, 24, 32, 48, 64, 96})\n\n### 5. Insufficient whitespace\n**Issue**: Every region is filled with content\n**Why**: Cognitive overload reduces information transfer; dense ≠ informative\n**Fix**: Whitespace should be at least 40% of total area (60%+ for minimalist)\n\n### 6. Too many fonts\n**Issue**: 3+ font families in use\n**Why**: Visual noise; weakens unity\n**Fix**: At most 2 (1 display + 1 body); use weight and size variation for richness\n\n### 7. Inconsistent alignment\n**Issue**: Mixed left-, center-, and right-aligned blocks\n**Why**: Breaks visual order\n**Fix**: Pick one alignment (typically left) and apply globally; centered alignment only for hero / pull-quote moments\n\n### 8. Decoration eclipses content\n**Issue**: Background patterns / gradients / shadows steal focus from primary content\n**Why**: Inverts the priority — users came for information, not for decoration\n**Fix**: Apply the deletion test: \"if I remove this decoration, does the design get worse?\" If no → remove\n\n### 9. Cyber-neon overuse\n**Issue**: Dark navy `#0D1117` + neon-glow accents\n**Why**: This is the GitHub-dark / \"AI dev tool\" cliché — every clone looks the same\n**Fix**: Pick a more distinctive palette; if dark mode is mandatory, choose a non-default base (deep warm gray, near-black with hint of color)\n\n### 10. Information density mismatched to medium\n**Issue**: A wall of text on a slide; 10 elements crammed into a social cover\n**Why**: Different media have different optimal density\n**Fix**:\n- Slides: 1 core idea per page\n- Cover image: 1 visual focal point\n- Infographic: layered (overview → detail)\n- PDF / docs: can be dense, but needs clear navigation\n\n---\n\n## Output Template (copy this when delivering a critique)\n\n```markdown\n## Design Critique\n\n**Overall: X.X / 10** [Excellent (8+) / Good (6–7.9) / Needs work (4–5.9) / Failing (<4)]\n\n**By dimension**:\n- Philosophy alignment: X / 10 — [one-sentence reason]\n- Visual hierarchy: X / 10 — [one-sentence reason]\n- Craft quality: X / 10 — [one-sentence reason]\n- Functionality: X / 10 — [one-sentence reason]\n- Originality: X / 10 — [one-sentence reason]\n\n### Keep\n- [Specific things done well, in design language — not \"the colors are nice\", say \"the muted terracotta against warm off-white reads as confident and editorial\"]\n\n### Fix (sorted by severity)\n\n**1. [Issue name]** — ⚠️ Critical / ⚡ Important / 💡 Polish\n- Current: [what it looks like now]\n- Why: [why it's a problem, anchored in a principle above]\n- Fix: [concrete change with specific values — \"increase title size from 32px to 56px\", not \"make titles bigger\"]\n\n**2. [Issue name]** — ⚠️ / ⚡ / 💡\n…\n\n### Quick Wins (top 3 if you only have 5 minutes)\n- [ ] [Highest-impact change that takes the least time]\n- [ ] [Second]\n- [ ] [Third]\n```\n\n---\n\n## Critique Anti-Patterns\n\n❌ **Vague taste claims**: \"the colors are off\" → bad. \"The accent saturation is too high — at oklch(0.65 0.25 25) it competes with the primary; reduce to 0.18 chroma to subordinate it\" → good.\n\n❌ **Praise without specifics**: \"looks great!\" provides zero learning. Always say *what* is great and *why*.\n\n❌ **Mixing severity**: putting a critical hierarchy bug next to a polish-level color tweak in the same list. Always sort by ⚠️ → ⚡ → 💡.\n\n❌ **More than 7 fix items**: cognitive overload. If there are more, group them — \"five spacing inconsistencies\" as one item, not five.\n\n❌ **Critiquing without grounding**: every \"Fix\" should reference a principle (hierarchy, craft, philosophy, etc.) so the user understands the *why*, not just the *what*.\n\n❌ **Critiquing the designer instead of the design**: \"you didn't think this through\" is unhelpful and not the agent's role. \"This element doesn't earn its place — consider removing\" is the right framing.\n\n"
|
|
10262
|
+
},
|
|
10263
|
+
{
|
|
10264
|
+
name: "references/design-directions.md",
|
|
10265
|
+
description: "Design Direction Advisor library: 6-school taxonomy, per-school anchor tables, AI-prompt templates (for vague \"give me directions\" requests)",
|
|
10266
|
+
getContent: () => "\n# Design Direction Advisor — Extended Reference\n\nRead this when the request is vague (\"make something nice\", \"I don't know what style I want\") and no design context exists. The main `SKILL.md` already covers the **mechanism** (3 differentiated directions, named designer references, hard rule against same-school picks). This file provides the **school taxonomy** — six high-level philosophical lenses, each with named anchors and the sample copy you use to recommend it.\n\n> **Terminology lock**: this file deals in **schools** (six high-level lenses) and **anchors** (named studios / brands / designers per school). The companion folder `style-recipes/` contains 25 **recipe** files — one file per anchor — with concrete, ready-to-paste configurations. When a user picks a school here, hand them off to the recipe files in that school for concrete palette / typography / spacing values. Load only the recipe files you actually need; the catalog index is at `style-recipes/INDEX.md`.\n\n---\n\n## How to Use This File\n\n1. Read the user's request and the four positioning questions (narrative role / viewing distance / visual temperature / capacity)\n2. Pick **3 schools from different rows** below that genuinely fit the user's context\n3. Recommend each with: named designer/studio + 2–3 lines of \"why this fits you\" + 3–4 signature visual cues + (optional) one famous touchstone work\n4. Wait for the user to pick one (or remix two)\n5. **After the user picks a school → read 2–3 recipe files from `style-recipes/` in that school** (e.g., picked *Information Architecture* → read `style-recipes/pentagram.md` + `style-recipes/bloomberg-terminal.md` + `style-recipes/tufte-dataink.md`). The recipe files carry the concrete values; this file does not duplicate them.\n6. The chosen recipe becomes the design context — write it into `brand-spec.md` and proceed to the main workflow\n\n---\n\n## The Six Schools (1 of 3 must come from each different row)\n\n### 1. Information Architecture\n\n**Vibe**: Rational, data-driven, restrained, hierarchy-led\n**Best for**: Safe / professional / B2B / data products / institutional\n**Why it works**: Treats the page as a *system* of typographic and grid relationships. The \"design\" disappears so the information speaks.\n\n| Anchor | What to borrow |\n|---|---|\n| **Pentagram** (Paula Scher, Michael Bierut) | Bold typography as image; identity through type relationships; sparing color use |\n| **Edward Tufte** | Maximum data-ink ratio; small multiples; smallest sufficient difference |\n| **Massimo Vignelli** | Helvetica-style restraint; strict grid; 6 typefaces is enough for a lifetime |\n| **Bloomberg Terminal** | Mission-critical density; amber-on-near-black; monospaced data |\n| **NYT / Broadsheet editorial** | Multi-deck hierarchy; serif headlines; place-rich photography |\n\n**Concrete starting points** (each is a single file in `style-recipes/` — read one): [`pentagram`](./style-recipes/pentagram.md) · [`vignelli-swiss-helvetica`](./style-recipes/vignelli-swiss-helvetica.md) · [`bloomberg-terminal`](./style-recipes/bloomberg-terminal.md) · [`tufte-dataink`](./style-recipes/tufte-dataink.md) · [`nyt-the-daily`](./style-recipes/nyt-the-daily.md) — each carries the palette, typography, spacing, and signature moves to paste straight into Step 3.\n\n**Sample copy when recommending**:\n> \"Pentagram-style information architecture — your dashboard becomes a system of typographic relationships rather than a UI. Headlines do the heavy visual lifting; everything else recedes. Best when you want institutional credibility and your data is the hero.\"\n\n---\n\n### 2. Editorial / Minimalist\n\n**Vibe**: Whitespace, refined typography, quiet luxury, considered\n**Best for**: Premium / high-end / quiet / lifestyle / prestige B2C\n**Why it works**: Treats whitespace as the primary design material. The reader/viewer gets room to breathe; restraint reads as confidence.\n\n| Anchor | What to borrow |\n|---|---|\n| **Kenya Hara (MUJI)** | Whiteness as a value; *ex-formation*; emptiness as fullness |\n| **Apple HIG / Marketing** | Generous negative space; hero product on white; one-thought-per-screen |\n| **Dieter Rams (Braun)** | \"Less but better\"; honest materials; functional decoration is a contradiction |\n| **Aesop** | Cream/sage palette; serif copy as conversation; product as protagonist |\n| **Monocle** | Magazine-grade kicker / headline / dek hierarchy; international considered |\n\n**Concrete starting points** (each is a single file in `style-recipes/` — read one): [`apple-hig`](./style-recipes/apple-hig.md) · [`muji-kenya-hara`](./style-recipes/muji-kenya-hara.md) · [`aesop`](./style-recipes/aesop.md) · [`dieter-rams-braun`](./style-recipes/dieter-rams-braun.md) · [`monocle-magazine`](./style-recipes/monocle-magazine.md) — each carries the palette, typography, spacing, and signature moves to paste straight into Step 3.\n\n**Sample copy when recommending**:\n> \"Kenya Hara-style editorial minimalism — the page is mostly whitespace, with one serif headline carrying emotional weight and the product anchored in a single hero shot. Best when premium positioning matters more than feature density.\"\n\n---\n\n### 3. Motion / Experimental\n\n**Vibe**: Bold, generative, sensory, kinetic, technical\n**Best for**: Distinctive / launch films / brand moments / awwwards-style / tech storytelling\n**Why it works**: Movement and surprise are the brand. Static screenshots can't capture the experience.\n\n| Anchor | What to borrow |\n|---|---|\n| **Field.io** | Generative type and form; data-driven motion; the page is a system that *makes* itself |\n| **Active Theory** | WebGL hero moments; physics-driven interactions; cinematic transitions |\n| **Resn** | Storytelling through scroll; payoff for exploration; surprise is the reward |\n\n**Concrete starting points** (each is a single file in `style-recipes/` — read one): [`field-io`](./style-recipes/field-io.md) · [`active-theory`](./style-recipes/active-theory.md) · [`resn-storytelling`](./style-recipes/resn-storytelling.md) — each carries the palette, typography, spacing, and signature moves to paste straight into Step 3.\n\n> Note: Vercel / Linear marketing pages use motion *as restraint*, not as the show — they live in the **Modern Tool / Builder SaaS** school below, not here. Reach for this school only when motion is genuinely the brand.\n\n**Sample copy when recommending**:\n> \"Field.io-style motion-led identity — the page generates itself in front of the visitor through choreographed scroll-driven sequences. Best when the launch *moment* matters and your audience will share clips. Note: this is the most labor-intensive of the three; budget accordingly.\"\n\n---\n\n### 4. Brutalist / Raw\n\n**Vibe**: Anti-design, honest, unpolished, confrontational\n**Best for**: Differentiated / confident / counter-culture / publishing / artist platforms\n**Why it works**: Ugly-on-purpose reads as authentic in a sea of polished AI defaults. The lack of consensus aesthetic *is* the aesthetic.\n\n| Anchor | What to borrow |\n|---|---|\n| **Are.na** | Raw HTML feel; system fonts on purpose; content > chrome |\n| **Bloomberg Businessweek covers** (Richard Turley era) | Typographic violence; magazine grid abused; copy as image |\n| **Balenciaga** (post-2017) | Default browser styling weaponized; hero text in Helvetica at absurd scale |\n| **Craigslist (yes, really)** | Information density without apology; everything is a link |\n\n**Concrete starting points** (each is a single file in `style-recipes/` — read one): [`are-na`](./style-recipes/are-na.md) · [`bloomberg-businessweek-turley`](./style-recipes/bloomberg-businessweek-turley.md) · [`balenciaga-post-2017`](./style-recipes/balenciaga-post-2017.md) — each carries the palette, typography, spacing, and signature moves to paste straight into Step 3.\n\n**Sample copy when recommending**:\n> \"Are.na/Bloomberg-style brutalism — system fonts, harsh type contrast, no rounded corners, no shadows. Confrontational on purpose. Best when you're a strong contrarian voice and want to repel the crowd that wants 'modern SaaS.' Warning: half-measures here look broken, not bold.\"\n\n---\n\n### 5. Warm Humanist\n\n**Vibe**: Approachable, organic, hand-touched, friendly without being childish\n**Best for**: Lifestyle / education / approachable B2C / community products / health\n**Why it works**: Conveys that real humans made this for real humans. Counters the \"robot wrote my landing page\" perception.\n\n| Anchor | What to borrow |\n|---|---|\n| **Mailchimp** (early Freddie era) | Hand-drawn marks; warm illustration; personality in microcopy |\n| **Stripe Press** | Editorial serif + warm palette + tactile object photography |\n| **Studio Dumbar** | Identity through movement and personality, not through restraint |\n| **Headspace / Calm** | Soft pastels, rounded everything, breathing-pace motion |\n\n**Concrete starting points** (each is a single file in `style-recipes/` — read one): [`mailchimp-freddie`](./style-recipes/mailchimp-freddie.md) · [`stripe-press`](./style-recipes/stripe-press.md) · [`headspace-meditation`](./style-recipes/headspace-meditation.md) — each carries the palette, typography, spacing, and signature moves to paste straight into Step 3.\n\n> Note: Notion (pre-AI era) borrows from this school's friendly tone but lives in the **Modern Tool / Builder SaaS** school below — it's a tool first, warmth second.\n\n**Sample copy when recommending**:\n> \"Stripe Press / early Mailchimp warmth — humanist serifs, cream palette, illustrations that feel hand-touched. Best when you want trust and approachability over institutional polish. Tone is 'friend who happens to be expert,' not 'expert addressing client.'\"\n\n---\n\n### 6. Modern Tool / Builder SaaS\n\n**Vibe**: Quiet luxury for tools, hairline detail, warm dark + monospace accents\n**Best for**: Developer tools, B2B SaaS, AI tools, infrastructure / platform products, productivity apps\n**Why it works**: Confident restraint reads as \"made by people who use tools,\" not \"made by marketers.\" Hairline borders, monospace shortcut chips, and a single accent color signal craft-led culture without shouting. This is the most under-served school in AI-default output — every model wants to reach for the purple-pink-blue gradient instead.\n\n| Anchor | What to borrow |\n|---|---|\n| **Linear** | Hairline 1px borders, warm dark ground, selective purple accent < 5% of pixels, keyboard-first chips |\n| **Vercel** (recent) | Black + white precision broken by *one* feathered gradient mesh; deploy-log realism in the hero |\n| **Raycast** | Glassy command-palette as hero; per-extension color dots used as small accents |\n| **Notion** (pre-AI era) | Friendly serif headlines + emoji-as-icon on cream surfaces; structure first, warmth second |\n\n**Concrete starting points** (each is a single file in `style-recipes/` — read one): [`linear`](./style-recipes/linear.md) · [`vercel-mesh`](./style-recipes/vercel-mesh.md) · [`raycast`](./style-recipes/raycast.md) · [`notion-pre-ai`](./style-recipes/notion-pre-ai.md) — each carries the palette, typography, spacing, and signature moves to paste straight into Step 3.\n\n**Sample copy when recommending**:\n> \"Linear-style modern-tool aesthetic — warm dark ground, hairline 1px borders, a single purple accent used on less than 5% of pixels, monospace shortcut chips. Best when your audience is technical and 'serious but designed' matters more than 'fun and accessible.' This is the recipe that defends most directly against AI-default Inter + blue button + 16px-radius output.\"\n\n---\n\n## When the User Picks (or Remixes)\n\nCommon user responses:\n\n- **\"I'll go with #2.\"** → Direction confirmed. Write it into `brand-spec.md`. Proceed to Step 2 with this as design context.\n- **\"I like A's color but C's layout.\"** → Confirm the remix in writing (\"So: minimalist editorial palette + motion-led layout choreography. Right?\"), then proceed.\n- **\"None of these feel right — show me more.\"** → Ask one targeted question to narrow (\"Are you closer to formal/institutional or playful/expressive?\"), then offer 3 fresh directions from rows you didn't show before.\n- **\"I don't know, you pick.\"** → Pick the safest one (usually Editorial / Minimalist), state your reasoning, and propose a 5-minute v0 to validate before committing.\n\n---\n\n## AI-Prompt Templates (when generating imagery to support a direction)\n\nFormat: `[philosophy DNA] + [content description] + [technical params]`\n\n✅ **Good** (specific characteristics):\n> \"Kenya Hara-influenced minimalism with 80% whitespace, single muted terracotta (#C04A1A) accent, GT Sectra serif headline, single product hero on warm off-white (#F2EFE8) ground, soft top-down lighting, 3:2 aspect\"\n\n❌ **Bad** (style names without DNA):\n> \"minimalist style, premium feel, high quality\"\n\nAlways include:\n- Color HEX (not \"warm\" / \"cool\")\n- Aspect ratio and dimensions\n- Composition rules (rule-of-thirds, centered, asymmetric)\n- What to *avoid* (e.g., \"no purple gradient, no emoji, no rounded cards\")\n\n> Each recipe file in `style-recipes/` ships a pre-written **AI prompt seed** tuned to that recipe's DNA — start from the one you're using rather than writing prompts from scratch.\n\n---\n\n## Anti-Patterns in Direction Recommendation\n\n❌ **Recommending 3 picks from the same row** — the user can't tell them apart; the entire point of \"differentiated directions\" collapses\n\n❌ **Recommending \"minimalism\" / \"modern\" / \"clean\"** as the direction name — these are not directions, they are AI-default words. Always anchor on a named designer/studio.\n\n❌ **Recommending without any \"why this fits you\"** — the user wanted *guidance*, not a multiple-choice quiz. Each option must explain its fit to their context (audience, purpose, budget, brand maturity).\n\n❌ **Showing 5+ directions** — choice paralysis. 3 is the sweet spot. If the first 3 all miss, ask one narrowing question and offer 3 fresh ones.\n\n❌ **Asking the user to score each direction 1–10** — that's offloading the recommendation back to them. Make a recommendation; the user will agree or push back.\n\n"
|
|
10267
|
+
},
|
|
10268
|
+
{
|
|
10269
|
+
name: "references/style-recipes/active-theory.md",
|
|
10270
|
+
description: "Active Theory (Cinematic WebGL) · Cinematic web experiences, WebGL heroes, physical-feeling interaction",
|
|
10271
|
+
getContent: () => "\n# active-theory — Active Theory (Cinematic WebGL)\n\n- **School**: Motion / Experimental\n- **Vibe**: Cinematic web experiences, WebGL heroes, physical-feeling interaction\n- **Best for**: Brand launch sites, game / entertainment products, \"experience marketing\" pieces\n- **Touchstone**: activetheory.net, NASA / Apple WWDC dev portals they've made, Doritos / movie tie-in launches\n\n**Palette**\n- Often a single dramatic hue from the project's content — black + one signature color from the brand or film\n- High contrast — deep black + bright accent\n- Tinted neutrals — never plain gray; gray-with-cast (cool blue cast for sci-fi, warm amber for cinematic)\n\n**Typography**\n- Display: a strong grotesque or a custom display face built for the campaign — Druk, Editorial New, ABC Diatype Mono\n- Body type secondary — most content rides over imagery; less reading, more witnessing\n- All-caps display common, with very tight or very open tracking depending on tone\n\n**Spacing**: cinematic — content sits centered or in unexpected corners against a full-bleed canvas\n\n**Radius**: 0\n\n**Shadow**: from WebGL lighting, not CSS\n\n**Motion**: feature-film-grade. Camera moves through a 3D space. Physics-driven debris / particles. The page is a stage.\n\n**Signature moves**\n- A full-screen WebGL hero scene that the user moves through (scroll = camera path)\n- Real-time physics or particle systems responding to cursor / device tilt\n- Carefully art-directed transitions between scenes (not generic fades)\n- Sound design integrated (subtle ambient audio that ducks during text passages)\n- A single moment of maximum impact — the recipe builds toward one payoff frame\n\n**Avoid**\n- Many small WebGL moments (one big set-piece is the recipe, not five small ones)\n- Trying to ship a content-heavy site this way (cinematic recipes work for marketing moments, not docs)\n- Reaching for off-the-shelf Three.js demos (this recipe demands hand-crafted scenes — generic WebGL reads as cheap)\n\n**AI prompt seed**\n> Cinematic VFX still, single dramatic moment from a sci-fi launch film, key light from one direction, deep shadows, single brand-accent hue floating in the scene, particle debris in air, 2.39:1 aspect.\n\n**Don't use when**\n- Performance / accessibility constraints rule out heavy WebGL\n- The product is utilitarian (this recipe is for moments, not for daily use)\n- The build budget is sub-3-weeks\n\n---\n\n> **Same school — Motion / Experimental**: [`field-io`](./field-io.md) · [`resn-storytelling`](./resn-storytelling.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10272
|
+
},
|
|
10273
|
+
{
|
|
10274
|
+
name: "references/style-recipes/aesop.md",
|
|
10275
|
+
description: "Aesop Skincare · Apothecary refinement, sage and ink, serif copy as conversation",
|
|
10276
|
+
getContent: () => "\n# aesop — Aesop Skincare\n\n- **School**: Editorial / Minimalist\n- **Vibe**: Apothecary refinement, sage and ink, serif copy as conversation\n- **Best for**: Premium consumer goods (skincare, candles, eyewear), restaurants with literary aspirations, hospitality\n- **Touchstone**: aesop.com, Aesop store windows, the brown amber bottle\n\n**Palette**\n- Ground: `#E8E4D9` (warm chamois) — sometimes `#F2EFE5`\n- Ink: `#1B1B1B` (true near-black for copy)\n- Sage muted: `#7A8470`\n- Amber bottle accent: `#7A4623` (used very sparingly, e.g., a single rule or seal mark)\n- Cream surface: `#F0EDE4`\n\n**Typography**\n- Display & body: a single transitional serif at every size — Suisse Works, Lyon Text, or GT Sectra (weight 400, italic available). Aesop pairs serif for body copy with a single sans for UI labels (Söhne or Helvetica Now).\n- Body size: 16–18px; line-height ~1.65\n- Labels in small caps (10–11px, letter-spaced 0.12em)\n\n**Spacing**: 8 / 16 / 24 / 40 / 72 / 120\n\n**Radius**: 0 throughout, including form fields. Aesop is square-edged.\n\n**Shadow**: none.\n\n**Motion**: subtle fades, slow drawer slide-ins (~450ms ease-out). Never spring-bounce.\n\n**Signature moves**\n- Body copy reads like product writing in a literary magazine — long, well-cadenced sentences, justified text, no marketing punchiness\n- Product is photographed on a warm chamois surface with a single sage prop (leaf, marble dish, linen) and a long shadow\n- Small-caps section labels (`INGREDIENTS` / `RITUAL`) above each block\n- All-text navigation in the masthead, letter-spaced\n- Asymmetric layouts: image left, generous right-margin block of copy; never centered-everything\n\n**Avoid**\n- Bold weights anywhere — Aesop is all regular and italic\n- Color other than sage / amber / ink — no blues, no purples, no UI-blue accents\n- Pop-up modals (Aesop's UI politely waits)\n- Lifestyle stock photography of smiling models — props and product only\n\n**AI prompt seed**\n> Amber-glass apothecary bottle on warm chamois #E8E4D9 textured paper, single linen napkin and dried sage sprig, raking afternoon window light, deep soft shadow at lower right, 4:5, editorial product photography, color grading warm with slight green cast.\n\n**Don't use when**\n- The product is software or digital-first — Aesop's tactile object photography defines half the recipe; you can't fake it with CSS\n- The audience expects price-comparison utility — Aesop never shows pricing prominently\n- The voice needs to be funny or casual — Aesop is sincere and slow\n\n---\n\n> **Same school — Editorial / Minimalist**: [`apple-hig`](./apple-hig.md) · [`muji-kenya-hara`](./muji-kenya-hara.md) · [`dieter-rams-braun`](./dieter-rams-braun.md) · [`monocle-magazine`](./monocle-magazine.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10277
|
+
},
|
|
10278
|
+
{
|
|
10279
|
+
name: "references/style-recipes/apple-hig.md",
|
|
10280
|
+
description: "Apple Human Interface · Generous space, hero product on white, one thought per screen",
|
|
10281
|
+
getContent: () => "\n# apple-hig — Apple Human Interface\n\n- **School**: Editorial / Minimalist\n- **Vibe**: Generous space, hero product on white, one thought per screen\n- **Best for**: Premium hardware, premium software, \"this product deserves a stage\" feel\n- **Touchstone**: apple.com product pages, Apple keynote slides, HIG documentation\n\n**Palette**\n- Ground: `#FFFFFF` (paper-pure white) or `#000000` (deep black, hero film moments only)\n- Ink: `#1D1D1F` (Apple's not-quite-black)\n- Soft surface: `#F5F5F7`\n- Muted text: `#86868B`\n- Accent (sparingly): system-blue `#0071E3`\n\n**Typography**\n- Display: SF Pro Display (weight 600 for headlines, 400 for sub-display) — fall back to Inter Tight only when SF Pro is unreachable\n- Body: SF Pro Text at 17px, line-height ~1.47\n- Captions: SF Pro at 12–14px, weight 400, color `#86868B`\n\n**Spacing**: 4 / 8 / 16 / 24 / 40 / 64 / 96 / 160. Section breaks are large (160px+ vertical). Headlines breathe.\n\n**Radius**: 12 (small components) / 18 (cards) / 22 (large panels). Apple is rarely sharp-cornered, never gummy-bear rounded.\n\n**Shadow**: barely visible. `0 1px 2px rgba(0,0,0,0.04)` at most. Elevation through whitespace and color contrast, not shadow.\n\n**Motion**: cubic-bezier expo-out; durations 350–650ms for layout moves, 150–250ms for hover. Never bouncy. Often a slow Ken Burns on hero imagery.\n\n**Signature moves**\n- One product photograph centered on whitespace, occupying ~40% of the hero\n- Hero headline in big display weight, body line below in muted gray (`#86868B`), single CTA in system-blue text-link form (no chunky button on hero — that comes later)\n- Section anatomy: tiny eyebrow label → big headline → one paragraph → one product shot → repeat. No tabs, no cards, no bento.\n- Hairline `#D2D2D7` dividers between sections, never anything heavier\n- Numbers are display-weight characters (e.g., a stat reads `12.9″`, with the `″` set in a smaller weight)\n\n**Avoid**\n- Any gradient mesh, glow, or \"tech atmosphere\" — Apple's gradients are almost always subtle silver-to-charcoal on a product surface, not background decoration\n- Stacking multiple CTAs in the hero\n- Card grids of features (Apple uses long vertical sections, one idea each)\n- Emoji of any kind on marketing pages\n\n**AI prompt seed** (for hero imagery)\n> Studio-lit product photograph, single object centered on pure-white #FFFFFF ground, soft top-down diffused light, subtle floor reflection, 16:9, no text, no people, no shadow drama, color grading neutral.\n\n**Don't use when**\n- The product is software-only and doesn't have a \"hero object\" to photograph — you'll end up with a blank stage\n- The brand wants to feel scrappy / startup / accessible — Apple HIG reads as expensive and serious\n- Audience is anti-tech-corporate — it'll feel \"stale Apple cosplay\"\n\n---\n\n> **Same school — Editorial / Minimalist**: [`muji-kenya-hara`](./muji-kenya-hara.md) · [`aesop`](./aesop.md) · [`dieter-rams-braun`](./dieter-rams-braun.md) · [`monocle-magazine`](./monocle-magazine.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10282
|
+
},
|
|
10283
|
+
{
|
|
10284
|
+
name: "references/style-recipes/are-na.md",
|
|
10285
|
+
description: "Are.na (Honest Web) · System fonts on purpose, content > chrome, the web of the late 90s but considered",
|
|
10286
|
+
getContent: () => "\n# are-na — Are.na (Honest Web)\n\n- **School**: Brutalist / Raw\n- **Vibe**: System fonts on purpose, content > chrome, the web of the late 90s but considered\n- **Best for**: Indie tools, creative platforms, \"research-flavored\" products, artist communities\n- **Touchstone**: are.na, the Are.na annual\n\n**Palette**\n- Ground: `#FFFFFF`\n- Ink: `#000000`\n- Are.na blue link: `#0000EE` (the classic browser-default blue, used unironically)\n- Visited link: `#551A8B` (also classic browser-default)\n- Hairline: `#CCCCCC`\n- Optional: warm cream surface `#F4F1E8` for cards\n\n**Typography**\n- Display & body: system serif (Times / Georgia) and / or system sans (the OS's default sans) on purpose\n- No custom font loading — the recipe leans on letting the browser pick\n- Body at 14–16px, line-height ~1.45\n- Italic and underlined links (also browser-default)\n\n**Spacing**: 4 / 8 / 16 / 24 / 32. Tight. Content-dense.\n\n**Radius**: 0\n\n**Shadow**: none\n\n**Motion**: none, or instant\n\n**Signature moves**\n- Underlined blue links, visited purple — left as browser default\n- Headings are just larger versions of body type, no special font\n- Tables, lists, and prose all flow without ornament\n- Footers are tiny, plain, link-rich (the early-web \"all links\" footer)\n- Cards (if used) are flat rectangles with a 1px gray border and no shadow\n\n**Avoid**\n- Custom fonts of any kind\n- Color other than the blue / purple link colors and the ink + ground\n- Hover animations, transitions of any kind\n- Modals, tooltips, micro-interactions\n- Any sense of \"polish\"\n\n**AI prompt seed**\n> Found-photograph collage on plain white paper, mixed sources, no Photoshop polish, slight imperfection, hand-cut feel, 4:3.\n\n**Don't use when**\n- The brand needs to feel \"premium\" — Are.na's recipe explicitly rejects premium signaling\n- The audience expects modern SaaS UX cues — they may bounce, reading the page as broken\n- Half-measures will be tempted — commit fully or pick another recipe; partial Are.na looks broken, not curated\n\n---\n\n> **Same school — Brutalist / Raw**: [`bloomberg-businessweek-turley`](./bloomberg-businessweek-turley.md) · [`balenciaga-post-2017`](./balenciaga-post-2017.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10287
|
+
},
|
|
10288
|
+
{
|
|
10289
|
+
name: "references/style-recipes/balenciaga-post-2017.md",
|
|
10290
|
+
description: "Balenciaga (post-Demna era) · Default browser styling weaponized, Helvetica at absurd scale, anti-luxury luxury",
|
|
10291
|
+
getContent: () => "\n# balenciaga-post-2017 — Balenciaga (post-Demna era)\n\n- **School**: Brutalist / Raw\n- **Vibe**: Default browser styling weaponized, Helvetica at absurd scale, anti-luxury luxury\n- **Best for**: Fashion / counter-culture / fragrance brands, \"we're too cool to design this\" positioning\n- **Touchstone**: balenciaga.com (any 2018+ snapshot via Wayback), Vetements campaigns, post-2017 luxury anti-design\n\n**Palette**\n- Ink: `#000000`\n- Ground: `#FFFFFF`\n- One muted off-white surface: `#F5F4F0`\n- No color. None. If a color appears it's in a single photograph and never as a UI element.\n\n**Typography**\n- Display & body: Helvetica or Arial — all weights, all sizes, *only this one family*\n- Headlines often set in all-caps at hero scale (140px+)\n- Body type small — 11–13px\n- Mixing weight is fine; mixing family is not\n\n**Spacing**: 8 / 16 / 24 / 40 — restrained, but not luxurious-spacious; more \"we didn't bother to soften it\"\n\n**Radius**: 0\n\n**Shadow**: none\n\n**Motion**: none — pages don't animate. Hover at most changes underline.\n\n**Signature moves**\n- All-caps Helvetica headlines at extreme scale, tightly tracked, often left-aligned hard against the page edge\n- Product photography is editorial / fashion-style — model + product, harsh lighting, awkward poses; never the smiling-stock-model pattern\n- Navigation is plain text links, sometimes just a vertical list at the top of the page\n- Captions and metadata stripped to minimum (model name, designer, location)\n- No buttons styled as buttons — CTAs are underlined text links\n\n**Avoid**\n- Any font other than Helvetica / Arial\n- Color\n- Polished UI affordances (rounded buttons, drop shadows, hover effects)\n- Decorative anything\n\n**AI prompt seed**\n> Fashion editorial photograph, single model against a plain off-white #F5F4F0 backdrop, harsh direct flash, awkward static pose, deadpan expression, 4:5.\n\n**Don't use when**\n- The brand has no fashion / counter-culture claim — recipe will read as cosplay\n- The audience expects to be \"sold to\" — Balenciaga's recipe ignores the audience on purpose\n- Conversion is critical — this recipe converts the in-group, not the casual visitor\n\n---\n\n> **Same school — Brutalist / Raw**: [`are-na`](./are-na.md) · [`bloomberg-businessweek-turley`](./bloomberg-businessweek-turley.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10292
|
+
},
|
|
10293
|
+
{
|
|
10294
|
+
name: "references/style-recipes/bloomberg-businessweek-turley.md",
|
|
10295
|
+
description: "Bloomberg Businessweek (Turley Era) · Typographic violence, magazine grid abused, copy *is* image",
|
|
10296
|
+
getContent: () => "\n# bloomberg-businessweek-turley — Bloomberg Businessweek (Turley Era)\n\n- **School**: Brutalist / Raw\n- **Vibe**: Typographic violence, magazine grid abused, copy *is* image\n- **Best for**: Editorial features, agency portfolios, opinionated content sites\n- **Touchstone**: Bloomberg Businessweek 2014–2019 covers (Richard Turley → Tracy Ma era), *Eye* magazine archive issues\n\n**Palette**\n- Aggressive — `#FF3D00` (high-alert orange), `#FFE800` (signal yellow), `#001AFF` (alert blue)\n- Always on `#FFFFFF` for max contrast — or inverted onto `#000000`\n- Black `#000000` text on white where readable; otherwise contrasting hue\n- Maximum 3 colors per spread\n\n**Typography**\n- Display: a single bold grotesque used at absurd scale — Founders Grotesk, Druk, ABC Whyte. Weight 800–900, set 200px+\n- Headlines often broken across lines on purpose, kerned tighter than comfortable\n- Body type left small in contrast — 12–14px serif (Plantin / Mercury)\n- All-caps + condensed common in display\n\n**Spacing**: deliberately broken — content pushed off-grid, sometimes touching edges or overflowing intentionally\n\n**Radius**: 0\n\n**Shadow**: none\n\n**Motion**: jarring cuts, not smooth eases. If anything moves it slams into place.\n\n**Signature moves**\n- Headline set so large it visibly breaks the grid (letters touching page edges)\n- Color blocks of saturated orange / yellow / blue placed under or behind type\n- Photo cutouts (subjects silhouetted, dropped onto color fields) — often awkwardly cropped on purpose\n- Tables of data styled like magazine spreads, not like UI components — bold, contrasty, opinionated\n- Captions in tiny serif italic positioned in unexpected corners\n\n**Avoid**\n- Tasteful design (the recipe is anti-tasteful on purpose)\n- More than 3 colors per spread\n- Smooth transitions\n- Web defaults (every element should feel set, not generated)\n\n**AI prompt seed**\n> Magazine cover composition, single bold word set in massive black grotesque at 200pt, behind it a flat field of saturated alert-orange #FF3D00, with a clumsily-cut photo silhouette overlaid at unexpected angle, slight print misregistration, 3:4.\n\n**Don't use when**\n- The brand voice is meant to feel calm or trustworthy — Turley-era Bloomberg is loud\n- The content is utility-focused — recipe is editorial only\n- The team is squeamish about deliberate ugliness — this recipe requires committing to it\n\n---\n\n> **Same school — Brutalist / Raw**: [`are-na`](./are-na.md) · [`balenciaga-post-2017`](./balenciaga-post-2017.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10297
|
+
},
|
|
10298
|
+
{
|
|
10299
|
+
name: "references/style-recipes/bloomberg-terminal.md",
|
|
10300
|
+
description: "Bloomberg Terminal · Maximum data-ink, mission-critical density, chrome-amber on near-black",
|
|
10301
|
+
getContent: () => "\n# bloomberg-terminal — Bloomberg Terminal\n\n- **School**: Information Architecture\n- **Vibe**: Maximum data-ink, mission-critical density, chrome-amber on near-black\n- **Best for**: Finance / trading / monitoring dashboards, \"professionals don't have time for whitespace\" products\n- **Touchstone**: a real Bloomberg Terminal screenshot (always reference the real thing — it's denser than memory suggests)\n\n**Palette**\n- Ground: `#0A0E1A` (deep navy-black)\n- Surface 1: `#11172A`\n- Surface 2: `#1A2138`\n- Amber primary: `#FFA02F` (the signature)\n- Positive (price-up): `#00B96B`\n- Negative (price-down): `#F23645`\n- Muted label: `#5E6680`\n- High-importance text: `#E8ECF4`\n\n**Typography**\n- Display & body: a monospaced workhorse — IBM Plex Mono, JetBrains Mono, or Berkeley Mono. Bloomberg itself uses a proprietary mono.\n- Sizes: 11 / 12 / 13 / 14px — densely packed. No 16px body. No 32px headlines.\n- All numerals tabular-aligned (right-aligned columns of digits)\n\n**Spacing**: 2 / 4 / 8 / 12. Multi-pane layouts with no luxury margins.\n\n**Radius**: 0 or 2px. Terminals don't round.\n\n**Shadow**: none. Elevation through hairline borders only (`#2A3050` 1px lines).\n\n**Motion**: ticker scroll (uniform linear), instant state flips, blink on data update (50–80ms flash). No eased animations.\n\n**Signature moves**\n- Multi-pane workspaces with hairline dividers — 4 to 9 panels visible at once\n- Amber text on near-black for the most important data; chrome-white for secondary\n- Status / ticker bar at top with marquee-scrolling tickers, color-coded up/down\n- Keyboard-shortcut chips in the margins (`F9: TRADE`)\n- Tabular data with monospaced digits, color-coded by delta\n\n**Avoid**\n- Any rounded corners\n- Hero sections / marketing-style headlines\n- Photography or illustration of any kind\n- Decorative gradients (gradient meshes are not data-ink)\n- Sans-serif body type (the monospace is the recipe)\n\n**AI prompt seed**\n> Trading workstation user interface, deep navy #0A0E1A background, multi-pane layout with amber #FFA02F headlines, monospaced data tables, ticker scroll across the top, no rounded corners, no illustrations, fixed-width font throughout.\n\n**Don't use when**\n- The audience is consumer (they'll bounce in 3 seconds — terminal density is acquired taste)\n- You can't fill the screen with real data — terminal aesthetic + dummy \"Lorem 1,234\" placeholders looks broken\n- The task is a marketing landing page — terminals are for working in, not for selling\n\n---\n\n> **Same school — Information Architecture**: [`pentagram`](./pentagram.md) · [`vignelli-swiss-helvetica`](./vignelli-swiss-helvetica.md) · [`tufte-dataink`](./tufte-dataink.md) · [`nyt-the-daily`](./nyt-the-daily.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10302
|
+
},
|
|
10303
|
+
{
|
|
10304
|
+
name: "references/style-recipes/dieter-rams-braun.md",
|
|
10305
|
+
description: "Dieter Rams / Braun · \"Less but better\", functional honesty, industrial restraint",
|
|
10306
|
+
getContent: () => "\n# dieter-rams-braun — Dieter Rams / Braun\n\n- **School**: Editorial / Minimalist\n- **Vibe**: \"Less but better\", functional honesty, industrial restraint\n- **Best for**: Hardware product pages, audio gear, industrial / tool brands, design-led B2B\n- **Touchstone**: Braun T3 radio, Braun ET66 calculator, Rams's *Ten Principles of Good Design*\n\n**Palette**\n- Ground: `#E4E1DC` (concrete light) or `#F5F4F0` (paper)\n- Ink: `#191919`\n- Industrial gray: `#A8A8A8`, `#5C5C5C`\n- Accent: single signal — Braun orange `#E96A26` or signal yellow `#F5C518`, used only on functional elements (a power dot, an active state)\n\n**Typography**\n- Display & body: a precision grotesk — Akzidenz-Grotesk, Helvetica Now, or Söhne. Weight 400 body, weight 500 for emphasis, never 700.\n- Numerals love a monospaced variant for spec tables\n\n**Spacing**: based on a 4px sub-grid; visible values 4 / 8 / 16 / 24 / 32 / 64 / 128. Functional, no \"luxury whitespace\" inflation.\n\n**Radius**: 2–4px maximum. Sharp by default.\n\n**Shadow**: none.\n\n**Motion**: utilitarian — instant state changes (~80–120ms), no flourish.\n\n**Signature moves**\n- Diagrammatic exploded views of the product with measurement callouts (real or fictional)\n- A single signal-orange dot on the page indicating \"active\" or \"power\" — never decorative\n- Tabular spec listings as a first-class layout element (not hidden behind a \"Specs\" toggle)\n- Every label exactly what it is, in plain language (\"ON / OFF\" not \"Toggle interaction mode\")\n- The page itself feels like a piece of equipment\n\n**Avoid**\n- Photography that romanticizes the product — Braun shoots it on a grid background like an engineering specimen\n- Multiple accent colors\n- Curves anywhere — even hover effects shouldn't bounce\n- Lifestyle imagery\n\n**AI prompt seed**\n> Industrial product photograph, 1960s Braun design language, single hardware object on light gray #E4E1DC grid background, frontal orthographic view, dimensions and labels in thin sans serif, color palette restricted to gray / black / single orange marker, 4:3.\n\n**Don't use when**\n- The product is soft / emotional / lifestyle — Rams discipline reads as cold for skincare or hospitality\n- The team wants warmth or personality — this recipe is impersonal by design\n- The audience is consumer / non-technical — they may read it as \"boring spec sheet\"\n\n---\n\n> **Same school — Editorial / Minimalist**: [`apple-hig`](./apple-hig.md) · [`muji-kenya-hara`](./muji-kenya-hara.md) · [`aesop`](./aesop.md) · [`monocle-magazine`](./monocle-magazine.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10307
|
+
},
|
|
10308
|
+
{
|
|
10309
|
+
name: "references/style-recipes/field-io.md",
|
|
10310
|
+
description: "Field.io (Generative Motion Identity) · The brand is movement; the page generates itself in front of the visitor",
|
|
10311
|
+
getContent: () => "\n# field-io — Field.io (Generative Motion Identity)\n\n- **School**: Motion / Experimental\n- **Vibe**: The brand is movement; the page generates itself in front of the visitor\n- **Best for**: Brand films, launch moments, agency portfolios, \"the visit is the experience\" sites\n- **Touchstone**: field.io, FIELD.SYSTEMS work, kinetic identity case studies\n\n**Palette**\n- Often dark base — `#0B0B0F` to `#000000`\n- One or two generative gradient hues used throughout the motion — frequently a single bold hue cycled through hue rotation, e.g., a deep cyan `#0CE0E5` blending into electric violet `#5B2EFF`, or generated procedurally\n- Ink for type: high-contrast white `#FFFFFF`\n- Secondary type: cool gray `#A0A4B0`\n\n**Typography**\n- Display: a variable font that *animates* on its own axes — Söhne Variable, Editorial New Variable, or Inter Display Variable\n- Type often morphs weight, width, or optical-size during scroll\n- Body type kept restrained — a single grotesque at neutral weight; the motion is the show, not the body\n\n**Spacing**: irregular by design — content lands in unexpected positions; the grid sometimes appears and disappears\n\n**Radius**: 0 — Field.io's work is rarely soft-cornered\n\n**Shadow**: not via CSS — through generative lighting in WebGL / Canvas scenes\n\n**Motion**: the entire recipe is motion. Multi-stage choreographed sequences. Scroll-driven not just for parallax but for state changes. Pages frequently feel like a video playing itself.\n\n**Signature moves**\n- Generative type sequences where letters appear, morph, and resolve into headlines on scroll\n- Particle / mesh systems that respond to cursor and scroll position\n- Long-tail eased curves (expo-out, quint-out) — `cubic-bezier(0.83, 0, 0.17, 1)` and similar\n- Section breaks where the entire page state transforms (a full-bleed canvas overtakes the layout)\n- Choreographed multi-element entries — six elements arrive on staggered delays, not all at once\n\n**Avoid**\n- Static recipes (this isn't a static recipe — a static screenshot will feel underwhelming)\n- Too many cursor-reactive elements (one or two key WebGL moments is the recipe, not the whole page)\n- Heavy text content — this recipe is for moments, not for reading\n\n**AI prompt seed**\n> Generative type composition, single phrase resolving from particle field, electric violet #5B2EFF and deep cyan #0CE0E5 light traces, on near-black background, long motion trails, 16:9 cinematic.\n\n**Don't use when**\n- The deliverable will live as a static screenshot (the recipe loses ~70% of its impact)\n- Build budget is small (this is the most labor-intensive of the 25)\n- The target audience uses low-end hardware or care about performance / accessibility above wow\n\n---\n\n> **Same school — Motion / Experimental**: [`active-theory`](./active-theory.md) · [`resn-storytelling`](./resn-storytelling.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10312
|
+
},
|
|
10313
|
+
{
|
|
10314
|
+
name: "references/style-recipes/headspace-meditation.md",
|
|
10315
|
+
description: "Headspace / Calm · Soft pastels, rounded everything, calming hand-drawn illustration",
|
|
10316
|
+
getContent: () => "\n# headspace-meditation — Headspace / Calm\n\n- **School**: Warm Humanist\n- **Vibe**: Soft pastels, rounded everything, calming hand-drawn illustration\n- **Best for**: Wellness / meditation / health apps, sleep / kids products, \"approachable expert\" services\n- **Touchstone**: headspace.com, the Headspace app, Calm.com lock-screen aesthetic\n\n**Palette**\n- Ground: warm peach `#FFE2C5` or `#FFEDD5`\n- Surface darker: warm coral `#F4A573`\n- Ink: warm dark teal `#1B3A47`\n- Secondary: `#5C6B7A`\n- Pop accents: muted lavender `#B0A5D1`, sage `#9DB67A`, salmon `#F5867B` (one per scene, rotating)\n\n**Typography**\n- Display: a rounded friendly sans — Apercu, GT America (rounded variant), Söhne Breit. Weight 600.\n- Body: same family at 16–17px, weight 400\n- Optional script for emotional moments — a single soft handwritten line in something like Caveat, used very sparingly\n\n**Spacing**: generous — 8 / 16 / 24 / 40 / 64 / 96\n\n**Radius**: large — 16 (small) / 24 (medium) / 32 (large). Buttons are pills (radius = height / 2).\n\n**Shadow**: soft and warm — `0 8px 24px rgba(244, 165, 115, 0.2)`. Often colored to match the ground.\n\n**Motion**: gentle, slow, breathing. 400–800ms eases. Hover lifts have a \"buoyant\" feel. A subtle breathing animation (1.5–4s cycle) on key elements.\n\n**Signature moves**\n- A central character / mascot illustrated in simple round shapes (Headspace's orange circle character)\n- Animated breathing circles that expand and contract in sync with body copy (\"breathe in… breathe out…\")\n- Warm peach gradient grounds that slowly drift between hues\n- Rounded everything — every container is a pill or a soft-corner rectangle\n- Calming microcopy (\"Take a moment. We'll wait.\")\n\n**Avoid**\n- Cool palettes — Headspace lives in warm tones, even at night-mode (warm dark teal not cool navy)\n- Sharp corners anywhere\n- Harsh motion (no bouncy springs — too jolting; gentle eases only)\n- More than 2 colors per scene\n\n**AI prompt seed**\n> Simple flat illustration in warm peach #FFE2C5 background, single round mascot character in coral with friendly face, soft pastel shapes around it (sage, lavender), no harsh edges anywhere, gentle hand-drawn imperfection, 4:5.\n\n**Don't use when**\n- The product is meant to feel sharp / sophisticated — Headspace's recipe reads as soft / caring\n- The brand has no wellness / care positioning — it'll feel infantilizing\n- The voice is meant to be authoritative or expert-led — recipe is gentle, not commanding\n\n---\n\n> **Same school — Warm Humanist**: [`mailchimp-freddie`](./mailchimp-freddie.md) · [`stripe-press`](./stripe-press.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10317
|
+
},
|
|
10318
|
+
{
|
|
10319
|
+
name: "references/style-recipes/INDEX.md",
|
|
10320
|
+
description: "Recipe catalog index: 25 named recipes with 3 indexes (by school / by best-for / by light-dark) + cross-cutting anti-patterns",
|
|
10321
|
+
getContent: () => "\n# Style Recipes — Catalog Index\n\nA catalog of 25 named, anchored design recipes — each one tied to a real brand / studio / designer and **stored as its own file in this directory**. Read this INDEX to discover what's available and how to choose; then read **one recipe file** to get the concrete values for Step 3.\n\n```\nreferences/style-recipes/\n├── INDEX.md ← you are here\n├── apple-hig.md\n├── muji-kenya-hara.md\n├── aesop.md\n├── ... (25 recipe files total)\n```\n\nThis catalog is the **anchored library**; `../design-directions.md` is the **school taxonomy** for vague-request conversations. The two work together:\n\n| Route | Tool | When |\n|---|---|---|\n| User has no idea — needs guidance | `../design-directions.md` (6-school 3-pick conversation) | \"Make me something nice\" / \"I don't know what style I want\" |\n| User has an anchor in mind | Read **one** file here directly | \"Make it Linear-style\" / \"Stripe Press feeling\" / \"Are.na vibe\" |\n| Direction Advisor narrowed to a school but user wants concreteness | Read this INDEX for the school's recipes → then read the 2–3 specific recipe files | Mid-conversation handoff |\n\n---\n\n## When to Read Recipe Files\n\n**Read a single recipe file** when:\n\n1. **The user names an anchor by brand, studio, or designer** — \"make a Linear-style landing page\", \"Aesop-feeling product page\", \"MUJI quietness\", \"Pentagram-grade type system\". Read **only that anchor's file** (`linear.md`, `aesop.md`, etc.) — never the whole catalog.\n2. **The Direction Advisor narrowed to a school** — read this INDEX to see which recipes live in that school, then read 2–3 specific files to present the user concrete choices.\n3. **You're in Step 3 and need a known-good palette / typography / spacing combo** — pick the closest recipe by school or best-for table below, then read that single file and adapt.\n\nDo **not** load every recipe file up front. The entire catalog is ~1400 lines if loaded together; loading one recipe is ~50 lines. **Loading the whole catalog when you only need one recipe is the exact anti-pattern this split is designed to prevent.**\n\nDo **not** read recipe files when:\n- The user provided their own brand assets / Figma / codebase — extract from those instead (Asset > Spec).\n- The task is dictated by an existing UI you're extending — match the visual vocabulary already there, don't impose a recipe.\n- The user gave you a screenshot of a specific reference page — that screenshot *is* the recipe; extract directly.\n\n---\n\n## File Format Conventions (every recipe file follows this anatomy)\n\n- **Title line** — `# <anchor-id> — <Human-readable name>`\n- **Anchor & school** — which real-world reference and which Direction-Advisor school it belongs to\n- **Vibe & best-for** — 1-line vibe summary plus the scenarios it actually serves\n- **Touchstone** — at least one named real product / object / publication to look at (search and verify before relying on memory)\n- **Palette** — hex values, named by role (ink / surface / accent / ground). Always restricted; never a 12-stop ramp.\n- **Typography** — real font names with weight numbers and size guidance. No \"Inter / Roboto\" fallbacks unless the anchor itself uses them.\n- **Spacing system** — concrete value ladder\n- **Radius / Shadow / Motion character** — described in **design language**, not code\n- **Signature moves** — 3–5 specific, opinionated, copy-able design decisions that make this recipe *recognisable*. This is the design DNA.\n- **Avoid** — anti-patterns inside this recipe (i.e., things that would silently turn it into AI slop)\n- **AI prompt seed** — when generating supporting imagery, what to ask for to stay in DNA\n- **Don't use when** — the boundary; situations where this recipe will misfire\n- **Footer** — peer recipes in the same school + a link back to this INDEX\n\n**These files contain no code.** Hex codes, font names, and spacing ladders are *design tokens described in words*, not code. The agent translates them to CSS / JSX in Step 3+ using the project's stack.\n\n---\n\n## Index 1 — By School\n\n| School | Recipes |\n|---|---|\n| **Editorial / Minimalist** | [`apple-hig`](./apple-hig.md) · [`muji-kenya-hara`](./muji-kenya-hara.md) · [`aesop`](./aesop.md) · [`dieter-rams-braun`](./dieter-rams-braun.md) · [`monocle-magazine`](./monocle-magazine.md) |\n| **Information Architecture** | [`pentagram`](./pentagram.md) · [`vignelli-swiss-helvetica`](./vignelli-swiss-helvetica.md) · [`bloomberg-terminal`](./bloomberg-terminal.md) · [`tufte-dataink`](./tufte-dataink.md) · [`nyt-the-daily`](./nyt-the-daily.md) |\n| **Modern Tool / Builder SaaS** | [`linear`](./linear.md) · [`vercel-mesh`](./vercel-mesh.md) · [`raycast`](./raycast.md) · [`notion-pre-ai`](./notion-pre-ai.md) |\n| **Motion / Experimental** | [`field-io`](./field-io.md) · [`active-theory`](./active-theory.md) · [`resn-storytelling`](./resn-storytelling.md) |\n| **Brutalist / Raw** | [`are-na`](./are-na.md) · [`bloomberg-businessweek-turley`](./bloomberg-businessweek-turley.md) · [`balenciaga-post-2017`](./balenciaga-post-2017.md) |\n| **Warm Humanist** | [`mailchimp-freddie`](./mailchimp-freddie.md) · [`stripe-press`](./stripe-press.md) · [`headspace-meditation`](./headspace-meditation.md) |\n| **Specialty / Genre** (not surfaced via Advisor) | [`y2k-retrofuturism`](./y2k-retrofuturism.md) · [`mid-century-modern`](./mid-century-modern.md) |\n\nThe first 6 schools mirror the Direction Advisor's 6 schools (in `../design-directions.md`) — so when the Advisor picks one of those, you know which recipe files to surface. The 7th school (Specialty / Genre) is **only reachable through direct anchor naming** — users wanting Y2K or Mid-Century always arrive with the anchor in hand, never through Advisor.\n\n## Index 2 — By Best-For\n\n| Scenario | First-choice recipes |\n|---|---|\n| B2B SaaS / developer tools | [`linear`](./linear.md) · [`vercel-mesh`](./vercel-mesh.md) · [`raycast`](./raycast.md) · [`pentagram`](./pentagram.md) |\n| Premium consumer / lifestyle | [`aesop`](./aesop.md) · [`muji-kenya-hara`](./muji-kenya-hara.md) · [`stripe-press`](./stripe-press.md) · [`monocle-magazine`](./monocle-magazine.md) |\n| Data product / dashboard / finance | [`bloomberg-terminal`](./bloomberg-terminal.md) · [`tufte-dataink`](./tufte-dataink.md) · [`vignelli-swiss-helvetica`](./vignelli-swiss-helvetica.md) |\n| Editorial / publishing / longform | [`nyt-the-daily`](./nyt-the-daily.md) · [`monocle-magazine`](./monocle-magazine.md) · [`stripe-press`](./stripe-press.md) · [`mailchimp-freddie`](./mailchimp-freddie.md) |\n| Launch moment / brand film / awwwards | [`field-io`](./field-io.md) · [`active-theory`](./active-theory.md) · [`resn-storytelling`](./resn-storytelling.md) · [`vercel-mesh`](./vercel-mesh.md) |\n| Differentiated / counter-culture / artist | [`are-na`](./are-na.md) · [`bloomberg-businessweek-turley`](./bloomberg-businessweek-turley.md) · [`balenciaga-post-2017`](./balenciaga-post-2017.md) |\n| Approachable B2C / community / health | [`mailchimp-freddie`](./mailchimp-freddie.md) · [`headspace-meditation`](./headspace-meditation.md) · [`notion-pre-ai`](./notion-pre-ai.md) |\n| Retro / theme / decade-coded | [`y2k-retrofuturism`](./y2k-retrofuturism.md) · [`mid-century-modern`](./mid-century-modern.md) |\n\n## Index 3 — By Mode (light / dark / either)\n\n| Mode | Recipes |\n|---|---|\n| Light-first | [`apple-hig`](./apple-hig.md) · [`muji-kenya-hara`](./muji-kenya-hara.md) · [`aesop`](./aesop.md) · [`dieter-rams-braun`](./dieter-rams-braun.md) · [`monocle-magazine`](./monocle-magazine.md) · [`pentagram`](./pentagram.md) · [`nyt-the-daily`](./nyt-the-daily.md) · [`stripe-press`](./stripe-press.md) · [`headspace-meditation`](./headspace-meditation.md) · [`mailchimp-freddie`](./mailchimp-freddie.md) · [`mid-century-modern`](./mid-century-modern.md) |\n| Dark-first | [`linear`](./linear.md) · [`vercel-mesh`](./vercel-mesh.md) · [`raycast`](./raycast.md) · [`bloomberg-terminal`](./bloomberg-terminal.md) · [`field-io`](./field-io.md) · [`active-theory`](./active-theory.md) · [`resn-storytelling`](./resn-storytelling.md) · [`y2k-retrofuturism`](./y2k-retrofuturism.md) |\n| Works either way | [`vignelli-swiss-helvetica`](./vignelli-swiss-helvetica.md) · [`tufte-dataink`](./tufte-dataink.md) · [`notion-pre-ai`](./notion-pre-ai.md) · [`are-na`](./are-na.md) · [`bloomberg-businessweek-turley`](./bloomberg-businessweek-turley.md) · [`balenciaga-post-2017`](./balenciaga-post-2017.md) |\n\n---\n\n## Cross-Cutting Anti-Patterns (apply to all 25 recipes)\n\nThese apply across every recipe in this catalog. Violating them collapses the recipe back into AI-default slop. **Read these even before reading an individual recipe file** — they're not duplicated in each file.\n\n### ❌ Don't combine two recipes mid-page\n\nPick **one** recipe and instantiate it fully. Adding \"Linear with Aesop accents\" or \"Pentagram with a Y2K hero\" usually reads as confused rather than original. Two-recipe remixes work only when the user explicitly asks and you can articulate *why* the marriage is coherent (e.g., \"Aesop palette on Pentagram grid for an apothecary catalog with editorial bones\").\n\n### ❌ Don't half-commit to brutalism / Y2K / mid-century\n\nThe Specialty / Genre and Brutalist / Raw recipes need full commitment. Half-Y2K reads as \"broken modern site\". Half-brutalism reads as \"unfinished design\". Either go all-in or pick a different recipe.\n\n### ❌ Don't default to Inter / Roboto / Arial / system-ui as display\n\nIf your chosen recipe specifies a font, use that font (or a real substitute *named in the recipe*). Defaulting to Inter erases the recipe's typographic identity, which is usually 30–40% of its signature.\n\n### ❌ Don't import every color in the palette\n\nEach recipe lists a restricted palette intentionally. If a recipe gives you 4 colors, don't add a 5th to \"balance things out\". The restriction *is* the recipe.\n\n### ❌ Don't add your own AI-default touches \"to make it pop\"\n\nIf the recipe says \"no shadow,\" don't add a subtle shadow. If it says \"no gradient,\" don't add a gradient mesh. If it says \"no emoji,\" don't add one. Every AI-default addition you make reduces the recipe's distinctiveness toward the mean.\n\n### ❌ Don't fake the photography style with CSS\n\nRecipes like Aesop, Stripe Press, MUJI, Apple HIG, Mailchimp, and Headspace rely on a specific photography or illustration style. If you can't source / generate that imagery, **say so to the user** — don't substitute CSS shapes. A recipe with a real hero photograph at 60% quality lands better than the same recipe with a CSS substitute at 0% recognition value.\n\n### ❌ Don't invent new recipes silently\n\nIf you find yourself drifting outside the listed recipes, **tell the user**: \"None of the 25 recipes fits — here's what I propose instead.\" A new recipe deserves its own anchor, its own concrete values, and its own signature moves articulated. Drifting silently produces the AI-default that this whole catalog is built to prevent.\n\n### ❌ Don't read the whole catalog when you need one recipe\n\nLoad only the recipe files you actually need. If the user said \"Linear-style\", read [`linear.md`](./linear.md) — not all 25. The 1-file-at-a-time pattern is what makes this catalog efficient; loading everything up front defeats progressive disclosure.\n\n---\n\n## When None of the 25 Fits\n\nOptions in this order:\n\n1. **Re-read the user's request** — sometimes the right recipe is obvious in hindsight (a \"data-led research tool\" is almost always [`tufte-dataink`](./tufte-dataink.md) or [`bloomberg-terminal`](./bloomberg-terminal.md), even when the user didn't name those)\n2. **Combine two recipes deliberately**, with explicit framing — see the warning above\n3. **Hand off to `../design-directions.md`** — propose 3 differentiated schools and let the user pick\n4. **Articulate a new recipe** — name an anchor, list concrete values, get user sign-off, then proceed\n\nAlways make the recipe choice explicit in your Step 3 design-system declaration so the user can confirm before code starts.\n\n"
|
|
10322
|
+
},
|
|
10323
|
+
{
|
|
10324
|
+
name: "references/style-recipes/linear.md",
|
|
10325
|
+
description: "Linear (Modern Builder Tool) · Quiet luxury for developer tools; warm dark, hairline detail, restraint as confidence",
|
|
10326
|
+
getContent: () => "\n# linear — Linear (Modern Builder Tool)\n\n- **School**: Modern Tool / Builder SaaS\n- **Vibe**: Quiet luxury for developer tools; warm dark, hairline detail, restraint as confidence\n- **Best for**: Developer tools, AI tools, B2B SaaS where \"serious + designed\" matters\n- **Touchstone**: linear.app, the Linear changelog page, Linear Method site\n\n**Palette**\n- Ground: `#08090A` (near-black with warm undertone, not pure black)\n- Surface 1: `#16171C`\n- Surface 2: `#1E1F25`\n- Surface 3 (raised): `#26272E`\n- Hairline border: `rgba(255,255,255,0.06)`\n- Primary text: `#F7F8F8`\n- Secondary text: `#9CA3AF`\n- Muted text: `#6B7280`\n- Accent: Linear purple `#5E6AD2` — used on < 5% of pixels\n- Gradient meshes (very controlled, < 8% opacity) in the hero only\n\n**Typography**\n- Display: Inter Tight at weight 600 (or Söhne, or Geist Sans) — never plain Inter at default weight, that's the AI-default\n- Body: Inter at 14–15px, weight 400–500, line-height 1.55\n- Mono: GeistMono, JetBrains Mono, or Berkeley Mono for inline code and shortcut chips\n- Letter-spacing on display headlines: -0.02em (tight)\n\n**Spacing**: 4 / 8 / 12 / 16 / 24 / 40 / 64 / 96\n\n**Radius**: 6 (small UI) / 12 (medium cards) / 16 (large panels). **Never above 16.** Linear's radius character is \"modest, precise, never gummy.\"\n\n**Shadow**: barely there — soft `0 1px 2px rgba(0,0,0,0.3)` on raised surfaces. **Never glow, never colored shadow.**\n\n**Motion**: ease-out around 150ms for hover, 350–450ms for layout moves with a quint curve (e.g., the famous cubic-bezier(0.22, 1, 0.36, 1)). State changes feel \"snappy but not bouncy.\"\n\n**Signature moves**\n- Hairline 1px borders in `rgba(255,255,255,0.06)` separating every panel, everywhere\n- Selective accent use — purple appears on focused / active states, on tiny pills, on key brand surfaces; never on body backgrounds\n- Inline code styled with monospaced font and dim background `#1E1F25`, color `#A78BFA`\n- Subtle gradient meshes in the hero (extremely controlled saturation, < 8% opacity) — *not* the bright purple-pink-blue AI cliché\n- Keyboard-shortcut chips throughout the UI (the brand celebrates keyboard-first)\n- A bottom-pinned screenshot of the actual product on landing pages\n\n**Avoid**\n- Emoji\n- Bouncy springs or elastic easings\n- More than one saturated color (Linear's purple is the only accent)\n- Border-radius above 16\n- Stock photography of people\n- \"Get Started Free\" hero CTAs styled like a 2018 SaaS landing page\n\n**AI prompt seed**\n> Abstract product UI screenshot, warm dark #08090A background, hairline borders, panels with #16171C and #1E1F25 surfaces, very subtle blue-purple ambient lighting at top edge, no people, no text on the image itself, 16:9.\n\n**Don't use when**\n- The brand is consumer-friendly / playful — Linear reads as professional and serious\n- The product needs warmth or hand-touched feel\n- The audience is non-technical (the keyboard-shortcut and monospace signals don't land)\n\n---\n\n> **Same school — Modern Tool / Builder SaaS**: [`vercel-mesh`](./vercel-mesh.md) · [`raycast`](./raycast.md) · [`notion-pre-ai`](./notion-pre-ai.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10327
|
+
},
|
|
10328
|
+
{
|
|
10329
|
+
name: "references/style-recipes/mailchimp-freddie.md",
|
|
10330
|
+
description: "Mailchimp (Freddie Era, c. 2018–2022) · Hand-drawn marks, warm yellow, personality in microcopy",
|
|
10331
|
+
getContent: () => "\n# mailchimp-freddie — Mailchimp (Freddie Era, c. 2018–2022)\n\n- **School**: Warm Humanist\n- **Vibe**: Hand-drawn marks, warm yellow, personality in microcopy\n- **Best for**: Small-business tools, creator products, community / education / approachable B2C\n- **Touchstone**: mailchimp.com pre-2023 redesigns (Wayback), Freddie illustration sets\n\n**Palette**\n- Ground: `#FFE01B` (Mailchimp yellow) for hero / brand moments, `#FFFFFF` for body\n- Ink: `#241C15` (warm near-black, never pure black)\n- Cream surface: `#FBEFE3`\n- Secondary: `#88837C`\n- Accent: a hot pop-pink `#FF4D74` or a warm coral, used very sparingly\n\n**Typography**\n- Display: a friendly grotesque — Helvetica Now Display, Söhne, Inter Tight at weight 700–800\n- Body: same family at 17–18px, weight 400, line-height 1.6\n- Decorative: occasional hand-drawn script or text-set-by-hand feel\n\n**Spacing**: 4 / 8 / 16 / 24 / 40 / 64 — generous but not airy\n\n**Radius**: 8 (small) / 16 (medium) / 24 (large pills). Buttons are pill-shaped.\n\n**Shadow**: soft and warm — `0 4px 12px rgba(36, 28, 21, 0.08)`\n\n**Motion**: bouncy welcomed. Spring physics on hover lifts, friendly easings.\n\n**Signature moves**\n- Freddie-style hand-drawn illustrations (winking mascot, line-drawn characters with personality) — never AI-generated, must be commissioned or use a single illustrator's hand\n- Yellow flood-fill section backgrounds breaking up the page\n- Pill-shaped buttons with a 2–3px black outline (Memphis-style)\n- Personality in microcopy (\"Send better email\" not \"Optimize delivery metrics\")\n- Asymmetric \"tilted\" illustration placement — never centered on a grid\n\n**Avoid**\n- Pure black anywhere (the recipe is warm-near-black)\n- Stock illustrations — the hand-drawn style is the differentiator\n- More than 3 colors visible at once (yellow + ink + one accent)\n- Cold corporate microcopy\n\n**AI prompt seed**\n> Hand-drawn illustration, single line-drawn character with warm color washes (yellow, coral), winking expression, on warm cream #FBEFE3 background, friendly approachable line style, deliberate imperfection, 4:5.\n\n**Don't use when**\n- Brand needs to feel premium / luxury — Mailchimp warmth reads as accessible / mass-market\n- Illustration can't be sourced from a real illustrator (recipe loses 50% impact)\n- Audience is enterprise-only and skeptical of \"friendly\"\n\n---\n\n> **Same school — Warm Humanist**: [`stripe-press`](./stripe-press.md) · [`headspace-meditation`](./headspace-meditation.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10332
|
+
},
|
|
10333
|
+
{
|
|
10334
|
+
name: "references/style-recipes/mid-century-modern.md",
|
|
10335
|
+
description: "Mid-Century Modern (Paul Rand / Saul Bass) · 1950s–60s American graphic modernism — geometric shapes, considered hand-feel, jaz…",
|
|
10336
|
+
getContent: () => "\n# mid-century-modern — Mid-Century Modern (Paul Rand / Saul Bass)\n\n- **School**: Specialty / Genre (also touches Information Architecture)\n- **Vibe**: 1950s–60s American graphic modernism — geometric shapes, considered hand-feel, jazz-album confidence\n- **Best for**: Cultural / film / publishing identity, premium consumer brands with heritage claim, \"tasteful sophistication\"\n- **Touchstone**: IBM logo (Paul Rand), Saul Bass Vertigo / Anatomy of a Murder posters, Alvin Lustig book covers, mid-century jazz album sleeves (Reid Miles / Blue Note)\n\n**Palette**\n- Ground: warm cream `#EBE3D2` or mustard `#D9A441` or muted teal `#3D6E70`\n- Ink: `#1A1A18`\n- Limited palette per piece — 3 colors max, one bold, two muted\n- Common mid-century pairings: mustard + brick + cream; teal + cream + black; sienna + ink + bone\n\n**Typography**\n- Display: a geometric grotesque (Futura, Avenir, ITC Avant Garde) at large scale, often condensed weight\n- Or a slab serif (Egyptian / Memphis) for editorial moments\n- Body: humanist serif (Sabon, Garamond) at 16–17px\n- Numerals often set in figures-as-image (a single big numeral as a graphic element)\n\n**Spacing**: structured grid; 4 / 8 / 16 / 24 / 32 / 48\n\n**Radius**: 0 throughout. Mid-century is geometric, not soft.\n\n**Shadow**: none — flat color shapes only\n\n**Motion**: minimal. Shapes slide into place on a single beat, like a Saul Bass title sequence.\n\n**Signature moves**\n- Geometric shape compositions — a single big circle, a triangle, a half-moon — as the primary visual\n- Hand-cut paper feel (shapes have slightly imperfect edges, suggesting they were cut and pasted)\n- Type integrated with the shapes (a headline curving around a circle, or sitting *inside* a colored block)\n- A single illustrative motif repeated (a stylized bird, an abstract figure) tying the piece together\n- Print-feel grain or paper texture at low opacity\n\n**Avoid**\n- Gradients — mid-century is flat color\n- Photography as the lead visual (illustration leads; photography supports)\n- More than 3 colors per piece\n- Rounded / soft-corner UI\n\n**AI prompt seed**\n> Mid-century modern poster composition in the style of Saul Bass, single bold geometric shape (large circle in mustard #D9A441) on warm cream #EBE3D2 paper background, flat color shapes, slight paper texture, hand-cut edges, no gradient, no shadow, 3:4.\n\n**Don't use when**\n- The product is feature-dense / utility-focused (mid-century is editorial / poster thinking)\n- The brand has no heritage / cultural / craft claim\n- Realistic photography is essential to the message\n\n---\n\n> **Same school — Specialty / Genre**: [`y2k-retrofuturism`](./y2k-retrofuturism.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10337
|
+
},
|
|
10338
|
+
{
|
|
10339
|
+
name: "references/style-recipes/monocle-magazine.md",
|
|
10340
|
+
description: "Monocle Magazine · International, considered, magazine-grade kicker / headline / dek hierarchy",
|
|
10341
|
+
getContent: () => "\n# monocle-magazine — Monocle Magazine\n\n- **School**: Editorial / Minimalist\n- **Vibe**: International, considered, magazine-grade kicker / headline / dek hierarchy\n- **Best for**: Editorial, travel / hospitality, journals, considered lifestyle publications\n- **Touchstone**: monocle.com, the Monocle quarterly, Cereal magazine\n\n**Palette**\n- Ground: `#F2EFE7` (cream paper)\n- Ink: `#1A1A1A`\n- Editorial accent red: `#C7322E` (Monocle's signature)\n- Olive: `#5E6347`\n- Hairline rules: `#C8C2B0`\n\n**Typography**\n- Display: a refined slab or transitional serif — Plantin, Mercury, or Tiempos Headline. Italic widely used.\n- Body: Plantin or Tiempos Text at 16–17px, line-height ~1.6\n- Sans for kickers / labels: a precision grotesk like Söhne at 10–11px, letter-spaced 0.08em, often in editorial red\n- Drop caps welcome on long-form features\n\n**Spacing**: 4 / 8 / 16 / 24 / 32 / 48 / 96. Multi-column work uses tight gutters (16–24px between columns).\n\n**Radius**: 0. Magazine pages don't round corners.\n\n**Shadow**: none.\n\n**Motion**: minimal. Page-turn fades. Slow Ken Burns on lead photographs.\n\n**Signature moves**\n- Three-deck hierarchy: tiny red kicker → bold serif headline → italic serif dek\n- Multi-column body copy (2–3 columns) with hairline column rules\n- Numbered features (`Feature 04 — Lisbon`) and small numerals in the margin\n- Lead photograph cropped tight at one edge, headline tucked into the empty negative space\n- Pull-quotes in italic, much larger than body, set in a single column\n\n**Avoid**\n- Sans-serif headlines — Monocle's signature is the serif voice\n- Stock photography — the recipe requires commissioned-feeling photography (warm, peopled, place-rich)\n- Card grids — this is magazine spread thinking, not SaaS thinking\n- More than one accent color (the editorial red is enough)\n\n**AI prompt seed**\n> Editorial magazine photograph, place-specific scene (e.g., Tokyo back street at dusk), warm color grading, slight film grain, one human subject in middle-ground, 3:2, color palette restricted to warm earth tones with one red object.\n\n**Don't use when**\n- The task is a SaaS product page — magazine craft will feel mismatched\n- There's no editorial / human / place content to lead with — without photography this recipe is hollow\n- The audience expects fast scanning — Monocle rewards reading, not skimming\n\n---\n\n> **Same school — Editorial / Minimalist**: [`apple-hig`](./apple-hig.md) · [`muji-kenya-hara`](./muji-kenya-hara.md) · [`aesop`](./aesop.md) · [`dieter-rams-braun`](./dieter-rams-braun.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10342
|
+
},
|
|
10343
|
+
{
|
|
10344
|
+
name: "references/style-recipes/muji-kenya-hara.md",
|
|
10345
|
+
description: "MUJI / Kenya Hara · Emptiness as fullness, off-white as a value, near-silence",
|
|
10346
|
+
getContent: () => "\n# muji-kenya-hara — MUJI / Kenya Hara\n\n- **School**: Editorial / Minimalist\n- **Vibe**: Emptiness as fullness, off-white as a value, near-silence\n- **Best for**: Premium quiet positioning, lifestyle goods, \"everyday craft\" identity\n- **Touchstone**: muji.com (any region), Kenya Hara's book *White*, MUJI ryokan signage\n\n**Palette**\n- Ground: `#F4F2EC` (warm paper off-white — never `#FFFFFF`)\n- Ink: `#2A2A28` (warm ink, never `#000000`)\n- Secondary text: `#7C7B76`\n- Hairline: `#D9D6CD`\n- Accent: there is none. If you must add one, a single brand red `#C8161D` used only as a tiny corner mark or rule.\n\n**Typography**\n- Display: a humanist sans like Söhne, Inter Tight, or Klim's Calibre — weight 400, never bolder than 500. Letterspacing slightly open (0.01em–0.02em).\n- Body: same family at 15–16px, line-height ~1.8\n- Japanese-bilingual deployments: Noto Sans CJK JP paired weight-for-weight\n\n**Spacing**: 8 / 16 / 32 / 48 / 96 / 160 / 240. The big numbers matter — 240px between sections is normal.\n\n**Radius**: 0. Hard, honest edges. Maybe 2px on form fields.\n\n**Shadow**: none.\n\n**Motion**: imperceptible. Fades over 600–900ms. Nothing translates more than a few px. Never bouncy.\n\n**Signature moves**\n- 60–80% of the page is empty space — content lives in a narrow column, far from edges\n- One product image per \"screen\", small relative to the page, captioned plainly\n- Tiny labels (10–11px, letter-spaced) describing what each section is — \"01 — Cotton\" — never headlines that shout\n- Square-bracket-style asides, kept neutral\n- Rules (1px hairlines) are the only ornament\n\n**Avoid**\n- Any saturated color other than the MUJI red as a corner mark\n- Drop shadows, glows, gradients of any kind\n- Tabs, accordions, mega-menus, sticky banners\n- \"Above the fold\" thinking — MUJI lets the eye start in the middle of the page\n\n**AI prompt seed**\n> Single household object on warm off-white #F4F2EC paper ground, raked daylight from upper left, very long quiet space around the object, 3:2, photographed on film, no shadow drama, neutral tone.\n\n**Don't use when**\n- The product needs to communicate feature density (specs, pricing tiers, comparison tables) — MUJI's quietness will hide the message\n- The audience scrolls fast and demands signal density (news / SaaS / dashboard) — they'll bounce\n- The brand is in a noisy competitive set and needs to shout — quietness is luxury, but luxury isn't always what's needed\n\n---\n\n> **Same school — Editorial / Minimalist**: [`apple-hig`](./apple-hig.md) · [`aesop`](./aesop.md) · [`dieter-rams-braun`](./dieter-rams-braun.md) · [`monocle-magazine`](./monocle-magazine.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10347
|
+
},
|
|
10348
|
+
{
|
|
10349
|
+
name: "references/style-recipes/notion-pre-ai.md",
|
|
10350
|
+
description: "Notion (pre-AI era, c. 2017–2022) · Friendly serif headline + emoji as first-class design element + soft cream + black ink",
|
|
10351
|
+
getContent: () => "\n# notion-pre-ai — Notion (pre-AI era, c. 2017–2022)\n\n- **School**: Modern Tool / Builder SaaS (warm-leaning, near Humanist border)\n- **Vibe**: Friendly serif headline + emoji as first-class design element + soft cream + black ink\n- **Best for**: Productivity tools for non-engineers, collaborative software, \"your second brain\" products\n- **Touchstone**: notion.so pre-2023 redesigns (Wayback), early Notion marketing decks\n\n**Palette**\n- Ground: `#FFFFFF`\n- Soft cream surface: `#F7F6F3`\n- Ink: `#37352F` (Notion's specific not-quite-black, warm cast)\n- Secondary text: `#787774`\n- Hairline: `#E9E9E7`\n- Accent palette: muted Notion colors — `#E4F4F1` (mint), `#FFEDD5` (peach), `#FCE7F3` (rose), `#FEF3C7` (sand) — used as soft block backgrounds, never as bright accents\n\n**Typography**\n- Display: a friendly modern serif — GT Sectra, Sentinel, or Source Serif Pro. Weight 500 for headlines.\n- Body: Inter or Söhne at 16px, line-height 1.6, weight 400\n- Emoji set at 1.25× body size, used as section markers and \"page icons\"\n\n**Spacing**: 4 / 8 / 16 / 24 / 40 / 64\n\n**Radius**: 4 (small UI) / 8 (cards) / 16 (large panels)\n\n**Shadow**: very soft — `0 2px 8px rgba(15, 15, 15, 0.04)`. Notion's shadows are felt, not seen.\n\n**Motion**: gentle ease-out around 200–300ms. Hover lifts cards by 2–4px with a soft shadow expansion.\n\n**Signature moves**\n- Section headers prefixed with a single emoji icon (`📖 Notes`, `🌱 Ideas`) — the emoji *is* the visual hierarchy\n- Hand-drawn doodles or simple line illustrations integrated into the hero (Notion's signature illustration style)\n- Block-based content metaphor visible in the layout — content reads like draggable cards rather than columns\n- Color-tagged soft pills for category labels (mint = active, peach = idea, etc.)\n- Friendly conversational microcopy\n\n**Avoid**\n- Too many emoji in close proximity (one per section header is the recipe, not five)\n- Dark mode unless explicitly required (Notion's identity is light)\n- Sans-serif headlines — the friendly serif is the differentiator\n- Aggressive CTAs — Notion's hero CTA reads as an invitation, not a demand\n\n**AI prompt seed**\n> Hand-drawn doodle illustration, simple black-line drawing of an organized desk scene with notebooks and houseplants, on cream #F7F6F3 background, friendly approachable line work, small color washes in soft mint and peach, 3:2.\n\n**Don't use when**\n- The audience is enterprise-serious — emoji as design will read as unprofessional\n- The product is graphics-heavy or needs visual density — Notion's recipe is text-led\n- The voice is meant to be authoritative — Notion is approachable, not commanding\n\n---\n\n> **Same school — Modern Tool / Builder SaaS**: [`linear`](./linear.md) · [`vercel-mesh`](./vercel-mesh.md) · [`raycast`](./raycast.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10352
|
+
},
|
|
10353
|
+
{
|
|
10354
|
+
name: "references/style-recipes/nyt-the-daily.md",
|
|
10355
|
+
description: "New York Times Editorial · Authoritative broadsheet, generations of typographic craft",
|
|
10356
|
+
getContent: () => "\n# nyt-the-daily — New York Times Editorial\n\n- **School**: Information Architecture\n- **Vibe**: Authoritative broadsheet, generations of typographic craft\n- **Best for**: Longform editorial, news / journalism products, narrative explainers\n- **Touchstone**: nytimes.com homepage, NYT Cooking, NYT investigative features\n\n**Palette**\n- Ground: `#FFFFFF`\n- Ink: `#121212`\n- Secondary text: `#666666`\n- NYT red `#D0021B` — used only for breaking-news indicators or sparingly\n- Soft section background: `#F7F7F7`\n- Hairline: `#E2E2E2`\n\n**Typography**\n- Display: Cheltenham (NYT's proprietary) — substitute Sentinel or Tiempos Headline. Weight 700 for hero, italic widely used.\n- Subhead: Imperial or Lyon Display at weight 400\n- Body: Imperial / Georgia / source serif at 18–20px, line-height ~1.55\n- Sans for UI / kickers: Franklin (NYT) — substitute Söhne Mono or Söhne, weight 500, often in small caps\n\n**Spacing**: 4 / 8 / 16 / 24 / 32 / 48 / 96. Multi-column grids for stories.\n\n**Radius**: 0 throughout.\n\n**Shadow**: none.\n\n**Motion**: minimal — sticky bylines, lazy-loaded images fading in, slow zoom on hero photo over 8–15s.\n\n**Signature moves**\n- Three-deck hierarchy: tiny eyebrow (\"OPINION\" / \"ANALYSIS\" / \"INVESTIGATION\" in small-caps sans) → bold serif headline → italic serif standfirst\n- Byline + timestamp block in sans below the headline, always\n- Pull-quotes set in italic serif at 1.5–2× body size, with hairline rules above and below\n- Multi-column body copy on wide viewports; single column on mobile (always readable)\n- Inline images with serif captions; captions matter and are styled distinctively\n\n**Avoid**\n- Sans-serif headlines (the recipe's signature is the serif voice)\n- Card grids of articles — NYT lays articles out as a front page, with hierarchy by size\n- Pretty UI buttons in the body of articles\n- Centered alignment for body (left-aligned justified is the recipe)\n\n**AI prompt seed**\n> Editorial photojournalism, single decisive moment image, natural lighting, 3:2 aspect, color grading neutral with slight desaturation, real-feeling not staged, place-specific.\n\n**Don't use when**\n- The product is a SaaS dashboard — NYT craft reads as overformal there\n- Content is short and punchy (one-line CTAs) — NYT needs longform to land\n- The brand has no editorial / authority claim — using NYT voice without the substance reads as cosplay\n\n---\n\n> **Same school — Information Architecture**: [`pentagram`](./pentagram.md) · [`vignelli-swiss-helvetica`](./vignelli-swiss-helvetica.md) · [`bloomberg-terminal`](./bloomberg-terminal.md) · [`tufte-dataink`](./tufte-dataink.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10357
|
+
},
|
|
10358
|
+
{
|
|
10359
|
+
name: "references/style-recipes/pentagram.md",
|
|
10360
|
+
description: "Pentagram / Paula Scher · Bold typography as image; type does the visual work; everything else recedes",
|
|
10361
|
+
getContent: () => "\n# pentagram — Pentagram / Paula Scher\n\n- **School**: Information Architecture\n- **Vibe**: Bold typography as image; type does the visual work; everything else recedes\n- **Best for**: B2B identity, cultural institutions, \"we mean business\" moments\n- **Touchstone**: pentagram.com, Paula Scher's Public Theater posters, MIT Media Lab identity\n\n**Palette**\n- High-contrast two-color: ink on ground. Common pairings:\n - Black `#000000` on white `#FFFFFF`\n - Cobalt `#1E3FFF` on cream `#F4F1E7`\n - Brick `#B83A1F` on bone `#EFE9DC`\n- A single secondary accent only if the project demands a third color (used very sparingly)\n\n**Typography**\n- Display: a single grotesque used at massive scale — Helvetica Now (weight 700–900), Söhne Breit, or Druk. Headlines are often **set bigger than the column they live in**, breaking the margin on purpose.\n- Body: the same family at body weight, 16–17px, line-height 1.45–1.55\n- Numerals: tabular figures from the same family\n\n**Spacing**: strict 12-column grid, generous gutters (24–32px). Baseline-grid alignment of every text element.\n\n**Radius**: 0.\n\n**Shadow**: none.\n\n**Motion**: typographic moves only — letters shift in, headlines slide on a baseline. No 3D, no bouncy, no parallax.\n\n**Signature moves**\n- Headlines so large they touch (or visibly break past) the column edges\n- Type as image — a single word or phrase at hero scale becomes the only graphic\n- Color blocks (a flat rectangle of accent color) used as section dividers, often with a single huge number on them (`02`)\n- Captions and metadata pinned to the grid in tiny precise type, often in the column gutter\n- A single rule line (1px) sometimes drawn across the entire page width, anchoring everything\n\n**Avoid**\n- Photography as the visual lead (type leads, not photo) — exception: if there is a hero photo, it lives behind / under enormous type\n- Multiple type families\n- Gradients, shadows, decorative anything\n- Card grids of content (Pentagram thinks in spreads, not cards)\n\n**AI prompt seed**\n> Editorial poster composition, single phrase set in massive black Helvetica weight 900, characters bleeding past the layout margins, behind it a flat field of cobalt #1E3FFF, no photography, no illustration, 3:4.\n\n**Don't use when**\n- Headlines can't be made bold and short (3–6 word phrases) — Pentagram needs strong copy\n- Many tiers of feature density must be communicated (this is institutional voice, not feature catalog)\n- The brand is meant to feel quiet — Pentagram is the opposite of quiet\n\n---\n\n> **Same school — Information Architecture**: [`vignelli-swiss-helvetica`](./vignelli-swiss-helvetica.md) · [`bloomberg-terminal`](./bloomberg-terminal.md) · [`tufte-dataink`](./tufte-dataink.md) · [`nyt-the-daily`](./nyt-the-daily.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10362
|
+
},
|
|
10363
|
+
{
|
|
10364
|
+
name: "references/style-recipes/raycast.md",
|
|
10365
|
+
description: "Raycast (Productivity Tool) · Glassy command-palette aesthetic, color extensions for personality, keyboard-first culture",
|
|
10366
|
+
getContent: () => "\n# raycast — Raycast (Productivity Tool)\n\n- **School**: Modern Tool / Builder SaaS\n- **Vibe**: Glassy command-palette aesthetic, color extensions for personality, keyboard-first culture\n- **Best for**: Productivity tools, command-palette / launcher products, dev utilities\n- **Touchstone**: raycast.com, Raycast app screenshots, raycast.com/store extension cards\n\n**Palette**\n- Ground: `#0F0F11` (charcoal with hint of blue)\n- Surface translucent: `rgba(255,255,255,0.06)` over a colored gradient backdrop\n- Hairline: `rgba(255,255,255,0.10)`\n- Primary text: `#FFFFFF`\n- Secondary text: `#B0B3B8`\n- Brand red accent: `#FF6363` — Raycast's signature, used on icons and key CTAs\n- Extension chips: bright per-tile colors (lime, coral, lavender, cyan) used as accent dots on small surfaces only\n\n**Typography**\n- Display: Söhne weight 600, or Inter Tight weight 600\n- Body: Inter at 14–15px, weight 500 (slightly heavier than Linear's 400 — Raycast feels punchier)\n- Mono: SF Mono or JetBrains Mono for shortcut chips\n- Letter-spacing on display: -0.02em\n\n**Spacing**: 4 / 8 / 12 / 16 / 24 / 40 / 64\n\n**Radius**: 8 (small) / 12 (medium) / 16 (cards) / 24 (large modal). Raycast's signature radius is the soft-corner command palette.\n\n**Shadow**: cushiony — `0 16px 40px rgba(0,0,0,0.4)` on the command palette card; barely any shadow elsewhere\n\n**Motion**: bouncy welcomed for key moments (the palette appearing, hover lift) — spring physics with mild overshoot. Otherwise quick 150ms ease-out.\n\n**Signature moves**\n- A floating command-palette screenshot in the hero, slightly tilted, casting a generous cushioned shadow, with a blurred colorful gradient backdrop visible through it (glass)\n- Every keyboard shortcut shown as a styled chip — `⌘` `↵` `⌥` set in a precision mono with a hairline border and dim background\n- Bright per-extension colors used as small accent dots on tile cards (a Notion tile has a tiny lavender dot, a GitHub tile has a charcoal one)\n- Sticky color-banded gradient backdrop behind the hero (oranges → magentas, but soft-focused)\n- Generously spaced section anatomy with clear hover lift on tiles\n\n**Avoid**\n- Too many bright extension colors on the same screen — they only work as small accents on a dark ground\n- Solid bright-color buttons (Raycast buttons are usually translucent white pills with shortcut chips)\n- Pure monochrome scenes — Raycast leans on color *just enough* to feel playful\n\n**AI prompt seed**\n> Productivity application interface, floating command palette card centered on the page, soft-focused colorful gradient backdrop in warm oranges and magentas behind it, dark charcoal foreground UI, cushioned drop shadow under the palette, no people, 16:9.\n\n**Don't use when**\n- The product isn't a launcher / command-palette / keyboard-first tool — the central glass palette image won't land otherwise\n- The audience doesn't know keyboard shortcuts (they'll miss the recipe's main signal)\n- A strictly enterprise tone is needed — Raycast skews playful\n\n---\n\n> **Same school — Modern Tool / Builder SaaS**: [`linear`](./linear.md) · [`vercel-mesh`](./vercel-mesh.md) · [`notion-pre-ai`](./notion-pre-ai.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10367
|
+
},
|
|
10368
|
+
{
|
|
10369
|
+
name: "references/style-recipes/resn-storytelling.md",
|
|
10370
|
+
description: "Resn (Story Through Scroll) · Surprise as the reward; the page tells a story to those who scroll",
|
|
10371
|
+
getContent: () => "\n# resn-storytelling — Resn (Story Through Scroll)\n\n- **School**: Motion / Experimental\n- **Vibe**: Surprise as the reward; the page tells a story to those who scroll\n- **Best for**: Brand storytelling, agency portfolios, campaign microsites\n- **Touchstone**: resn.co, Resn's case-study pages, awwwards site-of-the-day archives\n\n**Palette**\n- Project-driven — Resn's recipe is more about composition than a fixed palette\n- Often warm-cool contrasted within one piece (warm character against cool environment, or vice versa)\n- Saturated where the story demands; muted where it breathes\n\n**Typography**\n- A campaign-specific display face is almost always present — custom or unusual\n- Body type quiet, often a humanist sans at small sizes\n- Text often integrated *into* the scene (titles set in WebGL space, not on a fixed UI layer)\n\n**Spacing**: irregular and composition-driven; not grid-strict\n\n**Radius**: project-dependent\n\n**Shadow**: scene-baked, not component\n\n**Motion**: long-form choreography — a single page might tell a 90-second story across 8–12 scroll triggers, with payoffs at specific moments\n\n**Signature moves**\n- Scroll triggers reveal narrative beats — a character moves across the screen, a product is unveiled, a punchline lands\n- Easter eggs hidden for repeat visitors\n- Cursor-reactive surface materials (a hover changes a texture, an audio cue plays)\n- Multi-scene composition — each section is treated as a film scene with its own art direction\n- A reward at the end — a special interaction or scene only revealed on full scroll-through\n\n**Avoid**\n- Bloating with too many beats — Resn's recipe is \"fewer, better\" beats\n- Pure decoration without narrative — every motion moment should advance the story\n- Forgetting the punchline — the recipe needs a payoff scene\n\n**AI prompt seed**\n> Narrative scene from a brand microsite, mid-action moment, character or object in motion with intentional motion blur, warm-cool palette contrast, theatrical lighting, 16:9.\n\n**Don't use when**\n- There's no story to tell (the recipe needs narrative content)\n- Audience won't scroll patiently (Resn rewards patience — for impatient audiences, choose a static recipe)\n- The build is rushed — Resn's recipe demands choreography time\n\n---\n\n> **Same school — Motion / Experimental**: [`field-io`](./field-io.md) · [`active-theory`](./active-theory.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10372
|
+
},
|
|
10373
|
+
{
|
|
10374
|
+
name: "references/style-recipes/stripe-press.md",
|
|
10375
|
+
description: "Stripe Press · Books as objects, cream + ink, warm photography, the kind of design only a successful infrastructure company funds",
|
|
10376
|
+
getContent: () => "\n# stripe-press — Stripe Press\n\n- **School**: Warm Humanist (with editorial bones)\n- **Vibe**: Books as objects, cream + ink, warm photography, the kind of design only a successful infrastructure company funds\n- **Best for**: Editorial / publishing, longform features, \"books and ideas\" products\n- **Touchstone**: press.stripe.com, the physical Stripe Press books (cloth-bound, foil-stamped)\n\n**Palette**\n- Ground: `#F1ECDE` (warm bone)\n- Surface darker: `#E6DCC4`\n- Ink: `#1A1A18` (very warm near-black)\n- Secondary text: `#736D5A`\n- Foil-stamp accent: deep teal `#1B4B5A` or burnt sienna `#A04A2A` (book-cover-specific colors)\n- Hairline: `#C8BEA4`\n\n**Typography**\n- Display: a refined transitional or modern serif — GT Sectra, Domaine Display, Italian Old Style. Italic everywhere.\n- Body: an old-style serif at 18–19px, line-height 1.65 — Source Serif, Lyon Text, ITC Galliard\n- Sans for UI only: a humanist sans at small sizes for nav and captions — Söhne or GT America\n- Often pairs a wide display serif with a slim body serif\n\n**Spacing**: 4 / 8 / 16 / 24 / 32 / 48 / 96. Editorial breathing room.\n\n**Radius**: 0 throughout (books don't have rounded corners)\n\n**Shadow**: present on book objects — `0 24px 48px rgba(0, 0, 0, 0.18)` to make books feel three-dimensional on the page\n\n**Motion**: slow, slow, slow. Crossfades over 1–2 seconds. Books rotate gently in 3D on hover (~15° max). Ken Burns on hero photography (~20s).\n\n**Signature moves**\n- Book-cover photography as hero — the actual physical object photographed on cream paper with a warm raking light and an honest shadow\n- Wide-set serif italic for emphasis, set 1.5–2× body size\n- Author / chapter quotes in oversized italic serif, hung against a wide margin\n- Cloth-textured backgrounds for section breaks (subtle textile pattern at low opacity)\n- A single foil-stamp color per book / chapter (gold, deep teal, burnt sienna) — never combined\n\n**Avoid**\n- Pure white grounds (the warm cream is part of the recipe)\n- Sans-serif body type (the editorial serif is the signature)\n- Card grids of books — Stripe Press lays each book out as a feature, not a tile\n- Cold neutral gray — every gray here has a warm cast\n\n**AI prompt seed**\n> Hardcover book photographed on warm cream paper #F1ECDE, raking afternoon window light, deep soft shadow, single book object, cloth-bound spine visible, foil-stamped title in burnt sienna, 4:5, editorial product photography.\n\n**Don't use when**\n- The deliverable has no editorial / book / publication content\n- The brand voice is light or playful (Stripe Press is sincere and considered)\n- The audience won't sit for longform — recipe rewards reading\n\n---\n\n> **Same school — Warm Humanist**: [`mailchimp-freddie`](./mailchimp-freddie.md) · [`headspace-meditation`](./headspace-meditation.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10377
|
+
},
|
|
10378
|
+
{
|
|
10379
|
+
name: "references/style-recipes/tufte-dataink.md",
|
|
10380
|
+
description: "Edward Tufte / Maximum Data-Ink · Every pixel earns its place; the chart is the page",
|
|
10381
|
+
getContent: () => "\n# tufte-dataink — Edward Tufte / Maximum Data-Ink\n\n- **School**: Information Architecture\n- **Vibe**: Every pixel earns its place; the chart is the page\n- **Best for**: Analytical reports, scientific publications, data-led storytelling\n- **Touchstone**: *The Visual Display of Quantitative Information*, *Beautiful Evidence*, edwardtufte.com\n\n**Palette**\n- Ground: `#FBFAF6` (warm paper)\n- Ink: `#1B1B1A`\n- Two data colors only — typically a warm red `#A6300E` and a cool slate `#3E4A5C`. Add a third only if a third dimension genuinely needs it.\n- Faint reference rules: `#D8D2C2`\n\n**Typography**\n- Display & body: an old-style or transitional serif — ET Book (Tufte's own family), Equity, or Lyon Text. Weight 400 for body, italic for emphasis.\n- Body at 12–14px (Tufte deliberately uses small body text — the reader leans in)\n- Sans for axis labels only — a humanist sans at 10–11px, color `#5C5550`\n\n**Spacing**: tight. 4 / 8 / 12 / 16 / 24 / 48. Margins are filled with side-notes, not whitespace.\n\n**Radius**: 0.\n\n**Shadow**: none.\n\n**Motion**: none. The page is meant to be read still.\n\n**Signature moves**\n- Sparklines inline with body copy (a tiny chart embedded in a sentence)\n- Side-notes (marginalia) used heavily — annotations live in the right margin, not in tooltips\n- Charts have no chart-junk: no gridlines except faint ones, no border boxes, no 3D, no legends if direct labels work\n- Small multiples — a 3×3 grid of the same chart with different data\n- Direct labels on data series (the line is labeled at its endpoint, not in a legend box)\n\n**Avoid**\n- Pie charts (Tufte would not approve)\n- 3D anything\n- Gridlines darker than `#D8D2C2`\n- Multiple chart types on the same page when one would do\n- Color for decoration (every color must encode meaning)\n\n**AI prompt seed**\n> Editorial scientific figure, warm paper #FBFAF6 background, multi-line time-series chart with two data series in muted red and slate, direct labels at line endpoints, side-note callouts in italic serif, no chart frame, no legend, 4:3.\n\n**Don't use when**\n- The chart is decorative (Tufte demands the chart be the *point*)\n- The reader expects interactive tooltips / filters (Tufte is for static editorial figures)\n- The medium is mobile (small body type plus margin notes doesn't fit)\n\n---\n\n> **Same school — Information Architecture**: [`pentagram`](./pentagram.md) · [`vignelli-swiss-helvetica`](./vignelli-swiss-helvetica.md) · [`bloomberg-terminal`](./bloomberg-terminal.md) · [`nyt-the-daily`](./nyt-the-daily.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10382
|
+
},
|
|
10383
|
+
{
|
|
10384
|
+
name: "references/style-recipes/vercel-mesh.md",
|
|
10385
|
+
description: "Vercel (Gradient Mesh Era) · Black-and-white precision broken by a single shimmering gradient mesh",
|
|
10386
|
+
getContent: () => "\n# vercel-mesh — Vercel (Gradient Mesh Era)\n\n- **School**: Modern Tool / Builder SaaS (also touches Motion / Experimental)\n- **Vibe**: Black-and-white precision broken by a single shimmering gradient mesh\n- **Best for**: Platform / infrastructure tools, AI tools, deeply technical products that want to feel modern but not AI-cliché\n- **Touchstone**: vercel.com, the v0.dev landing, nextjs.org\n\n**Palette**\n- Ground: `#000000` (true black)\n- Surface 1: `#0A0A0A`\n- Surface 2: `#111111`\n- Hairline: `#1F1F1F` or `rgba(255,255,255,0.08)`\n- Primary text: `#EDEDED`\n- Secondary text: `#888888`\n- Gradient mesh accents (used only in hero / section breaks):\n - Cyan `#0070F3` → magenta `#FF0080` → orange `#F5A623` — but mixed at low saturation and high feathering, never a hard purple-pink AI gradient\n- One sharp accent for CTAs: white `#FFFFFF` button on black ground (high contrast)\n\n**Typography**\n- Display: Geist Sans (weight 500–600) — Vercel's own family. Substitute Inter Tight weight 600.\n- Body: Geist Sans at 15–16px, weight 400, line-height 1.6\n- Mono: Geist Mono — for code, terminal-style commands, deploy logs\n\n**Spacing**: 4 / 8 / 16 / 24 / 40 / 64 / 96 / 128\n\n**Radius**: 8 (small) / 12 (medium) / 16 (large). Slightly more generous than Linear, but still disciplined.\n\n**Shadow**: minimal at component level; the *page* feels lit by the gradient mesh, not by shadows.\n\n**Motion**: snappy ease-out (cubic-bezier(0.16, 1, 0.3, 1)), 200ms hovers, 500–700ms for layout. The mesh itself slowly drifts (10–20s loop).\n\n**Signature moves**\n- One full-bleed gradient mesh in the hero — diffuse, dreamy, feathered to black edges\n- Otherwise the page is black-and-white with hairline detail and monospace accents\n- Deploy / terminal log readouts as a hero element (real-feeling, not lorem)\n- Animated mesh on hover for cards (the card \"lights up\" with a soft glow from below)\n- White solid buttons (`#FFFFFF` on `#000000`) — high contrast, no gradient, no shadow\n\n**Avoid**\n- Bright saturated purple → pink gradients (that's the AI cliché the recipe is *replacing*)\n- More than one mesh per page\n- Glow on every component (selective use — one hero, maybe one section break, that's it)\n- Colored buttons (the brand button is white)\n\n**AI prompt seed**\n> Abstract atmospheric gradient on pure black #000000 background, deep blue #0070F3 fading into magenta #FF0080 and orange #F5A623, feathered edges blending into black, dreamy soft focus, no objects, no text, 16:9, very low saturation overall.\n\n**Don't use when**\n- The brand isn't a platform / infra / tooling product — meshes on a consumer site read as \"trying too hard\"\n- Multiple gradient meshes are needed (use one or none, never several)\n- The audience expects warm / human feel\n\n---\n\n> **Same school — Modern Tool / Builder SaaS**: [`linear`](./linear.md) · [`raycast`](./raycast.md) · [`notion-pre-ai`](./notion-pre-ai.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10387
|
+
},
|
|
10388
|
+
{
|
|
10389
|
+
name: "references/style-recipes/vignelli-swiss-helvetica.md",
|
|
10390
|
+
description: "Vignelli / Swiss International · Strict grid, Helvetica throughout, \"six typefaces are enough\"",
|
|
10391
|
+
getContent: () => "\n# vignelli-swiss-helvetica — Vignelli / Swiss International\n\n- **School**: Information Architecture\n- **Vibe**: Strict grid, Helvetica throughout, \"six typefaces are enough\"\n- **Best for**: Wayfinding / transit systems, institutional identity, deeply ordered information\n- **Touchstone**: 1972 NYC Subway map (Vignelli), Knoll catalogues, *The Vignelli Canon*\n\n**Palette**\n- Black `#000000` and white `#FFFFFF`\n- Plus one of: red `#E2231A` / yellow `#F5C518` / blue `#0033A0` / orange `#FF6F00` (only one — never two accents)\n- Cool gray `#8A8A8A` for secondary content\n\n**Typography**\n- Display & body: Helvetica or Helvetica Now exclusively. Weights: 400 / 500 / 700, no italic.\n- Hierarchy by size, not weight or color\n- Common scale: 72 / 48 / 32 / 24 / 16 / 12, all on the same baseline grid\n\n**Spacing**: 8px baseline grid, 12-column layout, generous baseline-to-baseline spacing equal to or greater than the type size.\n\n**Radius**: 0, always.\n\n**Shadow**: none.\n\n**Motion**: none, or instant. Swiss design is static by nature; if anything moves, it moves like signage being rotated into place.\n\n**Signature moves**\n- A bold horizontal or vertical color bar carries the section header in white Helvetica\n- The grid is **visible** when it helps — column gutters sometimes drawn as faint lines\n- Information sets (data, schedules, lists) get tabular layouts with strict baseline alignment\n- Body copy is left-aligned in a single readable measure (~60–70 characters); never centered\n- A single oversized number (chapter / section) sits at the top-left corner of each block\n\n**Avoid**\n- Serifs entirely\n- Color combinations other than ink + ground + one accent\n- Italic, drop shadows, gradients, any softness\n- \"Friendly\" copywriting tone — Swiss is matter-of-fact\n\n**AI prompt seed**\n> Transit-signage style composition, large directional arrow set in black on flat field of single accent color, condensed Helvetica destination labels, no decoration, 16:9 flat vector aesthetic.\n\n**Don't use when**\n- The brand needs warmth — Helvetica + grid reads as institutional / cold\n- The content is emotional / storytelling — Swiss strips emotion out\n- Modernity / freshness is the goal — Swiss design now reads as classical / archival\n\n---\n\n> **Same school — Information Architecture**: [`pentagram`](./pentagram.md) · [`bloomberg-terminal`](./bloomberg-terminal.md) · [`tufte-dataink`](./tufte-dataink.md) · [`nyt-the-daily`](./nyt-the-daily.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10392
|
+
},
|
|
10393
|
+
{
|
|
10394
|
+
name: "references/style-recipes/y2k-retrofuturism.md",
|
|
10395
|
+
description: "Y2K / Frutiger Aero Retrofuturism · Late-90s / early-2000s techno-optimism: chrome, glassy bubbles, jelly translucency, lens flare",
|
|
10396
|
+
getContent: () => "\n# y2k-retrofuturism — Y2K / Frutiger Aero Retrofuturism\n\n- **School**: Specialty / Genre\n- **Vibe**: Late-90s / early-2000s techno-optimism: chrome, glassy bubbles, jelly translucency, lens flare\n- **Best for**: Fashion / music / nostalgia-coded products, creative campaigns, anything referencing 1999–2005 internet\n- **Touchstone**: late-90s Apple iMac G3 ads, Windows Vista Aero, the original iPod aqua buttons, MySpace pages\n\n**Palette**\n- Ground: electric ice blue `#A3D5FF` or chrome silver `#E0E4EA`\n- Glass accents: cyan `#5FC9F8` → magenta `#FF66C4` (the iridescent / oil-slick gradient)\n- Chrome: brushed-steel highlights at `#C4C9D2` to `#F2F4F8`\n- Acid green pops: `#A8FF00` (used sparingly)\n- Ink: `#000000` or `#FFFFFF` depending on ground\n\n**Typography**\n- Display: a chunky rounded sans with personality — Frutiger, Eurostile, or Sharp Grotesk in heavy weight; sometimes a chrome-effect treatment\n- Body: friendly humanist sans like Frutiger or Avenir at 14–16px\n- Numerals love an LCD / digital-readout treatment for hero counters\n- Underline + sparkle treatments common on link hover\n\n**Spacing**: 4 / 8 / 16 / 24 — packed but bubbly\n\n**Radius**: gummy — 16 / 24 / 32 / 9999 (full circle). Buttons are pill-shaped jelly bubbles.\n\n**Shadow**: heavy and colored — glossy reflections, drop shadows like `0 16px 32px rgba(95, 201, 248, 0.4)`. Inner highlights to simulate glass.\n\n**Motion**: bouncy springs, glow pulses, lens flare sweeps. Anything that recalls early-Flash interactions.\n\n**Signature moves**\n- Glassy translucent buttons with an inner top highlight (faking aqua / lozenge)\n- Iridescent oil-slick gradients on hero / character imagery\n- Chrome-effect type with sparkle overlays\n- Lens flare or specular highlight on any glass surface\n- 3D / dimensional UI elements (the OS-X-Aqua dock effect)\n- Floating circular orbs / bubbles drifting in the background\n\n**Avoid**\n- Sharp corners\n- Muted / earthy palettes — Y2K is bright and synthetic\n- Restrained typography — Y2K is exuberant\n- Anything that reads as \"tasteful 2020s minimalism\"\n\n**AI prompt seed**\n> Y2K retrofuturism scene, chrome silver and ice-blue iridescent gradient background, floating glass orbs with cyan and magenta oil-slick reflections, lens flare in upper right, single bright translucent pill-shaped button, late-1990s techno-optimism vibe, 16:9.\n\n**Don't use when**\n- The brand has no nostalgic / retro / playful claim\n- The audience is enterprise / professional — this recipe is consumer-fashion-music coded\n- Long-form readability matters (Y2K is for moments, not for reading)\n\n---\n\n> **Same school — Specialty / Genre**: [`mid-century-modern`](./mid-century-modern.md) \n> **Browse all 25 recipes**: [INDEX.md](./INDEX.md)\n\n"
|
|
10397
|
+
}
|
|
10398
|
+
], qd = "web-design-engineer";
|
|
10399
|
+
function Jd() {
|
|
10400
|
+
return {
|
|
10401
|
+
name: qd,
|
|
10402
|
+
description: "Design taste & anti-cliché system: declare the design system (palette/type/spacing/motion) before coding, avoid AI-default aesthetics (purple gradients/emoji icons/Inter), 25 named style recipes (linear/apple-hig/muji/pentagram/bloomberg-terminal…), 5-dimension critique. Load BEFORE starting any design-heavy visual work.",
|
|
10403
|
+
getContent: () => Gd,
|
|
10404
|
+
references: Kd
|
|
10405
|
+
};
|
|
10406
|
+
}
|
|
10407
|
+
//#endregion
|
|
10239
10408
|
//#region src/core/sdk/htmlRenderCheck.ts
|
|
10240
|
-
var
|
|
10241
|
-
function
|
|
10409
|
+
var Yd = /SecurityError|localStorage|sessionStorage|document\.cookie|Access is denied for this document/i;
|
|
10410
|
+
function Xd(e) {
|
|
10242
10411
|
if (e.noDom) return {
|
|
10243
10412
|
verdict: "pass",
|
|
10244
10413
|
problems: [],
|
|
@@ -10261,10 +10430,10 @@ function Kd(e) {
|
|
|
10261
10430
|
let e = r.lineno === void 0 ? "" : `(第 ${r.lineno} 行)`;
|
|
10262
10431
|
switch (r.type) {
|
|
10263
10432
|
case "js-error":
|
|
10264
|
-
|
|
10433
|
+
Yd.test(r.message) ? n.push(`storage 类错误降级为观察(沙箱 opaque origin 访问 localStorage/cookie 抛 SecurityError,非代码缺陷):${r.message}${e}`) : t.push(`JS 运行时错误:${r.message}${e}${r.source ? ` [${r.source}]` : ""}`);
|
|
10265
10434
|
break;
|
|
10266
10435
|
case "console-error":
|
|
10267
|
-
|
|
10436
|
+
Yd.test(r.message) ? n.push(`console.error(storage 类,沙箱假阳性降观察):${r.message}`) : t.push(`console.error:${r.message}`);
|
|
10268
10437
|
break;
|
|
10269
10438
|
case "unhandledrejection":
|
|
10270
10439
|
t.push(`未捕获的异步 Promise 拒绝(unhandledrejection):${r.message}`);
|
|
@@ -10287,7 +10456,7 @@ function Kd(e) {
|
|
|
10287
10456
|
metrics: e.metrics
|
|
10288
10457
|
};
|
|
10289
10458
|
}
|
|
10290
|
-
function
|
|
10459
|
+
function Zd(e) {
|
|
10291
10460
|
return [
|
|
10292
10461
|
"(function(){",
|
|
10293
10462
|
`var N=${JSON.stringify(e)};`,
|
|
@@ -10314,23 +10483,23 @@ function qd(e) {
|
|
|
10314
10483
|
"})();"
|
|
10315
10484
|
].join("\n");
|
|
10316
10485
|
}
|
|
10317
|
-
function
|
|
10318
|
-
let n = `<script>${
|
|
10486
|
+
function Qd(e, t) {
|
|
10487
|
+
let n = `<script>${Zd(t)}<\/script>`;
|
|
10319
10488
|
return /<head[^>]*>/i.test(e) ? e.replace(/<head[^>]*>/i, (e) => e + n) : /<html[^>]*>/i.test(e) ? e.replace(/<html[^>]*>/i, (e) => e + n) : n + e;
|
|
10320
10489
|
}
|
|
10321
|
-
function
|
|
10490
|
+
function $d() {
|
|
10322
10491
|
return typeof document < "u" && typeof document.createElement == "function" && document.body !== void 0 && document.body !== null;
|
|
10323
10492
|
}
|
|
10324
|
-
var
|
|
10493
|
+
var ef = {
|
|
10325
10494
|
created: 0,
|
|
10326
10495
|
destroyed: 0
|
|
10327
10496
|
};
|
|
10328
|
-
function
|
|
10329
|
-
return { ...
|
|
10497
|
+
function tf() {
|
|
10498
|
+
return { ...ef };
|
|
10330
10499
|
}
|
|
10331
|
-
function
|
|
10500
|
+
function nf(e, t = {}) {
|
|
10332
10501
|
let n = t.silenceMs ?? 900, r = t.hardCapMs ?? 4e3, i = t.metricsGraceMs ?? 500;
|
|
10333
|
-
if (
|
|
10502
|
+
if (!$d()) return Promise.resolve({
|
|
10334
10503
|
handshake: !1,
|
|
10335
10504
|
signals: [],
|
|
10336
10505
|
noDom: !0
|
|
@@ -10338,9 +10507,9 @@ function Qd(e, t = {}) {
|
|
|
10338
10507
|
let a = `pg_rc_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
10339
10508
|
return new Promise((t) => {
|
|
10340
10509
|
let o = [], s = !1, c, l, u, d, f, p, m = !1;
|
|
10341
|
-
|
|
10510
|
+
ef.created++;
|
|
10342
10511
|
let h = () => {
|
|
10343
|
-
m || (m = !0, u && window.removeEventListener("message", u), d && clearTimeout(d), f && clearTimeout(f), p && clearTimeout(p), l && (l.remove(),
|
|
10512
|
+
m || (m = !0, u && window.removeEventListener("message", u), d && clearTimeout(d), f && clearTimeout(f), p && clearTimeout(p), l && (l.remove(), ef.destroyed++));
|
|
10344
10513
|
}, g = (e = !1) => {
|
|
10345
10514
|
h(), t({
|
|
10346
10515
|
handshake: s,
|
|
@@ -10387,14 +10556,14 @@ function Qd(e, t = {}) {
|
|
|
10387
10556
|
...r.source === void 0 ? {} : { source: String(r.source) },
|
|
10388
10557
|
...typeof r.lineno == "number" ? { lineno: r.lineno } : {}
|
|
10389
10558
|
}), s && _();
|
|
10390
|
-
}, window.addEventListener("message", u), f = setTimeout(() => g(!0), r), l = document.createElement("iframe"), l.setAttribute("sandbox", "allow-scripts"), l.setAttribute("aria-hidden", "true"), l.setAttribute("tabindex", "-1"), l.style.cssText = "position:fixed;left:-9999px;top:0;width:1024px;height:600px;visibility:hidden;border:0;opacity:0;pointer-events:none;", l.srcdoc =
|
|
10559
|
+
}, window.addEventListener("message", u), f = setTimeout(() => g(!0), r), l = document.createElement("iframe"), l.setAttribute("sandbox", "allow-scripts"), l.setAttribute("aria-hidden", "true"), l.setAttribute("tabindex", "-1"), l.style.cssText = "position:fixed;left:-9999px;top:0;width:1024px;height:600px;visibility:hidden;border:0;opacity:0;pointer-events:none;", l.srcdoc = Qd(e, a), document.body.appendChild(l);
|
|
10391
10560
|
});
|
|
10392
10561
|
}
|
|
10393
|
-
function
|
|
10394
|
-
let t = e.vfsPrefix ?? "html/", n = e.codeField ?? "code", r = e.maxTargets ?? 4, i = typeof e.runner == "function", a = e.runner ?? ((e) =>
|
|
10562
|
+
function rf(e = {}) {
|
|
10563
|
+
let t = e.vfsPrefix ?? "html/", n = e.codeField ?? "code", r = e.maxTargets ?? 4, i = typeof e.runner == "function", a = e.runner ?? ((e) => nf(e)), o, s = e.writablePaths ?? [];
|
|
10395
10564
|
return {
|
|
10396
10565
|
check: async ({ state: e, log: c }) => {
|
|
10397
|
-
if (!i &&
|
|
10566
|
+
if (!i && !$d()) return c?.("middleware", {
|
|
10398
10567
|
stage: "render_check_skip",
|
|
10399
10568
|
reason: "no-dom"
|
|
10400
10569
|
}), { ok: !0 };
|
|
@@ -10429,7 +10598,7 @@ function $d(e = {}) {
|
|
|
10429
10598
|
});
|
|
10430
10599
|
let g = [], _ = [], v = [];
|
|
10431
10600
|
for (let e of h) {
|
|
10432
|
-
let t =
|
|
10601
|
+
let t = Xd(await a(e.html));
|
|
10433
10602
|
for (let n of t.problems) g.push(`${e.label}:${n}`);
|
|
10434
10603
|
for (let n of t.warnings) _.push(`${e.label}:${n}`);
|
|
10435
10604
|
t.verdict === "unavailable" && v.push(e.label), c?.("middleware", {
|
|
@@ -10469,7 +10638,7 @@ function $d(e = {}) {
|
|
|
10469
10638
|
}
|
|
10470
10639
|
};
|
|
10471
10640
|
}
|
|
10472
|
-
function
|
|
10641
|
+
function af(e, t) {
|
|
10473
10642
|
return async (n) => {
|
|
10474
10643
|
let r = await e(n);
|
|
10475
10644
|
return r.ok ? t(n) : r;
|
|
@@ -10477,7 +10646,7 @@ function ef(e, t) {
|
|
|
10477
10646
|
}
|
|
10478
10647
|
//#endregion
|
|
10479
10648
|
//#region src/core/sdk/htmlSubagent.ts
|
|
10480
|
-
function
|
|
10649
|
+
function of(e, t, n) {
|
|
10481
10650
|
let r = "html";
|
|
10482
10651
|
return `你是纯代码组件生成专家。可用工具:vfs_write / vfs_edit / vfs_rm(写/改/删代码工作副本 vfs ${e}) / vfs_read + vfs_grep(读 vfs) / validate_code(代码格式自检) / write(写 data,writablePaths 限定;set/patch 增量或整体) / read / write_todos + update_todo(规划)。
|
|
10483
10652
|
|
|
@@ -10532,7 +10701,7 @@ ${`- 输出形态:完整、自包含的 HTML 页面(.${r},可独立成页)`}
|
|
|
10532
10701
|
- 一次想清楚 → 动手 → 验证 → 收口。**思考是手段不是目的,产出代码才是**。
|
|
10533
10702
|
8. **收口格式(必守,最后一行)**:收口回复 = 简短结果总结 + **末行交接笔记** \`[note] <一句话实现要点>\` —— 关键设计决策 / 用户偏好 / 踩坑(如「[note] 液面用 height keyframes 4.2s 循环;装饰仅灯串+光斑 2 类」)。框架只识别末尾 [note] 行存进组件、自动转交下次维护该组件的子 agent;**漏写 = 下任失去交接**(真 LLM 实测漏写率 3/4,务必末行带上)。一行内,只写可复用结论。`;
|
|
10534
10703
|
}
|
|
10535
|
-
function
|
|
10704
|
+
function sf(e, t) {
|
|
10536
10705
|
return `# HTML 完整页面生成规范
|
|
10537
10706
|
|
|
10538
10707
|
## 代码资产模型(重要)
|
|
@@ -10570,14 +10739,14 @@ function nf(e, t) {
|
|
|
10570
10739
|
- 语义化标签(button / nav / section);图片 alt;交互可键盘聚焦
|
|
10571
10740
|
- 颜色对比达标;不只用颜色传达信息`;
|
|
10572
10741
|
}
|
|
10573
|
-
function
|
|
10742
|
+
function cf(e = "components", t = "code") {
|
|
10574
10743
|
return {
|
|
10575
10744
|
name: "html-fragment",
|
|
10576
10745
|
description: "完整 HTML 页面生成规范:代码作为 data 资产 / vfs 工作副本 / 完整自包含可独立成页 / script+CSS 集中放置 / 可引外部 JS/CSS / validate_code 自检 / 安全底线 / 可访问性",
|
|
10577
|
-
getContent: () =>
|
|
10746
|
+
getContent: () => sf(e, t)
|
|
10578
10747
|
};
|
|
10579
10748
|
}
|
|
10580
|
-
function
|
|
10749
|
+
function lf(e = {}) {
|
|
10581
10750
|
let t = e.vfsPrefix ?? "html/";
|
|
10582
10751
|
return ({ state: e }) => {
|
|
10583
10752
|
let n = e.files;
|
|
@@ -10594,7 +10763,7 @@ function af(e = {}) {
|
|
|
10594
10763
|
} : { ok: !0 };
|
|
10595
10764
|
};
|
|
10596
10765
|
}
|
|
10597
|
-
function
|
|
10766
|
+
function uf(e) {
|
|
10598
10767
|
let n, i, a = {
|
|
10599
10768
|
name: "html-validate-tools",
|
|
10600
10769
|
beforeAgent: (e) => {
|
|
@@ -10647,26 +10816,26 @@ function of(e) {
|
|
|
10647
10816
|
i = e;
|
|
10648
10817
|
}, a;
|
|
10649
10818
|
}
|
|
10650
|
-
var
|
|
10651
|
-
function
|
|
10652
|
-
let { writablePaths: t, codeVfsPrefix: n = "html/", id: r = "html", description: i, planning: a = !0, summarization: o = !0, maxToolRounds: s = 12, temperature: c = .4, skills: l, extraTools: u, formatCheck: d = !0, codeField: f = "code", orchestratorPrompt: p = !0, craftNotes: m = !0, llm: h, thinkingMode: g, allowedTools: _ } = e;
|
|
10819
|
+
var df = 2;
|
|
10820
|
+
function ff(e = {}) {
|
|
10821
|
+
let { writablePaths: t, codeVfsPrefix: n = "html/", id: r = "html", description: i, planning: a = !0, summarization: o = !0, maxToolRounds: s = 12, temperature: c = .4, skills: l, extraTools: u, formatCheck: d = !0, codeField: f = "code", orchestratorPrompt: p = !0, craftNotes: m = !0, llm: h, thinkingMode: g, allowedTools: _, design: v } = e;
|
|
10653
10822
|
if (t !== void 0 && !Array.isArray(t)) throw Error("[page-agent-sdk][createHtmlSubagent] writablePaths 须为字符串数组(代码组件 data 区,如 [\"components\"])/ 省略以从 schema 自动推断");
|
|
10654
|
-
let
|
|
10655
|
-
a &&
|
|
10656
|
-
let
|
|
10823
|
+
let y = [];
|
|
10824
|
+
a && y.push(no());
|
|
10825
|
+
let b = d ? rf({
|
|
10657
10826
|
vfsPrefix: n,
|
|
10658
10827
|
codeField: f,
|
|
10659
10828
|
writablePaths: t ?? []
|
|
10660
10829
|
}) : void 0;
|
|
10661
10830
|
if (d) {
|
|
10662
|
-
|
|
10663
|
-
let e =
|
|
10664
|
-
|
|
10831
|
+
y.push(uf(n));
|
|
10832
|
+
let e = lf({ vfsPrefix: n }), t = Fl({ check: b ? af(e, b.check) : e });
|
|
10833
|
+
b && (t._setGetController = b.setGetController), y.push(t);
|
|
10665
10834
|
}
|
|
10666
|
-
let
|
|
10835
|
+
let x = u ?? [], ee = t?.[0] ?? "components", S = l === void 0, C = v === !1 ? null : v === void 0 || v === !0 ? Jd() : v, w = (e) => [...S ? [cf(e, f)] : l, ...C ? [C] : []], T = {
|
|
10667
10836
|
id: r,
|
|
10668
10837
|
description: i ?? `生成/修改纯代码组件(代码作为 data 资产,代码字段 ${f} 随 data 持久化;vfs 作工作副本;能规划(write_todos)+ 执行)。需写代码组件或灵活定制时委派`,
|
|
10669
|
-
systemPrompt:
|
|
10838
|
+
systemPrompt: of(n, f, ee),
|
|
10670
10839
|
...h ? { llm: h } : {},
|
|
10671
10840
|
...g ? { thinkingMode: g } : {},
|
|
10672
10841
|
writablePaths: t ?? [],
|
|
@@ -10678,29 +10847,29 @@ function cf(e = {}) {
|
|
|
10678
10847
|
"vfs_read",
|
|
10679
10848
|
..._ ?? []
|
|
10680
10849
|
],
|
|
10681
|
-
middleware:
|
|
10850
|
+
middleware: y.length ? y : void 0,
|
|
10682
10851
|
summarization: o === !1 ? void 0 : o,
|
|
10683
|
-
maxVerifyAttempts: d ?
|
|
10852
|
+
maxVerifyAttempts: d ? df : void 0,
|
|
10684
10853
|
temperature: c,
|
|
10685
|
-
skills: ee
|
|
10854
|
+
skills: w(ee),
|
|
10686
10855
|
maxToolRounds: s,
|
|
10687
|
-
tools:
|
|
10856
|
+
tools: x.length ? x : void 0,
|
|
10688
10857
|
_codeAsset: {
|
|
10689
10858
|
writablePaths: t ?? [],
|
|
10690
10859
|
codeVfsPrefix: n,
|
|
10691
10860
|
ext: "html",
|
|
10692
10861
|
codeField: f,
|
|
10693
10862
|
craftNotes: m,
|
|
10694
|
-
...p ? { orchestratorPrompt: fs(r, f) } : {}
|
|
10863
|
+
...p ? { orchestratorPrompt: fs(r, f, v === void 0 || v === !0) } : {}
|
|
10695
10864
|
}
|
|
10696
10865
|
};
|
|
10697
|
-
return
|
|
10698
|
-
|
|
10699
|
-
},
|
|
10866
|
+
return T._rebuildCodeAssetPaths = (e) => {
|
|
10867
|
+
T.systemPrompt = of(n, f, e), T.skills = w(e), b?.setWritablePaths([e]);
|
|
10868
|
+
}, T;
|
|
10700
10869
|
}
|
|
10701
10870
|
//#endregion
|
|
10702
10871
|
//#region src/core/sdk/defineTool.ts
|
|
10703
|
-
function
|
|
10872
|
+
function pf(e) {
|
|
10704
10873
|
let t = r(async (t) => {
|
|
10705
10874
|
let n = await e.handler(t);
|
|
10706
10875
|
return typeof n == "string" ? n : JSON.stringify(n);
|
|
@@ -10713,7 +10882,7 @@ function lf(e) {
|
|
|
10713
10882
|
}
|
|
10714
10883
|
//#endregion
|
|
10715
10884
|
//#region src/core/sdk/inspectContextTool.ts
|
|
10716
|
-
var
|
|
10885
|
+
var mf = t.object({
|
|
10717
10886
|
path: t.string().optional().describe("分类 key 过滤(如 toolResults/history/systemPrompt/dataHint);不传=全部分类"),
|
|
10718
10887
|
role: t.enum([
|
|
10719
10888
|
"user",
|
|
@@ -10723,11 +10892,11 @@ var uf = t.object({
|
|
|
10723
10892
|
]).optional().describe("聚焦每轮首句的角色(assistant→回复摘要,user→问题摘要);不影响工具列表"),
|
|
10724
10893
|
limit: t.number().int().min(1).max(50).optional().describe("返回最近 N 轮(默认全部;超 50 自动截最近 50 防返回过大)")
|
|
10725
10894
|
});
|
|
10726
|
-
function
|
|
10727
|
-
return
|
|
10895
|
+
function hf(e) {
|
|
10896
|
+
return pf({
|
|
10728
10897
|
name: "inspect_context",
|
|
10729
10898
|
description: "查看当前对话上下文的构成(供压缩决策)。返回:totalTokens(历史轮次 token 总和,与触发判断同口径)、occupancy(窗口占用比)、contextWindow、categories(分类占用)、rounds(每轮 token/工具/首句)。先调用本工具查看构成,再决定压缩策略。",
|
|
10730
|
-
schema:
|
|
10899
|
+
schema: mf,
|
|
10731
10900
|
handler: (t) => {
|
|
10732
10901
|
let n = vc(e.getMessages()), r = n.length > 50 ? n.slice(n.length - 50) : n, i = (t.limit && r.length > t.limit ? r.slice(r.length - t.limit) : r).map((e) => {
|
|
10733
10902
|
let n = Ac(e), r = bc(e), i = t.role === "assistant" ? e.assistantMsgs[0] ?? e.userMsg : e.userMsg, a = yc(typeof i.content == "string" ? i.content : "", 60);
|
|
@@ -10750,7 +10919,7 @@ function df(e) {
|
|
|
10750
10919
|
}
|
|
10751
10920
|
//#endregion
|
|
10752
10921
|
//#region src/core/sdk/compressDecision.ts
|
|
10753
|
-
var
|
|
10922
|
+
var gf = t.object({
|
|
10754
10923
|
keepRounds: t.number().int().min(0).max(50).optional(),
|
|
10755
10924
|
windowRatio: t.number().min(0).max(1).optional(),
|
|
10756
10925
|
summarize: t.object({ mode: t.enum(["index", "llm"]) }),
|
|
@@ -10760,23 +10929,23 @@ var ff = t.object({
|
|
|
10760
10929
|
}).refine((e) => e.keepRounds !== void 0 || e.windowRatio !== void 0, { message: "keepRounds 与 windowRatio 至少填一个(按当前触发模式)" });
|
|
10761
10930
|
//#endregion
|
|
10762
10931
|
//#region src/core/sdk/llmResolver.ts
|
|
10763
|
-
function
|
|
10932
|
+
function _f(e) {
|
|
10764
10933
|
let t = e.find((e) => e.role === "user");
|
|
10765
10934
|
if (!t) return;
|
|
10766
10935
|
let n = t.content, r = (typeof n == "string" ? n : Array.isArray(n) ? n.map((e) => typeof e == "string" ? e : e?.text ?? "").join("") : String(n ?? "")).trim().replace(/[\n\r]+/g, " ");
|
|
10767
10936
|
if (r) return r.length > 30 ? r.slice(0, 30) + "…" : r;
|
|
10768
10937
|
}
|
|
10769
|
-
function
|
|
10938
|
+
function vf(e) {
|
|
10770
10939
|
return !!e && typeof e == "object" && typeof e.invoke == "function" && typeof e.stream == "function";
|
|
10771
10940
|
}
|
|
10772
|
-
function
|
|
10941
|
+
function yf(e) {
|
|
10773
10942
|
let t = e.content;
|
|
10774
10943
|
return typeof t == "string" ? t : Array.isArray(t) ? t.map((e) => typeof e == "string" ? e : e?.text ?? "").join("") : String(t ?? "");
|
|
10775
10944
|
}
|
|
10776
|
-
function
|
|
10945
|
+
function bf(e) {
|
|
10777
10946
|
let t = e.summaryLlm ?? e.llm;
|
|
10778
10947
|
if (!t) return;
|
|
10779
|
-
let n = e.summaryTemperature ?? .3, r = e.summaryMaxTokens ?? 1024, i = e.summaryTimeoutMs ?? 15e3, s =
|
|
10948
|
+
let n = e.summaryTemperature ?? .3, r = e.summaryMaxTokens ?? 1024, i = e.summaryTimeoutMs ?? 15e3, s = vf(t) ? t : null, c = vf(t) ? null : t;
|
|
10780
10949
|
if (c && !c.apiKey) {
|
|
10781
10950
|
e.summaryLlm && console.warn("[page-agent-sdk][summarization] summaryLlm 已配置但缺 apiKey,摘要回退主 agent 模型或零成本索引摘要");
|
|
10782
10951
|
return;
|
|
@@ -10789,16 +10958,16 @@ function gf(e) {
|
|
|
10789
10958
|
temperature: n,
|
|
10790
10959
|
maxTokens: r,
|
|
10791
10960
|
thinkingFallback: "simple"
|
|
10792
|
-
})),
|
|
10961
|
+
})), yf(await l.invoke([new o("你是对话历史压缩助手。把下面按轮次索引的对话要点,改写成一段连贯、紧凑的中文摘要,保留关键事实、用户意图与已用工具,不要编造。直接输出摘要正文。"), new a(e)], { signal: t.signal })).trim();
|
|
10793
10962
|
} finally {
|
|
10794
10963
|
clearTimeout(s);
|
|
10795
10964
|
}
|
|
10796
10965
|
};
|
|
10797
10966
|
}
|
|
10798
|
-
function
|
|
10967
|
+
function xf(e) {
|
|
10799
10968
|
let t = e.titleLlm ?? e.summaryLlm ?? e.llm;
|
|
10800
10969
|
if (!t) return;
|
|
10801
|
-
let n =
|
|
10970
|
+
let n = vf(t) ? t : null, r = vf(t) ? null : t;
|
|
10802
10971
|
if (r && !r.apiKey) return;
|
|
10803
10972
|
let i = n;
|
|
10804
10973
|
return async (t) => {
|
|
@@ -10810,7 +10979,7 @@ function _f(e) {
|
|
|
10810
10979
|
thinkingFallback: "simple"
|
|
10811
10980
|
}));
|
|
10812
10981
|
let t = await i.invoke([new o(e.i18n?.locale === "en-US" ? "Generate a short English title (max 10 words) summarizing the conversation below. Output the title text only, no quotes or punctuation." : "根据以下对话的主旨,生成一个简短的中文标题(不超过15个字,不要标点和引号,直接输出标题文字)。"), new a(n)], { signal: s.signal }), c = e.i18n?.locale === "en-US" ? 60 : 20;
|
|
10813
|
-
return
|
|
10982
|
+
return yf(t).trim().replace(/^["'""「『]|["'""」』]$/g, "").split("\n")[0].slice(0, c);
|
|
10814
10983
|
} catch {
|
|
10815
10984
|
return "";
|
|
10816
10985
|
} finally {
|
|
@@ -10818,10 +10987,10 @@ function _f(e) {
|
|
|
10818
10987
|
}
|
|
10819
10988
|
};
|
|
10820
10989
|
}
|
|
10821
|
-
function
|
|
10990
|
+
function Sf() {
|
|
10822
10991
|
return `call_dec_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
10823
10992
|
}
|
|
10824
|
-
function
|
|
10993
|
+
function Cf(e) {
|
|
10825
10994
|
let t = e.match(/```(?:json)?\s*([\s\S]*?)```/), n = t ? t[1] : e, r = n.indexOf("{"), i = n.lastIndexOf("}");
|
|
10826
10995
|
if (r < 0 || i < 0 || i <= r) return null;
|
|
10827
10996
|
try {
|
|
@@ -10830,21 +10999,21 @@ function yf(e) {
|
|
|
10830
10999
|
return null;
|
|
10831
11000
|
}
|
|
10832
11001
|
}
|
|
10833
|
-
function
|
|
11002
|
+
function wf(e) {
|
|
10834
11003
|
if (!e) return null;
|
|
10835
|
-
let t =
|
|
11004
|
+
let t = Cf(e);
|
|
10836
11005
|
if (t == null) return null;
|
|
10837
|
-
let n =
|
|
11006
|
+
let n = gf.safeParse(t);
|
|
10838
11007
|
return n.success ? n.data : null;
|
|
10839
11008
|
}
|
|
10840
|
-
async function
|
|
11009
|
+
async function Tf(e, t, n, r) {
|
|
10841
11010
|
let i = [...n];
|
|
10842
11011
|
for (let n = 0; n < 3; n++) {
|
|
10843
11012
|
let n = await e.invoke(i, { signal: r }), a = n?.tool_calls ?? [];
|
|
10844
|
-
if (!a.length) return
|
|
11013
|
+
if (!a.length) return yf(n).trim();
|
|
10845
11014
|
i = [...i, n];
|
|
10846
11015
|
for (let e of a) {
|
|
10847
|
-
let n = e.id ??
|
|
11016
|
+
let n = e.id ?? Sf();
|
|
10848
11017
|
try {
|
|
10849
11018
|
let r = await t.invoke(e.args ?? {}), a = typeof r == "string" ? r : r?.content ?? JSON.stringify(r);
|
|
10850
11019
|
i = [...i, new s({
|
|
@@ -10861,10 +11030,10 @@ async function xf(e, t, n, r) {
|
|
|
10861
11030
|
}
|
|
10862
11031
|
return "";
|
|
10863
11032
|
}
|
|
10864
|
-
function
|
|
11033
|
+
function Ef(e) {
|
|
10865
11034
|
let t = e.summaryLlm ?? e.llm;
|
|
10866
11035
|
if (!t) return;
|
|
10867
|
-
let n =
|
|
11036
|
+
let n = vf(t) ? t : null, r = vf(t) ? null : t;
|
|
10868
11037
|
if (r && !r.apiKey) return;
|
|
10869
11038
|
let i = e.decisionTimeoutMs ?? 6e3, s = e.decisionMaxTokens ?? 2048, c = n;
|
|
10870
11039
|
return async (e) => {
|
|
@@ -10877,12 +11046,12 @@ function Sf(e) {
|
|
|
10877
11046
|
}));
|
|
10878
11047
|
let n = c;
|
|
10879
11048
|
if (typeof n.bindTools != "function") return null;
|
|
10880
|
-
let i =
|
|
11049
|
+
let i = hf({
|
|
10881
11050
|
getMessages: e.getMessages,
|
|
10882
11051
|
getSnapshot: e.getSnapshot,
|
|
10883
11052
|
contextWindow: e.contextWindow
|
|
10884
11053
|
}), l = n.bindTools([i]), u = e.triggerMode === "token" ? "token 模式:必填 windowRatio(0~1 保留窗口预算比例,如 0.4);keepRounds 可选" : "轮数模式:必填 keepRounds(保留最近几轮,如 6);windowRatio 可选", d = [new o(`你是对话历史压缩决策助手。当前触发模式 = ${e.triggerMode}(${e.triggerReason})。先调用 inspect_context 查看上下文构成,再输出压缩决策 JSON。${u}。仅输出 JSON,不要多余文字/markdown。schema:{keepRounds?(int 0-50), windowRatio?(0-1), summarize:{mode:"index"|"llm"}, recallTopK?(int 0-10), preserveTools?(string[]≤10), reason?(≤200字)}。keepRounds/windowRatio 至少填一个。`), new a(`触发原因:${e.triggerReason}\n上下文窗口:${e.contextWindow ?? "未知"} tokens\n阈值比例:${e.thresholdRatio ?? "未知"}\n\n请决策压缩策略(先 inspect_context 查看,再输出 JSON)。`)];
|
|
10885
|
-
return
|
|
11054
|
+
return wf(await Tf(l, i, d, t.signal)) || wf(await Tf(l, i, [...d, new a("上一次输出无效(JSON 解析失败或 schema 校验不通过)。请只输出符合 schema 的压缩决策 JSON,不要多余文字/markdown 代码块。")], t.signal));
|
|
10886
11055
|
} catch {
|
|
10887
11056
|
return null;
|
|
10888
11057
|
} finally {
|
|
@@ -10890,8 +11059,8 @@ function Sf(e) {
|
|
|
10890
11059
|
}
|
|
10891
11060
|
};
|
|
10892
11061
|
}
|
|
10893
|
-
function
|
|
10894
|
-
let t = e.llm, n =
|
|
11062
|
+
function Df(e) {
|
|
11063
|
+
let t = e.llm, n = vf(e.llm) ? void 0 : e.llm;
|
|
10895
11064
|
return {
|
|
10896
11065
|
modelCaps: m({
|
|
10897
11066
|
model: n?.model ?? t?.model ?? t?.modelName,
|
|
@@ -10899,14 +11068,14 @@ function Cf(e) {
|
|
|
10899
11068
|
maxOutputTokens: e.maxOutputTokens ?? n?.maxOutputTokens ?? t?.maxOutputTokens,
|
|
10900
11069
|
vision: n?.vision ?? t?.vision
|
|
10901
11070
|
}),
|
|
10902
|
-
summaryLlmInvoke:
|
|
10903
|
-
titleLlmInvoke:
|
|
10904
|
-
compressDecisionInvoke:
|
|
11071
|
+
summaryLlmInvoke: bf(e),
|
|
11072
|
+
titleLlmInvoke: xf(e),
|
|
11073
|
+
compressDecisionInvoke: Ef(e)
|
|
10905
11074
|
};
|
|
10906
11075
|
}
|
|
10907
11076
|
//#endregion
|
|
10908
11077
|
//#region src/core/sdk/conflictManager.ts
|
|
10909
|
-
function
|
|
11078
|
+
function Of(e, t) {
|
|
10910
11079
|
let n = /* @__PURE__ */ Lt(null), r = 0;
|
|
10911
11080
|
function i(i, a) {
|
|
10912
11081
|
let o = t?.() ?? "ask";
|
|
@@ -10952,7 +11121,7 @@ function wf(e, t) {
|
|
|
10952
11121
|
}
|
|
10953
11122
|
//#endregion
|
|
10954
11123
|
//#region src/core/utils/id.ts
|
|
10955
|
-
function
|
|
11124
|
+
function kf() {
|
|
10956
11125
|
try {
|
|
10957
11126
|
if (typeof crypto < "u" && typeof crypto.randomUUID == "function") return crypto.randomUUID();
|
|
10958
11127
|
} catch {}
|
|
@@ -10960,7 +11129,7 @@ function Tf() {
|
|
|
10960
11129
|
}
|
|
10961
11130
|
//#endregion
|
|
10962
11131
|
//#region src/core/backends/storage.ts
|
|
10963
|
-
var
|
|
11132
|
+
var Af = "chat-sdk", jf = 50 * 1024 * 1024, Mf = 4 * 1024 * 1024, Nf = 10 * 1024 * 1024, Pf = .9, Ff = 500, If = 5e3, Lf = 300, Rf = "__meta__", zf = "v:1", Bf = [
|
|
10964
11133
|
"messages",
|
|
10965
11134
|
"vfs",
|
|
10966
11135
|
"todos",
|
|
@@ -10972,51 +11141,51 @@ var Ef = "chat-sdk", Df = 50 * 1024 * 1024, Of = 4 * 1024 * 1024, kf = 10 * 1024
|
|
|
10972
11141
|
"focus",
|
|
10973
11142
|
"planConfirmation"
|
|
10974
11143
|
];
|
|
10975
|
-
function
|
|
10976
|
-
return e === "local" || e === "session" ?
|
|
11144
|
+
function Vf(e) {
|
|
11145
|
+
return e === "local" || e === "session" ? Mf : jf;
|
|
10977
11146
|
}
|
|
10978
|
-
function
|
|
11147
|
+
function Hf(e) {
|
|
10979
11148
|
if (typeof e != "object" || !e) return !1;
|
|
10980
11149
|
let t = e;
|
|
10981
11150
|
return t.name === "QuotaExceededError" || t.name === "NS_ERROR_DOM_QUOTA_REACHED" || t.code === 22 || t.code === 1014;
|
|
10982
11151
|
}
|
|
10983
|
-
function
|
|
11152
|
+
function Uf(e, t, n, r) {
|
|
10984
11153
|
return [
|
|
10985
|
-
|
|
11154
|
+
zf,
|
|
10986
11155
|
e,
|
|
10987
11156
|
t,
|
|
10988
11157
|
n,
|
|
10989
11158
|
r
|
|
10990
11159
|
].join("::");
|
|
10991
11160
|
}
|
|
10992
|
-
function
|
|
11161
|
+
function Wf(e, t, n) {
|
|
10993
11162
|
return [
|
|
10994
|
-
|
|
11163
|
+
zf,
|
|
10995
11164
|
e,
|
|
10996
11165
|
t,
|
|
10997
11166
|
n
|
|
10998
11167
|
].join("::") + "::";
|
|
10999
11168
|
}
|
|
11000
|
-
function
|
|
11169
|
+
function Gf(e, t) {
|
|
11001
11170
|
return [
|
|
11002
|
-
|
|
11171
|
+
zf,
|
|
11003
11172
|
e,
|
|
11004
11173
|
t
|
|
11005
11174
|
].join("::") + "::";
|
|
11006
11175
|
}
|
|
11007
|
-
function
|
|
11008
|
-
return [
|
|
11176
|
+
function Kf(e) {
|
|
11177
|
+
return [zf, e].join("::") + "::";
|
|
11009
11178
|
}
|
|
11010
|
-
var
|
|
11011
|
-
function
|
|
11012
|
-
|
|
11179
|
+
var qf = null;
|
|
11180
|
+
function Jf(e) {
|
|
11181
|
+
qf ||= new TextEncoder();
|
|
11013
11182
|
try {
|
|
11014
|
-
return
|
|
11183
|
+
return qf.encode(JSON.stringify(e)).length;
|
|
11015
11184
|
} catch {
|
|
11016
11185
|
return 0;
|
|
11017
11186
|
}
|
|
11018
11187
|
}
|
|
11019
|
-
function
|
|
11188
|
+
function Yf(e, t, n) {
|
|
11020
11189
|
let r = e.reduce((e, t) => e + t.bytes, 0);
|
|
11021
11190
|
if (r <= t) return [];
|
|
11022
11191
|
let i = t * n, a = [...e].sort((e, t) => e.lastAccessed - t.lastAccessed), o = [];
|
|
@@ -11026,7 +11195,7 @@ function Gf(e, t, n) {
|
|
|
11026
11195
|
}
|
|
11027
11196
|
return o;
|
|
11028
11197
|
}
|
|
11029
|
-
function
|
|
11198
|
+
function Xf() {
|
|
11030
11199
|
let e = /* @__PURE__ */ new Map();
|
|
11031
11200
|
return {
|
|
11032
11201
|
async get(t) {
|
|
@@ -11046,7 +11215,7 @@ function Kf() {
|
|
|
11046
11215
|
}
|
|
11047
11216
|
};
|
|
11048
11217
|
}
|
|
11049
|
-
function
|
|
11218
|
+
function Zf(e) {
|
|
11050
11219
|
return new Promise((t, n) => {
|
|
11051
11220
|
if (typeof indexedDB > "u") {
|
|
11052
11221
|
n(/* @__PURE__ */ Error("indexedDB 不可用"));
|
|
@@ -11107,7 +11276,7 @@ function qf(e) {
|
|
|
11107
11276
|
}, r.onerror = () => n(r.error);
|
|
11108
11277
|
});
|
|
11109
11278
|
}
|
|
11110
|
-
function
|
|
11279
|
+
function Qf(e) {
|
|
11111
11280
|
return {
|
|
11112
11281
|
async get(t) {
|
|
11113
11282
|
let n = e.getItem(t);
|
|
@@ -11152,24 +11321,24 @@ function Jf(e) {
|
|
|
11152
11321
|
}
|
|
11153
11322
|
};
|
|
11154
11323
|
}
|
|
11155
|
-
function
|
|
11324
|
+
function $f(e, t, n) {
|
|
11156
11325
|
let r = (e.get(t) ?? Promise.resolve()).then(n, n);
|
|
11157
11326
|
return e.set(t, r.then(() => void 0, () => void 0)), r;
|
|
11158
11327
|
}
|
|
11159
|
-
function
|
|
11160
|
-
return
|
|
11328
|
+
function ep(e = {}) {
|
|
11329
|
+
return np(e, typeof e.backend == "object" && e.backend ? e.backend : void 0);
|
|
11161
11330
|
}
|
|
11162
|
-
function
|
|
11163
|
-
return
|
|
11331
|
+
function tp(e, t) {
|
|
11332
|
+
return np(e, t);
|
|
11164
11333
|
}
|
|
11165
|
-
function
|
|
11166
|
-
let n = e.dbName ??
|
|
11334
|
+
function np(e = {}, t) {
|
|
11335
|
+
let n = e.dbName ?? Af, r = typeof e.backend == "string" ? e.backend ?? "indexed" : "indexed", i = e.maxBytes ?? Vf(r), a = e.maxBytesPerSession ?? (i === Infinity ? Infinity : Nf), o = e.evictionWatermark ?? Pf, s = e.debounceMs ?? Ff, c = /* @__PURE__ */ new Set();
|
|
11167
11336
|
function l(e) {
|
|
11168
11337
|
for (let t of c) try {
|
|
11169
11338
|
t(e);
|
|
11170
11339
|
} catch {}
|
|
11171
11340
|
}
|
|
11172
|
-
let u =
|
|
11341
|
+
let u = Xf(), d = !1, f, p = new Promise((e) => {
|
|
11173
11342
|
f = e;
|
|
11174
11343
|
});
|
|
11175
11344
|
(async () => {
|
|
@@ -11180,16 +11349,16 @@ function Qf(e = {}, t) {
|
|
|
11180
11349
|
}
|
|
11181
11350
|
if (r === "indexed") {
|
|
11182
11351
|
if (typeof indexedDB > "u") throw Error("indexedDB 不可用");
|
|
11183
|
-
u = await
|
|
11352
|
+
u = await Zf(n), f(!0);
|
|
11184
11353
|
} else if (r === "session") {
|
|
11185
11354
|
if (typeof sessionStorage > "u") throw Error("sessionStorage 不可用");
|
|
11186
|
-
u =
|
|
11355
|
+
u = Qf(sessionStorage), f(!0);
|
|
11187
11356
|
} else if (r === "local") {
|
|
11188
11357
|
if (typeof localStorage > "u") throw Error("localStorage 不可用");
|
|
11189
|
-
u =
|
|
11190
|
-
} else u =
|
|
11358
|
+
u = Qf(localStorage), f(!0);
|
|
11359
|
+
} else u = Xf(), f(!1);
|
|
11191
11360
|
} catch (e) {
|
|
11192
|
-
u =
|
|
11361
|
+
u = Xf();
|
|
11193
11362
|
let t = e instanceof Error ? e.message : String(e);
|
|
11194
11363
|
f(!1), Promise.resolve().then(() => l({
|
|
11195
11364
|
type: "degraded",
|
|
@@ -11199,10 +11368,10 @@ function Qf(e = {}, t) {
|
|
|
11199
11368
|
})();
|
|
11200
11369
|
let m = /* @__PURE__ */ new Map(), h = /* @__PURE__ */ new Map(), g = /* @__PURE__ */ new Map();
|
|
11201
11370
|
function _(e, t, r, i) {
|
|
11202
|
-
let o =
|
|
11203
|
-
return
|
|
11371
|
+
let o = Uf(n, r, i, Rf);
|
|
11372
|
+
return $f(g, o, async () => {
|
|
11204
11373
|
let n = async () => {
|
|
11205
|
-
let n = await u.get(o), s = await u.get(e), c = s == null ? 0 :
|
|
11374
|
+
let n = await u.get(o), s = await u.get(e), c = s == null ? 0 : Jf(s), d = Jf(t), f = (n?.bytes ?? 0) + (d - c);
|
|
11206
11375
|
if (f > a) {
|
|
11207
11376
|
l({
|
|
11208
11377
|
type: "quota",
|
|
@@ -11224,12 +11393,12 @@ function Qf(e = {}, t) {
|
|
|
11224
11393
|
try {
|
|
11225
11394
|
await n();
|
|
11226
11395
|
} catch (e) {
|
|
11227
|
-
if (
|
|
11396
|
+
if (Hf(e) && !d) {
|
|
11228
11397
|
d = !0;
|
|
11229
11398
|
try {
|
|
11230
11399
|
await x();
|
|
11231
11400
|
} catch {}
|
|
11232
|
-
u =
|
|
11401
|
+
u = Xf(), Promise.resolve().then(() => l({
|
|
11233
11402
|
type: "degraded",
|
|
11234
11403
|
reason: "存储配额超限(QuotaExceededError),已淘汰最旧会话并降级内存"
|
|
11235
11404
|
}));
|
|
@@ -11241,7 +11410,7 @@ function Qf(e = {}, t) {
|
|
|
11241
11410
|
});
|
|
11242
11411
|
}
|
|
11243
11412
|
function v(e, t, r, i) {
|
|
11244
|
-
let a =
|
|
11413
|
+
let a = Uf(n, e, t, r), o = m.get(a);
|
|
11245
11414
|
o && o.resolve();
|
|
11246
11415
|
let c = h.get(a);
|
|
11247
11416
|
return c && clearTimeout(c), new Promise((n) => {
|
|
@@ -11268,16 +11437,16 @@ function Qf(e = {}, t) {
|
|
|
11268
11437
|
function b() {
|
|
11269
11438
|
y ||= setTimeout(() => {
|
|
11270
11439
|
y = null, x();
|
|
11271
|
-
},
|
|
11440
|
+
}, Lf);
|
|
11272
11441
|
}
|
|
11273
11442
|
async function x() {
|
|
11274
11443
|
try {
|
|
11275
11444
|
let e = [];
|
|
11276
|
-
await u.scan(
|
|
11445
|
+
await u.scan(Kf(n), (t, n) => {
|
|
11277
11446
|
t.endsWith("::__meta__") && e.push(n);
|
|
11278
11447
|
});
|
|
11279
|
-
let t =
|
|
11280
|
-
for (let e of t) await u.clearPrefix(
|
|
11448
|
+
let t = Yf(e, i, o);
|
|
11449
|
+
for (let e of t) await u.clearPrefix(Wf(n, e.agentId, e.sessionId)), l({
|
|
11281
11450
|
type: "evicted",
|
|
11282
11451
|
agentId: e.agentId,
|
|
11283
11452
|
sessionId: e.sessionId,
|
|
@@ -11294,13 +11463,13 @@ function Qf(e = {}, t) {
|
|
|
11294
11463
|
ready: p,
|
|
11295
11464
|
async listSessions(e) {
|
|
11296
11465
|
let t = [];
|
|
11297
|
-
return await u.scan(
|
|
11466
|
+
return await u.scan(Gf(n, e), (e, n) => {
|
|
11298
11467
|
e.endsWith("::__meta__") && t.push(n);
|
|
11299
11468
|
}), t.sort((e, t) => t.lastAccessed - e.lastAccessed);
|
|
11300
11469
|
},
|
|
11301
11470
|
async load(e, t) {
|
|
11302
|
-
let r =
|
|
11303
|
-
return await
|
|
11471
|
+
let r = Uf(n, e, t, Rf);
|
|
11472
|
+
return await $f(g, r, async () => {
|
|
11304
11473
|
let i = await u.get(r);
|
|
11305
11474
|
if (!i) return;
|
|
11306
11475
|
let a = {
|
|
@@ -11309,8 +11478,8 @@ function Qf(e = {}, t) {
|
|
|
11309
11478
|
todos: [],
|
|
11310
11479
|
memory: ""
|
|
11311
11480
|
};
|
|
11312
|
-
for (let r of
|
|
11313
|
-
let i = await u.get(
|
|
11481
|
+
for (let r of Bf) {
|
|
11482
|
+
let i = await u.get(Uf(n, e, t, r));
|
|
11314
11483
|
i != null && (a[r] = i);
|
|
11315
11484
|
}
|
|
11316
11485
|
i.lastAccessed = Date.now();
|
|
@@ -11322,12 +11491,12 @@ function Qf(e = {}, t) {
|
|
|
11322
11491
|
},
|
|
11323
11492
|
async save(e, t, n) {
|
|
11324
11493
|
let r = [];
|
|
11325
|
-
for (let i of
|
|
11494
|
+
for (let i of Bf) n[i] !== void 0 && r.push(v(e, t, i, n[i]));
|
|
11326
11495
|
await Promise.all(r);
|
|
11327
11496
|
},
|
|
11328
11497
|
async updateTitle(e, t, r) {
|
|
11329
|
-
let i =
|
|
11330
|
-
return
|
|
11498
|
+
let i = Uf(n, e, t, Rf);
|
|
11499
|
+
return $f(g, i, async () => {
|
|
11331
11500
|
let e = await u.get(i);
|
|
11332
11501
|
e && (e.title = r, e.lastAccessed = Date.now(), await u.set(i, e));
|
|
11333
11502
|
});
|
|
@@ -11337,7 +11506,7 @@ function Qf(e = {}, t) {
|
|
|
11337
11506
|
let t = h.get(e);
|
|
11338
11507
|
t && (clearTimeout(t), h.delete(e));
|
|
11339
11508
|
}
|
|
11340
|
-
let t = e.flushTimeoutMs ??
|
|
11509
|
+
let t = e.flushTimeoutMs ?? If, n = Array.from(m.entries());
|
|
11341
11510
|
await Promise.all(n.map(([e, n]) => new Promise((r) => {
|
|
11342
11511
|
let i;
|
|
11343
11512
|
_(e, n.value, n.agentId, n.sessionId).then(() => {
|
|
@@ -11353,13 +11522,13 @@ function Qf(e = {}, t) {
|
|
|
11353
11522
|
}))), l({ type: "flush" }), x();
|
|
11354
11523
|
},
|
|
11355
11524
|
async deleteSession(e, t) {
|
|
11356
|
-
let r =
|
|
11525
|
+
let r = Wf(n, e, t);
|
|
11357
11526
|
for (let [e, t] of m.entries()) e.startsWith(r) && (t.resolve(), m.delete(e));
|
|
11358
11527
|
for (let [e, t] of h.entries()) e.startsWith(r) && (clearTimeout(t), h.delete(e));
|
|
11359
11528
|
await u.clearPrefix(r);
|
|
11360
11529
|
},
|
|
11361
11530
|
async createSession(e, t, r) {
|
|
11362
|
-
let i = r ??
|
|
11531
|
+
let i = r ?? kf(), a = Date.now(), o = {
|
|
11363
11532
|
agentId: e,
|
|
11364
11533
|
sessionId: i,
|
|
11365
11534
|
createdAt: a,
|
|
@@ -11368,7 +11537,7 @@ function Qf(e = {}, t) {
|
|
|
11368
11537
|
title: t
|
|
11369
11538
|
};
|
|
11370
11539
|
try {
|
|
11371
|
-
await u.set(
|
|
11540
|
+
await u.set(Uf(n, e, i, Rf), o);
|
|
11372
11541
|
} catch {}
|
|
11373
11542
|
return i;
|
|
11374
11543
|
},
|
|
@@ -11385,15 +11554,15 @@ function Qf(e = {}, t) {
|
|
|
11385
11554
|
}
|
|
11386
11555
|
//#endregion
|
|
11387
11556
|
//#region src/core/sdk/optionsResolver.ts
|
|
11388
|
-
function
|
|
11389
|
-
return e === void 0 ?
|
|
11557
|
+
function rp(e) {
|
|
11558
|
+
return e === void 0 ? ep({ backend: "memory" }) : e === !1 ? null : typeof e == "string" ? ep({ backend: e }) : e.enabled === !1 ? null : ep(e);
|
|
11390
11559
|
}
|
|
11391
|
-
function
|
|
11560
|
+
function ip(e) {
|
|
11392
11561
|
return e.dialog ?? {};
|
|
11393
11562
|
}
|
|
11394
11563
|
//#endregion
|
|
11395
11564
|
//#region src/core/capabilities.ts
|
|
11396
|
-
var
|
|
11565
|
+
var ap = [
|
|
11397
11566
|
{
|
|
11398
11567
|
name: "dataOps",
|
|
11399
11568
|
defaultOn: !0
|
|
@@ -11469,13 +11638,13 @@ var tp = [
|
|
|
11469
11638
|
requires: ["summarization"]
|
|
11470
11639
|
}
|
|
11471
11640
|
];
|
|
11472
|
-
function
|
|
11641
|
+
function op(e) {
|
|
11473
11642
|
let t = {};
|
|
11474
|
-
for (let n of
|
|
11643
|
+
for (let n of ap) {
|
|
11475
11644
|
let r = e?.[n.name];
|
|
11476
11645
|
t[n.name] = n.defaultOn ? r !== !1 : r === !0;
|
|
11477
11646
|
}
|
|
11478
|
-
for (let e of
|
|
11647
|
+
for (let e of ap) if (e.requires && t[e.name]) {
|
|
11479
11648
|
for (let n of e.requires) if (!t[n]) {
|
|
11480
11649
|
t[e.name] = !1;
|
|
11481
11650
|
break;
|
|
@@ -11485,7 +11654,7 @@ function np(e) {
|
|
|
11485
11654
|
}
|
|
11486
11655
|
//#endregion
|
|
11487
11656
|
//#region src/core/sdk/events.ts
|
|
11488
|
-
function
|
|
11657
|
+
function sp(e) {
|
|
11489
11658
|
let t = /* @__PURE__ */ new Set(), n = (n) => {
|
|
11490
11659
|
if (n.type !== "approval_request") {
|
|
11491
11660
|
if (e) try {
|
|
@@ -11511,7 +11680,7 @@ function rp(e) {
|
|
|
11511
11680
|
}
|
|
11512
11681
|
//#endregion
|
|
11513
11682
|
//#region src/core/sdk/contextPreset.ts
|
|
11514
|
-
var
|
|
11683
|
+
var cp = {
|
|
11515
11684
|
auto: {
|
|
11516
11685
|
summaryThresholdRatio: .5,
|
|
11517
11686
|
windowRatio: .4,
|
|
@@ -11539,7 +11708,7 @@ var ip = {
|
|
|
11539
11708
|
enableRecall: !0,
|
|
11540
11709
|
enableLLMSummary: !0
|
|
11541
11710
|
}
|
|
11542
|
-
},
|
|
11711
|
+
}, lp = {
|
|
11543
11712
|
auto: ["describe_data", "read"],
|
|
11544
11713
|
conservative: ["describe_data"],
|
|
11545
11714
|
aggressive: ["describe_data", "read"],
|
|
@@ -11550,8 +11719,8 @@ var ip = {
|
|
|
11550
11719
|
"search_data"
|
|
11551
11720
|
]
|
|
11552
11721
|
};
|
|
11553
|
-
function
|
|
11554
|
-
let n =
|
|
11722
|
+
function up(e, t) {
|
|
11723
|
+
let n = cp[e.contextPreset ?? "auto"] ?? {}, r = e.contextOptions === !1 ? {} : e.contextOptions ?? {};
|
|
11555
11724
|
return {
|
|
11556
11725
|
...n,
|
|
11557
11726
|
...r,
|
|
@@ -11561,7 +11730,7 @@ function op(e, t) {
|
|
|
11561
11730
|
}
|
|
11562
11731
|
//#endregion
|
|
11563
11732
|
//#region src/core/sdk/middlewareStack.ts
|
|
11564
|
-
var
|
|
11733
|
+
var dp = {
|
|
11565
11734
|
dataHint: 10,
|
|
11566
11735
|
usageHints: 20,
|
|
11567
11736
|
todos: 30,
|
|
@@ -11578,17 +11747,17 @@ var sp = {
|
|
|
11578
11747
|
subagents: 140,
|
|
11579
11748
|
augmentSystem: 150
|
|
11580
11749
|
};
|
|
11581
|
-
function
|
|
11750
|
+
function fp(e) {
|
|
11582
11751
|
return e.map((e, t) => ({
|
|
11583
11752
|
m: e,
|
|
11584
11753
|
i: t,
|
|
11585
|
-
p:
|
|
11754
|
+
p: dp[e.name] ?? Infinity
|
|
11586
11755
|
})).sort((e, t) => e.p - t.p || e.i - t.i).map((e) => e.m);
|
|
11587
11756
|
}
|
|
11588
11757
|
//#endregion
|
|
11589
11758
|
//#region src/core/tools/fetchDoc.ts
|
|
11590
|
-
var
|
|
11591
|
-
let n = new AbortController(), r = setTimeout(() => n.abort(),
|
|
11759
|
+
var pp = 3e4, mp = [r(async ({ url: e, as: t }) => {
|
|
11760
|
+
let n = new AbortController(), r = setTimeout(() => n.abort(), pp);
|
|
11592
11761
|
try {
|
|
11593
11762
|
let r = await fetch(e, {
|
|
11594
11763
|
method: "GET",
|
|
@@ -11605,7 +11774,7 @@ var lp = 3e4, up = [r(async ({ url: e, as: t }) => {
|
|
|
11605
11774
|
].join("\n");
|
|
11606
11775
|
} catch (t) {
|
|
11607
11776
|
let r = t?.message || String(t);
|
|
11608
|
-
return n.signal.aborted ? `获取失败:请求超时(${
|
|
11777
|
+
return n.signal.aborted ? `获取失败:请求超时(${pp}ms,${e})。` : /Failed to fetch|NetworkError|CORS|blocked/i.test(r) ? `获取失败:可能是 CORS 跨域拦截或网络错误(${r})。浏览器仅能 GET 同源或服务端已配置 CORS 的资源;跨域抓取需后端代理。` : `获取失败:${r}`;
|
|
11609
11778
|
} finally {
|
|
11610
11779
|
clearTimeout(r);
|
|
11611
11780
|
}
|
|
@@ -11616,7 +11785,7 @@ var lp = 3e4, up = [r(async ({ url: e, as: t }) => {
|
|
|
11616
11785
|
url: t.string().describe("要抓取的 URL(同源或已配 CORS)"),
|
|
11617
11786
|
as: t.enum(["text", "markdown"]).optional().describe("返回格式标签,默认 text")
|
|
11618
11787
|
})
|
|
11619
|
-
})],
|
|
11788
|
+
})], hp = [
|
|
11620
11789
|
"id",
|
|
11621
11790
|
"class",
|
|
11622
11791
|
"href",
|
|
@@ -11628,12 +11797,12 @@ var lp = 3e4, up = [r(async ({ url: e, as: t }) => {
|
|
|
11628
11797
|
"aria-label",
|
|
11629
11798
|
"name",
|
|
11630
11799
|
"type"
|
|
11631
|
-
],
|
|
11632
|
-
function
|
|
11800
|
+
], gp = /^(value|srcdoc|formaction|on\w+)$/i, _p = /token|secret|password|passwd|api[-_]?key|auth|cred|csrf|session/i;
|
|
11801
|
+
function vp(e, t) {
|
|
11633
11802
|
if (!e) return null;
|
|
11634
|
-
let { depth: n, attrs: r, includeText: i = !0 } = t, a = r !== void 0, o = r ??
|
|
11803
|
+
let { depth: n, attrs: r, includeText: i = !0 } = t, a = r !== void 0, o = r ?? hp, s = (e) => {
|
|
11635
11804
|
let t = {};
|
|
11636
|
-
for (let n of Array.from(e.attributes)) (a ? o.includes(n.name) : o.includes(n.name) || n.name.startsWith("data-")) && (
|
|
11805
|
+
for (let n of Array.from(e.attributes)) (a ? o.includes(n.name) : o.includes(n.name) || n.name.startsWith("data-")) && (gp.test(n.name) || _p.test(n.name) || (t[n.name] = n.value));
|
|
11637
11806
|
return t;
|
|
11638
11807
|
}, c = (e, t) => {
|
|
11639
11808
|
let n = {
|
|
@@ -11649,10 +11818,10 @@ function mp(e, t) {
|
|
|
11649
11818
|
};
|
|
11650
11819
|
return c(e, n);
|
|
11651
11820
|
}
|
|
11652
|
-
var
|
|
11821
|
+
var yp = r(({ selector: e, depth: t, attrs: n, includeText: r }) => {
|
|
11653
11822
|
let i = e ? document.querySelector(e) : document.body;
|
|
11654
11823
|
if (!i) return `未找到匹配元素:selector="${e}"`;
|
|
11655
|
-
let a =
|
|
11824
|
+
let a = vp(i, {
|
|
11656
11825
|
depth: t ?? 3,
|
|
11657
11826
|
attrs: n,
|
|
11658
11827
|
includeText: r ?? !0
|
|
@@ -11667,22 +11836,22 @@ var hp = r(({ selector: e, depth: t, attrs: n, includeText: r }) => {
|
|
|
11667
11836
|
attrs: t.array(t.string()).optional().describe("属性白名单(传了 = 严格白名单;不传 = 默认常用 + data-*)"),
|
|
11668
11837
|
includeText: t.boolean().optional().describe("是否包含直接文本(默认 true)")
|
|
11669
11838
|
})
|
|
11670
|
-
}),
|
|
11671
|
-
function
|
|
11672
|
-
if (
|
|
11839
|
+
}), bp = [yp], xp = /* @__PURE__ */ "display.position.visibility.opacity.color.background-color.background-image.font-size.font-weight.line-height.text-align.z-index.overflow.width.height.margin.padding.border.border-radius.box-shadow.transform.transition.animation.cursor.pointer-events.flex-direction.gap.max-width.min-height".split("."), Sp = /* @__PURE__ */ new WeakMap(), Cp = !1;
|
|
11840
|
+
function wp() {
|
|
11841
|
+
if (Cp) return;
|
|
11673
11842
|
let e = globalThis.EventTarget;
|
|
11674
11843
|
if (!e?.prototype?.addEventListener) return;
|
|
11675
|
-
|
|
11844
|
+
Cp = !0;
|
|
11676
11845
|
let t = e.prototype.addEventListener, n = e.prototype.removeEventListener;
|
|
11677
11846
|
e.prototype.addEventListener = function(e, ...n) {
|
|
11678
11847
|
try {
|
|
11679
|
-
let t =
|
|
11680
|
-
t || (t = /* @__PURE__ */ new Map(),
|
|
11848
|
+
let t = Sp.get(this);
|
|
11849
|
+
t || (t = /* @__PURE__ */ new Map(), Sp.set(this, t)), t.set(String(e), (t.get(String(e)) ?? 0) + 1);
|
|
11681
11850
|
} catch {}
|
|
11682
11851
|
return t.call(this, e, ...n);
|
|
11683
11852
|
}, e.prototype.removeEventListener = function(e, ...t) {
|
|
11684
11853
|
try {
|
|
11685
|
-
let t =
|
|
11854
|
+
let t = Sp.get(this);
|
|
11686
11855
|
if (t) {
|
|
11687
11856
|
let n = (t.get(String(e)) ?? 1) - 1;
|
|
11688
11857
|
n <= 0 ? t.delete(String(e)) : t.set(String(e), n);
|
|
@@ -11691,10 +11860,10 @@ function bp() {
|
|
|
11691
11860
|
return n.call(this, e, ...t);
|
|
11692
11861
|
};
|
|
11693
11862
|
}
|
|
11694
|
-
function
|
|
11695
|
-
return [...
|
|
11863
|
+
function Tp(e) {
|
|
11864
|
+
return [...Sp.get(e)?.entries() ?? []].filter(([, e]) => e > 0).map(([e]) => e);
|
|
11696
11865
|
}
|
|
11697
|
-
function
|
|
11866
|
+
function Ep(e, t = 12) {
|
|
11698
11867
|
let n = [], r = e, i = 0;
|
|
11699
11868
|
for (; r && r.tagName && i < t;) {
|
|
11700
11869
|
let e = r.tagName.toLowerCase(), t = r.id;
|
|
@@ -11715,7 +11884,7 @@ function Sp(e, t = 12) {
|
|
|
11715
11884
|
}
|
|
11716
11885
|
return n.join(" > ");
|
|
11717
11886
|
}
|
|
11718
|
-
function
|
|
11887
|
+
function Dp(e, t, n = {}) {
|
|
11719
11888
|
if (!e) return {
|
|
11720
11889
|
hits: [],
|
|
11721
11890
|
total: 0,
|
|
@@ -11753,7 +11922,7 @@ function Cp(e, t, n = {}) {
|
|
|
11753
11922
|
n = r.slice(Math.max(0, i - 20), i + t.length + 40);
|
|
11754
11923
|
}
|
|
11755
11924
|
return {
|
|
11756
|
-
selector:
|
|
11925
|
+
selector: Ep(e),
|
|
11757
11926
|
tag: e.tagName.toLowerCase(),
|
|
11758
11927
|
text: n.slice(0, 120)
|
|
11759
11928
|
};
|
|
@@ -11762,7 +11931,7 @@ function Cp(e, t, n = {}) {
|
|
|
11762
11931
|
truncated: a.length > o
|
|
11763
11932
|
};
|
|
11764
11933
|
}
|
|
11765
|
-
var
|
|
11934
|
+
var Op = [
|
|
11766
11935
|
"content",
|
|
11767
11936
|
"display",
|
|
11768
11937
|
"width",
|
|
@@ -11770,14 +11939,14 @@ var wp = [
|
|
|
11770
11939
|
"background-color",
|
|
11771
11940
|
"position"
|
|
11772
11941
|
];
|
|
11773
|
-
function
|
|
11942
|
+
function kp(e, t = {}) {
|
|
11774
11943
|
if (!e) return null;
|
|
11775
11944
|
let n = {
|
|
11776
|
-
selector:
|
|
11945
|
+
selector: Ep(e),
|
|
11777
11946
|
tag: e.tagName.toLowerCase(),
|
|
11778
11947
|
attrs: {}
|
|
11779
11948
|
};
|
|
11780
|
-
for (let t of Array.from(e.attributes)) !(
|
|
11949
|
+
for (let t of Array.from(e.attributes)) !(hp.includes(t.name) || t.name.startsWith("data-")) || gp.test(t.name) || _p.test(t.name) || (n.attrs[t.name] = t.value);
|
|
11781
11950
|
let r = Array.from(e.childNodes).filter((e) => e.nodeType === 3).map((e) => e.textContent || "").join("").trim();
|
|
11782
11951
|
r && (n.text = r.slice(0, 400));
|
|
11783
11952
|
let i = String(e.innerText ?? e.textContent ?? "").trim();
|
|
@@ -11799,7 +11968,7 @@ function Tp(e, t = {}) {
|
|
|
11799
11968
|
if (typeof a != "function") return r;
|
|
11800
11969
|
try {
|
|
11801
11970
|
let i = a(e, n ?? null);
|
|
11802
|
-
for (let e of n ?
|
|
11971
|
+
for (let e of n ? Op : t.styles ?? xp) {
|
|
11803
11972
|
let t = typeof i.getPropertyValue == "function" ? i.getPropertyValue(e) : i[e];
|
|
11804
11973
|
t && (r[e] = String(t).slice(0, 200));
|
|
11805
11974
|
}
|
|
@@ -11817,16 +11986,16 @@ function Tp(e, t = {}) {
|
|
|
11817
11986
|
n.events = {
|
|
11818
11987
|
inline: t,
|
|
11819
11988
|
vue: r ? Object.keys(r).filter((e) => /^on[A-Z]/.test(e)).map((e) => e.slice(2).toLowerCase()) : [],
|
|
11820
|
-
captured:
|
|
11989
|
+
captured: Tp(e)
|
|
11821
11990
|
};
|
|
11822
11991
|
}
|
|
11823
11992
|
return n;
|
|
11824
11993
|
}
|
|
11825
|
-
var
|
|
11826
|
-
|
|
11994
|
+
var Ap = r(({ query: e, mode: t, limit: n, root: r }) => {
|
|
11995
|
+
wp();
|
|
11827
11996
|
let i = r ? document.querySelector(r) : document;
|
|
11828
11997
|
if (r && !i) return `未找到 root 元素:selector="${r}"`;
|
|
11829
|
-
let a =
|
|
11998
|
+
let a = Dp(i, e, {
|
|
11830
11999
|
mode: t,
|
|
11831
12000
|
limit: n
|
|
11832
12001
|
});
|
|
@@ -11843,11 +12012,11 @@ var Ep = r(({ query: e, mode: t, limit: n, root: r }) => {
|
|
|
11843
12012
|
limit: t.number().int().min(1).max(20).optional().describe("返回上限(默认 10)"),
|
|
11844
12013
|
root: t.string().optional().describe("限定搜索范围的容器选择器(默认整页)")
|
|
11845
12014
|
})
|
|
11846
|
-
}),
|
|
11847
|
-
|
|
12015
|
+
}), jp = r(({ selector: e, styles: t, includeHtml: n, htmlLimit: r, includeEvents: i, includeRect: a, pseudo: o }) => {
|
|
12016
|
+
wp();
|
|
11848
12017
|
let s = document.querySelector(e);
|
|
11849
12018
|
if (!s) return `未找到匹配元素:selector="${e}"(可先用 dom_search 定位)`;
|
|
11850
|
-
let c =
|
|
12019
|
+
let c = kp(s, {
|
|
11851
12020
|
styles: t,
|
|
11852
12021
|
includeHtml: n,
|
|
11853
12022
|
htmlLimit: r,
|
|
@@ -11868,7 +12037,7 @@ var Ep = r(({ query: e, mode: t, limit: n, root: r }) => {
|
|
|
11868
12037
|
includeRect: t.boolean().optional().describe("是否含几何位置(默认 true)"),
|
|
11869
12038
|
pseudo: t.boolean().optional().describe("是否含 ::before/::after 伪元素摘要(默认 false)")
|
|
11870
12039
|
})
|
|
11871
|
-
}),
|
|
12040
|
+
}), Mp = {
|
|
11872
12041
|
name: "dom-inspect",
|
|
11873
12042
|
description: "页面 DOM 深度检视工具(dom_search 搜索元素 / dom_info 读内容·计算样式·事件绑定·几何)。定位元素、验证样式落地、排查交互绑定时加载",
|
|
11874
12043
|
getContent: () => [
|
|
@@ -11885,15 +12054,15 @@ var Ep = r(({ query: e, mode: t, limit: n, root: r }) => {
|
|
|
11885
12054
|
"## 排障套路",
|
|
11886
12055
|
"1. dom_search(mode:\"text\", query:按钮文案) 定位 → 2. dom_info(styles:[\"display\",\"background-color\",\"pointer-events\"]) 验证样式/点击性 → 3. 不符则改数据(get_dom 看结构对照)"
|
|
11887
12056
|
].join("\n"),
|
|
11888
|
-
tools: [() => [
|
|
12057
|
+
tools: [() => [Ap, jp]]
|
|
11889
12058
|
};
|
|
11890
12059
|
//#endregion
|
|
11891
12060
|
//#region src/core/tools/envTool.ts
|
|
11892
|
-
function
|
|
12061
|
+
function Np(e) {
|
|
11893
12062
|
let t = e;
|
|
11894
12063
|
return typeof t.tagName == "string" || typeof t.nodeType == "number" && typeof t.nodeName == "string";
|
|
11895
12064
|
}
|
|
11896
|
-
function
|
|
12065
|
+
function Pp(e, t = 3, n = 2e3, r, i) {
|
|
11897
12066
|
if (e == null) return e;
|
|
11898
12067
|
if (typeof e == "string") return e.length > n ? e.slice(0, n) + "…(已截断)" : e;
|
|
11899
12068
|
if (typeof e == "number" || typeof e == "boolean") return e;
|
|
@@ -11902,14 +12071,14 @@ function Ap(e, t = 3, n = 2e3, r, i) {
|
|
|
11902
12071
|
if (typeof e == "bigint") return `${e.toString()}n`;
|
|
11903
12072
|
if (typeof e != "object") return String(e);
|
|
11904
12073
|
let a = e;
|
|
11905
|
-
if (
|
|
12074
|
+
if (Np(a)) {
|
|
11906
12075
|
let e = a;
|
|
11907
12076
|
return e.tagName ? `[Element: <${e.tagName.toLowerCase()}>]` : `[Node: ${e.nodeName}]`;
|
|
11908
12077
|
}
|
|
11909
12078
|
if (r?.has(a)) return "[Circular]";
|
|
11910
12079
|
let o = r ?? /* @__PURE__ */ new WeakSet();
|
|
11911
12080
|
if (o.add(a), Array.isArray(e)) {
|
|
11912
|
-
let r = e.slice(0, 100).map((e) =>
|
|
12081
|
+
let r = e.slice(0, 100).map((e) => Pp(e, t - 1, n, o, i));
|
|
11913
12082
|
return e.length > 100 && r.push(`…(共 ${e.length} 项,已截断)`), r;
|
|
11914
12083
|
}
|
|
11915
12084
|
if (t < 0) return "(对象,已截断)";
|
|
@@ -11922,14 +12091,14 @@ function Ap(e, t = 3, n = 2e3, r, i) {
|
|
|
11922
12091
|
}
|
|
11923
12092
|
try {
|
|
11924
12093
|
let e = a[r];
|
|
11925
|
-
i?.test(r) ? s[r] = "[REDACTED]" : s[r] =
|
|
12094
|
+
i?.test(r) ? s[r] = "[REDACTED]" : s[r] = Pp(e, t - 1, n, o, i);
|
|
11926
12095
|
} catch {
|
|
11927
12096
|
s[r] = "(getter 抛错)";
|
|
11928
12097
|
}
|
|
11929
12098
|
}
|
|
11930
12099
|
return s;
|
|
11931
12100
|
}
|
|
11932
|
-
function
|
|
12101
|
+
function Fp(e) {
|
|
11933
12102
|
let t = e ?? (typeof window < "u" ? window : {}), n = t.navigator, r = t.location, i = t.document;
|
|
11934
12103
|
return {
|
|
11935
12104
|
location: {
|
|
@@ -11962,9 +12131,9 @@ function jp(e) {
|
|
|
11962
12131
|
} : "(无 document)"
|
|
11963
12132
|
};
|
|
11964
12133
|
}
|
|
11965
|
-
var
|
|
12134
|
+
var Ip = /^(localStorage|sessionStorage|cookie|document|frames|frameElement|opener|parent|top|self|window|globalThis|closed|history|navigation|trustedTypes|origin|crossOriginIsolated)$/, Lp = /token|secret|password|passwd|api[-_]?key|auth|cred|csrf|session|ticket/i, Rp = r(({ key: e }) => {
|
|
11966
12135
|
if (e) {
|
|
11967
|
-
if (
|
|
12136
|
+
if (Ip.test(e) || Lp.test(e)) return JSON.stringify({
|
|
11968
12137
|
key: e,
|
|
11969
12138
|
denied: !0,
|
|
11970
12139
|
reason: "安全:该 key 在 denylist(localStorage/sessionStorage/cookie/document 或敏感命名 token/secret/key/auth),不读取防泄漏"
|
|
@@ -11974,18 +12143,18 @@ var Mp = /^(localStorage|sessionStorage|cookie|document|frames|frameElement|open
|
|
|
11974
12143
|
key: e,
|
|
11975
12144
|
exists: t !== void 0,
|
|
11976
12145
|
type: typeof t,
|
|
11977
|
-
value:
|
|
12146
|
+
value: Pp(t, 3, 2e3, void 0, Lp)
|
|
11978
12147
|
}, null, 2);
|
|
11979
12148
|
}
|
|
11980
|
-
return JSON.stringify(
|
|
12149
|
+
return JSON.stringify(Fp(), null, 2);
|
|
11981
12150
|
}, {
|
|
11982
12151
|
name: "inspect_env",
|
|
11983
12152
|
description: "读宿主页面环境信息(排查调试)。不传参返回摘要(URL/origin、浏览器/语言、viewport、title/readyState);传 key 读指定 window 属性值(如 key:\"appConfig\")。只读,大结果自动外存 vfs。",
|
|
11984
12153
|
schema: t.object({ key: t.string().optional().describe("要读取的 window 属性名(如 \"appConfig\"/\"__DEBUG__\");不传 = 返回环境摘要") })
|
|
11985
|
-
}),
|
|
12154
|
+
}), zp = [Rp];
|
|
11986
12155
|
//#endregion
|
|
11987
12156
|
//#region src/core/harness/budget.ts
|
|
11988
|
-
function
|
|
12157
|
+
function Bp(e, t, n) {
|
|
11989
12158
|
let r = 0;
|
|
11990
12159
|
return {
|
|
11991
12160
|
name: "budget",
|
|
@@ -12010,12 +12179,12 @@ function Ip(e, t, n) {
|
|
|
12010
12179
|
}
|
|
12011
12180
|
//#endregion
|
|
12012
12181
|
//#region src/core/sdk/actions.ts
|
|
12013
|
-
var
|
|
12014
|
-
function
|
|
12182
|
+
var Vp = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
12183
|
+
function Hp(e) {
|
|
12015
12184
|
let n = [];
|
|
12016
12185
|
for (let [i, a] of Object.entries(e)) {
|
|
12017
|
-
if (!
|
|
12018
|
-
console.warn(`[page-agent-sdk][actions] 动作名 "${i}" 非法(须匹配 ${
|
|
12186
|
+
if (!Vp.test(i)) {
|
|
12187
|
+
console.warn(`[page-agent-sdk][actions] 动作名 "${i}" 非法(须匹配 ${Vp}),已跳过`);
|
|
12019
12188
|
continue;
|
|
12020
12189
|
}
|
|
12021
12190
|
let e = a.description || `宿主动作 ${i}`;
|
|
@@ -12035,9 +12204,9 @@ function Rp(e) {
|
|
|
12035
12204
|
}
|
|
12036
12205
|
return n;
|
|
12037
12206
|
}
|
|
12038
|
-
function
|
|
12207
|
+
function Up(e) {
|
|
12039
12208
|
let t = {};
|
|
12040
|
-
for (let [n, r] of Object.entries(e))
|
|
12209
|
+
for (let [n, r] of Object.entries(e)) Vp.test(n) && (t[n] = {
|
|
12041
12210
|
description: r.description,
|
|
12042
12211
|
hasParams: !!r.params
|
|
12043
12212
|
});
|
|
@@ -12045,12 +12214,12 @@ function zp(e) {
|
|
|
12045
12214
|
}
|
|
12046
12215
|
//#endregion
|
|
12047
12216
|
//#region src/core/toolsets.ts
|
|
12048
|
-
var
|
|
12049
|
-
function
|
|
12217
|
+
var Wp = mp, Gp = bp;
|
|
12218
|
+
function Kp(e, t) {
|
|
12050
12219
|
return ad(e, t);
|
|
12051
12220
|
}
|
|
12052
|
-
function
|
|
12053
|
-
let a =
|
|
12221
|
+
function qp(e, t, n, r, i) {
|
|
12222
|
+
let a = op(e);
|
|
12054
12223
|
return [
|
|
12055
12224
|
...a.dataOps ? t : [],
|
|
12056
12225
|
...a.fetch ? n : [],
|
|
@@ -12060,7 +12229,7 @@ function Up(e, t, n, r, i) {
|
|
|
12060
12229
|
}
|
|
12061
12230
|
//#endregion
|
|
12062
12231
|
//#region src/core/sdk/toolRegistry.ts
|
|
12063
|
-
function
|
|
12232
|
+
function Jp(e) {
|
|
12064
12233
|
let t = /* @__PURE__ */ new Map(), n = [];
|
|
12065
12234
|
for (let r of e) for (let e of r.tools) {
|
|
12066
12235
|
let i = t.get(e.name);
|
|
@@ -12080,15 +12249,15 @@ function Wp(e) {
|
|
|
12080
12249
|
}
|
|
12081
12250
|
//#endregion
|
|
12082
12251
|
//#region src/core/harness/usageHints.ts
|
|
12083
|
-
var
|
|
12084
|
-
function
|
|
12085
|
-
let r =
|
|
12252
|
+
var Yp = .7;
|
|
12253
|
+
function Xp(e, t, n) {
|
|
12254
|
+
let r = op(e);
|
|
12086
12255
|
return {
|
|
12087
12256
|
name: "usageHints",
|
|
12088
12257
|
augmentPrompt: (i) => {
|
|
12089
12258
|
let a = [];
|
|
12090
|
-
if (r.planning && (a.push("【自适应规划】按任务复杂度决定是否先规划,不要对简单任务过度编排:"), a.push(" · 简单/明确任务(改单字段、调样式、查值)→ 直接 read/write 执行,不必 write_todos。"), a.push(" · 复杂任务(多步、大改、有歧义、不可逆)→ 先 write_todos 拆解,首个任务标 in_progress,逐项推进。"), a.push(" · 执行中发现步骤要改/补/细分 → 用 update_todo({id, content?, status?}) 按 id 增量改单项,不必重传整个清单。"), a.push(" · update_todo 标 completed 时附 evidence: 本次实际写入的 jsonPath(如 \"components.2\"),供收口对账;工作经委派子 agent 完成等无主写路径时,如实写明完成方式(勿编造路径)。"), e?.humanConfirm ? a.push(" · 规划出多步方案若需用户拍板 → 先 request_human_confirmation 给方案选项,确认后再执行。") : a.push(" · 规划出多步方案若需用户拍板 → 以文字列出方案选项等用户回复,勿自行拍板。"), a.push(" · 计划修订有次数上限(maxPlanRevisions,默认 5,只计 write_todos 调用、调研轮不计):勿反复改计划而不执行,规划充分后即开始 write 落地。")), t && (a.push("改主数据前先 read({jsonPath}) 读其当前真实值(返回末尾 hash=xxx 为乐观锁标识),基于真实值改,不要凭记忆。写入是否被自动校验由集成方 conflictWatchFields 声明决定:若已声明且主数据在你 read 之后被外部改过,会触发冲突——集成方若开启人工介入,工具会挂起等用户决定(保留外部/强制覆盖/回退),你应等待工具返回后按结果继续(保留外部→重新 read 再改;强制覆盖→已写入,继续;回退→已回退到历史快照,基于回退值重写);未开启人工介入时返回 VERSION_CONFLICT 不写入,重新 read 拿最新值再改。"), a.push("
|
|
12091
|
-
let t = e.subagents.filter((e) => (e.temperature ?? 0) >=
|
|
12259
|
+
if (r.planning && (a.push("【自适应规划】按任务复杂度决定是否先规划,不要对简单任务过度编排:"), a.push(" · 简单/明确任务(改单字段、调样式、查值)→ 直接 read/write 执行,不必 write_todos。"), a.push(" · 复杂任务(多步、大改、有歧义、不可逆)→ 先 write_todos 拆解,首个任务标 in_progress,逐项推进。"), a.push(" · 执行中发现步骤要改/补/细分 → 用 update_todo({id, content?, status?}) 按 id 增量改单项,不必重传整个清单。"), a.push(" · update_todo 标 completed 时附 evidence: 本次实际写入的 jsonPath(如 \"components.2\"),供收口对账;工作经委派子 agent 完成等无主写路径时,如实写明完成方式(勿编造路径)。"), e?.humanConfirm ? a.push(" · 规划出多步方案若需用户拍板 → 先 request_human_confirmation 给方案选项,确认后再执行。") : a.push(" · 规划出多步方案若需用户拍板 → 以文字列出方案选项等用户回复,勿自行拍板。"), a.push(" · 计划修订有次数上限(maxPlanRevisions,默认 5,只计 write_todos 调用、调研轮不计):勿反复改计划而不执行,规划充分后即开始 write 落地。")), t && (a.push("改主数据前先 read({jsonPath}) 读其当前真实值(返回末尾 hash=xxx 为乐观锁标识),基于真实值改,不要凭记忆。写入是否被自动校验由集成方 conflictWatchFields 声明决定:若已声明且主数据在你 read 之后被外部改过,会触发冲突——集成方若开启人工介入,工具会挂起等用户决定(保留外部/强制覆盖/回退),你应等待工具返回后按结果继续(保留外部→重新 read 再改;强制覆盖→已写入,继续;回退→已回退到历史快照,基于回退值重写);未开启人工介入时返回 VERSION_CONFLICT 不写入,重新 read 拿最新值再改。"), a.push("读类分流:取值/结构 → read(多路径用 jsonPaths);字段约束 → schema_data;条件筛选定位 → query_data(多条一次用 queries);名字模糊找 → search_data;整体说明 → read 不传路径(= describe_data)。"), a.push("修改大对象/数组优先用 write 增量改({patch:{op,jsonPath,value}} 只发改动部分),避免 write({value}) 整体重传被 max_tokens 截断导致 JSON 不完整、校验失败。"), a.push("修改主数据出错时可用 restore_data 回退最近一次。"), a.push("在大数组里按条件筛选元素用 query_data(JSONPath,如 $.components[?(@.type==\"card\" && @.price<100)]),返回匹配元素的 path/index;定位后再 write({patch}) 改。"), a.push("找名字记不清的元素用 search_data(支持 substring/regex/fuzzy 模糊搜索)。"), a.push("需要过滤/映射/聚合/批量重写大数组时用 eval_script(沙箱脚本,入参 data);只读探查用 mode=query,批量重写用 mode=transform(返回值经校验后落地)。"), a.push("读大数组用 read({jsonPath,offset,limit}) 分页(返回 hasMore=true 时 offset+=limit 续读);一次读多个不相关子路径用 read({jsonPaths});复杂改动先 write({...,dryRun:true}) 预检不落盘。"), a.push("【省轮次·批量】≥2 个路径一律 read({jsonPaths:[...]}) 一次取回(路径互不相关也合批,禁止连续单路径 read);多条件筛选用 query_data({queries:[expr1,expr2,…]}) 一次取回(2-10 条);改多处用 write({patches:[{op:\"set\",jsonPath,value},...]}) 一次提交多改动(原子任一失败回滚),勿逐个 write 烧轮次预算。"), a.push("读到 <subtree Nkb keys:[…] #指纹> 或 <code Nkb> 占位 = 该子树键名/体积可见但内容未见:写入前先窄读全文(read({jsonPath:\"该子树路径\"}),结果根豁免返全文)或 set_focus 聚焦该区域;勿凭键名印象猜路径/猜值直写(直接写占位子树会被拦下要求先窄读)。"), a.push("对比当前与历史快照(或一段 JSON against)的差异用 diff_data({snapshotId?,against?})(返回结构化 path→from/to,verify 自纠/操作审计/\"刚才改了啥\");只读查历史快照值用 history_data({id?,jsonPath?})。")), r.subagent && a.push("独立子任务可 spawn_agent 委派(过程隔离,不占主上下文):默认只读,需要子 agent 写数据时传 writablePaths(路径前缀白名单,越界 PATH_OUT_OF_SCOPE)。多个独立子任务可 spawn_agents 并行委派(各子互不通信,结论由你汇总;并行委派不可授写权限,写操作由你收尾执行)。"), r.inspectEnv && a.push("排查页面环境(当前 URL/浏览器/视口/集成方调试变量)用 inspect_env——不传参返回环境摘要(location/navigator/viewport/document),传 key 读特定 window 属性(如 inspect_env({key:\"appConfig\"}) 读 window.appConfig)。改完数据看渲染、定位\"为何没生效\"时用它(只读,不改数据)。"), r.domInspect && a.push("改完数据想确认渲染是否生效(或定位元素/辅助 UI 设计问答)用 get_dom({selector?,depth?}) 读渲染后 DOM(结构化返回 tag/attrs/text/children,depth 控制深度防爆炸,只读)。配合宿主 actions(save_draft/publish 等)形成\"改数据→get_dom 看渲染→触发动作\"闭环。"), r.draftWrite && (a.push("生成超大 JSON(如 50+ 组件页面,单次 write 受 max_tokens 限制装不下)用 draft_write 分块构建 → draft_commit 原子提交:draft_write({draftId, chunk, mode}) mode:\"start\" 新建/\"append\" 追加(拼 JSON 片段到 drafts 池);累积完 draft_commit({draftId}) 合并 + schema 校验 + 写主数据(失败草稿保留可修后重试,成功自动清草稿)。小改仍用 write patch,只在大 JSON 从零生成时用 draft。"), a.push("⚠️ 大 JSON 分块构建是典型多轮工具调用(draft_write×N + draft_commit + read 确认 + 调研 read/query),默认 maxToolRounds=30(3.43 起;轮次预算吃紧时 system 会注入预算提示段,按提示优先收口);目标组件数很大时集成方仍可在 createChatSdk 显式上调 maxToolRounds(按 N+10 估算)。draft_commit 提交同样走乐观锁(改前 read 拿 hash,bind 被改过会触发冲突介入,不静默覆盖)。")), r.focus && (a.push("【上下文聚焦】判断任务范围,用 set_focus/add_focus/remove_focus/clear_focus 自动收敛工作范围:"), a.push(" · 局部任务(只改某一组件/区域,如「调导航栏」「改 components.3 样式」)→ 先 read 定位 jsonPath,再 set_focus({path:\"该子树路径\"}) 聚焦;聚焦后每轮只看该子树结构,写其他位置会被 PATH_DENIED 拒绝。"), a.push(" · 多个相关组件(如「同时改导航栏和页脚」)→ set_focus 聚焦首个后用 add_focus({path}) 追加其余;聚焦后可写任一焦点子树,越界仍被拒;移除单个用 remove_focus({path})。"), a.push(" · 全局任务(多处/整体结构,如「重排所有组件」「换主题」)→ 不要聚焦,保持全量视野直接写。"), a.push(" · 完成局部精修、要转向其他区域或做整体改动 → 调 clear_focus 退出聚焦(清空全部焦点),恢复全部读写权限。"), a.push(" · set_focus/add_focus 的 path 必须在 schema 内(类型校验);不确定路径先 read/describe_data 查。")), e?.subagents?.length) {
|
|
12260
|
+
let t = e.subagents.filter((e) => (e.temperature ?? 0) >= Yp || /规划|创意|设计|方案|brainstorm|plan/i.test(e.description)), n = e.subagents.filter((e) => (e.temperature ?? 0) < Yp && /反思|审查|挑刺|校验|review|critique|reflect/i.test(e.description));
|
|
12092
12261
|
a.push("【规划-反思-执行·路由】按任务性质选模式,不要对简单任务过度编排:"), t.length && (a.push(` · 创作/设计/开放性需求(如"设计主题风格""换个感觉")→ 先调 ${t.map((e) => "use_" + e.id).join("/")} 出 2-3 套方案(高温创意),`), a.push(e?.humanConfirm ? " 不要自己拍板;拿到方案后,若需用户拍板用 request_human_confirmation 弹选项。" : " 不要自己拍板;拿到方案后以文字列出选项等用户回复(humanConfirm 未开,勿调不存在工具)。")), n.length && a.push(` · 严谨/易错/校验类 → 可先调 ${n.map((e) => "use_" + e.id).join("/")} 反思挑刺(低温审查),据反馈修订。`), a.push(" · 方案定稿后,由你(主 agent)用 write 落地成 JSON(低温度执行 + schema 校验 + 写前确认)。"), a.push(" · 简单/明确任务(如\"标题改红色\")直接执行,不必走规划-反思。");
|
|
12093
12262
|
}
|
|
12094
12263
|
e?.humanConfirm && (a.push("【人工确认·必读】以下情形必须先调 request_human_confirmation 征询用户、拿到答复后再继续,不要自行拍板:"), a.push(" 1) 用户让你「给方案/列选项/我来选/挑一个」时:把每个方案作为一个 option,调 request_human_confirmation(question=简述, options=[方案A,方案B,...], recommendation=你推荐的)。不要只回文字罗列方案让用户自己回复——要用工具把选项做成可点选按钮。"), a.push(" 2) 需求有歧义/不确定时:调工具问清楚(options 不传则用户答同意或拒绝)。"), a.push(" 3) 即将执行高风险不可逆操作(删除/覆盖/批量改动)前:调工具确认。"), a.push(" 4) 规划出多步方案需用户确认时:把整个方案(或关键分步)作为 option 调 request_human_confirmation,确认后再执行。"), a.push("用户在选项里选了哪个,就按那个方案继续;选「拒绝」则停止并询问如何调整。"));
|
|
@@ -12108,7 +12277,7 @@ function Kp(e, t, n) {
|
|
|
12108
12277
|
}
|
|
12109
12278
|
//#endregion
|
|
12110
12279
|
//#region src/core/harness/resourcesPin.ts
|
|
12111
|
-
function
|
|
12280
|
+
function Zp(e) {
|
|
12112
12281
|
return {
|
|
12113
12282
|
name: "resourcesPin",
|
|
12114
12283
|
augmentPrompt: () => {
|
|
@@ -12122,66 +12291,66 @@ function qp(e) {
|
|
|
12122
12291
|
}
|
|
12123
12292
|
//#endregion
|
|
12124
12293
|
//#region src/core/backends/skillStore.ts
|
|
12125
|
-
var
|
|
12126
|
-
function
|
|
12127
|
-
return `${
|
|
12294
|
+
var Qp = "v:1::skill-store", $p = "chat-sdk";
|
|
12295
|
+
function em(e, t, n) {
|
|
12296
|
+
return `${Qp}::${e}::${t}::${n}`;
|
|
12128
12297
|
}
|
|
12129
|
-
function
|
|
12130
|
-
return `${
|
|
12298
|
+
function tm(e, t) {
|
|
12299
|
+
return `${Qp}::${e}::${t}::`;
|
|
12131
12300
|
}
|
|
12132
|
-
function
|
|
12133
|
-
let t = e.dbName ??
|
|
12301
|
+
function nm(e = {}) {
|
|
12302
|
+
let t = e.dbName ?? $p, n = e.backend ?? "indexed", r = e.id ?? "", i, a, o = new Promise((e) => {
|
|
12134
12303
|
a = e;
|
|
12135
12304
|
});
|
|
12136
12305
|
return (async () => {
|
|
12137
12306
|
try {
|
|
12138
12307
|
if (n === "indexed") {
|
|
12139
12308
|
if (typeof indexedDB > "u") throw Error("indexedDB 不可用");
|
|
12140
|
-
i = await
|
|
12309
|
+
i = await Zf(t), a(!0);
|
|
12141
12310
|
} else if (n === "session") {
|
|
12142
12311
|
if (typeof sessionStorage > "u") throw Error("sessionStorage 不可用");
|
|
12143
|
-
i =
|
|
12312
|
+
i = Qf(sessionStorage), a(!0);
|
|
12144
12313
|
} else if (n === "local") {
|
|
12145
12314
|
if (typeof localStorage > "u") throw Error("localStorage 不可用");
|
|
12146
|
-
i =
|
|
12147
|
-
} else i =
|
|
12315
|
+
i = Qf(localStorage), a(!0);
|
|
12316
|
+
} else i = Xf(), a(!1);
|
|
12148
12317
|
} catch {
|
|
12149
|
-
i =
|
|
12318
|
+
i = Xf(), a(!1);
|
|
12150
12319
|
}
|
|
12151
12320
|
})(), {
|
|
12152
12321
|
ready: o,
|
|
12153
12322
|
async list() {
|
|
12154
12323
|
let e = [];
|
|
12155
|
-
return await i.scan(
|
|
12324
|
+
return await i.scan(tm(t, r), (t, n) => {
|
|
12156
12325
|
e.push(n);
|
|
12157
12326
|
}), e;
|
|
12158
12327
|
},
|
|
12159
12328
|
async get(e) {
|
|
12160
|
-
return await i.get(
|
|
12329
|
+
return await i.get(em(t, r, e));
|
|
12161
12330
|
},
|
|
12162
12331
|
async put(e) {
|
|
12163
12332
|
try {
|
|
12164
|
-
await i.set(
|
|
12333
|
+
await i.set(em(t, r, e.name), e);
|
|
12165
12334
|
} catch (e) {
|
|
12166
|
-
|
|
12335
|
+
Hf(e) && (i = Xf());
|
|
12167
12336
|
}
|
|
12168
12337
|
},
|
|
12169
12338
|
async remove(e) {
|
|
12170
|
-
let n =
|
|
12339
|
+
let n = em(t, r, e);
|
|
12171
12340
|
return await i.get(n) == null ? !1 : (await i.del(n), !0);
|
|
12172
12341
|
},
|
|
12173
12342
|
async clear() {
|
|
12174
|
-
await i.clearPrefix(
|
|
12343
|
+
await i.clearPrefix(tm(t, r));
|
|
12175
12344
|
},
|
|
12176
12345
|
dispose() {}
|
|
12177
12346
|
};
|
|
12178
12347
|
}
|
|
12179
|
-
var
|
|
12180
|
-
function
|
|
12348
|
+
var rm = /large_results\/[^\s"'`),]+\.txt/g;
|
|
12349
|
+
function im(e) {
|
|
12181
12350
|
let t = /* @__PURE__ */ new Set(), n = (e) => {
|
|
12182
12351
|
if (typeof e != "string" || !e) return;
|
|
12183
12352
|
let n;
|
|
12184
|
-
for (
|
|
12353
|
+
for (rm.lastIndex = 0; (n = rm.exec(e)) !== null;) t.add(n[0]);
|
|
12185
12354
|
};
|
|
12186
12355
|
for (let r of e) {
|
|
12187
12356
|
if (n(r.content), r.steps) for (let e of r.steps) n(e.result);
|
|
@@ -12189,14 +12358,14 @@ function em(e) {
|
|
|
12189
12358
|
}
|
|
12190
12359
|
return t;
|
|
12191
12360
|
}
|
|
12192
|
-
function
|
|
12361
|
+
function am(e, t) {
|
|
12193
12362
|
let n = [];
|
|
12194
12363
|
for (let r of Object.keys(e)) r.startsWith("large_results/") && !t.has(r) && n.push(r);
|
|
12195
12364
|
return n;
|
|
12196
12365
|
}
|
|
12197
12366
|
//#endregion
|
|
12198
12367
|
//#region src/core/utils/serialRunner.ts
|
|
12199
|
-
function
|
|
12368
|
+
function om() {
|
|
12200
12369
|
let e = Promise.resolve();
|
|
12201
12370
|
return function(t) {
|
|
12202
12371
|
let n = e.then(t, t);
|
|
@@ -12205,24 +12374,24 @@ function nm() {
|
|
|
12205
12374
|
}
|
|
12206
12375
|
//#endregion
|
|
12207
12376
|
//#region src/core/sdk/createChatSdk.ts
|
|
12208
|
-
var
|
|
12209
|
-
function
|
|
12377
|
+
var sm = 30, cm = /* @__PURE__ */ new Map();
|
|
12378
|
+
function lm(e, t) {
|
|
12210
12379
|
return e === "restore_data" ? "restore" : e === "write" ? t?.dryRun ? null : t?.del ? "delete" : t?.patch || t?.patches ? "edit" : "set" : e === "eval_script" ? t?.mode === "transform" ? "edit" : null : e === "draft_commit" || e === "resource_update" ? "edit" : null;
|
|
12211
12380
|
}
|
|
12212
|
-
function
|
|
12381
|
+
function um(e) {
|
|
12213
12382
|
return e === "spawn_agent" || e === "spawn_agents" || e.startsWith("use_");
|
|
12214
12383
|
}
|
|
12215
|
-
function
|
|
12384
|
+
function dm(e, t, n, r) {
|
|
12216
12385
|
let i = 0;
|
|
12217
12386
|
return {
|
|
12218
12387
|
name: "sdk-events",
|
|
12219
12388
|
wrapToolCall: async (t, r) => {
|
|
12220
|
-
let i = await r(t), a =
|
|
12389
|
+
let i = await r(t), a = lm(t.name, t.args), o = typeof i.content == "string" && i.content.startsWith("ERROR:");
|
|
12221
12390
|
return a && !o ? e({
|
|
12222
12391
|
type: "data_change",
|
|
12223
12392
|
operation: a,
|
|
12224
12393
|
value: n()?.bind
|
|
12225
|
-
}) : i.status === "done" && !o &&
|
|
12394
|
+
}) : i.status === "done" && !o && um(t.name) && e({
|
|
12226
12395
|
type: "data_change",
|
|
12227
12396
|
operation: "edit",
|
|
12228
12397
|
value: n()?.bind
|
|
@@ -12250,7 +12419,7 @@ function sm(e, t, n, r) {
|
|
|
12250
12419
|
}
|
|
12251
12420
|
};
|
|
12252
12421
|
}
|
|
12253
|
-
function
|
|
12422
|
+
function fm(e, t, n, r, i) {
|
|
12254
12423
|
if (!n) return i && console.warn(`[page-agent-sdk][focus] capabilities.focus 关闭,${t} 忽略`), {
|
|
12255
12424
|
ok: !1,
|
|
12256
12425
|
error: "capabilities.focus 关闭"
|
|
@@ -12268,12 +12437,12 @@ function cm(e, t, n, r, i) {
|
|
|
12268
12437
|
error: "当前无主数据 schema,无法聚焦"
|
|
12269
12438
|
});
|
|
12270
12439
|
}
|
|
12271
|
-
function
|
|
12440
|
+
function pm(e, n) {
|
|
12272
12441
|
let i = {
|
|
12273
12442
|
prompt_tokens: 0,
|
|
12274
12443
|
completion_tokens: 0,
|
|
12275
12444
|
total_tokens: 0
|
|
12276
|
-
}, a =
|
|
12445
|
+
}, a = Of(() => J, () => e.conflictPolicy ?? "ask"), o = /* @__PURE__ */ Lt(0), s = rp(e.storage);
|
|
12277
12446
|
s && (e.debug && s.onEvent((e) => console.log("[page-agent-sdk][storage]", e)), s.onEvent((e) => {
|
|
12278
12447
|
e.type === "quota" && X.agent?.debugLogs?.value?.push({
|
|
12279
12448
|
timestamp: Date.now(),
|
|
@@ -12285,9 +12454,9 @@ function lm(e, n) {
|
|
|
12285
12454
|
}
|
|
12286
12455
|
});
|
|
12287
12456
|
}));
|
|
12288
|
-
let { modelCaps: c, summaryLlmInvoke: l, titleLlmInvoke: u, compressDecisionInvoke: d } =
|
|
12457
|
+
let { modelCaps: c, summaryLlmInvoke: l, titleLlmInvoke: u, compressDecisionInvoke: d } = Df(e), f = c;
|
|
12289
12458
|
if (e.debug && console.log("[page-agent-sdk][modelCaps]", f), f.contextWindow < 2e5) throw Error(`[page-agent-sdk] 模型上下文窗口 ${f.contextWindow} 小于最小支持 ${p}(需 ≥200K 窗口模型,如 GLM-5.2/Claude/Kimi/Qwen-1M/DeepSeek-v4)`);
|
|
12290
|
-
let h =
|
|
12459
|
+
let h = up(e, f.contextWindow), g = h.enableLLMSummary !== !1, _ = (e.contextOptions && e.contextOptions.preserveLastToolResults) ?? lp[e.contextPreset ?? "auto"], v = new Set(_), y = e.llm;
|
|
12291
12460
|
e.debug && !l && console.warn("[page-agent-sdk][summarization] 未构造 llmInvoke(apiKey 缺失?),摘要回退零成本索引摘要");
|
|
12292
12461
|
let b = /* @__PURE__ */ Et([]), x = mu(e.vfs?.initialFiles, {
|
|
12293
12462
|
persist: s ? { save: (e) => {
|
|
@@ -12343,8 +12512,8 @@ function lm(e, n) {
|
|
|
12343
12512
|
let ie = { current: null }, M = e.data ? {
|
|
12344
12513
|
...e.data,
|
|
12345
12514
|
description: e.data.description ?? "主数据对象"
|
|
12346
|
-
} : void 0, N = !(e.subagents ?? []).some((e) => e._codeAsset) &&
|
|
12347
|
-
N && M?.schema && Xo(M.schema).length && (P = [...e.subagents ?? [],
|
|
12515
|
+
} : void 0, N = !(e.subagents ?? []).some((e) => e._codeAsset) && op(e.capabilities).subagent, P = e.subagents;
|
|
12516
|
+
N && M?.schema && Xo(M.schema).length && (P = [...e.subagents ?? [], ff()], console.info("[page-agent-sdk] 检测到 schema 含代码组件数组(如 components[].code),已自动装配 HTML 代码子 agent(委派编排 + vfs 工作副本 + 格式校验 + 增量 commit)。需定制(codeField/formatCheck/craftNotes 等)请显式传 createHtmlSubagent(...)"));
|
|
12348
12517
|
let ae = (P ?? []).filter((e) => !!e._codeAsset), F = /* @__PURE__ */ new Set();
|
|
12349
12518
|
for (let e of ae) {
|
|
12350
12519
|
if (e._codeAsset.writablePaths.length) continue;
|
|
@@ -12360,7 +12529,7 @@ function lm(e, n) {
|
|
|
12360
12529
|
getTodos: () => ie.current?.getState?.()?.todos ?? [],
|
|
12361
12530
|
messages: b,
|
|
12362
12531
|
maxCheckpoints: le && typeof le == "object" ? le.maxCheckpoints : void 0
|
|
12363
|
-
}) : null, de = !le || typeof le != "object" || le.auto !== !1, z =
|
|
12532
|
+
}) : null, de = !le || typeof le != "object" || le.auto !== !1, z = op(e.capabilities), B = z.dataOps, fe = z.draftWrite, pe = z.automation, me = uu({
|
|
12364
12533
|
...e,
|
|
12365
12534
|
locale: e.i18n?.locale
|
|
12366
12535
|
});
|
|
@@ -12377,7 +12546,7 @@ function lm(e, n) {
|
|
|
12377
12546
|
vfsStore: fe || M?.resources?.length ? x : void 0,
|
|
12378
12547
|
...se.length ? { pgIdPaths: se } : {},
|
|
12379
12548
|
...ce.length ? { largeTextPaths: ce } : {}
|
|
12380
|
-
}) : [], he = B ? V : [], H = B && M ? V.controller ?? null : null, ge = B && M?.resources?.length && H?.getResourcesSnapshot ?
|
|
12549
|
+
}) : [], he = B ? V : [], H = B && M ? V.controller ?? null : null, ge = B && M?.resources?.length && H?.getResourcesSnapshot ? Zp({ getResourcesSnapshot: () => H?.getResourcesSnapshot?.() ?? [] }) : void 0, U = () => H?.get() ?? M, _e = new Set(V.map((e) => e.name)), ve = e.conflictWatchFields ?? [], ye = !ve.includes("*") && ve.length ? new Set(ve) : void 0, be = H ? Rd({
|
|
12381
12550
|
getBind: () => U()?.bind,
|
|
12382
12551
|
recomputeAll: () => H.recomputeAllBaselines?.(),
|
|
12383
12552
|
hasBaselines: () => H.hasBaselines?.() ?? !1,
|
|
@@ -12394,15 +12563,15 @@ function lm(e, n) {
|
|
|
12394
12563
|
}
|
|
12395
12564
|
}), Vt(n));
|
|
12396
12565
|
}
|
|
12397
|
-
}) : void 0, W = /* @__PURE__ */ new Map(), xe =
|
|
12398
|
-
...
|
|
12399
|
-
|
|
12400
|
-
|
|
12401
|
-
] :
|
|
12566
|
+
}) : void 0, W = /* @__PURE__ */ new Map(), xe = qp(z, he, mp, z.domInspect && !z.skills ? [
|
|
12567
|
+
...bp,
|
|
12568
|
+
Ap,
|
|
12569
|
+
jp
|
|
12570
|
+
] : bp, zp);
|
|
12402
12571
|
xe.forEach((e) => W.set(e.name, "builtin"));
|
|
12403
12572
|
let Se = [...e.tools || []];
|
|
12404
12573
|
Se.forEach((e) => W.set(e.name, "user"));
|
|
12405
|
-
let Ce =
|
|
12574
|
+
let Ce = Hp(e.actions ?? {});
|
|
12406
12575
|
Ce.forEach((e) => W.set(e.name, "action"));
|
|
12407
12576
|
let we = [], Te = !1, Ee = e.humanConfirm !== !1 && (!e.approval || e.approval.humanConfirmTool !== !1), De = Ee ? Tl() : null;
|
|
12408
12577
|
De && W.set(Cl, "builtin");
|
|
@@ -12475,7 +12644,7 @@ function lm(e, n) {
|
|
|
12475
12644
|
let G = [], Fe = /* @__PURE__ */ new Map(), K = Ie();
|
|
12476
12645
|
function Ie() {
|
|
12477
12646
|
ki(Se), ki(Ce), ki(G);
|
|
12478
|
-
let { tools: e, collisions: t } =
|
|
12647
|
+
let { tools: e, collisions: t } = Jp([
|
|
12479
12648
|
{
|
|
12480
12649
|
label: "builtin",
|
|
12481
12650
|
tools: xe
|
|
@@ -12602,7 +12771,7 @@ function lm(e, n) {
|
|
|
12602
12771
|
resolveComponents: (e) => Td(e, yd(U()?.bind, se))
|
|
12603
12772
|
} : {}
|
|
12604
12773
|
}) : void 0, ct = st ? st.controller : null, lt = (t) => {
|
|
12605
|
-
let n = t.thinkingMode ?? e.subagent?.thinkingMode, r = n ?
|
|
12774
|
+
let n = t.thinkingMode ?? e.subagent?.thinkingMode, r = n ? vf(t.llm ?? e.llm) ? "instance-noop" : "applied" : "inherited";
|
|
12606
12775
|
return {
|
|
12607
12776
|
...t,
|
|
12608
12777
|
...n ? { thinkingMode: n } : {},
|
|
@@ -12623,8 +12792,8 @@ function lm(e, n) {
|
|
|
12623
12792
|
} : void 0
|
|
12624
12793
|
}) : void 0, pt = Xe ? tu({
|
|
12625
12794
|
contextWindow: f.contextWindow,
|
|
12626
|
-
thresholdRatio:
|
|
12627
|
-
}) : void 0, mt =
|
|
12795
|
+
thresholdRatio: up(e, f.contextWindow).summaryThresholdRatio
|
|
12796
|
+
}) : void 0, mt = Xp({
|
|
12628
12797
|
...z,
|
|
12629
12798
|
humanConfirm: Ee,
|
|
12630
12799
|
subagents: oe?.map(lt)
|
|
@@ -12644,7 +12813,7 @@ function lm(e, n) {
|
|
|
12644
12813
|
return;
|
|
12645
12814
|
}
|
|
12646
12815
|
}
|
|
12647
|
-
} : null, _t =
|
|
12816
|
+
} : null, _t = sp(e.onEvent), J = _t.emit, vt, yt = [], bt = (e) => ({
|
|
12648
12817
|
name: e.name,
|
|
12649
12818
|
description: e.description,
|
|
12650
12819
|
content: typeof e.getContent == "function" ? e.getContent() : ""
|
|
@@ -12652,7 +12821,7 @@ function lm(e, n) {
|
|
|
12652
12821
|
name: e.name,
|
|
12653
12822
|
description: e.description,
|
|
12654
12823
|
getContent: () => e.content
|
|
12655
|
-
}), St = e.skillStorage === !1 ? null :
|
|
12824
|
+
}), St = e.skillStorage === !1 ? null : nm({
|
|
12656
12825
|
...typeof e.skillStorage == "object" ? e.skillStorage : {},
|
|
12657
12826
|
id: e.skillStorage && typeof e.skillStorage == "object" && e.skillStorage.id ? e.skillStorage.id : `agent::${n}`
|
|
12658
12827
|
}), Ct = () => {
|
|
@@ -12684,14 +12853,14 @@ function lm(e, n) {
|
|
|
12684
12853
|
decideInvoke: d,
|
|
12685
12854
|
getSnapshot: () => pt?.getSnapshot()
|
|
12686
12855
|
} : {}
|
|
12687
|
-
}) : void 0, Dt =
|
|
12856
|
+
}) : void 0, Dt = fp([
|
|
12688
12857
|
...ht ? [ht] : [],
|
|
12689
12858
|
mt,
|
|
12690
12859
|
...Re ? [w] : [],
|
|
12691
12860
|
T,
|
|
12692
12861
|
E,
|
|
12693
12862
|
...Le ? [C] : [],
|
|
12694
|
-
...Be ? [vt = _c([...z.domInspect && !(e.skills || []).some((e) => e.name ===
|
|
12863
|
+
...Be ? [vt = _c([...z.domInspect && !(e.skills || []).some((e) => e.name === Mp.name) ? [Mp] : [], ...e.skills || []], {
|
|
12695
12864
|
readVfs: Ve ? (e) => x.files[e]?.content : void 0,
|
|
12696
12865
|
onToolsReady: (e, t) => {
|
|
12697
12866
|
for (let n of t) {
|
|
@@ -12721,12 +12890,12 @@ function lm(e, n) {
|
|
|
12721
12890
|
...gt ? [gt] : [],
|
|
12722
12891
|
...pt ? [pt] : [],
|
|
12723
12892
|
...e.middleware || [],
|
|
12724
|
-
...pe ? [
|
|
12893
|
+
...pe ? [Bp(i, {
|
|
12725
12894
|
tokenBudget: e.tokenBudget,
|
|
12726
12895
|
timeBudgetMs: e.timeBudgetMs
|
|
12727
12896
|
}, J)] : [],
|
|
12728
|
-
|
|
12729
|
-
]), Ot = e.maxMemoryRounds ??
|
|
12897
|
+
dm(J, b, U, i)
|
|
12898
|
+
]), Ot = e.maxMemoryRounds ?? sm;
|
|
12730
12899
|
function kt(e) {
|
|
12731
12900
|
return (e) => {
|
|
12732
12901
|
e.type !== "approval_request" && J(e);
|
|
@@ -12740,7 +12909,7 @@ function lm(e, n) {
|
|
|
12740
12909
|
e.debug && console.warn("[page-agent-sdk][persist] refreshSessions 失败(已吞):", t);
|
|
12741
12910
|
}
|
|
12742
12911
|
}
|
|
12743
|
-
let Mt =
|
|
12912
|
+
let Mt = om(), Nt = /* @__PURE__ */ new Set();
|
|
12744
12913
|
function Y(e) {
|
|
12745
12914
|
let t = new AbortController(), n = () => t.abort();
|
|
12746
12915
|
return e && (e.aborted ? t.abort() : e.addEventListener("abort", n, { once: !0 })), Nt.add(t), {
|
|
@@ -12849,7 +13018,7 @@ function lm(e, n) {
|
|
|
12849
13018
|
code: "IMAGE_UNSUPPORTED_MODEL"
|
|
12850
13019
|
}), Error(`[page-agent-sdk] ${e}`);
|
|
12851
13020
|
}
|
|
12852
|
-
x?.setProtectedRefs?.(
|
|
13021
|
+
x?.setProtectedRefs?.(im(b));
|
|
12853
13022
|
let i = pe ? t.maxAutoRetries ?? 1 : 0, o = 0, c = !1, l = kt(t.signal);
|
|
12854
13023
|
if (t.signal) {
|
|
12855
13024
|
let e = () => a.resolve("keep_external");
|
|
@@ -12924,7 +13093,7 @@ function lm(e, n) {
|
|
|
12924
13093
|
if (await X.initDone, !e.length) return [];
|
|
12925
13094
|
if (X.refCount <= 0) throw Error("page-agent-sdk: agent 已释放(unmount),拒绝排队中的 batch");
|
|
12926
13095
|
let r = [], i = kt(n);
|
|
12927
|
-
if (x?.setProtectedRefs?.(
|
|
13096
|
+
if (x?.setProtectedRefs?.(im(b)), n) {
|
|
12928
13097
|
let e = () => a.resolve("keep_external");
|
|
12929
13098
|
n.aborted ? e() : n.addEventListener("abort", e, { once: !0 });
|
|
12930
13099
|
}
|
|
@@ -13051,7 +13220,7 @@ function lm(e, n) {
|
|
|
13051
13220
|
})), e.memory && Gt({ memory: re.get() || (typeof e.memory == "string" ? e.memory : "") }), jt(), Ht = void 0, Ut = !1, X.infoTick.value++, r;
|
|
13052
13221
|
},
|
|
13053
13222
|
resetSession: () => {
|
|
13054
|
-
Pt(), a.resolve("keep_external"), X.sessionId =
|
|
13223
|
+
Pt(), a.resolve("keep_external"), X.sessionId = kf(), b.splice(0, b.length), x.clear?.(), C.reset([]), e.memory || re.reset(""), w.reset(), j.reset(), A.reset(), Tt?.reset(), E.reset(), D = void 0, R && R.importStack([]), X.agent && (X.agent.debugLogs.value = [], X.agent.resetStaleReadsInvalidated?.()), s && s.createSession(X.agentId, e.session?.title, X.sessionId).catch((t) => {
|
|
13055
13224
|
e.debug && console.warn("[page-agent-sdk][persist] createSession 失败(已吞):", t);
|
|
13056
13225
|
}), J({
|
|
13057
13226
|
type: "session_restored",
|
|
@@ -13071,7 +13240,7 @@ function lm(e, n) {
|
|
|
13071
13240
|
code: "IMAGE_UNSUPPORTED_MODEL"
|
|
13072
13241
|
}), Error(`[page-agent-sdk] ${e}`);
|
|
13073
13242
|
}
|
|
13074
|
-
x?.setProtectedRefs?.(
|
|
13243
|
+
x?.setProtectedRefs?.(im(e));
|
|
13075
13244
|
let i = (e) => {
|
|
13076
13245
|
t?.(e), J(e), e.type === "subagent" && (e.kind === "tool_call" || e.kind === "tool_result") && X.infoTick.value++;
|
|
13077
13246
|
};
|
|
@@ -13086,7 +13255,7 @@ function lm(e, n) {
|
|
|
13086
13255
|
if (X.refCount--, X.refCount <= 0) {
|
|
13087
13256
|
Pt(), Te = !0, s && (x.flush?.(), s.flush(), s.dispose()), St && St.dispose();
|
|
13088
13257
|
let e = X.mcpClosers.splice(0);
|
|
13089
|
-
e.length && Promise.allSettled(e.map((e) => e())),
|
|
13258
|
+
e.length && Promise.allSettled(e.map((e) => e())), cm.delete(n);
|
|
13090
13259
|
}
|
|
13091
13260
|
},
|
|
13092
13261
|
resolveConflict: a.resolve,
|
|
@@ -13106,14 +13275,14 @@ function lm(e, n) {
|
|
|
13106
13275
|
},
|
|
13107
13276
|
setLlm(t) {
|
|
13108
13277
|
let n;
|
|
13109
|
-
if (
|
|
13278
|
+
if (vf(t)) n = t;
|
|
13110
13279
|
else {
|
|
13111
13280
|
let e = t;
|
|
13112
13281
|
if ((e.provider ?? "openai") === "anthropic") throw Error("[page-agent-sdk][setLlm] 切换到 Anthropic 需传 BaseChatModel 实例(动态 import 无法同步);请先 const { ChatAnthropic } = await import(\"@langchain/anthropic\") 构造实例后传入");
|
|
13113
13282
|
n = ee(e);
|
|
13114
13283
|
}
|
|
13115
13284
|
typeof n.bindTools != "function" && e.debug && console.warn("[page-agent-sdk][setLlm] 新模型不支持 bindTools(tool calling 会失效)"), y = t;
|
|
13116
|
-
let r =
|
|
13285
|
+
let r = vf(t) ? void 0 : t;
|
|
13117
13286
|
if (f = m({
|
|
13118
13287
|
model: r?.model ?? t.model ?? t.modelName,
|
|
13119
13288
|
contextWindow: e.contextWindow ?? r?.contextWindow,
|
|
@@ -13157,7 +13326,7 @@ function lm(e, n) {
|
|
|
13157
13326
|
return {
|
|
13158
13327
|
id: n,
|
|
13159
13328
|
sessionId: X.sessionId,
|
|
13160
|
-
model:
|
|
13329
|
+
model: vf(y) ? y.model ?? y.modelName : y.model,
|
|
13161
13330
|
systemPrompt: X.agent?.getEffectiveSystemPrompt?.() ?? me + lu(U(), e.schemaHint),
|
|
13162
13331
|
tools: (X.agent?.allTools ?? K).map((e) => ({
|
|
13163
13332
|
name: e.name,
|
|
@@ -13197,7 +13366,7 @@ function lm(e, n) {
|
|
|
13197
13366
|
workingMemory: ze ? j.getWorkingMemory() : void 0,
|
|
13198
13367
|
focus: ke ? A.getFocus() : void 0,
|
|
13199
13368
|
focuses: ke ? A.getFocuses() : [],
|
|
13200
|
-
actions:
|
|
13369
|
+
actions: Up(e.actions ?? {}),
|
|
13201
13370
|
subagent: {
|
|
13202
13371
|
enabled: !!nt,
|
|
13203
13372
|
maxDepth: e.subagent?.maxDepth ?? 1,
|
|
@@ -13284,11 +13453,11 @@ function lm(e, n) {
|
|
|
13284
13453
|
return ke ? A.getFocuses() : [];
|
|
13285
13454
|
},
|
|
13286
13455
|
setFocus(t) {
|
|
13287
|
-
let n =
|
|
13456
|
+
let n = fm(t, "setFocus", ke, () => U()?.schema, e.debug);
|
|
13288
13457
|
return n.ok ? (A.setFocus(t), e.debug && A.isInvokeActive() && console.info("[page-agent-sdk][focus] 在途流程进行中,聚焦将在下一次输入生效(当前流程不受影响)"), X.infoTick.value++, { ok: !0 }) : n;
|
|
13289
13458
|
},
|
|
13290
13459
|
addFocus(t) {
|
|
13291
|
-
let n =
|
|
13460
|
+
let n = fm(t, "addFocus", ke, () => U()?.schema, e.debug);
|
|
13292
13461
|
return n.ok ? A.getFocuses().length >= 8 ? (e.debug && console.warn("[page-agent-sdk][focus] addFocus 拒绝:焦点数已达上限 8(augmentPrompt 注入每焦点子树 schema,过多致 system prompt 超预算)"), {
|
|
13293
13462
|
ok: !1,
|
|
13294
13463
|
error: "焦点数上限 8(避免 system prompt 撑爆)"
|
|
@@ -13362,14 +13531,14 @@ function lm(e, n) {
|
|
|
13362
13531
|
}
|
|
13363
13532
|
function Rt(e) {
|
|
13364
13533
|
let t = {};
|
|
13365
|
-
for (let n of
|
|
13534
|
+
for (let n of im(e)) {
|
|
13366
13535
|
let e = x.files[n];
|
|
13367
13536
|
e && (t[n] = e.content);
|
|
13368
13537
|
}
|
|
13369
13538
|
return t;
|
|
13370
13539
|
}
|
|
13371
13540
|
function zt() {
|
|
13372
|
-
let e =
|
|
13541
|
+
let e = am(x.files, im(b));
|
|
13373
13542
|
for (let t of e) delete x.files[t];
|
|
13374
13543
|
}
|
|
13375
13544
|
function Bt() {
|
|
@@ -13473,7 +13642,7 @@ function lm(e, n) {
|
|
|
13473
13642
|
Gt({ focus: e.length ? e : null });
|
|
13474
13643
|
}
|
|
13475
13644
|
pe && R && (Gt({ checkpoints: R.exportStack() }), Gt({ usage: i }));
|
|
13476
|
-
let t =
|
|
13645
|
+
let t = _f(b);
|
|
13477
13646
|
if (t && t !== Ht && (Ht = t, Kt(X.sessionId, t)), e.autoTitle !== !1 && u && !Ut && b.some((e) => e.role === "user") && b.some((e) => e.role === "assistant")) {
|
|
13478
13647
|
Ut = !0;
|
|
13479
13648
|
let e = X.sessionId;
|
|
@@ -13551,7 +13720,7 @@ function lm(e, n) {
|
|
|
13551
13720
|
}
|
|
13552
13721
|
}));
|
|
13553
13722
|
})();
|
|
13554
|
-
let t =
|
|
13723
|
+
let t = vf(e.llm) ? e.llm : await S(e.llm);
|
|
13555
13724
|
X.agent = Po({
|
|
13556
13725
|
llm: t,
|
|
13557
13726
|
systemPrompt: me,
|
|
@@ -13567,7 +13736,7 @@ function lm(e, n) {
|
|
|
13567
13736
|
contextWindow: f.contextWindow,
|
|
13568
13737
|
maxOutputTokens: f.maxOutputTokens,
|
|
13569
13738
|
vision: f.vision,
|
|
13570
|
-
imageContentFormat: !
|
|
13739
|
+
imageContentFormat: !vf(e.llm) && (e.llm.provider ?? "openai") === "anthropic" ? "anthropic" : "openai",
|
|
13571
13740
|
maxVerifyAttempts: Ye ? Ke : 0,
|
|
13572
13741
|
roundTokenBudget: e.roundTokenBudget ?? 0,
|
|
13573
13742
|
staleReadInvalidation: e.staleReadInvalidation,
|
|
@@ -13580,12 +13749,12 @@ function lm(e, n) {
|
|
|
13580
13749
|
});
|
|
13581
13750
|
})(), X;
|
|
13582
13751
|
}
|
|
13583
|
-
function
|
|
13584
|
-
let n = e.id ??
|
|
13752
|
+
function mm(e, t) {
|
|
13753
|
+
let n = e.id ?? kf();
|
|
13585
13754
|
e.id || console.warn(`[page-agent-sdk] 未传 options.id,已生成随机 id "${n}"。刷新后持久化数据无法恢复,请传稳定 id。`);
|
|
13586
|
-
let r = e.streaming ?? !0, i = e.ui ?? "default", a, o = e.shareContext ?
|
|
13587
|
-
o ? a = o : (a =
|
|
13588
|
-
let s = null, c =
|
|
13755
|
+
let r = e.streaming ?? !0, i = e.ui ?? "default", a, o = e.shareContext ? cm.get(n) : void 0;
|
|
13756
|
+
o ? a = o : (a = pm(e, n), e.shareContext && cm.set(n, a)), a.refCount++;
|
|
13757
|
+
let s = null, c = ip(e), l = null, u = null;
|
|
13589
13758
|
async function d(n) {
|
|
13590
13759
|
if (await a.initDone, s) {
|
|
13591
13760
|
s.show();
|
|
@@ -13804,7 +13973,7 @@ function um(e, t) {
|
|
|
13804
13973
|
}
|
|
13805
13974
|
//#endregion
|
|
13806
13975
|
//#region src/core/sdk/ragSubagent.ts
|
|
13807
|
-
function
|
|
13976
|
+
function hm(e, t, n, r, i) {
|
|
13808
13977
|
let a = [];
|
|
13809
13978
|
e && a.push("vfs_grep + vfs_read(搜预注入文档)"), t && a.push(`${r}(语义检索)`), n && a.push(`${i}(按 source 加载)`), a.push("fetch_document(公网 URL)");
|
|
13810
13979
|
let o = [];
|
|
@@ -13819,7 +13988,7 @@ function dm(e, t, n, r, i) {
|
|
|
13819
13988
|
|
|
13820
13989
|
你是只读检索角色,不要尝试修改任何数据。不要尝试上面未列出的工具。`;
|
|
13821
13990
|
}
|
|
13822
|
-
function
|
|
13991
|
+
function gm(e, t, n, r, i) {
|
|
13823
13992
|
let a = [], o = [];
|
|
13824
13993
|
return e && (a.push("- vfs_grep / vfs_read:搜集成方预注入 vfs 的文档(组件文档 / UI 规范)—— 快、静态,先查"), o.push("- 静态文档(组件库文档 / UI 规范):vfs_grep")), t && (a.push(`- ${r}:语义检索(retriever 向量库)—— 模糊匹配,关键词不确定 / 语义相似时用`), o.push(`- 语义相似(「瀑布流组件」「倒计时」「拼团」):${r}`)), n && (a.push(`- ${i}:按 source 精确加载(已知文档 id / URL / key)—— 精确取特定文档`), o.push(`- 已知文档 id / URL:${i}`)), a.push("- fetch_document:公网 URL —— 查公开资料"), o.push("- 公开 URL(设计规范网站 / 第三方文档):fetch_document"), `# 知识检索策略(RAG)
|
|
13825
13994
|
|
|
@@ -13836,12 +14005,12 @@ ${a.join("\n")}
|
|
|
13836
14005
|
## 何时用何源
|
|
13837
14006
|
${o.join("\n")}`;
|
|
13838
14007
|
}
|
|
13839
|
-
var
|
|
14008
|
+
var _m = {
|
|
13840
14009
|
name: "rag-search",
|
|
13841
14010
|
description: "多源知识检索策略:vfs 预注入文档 → 语义检索 → 指定文档加载 → 公网;综合结构化结论 + 溯源",
|
|
13842
|
-
getContent: () =>
|
|
14011
|
+
getContent: () => gm(!0, !0, !0, "search_docs", "load_doc")
|
|
13843
14012
|
};
|
|
13844
|
-
function
|
|
14013
|
+
function vm(e, n, i) {
|
|
13845
14014
|
let a = r(async ({ query: e, topK: t }) => {
|
|
13846
14015
|
try {
|
|
13847
14016
|
let r = await n(e, { topK: t ?? i });
|
|
@@ -13859,7 +14028,7 @@ function mm(e, n, i) {
|
|
|
13859
14028
|
});
|
|
13860
14029
|
return ki([a]), a;
|
|
13861
14030
|
}
|
|
13862
|
-
function
|
|
14031
|
+
function ym(e, n) {
|
|
13863
14032
|
let i = r(async ({ source: e }) => {
|
|
13864
14033
|
try {
|
|
13865
14034
|
let t = await n(e), r = Array.isArray(t) ? t : [t];
|
|
@@ -13874,19 +14043,19 @@ function hm(e, n) {
|
|
|
13874
14043
|
});
|
|
13875
14044
|
return ki([i]), i;
|
|
13876
14045
|
}
|
|
13877
|
-
function
|
|
14046
|
+
function bm(e) {
|
|
13878
14047
|
let { retriever: t, loader: n, useVfs: r = !0, id: i = "rag", description: a, topK: o = 5, searchToolName: s = "search_docs", loadToolName: c = "load_doc", maxToolRounds: l = 8, summarization: u, skills: d, extraTools: f } = e;
|
|
13879
14048
|
if (!t && !n && r === !1) throw Error("[page-agent-sdk][createRagSubagent] 至少配一种知识源:retriever / loader / useVfs(true)");
|
|
13880
14049
|
let p = [];
|
|
13881
|
-
t && p.push(
|
|
14050
|
+
t && p.push(vm(s, t, o)), n && p.push(ym(c, n)), f && p.push(...f);
|
|
13882
14051
|
let m = r ? [
|
|
13883
14052
|
"vfs_grep",
|
|
13884
14053
|
"vfs_read",
|
|
13885
14054
|
"vfs_json_read"
|
|
13886
|
-
] : void 0, h =
|
|
14055
|
+
] : void 0, h = hm(r, !!t, !!n, s, c), g = {
|
|
13887
14056
|
name: "rag-search",
|
|
13888
|
-
description:
|
|
13889
|
-
getContent: () =>
|
|
14057
|
+
description: _m.description,
|
|
14058
|
+
getContent: () => gm(r, !!t, !!n, s, c)
|
|
13890
14059
|
};
|
|
13891
14060
|
return {
|
|
13892
14061
|
id: i,
|
|
@@ -13901,7 +14070,7 @@ function gm(e) {
|
|
|
13901
14070
|
}
|
|
13902
14071
|
//#endregion
|
|
13903
14072
|
//#region src/core/utils/clipboard.ts
|
|
13904
|
-
async function
|
|
14073
|
+
async function xm(e) {
|
|
13905
14074
|
try {
|
|
13906
14075
|
let t = globalThis.navigator?.clipboard;
|
|
13907
14076
|
if (t && typeof t.writeText == "function") return await t.writeText(e), !0;
|
|
@@ -13919,8 +14088,8 @@ async function _m(e) {
|
|
|
13919
14088
|
}
|
|
13920
14089
|
//#endregion
|
|
13921
14090
|
//#region src/core/composables/useChat.ts
|
|
13922
|
-
var
|
|
13923
|
-
function
|
|
14091
|
+
var Sm = 4e3;
|
|
14092
|
+
function Cm(e = {}) {
|
|
13924
14093
|
let { fetchResponse: t, fetchStream: n, onPersist: r, onClear: i, onQueuedCleared: a, onBeforeRegenerate: o } = e, s = /* @__PURE__ */ Et({
|
|
13925
14094
|
messages: e.messages ?? [],
|
|
13926
14095
|
loading: !1,
|
|
@@ -14000,7 +14169,7 @@ function ym(e = {}) {
|
|
|
14000
14169
|
let t = e.delta || "";
|
|
14001
14170
|
n.subReasonFull = (n.subReasonFull || "") + t;
|
|
14002
14171
|
let r = (n.subReason || "") + t;
|
|
14003
|
-
n.subReason = r.length >
|
|
14172
|
+
n.subReason = r.length > Sm ? r.slice(-4e3) : r;
|
|
14004
14173
|
break;
|
|
14005
14174
|
}
|
|
14006
14175
|
n.children ||= [];
|
|
@@ -14130,7 +14299,7 @@ function ym(e = {}) {
|
|
|
14130
14299
|
}
|
|
14131
14300
|
//#endregion
|
|
14132
14301
|
//#region src/core/components/icons.ts
|
|
14133
|
-
var
|
|
14302
|
+
var wm = {
|
|
14134
14303
|
header: "🤖",
|
|
14135
14304
|
subagent: "🤖",
|
|
14136
14305
|
subagentProgress: "🧬",
|
|
@@ -14141,10 +14310,10 @@ var bm = {
|
|
|
14141
14310
|
recommend: "💡",
|
|
14142
14311
|
conflict: "⚠️"
|
|
14143
14312
|
};
|
|
14144
|
-
function
|
|
14145
|
-
let t = { ...
|
|
14313
|
+
function Tm(e) {
|
|
14314
|
+
let t = { ...wm };
|
|
14146
14315
|
if (!e) return t;
|
|
14147
|
-
let n = Object.keys(
|
|
14316
|
+
let n = Object.keys(wm);
|
|
14148
14317
|
for (let r of n) {
|
|
14149
14318
|
let n = e[r];
|
|
14150
14319
|
typeof n == "string" && (t[r] = n);
|
|
@@ -14153,7 +14322,7 @@ function xm(e) {
|
|
|
14153
14322
|
}
|
|
14154
14323
|
//#endregion
|
|
14155
14324
|
//#region src/core/components/messages.ts
|
|
14156
|
-
var
|
|
14325
|
+
var Em = {
|
|
14157
14326
|
defaultTitle: "AI 助手",
|
|
14158
14327
|
inputPlaceholder: "输入消息,Enter 发送...",
|
|
14159
14328
|
newSession: "新建会话",
|
|
@@ -14377,8 +14546,8 @@ var Sm = {
|
|
|
14377
14546
|
codeDemoText: "这是一段示例文字,用于展示 CSS 效果。",
|
|
14378
14547
|
codeDemoButton: "按钮",
|
|
14379
14548
|
codeDemoInput: "输入框"
|
|
14380
|
-
},
|
|
14381
|
-
"zh-CN":
|
|
14549
|
+
}, Dm = {
|
|
14550
|
+
"zh-CN": Em,
|
|
14382
14551
|
"en-US": {
|
|
14383
14552
|
defaultTitle: "AI Assistant",
|
|
14384
14553
|
inputPlaceholder: "Type a message, Enter to send...",
|
|
@@ -14605,10 +14774,10 @@ var Sm = {
|
|
|
14605
14774
|
codeDemoInput: "Input"
|
|
14606
14775
|
}
|
|
14607
14776
|
};
|
|
14608
|
-
function
|
|
14777
|
+
function Om(e = "zh-CN", t) {
|
|
14609
14778
|
let n = {
|
|
14610
|
-
...
|
|
14611
|
-
...
|
|
14779
|
+
...Em,
|
|
14780
|
+
...Dm[e]
|
|
14612
14781
|
};
|
|
14613
14782
|
if (!t) return n;
|
|
14614
14783
|
let r = { ...n };
|
|
@@ -14620,9 +14789,9 @@ function wm(e = "zh-CN", t) {
|
|
|
14620
14789
|
}
|
|
14621
14790
|
//#endregion
|
|
14622
14791
|
//#region src/core/composables/chatContext.ts
|
|
14623
|
-
var
|
|
14624
|
-
function
|
|
14625
|
-
let t =
|
|
14792
|
+
var km = Symbol("chatContext");
|
|
14793
|
+
function Am(e = {}) {
|
|
14794
|
+
let t = Cm({
|
|
14626
14795
|
fetchResponse: e.fetchResponse,
|
|
14627
14796
|
fetchStream: e.fetchStream,
|
|
14628
14797
|
messages: e.messages,
|
|
@@ -14644,7 +14813,7 @@ function Em(e = {}) {
|
|
|
14644
14813
|
}, h = /* @__PURE__ */ Lt({}), g = (e) => h.value[e] === !0, _ = (e) => {
|
|
14645
14814
|
h.value[e] = !g(e);
|
|
14646
14815
|
}, v = /* @__PURE__ */ Lt(!1), y = (e) => {
|
|
14647
|
-
|
|
14816
|
+
xm(e).then((e) => {
|
|
14648
14817
|
e && (v.value = !0, setTimeout(() => {
|
|
14649
14818
|
v.value = !1;
|
|
14650
14819
|
}, 1500));
|
|
@@ -14706,7 +14875,7 @@ function Em(e = {}) {
|
|
|
14706
14875
|
e.onClearFocus?.();
|
|
14707
14876
|
}, ae = (t) => {
|
|
14708
14877
|
e.onFocusChipClick?.(t);
|
|
14709
|
-
}, F =
|
|
14878
|
+
}, F = Tm(e.icons), I = e.locale ?? "zh-CN", oe = Om(I, e.dialogMessages);
|
|
14710
14879
|
return {
|
|
14711
14880
|
chat: t,
|
|
14712
14881
|
inputText: o,
|
|
@@ -14748,15 +14917,15 @@ function Em(e = {}) {
|
|
|
14748
14917
|
toolStepView: e.toolStepView
|
|
14749
14918
|
};
|
|
14750
14919
|
}
|
|
14751
|
-
function
|
|
14752
|
-
let e = Gn(
|
|
14920
|
+
function jm() {
|
|
14921
|
+
let e = Gn(km);
|
|
14753
14922
|
if (!e) throw Error("useChatContext 必须在 provide(chatContextKey) 的组件子树内调用");
|
|
14754
14923
|
return e;
|
|
14755
14924
|
}
|
|
14756
14925
|
//#endregion
|
|
14757
14926
|
//#region src/core/index.headless.ts
|
|
14758
|
-
function
|
|
14759
|
-
return
|
|
14927
|
+
function Mm(e) {
|
|
14928
|
+
return mm(e);
|
|
14760
14929
|
}
|
|
14761
14930
|
//#endregion
|
|
14762
|
-
export {
|
|
14931
|
+
export { ap as CAPABILITIES, cp as CONTEXT_PRESETS, gf as CompressDecisionSchema, xp as DEFAULT_COMPUTED_STYLES, Pc as DEFAULT_PROMPT_SOFT_CAP, su as DEFAULT_SYSTEM_PROMPT, od as HTML_VOID_TAGS, Cl as HUMAN_CONFIRM_TOOL_NAME, li as ImageInputError, p as MIN_CONTEXT_WINDOW, Ki as MODEL_UNAVAILABLE_GUIDANCE, Nc as SOFT_CAP_MIN_WINDOW, Dc as STOP_WORDS, js as UNSAFE_KEYS, Up as actionsToInspectInfo, Hp as actionsToTools, ai as agentError, eu as analyzeContext, Ws as applyPatchToClone, qs as applyPatchToLive, Ju as applyPatchesToBind, Wo as arrayMinLength, ii as asAgentError, Zd as buildCollectorJs, Ep as buildCssPath, lu as buildDataPrompt, Ud as buildDiagnosticsReport, Qd as buildSandboxSrcdoc, uu as buildSystemPrompt, km as chatContextKey, qu as commitSetToBind, af as composeStructureThenRender, bi as compressImage, ou as connectMcp, S as constructLlmFromConfig, ee as constructOpenLlmSync, xm as copyText, Po as createAgent, Sl as createApprovalMiddleware, Am as createChatContext, Mm as createChatSdk, Nl as createCheckpointManager, Pl as createCheckpointMiddleware, Of as createConflictManager, tu as createContextInspectorMiddleware, ad as createDataOps, lf as createHtmlFormatCheck, rf as createHtmlRenderCheck, ff as createHtmlSubagent, El as createHumanConfirmMiddleware, Tl as createHumanConfirmTool, Xf as createMemoryBackend, hl as createMemoryMiddleware, u as createProxyLlm, bm as createRagSubagent, Qi as createSandboxRunner, sp as createSdkEvents, om as createSerialRunner, ep as createSessionStore, tp as createSessionStoreWithBackend, nm as createSkillStore, ol as createSubagentMiddleware, Bc as createSubagentTracker, ll as createSubagentsMiddleware, Xp as createUsageHintsMiddleware, Fl as createVerifyMiddleware, mu as createVfs, Qf as createWebStorageBackend, Vl as createWriteBackCheck, qi as decorateModelUnavailable, Is as deepClone, Kp as defineDataToolset, rc as defineSkill, pf as defineTool, Fs as deleteByPath, _f as deriveTitle, is as describeSchemaNode, bo as detectGarbledToolCall, Zs as diffObjects, jp as domInfoTool, Mp as domInspectSkill, Ap as domSearchTool, vp as domToStructure, bp as domTools, Gp as domToolsStatic, Go as elementSchemaCandidates, wp as ensureDomListenerRecorder, kc as estimateMessageTokens, Ac as estimateRoundTokens, h as estimateTokens, w as extractReasoningDelta, _s as extractSchemaHint, nu as extractText, C as extractTextDelta, T as extractUsage, mp as fetchDocTools, Wp as fetchTools, Xs as findStrippedKeys, as as formatConstraints, ei as formatZodIssues, Q as getByPath, yp as getDomTool, kp as getElementInfo, Fp as getEnvSummary, Tp as getRecordedListeners, tf as getSandboxLifecycle, Ro as getSchemaAtPath, Fo as getSchemaTopKeys, Hs as hashValue, fs as htmlOrchestratorPrompt, jc as indexSummarize, Rp as inspectEnvTool, zp as inspectTools, vf as isChatModel, Wi as isContextLengthError, Gi as isModelUnavailableError, Io as isPathAllowed, Hf as isQuotaError, Ms as isUnsafePath, ca as jpEval, ni as jsonParseError, Bs as limitDepth, Vd as maskUrlCredentials, Rs as maybeParseValue, Fd as measureWriteScale, Xd as normalizeRenderResult, D as normalizeUsage, _ as offloadPassThroughChars, g as offloadThresholdChars, ds as presets, Qo as projectBySchema, Zo as projectBySchemaDeep, zs as projectFields, Mc as recallRounds, nf as renderInSandbox, os as renderSchemaHint, ss as renderSchemaOverview, us as renderSchemaShallow, op as resolveCapabilities, up as resolveContextOptions, ip as resolveDialogConfig, Df as resolveLlm, m as resolveModelCaps, Fc as resolvePromptSoftCap, Vo as resolveSchemaPath, rp as resolveStorage, Ys as restoreInPlace, Js as restoreLive, ri as routeError, fa as runSandboxedScript, Ns as safeMerge, Pp as safeSerialize, $ as safeStringify, Uo as schemaHasRefinement, Dp as searchDom, ua as searchJson, qp as selectBuiltinTools, Ps as setByPath, Ic as shouldTriggerCompression, Wd as stringifyDiagnosticsReport, ps as systemPromptHelpers, Oc as tokenize, Z as toolError, Lo as unwrapSchema, Cm as useChat, jm as useChatContext, Ho as validateAtPath, cd as validateHtmlFormat, Gu as validateRootValueLocally, Ku as validateWriteLocally, Us as watchFieldsHash, e as z, ti as zodError };
|