tavern-harness 0.1.13 → 0.1.15

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/index.html +1 -1
  3. package/package.json +1 -1
  4. package/persistence-server.mjs +54 -2
  5. package/sandbox-server.mjs +341 -90
  6. package/src/App.tsx +91 -25
  7. package/src/components/AchievementModal.tsx +4 -2
  8. package/src/components/CharactersView.tsx +53 -25
  9. package/src/components/ChatView.tsx +264 -135
  10. package/src/components/Dashboard.tsx +12 -8
  11. package/src/components/DeleteConfirmDialog.tsx +1 -1
  12. package/src/components/FileDisplayModal.tsx +90 -102
  13. package/src/components/GameplayDialogs.tsx +2 -2
  14. package/src/components/NewSessionMenu.tsx +1 -1
  15. package/src/components/SettingsView.tsx +46 -29
  16. package/src/components/Sidebar.tsx +30 -3
  17. package/src/components/StatsView.tsx +55 -45
  18. package/src/components/VersionHistoryModal.tsx +1 -1
  19. package/src/components/WorkspaceFileManagerModal.tsx +28 -3
  20. package/src/components/shared.tsx +84 -24
  21. package/src/core/achievements.ts +22 -4
  22. package/src/core/avatarThumbnail.ts +245 -0
  23. package/src/core/characterBook.ts +2 -1
  24. package/src/core/i18n.ts +51 -9
  25. package/src/core/markdown.ts +3 -2
  26. package/src/core/mobileViewport.ts +18 -3
  27. package/src/core/openai.ts +44 -22
  28. package/src/core/randomId.ts +19 -0
  29. package/src/core/rowFingerprint.ts +75 -0
  30. package/src/core/stats.ts +50 -5
  31. package/src/core/stickyBottom.ts +10 -1
  32. package/src/core/textPreview.ts +16 -0
  33. package/src/core/toolDefinitions.ts +9 -9
  34. package/src/core/tools/generatedSkillExecutor.ts +52 -32
  35. package/src/db/database.ts +101 -13
  36. package/src/generated/changelog.json +159 -1
  37. package/src/main.tsx +12 -21
  38. package/src/store/store.ts +213 -65
  39. package/src/theme/chat.css +62 -71
  40. package/src/theme/global.css +85 -21
  41. package/src/theme/theme.ts +13 -10
  42. package/src/theme/views.css +108 -8
  43. package/src/types/models.ts +2 -0
  44. package/vite.config.ts +128 -4
package/CHANGELOG.md CHANGED
@@ -3,6 +3,120 @@
3
3
  > 本文件由 `scripts/changelog.mjs` 从 `changelog/v*.md` 生成,请勿手动编辑。
4
4
  > 版本对比链接见文末;源文件位于仓库 `changelog/` 目录。
5
5
 
6
+ ## [0.1.15] - 2026-10-25
7
+
8
+ ### 中文
9
+
10
+ #### 新增
11
+
12
+ - 本地命令支持管道与重定向:可用 `|`、`<`、`>`、`>>`、`2>`、`2>&1` 组合命令,例如 `cat log.txt | grep error`;读写路径仍限制在会话工作区内,变量展开、命令替换、heredoc、后台执行与交互式命令依旧不支持
13
+ - 工作区文件管理器新增「在文件管理器中打开」:一键在运行 Tavern Harness 的设备上打开当前会话的工作目录;通过局域网从手机访问时会提示仅限本机
14
+
15
+ #### 改进
16
+
17
+ - HTML 预览支持多文件应用:HTML 现在可以加载同目录下的脚本、样式、图片和字体,沙箱内的指针锁定等交互也可正常使用
18
+ - 大工具参数的流式生成更流畅:参数较大时自动降低草稿刷新与本地写入频率,只保留最新快照,减少界面卡顿和写入积压
19
+ - 画中画预览更易操作:可从四角任意缩放、拖动标题栏移动窗口,移动端初始窗口尺寸更合适
20
+
21
+ #### 修复
22
+
23
+ - 修复模型输出中的原始 HTML 被当成页面结构渲染的问题:现在按纯文本显示,消息排版不再被意外改变
24
+ - 修复生成大型工具参数(例如写入大文件)耗时较长时,已建立的响应可能因 60 秒无数据而中断的问题;现在只有建立连接受 20 秒超时限制,生成过程可随时手动停止
25
+
26
+ #### 内部
27
+
28
+ - 新增 `/preview_read` 端点与 Vite 预览中间件:按会话工作区读取预览资源,上限 8MB,并按扩展名返回正确的 MIME 类型
29
+ - 命令执行改为流式连接管道各段与重定向文件,超时或出错时结束整个进程组
30
+ - 补充管道、重定向与预览端点的测试,并修正生成技能的工具与参数说明
31
+
32
+ ### English
33
+
34
+ #### Added
35
+
36
+ - Local commands now support pipes and redirection: combine commands with `|`, `<`, `>`, `>>`, `2>`, and `2>&1`, for example `cat log.txt | grep error`; read and write paths stay inside the session workspace, and expansion, command substitution, heredocs, background jobs, and interactive commands remain unsupported
37
+ - The workspace file manager has an "Open in file manager" button that opens the current conversation's workspace on the machine running Tavern Harness; when the app is opened over the LAN from a phone, it explains that this only works on the host device
38
+
39
+ #### Changed
40
+
41
+ - HTML previews now support multi-file apps: HTML can load same-directory scripts, styles, images, and fonts, and sandboxed interactions such as pointer lock work
42
+ - Smoother streaming for large tool arguments: once an argument gets large, draft updates and local writes happen less often and only the latest snapshot is kept, reducing UI stalls and write backlogs
43
+ - Picture-in-picture previews are easier to use: resize from any corner, drag the title bar to move the window, and the initial window size fits mobile screens better
44
+
45
+ #### Fixed
46
+
47
+ - Fixed raw HTML in model output being rendered as page structure: it now appears as plain text, so message layout is no longer altered unexpectedly
48
+ - Fixed connected responses being interrupted after 60 seconds without data while generating large tool arguments (such as writing a big file); only establishing the connection is still subject to a 20-second timeout, and generation can be stopped manually at any time
49
+
50
+ #### Internal
51
+
52
+ - Added a `/preview_read` endpoint and Vite preview middleware that reads preview assets from the session workspace, caps them at 8MB, and serves the correct MIME type by extension
53
+ - Command execution now streams between pipeline stages and to and from redirected files, ending the whole process group on timeout or failure
54
+ - Added tests for pipes, redirection, and the preview endpoint, and clarified generated-skill tool and parameter descriptions
55
+
56
+ ## [0.1.14] - 2026-10-24
57
+
58
+ ### 中文
59
+
60
+ #### 新增
61
+
62
+ - 设置 → 生成参数新增「重置」按钮,一键恢复温度、Top P、Top K 等默认值;已是默认值时按钮不可用
63
+
64
+ #### 改进
65
+
66
+ - 启动体验:界面先于数据加载显示,启动期间展示加载动画而非空白页;启动数据合并为一次快照请求,长列表通过增量渲染与缓存保持流畅
67
+ - 成就页重新排版:奖杯陈列柜置顶,生涯统计与「重置统计」分区展示
68
+ - 统一弹窗尺寸(紧凑 / 标准 / 宽屏三档)并适配移动端;弹窗不再响应点击遮罩关闭,避免编辑内容时误触丢失
69
+ - 流式生成更稳定:自动跟随最新内容,向上回看历史时不会被拉回底部;键盘弹出时思考与回复内容不再被输入框遮挡
70
+ - 移动端消息编辑器:键盘开合过渡更平滑,消息操作与编辑时的附件按钮更易用
71
+ - 聊天附件、思考面板与工具卡片的样式更清晰,浅色主题配色更柔和统一
72
+
73
+ #### 修复
74
+
75
+ - 修复生成失败或模型返回空内容时界面停留在「生成中」的问题:现在会回滚本轮并提示;已收到的部分正文会保留并标注「生成已中断」
76
+ - 修复通过局域网地址(侧栏二维码)在手机上打开时,回合可能一直卡住没有回复的问题;连接超时会自动改用同源代理,全部失败时明确提示网络错误
77
+ - 修复刷新或切换标签页时短暂闪出错误主题的问题,首帧即应用正确的深浅色背景
78
+ - 修复首页角色卡片不显示自定义头像的问题
79
+ - 修复移动端消息操作按钮、编辑附件动作与聊天附件样式的问题
80
+ - 修复启动时模型服务列表可能为空、旧会话消息加载较慢的问题
81
+
82
+ #### 内部
83
+
84
+ - 持久化服务新增启动快照与按字段查询接口,减少启动请求数量,并补充相应测试
85
+ - 新增头像缩略图与行指纹缓存,降低长列表重复渲染开销
86
+ - 统一弹窗组件与尺寸定义,移除重复的遮罩点击逻辑
87
+ - 补全 yolo 集成测试所需的持久化接口 mock
88
+
89
+ ### English
90
+
91
+ #### Added
92
+
93
+ - New "Reset" button in Settings → Generation parameters restores the default temperature, Top P, Top K, and related values; it is disabled when everything is already at defaults
94
+
95
+ #### Changed
96
+
97
+ - Startup: the UI mounts before data loads and shows a loading indicator instead of a blank page; startup data is fetched in a single snapshot request, and long lists stay smooth through incremental rendering and caching
98
+ - Achievements page reorganized: the trophy shelf comes first, with career stats and a separate "Reset stats" section below
99
+ - Modals now share three sizes (compact, standard, wide) and adapt to mobile; clicking the backdrop no longer dismisses them, preventing accidental loss of edits
100
+ - Streaming is more stable: the view follows new content automatically but stops following when you scroll up to read history, and the keyboard no longer covers thinking or reply content
101
+ - Mobile message editor: smoother keyboard transitions and easier access to attachment actions while editing
102
+ - Clearer styling for chat attachments, thinking panels, and tool cards, with softer, more consistent light-theme colors
103
+
104
+ #### Fixed
105
+
106
+ - Fixed the UI getting stuck in "generating" when generation failed or the model returned no content: the turn now rolls back with a clear message, while partial text is kept and marked as interrupted
107
+ - Fixed turns occasionally hanging without a reply when the app is opened on a phone through its LAN address (sidebar QR code); connection timeouts now fall back to the same-origin proxy and report a network error when every candidate fails
108
+ - Fixed a brief flash of the wrong theme when refreshing or switching tabs; the correct light/dark background is applied on the first frame
109
+ - Fixed character cards on the home page not showing custom avatars
110
+ - Fixed mobile message action buttons, attachment actions while editing, and chat attachment styling
111
+ - Fixed the model provider list sometimes being empty at startup and slow loading of older session messages
112
+
113
+ #### Internal
114
+
115
+ - Added startup snapshot and list-by-field persistence endpoints to reduce startup requests, with tests
116
+ - Added avatar thumbnail and row fingerprint caches to cut repeated rendering work in long lists
117
+ - Unified the modal component and sizing, removing duplicate backdrop-click logic
118
+ - Filled in the persistence endpoint mocks required by the yolo integration test
119
+
6
120
  ## [0.1.13] - 2026-10-21
7
121
 
8
122
  ### 中文
@@ -330,6 +444,8 @@
330
444
  - Vite + React + Zustand + Dexie stack with browser-side local persistence
331
445
 
332
446
  <!-- 链接定义 -->
447
+ [0.1.15]: https://github.com/Rannichan/Tavern-Harness/compare/v0.1.14...v0.1.15
448
+ [0.1.14]: https://github.com/Rannichan/Tavern-Harness/compare/v0.1.13...v0.1.14
333
449
  [0.1.13]: https://github.com/Rannichan/Tavern-Harness/compare/v0.1.12...v0.1.13
334
450
  [0.1.12]: https://github.com/Rannichan/Tavern-Harness/compare/v0.1.11...v0.1.12
335
451
  [0.1.11]: https://github.com/Rannichan/Tavern-Harness/compare/v0.1.10...v0.1.11
package/index.html CHANGED
@@ -14,7 +14,7 @@
14
14
  const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
15
15
  const dark = mode === 'dark' || (mode !== 'light' && systemDark);
16
16
  const root = document.documentElement;
17
- const background = dark ? '#20160E' : '#F7F2E9';
17
+ const background = dark ? '#20160E' : '#F7F0E4';
18
18
  root.dataset.theme = dark ? 'dark' : 'light';
19
19
  root.style.colorScheme = dark ? 'dark' : 'light';
20
20
  root.style.backgroundColor = background;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tavern-harness",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Tavern Harness — 酒馆模式下的本地 AI Agent 助手(MyAgent-Android 的 Web 复刻)",
5
5
  "private": false,
6
6
  "type": "module",
@@ -152,6 +152,18 @@ function saveRecord(collection, key, value) {
152
152
 
153
153
  const getRecord = database.prepare('SELECT value_json, updated_at FROM app_records WHERE collection = ? AND record_key = ?');
154
154
  const listRecords = database.prepare('SELECT record_key, value_json, updated_at FROM app_records WHERE collection = ? ORDER BY record_key');
155
+ const listRecordsByJsonField = database.prepare(`
156
+ SELECT record_key, value_json, updated_at
157
+ FROM app_records
158
+ WHERE collection = ? AND json_extract(value_json, ?) = ?
159
+ ORDER BY record_key
160
+ `);
161
+ const listStartupRecords = database.prepare(`
162
+ SELECT collection, record_key, value_json, updated_at
163
+ FROM app_records
164
+ WHERE collection IN ('settings', 'providers', 'npcs', 'sessions', 'worldBooks', 'tools', 'achievementUnlocks')
165
+ ORDER BY collection, record_key
166
+ `);
155
167
  const deleteRecord = database.prepare('DELETE FROM app_records WHERE collection = ? AND record_key = ?');
156
168
  const clearCollection = database.prepare('DELETE FROM app_records WHERE collection = ?');
157
169
  const nextNumericRecordKey = database.prepare(`
@@ -220,6 +232,31 @@ const server = createServer(async (req, res) => {
220
232
  json(res, 200, { ok: true, leaseId: writeLock.leaseId, lock: lockState(clientId) });
221
233
  return;
222
234
  }
235
+ if (pathname === '/startup/snapshot') {
236
+ if (!clientId || clientId.length > 255) {
237
+ json(res, 400, { ok: false, message: 'A valid clientId is required' });
238
+ return;
239
+ }
240
+ if (!writeLock) setWriteLock({ clientId, leaseId: randomBytes(24).toString('base64url') });
241
+ const collections = {
242
+ settings: [],
243
+ providers: [],
244
+ npcs: [],
245
+ sessions: [],
246
+ worldBooks: [],
247
+ tools: [],
248
+ achievementUnlocks: [],
249
+ };
250
+ for (const row of listStartupRecords.all()) {
251
+ collections[row.collection].push({
252
+ key: row.record_key,
253
+ value: JSON.parse(row.value_json),
254
+ updatedAt: row.updated_at,
255
+ });
256
+ }
257
+ json(res, 200, { ok: true, lock: lockState(clientId), collections });
258
+ return;
259
+ }
223
260
  if (!pathname.startsWith('/records/')) {
224
261
  json(res, 404, { ok: false, message: `Unknown persistence endpoint: ${pathname}` });
225
262
  return;
@@ -231,8 +268,23 @@ const server = createServer(async (req, res) => {
231
268
  return;
232
269
  }
233
270
 
234
- if (pathname === '/records/list') {
235
- const records = listRecords.all(collection).map((row) => ({
271
+ if (pathname === '/records/list' || pathname === '/records/list-by-field') {
272
+ let rows;
273
+ if (pathname === '/records/list-by-field') {
274
+ const { field, value } = body;
275
+ if (typeof field !== 'string' || !/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(field)) {
276
+ json(res, 400, { ok: false, message: 'Invalid record field' });
277
+ return;
278
+ }
279
+ if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') {
280
+ json(res, 400, { ok: false, message: 'Invalid record field value' });
281
+ return;
282
+ }
283
+ rows = listRecordsByJsonField.all(collection, `$.${field}`, value);
284
+ } else {
285
+ rows = listRecords.all(collection);
286
+ }
287
+ const records = rows.map((row) => ({
236
288
  key: row.record_key,
237
289
  value: JSON.parse(row.value_json),
238
290
  updatedAt: row.updated_at,