@xyagent/cli 0.0.1 → 1.1.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 (72) hide show
  1. package/README.md +42 -0
  2. package/bin/agentlink +1002 -245
  3. package/bin/agentlink-agent +0 -0
  4. package/bin/agentlink-mcp-stdio +0 -0
  5. package/package.json +5 -3
  6. package/src/core.mjs +26 -0
  7. package/src/tunnel_service.mjs +17 -1
  8. package/src-ext/bin.mjs +12 -12
  9. package/src-ext/commands/agent.mjs +33 -10
  10. package/src-ext/commands/pair.mjs +122 -23
  11. package/src-ext/commands/service.mjs +1 -1
  12. package/src-ext/core/activeRuns.mjs +26 -9
  13. package/src-ext/core/agentlinkToolsBootstrap.mjs +1 -1
  14. package/src-ext/core/autoTunnelDetector.mjs +6 -1
  15. package/src-ext/core/autoTunnelWiring.mjs +1 -1
  16. package/src-ext/core/bridgeSelfUninstall.mjs +1 -1
  17. package/src-ext/core/defaultWorkspace.mjs +43 -13
  18. package/src-ext/core/defaultWorkspaceSync.mjs +20 -0
  19. package/src-ext/core/installationIdentity.mjs +94 -0
  20. package/src-ext/core/mcpRuntimeFanout.mjs +23 -1
  21. package/src-ext/core/pairCodeClient.mjs +48 -8
  22. package/src-ext/core/pairInventory.mjs +31 -6
  23. package/src-ext/core/relayWorker.mjs +22 -2
  24. package/src-ext/core/runtimeRegistry.mjs +180 -0
  25. package/src-ext/core/scanDeeplink.mjs +67 -0
  26. package/src-ext/core/scanPairFlow.mjs +28 -0
  27. package/src-ext/core/scanQrRenderer.mjs +40 -0
  28. package/src-ext/core/scanWorkspaces.mjs +163 -23
  29. package/src-ext/core/unifiedDispatchHandler.mjs +12 -5
  30. package/src-ext/core/usageReporter.mjs +1 -2
  31. package/src-ext/openclaw-plugin/envelope-builder.cjs +2 -2
  32. package/src-ext/openclaw-plugin/todoTranslatorUtils.cjs +1 -1
  33. package/src-ext/runtime/_shared/bridgedSessionLedger.mjs +60 -0
  34. package/src-ext/runtime/_shared/claudeSchemaEvent.mjs +49 -0
  35. package/src-ext/runtime/_shared/headlessCliBridge.mjs +168 -0
  36. package/src-ext/runtime/_shared/jsonMcpConfigAdapter.mjs +70 -0
  37. package/src-ext/runtime/_shared/ndjsonProcess.mjs +141 -0
  38. package/src-ext/runtime/_shared/relayObjectToBlock.mjs +9 -11
  39. package/src-ext/runtime/_shared/resolveWorkspaceCwd.mjs +47 -0
  40. package/src-ext/runtime/_shared/slashCommandRouter.mjs +10 -0
  41. package/src-ext/runtime/_shared/todoTranslatorUtils.mjs +1 -1
  42. package/src-ext/runtime/claude/handleRequest.mjs +15 -37
  43. package/src-ext/runtime/claude/launcher.mjs +0 -6
  44. package/src-ext/runtime/claude/stdoutParser.mjs +8 -3
  45. package/src-ext/runtime/codebuddy/index.mjs +41 -0
  46. package/src-ext/runtime/codex/handleRequest.mjs +13 -35
  47. package/src-ext/runtime/cursor/index.mjs +46 -0
  48. package/src-ext/runtime/cursor/mcpConfigAdapter.mjs +15 -0
  49. package/src-ext/runtime/deepagents/preflight.mjs +57 -0
  50. package/src-ext/runtime/hermes/envSetup.mjs +22 -8
  51. package/src-ext/runtime/hermes/gatewayManager.mjs +239 -3
  52. package/src-ext/runtime/hermes/handleRequest.mjs +13 -0
  53. package/src-ext/runtime/hermes/httpBackend.mjs +12 -1
  54. package/src-ext/runtime/hermes/index.mjs +1 -1
  55. package/src-ext/runtime/hermes/preflight.mjs +2 -1
  56. package/src-ext/runtime/kimi/index.mjs +100 -0
  57. package/src-ext/runtime/openclaw/buildOpenclawDaemonInput.mjs +0 -6
  58. package/src-ext/runtime/openclaw/workspaceContext.mjs +58 -0
  59. package/src-ext/runtime/opencode/index.mjs +48 -0
  60. package/src-ext/runtime/opencode/mcpConfigAdapter.mjs +15 -0
  61. package/src-ext/runtime/opencode/preflight.mjs +72 -0
  62. package/src-ext/runtime/qwen/index.mjs +42 -0
  63. package/src-ext/service/serviceManager.mjs +120 -42
  64. package/src-shared/envelope_builder.mjs +2 -2
  65. package/src-ext/runtime/picoclaw/constants.mjs +0 -39
  66. package/src-ext/runtime/picoclaw/handleRequest.mjs +0 -289
  67. package/src-ext/runtime/picoclaw/index.mjs +0 -314
  68. package/src-ext/runtime/picoclaw/pairFlow.mjs +0 -224
  69. package/src-ext/runtime/picoclaw/state.mjs +0 -78
  70. package/src-ext/runtime/picoclaw/todoTranslator.mjs +0 -67
  71. package/src-ext/runtime/picoclaw/translator.mjs +0 -272
  72. package/src-ext/runtime/picoclaw/wsClient.mjs +0 -129
@@ -1,272 +0,0 @@
1
- // Pico Protocol ↔ OpenAI Responses API 风格 native event 翻译器。
2
- //
3
- // CLI 把巴布(PicoClaw)WebSocket 上的 PicoMessage 翻译成服务端
4
- // `internal/adapter/picoclaw` 期望的 native event 流(与 openclaw 共享 mapper)。
5
- //
6
- // PicoMessage 格式:
7
- // { type, id, session_id, timestamp, payload }
8
- //
9
- // CLI 出参格式(与 openclaw bridge 现有产物对齐):
10
- // { event: "start" }
11
- // { event: "chunk", data: JSON.stringify({ type: "response.output_text.delta", delta: "..." }) }
12
- // { event: "chunk", data: JSON.stringify({ type: "response.reasoning_summary_text.delta", delta: "..." }) }
13
- // { event: "chunk", data: JSON.stringify({ type: "response.function_call_arguments.delta", call_id, name, arguments }) }
14
- // { event: "chunk", data: JSON.stringify({ type: "response.output_text.done", text: "<full>" }) }
15
- // { event: "end" }
16
- // { event: "error", error: "msg" }
17
- //
18
- // **Pico → OpenClaw 映射表**:
19
- //
20
- // | Pico (server→client) | OpenClaw native (CLI emits) |
21
- // |----------------------|------------------------------|
22
- // | message.create | { event: "start" }(每个 turn 起头一次)|
23
- // | message.update (default kind, payload.content) | response.output_text.delta(算 delta) |
24
- // | message.update (kind=thought) | response.reasoning_summary_text.delta |
25
- // | message.update (kind=tool_calls) | response.function_call_arguments.delta(简化:把 tool_calls JSON 序列化进 arguments) |
26
- // | typing.stop / 最后一条 update | response.output_text.done + { event: "end" } |
27
- // | media.create | 一期降级:转成 response.output_text.delta 加占位文本(支持 base64/url 二期) |
28
- // | error | { event: "error", error: msg } + { event: "end" } |
29
- // | pong | drop |
30
- //
31
- // **Delta 计算**: PicoClaw 的 message.update 通常发"累积全文",CLI 通过比对
32
- // 内部 lastText 字段算出 delta 增量再发。如果服务端遇到回退场景(content
33
- // 比上次短),按"重置 + 全量重发"处理(不应发生,但写防御代码)。
34
-
35
- const KIND_THOUGHT = "thought";
36
- const KIND_TOOL_CALLS = "tool_calls";
37
-
38
- // 单条 turn 的累积状态。每个 turn 一个,`reset()` 后复用。
39
- export function createTurnState() {
40
- return {
41
- started: false,
42
- finalized: false,
43
- // 主回复正文的累积值
44
- textAccum: "",
45
- // 思考链的累积值
46
- thoughtAccum: "",
47
- // tool_calls 列表的累积 JSON(上次发送的)
48
- toolCallsAccum: "",
49
- // 上次响应的 PicoClaw message_id
50
- lastMessageID: "",
51
- };
52
- }
53
-
54
- /**
55
- * 翻译一条 PicoMessage 为 0..N 个 native event。
56
- *
57
- * @param {object} msg PicoMessage `{ type, id, session_id, timestamp, payload }`
58
- * @param {object} state createTurnState() 返回的状态
59
- * @returns {Array<{event:string, data?:string, error?:string}>} native events
60
- */
61
- export function translatePicoMessage(msg, state) {
62
- if (!msg || typeof msg !== "object") return [];
63
- // Bug 4 防御:turn 已 finalized 后再来 update/media 等都直接丢弃,避免发出
64
- // 服务端 run 已 completed 之后的 delta 导致状态机错位。pong/error 仍处理
65
- // (error 即使在 finalize 后也要透传)。
66
- if (state.finalized && msg.type !== "error" && msg.type !== "pong") {
67
- return [];
68
- }
69
- const out = [];
70
- switch (msg.type) {
71
- case "message.create": {
72
- // 一个 turn 的起始 — 服务端 openclaw mapper 期望 "start"。
73
- if (!state.started) {
74
- state.started = true;
75
- out.push({ event: "start" });
76
- }
77
- // payload.content 可能携带初始文本,按 update 处理一次。
78
- const content = readContent(msg);
79
- if (content) {
80
- emitDeltaForKind(out, state, msg, content);
81
- }
82
- return out;
83
- }
84
- case "message.update": {
85
- if (!state.started) {
86
- state.started = true;
87
- out.push({ event: "start" });
88
- }
89
- const content = readContent(msg);
90
- if (content !== null && content !== undefined) {
91
- emitDeltaForKind(out, state, msg, content);
92
- }
93
- return out;
94
- }
95
- case "media.create": {
96
- // 一期:把 media 描述 inline 成正文占位文字,告诉用户"这里有一张图"。
97
- // 二期会接 /pico/media/<id> 下载 + 转 base64 + response.output_text.delta with image。
98
- if (!state.started) {
99
- state.started = true;
100
- out.push({ event: "start" });
101
- }
102
- const placeholder = describeMedia(msg);
103
- if (placeholder) {
104
- appendDelta(out, state, "text", placeholder);
105
- }
106
- return out;
107
- }
108
- case "typing.stop": {
109
- // 流结束信号 — 收到后 finalize。
110
- finalizeTurn(out, state);
111
- return out;
112
- }
113
- case "error": {
114
- const errMsg = (msg.payload && (msg.payload.message || msg.payload.error)) || "picoclaw error";
115
- out.push({ event: "error", error: String(errMsg) });
116
- // error 后强制 end,避免服务端 adapter 觉得 turn 悬空
117
- if (!state.finalized) {
118
- state.finalized = true;
119
- out.push({ event: "end" });
120
- }
121
- return out;
122
- }
123
- case "typing.start":
124
- case "pong":
125
- return [];
126
- default:
127
- // 未识别消息类型 — 一期忽略,日志侧记录(translator 不持有 logger;调用方记)。
128
- return [];
129
- }
130
- }
131
-
132
- /**
133
- * 在调用方决定 turn 已结束时(比如 WS 端关闭、超时、显式 typing.stop 都没拿到),
134
- * 强制 finalize。幂等。
135
- */
136
- export function forceFinalize(state) {
137
- const out = [];
138
- finalizeTurn(out, state);
139
- return out;
140
- }
141
-
142
- // ── internal ────────────────────────────────────────────────────
143
-
144
- function emitDeltaForKind(out, state, msg, content) {
145
- const kind = readKind(msg);
146
- if (kind === KIND_THOUGHT) {
147
- appendDelta(out, state, "thought", content);
148
- return;
149
- }
150
- if (kind === KIND_TOOL_CALLS) {
151
- // tool_calls 的 content 期望是 JSON(数组或对象);序列化成 arguments string。
152
- const argsJSON = typeof content === "string" ? content : safeJSON(content);
153
- if (argsJSON !== state.toolCallsAccum) {
154
- state.toolCallsAccum = argsJSON;
155
- out.push({
156
- event: "chunk",
157
- data: JSON.stringify({
158
- type: "response.function_call_arguments.delta",
159
- call_id: String(msg.id || msg.session_id || ""),
160
- name: pickToolName(msg) || "tool_call",
161
- arguments: argsJSON,
162
- }),
163
- });
164
- }
165
- return;
166
- }
167
- // 默认:正文 delta
168
- appendDelta(out, state, "text", content);
169
- }
170
-
171
- function appendDelta(out, state, kind, content) {
172
- const text = typeof content === "string" ? content : String(content);
173
- let delta;
174
- if (kind === "text") {
175
- delta = computeDelta(state.textAccum, text);
176
- if (delta == null) return;
177
- state.textAccum = text;
178
- out.push({
179
- event: "chunk",
180
- data: JSON.stringify({
181
- type: "response.output_text.delta",
182
- delta,
183
- }),
184
- });
185
- } else if (kind === "thought") {
186
- delta = computeDelta(state.thoughtAccum, text);
187
- if (delta == null) return;
188
- state.thoughtAccum = text;
189
- out.push({
190
- event: "chunk",
191
- data: JSON.stringify({
192
- type: "response.reasoning_summary_text.delta",
193
- delta,
194
- }),
195
- });
196
- }
197
- }
198
-
199
- // computeDelta: 优先按"累积全文"模型计算增量;若新文本比旧短(异常 reset),
200
- // 把 prev 当空,delta = full new。返回 null 表示没增量(同文本)。
201
- function computeDelta(prev, next) {
202
- if (next === prev) return null;
203
- if (next.startsWith(prev)) return next.slice(prev.length);
204
- // 回退/重置 — 直接整段重发
205
- return next;
206
- }
207
-
208
- function finalizeTurn(out, state) {
209
- if (state.finalized) return;
210
- state.finalized = true;
211
- if (state.started && state.textAccum) {
212
- out.push({
213
- event: "chunk",
214
- data: JSON.stringify({
215
- type: "response.output_text.done",
216
- text: state.textAccum,
217
- }),
218
- });
219
- }
220
- if (state.started) {
221
- out.push({ event: "end" });
222
- }
223
- }
224
-
225
- function readContent(msg) {
226
- if (!msg || typeof msg.payload !== "object" || msg.payload === null) return null;
227
- const c = msg.payload.content;
228
- if (c === undefined || c === null) return null;
229
- if (typeof c === "string") return c;
230
- return c; // 留给 emitDeltaForKind 决定如何处理(tool_calls 用 JSON)
231
- }
232
-
233
- function readKind(msg) {
234
- if (!msg || typeof msg.payload !== "object" || msg.payload === null) return "";
235
- const k = msg.payload.kind;
236
- if (typeof k === "string") return k.trim().toLowerCase();
237
- // legacy: payload.thought=true
238
- if (msg.payload.thought === true) return KIND_THOUGHT;
239
- return "";
240
- }
241
-
242
- function pickToolName(msg) {
243
- if (!msg || typeof msg.payload !== "object" || msg.payload === null) return "";
244
- if (Array.isArray(msg.payload.tool_calls) && msg.payload.tool_calls[0]) {
245
- const t = msg.payload.tool_calls[0];
246
- if (t && typeof t === "object") {
247
- if (typeof t.name === "string") return t.name;
248
- if (t.function && typeof t.function.name === "string") return t.function.name;
249
- }
250
- }
251
- return "";
252
- }
253
-
254
- function describeMedia(msg) {
255
- if (!msg || typeof msg.payload !== "object" || msg.payload === null) return "";
256
- const p = msg.payload;
257
- if (typeof p.url === "string" && p.url.length > 0) {
258
- return `\n[media: ${p.url}]\n`;
259
- }
260
- if (typeof p.id === "string" && p.id.length > 0) {
261
- return `\n[media:${p.id}]\n`;
262
- }
263
- return "\n[media]\n";
264
- }
265
-
266
- function safeJSON(v) {
267
- try {
268
- return JSON.stringify(v);
269
- } catch {
270
- return "";
271
- }
272
- }
@@ -1,129 +0,0 @@
1
- // PicoClaw daemon WebSocket 客户端。
2
- //
3
- // PicoClaw daemon 默认监听 127.0.0.1:18790,WS 路径 /pico/ws。
4
- // 鉴权 3 种方式(picoclaw 自身支持):
5
- // 1. Authorization: Bearer <token> (服务端 / Node `ws` 包可用)
6
- // 2. Sec-WebSocket-Protocol "token.<value>" (浏览器 / Node 22 内置 WebSocket 用这条)
7
- // 3. query 参数 ?token=<value>(picoclaw 必须开启 AllowTokenQuery)
8
- //
9
- // 这里走 #2 子协议 — Node 22 内置 WebSocket 不支持自定义 header,但支持
10
- // 第二参数 protocols。`@xyagent/cli` 之前没有任何 npm 依赖,我们保持零依赖,
11
- // 直接用 globalThis.WebSocket(Node ≥21 内置)。
12
- //
13
- // 通讯使用 PicoMessage:
14
- // { type, id, session_id, timestamp, payload }
15
- //
16
- // CLI 端只需:
17
- // send(msg) —— 发消息(JSON 序列化)
18
- // onMessage(fn) —— 收消息回调
19
- // onClose(fn) —— 断开回调(自动重连由调用方决定)
20
- // close() —— 主动关闭
21
-
22
- import {
23
- PICOCLAW_TOKEN,
24
- PICOCLAW_WS_URL,
25
- } from "./constants.mjs";
26
-
27
- /**
28
- * 建一个 PicoClaw 连接。
29
- * @returns {Promise<PicoclawConn>}
30
- */
31
- export function connect({ url, token, log }) {
32
- const target = url || PICOCLAW_WS_URL;
33
- const bearer = token || PICOCLAW_TOKEN;
34
- return new Promise((resolve, reject) => {
35
- const subprotocol = `token.${bearer}`;
36
- const WS = globalThis.WebSocket;
37
- if (!WS) {
38
- reject(new Error("global WebSocket not available; require Node >= 21"));
39
- return;
40
- }
41
- const ws = new WS(target, [subprotocol]);
42
- let settled = false;
43
- ws.addEventListener("open", () => {
44
- if (settled) return;
45
- settled = true;
46
- resolve(makeConn(ws, log, target));
47
- });
48
- ws.addEventListener("error", (ev) => {
49
- if (settled) return;
50
- settled = true;
51
- const msg = (ev && ev.message) || "ws error";
52
- reject(new Error(String(msg)));
53
- });
54
- ws.addEventListener("close", (ev) => {
55
- if (settled) return;
56
- settled = true;
57
- reject(new Error(`picoclaw ws closed before open: code=${ev?.code}`));
58
- });
59
- });
60
- }
61
-
62
- function makeConn(ws, log, target) {
63
- /** @type {Set<(msg:any)=>void>} */
64
- const messageHandlers = new Set();
65
- /** @type {Set<(reason:string)=>void>} */
66
- const closeHandlers = new Set();
67
-
68
- ws.addEventListener("message", (ev) => {
69
- let parsed;
70
- try {
71
- const data = typeof ev.data === "string" ? ev.data : Buffer.from(ev.data).toString("utf8");
72
- parsed = JSON.parse(data);
73
- } catch (err) {
74
- if (log) log.warn("picoclaw.ws.parse.err", "bad pico message", { err: String(err?.message || err) });
75
- return;
76
- }
77
- for (const h of messageHandlers) {
78
- try { h(parsed); } catch (err) {
79
- if (log) log.warn("picoclaw.ws.handler.err", "handler threw", { err: String(err?.message || err) });
80
- }
81
- }
82
- });
83
- ws.addEventListener("close", (ev) => {
84
- if (log) log.info("picoclaw.ws.closed", "ws closed", { code: ev?.code, reason: String(ev?.reason || "") });
85
- for (const h of closeHandlers) {
86
- try { h(`code=${ev?.code}`); } catch {}
87
- }
88
- });
89
- ws.addEventListener("error", (ev) => {
90
- if (log) log.warn("picoclaw.ws.err", "ws error", { err: String(ev?.message || "ws error") });
91
- });
92
-
93
- if (log) log.info("picoclaw.ws.open", "connected", { url: target });
94
-
95
- return {
96
- send(picoMessage) {
97
- if (ws.readyState !== 1 /* OPEN */) {
98
- throw new Error(`picoclaw ws not open: state=${ws.readyState}`);
99
- }
100
- ws.send(JSON.stringify(picoMessage));
101
- },
102
- onMessage(fn) { messageHandlers.add(fn); return () => messageHandlers.delete(fn); },
103
- onClose(fn) { closeHandlers.add(fn); return () => closeHandlers.delete(fn); },
104
- close(reason) {
105
- try { ws.close(1000, reason || "client close"); } catch {}
106
- },
107
- get readyState() { return ws.readyState; },
108
- get url() { return target; },
109
- };
110
- }
111
-
112
- /**
113
- * 构造 message.send 帧(用户消息进入 PicoClaw)。
114
- *
115
- * @param {{sessionId:string, content:string, requestId?:string}} arg
116
- */
117
- export function buildMessageSend({ sessionId, content, requestId }) {
118
- return {
119
- type: "message.send",
120
- id: requestId || randomID(),
121
- session_id: sessionId,
122
- timestamp: Date.now(),
123
- payload: { content: String(content || "") },
124
- };
125
- }
126
-
127
- function randomID() {
128
- return Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
129
- }