agent-loft 0.2.31 → 0.2.33
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 +14 -5
- package/package.json +1 -1
- package/src/agents/claude/driver.ts +90 -8
- package/src/agents/claude/session.ts +81 -12
- package/src/agents/codex/session.ts +70 -13
- package/src/channels/feishu/bot.ts +2 -2
- package/src/channels/web/api.ts +72 -2
- package/src/channels/web/conv-pages.ts +68 -0
- package/src/channels/web/http.ts +2 -2
- package/src/channels/web/server.ts +20 -3
- package/src/channels/web/skills-api.ts +225 -0
- package/src/core/chat-mode.ts +3 -3
- package/src/core/command-catalog.ts +8 -1
- package/src/core/commands.ts +3 -0
- package/src/core/hub.ts +168 -18
- package/src/core/personal.ts +3 -1
- package/src/core/skill-runtime.ts +359 -0
- package/src/core/skills-bridge.ts +288 -0
- package/src/core/skills.ts +676 -0
- package/src/core/tasks-bridge.ts +5 -1
- package/src/core/tasks-mcp.ts +11 -0
- package/src/core/types.ts +44 -3
- package/src/core/usage.ts +70 -0
- package/src/core/zip.ts +98 -0
- package/src/main.ts +4 -0
- package/src/mcp/tasks-server.ts +125 -4
- package/web/css/composer.css +0 -25
- package/web/css/messages.css +169 -0
- package/web/css/sidebar.css +5 -5
- package/web/css/skills.css +385 -0
- package/web/css/topbar.css +131 -0
- package/web/index.html +1 -0
- package/web/lib/dom.js +3 -1
- package/web/lib/markdown.js +84 -14
- package/web/lib/ws.js +49 -5
- package/web/settings/index.js +7 -3
- package/web/settings/skills.js +617 -0
- package/web/state.js +4 -3
- package/web/views/composer.js +16 -3
- package/web/views/interaction.js +12 -4
- package/web/views/layout.js +22 -15
- package/web/views/mention.js +193 -0
- 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 -16
- package/web/views/tasks.js +11 -2
- package/web/views/topbar.js +168 -40
package/web/lib/ws.js
CHANGED
|
@@ -2,7 +2,7 @@ import { h } from "./dom.js";
|
|
|
2
2
|
import { api } from "./api.js";
|
|
3
3
|
import { desktop, S, ui } from "../state.js";
|
|
4
4
|
import { showAuth } from "../views/auth.js";
|
|
5
|
-
import { renderSidebar, scheduleSidebar } from "../views/sidebar.js";
|
|
5
|
+
import { applyBootstrap, bootstrapQuery, renderSidebar, scheduleSidebar } from "../views/sidebar.js";
|
|
6
6
|
import { scheduleQuota } from "../views/quota.js";
|
|
7
7
|
import { renderTopbar } from "../views/topbar.js";
|
|
8
8
|
import { applyEvent, openConversation } from "../views/messages.js";
|
|
@@ -92,13 +92,52 @@ export const WS = {
|
|
|
92
92
|
},
|
|
93
93
|
|
|
94
94
|
/** 告诉服务器我是否在前台、正看着哪条会话:正看着的会话不推系统通知 */
|
|
95
|
+
/**
|
|
96
|
+
* 告诉服务端「我正看着这条会话」:看着的时候不推送。要页面在前台、有焦点,而且最近还有人在动它 ——
|
|
97
|
+
* 窗口开着人走开了(电脑上停在这条会话、人在用手机),照样推
|
|
98
|
+
*/
|
|
95
99
|
presence() {
|
|
96
|
-
const visible = document.visibilityState === "visible" && document.hasFocus();
|
|
100
|
+
const visible = document.visibilityState === "visible" && document.hasFocus() && Date.now() - lastInput < IDLE_MS;
|
|
97
101
|
this.raw({ t: "presence", visible, key: S.current ?? "" });
|
|
98
102
|
},
|
|
99
103
|
};
|
|
100
104
|
|
|
101
|
-
|
|
105
|
+
// 切回窗口本身就算动了一下
|
|
106
|
+
window.addEventListener("focus", () => (markActive(), WS.presence()));
|
|
107
|
+
window.addEventListener("blur", () => WS.presence());
|
|
108
|
+
|
|
109
|
+
// ---------- 有没有人在动页面 ----------
|
|
110
|
+
// 超过 IDLE_MS 没点、没按键、没滚动,就当人不在了:报一次 presence,服务端照常推送;一动又报回来
|
|
111
|
+
|
|
112
|
+
const IDLE_MS = 2 * 60_000;
|
|
113
|
+
let lastInput = Date.now();
|
|
114
|
+
let idle = false;
|
|
115
|
+
let idleTimer = 0;
|
|
116
|
+
function markActive() {
|
|
117
|
+
lastInput = Date.now();
|
|
118
|
+
idle = false;
|
|
119
|
+
armIdle();
|
|
120
|
+
}
|
|
121
|
+
function armIdle() {
|
|
122
|
+
clearTimeout(idleTimer);
|
|
123
|
+
idleTimer = setTimeout(() => {
|
|
124
|
+
idle = true;
|
|
125
|
+
WS.presence();
|
|
126
|
+
}, IDLE_MS);
|
|
127
|
+
}
|
|
128
|
+
function onInput() {
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
// 滚动一秒几十次,重设计时器不用那么勤
|
|
131
|
+
if (!idle && now - lastInput < 5000) return;
|
|
132
|
+
lastInput = now;
|
|
133
|
+
if (idle) {
|
|
134
|
+
idle = false;
|
|
135
|
+
WS.presence();
|
|
136
|
+
}
|
|
137
|
+
armIdle();
|
|
138
|
+
}
|
|
139
|
+
for (const ev of ["pointerdown", "keydown", "wheel", "touchstart", "scroll"]) window.addEventListener(ev, onInput, { capture: true, passive: true });
|
|
140
|
+
armIdle();
|
|
102
141
|
|
|
103
142
|
/** 打开 / 切回页面:清掉桌面图标角标和计数(计数由 sw.js 在收到推送时累加) */
|
|
104
143
|
export function clearAppBadge() {
|
|
@@ -108,6 +147,7 @@ export function clearAppBadge() {
|
|
|
108
147
|
}
|
|
109
148
|
|
|
110
149
|
document.addEventListener("visibilitychange", () => {
|
|
150
|
+
if (document.visibilityState === "visible") markActive();
|
|
111
151
|
WS.presence();
|
|
112
152
|
clearAppBadge();
|
|
113
153
|
// 手机切回前台:连接多半已经被系统掐了,立刻重连而不是等退避
|
|
@@ -120,8 +160,8 @@ document.addEventListener("visibilitychange", () => {
|
|
|
120
160
|
/** 重连后:以服务器上的会话列表和待处理授权为准,清掉本地已失效的 */
|
|
121
161
|
async function resync() {
|
|
122
162
|
try {
|
|
123
|
-
const data = await api(
|
|
124
|
-
|
|
163
|
+
const data = await api(`/api/bootstrap${bootstrapQuery()}`);
|
|
164
|
+
applyBootstrap(data);
|
|
125
165
|
const live = new Set(data.pending.map((i) => i.id));
|
|
126
166
|
for (const ix of S.pending.values()) {
|
|
127
167
|
// 快照生成之后才出现的授权,快照里当然没有,不能当成「已在别处处理」
|
|
@@ -201,6 +241,10 @@ function onWsMessage(msg) {
|
|
|
201
241
|
// 自己的定时任务变了:定时任务页开着的话会去重新拉(views/tasks.js)
|
|
202
242
|
document.dispatchEvent(new Event("aim:tasks"));
|
|
203
243
|
return;
|
|
244
|
+
case "skills":
|
|
245
|
+
// 技能变了:设置里的技能页开着就重新拉,@ 补全的缓存作废
|
|
246
|
+
document.dispatchEvent(new Event("aim:skills"));
|
|
247
|
+
return;
|
|
204
248
|
case "notice":
|
|
205
249
|
// 正看着这条会话就不打扰;普通网页靠 Web Push,这里只给桌面端
|
|
206
250
|
if (!desktop) return;
|
package/web/settings/index.js
CHANGED
|
@@ -9,11 +9,13 @@ import { agentsTab } from "./agents.js";
|
|
|
9
9
|
import { feishuTab } from "./feishu.js";
|
|
10
10
|
import { authTab, securityTab } from "./security.js";
|
|
11
11
|
import { memoryTab } from "./memory.js";
|
|
12
|
+
import { skillsTab } from "./skills.js";
|
|
12
13
|
import { systemTab, usersTab } from "./users.js";
|
|
13
14
|
|
|
14
15
|
// ---------- 设置 ----------
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
/** tab:直接打开哪一页(标签名,如「技能」),不给就是第一页 */
|
|
18
|
+
export async function openSettings(tab) {
|
|
17
19
|
const nav = h("div", { class: "settings-nav" });
|
|
18
20
|
const panel = h("div", { class: "settings-body" });
|
|
19
21
|
// 设置弹窗高度固定,切标签页时不跟着内容一伸一缩
|
|
@@ -22,6 +24,7 @@ export async function openSettings() {
|
|
|
22
24
|
if (!isAdmin()) {
|
|
23
25
|
const tabs = [
|
|
24
26
|
["记忆", () => memoryTab(panel)],
|
|
27
|
+
["技能", () => skillsTab(panel)],
|
|
25
28
|
["通知", () => notifyTab(panel)],
|
|
26
29
|
["账号", () => securityTab(panel)],
|
|
27
30
|
];
|
|
@@ -31,7 +34,7 @@ export async function openSettings() {
|
|
|
31
34
|
tabs[i][1]();
|
|
32
35
|
};
|
|
33
36
|
nav.append(...tabs.map(([name], i) => h("button", { onclick: () => select(i) }, name)));
|
|
34
|
-
select(0);
|
|
37
|
+
select(Math.max(0, tabs.findIndex(([name]) => name === tab)));
|
|
35
38
|
return;
|
|
36
39
|
}
|
|
37
40
|
let cfg;
|
|
@@ -45,6 +48,7 @@ export async function openSettings() {
|
|
|
45
48
|
["通用", () => generalTab(cfg, panel)],
|
|
46
49
|
["Agent", () => agentsTab(cfg, panel)],
|
|
47
50
|
["命令", () => commandsTab(cfg, panel)],
|
|
51
|
+
["技能", () => skillsTab(panel)],
|
|
48
52
|
["飞书机器人", () => feishuTab(cfg, panel)],
|
|
49
53
|
["授权", () => authTab(panel)],
|
|
50
54
|
["记忆", () => memoryTab(panel)],
|
|
@@ -59,5 +63,5 @@ export async function openSettings() {
|
|
|
59
63
|
tabs[i][1]();
|
|
60
64
|
};
|
|
61
65
|
nav.append(...tabs.map(([name], i) => h("button", { onclick: () => select(i) }, name)));
|
|
62
|
-
select(0);
|
|
66
|
+
select(Math.max(0, tabs.findIndex(([name]) => name === tab)));
|
|
63
67
|
}
|