@my-life-buddies/cli 0.24.1 → 0.25.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/README.md +5 -1
- package/dist/bin/buddy.js +5 -2
- package/dist/bin/buddy.js.map +2 -2
- package/dist/bin/core.js +4 -2
- package/dist/bin/core.js.map +2 -2
- package/dist/bin/preview.js +535 -26
- package/dist/bin/preview.js.map +4 -4
- package/dist/web/app.css +34 -0
- package/dist/web/app.js +30 -7
- package/dist/web/chat-markdown.js +181 -0
- package/dist/web/diagnostics.js +14 -5
- package/dist/web/index.html +27 -0
- package/dist/web/tool-inventory.js +94 -0
- package/package.json +5 -4
package/dist/web/diagnostics.js
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
const models = $("model-records"), logs = $("protocol-log");
|
|
6
6
|
const modelConversation = $("models-conversation"), logConversation = $("logs-conversation");
|
|
7
7
|
const kind = $("models-kind"), level = $("logs-level");
|
|
8
|
+
const responseKind = $("models-response-kind");
|
|
8
9
|
const modelSearch = $("models-search"), logSearch = $("logs-search");
|
|
9
10
|
let snapshot = {}, conversation = "", visibleLogs = [];
|
|
10
11
|
const cached = new Map(), expanded = new Map();
|
|
11
12
|
const object = value => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
12
13
|
const json = value => typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
13
14
|
const number = value => typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
15
|
+
const newestFirst = (a, b) => (Date.parse(b.timestamp) || 0) - (Date.parse(a.timestamp) || 0);
|
|
14
16
|
const metricHelp = {
|
|
15
17
|
"模型响应耗时": "本次模型请求发出至响应结束。",
|
|
16
18
|
"模型首字耗时": "本次模型请求发出至首段输出。",
|
|
@@ -54,7 +56,7 @@
|
|
|
54
56
|
const content = Array.isArray(data.content) ? data.content : [];
|
|
55
57
|
const texts = content.filter(item => item?.type === "text").map(item => item.text).filter(text => typeof text === "string");
|
|
56
58
|
const tools = content.filter(item => item?.type === "toolCall").map(item => item.name).filter(name => typeof name === "string");
|
|
57
|
-
return typeof data.error === "string" ? data.error : tools.length ?
|
|
59
|
+
return typeof data.error === "string" ? data.error : tools.length ? `工具调用:${tools.join("、")}` : texts.join(" ") || record.title;
|
|
58
60
|
}
|
|
59
61
|
function row(record, model) {
|
|
60
62
|
const data = object(record.data);
|
|
@@ -155,8 +157,15 @@
|
|
|
155
157
|
const runtime = snapshot.diagnostics?.records || [];
|
|
156
158
|
updateConversations(runtime);
|
|
157
159
|
const matches = (record, search) => (!conversation || record.convoKey === conversation) && (!search || record.raw.toLocaleLowerCase().includes(search.toLocaleLowerCase()));
|
|
158
|
-
const modelRecords = runtime.filter(record => record.kind === "request" || record.kind === "response");
|
|
159
|
-
|
|
160
|
+
const modelRecords = runtime.filter(record => record.kind === "request" || record.kind === "response").sort(newestFirst);
|
|
161
|
+
if ($("models-response-filter")) $("models-response-filter").hidden = kind.value === "request";
|
|
162
|
+
const filteredModels = modelRecords.filter(record => {
|
|
163
|
+
if ((kind.value && record.kind !== kind.value) || !matches(record, modelSearch.value.trim())) return false;
|
|
164
|
+
if (!responseKind?.value || kind.value === "request") return true;
|
|
165
|
+
if (record.kind !== "response" || record.truncated || !Array.isArray(record.data?.content)) return false;
|
|
166
|
+
const hasTool = record.data.content.some(block => block?.type === "toolCall");
|
|
167
|
+
return responseKind.value === "tool" ? hasTool : !hasTool && record.data.content.some(block => block?.type === "text");
|
|
168
|
+
});
|
|
160
169
|
reconcile(models, filteredModels, true, modelRecords.length
|
|
161
170
|
? ["没有匹配的模型记录", "试试其他会话、类型或关键词。"]
|
|
162
171
|
: ["等待模型调用", "启动 DEV 并发送消息后,Runtime 提供的模型请求与响应会出现在这里。旧版本或未输出这些日志的 Runtime 可先查看运行日志。"]);
|
|
@@ -167,7 +176,7 @@
|
|
|
167
176
|
id: event.eventId, timestamp: event.timestamp, source: event.channel, level: /failed|error|stderr/.test(event.type) ? "error" : "info", title: event.summary || event.type,
|
|
168
177
|
kind: "log", raw: `${event.timestamp} ${event.channel} ${event.type}${event.runId ? ` ${event.runId}` : ""}\n${event.summary || ""}`,
|
|
169
178
|
}));
|
|
170
|
-
const allLogs = [...runtime, ...other].sort(
|
|
179
|
+
const allLogs = [...runtime, ...other].sort(newestFirst);
|
|
171
180
|
visibleLogs = allLogs.filter(record => (!level.value || record.level === level.value || level.value === "error" && (record.data?.error || record.data?.stopReason === "error")) && matches(record, logSearch.value.trim()));
|
|
172
181
|
reconcile(logs, visibleLogs, false, allLogs.length ? ["没有匹配的日志", "调整会话、级别或搜索条件后再试。"] : ["等待运行日志", "启动 DEV 后,这里会保留进程输出与连接事件。"]);
|
|
173
182
|
$("logs-count").textContent = `${visibleLogs.length} 条可见日志${deltas ? ` · 已折叠 ${deltas} 条文字分片事件` : ""}${snapshot.diagnostics?.dropped ? ` · 已淘汰 ${snapshot.diagnostics.dropped} 条较早日志` : ""}`;
|
|
@@ -178,7 +187,7 @@
|
|
|
178
187
|
select.addEventListener("change", () => { conversation = select.value; render(); });
|
|
179
188
|
select.addEventListener("blur", render);
|
|
180
189
|
}
|
|
181
|
-
for (const input of [kind, level, modelSearch, logSearch]) input.addEventListener(input.type === "search" ? "input" : "change", render);
|
|
190
|
+
for (const input of [kind, level, modelSearch, logSearch, responseKind].filter(Boolean)) input.addEventListener(input.type === "search" ? "input" : "change", render);
|
|
182
191
|
return { update(next) { snapshot = next; render(); }, copyLogs() { return copy(visibleLogs.map(record => record.raw).join("\n\n")); } };
|
|
183
192
|
};
|
|
184
193
|
})();
|
package/dist/web/index.html
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
<script src="/assets/iphone-16-max.js" defer></script>
|
|
15
15
|
<script src="/assets/widgets.js" defer></script>
|
|
16
16
|
<script src="/assets/diagnostics.js" defer></script>
|
|
17
|
+
<script src="/assets/tool-inventory.js" defer></script>
|
|
18
|
+
<script src="/assets/chat-markdown.js" defer></script>
|
|
17
19
|
<script src="/assets/app.js" defer></script>
|
|
18
20
|
<link rel="stylesheet" href="/assets/widgets.css">
|
|
19
21
|
</head>
|
|
@@ -39,6 +41,20 @@
|
|
|
39
41
|
<button id="tab-protocol" role="tab" aria-selected="false" aria-controls="panel-protocol" tabindex="-1">
|
|
40
42
|
<svg aria-hidden="true" viewBox="0 0 24 24"><rect x="3.5" y="5" width="17" height="14" rx="2"/><path d="m7.5 9 3 3-3 3M13 15h3.5"/></svg><span>运行日志</span>
|
|
41
43
|
</button>
|
|
44
|
+
<button id="tab-tools" role="tab" aria-selected="false" aria-controls="panel-tools" tabindex="-1">
|
|
45
|
+
<!-- Lucide wrench — ISC License, Copyright (c) 2026 Lucide Icons and Contributors.
|
|
46
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
47
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
48
|
+
copyright notice and this permission notice appear in all copies.
|
|
49
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
50
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
51
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
52
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
53
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
54
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
55
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -->
|
|
56
|
+
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z"/></svg><span>工具列表</span>
|
|
57
|
+
</button>
|
|
42
58
|
</nav>
|
|
43
59
|
</div>
|
|
44
60
|
|
|
@@ -89,11 +105,22 @@
|
|
|
89
105
|
<div class="diagnostic-filters">
|
|
90
106
|
<label>会话<select id="models-conversation" aria-label="模型会话"><option value="">全部会话</option></select></label>
|
|
91
107
|
<label>类型<select id="models-kind"><option value="">请求与响应</option><option value="request">模型请求</option><option value="response">模型响应</option></select></label>
|
|
108
|
+
<label id="models-response-filter">响应类型<select id="models-response-kind"><option value="">全部响应</option><option value="tool">工具调用</option><option value="text">文本响应</option></select></label>
|
|
92
109
|
<label class="diagnostic-search">搜索<input id="models-search" type="search" placeholder="搜索模型、工具或内容" autocomplete="off"></label>
|
|
93
110
|
</div>
|
|
94
111
|
<p class="diagnostic-note" id="models-count"></p>
|
|
95
112
|
<div id="model-records" class="diagnostic-records"></div>
|
|
96
113
|
</section>
|
|
114
|
+
<section id="panel-tools" class="debug-panel" role="tabpanel" aria-labelledby="tab-tools" hidden>
|
|
115
|
+
<div class="section-heading"><div><h3>工具列表 <span id="tools-total"></span></h3><p id="tools-caption">连接本地工程后,查看已识别的工具及用途。</p></div><span id="tools-read-state" class="inventory-read-state"></span></div>
|
|
116
|
+
<p id="tools-notice" class="inventory-notice" role="status" hidden></p>
|
|
117
|
+
<div id="tools-filters" class="diagnostic-filters" hidden>
|
|
118
|
+
<label class="diagnostic-search">搜索<input id="tools-search" type="search" placeholder="搜索工具名称或用途" autocomplete="off"></label>
|
|
119
|
+
<label>来源<select id="tools-origin"><option value="">全部来源</option><option value="custom">自定义工具</option><option value="platform">平台工具</option></select></label>
|
|
120
|
+
</div>
|
|
121
|
+
<div id="tool-inventory"></div>
|
|
122
|
+
<p class="diagnostic-note" id="tools-footer" hidden>来自本地工程分析。每次请求实际提供的工具可在「模型调用」中查看。</p>
|
|
123
|
+
</section>
|
|
97
124
|
<section id="panel-overview" class="debug-panel" role="tabpanel" aria-labelledby="tab-overview">
|
|
98
125
|
<div class="run-strip">
|
|
99
126
|
<div class="run-heading">
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
"use strict";
|
|
3
|
+
// UI contract only. The CLI supplies inspection; this view never executes project code.
|
|
4
|
+
window.createPreviewToolInventory = ({ node }) => {
|
|
5
|
+
const $ = id => document.getElementById(id);
|
|
6
|
+
const list = $("tool-inventory"), search = $("tools-search"), origin = $("tools-origin");
|
|
7
|
+
const expanded = new Set();
|
|
8
|
+
let snapshot = {}, signature = "", known = [];
|
|
9
|
+
const record = value => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
10
|
+
const text = value => typeof value === "string" ? value : "";
|
|
11
|
+
const sourceLabel = value => ({ custom: "自定义工具", platform: "平台工具" })[value] || "来源未确定";
|
|
12
|
+
function renderList() {
|
|
13
|
+
const query = search.value.trim().toLocaleLowerCase();
|
|
14
|
+
const filtered = known.filter(tool => (!origin.value || tool.origin === origin.value)
|
|
15
|
+
&& (!query || `${tool.name} ${tool.description || ""}`.toLocaleLowerCase().includes(query)));
|
|
16
|
+
const fragment = document.createDocumentFragment();
|
|
17
|
+
for (const tool of filtered) {
|
|
18
|
+
const row = node("details", { class: "inventory-tool", open: expanded.has(tool.name) });
|
|
19
|
+
row.addEventListener("toggle", () => row.open ? expanded.add(tool.name) : expanded.delete(tool.name));
|
|
20
|
+
const heading = node("summary");
|
|
21
|
+
const label = node("div", { class: "inventory-tool-title" });
|
|
22
|
+
label.append(node("strong", {}, tool.name), node("span", { class: "inventory-origin" }, sourceLabel(tool.origin)));
|
|
23
|
+
heading.append(label, node("p", {}, text(tool.description) || "工具描述尚未读取。"));
|
|
24
|
+
const body = node("div", { class: "inventory-tool-body" });
|
|
25
|
+
if (text(tool.reason)) body.append(node("p", { class: "diagnostic-note" }, tool.reason));
|
|
26
|
+
const schema = record(tool.parameters), properties = record(schema.properties);
|
|
27
|
+
const names = Object.keys(properties), required = Array.isArray(schema.required) ? schema.required : [];
|
|
28
|
+
body.append(node("h4", {}, "参数"));
|
|
29
|
+
if (names.length) {
|
|
30
|
+
const table = node("table", { class: "inventory-parameters" });
|
|
31
|
+
const head = node("thead"), header = node("tr");
|
|
32
|
+
for (const title of ["名称", "类型", "说明"]) header.append(node("th", { scope: "col" }, title));
|
|
33
|
+
head.append(header); table.append(head);
|
|
34
|
+
const rows = node("tbody");
|
|
35
|
+
for (const name of names) {
|
|
36
|
+
const param = record(properties[name]), tr = node("tr");
|
|
37
|
+
tr.append(node("td", {}, `${name}${required.includes(name) ? " · 必填" : ""}`),
|
|
38
|
+
node("td", {}, Array.isArray(param.type) ? param.type.join(" / ") : text(param.type) || "见 Schema"),
|
|
39
|
+
node("td", {}, text(param.description) || "未提供说明"));
|
|
40
|
+
rows.append(tr);
|
|
41
|
+
}
|
|
42
|
+
table.append(rows); body.append(table);
|
|
43
|
+
} else body.append(node("p", { class: "diagnostic-note" }, tool.parameters ? "无固定参数字段,详见 Schema。" : "参数定义尚未读取。"));
|
|
44
|
+
if (tool.parameters) {
|
|
45
|
+
const schemaView = node("details", { class: "diagnostic-section" });
|
|
46
|
+
schemaView.append(node("summary", {}, "参数 Schema"), node("pre", {}, JSON.stringify(tool.parameters, null, 2)));
|
|
47
|
+
body.append(schemaView);
|
|
48
|
+
}
|
|
49
|
+
row.append(heading, body); fragment.append(row);
|
|
50
|
+
}
|
|
51
|
+
if (!filtered.length) {
|
|
52
|
+
const empty = node("div", { class: "diagnostic-empty" });
|
|
53
|
+
const status = snapshot.inspection?.status;
|
|
54
|
+
empty.append(node("strong", {}, known.length ? "没有匹配的工具" : status === "analyzing" ? "正在分析工具注册关系" : status === "complete" ? "未配置工具" : "工具列表尚未读取"));
|
|
55
|
+
empty.append(node("p", {}, known.length ? "请调整工具名称、用途关键词或来源。" : status === "complete" ? "当前工程中未识别到已注册的工具。" : status === "analyzing" ? "读取完成后自动展示,无需发送消息。" : snapshot.connected ? "当前连接尚未提供工程分析结果。未读取不表示没有工具。" : "连接本地工程后,工具及用途会显示在这里。"));
|
|
56
|
+
fragment.append(empty);
|
|
57
|
+
}
|
|
58
|
+
const unresolved = snapshot.inspection?.unresolved;
|
|
59
|
+
if (Array.isArray(unresolved) && !query && !origin.value) {
|
|
60
|
+
for (const item of unresolved) {
|
|
61
|
+
const pending = node("div", { class: "inventory-unresolved" });
|
|
62
|
+
pending.append(node("strong", {}, text(item.name) || "动态工具 · 待确认"), node("p", {}, text(item.reason) || "依赖运行时信息,无法静态确定。"));
|
|
63
|
+
fragment.append(pending);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
list.replaceChildren(fragment);
|
|
67
|
+
}
|
|
68
|
+
function update(next) {
|
|
69
|
+
const nextSignature = JSON.stringify(next);
|
|
70
|
+
if (nextSignature === signature) return;
|
|
71
|
+
if (snapshot.buddyId !== next.buddyId) { expanded.clear(); search.value = ""; origin.value = ""; }
|
|
72
|
+
signature = nextSignature; snapshot = next;
|
|
73
|
+
const inspection = record(next.inspection), model = record(inspection.model);
|
|
74
|
+
known = (Array.isArray(inspection.tools) ? inspection.tools : []).filter(tool => tool && typeof tool.name === "string");
|
|
75
|
+
const loading = inspection.status === "analyzing", partial = inspection.status === "partial";
|
|
76
|
+
const incomplete = partial && (!Array.isArray(inspection.unresolved) || inspection.unresolved.length > 0);
|
|
77
|
+
const modelLabel = model.status === "dynamic" ? "动态配置,待运行确认" : text(model.value) || text(next.fallbackModel) || (loading ? "分析中" : "未读取");
|
|
78
|
+
$("environment-model").replaceChildren(node("span", {}, modelLabel));
|
|
79
|
+
if (text(model.reason)) $("environment-model").append(node("p", { class: "inventory-model-note" }, model.reason));
|
|
80
|
+
$("tools-total").textContent = known.length ? `${known.length}${incomplete ? " 已识别" : ""}` : "";
|
|
81
|
+
$("tools-caption").textContent = known.length ? `${known.filter(tool => tool.origin === "custom").length} 个自定义工具 · ${known.filter(tool => tool.origin === "platform").length} 个平台工具` : "本地工程中已注册的工具及用途。";
|
|
82
|
+
$("tools-read-state").textContent = loading ? "分析中" : !next.connected && next.inspection ? "上次读取" : incomplete ? "部分已识别" : partial ? "部分详情待确认" : inspection.status === "complete" ? "已读取" : "未读取";
|
|
83
|
+
const notes = [];
|
|
84
|
+
if (!next.connected && next.inspection) notes.push("连接已断开,以下为上次读取的结果。");
|
|
85
|
+
if (incomplete) notes.push("部分工具依赖运行时信息,当前列表可能不完整。每次请求实际提供的工具可在「模型调用」中查看。");
|
|
86
|
+
else if (partial) notes.push("部分工具的描述或参数尚未完整读取,展开对应工具可查看说明。");
|
|
87
|
+
$("tools-notice").hidden = !notes.length; $("tools-notice").textContent = notes.join(" ");
|
|
88
|
+
$("tools-filters").hidden = !known.length; $("tools-footer").hidden = !next.inspection;
|
|
89
|
+
renderList();
|
|
90
|
+
}
|
|
91
|
+
search.addEventListener("input", renderList); origin.addEventListener("change", renderList);
|
|
92
|
+
return { update };
|
|
93
|
+
};
|
|
94
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@my-life-buddies/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "搭搭 Developer Platform 命令行工具",
|
|
6
6
|
"publishConfig": {
|
|
@@ -38,11 +38,12 @@
|
|
|
38
38
|
"tar-stream": "^3.2.1",
|
|
39
39
|
"@sneas/telephone": "1.1.2",
|
|
40
40
|
"qrcode": "1.5.4",
|
|
41
|
-
"ws": "8.21.3"
|
|
41
|
+
"ws": "8.21.3",
|
|
42
|
+
"acorn": "8.15.0"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
|
-
"@buddy/cli-core": "0.
|
|
45
|
-
"@buddy/preview": "0.
|
|
45
|
+
"@buddy/cli-core": "0.25.0",
|
|
46
|
+
"@buddy/preview": "0.25.0"
|
|
46
47
|
},
|
|
47
48
|
"repository": {
|
|
48
49
|
"type": "git",
|