personal-bot 0.2.9 → 0.2.10
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 +22 -11
- package/README.zh-CN.md +20 -9
- package/docs/README.md +3 -2
- package/docs/agent-collaboration.md +1 -1
- package/docs/dsh.md +4 -6
- package/docs/file-storage.md +7 -4
- package/docs/history.md +2 -9
- package/docs/installation-design.md +4 -1
- package/docs/model-context.md +4 -4
- package/docs/technical-design.md +11 -9
- package/docs/workspace-configuration.md +55 -0
- package/lib/client.js +28 -3
- package/lib/dsh.js +0 -5
- package/lib/extension/dsh/command.js +14 -10
- package/lib/extension/dsh/history.js +1 -1
- package/lib/extension/dsh/management.d.ts +4 -0
- package/lib/extension/dsh/management.js +20 -5
- package/lib/extension/dsh/workspace.d.ts +7 -1
- package/lib/extension/dsh/workspace.js +68 -1
- package/lib/extension/setup-cli.js +1 -1
- package/lib/integrations/dsh/skills.d.ts +4 -0
- package/lib/integrations/dsh/skills.js +27 -0
- package/lib/model/history.d.ts +1 -0
- package/lib/model/workspace-config.d.ts +9 -0
- package/lib/model/workspace-config.js +1 -0
- package/lib/services/history/history-service.d.ts +1 -2
- package/lib/services/history/history-service.js +27 -40
- package/lib/services/workspace/workspace-config.d.ts +7 -0
- package/lib/services/workspace/workspace-config.js +50 -0
- package/lib/services/workspace/workspace-preparation.d.ts +1 -0
- package/lib/services/workspace/workspace-preparation.js +8 -0
- package/lib/storage/history-files.d.ts +5 -1
- package/lib/storage/history-files.js +68 -15
- package/lib/ui/dsh/management.js +27 -3
- package/npm-shrinkwrap.json +7 -3
- package/package.json +7 -4
- package/python/README.md +2 -2
- package/src/dsh.ts +0 -5
- package/src/extension/command.ts +25 -8
- package/src/extension/dsh/command.ts +13 -8
- package/src/extension/dsh/history.ts +2 -2
- package/src/extension/dsh/management.ts +21 -5
- package/src/extension/dsh/workspace.ts +62 -5
- package/src/extension/events.ts +18 -1
- package/src/extension/lifecycle.ts +9 -2
- package/src/extension/setup-cli.ts +1 -1
- package/src/extension/subagent-entry.ts +4 -1
- package/src/integrations/dsh/skills.ts +30 -0
- package/src/integrations/pi/skills.ts +52 -0
- package/src/model/history.ts +1 -0
- package/src/model/workspace-config.ts +10 -0
- package/src/pbot.ts +2 -3
- package/src/services/history/history-service.ts +15 -25
- package/src/services/workspace/workspace-config.ts +49 -0
- package/src/services/workspace/workspace-preparation.ts +8 -0
- package/src/storage/history-files.ts +53 -15
- package/src/ui/dsh/management.ts +20 -3
- package/docs/dsh.en.md +0 -339
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { Type } from "../../common/typebox.js";
|
|
2
2
|
import { object, json } from "../../common/json.js";
|
|
3
3
|
import { requireThat, schema } from "../../common/validation.js";
|
|
4
|
-
import { prepareWorkspace } from "../../services/workspace/workspace-preparation.js";
|
|
5
4
|
import { executeAgent } from "../../tools/agents/actions.js";
|
|
6
5
|
import { dshAgentActionSchema } from "../../tools/agents/dsh-contract.js";
|
|
7
6
|
import { executeTeam, executeTeamMessage } from "../../tools/teams/actions.js";
|
|
8
7
|
import { teamActionSchema, teamParametersSchema, teamMessageActionSchema, teamMessageParametersSchema } from "../../tools/teams/contract.js";
|
|
9
|
-
import {
|
|
8
|
+
import { dshWorkspaceStatus } from "./management.js";
|
|
10
9
|
const validAgent = schema(dshAgentActionSchema), validTeam = schema(teamActionSchema), validMessage = schema(teamMessageActionSchema);
|
|
11
10
|
const chatSchema = Type.Object({ agentId: Type.String({ minLength: 1 }), text: Type.Optional(Type.String({ maxLength: 24000 })),
|
|
12
11
|
delivery: Type.Optional(Type.Union([Type.Literal("steer"), Type.Literal("followUp")])),
|
|
@@ -18,23 +17,28 @@ export function registerDshCommand(ctx, workspaces, management) {
|
|
|
18
17
|
input: { hint: "status | init | python setup | agent/chat/team/post/history/records JSON", attachments: true },
|
|
19
18
|
async handler({ agent, rawInput, signal, attachments, commandId }) {
|
|
20
19
|
signal.throwIfAborted();
|
|
20
|
+
const command = rawInput.trim(), split = command.search(/\s/);
|
|
21
|
+
if (!command || command === "status") {
|
|
22
|
+
requireThat(!attachments.length, "状态查询不接受附件。");
|
|
23
|
+
const status = await dshWorkspaceStatus(workspaces, agent);
|
|
24
|
+
signal.throwIfAborted();
|
|
25
|
+
return { kind: "success", text: JSON.stringify(status, null, 2) };
|
|
26
|
+
}
|
|
27
|
+
if (command === "init") {
|
|
28
|
+
requireThat(!attachments.length, "初始化不接受附件。");
|
|
29
|
+
await workspaces.initialize(agent);
|
|
30
|
+
signal.throwIfAborted();
|
|
31
|
+
return { kind: "success", text: JSON.stringify(await dshWorkspaceStatus(workspaces, agent), null, 2) };
|
|
32
|
+
}
|
|
21
33
|
const workspace = await workspaces.get(agent);
|
|
22
34
|
signal.throwIfAborted();
|
|
23
|
-
const command = rawInput.trim(), split = command.search(/\s/);
|
|
24
35
|
const verb = split < 0 ? command : command.slice(0, split);
|
|
25
36
|
requireThat(verb === "chat" || !attachments.length, "输入框附件仅用于 chat 人工对话。");
|
|
26
|
-
if (command === "init") {
|
|
27
|
-
await prepareWorkspace(workspace.root);
|
|
28
|
-
workspace.check();
|
|
29
|
-
return { kind: "success", text: "资料目录已准备;已有文件保留。" };
|
|
30
|
-
}
|
|
31
37
|
if (command === "python setup") {
|
|
32
38
|
const result = await workspaces.preparePython();
|
|
33
39
|
workspace.check();
|
|
34
40
|
return { kind: "success", text: JSON.stringify(result, null, 2) };
|
|
35
41
|
}
|
|
36
|
-
if (!command || command === "status")
|
|
37
|
-
return { kind: "success", text: JSON.stringify(dshStatus(workspaces, workspace), null, 2) };
|
|
38
42
|
requireThat(split >= 0, usage);
|
|
39
43
|
const input = JSON.parse(command.slice(split));
|
|
40
44
|
requireThat(object(input), "命令参数必须是 JSON 对象。");
|
|
@@ -17,7 +17,7 @@ export class DshHistoryWork {
|
|
|
17
17
|
constructor(ctx, root) {
|
|
18
18
|
this.ctx = ctx;
|
|
19
19
|
const agents = new AgentFiles(dshHomePath("pbot", "subagents"), "dsh");
|
|
20
|
-
this.service = new HistoryService(root, new HistoryFiles(join(root, ".
|
|
20
|
+
this.service = new HistoryService(root, new HistoryFiles(join(root, ".pbot/history"), "dsh"), new DshHistory(ctx, root, () => agents.list().filter(value => value.workspace === root).map(value => value.agentId)), new HistoryFiles(join(root, ".pbot/history")));
|
|
21
21
|
}
|
|
22
22
|
attach(agent) { this.#parents.add(agent); return () => { this.#parents.delete(agent); }; }
|
|
23
23
|
cancel() { this.#controller?.abort(); }
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
1
2
|
import type { Context } from "@deepseek-ai/cordis";
|
|
2
3
|
import { TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
3
4
|
import { type Json } from "../../common/json.js";
|
|
4
5
|
import type { DshWorkspaces, DshWorkspace } from "./workspace.js";
|
|
5
6
|
export declare function dshStatus(workspaces: DshWorkspaces, workspace: DshWorkspace): Json;
|
|
7
|
+
/** Status remains available when this workspace has not opted in. */
|
|
8
|
+
export declare function dshWorkspaceStatus(workspaces: DshWorkspaces, agent: Agent): Promise<Json>;
|
|
6
9
|
/** Native Remote owns transport and access checks. These workspace reads do not admit or cancel execution. */
|
|
7
10
|
export declare class PbotManagement extends TypertRemoteService {
|
|
8
11
|
private readonly workspaces;
|
|
9
12
|
constructor(ctx: Context, workspaces: DshWorkspaces);
|
|
13
|
+
private parent;
|
|
10
14
|
private workspace;
|
|
11
15
|
snapshot(request: Json, signal: AbortSignal): Promise<Json>;
|
|
12
16
|
expert(request: Json, signal: AbortSignal): Promise<Json>;
|
|
@@ -106,11 +106,20 @@ const validRequest = schema(requestSchema), validExpert = schema(expertSchema),
|
|
|
106
106
|
export function dshStatus(workspaces, workspace) {
|
|
107
107
|
workspace.check();
|
|
108
108
|
const agents = workspace.agents.list(workspace.owner);
|
|
109
|
-
return json({ host: "dsh", release: packageVersion, workspaceRoot: workspace.root, sessionId: workspace.agent.id,
|
|
109
|
+
return json({ host: "dsh", release: packageVersion, enabled: true, state: "running", workspaceRoot: workspace.root, sessionId: workspace.agent.id,
|
|
110
110
|
model: workspace.agent.model.current() ?? null, python: workspaces.python.status,
|
|
111
111
|
controlError: workspace.controlError ?? null, history: workspace.history.status,
|
|
112
112
|
agents, teams: workspace.teams.list(workspace.owner, agents) });
|
|
113
113
|
}
|
|
114
|
+
/** Status remains available when this workspace has not opted in. */
|
|
115
|
+
export async function dshWorkspaceStatus(workspaces, agent) {
|
|
116
|
+
const { root, configuration } = await workspaces.configuration(agent);
|
|
117
|
+
if (!configuration.enabled)
|
|
118
|
+
return json({ host: "dsh", release: packageVersion, enabled: false,
|
|
119
|
+
state: configuration.error ? "error" : "disabled", nextStep: configuration.error ? configuration.error : "/pbot init",
|
|
120
|
+
workspaceRoot: root, sessionId: agent.id, configuration, model: agent.model.current() ?? null, agents: [], teams: [] });
|
|
121
|
+
return json({ ...dshStatus(workspaces, await workspaces.get(agent)), configuration });
|
|
122
|
+
}
|
|
114
123
|
/** Native Remote owns transport and access checks. These workspace reads do not admit or cancel execution. */
|
|
115
124
|
let PbotManagement = (() => {
|
|
116
125
|
let _classSuper = TypertRemoteService;
|
|
@@ -137,21 +146,27 @@ let PbotManagement = (() => {
|
|
|
137
146
|
super(ctx, "pbotManagement", { namespace: "pbot" });
|
|
138
147
|
this.workspaces = workspaces;
|
|
139
148
|
}
|
|
140
|
-
|
|
149
|
+
parent(request, signal) {
|
|
141
150
|
signal.throwIfAborted();
|
|
142
151
|
requireThat(validRequest(request), "管理查询参数无效。");
|
|
143
152
|
const args = request;
|
|
144
153
|
const agent = this.ctx.agents.get(SessionId(args.parentSessionId));
|
|
145
154
|
requireThat(agent, "父会话未加载或已关闭,请在原生会话页面重新打开。");
|
|
155
|
+
return { agent, input: args.input };
|
|
156
|
+
}
|
|
157
|
+
async workspace(request, signal) {
|
|
158
|
+
const { agent, input } = this.parent(request, signal);
|
|
146
159
|
const workspace = await this.workspaces.get(agent);
|
|
147
160
|
signal.throwIfAborted();
|
|
148
161
|
workspace.check();
|
|
149
|
-
return { workspace, input
|
|
162
|
+
return { workspace, input, signal: AbortSignal.any([signal, workspace.lifetime.signal]) };
|
|
150
163
|
}
|
|
151
164
|
async snapshot(request, signal) {
|
|
152
|
-
const {
|
|
165
|
+
const { agent, input } = this.parent(request, signal);
|
|
153
166
|
requireThat(input === undefined, "工作区状态查询不接受额外参数。");
|
|
154
|
-
|
|
167
|
+
const status = await dshWorkspaceStatus(this.workspaces, agent);
|
|
168
|
+
signal.throwIfAborted();
|
|
169
|
+
return status;
|
|
155
170
|
}
|
|
156
171
|
async expert(request, signal) {
|
|
157
172
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
@@ -8,6 +8,7 @@ import { DshAgentRuntime } from "./agent-runtime.js";
|
|
|
8
8
|
import { DshHistoryWork } from "./history.js";
|
|
9
9
|
import type { HistoryService } from "../../services/history/history-service.js";
|
|
10
10
|
import { PythonEnvironmentService } from "../../services/workspace/python-environment-service.js";
|
|
11
|
+
import type { WorkspaceConfiguration } from "../../model/workspace-config.js";
|
|
11
12
|
export declare const dshDependencies: string[];
|
|
12
13
|
export declare class DshWorkspace {
|
|
13
14
|
#private;
|
|
@@ -38,8 +39,13 @@ export declare class DshWorkspaces {
|
|
|
38
39
|
readonly python: PythonEnvironmentService;
|
|
39
40
|
constructor(ctx: Context);
|
|
40
41
|
private history;
|
|
41
|
-
open(agent: Agent): Promise<DshWorkspace>;
|
|
42
|
+
open(agent: Agent): Promise<DshWorkspace | undefined>;
|
|
42
43
|
get(agent: Agent): Promise<DshWorkspace>;
|
|
44
|
+
configuration(agent: Agent): Promise<{
|
|
45
|
+
root: string;
|
|
46
|
+
configuration: WorkspaceConfiguration;
|
|
47
|
+
}>;
|
|
48
|
+
initialize(agent: Agent): Promise<DshWorkspace>;
|
|
43
49
|
preparePython(): Promise<{
|
|
44
50
|
directory: string;
|
|
45
51
|
python: string;
|
|
@@ -14,6 +14,9 @@ import { registerDshHistoryTool } from "../../tools/history/dsh-tool.js";
|
|
|
14
14
|
import { activatePython } from "../../integrations/python-activation.js";
|
|
15
15
|
import { PythonEnvironmentService } from "../../services/workspace/python-environment-service.js";
|
|
16
16
|
import { requireThat } from "../../common/validation.js";
|
|
17
|
+
import { registerDshSkills } from "../../integrations/dsh/skills.js";
|
|
18
|
+
import { readWorkspaceConfig, workspaceDisabledMessage } from "../../services/workspace/workspace-config.js";
|
|
19
|
+
import { initializeWorkspace } from "../../services/workspace/workspace-preparation.js";
|
|
17
20
|
export const dshDependencies = ["agents", "sessions", "sessionPersistence", "sessionQuery", "llm", "tools", "systemPrompt", "skills", "fs", "attachments", "workingDirectory", "approval"];
|
|
18
21
|
export class DshWorkspace {
|
|
19
22
|
ctx;
|
|
@@ -52,6 +55,7 @@ export class DshWorkspace {
|
|
|
52
55
|
this.#experts.set(d.agentId, runtime);
|
|
53
56
|
scope.effect(() => () => { if (this.#experts.get(d.agentId) === runtime)
|
|
54
57
|
this.#experts.delete(d.agentId); });
|
|
58
|
+
registerDshSkills(scope, child);
|
|
55
59
|
await dshResources(scope, child, root, "general-purpose", () => python.status, runtime.check);
|
|
56
60
|
const teamTool = dshExpertTools(scope, child, d.options);
|
|
57
61
|
if (teamTool)
|
|
@@ -77,6 +81,7 @@ export class DshWorkspace {
|
|
|
77
81
|
async open() {
|
|
78
82
|
this.check();
|
|
79
83
|
this.#scope = await this.agent.ctx.plugin({ name: "pbot-coordinator", inject: dshDependencies, apply: async (ctx) => {
|
|
84
|
+
registerDshSkills(ctx, this.agent);
|
|
80
85
|
await dshResources(ctx, this.agent, this.root, "coordinator", () => this.python.status, this.check);
|
|
81
86
|
registerDshFileEvents(ctx, this.agent, this.root, false, this.check);
|
|
82
87
|
registerDshAgentTool(ctx, this.agent, this.agents, this.owner, this.check);
|
|
@@ -152,6 +157,8 @@ export class DshWorkspaces {
|
|
|
152
157
|
ctx;
|
|
153
158
|
python = new PythonEnvironmentService(process.cwd());
|
|
154
159
|
#parents = new Map();
|
|
160
|
+
#initializing = new Map();
|
|
161
|
+
#configurations = new Map();
|
|
155
162
|
#parentEffects = new Map();
|
|
156
163
|
#histories = new Map();
|
|
157
164
|
#lifetime = new AbortController();
|
|
@@ -202,6 +209,20 @@ export class DshWorkspaces {
|
|
|
202
209
|
const opening = (async () => {
|
|
203
210
|
const root = await realpath(agent.session.header.cwd ?? process.cwd());
|
|
204
211
|
this.#lifetime.signal.throwIfAborted();
|
|
212
|
+
const configuration = await readWorkspaceConfig(root);
|
|
213
|
+
this.#lifetime.signal.throwIfAborted();
|
|
214
|
+
this.#configurations.set(agent, { root, configuration });
|
|
215
|
+
if (!configuration.enabled) {
|
|
216
|
+
if (configuration.error)
|
|
217
|
+
this.ctx.logger.warn(configuration.error);
|
|
218
|
+
const dispose = agent.ctx.effect(() => () => {
|
|
219
|
+
this.#parents.delete(agent);
|
|
220
|
+
this.#configurations.delete(agent);
|
|
221
|
+
this.#parentEffects.delete(agent);
|
|
222
|
+
});
|
|
223
|
+
this.#parentEffects.set(agent, dispose);
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
205
226
|
const history = await this.history(root);
|
|
206
227
|
let workspace;
|
|
207
228
|
try {
|
|
@@ -209,12 +230,14 @@ export class DshWorkspaces {
|
|
|
209
230
|
const owned = workspace;
|
|
210
231
|
const dispose = agent.ctx.effect(() => async () => {
|
|
211
232
|
this.#parents.delete(agent);
|
|
233
|
+
this.#configurations.delete(agent);
|
|
212
234
|
this.#parentEffects.delete(agent);
|
|
213
235
|
await owned.close();
|
|
214
236
|
});
|
|
215
237
|
this.#parentEffects.set(agent, dispose);
|
|
216
238
|
await workspace.open();
|
|
217
239
|
this.#lifetime.signal.throwIfAborted();
|
|
240
|
+
void this.preparePython().catch(error => this.ctx.logger.warn(String(error)));
|
|
218
241
|
return workspace;
|
|
219
242
|
}
|
|
220
243
|
catch (error) {
|
|
@@ -246,9 +269,51 @@ export class DshWorkspaces {
|
|
|
246
269
|
this.#lifetime.signal.throwIfAborted();
|
|
247
270
|
requireThat(this.ctx.agents.get(agent.id) === agent, "父会话已关闭。");
|
|
248
271
|
const workspace = await this.open(agent);
|
|
272
|
+
const configuration = this.#configurations.get(agent)?.configuration;
|
|
273
|
+
requireThat(workspace, configuration ? workspaceDisabledMessage(configuration) : "pbot 工作区实例不可用。");
|
|
249
274
|
workspace.check();
|
|
250
275
|
return workspace;
|
|
251
276
|
}
|
|
277
|
+
async configuration(agent) {
|
|
278
|
+
this.#lifetime.signal.throwIfAborted();
|
|
279
|
+
requireThat(this.ctx.agents.get(agent.id) === agent, "父会话已关闭。");
|
|
280
|
+
await this.open(agent);
|
|
281
|
+
this.#lifetime.signal.throwIfAborted();
|
|
282
|
+
const current = this.#configurations.get(agent);
|
|
283
|
+
requireThat(current && this.ctx.agents.get(agent.id) === agent, "父会话已关闭。");
|
|
284
|
+
return current;
|
|
285
|
+
}
|
|
286
|
+
async initialize(agent) {
|
|
287
|
+
const { root } = await this.configuration(agent);
|
|
288
|
+
let pending = this.#initializing.get(root);
|
|
289
|
+
if (!pending) {
|
|
290
|
+
pending = (async () => {
|
|
291
|
+
await initializeWorkspace(root);
|
|
292
|
+
this.#lifetime.signal.throwIfAborted();
|
|
293
|
+
const configuration = await readWorkspaceConfig(root);
|
|
294
|
+
for (const [parent, current] of [...this.#configurations]) {
|
|
295
|
+
if (current.root !== root || this.ctx.agents.get(parent.id) !== parent)
|
|
296
|
+
continue;
|
|
297
|
+
const workspace = await this.#parents.get(parent);
|
|
298
|
+
if (workspace) {
|
|
299
|
+
this.#configurations.set(parent, { root, configuration });
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
await this.#parentEffects.get(parent)?.();
|
|
303
|
+
await this.open(parent);
|
|
304
|
+
}
|
|
305
|
+
})();
|
|
306
|
+
this.#initializing.set(root, pending);
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
await pending;
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
if (this.#initializing.get(root) === pending)
|
|
313
|
+
this.#initializing.delete(root);
|
|
314
|
+
}
|
|
315
|
+
return this.get(agent);
|
|
316
|
+
}
|
|
252
317
|
preparePython() {
|
|
253
318
|
this.#lifetime.signal.throwIfAborted();
|
|
254
319
|
if (this.#preparing)
|
|
@@ -268,8 +333,9 @@ export class DshWorkspaces {
|
|
|
268
333
|
this.#lifetime.abort();
|
|
269
334
|
this.#closing = (async () => {
|
|
270
335
|
try {
|
|
336
|
+
await Promise.allSettled(this.#initializing.values());
|
|
271
337
|
const opened = await Promise.allSettled(this.#parents.values());
|
|
272
|
-
const closed = await Promise.allSettled(opened.
|
|
338
|
+
const closed = await Promise.allSettled(opened.flatMap(value => value.status === "fulfilled" && value.value ? [value.value.close()] : []));
|
|
273
339
|
const effects = await Promise.allSettled([...this.#parentEffects.values()].map(dispose => dispose()));
|
|
274
340
|
const histories = await Promise.allSettled([...this.#histories.values()].map(value => value.work.close()));
|
|
275
341
|
await this.python.close();
|
|
@@ -279,6 +345,7 @@ export class DshWorkspaces {
|
|
|
279
345
|
}
|
|
280
346
|
finally {
|
|
281
347
|
this.#parents.clear();
|
|
348
|
+
this.#configurations.clear();
|
|
282
349
|
this.#parentEffects.clear();
|
|
283
350
|
this.#histories.clear();
|
|
284
351
|
this.#restorePython?.();
|
|
@@ -115,7 +115,7 @@ export async function main(args) {
|
|
|
115
115
|
if (!result.pi)
|
|
116
116
|
return 1;
|
|
117
117
|
ui.text("\n软件安装阶段结束。初始化、Python 环境和外部服务的状态由 Pi/pbot/插件分别报告。");
|
|
118
|
-
if (await ui.confirm(failed ? "
|
|
118
|
+
if (await ui.confirm(failed ? "部分插件失败,是否仍启动 Pi 并初始化启用工作区" : "启动 Pi 并初始化启用工作区", !failed)) {
|
|
119
119
|
process.stdin.pause();
|
|
120
120
|
await host.start(result.pi, workspace);
|
|
121
121
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
3
|
+
/** Keep bundled methods local to this Agent without overriding its native project/user methods. */
|
|
4
|
+
export declare function registerDshSkills(ctx: Context, agent: Agent): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { scopeParentOf } from "@deepseek-ai/dsh-scope";
|
|
3
|
+
import { FileSystemSkillProvider } from "@deepseek-ai/dsh-skill-filesystem";
|
|
4
|
+
import { resourceRoot } from "../../common/paths.js";
|
|
5
|
+
/** Keep bundled methods local to this Agent without overriding its native project/user methods. */
|
|
6
|
+
export function registerDshSkills(ctx, agent) {
|
|
7
|
+
let filesystem;
|
|
8
|
+
ctx.skills.registerProvider(control => {
|
|
9
|
+
filesystem = new FileSystemSkillProvider(ctx, control, {
|
|
10
|
+
providerName: "pbot", includeDefaultRoots: false, bundledSkillDir: join(resourceRoot, "skills"),
|
|
11
|
+
});
|
|
12
|
+
return {
|
|
13
|
+
name: "pbot",
|
|
14
|
+
async list(options) {
|
|
15
|
+
const inherited = await ctx.skills.snapshot({ ...options, scope: scopeParentOf(agent) });
|
|
16
|
+
const preferred = new Set(inherited.skills.filter(skill => skill.provider !== "pbot").map(skill => skill.name));
|
|
17
|
+
const listed = await filesystem.list(options);
|
|
18
|
+
const candidates = Array.isArray(listed) ? listed : listed.candidates;
|
|
19
|
+
return { candidates: candidates.filter(skill => !preferred.has(skill.name)),
|
|
20
|
+
complete: inherited.complete && (Array.isArray(listed) || listed.complete) };
|
|
21
|
+
},
|
|
22
|
+
get: (candidate, options) => filesystem.get(candidate, options),
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
ctx.effect(() => () => filesystem.dispose());
|
|
26
|
+
ctx.on("fs/observed", target => { filesystem.observeHostMutation(target.displayPath); });
|
|
27
|
+
}
|
package/lib/model/history.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -18,9 +18,8 @@ export declare class HistoryService {
|
|
|
18
18
|
readonly root: string;
|
|
19
19
|
private readonly files;
|
|
20
20
|
private readonly source;
|
|
21
|
-
private previousFiles?;
|
|
22
21
|
private readonly otherFiles?;
|
|
23
|
-
constructor(root: string, files: HistoryFiles, source: HistorySource,
|
|
22
|
+
constructor(root: string, files: HistoryFiles, source: HistorySource, otherFiles?: HistoryFiles | undefined);
|
|
24
23
|
check(): void;
|
|
25
24
|
get warnings(): string[];
|
|
26
25
|
get status(): {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { requireThat } from "../../common/validation.js";
|
|
3
3
|
/** Shared prefixes reuse nodes; only consecutive exchanges on one native branch can merge. */
|
|
4
|
-
export function indexHistory(session, previous, host = "pi") {
|
|
4
|
+
export function indexHistory(session, previous, host = previous?.host ?? "pi") {
|
|
5
5
|
const id = previous?.id ?? (host === "dsh" ? "dsh-" : "") + randomUUID(), nodes = new Map();
|
|
6
6
|
const old = new Map(previous?.nodes.map(node => [node.id, node])), changed = new Set();
|
|
7
7
|
const add = (value, level, children = []) => {
|
|
@@ -40,7 +40,7 @@ export function indexHistory(session, previous, host = "pi") {
|
|
|
40
40
|
for (const node of nodes.values())
|
|
41
41
|
for (const child of node.children)
|
|
42
42
|
nodes.get(child).parents.push(node.id);
|
|
43
|
-
return { id, sessionId: session.sessionId, name: session.name, nodes: [...nodes.values()] };
|
|
43
|
+
return { host, id, sessionId: session.sessionId, name: session.name, nodes: [...nodes.values()] };
|
|
44
44
|
}
|
|
45
45
|
const page = (values, offset, limit) => ({ values: values.slice(offset, offset + limit),
|
|
46
46
|
nextOffset: offset + limit < values.length ? offset + limit : null });
|
|
@@ -66,7 +66,6 @@ export class HistoryService {
|
|
|
66
66
|
root;
|
|
67
67
|
files;
|
|
68
68
|
source;
|
|
69
|
-
previousFiles;
|
|
70
69
|
otherFiles;
|
|
71
70
|
#records = [];
|
|
72
71
|
#closed = false;
|
|
@@ -75,17 +74,18 @@ export class HistoryService {
|
|
|
75
74
|
#work;
|
|
76
75
|
#sourceWarnings = [];
|
|
77
76
|
#summaryError;
|
|
78
|
-
constructor(root, files, source,
|
|
77
|
+
constructor(root, files, source, otherFiles) {
|
|
79
78
|
this.root = root;
|
|
80
79
|
this.files = files;
|
|
81
80
|
this.source = source;
|
|
82
|
-
this.previousFiles = previousFiles;
|
|
83
81
|
this.otherFiles = otherFiles;
|
|
84
82
|
requireThat(source.host === files.host && (!otherFiles || otherFiles.host !== files.host), "历史来源与归档目录不匹配。");
|
|
85
|
-
requireThat(files.host === "pi" || !previousFiles, "dsh 不导入 Pi 旧归档。");
|
|
86
83
|
}
|
|
87
84
|
check() { requireThat(!this.#closed, "历史服务已关闭。"); }
|
|
88
|
-
get warnings() {
|
|
85
|
+
get warnings() {
|
|
86
|
+
return [...new Set([...this.#sourceWarnings, ...this.files.warnings, ...(this.otherFiles?.warnings ?? []),
|
|
87
|
+
...(this.#summaryError ? [this.files.host + " 摘要:" + this.#summaryError] : [])])];
|
|
88
|
+
}
|
|
89
89
|
get status() {
|
|
90
90
|
return { loaded: this.#loaded, organizing: Boolean(this.#work), pending: this.#loaded ? this.#records.filter(value => value.host === this.files.host)
|
|
91
91
|
.reduce((total, value) => total + value.record.nodes.filter(node => node.kind === "summary" && !node.ready).length, 0) : null, warnings: this.warnings };
|
|
@@ -105,18 +105,13 @@ export class HistoryService {
|
|
|
105
105
|
const warning = (host, error) => { this.check(); signal?.throwIfAborted(); warnings.push(host + ": " + String(error)); };
|
|
106
106
|
let known = [];
|
|
107
107
|
try {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
imports = this.previousFiles.list(this.root);
|
|
114
|
-
importedPrevious = true;
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
warning("pi", error);
|
|
118
|
-
}
|
|
108
|
+
try {
|
|
109
|
+
this.files.migrate(this.root);
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
known = this.files.list();
|
|
119
113
|
}
|
|
114
|
+
const before = known;
|
|
120
115
|
let sessions = [];
|
|
121
116
|
try {
|
|
122
117
|
const scanned = await this.source.scan(location, signal);
|
|
@@ -128,32 +123,20 @@ export class HistoryService {
|
|
|
128
123
|
}
|
|
129
124
|
this.check();
|
|
130
125
|
signal?.throwIfAborted();
|
|
131
|
-
if (sessions.length
|
|
126
|
+
if (sessions.length) {
|
|
132
127
|
known = this.files.control(() => {
|
|
133
128
|
this.check();
|
|
134
129
|
signal?.throwIfAborted();
|
|
135
|
-
const latest = this.files.list(), current = new Map(latest.map(record => [record.sessionId, record]))
|
|
136
|
-
if (this.files.host === "pi") {
|
|
137
|
-
for (const value of latest)
|
|
138
|
-
if ("file" in value) {
|
|
139
|
-
const { id, sessionId, name, nodes } = value, clean = { id, sessionId, name, nodes };
|
|
140
|
-
this.files.save(clean);
|
|
141
|
-
current.set(sessionId, clean);
|
|
142
|
-
}
|
|
143
|
-
for (const { id, sessionId, name, nodes } of imports)
|
|
144
|
-
if (!current.has(sessionId)) {
|
|
145
|
-
const value = { id, sessionId, name, nodes };
|
|
146
|
-
this.files.save(value);
|
|
147
|
-
current.set(sessionId, value);
|
|
148
|
-
imported.add(sessionId);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
130
|
+
const latest = this.files.list(), current = new Map(latest.map(record => [record.sessionId, record]));
|
|
151
131
|
for (const session of sessions) {
|
|
152
132
|
const previous = current.get(session.sessionId), captured = before.find(record => record.sessionId === session.sessionId);
|
|
133
|
+
// An unreadable archive may own this session; avoid assigning it a second root ID.
|
|
134
|
+
if (!previous && this.files.warnings.length)
|
|
135
|
+
continue;
|
|
153
136
|
// A concurrent source update must not be overwritten by this older scan. Summary-only updates remain reusable.
|
|
154
137
|
if (captured && rawSource(captured) !== rawSource(previous))
|
|
155
138
|
continue;
|
|
156
|
-
if (!captured && previous
|
|
139
|
+
if (!captured && previous)
|
|
157
140
|
continue;
|
|
158
141
|
const value = indexHistory(session, previous, this.files.host);
|
|
159
142
|
if (JSON.stringify(previous) !== JSON.stringify(value))
|
|
@@ -163,16 +146,20 @@ export class HistoryService {
|
|
|
163
146
|
return [...current.values()];
|
|
164
147
|
});
|
|
165
148
|
}
|
|
166
|
-
if (importedPrevious)
|
|
167
|
-
this.previousFiles = undefined;
|
|
168
149
|
}
|
|
169
150
|
catch (error) {
|
|
170
151
|
warning(this.files.host, error);
|
|
171
152
|
}
|
|
172
|
-
records.push(...known.map(record => ({ host:
|
|
153
|
+
records.push(...known.map(record => ({ host: record.host, record })));
|
|
173
154
|
if (this.otherFiles) {
|
|
174
155
|
try {
|
|
175
|
-
|
|
156
|
+
this.otherFiles.migrate(this.root);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
warning(this.otherFiles.host, error);
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
records.push(...this.otherFiles.list().map(record => ({ host: record.host, record })));
|
|
176
163
|
}
|
|
177
164
|
catch (error) {
|
|
178
165
|
warning(this.otherFiles.host, error);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WorkspaceConfiguration } from "../../model/workspace-config.js";
|
|
2
|
+
export declare function workspaceConfigPath(root: string): string;
|
|
3
|
+
/** Read only this workspace's configuration; parent directories never grant activation. */
|
|
4
|
+
export declare function readWorkspaceConfig(root: string): Promise<WorkspaceConfiguration>;
|
|
5
|
+
/** Explicit init opts in this workspace while preserving its other configuration fields. */
|
|
6
|
+
export declare function initializeWorkspaceConfig(root: string): Promise<void>;
|
|
7
|
+
export declare function workspaceDisabledMessage(configuration: WorkspaceConfiguration): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { object } from "../../common/json.js";
|
|
4
|
+
import { readJsonFile, writeJsonFile } from "../../storage/atomic-json.js";
|
|
5
|
+
function config(value) {
|
|
6
|
+
if (!object(value) || value.enabled !== undefined && typeof value.enabled !== "boolean") {
|
|
7
|
+
throw new Error("配置必须是 JSON 对象,enabled 必须是布尔值。");
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
export function workspaceConfigPath(root) {
|
|
12
|
+
return join(root, ".pbot", "config.json");
|
|
13
|
+
}
|
|
14
|
+
/** Read only this workspace's configuration; parent directories never grant activation. */
|
|
15
|
+
export async function readWorkspaceConfig(root) {
|
|
16
|
+
const path = workspaceConfigPath(root);
|
|
17
|
+
let content;
|
|
18
|
+
try {
|
|
19
|
+
content = await readFile(path, "utf8");
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (error.code === "ENOENT")
|
|
23
|
+
return { path, present: false, enabled: false };
|
|
24
|
+
return { path, present: true, enabled: false, error: `无法读取工作区配置 ${path}: ${String(error)}` };
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const value = config(JSON.parse(content));
|
|
28
|
+
return { path, present: true, enabled: value.enabled === true };
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
return { path, present: true, enabled: false, error: `工作区配置无效 ${path}: ${String(error)}` };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Explicit init opts in this workspace while preserving its other configuration fields. */
|
|
35
|
+
export async function initializeWorkspaceConfig(root) {
|
|
36
|
+
const current = await readWorkspaceConfig(root);
|
|
37
|
+
if (current.error)
|
|
38
|
+
throw new Error(current.error);
|
|
39
|
+
if (current.enabled)
|
|
40
|
+
return;
|
|
41
|
+
const directory = dirname(current.path);
|
|
42
|
+
await mkdir(directory, { recursive: true });
|
|
43
|
+
const value = current.present ? readJsonFile(directory, "config.json", config) : {};
|
|
44
|
+
const written = writeJsonFile(directory, "config.json", { ...value, enabled: true }, config, !current.present);
|
|
45
|
+
if (!written)
|
|
46
|
+
await initializeWorkspaceConfig(root);
|
|
47
|
+
}
|
|
48
|
+
export function workspaceDisabledMessage(configuration) {
|
|
49
|
+
return configuration.error ?? `Personal Bot 未在此工作区启用。请执行 /pbot init 初始化并启用;配置文件:${configuration.path}`;
|
|
50
|
+
}
|
|
@@ -2,6 +2,14 @@ import { lstat, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { requireThat } from "../../common/validation.js";
|
|
4
4
|
import { resourceRoot } from "../../common/paths.js";
|
|
5
|
+
import { initializeWorkspaceConfig, readWorkspaceConfig } from "./workspace-config.js";
|
|
6
|
+
export async function initializeWorkspace(root) {
|
|
7
|
+
const configuration = await readWorkspaceConfig(root);
|
|
8
|
+
if (configuration.error)
|
|
9
|
+
throw new Error(configuration.error);
|
|
10
|
+
await prepareWorkspace(root);
|
|
11
|
+
await initializeWorkspaceConfig(root);
|
|
12
|
+
}
|
|
5
13
|
export const directories = ["areas", "projects", "knowledge", "providers", "scratch", "agents"];
|
|
6
14
|
export async function prepareWorkspace(root) {
|
|
7
15
|
for (const part of directories) {
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { HistoryHost, HistoryRecord } from "../model/history.js";
|
|
2
2
|
export declare class HistoryFiles {
|
|
3
|
+
#private;
|
|
3
4
|
readonly directory: string;
|
|
4
5
|
readonly host: HistoryHost;
|
|
5
6
|
constructor(directory: string, host?: HistoryHost);
|
|
6
|
-
|
|
7
|
+
get warnings(): readonly string[];
|
|
8
|
+
list(): HistoryRecord[];
|
|
9
|
+
/** Publish each legacy archive with its directory's host before removing the old copy. */
|
|
10
|
+
migrate(root: string): void;
|
|
7
11
|
/** The callback is synchronous: no SDK reads or model work can hold this source's write lock. */
|
|
8
12
|
control<T>(operation: () => T): T;
|
|
9
13
|
save(value: HistoryRecord): void;
|