agent-loft 0.2.32 → 0.2.34
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 +9 -3
- package/package.json +1 -1
- package/src/agents/claude/driver.ts +80 -2
- package/src/agents/claude/session.ts +51 -7
- package/src/agents/codex/session.ts +43 -7
- package/src/channels/feishu/bot.ts +2 -2
- package/src/channels/web/api.ts +84 -2
- package/src/channels/web/conv-pages.ts +68 -0
- package/src/channels/web/server.ts +13 -4
- package/src/core/hub.ts +126 -19
- package/src/core/model-prices.json +103 -0
- package/src/core/pricing.ts +93 -0
- package/src/core/skill-runtime.ts +1 -1
- package/src/core/skills.ts +2 -1
- package/src/core/types.ts +24 -2
- package/src/core/usage.ts +144 -0
- package/web/css/composer.css +0 -25
- package/web/css/messages.css +169 -0
- package/web/css/settings.css +194 -0
- package/web/css/sidebar.css +5 -5
- package/web/css/skills.css +18 -1
- package/web/css/topbar.css +131 -0
- package/web/lib/dom.js +2 -1
- package/web/lib/markdown.js +84 -14
- package/web/lib/ws.js +46 -6
- package/web/settings/index.js +11 -4
- package/web/{views → settings}/skills.js +86 -172
- package/web/settings/usage.js +272 -0
- package/web/state.js +5 -4
- package/web/views/composer.js +2 -1
- package/web/views/interaction.js +12 -4
- package/web/views/layout.js +22 -25
- package/web/views/mention.js +2 -2
- package/web/views/messages.js +239 -81
- package/web/views/msg-nav.js +120 -0
- package/web/views/quota.js +1 -0
- package/web/views/sidebar.js +114 -17
- package/web/views/topbar.js +169 -41
|
@@ -2,54 +2,41 @@ import { fill, h, icon, starsLoader, toast } from "../lib/dom.js";
|
|
|
2
2
|
import { api } from "../lib/api.js";
|
|
3
3
|
import { renderMarkdown } from "../lib/markdown.js";
|
|
4
4
|
import { confirmDialog, modal, showMenu } from "../lib/overlay.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (S.current) WS.raw({ t: "unsub", key: S.current });
|
|
21
|
-
S.current = null;
|
|
22
|
-
S.view = "skills";
|
|
23
|
-
S.items = [];
|
|
24
|
-
S.turnAi = null;
|
|
25
|
-
history.replaceState(null, "", "#skills");
|
|
26
|
-
WS.presence();
|
|
27
|
-
ui.spacer.style.height = "0px";
|
|
28
|
-
ui.follow = false;
|
|
29
|
-
// 和定时任务页一样:藏起输入框(css/tasks.css 的 .tasks-view)
|
|
30
|
-
ui.main.classList.add("tasks-view");
|
|
31
|
-
closeSide();
|
|
32
|
-
renderTopbar();
|
|
33
|
-
renderSidebar();
|
|
34
|
-
fill(ui.msgInner, h("div", { class: "empty" }, starsLoader("lg")));
|
|
5
|
+
|
|
6
|
+
// ---------- 技能(设置里的一页) ----------
|
|
7
|
+
// 我的技能:自己建的,加上以前从别人那装的(可以停用、填变量、卸载);管理员另有「本机技能」(机器上原有的)。
|
|
8
|
+
// 技能有变化时服务端推 { t: "skills" }(lib/ws.js 转成 aim:skills 事件),这一页开着就重新拉。
|
|
9
|
+
|
|
10
|
+
const K = { data: null, tab: "mine", query: "", token: 0, reloadTimer: 0, box: null };
|
|
11
|
+
|
|
12
|
+
/** 以前分享出去的技能(现在不能再设了):卡片上标一下还有谁看得到 */
|
|
13
|
+
const SHARED = { users: "分享给了指定的人", all: "所有人可见" };
|
|
14
|
+
|
|
15
|
+
export async function skillsTab(panel) {
|
|
16
|
+
K.tab = "mine";
|
|
17
|
+
K.query = "";
|
|
18
|
+
K.box = h("div", { class: "skills-page" }, h("div", { class: "section" }, h("h4", {}, "技能"), h("span", { class: "spinner" })));
|
|
19
|
+
fill(panel, K.box);
|
|
35
20
|
await reload();
|
|
36
|
-
ui.messages.scrollTop = 0;
|
|
37
21
|
}
|
|
38
22
|
|
|
23
|
+
/** 这一页还开着(没关设置、没切到别的标签页) */
|
|
24
|
+
const showing = () => !!K.box?.isConnected;
|
|
25
|
+
|
|
39
26
|
async function reload() {
|
|
40
27
|
const my = ++K.token;
|
|
41
28
|
try {
|
|
42
29
|
const data = await api("/api/skills");
|
|
43
|
-
if (my !== K.token ||
|
|
30
|
+
if (my !== K.token || !showing()) return;
|
|
44
31
|
K.data = data;
|
|
45
32
|
render();
|
|
46
33
|
} catch (err) {
|
|
47
|
-
if (my === K.token &&
|
|
34
|
+
if (my === K.token && showing()) fill(K.box, h("div", { class: "error-text" }, err.message));
|
|
48
35
|
}
|
|
49
36
|
}
|
|
50
37
|
|
|
51
38
|
document.addEventListener("aim:skills", () => {
|
|
52
|
-
if (
|
|
39
|
+
if (!showing()) return;
|
|
53
40
|
clearTimeout(K.reloadTimer);
|
|
54
41
|
K.reloadTimer = setTimeout(() => void reload(), 300);
|
|
55
42
|
});
|
|
@@ -58,76 +45,66 @@ function mine() {
|
|
|
58
45
|
return K.data.skills.filter((s) => s.installed).sort((a, b) => a.name.localeCompare(b.name));
|
|
59
46
|
}
|
|
60
47
|
|
|
61
|
-
function plaza() {
|
|
62
|
-
return K.data.skills.filter((s) => !s.mine).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
48
|
function matches(s) {
|
|
66
49
|
const q = K.query.trim().toLowerCase();
|
|
67
50
|
return !q || s.name.includes(q) || s.description.toLowerCase().includes(q) || (s.ownerName ?? "").toLowerCase().includes(q);
|
|
68
51
|
}
|
|
69
52
|
|
|
70
53
|
function render() {
|
|
71
|
-
const tabs =
|
|
72
|
-
[
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
54
|
+
const tabs = K.data.machine
|
|
55
|
+
? [
|
|
56
|
+
["mine", `我的技能 ${mine().length}`],
|
|
57
|
+
["machine", `本机技能 ${K.data.machine.length}`],
|
|
58
|
+
]
|
|
59
|
+
: [];
|
|
60
|
+
const search = h("input", { class: "skill-search", type: "search", placeholder: "搜索名字、说明", value: K.query });
|
|
77
61
|
search.oninput = () => {
|
|
78
62
|
K.query = search.value;
|
|
79
63
|
fill(listBox, renderList());
|
|
80
64
|
};
|
|
81
65
|
const listBox = h("div", { class: "skill-list-box" }, renderList());
|
|
82
66
|
fill(
|
|
83
|
-
|
|
67
|
+
K.box,
|
|
84
68
|
h(
|
|
85
69
|
"div",
|
|
86
|
-
{ class: "
|
|
70
|
+
{ class: "skills-head" },
|
|
87
71
|
h(
|
|
88
|
-
"
|
|
89
|
-
{ class: "
|
|
90
|
-
|
|
91
|
-
"div",
|
|
92
|
-
{ class: "grow" },
|
|
93
|
-
h("h2", {}, "技能"),
|
|
94
|
-
h(
|
|
95
|
-
"p",
|
|
96
|
-
{},
|
|
97
|
-
"技能是一份写给 agent 的做事方法(SKILL.md),可以带脚本和资料,Claude 和 Codex 都能用。工作模式下 agent 遇到对得上的事会自己用;在输入框或定时任务里输入 @ 可以点名用哪个。",
|
|
98
|
-
),
|
|
99
|
-
),
|
|
100
|
-
h(
|
|
101
|
-
"div",
|
|
102
|
-
{ class: "skill-head-btns" },
|
|
103
|
-
h("button", { class: "btn", onclick: () => pickUpload() }, "上传"),
|
|
104
|
-
h("button", { class: "btn primary", onclick: () => openEditor() }, icon("plus"), "新建"),
|
|
105
|
-
),
|
|
72
|
+
"p",
|
|
73
|
+
{ class: "skill-note" },
|
|
74
|
+
"技能是一份写给 agent 的做事方法(SKILL.md),可以带脚本和资料,Claude 和 Codex 都能用。工作模式下 agent 遇到对得上的事会自己用;在输入框或定时任务里输入 @ 可以点名用哪个。",
|
|
106
75
|
),
|
|
107
76
|
h(
|
|
108
77
|
"div",
|
|
109
|
-
{ class: "skill-
|
|
110
|
-
h(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
78
|
+
{ class: "skill-head-btns" },
|
|
79
|
+
h("button", { class: "btn", onclick: () => pickUpload() }, "上传"),
|
|
80
|
+
h("button", { class: "btn primary", onclick: () => openEditor() }, icon("plus"), "新建"),
|
|
81
|
+
),
|
|
82
|
+
),
|
|
83
|
+
h(
|
|
84
|
+
"div",
|
|
85
|
+
{ class: "skill-tabs" },
|
|
86
|
+
tabs.length
|
|
87
|
+
? h(
|
|
88
|
+
"div",
|
|
89
|
+
{ class: "seg" },
|
|
90
|
+
tabs.map(([id, label]) =>
|
|
91
|
+
h(
|
|
92
|
+
"button",
|
|
93
|
+
{
|
|
94
|
+
class: K.tab === id ? "on" : "",
|
|
95
|
+
onclick: () => {
|
|
96
|
+
K.tab = id;
|
|
97
|
+
render();
|
|
98
|
+
},
|
|
121
99
|
},
|
|
122
|
-
|
|
123
|
-
|
|
100
|
+
label,
|
|
101
|
+
),
|
|
124
102
|
),
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
),
|
|
129
|
-
listBox,
|
|
103
|
+
)
|
|
104
|
+
: null,
|
|
105
|
+
search,
|
|
130
106
|
),
|
|
107
|
+
listBox,
|
|
131
108
|
);
|
|
132
109
|
}
|
|
133
110
|
|
|
@@ -135,16 +112,10 @@ function renderList() {
|
|
|
135
112
|
if (K.tab === "machine") {
|
|
136
113
|
const list = K.data.machine.filter((m) => matches({ ...m, ownerName: "" }));
|
|
137
114
|
return [
|
|
138
|
-
h("p", { class: "skill-note" }, "这台机器上原有的技能(~/.claude/skills、~/.agents/skills
|
|
115
|
+
h("p", { class: "skill-note" }, "这台机器上原有的技能(~/.claude/skills、~/.agents/skills 等)。只有管理员的会话能用,其他人的会话里看不到、也调不了。要给别人用,请复制内容,在他的账号里新建一个。"),
|
|
139
116
|
list.length ? h("div", { class: "task-list" }, list.map(machineCard)) : h("div", { class: "skill-empty" }, "没有找到"),
|
|
140
117
|
];
|
|
141
118
|
}
|
|
142
|
-
if (K.tab === "plaza") {
|
|
143
|
-
const list = plaza().filter(matches);
|
|
144
|
-
return list.length
|
|
145
|
-
? h("div", { class: "task-list" }, list.map(plazaCard))
|
|
146
|
-
: h("div", { class: "skill-empty" }, K.query ? "没有找到" : "还没有人分享技能给你。自己建的技能可以设成「所有人」或者分享给指定的人。");
|
|
147
|
-
}
|
|
148
119
|
const list = mine().filter(matches);
|
|
149
120
|
if (list.length) return h("div", { class: "task-list" }, list.map(mineCard));
|
|
150
121
|
if (K.query) return h("div", { class: "skill-empty" }, "没有找到");
|
|
@@ -152,11 +123,7 @@ function renderList() {
|
|
|
152
123
|
"div",
|
|
153
124
|
{ class: "tasks-empty" },
|
|
154
125
|
h("div", { class: "t" }, "还没有技能"),
|
|
155
|
-
h(
|
|
156
|
-
"div",
|
|
157
|
-
{ class: "s" },
|
|
158
|
-
"点「新建」写一个,「上传」一个技能包(.zip / .skill / SKILL.md / 文件夹),去「技能广场」装别人分享的,或者在对话里让 agent「把刚才的做法存成技能」。",
|
|
159
|
-
),
|
|
126
|
+
h("div", { class: "s" }, "点「新建」写一个,「上传」一个技能包(.zip / .skill / SKILL.md / 文件夹),或者在对话里让 agent「把刚才的做法存成技能」。"),
|
|
160
127
|
);
|
|
161
128
|
}
|
|
162
129
|
|
|
@@ -203,7 +170,7 @@ function mineCard(s) {
|
|
|
203
170
|
"sep",
|
|
204
171
|
s.mine
|
|
205
172
|
? { label: "删除", danger: true, onclick: () => removeSkill(s) }
|
|
206
|
-
: { label: "卸载", danger: true, onclick: () =>
|
|
173
|
+
: { label: "卸载", danger: true, onclick: () => uninstall(s) },
|
|
207
174
|
]);
|
|
208
175
|
return h(
|
|
209
176
|
"div",
|
|
@@ -219,7 +186,7 @@ function mineCard(s) {
|
|
|
219
186
|
"div",
|
|
220
187
|
{ class: "task-meta" },
|
|
221
188
|
h("span", {}, s.mine ? "我建的" : `${s.ownerName} 建的`),
|
|
222
|
-
s.mine ? h("span", {},
|
|
189
|
+
s.mine && SHARED[s.visibility] ? h("span", {}, SHARED[s.visibility]) : null,
|
|
223
190
|
s.mine && s.installs ? h("span", {}, `${s.installs} 人安装`) : null,
|
|
224
191
|
s.source === "agent" ? h("span", {}, "agent 生成") : null,
|
|
225
192
|
varsState(s),
|
|
@@ -231,28 +198,6 @@ function mineCard(s) {
|
|
|
231
198
|
);
|
|
232
199
|
}
|
|
233
200
|
|
|
234
|
-
function plazaCard(s) {
|
|
235
|
-
const btn = s.installed
|
|
236
|
-
? h("button", { class: "btn", onclick: () => install(s, false) }, "已安装")
|
|
237
|
-
: h("button", { class: "btn primary", onclick: () => install(s, true) }, "安装");
|
|
238
|
-
const more = s.canDelete ? h("button", { class: "icon-btn", title: "更多" }, icon("more")) : null;
|
|
239
|
-
if (more) more.onclick = () => showMenu(more, [{ label: "下架(删除)", danger: true, onclick: () => removeSkill(s) }]);
|
|
240
|
-
return h(
|
|
241
|
-
"div",
|
|
242
|
-
{ class: "task-card skill-card" },
|
|
243
|
-
h("div", { class: "task-top" }, h("button", { class: "task-title skill-name", title: "查看", onclick: () => openDetail(s) }, s.name), btn, more),
|
|
244
|
-
h(
|
|
245
|
-
"div",
|
|
246
|
-
{ class: "task-meta" },
|
|
247
|
-
h("span", {}, `${s.ownerName} 建的`),
|
|
248
|
-
h("span", {}, s.visibility === "all" ? "公开" : "分享给你"),
|
|
249
|
-
h("span", {}, `更新于 ${new Date(s.updatedAt).toLocaleDateString()}`),
|
|
250
|
-
s.vars.length ? h("span", {}, `要填 ${s.vars.length} 个变量`) : null,
|
|
251
|
-
),
|
|
252
|
-
h("div", { class: "task-prompt" }, s.description),
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
201
|
function machineCard(m) {
|
|
257
202
|
return h(
|
|
258
203
|
"div",
|
|
@@ -265,12 +210,19 @@ function machineCard(m) {
|
|
|
265
210
|
|
|
266
211
|
// ---------- 操作 ----------
|
|
267
212
|
|
|
268
|
-
|
|
213
|
+
/** 卸载以前从别人那装的技能。不再有广场,卸了就装不回来了,先问一句 */
|
|
214
|
+
async function uninstall(s) {
|
|
215
|
+
const sure = await confirmDialog({
|
|
216
|
+
title: `卸载「${s.name}」?`,
|
|
217
|
+
message: `这是 ${s.ownerName} 建的技能。卸载后就装不回来了,要用的话得请他把内容发给你,自己新建一个。`,
|
|
218
|
+
confirmText: "卸载",
|
|
219
|
+
danger: true,
|
|
220
|
+
});
|
|
221
|
+
if (!sure) return;
|
|
269
222
|
try {
|
|
270
|
-
await api(`/api/skills/${s.id}/install`, { method: "POST", body: { on } });
|
|
271
|
-
toast(
|
|
272
|
-
|
|
273
|
-
if (on && s.vars.some((v) => v.required)) openVars(K.data.skills.find((x) => x.id === s.id) ?? s);
|
|
223
|
+
await api(`/api/skills/${s.id}/install`, { method: "POST", body: { on: false } });
|
|
224
|
+
toast(`已卸载 ${s.name}`);
|
|
225
|
+
void reload();
|
|
274
226
|
} catch (err) {
|
|
275
227
|
toast(err.message);
|
|
276
228
|
}
|
|
@@ -278,11 +230,9 @@ async function install(s, on) {
|
|
|
278
230
|
|
|
279
231
|
async function removeSkill(s) {
|
|
280
232
|
const sure = await confirmDialog({
|
|
281
|
-
title:
|
|
282
|
-
message: s.
|
|
283
|
-
|
|
284
|
-
: `这是 ${s.ownerName} 建的技能,下架会直接删除它,装了的人都用不了了。`,
|
|
285
|
-
confirmText: s.mine ? "删除" : "下架",
|
|
233
|
+
title: `删除技能「${s.name}」?`,
|
|
234
|
+
message: `删除后找不回来${s.installs ? `,装了它的 ${s.installs} 个人也用不了了` : ""}。`,
|
|
235
|
+
confirmText: "删除",
|
|
286
236
|
danger: true,
|
|
287
237
|
});
|
|
288
238
|
if (!sure) return;
|
|
@@ -314,7 +264,7 @@ async function openDetail(s) {
|
|
|
314
264
|
"div",
|
|
315
265
|
{ class: "task-meta" },
|
|
316
266
|
h("span", {}, d.mine ? "我建的" : `${d.ownerName} 建的`),
|
|
317
|
-
h("span", {},
|
|
267
|
+
d.mine && SHARED[d.visibility] ? h("span", {}, SHARED[d.visibility]) : null,
|
|
318
268
|
h("span", {}, `更新于 ${new Date(d.updatedAt).toLocaleString()}`),
|
|
319
269
|
),
|
|
320
270
|
h("p", { class: "skill-desc" }, d.description),
|
|
@@ -464,38 +414,6 @@ function openVars(s) {
|
|
|
464
414
|
|
|
465
415
|
// ---------- 新建 / 编辑 ----------
|
|
466
416
|
|
|
467
|
-
/** 谁能看到:仅自己 / 指定的人 / 所有人 */
|
|
468
|
-
function visibilityPicker(cur) {
|
|
469
|
-
const state = { visibility: cur?.visibility ?? "private", sharedWith: new Set(cur?.sharedWith ?? []) };
|
|
470
|
-
const users = K.data.users;
|
|
471
|
-
const select = h(
|
|
472
|
-
"select",
|
|
473
|
-
{},
|
|
474
|
-
Object.entries(VISIBILITY).map(([v, label]) =>
|
|
475
|
-
h("option", { value: v, selected: v === state.visibility, disabled: v === "users" && users.length === 0 }, v === "all" ? "所有人(出现在技能广场)" : label),
|
|
476
|
-
),
|
|
477
|
-
);
|
|
478
|
-
const people = h(
|
|
479
|
-
"div",
|
|
480
|
-
{ class: "skill-people" },
|
|
481
|
-
users.map((u) => {
|
|
482
|
-
const box = h("input", { type: "checkbox", checked: state.sharedWith.has(u.id) });
|
|
483
|
-
box.onchange = () => (box.checked ? state.sharedWith.add(u.id) : state.sharedWith.delete(u.id));
|
|
484
|
-
return h("label", { class: "skill-person" }, box, u.name);
|
|
485
|
-
}),
|
|
486
|
-
);
|
|
487
|
-
const sync = () => {
|
|
488
|
-
state.visibility = select.value;
|
|
489
|
-
people.hidden = select.value !== "users";
|
|
490
|
-
};
|
|
491
|
-
select.onchange = sync;
|
|
492
|
-
sync();
|
|
493
|
-
return {
|
|
494
|
-
el: h("div", { class: "skill-visibility" }, select, people),
|
|
495
|
-
value: () => ({ visibility: state.visibility, sharedWith: [...state.sharedWith] }),
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
|
|
499
417
|
/** 变量声明的编辑器:一行一个 */
|
|
500
418
|
function varsEditor(list) {
|
|
501
419
|
const rows = [];
|
|
@@ -580,7 +498,6 @@ async function openEditor(s) {
|
|
|
580
498
|
const body = h("textarea", { rows: 14, class: "skill-body-input", spellcheck: "false", placeholder: "写给 agent 的操作说明(Markdown)" });
|
|
581
499
|
body.value = d?.body ?? (s ? "" : BODY_TEMPLATE);
|
|
582
500
|
const vars = varsEditor(d?.vars);
|
|
583
|
-
const vis = visibilityPicker(d);
|
|
584
501
|
const save = h("button", { class: "btn primary" }, d ? "保存" : "创建");
|
|
585
502
|
const close = modal({
|
|
586
503
|
title: d ? `编辑 ${d.name}` : "新建技能",
|
|
@@ -597,21 +514,19 @@ async function openEditor(s) {
|
|
|
597
514
|
vars.el,
|
|
598
515
|
"要调的接口地址、密钥之类,每个人填自己的值。普通变量注入到他自己的会话环境里,正文里用 $变量名 引用;密钥再填上「只允许发往」的地址就受保护:agent 看不到值,正文里写成让 agent 用 skill_request 工具发请求、在地址或请求头里写 {{变量名}}。",
|
|
599
516
|
),
|
|
600
|
-
field("谁能看到", vis.el, "看得到的人可以在技能广场安装。你以后改了,装了的人也跟着用新的。"),
|
|
601
517
|
),
|
|
602
518
|
foot: h("div", { class: "modal-foot" }, save),
|
|
603
519
|
});
|
|
604
520
|
(d ? desc : name).focus();
|
|
605
521
|
save.onclick = async () => {
|
|
606
|
-
const payload = { description: desc.value, body: body.value, vars: vars.value()
|
|
522
|
+
const payload = { description: desc.value, body: body.value, vars: vars.value() };
|
|
607
523
|
save.disabled = true;
|
|
608
524
|
try {
|
|
609
525
|
if (d) await api(`/api/skills/${d.id}`, { method: "PATCH", body: payload });
|
|
610
526
|
else await api("/api/skills", { method: "POST", body: { name: name.value.trim().toLowerCase(), ...payload } });
|
|
611
527
|
close();
|
|
612
528
|
toast(d ? "已保存" : "已创建");
|
|
613
|
-
|
|
614
|
-
else void openSkills();
|
|
529
|
+
void reload();
|
|
615
530
|
} catch (err) {
|
|
616
531
|
save.disabled = false;
|
|
617
532
|
toast(err.message);
|
|
@@ -634,7 +549,7 @@ function toBase64(buf) {
|
|
|
634
549
|
|
|
635
550
|
/**
|
|
636
551
|
* 上传技能:压缩包(.zip / .skill)、单个 SKILL.md,或者整个文件夹。target 给了就是替换那个技能的内容。
|
|
637
|
-
*
|
|
552
|
+
* 传完打开编辑框让人确认说明和变量。
|
|
638
553
|
*/
|
|
639
554
|
function pickUpload(target) {
|
|
640
555
|
const choose = (folder) => {
|
|
@@ -653,7 +568,7 @@ function pickUpload(target) {
|
|
|
653
568
|
"div",
|
|
654
569
|
{ class: "modal-body" },
|
|
655
570
|
h("p", { class: "skill-note" }, "技能包里要有 SKILL.md(开头的 frontmatter 写 name 和 description),可以带脚本、模板等文件。要声明变量的话,放一个 agent-loft.json:{ \"vars\": [{ \"name\": \"MY_API_KEY\", \"secret\": true, \"required\": true }] }。"),
|
|
656
|
-
target ? h("p", { class: "skill-note" }, "替换后名字不变,包里写的 name
|
|
571
|
+
target ? h("p", { class: "skill-note" }, "替换后名字不变,包里写的 name 会被忽略。") : null,
|
|
657
572
|
h(
|
|
658
573
|
"div",
|
|
659
574
|
{ class: "skill-upload-btns" },
|
|
@@ -692,9 +607,8 @@ async function upload(files, target) {
|
|
|
692
607
|
return;
|
|
693
608
|
}
|
|
694
609
|
const s = await api("/api/skills", { method: "POST", body: { upload: payload, visibility: "private" } });
|
|
695
|
-
toast(`已导入 ${s.name}
|
|
696
|
-
|
|
697
|
-
else await openSkills();
|
|
610
|
+
toast(`已导入 ${s.name}`);
|
|
611
|
+
void reload();
|
|
698
612
|
await openEditor(s);
|
|
699
613
|
} catch (err) {
|
|
700
614
|
toast(err.message);
|