hob-bot 1.0.4 → 1.2.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 +76 -6
- package/README.zh-CN.md +76 -6
- package/dist/web/assets/DesktopView-CEBJFAIN.js +5 -0
- package/dist/web/assets/index-Di5lyTHO.css +1 -0
- package/dist/web/assets/index-y09i1RGd.js +267 -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/package.json +7 -1
- package/server/src/actions.ts +16 -56
- package/server/src/assistant-skills.ts +215 -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 +21 -11
- 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 +325 -13
- package/server/src/job-view.ts +52 -0
- package/server/src/jobs.ts +326 -44
- 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 +4 -4
- package/server/src/pages.ts +1 -1
- package/server/src/pi.ts +92 -16
- package/server/src/prompt.ts +51 -156
- package/server/src/providers.ts +3 -2
- package/server/src/push.ts +39 -5
- package/server/src/router-activity.ts +6 -0
- package/server/src/router-tools.ts +184 -39
- package/server/src/router.ts +154 -17
- 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/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/skills.ts +18 -0
- package/shared/types.ts +104 -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-BCGEZvgg.css +0 -1
- package/dist/web/assets/index-BqkIAlk9.js +0 -258
package/server/src/transcript.ts
CHANGED
|
@@ -4,6 +4,9 @@ import { commandSummary } from "../../shared/command.ts";
|
|
|
4
4
|
import type { JobStep } from "../../shared/types.ts";
|
|
5
5
|
import { showPath } from "./text.ts";
|
|
6
6
|
|
|
7
|
+
/** 沉淀技能那一轮发给任务的话的开头:任务详情里这句显示成「沉淀技能」。 */
|
|
8
|
+
export const SEDIMENT_PROMPT_HEAD = "这件事以后还会再做。回顾刚才的过程,把能复用的做法沉淀成技能";
|
|
9
|
+
|
|
7
10
|
/** transcript 里单步输出最多这么多字,超出的整段落盘,页面按路径去拉。 */
|
|
8
11
|
export const STEP_OUTPUT_LIMIT = 20 * 1024;
|
|
9
12
|
/** 实时事件里的单步输出最多这么多字(全文落盘,给路径)。 */
|
|
@@ -84,8 +87,19 @@ const MEMORY_TOOLS: Record<string, string> = {
|
|
|
84
87
|
lore_status: "查看记忆状态",
|
|
85
88
|
};
|
|
86
89
|
|
|
90
|
+
/** MCP 工具(mcp__<服务>__<工具>)显示成「服务名 · 工具名」,不露原始名。 */
|
|
91
|
+
function mcpToolDisplay(name: string, mcpServers: Record<string, string> | undefined): string | undefined {
|
|
92
|
+
if (!name.startsWith("mcp__")) return undefined;
|
|
93
|
+
const rest = name.slice("mcp__".length);
|
|
94
|
+
const split = rest.indexOf("__");
|
|
95
|
+
if (split <= 0) return undefined;
|
|
96
|
+
const server = mcpServers?.[rest.slice(0, split)] ?? rest.slice(0, split);
|
|
97
|
+
const tool = rest.slice(split + 2) || "?";
|
|
98
|
+
return `${server} · ${tool}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
87
101
|
/** 一行中文:「运行 `npm test`」「修改 server/src/jobs.ts」「读取 README.md」。 */
|
|
88
|
-
export function toolTitle(name: string, args: Record<string, unknown>): string {
|
|
102
|
+
export function toolTitle(name: string, args: Record<string, unknown>, mcpServers?: Record<string, string>): string {
|
|
89
103
|
const path = String(args.path ?? "");
|
|
90
104
|
switch (name) {
|
|
91
105
|
case "bash":
|
|
@@ -104,6 +118,8 @@ export function toolTitle(name: string, args: Record<string, unknown>): string {
|
|
|
104
118
|
return `列出 ${path}`;
|
|
105
119
|
case "report":
|
|
106
120
|
return "提交结果";
|
|
121
|
+
case "save_skill":
|
|
122
|
+
return `保存技能:${oneLine(String(args.title ?? ""), 60)}`;
|
|
107
123
|
case "ask_user":
|
|
108
124
|
return `询问用户:${oneLine(String(args.title ?? ""), 60)}`;
|
|
109
125
|
case "overview":
|
|
@@ -112,9 +128,40 @@ export function toolTitle(name: string, args: Record<string, unknown>): string {
|
|
|
112
128
|
return `生成图片:${oneLine(String(args.prompt ?? ""), 60)}`;
|
|
113
129
|
case "web_search":
|
|
114
130
|
return `联网搜索:${oneLine(String(args.query ?? ""), 60)}`;
|
|
131
|
+
case "find_knowledge":
|
|
132
|
+
return args.query ? `找资料:${oneLine(String(args.query), 60)}` : "找资料";
|
|
133
|
+
case "search_knowledge":
|
|
134
|
+
return args.query ? `查知识库:${oneLine(String(args.query), 60)}` : "查知识库";
|
|
115
135
|
case "web_fetch":
|
|
116
136
|
return `读取网页:${oneLine(String(args.url ?? ""), 60)}`;
|
|
137
|
+
case "browser_open": {
|
|
138
|
+
let target = oneLine(String(args.url ?? ""), 60);
|
|
139
|
+
try { target = new URL(target).hostname; } catch { /* 无效地址保留原样 */ }
|
|
140
|
+
return `浏览器 · 打开 ${target}`;
|
|
141
|
+
}
|
|
142
|
+
case "browser_snapshot": return "浏览器 · 读取页面";
|
|
143
|
+
case "browser_click": return "浏览器 · 点击";
|
|
144
|
+
case "browser_type": return "浏览器 · 输入";
|
|
145
|
+
case "browser_press": return "浏览器 · 按键";
|
|
146
|
+
case "browser_back": return "浏览器 · 返回";
|
|
147
|
+
case "browser_screenshot": return "浏览器 · 截图";
|
|
148
|
+
case "desktop_screenshot": return "桌面 · 截图";
|
|
149
|
+
case "desktop_action": {
|
|
150
|
+
const names: Record<string, string> = { click: "点击", move: "移动", drag: "拖动", type: "输入", key: "按键", scroll: "滚动", wait: "等待" };
|
|
151
|
+
const first = Array.isArray(args.actions) ? args.actions[0] as { type?: string } | undefined : undefined;
|
|
152
|
+
return `桌面 · ${names[first?.type ?? ""] ?? "操作"}`;
|
|
153
|
+
}
|
|
154
|
+
case "tool_search":
|
|
155
|
+
return `搜索可用工具:${oneLine(String(args.query ?? ""), 60)}`;
|
|
156
|
+
case "list_mcp_resources":
|
|
157
|
+
return "列出 MCP 资源";
|
|
158
|
+
case "list_mcp_resource_templates":
|
|
159
|
+
return "列出 MCP 资源模板";
|
|
160
|
+
case "read_mcp_resource":
|
|
161
|
+
return `读取 MCP 资源:${oneLine(String(args.uri ?? ""), 90)}`;
|
|
117
162
|
default: {
|
|
163
|
+
const mcp = mcpToolDisplay(name, mcpServers);
|
|
164
|
+
if (mcp) return `调用 ${mcp}`;
|
|
118
165
|
const label = MEMORY_TOOLS[name];
|
|
119
166
|
if (!label) return name;
|
|
120
167
|
const target = String(args.uri ?? args.query ?? "");
|
|
@@ -148,17 +195,20 @@ export interface ToolStepInput {
|
|
|
148
195
|
at?: string;
|
|
149
196
|
cwd?: string;
|
|
150
197
|
running?: boolean;
|
|
198
|
+
/** MCP 工具的显示名映射(转义后的服务名 → 配置里的原名) */
|
|
199
|
+
mcpServers?: Record<string, string>;
|
|
151
200
|
}
|
|
152
201
|
|
|
153
202
|
/** 一次工具调用 → 一个步骤(还没有结果)。实时事件和落盘推导都用它,页面按 id 合并。 */
|
|
154
203
|
export function toolStep(input: ToolStepInput): JobStep {
|
|
155
204
|
const args = toolArgs(input.name, input.args, input.cwd ?? "");
|
|
205
|
+
const mcp = mcpToolDisplay(input.name, input.mcpServers);
|
|
156
206
|
return {
|
|
157
207
|
id: input.id,
|
|
158
208
|
kind: "tool",
|
|
159
209
|
at: input.at ?? new Date().toISOString(),
|
|
160
|
-
name: input.name,
|
|
161
|
-
title: toolTitle(input.name, args),
|
|
210
|
+
name: mcp ?? input.name,
|
|
211
|
+
title: toolTitle(input.name, args, input.mcpServers),
|
|
162
212
|
args,
|
|
163
213
|
...(input.running ? { running: true } : {}),
|
|
164
214
|
};
|
|
@@ -193,6 +243,8 @@ export interface DeriveOptions {
|
|
|
193
243
|
limit?: number;
|
|
194
244
|
/** 输出过长时落盘,返回路径 */
|
|
195
245
|
save?: (stepId: string, text: string) => string;
|
|
246
|
+
/** MCP 工具的显示名映射(转义后的服务名 → 配置里的原名) */
|
|
247
|
+
mcpServers?: Record<string, string>;
|
|
196
248
|
}
|
|
197
249
|
|
|
198
250
|
/** 从任务会话文件推导整个过程:说明文字、思考摘要、工具调用(含结果)。 */
|
|
@@ -237,6 +289,7 @@ export function deriveSteps(file: string, options: DeriveOptions = {}): JobStep[
|
|
|
237
289
|
args: block.arguments ?? {},
|
|
238
290
|
at,
|
|
239
291
|
cwd: options.cwd ?? "",
|
|
292
|
+
mcpServers: options.mcpServers,
|
|
240
293
|
running: true,
|
|
241
294
|
});
|
|
242
295
|
toolIndex.set(step.id, steps.length);
|
|
@@ -265,7 +318,7 @@ export function deriveSteps(file: string, options: DeriveOptions = {}): JobStep[
|
|
|
265
318
|
seenUser = true; // 第一句是任务说明,详情头部已经有
|
|
266
319
|
continue;
|
|
267
320
|
}
|
|
268
|
-
steps.push({ id: `u${message.timestamp ?? at}`, kind: "text", at, from: "user", text });
|
|
321
|
+
steps.push({ id: `u${message.timestamp ?? at}`, kind: "text", at, from: "user", text: text.startsWith(SEDIMENT_PROMPT_HEAD) ? "沉淀技能" : text });
|
|
269
322
|
}
|
|
270
323
|
}
|
|
271
324
|
return steps;
|
|
@@ -13,15 +13,14 @@ export function registerReportTool(runtime: HobRuntime, jobId: string): ToolDefi
|
|
|
13
13
|
return {
|
|
14
14
|
name: "report",
|
|
15
15
|
label: "提交结果",
|
|
16
|
-
description:
|
|
17
|
-
"任务结束时必须调用一次,提交结果。summary 用中文写给用户:一两句结论 + 关键数字,不要罗列过程。",
|
|
16
|
+
description: "任务结束时调用一次,提交结果。",
|
|
18
17
|
promptSnippet: "任务结束必须调用 report 提交结果。",
|
|
19
18
|
parameters: Type.Object({
|
|
20
19
|
outcome: Type.Unsafe<"done" | "stuck" | "need_approval">(
|
|
21
|
-
Type.String({ description: "done
|
|
20
|
+
Type.String({ description: "done 做完 / stuck 做不下去(写明缺什么)/ need_approval 需要用户拍板" }),
|
|
22
21
|
),
|
|
23
|
-
|
|
24
|
-
files: Type.Optional(Type.Array(Type.String(), { description: "
|
|
22
|
+
result: Type.String({ description: "完整结果:结论、数据、链接、文件路径;写给派活的助手。" }),
|
|
23
|
+
files: Type.Optional(Type.Array(Type.String(), { description: "要给用户看的文件(绝对路径)" })),
|
|
25
24
|
}),
|
|
26
25
|
execute: async (_id, params: any) => {
|
|
27
26
|
// 不认识的结果不能当成已完成:退回让模型重新调用
|
|
@@ -34,7 +33,7 @@ export function registerReportTool(runtime: HobRuntime, jobId: string): ToolDefi
|
|
|
34
33
|
}
|
|
35
34
|
runtime.jobs.reportFromTool(jobId, {
|
|
36
35
|
outcome: params.outcome,
|
|
37
|
-
|
|
36
|
+
result: String(params.result ?? ""),
|
|
38
37
|
...(Array.isArray(params.files) && params.files.length ? { files: params.files as string[] } : {}),
|
|
39
38
|
});
|
|
40
39
|
return ok("结果已记录。");
|
|
@@ -47,7 +46,7 @@ export function registerAskTool(runtime: HobRuntime, jobId: string): ToolDefinit
|
|
|
47
46
|
return {
|
|
48
47
|
name: "ask_user",
|
|
49
48
|
label: "询问用户",
|
|
50
|
-
description: "
|
|
49
|
+
description: "在对话里出选择题卡片(2 到 3 个选项),等用户选,返回用户的选择。用于用户定的规则要求先确认的事,或发送、删除、付费这类撤不回的事。",
|
|
51
50
|
promptSnippet: "规则要求执行前确认的事,执行前必须调用 ask_user。",
|
|
52
51
|
parameters: Type.Object({
|
|
53
52
|
title: Type.String({ description: "问题,一句话" }),
|
|
@@ -55,27 +54,30 @@ export function registerAskTool(runtime: HobRuntime, jobId: string): ToolDefinit
|
|
|
55
54
|
options: Type.Array(
|
|
56
55
|
Type.Object({
|
|
57
56
|
label: Type.String({ description: "选项文字" }),
|
|
58
|
-
always: Type.Optional(Type.Boolean({ description: "选中后将这句话记为规则" })),
|
|
59
57
|
}),
|
|
60
58
|
{ description: "2 到 3 个选项", minItems: 2, maxItems: 3 },
|
|
61
59
|
),
|
|
62
|
-
|
|
60
|
+
remember: Type.Optional(Type.String({ description: "同类事以后可以不再问时,填这类事的简称(如「清理临时目录」);卡片会多一个「以后直接做」的选项。" })),
|
|
63
61
|
}),
|
|
64
62
|
execute: async (_id, params: any) => {
|
|
65
|
-
const labels: { label: string
|
|
63
|
+
const labels: { label: string }[] = Array.isArray(params.options) ? params.options : [];
|
|
66
64
|
if (labels.length < 2 || labels.length > 3) {
|
|
67
65
|
return { content: [{ type: "text" as const, text: "选项要 2 到 3 个。" }], details: undefined, isError: true };
|
|
68
66
|
}
|
|
67
|
+
const remember = typeof params.remember === "string" ? params.remember.trim() : "";
|
|
68
|
+
if (remember && labels.length > 2) {
|
|
69
|
+
return { content: [{ type: "text" as const, text: "带 remember 时最多给 2 个选项。" }], details: undefined, isError: true };
|
|
70
|
+
}
|
|
69
71
|
const options: ApprovalOption[] = labels.map((option, index) => ({
|
|
70
72
|
key: (["A", "B", "C"] as const)[index],
|
|
71
73
|
label: String(option.label),
|
|
72
|
-
...(option.always ? { always: true } : {}),
|
|
73
74
|
}));
|
|
75
|
+
if (remember) options.push({ key: "C", label: `以后「${remember}」直接做`, always: true });
|
|
74
76
|
const answer = await runtime.jobs.askQuestion(jobId, {
|
|
75
77
|
title: String(params.title),
|
|
76
78
|
...(params.detail ? { detail: String(params.detail) } : {}),
|
|
77
79
|
options,
|
|
78
|
-
...(
|
|
80
|
+
...(remember ? { ruleText: remember } : {}),
|
|
79
81
|
});
|
|
80
82
|
// 用户可能没选给出的选项,而是自己写了一段:answer 已经是回给模型的整句
|
|
81
83
|
return ok(answer);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// 知识库的数据契约:服务端(server/src/knowledge.ts、http.ts)和页面共用。设计见 .scratch/v2/design-knowledge.md。
|
|
2
|
+
|
|
3
|
+
import type { FileKind } from "./types.ts";
|
|
4
|
+
|
|
5
|
+
/** 怎么进来的:助手产出、对话上传(这两类自动收,不建索引),或在知识库里上传。 */
|
|
6
|
+
export type KnowledgeOrigin = "output" | "chat" | "upload";
|
|
7
|
+
|
|
8
|
+
/** 页面上的类型筛选;一条只属于一组,由 FileKind 决定(见 knowledgeGroup)。 */
|
|
9
|
+
export type KnowledgeGroup = "image" | "document" | "table" | "page" | "other";
|
|
10
|
+
|
|
11
|
+
export interface KnowledgeVersion {
|
|
12
|
+
hash: string;
|
|
13
|
+
path: string;
|
|
14
|
+
size: number;
|
|
15
|
+
at: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface KnowledgeEntry {
|
|
19
|
+
id: string; // k-xxxx
|
|
20
|
+
origin: KnowledgeOrigin;
|
|
21
|
+
name: string; // 显示名:原文件名或页面标题,用户可改
|
|
22
|
+
description?: string; // 页面摘要、生图提示词或任务标题
|
|
23
|
+
kind: FileKind;
|
|
24
|
+
path: string; // 当前版本在知识库目录里的绝对路径,经 /api/files 取
|
|
25
|
+
hash: string;
|
|
26
|
+
size: number;
|
|
27
|
+
createdAt: string;
|
|
28
|
+
updatedAt: string; // 只跟内容走(新版本时更新),改名和整理不动;列表按它排序
|
|
29
|
+
versions?: KnowledgeVersion[]; // 旧版本,旧的在前
|
|
30
|
+
source?: { assistantId: string; itemId?: string; jobId?: string }; // 出处
|
|
31
|
+
collections: string[]; // 所在的用户知识库 id
|
|
32
|
+
index?: KnowledgeIndex; // 二期:只在条目属于至少一个用户知识库时有
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface KnowledgeCollection {
|
|
36
|
+
id: string; // kc-xxxx
|
|
37
|
+
name: string;
|
|
38
|
+
createdAt: string;
|
|
39
|
+
count: number; // 里面有几条
|
|
40
|
+
indexed?: number; // 二期:索引已建好的条数
|
|
41
|
+
failed?: number; // 二期:建索引失败的条数
|
|
42
|
+
unsupported?: number; // 二期:这类文件还不能建索引的条数(不算进「已建索引 a/b」的 b)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 二期:用户知识库里文件的索引状态。queued/indexing/failed 的文件查不到;unsupported = 这类文件还不能建索引。 */
|
|
46
|
+
export type KnowledgeIndexState = "queued" | "indexing" | "ready" | "failed" | "unsupported";
|
|
47
|
+
|
|
48
|
+
export interface KnowledgeIndex {
|
|
49
|
+
state: KnowledgeIndexState;
|
|
50
|
+
chunks?: number; // ready 时有几段
|
|
51
|
+
error?: string; // failed / unsupported 的原因,直接给用户看
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 一段原文。出处链接 #k/<条目 id>/<index> 指到这里。 */
|
|
55
|
+
export interface KnowledgeChunk {
|
|
56
|
+
index: number; // 在这个文件里是第几段,从 0 起
|
|
57
|
+
heading: string; // 标题路径:「文件名 › 一级 › 二级」
|
|
58
|
+
text: string;
|
|
59
|
+
page?: number; // PDF 的页码,从 1 起
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** GET /api/knowledge/entries/:id[?chunk=N] 的返回。 */
|
|
63
|
+
export interface KnowledgeEntryView {
|
|
64
|
+
entry: KnowledgeEntry;
|
|
65
|
+
chunk?: KnowledgeChunk; // 带了 chunk 且这段存在时才有
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** 出处链接:工具和自动检索给出,助手照抄进回答,界面点开定位。 */
|
|
69
|
+
export function knowledgeLink(entryId: string, chunk: number): string {
|
|
70
|
+
return `#k/${entryId}/${chunk}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function parseKnowledgeLink(href: string): { entryId: string; chunk?: number } | null {
|
|
74
|
+
const match = /^#k\/(k-[\w-]+)(?:\/(\d+))?$/.exec(href.trim());
|
|
75
|
+
return match ? { entryId: match[1]!, ...(match[2] !== undefined ? { chunk: Number(match[2]) } : {}) } : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** GET /api/knowledge 的返回。 */
|
|
79
|
+
export interface KnowledgePage {
|
|
80
|
+
entries: (KnowledgeEntry & { snippet?: string })[]; // snippet:关键词命中文字时的前后片段
|
|
81
|
+
hasMore: boolean;
|
|
82
|
+
collections: KnowledgeCollection[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** 类型筛选的分组。 */
|
|
86
|
+
export function knowledgeGroup(kind: FileKind): KnowledgeGroup {
|
|
87
|
+
if (kind === "image" || kind === "svg") return "image";
|
|
88
|
+
if (kind === "csv" || kind === "json") return "table";
|
|
89
|
+
if (kind === "html") return "page";
|
|
90
|
+
if (kind === "markdown" || kind === "text" || kind === "pdf" || kind === "code" || kind === "xml") return "document";
|
|
91
|
+
return "other";
|
|
92
|
+
}
|
package/shared/mcp.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// MCP(Model Context Protocol)服务的契约:设置 → MCP 页和 REST 接口共用。
|
|
2
|
+
// 配置文件就是引擎读的那份 HOB_HOME/engine/mcp.json;说明见 docs/architecture.md 的 MCP 一节。
|
|
3
|
+
|
|
4
|
+
/** 服务里每个工具怎么给模型:direct = 直接提供;deferred = 按需搜索。 */
|
|
5
|
+
export type McpExposureChoice = "direct" | "deferred";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 环境变量 / 请求头的值:密钥永不回传。redacted = 只知道「已设置」;
|
|
9
|
+
* 整值 `${VAR}` 引用或 `!command` 不含密钥,原样回显。
|
|
10
|
+
*/
|
|
11
|
+
export interface McpSecretValue {
|
|
12
|
+
redacted: boolean;
|
|
13
|
+
value?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** GET /api/mcp:列表里的一项(不含密钥明文)。valid = false 表示手改坏了,只可编辑或删除。 */
|
|
17
|
+
export interface McpServerView {
|
|
18
|
+
name: string;
|
|
19
|
+
valid: boolean;
|
|
20
|
+
invalidReason?: string;
|
|
21
|
+
kind?: "stdio" | "http";
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
exposure: McpExposureChoice;
|
|
24
|
+
description?: string;
|
|
25
|
+
/** 本地命令(stdio) */
|
|
26
|
+
command?: string;
|
|
27
|
+
args?: string[];
|
|
28
|
+
env?: Record<string, McpSecretValue>;
|
|
29
|
+
cwd?: string;
|
|
30
|
+
/** 远程地址(streamable HTTP) */
|
|
31
|
+
url?: string;
|
|
32
|
+
headers?: Record<string, McpSecretValue>;
|
|
33
|
+
/** 单独关掉的工具名(toolExposure 里设为 hidden 的) */
|
|
34
|
+
hiddenTools?: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** GET /api/mcp。 */
|
|
38
|
+
export interface McpView {
|
|
39
|
+
servers: McpServerView[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** POST /api/mcp/:name/test(失败也是 200,原因在这里)。 */
|
|
43
|
+
export type McpTestResult =
|
|
44
|
+
| { ok: true; tools: { name: string; description: string }[] }
|
|
45
|
+
| { ok: false; state: "failed" | "needs_auth"; error: string; stderrTail: string[] };
|
|
46
|
+
|
|
47
|
+
/** 环境变量 / 请求头逐键的改法:保留已存的值 / 删除 / 换成新值。 */
|
|
48
|
+
export type McpSecretMutation = { mode: "preserve" } | { mode: "remove" } | { mode: "replace"; value: string };
|
|
49
|
+
|
|
50
|
+
/** PUT /api/mcp/:name:新建或改一个服务。提供方式、启用状态不在这里改。 */
|
|
51
|
+
export interface McpServerSave {
|
|
52
|
+
kind: "stdio" | "http";
|
|
53
|
+
command?: string;
|
|
54
|
+
args?: string[];
|
|
55
|
+
cwd?: string;
|
|
56
|
+
url?: string;
|
|
57
|
+
description?: string;
|
|
58
|
+
env?: Record<string, McpSecretMutation>;
|
|
59
|
+
headers?: Record<string, McpSecretMutation>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** PATCH /api/mcp/:name:启用开关、提供方式、单独关掉的工具(完整列表,全量替换)。 */
|
|
63
|
+
export interface McpServerPatch {
|
|
64
|
+
enabled?: boolean;
|
|
65
|
+
exposure?: McpExposureChoice;
|
|
66
|
+
hiddenTools?: string[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** GET /api/mcp/import:本机其他客户端里配的 MCP 服务(只读来源文件)。 */
|
|
70
|
+
export interface McpImportItem {
|
|
71
|
+
/** 勾选时回传的标识:来源:服务名 */
|
|
72
|
+
id: string;
|
|
73
|
+
name: string;
|
|
74
|
+
source: "pi" | "claude" | "cursor";
|
|
75
|
+
kind?: "stdio" | "http";
|
|
76
|
+
exists: boolean; // Hob 里已有同名(- 和 _ 视为同名),导入会跳过
|
|
77
|
+
selectable: boolean;
|
|
78
|
+
note?: string; // 不可选的原因
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface McpImportView {
|
|
82
|
+
/** 读了哪些位置(界面显示用) */
|
|
83
|
+
sources: string[];
|
|
84
|
+
items: McpImportItem[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** POST /api/mcp/import {ids}。 */
|
|
88
|
+
export interface McpImportResult {
|
|
89
|
+
imported: string[];
|
|
90
|
+
skipped: string[]; // 跳过的,带说明
|
|
91
|
+
}
|
package/shared/skills.ts
CHANGED
|
@@ -35,3 +35,21 @@ export interface SkillImportResult {
|
|
|
35
35
|
skipped: string[];
|
|
36
36
|
errors: string[];
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 助手自己沉淀的技能:HOB_HOME/assistants/<id>/skills/<name>/SKILL.md(加 scripts/、references/),
|
|
41
|
+
* 元数据在 assistants/<id>/skills/skills.json。只有这个助手的任务加载。
|
|
42
|
+
*/
|
|
43
|
+
export interface AssistantSkill {
|
|
44
|
+
name: string; // 目录名:小写字母、数字、连字符
|
|
45
|
+
title: string; // 界面上显示的名字,如「网关每日巡检」
|
|
46
|
+
description: string; // 一句话:什么时候用
|
|
47
|
+
path: string; // SKILL.md 的绝对路径(预览用)
|
|
48
|
+
version: number; // 每次修补 +1,旧版在 skills/<name>/.history/<版本>/
|
|
49
|
+
createdAt: string;
|
|
50
|
+
updatedAt: string;
|
|
51
|
+
fromJobId?: string; // 最近一次从哪个任务沉淀或修补
|
|
52
|
+
uses: number; // 被任务读过几次
|
|
53
|
+
lastUsedAt?: string;
|
|
54
|
+
enabled: boolean;
|
|
55
|
+
}
|
package/shared/types.ts
CHANGED
|
@@ -126,6 +126,28 @@ export interface ModelThinkingView {
|
|
|
126
126
|
custom: boolean; // 存过自定义档位(可以恢复默认)
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
export interface ClaudeCodeSettings {
|
|
130
|
+
enabled: boolean;
|
|
131
|
+
plan: "pro" | "max";
|
|
132
|
+
executable: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface ClaudeCodeView {
|
|
136
|
+
settings: SettingsFile;
|
|
137
|
+
status: ClaudeCodeStatus;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** 本机 Claude Code 状态,不包含凭据或程序原始输出。 */
|
|
141
|
+
export interface ClaudeCodeStatus {
|
|
142
|
+
found: boolean;
|
|
143
|
+
executable?: string;
|
|
144
|
+
version?: string;
|
|
145
|
+
loggedIn: boolean;
|
|
146
|
+
email?: string;
|
|
147
|
+
plan?: string;
|
|
148
|
+
error?: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
129
151
|
/** GET /api/providers:已配的服务商、引擎自带的服务商目录、跟着一起回的整份设置(前端要新 revision)。 */
|
|
130
152
|
export interface ProvidersView {
|
|
131
153
|
providers: ProviderView[];
|
|
@@ -214,6 +236,8 @@ export interface Assistant {
|
|
|
214
236
|
};
|
|
215
237
|
/** 派出去的活能用的能力;缺省全开(读总是有) */
|
|
216
238
|
abilities?: { exec: boolean; write: boolean; image: boolean };
|
|
239
|
+
/** 知识库二期:绑定的用户知识库 id。缺省 = 没绑定:不自动查,也没有 search_knowledge */
|
|
240
|
+
knowledge?: string[];
|
|
217
241
|
createdAt: Iso;
|
|
218
242
|
splitFrom?: { from: string; at: Iso; conversations: number };
|
|
219
243
|
}
|
|
@@ -245,6 +269,9 @@ export interface RoutineRun {
|
|
|
245
269
|
jobId: string;
|
|
246
270
|
at: Iso;
|
|
247
271
|
status: JobStatus;
|
|
272
|
+
durationMs?: number; // 技能二期:这次用时
|
|
273
|
+
tools?: number; // 技能二期:工具调用次数
|
|
274
|
+
tokens?: number; // 技能二期:输入加输出 token
|
|
248
275
|
}
|
|
249
276
|
|
|
250
277
|
export interface Routine {
|
|
@@ -256,6 +283,7 @@ export interface Routine {
|
|
|
256
283
|
active: boolean;
|
|
257
284
|
createdAt: Iso;
|
|
258
285
|
runs: RoutineRun[]; // 最近 20 次,新的在前
|
|
286
|
+
skill?: string; // 绑定这个助手自己的技能(AssistantSkill.name):每次执行按它做
|
|
259
287
|
}
|
|
260
288
|
|
|
261
289
|
export interface Delegation {
|
|
@@ -264,7 +292,7 @@ export interface Delegation {
|
|
|
264
292
|
to: string;
|
|
265
293
|
text: string;
|
|
266
294
|
createdAt: Iso;
|
|
267
|
-
status: "running" | "waiting" | "done" | "stuck" | "interrupted" | "returned";
|
|
295
|
+
status: "running" | "waiting" | "done" | "stuck" | "interrupted" | "returned" | "takenOver";
|
|
268
296
|
jobIds: string[];
|
|
269
297
|
reply?: string;
|
|
270
298
|
returnedText?: string; // 退回时补充的用户要求
|
|
@@ -342,7 +370,7 @@ export interface JobsPage {
|
|
|
342
370
|
export interface Job {
|
|
343
371
|
id: string; // "t-" + 4 位以上
|
|
344
372
|
assistantId: string;
|
|
345
|
-
assistantName?: string; //
|
|
373
|
+
assistantName?: string; // 建任务时的助手名:助手归档后,任务详情里照样显示
|
|
346
374
|
title: string; // 如 "查页面卡顿"
|
|
347
375
|
instruction: string;
|
|
348
376
|
status: JobStatus;
|
|
@@ -352,8 +380,9 @@ export interface Job {
|
|
|
352
380
|
endedAt?: Iso;
|
|
353
381
|
usage?: JobUsage; // 输入/输出 token 和花费
|
|
354
382
|
durationMs?: number; // 用时(结束时间 − 开始时间)
|
|
383
|
+
expect?: { minutes: number; source: "assistant" | "routine" | "default"; runs?: number };
|
|
355
384
|
stats: { reads: number; commands: number; edits: number };
|
|
356
|
-
summary?: string; // report
|
|
385
|
+
summary?: string; // report 的完整结果
|
|
357
386
|
reportPath?: string; // 完整报告
|
|
358
387
|
files?: string[]; // 产出文件(绝对路径)
|
|
359
388
|
cwd?: string; // 工作目录(缺省为 HOB_HOME/jobs/<id>/work)
|
|
@@ -362,10 +391,26 @@ export interface Job {
|
|
|
362
391
|
modelReason?: string; // 换了模型的原因,如「需要看图,换成了 X」
|
|
363
392
|
inputs?: string[]; // 交给任务的附件(绝对路径)
|
|
364
393
|
routineId?: string; // 由定时触发
|
|
394
|
+
skillsUsed?: string[]; // 这次读过的本助手技能(AssistantSkill.name)
|
|
365
395
|
delegationId?: string; // 转交回合创建的任务
|
|
396
|
+
/** 续做前每一轮结束时的样子(第 0 轮起)。对话里前面几张任务卡照它显示,最后一张跟着任务实时变 */
|
|
397
|
+
rounds?: JobRound[];
|
|
366
398
|
activity: ActivityLine[]; // 最近 200 行
|
|
367
399
|
}
|
|
368
400
|
|
|
401
|
+
export interface JobRound {
|
|
402
|
+
summary?: string;
|
|
403
|
+
files?: string[];
|
|
404
|
+
usage?: JobUsage;
|
|
405
|
+
reportPath?: string;
|
|
406
|
+
followUp?: string;
|
|
407
|
+
status: JobStatus;
|
|
408
|
+
statusReason?: string;
|
|
409
|
+
startedAt?: Iso;
|
|
410
|
+
endedAt?: Iso;
|
|
411
|
+
durationMs?: number;
|
|
412
|
+
}
|
|
413
|
+
|
|
369
414
|
export interface ApprovalOption {
|
|
370
415
|
key: "A" | "B" | "C";
|
|
371
416
|
label: string; // 如 "以后同类内容直接发布,无需确认"
|
|
@@ -408,16 +453,17 @@ export interface Draft {
|
|
|
408
453
|
createdAt: Iso;
|
|
409
454
|
}
|
|
410
455
|
|
|
411
|
-
export type NoteIcon = "memory" | "rule" | "routine" | "split" | "info";
|
|
456
|
+
export type NoteIcon = "memory" | "rule" | "routine" | "split" | "info" | "skill";
|
|
412
457
|
|
|
413
458
|
export type Item = (
|
|
414
459
|
| { kind: "user"; id: string; at: Iso; text: string; quote?: { itemId: string; text: string }; files?: string[]; from?: { assistantId: string; name: string; delegationId: string } }
|
|
415
|
-
| { kind: "assistant"; id: string; at: Iso; text: string; streaming?: boolean; routine?: { id: string; name: string } }
|
|
416
|
-
| { kind: "note"; id: string; at: Iso; icon: NoteIcon; text: string; handbackId?: string } // 居中灰色一行,如 "记忆已更新 · 不看融资新闻"
|
|
460
|
+
| { kind: "assistant"; id: string; at: Iso; text: string; streaming?: boolean; routine?: { id: string; name: string; jobId?: string } }
|
|
461
|
+
| { kind: "note"; id: string; at: Iso; icon: NoteIcon; text: string; handbackId?: string; path?: string } // path:点了预览这个文件(如技能的 SKILL.md) // 居中灰色一行,如 "记忆已更新 · 不看融资新闻"
|
|
417
462
|
| { kind: "question"; id: string; at: Iso; approvalId: string }
|
|
418
|
-
| { kind: "job"; id: string; at: Iso; jobId: string }
|
|
463
|
+
| { kind: "job"; id: string; at: Iso; jobId: string; round?: number } // 第几轮(续做一次 +1),缺省是第 0 轮
|
|
419
464
|
| { kind: "delegation"; id: string; at: Iso; delegationId: string }
|
|
420
465
|
| { kind: "draft"; id: string; at: Iso; draftId: string }
|
|
466
|
+
| { kind: "image"; id: string; at: Iso; prompt: string; size?: string; startedAt: Iso; status: "pending" | "done" | "failed"; path?: string; error?: string }
|
|
421
467
|
| { kind: "file"; id: string; at: Iso; path: string; name: string; meta?: string } // meta 如 "3200 字 · 4 张图"
|
|
422
468
|
) & {
|
|
423
469
|
/** 这条开启新的一组,前端在它上面拉开间距:事件触发的新一轮回复,或任务直接发进对话的图片、提问卡 */
|
|
@@ -468,6 +514,7 @@ export interface MemoryConfig {
|
|
|
468
514
|
provider: MemoryProvider;
|
|
469
515
|
providerExplicit: boolean; // 用户在设置里明确选过(没选过就是按有没有配好 Lore 推出的)
|
|
470
516
|
lore: LoreConnectionInfo;
|
|
517
|
+
lorePlugin: string; // Hob 自带的 Lore 插件版本(Lore 正式版的版本号)
|
|
471
518
|
}
|
|
472
519
|
|
|
473
520
|
/** POST /api/memory/lore-test:测试失败也是 200,原因在这里。 */
|
|
@@ -508,13 +555,47 @@ export interface Settings {
|
|
|
508
555
|
models: ModelSettings;
|
|
509
556
|
image: ImageSettings;
|
|
510
557
|
search: SearchSettings;
|
|
558
|
+
/** 云桌面令牌保存在 secrets.json,不包含在设置中。 */
|
|
559
|
+
desktop?: { url: string; enabled: boolean; publicAssistants?: string[] };
|
|
560
|
+
claude?: ClaudeCodeSettings;
|
|
511
561
|
/** 停用的技能,键为来源和技能名。 */
|
|
512
562
|
skills?: { disabled: string[] };
|
|
513
563
|
work: WorkSettings;
|
|
514
564
|
push: PushSettings;
|
|
565
|
+
/** 知识库二期:向量模型(Key 在 secrets.json 的 embedding);缺省 = 关闭,只按关键词查 */
|
|
566
|
+
knowledge?: { embedding: EmbeddingSettings };
|
|
515
567
|
}
|
|
516
568
|
|
|
569
|
+
/** 设置 → 知识库:向量模型,照图像生成的做法(兼容接口的地址 + 模型,Key 单独存)。 */
|
|
570
|
+
export type EmbeddingSettings = { source: "off" } | { source: "openai"; baseUrl: string; model: string };
|
|
571
|
+
|
|
572
|
+
/** GET /api/knowledge/embedding:Key 配没配(永不回显)。 */
|
|
573
|
+
export interface EmbeddingKeyView {
|
|
574
|
+
keyConfigured: boolean;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/** PUT /api/knowledge/embedding 的返回:整份设置和 Key 状态。 */
|
|
578
|
+
export interface EmbeddingSettingsSaved {
|
|
579
|
+
settings: SettingsFile;
|
|
580
|
+
keyConfigured: boolean;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/** POST /api/knowledge/embedding/test:失败也是 200,原因在这里。 */
|
|
584
|
+
export type EmbeddingTestResult = { ok: true; dims: number } | { ok: false; error: string };
|
|
585
|
+
|
|
517
586
|
/** HOB_HOME/settings.json:界面可改的设置,写入走 revision 比对(CAS)。 */
|
|
587
|
+
export interface DesktopView {
|
|
588
|
+
revision: number;
|
|
589
|
+
url: string;
|
|
590
|
+
enabled: boolean;
|
|
591
|
+
deployed: boolean;
|
|
592
|
+
tokenConfigured: boolean;
|
|
593
|
+
publicAssistants: string[];
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
export interface DesktopScreen { id: string; running: boolean; browserRunning: boolean; viewers: number; lastActiveAt: string | null }
|
|
597
|
+
export type DesktopTestResult = { ok: true; health: { version: string; display: { width: number; height: number }; screens: DesktopScreen[] } } | { ok: false; error: string };
|
|
598
|
+
|
|
518
599
|
export interface SettingsFile {
|
|
519
600
|
revision: number;
|
|
520
601
|
settings: Settings;
|
|
@@ -583,20 +664,18 @@ export interface FileMeta {
|
|
|
583
664
|
page?: boolean; // 展示页面目录中的渲染产物
|
|
584
665
|
}
|
|
585
666
|
|
|
586
|
-
/**
|
|
587
|
-
export
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
size: number;
|
|
592
|
-
modifiedAt: Iso;
|
|
593
|
-
kind: FileKind;
|
|
594
|
-
}
|
|
667
|
+
/** 工作目录中的一项,目录的 count 是其下一层可见项数。 */
|
|
668
|
+
export type JobFileEntry = { path: string; rel: string; name: string } & (
|
|
669
|
+
| { type: "file"; size: number; modifiedAt: Iso; kind: FileKind }
|
|
670
|
+
| { type: "directory"; count: number }
|
|
671
|
+
);
|
|
595
672
|
|
|
596
|
-
/**
|
|
673
|
+
/** 一层最多返回 200 项,total 为截断前的总数。 */
|
|
597
674
|
export interface JobFilesView {
|
|
598
|
-
files: JobFileEntry[];
|
|
599
|
-
|
|
675
|
+
files: JobFileEntry[];
|
|
676
|
+
total: number;
|
|
677
|
+
fileCount: number;
|
|
678
|
+
directoryCount: number;
|
|
600
679
|
}
|
|
601
680
|
|
|
602
681
|
export interface Bootstrap {
|
|
@@ -609,12 +688,15 @@ export interface Bootstrap {
|
|
|
609
688
|
}
|
|
610
689
|
|
|
611
690
|
export type HobEvent =
|
|
691
|
+
| { type: "desktop-control"; screen: string; taken: boolean }
|
|
692
|
+
| { type: "desktop-screens"; screens: DesktopScreen[] }
|
|
612
693
|
| ({ type: "page-live" } & PageLive)
|
|
613
694
|
| { type: "assistant"; assistant: AssistantSummary } // 新建或变化
|
|
614
695
|
| { type: "assistant_removed"; id: string }
|
|
615
696
|
| { type: "item"; assistantId: string; item: Item } // 新条目,或按 id 替换
|
|
616
697
|
| { type: "delta"; assistantId: string; itemId: string; text: string } // 流式追加到 assistant 条目
|
|
617
698
|
| { type: "job"; job: Job }
|
|
699
|
+
| { type: "sediment-finished"; jobId: string }
|
|
618
700
|
| { type: "delegation"; delegation: Delegation }
|
|
619
701
|
| { type: "job_step"; jobId: string; step: JobStep } // 新的一步,或按 id 替换(输出流式更新、跑完补结果)
|
|
620
702
|
| { type: "router-activity"; assistantId: string; activity: RouterActivity | null }
|
|
@@ -623,7 +705,9 @@ export type HobEvent =
|
|
|
623
705
|
| { type: "draft"; draft: Draft }
|
|
624
706
|
| { type: "routine"; routine: Routine }
|
|
625
707
|
| { type: "routine_removed"; id: string }
|
|
626
|
-
| { type: "profile"; assistantId: string }
|
|
708
|
+
| { type: "profile"; assistantId: string } // rules/memories/worklog 变了,前端重拉 profile
|
|
709
|
+
| { type: "knowledge" } // 知识库有变化,打开着的知识库页面重拉
|
|
710
|
+
| { type: "skills"; assistantId: string }; // 这个助手的技能有变化,打开着的技能列表重拉
|
|
627
711
|
|
|
628
712
|
/** POST /api/assistants/:id/messages 的请求体 */
|
|
629
713
|
export interface SendMessageBody {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Hob 补丁
|
|
2
|
+
|
|
3
|
+
来源:`pi-claude-bridge@0.9.3`,MIT,原许可证见 `LICENSE`。
|
|
4
|
+
|
|
5
|
+
- 只读取 Hob 引擎目录的 `claude-bridge.json`,不合并任务工作目录的配置。
|
|
6
|
+
- Claude Code 普通请求和摘要请求都传 `--safe-mode`;摘要请求不再传空的 `settingSources`,因为当前 Claude Code 登录在该参数下不可用。
|
|
7
|
+
- 不通过全局环境设置安全模式,避免影响用户在任务中启动的 Claude Code。
|
|
8
|
+
- Hob 在模型目录初始化时预注册模型,所有会话复用同一个扩展模块;生命周期清理仅影响当前会话,避免并行对话和任务互相清除状态。
|
|
9
|
+
- 不修改 Hob 进程的 Claude Code 环境变量;启用桥调试日志时记录实际追加的系统提示词,供隔离验收。
|
|
10
|
+
- 移除桥直接读取、重建和删除 Claude Code 会话文件的路径。无法直接续接时,以 SDK 消息重放 Hob 自己的历史(包含图片);正常续接仍由 Claude Code 处理。这可能增加重启或上下文改写后的 token 用量。
|