@epoch-agent/server 0.1.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/LICENSE +219 -0
- package/README.md +1440 -0
- package/dist/index.d.ts +3907 -0
- package/dist/index.js +3785 -0
- package/dist/web/assets/diff-woXpYk--.js +1 -0
- package/dist/web/assets/index-D3Z9KCql.js +295 -0
- package/dist/web/assets/index-W6_gte_K.css +1 -0
- package/dist/web/assets/json-qhed-kSA.js +1 -0
- package/dist/web/assets/markdown-BYOwaDjH.js +1 -0
- package/dist/web/assets/shellscript-CLZ0U2zV.js +1 -0
- package/dist/web/assets/typescript-bsJCZSQ-.js +1 -0
- package/dist/web/assets/yaml-rwi0_p6S.js +1 -0
- package/dist/web/index.html +155 -0
- package/package.json +43 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<!--
|
|
3
|
+
`lang` 和 `data-theme` 的初值都是**占位**,真值由文档头里那段内联脚本在第一帧
|
|
4
|
+
之前写上去。写成 `zh-CN` / `light` 而不是留空:脚本被 CSP 挡下、或者哪天被谁
|
|
5
|
+
删掉时,页面退化成「浅色 + 中文」这个还能看的形态,而不是一个没有
|
|
6
|
+
`[data-theme]`、因此一个颜色变量都没定义的裸 HTML。
|
|
7
|
+
|
|
8
|
+
⚠️⚠️ **这段注释里绝对不能出现 head 或 body 的起始标签字面量**(连反引号包着
|
|
9
|
+
也不行),下面每一段注释同理。
|
|
10
|
+
|
|
11
|
+
vite 的 dev server 靠**第一个** head 起始标签的位置注入两样东西:react 的
|
|
12
|
+
refresh preamble 和 `/@vite/client`。它是按字符串位置插的,**不解析注释** ——
|
|
13
|
+
这段注释原来在这儿写了一个 head 起始标签(想指路「真值由下面那段内联脚本写」),
|
|
14
|
+
于是那两个 `<script>` 被插进了这条注释**内部**,永远不执行。症状是打开页面就报
|
|
15
|
+
「@vitejs/plugin-react can't detect preamble」,而且它**只在 dev server 上出现**:
|
|
16
|
+
`vite build` 走的是另一条注入路径(插在真 head 的末尾),产物一直是好的。
|
|
17
|
+
也就是说这个坑只有真的去跑 `pnpm web:dev` 才会撞上 —— 它藏了整整五个 PR。
|
|
18
|
+
-->
|
|
19
|
+
<html lang="zh-CN" data-theme="light">
|
|
20
|
+
<head>
|
|
21
|
+
<meta charset="UTF-8" />
|
|
22
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
23
|
+
<!--
|
|
24
|
+
本地服务,不该被任何一层缓存住:`epoch web` 每次起来都可能是新版本的产物,
|
|
25
|
+
而端口经常是同一个 —— 缓存住等于用旧界面连新服务。
|
|
26
|
+
-->
|
|
27
|
+
<meta http-equiv="Cache-Control" content="no-store" />
|
|
28
|
+
<!--
|
|
29
|
+
URL 上带着一次性的 `?token=`(§6.3)。referrer 会把整条 URL 带给外链目标,
|
|
30
|
+
所以一律不发 —— 界面里出现任何一个外部链接时这条就开始起作用了。
|
|
31
|
+
-->
|
|
32
|
+
<meta name="referrer" content="no-referrer" />
|
|
33
|
+
<!--
|
|
34
|
+
**产品名,不进 catalog** —— 两种语言下同一句。决定 19 撤掉的是**界面上**
|
|
35
|
+
那个 `epoch` 词标;`<title>`、文档标题、`~/.epoch/` 这类路径全部原样不动:
|
|
36
|
+
那些不是品牌,是标识符。
|
|
37
|
+
-->
|
|
38
|
+
<title>epoch</title>
|
|
39
|
+
|
|
40
|
+
<!--
|
|
41
|
+
============================================================
|
|
42
|
+
主题解析 —— **必须内联,必须在任何 CSS 之前**(决定 21)
|
|
43
|
+
============================================================
|
|
44
|
+
|
|
45
|
+
FOUC 是这一条唯一的技术风险:CSS 先于 js 生效,等外部 bundle 加载完再决定
|
|
46
|
+
的话,深色系统上会先闪一帧白(验收 4.1 第 4 条)。所以这十几行不能挪进
|
|
47
|
+
`src/theme/`,也不能加 `defer`。
|
|
48
|
+
|
|
49
|
+
⚠️ **它和 `src/prefs.ts` 是真的两份。** 两处的 key 名必须逐字一致,
|
|
50
|
+
`__tests__/prefs.test.ts` 钉着这件事 —— 那条用例读的就是这个文件。
|
|
51
|
+
重复的只有「读 key + 判 media query + 写 dataset」这十几个字符,
|
|
52
|
+
换来的是首屏不闪。
|
|
53
|
+
|
|
54
|
+
顺带把 `lang` 也在这儿写了。语言**不需要**这段脚本来防闪(文案是 React 树上
|
|
55
|
+
的一个值,首屏渲染时读的就是已经解析好的语言,见 `src/i18n/pref.ts`),
|
|
56
|
+
但 `<html lang>` 是个**属性**不是文案:屏幕阅读器和浏览器的「要不要翻译这
|
|
57
|
+
一页」都读它,而它在 React 挂载之前就已经被读过了。这段脚本本来就要跑,
|
|
58
|
+
顺手写上去是零成本的。
|
|
59
|
+
|
|
60
|
+
`try/catch` 包住 localStorage:Safari 无痕 / 企业策略 / 被嵌进 iframe 时,
|
|
61
|
+
取 `localStorage` 这个属性本身就抛。一个显示偏好不该让整张页面白屏。
|
|
62
|
+
|
|
63
|
+
## 首屏 query 是**宿主给的初值**(方案 54 §七)
|
|
64
|
+
|
|
65
|
+
嵌入宿主(Electron / VS Code)把这张界面装进自己的窗口,第一诉求是别和自己
|
|
66
|
+
的外壳撞色。它够不着 `epoch.theme` 这个 key —— `@epoch-agent/web` 是
|
|
67
|
+
`private: true`,宿主 import 不到,只能把这个字符串抄进自己的代码里,
|
|
68
|
+
然后在页面脚本之前挂一个 preload 去写 localStorage。那是在依赖一个我们
|
|
69
|
+
随时能改的名字。
|
|
70
|
+
|
|
71
|
+
所以 `createWebServer({ ui: { theme, lang } })` 会把值拼进首屏地址
|
|
72
|
+
(`?theme=dark&lang=zh`,拼法在 server 的 `bind.ts`),这段脚本认它。
|
|
73
|
+
两条约束定死,别自己发挥:
|
|
74
|
+
|
|
75
|
+
1. **一次性的初值,不是持续覆盖。** 认出来就写进 localStorage 再按现有
|
|
76
|
+
逻辑走,然后**从地址栏里抹掉**(见末尾那段 `replaceState`)。
|
|
77
|
+
主题是用户偏好,宿主只该给一个合理的起点 —— 用户在设置页仍然改得动,
|
|
78
|
+
下次启动宿主再给一次初值
|
|
79
|
+
2. **认不出来的值当没给**,和 `readPref` 的坏值策略一个字不差。
|
|
80
|
+
query 和 localStorage 认的是**同一个值域**,两处不许分叉
|
|
81
|
+
-->
|
|
82
|
+
<script>
|
|
83
|
+
(function () {
|
|
84
|
+
// 地址栏畸形(`decodeURI` 抛)时当作没给 —— 一个显示偏好不该白屏
|
|
85
|
+
var query = null;
|
|
86
|
+
try {
|
|
87
|
+
query = new URLSearchParams(location.search);
|
|
88
|
+
} catch (_) {
|
|
89
|
+
query = null;
|
|
90
|
+
}
|
|
91
|
+
var taken = false;
|
|
92
|
+
|
|
93
|
+
var read = function (key, param, allowed) {
|
|
94
|
+
// 先看宿主给的初值。`allowed` 两处共用,值域天然不会分叉
|
|
95
|
+
var given = query && query.get(param);
|
|
96
|
+
if (allowed.indexOf(given) !== -1) {
|
|
97
|
+
taken = true;
|
|
98
|
+
try {
|
|
99
|
+
localStorage.setItem(key, given);
|
|
100
|
+
} catch (_) {
|
|
101
|
+
// 存储不可用(无痕 / 策略):这一次记不住,但这一屏照样按它渲染
|
|
102
|
+
}
|
|
103
|
+
return given;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
var raw = localStorage.getItem(key);
|
|
107
|
+
return allowed.indexOf(raw) === -1 ? 'system' : raw;
|
|
108
|
+
} catch (_) {
|
|
109
|
+
return 'system';
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
var root = document.documentElement;
|
|
114
|
+
|
|
115
|
+
var theme = read('epoch.theme', 'theme', ['system', 'light', 'dark']);
|
|
116
|
+
if (theme === 'system') {
|
|
117
|
+
theme = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
118
|
+
}
|
|
119
|
+
root.dataset.theme = theme;
|
|
120
|
+
|
|
121
|
+
var lang = read('epoch.lang', 'lang', ['system', 'zh', 'en']);
|
|
122
|
+
if (lang === 'system') {
|
|
123
|
+
// 与 src/i18n/pref.ts 的 `langFromLocale` 同一条判定:zh 开头 → zh,
|
|
124
|
+
// 其余一律 en(系统 locale 是一个信号 —— 法语用户看英文比看中文强)
|
|
125
|
+
var tag = ((navigator.languages || [])[0] || navigator.language || '').toLowerCase();
|
|
126
|
+
lang = tag.indexOf('zh') === 0 ? 'zh' : 'en';
|
|
127
|
+
}
|
|
128
|
+
root.lang = lang === 'zh' ? 'zh-CN' : 'en';
|
|
129
|
+
|
|
130
|
+
// 吃掉就从地址栏里抹掉。不抹的话它就不是**初值**而是「每次刷新都生效的
|
|
131
|
+
// 覆盖」:用户在设置页改成浅色、刷一下又变回宿主给的深色,而他改的那个值
|
|
132
|
+
// 还好好地存在 localStorage 里 —— 那正是「宿主和用户抢方向盘」。
|
|
133
|
+
// `?token=` 那一半由服务端的 303 抹(routes.ts),这一半只能在这里抹:
|
|
134
|
+
// 它必须先被这段脚本读到。
|
|
135
|
+
if (taken) {
|
|
136
|
+
try {
|
|
137
|
+
query.delete('theme');
|
|
138
|
+
query.delete('lang');
|
|
139
|
+
var rest = query.toString();
|
|
140
|
+
var next = location.pathname + (rest ? '?' + rest : '') + location.hash;
|
|
141
|
+
history.replaceState(null, '', next);
|
|
142
|
+
} catch (_) {
|
|
143
|
+
// 没有 history(老浏览器 / 某些沙箱):值已经落进 localStorage 了,
|
|
144
|
+
// 这一屏是对的,代价只是地址栏上多两个参数
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
})();
|
|
148
|
+
</script>
|
|
149
|
+
<script type="module" crossorigin src="/assets/index-D3Z9KCql.js"></script>
|
|
150
|
+
<link rel="stylesheet" crossorigin href="/assets/index-W6_gte_K.css">
|
|
151
|
+
</head>
|
|
152
|
+
<body>
|
|
153
|
+
<div id="root"></div>
|
|
154
|
+
</body>
|
|
155
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@epoch-agent/server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "epoch-agent 本地 HTTP + SSE 服务端:给浏览器的那一层,嵌入方不需要它",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/Ddbor/epoch-agent.git"
|
|
9
|
+
},
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
|
+
"author": "epoch-agent",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@epoch-agent/protocol": "0.1.0",
|
|
26
|
+
"@epoch-agent/runtime": "0.1.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^22.0.0",
|
|
30
|
+
"tsup": "^8.3.6",
|
|
31
|
+
"typescript": "^5.8.3",
|
|
32
|
+
"vitest": "^3.0.0"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=22.0.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup && node scripts/copy-web-assets.mjs",
|
|
39
|
+
"dev": "tsup --watch",
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"typecheck": "tsc --noEmit"
|
|
42
|
+
}
|
|
43
|
+
}
|