dsh-plugin-beyond-simulator 1.0.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/LICENSE +232 -0
- package/README.md +193 -0
- package/dsh-plugin/cordis.patch.yml +228 -0
- package/dsh-plugin/dist/client.js +26 -0
- package/dsh-plugin/dist/index.js +107 -0
- package/dsh-plugin/dist/play-renderer.js +936 -0
- package/dsh-plugin/dist/preset.js +1 -0
- package/dsh-plugin/dist/skill.js +1 -0
- package/dsh-plugin/dist/worker.js +105 -0
- package/dsh-plugin/lib/play.html +339 -0
- package/dsh-plugin/presets/wonderland-lua-builder/README.md +21 -0
- package/dsh-plugin/presets/wonderland-lua-builder/agent.cordis.yml +214 -0
- package/dsh-plugin/presets/wonderland-lua-builder/evals/agent-behavior.md +143 -0
- package/dsh-plugin/presets/wonderland-lua-builder/preset.yml +2 -0
- package/dsh-plugin/presets/wonderland-lua-builder/skills/qxqy-game-studio/SKILL.md +44 -0
- package/dsh-plugin/presets/wonderland-lua-builder/skills/qxqy-game-studio/references/design-and-tests.md +167 -0
- package/dsh-plugin/presets/wonderland-lua-builder/skills/qxqy-game-studio/references/prototype-art-playtest.md +38 -0
- package/dsh-plugin/presets/wonderland-lua-builder/skills/qxqy-game-studio/references/workflow.md +66 -0
- package/dsh-plugin/skill.md +166 -0
- package/package.json +56 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>千星沙箱 · 独立试玩</title>
|
|
7
|
+
<style>
|
|
8
|
+
*{box-sizing:border-box}html,body{width:100%;height:100%;margin:0;overflow:hidden}
|
|
9
|
+
body{--bg:#0c1422;--panel:#101a2a;--panel2:#152238;--line:#263750;--text:#edf3ff;--muted:#8f9caf;--workspace:#111c2c;--input:#0d1726;--blue:#4382f3;color:var(--text);background:var(--bg);font:13px/1.45 Inter,"Microsoft YaHei UI","Microsoft YaHei",sans-serif}
|
|
10
|
+
body[data-theme=light]{--bg:#eef1f5;--panel:#fff;--panel2:#f3f5f8;--line:#d5dbe4;--text:#20252c;--muted:#68717e;--workspace:#d9dee6;--input:#fff}
|
|
11
|
+
button{font:inherit;color:inherit;cursor:pointer}button:disabled{opacity:.42;cursor:default}.app{height:100%;display:grid;grid-template-rows:56px minmax(0,1fr) 34px;background:var(--bg)}
|
|
12
|
+
.bar{display:grid;grid-template-columns:minmax(250px,1fr) auto minmax(360px,1fr);align-items:center;gap:12px;padding:0 14px;border-bottom:1px solid var(--line);background:linear-gradient(180deg,var(--panel2),var(--panel));box-shadow:0 1px 12px #0003;z-index:2}
|
|
13
|
+
.brand,.device,.view-tabs,.actions,.status{display:flex;align-items:center}.brand{gap:10px;min-width:0}.icon{width:30px;height:30px;display:grid;place-items:center;border:1px solid var(--line);border-radius:6px;background:var(--input);font-size:11px}.brand strong{font-size:16px}.device{gap:8px;margin-left:18px;color:var(--muted)}.device b{color:var(--text);font-weight:500}.view-tabs{height:36px;padding:3px;border:1px solid var(--line);border-radius:7px;background:var(--input)}.view-tabs button{min-width:100px;height:28px;padding:0 16px;border:0;border-radius:5px;background:transparent;color:var(--muted)}.view-tabs button.active{color:#fff;background:linear-gradient(#4c87fa,#376fd7);box-shadow:0 0 0 1px #5e90ef55}.actions{justify-content:flex-end;gap:7px}.actions button{height:34px;padding:0 12px;border:1px solid var(--line);border-radius:6px;background:var(--input)}.actions button:hover:not(:disabled){border-color:var(--blue)}.actions .stop{color:#f17884}.actions .primary{color:#fff;border-color:#3265d8;background:linear-gradient(#4c87fa,#376fd7)}.devsel,.playersel,.viewsel{height:30px;width:150px;margin-left:12px;padding:0 4px;border:1px solid var(--line);border-radius:6px;background:var(--input);color:var(--text);text-overflow:ellipsis}.playersel,.viewsel{width:108px}
|
|
14
|
+
.views{position:relative;min-width:0;min-height:0}.play-view{position:absolute;inset:0}.debug-view{position:absolute;inset:0;display:grid;grid-template-columns:minmax(280px,32%) minmax(420px,1fr);background:var(--bg)}.panel{min-height:0;background:color-mix(in srgb,var(--panel) 96%,transparent)}.tree-panel{display:grid;grid-template-rows:42px minmax(0,1fr) 42px minmax(160px,32%);border-right:1px solid var(--line)}.log-stack{min-height:0;display:grid;grid-template-rows:minmax(0,1fr) minmax(0,1fr) auto}.log-panel{display:grid;grid-template-rows:42px minmax(0,1fr);min-height:0}.log-panel + .log-panel{border-top:1px solid var(--line)}.case-panel{display:grid;grid-template-rows:42px auto;border-top:1px solid var(--line)}.case-body{display:grid;grid-template-columns:1fr auto auto;gap:8px;align-items:center;padding:10px 14px}.case-body input,.case-body textarea{width:100%;min-height:34px;padding:6px 8px;border:1px solid var(--line);border-radius:6px;background:var(--input);color:var(--text);font:12px/1.4 Consolas,"Microsoft YaHei UI",monospace}.case-body textarea{min-height:72px;resize:vertical}.vars{min-height:0;overflow:auto;padding:8px 10px}.var-row{display:grid;grid-template-columns:72px minmax(72px,1fr) 52px minmax(80px,1.2fr);gap:6px;align-items:center;padding:5px 4px;border-bottom:1px solid color-mix(in srgb,var(--line) 70%,transparent);font:12px/1.4 Consolas,"Microsoft YaHei UI",monospace}.var-row b{font-weight:650;color:var(--text)}.var-empty{padding:14px 8px;color:var(--muted)}.panel-head{display:flex;align-items:center;padding:0 14px;border-bottom:1px solid var(--line);font-weight:650}.panel-head small{margin-left:auto;color:var(--muted);font-weight:400}.tree{min-height:0;overflow:auto;padding:8px 7px}.log{min-height:0;overflow:auto;padding:12px 16px;white-space:pre-wrap;color:var(--muted);font:12px/1.8 Consolas,"Microsoft YaHei UI",monospace}.log:empty:before{content:"暂无日志。";color:var(--muted)}#log:empty:before{content:"暂无客户端脚本日志。Lua print / printerr 会显示在这里。"}#serverLog:empty:before{content:"暂无服务端日志。变量缺失、越界和信号投递记录在这里,不会混入脚本输出。"}
|
|
15
|
+
.workspace{position:absolute;inset:0;min-width:0;min-height:0;display:flex;align-items:center;justify-content:center;padding:28px;overflow:hidden;background-color:var(--workspace);background-image:radial-gradient(#ffffff0c 1px,transparent 1px),linear-gradient(135deg,#ffffff07 25%,transparent 25%,transparent 75%,#00000008 75%);background-size:4px 4px,24px 24px}.stage-shell{position:relative;filter:drop-shadow(0 14px 28px #0005)}.stage-shell.veiled{visibility:hidden}.loading{position:absolute;inset:0;z-index:3;display:flex;align-items:center;justify-content:center;gap:10px;color:var(--muted);background:var(--bg)}.loading .spin{width:14px;height:14px;border-radius:50%;border:2px solid #4382f338;border-top-color:var(--blue);animation:qxqy-spin .9s linear infinite}@keyframes qxqy-spin{to{transform:rotate(1turn)}}.stage-label{position:absolute;left:0;top:-23px;color:var(--muted);font-size:11px}.stage{position:relative;overflow:hidden;border:1px solid #4d7899;outline:1px solid #0005;background:radial-gradient(circle at 50% 45%,#123a6b,#061934 72%,#071426);touch-action:none;user-select:none}.fatal{position:absolute;inset:0;display:grid;place-items:center;padding:24px;color:#ef7784;background:color-mix(in srgb,var(--workspace) 88%,transparent)}.hidden{display:none!important}
|
|
16
|
+
.stage-canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
|
|
17
|
+
.status{gap:14px;padding:0 14px;border-top:1px solid var(--line);background:var(--panel);color:var(--muted);font-size:11px}.status .live{color:#62d398}.status .error{color:#f07480}.spacer{flex:1}.metric{padding-left:14px;border-left:1px solid var(--line)}
|
|
18
|
+
@media(max-width:1050px){.bar{grid-template-columns:1fr auto 1fr}.device{display:none}.devsel{width:118px;margin-left:8px}.view-tabs button{min-width:78px}.workspace{padding:20px}.debug-view{grid-template-columns:260px minmax(360px,1fr)}}
|
|
19
|
+
@media(max-width:760px){.debug-view{grid-template-columns:1fr}.tree-panel{display:none}.brand strong{font-size:14px}.brand .devsel{width:104px;margin-left:6px}.actions button{padding:0 9px}.actions #theme{display:none}.view-tabs button{min-width:64px;padding:0 10px}}
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<div class="app">
|
|
24
|
+
<header class="bar">
|
|
25
|
+
<div class="brand"><span class="icon">UI</span><strong>模拟器</strong><select id="deviceSel" class="devsel hidden" aria-label="切换设备画布" title="切换设备画布:以新画布重建运行时,GetUICanvasSize 与预设像素一致"></select><select id="playerCountSel" class="playersel hidden" aria-label="试玩人数" title="试玩人数:最多 8 人,每人一份独立客户端"></select><select id="playerViewSel" class="viewsel hidden" aria-label="当前玩家视角" title="切换当前玩家视角:输入、画面和 PlayerSelf 都跟随该玩家"></select><span class="device">设备 <b id="resolution">准备中…</b></span></div>
|
|
26
|
+
<div class="view-tabs" role="tablist" aria-label="试玩页面"><button class="active" data-view="play" role="tab">试玩</button><button data-view="log" role="tab">日志</button></div>
|
|
27
|
+
<div class="actions"><button id="pause">Ⅱ 暂停运行</button><button id="step" disabled title="暂停后可单帧调试">▷ 单帧调试</button><button class="stop" id="stop">■ 结束试玩</button><button class="primary hidden" id="restart">↻ 重新开始</button><button id="theme" title="切换亮暗主题">◐</button><button id="close">返回编辑</button></div>
|
|
28
|
+
</header>
|
|
29
|
+
<div class="views">
|
|
30
|
+
<main class="workspace play-view" id="workspace"><div class="stage-shell veiled" id="shell"><div class="stage-label" id="stageLabel"></div><div class="stage" id="stage"><canvas id="cv" class="stage-canvas"></canvas></div></div><div class="loading" id="loading"><span class="spin"></span>正在准备试玩…</div><div class="fatal hidden" id="fatal"></div></main>
|
|
31
|
+
<section class="debug-view hidden" id="logView">
|
|
32
|
+
<aside class="panel tree-panel"><div class="panel-head">运行时场景树 <small id="nodeCount">0 节点</small></div><div class="tree" id="sceneTree"></div><div class="panel-head">服务端变量 <small id="varCount">0 项</small></div><div class="vars" id="serverVars"></div></aside>
|
|
33
|
+
<div class="log-stack"><main class="panel log-panel"><div class="panel-head">客户端脚本日志 <small>最近 100 条</small></div><div class="log" id="log"></div></main><aside class="panel log-panel"><div class="panel-head">服务端日志 <small>变量 / 信号</small></div><div class="log" id="serverLog"></div></aside><section class="panel case-panel"><div class="panel-head">自动测用例 <small id="caseHint">先试玩,再保存回放</small></div><div class="case-body"><textarea id="caseAsserts" spellcheck="false" placeholder='断言 JSON 数组,例如 [{"kind":"log","contains":"clicked"}]'></textarea><button id="saveCase">保存用例</button><button id="runCase">回放用例</button></div></section></div>
|
|
34
|
+
</section>
|
|
35
|
+
</div>
|
|
36
|
+
<footer class="status"><span class="live" id="state">● 正在启动</span><span id="viewMode">页面:试玩</span><span class="spacer"></span><span class="metric" id="frame">Frame 0</span><span class="metric" id="time">0.00s</span></footer>
|
|
37
|
+
</div>
|
|
38
|
+
<script type="module">
|
|
39
|
+
import { PixiPlayRenderer, createPlaySession } from '/qxqy-simulator/play-renderer.js'
|
|
40
|
+
|
|
41
|
+
(() => {
|
|
42
|
+
'use strict'
|
|
43
|
+
const API = '/qxqy-simulator/api/play'
|
|
44
|
+
const sessionId = decodeURIComponent(location.hash.slice(1))
|
|
45
|
+
const $ = (id) => document.getElementById(id)
|
|
46
|
+
const stage = $('stage')
|
|
47
|
+
const cv = $('cv')
|
|
48
|
+
const renderer = new PixiPlayRenderer(cv)
|
|
49
|
+
const shell = $('shell')
|
|
50
|
+
const workspace = $('workspace')
|
|
51
|
+
const logView = $('logView')
|
|
52
|
+
let theme = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
53
|
+
let fittedCanvas = ''
|
|
54
|
+
|
|
55
|
+
function applyTheme() { document.body.dataset.theme = theme }
|
|
56
|
+
applyTheme()
|
|
57
|
+
|
|
58
|
+
function setView(view) {
|
|
59
|
+
const showLog = view === 'log'
|
|
60
|
+
workspace.classList.toggle('hidden', showLog)
|
|
61
|
+
logView.classList.toggle('hidden', !showLog)
|
|
62
|
+
$('viewMode').textContent = showLog ? '页面:日志' : '页面:试玩'
|
|
63
|
+
for (const button of document.querySelectorAll('[data-view]')) button.classList.toggle('active', button.dataset.view === view)
|
|
64
|
+
if (!showLog) requestAnimationFrame(() => fitStage(true))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function api(action, args = {}) {
|
|
68
|
+
const response = await fetch(API, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'content-type': 'application/json' },
|
|
71
|
+
body: JSON.stringify({ sessionId, action, args }),
|
|
72
|
+
})
|
|
73
|
+
const envelope = await response.json().catch(() => null)
|
|
74
|
+
if (!response.ok || !envelope || envelope.ok !== true) throw new Error(envelope?.error || '试玩请求失败')
|
|
75
|
+
return envelope.value
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function renderVars(rows) {
|
|
79
|
+
const host = $('serverVars')
|
|
80
|
+
host.replaceChildren()
|
|
81
|
+
const list = Array.isArray(rows) ? rows : []
|
|
82
|
+
$('varCount').textContent = list.length + ' 项'
|
|
83
|
+
if (!list.length) {
|
|
84
|
+
const empty = document.createElement('div')
|
|
85
|
+
empty.className = 'var-empty'
|
|
86
|
+
empty.textContent = '暂无自定义变量。脚本 Get/Set 或 serverSet 之后会出现在这里。'
|
|
87
|
+
host.appendChild(empty)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
for (const row of list) {
|
|
91
|
+
const el = document.createElement('div')
|
|
92
|
+
el.className = 'var-row'
|
|
93
|
+
const type = row.type || '—'
|
|
94
|
+
const value = row.value === undefined ? 'nil' : JSON.stringify(row.value)
|
|
95
|
+
const entityCell = document.createElement('span')
|
|
96
|
+
entityCell.textContent = row.entityLabel || row.entityType || ''
|
|
97
|
+
const nameCell = document.createElement('b')
|
|
98
|
+
nameCell.textContent = row.name || ''
|
|
99
|
+
const typeCell = document.createElement('span')
|
|
100
|
+
typeCell.textContent = type
|
|
101
|
+
const valueCell = document.createElement('span')
|
|
102
|
+
valueCell.textContent = value
|
|
103
|
+
el.append(entityCell, nameCell, typeCell, valueCell)
|
|
104
|
+
host.appendChild(el)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function renderTree(snapshot) {
|
|
109
|
+
const host = $('sceneTree')
|
|
110
|
+
host.replaceChildren()
|
|
111
|
+
const empty = document.createElement('div')
|
|
112
|
+
empty.className = 'var-empty'
|
|
113
|
+
empty.textContent = '完整运行树请用 Agent 的 inspect 快照。舞台按左树上方盖住下方绘制。'
|
|
114
|
+
host.appendChild(empty)
|
|
115
|
+
$('nodeCount').textContent = (snapshot?.scene?.count || snapshot?.paint?.length || 0) + ' 个可见图元'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function fitStage(force = false) {
|
|
119
|
+
const snapshot = play.snapshot
|
|
120
|
+
if (!snapshot) return
|
|
121
|
+
const key = snapshot.canvasWidth + '×' + snapshot.canvasHeight
|
|
122
|
+
if (!force && (fittedCanvas === key || !Number.isFinite(snapshot.canvasWidth) || !Number.isFinite(snapshot.canvasHeight))) return
|
|
123
|
+
fittedCanvas = key
|
|
124
|
+
const maxW = Math.max(120, workspace.clientWidth - 48)
|
|
125
|
+
const maxH = Math.max(80, workspace.clientHeight - 48)
|
|
126
|
+
const scale = Math.min(maxW / snapshot.canvasWidth, maxH / snapshot.canvasHeight)
|
|
127
|
+
if (!Number.isFinite(scale) || scale <= 0) return
|
|
128
|
+
shell.style.width = (snapshot.canvasWidth * scale) + 'px'
|
|
129
|
+
shell.style.height = (snapshot.canvasHeight * scale) + 'px'
|
|
130
|
+
stage.style.width = '100%'
|
|
131
|
+
stage.style.height = '100%'
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function revealStage() {
|
|
135
|
+
shell.classList.remove('veiled')
|
|
136
|
+
$('loading').classList.add('hidden')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function applyStatus(next) {
|
|
140
|
+
$('frame').textContent = 'Frame ' + (next.frame || 0)
|
|
141
|
+
$('time').textContent = Number(next.time || 0).toFixed(2) + 's'
|
|
142
|
+
$('pause').textContent = next.paused ? '▶ 继续运行' : 'Ⅱ 暂停运行'
|
|
143
|
+
$('pause').disabled = false
|
|
144
|
+
$('step').disabled = !next.paused
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function syncDeviceSelect(next) {
|
|
148
|
+
const sel = $('deviceSel')
|
|
149
|
+
const presets = Array.isArray(next.canvasPresets) ? next.canvasPresets : []
|
|
150
|
+
if (!presets.length || !next.canvasId) {
|
|
151
|
+
sel.replaceChildren()
|
|
152
|
+
sel.classList.add('hidden')
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
const stale = sel.options.length !== presets.length
|
|
156
|
+
|| [...sel.options].some((opt, i) => opt.value !== presets[i].id)
|
|
157
|
+
if (stale) {
|
|
158
|
+
sel.replaceChildren(...presets.map((preset) => {
|
|
159
|
+
const opt = document.createElement('option')
|
|
160
|
+
opt.value = preset.id
|
|
161
|
+
opt.textContent = preset.label || (preset.id + ' · ' + preset.width + '×' + preset.height)
|
|
162
|
+
return opt
|
|
163
|
+
}))
|
|
164
|
+
}
|
|
165
|
+
if (sel.value !== next.canvasId) sel.value = next.canvasId
|
|
166
|
+
sel.classList.remove('hidden')
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function fillCountSelect(sel, count) {
|
|
170
|
+
const n = Math.max(1, Math.min(8, Number(count) || 1))
|
|
171
|
+
if (sel.options.length !== 8) {
|
|
172
|
+
sel.replaceChildren(...Array.from({ length: 8 }, (_, i) => {
|
|
173
|
+
const opt = document.createElement('option')
|
|
174
|
+
opt.value = String(i + 1)
|
|
175
|
+
opt.textContent = (i + 1) + ' 人'
|
|
176
|
+
return opt
|
|
177
|
+
}))
|
|
178
|
+
}
|
|
179
|
+
sel.value = String(n)
|
|
180
|
+
sel.classList.remove('hidden')
|
|
181
|
+
return n
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function syncPlayerSelects(next) {
|
|
185
|
+
const countSel = $('playerCountSel')
|
|
186
|
+
const viewSel = $('playerViewSel')
|
|
187
|
+
const count = fillCountSelect(countSel, next.playerCount || 1)
|
|
188
|
+
const players = Array.isArray(next.players) && next.players.length
|
|
189
|
+
? next.players
|
|
190
|
+
: Array.from({ length: count }, (_, i) => ({ index: i + 1, label: '玩家' + (i + 1) }))
|
|
191
|
+
const stale = viewSel.options.length !== players.length
|
|
192
|
+
|| [...viewSel.options].some((opt, i) => Number(opt.value) !== players[i].index)
|
|
193
|
+
if (stale) {
|
|
194
|
+
viewSel.replaceChildren(...players.map((player) => {
|
|
195
|
+
const opt = document.createElement('option')
|
|
196
|
+
opt.value = String(player.index)
|
|
197
|
+
opt.textContent = player.label || ('玩家' + player.index)
|
|
198
|
+
return opt
|
|
199
|
+
}))
|
|
200
|
+
}
|
|
201
|
+
const view = Number(next.viewPlayerIndex) || 1
|
|
202
|
+
if (viewSel.value !== String(view)) viewSel.value = String(view)
|
|
203
|
+
viewSel.classList.toggle('hidden', count < 2)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function renderChrome(next) {
|
|
207
|
+
$('fatal').classList.add('hidden')
|
|
208
|
+
fitStage()
|
|
209
|
+
syncDeviceSelect(next)
|
|
210
|
+
syncPlayerSelects(next)
|
|
211
|
+
$('resolution').textContent = next.canvasWidth + '×' + next.canvasHeight
|
|
212
|
+
const viewLabel = next.playerCount > 1 ? (' · 玩家' + (next.viewPlayerIndex || 1)) : ''
|
|
213
|
+
$('stageLabel').textContent = next.canvasWidth + '×' + next.canvasHeight + viewLabel + ' · 点击输入'
|
|
214
|
+
applyStatus(next)
|
|
215
|
+
$('stop').disabled = false
|
|
216
|
+
$('restart').classList.add('hidden')
|
|
217
|
+
$('state').textContent = next.mountError ? '⚠ 脚本挂载失败' : next.paused ? 'Ⅱ 已暂停' : '● 试玩运行中'
|
|
218
|
+
$('state').className = next.mountError ? 'error' : 'live'
|
|
219
|
+
renderTree(next)
|
|
220
|
+
renderVars(next.serverVars)
|
|
221
|
+
$('log').textContent = (next.logs || []).slice(-100).map((entry) => Number(entry.time || 0).toFixed(2).padStart(7, ' ') + ' [' + entry.level + '] ' + entry.text).join('\n')
|
|
222
|
+
$('serverLog').textContent = (next.serverLogs || next.server?.logs || []).slice(-100).map((entry) => Number(entry.time || 0).toFixed(2).padStart(7, ' ') + ' [' + entry.level + '] ' + entry.text).join('\n')
|
|
223
|
+
$('caseHint').textContent = (next.history && next.history.length ? ('已记录 ' + next.history.length + ' 步') : '先试玩,再保存回放')
|
|
224
|
+
if (next.mountError) setView('log')
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function showError(error) {
|
|
228
|
+
$('loading').classList.add('hidden')
|
|
229
|
+
$('fatal').classList.remove('hidden')
|
|
230
|
+
$('fatal').textContent = error?.message || String(error)
|
|
231
|
+
$('state').className = 'error'
|
|
232
|
+
$('state').textContent = '⚠ 试玩错误'
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const play = createPlaySession({
|
|
236
|
+
renderer,
|
|
237
|
+
api,
|
|
238
|
+
onSnapshot: renderChrome,
|
|
239
|
+
onStatus: applyStatus,
|
|
240
|
+
onError: showError,
|
|
241
|
+
})
|
|
242
|
+
play.bindInput(stage, { keysOn: window })
|
|
243
|
+
|
|
244
|
+
$('step').addEventListener('click', () => void play.act('step', { dt: 1 / 30 }))
|
|
245
|
+
$('pause').addEventListener('click', () => void play.act(play.snapshot?.paused ? 'resume' : 'pause'))
|
|
246
|
+
$('stop').addEventListener('click', async () => {
|
|
247
|
+
if (!play.running) return
|
|
248
|
+
try { await play.stop() } catch (error) { showError(error); return }
|
|
249
|
+
$('state').textContent = '■ 试玩已结束,可重新开始或返回编辑'
|
|
250
|
+
$('state').className = ''
|
|
251
|
+
$('pause').disabled = true
|
|
252
|
+
$('step').disabled = true
|
|
253
|
+
$('stop').disabled = true
|
|
254
|
+
$('restart').classList.remove('hidden')
|
|
255
|
+
})
|
|
256
|
+
$('restart').addEventListener('click', async () => {
|
|
257
|
+
try {
|
|
258
|
+
fittedCanvas = ''
|
|
259
|
+
const keepArgs = {}
|
|
260
|
+
const snapshot = play.snapshot
|
|
261
|
+
if (snapshot?.canvasId) keepArgs.canvasId = snapshot.canvasId
|
|
262
|
+
if (snapshot?.playerCount) keepArgs.playerCount = snapshot.playerCount
|
|
263
|
+
if (snapshot?.viewPlayerIndex) keepArgs.viewPlayerIndex = snapshot.viewPlayerIndex
|
|
264
|
+
await play.start(keepArgs)
|
|
265
|
+
fitStage(true)
|
|
266
|
+
} catch (error) { showError(error) }
|
|
267
|
+
})
|
|
268
|
+
$('playerCountSel').addEventListener('change', async () => {
|
|
269
|
+
const playerCount = Number($('playerCountSel').value) || 1
|
|
270
|
+
try {
|
|
271
|
+
shell.classList.add('veiled')
|
|
272
|
+
$('loading').classList.remove('hidden')
|
|
273
|
+
fittedCanvas = ''
|
|
274
|
+
const args = { playerCount, viewPlayerIndex: 1 }
|
|
275
|
+
if (play.snapshot?.canvasId) args.canvasId = play.snapshot.canvasId
|
|
276
|
+
await play.start(args)
|
|
277
|
+
fitStage(true)
|
|
278
|
+
revealStage()
|
|
279
|
+
} catch (error) { showError(error) }
|
|
280
|
+
})
|
|
281
|
+
$('playerViewSel').addEventListener('change', async () => {
|
|
282
|
+
const playerIndex = Number($('playerViewSel').value) || 1
|
|
283
|
+
if (!play.snapshot || !play.running) return
|
|
284
|
+
try { await play.view({ playerIndex }) }
|
|
285
|
+
catch (error) { showError(error) }
|
|
286
|
+
})
|
|
287
|
+
$('deviceSel').addEventListener('change', async () => {
|
|
288
|
+
const canvasId = $('deviceSel').value
|
|
289
|
+
if (!canvasId) return
|
|
290
|
+
try {
|
|
291
|
+
shell.classList.add('veiled')
|
|
292
|
+
$('loading').classList.remove('hidden')
|
|
293
|
+
fittedCanvas = ''
|
|
294
|
+
await play.device({
|
|
295
|
+
canvasId,
|
|
296
|
+
playerCount: play.snapshot?.playerCount || 1,
|
|
297
|
+
viewPlayerIndex: play.snapshot?.viewPlayerIndex || 1,
|
|
298
|
+
})
|
|
299
|
+
fitStage(true)
|
|
300
|
+
revealStage()
|
|
301
|
+
} catch (error) { showError(error) }
|
|
302
|
+
})
|
|
303
|
+
$('close').addEventListener('click', async () => { if (play.running) await play.stop().catch(() => {}); window.close() })
|
|
304
|
+
$('theme').addEventListener('click', () => { theme = theme === 'dark' ? 'light' : 'dark'; applyTheme() })
|
|
305
|
+
let lastCase = null
|
|
306
|
+
function parseAsserts() {
|
|
307
|
+
const text = $('caseAsserts').value.trim()
|
|
308
|
+
if (!text) return []
|
|
309
|
+
const parsed = JSON.parse(text)
|
|
310
|
+
if (!Array.isArray(parsed)) throw new Error('断言必须是 JSON 数组')
|
|
311
|
+
return parsed
|
|
312
|
+
}
|
|
313
|
+
$('saveCase').addEventListener('click', async () => {
|
|
314
|
+
try {
|
|
315
|
+
lastCase = await api('saveCase', { name: 'play-case', asserts: parseAsserts() })
|
|
316
|
+
$('caseHint').textContent = '已保存 ' + (lastCase.events || []).length + ' 步 / ' + (lastCase.asserts || []).length + ' 条断言'
|
|
317
|
+
} catch (error) { showError(error) }
|
|
318
|
+
})
|
|
319
|
+
$('runCase').addEventListener('click', async () => {
|
|
320
|
+
try {
|
|
321
|
+
if (!lastCase) lastCase = await api('saveCase', { name: 'play-case', asserts: parseAsserts() })
|
|
322
|
+
const report = await api('runCase', { case: lastCase })
|
|
323
|
+
$('caseHint').textContent = report.passed ? '回放通过' : ('回放失败 @' + (report.failedAt ?? '?') + 's')
|
|
324
|
+
if (!report.passed) setView('log')
|
|
325
|
+
} catch (error) { showError(error) }
|
|
326
|
+
})
|
|
327
|
+
for (const button of document.querySelectorAll('[data-view]')) button.addEventListener('click', () => setView(button.dataset.view))
|
|
328
|
+
addEventListener('resize', () => fitStage(true))
|
|
329
|
+
addEventListener('pagehide', () => {
|
|
330
|
+
play.destroy()
|
|
331
|
+
fetch(API, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ sessionId, action: 'stop', args: {} }), keepalive: true }).catch(() => {})
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
if (!sessionId) showError(new Error('缺少会话 ID'))
|
|
335
|
+
else play.start().then(() => setTimeout(revealStage, 0)).catch(showError)
|
|
336
|
+
})()
|
|
337
|
+
</script>
|
|
338
|
+
</body>
|
|
339
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# 千星 2D+Lua 游戏制作 Agent 预设
|
|
2
|
+
|
|
3
|
+
预设 ID:`wonderland-lua-builder`;显示名:**千星 2D+Lua 游戏制作**。它引导 Agent 用千星沙箱模拟器制作客户端 UI + Lua 游戏,并把最终真机验证留给实际游戏环境。
|
|
4
|
+
|
|
5
|
+
## 七步制作游戏
|
|
6
|
+
|
|
7
|
+
1. **策划案**:明确玩法、操作、计分、胜负与首版范围。
|
|
8
|
+
2. **TDD 制定测试用例**:先写开局、成功、失败、重开和边界情况的操作与预期结果。
|
|
9
|
+
3. **HTML 效果展示**:做可打开试玩的效果演示,确认画面、操作与节奏。
|
|
10
|
+
4. **准备千星美术参考图和素材**:根据确认的效果准备参考图、目标图片 ID、图元和 UI 还原方案。
|
|
11
|
+
5. **Lua 编码实现**:核对 2D API,在模拟器中搭建控件、脚本和完整存档;执行测试驱动的 Red→Green→Regress。
|
|
12
|
+
6. **测试**:运行用例,模拟器试玩与截图,检查移动端/PC 布局并修复问题。
|
|
13
|
+
7. **真机试玩验证与 bug 修复**:导出资产供用户在千星奇域试玩,依据真机回传复现、修复和回归。
|
|
14
|
+
|
|
15
|
+
`PREFLIGHT` 是读取已有工程与能力的接入动作,不计入七步。步骤 1–4 不操作模拟器;HTML 是体验展示,不是 Lua 或真机证据。已有工程从需要补齐的步骤继续,窄修复不重走无关流程。细节见 [Skill](skills/qxqy-game-studio/SKILL.md)及[工作流](skills/qxqy-game-studio/references/workflow.md)。
|
|
16
|
+
|
|
17
|
+
## 使用与分发
|
|
18
|
+
|
|
19
|
+
DSH 0.1.7-rc.1 起,本仓库根包 `dsh-plugin-beyond-simulator` 在构建时读取这里的 `preset.yml` 和 `agent.cordis.yml`,生成 `dsh-plugin/cordis.patch.yml` 中的 Agent 预设注册项。旧版复制入口同时保留,启动时仅在 `$DSH_HOME/.agent-presets/wonderland-lua-builder` 不存在时复制包内预设,不覆盖用户修改。通过仓库根目录的 `dsh plugin --profile web add github:1475505/miliastra-beyond-simulator` 安装插件后,在 DSH 的 **设置 → Agent 预设** 中查找本预设;新任务可选择它,已有任务不会自动切换。
|
|
20
|
+
|
|
21
|
+
写 Lua 之前,Agent 工作区需要用户提供的千星 2D API 文档与 `AGENTS.md`;本预设不捆绑官方知识库。模拟器只预览 `100001–100006` 六种基础图元,其他官方图片 ID 在模拟器中显示缺失框,需在真机确认。行为评估见 [agent-behavior.md](evals/agent-behavior.md)。
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# The `wonderland-lua-builder` agent preset: a Genshin Miliastra Wonderland
|
|
2
|
+
# (千星奇域) 2D+Lua game-studio agent, copied from `standard`.
|
|
3
|
+
#
|
|
4
|
+
# This file is an AGENT-PLANE composition. The roster mounts it ONCE under a
|
|
5
|
+
# standing scope; every session naming it joins by scope parentage. The host
|
|
6
|
+
# composition keeps the registries, sandbox, approval stack, persistence, and
|
|
7
|
+
# the model route. Simulator Tools come from `dsh-plugin-beyond-simulator`.
|
|
8
|
+
# 2D Lua knowledge is the user's workspace (AGENTS.md / knowledge docs they
|
|
9
|
+
# place when creating the agent workspace). Do not bundle a corpus in this
|
|
10
|
+
# preset and do not route through miliastra-knowledge (3D sandbox nodes).
|
|
11
|
+
#
|
|
12
|
+
# A service row here MUST sit inside a group carrying an `isolate` realm.
|
|
13
|
+
|
|
14
|
+
# ── identity ────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
- id: persona
|
|
17
|
+
name: '@deepseek-ai/dsh-persona'
|
|
18
|
+
config:
|
|
19
|
+
prefix: |-
|
|
20
|
+
You are a 千星奇域 2D+Lua game studio director powered by the {{model}} model. Your working directory is {{cwd}}.
|
|
21
|
+
|
|
22
|
+
You make playable UI+Lua games for 原神千星奇域 (Miliastra Wonderland): client UI controls, client Lua, and a thin server of custom variables/signals. You are not a 3D level-graph designer. Load `qxqy-game-studio` before creating, changing, reviewing, or polishing a game, then load only the stage skills it routes to. Read 2D API and observed pitfalls from the current workspace (AGENTS.md and knowledge docs the user placed there). Never use `miliastra-knowledge` for 2D UI+Lua and never invent official APIs, imageIds, GIA fields, or simulator support. If the workspace has no API docs, ask the user to add them before writing Lua that depends on editor APIs.
|
|
23
|
+
|
|
24
|
+
Keep one current step after PREFLIGHT: (1) game design document, (2) TDD test cases before production code, (3) playable HTML effect showcase, (4) Miliastra art reference images and assets, (5) Lua implementation in the simulator, (6) simulator testing and fixes, (7) real-device play validation and bug fixes. Follow this order for new games. Existing projects resume from the earliest missing evidence; narrow fixes need not repeat unrelated steps.
|
|
25
|
+
|
|
26
|
+
Steps 1–4 do not open the simulator, edit saves, or runCase. Design test cases in step 2; a valid TDD Red requires a runnable skeleton in step 5. The HTML showcase in step 3 is opened by the user (this composition has no browser-play tool; record browser-run: user), is not Lua source, and is not the ship artifact. Prepare Miliastra art references and assets after the HTML experience is agreed. Use qxqy_studio_play, qxqy_studio_ui_screenshot, and qxqy_studio_play_screenshot for step 6; only a real device can establish step 7. Do not treat web search as playtesting. Coordinates: 千星 origin bottom-left, Y up; HTML is usually top-left, Y down — never copy CSS pixels into Lua.
|
|
27
|
+
|
|
28
|
+
The deliverable is a game that runs in the simulator and on device (Lua + save/GIA), not a webpage. Official Genshin/千星 art and imageIds are allowed; the simulator only previews primitives 100001–100006 (missing box is a preview limit). Design qxqy tests before production rules; a valid Red is a running skeleton whose intended behavior is absent. Fun is judged with the HTML demo first, then simulator play/screenshots — not fake log asserts.
|
|
29
|
+
|
|
30
|
+
First milestone is one complete game session. Saves: workspace/<slug>/<slug>.save.json with qxqy-simulator-save near the file head. Simulator green is not ship. Ask the user only for meaningful design or experience decisions, art direction forks, and real-device observations. Inspect the repo and tools yourself.
|
|
31
|
+
|
|
32
|
+
Maintain a living tech-architecture doc (docs/tech-architecture.md) throughout development, in the spirit of Karpathy's llm.c dev wiki: module breakdown, what each Lua file, client control/container, and server variable/signal is responsible for, key data flow and mount points. Update it with every structural change — it is the operability manual after delivery, and stale entries are worse than none.
|
|
33
|
+
|
|
34
|
+
- id: agent-instructions
|
|
35
|
+
name: '@deepseek-ai/dsh-agent-instructions'
|
|
36
|
+
config:
|
|
37
|
+
maxBytes: 65536
|
|
38
|
+
|
|
39
|
+
# ── shell ───────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
# `shell-env` stays in the HOST composition. Both shell tools consume the host
|
|
42
|
+
# registry from here; their executors (`bash-sandbox`/`pwsh-sandbox`) are
|
|
43
|
+
# host-plane too.
|
|
44
|
+
- id: tool-bash
|
|
45
|
+
name: '@deepseek-ai/dsh-tool-bash'
|
|
46
|
+
disabled: !!js process.platform === 'win32'
|
|
47
|
+
|
|
48
|
+
- id: tool-pwsh
|
|
49
|
+
name: '@deepseek-ai/dsh-tool-pwsh'
|
|
50
|
+
disabled: !!js process.platform !== 'win32'
|
|
51
|
+
|
|
52
|
+
# ── filesystem ──────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
# Both register into the host `tools` registry and provide nothing, so
|
|
55
|
+
# they need no realm. The `fs` service and its policy stay in the host.
|
|
56
|
+
- id: tool-fs
|
|
57
|
+
name: '@deepseek-ai/dsh-tool-fs'
|
|
58
|
+
|
|
59
|
+
- id: tool-fs-search
|
|
60
|
+
name: '@deepseek-ai/dsh-tool-fs-search'
|
|
61
|
+
config:
|
|
62
|
+
sampleOverCapGlobResults: false
|
|
63
|
+
|
|
64
|
+
# ── background jobs ────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
# Only the model-facing controls. The task REGISTRY stays on the host plane.
|
|
67
|
+
- id: tool-jobs
|
|
68
|
+
name: '@deepseek-ai/dsh-tool-jobs'
|
|
69
|
+
|
|
70
|
+
# ── skills ──────────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
# The skill REGISTRY lives in the host composition and is layered per scope.
|
|
73
|
+
# `customSkillDirs` ships this preset's studio workflow with the copy; project
|
|
74
|
+
# and user roots remain enabled so `~/.dsh/skills` and workspace skills still
|
|
75
|
+
# appear. The simulator plugin still registers globally; miliastra-knowledge
|
|
76
|
+
# is not part of this preset's workflow.
|
|
77
|
+
- id: skill-filesystem
|
|
78
|
+
name: '@deepseek-ai/dsh-skill-filesystem'
|
|
79
|
+
config:
|
|
80
|
+
customSkillDirs:
|
|
81
|
+
- !!js "process.getBuiltinModule('node:url').fileURLToPath(new URL('skills/', baseUrl))"
|
|
82
|
+
|
|
83
|
+
- id: tool-skill
|
|
84
|
+
name: '@deepseek-ai/dsh-tool-skill'
|
|
85
|
+
|
|
86
|
+
# ── goals ───────────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
# Only the model-facing tool. The goal SERVICE stays on the host plane.
|
|
89
|
+
- id: tool-goal
|
|
90
|
+
name: '@deepseek-ai/dsh-tool-goal'
|
|
91
|
+
|
|
92
|
+
# ── plan mode ───────────────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
- id: planning
|
|
95
|
+
name: cordis:group
|
|
96
|
+
group: true
|
|
97
|
+
isolate:
|
|
98
|
+
planMode: true
|
|
99
|
+
config:
|
|
100
|
+
- id: plan-mode
|
|
101
|
+
name: '@deepseek-ai/dsh-plan-mode'
|
|
102
|
+
config:
|
|
103
|
+
section: |
|
|
104
|
+
You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
|
|
105
|
+
|
|
106
|
+
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
|
|
107
|
+
|
|
108
|
+
The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
|
|
109
|
+
|
|
110
|
+
Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
|
|
111
|
+
|
|
112
|
+
For 千星奇域 games, plan the seven-step workflow in order: (1) 策划案, (2) TDD 制定测试用例, (3) HTML 效果展示, (4) 准备千星美术参考图和素材, (5) Lua 编码实现, (6) 测试, (7) 真机试玩验证与 bug 修复. Steps 1–4 do not use the simulator; first valid TDD Red comes after a runnable Lua skeleton exists in step 5. The ship artifact is a game that runs in the simulator and on device, not HTML. Never translate DOM/CSS/JS to Lua, never copy HTML coordinates (top-left) into 千星 (bottom-left, Y up). Official imageIds are allowed; simulator missing-box is a preview limit. Cover the three-asset split, workspace/<slug>/<slug>.save.json, canvases, budgets, GIA warnings, and real-device pass. Route Lua/API through workspace knowledge docs the user provides, never miliastra. State entry/exit evidence and rollbacks so another engineer need not invent design.
|
|
113
|
+
|
|
114
|
+
When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
|
|
115
|
+
|
|
116
|
+
# ── compaction ──────────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
- id: compaction
|
|
119
|
+
name: cordis:group
|
|
120
|
+
group: true
|
|
121
|
+
isolate:
|
|
122
|
+
compaction: true
|
|
123
|
+
toolResultPruner: true
|
|
124
|
+
config:
|
|
125
|
+
- id: compaction-basic
|
|
126
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
127
|
+
|
|
128
|
+
- id: command-compact
|
|
129
|
+
name: '@deepseek-ai/dsh-command-compact'
|
|
130
|
+
|
|
131
|
+
- id: tool-result-pruner
|
|
132
|
+
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
133
|
+
config:
|
|
134
|
+
thresholdChars: 8192
|
|
135
|
+
headChars: 4096
|
|
136
|
+
tailChars: 1024
|
|
137
|
+
|
|
138
|
+
# ── delegation and workflows ────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
# The `subagents` registry stays on the HOST plane. `workflowEngine` is
|
|
141
|
+
# isolated here because nothing outside an agent reads it.
|
|
142
|
+
- id: delegation
|
|
143
|
+
name: cordis:group
|
|
144
|
+
group: true
|
|
145
|
+
isolate:
|
|
146
|
+
workflowEngine: true
|
|
147
|
+
config:
|
|
148
|
+
- id: tool-subagent-control
|
|
149
|
+
name: '@deepseek-ai/dsh-tool-subagent-control'
|
|
150
|
+
|
|
151
|
+
- id: tool-subagent-list-agents
|
|
152
|
+
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
|
|
153
|
+
|
|
154
|
+
- id: tool-subagent
|
|
155
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
156
|
+
config:
|
|
157
|
+
provider: spawn
|
|
158
|
+
toolName: subagent
|
|
159
|
+
backgroundMode: continuable
|
|
160
|
+
|
|
161
|
+
- id: tool-subagent-fork
|
|
162
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
163
|
+
config:
|
|
164
|
+
provider: fork
|
|
165
|
+
toolName: subagent_fork
|
|
166
|
+
backgroundMode: continuable
|
|
167
|
+
|
|
168
|
+
- id: tool-subagent-codex
|
|
169
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
170
|
+
disabled: true
|
|
171
|
+
config:
|
|
172
|
+
provider: codex
|
|
173
|
+
toolName: subagent_codex
|
|
174
|
+
enableRunInBackground: false
|
|
175
|
+
maxDepth: provider-managed
|
|
176
|
+
|
|
177
|
+
- id: tool-subagent-claude-code
|
|
178
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
179
|
+
disabled: true
|
|
180
|
+
config:
|
|
181
|
+
provider: claude-code
|
|
182
|
+
toolName: subagent_claude_code
|
|
183
|
+
enableRunInBackground: false
|
|
184
|
+
maxDepth: provider-managed
|
|
185
|
+
|
|
186
|
+
- id: workflow-ptc
|
|
187
|
+
name: '@deepseek-ai/dsh-workflow-ptc'
|
|
188
|
+
config:
|
|
189
|
+
provider: spawn
|
|
190
|
+
|
|
191
|
+
- id: tool-workflow
|
|
192
|
+
name: '@deepseek-ai/dsh-tool-workflow'
|
|
193
|
+
|
|
194
|
+
- id: tool-ralph
|
|
195
|
+
name: '@deepseek-ai/dsh-tool-ralph'
|
|
196
|
+
config:
|
|
197
|
+
subagentProvider: spawn
|
|
198
|
+
maxRounds: 64
|
|
199
|
+
|
|
200
|
+
# ── remaining model-facing rows ─────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
- id: tool-ask-user
|
|
203
|
+
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
204
|
+
|
|
205
|
+
- id: tool-todo
|
|
206
|
+
name: '@deepseek-ai/dsh-tool-todo'
|
|
207
|
+
config:
|
|
208
|
+
allowParallelInProgress: true
|
|
209
|
+
|
|
210
|
+
- id: tool-web
|
|
211
|
+
name: '@deepseek-ai/dsh-tool-web'
|
|
212
|
+
config:
|
|
213
|
+
fetch: false
|
|
214
|
+
searchTimeoutMs: 60000
|