@xyagent/cli 0.0.1 → 1.0.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.
Files changed (32) hide show
  1. package/bin/agentlink +551 -176
  2. package/bin/agentlink-agent +0 -0
  3. package/bin/agentlink-mcp-stdio +0 -0
  4. package/package.json +4 -2
  5. package/src/core.mjs +26 -0
  6. package/src-ext/bin.mjs +0 -1
  7. package/src-ext/commands/agent.mjs +0 -11
  8. package/src-ext/core/agentlinkToolsBootstrap.mjs +1 -1
  9. package/src-ext/core/autoTunnelDetector.mjs +6 -1
  10. package/src-ext/core/autoTunnelWiring.mjs +1 -1
  11. package/src-ext/core/bridgeSelfUninstall.mjs +1 -1
  12. package/src-ext/core/relayWorker.mjs +1 -1
  13. package/src-ext/core/scanDeeplink.mjs +67 -0
  14. package/src-ext/core/scanPairFlow.mjs +28 -0
  15. package/src-ext/core/scanQrRenderer.mjs +40 -0
  16. package/src-ext/core/unifiedDispatchHandler.mjs +3 -3
  17. package/src-ext/core/usageReporter.mjs +1 -2
  18. package/src-ext/openclaw-plugin/envelope-builder.cjs +2 -2
  19. package/src-ext/openclaw-plugin/todoTranslatorUtils.cjs +1 -1
  20. package/src-ext/runtime/_shared/relayObjectToBlock.mjs +9 -11
  21. package/src-ext/runtime/_shared/todoTranslatorUtils.mjs +1 -1
  22. package/src-ext/runtime/claude/launcher.mjs +0 -6
  23. package/src-ext/runtime/openclaw/buildOpenclawDaemonInput.mjs +0 -6
  24. package/src-shared/envelope_builder.mjs +2 -2
  25. package/src-ext/runtime/picoclaw/constants.mjs +0 -39
  26. package/src-ext/runtime/picoclaw/handleRequest.mjs +0 -289
  27. package/src-ext/runtime/picoclaw/index.mjs +0 -314
  28. package/src-ext/runtime/picoclaw/pairFlow.mjs +0 -224
  29. package/src-ext/runtime/picoclaw/state.mjs +0 -78
  30. package/src-ext/runtime/picoclaw/todoTranslator.mjs +0 -67
  31. package/src-ext/runtime/picoclaw/translator.mjs +0 -272
  32. package/src-ext/runtime/picoclaw/wsClient.mjs +0 -129
File without changes
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xyagent/cli",
3
- "version": "0.0.1",
3
+ "version": "1.0.0",
4
4
  "private": false,
5
5
  "description": "Relay-first CLI for pairing and bridging OpenClaw gateway traffic",
6
6
  "type": "module",
@@ -33,6 +33,8 @@
33
33
  "agentlink-mcp-stdio": "bin/agentlink-mcp-stdio"
34
34
  },
35
35
  "dependencies": {
36
- "pdfjs-dist": "^4.10.38"
36
+ "pdfjs-dist": "^4.10.38",
37
+ "qrcode": "^1.5.4",
38
+ "qrcode-terminal": "^0.12.0"
37
39
  }
38
40
  }
package/src/core.mjs CHANGED
@@ -80,6 +80,16 @@ export function normalizeProvidedCode(raw, onInvalid = throwError) {
80
80
  if (/^\d{4}$/.test(compact)) return compact;
81
81
  if (/^\d{6}$/.test(compact)) return compact;
82
82
  if (/^[A-Z0-9]{8}$/.test(compact)) return formatCode(compact);
83
+ // High-entropy scan code (`agentlink scan`, unify-scan-pairing): generated
84
+ // locally by the CLI via generateScanPairCode(), never hand-typed by a user
85
+ // (QR/deeplink transport only) — so no dash-grouping is applied. Contract
86
+ // with go_relay (real-device P0 fix): EXACTLY 16 uppercase hex chars
87
+ // (64 bits) — go_relay's `pair_sessions.pair_code` column is `varchar(16)`
88
+ // (no DDL), and go_relay's own validation was tightened to the same
89
+ // `^[0-9A-F]{16}$`. Do not widen this range again without a matching DDL —
90
+ // a wider CLI-side accept here previously let an 18-char/72-bit code
91
+ // through that overflowed the column (HTTP 500 "Data too long").
92
+ if (/^[A-Z0-9]{16}$/.test(compact)) return compact;
83
93
  return onInvalid(
84
94
  "invalid --code format. expected 4 digits (e.g. 1234), 6 digits (e.g. 123456), " +
85
95
  "or 8 alphanumeric characters (e.g. ABCD-1234; case-insensitive, dash optional)",
@@ -92,6 +102,22 @@ export function generatePairCode(random = Math.random, numericCodeLength = NUMER
92
102
  return String(min + Math.floor(random() * (max - min)));
93
103
  }
94
104
 
105
+ // High-entropy pair code for `agentlink scan` (unify-scan-pairing spec's "安全
106
+ // 信封" §1: >=64-bit entropy, cryptographic random source, unenumerable).
107
+ // EXACTLY 8 random bytes -> 16 uppercase hex chars = 64 bits — pinned to this
108
+ // exact length to fit go_relay's `pair_sessions.pair_code varchar(16)` column
109
+ // (no DDL, per user decision after a real-device P0: an earlier 9-byte/18-char
110
+ // version overflowed the column with HTTP 500 "Data too long"). 64 bits still
111
+ // clears the spec's >=64-bit floor for a short-TTL(<=5min) + rate-limited
112
+ // (30/min) one-time code. Never hand-typed by a user, so no dash-grouping
113
+ // (contrast generatePairCode()/formatCode() above, which target human entry).
114
+ // Do not change the byte count without re-confirming the column width with
115
+ // go_relay first.
116
+ export function generateScanPairCode(randomBytesFn = randomBytes) {
117
+ const buf = randomBytesFn(8);
118
+ return Buffer.from(buf).toString("hex").toUpperCase();
119
+ }
120
+
95
121
  export function parseNonNegativeInt(value, fallback = 0) {
96
122
  if (typeof value === "number" && Number.isFinite(value)) {
97
123
  return value >= 0 ? Math.trunc(value) : fallback;
package/src-ext/bin.mjs CHANGED
@@ -94,7 +94,6 @@ async function main() {
94
94
  case "claude":
95
95
  case "codex":
96
96
  case "hermes":
97
- case "picoclaw":
98
97
  await runAgentCommand({ runtime: subcommand, options, positional, log });
99
98
  return;
100
99
  case "service":
@@ -1,7 +1,6 @@
1
1
  import { runClaudeBridge } from "../runtime/claude/index.mjs";
2
2
  import { runCodexBridge } from "../runtime/codex/index.mjs";
3
3
  import { runHermesBridge } from "../runtime/hermes/index.mjs";
4
- import { runPicoclawBridge } from "../runtime/picoclaw/index.mjs";
5
4
  import { runPairInstall, runUninstall } from "./pair.mjs";
6
5
 
7
6
  /**
@@ -17,13 +16,6 @@ export async function runAgentCommand({ runtime, options, positional, log }) {
17
16
  const action = (positional[0] || "").toLowerCase();
18
17
 
19
18
  if (action === "pair") {
20
- // picoclaw 不走传统 `pair <code>`,daemon 启动后自动配对(QR 流)。
21
- if (runtime === "picoclaw") {
22
- process.stderr.write(
23
- "picoclaw 不需要 pair 子命令,直接启动 daemon: agentlink-agent picoclaw\n",
24
- );
25
- process.exit(2);
26
- }
27
19
  const code = String(positional[1] || options.pair || "").trim();
28
20
  if (!code) {
29
21
  process.stderr.write(`usage: agentlink pair <code> -r ${runtime}\n`);
@@ -55,9 +47,6 @@ export async function runAgentCommand({ runtime, options, positional, log }) {
55
47
  case "hermes":
56
48
  await runHermesBridge({ options, positional, log: rtLog });
57
49
  return;
58
- case "picoclaw":
59
- await runPicoclawBridge({ options, positional, log: rtLog });
60
- return;
61
50
  default:
62
51
  rtLog.error("bridge.unknown_runtime", "unsupported runtime", { runtime });
63
52
  process.stderr.write(`unsupported runtime: ${runtime}\n`);
@@ -19,7 +19,7 @@ import { createPathAttachScanner } from "./bridgeAttachAutoScanner.mjs";
19
19
 
20
20
  /**
21
21
  * @param {object} opts
22
- * @param {string} opts.runtime "claude"|"codex"|"hermes"|"openclaw"|"picoclaw"
22
+ * @param {string} opts.runtime "claude"|"codex"|"hermes"|"openclaw"
23
23
  * @param {string} opts.gwId gateway id (= clientUserID 跨 pair_session 反查的键)
24
24
  * @param {string} opts.relayUrl relay base URL
25
25
  * @param {string} opts.bridgeToken gateway/client token, used by attach 工具调 relay sign/commit
@@ -22,7 +22,12 @@ const LAN_HOST =
22
22
 
23
23
  // URL 尾部:可省略;若有,以 / ? # 起,止于空白或闭合标点。
24
24
  // 不包含 ) ] > " ' ` 避免吞 markdown / HTML / 引号边界。
25
- const URL_TAIL = "(?:[/?#][^\\s)\\]>\"'`]*)?";
25
+ // 另排除全角标点/括号(＀-￯,含全角 `()`)与 CJK 字符(CJK 标点
26
+ //  -ヿ、汉字 一-鿿、谚文 가-힯):模型常在 URL 后
27
+ // 紧跟中文说明(如 `index.html(返回 200 正常)`),不排除会把中文吞进 URL,
28
+ // 导致穿透 URL 与 markdown label 都带上乱码路径 → WebView 404。
29
+ const URL_TAIL =
30
+ "(?:[/?#][^\\s)\\]>\"'`\\u3000-\\u30FF\\u4E00-\\u9FFF\\uAC00-\\uD7AF\\uFF00-\\uFFEF]*)?";
26
31
 
27
32
  function buildRegex(hostFragment) {
28
33
  // 锚定:URL 前必须有非字母数字(或字符串开头)
@@ -8,7 +8,7 @@
8
8
  // 则禁用)选择是否注册到 bridgeHooks 全局 registry。
9
9
  // - 多次调用幂等:用模块级 flag 防止重复注册。
10
10
  //
11
- // 调用方:每个 runtime 入口(src-ext/runtime/{claude,codex,hermes,picoclaw}/index.mjs)
11
+ // 调用方:每个 runtime 入口(src-ext/runtime/{claude,codex,hermes}/index.mjs)
12
12
  // 在 bridge 主循环启动前调一次 installAutoTunnel({ relayUrl, log })。
13
13
 
14
14
  import fs from "node:fs";
@@ -43,7 +43,7 @@ function findCliBinary() {
43
43
 
44
44
  /**
45
45
  * @param {{ runtime: string, log: any }} opts
46
- * runtime: hermes / claude / codex / openclaw / picoclaw
46
+ * runtime: hermes / claude / codex / openclaw
47
47
  * log: bridge 自己的 logger(已经带 cid / gw context)
48
48
  *
49
49
  * 返回 Promise,不抛错 —— 任一步失败都 best-effort,最差也就是用户手动
@@ -111,7 +111,7 @@ export function createRelayWorker(opts) {
111
111
  // App 端用 `meta.online ?? meta.connected ?? meta.alive ?? meta.state` 串行
112
112
  // 兜底来判 bridge 是否在线(见 agent_app/matelink-app/lib/src/models/relay_models.dart:477)。
113
113
  // legacy openclaw bridge 的 `buildBridgePresenceMeta` 一直会发这套字段,
114
- // ext runtime(hermes / claude / codex / picoclaw)漏发会导致 onlineHint=null
114
+ // ext runtime(hermes / claude / codex)漏发会导致 onlineHint=null
115
115
  // → App UI 兜底显示"离线"(即用户报"配对完显示离线"的根因)。
116
116
  // detail === "stopped" 时表示主动下线,其它("ready" / "heartbeat" / "alive")都视为在线。
117
117
  async function sendMeta(detail) {
@@ -0,0 +1,67 @@
1
+ // `agentlink scan` 中性 deeplink 构造 + 本地 QR PNG 渲染。
2
+ //
3
+ // unify-scan-pairing 取代了上一代扫码硬件方案:旧方案的 QR payload/PNG 由**服务端**
4
+ // 在专用 create 端点响应里下发(`qr_payload` + `qr_png_base64`,已随硬件方案一并
5
+ // 删除)。新契约下 `POST /v1/pair-sessions` host-claim 响应不带任何二维码内容——
6
+ // CLI 必须自己构造 payload、自己渲染 PNG。
7
+ //
8
+ // 契约(spec「中性 deeplink 契约」,冻结,App 侧对齐):
9
+ // agentlink://pair/scan?v=1&code=<CODE>&relay=<urlencoded relay base>
10
+ // v = payload 版本号(当前 1)
11
+ // code = 高熵 pair code(generateScanPairCode(),见 src/core.mjs)
12
+ // relay= 可选;缺省时 App 用自己已配置的 relay。
13
+
14
+ import fs from "node:fs";
15
+ import path from "node:path";
16
+
17
+ const DEFAULT_DEEPLINK_VERSION = 1;
18
+
19
+ /**
20
+ * 构造中性 deeplink payload 字符串。
21
+ *
22
+ * @param {{ code: string, relay?: string, version?: number }} opts
23
+ * @returns {string}
24
+ */
25
+ export function buildScanDeeplink({ code, relay, version = DEFAULT_DEEPLINK_VERSION } = {}) {
26
+ const trimmedCode = String(code ?? "").trim();
27
+ if (!trimmedCode) {
28
+ throw new Error("buildScanDeeplink: code is required");
29
+ }
30
+ const params = new URLSearchParams();
31
+ params.set("v", String(version));
32
+ params.set("code", trimmedCode);
33
+ const trimmedRelay = String(relay ?? "").trim();
34
+ if (trimmedRelay) {
35
+ params.set("relay", trimmedRelay);
36
+ }
37
+ return `agentlink://pair/scan?${params.toString()}`;
38
+ }
39
+
40
+ /**
41
+ * 把 payload 渲染成 QR PNG 并落地到 outputPath。永不抛错——渲染失败时返回
42
+ * `{ ok: false, error }`,调用方(runScan)据此打印兜底提示,不中断守护循环
43
+ * (spec S5 的兜底精神:QR 展示失败不能中断配对流程;这里同样适用于 PNG 落地失败,
44
+ * 例如磁盘写入被拒)。
45
+ *
46
+ * @param {{
47
+ * payload: string,
48
+ * outputPath: string,
49
+ * generateImpl?: (payload: string, outputPath: string) => Promise<void>,
50
+ * }} opts
51
+ * @returns {Promise<{ ok: boolean, path?: string, error?: string }>}
52
+ */
53
+ export async function renderScanQrPng({ payload, outputPath, generateImpl }) {
54
+ const generate = generateImpl || defaultGenerateImpl;
55
+ try {
56
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
57
+ await generate(payload, outputPath);
58
+ return { ok: true, path: outputPath };
59
+ } catch (err) {
60
+ return { ok: false, error: String(err?.message || err) };
61
+ }
62
+ }
63
+
64
+ async function defaultGenerateImpl(payload, outputPath) {
65
+ const { default: QRCode } = await import("qrcode");
66
+ await QRCode.toFile(outputPath, String(payload ?? ""), { type: "png" });
67
+ }
@@ -0,0 +1,28 @@
1
+ // `agentlink scan` 二维码 PNG 落地路径 + 清理工具。
2
+ //
3
+ // unify-scan-pairing 全量移除了上一代扫码硬件方案:本模块曾承载其专用
4
+ // pair-code create/poll 客户端(`createPairCode`/`pollPairStatus`/
5
+ // `runScanOutcodeLoop`)、专用命名空间常量、只写不 issue 的
6
+ // `buildScanConfigPatch`,以及一个命名空间归一 hack——这些全部随该硬件方案的
7
+ // 专用端点一并删除,新扫码路径改走 `pair_sessions` host-claim(见 bin/agentlink
8
+ // 的 runScan/runPair pairMode="scan" 分支)+ 本地渲染的中性 deeplink QR
9
+ // (见 scanDeeplink.mjs)。
10
+ //
11
+ // 保留下来的只有与具体硬件方案无关的通用 QR 文件落地/清理工具:CLI 出码循环
12
+ // 每轮把新二维码 PNG 写到约定路径,过期重出码或配对成功后清掉旧文件。
13
+
14
+ import fs from "node:fs";
15
+
16
+ // 二维码 PNG 落地兜底路径,可用 --qr-path 或 AGENTLINK_SCAN_QR_PNG_PATH 覆盖。
17
+ export const SCAN_QR_PNG_OUTPUT_PATH =
18
+ process.env.AGENTLINK_SCAN_QR_PNG_PATH?.trim() || "/tmp/bubu/scan-pair-qr.png";
19
+
20
+ /**
21
+ * 删除 QR 图片(配对成功/重新出码前清理)。不存在时是安全的 no-op。
22
+ */
23
+ export function clearQRImage(outputPath) {
24
+ const target = outputPath || SCAN_QR_PNG_OUTPUT_PATH;
25
+ try {
26
+ fs.unlinkSync(target);
27
+ } catch {}
28
+ }
@@ -0,0 +1,40 @@
1
+ // `agentlink scan` 终端二维码渲染 —— 封装 `qrcode-terminal`(`small: true`)。
2
+ //
3
+ // spec S6(二维码展示兜底):终端不支持 ASCII 二维码渲染时(渲染抛错),
4
+ // SHALL 回退为打印 payload 文本,不因渲染失败中断配对流程。因此本函数
5
+ // **永不抛错**——渲染失败被内部吞掉并转成文本兜底。
6
+
7
+ import qrcodeTerminal from "qrcode-terminal";
8
+
9
+ /**
10
+ * 在终端渲染 payload 的 ASCII 二维码;渲染失败时回退打印 payload 文本。
11
+ *
12
+ * @param {string} payload 二维码内容(qr_payload deeplink)
13
+ * @param {{
14
+ * generateImpl?: (input: string, opts: {small: boolean}, cb: (output: string) => void) => void,
15
+ * print?: (s: string) => void,
16
+ * warn?: (s: string) => void,
17
+ * }} [deps]
18
+ * @returns {{ ok: boolean, mode: "qr" | "text", error?: string }}
19
+ */
20
+ export function renderQrToTerminal(payload, { generateImpl, print, warn } = {}) {
21
+ // NB: must call through `qrcodeTerminal.generate(...)`, not a bare
22
+ // reference to the method — qrcode-terminal's generate() reads `this.error`
23
+ // internally, and a detached reference loses that binding (`this` would be
24
+ // undefined under ESM's implicit strict mode), throwing on every call.
25
+ const generate = generateImpl || ((input, opts, cb) => qrcodeTerminal.generate(input, opts, cb));
26
+ const doPrint = print || ((s) => console.log(s));
27
+ const doWarn = warn || ((s) => console.warn(s));
28
+ const text = payload == null ? "" : String(payload);
29
+ try {
30
+ generate(text, { small: true }, (output) => {
31
+ doPrint(output);
32
+ });
33
+ return { ok: true, mode: "qr" };
34
+ } catch (err) {
35
+ const message = String(err?.message || err);
36
+ doWarn(`⚠ 二维码渲染失败,回退为文本(仍可完成配对):${message}`);
37
+ doPrint(text);
38
+ return { ok: false, mode: "text", error: message };
39
+ }
40
+ }
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Injection points for testing:
11
11
  * producerFactory({ relayUrl, bridgeToken, threadId }) → { postEvents }
12
- * openclawInvoke({ input, onChunk }) → async void (default: picoclaw wsHolder)
12
+ * openclawInvoke({ input, onChunk }) → async void (default: openclaw wsHolder)
13
13
  * flushPolicy({ size, timeMs }) → 控制 micro-batched flush 触发阈值(默认 {size:1, timeMs:0} —— 即来即发,保流式体验)
14
14
  */
15
15
 
@@ -29,7 +29,7 @@ import { signGetCosObject } from "./cosUploadClient.mjs";
29
29
  // Streaming-first policy: size:1 means every envelope (especially
30
30
  // content_block.delta) posts immediately, so the App SSE stream receives
31
31
  // chunks as the runtime produces them rather than in 8-envelope batches.
32
- // Tests/picoclaw replay can override via flushPolicy if they want fewer POSTs.
32
+ // Tests can override via flushPolicy if they want fewer POSTs.
33
33
  const DEFAULT_FLUSH_POLICY = Object.freeze({ size: 1, timeMs: 0 });
34
34
  const STREAM_DEBUG = process.env.BUBBO_DEBUG_STREAM === "1";
35
35
 
@@ -343,7 +343,7 @@ export function createUnifiedDispatchHandler({
343
343
  let runPausedFromRuntime = false;
344
344
 
345
345
  if (typeof openclawInvoke === "function") {
346
- // Injected (test mode or picoclaw ws integration)
346
+ // Injected (test mode or ws integration)
347
347
  // thread_id 透传给 runtime adapter,让需要"按 thread 维护原生 session
348
348
  // 续接"的 runtime(如 claude --resume <sid>)可以做 mapping。
349
349
  await openclawInvoke({
@@ -40,8 +40,7 @@ export function createUsageReporter({ publishEvent, runtime, log, debounceMs = D
40
40
  if (
41
41
  runtime !== "claude" &&
42
42
  runtime !== "codex" &&
43
- runtime !== "hermes" &&
44
- runtime !== "picoclaw"
43
+ runtime !== "hermes"
45
44
  ) {
46
45
  throw new Error(`createUsageReporter: bad runtime ${runtime}`);
47
46
  }
@@ -448,7 +448,7 @@ function translateOpenClawChunkToEnvelope(chunk, runId, blockState) {
448
448
  }
449
449
  envelopes.push(env);
450
450
  } else if (event === "response.function_call_arguments.delta") {
451
- // picoclaw legacy event name → maps to tool_use.start + arguments.delta.
451
+ // legacy event name → maps to tool_use.start + arguments.delta.
452
452
  // chunk shape: { call_id, name, arguments } where arguments is a JSON
453
453
  // fragment to append. We auto-start a tool_use block on first occurrence.
454
454
  const callId = String(chunk.call_id || "");
@@ -540,7 +540,7 @@ function translateOpenClawChunkToEnvelope(chunk, runId, blockState) {
540
540
  envelopes.push(started, completed);
541
541
  } else if (event === "response.output_text.done") {
542
542
  // No incremental envelope needed — full text already accumulated via deltas.
543
- // If runtime carries authoritative full text on .done (e.g. picoclaw), prefer it.
543
+ // If the runtime carries authoritative full text on .done, prefer it.
544
544
  if (typeof chunk.text === "string" && chunk.text.length > 0) {
545
545
  text.content = chunk.text;
546
546
  }
@@ -75,7 +75,7 @@ function normalizeStatus(raw) {
75
75
  * rawItems 中每项支持字段:
76
76
  * - id?: string(有则保留,无则生成 td_{idx})
77
77
  * - content?: string(主内容字段,Claude/Hermes 用)
78
- * - step?: string(Codex/picoclaw 用,作为 content 的 fallback)
78
+ * - step?: string(Codex 用,作为 content 的 fallback)
79
79
  * - status?: string(经 normalizeStatus 归一)
80
80
  *
81
81
  * @param {Array<{id?: string, content?: string, step?: string, status?: string}>} rawItems
@@ -12,9 +12,9 @@
12
12
  * { type: "image"|"attachment", source: { kind: "url", url, media_type, attachment_kind }, name?, mime?, size? }
13
13
  *
14
14
  * @param {object} opts
15
- * @param {string} opts.runtimeHint - "claude"|"openclaw"|"picoclaw"|"codex"|"hermes"
15
+ * @param {string} opts.runtimeHint - "claude"|"openclaw"|"codex"|"hermes"
16
16
  * @param {string[]|null} opts.modelCaps - modalities 数组(如 ["text","image","pdf"])。
17
- * null 表示跳过 caps 检查(picoclaw)。
17
+ * null 表示跳过 caps 检查。
18
18
  * 缺失时默认 ["text"]。
19
19
  * @param {Function} opts.fetchAndBase64Encode - async (url, opts) => { base64, mediaType, bytes }
20
20
  * @param {Function} [opts.fetchAndDecodeText] - async (url, opts) => string
@@ -26,7 +26,6 @@
26
26
  * { type: "input_image", source: { type: "base64", media_type, data } }
27
27
  * { type: "input_file", source: { type: "base64", media_type: "application/pdf", filename, data } }
28
28
  * { type: "input_text", text }
29
- * { type: "url_ref", url, media_type } // picoclaw only
30
29
  *
31
30
  * ErrorResult:
32
31
  * { error: { code: "caps_mismatch", message, model?, missing_modality } }
@@ -70,8 +69,12 @@ function resolveAttachmentKind(block) {
70
69
 
71
70
  export async function relayObjectToBlock(block, opts = {}) {
72
71
  const {
73
- runtimeHint,
74
- modelCaps, // null = skip caps check (picoclaw); undefined/missing = default ["text"]
72
+ // 曾经唯一消费 runtimeHint 的分支(url_ref 直通)是移除硬件方案时删掉的
73
+ // 专属逻辑;所有真实 runtime(openclaw/claude/hermes)现在走同一条
74
+ // fetch+base64 路径,不再需要按 runtime 分叉。保留在签名里是为了不动调用方
75
+ // (openclaw/claude/hermes 三处仍显式传它),留作未来按 runtime 差异化的挂钩点。
76
+ runtimeHint: _runtimeHint,
77
+ modelCaps, // null = skip caps check; undefined/missing = default ["text"]
75
78
  fetchAndBase64Encode,
76
79
  fetchAndDecodeText,
77
80
  } = opts;
@@ -107,12 +110,7 @@ export async function relayObjectToBlock(block, opts = {}) {
107
110
  };
108
111
  }
109
112
 
110
- // 2. picoclaw url_ref(无 fetch)
111
- if (runtimeHint === "picoclaw") {
112
- return { type: "url_ref", url, media_type: mediaType };
113
- }
114
-
115
- // 3. fetch + base64
113
+ // 2. fetch + base64
116
114
  try {
117
115
  const { base64, mediaType: resolvedMime, bytes } = await fetchAndBase64Encode(url, {
118
116
  expectedMime: mediaType,
@@ -72,7 +72,7 @@ export function normalizeStatus(raw) {
72
72
  * rawItems 中每项支持字段:
73
73
  * - id?: string(有则保留,无则生成 td_{idx})
74
74
  * - content?: string(主内容字段,Claude/Hermes 用)
75
- * - step?: string(Codex/picoclaw 用,作为 content 的 fallback)
75
+ * - step?: string(Codex 用,作为 content 的 fallback)
76
76
  * - status?: string(经 normalizeStatus 归一)
77
77
  *
78
78
  * @param {Array<{id?: string, content?: string, step?: string, status?: string}>} rawItems
@@ -285,12 +285,6 @@ export async function writeUserMessage(child, text, blocks = [], { _fetchAndBase
285
285
  case "input_text":
286
286
  content.push({ type: "text", text: result.text });
287
287
  break;
288
- case "url_ref":
289
- // picoclaw only — claude runtime never returns url_ref from relayObjectToBlock;
290
- // if this branch is somehow hit, drop to a safe description with NO URL in LLM context
291
- // (铁律 #1: signed URL must not appear in LLM input). (CR-003)
292
- content.push({ type: "text", text: `[image] (preview unavailable for this runtime)` });
293
- break;
294
288
  default:
295
289
  break;
296
290
  }
@@ -127,12 +127,6 @@ export async function buildOpenclawDaemonInput(textInput, blocks, {
127
127
  // text 文件内容 / binary 描述 / document text extraction fallback
128
128
  content.push({ type: "input_text", text: result.text });
129
129
  break;
130
- case "url_ref":
131
- // picoclaw only — openclaw/claude runtimes never return url_ref from relayObjectToBlock;
132
- // if this branch is somehow hit, drop to a safe description with NO URL in LLM context
133
- // (铁律 #1: signed URL must not appear in LLM input). (CR-003)
134
- content.push({ type: "input_text", text: `[image] (preview unavailable for this runtime)` });
135
- break;
136
130
  default:
137
131
  break;
138
132
  }
@@ -445,7 +445,7 @@ export function translateOpenClawChunkToEnvelope(chunk, runId, blockState) {
445
445
  }
446
446
  envelopes.push(env);
447
447
  } else if (event === "response.function_call_arguments.delta") {
448
- // picoclaw legacy event name → maps to tool_use.start + arguments.delta.
448
+ // legacy event name → maps to tool_use.start + arguments.delta.
449
449
  // chunk shape: { call_id, name, arguments } where arguments is a JSON
450
450
  // fragment to append. We auto-start a tool_use block on first occurrence.
451
451
  const callId = String(chunk.call_id || "");
@@ -537,7 +537,7 @@ export function translateOpenClawChunkToEnvelope(chunk, runId, blockState) {
537
537
  envelopes.push(started, completed);
538
538
  } else if (event === "response.output_text.done") {
539
539
  // No incremental envelope needed — full text already accumulated via deltas.
540
- // If runtime carries authoritative full text on .done (e.g. picoclaw), prefer it.
540
+ // If the runtime carries authoritative full text on .done, prefer it.
541
541
  if (typeof chunk.text === "string" && chunk.text.length > 0) {
542
542
  text.content = chunk.text;
543
543
  }
@@ -1,39 +0,0 @@
1
- // 巴布(PicoClaw)运行时常量。
2
- //
3
- // 部署假设:matelink-cli 与 picoclaw daemon 烧录在同一开发板,
4
- // cli 默认连本地回环 picoclaw WebSocket。token 烧录时固化,cli 端写死同值。
5
- //
6
- // 如需改 token,**两处**(开发板 picoclaw config + 此文件)必须同步,否则连接失败。
7
-
8
- import process from "node:process";
9
-
10
- // PicoClaw daemon 的 WebSocket 端点(Pico Protocol)。
11
- // 默认 127.0.0.1:18790/pico/ws,可通过 env 覆盖(开发/调试用)。
12
- export const PICOCLAW_WS_URL =
13
- process.env.PICOCLAW_WS_URL?.trim() || "ws://127.0.0.1:18790/pico/ws";
14
-
15
- // PicoClaw Bearer token —— 与开发板 picoclaw config 里 [pico].token 一致。
16
- // 生产烧录时双方都写死成下面这个固定值;切换值时必须双侧同步。
17
- export const PICOCLAW_TOKEN =
18
- process.env.PICOCLAW_TOKEN?.trim() || "bubu-local-token";
19
-
20
- // 巴布配对二维码 PNG 输出路径 —— 屏幕显示模块从这个路径加载图片。
21
- // 默认写到 /tmp/bubu/pair-qr.png,可通过 env 覆盖。
22
- export const QR_PNG_OUTPUT_PATH =
23
- process.env.BUBU_QR_PNG_PATH?.trim() || "/tmp/bubu/pair-qr.png";
24
-
25
- // 配对状态轮询间隔(秒)。服务端建议值在 create 响应里返回,这是兜底。
26
- export const PAIR_STATUS_POLL_INTERVAL_SEC = 2;
27
-
28
- // 配对状态最长等待时间(秒)。超过后 cli 重新生成 QR(配对码已过期)。
29
- export const PAIR_STATUS_POLL_MAX_SEC = 300;
30
-
31
- // 长轮询拉取请求的等待时长(秒)。
32
- export const LONG_POLL_WAIT_SEC = 30;
33
-
34
- // PicoClaw WS 重连退避(毫秒)— 指数退避,上限 30s。
35
- export const WS_RECONNECT_INITIAL_MS = 1000;
36
- export const WS_RECONNECT_MAX_MS = 30000;
37
-
38
- // runtime 名 — 必须与服务端 RuntimeKindPicoclaw 字符串一致。
39
- export const RUNTIME_NAME = "picoclaw";