@next-open-ai/openbot 0.1.1
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 +212 -0
- package/dist/agent/agent-dir.d.ts +14 -0
- package/dist/agent/agent-dir.js +75 -0
- package/dist/agent/agent-manager.d.ts +61 -0
- package/dist/agent/agent-manager.js +257 -0
- package/dist/agent/config-manager.d.ts +25 -0
- package/dist/agent/config-manager.js +84 -0
- package/dist/agent/desktop-config.d.ts +15 -0
- package/dist/agent/desktop-config.js +91 -0
- package/dist/agent/run.d.ts +26 -0
- package/dist/agent/run.js +65 -0
- package/dist/agent/skills.d.ts +20 -0
- package/dist/agent/skills.js +86 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +168 -0
- package/dist/gateway/backend-url.d.ts +2 -0
- package/dist/gateway/backend-url.js +11 -0
- package/dist/gateway/clients.d.ts +5 -0
- package/dist/gateway/clients.js +4 -0
- package/dist/gateway/connection-handler.d.ts +6 -0
- package/dist/gateway/connection-handler.js +48 -0
- package/dist/gateway/desktop-config.d.ts +7 -0
- package/dist/gateway/desktop-config.js +25 -0
- package/dist/gateway/index.d.ts +3 -0
- package/dist/gateway/index.js +2 -0
- package/dist/gateway/message-handler.d.ts +5 -0
- package/dist/gateway/message-handler.js +65 -0
- package/dist/gateway/methods/agent-cancel.d.ts +10 -0
- package/dist/gateway/methods/agent-cancel.js +17 -0
- package/dist/gateway/methods/agent-chat.d.ts +8 -0
- package/dist/gateway/methods/agent-chat.js +194 -0
- package/dist/gateway/methods/connect.d.ts +8 -0
- package/dist/gateway/methods/connect.js +15 -0
- package/dist/gateway/methods/install-skill-from-path.d.ts +13 -0
- package/dist/gateway/methods/install-skill-from-path.js +48 -0
- package/dist/gateway/methods/run-scheduled-task.d.ts +13 -0
- package/dist/gateway/methods/run-scheduled-task.js +164 -0
- package/dist/gateway/server.d.ts +10 -0
- package/dist/gateway/server.js +268 -0
- package/dist/gateway/types.d.ts +76 -0
- package/dist/gateway/types.js +1 -0
- package/dist/gateway/utils.d.ts +22 -0
- package/dist/gateway/utils.js +67 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/memory/build-summary.d.ts +6 -0
- package/dist/memory/build-summary.js +27 -0
- package/dist/memory/compaction-extension.d.ts +6 -0
- package/dist/memory/compaction-extension.js +23 -0
- package/dist/memory/embedding.d.ts +10 -0
- package/dist/memory/embedding.js +22 -0
- package/dist/memory/index.d.ts +29 -0
- package/dist/memory/index.js +66 -0
- package/dist/memory/types.d.ts +16 -0
- package/dist/memory/types.js +1 -0
- package/dist/memory/vector-store.d.ts +15 -0
- package/dist/memory/vector-store.js +65 -0
- package/dist/server/agent-config/agent-config.controller.d.ts +30 -0
- package/dist/server/agent-config/agent-config.controller.js +83 -0
- package/dist/server/agent-config/agent-config.module.d.ts +2 -0
- package/dist/server/agent-config/agent-config.module.js +19 -0
- package/dist/server/agent-config/agent-config.service.d.ts +34 -0
- package/dist/server/agent-config/agent-config.service.js +171 -0
- package/dist/server/agents/agents.controller.d.ts +41 -0
- package/dist/server/agents/agents.controller.js +120 -0
- package/dist/server/agents/agents.gateway.d.ts +21 -0
- package/dist/server/agents/agents.gateway.js +103 -0
- package/dist/server/agents/agents.module.d.ts +2 -0
- package/dist/server/agents/agents.module.js +20 -0
- package/dist/server/agents/agents.service.d.ts +63 -0
- package/dist/server/agents/agents.service.js +167 -0
- package/dist/server/app.module.d.ts +2 -0
- package/dist/server/app.module.js +36 -0
- package/dist/server/auth/auth.controller.d.ts +20 -0
- package/dist/server/auth/auth.controller.js +64 -0
- package/dist/server/auth/auth.module.d.ts +2 -0
- package/dist/server/auth/auth.module.js +19 -0
- package/dist/server/config/config.controller.d.ts +51 -0
- package/dist/server/config/config.controller.js +81 -0
- package/dist/server/config/config.module.d.ts +2 -0
- package/dist/server/config/config.module.js +19 -0
- package/dist/server/config/config.service.d.ts +34 -0
- package/dist/server/config/config.service.js +91 -0
- package/dist/server/database/database.module.d.ts +2 -0
- package/dist/server/database/database.module.js +18 -0
- package/dist/server/database/database.service.d.ts +11 -0
- package/dist/server/database/database.service.js +137 -0
- package/dist/server/main.d.ts +1 -0
- package/dist/server/main.js +18 -0
- package/dist/server/skills/skills.controller.d.ts +63 -0
- package/dist/server/skills/skills.controller.js +194 -0
- package/dist/server/skills/skills.module.d.ts +2 -0
- package/dist/server/skills/skills.module.js +22 -0
- package/dist/server/skills/skills.service.d.ts +63 -0
- package/dist/server/skills/skills.service.js +324 -0
- package/dist/server/tasks/tasks.controller.d.ts +52 -0
- package/dist/server/tasks/tasks.controller.js +163 -0
- package/dist/server/tasks/tasks.module.d.ts +2 -0
- package/dist/server/tasks/tasks.module.js +22 -0
- package/dist/server/tasks/tasks.service.d.ts +84 -0
- package/dist/server/tasks/tasks.service.js +313 -0
- package/dist/server/usage/usage.controller.d.ts +12 -0
- package/dist/server/usage/usage.controller.js +46 -0
- package/dist/server/usage/usage.module.d.ts +2 -0
- package/dist/server/usage/usage.module.js +19 -0
- package/dist/server/usage/usage.service.d.ts +21 -0
- package/dist/server/usage/usage.service.js +55 -0
- package/dist/server/users/users.controller.d.ts +35 -0
- package/dist/server/users/users.controller.js +69 -0
- package/dist/server/users/users.module.d.ts +2 -0
- package/dist/server/users/users.module.js +19 -0
- package/dist/server/users/users.service.d.ts +39 -0
- package/dist/server/users/users.service.js +140 -0
- package/dist/server/workspace/workspace.controller.d.ts +24 -0
- package/dist/server/workspace/workspace.controller.js +132 -0
- package/dist/server/workspace/workspace.module.d.ts +2 -0
- package/dist/server/workspace/workspace.module.js +21 -0
- package/dist/server/workspace/workspace.service.d.ts +25 -0
- package/dist/server/workspace/workspace.service.js +103 -0
- package/dist/tools/browser-tool.d.ts +10 -0
- package/dist/tools/browser-tool.js +362 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +3 -0
- package/dist/tools/install-skill-tool.d.ts +9 -0
- package/dist/tools/install-skill-tool.js +77 -0
- package/dist/tools/save-experience-tool.d.ts +5 -0
- package/dist/tools/save-experience-tool.js +54 -0
- package/package.json +80 -0
- package/skills/agent-browser/SKILL.md +207 -0
- package/skills/agent-browser/references/authentication.md +202 -0
- package/skills/agent-browser/references/commands.md +259 -0
- package/skills/agent-browser/references/proxy-support.md +188 -0
- package/skills/agent-browser/references/session-management.md +193 -0
- package/skills/agent-browser/references/snapshot-refs.md +194 -0
- package/skills/agent-browser/references/video-recording.md +173 -0
- package/skills/agent-browser/templates/authenticated-session.sh +97 -0
- package/skills/agent-browser/templates/capture-workflow.sh +69 -0
- package/skills/agent-browser/templates/form-automation.sh +62 -0
- package/skills/find-skills/SKILL.md +140 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { AuthStorage } from "@mariozechner/pi-coding-agent";
|
|
4
|
+
import { getOpenbotAgentDir, ensureDefaultAgentDir } from "./agent-dir.js";
|
|
5
|
+
/**
|
|
6
|
+
* Manage agent configuration (auth.json, models.json)
|
|
7
|
+
*/
|
|
8
|
+
export class ConfigManager {
|
|
9
|
+
agentDir;
|
|
10
|
+
authStorage;
|
|
11
|
+
constructor(agentDir = getOpenbotAgentDir()) {
|
|
12
|
+
this.agentDir = agentDir;
|
|
13
|
+
ensureDefaultAgentDir(this.agentDir);
|
|
14
|
+
const authPath = join(this.agentDir, "auth.json");
|
|
15
|
+
this.authStorage = new AuthStorage(authPath);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Save API key for a provider
|
|
19
|
+
*/
|
|
20
|
+
async login(provider, apiKey) {
|
|
21
|
+
this.authStorage.set(provider, {
|
|
22
|
+
type: "api_key",
|
|
23
|
+
key: apiKey
|
|
24
|
+
});
|
|
25
|
+
console.log(`[config] Successfully saved API key for provider: ${provider}`);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Update default model for a provider in models.json
|
|
29
|
+
*/
|
|
30
|
+
async setModel(provider, modelId) {
|
|
31
|
+
const modelsPath = join(this.agentDir, "models.json");
|
|
32
|
+
if (!existsSync(modelsPath)) {
|
|
33
|
+
throw new Error(`models.json not found at ${modelsPath}`);
|
|
34
|
+
}
|
|
35
|
+
const config = JSON.parse(readFileSync(modelsPath, "utf-8"));
|
|
36
|
+
if (!config.providers[provider]) {
|
|
37
|
+
throw new Error(`Provider "${provider}" not found in models.json`);
|
|
38
|
+
}
|
|
39
|
+
// Check if model exists in that provider's list
|
|
40
|
+
const providerConfig = config.providers[provider];
|
|
41
|
+
const modelExists = providerConfig.models?.some((m) => m.id === modelId);
|
|
42
|
+
if (!modelExists) {
|
|
43
|
+
console.warn(`[config] Warning: Model "${modelId}" not found in the defined list for "${provider}". Adding it anyway.`);
|
|
44
|
+
if (!providerConfig.models)
|
|
45
|
+
providerConfig.models = [];
|
|
46
|
+
providerConfig.models.push({
|
|
47
|
+
id: modelId,
|
|
48
|
+
name: modelId,
|
|
49
|
+
contextWindow: 128000,
|
|
50
|
+
supportsTools: true
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// In this simple implementation, we assume the first model in the list
|
|
54
|
+
// might be intended as default by some logic, although the registry
|
|
55
|
+
// usually needs explicit selection.
|
|
56
|
+
// For now, let's just reorder so the selected model is first.
|
|
57
|
+
const modelIndex = providerConfig.models.findIndex((m) => m.id === modelId);
|
|
58
|
+
if (modelIndex > 0) {
|
|
59
|
+
const [model] = providerConfig.models.splice(modelIndex, 1);
|
|
60
|
+
providerConfig.models.unshift(model);
|
|
61
|
+
}
|
|
62
|
+
writeFileSync(modelsPath, JSON.stringify(config, null, 2), "utf-8");
|
|
63
|
+
console.log(`[config] Updated default model for "${provider}" to "${modelId}"`);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* List current configurations
|
|
67
|
+
*/
|
|
68
|
+
list() {
|
|
69
|
+
const modelsPath = join(this.agentDir, "models.json");
|
|
70
|
+
if (!existsSync(modelsPath))
|
|
71
|
+
return [];
|
|
72
|
+
const config = JSON.parse(readFileSync(modelsPath, "utf-8"));
|
|
73
|
+
const results = [];
|
|
74
|
+
for (const [providerId, providerConfig] of Object.entries(config.providers)) {
|
|
75
|
+
const firstModel = providerConfig.models?.[0]?.id || "none";
|
|
76
|
+
results.push({
|
|
77
|
+
provider: providerId,
|
|
78
|
+
model: firstModel,
|
|
79
|
+
hasKey: this.authStorage.has(providerId)
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return results;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface DesktopAgentConfig {
|
|
2
|
+
provider: string;
|
|
3
|
+
model: string;
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* 从 config.json 读取缺省智能体 id(defaultAgentId)。
|
|
8
|
+
* 若未配置、或该 id 在 agents.json 中不存在,则返回 default,即使用主智能体配置创建 session。
|
|
9
|
+
*/
|
|
10
|
+
export declare function getBoundAgentIdForCli(): Promise<string>;
|
|
11
|
+
/**
|
|
12
|
+
* 根据 workspace(agent id)从桌面配置中读取该 agent 的 provider、model 及 API Key。
|
|
13
|
+
* 若文件不存在或解析失败返回 null,调用方使用环境变量或默认值。
|
|
14
|
+
*/
|
|
15
|
+
export declare function loadDesktopAgentConfig(workspace: string): Promise<DesktopAgentConfig | null>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 从桌面端配置目录(~/.openbot/desktop)读取 config.json 与 agents.json,
|
|
3
|
+
* 供 CLI / Gateway 等与桌面设置一致的 provider、model、apiKey。
|
|
4
|
+
*/
|
|
5
|
+
import { readFile } from "fs/promises";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { existsSync } from "fs";
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
function getDesktopDir() {
|
|
10
|
+
const home = process.env.HOME || process.env.USERPROFILE || homedir();
|
|
11
|
+
return join(home, ".openbot", "desktop");
|
|
12
|
+
}
|
|
13
|
+
const DEFAULT_AGENT_ID = "default";
|
|
14
|
+
/**
|
|
15
|
+
* 从 config.json 读取缺省智能体 id(defaultAgentId)。
|
|
16
|
+
* 若未配置、或该 id 在 agents.json 中不存在,则返回 default,即使用主智能体配置创建 session。
|
|
17
|
+
*/
|
|
18
|
+
export async function getBoundAgentIdForCli() {
|
|
19
|
+
const desktopDir = getDesktopDir();
|
|
20
|
+
const configPath = join(desktopDir, "config.json");
|
|
21
|
+
const agentsPath = join(desktopDir, "agents.json");
|
|
22
|
+
let boundId = DEFAULT_AGENT_ID;
|
|
23
|
+
if (existsSync(configPath)) {
|
|
24
|
+
try {
|
|
25
|
+
const raw = await readFile(configPath, "utf-8");
|
|
26
|
+
const config = JSON.parse(raw);
|
|
27
|
+
const id = config.defaultAgentId ? String(config.defaultAgentId).trim() : "";
|
|
28
|
+
if (id)
|
|
29
|
+
boundId = id;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// ignore, use default
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (boundId === DEFAULT_AGENT_ID)
|
|
36
|
+
return DEFAULT_AGENT_ID;
|
|
37
|
+
if (!existsSync(agentsPath))
|
|
38
|
+
return DEFAULT_AGENT_ID;
|
|
39
|
+
try {
|
|
40
|
+
const raw = await readFile(agentsPath, "utf-8");
|
|
41
|
+
const data = JSON.parse(raw);
|
|
42
|
+
const agents = Array.isArray(data.agents) ? data.agents : [];
|
|
43
|
+
const found = agents.some((a) => a.id === boundId);
|
|
44
|
+
return found ? boundId : DEFAULT_AGENT_ID;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return DEFAULT_AGENT_ID;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 根据 workspace(agent id)从桌面配置中读取该 agent 的 provider、model 及 API Key。
|
|
52
|
+
* 若文件不存在或解析失败返回 null,调用方使用环境变量或默认值。
|
|
53
|
+
*/
|
|
54
|
+
export async function loadDesktopAgentConfig(workspace) {
|
|
55
|
+
const desktopDir = getDesktopDir();
|
|
56
|
+
const configPath = join(desktopDir, "config.json");
|
|
57
|
+
const agentsPath = join(desktopDir, "agents.json");
|
|
58
|
+
let config = {};
|
|
59
|
+
if (existsSync(configPath)) {
|
|
60
|
+
try {
|
|
61
|
+
const raw = await readFile(configPath, "utf-8");
|
|
62
|
+
config = JSON.parse(raw);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const agentId = workspace === "default" ? "default" : workspace;
|
|
69
|
+
let provider = config.defaultProvider ?? "deepseek";
|
|
70
|
+
let model = config.defaultModel ?? "deepseek-chat";
|
|
71
|
+
if (existsSync(agentsPath)) {
|
|
72
|
+
try {
|
|
73
|
+
const raw = await readFile(agentsPath, "utf-8");
|
|
74
|
+
const data = JSON.parse(raw);
|
|
75
|
+
const agents = Array.isArray(data.agents) ? data.agents : [];
|
|
76
|
+
const agent = agents.find((a) => a.id === agentId);
|
|
77
|
+
if (agent?.provider)
|
|
78
|
+
provider = agent.provider;
|
|
79
|
+
if (agent?.model)
|
|
80
|
+
model = agent.model;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// ignore
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const provConfig = config.providers?.[provider];
|
|
87
|
+
const apiKey = provConfig?.apiKey && typeof provConfig.apiKey === "string" && provConfig.apiKey.trim()
|
|
88
|
+
? provConfig.apiKey.trim()
|
|
89
|
+
: undefined;
|
|
90
|
+
return { provider, model, apiKey: apiKey ?? undefined };
|
|
91
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface RunOptions {
|
|
2
|
+
/** Workspace name (e.g. "default") */
|
|
3
|
+
workspace?: string;
|
|
4
|
+
/** Additional skill paths */
|
|
5
|
+
skillPaths?: string[];
|
|
6
|
+
/** 用户提示词 */
|
|
7
|
+
userPrompt: string;
|
|
8
|
+
/** 是否仅打印组装后的内容,不调 LLM */
|
|
9
|
+
dryRun?: boolean;
|
|
10
|
+
/** API key,不传则从 pi 的 auth 或 OPENAI_API_KEY 读取 */
|
|
11
|
+
apiKey?: string;
|
|
12
|
+
/** 模型 ID(如 deepseek-chat、qwen-max) */
|
|
13
|
+
model?: string;
|
|
14
|
+
/** Provider(如 deepseek、dashscope、openai) */
|
|
15
|
+
provider?: string;
|
|
16
|
+
/** Agent 配置目录,默认 ~/.openbot/agent */
|
|
17
|
+
agentDir?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface RunResult {
|
|
20
|
+
systemPrompt: string;
|
|
21
|
+
userPrompt: string;
|
|
22
|
+
/** 若调用了 API,则为助手回复 */
|
|
23
|
+
assistantContent?: string;
|
|
24
|
+
dryRun: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare function run(options: RunOptions): Promise<RunResult>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { getOpenbotAgentDir } from "./agent-dir.js";
|
|
2
|
+
import { AgentManager } from "./agent-manager.js";
|
|
3
|
+
export async function run(options) {
|
|
4
|
+
const { workspace = "default", skillPaths = [], userPrompt, dryRun: explicitDryRun, apiKey, model: modelId = "deepseek-chat", provider = "deepseek", agentDir = getOpenbotAgentDir(), } = options;
|
|
5
|
+
const manager = new AgentManager({ agentDir, workspace, skillPaths });
|
|
6
|
+
const { systemPrompt } = await manager.getContext();
|
|
7
|
+
// Determine dry run
|
|
8
|
+
const dryRun = !!explicitDryRun; // Ensure boolean
|
|
9
|
+
const result = {
|
|
10
|
+
systemPrompt,
|
|
11
|
+
userPrompt,
|
|
12
|
+
dryRun,
|
|
13
|
+
};
|
|
14
|
+
if (dryRun) {
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
// Create a temporary session for this run
|
|
18
|
+
const sessionId = `cli-${Date.now()}`;
|
|
19
|
+
const session = await manager.getOrCreateSession(sessionId, {
|
|
20
|
+
provider,
|
|
21
|
+
modelId,
|
|
22
|
+
apiKey,
|
|
23
|
+
});
|
|
24
|
+
// Collect assistant response
|
|
25
|
+
let assistantContent = "";
|
|
26
|
+
session.subscribe((event) => {
|
|
27
|
+
switch (event.type) {
|
|
28
|
+
case "message_update": {
|
|
29
|
+
const evt = event.assistantMessageEvent;
|
|
30
|
+
if (evt.type === "text_delta") {
|
|
31
|
+
assistantContent += evt.delta;
|
|
32
|
+
process.stdout.write(evt.delta);
|
|
33
|
+
}
|
|
34
|
+
else if (evt.type === "thinking_delta") {
|
|
35
|
+
process.stdout.write(evt.delta);
|
|
36
|
+
}
|
|
37
|
+
else if (evt.type === "error") {
|
|
38
|
+
console.error(`\n[model:error] ${evt.error.errorMessage || "Unknown error"}`);
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
case "message_end": {
|
|
43
|
+
if (event.message.role === "assistant" && event.message.errorMessage) {
|
|
44
|
+
console.error(`\n[message:error] ${event.message.errorMessage}`);
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case "tool_execution_start": {
|
|
49
|
+
console.log(`\n[tool:start] ${event.toolName}(${JSON.stringify(event.args)})`);
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case "tool_execution_end": {
|
|
53
|
+
const status = event.isError ? "failed" : "ok";
|
|
54
|
+
console.log(`[tool:end] ${event.toolName} -> ${status}`);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
// Send prompt and wait for completion
|
|
60
|
+
await session.prompt(userPrompt);
|
|
61
|
+
// Clean up session
|
|
62
|
+
manager.deleteSession(sessionId);
|
|
63
|
+
result.assistantContent = assistantContent.trim();
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type Skill } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
export type { Skill };
|
|
3
|
+
export interface LoadSkillsFromDirOptions {
|
|
4
|
+
dir: string;
|
|
5
|
+
source: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 从目录加载 skills(委托给 pi-coding-agent,规则与其一致)
|
|
9
|
+
*/
|
|
10
|
+
export declare function loadSkillsFromDir(options: LoadSkillsFromDirOptions): Skill[];
|
|
11
|
+
/**
|
|
12
|
+
* 从多个路径加载 skills(文件或目录),合并去重(按 name)
|
|
13
|
+
* 目录使用 pi-coding-agent 的 loadSkillsFromDir,单文件使用本地解析
|
|
14
|
+
*/
|
|
15
|
+
export declare function loadSkillsFromPaths(paths: string[], source?: string): Skill[];
|
|
16
|
+
/**
|
|
17
|
+
* 将 Skill[] 格式化为系统提示中的一段(委托给 pi-coding-agent,对齐 agentskills.io)
|
|
18
|
+
*/
|
|
19
|
+
declare function formatSkillsForPrompt(skills: Skill[]): string;
|
|
20
|
+
export { formatSkillsForPrompt };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join, dirname, basename } from "node:path";
|
|
3
|
+
import { formatSkillsForPrompt as piFormatSkillsForPrompt, loadSkillsFromDir as piLoadSkillsFromDir, } from "@mariozechner/pi-coding-agent";
|
|
4
|
+
/**
|
|
5
|
+
* 简单 frontmatter 解析:--- 与 --- 之间的 YAML 风格键值
|
|
6
|
+
*/
|
|
7
|
+
function parseFrontmatter(raw) {
|
|
8
|
+
const match = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
9
|
+
if (!match)
|
|
10
|
+
return {};
|
|
11
|
+
const block = match[1];
|
|
12
|
+
const out = {};
|
|
13
|
+
for (const line of block.split("\n")) {
|
|
14
|
+
const m = line.match(/^\s*([a-z-]+)\s*:\s*(.*)$/);
|
|
15
|
+
if (m)
|
|
16
|
+
out[m[1].trim()] = m[2].trim().replace(/^["']|["']$/g, "");
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
name: out.name,
|
|
20
|
+
description: out.description,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 从单文件加载一个 Skill(与 pi-coding-agent Skill 结构一致)
|
|
25
|
+
*/
|
|
26
|
+
function loadSkillFromFile(filePath, source) {
|
|
27
|
+
try {
|
|
28
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
29
|
+
const { name, description } = parseFrontmatter(raw);
|
|
30
|
+
const skillDir = dirname(filePath);
|
|
31
|
+
const parentDirName = basename(skillDir);
|
|
32
|
+
const finalName = (name || parentDirName).trim();
|
|
33
|
+
const finalDesc = (description || "").trim();
|
|
34
|
+
if (!finalDesc)
|
|
35
|
+
return null;
|
|
36
|
+
return {
|
|
37
|
+
name: finalName,
|
|
38
|
+
description: finalDesc,
|
|
39
|
+
filePath,
|
|
40
|
+
baseDir: skillDir,
|
|
41
|
+
source,
|
|
42
|
+
disableModelInvocation: false,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 从目录加载 skills(委托给 pi-coding-agent,规则与其一致)
|
|
51
|
+
*/
|
|
52
|
+
export function loadSkillsFromDir(options) {
|
|
53
|
+
const result = piLoadSkillsFromDir(options);
|
|
54
|
+
return result.skills ?? [];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 从多个路径加载 skills(文件或目录),合并去重(按 name)
|
|
58
|
+
* 目录使用 pi-coding-agent 的 loadSkillsFromDir,单文件使用本地解析
|
|
59
|
+
*/
|
|
60
|
+
export function loadSkillsFromPaths(paths, source = "user") {
|
|
61
|
+
const byName = new Map();
|
|
62
|
+
for (const p of paths) {
|
|
63
|
+
const resolved = p.startsWith("~") ? join(process.env.HOME || "", p.slice(1)) : p;
|
|
64
|
+
if (!existsSync(resolved))
|
|
65
|
+
continue;
|
|
66
|
+
const stat = statSync(resolved);
|
|
67
|
+
if (stat.isFile() && resolved.endsWith(".md")) {
|
|
68
|
+
const skill = loadSkillFromFile(resolved, source);
|
|
69
|
+
if (skill)
|
|
70
|
+
byName.set(skill.name, skill);
|
|
71
|
+
}
|
|
72
|
+
else if (stat.isDirectory()) {
|
|
73
|
+
for (const skill of loadSkillsFromDir({ dir: resolved, source })) {
|
|
74
|
+
byName.set(skill.name, skill);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return Array.from(byName.values());
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 将 Skill[] 格式化为系统提示中的一段(委托给 pi-coding-agent,对齐 agentskills.io)
|
|
82
|
+
*/
|
|
83
|
+
function formatSkillsForPrompt(skills) {
|
|
84
|
+
return piFormatSkillsForPrompt(skills);
|
|
85
|
+
}
|
|
86
|
+
export { formatSkillsForPrompt };
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { getOpenbotAgentDir } from "./agent/agent-dir.js";
|
|
7
|
+
import { run } from "./agent/run.js";
|
|
8
|
+
import { ConfigManager } from "./agent/config-manager.js";
|
|
9
|
+
import { loadDesktopAgentConfig, getBoundAgentIdForCli } from "./agent/desktop-config.js";
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const PKG = require("../package.json");
|
|
12
|
+
// Based on distillate, the package root is one level up from dist/
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const PKG_ROOT = join(__dirname, "..");
|
|
15
|
+
async function runAction(positionalPrompt, opts) {
|
|
16
|
+
const skillPaths = opts.skillPath || [];
|
|
17
|
+
// 指定智能体:-a/--agent 优先,其次 -w/--workspace(且非 default);不提供则使用配置中的缺省智能体
|
|
18
|
+
const explicitAgent = (opts.agent && String(opts.agent).trim()) ||
|
|
19
|
+
(opts.workspace && opts.workspace !== "default" ? opts.workspace : "");
|
|
20
|
+
const workspace = explicitAgent ? explicitAgent.trim() : await getBoundAgentIdForCli();
|
|
21
|
+
const prompt = (opts.prompt ?? positionalPrompt ?? "").trim();
|
|
22
|
+
if (!prompt) {
|
|
23
|
+
console.error("Error: 请提供提示词(位置参数或 --prompt)");
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
console.error(`[openbot] Using agent: ${workspace}${!explicitAgent ? " (default from config)" : ""}`);
|
|
27
|
+
// 未传 --api-key / --provider / --model 时从桌面配置(~/.openbot/desktop)读取
|
|
28
|
+
const needDesktop = opts.apiKey === undefined || opts.provider === undefined || opts.model === undefined;
|
|
29
|
+
const desktopConfig = needDesktop ? await loadDesktopAgentConfig(workspace) : null;
|
|
30
|
+
const provider = opts.provider ?? desktopConfig?.provider ?? "deepseek";
|
|
31
|
+
const model = opts.model ?? desktopConfig?.model ?? "deepseek-chat";
|
|
32
|
+
const apiKey = opts.apiKey ?? process.env.OPENAI_API_KEY ?? desktopConfig?.apiKey ?? "";
|
|
33
|
+
if (desktopConfig && (desktopConfig.provider || desktopConfig.model)) {
|
|
34
|
+
console.error(`[openbot] Using model: ${provider}/${model} (from desktop config)`);
|
|
35
|
+
}
|
|
36
|
+
if (opts.timing)
|
|
37
|
+
process.env.OPENBOT_TIMING = "1";
|
|
38
|
+
try {
|
|
39
|
+
const result = await run({
|
|
40
|
+
workspace,
|
|
41
|
+
skillPaths,
|
|
42
|
+
userPrompt: prompt,
|
|
43
|
+
dryRun: opts.dryRun ?? false,
|
|
44
|
+
model,
|
|
45
|
+
provider,
|
|
46
|
+
agentDir: opts.agentDir ?? getOpenbotAgentDir(),
|
|
47
|
+
apiKey: apiKey || undefined,
|
|
48
|
+
});
|
|
49
|
+
if (result.dryRun) {
|
|
50
|
+
console.log("=== System prompt (skills) ===");
|
|
51
|
+
console.log(result.systemPrompt);
|
|
52
|
+
console.log("\n=== User prompt ===");
|
|
53
|
+
console.log(result.userPrompt);
|
|
54
|
+
console.log("\n(dry-run: 未调用 LLM,设置 OPENAI_API_KEY 可实际调用)");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// Content is already streamed by run()
|
|
58
|
+
if (!result.dryRun &&
|
|
59
|
+
(result.assistantContent === undefined || result.assistantContent === "")) {
|
|
60
|
+
console.warn("(模型返回为空;若此前有工具调用提示,说明当前为单轮模式,模型会基于技能描述直接以文本回答。)");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
console.error(err);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const program = new Command();
|
|
69
|
+
program
|
|
70
|
+
.name("openbot")
|
|
71
|
+
.description("CLI to run prompts with skill paths (Agent Skills style)")
|
|
72
|
+
.version(PKG.version, "-v, --version", "显示版本号")
|
|
73
|
+
.option("-s, --skill-path <paths...>", "Additional skill paths to load")
|
|
74
|
+
.option("-a, --agent <id>", "指定智能体 ID,不传则使用配置中的缺省智能体")
|
|
75
|
+
.option("-w, --workspace <name>", "Workspace/Agent 名称(同 --agent,兼容旧用法)", "default")
|
|
76
|
+
.option("-p, --prompt <text>", "用户提示词(与位置参数二选一)")
|
|
77
|
+
.option("--dry-run", "只输出组装的 system/user 内容,不调用 LLM")
|
|
78
|
+
.option("--model <id>", "模型 ID", "deepseek-chat")
|
|
79
|
+
.option("--provider <name>", "Provider(pi ModelRegistry);可选 deepseek、dashscope、openai", "deepseek")
|
|
80
|
+
.option("--agent-dir <path>", "Agent 配置目录(默认 ~/.openbot/agent)", getOpenbotAgentDir())
|
|
81
|
+
.option("--api-key <key>", "API Key(不传则使用环境变量 OPENAI_API_KEY)")
|
|
82
|
+
.option("--timing", "打印每轮 LLM 与 tool 耗时到 stderr")
|
|
83
|
+
.option("--max-tool-turns <n>", "最大工具调用轮数(默认 30);可设环境变量 OPENBOT_MAX_TOOL_TURNS", (v) => parseInt(v, 10) || 0, 0)
|
|
84
|
+
.argument("[prompt]", "用户提示词(与 --prompt 二选一)")
|
|
85
|
+
.action(async (positionalPrompt) => {
|
|
86
|
+
await runAction(positionalPrompt, program.opts());
|
|
87
|
+
});
|
|
88
|
+
program.addHelpText("after", `
|
|
89
|
+
Environment:
|
|
90
|
+
DEEPSEEK_API_KEY 默认 provider 为 deepseek 时使用;不设时回退 OPENAI_API_KEY
|
|
91
|
+
OPENAI_API_KEY 通用 API Key(可被 --api-key 覆盖)
|
|
92
|
+
DASHSCOPE_API_KEY provider=dashscope 时使用;不设时回退 OPENAI_API_KEY
|
|
93
|
+
OPENAI_BASE_URL 可选,在 pi 未找到模型时使用的 endpoint
|
|
94
|
+
OPENBOT_AGENT_DIR 缺省 agent 目录(默认 ~/.openbot/agent)
|
|
95
|
+
OPENBOT_TIMING=1 等同 --timing
|
|
96
|
+
OPENBOT_ALLOW_RUN_CODE 缺省 1(启用 run_python);设为 0 关闭
|
|
97
|
+
OPENBOT_MAX_TOOL_TURNS 最大工具轮数(默认 30)
|
|
98
|
+
|
|
99
|
+
Examples:
|
|
100
|
+
openbot "总结一下当前有哪些技能"
|
|
101
|
+
openbot -a my-agent "总结一下当前有哪些技能" 使用指定智能体
|
|
102
|
+
openbot -s ./skills "总结一下当前有哪些技能"
|
|
103
|
+
openbot -s ./my-skills --prompt "用 weather 技能查北京天气" --dry-run
|
|
104
|
+
`);
|
|
105
|
+
// Gateway server command
|
|
106
|
+
program
|
|
107
|
+
.command("gateway")
|
|
108
|
+
.description("Start WebSocket gateway server")
|
|
109
|
+
.option("-p, --port <port>", "Port to listen on", "3000")
|
|
110
|
+
.action(async (options) => {
|
|
111
|
+
const port = parseInt(options.port, 10);
|
|
112
|
+
if (isNaN(port) || port <= 0 || port > 65535) {
|
|
113
|
+
console.error("Error: Invalid port number");
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
const { startGatewayServer } = await import("./gateway/index.js");
|
|
117
|
+
const { close } = await startGatewayServer(port);
|
|
118
|
+
// Handle graceful shutdown
|
|
119
|
+
const shutdown = async () => {
|
|
120
|
+
console.log("\nShutting down...");
|
|
121
|
+
await close();
|
|
122
|
+
process.exit(0);
|
|
123
|
+
};
|
|
124
|
+
process.on("SIGINT", shutdown);
|
|
125
|
+
process.on("SIGTERM", shutdown);
|
|
126
|
+
});
|
|
127
|
+
// Login command
|
|
128
|
+
program
|
|
129
|
+
.command("login")
|
|
130
|
+
.description("Save API key for a provider persistently")
|
|
131
|
+
.argument("<provider>", "Provider name (e.g., deepseek, dashscope, openai)")
|
|
132
|
+
.argument("<apiKey>", "API Key")
|
|
133
|
+
.action(async (provider, apiKey) => {
|
|
134
|
+
const config = new ConfigManager(program.opts().agentDir);
|
|
135
|
+
await config.login(provider, apiKey);
|
|
136
|
+
});
|
|
137
|
+
// Config command
|
|
138
|
+
const configCmd = program.command("config").description("Manage configurations");
|
|
139
|
+
configCmd
|
|
140
|
+
.command("set-model")
|
|
141
|
+
.description("Set default model for a provider")
|
|
142
|
+
.argument("<provider>", "Provider name")
|
|
143
|
+
.argument("<modelId>", "Model ID")
|
|
144
|
+
.action(async (provider, modelId) => {
|
|
145
|
+
const cm = new ConfigManager(program.opts().agentDir);
|
|
146
|
+
await cm.setModel(provider, modelId);
|
|
147
|
+
});
|
|
148
|
+
configCmd
|
|
149
|
+
.command("list")
|
|
150
|
+
.description("List current configurations")
|
|
151
|
+
.action(() => {
|
|
152
|
+
const cm = new ConfigManager(program.opts().agentDir);
|
|
153
|
+
const results = cm.list();
|
|
154
|
+
if (results.length === 0) {
|
|
155
|
+
console.log("No providers found in models.json.");
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
console.table(results.map(r => ({
|
|
159
|
+
Provider: r.provider,
|
|
160
|
+
"Default Model": r.model,
|
|
161
|
+
"Auth Configured": r.hasKey ? "✅ Yes" : "❌ No"
|
|
162
|
+
})));
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
166
|
+
console.error(err);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend base URL for the Nest Desktop Server (e.g. http://localhost:3001).
|
|
3
|
+
* Set by startGatewayServer so agent-chat / usage reporting can POST to server-api.
|
|
4
|
+
*/
|
|
5
|
+
let backendBaseUrl = null;
|
|
6
|
+
export function setBackendBaseUrl(url) {
|
|
7
|
+
backendBaseUrl = url;
|
|
8
|
+
}
|
|
9
|
+
export function getBackendBaseUrl() {
|
|
10
|
+
return backendBaseUrl;
|
|
11
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { send, createEvent } from "./utils.js";
|
|
3
|
+
import { handleMessage } from "./message-handler.js";
|
|
4
|
+
import { connectedClients } from "./clients.js";
|
|
5
|
+
/**
|
|
6
|
+
* Handle new WebSocket connection
|
|
7
|
+
*/
|
|
8
|
+
export function handleConnection(ws, req) {
|
|
9
|
+
const connId = randomUUID();
|
|
10
|
+
const remoteAddr = req.socket.remoteAddress;
|
|
11
|
+
console.log(`New connection: ${connId} from ${remoteAddr}`);
|
|
12
|
+
// Create client object
|
|
13
|
+
const client = {
|
|
14
|
+
id: connId,
|
|
15
|
+
ws,
|
|
16
|
+
authenticated: false,
|
|
17
|
+
connectedAt: Date.now(),
|
|
18
|
+
};
|
|
19
|
+
connectedClients.add(client);
|
|
20
|
+
// Send connection challenge
|
|
21
|
+
send(ws, createEvent("connect.challenge", {
|
|
22
|
+
nonce: randomUUID(),
|
|
23
|
+
ts: Date.now(),
|
|
24
|
+
}));
|
|
25
|
+
// Handle incoming messages
|
|
26
|
+
ws.on("message", async (data) => {
|
|
27
|
+
console.log(`Raw message received from ${connId}, type: ${typeof data}`);
|
|
28
|
+
try {
|
|
29
|
+
// Convert RawData to Buffer
|
|
30
|
+
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data.toString());
|
|
31
|
+
console.log(`Message buffer length: ${buffer.length}`);
|
|
32
|
+
await handleMessage(client, buffer);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
console.error(`Error handling message from ${connId}:`, error);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
// Handle connection close
|
|
39
|
+
ws.on("close", (code, reason) => {
|
|
40
|
+
connectedClients.delete(client);
|
|
41
|
+
const duration = Date.now() - client.connectedAt;
|
|
42
|
+
console.log(`Connection closed: ${connId} (duration: ${duration}ms, code: ${code}, reason: ${reason.toString()})`);
|
|
43
|
+
});
|
|
44
|
+
// Handle errors
|
|
45
|
+
ws.on("error", (error) => {
|
|
46
|
+
console.error(`WebSocket error for ${connId}:`, error);
|
|
47
|
+
});
|
|
48
|
+
}
|