galaxy-opc-plugin 0.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.
Files changed (46) hide show
  1. package/README.md +166 -0
  2. package/index.ts +145 -0
  3. package/openclaw.plugin.json +17 -0
  4. package/package.json +47 -0
  5. package/skills/basic-crm/SKILL.md +81 -0
  6. package/skills/basic-finance/SKILL.md +100 -0
  7. package/skills/business-monitoring/SKILL.md +120 -0
  8. package/skills/company-lifecycle/SKILL.md +99 -0
  9. package/skills/company-registration/SKILL.md +80 -0
  10. package/skills/finance-tax/SKILL.md +150 -0
  11. package/skills/hr-assistant/SKILL.md +127 -0
  12. package/skills/investment-management/SKILL.md +101 -0
  13. package/skills/legal-assistant/SKILL.md +113 -0
  14. package/skills/media-ops/SKILL.md +101 -0
  15. package/skills/procurement-management/SKILL.md +91 -0
  16. package/skills/project-management/SKILL.md +125 -0
  17. package/src/api/companies.ts +193 -0
  18. package/src/api/dashboard.ts +25 -0
  19. package/src/api/routes.ts +14 -0
  20. package/src/db/index.ts +63 -0
  21. package/src/db/migrations.ts +67 -0
  22. package/src/db/schema.ts +518 -0
  23. package/src/db/sqlite-adapter.ts +366 -0
  24. package/src/opc/company-manager.ts +82 -0
  25. package/src/opc/context-injector.ts +186 -0
  26. package/src/opc/reminder-service.ts +289 -0
  27. package/src/opc/types.ts +330 -0
  28. package/src/opc/workspace-factory.ts +189 -0
  29. package/src/tools/acquisition-tool.ts +150 -0
  30. package/src/tools/asset-package-tool.ts +283 -0
  31. package/src/tools/finance-tool.ts +244 -0
  32. package/src/tools/hr-tool.ts +211 -0
  33. package/src/tools/investment-tool.ts +201 -0
  34. package/src/tools/legal-tool.ts +191 -0
  35. package/src/tools/lifecycle-tool.ts +251 -0
  36. package/src/tools/media-tool.ts +174 -0
  37. package/src/tools/monitoring-tool.ts +207 -0
  38. package/src/tools/opb-tool.ts +193 -0
  39. package/src/tools/opc-tool.ts +206 -0
  40. package/src/tools/procurement-tool.ts +191 -0
  41. package/src/tools/project-tool.ts +203 -0
  42. package/src/tools/schemas.ts +163 -0
  43. package/src/tools/staff-tool.ts +211 -0
  44. package/src/utils/tool-helper.ts +16 -0
  45. package/src/web/config-ui.ts +3501 -0
  46. package/src/web/landing-page.ts +269 -0
@@ -0,0 +1,366 @@
1
+ /**
2
+ * 星环OPC中心 — SQLite 数据库适配器
3
+ */
4
+
5
+ import Database from "better-sqlite3";
6
+ import type {
7
+ OpcCompany,
8
+ OpcCompanyStatus,
9
+ OpcContact,
10
+ OpcEmployee,
11
+ OpcTransaction,
12
+ } from "../opc/types.js";
13
+ import type { OpcDatabase } from "./index.js";
14
+ import { runMigrations } from "./migrations.js";
15
+ import { OPC_INDEXES, OPC_TABLES } from "./schema.js";
16
+
17
+ function generateId(): string {
18
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
19
+ }
20
+
21
+ export class SqliteAdapter implements OpcDatabase {
22
+ private db: Database.Database;
23
+
24
+ constructor(dbPath: string) {
25
+ this.db = new Database(dbPath);
26
+ this.db.pragma("journal_mode = WAL");
27
+ this.db.pragma("foreign_keys = ON");
28
+ this.initialize();
29
+ }
30
+
31
+ private initialize(): void {
32
+ for (const sql of Object.values(OPC_TABLES)) {
33
+ this.db.exec(sql);
34
+ }
35
+ for (const idx of OPC_INDEXES) {
36
+ this.db.exec(idx);
37
+ }
38
+ runMigrations(this.db);
39
+ }
40
+
41
+ /** 通用查询方法,供 Phase 2 工具使用 */
42
+ query(sql: string, ...params: unknown[]): unknown[] {
43
+ return this.db.prepare(sql).all(...params);
44
+ }
45
+
46
+ /** 通用单行查询 */
47
+ queryOne(sql: string, ...params: unknown[]): unknown | null {
48
+ return this.db.prepare(sql).get(...params) ?? null;
49
+ }
50
+
51
+ /** 通用执行 */
52
+ execute(sql: string, ...params: unknown[]): { changes: number } {
53
+ const result = this.db.prepare(sql).run(...params);
54
+ return { changes: result.changes };
55
+ }
56
+
57
+ /** 生成 ID(公开版本供工具使用) */
58
+ genId(): string {
59
+ return generateId();
60
+ }
61
+
62
+ close(): void {
63
+ this.db.close();
64
+ }
65
+
66
+ // ── Companies ──────────────────────────────────────────────
67
+
68
+ createCompany(data: Omit<OpcCompany, "id" | "created_at" | "updated_at">): OpcCompany {
69
+ const id = generateId();
70
+ const now = new Date().toISOString();
71
+ this.db
72
+ .prepare(
73
+ `INSERT INTO opc_companies (id, name, industry, owner_name, owner_contact, status, registered_capital, description, created_at, updated_at)
74
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
75
+ )
76
+ .run(
77
+ id,
78
+ data.name,
79
+ data.industry,
80
+ data.owner_name,
81
+ data.owner_contact,
82
+ data.status,
83
+ data.registered_capital,
84
+ data.description,
85
+ now,
86
+ now,
87
+ );
88
+ return this.getCompany(id)!;
89
+ }
90
+
91
+ getCompany(id: string): OpcCompany | null {
92
+ return (this.db.prepare("SELECT * FROM opc_companies WHERE id = ?").get(id) as OpcCompany) ?? null;
93
+ }
94
+
95
+ listCompanies(status?: OpcCompanyStatus): OpcCompany[] {
96
+ if (status) {
97
+ return this.db
98
+ .prepare("SELECT * FROM opc_companies WHERE status = ? ORDER BY created_at DESC")
99
+ .all(status) as OpcCompany[];
100
+ }
101
+ return this.db
102
+ .prepare("SELECT * FROM opc_companies ORDER BY created_at DESC")
103
+ .all() as OpcCompany[];
104
+ }
105
+
106
+ updateCompany(id: string, data: Partial<OpcCompany>): OpcCompany | null {
107
+ const existing = this.getCompany(id);
108
+ if (!existing) return null;
109
+
110
+ const ALLOWED = new Set(["name", "industry", "owner_name", "owner_contact",
111
+ "status", "registered_capital", "description"]);
112
+
113
+ const fields: string[] = [];
114
+ const values: unknown[] = [];
115
+
116
+ for (const [key, val] of Object.entries(data)) {
117
+ if (!ALLOWED.has(key)) continue;
118
+ fields.push(`${key} = ?`);
119
+ values.push(val);
120
+ }
121
+ fields.push("updated_at = ?");
122
+ values.push(new Date().toISOString());
123
+ values.push(id);
124
+
125
+ this.db.prepare(`UPDATE opc_companies SET ${fields.join(", ")} WHERE id = ?`).run(...values);
126
+ return this.getCompany(id);
127
+ }
128
+
129
+ deleteCompany(id: string): boolean {
130
+ const result = this.db.prepare("DELETE FROM opc_companies WHERE id = ?").run(id);
131
+ return result.changes > 0;
132
+ }
133
+
134
+ // ── Employees ──────────────────────────────────────────────
135
+
136
+ createEmployee(data: Omit<OpcEmployee, "id" | "created_at">): OpcEmployee {
137
+ const id = generateId();
138
+ const now = new Date().toISOString();
139
+ this.db
140
+ .prepare(
141
+ `INSERT INTO opc_employees (id, company_id, name, role, skills, status, created_at)
142
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
143
+ )
144
+ .run(id, data.company_id, data.name, data.role, data.skills, data.status, now);
145
+ return this.getEmployee(id)!;
146
+ }
147
+
148
+ getEmployee(id: string): OpcEmployee | null {
149
+ return (this.db.prepare("SELECT * FROM opc_employees WHERE id = ?").get(id) as OpcEmployee) ?? null;
150
+ }
151
+
152
+ listEmployees(companyId: string): OpcEmployee[] {
153
+ return this.db
154
+ .prepare("SELECT * FROM opc_employees WHERE company_id = ? ORDER BY created_at DESC")
155
+ .all(companyId) as OpcEmployee[];
156
+ }
157
+
158
+ // ── Transactions ───────────────────────────────────────────
159
+
160
+ createTransaction(data: Omit<OpcTransaction, "id" | "created_at">): OpcTransaction {
161
+ const id = generateId();
162
+ const now = new Date().toISOString();
163
+ this.db
164
+ .prepare(
165
+ `INSERT INTO opc_transactions (id, company_id, type, category, amount, description, counterparty, transaction_date, created_at)
166
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
167
+ )
168
+ .run(
169
+ id,
170
+ data.company_id,
171
+ data.type,
172
+ data.category,
173
+ data.amount,
174
+ data.description,
175
+ data.counterparty,
176
+ data.transaction_date,
177
+ now,
178
+ );
179
+ return this.getTransaction(id)!;
180
+ }
181
+
182
+ getTransaction(id: string): OpcTransaction | null {
183
+ return (
184
+ (this.db.prepare("SELECT * FROM opc_transactions WHERE id = ?").get(id) as OpcTransaction) ?? null
185
+ );
186
+ }
187
+
188
+ listTransactions(
189
+ companyId: string,
190
+ opts?: { type?: string; startDate?: string; endDate?: string; limit?: number },
191
+ ): OpcTransaction[] {
192
+ let sql = "SELECT * FROM opc_transactions WHERE company_id = ?";
193
+ const params: unknown[] = [companyId];
194
+
195
+ if (opts?.type) {
196
+ sql += " AND type = ?";
197
+ params.push(opts.type);
198
+ }
199
+ if (opts?.startDate) {
200
+ sql += " AND transaction_date >= ?";
201
+ params.push(opts.startDate);
202
+ }
203
+ if (opts?.endDate) {
204
+ sql += " AND transaction_date <= ?";
205
+ params.push(opts.endDate);
206
+ }
207
+ sql += " ORDER BY transaction_date DESC";
208
+ if (opts?.limit) {
209
+ sql += " LIMIT ?";
210
+ params.push(opts.limit);
211
+ }
212
+
213
+ return this.db.prepare(sql).all(...params) as OpcTransaction[];
214
+ }
215
+
216
+ getFinanceSummary(
217
+ companyId: string,
218
+ startDate?: string,
219
+ endDate?: string,
220
+ ): { total_income: number; total_expense: number; net: number; count: number } {
221
+ let sql = `
222
+ SELECT
223
+ COALESCE(SUM(CASE WHEN type='income' THEN amount ELSE 0 END), 0) as total_income,
224
+ COALESCE(SUM(CASE WHEN type='expense' THEN amount ELSE 0 END), 0) as total_expense,
225
+ COUNT(*) as count
226
+ FROM opc_transactions
227
+ WHERE company_id = ?
228
+ `;
229
+ const params: unknown[] = [companyId];
230
+
231
+ if (startDate) {
232
+ sql += " AND transaction_date >= ?";
233
+ params.push(startDate);
234
+ }
235
+ if (endDate) {
236
+ sql += " AND transaction_date <= ?";
237
+ params.push(endDate);
238
+ }
239
+
240
+ const row = this.db.prepare(sql).get(...params) as {
241
+ total_income: number;
242
+ total_expense: number;
243
+ count: number;
244
+ };
245
+ return {
246
+ total_income: row.total_income,
247
+ total_expense: row.total_expense,
248
+ net: row.total_income - row.total_expense,
249
+ count: row.count,
250
+ };
251
+ }
252
+
253
+ // ── Contacts ───────────────────────────────────────────────
254
+
255
+ createContact(data: Omit<OpcContact, "id" | "created_at" | "updated_at">): OpcContact {
256
+ const id = generateId();
257
+ const now = new Date().toISOString();
258
+ this.db
259
+ .prepare(
260
+ `INSERT INTO opc_contacts (id, company_id, name, phone, email, company_name, tags, notes, last_contact_date, created_at, updated_at)
261
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
262
+ )
263
+ .run(
264
+ id,
265
+ data.company_id,
266
+ data.name,
267
+ data.phone,
268
+ data.email,
269
+ data.company_name,
270
+ data.tags,
271
+ data.notes,
272
+ data.last_contact_date,
273
+ now,
274
+ now,
275
+ );
276
+ return this.getContact(id)!;
277
+ }
278
+
279
+ getContact(id: string): OpcContact | null {
280
+ return (this.db.prepare("SELECT * FROM opc_contacts WHERE id = ?").get(id) as OpcContact) ?? null;
281
+ }
282
+
283
+ listContacts(companyId: string, tag?: string): OpcContact[] {
284
+ if (tag) {
285
+ return this.db
286
+ .prepare(
287
+ "SELECT * FROM opc_contacts WHERE company_id = ? AND tags LIKE ? ORDER BY updated_at DESC",
288
+ )
289
+ .all(companyId, `%${tag}%`) as OpcContact[];
290
+ }
291
+ return this.db
292
+ .prepare("SELECT * FROM opc_contacts WHERE company_id = ? ORDER BY updated_at DESC")
293
+ .all(companyId) as OpcContact[];
294
+ }
295
+
296
+ updateContact(id: string, data: Partial<OpcContact>): OpcContact | null {
297
+ const existing = this.getContact(id);
298
+ if (!existing) return null;
299
+
300
+ const ALLOWED = new Set(["name", "phone", "email", "company_name",
301
+ "tags", "notes", "last_contact_date"]);
302
+
303
+ const fields: string[] = [];
304
+ const values: unknown[] = [];
305
+
306
+ for (const [key, val] of Object.entries(data)) {
307
+ if (!ALLOWED.has(key)) continue;
308
+ fields.push(`${key} = ?`);
309
+ values.push(val);
310
+ }
311
+ fields.push("updated_at = ?");
312
+ values.push(new Date().toISOString());
313
+ values.push(id);
314
+
315
+ this.db.prepare(`UPDATE opc_contacts SET ${fields.join(", ")} WHERE id = ?`).run(...values);
316
+ return this.getContact(id);
317
+ }
318
+
319
+ deleteContact(id: string): boolean {
320
+ const result = this.db.prepare("DELETE FROM opc_contacts WHERE id = ?").run(id);
321
+ return result.changes > 0;
322
+ }
323
+
324
+ // ── Dashboard ──────────────────────────────────────────────
325
+
326
+ getDashboardStats(): {
327
+ total_companies: number;
328
+ active_companies: number;
329
+ total_transactions: number;
330
+ total_contacts: number;
331
+ total_revenue: number;
332
+ total_expense: number;
333
+ } {
334
+ const companies = this.db
335
+ .prepare(
336
+ `SELECT
337
+ COUNT(*) as total,
338
+ SUM(CASE WHEN status='active' THEN 1 ELSE 0 END) as active
339
+ FROM opc_companies`,
340
+ )
341
+ .get() as { total: number; active: number };
342
+
343
+ const transactions = this.db
344
+ .prepare(
345
+ `SELECT
346
+ COUNT(*) as total,
347
+ COALESCE(SUM(CASE WHEN type='income' THEN amount ELSE 0 END), 0) as revenue,
348
+ COALESCE(SUM(CASE WHEN type='expense' THEN amount ELSE 0 END), 0) as expense
349
+ FROM opc_transactions`,
350
+ )
351
+ .get() as { total: number; revenue: number; expense: number };
352
+
353
+ const contacts = this.db
354
+ .prepare("SELECT COUNT(*) as total FROM opc_contacts")
355
+ .get() as { total: number };
356
+
357
+ return {
358
+ total_companies: companies.total,
359
+ active_companies: companies.active,
360
+ total_transactions: transactions.total,
361
+ total_contacts: contacts.total,
362
+ total_revenue: transactions.revenue,
363
+ total_expense: transactions.expense,
364
+ };
365
+ }
366
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * 星环OPC中心 — 公司管理器
3
+ *
4
+ * 负责公司 CRUD、状态流转和后台服务。
5
+ */
6
+
7
+ import type { OpcDatabase } from "../db/index.js";
8
+ import type { OpcCompany, OpcCompanyStatus } from "./types.js";
9
+
10
+ /** 合法的状态流转规则 */
11
+ const VALID_TRANSITIONS: Record<OpcCompanyStatus, OpcCompanyStatus[]> = {
12
+ pending: ["active", "terminated"],
13
+ active: ["suspended", "acquired", "packaged", "terminated"],
14
+ suspended: ["active", "terminated"],
15
+ acquired: ["terminated"],
16
+ packaged: ["terminated"],
17
+ terminated: [],
18
+ };
19
+
20
+ export class CompanyManager {
21
+ constructor(private db: OpcDatabase) {}
22
+
23
+ /** 注册新公司(状态为 pending) */
24
+ registerCompany(data: {
25
+ name: string;
26
+ industry: string;
27
+ owner_name: string;
28
+ owner_contact?: string;
29
+ registered_capital?: number;
30
+ description?: string;
31
+ }): OpcCompany {
32
+ return this.db.createCompany({
33
+ name: data.name,
34
+ industry: data.industry,
35
+ owner_name: data.owner_name,
36
+ owner_contact: data.owner_contact ?? "",
37
+ status: "pending",
38
+ registered_capital: data.registered_capital ?? 0,
39
+ description: data.description ?? "",
40
+ });
41
+ }
42
+
43
+ /** 激活公司 */
44
+ activateCompany(companyId: string): OpcCompany | null {
45
+ return this.transitionStatus(companyId, "active");
46
+ }
47
+
48
+ /** 变更公司状态(校验合法流转) */
49
+ transitionStatus(companyId: string, newStatus: OpcCompanyStatus): OpcCompany | null {
50
+ const company = this.db.getCompany(companyId);
51
+ if (!company) return null;
52
+
53
+ const allowed = VALID_TRANSITIONS[company.status];
54
+ if (!allowed.includes(newStatus)) {
55
+ throw new Error(
56
+ `不允许从 "${company.status}" 变更为 "${newStatus}"。` +
57
+ `允许的目标状态: ${allowed.join(", ") || "无(终态)"}`,
58
+ );
59
+ }
60
+
61
+ return this.db.updateCompany(companyId, { status: newStatus });
62
+ }
63
+
64
+ getCompany(id: string): OpcCompany | null {
65
+ return this.db.getCompany(id);
66
+ }
67
+
68
+ listCompanies(status?: OpcCompanyStatus): OpcCompany[] {
69
+ return this.db.listCompanies(status);
70
+ }
71
+
72
+ updateCompany(
73
+ id: string,
74
+ data: { name?: string; industry?: string; description?: string; owner_contact?: string },
75
+ ): OpcCompany | null {
76
+ return this.db.updateCompany(id, data);
77
+ }
78
+
79
+ deleteCompany(id: string): boolean {
80
+ return this.db.deleteCompany(id);
81
+ }
82
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * 星环OPC中心 — 上下文注入钩子
3
+ *
4
+ * 通过 before_prompt_build 钩子,在每次 Agent 会话中
5
+ * 自动注入公司信息 + AI 员工岗位配置,让 AI 员工了解自己
6
+ * 服务的公司和自己承担的角色职责。
7
+ *
8
+ * 同时提供新手引导:
9
+ * - 数据库无公司时:注入完整的首次使用引导,主动带领用户走 SOP 第一步
10
+ * - 数据库有公司但在普通对话(非 opc-xxx agent)时:注入简短功能菜单提示
11
+ */
12
+
13
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
14
+ import type { OpcDatabase } from "../db/index.js";
15
+
16
+ type StaffRow = { role: string; role_name: string; system_prompt: string };
17
+
18
+ /**
19
+ * 注册上下文注入钩子。
20
+ * 当 agentId 以 "opc-" 开头时,自动注入公司上下文 + 启用的 AI 员工岗位提示词。
21
+ * 当 agentId 不以 "opc-" 开头时,注入新手引导或功能菜单。
22
+ */
23
+ export function registerContextInjector(api: OpenClawPluginApi, db: OpcDatabase): void {
24
+ api.on("before_prompt_build", (_event, ctx) => {
25
+ const agentId = ctx.agentId;
26
+
27
+ // ── 公司专属 Agent:注入公司上下文 ─────────────────────────
28
+ if (agentId && agentId.startsWith("opc-")) {
29
+ const companyId = agentId.slice(4);
30
+ const company = db.getCompany(companyId);
31
+ if (!company) return;
32
+
33
+ const finance = db.getFinanceSummary(companyId);
34
+ const staffRows = db.query(
35
+ `SELECT role, role_name, system_prompt FROM opc_staff_config
36
+ WHERE company_id = ? AND enabled = 1 AND system_prompt != ''
37
+ ORDER BY role`,
38
+ companyId,
39
+ ) as StaffRow[];
40
+
41
+ const lines = [
42
+ "## 当前服务的公司信息",
43
+ "",
44
+ `- **公司名称**: ${company.name}`,
45
+ `- **所属行业**: ${company.industry}`,
46
+ `- **公司状态**: ${company.status}`,
47
+ `- **创办人**: ${company.owner_name}`,
48
+ `- **注册资本**: ${company.registered_capital.toLocaleString()} 元`,
49
+ `- **简介**: ${company.description || "暂无"}`,
50
+ "",
51
+ "## 财务概况",
52
+ "",
53
+ `- **总收入**: ${finance.total_income.toLocaleString()} 元`,
54
+ `- **总支出**: ${finance.total_expense.toLocaleString()} 元`,
55
+ `- **净收支**: ${finance.net.toLocaleString()} 元`,
56
+ `- **交易笔数**: ${finance.count}`,
57
+ ];
58
+
59
+ if (staffRows.length > 0) {
60
+ lines.push("", "## AI 员工团队", "");
61
+ lines.push("你是这家公司的 AI 助理总管,负责接收老板指令并调度 AI 员工完成任务。");
62
+ lines.push("");
63
+ lines.push("**调度方式**:当老板要你安排某个员工去处理某件事时,使用 `sessions_spawn` 工具:");
64
+ lines.push("- `task` 字段:先写员工的角色设定(system prompt),再写具体任务,格式如下:");
65
+ lines.push(" ```");
66
+ lines.push(" [角色设定]");
67
+ lines.push(" {员工的系统提示词}");
68
+ lines.push("");
69
+ lines.push(" [公司信息]");
70
+ lines.push(" {公司名称、行业等基本信息}");
71
+ lines.push("");
72
+ lines.push(" [任务]");
73
+ lines.push(" {老板交代的具体任务}");
74
+ lines.push("");
75
+ lines.push(" [完成后操作]");
76
+ lines.push(` 任务完成后,必须使用 sessions_send 工具将结果发送回请求者:`);
77
+ lines.push(` - sessionKey: "agent:${agentId}:main"`);
78
+ lines.push(" - message: 你的完整工作结果");
79
+ lines.push(" ```");
80
+ lines.push("- 多个员工可以同时派遣,并行工作");
81
+ lines.push("");
82
+ lines.push("**当前公司 AI 员工列表**:");
83
+ lines.push("");
84
+ for (const staff of staffRows) {
85
+ lines.push(`### ${staff.role_name}(岗位代号:${staff.role})`);
86
+ lines.push("**系统提示词**:");
87
+ lines.push(staff.system_prompt);
88
+ lines.push("");
89
+ }
90
+ lines.push("**使用示例**:");
91
+ lines.push("- 老板说「让财务帮我查一下本月收支」→ 你调用 sessions_spawn,task 里先写财务顾问的角色设定,再写查询任务");
92
+ lines.push("- 老板说「让法务和HR同时处理...」→ 你同时发起两个 sessions_spawn,两个员工并行工作");
93
+ lines.push("- 老板直接问你问题(不涉及具体员工)→ 你直接回答,不需要派遣");
94
+ } else {
95
+ lines.push("", "你是这家一人公司的 AI 员工。请基于以上信息为创业者提供专业服务。");
96
+ lines.push("提示:可在管理后台的 AI员工 Tab 点「一键初始化默认岗位」来配置专业 AI 员工团队。");
97
+ }
98
+
99
+ lines.push("使用中文回复。");
100
+ return { prependContext: lines.join("\n") };
101
+ }
102
+
103
+ // ── 普通对话:检测是否需要新手引导 ─────────────────────────
104
+ const companyCount = (db.query("SELECT COUNT(*) as cnt FROM opc_companies") as { cnt: number }[])[0]?.cnt ?? 0;
105
+
106
+ if (companyCount === 0) {
107
+ // 全新安装,没有任何公司 → OPB 方法论新手引导
108
+ return {
109
+ prependContext: [
110
+ "## 你是星环OPC中心的 AI 助手兼一人企业方法论顾问",
111
+ "",
112
+ "用户刚刚安装了「星环OPC中心」插件,这是第一次使用。",
113
+ "",
114
+ "**你的任务:主动引导用户用 OPB 方法论规划他的一人企业,然后帮他注册第一家公司。**",
115
+ "",
116
+ "请按以下步骤引导用户:",
117
+ "",
118
+ "### 第一步:热情介绍",
119
+ "用一段话介绍星环OPC能做什么:",
120
+ "- 帮助规划和运营一人企业(基于《一人企业方法论2.0》框架)",
121
+ "- 提供公司管理、财税、合同、HR、项目等全套工具",
122
+ "- 有 AI 员工团队,可以派遣财务顾问、法务助手、HR专员等",
123
+ "",
124
+ "### 第二步:了解用户现状",
125
+ "询问用户:",
126
+ "- 目前是否已经有业务方向/想法?",
127
+ "- 是否还在职(side project 阶段)还是已经全职创业?",
128
+ "- 主要的专业技能或资源优势是什么?",
129
+ "",
130
+ "### 第三步:根据用户回答给出建议",
131
+ "- 如果有想法 → 用赛道选择框架分析(小众强需求 vs 大众刚需,建议聚焦小众强需求)",
132
+ "- 如果没想法 → 引导发现副产品优势(工作副产品、生活副产品、兴趣副产品)",
133
+ "- 提示:一人企业核心是「以小博大」,找到结构性优势",
134
+ "",
135
+ "### 第四步:引导填写 OPB Canvas(简化版)",
136
+ "告诉用户,在注册公司之前,先用几个问题帮他梳理商业模式:",
137
+ "1. **价值主张**:你能为客户解决什么核心问题?",
138
+ "2. **目标客群**:谁是你最精准的客户(越具体越好)?",
139
+ "3. **竞争策略**:你打算用什么方式避开直接竞争(加入生态/差异化/创造新品类)?",
140
+ "4. **收入来源**:初期通过什么方式变现?",
141
+ "",
142
+ "### 第五步:注册公司",
143
+ "Canvas 梳理完后,帮用户注册第一家公司,调用 opc_manage 工具,action 为 register_company。",
144
+ "",
145
+ "语气要热情、专业、像一位懂创业的朋友。使用中文回复。",
146
+ ].join("\n"),
147
+ };
148
+ }
149
+
150
+ // 已有公司 → 注入简短功能提示,让 AI 知道自己有哪些能力
151
+ const companies = db.query(
152
+ "SELECT id, name, status FROM opc_companies ORDER BY created_at DESC LIMIT 5",
153
+ ) as { id: string; name: string; status: string }[];
154
+
155
+ const companyList = companies.map(c => `- ${c.name}(${c.status})`).join("\n");
156
+
157
+ return {
158
+ prependContext: [
159
+ "## 星环OPC中心 AI 助手",
160
+ "",
161
+ "你是星环OPC中心的 AI 助手兼一人企业方法论顾问,可以帮用户管理旗下的一人公司。",
162
+ "",
163
+ `当前平台共有 ${companyCount} 家公司:`,
164
+ companyList,
165
+ "",
166
+ "**本月 OPB 月报**:建议每月初回顾 MRR(月经常性收入)、资产变化、用户池数据。需要我帮你生成月报吗?",
167
+ "",
168
+ "你能做的事情包括(用户直接用自然语言告诉你即可):",
169
+ "- **公司管理**:注册公司、激活公司、查询公司信息",
170
+ "- **收支记录**:记录收入/支出、查看财务概况",
171
+ "- **合同管理**:创建合同、查询合同、到期提醒",
172
+ "- **AI 员工**:为公司初始化 AI 员工团队(财务/HR/法务/市场等岗位)",
173
+ "- **税务管理**:创建税务申报记录、查询纳税情况",
174
+ "- **投融资**:创建融资轮次、记录投资人",
175
+ "- **项目管理**:创建项目、跟踪任务进度",
176
+ "- **监控告警**:查看系统自动生成的风险告警",
177
+ "- **OPB方法论咨询**:赛道选择、竞争策略、基础设施规划、月报生成",
178
+ "",
179
+ "管理后台:http://localhost:18789/opc/admin",
180
+ "",
181
+ "请用中文回复,根据用户的需求调用合适的工具。",
182
+ ].join("\n"),
183
+ };
184
+ });
185
+ }
186
+