hob-bot 1.4.0 → 1.5.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.
@@ -20,13 +20,14 @@ import type {
20
20
  ModelThinkingSaved,
21
21
  ModelsView,
22
22
  PushSubscriptionJson,
23
+ RoutineCheck,
23
24
  SettingsPatch,
24
25
  UsagePricePatch,
25
26
  UsagePriceSaved,
26
27
  } from "../../shared/types.ts";
27
28
  import { parseSprite } from "../../shared/sprites.ts";
28
29
  import { answerApproval, archiveAssistant, createFromDraft, dismissDraft } from "./actions.ts";
29
- import { setPassword } from "./config.ts";
30
+ import { setPassword, assistantFilesDir, cacheDir } from "./config.ts";
30
31
  import { newId } from "./ids.ts";
31
32
  import { claudeStatus } from "./claude-status.ts";
32
33
  import { checkDesktopUrl } from "./desktop.ts";
@@ -570,7 +571,7 @@ export function createApp(runtime: HobRuntime, options: { webDir?: string; home:
570
571
  const path = await generateImage(
571
572
  {
572
573
  home: options.home,
573
- dir: join(options.home, "previews"),
574
+ dir: join(cacheDir(options.home), "previews"),
574
575
  config,
575
576
  ...(typeof body.apiKey === "string" && body.apiKey.trim() ? { apiKey: body.apiKey.trim() } : {}),
576
577
  },
@@ -832,6 +833,8 @@ export function createApp(runtime: HobRuntime, options: { webDir?: string; home:
832
833
  ...(typeof body.dutyLong === "string" ? { dutyLong: body.dutyLong } : {}),
833
834
  ...(typeof body.sprite === "string" ? { sprite: body.sprite } : {}),
834
835
  ...(typeof body.pinned === "boolean" ? { pinned: body.pinned } : {}),
836
+ // 置顶区里的位置:拖动头像换位置后按 0、1、2… 存,取整且不为负
837
+ ...(typeof body.pinOrder === "number" && Number.isFinite(body.pinOrder) ? { pinOrder: Math.max(0, Math.round(body.pinOrder)) } : {}),
835
838
  ...(typeof body.hidden === "boolean" ? { hidden: body.hidden } : {}),
836
839
  ...(typeof body.notify === "boolean" ? { notify: body.notify } : {}),
837
840
  ...(models ? { models } : {}),
@@ -898,6 +901,8 @@ export function createApp(runtime: HobRuntime, options: { webDir?: string; home:
898
901
  name?: string;
899
902
  instruction?: string;
900
903
  schedules?: string[];
904
+ check?: RoutineCheck;
905
+ once?: boolean;
901
906
  };
902
907
  if (!body.assistantId || !body.name || !body.instruction || !body.schedules?.length) {
903
908
  return c.json({ error: "缺字段:assistantId、name、instruction、schedules" }, 400);
@@ -908,6 +913,8 @@ export function createApp(runtime: HobRuntime, options: { webDir?: string; home:
908
913
  name: body.name,
909
914
  instruction: body.instruction,
910
915
  schedules: body.schedules,
916
+ ...(body.check ? { check: body.check } : {}),
917
+ ...(body.once ? { once: true } : {}),
911
918
  });
912
919
  return c.json({ routine });
913
920
  } catch (error) {
@@ -922,6 +929,8 @@ export function createApp(runtime: HobRuntime, options: { webDir?: string; home:
922
929
  schedules?: string[];
923
930
  active?: boolean;
924
931
  skill?: string | null;
932
+ check?: RoutineCheck | null;
933
+ once?: boolean;
925
934
  };
926
935
  try {
927
936
  const routine = routines.update(c.req.param("id"), body);
@@ -943,8 +952,8 @@ export function createApp(runtime: HobRuntime, options: { webDir?: string; home:
943
952
 
944
953
  app.post("/api/routines/:id/run", async (c) => {
945
954
  try {
946
- const job = await routines.runNow(c.req.param("id"));
947
- return c.json({ job });
955
+ const result = await routines.runNow(c.req.param("id"));
956
+ return c.json(result);
948
957
  } catch (error) {
949
958
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 404);
950
959
  }
@@ -1053,8 +1062,11 @@ export function createApp(runtime: HobRuntime, options: { webDir?: string; home:
1053
1062
  });
1054
1063
 
1055
1064
  app.post("/api/uploads", async (c) => {
1065
+ const assistantId = (c.req.query("assistantId") ?? "").trim();
1066
+ if (!assistantId) return c.json({ error: "缺少 assistantId:上传的文件要放在某个助手的目录下" }, 400);
1067
+ if (!store.assistant(assistantId)) return c.json({ error: `助手不存在:${assistantId}` }, 404);
1056
1068
  const body = await c.req.parseBody({ all: true });
1057
- const uploads = join(options.home, "uploads");
1069
+ const uploads = assistantFilesDir(options.home, assistantId, "uploads");
1058
1070
  mkdirSync(uploads, { recursive: true });
1059
1071
  const saved: { path: string; name: string }[] = [];
1060
1072
  const values = Object.values(body).flatMap((value) => (Array.isArray(value) ? value : [value]));
@@ -18,6 +18,7 @@ import type {
18
18
  } from "../../shared/types.ts";
19
19
  import { IMAGE_TYPES, isImagePath } from "../../shared/media.ts";
20
20
  import { newId } from "./ids.ts";
21
+ import { resolveMovedPath } from "./layout.ts";
21
22
  import { availableDesktopTools, type DesktopToolSession } from "./desktop-tools.ts";
22
23
  import { imageConfigured } from "./image.ts";
23
24
  import { boundKnowledge, findKnowledgeTool, searchKnowledgeTool } from "./knowledge-tools.ts";
@@ -220,6 +221,8 @@ export class JobManager {
220
221
  cwd: job.cwd ?? "",
221
222
  save: (stepId, text) => this.store.writeStepOutput(jobId, stepId, text),
222
223
  mcpServers: mcpServerDisplay(this.runtime.home),
224
+ // 迁移前写进会话的绝对路径:换成新位置后,详情页里还是短文件名
225
+ resolvePath: (path) => resolveMovedPath(this.runtime.home, path),
223
226
  });
224
227
  return [...steps, ...(this.workers.get(jobId)?.queued ?? [])];
225
228
  }
@@ -7,10 +7,15 @@ import type { EmbeddingSettings } from "../../shared/types.ts";
7
7
  import { embed, embeddingKey, embeddingModel } from "./embedding.ts";
8
8
  import { extractChunks, UnsupportedIndex, type IndexedChunk } from "./knowledge-index.ts";
9
9
  import type { Bus } from "./bus.ts";
10
- import { looksText } from "./files.ts";
10
+ import { looksText, isInPagesDir } from "./files.ts";
11
11
  import { newId } from "./ids.ts";
12
12
  import type { Store } from "./store.ts";
13
13
 
14
+ /** 从数据目录内的相对路径推任务 id:老布局 jobs/<jid>/…,新布局 assistants/<aid>/jobs/<jid>/… */
15
+ function jobIdFromPath(rel: string): string | undefined {
16
+ return /^(?:assistants[/\\][^/\\]+[/\\])?jobs[/\\]([^/\\]+)[/\\]/.exec(rel)?.[1];
17
+ }
18
+
14
19
  type Entry = KnowledgeEntry & { sourceKey?: string };
15
20
  type CollectInput = { origin: "output" | "chat"; path: string; name?: string; description?: string; assistantId: string; itemId?: string; jobId?: string };
16
21
  type Query = { origin?: string; collection?: string; kind?: string; assistantId?: string; q?: string; before?: string; limit?: number };
@@ -188,7 +193,7 @@ export class KnowledgeStore {
188
193
  const originalName = basename(input.path);
189
194
  const name = input.name || (input.origin === "chat" ? originalName.replace(/^u-[^-]+-/, "") : originalName);
190
195
  const kind = fileKind(originalName) ?? (looksText(data) ? "text" : "other");
191
- const inPages = !relative(join(this.home, "pages"), resolve(input.path)).startsWith(`..${sep}`) && relative(join(this.home, "pages"), resolve(input.path)) !== "..";
196
+ const inPages = isInPagesDir(relative(this.home, resolve(input.path)));
192
197
  const ext = extname(originalName).toLowerCase().replace(/[^.a-z0-9]/g, "");
193
198
  const path = join(this.root, inPages ? "pages" : "blobs", `${hash}${inPages ? ".html" : ext}`);
194
199
  mkdirSync(dirname(path), { recursive: true });
@@ -201,8 +206,8 @@ export class KnowledgeStore {
201
206
  : content;
202
207
  writeFileSync(join(this.root, "text", `${hash}.txt`), Buffer.from(text).subarray(0, 64 * 1024), { mode: 0o600 });
203
208
  }
204
- const rel = relative(join(this.home, "jobs"), resolve(input.path));
205
- const jobId = input.jobId || (!rel.startsWith(`..${sep}`) && rel !== ".." && rel.includes(sep) ? rel.split(sep)[0] : undefined);
209
+ const rel = relative(this.home, resolve(input.path));
210
+ const jobId = input.jobId || jobIdFromPath(rel);
206
211
  const description = input.description || (jobId ? this.store.job(jobId)?.title : undefined);
207
212
  const now = this.timestamp(entries);
208
213
  if (previous) {
@@ -0,0 +1,25 @@
1
+ // HOB_HOME 布局迁移的命令行入口:npm run layout -- --dry-run | --restore
2
+ //
3
+ // 正常情况下启动时自动迁移;这两个开关给试跑和出事后的恢复用。
4
+ // --dry-run 只打印计划,什么都不动;--restore 从 backup-v1-* 恢复成老布局。
5
+ import { resolveHome } from "./config.ts";
6
+ import { migrateHome, restoreHome } from "./layout.ts";
7
+
8
+ const args = new Set(process.argv.slice(2));
9
+ const home = resolveHome();
10
+ const usage = "用法:npm run layout -- [--dry-run | --restore](npm 安装的用 hob layout [--dry-run | --restore])";
11
+
12
+ if (args.has("--help") || args.has("-h")) {
13
+ console.log(usage);
14
+ } else if (args.has("--dry-run")) {
15
+ console.log(`数据目录:${home}`);
16
+ const summary = migrateHome(home, { dryRun: true });
17
+ console.log(summary.status === "dry-run" ? "(dry-run 没有改动任何文件)" : `当前状态:${summary.status}`);
18
+ } else if (args.has("--restore")) {
19
+ console.log(`数据目录:${home}`);
20
+ const result = restoreHome(home);
21
+ console.log(`已恢复到老布局:${result.files} 个文件,备份在 ${result.backupDir}。下次启动会重新迁移。`);
22
+ } else {
23
+ console.error(usage);
24
+ process.exit(1);
25
+ }