hob-bot 1.0.3 → 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.
- package/README.md +75 -5
- package/README.zh-CN.md +75 -5
- package/dist/web/assets/DesktopView-rVxacveF.js +5 -0
- package/dist/web/assets/index-B-8ISOr1.js +267 -0
- package/dist/web/assets/index-Cu1Pf8yX.css +1 -0
- package/dist/web/assets/{math-zZ4JA8eM.js → math-weKGDUmL.js} +2 -2
- package/dist/web/index.html +2 -2
- package/dist/web/mock/desktop.jpg +0 -0
- package/dist/web/mock/photo.jpg +0 -0
- package/dist/web/mock/photo2.jpg +0 -0
- package/dist/web/mock/sunset.html +90 -0
- package/dist/web/mock//347/201/253/346/230/237/346/227/245/350/220/275/347/244/272/346/204/217/345/233/276.md +34 -0
- package/package.json +10 -2
- package/server/src/actions.ts +24 -57
- package/server/src/assistant-skills.ts +201 -0
- package/server/src/boot.ts +1 -0
- package/server/src/claude-status.ts +47 -0
- package/server/src/config.ts +3 -1
- package/server/src/delegations.ts +40 -12
- package/server/src/desktop-control.ts +152 -0
- package/server/src/desktop-events.ts +63 -0
- package/server/src/desktop-public.ts +43 -0
- package/server/src/desktop-tools.ts +161 -0
- package/server/src/desktop.ts +76 -0
- package/server/src/embedding.ts +52 -0
- package/server/src/files.ts +63 -43
- package/server/src/http.ts +334 -15
- package/server/src/jobs.ts +330 -59
- package/server/src/knowledge-index.ts +123 -0
- package/server/src/knowledge-tools.ts +94 -0
- package/server/src/knowledge.ts +451 -0
- package/server/src/lore-entry.ts +10 -0
- package/server/src/main.ts +6 -0
- package/server/src/mcp.ts +745 -0
- package/server/src/memory.ts +11 -4
- package/server/src/now.ts +33 -0
- package/server/src/overview.ts +1 -1
- package/server/src/pages.ts +1 -1
- package/server/src/pi.ts +92 -16
- package/server/src/prompt.ts +46 -154
- package/server/src/providers.ts +3 -2
- package/server/src/push.ts +42 -7
- package/server/src/router-activity.ts +4 -0
- package/server/src/router-tools.ts +132 -38
- package/server/src/router.ts +159 -19
- package/server/src/routines.ts +14 -4
- package/server/src/runtime.ts +15 -0
- package/server/src/settings.ts +43 -1
- package/server/src/store.ts +4 -3
- package/server/src/thumbs.ts +49 -0
- package/server/src/transcript.ts +57 -4
- package/server/src/worker-tools.ts +14 -12
- package/shared/knowledge.ts +92 -0
- package/shared/mcp.ts +91 -0
- package/shared/media.ts +6 -0
- package/shared/plain-text.ts +31 -0
- package/shared/skills.ts +18 -0
- package/shared/sprites.ts +198 -0
- package/shared/types.ts +105 -20
- package/vendor/claude-bridge/HOB-PATCH.md +10 -0
- package/vendor/claude-bridge/LICENSE +21 -0
- package/vendor/claude-bridge/package.json +62 -0
- package/vendor/claude-bridge/src/agents-md.ts +14 -0
- package/vendor/claude-bridge/src/askclaude-schema.ts +92 -0
- package/vendor/claude-bridge/src/askclaude-ui.ts +90 -0
- package/vendor/claude-bridge/src/attachments.ts +135 -0
- package/vendor/claude-bridge/src/config.ts +105 -0
- package/vendor/claude-bridge/src/convert.ts +222 -0
- package/vendor/claude-bridge/src/extract-tool-results.ts +47 -0
- package/vendor/claude-bridge/src/index.ts +2503 -0
- package/vendor/claude-bridge/src/log-paths.ts +19 -0
- package/vendor/claude-bridge/src/mcp-server.ts +169 -0
- package/vendor/claude-bridge/src/models.ts +164 -0
- package/vendor/claude-bridge/src/prompt-capture.ts +420 -0
- package/vendor/claude-bridge/src/prompt-stream.ts +102 -0
- package/vendor/claude-bridge/src/query-state.ts +118 -0
- package/vendor/claude-bridge/src/rate-limit.ts +48 -0
- package/vendor/claude-bridge/src/session-cwd.ts +38 -0
- package/vendor/claude-bridge/src/session-verify.ts +38 -0
- package/vendor/claude-bridge/src/skills.ts +20 -0
- package/vendor/claude-bridge/src/transcript.ts +86 -0
- package/vendor/claude-bridge/src/usage.ts +53 -0
- package/vendor/lore-pi-extension/package.json +1 -1
- package/vendor/lore-pi-extension/tools.ts +8 -5
- package/dist/web/assets/index-C72gAwgN.css +0 -1
- package/dist/web/assets/index-Dyw5G8Pg.js +0 -230
package/server/src/files.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// /api/files 的取文件规则:路径先解开符号链接,再判断能不能给页面看。
|
|
2
|
-
import { closeSync, openSync, readSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
3
|
-
import { basename, join, relative, resolve, sep } from "node:path";
|
|
2
|
+
import { closeSync, existsSync, lstatSync, openSync, readSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
3
|
+
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { fileKind, isTextKind, mimeOf } from "../../shared/files.ts";
|
|
5
5
|
import type { FileKind, JobFileEntry, JobFilesView } from "../../shared/types.ts";
|
|
6
6
|
|
|
@@ -11,13 +11,15 @@ const DENY_ROOT_DIRS = new Set(["engine", "lore"]);
|
|
|
11
11
|
/** 备份文件一律不给(凭据的备份也在里面)。 */
|
|
12
12
|
const DENY_ANY_FILE = /\.bak/i;
|
|
13
13
|
|
|
14
|
+
/** 任务工作目录里不对页面开放的:密钥目录、git 内部、.env、私钥。 */
|
|
15
|
+
const DENY_WORK_DIRS = /(^|[/\\])\.(ssh|gnupg|aws|git)([/\\]|$)/;
|
|
16
|
+
const DENY_WORK_FILE = /^(\.env.*|id_[a-z0-9]+|.*\.(pem|key))$/i;
|
|
17
|
+
|
|
14
18
|
/** 预览面板最多拉 1 MB 文本。 */
|
|
15
19
|
export const MAX_TEXT_BYTES = 1024 * 1024;
|
|
16
20
|
|
|
17
21
|
/** 任务工作目录里最多列 200 个文件。 */
|
|
18
22
|
const MAX_JOB_FILES = 200;
|
|
19
|
-
/** 递归太深的目录不进去(产出文件不该有这么深)。 */
|
|
20
|
-
const MAX_DEPTH = 8;
|
|
21
23
|
const SKIP_DIRS = new Set(["node_modules", ".git"]);
|
|
22
24
|
|
|
23
25
|
export interface FileInfo {
|
|
@@ -68,10 +70,11 @@ function headOf(path: string): Uint8Array {
|
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
/**
|
|
71
|
-
* 解析一个路径:realpath
|
|
73
|
+
* 解析一个路径:realpath 后必须在数据目录或某个任务的工作目录里(符号链接指出去就是 403),
|
|
72
74
|
* 凭据和内部状态文件不开放,不是普通文件当 404。
|
|
75
|
+
* 包含数据目录的工作目录(家目录、根目录这类)太宽,不算数。
|
|
73
76
|
*/
|
|
74
|
-
export function lookupFile(home: string, requested: string): Lookup {
|
|
77
|
+
export function lookupFile(home: string, requested: string, workDirs: string[] = []): Lookup {
|
|
75
78
|
const root = realpathSync(resolve(home));
|
|
76
79
|
let path: string;
|
|
77
80
|
let size: number;
|
|
@@ -85,58 +88,75 @@ export function lookupFile(home: string, requested: string): Lookup {
|
|
|
85
88
|
} catch {
|
|
86
89
|
return { ok: false, status: 404, error: "文件不存在" };
|
|
87
90
|
}
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
const inHome = path.startsWith(`${root}${sep}`);
|
|
92
|
+
const work = inHome ? undefined : workRoots(root, workDirs).find((dir) => path.startsWith(`${dir}${sep}`));
|
|
93
|
+
if (!inHome && !work) return { ok: false, status: 403, error: "只允许读 Hob 自己的文件" };
|
|
94
|
+
const relativePath = relative(inHome ? root : work!, path);
|
|
95
|
+
if ((inHome && denied(root, path)) || DENY_WORK_DIRS.test(relativePath) || DENY_WORK_FILE.test(basename(path)) || DENY_ANY_FILE.test(basename(path))) {
|
|
96
|
+
return { ok: false, status: 403, error: "这个文件不对页面开放" };
|
|
97
|
+
}
|
|
90
98
|
const name = basename(path);
|
|
91
99
|
const kind = fileKind(name) ?? (looksText(headOf(path)) ? "text" : "other");
|
|
92
100
|
const text = isTextKind(kind);
|
|
93
101
|
return {
|
|
94
102
|
ok: true,
|
|
95
|
-
info: { path, name, size, modifiedAt, kind, text, mime: mimeOf(name), ...(/^pages[/\\][0-9a-f-]{36}\.html$/i.test(relative(root, path)) ? { page: true } : {}) },
|
|
103
|
+
info: { path, name, size, modifiedAt, kind, text, mime: mimeOf(name), ...(/^(?:pages[/\\][0-9a-f-]{36}|knowledge[/\\]pages[/\\][0-9a-f]{64})\.html$/i.test(relative(root, path)) ? { page: true } : {}) },
|
|
96
104
|
};
|
|
97
105
|
}
|
|
98
106
|
|
|
99
|
-
function
|
|
100
|
-
|
|
107
|
+
function workRoots(root: string, dirs: string[]): string[] {
|
|
108
|
+
const out: string[] = [];
|
|
109
|
+
for (const dir of new Set(dirs)) {
|
|
110
|
+
try {
|
|
111
|
+
const real = realpathSync(resolve(dir));
|
|
112
|
+
if (!`${root}${sep}`.startsWith(`${real === sep ? "" : real}${sep}`)) out.push(real);
|
|
113
|
+
} catch {
|
|
114
|
+
// 目录已经删了:不算
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function visibleEntries(dir: string) {
|
|
121
|
+
return readdirSync(dir, { withFileTypes: true }).filter((entry) =>
|
|
122
|
+
!entry.isSymbolicLink() && (entry.isFile() || (entry.isDirectory() && !SKIP_DIRS.has(entry.name))));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** 一次只列一层;拒绝越界和符号链接。工作目录还没建出来时当作空的。 */
|
|
126
|
+
export function listJobFiles(workDir: string, subdir = ""): JobFilesView {
|
|
127
|
+
const parts = subdir.split(/[\\/]/).filter(Boolean);
|
|
128
|
+
if (isAbsolute(subdir) || parts.some((part) => part === ".." || SKIP_DIRS.has(part))) throw new Error("工作目录路径无效");
|
|
129
|
+
const base = resolve(workDir);
|
|
130
|
+
if (!parts.length && !existsSync(base)) return { files: [], total: 0, fileCount: 0, directoryCount: 0 };
|
|
131
|
+
let dir = base;
|
|
101
132
|
let entries;
|
|
102
133
|
try {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
134
|
+
for (const part of parts) {
|
|
135
|
+
dir = join(dir, part);
|
|
136
|
+
const info = lstatSync(dir);
|
|
137
|
+
if (info.isSymbolicLink() || !info.isDirectory()) throw new Error("不是文件夹");
|
|
138
|
+
}
|
|
139
|
+
entries = visibleEntries(dir);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
142
|
+
throw new Error(code === "ENOENT" ? "这个文件夹已经不在了" : code === "EACCES" ? "没有权限读取这个文件夹" : "工作目录路径无效");
|
|
106
143
|
}
|
|
144
|
+
const all: JobFileEntry[] = [];
|
|
107
145
|
for (const entry of entries) {
|
|
108
|
-
// 符号链接不跟:可能指到目录外面,也可能绕成环
|
|
109
|
-
if (entry.isSymbolicLink()) continue;
|
|
110
146
|
const path = join(dir, entry.name);
|
|
111
|
-
|
|
112
|
-
if (SKIP_DIRS.has(entry.name)) continue;
|
|
113
|
-
if (walk(path, base, depth + 1, out)) return true;
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
if (!entry.isFile()) continue;
|
|
117
|
-
let stats;
|
|
147
|
+
const common = { path, rel: relative(base, path), name: entry.name };
|
|
118
148
|
try {
|
|
119
|
-
|
|
149
|
+
if (entry.isDirectory()) {
|
|
150
|
+
all.push({ ...common, type: "directory", count: visibleEntries(path).length });
|
|
151
|
+
} else {
|
|
152
|
+
const stats = statSync(path);
|
|
153
|
+
all.push({ ...common, type: "file", size: stats.size, modifiedAt: stats.mtime.toISOString(), kind: fileKind(entry.name) ?? (looksText(headOf(path)) ? "text" : "other") });
|
|
154
|
+
}
|
|
120
155
|
} catch {
|
|
121
|
-
|
|
156
|
+
// 列的时候刚被删掉或读不了(在跑的任务常有临时文件):跳过这一项
|
|
122
157
|
}
|
|
123
|
-
out.push({
|
|
124
|
-
path,
|
|
125
|
-
rel: relative(base, path),
|
|
126
|
-
name: entry.name,
|
|
127
|
-
size: stats.size,
|
|
128
|
-
modifiedAt: stats.mtime.toISOString(),
|
|
129
|
-
kind: fileKind(entry.name) ?? (looksText(headOf(path)) ? "text" : "other"),
|
|
130
|
-
});
|
|
131
|
-
if (out.length >= MAX_JOB_FILES + 1) return true;
|
|
132
158
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
/** 任务工作目录里的文件:递归、修改时间倒序、最多 200 个,跳过 node_modules 和 .git。 */
|
|
137
|
-
export function listJobFiles(workDir: string): JobFilesView {
|
|
138
|
-
const all: JobFileEntry[] = [];
|
|
139
|
-
walk(workDir, workDir, 0, all);
|
|
140
|
-
all.sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt));
|
|
141
|
-
return { files: all.slice(0, MAX_JOB_FILES), hasMore: all.length > MAX_JOB_FILES };
|
|
159
|
+
all.sort((a, b) => a.type !== b.type ? (a.type === "directory" ? -1 : 1) : a.type === "file" && b.type === "file" ? b.modifiedAt.localeCompare(a.modifiedAt) || a.name.localeCompare(b.name) : a.name.localeCompare(b.name));
|
|
160
|
+
const directoryCount = all.filter((entry) => entry.type === "directory").length;
|
|
161
|
+
return { files: all.slice(0, MAX_JOB_FILES), total: all.length, directoryCount, fileCount: all.length - directoryCount };
|
|
142
162
|
}
|