tavern-harness 0.1.5 → 0.1.7
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 +23 -49
- package/README.zh.md +24 -49
- package/package.json +1 -1
- package/src/core/gameplay.ts +23 -10
- package/src/core/prompts.ts +1 -3
package/README.md
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# Tavern Harness · An AI-Native Role-Playing Sandbox
|
|
2
2
|
|
|
3
|
-
[
|
|
3
|
+
[Product Site](https://rannichan.github.io/Tavern-Harness/) · [Wiki](https://rannichan.github.io/Tavern-Harness/wiki.html) · [GitHub Repository](https://github.com/Rannichan/Tavern-Harness) · [Discord](https://discord.gg/kPSWGeaHx)
|
|
4
4
|
|
|
5
5
|
<a id="run"></a>
|
|
6
6
|
## 1 · Getting Started
|
|
7
7
|
|
|
8
|
-
> Requires **Node.js 18 or later**. Tavern Harness is a frontend web app with an optional local sandbox service—no Docker or database server required.
|
|
9
|
-
|
|
10
8
|
### Option 1: One-Command Start (Recommended)
|
|
11
9
|
|
|
12
10
|
No repository clone or global installation is required:
|
|
@@ -22,9 +20,28 @@ npx tavern-harness --no-open
|
|
|
22
20
|
npx tavern-harness --port 5273
|
|
23
21
|
```
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
### Option 2: Global Installation (Recommended for Regular Use)
|
|
24
|
+
|
|
25
|
+
Install once, then start Tavern Harness from any directory:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g tavern-harness
|
|
29
|
+
tavern-harness
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
To update it:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm update -g tavern-harness
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
To uninstall it:
|
|
26
39
|
|
|
27
|
-
|
|
40
|
+
```bash
|
|
41
|
+
npm uninstall -g tavern-harness
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Option 3: Build from Source
|
|
28
45
|
|
|
29
46
|
```bash
|
|
30
47
|
git clone https://github.com/Rannichan/Tavern-Harness.git
|
|
@@ -82,50 +99,7 @@ The project has no external harness dependency, and its UI is designed for a cle
|
|
|
82
99
|
<a id="gameplay"></a>
|
|
83
100
|
## 3 · Gameplay Examples
|
|
84
101
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
### Example 1: Late-Night Company (No Setup)
|
|
88
|
-
|
|
89
|
-
1. Create a new conversation → select **NPC Chat** → choose **Tavern Keeper** or any custom character.
|
|
90
|
-
2. Chat naturally as you would with a friend. Enable **thinking mode** to see the Keeper's reasoning.
|
|
91
|
-
3. Long-press or right-click any message to edit history, regenerate, inspect raw logs, or fork a branch.
|
|
92
|
-
|
|
93
|
-
### Example 2: Tabletop Dice Rolls (Role-Playing)
|
|
94
|
-
|
|
95
|
-
1. Create an **NPC Chat** for a GM and give the character a prompt such as: “You are the GM for this campaign; use `roll_dice` to decide fate.”
|
|
96
|
-
2. Use the opening message to introduce a story hook. The Keeper can call `roll_dice` and display results such as critical successes or failures.
|
|
97
|
-
3. Add campaign rules and world setting to a Lorebook, then bind it to the conversation to turn the character into a complete world.
|
|
98
|
-
|
|
99
|
-
### Example 3: A Group-Chat Script (Up to Six Characters)
|
|
100
|
-
|
|
101
|
-
1. Create multiple NPCs in the **Character Workshop**, such as a detective, butler, and protagonist.
|
|
102
|
-
2. Create a **Group Chat**, add up to six characters, drag to set their speaking order, or enable random ordering.
|
|
103
|
-
3. Use `@character-name` to choose the next speaker. Enter `/new` to begin a new topic with truncated context, or `/pass` to skip your turn.
|
|
104
|
-
|
|
105
|
-
### Example 4: Create Skills in the Moment
|
|
106
|
-
|
|
107
|
-
1. Ask the Keeper: “Create a skill that accepts a city and returns the weather.” It can call `create_skill` to create a new `template` or `http_get` skill.
|
|
108
|
-
2. New skills are disabled for all characters by default. Enable them for the desired character in **Character Workshop → Skills**.
|
|
109
|
-
3. Skills can use `$read`, `$write`, `$append`, and `$list` to work with the conversation workspace, with no restart required.
|
|
110
|
-
|
|
111
|
-
### Example 5: A Persistent Mini-Game (Advanced Sandbox)
|
|
112
|
-
|
|
113
|
-
1. Ask the Keeper to write a `javascript` skill for an affinity system, saving each turn's value to `state.json` with `$write`.
|
|
114
|
-
2. Create another skill that reads with `$read` and uses `file_write` to generate an HTML dashboard, `dashboard.html`.
|
|
115
|
-
3. Use `file_display` to visualize value changes in a pop-up; workspace files can be previewed in an isolated, resizable HTML view.
|
|
116
|
-
4. Deleting the conversation also removes its `sandbox_workspace/session-<id>/` directory.
|
|
117
|
-
|
|
118
|
-
### Example 6: Organize Files with Shell Skills
|
|
119
|
-
|
|
120
|
-
1. Ask the Keeper in chat to use `ls` to inspect the workspace, `cat` to read files, and `cp` to archive creative work.
|
|
121
|
-
2. Reads and writes must remain in the current conversation workspace. Non-allowlisted commands such as `node` and `git`, plus modifying operations such as `mv` and `rm`, require confirmation.
|
|
122
|
-
3. Regular conversations have read-only access to `public/`. Only the built-in Tavern Keeper can write to the public area, allowing shared assets across conversations.
|
|
123
|
-
|
|
124
|
-
### Example 7: Take an Entire World with You (Import, Export, and Fork)
|
|
125
|
-
|
|
126
|
-
1. Mid-game, choose **Export Game** and decide whether to include conversation history to create a self-contained JSON file.
|
|
127
|
-
2. Send the JSON to a friend, who can use **Import Game** to restore your characters and world in one step. Conflicts create copies instead of overwriting data.
|
|
128
|
-
3. Or create a branch / fork to explore multiple endings from the same history.
|
|
102
|
+
See the [interactive gameplay demos](https://rannichan.github.io/Tavern-Harness/#demo).
|
|
129
103
|
|
|
130
104
|
<a id="feedback"></a>
|
|
131
105
|
## 4 · Community and Feedback
|
package/README.zh.md
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# Tavern Harness · AI 原生的角色扮演沙盒
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[产品主页](https://rannichan.github.io/Tavern-Harness/) · [Wiki](https://rannichan.github.io/Tavern-Harness/wiki.html) · [GitHub 仓库](https://github.com/Rannichan/Tavern-Harness) · [Discord 交流](https://discord.gg/kPSWGeaHx)
|
|
4
4
|
|
|
5
5
|
<a id="run"></a>
|
|
6
6
|
## 1 · 如何运行
|
|
7
7
|
|
|
8
|
-
> 需要本机装有 **Node.js ≥ 18**。Tavern Harness 是一个纯前端 Web 应用 + 可选的本地沙箱服务,无需 Docker、无需数据库服务。
|
|
9
|
-
|
|
10
8
|
### 方式一:一键启动(推荐)
|
|
11
9
|
|
|
12
10
|
无需克隆仓库或全局安装:
|
|
@@ -22,9 +20,29 @@ npx tavern-harness --no-open
|
|
|
22
20
|
npx tavern-harness --port 5273
|
|
23
21
|
```
|
|
24
22
|
|
|
25
|
-
服务仅监听本机。使用 shell 技能时,命令会以启动命令的当前用户权限在本机执行;请仅运行来自可信来源的版本,并审阅每次命令确认弹窗。
|
|
26
23
|
|
|
27
|
-
###
|
|
24
|
+
### 方式二:全局安装(推荐长期使用)
|
|
25
|
+
|
|
26
|
+
安装一次后,可从任意目录启动 Tavern Harness:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install -g tavern-harness
|
|
30
|
+
tavern-harness
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
更新:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm update -g tavern-harness
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
卸载:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm uninstall -g tavern-harness
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 方式三:从源码安装
|
|
28
46
|
|
|
29
47
|
```bash
|
|
30
48
|
git clone https://github.com/Rannichan/Tavern-Harness.git
|
|
@@ -82,50 +100,7 @@ npm run dev # 开发服务器默认端口 5173,被占用时自动顺延
|
|
|
82
100
|
<a id="gameplay"></a>
|
|
83
101
|
## 3 · 玩法示例
|
|
84
102
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
### 示例一:深夜陪伴(零门槛)
|
|
88
|
-
|
|
89
|
-
1. 新建对话 → 选「NPC 对话」→ 角色「酒馆老板」(或任意自定义角色)
|
|
90
|
-
2. 直接开聊,像跟朋友一样对话;开启**思考模式**还能看到老板的内心盘算
|
|
91
|
-
3. 长按 / 右键任意消息:编辑历史、重新生成、查看原始日志、Fork 分支
|
|
92
|
-
|
|
93
|
-
### 示例二:跑团掷骰(角色扮演)
|
|
94
|
-
|
|
95
|
-
1. 新建「NPC 对话」,角色当 GM:人设写上「你是主持本团的 GM,用 roll_dice 决定命运」
|
|
96
|
-
2. 开场白抛出剧情钩子;老板会自动调用 `roll_dice`,并在消息里呈现「大成功!/ 大失败!」
|
|
97
|
-
3. 把团规、世界观写进 Lorebook 绑定到该会话,人设即刻变成一个完整世界
|
|
98
|
-
|
|
99
|
-
### 示例三:群聊剧本(最多 6 人一台戏)
|
|
100
|
-
|
|
101
|
-
1. 「角色工坊」里建多个 NPC(如侦探、管家、女主)
|
|
102
|
-
2. 新建「群聊」对话,添加至多 6 个角色:拖拽设定发言顺序,或开启随机洗牌
|
|
103
|
-
3. 用 `@角色名` 点名下一个发言者;输入 `/new` 开新话题(截断上下文);输入 `/pass` 跳过自己本轮发言
|
|
104
|
-
|
|
105
|
-
### 示例四:现场造技能(自造能力)
|
|
106
|
-
|
|
107
|
-
1. 对老板说:「帮我生成一个技能,输入城市名返回天气」,它会调用 `create_skill` 写出 `template` / `http_get` 实现的新技能
|
|
108
|
-
2. 新技能默认未启用于任何角色,去「角色工坊 → 技能表」为要用的角色勾选生效
|
|
109
|
-
3. 技能支持 `$read` / `$write` / `$append` / `$list` 桥接会话工作区,无需重启即时生效
|
|
110
|
-
|
|
111
|
-
### 示例五:持久化小游戏(沙盒高阶)
|
|
112
|
-
|
|
113
|
-
1. 让老板写一个 `javascript` 技能实现好感度系统:每轮数值经 `$write` 存进工作目录 `state.json`
|
|
114
|
-
2. 再写一个读取 `$read` 的技能,用 `file_write` 生成 HTML 仪表盘 `dashboard.html`
|
|
115
|
-
3. `file_display` 直接弹窗图形化展示数值变化(HTML 沙箱渲染、可缩放、可画中画),工作区内文件均可预览
|
|
116
|
-
4. 删除会话时磁盘目录 `sandbox_workspace/session-<id>/` 自动一并清理
|
|
117
|
-
|
|
118
|
-
### 示例六:整理文件(shell)
|
|
119
|
-
|
|
120
|
-
1. 对话里让老板 `ls` 看工作区、`cat` 读文件、`cp` 归档你的创作文稿
|
|
121
|
-
2. 读写必须留在本会话工作目录内;白名单外命令(如 `node`、`git`)与 `mv` / `rm` 类修改操作都会先弹窗确认再执行
|
|
122
|
-
3. 普通会话对公共目录 `public/` 只读——只有内置「酒馆老板」的会话能写入公共区,供所有会话共享素材
|
|
123
|
-
|
|
124
|
-
### 示例七:携带整个世界(导入导出 / Fork)
|
|
125
|
-
|
|
126
|
-
1. 一局游戏玩到中期,「导出游戏」勾选是否包含对话历史,得到一个自包含 JSON
|
|
127
|
-
2. 把 JSON 发给朋友,对方「导入游戏」一键还原你的角色与世界(冲突自动建副本不覆盖)
|
|
128
|
-
3. 或自己「创建分支 / Fork」:同一段历史分岔出多个平行结局
|
|
103
|
+
请查看[互动玩法演示](https://rannichan.github.io/Tavern-Harness/#demo)。
|
|
129
104
|
|
|
130
105
|
<a id="feedback"></a>
|
|
131
106
|
## 4 · 交流反馈
|
package/package.json
CHANGED
package/src/core/gameplay.ts
CHANGED
|
@@ -371,18 +371,21 @@ export async function importGameplay(payload: unknown): Promise<ImportGameplayRe
|
|
|
371
371
|
// 创建对话的同时预建其专属工作目录
|
|
372
372
|
await ensureSessionWorkspaceDir(workspaceDir);
|
|
373
373
|
|
|
374
|
-
// ---- 5. 参与者(玩家恒定 -1;NPC participantId = 新 npc id
|
|
374
|
+
// ---- 5. 参与者(玩家恒定 -1;NPC participantId = 新 npc id,保留源座位顺序) ----
|
|
375
375
|
const participants: ChatParticipant[] = [];
|
|
376
|
-
participants.push({
|
|
377
|
-
sessionId,
|
|
378
|
-
participantId: -1,
|
|
379
|
-
kind: 'PLAYER',
|
|
380
|
-
npcId: null,
|
|
381
|
-
displayName: '用户',
|
|
382
|
-
seatOrder: 0,
|
|
383
|
-
});
|
|
384
376
|
const sortedSrcParticipants = [...data.sessionParticipants].sort((a, b) => a.seatOrder - b.seatOrder);
|
|
385
377
|
for (const sp of sortedSrcParticipants) {
|
|
378
|
+
if (sp.kind === 'PLAYER') {
|
|
379
|
+
participants.push({
|
|
380
|
+
sessionId,
|
|
381
|
+
participantId: -1,
|
|
382
|
+
kind: 'PLAYER',
|
|
383
|
+
npcId: null,
|
|
384
|
+
displayName: sp.displayName || '用户',
|
|
385
|
+
seatOrder: sp.seatOrder,
|
|
386
|
+
});
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
386
389
|
if (sp.kind !== 'NPC' || sp.npcId == null) continue;
|
|
387
390
|
const newNpcId = sourceNpcIdToNew.get(sp.npcId) ?? null;
|
|
388
391
|
if (newNpcId == null) continue;
|
|
@@ -392,7 +395,17 @@ export async function importGameplay(payload: unknown): Promise<ImportGameplayRe
|
|
|
392
395
|
kind: 'NPC',
|
|
393
396
|
npcId: newNpcId,
|
|
394
397
|
displayName: sp.displayName || '',
|
|
395
|
-
seatOrder:
|
|
398
|
+
seatOrder: sp.seatOrder,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
if (!participants.some((p) => p.kind === 'PLAYER')) {
|
|
402
|
+
participants.push({
|
|
403
|
+
sessionId,
|
|
404
|
+
participantId: -1,
|
|
405
|
+
kind: 'PLAYER',
|
|
406
|
+
npcId: null,
|
|
407
|
+
displayName: '用户',
|
|
408
|
+
seatOrder: 0,
|
|
396
409
|
});
|
|
397
410
|
}
|
|
398
411
|
await db.participants.bulkAdd(participants);
|
package/src/core/prompts.ts
CHANGED
|
@@ -44,9 +44,7 @@ export function buildGroupSystemPrompt(
|
|
|
44
44
|
? `The other participants are: ${others.join(', ')}.\nThe user (a participant named "${playerName}") may speak at any moment — when they do, respond naturally as ${activeSpeakerName}.\n`
|
|
45
45
|
: '') +
|
|
46
46
|
`Messages prefixed with [${translate('prompt.roleTag')}] are utterances by that participant; messages prefixed with [${playerName}] are the user's.\n` +
|
|
47
|
-
(
|
|
48
|
-
? `End your reply by mentioning exactly one next participant with @name. If your reply already mentions a participant, do not add another mention.\n`
|
|
49
|
-
: `When you need to direct the next speaker, you may use @${translate('prompt.roleTag')} in your reply to call on another participant.\n`) +
|
|
47
|
+
`When you need to direct the next speaker, you may use @${translate('prompt.roleTag')} in your reply to call on another participant.\n` +
|
|
50
48
|
`\n=== ${activeSpeakerName} ===\n` +
|
|
51
49
|
activeNpcPrompt +
|
|
52
50
|
(worldBookContent ? `\n\n=== ${translate('prompt.worldBook')} ===\n${worldBookContent}` : '') +
|