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.
@@ -355,7 +355,9 @@ def _claw_module():
355
355
 
356
356
 
357
357
  class OpenClawTransport:
358
- """Persistent-session transport over ClawHive's chat-completions endpoint.
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
- raise AgentError("未找到运行中的 ClawHive gateway:" + (info.get("why") or ""))
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("ClawHive gateway 的 OpenAI 兼容端点未启用:" + (info.get("why") or ""))
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 ClawHive's own UI; offering
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: we bind to the conversation
395
- # the user has open right now (see bind).
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
- key = sid or self.claw.active_session()
400
- if not key:
401
- raise AgentError("没有找到正在使用的 ClawHive 会话;先在 ClawHive 里打开一个对话")
402
- return key
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-control{padding:13px;border:1px solid var(--line-2);border-radius:9px;background:var(--card);margin-bottom:13px}
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
- .workflow-control .row{margin-top:9px}.workflow-control .row .btn{flex:1}
37
- .workflow-control .note{margin-top:8px}
38
- .agent-workflow{border:1px solid #cbd7c8;border-radius:9px;background:var(--card);margin:12px 0;overflow:hidden}
39
- .agent-workflow>header{padding:10px 12px;border-bottom:1px solid var(--line);font-size:12px;font-weight:600}
40
- .agent-workflow .ap-stream{padding:10px;max-height:360px;overflow:auto}
41
- .agent-workflow .ap-status{margin:0 0 8px}
42
- .agent-workflow .ap-compose{padding:10px}
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 (ClawHive) · 活跃会话直连</option></select>
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
- function workflowControlsHtml() {
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 labels = { select:"智能选片", crop:"生成构图方案", grade:"生成调色方案",
79
- retouch:"开始修图", finish:"生成所选作品", caption:"生成 3 版文案" };
80
- return `<section class="workflow-control"><h3>本步由谁处理</h3>
81
- <div class="workflow-mode" role="group" aria-label="AI 执行来源">
82
- <button class="${agent ? "" : "on"}" aria-pressed="${!agent}" data-ai-mode="gateway">模型网关</button>
83
- <button class="${agent ? "on" : ""}" aria-pressed="${agent}" data-ai-mode="agent">Agent 自带模型</button>
84
- </div>
85
- <p class="note" id="wf-mode-note">${agent ? "使用已绑定 Agent 的模型、工具与会话上下文。" : "使用配置的模型网关;AI 分析会发送照片预览,不进入 Agent 会话。"}</p>
86
- ${S.step === "finish" ? `<label class="note" for="finish-mode">本次制作</label>
87
- <select id="finish-mode" style="width:100%;padding:7px;border:1px solid var(--line-2);border-radius:6px">
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
- <div class="row"><button class="btn btn-sm" id="wf-review" onclick="runCurrentStep('review')">检查本步</button>
97
- <button class="btn btn-sm btn-ai" id="wf-work" onclick="runCurrentStep('work')">${labels[S.step] || "处理本步"}</button></div>
98
- <p class="note" id="wf-connection" ${agent ? "" : 'style="display:none"'}>${agent ? "连接状态正在同步…" : ""}</p>
99
- ${agent ? `<button class="btn btn-sm ghost" onclick="openAgentPanel()">管理 Agent 连接</button>` : ""}
100
- </section>`;
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 agentWorkflowHtml() {
146
+ function agentStreamHtml() {
103
147
  if (!useAgentModel()) return "";
104
- return `<section class="agent-workflow" aria-label="当前步骤的 Agent 协作">
105
- <header>本步回复与反馈 · 同一会话</header>
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="ap-compose">
113
- <textarea id="ap-input" placeholder="继续追问、补充要求,或确认采用刚才的方案。仍然进入同一个 Agent 会话。" aria-label="给绑定 Agent 的回复">${H(AP.draft)}</textarea>
114
- <div class="row"><button class="btn btn-sm btn-primary" id="ap-send" onclick="sendAgentReply()">继续对话</button>
115
- <button class="btn btn-sm" id="ap-steer" onclick="controlAgent('steer')">补充到当前执行</button>
116
- <button class="btn btn-sm ghost" id="ap-stop" onclick="controlAgent('stop')">停止本轮</button></div>
117
- <button class="btn btn-sm ghost" style="margin-top:6px" onclick="syncAgentHistory()">同步会话回复</button>
118
- <p class="note" style="margin-top:6px">收到回复不会自动跳步。确认完成后,再使用本步底部的「进入下一步」。</p>
119
- </div></section>`;
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 wireWorkflow() {
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", $("cp-intent")?.value || "", S.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 内嵌的 OpenClaw gateway,绑定你正在看的那个会话;整轮执行,无中间批准。需在 openclaw.json 启用 chatCompletions 端点。";
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 = ""; if ($("ap-input")) $("ap-input").value = ""; pollJobs(); pollAgent();
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, ($("ap-input")?.value || AP.draft).trim());
414
+ return sendAgentMessage(action, deckInputText());
364
415
  }
365
416
  function sendAgentReply() {
366
- const text = ($("ap-input")?.value || AP.draft).trim();
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 = ($("ap-input")?.value || AP.draft).trim();
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 = ""; if ($("ap-input")) $("ap-input").value = ""; }
427
+ if (action === "steer") { AP.draft = ""; const el = deckInput(); if (el) el.value = ""; }
377
428
  pollAgent();
378
429
  }
379
430
  function answerAgent() {