trip-moments-skill 1.1.2 → 1.3.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/.agents/skills/trip-moments/scripts/agent_transport.py +37 -11
- package/.agents/skills/trip-moments/scripts/agent_ui.py +106 -55
- package/.agents/skills/trip-moments/scripts/clawbridge.py +208 -105
- package/.agents/skills/trip-moments/scripts/journey_ui.py +7 -14
- package/.agents/skills/trip-moments/scripts/selftest.py +151 -4
- package/.agents/skills/trip-moments/scripts/studio.py +14 -1
- package/.agents/skills/trip-moments/scripts/studio_ui.py +335 -224
- package/README.md +9 -4
- package/bin/install.js +1 -1
- package/package.json +1 -1
|
@@ -355,7 +355,9 @@ def _claw_module():
|
|
|
355
355
|
|
|
356
356
|
|
|
357
357
|
class OpenClawTransport:
|
|
358
|
-
"""Persistent-session transport over
|
|
358
|
+
"""Persistent-session transport over an OpenClaw gateway's chat-completions
|
|
359
|
+
endpoint — either ClawHive's embedded gateway or the OpenClaw desktop app's
|
|
360
|
+
WSL-backed one (both are `openclaw`, discovered by clawbridge).
|
|
359
361
|
|
|
360
362
|
The gateway serves ONE turn per session lane (#235): `start()` is a full
|
|
361
363
|
blocking agent turn, not a run-id handle. We still model it as
|
|
@@ -366,6 +368,13 @@ class OpenClawTransport:
|
|
|
366
368
|
"""
|
|
367
369
|
|
|
368
370
|
provider = "openclaw"
|
|
371
|
+
# The WSL gateway's session book is SQLite inside the distro and the wsl$
|
|
372
|
+
# UNC paths are unreachable from Windows, so there is no "the conversation
|
|
373
|
+
# the user is looking at" to bind to there. This explicit key gives the
|
|
374
|
+
# workbench one stable thread instead: the gateway routes it to a session of
|
|
375
|
+
# its own (only `subagent:`/`cron:`/`acp:` are reserved) and repeated turns
|
|
376
|
+
# continue that conversation rather than starting fresh each time.
|
|
377
|
+
WORKBENCH_SESSION = "studio-workbench"
|
|
369
378
|
|
|
370
379
|
def __init__(self, cwd=""):
|
|
371
380
|
self.claw = _claw_module()
|
|
@@ -378,28 +387,45 @@ class OpenClawTransport:
|
|
|
378
387
|
def probe(self):
|
|
379
388
|
info = self.claw.discover(force=True)
|
|
380
389
|
if not info.get("base"):
|
|
381
|
-
|
|
390
|
+
# A gateway that answered but has the opt-in endpoint disabled is NOT
|
|
391
|
+
# the same as no gateway: say which one it was and what to turn on,
|
|
392
|
+
# otherwise the only message the user gets is 「未找到」 while their
|
|
393
|
+
# OpenClaw is sitting right there on 18789.
|
|
394
|
+
why = info.get("why") or ""
|
|
395
|
+
if info.get("via"):
|
|
396
|
+
raise AgentError(f"{info.get('name') or 'OpenClaw'} 通道不可用:{why}")
|
|
397
|
+
raise AgentError("未找到运行中的 OpenClaw gateway:" + why)
|
|
382
398
|
if not info.get("enabled"):
|
|
383
|
-
raise AgentError("
|
|
399
|
+
raise AgentError("OpenClaw gateway 的 OpenAI 兼容端点未启用:" + (info.get("why") or ""))
|
|
384
400
|
return {"ready": True, "provider": self.provider, "native_sessions": True,
|
|
385
401
|
"streaming": False, "steer": False, "approvals": False,
|
|
386
|
-
"port": info.get("port"), "agents": info.get("agents") or []
|
|
402
|
+
"port": info.get("port"), "agents": info.get("agents") or [],
|
|
403
|
+
"gateway": info.get("name") or "", "via": info.get("via") or ""}
|
|
387
404
|
|
|
388
405
|
def sessions(self):
|
|
389
|
-
# The desktop conversation list belongs to
|
|
406
|
+
# The desktop conversation list belongs to that app's own UI; offering
|
|
390
407
|
# it here would invite binding to a session nobody is looking at.
|
|
391
408
|
return []
|
|
392
409
|
|
|
393
410
|
def create(self, title, cwd):
|
|
394
|
-
# There is no session-creation endpoint
|
|
395
|
-
#
|
|
411
|
+
# There is no session-creation endpoint on this API surface; the bind
|
|
412
|
+
# below decides which thread turns will land in.
|
|
396
413
|
return ""
|
|
397
414
|
|
|
398
415
|
def bind(self, sid):
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
416
|
+
if sid:
|
|
417
|
+
return sid
|
|
418
|
+
key = self.claw.active_session()
|
|
419
|
+
if key:
|
|
420
|
+
return key # ClawHive: the open conversation
|
|
421
|
+
info = self.claw.discover()
|
|
422
|
+
if info.get("via"):
|
|
423
|
+
# Keyed off `via`, not `base`: a gateway that ANSWERED but has the
|
|
424
|
+
# endpoint disabled is still a gateway (that is the default state),
|
|
425
|
+
# and probe() already refused with the exact config key before we get
|
|
426
|
+
# here. Binding then succeeds and the first turn reports the truth.
|
|
427
|
+
return self.WORKBENCH_SESSION # WSL gateway: our own thread
|
|
428
|
+
raise AgentError("没有找到正在使用的 ClawHive 会话;先在 ClawHive 里打开一个对话")
|
|
403
429
|
|
|
404
430
|
# -- turns ---------------------------------------------------------------
|
|
405
431
|
def start(self, sid, text, key):
|
|
@@ -28,18 +28,51 @@ AGENT_STYLES = r"""
|
|
|
28
28
|
.ap-compose textarea{width:100%;min-height:65px;max-height:160px;resize:vertical;padding:9px;border:1px solid var(--line-2);border-radius:7px;font-size:12px}
|
|
29
29
|
.ap-compose .row{margin-top:8px}.ap-compose .btn{white-space:normal}
|
|
30
30
|
.ap-next{width:100%;margin-top:8px}
|
|
31
|
-
.workflow-
|
|
32
|
-
.workflow-control h3{font-size:12px;margin-bottom:8px}
|
|
33
|
-
.workflow-mode{display:grid;grid-template-columns:1fr 1fr;gap:5px}
|
|
31
|
+
.workflow-mode{display:grid;grid-template-columns:1fr 1fr;gap:5px;min-width:0}
|
|
34
32
|
.workflow-mode button{padding:8px 5px;border:1px solid var(--line-2);border-radius:6px;background:var(--paper-hi);cursor:pointer;font-size:11px}
|
|
35
33
|
.workflow-mode button.on{background:var(--navy);border-color:var(--navy);color:#fff}
|
|
36
|
-
|
|
37
|
-
.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
.
|
|
41
|
-
.
|
|
42
|
-
|
|
34
|
+
/* ---- the per-step action deck -------------------------------------------
|
|
35
|
+
写要求 → 点按钮 → 看结果 in one card. Before this the request box sat in the
|
|
36
|
+
middle card, the trigger at the top of the rail and the result at the bottom,
|
|
37
|
+
and five labels told the user to click 「上方」. */
|
|
38
|
+
.deck{border:1px solid var(--line-2);border-radius:10px;background:var(--card);margin-bottom:13px;overflow:hidden}
|
|
39
|
+
.deck-h{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid var(--line);
|
|
40
|
+
background:linear-gradient(120deg,#f7f5ee,#eef2ee)}
|
|
41
|
+
.deck-t{font-size:12px;font-weight:600}.deck-s{font-size:10px;color:var(--sub);text-align:right}
|
|
42
|
+
.deck-ctl{padding:10px 12px 2px}
|
|
43
|
+
.deck-row{display:flex;align-items:center;gap:9px;margin-bottom:8px}
|
|
44
|
+
.deck-k{font-size:10.5px;color:var(--sub);flex:none;width:52px}
|
|
45
|
+
.deck-row>.workflow-mode{flex:1}
|
|
46
|
+
.deck-row select{flex:1;min-width:0;padding:7px;border:1px solid var(--line-2);border-radius:6px;background:#fff;font-size:11px}
|
|
47
|
+
.deck-ctl #wf-mode-note{margin:0 0 10px}
|
|
48
|
+
.deck-write{padding:0 12px}
|
|
49
|
+
.deck-write .pref-grid{margin-top:2px}
|
|
50
|
+
.deck-write .cp-actions{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px}
|
|
51
|
+
.deck-write .cp-actions button{font-size:11px;padding:6px 10px;border:1px solid #becdbb;border-radius:6px;
|
|
52
|
+
background:#f4f8f2;color:#2c4a37;cursor:pointer;transition:all .16s var(--ease)}
|
|
53
|
+
.deck-write .cp-actions button:hover{background:#fff;transform:translateY(-1px)}
|
|
54
|
+
.deck-l{display:block;font-size:10.5px;color:var(--sub);margin:2px 0 5px}
|
|
55
|
+
.deck-write textarea{width:100%;min-height:62px;max-height:170px;resize:vertical;padding:9px;font-size:12px;
|
|
56
|
+
border:1px solid var(--line-2);border-radius:7px;background:#fff;font-family:inherit}
|
|
57
|
+
.deck-go{display:flex;gap:7px;flex-wrap:wrap;padding:10px 12px 0}
|
|
58
|
+
.deck-go .btn{white-space:normal}
|
|
59
|
+
.deck-go #wf-work{flex:1;min-width:120px}
|
|
60
|
+
.deck-go .note{margin:0}
|
|
61
|
+
#wf-connection{margin:8px 12px 0}
|
|
62
|
+
#wf-connection:empty{display:none}
|
|
63
|
+
.deck-brief{margin:9px 12px 0;border:1px solid var(--line);border-radius:7px;background:var(--paper-hi)}
|
|
64
|
+
.deck-brief>summary{font-size:10.5px;color:var(--sub);padding:8px 10px;cursor:pointer}
|
|
65
|
+
.deck-brief pre{white-space:pre-wrap;overflow-wrap:anywhere;font:10.5px/1.65 var(--mono);color:var(--ink-2);
|
|
66
|
+
max-height:300px;overflow:auto;padding:0 10px 9px;margin:0}
|
|
67
|
+
.deck-out{padding:11px 12px 12px;border-top:1px solid var(--line);margin-top:11px}
|
|
68
|
+
.deck-out-h{display:flex;align-items:center;gap:8px;font-size:10px;color:var(--sub);
|
|
69
|
+
text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px}
|
|
70
|
+
.deck-out-h .sp{flex:1}.deck-out-s{font-size:10px;color:var(--sub);text-transform:none;letter-spacing:0}
|
|
71
|
+
.deck-out .ai-out:empty{margin:0}
|
|
72
|
+
.deck-agent{border-top:1px dashed var(--line-2);padding:11px 12px 12px;background:#fbfcfa}
|
|
73
|
+
.deck-agent .ap-stream{max-height:340px;overflow:auto}
|
|
74
|
+
.deck-agent .ap-status{margin:0 0 8px}
|
|
75
|
+
.deck .proposal-card{margin:0 0 9px}
|
|
43
76
|
@media(max-width:720px){.agent-panel{top:8px;bottom:8px;right:8px;width:calc(100vw - 16px)}}
|
|
44
77
|
"""
|
|
45
78
|
|
|
@@ -50,7 +83,7 @@ AGENT_SHELL = r"""
|
|
|
50
83
|
<div class="ap-body" id="ap-body">
|
|
51
84
|
<details class="ap-setup" id="ap-setup" open><summary>连接与会话绑定</summary>
|
|
52
85
|
<label for="ap-kind">Agent 通道</label><select id="ap-kind" onchange="agentProviderHint()">
|
|
53
|
-
<option value="hermes">Hermes · 原生持续会话</option><option value="codex">Codex App Server · 工作台专用会话</option><option value="openclaw">OpenClaw
|
|
86
|
+
<option value="hermes">Hermes · 原生持续会话</option><option value="codex">Codex App Server · 工作台专用会话</option><option value="openclaw">OpenClaw gateway · 整轮直连(ClawHive 或 WSL)</option></select>
|
|
54
87
|
<div id="ap-hermes-fields"><label for="ap-url">本机 Hermes API 地址</label><input id="ap-url" value="http://127.0.0.1:8642">
|
|
55
88
|
<label for="ap-key">API 密钥(留空读取本机 Hermes 配置)</label><input id="ap-key" type="password" autocomplete="off"></div>
|
|
56
89
|
<p class="note" id="ap-hint" style="margin-top:8px"></p>
|
|
@@ -73,57 +106,71 @@ AGENT_SHELL = r"""
|
|
|
73
106
|
AGENT_SCRIPT = r"""
|
|
74
107
|
const AP = { open: false, data: null, polling: false, timer: null, seq: -1, pending: false, draft: "", answers: {} };
|
|
75
108
|
function useAgentModel() { return S.data?.preferences?.ai_mode === "agent"; }
|
|
76
|
-
|
|
109
|
+
/* The deck's control rows. No trigger button here any more: the deck owns the
|
|
110
|
+
写要求 → 点按钮 → 看结果 order, so the button sits under the input box. */
|
|
111
|
+
function deckControlsHtml() {
|
|
77
112
|
const agent = useAgentModel();
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<option value="art" ${S.finishMode !== "local" ? "selected" : ""}>AI 艺术作品 · 使用所选模型来源</option>
|
|
89
|
-
<option value="local" ${S.finishMode === "local" ? "selected" : ""}>本地画板/九宫格 · 不调用模型</option>
|
|
90
|
-
</select><p class="note">在下方选择风格或版式,再点击「生成所选作品」。此步不打包。</p>` : ""}
|
|
91
|
-
${["crop","grade"].includes(S.step) ? `<label class="note" for="wf-scope">处理范围</label>
|
|
92
|
-
<select id="wf-scope" style="width:100%;padding:7px;border:1px solid var(--line-2);border-radius:6px">
|
|
93
|
-
<option value="current" ${S.aiScope !== "all" ? "selected" : ""}>当前第 ${S.sel} 格</option>
|
|
113
|
+
const sel = S.sel != null ? S.sel : 1;
|
|
114
|
+
return `<div class="deck-ctl">
|
|
115
|
+
<div class="deck-row"><span class="deck-k">谁来做</span>
|
|
116
|
+
<div class="workflow-mode" role="group" aria-label="AI 执行来源">
|
|
117
|
+
<button class="${agent ? "" : "on"}" aria-pressed="${!agent}" data-ai-mode="gateway">模型网关</button>
|
|
118
|
+
<button class="${agent ? "on" : ""}" aria-pressed="${agent}" data-ai-mode="agent">Agent 自带模型</button>
|
|
119
|
+
</div></div>
|
|
120
|
+
${["crop","grade"].includes(S.step) ? `<div class="deck-row"><span class="deck-k">处理范围</span>
|
|
121
|
+
<select id="wf-scope" aria-label="处理范围">
|
|
122
|
+
<option value="current" ${S.aiScope !== "all" ? "selected" : ""}>当前第 ${sel} 格</option>
|
|
94
123
|
<option value="all" ${S.aiScope === "all" ? "selected" : ""}>全部已选照片</option>
|
|
95
|
-
</select>` : ""}
|
|
96
|
-
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
124
|
+
</select></div>` : ""}
|
|
125
|
+
${S.step === "finish" ? `<div class="deck-row"><span class="deck-k">本次制作</span>
|
|
126
|
+
<select id="finish-mode" aria-label="本次制作方式">
|
|
127
|
+
<option value="art" ${S.finishMode !== "local" ? "selected" : ""}>AI 艺术作品 · 用所选模型来源</option>
|
|
128
|
+
<option value="local" ${S.finishMode === "local" ? "selected" : ""}>本地画板/九宫格 · 不调模型</option>
|
|
129
|
+
</select></div>` : ""}
|
|
130
|
+
<p class="note" id="wf-mode-note">${agent
|
|
131
|
+
? "用已绑定 Agent 的模型、工具与会话上下文;检查、处理、追问都在本卡完成。"
|
|
132
|
+
: "用配置的模型网关;AI 分析会发送照片预览,不进入 Agent 会话。"}</p>
|
|
133
|
+
<p class="note" id="claw-note" style="display:none"></p>
|
|
134
|
+
</div>`;
|
|
135
|
+
}
|
|
136
|
+
/* One input box serves both 「本步要求」 and 「继续追问」, so the session buttons
|
|
137
|
+
live next to the trigger instead of under a second textarea. */
|
|
138
|
+
function deckSessionButtons() {
|
|
139
|
+
const active = !!AP.pending || !!(AP.data && AP.data.active);
|
|
140
|
+
if (active) {
|
|
141
|
+
return `<button class="btn btn-sm" id="ap-steer" onclick="controlAgent('steer')">补充到当前执行</button>
|
|
142
|
+
<button class="btn btn-sm ghost" id="ap-stop" onclick="controlAgent('stop')">停止本轮</button>`;
|
|
143
|
+
}
|
|
144
|
+
return `<button class="btn btn-sm" id="ap-send" onclick="sendAgentReply()">继续对话</button>`;
|
|
101
145
|
}
|
|
102
|
-
function
|
|
146
|
+
function agentStreamHtml() {
|
|
103
147
|
if (!useAgentModel()) return "";
|
|
104
|
-
return `<
|
|
105
|
-
<
|
|
148
|
+
return `<div class="deck-agent">
|
|
149
|
+
<div class="deck-out-h"><span>Agent 会话</span><span class="sp"></span>
|
|
150
|
+
<span class="deck-out-s">同一会话 · 回复不会自动跳步</span></div>
|
|
106
151
|
<div class="ap-stream" id="ap-stream">
|
|
107
152
|
<div class="ap-status" id="ap-run-status">正在读取绑定会话…</div>
|
|
108
153
|
<div id="ap-messages"></div><div id="ap-live" class="ap-live" style="display:none" role="status"></div>
|
|
109
154
|
<div id="ap-approval"></div>
|
|
110
155
|
<details class="ap-tools"><summary>本轮真实工具事件</summary><div id="ap-events"></div></details>
|
|
111
156
|
</div>
|
|
112
|
-
<div class="
|
|
113
|
-
<
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
157
|
+
<div class="row" style="margin-top:7px">
|
|
158
|
+
<button class="btn btn-sm ghost" onclick="syncAgentHistory()">同步会话回复</button>
|
|
159
|
+
<button class="btn btn-sm ghost" onclick="openAgentPanel()">管理 Agent 连接</button>
|
|
160
|
+
</div>
|
|
161
|
+
</div>`;
|
|
162
|
+
}
|
|
163
|
+
function deckInput() { return $("deck-intent"); }
|
|
164
|
+
function deckInputText() {
|
|
165
|
+
const el = deckInput();
|
|
166
|
+
return el ? el.value.trim() : (S.copilotIntent || "").trim();
|
|
120
167
|
}
|
|
121
168
|
function startAgentPolling() {
|
|
122
169
|
if (!AP.timer) AP.timer = setInterval(() => {
|
|
123
170
|
if (AP.open || useAgentModel() || AP.data?.active) pollAgent();
|
|
124
171
|
}, 850);
|
|
125
172
|
}
|
|
126
|
-
function
|
|
173
|
+
function wireDeckControls() {
|
|
127
174
|
const finishMode = $("finish-mode");
|
|
128
175
|
if (finishMode) finishMode.addEventListener("change", () => {
|
|
129
176
|
S.finishMode = finishMode.value;
|
|
@@ -132,8 +179,6 @@ function wireWorkflow() {
|
|
|
132
179
|
document.querySelectorAll("[data-ai-mode]").forEach(el => el.addEventListener("click", () => setAIMode(el.dataset.aiMode)));
|
|
133
180
|
const scope = $("wf-scope");
|
|
134
181
|
if (scope) scope.addEventListener("change", () => { S.aiScope = scope.value; });
|
|
135
|
-
const input = $("ap-input");
|
|
136
|
-
if (input) input.addEventListener("input", () => { AP.draft = input.value; });
|
|
137
182
|
if (useAgentModel()) {
|
|
138
183
|
startAgentPolling();
|
|
139
184
|
AP.seq = -1; AP.approvalId = null;
|
|
@@ -187,7 +232,7 @@ async function runCurrentStep(kind) {
|
|
|
187
232
|
if (useAgentModel()) {
|
|
188
233
|
if (kind === "review") {
|
|
189
234
|
if (!await requireAgentConnection()) return;
|
|
190
|
-
return sendAgentMessage("review_step",
|
|
235
|
+
return sendAgentMessage("review_step", deckInputText(), S.step);
|
|
191
236
|
}
|
|
192
237
|
if (S.step === "retouch") return aiEdit();
|
|
193
238
|
const operation = { select:"select9", crop:"compose", grade:S.aiScope === "all" ? "grade_all" : "grade_plan", retouch:"edit", finish:"finish", caption:"caption" }[S.step];
|
|
@@ -231,7 +276,7 @@ function agentProviderHint() {
|
|
|
231
276
|
? "绑定 Hermes 原生会话,后续回复与步骤按钮都延续该会话。连接密钥仅由本机服务使用。"
|
|
232
277
|
: kind === "codex"
|
|
233
278
|
? "使用本机已登录的 Codex CLI,创建工作台专用的持续任务;不接管你当前 Codex 桌面聊天。工具批准仍需你确认。"
|
|
234
|
-
: "直连 ClawHive
|
|
279
|
+
: "直连 OpenClaw gateway —— ClawHive 内嵌的那个,或 OpenClaw 桌面版在 WSL 里跑的那个。整轮执行,无中间批准;需在该 gateway 的 openclaw.json 里启用 chatCompletions 端点。ClawHive 上会绑定你正在看的会话;WSL gateway 读不到会话簿,改用工作台专用会话。";
|
|
235
280
|
}
|
|
236
281
|
async function pollAgent() {
|
|
237
282
|
if (AP.polling) return;
|
|
@@ -355,25 +400,31 @@ async function sendAgentMessage(action, message, step = S.step, options = {}) {
|
|
|
355
400
|
try {
|
|
356
401
|
const data = await post("/api/agent/send", { ...options, action, message, step, slot: S.sel });
|
|
357
402
|
if (!data.ok) { toast(data.error, "err", 6500); return false; }
|
|
358
|
-
AP.draft = "";
|
|
403
|
+
AP.draft = "";
|
|
404
|
+
const el = deckInput();
|
|
405
|
+
if (el) el.value = "";
|
|
406
|
+
S.copilotIntent = "";
|
|
407
|
+
(S.copilotDrafts = S.copilotDrafts || {})[copilotDraftKey()] = "";
|
|
408
|
+
S.deckBrief = "";
|
|
409
|
+
pollJobs(); pollAgent();
|
|
359
410
|
return true;
|
|
360
411
|
} finally { AP.pending = false; }
|
|
361
412
|
}
|
|
362
413
|
function sendAgentStep(action) {
|
|
363
|
-
return sendAgentMessage(action, (
|
|
414
|
+
return sendAgentMessage(action, deckInputText());
|
|
364
415
|
}
|
|
365
416
|
function sendAgentReply() {
|
|
366
|
-
const text = (
|
|
417
|
+
const text = deckInputText();
|
|
367
418
|
if (!text) { toast("请写下本轮回复或修改要求"); return; }
|
|
368
419
|
return sendAgentMessage("", text);
|
|
369
420
|
}
|
|
370
421
|
async function controlAgent(action, answers) {
|
|
371
|
-
const text = (
|
|
422
|
+
const text = deckInputText();
|
|
372
423
|
if (action === "steer" && !text) { toast("请先写下要补充的要求"); return; }
|
|
373
424
|
const data = await post("/api/agent/control", { action, text, answers,
|
|
374
425
|
run_id: AP.data?.run_id, request_id: AP.data?.approval?.request_id });
|
|
375
426
|
if (!data.ok) { toast(data.error, "err"); return; }
|
|
376
|
-
if (action === "steer") { AP.draft = "";
|
|
427
|
+
if (action === "steer") { AP.draft = ""; const el = deckInput(); if (el) el.value = ""; }
|
|
377
428
|
pollAgent();
|
|
378
429
|
}
|
|
379
430
|
function answerAgent() {
|