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/memory.ts
CHANGED
|
@@ -228,6 +228,11 @@ export async function testLoreConnection(
|
|
|
228
228
|
return { ok: true, baseUrl: rawBase };
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
/** Hob 自带的 Lore 插件版本:vendor 里放的是 Lore 正式版,换版本用 `npm run update-lore -- <版本号>`。 */
|
|
232
|
+
export const LORE_PLUGIN_VERSION: string = JSON.parse(
|
|
233
|
+
readFileSync(new URL("../../vendor/lore-pi-extension/package.json", import.meta.url), "utf8"),
|
|
234
|
+
).version;
|
|
235
|
+
|
|
231
236
|
/** 提示词里「记忆」一节的文案:按当前提供者给,关闭时整节不出。 */
|
|
232
237
|
export function memoryPromptLines(provider: MemoryProvider): string[] {
|
|
233
238
|
if (provider === "local") {
|
|
@@ -239,7 +244,7 @@ export function memoryPromptLines(provider: MemoryProvider): string[] {
|
|
|
239
244
|
if (provider === "lore") {
|
|
240
245
|
return [
|
|
241
246
|
"## 记忆",
|
|
242
|
-
"
|
|
247
|
+
"长期记忆在 Lore:过去的偏好、决定和约定先查 Lore;值得长期记住的新事实记进 Lore。读到的记忆和你刚核实的事实对不上(过时或有错),当场改对;整条已经不成立或没用了,就删掉。",
|
|
243
248
|
];
|
|
244
249
|
}
|
|
245
250
|
return [];
|
|
@@ -254,11 +259,11 @@ export const MEMORY_TOOL_PATTERNS: Record<MemoryProvider, string[]> = {
|
|
|
254
259
|
|
|
255
260
|
/**
|
|
256
261
|
* 会写记忆的工具:Router 调成功后在对话里出一行「记忆已更新 · …」。
|
|
257
|
-
* 名字取自捆绑插件的实际注册(pi-memory 0.4.2 的 memory_write,Lore
|
|
262
|
+
* 名字取自捆绑插件的实际注册(pi-memory 0.4.2 的 memory_write,Lore 插件的建/改节点)。
|
|
258
263
|
*/
|
|
259
264
|
export const MEMORY_WRITE_TOOLS = new Set(["memory_write", "lore_create_node", "lore_update_node"]);
|
|
260
265
|
|
|
261
|
-
/** note 里的摘要:写入内容的第一行,最多
|
|
266
|
+
/** note 里的摘要:写入内容的第一行,最多 80 个字符。 */
|
|
262
267
|
export function memoryNoteText(params: unknown): string | undefined {
|
|
263
268
|
const record = isRecord(params) ? params : {};
|
|
264
269
|
for (const key of ["content", "text", "title", "new_text", "old_text"]) {
|
|
@@ -266,7 +271,9 @@ export function memoryNoteText(params: unknown): string | undefined {
|
|
|
266
271
|
if (typeof value !== "string") continue;
|
|
267
272
|
const first = value.split("\n").map((line) => line.trim()).find((line) => line !== "");
|
|
268
273
|
if (!first) continue;
|
|
269
|
-
|
|
274
|
+
const chars = Array.from(first.replace(/[::,,;;、]+\s*$/u, ""));
|
|
275
|
+
if (!chars.length) continue;
|
|
276
|
+
return chars.length > 80 ? `${chars.slice(0, 80).join("").replace(/[::,,;;、]+\s*$/u, "")}…` : chars.join("");
|
|
270
277
|
}
|
|
271
278
|
return undefined;
|
|
272
279
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { HobRuntime } from "./runtime.ts";
|
|
2
|
+
import { nowText } from "./time.ts";
|
|
3
|
+
|
|
4
|
+
/** 每条 Router 消息附带的当前状态,不显示在对话里。 */
|
|
5
|
+
export function nowBlock(runtime: HobRuntime, assistantId: string): string {
|
|
6
|
+
const now = new Date();
|
|
7
|
+
const timezone = runtime.settings.settings.general.timezone;
|
|
8
|
+
const clock = (value: string) => new Intl.DateTimeFormat("sv-SE", {
|
|
9
|
+
timeZone: timezone, hour: "2-digit", minute: "2-digit", hour12: false,
|
|
10
|
+
}).format(new Date(value));
|
|
11
|
+
const short = (value: string) => Array.from(value).length > 20 ? `${Array.from(value).slice(0, 20).join("")}…` : value;
|
|
12
|
+
const jobs = runtime.store.jobs().filter((job) => job.assistantId === assistantId);
|
|
13
|
+
const lines = [`【现在】${nowText(timezone, now)}`];
|
|
14
|
+
const active = jobs.filter((job) => ["running", "queued", "waiting"].includes(job.status)).map((job) => {
|
|
15
|
+
const minutes = job.startedAt ? Math.floor((now.getTime() - new Date(job.startedAt).getTime()) / 60_000) : 0;
|
|
16
|
+
const status = job.status === "queued" ? "排队中" : job.status === "waiting" ? "等用户确认" : minutes < 1 ? "刚开始" : `运行 ${minutes} 分钟`;
|
|
17
|
+
return `${job.id}「${short(job.title)}」,${status}`;
|
|
18
|
+
});
|
|
19
|
+
if (active.length) lines.push(`进行中:${active.join(";")}`);
|
|
20
|
+
if (assistantId === "hob") {
|
|
21
|
+
const delegations = runtime.store.delegations().filter((item) => item.from === assistantId && (item.status === "running" || item.status === "waiting"))
|
|
22
|
+
.map((item) => `${runtime.store.assistant(item.to)?.name ?? item.to}「${short(item.text)}」,${clock(item.createdAt)} 起`);
|
|
23
|
+
if (delegations.length) lines.push(`转交中:${delegations.join(";")}`);
|
|
24
|
+
}
|
|
25
|
+
const approvals = runtime.store.approvals().filter((item) => item.assistantId === assistantId && item.status === "pending").map((item) => item.title);
|
|
26
|
+
if (approvals.length) lines.push(`等用户选:${approvals.join(";")}`);
|
|
27
|
+
const names = { done: "已完成", stuck: "受阻", failed: "失败", stopped: "已停止" } as const;
|
|
28
|
+
const recent = jobs.filter((job) => job.endedAt && !job.routineId && job.status in names && new Date(job.endedAt).getTime() >= now.getTime() - 86_400_000 && new Date(job.endedAt).getTime() <= now.getTime())
|
|
29
|
+
.sort((a, b) => b.endedAt!.localeCompare(a.endedAt!)).slice(0, 5)
|
|
30
|
+
.map((job) => `${job.id}「${short(job.title)}」${clock(job.endedAt!)} ${names[job.status as keyof typeof names]}`);
|
|
31
|
+
if (recent.length) lines.push(`最近结束:${recent.join(";")}`);
|
|
32
|
+
return lines.join("\n");
|
|
33
|
+
}
|
package/server/src/overview.ts
CHANGED
|
@@ -100,7 +100,7 @@ export function overviewTool(runtime: HobRuntime): ToolDefinition<any, any> {
|
|
|
100
100
|
name: "overview",
|
|
101
101
|
label: "查看这段时间的记录",
|
|
102
102
|
description:
|
|
103
|
-
"
|
|
103
|
+
"查各助手一段时间内的日志和任务,用于早报、日报,或回答「这段时间做了什么」。",
|
|
104
104
|
promptSnippet: "写早报/汇总时用 overview 查看各助手这段时间的记录。",
|
|
105
105
|
parameters: Type.Object({
|
|
106
106
|
sinceHours: Type.Optional(Type.Integer({ description: "往回看多少小时,默认 24,最多 168" })),
|
package/server/src/pages.ts
CHANGED
|
@@ -45,7 +45,7 @@ export function pageTool(runtime: HobRuntime, assistantId: string, router: boole
|
|
|
45
45
|
if (!title || !summary) throw new Error("标题和摘要不能为空。");
|
|
46
46
|
const path = await renderPage(runtime.home, title, summary, String(params.html ?? ""));
|
|
47
47
|
await runtime.router.addItem(assistantId, { kind: "file", path, name: title, meta: summary });
|
|
48
|
-
return { content: [{ type: "text" as const, text: `页面已展示:${path}
|
|
48
|
+
return { content: [{ type: "text" as const, text: `页面已展示:${path}` }], details: { path }, ...(router ? { terminate: true } : {}) };
|
|
49
49
|
} catch (error) {
|
|
50
50
|
return { content: [{ type: "text" as const, text: error instanceof Error ? error.message : String(error) }], details: undefined, isError: true };
|
|
51
51
|
}
|
package/server/src/pi.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createAgentSession,
|
|
3
3
|
createExtensionRuntime,
|
|
4
|
+
createMcpExtension,
|
|
5
|
+
createToolSearchExtension,
|
|
4
6
|
DefaultResourceLoader,
|
|
5
7
|
ModelRuntime,
|
|
6
8
|
SessionManager,
|
|
@@ -9,12 +11,13 @@ import {
|
|
|
9
11
|
type ResourceLoader,
|
|
10
12
|
} from "@earendil-works/pi-coding-agent";
|
|
11
13
|
import type { Model, Provider } from "@earendil-works/pi-ai";
|
|
12
|
-
import type { AgentSession, Skill, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
13
|
-
import { mkdirSync, readFileSync } from "node:fs";
|
|
14
|
+
import type { AgentSession, ExtensionFactory, Skill, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
14
16
|
import { createRequire } from "node:module";
|
|
15
|
-
import { dirname, join } from "node:path";
|
|
17
|
+
import { dirname, join, resolve } from "node:path";
|
|
16
18
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
17
|
-
import type { MemoryProvider } from "../../shared/types.ts";
|
|
19
|
+
import type { ClaudeCodeSettings, MemoryProvider } from "../../shared/types.ts";
|
|
20
|
+
import { claudeStatus } from "./claude-status.ts";
|
|
18
21
|
import type { ThinkingLevel } from "./config.ts";
|
|
19
22
|
import { MEMORY_TOOL_PATTERNS } from "./memory.ts";
|
|
20
23
|
|
|
@@ -39,9 +42,13 @@ export interface OpenSessionOptions {
|
|
|
39
42
|
customTools: ToolDefinition<any, any>[];
|
|
40
43
|
/** 这一轮用哪个记忆提供者:决定加载哪个捆绑插件(off = 两个都不加载) */
|
|
41
44
|
memory?: MemoryProvider;
|
|
45
|
+
/** 任务会话加载 MCP 服务(引擎目录的 mcp.json);对话会话不加载。 */
|
|
46
|
+
mcp?: boolean;
|
|
42
47
|
/** 任务使用的技能;对话会话不加载。 */
|
|
43
48
|
skills?: Skill[];
|
|
44
49
|
disabledSkills?: string[];
|
|
50
|
+
/** Router 大结果落盘目录;任务不传。 */
|
|
51
|
+
spillDir?: string;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
export interface OpenedSession {
|
|
@@ -53,8 +60,8 @@ export interface OpenedSession {
|
|
|
53
60
|
* pi 的 AgentSession 在构建时就把系统提示词算进缓存,之后 ResourceLoader 现读 store 也用不上,
|
|
54
61
|
* 所以要每轮开始前调一次:重设一遍当前工具集会让它按 loader 重算提示词,下一轮生效。
|
|
55
62
|
*/
|
|
56
|
-
export function refreshSystemPrompt(session: AgentSession): void {
|
|
57
|
-
session.setActiveToolsByName(
|
|
63
|
+
export function refreshSystemPrompt(session: AgentSession, tools = session.getActiveToolNames()): void {
|
|
64
|
+
session.setActiveToolsByName(tools);
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
const requireFromHere = createRequire(import.meta.url);
|
|
@@ -100,7 +107,8 @@ function emptyLoader(systemPrompt: () => string): ResourceLoader {
|
|
|
100
107
|
}
|
|
101
108
|
|
|
102
109
|
/**
|
|
103
|
-
* 记忆插件互斥断言 +
|
|
110
|
+
* 记忆插件互斥断言 + 本地记忆的工具修剪。只看捆绑插件(additionalExtensionPaths):
|
|
111
|
+
* 内联工厂(MCP、按需搜索)不是记忆插件,直接放行。
|
|
104
112
|
*
|
|
105
113
|
* 一次只加载一家的插件:万一哪天两条路径同时挂上,这里立刻报错,而不是把用户的记忆
|
|
106
114
|
* 分裂成两份。pi-memory 没带 qmd,`memory_search` 只会报错,所以在注册前就去掉。
|
|
@@ -109,9 +117,11 @@ export function filterMemoryExtensions(
|
|
|
109
117
|
result: LoadExtensionsResult,
|
|
110
118
|
provider: MemoryProvider,
|
|
111
119
|
): LoadExtensionsResult {
|
|
112
|
-
|
|
120
|
+
const isInline = (extension: LoadExtensionsResult["extensions"][number]) => extension.path.startsWith("<inline:");
|
|
121
|
+
const bundled = result.extensions.filter((extension) => !isInline(extension));
|
|
122
|
+
if (bundled.length > 1) throw new Error("记忆插件互斥:一次只能加载一个记忆插件。");
|
|
113
123
|
const families = new Set<MemoryProvider>();
|
|
114
|
-
for (const extension of
|
|
124
|
+
for (const extension of bundled) {
|
|
115
125
|
for (const name of extension.tools?.keys() ?? []) {
|
|
116
126
|
if (PI_MEMORY_TOOLS.test(name)) families.add("local");
|
|
117
127
|
else if (name.startsWith("lore_")) families.add("lore");
|
|
@@ -122,13 +132,13 @@ export function filterMemoryExtensions(
|
|
|
122
132
|
if (provider === "off" ? loaded !== undefined : loaded !== provider) {
|
|
123
133
|
throw new Error(`记忆插件与当前记忆来源不一致:期望 ${provider},实际 ${loaded ?? "无"}。`);
|
|
124
134
|
}
|
|
125
|
-
const extensions =
|
|
135
|
+
const extensions = bundled.map((extension) => {
|
|
126
136
|
if (!extension.tools?.has("memory_search")) return extension;
|
|
127
137
|
const tools = new Map(extension.tools);
|
|
128
138
|
tools.delete("memory_search");
|
|
129
139
|
return { ...extension, tools };
|
|
130
140
|
});
|
|
131
|
-
return { ...result, extensions };
|
|
141
|
+
return { ...result, extensions: [...result.extensions.filter(isInline), ...extensions] };
|
|
132
142
|
}
|
|
133
143
|
|
|
134
144
|
/**
|
|
@@ -143,6 +153,7 @@ export class PiHost {
|
|
|
143
153
|
/** 服务商套进引擎(设 Key、注册兼容接口);引擎建好、返回之前调一次。 */
|
|
144
154
|
configure?: (runtime: ModelRuntime) => Promise<void>;
|
|
145
155
|
private runtimePromise?: Promise<ModelRuntime>;
|
|
156
|
+
private claudeFactory?: ExtensionFactory;
|
|
146
157
|
/** Lore 插件在 resources_discover 中注册的目录。 */
|
|
147
158
|
loreSkillPaths: string[] = [];
|
|
148
159
|
|
|
@@ -167,6 +178,35 @@ export class PiHost {
|
|
|
167
178
|
return this.runtimePromise;
|
|
168
179
|
}
|
|
169
180
|
|
|
181
|
+
/** 会话尚未打开时先注册桥的模型目录,之后会话复用同一扩展模块。 */
|
|
182
|
+
async configureClaude(runtime: ModelRuntime, home: string, config: ClaudeCodeSettings | undefined): Promise<void> {
|
|
183
|
+
const status = config?.enabled ? await claudeStatus(home, config.executable || undefined) : undefined;
|
|
184
|
+
if (!config?.enabled || !status?.found || !status.loggedIn || !status.executable) {
|
|
185
|
+
this.claudeFactory = undefined;
|
|
186
|
+
runtime.unregisterProvider("claude-bridge");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
mkdirSync(this.engineDir, { recursive: true });
|
|
190
|
+
writeFileSync(join(this.engineDir, "claude-bridge.json"), JSON.stringify({
|
|
191
|
+
provider: { plan: config.plan, strictMcpConfig: true, autoMemoryEnabled: false, pathToClaudeCodeExecutable: status.executable },
|
|
192
|
+
askClaude: { enabled: false },
|
|
193
|
+
}, null, 2) + "\n", { mode: 0o600 });
|
|
194
|
+
const path = new URL("../../vendor/claude-bridge/src/index.ts", import.meta.url).href;
|
|
195
|
+
const factory = (await import(path)).default as ExtensionFactory;
|
|
196
|
+
const loader = new DefaultResourceLoader({
|
|
197
|
+
cwd: home, agentDir: this.engineDir, settingsManager: SettingsManager.inMemory(),
|
|
198
|
+
noExtensions: true, noSkills: true, noPromptTemplates: true, noThemes: true, noContextFiles: true,
|
|
199
|
+
extensionFactories: [factory],
|
|
200
|
+
});
|
|
201
|
+
await loader.reload();
|
|
202
|
+
const loaded = loader.getExtensions();
|
|
203
|
+
if (loaded.errors.length) throw new Error(`Claude Code 扩展加载失败:${loaded.errors[0].error}`);
|
|
204
|
+
const registration = loaded.runtime.pendingProviderRegistrations.find((entry) => entry.name === "claude-bridge");
|
|
205
|
+
if (!registration) throw new Error("Claude Code 扩展未注册模型。");
|
|
206
|
+
runtime.registerProvider(registration.name, registration.config);
|
|
207
|
+
this.claudeFactory = factory;
|
|
208
|
+
}
|
|
209
|
+
|
|
170
210
|
/** 设置页先于任何会话打开时,也通过插件自己的发现流程取得目录。 */
|
|
171
211
|
async discoverLoreSkills(): Promise<void> {
|
|
172
212
|
const pluginDir = dirname(bundledPluginEntry(MEMORY_PACKAGES.lore));
|
|
@@ -200,7 +240,7 @@ export class PiHost {
|
|
|
200
240
|
settingsManager: SettingsManager,
|
|
201
241
|
): Promise<ResourceLoader> {
|
|
202
242
|
const provider = options.memory ?? "off";
|
|
203
|
-
const pluginPath = provider === "off" ? undefined : bundledPluginEntry(MEMORY_PACKAGES
|
|
243
|
+
const pluginPath = provider === "off" ? undefined : provider === "lore" ? fileURLToPath(new URL("./lore-entry.ts", import.meta.url)) : bundledPluginEntry(MEMORY_PACKAGES.local);
|
|
204
244
|
if (provider === "local") {
|
|
205
245
|
// 会话被频繁回收,退出总结只会白花一次模型调用(和参考产品一致)
|
|
206
246
|
process.env.PI_MEMORY_EXIT_SUMMARY = "0";
|
|
@@ -215,6 +255,36 @@ export class PiHost {
|
|
|
215
255
|
noThemes: true,
|
|
216
256
|
noContextFiles: true,
|
|
217
257
|
additionalExtensionPaths: pluginPath ? [pluginPath] : [],
|
|
258
|
+
// 任务会话的 MCP:读引擎目录的 mcp.json,会话启动(bindExtensions)时连服务。
|
|
259
|
+
// 按需搜索(deferred)的服务靠 tool_search 加载,codemode 不提供:不加载那个扩展。
|
|
260
|
+
extensionFactories: [
|
|
261
|
+
...(this.claudeFactory ? [this.claudeFactory] : []),
|
|
262
|
+
...(options.mcp ? [createMcpExtension(), createToolSearchExtension()] : []),
|
|
263
|
+
...(options.spillDir ? [((pi) => {
|
|
264
|
+
pi.on("tool_result", async (event) => {
|
|
265
|
+
if (event.toolName === "read") return;
|
|
266
|
+
const full = event.content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
267
|
+
if (full.length <= 4000) return;
|
|
268
|
+
const dir = resolve(options.spillDir!);
|
|
269
|
+
const file = join(dir, `${event.toolCallId.replace(/[^a-zA-Z0-9_-]/g, "") || "tool"}.txt`);
|
|
270
|
+
try {
|
|
271
|
+
mkdirSync(dir, { recursive: true });
|
|
272
|
+
writeFileSync(file, full);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.error(`工具结果落盘失败:${file}`, error);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
let first = true;
|
|
278
|
+
const content = event.content.flatMap<(typeof event.content)[number]>((block) => {
|
|
279
|
+
if (block.type !== "text") return [block];
|
|
280
|
+
if (!first) return [];
|
|
281
|
+
first = false;
|
|
282
|
+
return [{ type: "text" as const, text: `${full.slice(0, 4000)}\n\n(共 ${full.length} 字,这里是前 4,000 字;全文:${file})` }];
|
|
283
|
+
});
|
|
284
|
+
return { content };
|
|
285
|
+
});
|
|
286
|
+
}) satisfies ExtensionFactory] : []),
|
|
287
|
+
],
|
|
218
288
|
extensionsOverride: (result) => filterMemoryExtensions(result, provider),
|
|
219
289
|
});
|
|
220
290
|
await base.reload();
|
|
@@ -225,10 +295,11 @@ export class PiHost {
|
|
|
225
295
|
getSkills: () => {
|
|
226
296
|
if (!options.skills) return { skills: [], diagnostics: [] };
|
|
227
297
|
const discovered = base.getSkills();
|
|
298
|
+
const assistant = options.skills.filter((skill) => skill.sourceInfo.source === "assistant");
|
|
228
299
|
const own = options.skills.filter((skill) => skill.sourceInfo.source === "hob");
|
|
229
|
-
const common = options.skills.filter((skill) => skill.sourceInfo.source !== "hob");
|
|
300
|
+
const common = options.skills.filter((skill) => skill.sourceInfo.source !== "assistant" && skill.sourceInfo.source !== "hob");
|
|
230
301
|
const seen = new Set<string>();
|
|
231
|
-
const skills = [...own, ...discovered.skills, ...common].filter((skill) => {
|
|
302
|
+
const skills = [...assistant, ...own, ...discovered.skills, ...common].filter((skill) => {
|
|
232
303
|
if (discovered.skills.includes(skill) && options.disabledSkills?.includes(`lore:${skill.name}`)) return false;
|
|
233
304
|
if (seen.has(skill.name)) return false;
|
|
234
305
|
seen.add(skill.name);
|
|
@@ -248,7 +319,10 @@ export class PiHost {
|
|
|
248
319
|
mkdirSync(dirname(options.file), { recursive: true });
|
|
249
320
|
const model = await this.model(options.model);
|
|
250
321
|
const manager = SessionManager.open(options.file, dirname(options.file), options.cwd);
|
|
251
|
-
const
|
|
322
|
+
const modelOverrides = options.spillDir ? Object.fromEntries((await this.runtime()).getAllModels().filter((item) => "contextWindow" in item).map((item) => [
|
|
323
|
+
`${item.provider}/${item.id}`, { reserveTokens: Math.max(16_384, item.contextWindow - 100_000) },
|
|
324
|
+
])) : undefined;
|
|
325
|
+
const settingsManager = SettingsManager.inMemory({ compaction: { enabled: true, ...(modelOverrides ? { modelOverrides } : {}) }, retry: { enabled: true } });
|
|
252
326
|
const resourceLoader = await this.resourceLoader(options, settingsManager);
|
|
253
327
|
const memoryTools = MEMORY_TOOL_PATTERNS[options.memory ?? "off"];
|
|
254
328
|
const { session } = await createAgentSession({
|
|
@@ -263,10 +337,12 @@ export class PiHost {
|
|
|
263
337
|
tools: [...options.tools, ...memoryTools],
|
|
264
338
|
customTools: options.customTools,
|
|
265
339
|
});
|
|
266
|
-
// resources_discover 由 bindExtensions
|
|
340
|
+
// resources_discover 由 bindExtensions 触发;Lore 注册技能目录,MCP 连服务(都在后台)
|
|
267
341
|
if (options.memory === "lore") {
|
|
268
342
|
this.loreSkillPaths = [];
|
|
269
343
|
await session.bindExtensions({ mode: "print" });
|
|
344
|
+
} else if (options.mcp || this.claudeFactory) {
|
|
345
|
+
await session.bindExtensions({ mode: "print" });
|
|
270
346
|
}
|
|
271
347
|
return { session, manager };
|
|
272
348
|
}
|
package/server/src/prompt.ts
CHANGED
|
@@ -1,128 +1,78 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
-
import { PROP_IDS, PROPS } from "../../shared/sprites.ts";
|
|
3
2
|
import type { GeneralSettings, MemoryProvider, ThinkingLevel } from "../../shared/types.ts";
|
|
4
3
|
import type { Store } from "./store.ts";
|
|
5
4
|
import { cronToChinese } from "./cron-text.ts";
|
|
6
5
|
import { memoryPromptLines } from "./memory.ts";
|
|
7
|
-
import { nowText } from "./time.ts";
|
|
8
6
|
|
|
9
|
-
/** 提示词里列的模型:助手能派其中一个。 */
|
|
10
7
|
export interface PromptModel {
|
|
11
8
|
name: string;
|
|
12
9
|
ref: string;
|
|
13
|
-
note: string;
|
|
14
|
-
thinking: ThinkingLevel;
|
|
15
|
-
levels?: ThinkingLevel[];
|
|
16
|
-
isDefault: boolean;
|
|
10
|
+
note: string;
|
|
11
|
+
thinking: ThinkingLevel;
|
|
12
|
+
levels?: ThinkingLevel[];
|
|
13
|
+
isDefault: boolean;
|
|
17
14
|
}
|
|
18
15
|
|
|
19
|
-
/** 派出去的活能做什么(助手设置 → 能力)。 */
|
|
20
16
|
export interface Abilities {
|
|
21
17
|
exec: boolean;
|
|
22
18
|
write: boolean;
|
|
23
19
|
image: boolean;
|
|
24
20
|
}
|
|
25
21
|
|
|
26
|
-
/** 提示词里要用的设置:时区、「关于你」、记忆提供者、模型、能力。 */
|
|
27
22
|
export interface PromptSettings {
|
|
28
23
|
timezone: string;
|
|
29
24
|
aboutYou: GeneralSettings["aboutYou"];
|
|
30
25
|
memory: MemoryProvider;
|
|
31
|
-
/** 这个助手能派的模型(Router 用) */
|
|
32
26
|
models?: PromptModel[];
|
|
33
27
|
skills?: { name: string; description: string }[];
|
|
28
|
+
assistantSkills?: { title: string; description: string }[];
|
|
29
|
+
knowledgeNames?: string[];
|
|
34
30
|
thinkingChoice?: "auto" | "fixed";
|
|
35
|
-
/** 任务能做什么(Worker 用) */
|
|
36
31
|
abilities?: Abilities;
|
|
37
32
|
}
|
|
38
33
|
|
|
39
|
-
|
|
40
|
-
function aboutLines(aboutYou: GeneralSettings["aboutYou"]): string[] {
|
|
34
|
+
function aboutSection(aboutYou: GeneralSettings["aboutYou"]): string[] {
|
|
41
35
|
const lines: string[] = [];
|
|
42
36
|
if (aboutYou.name) lines.push(`- 称呼:${aboutYou.name}`);
|
|
43
37
|
if (aboutYou.language) lines.push(`- 语言:${aboutYou.language}`);
|
|
44
38
|
if (aboutYou.notes) lines.push(`- 其他:${aboutYou.notes}`);
|
|
45
|
-
return lines;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function aboutSection(aboutYou: GeneralSettings["aboutYou"]): string[] {
|
|
49
|
-
const lines = aboutLines(aboutYou);
|
|
50
|
-
return lines.length ? ["", "## 关于用户", ...lines] : [];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** 「可以派的模型」:只有一个时整节只一行。 */
|
|
54
|
-
function modelsSection(models: PromptModel[], fixed: boolean): string[] {
|
|
55
|
-
if (!models.length) return [];
|
|
56
|
-
const lines = ["", "## 可以派的模型", "聪明、成本、速度都是 0–100:越高越聪明、越贵、越快。按任务难度、可接受的花费和要多快来挑;简单的活别派给又贵又慢的模型。"];
|
|
57
|
-
if (fixed) {
|
|
58
|
-
for (const model of models) {
|
|
59
|
-
lines.push(`- ${model.ref}${model.isDefault ? "(默认)" : ""}${model.note ? ` · ${model.note}` : ""} · 思考档 ${model.thinking}`);
|
|
60
|
-
}
|
|
61
|
-
if (models.length > 1) lines.push("按任务难度和类型选择:start_job 的 model 填括号里的 ref;不确定就不填,使用默认值。");
|
|
62
|
-
return lines;
|
|
63
|
-
}
|
|
64
|
-
if (models.length === 1) {
|
|
65
|
-
const only = models[0];
|
|
66
|
-
lines.push(
|
|
67
|
-
`- ${only.ref}${only.isDefault ? "(默认)" : ""}${only.note ? ` · ${only.note}` : ""} · 思考档默认 ${only.thinking},可选 ${(only.levels ?? [only.thinking]).join("、")}:start_job 不用填 model,需要更强推理时填 thinking。`,
|
|
68
|
-
);
|
|
69
|
-
return lines;
|
|
70
|
-
}
|
|
71
|
-
for (const model of models) {
|
|
72
|
-
lines.push(
|
|
73
|
-
`- ${model.ref}${model.isDefault ? "(默认)" : ""}${model.note ? ` · ${model.note}` : ""} · 思考档默认 ${model.thinking},可选 ${(model.levels ?? [model.thinking]).join("、")}`,
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
lines.push("按任务难度和类型选择:start_job 的 model 填括号里的 ref,thinking 按需要填;不确定就不填,使用默认值。");
|
|
77
|
-
return lines;
|
|
39
|
+
return lines.length ? ["", "## 用户", ...lines] : [];
|
|
78
40
|
}
|
|
79
41
|
|
|
80
|
-
/** 「你能做什么」:把助手开关关掉的能力明确写成不能做。 */
|
|
81
|
-
function abilitiesSection(abilities: Abilities): string[] {
|
|
82
|
-
const can: string[] = ["读文件(read、grep、find、ls)"];
|
|
83
|
-
const cannot: string[] = [];
|
|
84
|
-
(abilities.exec ? can : cannot).push("执行命令(bash)");
|
|
85
|
-
(abilities.write ? can : cannot).push("改文件(edit、write)");
|
|
86
|
-
(abilities.image ? can : cannot).push("生图(generate_image)");
|
|
87
|
-
const lines = ["", "## 你能做什么", `- 能做:${can.join("、")}`];
|
|
88
|
-
if (cannot.length) {
|
|
89
|
-
lines.push(`- 不能做:${cannot.join("、")}。确需该能力时用 report(stuck) 说明缺少什么,不要勉强完成。`);
|
|
90
|
-
}
|
|
91
|
-
return lines;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Router 的系统提示词:每轮现读 store,所以改了规矩/补充说明下一轮就生效。 */
|
|
95
42
|
export function buildSystemPrompt(store: Store, assistantId: string, settings: PromptSettings): string {
|
|
96
43
|
const assistant = store.assistant(assistantId);
|
|
97
44
|
if (!assistant) throw new Error(`助手不存在:${assistantId}`);
|
|
98
45
|
const rules = store.rules(assistantId);
|
|
99
46
|
const routines = store.routines().filter((routine) => routine.assistantId === assistantId);
|
|
100
47
|
const others = store.assistants().filter((item) => item.id !== assistantId && !item.hidden);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
48
|
+
const lines = assistant.primary ? [
|
|
49
|
+
`你是「${assistant.name}」,用户的主助手。`,
|
|
50
|
+
"你和用户在一对一的对话里合作,用户的每条消息都是对你说的。",
|
|
51
|
+
] : [
|
|
52
|
+
`你是「${assistant.name}」,用户的助手,负责${assistant.duty}。`,
|
|
105
53
|
...(assistant.dutyLong?.trim() ? [`详细职责:${assistant.dutyLong.trim()}`] : []),
|
|
106
|
-
|
|
54
|
+
"你和用户在一对一的对话里合作。主助手 Hob 会把归你管的事转给你:转来的消息带着用户的原话,等同用户本人的要求,结果会自动回到 Hob。",
|
|
107
55
|
];
|
|
108
|
-
|
|
56
|
+
lines.push(
|
|
57
|
+
"用简洁、规范的书面中文回复,先给结论。",
|
|
58
|
+
"", "## 你手里有什么",
|
|
59
|
+
"你能直接看:文件和图片、任务的结果和产出、网页、记忆。",
|
|
60
|
+
"跑命令、改文件、操作设备这些动手的事在后台任务手里:任务在自己的会话和目录里做,做完把完整结果发回给你。",
|
|
61
|
+
"任务结束后还能接着做。一件事交给任务或其他助手后由它负责到底,跟进也交给它。",
|
|
62
|
+
);
|
|
63
|
+
if (settings.assistantSkills?.length) lines.push(`你沉淀过的技能:${settings.assistantSkills.map((skill) => `${skill.title}:${skill.description}`).join("、")}。派同类的活时在指令里写明按哪个技能做。`);
|
|
64
|
+
if (settings.knowledgeNames?.length) lines.push(`能查的知识库:${settings.knowledgeNames.join("、")}。问到可能写在这些库里的内容,先用 search_knowledge 查。`);
|
|
65
|
+
if (settings.skills?.length) lines.push(`任务能用的技能:${settings.skills.map((skill) => skill.name).join("、")}`);
|
|
109
66
|
lines.push(...aboutSection(settings.aboutYou));
|
|
110
|
-
lines.push(...modelsSection(settings.models ?? [], settings.thinkingChoice === "fixed"));
|
|
111
|
-
if (settings.skills?.length) {
|
|
112
|
-
lines.push("", "## 任务可用的技能", ...settings.skills.map((skill) => `- ${skill.name}:${skill.description}`));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
67
|
if (rules.length) {
|
|
116
|
-
lines.push("", "##
|
|
68
|
+
lines.push("", "## 用户定的规则");
|
|
117
69
|
for (const rule of rules) lines.push(`- ${rule.mode === "ask" ? "执行前确认" : "直接执行"}:${rule.text}`);
|
|
118
70
|
}
|
|
119
|
-
if (assistant.instructions.trim())
|
|
120
|
-
lines.push("", "## 补充说明", assistant.instructions.trim());
|
|
121
|
-
}
|
|
71
|
+
if (assistant.instructions.trim()) lines.push("", "## 补充说明", assistant.instructions.trim());
|
|
122
72
|
const memory = memoryPromptLines(settings.memory);
|
|
123
73
|
if (memory.length) lines.push("", ...memory);
|
|
124
74
|
if (routines.length) {
|
|
125
|
-
lines.push("", "##
|
|
75
|
+
lines.push("", "## 定时");
|
|
126
76
|
for (const routine of routines) {
|
|
127
77
|
const when = routine.schedules.map(cronToChinese).join("、");
|
|
128
78
|
lines.push(`- ${routine.name} · ${when}${routine.active ? "" : "(暂停)"}:${routine.instruction}`);
|
|
@@ -130,98 +80,40 @@ export function buildSystemPrompt(store: Store, assistantId: string, settings: P
|
|
|
130
80
|
}
|
|
131
81
|
if (assistant.primary && others.length) {
|
|
132
82
|
lines.push("", "## 其他助手");
|
|
133
|
-
for (const other of others) {
|
|
134
|
-
const ask = store.rules(other.id).filter((rule) => rule.mode === "ask").map((rule) => rule.text);
|
|
135
|
-
lines.push(`- ${other.name}(${other.id}):${other.duty}${other.dutyLong?.trim() ? `。详细职责:${other.dutyLong.trim()}` : ""}${ask.length ? `。执行前要确认:${ask.join(";")}` : ""}`);
|
|
136
|
-
}
|
|
137
|
-
lines.push("转交前先对照详细职责挑最合适的助手;没有合适的就自己派任务,或者提议创建新助手。");
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (!assistant.primary) {
|
|
141
|
-
lines.push("", "## 转来的事", "带「转自 Hob」的消息是主助手替用户转来的,等同于用户本人的要求:照着去做,需要执行就直接 start_job,不要只回复计划;做完不用另外给 Hob 发消息,结果会自动回到 Hob。", "", "## 不归你管的事", "主助手 Hob 负责在各助手之间分派。用户的要求明显超出你的职责时,调用 hand_back_to_hob 交回 Hob,不要硬做;在你职责内的事照常自己做。");
|
|
83
|
+
for (const other of others) lines.push(`- ${other.name}(${other.id}):${other.duty}。${other.dutyLong?.trim() ?? ""}`);
|
|
142
84
|
}
|
|
143
|
-
|
|
144
|
-
lines.push(
|
|
145
|
-
"",
|
|
146
|
-
"## 工作方式",
|
|
147
|
-
"- 说要做的事,就在同一条消息里调用对应工具去做;不要只描述计划然后停下等用户催。只有规则要求确认或操作不可逆时才停下来问。",
|
|
148
|
-
"- 用简洁、规范的书面中文回复,直接给出结论,不要复述步骤、不要罗列清单。",
|
|
149
|
-
"- 你不能亲自执行任何操作:没有 bash、read、edit、write 这些工具(联网搜索除外)。需要执行任务时调用 start_job 交给后台任务,任务完成后会有事件回到这里。",
|
|
150
|
-
"- 需要最新信息或核对事实时,直接用 web_search / web_fetch 查,不必为此派任务;要多步调研、写文件或跑命令时再用 start_job。",
|
|
151
|
-
"- 任务本身就是带完整工具的编码 agent:写代码、改项目、查文件都是普通任务,用 start_job 的 cwd 指定任务的工作目录。",
|
|
152
|
-
"- 只有规则标为「执行前确认」,或者操作不可逆(发送、删除、付费)时,才用 ask_user 请用户确认。",
|
|
153
|
-
"- 会产生卡片的工具(start_job、ask_user、propose_assistant):先在同一条消息里写一句话(要做什么、等用户确认什么),再调用工具。这些工具会返回“本轮到此结束”:卡片已经出现在对话里,内容齐全,调用完立即结束这一轮,不要再补任何文字(“草稿卡在上面了”“点创建就成”这类话都是重复的废话)。",
|
|
154
|
-
"- 用 ask_user 时,如果同类操作以后很可能还会遇到(日常的常规操作;发布、删除、付费这类不可逆的事除外),第三个选项写“以后同类操作直接执行,无需确认”(always),并填一句很短的 ruleText,如“清理临时目录”。",
|
|
155
|
-
"- 用户说“以后”“别再”这类要求时,用 set_rule 记入规则,不要只做口头承诺。",
|
|
156
|
-
...(assistant.primary
|
|
157
|
-
? [
|
|
158
|
-
"- 用户要早报、日报,或者问“昨晚/今天都做了什么”时,先调 overview 看各助手这段时间的日志和任务,再照实写一两句汇总,不要凭印象编造。",
|
|
159
|
-
]
|
|
160
|
-
: []),
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
if (assistant.primary) {
|
|
164
|
-
lines.push(
|
|
165
|
-
"- 同类任务在对话中反复出现(同一件事被要求 3 次以上),就用 propose_assistant 提议创建专属助手。",
|
|
166
|
-
"- propose_assistant 的 duty 是左栏头像下面那行小字:最多 8 个字,如「线上和机器」「模型和 agent」;更详细的职责写在 dutyLong(草稿卡里的「职责」行)。",
|
|
167
|
-
`- propose_assistant 的 sprite 按职责选:${PROP_IDS.filter((id) => id !== "hob")
|
|
168
|
-
.map((id) => `${id} ${PROPS[id].label}`)
|
|
169
|
-
.join(",")};hob 那个火苗是主助手专用的,不要用。`,
|
|
170
|
-
"- 库里没有贴合职责的道具时,可以自己画一件:sprite 填 custom,prop_art 给出 art、pal、dx 画一件 16×16 的像素道具;画得不合适会返回原因,照着改好重发就行。",
|
|
171
|
-
"- 有更合适的助手时用 message_assistant 把任务转过去。",
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
lines.push("- 用户问你在做什么、有什么在运行,可依据任务事件回答,不要编造进度。");
|
|
175
|
-
lines.push(
|
|
176
|
-
"- 最后重申:start_job、ask_user、propose_assistant 返回“本轮到此结束”后,你说的下一个字都是多余的,直接结束回合。",
|
|
177
|
-
);
|
|
178
85
|
return lines.join("\n");
|
|
179
86
|
}
|
|
180
87
|
|
|
181
|
-
/** Worker 的系统提示词:所属助手、要问的事、补充说明、任务说明、必须 report 收尾。 */
|
|
182
88
|
export function buildWorkerPrompt(store: Store, jobId: string, settings: PromptSettings): string {
|
|
183
89
|
const job = store.job(jobId);
|
|
184
90
|
if (!job) throw new Error(`任务不存在:${jobId}`);
|
|
185
91
|
const assistant = store.assistant(job.assistantId);
|
|
92
|
+
const name = assistant?.name ?? job.assistantId;
|
|
186
93
|
const askFirst = store.rules(job.assistantId).filter((rule) => rule.mode === "ask");
|
|
187
|
-
const lines
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
94
|
+
const lines = [
|
|
95
|
+
`你是「${name}」助手派出的后台任务,在自己的会话里把交给你的事做完。`,
|
|
96
|
+
`工作目录:${job.cwd ?? homedir()}。产出放在这里;读取不限目录。`,
|
|
97
|
+
`交给你的消息里有用户的原话、最近的对话、附件和「${name}」的备注。`,
|
|
98
|
+
`做完调用 report:result 写完整结果,包括结论、数据、链接和文件路径。它会交给「${name}」,由它告诉用户。`,
|
|
99
|
+
"之后用户可能还会找你补充、纠正或追问,这些都会发到这个会话里;接着做完,再调用 report。",
|
|
191
100
|
];
|
|
101
|
+
if (settings.knowledgeNames?.length) lines.push(`能查的知识库:${settings.knowledgeNames.join("、")}。问到可能写在这些库里的内容,先用 search_knowledge 查。`);
|
|
102
|
+
if (settings.abilities) {
|
|
103
|
+
const cannot = [
|
|
104
|
+
...(!settings.abilities.exec ? ["执行命令(bash)"] : []),
|
|
105
|
+
...(!settings.abilities.write ? ["改文件(edit、write)"] : []),
|
|
106
|
+
...(!settings.abilities.image ? ["生图(generate_image)"] : []),
|
|
107
|
+
];
|
|
108
|
+
if (cannot.length) lines.push(`不能做:${cannot.join("、")}。`);
|
|
109
|
+
}
|
|
192
110
|
lines.push(...aboutSection(settings.aboutYou));
|
|
193
|
-
if (settings.abilities) lines.push(...abilitiesSection(settings.abilities));
|
|
194
111
|
if (askFirst.length) {
|
|
195
|
-
lines.push("", "##
|
|
112
|
+
lines.push("", "## 这些事执行前用 ask_user 问用户");
|
|
196
113
|
for (const rule of askFirst) lines.push(`- ${rule.text}`);
|
|
197
114
|
}
|
|
198
|
-
if (assistant?.instructions.trim())
|
|
199
|
-
lines.push("", "## 补充说明", assistant.instructions.trim());
|
|
200
|
-
}
|
|
115
|
+
if (assistant?.instructions.trim()) lines.push("", "## 补充说明", assistant.instructions.trim());
|
|
201
116
|
const memory = memoryPromptLines(settings.memory);
|
|
202
117
|
if (memory.length) lines.push("", ...memory);
|
|
203
|
-
if (assistant?.primary) {
|
|
204
|
-
lines.push(
|
|
205
|
-
"",
|
|
206
|
-
"## 早报",
|
|
207
|
-
"- 用户要早报、日报或汇总“昨晚/今天都做了什么”时,先调 overview(sinceHours) 拿各助手这段时间的日志和任务,再照实写汇总。",
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
lines.push(
|
|
211
|
-
"",
|
|
212
|
-
`## 任务:${job.title}`,
|
|
213
|
-
job.instruction,
|
|
214
|
-
...(job.inputs?.length ? ["", "## 你收到的文件", ...job.inputs.map((file) => `- ${file}`)] : []),
|
|
215
|
-
"",
|
|
216
|
-
`工作目录:${job.cwd ?? homedir()}`,
|
|
217
|
-
"",
|
|
218
|
-
"## 要求",
|
|
219
|
-
"- 产出文件只写在工作目录里:新建的文件、下载的内容、导出的结果都放在这个目录,不要写到用户主目录或其他位置。",
|
|
220
|
-
"- 读取不受目录限制:可读取任何位置的文件作参考,只是不要写入这些位置。",
|
|
221
|
-
"- 要给用户看的文件放进 report 的 files(绝对路径,文件本身在工作目录里)。",
|
|
222
|
-
"- 完成后必须调用 report 提交结果(outcome:已完成 / 受阻 / 待确认),不调用视为未完成。",
|
|
223
|
-
"- summary 用中文写给用户:一两句结论 + 关键数字,不要罗列过程。",
|
|
224
|
-
"- 受阻时调用 report(stuck) 说明受阻原因;要做不该做的、无法撤回的事就调用 ask_user。",
|
|
225
|
-
);
|
|
226
118
|
return lines.join("\n");
|
|
227
119
|
}
|
package/server/src/providers.ts
CHANGED
|
@@ -296,7 +296,7 @@ export class ProviderService {
|
|
|
296
296
|
);
|
|
297
297
|
const builtin = runtime
|
|
298
298
|
.getProviders()
|
|
299
|
-
.filter((provider) => !customIds.has(provider.id))
|
|
299
|
+
.filter((provider) => provider.id !== "claude-bridge" && !customIds.has(provider.id))
|
|
300
300
|
.map((provider) => ({ id: provider.id, name: provider.name }))
|
|
301
301
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
302
302
|
const models: Record<string, ModelThinkingView[]> = {};
|
|
@@ -341,6 +341,7 @@ export class ProviderService {
|
|
|
341
341
|
runtime.registerProvider(provider.id, config);
|
|
342
342
|
}
|
|
343
343
|
await this.applyBuiltinThinking(runtime);
|
|
344
|
+
await this.options.pi.configureClaude(runtime, this.options.home, this.settings.settings.claude);
|
|
344
345
|
}
|
|
345
346
|
|
|
346
347
|
/**
|
|
@@ -400,7 +401,7 @@ export class ProviderService {
|
|
|
400
401
|
const builtin = new Map(
|
|
401
402
|
runtime
|
|
402
403
|
.getProviders()
|
|
403
|
-
.filter((provider) => !customIds.has(provider.id))
|
|
404
|
+
.filter((provider) => provider.id !== "claude-bridge" && !customIds.has(provider.id))
|
|
404
405
|
.map((provider) => [provider.id, provider] as const),
|
|
405
406
|
);
|
|
406
407
|
const source = localPiDir();
|