@ai-zen/agents-host 0.0.0-stage → 0.1.0-alpha.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.
Files changed (146) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/LICENSE +21 -0
  3. package/README.md +759 -2
  4. package/dist/app.d.ts +384 -0
  5. package/dist/app.js +1148 -0
  6. package/dist/cli.d.ts +22 -0
  7. package/dist/cli.js +354 -0
  8. package/dist/config.d.ts +97 -0
  9. package/dist/config.js +171 -0
  10. package/dist/handlers/agentHandlers.d.ts +18 -0
  11. package/dist/handlers/agentHandlers.js +20 -0
  12. package/dist/handlers/assetHandlers.d.ts +29 -0
  13. package/dist/handlers/assetHandlers.js +35 -0
  14. package/dist/handlers/capabilities.d.ts +26 -0
  15. package/dist/handlers/capabilities.js +138 -0
  16. package/dist/handlers/completionHandlers.d.ts +18 -0
  17. package/dist/handlers/completionHandlers.js +13 -0
  18. package/dist/handlers/configHandlers.d.ts +31 -0
  19. package/dist/handlers/configHandlers.js +59 -0
  20. package/dist/handlers/conversationHandlers.d.ts +118 -0
  21. package/dist/handlers/conversationHandlers.js +314 -0
  22. package/dist/handlers/handoffHandlers.d.ts +17 -0
  23. package/dist/handlers/handoffHandlers.js +14 -0
  24. package/dist/handlers/hostHandlers.d.ts +39 -0
  25. package/dist/handlers/hostHandlers.js +56 -0
  26. package/dist/handlers/index.d.ts +128 -0
  27. package/dist/handlers/index.js +92 -0
  28. package/dist/handlers/messageHandlers.d.ts +11 -0
  29. package/dist/handlers/messageHandlers.js +10 -0
  30. package/dist/handlers/modelHandlers.d.ts +17 -0
  31. package/dist/handlers/modelHandlers.js +19 -0
  32. package/dist/handlers/noticeHandlers.d.ts +24 -0
  33. package/dist/handlers/noticeHandlers.js +24 -0
  34. package/dist/handlers/questionHandlers.d.ts +16 -0
  35. package/dist/handlers/questionHandlers.js +16 -0
  36. package/dist/handlers/searchHandlers.d.ts +12 -0
  37. package/dist/handlers/searchHandlers.js +12 -0
  38. package/dist/handlers/workspaceHandlers.d.ts +29 -0
  39. package/dist/handlers/workspaceHandlers.js +47 -0
  40. package/dist/i18n/en.d.ts +243 -0
  41. package/dist/i18n/en.js +270 -0
  42. package/dist/i18n/index.d.ts +43 -0
  43. package/dist/i18n/index.js +72 -0
  44. package/dist/i18n/zh.d.ts +240 -0
  45. package/dist/i18n/zh.js +257 -0
  46. package/dist/index.d.ts +17 -0
  47. package/dist/index.js +15 -0
  48. package/dist/net/UploadReceiver.d.ts +186 -0
  49. package/dist/net/UploadReceiver.js +494 -0
  50. package/dist/net/WsHub.d.ts +231 -0
  51. package/dist/net/WsHub.js +689 -0
  52. package/dist/net/auth.d.ts +73 -0
  53. package/dist/net/auth.js +74 -0
  54. package/dist/net/discovery.d.ts +72 -0
  55. package/dist/net/discovery.js +171 -0
  56. package/dist/net/index.d.ts +14 -0
  57. package/dist/net/index.js +10 -0
  58. package/dist/runtime/ChatRunner.d.ts +374 -0
  59. package/dist/runtime/ChatRunner.js +1204 -0
  60. package/dist/runtime/ConversationDrafts.d.ts +43 -0
  61. package/dist/runtime/ConversationDrafts.js +47 -0
  62. package/dist/runtime/RunRegistry.d.ts +79 -0
  63. package/dist/runtime/RunRegistry.js +108 -0
  64. package/dist/runtime/ScopeRegistry.d.ts +153 -0
  65. package/dist/runtime/ScopeRegistry.js +259 -0
  66. package/dist/runtime/TransientRunner.d.ts +90 -0
  67. package/dist/runtime/TransientRunner.js +179 -0
  68. package/dist/runtime/index.d.ts +14 -0
  69. package/dist/runtime/index.js +10 -0
  70. package/dist/services/AgentService.d.ts +150 -0
  71. package/dist/services/AgentService.js +718 -0
  72. package/dist/services/AssetService.d.ts +338 -0
  73. package/dist/services/AssetService.js +739 -0
  74. package/dist/services/AutoNaming.d.ts +92 -0
  75. package/dist/services/AutoNaming.js +235 -0
  76. package/dist/services/CompletionService.d.ts +122 -0
  77. package/dist/services/CompletionService.js +442 -0
  78. package/dist/services/ConfigService.d.ts +171 -0
  79. package/dist/services/ConfigService.js +695 -0
  80. package/dist/services/ConversationService.d.ts +274 -0
  81. package/dist/services/ConversationService.js +902 -0
  82. package/dist/services/CredentialService.d.ts +33 -0
  83. package/dist/services/CredentialService.js +86 -0
  84. package/dist/services/DiscoveryService.d.ts +182 -0
  85. package/dist/services/DiscoveryService.js +998 -0
  86. package/dist/services/HandoffService.d.ts +189 -0
  87. package/dist/services/HandoffService.js +414 -0
  88. package/dist/services/Logger.d.ts +130 -0
  89. package/dist/services/Logger.js +256 -0
  90. package/dist/services/MessageService.d.ts +88 -0
  91. package/dist/services/MessageService.js +280 -0
  92. package/dist/services/ModelService.d.ts +89 -0
  93. package/dist/services/ModelService.js +489 -0
  94. package/dist/services/NoticeService.d.ts +93 -0
  95. package/dist/services/NoticeService.js +217 -0
  96. package/dist/services/QuestionService.d.ts +184 -0
  97. package/dist/services/QuestionService.js +457 -0
  98. package/dist/services/SearchService.d.ts +54 -0
  99. package/dist/services/SearchService.js +117 -0
  100. package/dist/services/WorkspaceService.d.ts +96 -0
  101. package/dist/services/WorkspaceService.js +213 -0
  102. package/dist/services/imageSize.d.ts +41 -0
  103. package/dist/services/imageSize.js +146 -0
  104. package/dist/services/index.d.ts +20 -0
  105. package/dist/services/index.js +15 -0
  106. package/dist/services/secretMask.d.ts +24 -0
  107. package/dist/services/secretMask.js +34 -0
  108. package/dist/storage/AssetRepository.d.ts +87 -0
  109. package/dist/storage/AssetRepository.js +180 -0
  110. package/dist/storage/ConversationRepository.d.ts +55 -0
  111. package/dist/storage/ConversationRepository.js +128 -0
  112. package/dist/storage/CredentialRepository.d.ts +28 -0
  113. package/dist/storage/CredentialRepository.js +47 -0
  114. package/dist/storage/Database.d.ts +95 -0
  115. package/dist/storage/Database.js +180 -0
  116. package/dist/storage/MessageRepository.d.ts +104 -0
  117. package/dist/storage/MessageRepository.js +323 -0
  118. package/dist/storage/MetaRepository.d.ts +21 -0
  119. package/dist/storage/MetaRepository.js +33 -0
  120. package/dist/storage/NoticeRepository.d.ts +31 -0
  121. package/dist/storage/NoticeRepository.js +89 -0
  122. package/dist/storage/QuestionRepository.d.ts +34 -0
  123. package/dist/storage/QuestionRepository.js +96 -0
  124. package/dist/storage/WorkspaceRepository.d.ts +25 -0
  125. package/dist/storage/WorkspaceRepository.js +44 -0
  126. package/dist/storage/dbWorker.d.ts +10 -0
  127. package/dist/storage/dbWorker.js +132 -0
  128. package/dist/storage/index.d.ts +20 -0
  129. package/dist/storage/index.js +16 -0
  130. package/dist/storage/migrations.d.ts +26 -0
  131. package/dist/storage/migrations.js +207 -0
  132. package/dist/storage/types.d.ts +298 -0
  133. package/dist/storage/types.js +14 -0
  134. package/dist/wire/errors.d.ts +105 -0
  135. package/dist/wire/errors.js +143 -0
  136. package/dist/wire/events.d.ts +102 -0
  137. package/dist/wire/events.js +13 -0
  138. package/dist/wire/frames.d.ts +106 -0
  139. package/dist/wire/frames.js +156 -0
  140. package/dist/wire/index.d.ts +31 -0
  141. package/dist/wire/index.js +31 -0
  142. package/dist/wire/methods.d.ts +1896 -0
  143. package/dist/wire/methods.js +99 -0
  144. package/dist/wire/states.d.ts +487 -0
  145. package/dist/wire/states.js +61 -0
  146. package/package.json +63 -3
@@ -0,0 +1,31 @@
1
+ /**
2
+ * `notices` 表仓储:通知持久化(PRD L3:手动清 + 持久化)。
3
+ *
4
+ * 库里存的是**码 + 参数**而不是渲染好的句子:多语言在读出时按连接的语言渲染
5
+ * (architecture.md §6),否则一条通知存死一种语言,换个语言连回来就错了。
6
+ *
7
+ * `durationMs` 为 null 表示永久存在;有值时到点消失,靠 `deleteExpired` 清掉。
8
+ *
9
+ * `conversationId` 是「这条通知与哪段对话有关」的真值列(可空 = 全域通知),
10
+ * 按对话过滤与按对话清除都走它。
11
+ */
12
+ import type { Database } from "./Database.js";
13
+ import type { Notice, NoticeCreateInput } from "./types.js";
14
+ export declare class NoticeRepository {
15
+ private readonly db;
16
+ constructor(db: Database);
17
+ create(input: NoticeCreateInput): Promise<void>;
18
+ get(id: string): Promise<Notice | undefined>;
19
+ /**
20
+ * 仍然该显示的通知:永久的(`durationMs IS NULL`)加上还没到点的。
21
+ * 已到点但还没被 `deleteExpired` 扫掉的,这里也不返回,免得读到已过期的通知。
22
+ */
23
+ listActive(now: number): Promise<Notice[]>;
24
+ /** 手动清除一条(PRD L3:永久通知只能这样消失) */
25
+ clear(id: string): Promise<number>;
26
+ clearAll(): Promise<number>;
27
+ /** 清掉挂在一段对话上的通知(`notice.clear({ conversationId })` 的读写路径) */
28
+ clearByConversation(conversationId: string): Promise<number>;
29
+ /** 清掉已经到点的通知,返回清掉的条数 */
30
+ deleteExpired(now: number): Promise<number>;
31
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * `notices` 表仓储:通知持久化(PRD L3:手动清 + 持久化)。
3
+ *
4
+ * 库里存的是**码 + 参数**而不是渲染好的句子:多语言在读出时按连接的语言渲染
5
+ * (architecture.md §6),否则一条通知存死一种语言,换个语言连回来就错了。
6
+ *
7
+ * `durationMs` 为 null 表示永久存在;有值时到点消失,靠 `deleteExpired` 清掉。
8
+ *
9
+ * `conversationId` 是「这条通知与哪段对话有关」的真值列(可空 = 全域通知),
10
+ * 按对话过滤与按对话清除都走它。
11
+ */
12
+ const COLUMNS = "id, level, code, params, durationMs, conversationId, createdAt";
13
+ function parseParams(value) {
14
+ if (value === null)
15
+ return null;
16
+ try {
17
+ const parsed = JSON.parse(value);
18
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
19
+ return parsed;
20
+ }
21
+ return null;
22
+ }
23
+ catch {
24
+ return null;
25
+ }
26
+ }
27
+ function toNotice(row) {
28
+ return {
29
+ id: row.id,
30
+ level: row.level,
31
+ code: row.code,
32
+ params: parseParams(row.params),
33
+ durationMs: row.durationMs,
34
+ conversationId: row.conversationId,
35
+ createdAt: row.createdAt,
36
+ };
37
+ }
38
+ export class NoticeRepository {
39
+ db;
40
+ constructor(db) {
41
+ this.db = db;
42
+ }
43
+ async create(input) {
44
+ await this.db.run(`INSERT INTO notices (${COLUMNS}) VALUES (?, ?, ?, ?, ?, ?, ?)`, [
45
+ input.id,
46
+ input.level,
47
+ input.code,
48
+ input.params === undefined || input.params === null ? null : JSON.stringify(input.params),
49
+ input.durationMs ?? null,
50
+ input.conversationId ?? null,
51
+ input.createdAt,
52
+ ]);
53
+ }
54
+ async get(id) {
55
+ const row = await this.db.get(`SELECT ${COLUMNS} FROM notices WHERE id = ?`, [id]);
56
+ return row ? toNotice(row) : undefined;
57
+ }
58
+ /**
59
+ * 仍然该显示的通知:永久的(`durationMs IS NULL`)加上还没到点的。
60
+ * 已到点但还没被 `deleteExpired` 扫掉的,这里也不返回,免得读到已过期的通知。
61
+ */
62
+ async listActive(now) {
63
+ const rows = await this.db.all(`SELECT ${COLUMNS} FROM notices
64
+ WHERE durationMs IS NULL OR createdAt + durationMs > ?
65
+ ORDER BY createdAt ASC, id ASC`, [now]);
66
+ return rows.map(toNotice);
67
+ }
68
+ /** 手动清除一条(PRD L3:永久通知只能这样消失) */
69
+ async clear(id) {
70
+ const result = await this.db.run("DELETE FROM notices WHERE id = ?", [id]);
71
+ return result.changes;
72
+ }
73
+ async clearAll() {
74
+ const result = await this.db.run("DELETE FROM notices");
75
+ return result.changes;
76
+ }
77
+ /** 清掉挂在一段对话上的通知(`notice.clear({ conversationId })` 的读写路径) */
78
+ async clearByConversation(conversationId) {
79
+ const result = await this.db.run("DELETE FROM notices WHERE conversationId = ?", [
80
+ conversationId,
81
+ ]);
82
+ return result.changes;
83
+ }
84
+ /** 清掉已经到点的通知,返回清掉的条数 */
85
+ async deleteExpired(now) {
86
+ const result = await this.db.run("DELETE FROM notices WHERE durationMs IS NOT NULL AND createdAt + durationMs <= ?", [now]);
87
+ return result.changes;
88
+ }
89
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `questions` 表仓储:问题对象的持久化(PRD D22:有状态、有生命周期、多端看同一个)。
3
+ *
4
+ * 状态机取值(`state`)与问题种类的具体含义属于上层(QuestionCenter 那一层),存储层
5
+ * 只按调用方给的字符串存取,不解释它、也不替它做转移。
6
+ */
7
+ import type { Database } from "./Database.js";
8
+ import type { Question, QuestionCreateInput } from "./types.js";
9
+ /** 要更新的字段;只给想改的那几项 */
10
+ export interface QuestionPatch {
11
+ state?: string;
12
+ /** 回答内容(会被 JSON 序列化);显式传 `null` 表示清空 */
13
+ answer?: unknown;
14
+ answeredAt?: number | null;
15
+ updatedAt: number;
16
+ }
17
+ export declare class QuestionRepository {
18
+ private readonly db;
19
+ constructor(db: Database);
20
+ create(input: QuestionCreateInput): Promise<void>;
21
+ get(id: string): Promise<Question | undefined>;
22
+ /** 某段对话下的问题,按创建时间升序;给了 `state` 就只取这个状态 */
23
+ listByConversation(conversationId: string, state?: string): Promise<Question[]>;
24
+ /**
25
+ * 全库的问题,按创建时间升序(R61:`question.list` 不要求给对话)。
26
+ */
27
+ listAll(state?: string): Promise<Question[]>;
28
+ /**
29
+ * 更新状态 / 回答。只拼出真正要改的列,没给的列保持不变
30
+ * (例如只改 state 时不会把已有的 answer 抹掉)。
31
+ */
32
+ update(id: string, patch: QuestionPatch): Promise<number>;
33
+ delete(id: string): Promise<number>;
34
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * `questions` 表仓储:问题对象的持久化(PRD D22:有状态、有生命周期、多端看同一个)。
3
+ *
4
+ * 状态机取值(`state`)与问题种类的具体含义属于上层(QuestionCenter 那一层),存储层
5
+ * 只按调用方给的字符串存取,不解释它、也不替它做转移。
6
+ */
7
+ const COLUMNS = "id, conversationId, kind, payload, state, answer, createdAt, updatedAt, answeredAt";
8
+ function parseJson(value) {
9
+ if (value === null)
10
+ return null;
11
+ try {
12
+ return JSON.parse(value);
13
+ }
14
+ catch {
15
+ return value;
16
+ }
17
+ }
18
+ function toQuestion(row) {
19
+ return {
20
+ id: row.id,
21
+ conversationId: row.conversationId,
22
+ kind: row.kind,
23
+ payload: parseJson(row.payload),
24
+ state: row.state,
25
+ answer: parseJson(row.answer),
26
+ createdAt: row.createdAt,
27
+ updatedAt: row.updatedAt,
28
+ answeredAt: row.answeredAt,
29
+ };
30
+ }
31
+ export class QuestionRepository {
32
+ db;
33
+ constructor(db) {
34
+ this.db = db;
35
+ }
36
+ async create(input) {
37
+ await this.db.run(`INSERT INTO questions (${COLUMNS}) VALUES (?, ?, ?, ?, ?, NULL, ?, ?, NULL)`, [
38
+ input.id,
39
+ input.conversationId,
40
+ input.kind,
41
+ JSON.stringify(input.payload ?? null),
42
+ input.state,
43
+ input.createdAt,
44
+ input.createdAt,
45
+ ]);
46
+ }
47
+ async get(id) {
48
+ const row = await this.db.get(`SELECT ${COLUMNS} FROM questions WHERE id = ?`, [id]);
49
+ return row ? toQuestion(row) : undefined;
50
+ }
51
+ /** 某段对话下的问题,按创建时间升序;给了 `state` 就只取这个状态 */
52
+ async listByConversation(conversationId, state) {
53
+ const rows = state === undefined
54
+ ? await this.db.all(`SELECT ${COLUMNS} FROM questions WHERE conversationId = ? ORDER BY createdAt ASC, id ASC`, [conversationId])
55
+ : await this.db.all(`SELECT ${COLUMNS} FROM questions
56
+ WHERE conversationId = ? AND state = ?
57
+ ORDER BY createdAt ASC, id ASC`, [conversationId, state]);
58
+ return rows.map(toQuestion);
59
+ }
60
+ /**
61
+ * 全库的问题,按创建时间升序(R61:`question.list` 不要求给对话)。
62
+ */
63
+ async listAll(state) {
64
+ const rows = state === undefined
65
+ ? await this.db.all(`SELECT ${COLUMNS} FROM questions ORDER BY createdAt ASC, id ASC`)
66
+ : await this.db.all(`SELECT ${COLUMNS} FROM questions WHERE state = ? ORDER BY createdAt ASC, id ASC`, [state]);
67
+ return rows.map(toQuestion);
68
+ }
69
+ /**
70
+ * 更新状态 / 回答。只拼出真正要改的列,没给的列保持不变
71
+ * (例如只改 state 时不会把已有的 answer 抹掉)。
72
+ */
73
+ async update(id, patch) {
74
+ const assignments = ["updatedAt = ?"];
75
+ const params = [patch.updatedAt];
76
+ if (patch.state !== undefined) {
77
+ assignments.push("state = ?");
78
+ params.push(patch.state);
79
+ }
80
+ if ("answer" in patch) {
81
+ assignments.push("answer = ?");
82
+ params.push(patch.answer === undefined ? null : JSON.stringify(patch.answer));
83
+ }
84
+ if (patch.answeredAt !== undefined) {
85
+ assignments.push("answeredAt = ?");
86
+ params.push(patch.answeredAt);
87
+ }
88
+ params.push(id);
89
+ const result = await this.db.run(`UPDATE questions SET ${assignments.join(", ")} WHERE id = ?`, params);
90
+ return result.changes;
91
+ }
92
+ async delete(id) {
93
+ const result = await this.db.run("DELETE FROM questions WHERE id = ?", [id]);
94
+ return result.changes;
95
+ }
96
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `workspaces` 表仓储:只做 SQL,不含业务判断。
3
+ *
4
+ * 一个 Workspace 对应一个工作目录,`cwd` 创建后不可变(PRD D9:搬家=新 Workspace),
5
+ * 所以这里没有改 cwd 的方法 —— 想改只能建新的。
6
+ */
7
+ import type { Database } from "./Database.js";
8
+ import type { WorkspaceCreateInput, WorkspaceRow } from "./types.js";
9
+ export declare class WorkspaceRepository {
10
+ private readonly db;
11
+ constructor(db: Database);
12
+ create(input: WorkspaceCreateInput): Promise<void>;
13
+ get(id: string): Promise<WorkspaceRow | undefined>;
14
+ /** 按 cwd 找登记过的 Workspace(PRD D10:普通对话遇到未登记的 cwd 时问一次) */
15
+ getByCwd(cwd: string): Promise<WorkspaceRow | undefined>;
16
+ /** 全部 Workspace,新建的排前面 */
17
+ list(): Promise<WorkspaceRow[]>;
18
+ rename(id: string, name: string): Promise<number>;
19
+ /**
20
+ * 删 Workspace:它名下的 conversations / messages / assets / questions 靠外键级联清掉
21
+ * (PRD D11:删项目连带删它名下的对话)。目录本身不在存储层职能范围内。
22
+ */
23
+ delete(id: string): Promise<number>;
24
+ count(): Promise<number>;
25
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * `workspaces` 表仓储:只做 SQL,不含业务判断。
3
+ *
4
+ * 一个 Workspace 对应一个工作目录,`cwd` 创建后不可变(PRD D9:搬家=新 Workspace),
5
+ * 所以这里没有改 cwd 的方法 —— 想改只能建新的。
6
+ */
7
+ const COLUMNS = "id, name, cwd, createdAt";
8
+ export class WorkspaceRepository {
9
+ db;
10
+ constructor(db) {
11
+ this.db = db;
12
+ }
13
+ async create(input) {
14
+ // cwd 在表上唯一:同一个目录重复登记会在这里直接报 UNIQUE 约束错误
15
+ await this.db.run(`INSERT INTO workspaces (${COLUMNS}) VALUES (?, ?, ?, ?)`, [input.id, input.name, input.cwd, input.createdAt]);
16
+ }
17
+ async get(id) {
18
+ return this.db.get(`SELECT ${COLUMNS} FROM workspaces WHERE id = ?`, [id]);
19
+ }
20
+ /** 按 cwd 找登记过的 Workspace(PRD D10:普通对话遇到未登记的 cwd 时问一次) */
21
+ async getByCwd(cwd) {
22
+ return this.db.get(`SELECT ${COLUMNS} FROM workspaces WHERE cwd = ?`, [cwd]);
23
+ }
24
+ /** 全部 Workspace,新建的排前面 */
25
+ async list() {
26
+ return this.db.all(`SELECT ${COLUMNS} FROM workspaces ORDER BY createdAt DESC, id DESC`);
27
+ }
28
+ async rename(id, name) {
29
+ const result = await this.db.run("UPDATE workspaces SET name = ? WHERE id = ?", [name, id]);
30
+ return result.changes;
31
+ }
32
+ /**
33
+ * 删 Workspace:它名下的 conversations / messages / assets / questions 靠外键级联清掉
34
+ * (PRD D11:删项目连带删它名下的对话)。目录本身不在存储层职能范围内。
35
+ */
36
+ async delete(id) {
37
+ const result = await this.db.run("DELETE FROM workspaces WHERE id = ?", [id]);
38
+ return result.changes;
39
+ }
40
+ async count() {
41
+ const row = await this.db.get("SELECT count(*) AS n FROM workspaces");
42
+ return row?.n ?? 0;
43
+ }
44
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * SQLite worker 线程:`node:sqlite` 只提供同步 API(`DatabaseSync`),在主线程上执行会
3
+ * 直接卡住事件循环(宿主要和多个 WebSocket 连接同时打交道,卡一次就是全体卡住),
4
+ * 所以数据库句柄整个住在 worker 里,主线程只发消息、按 id 收结果。
5
+ *
6
+ * 这里刻意只 import Node 内置模块、不 import 任何同目录文件:
7
+ * worker 是以自己的模块图独立启动的,依赖越少启动越快、也不会踩到「源码是 .ts / 产物是
8
+ * .js」这种路径差异。
9
+ */
10
+ export {};
@@ -0,0 +1,132 @@
1
+ /**
2
+ * SQLite worker 线程:`node:sqlite` 只提供同步 API(`DatabaseSync`),在主线程上执行会
3
+ * 直接卡住事件循环(宿主要和多个 WebSocket 连接同时打交道,卡一次就是全体卡住),
4
+ * 所以数据库句柄整个住在 worker 里,主线程只发消息、按 id 收结果。
5
+ *
6
+ * 这里刻意只 import Node 内置模块、不 import 任何同目录文件:
7
+ * worker 是以自己的模块图独立启动的,依赖越少启动越快、也不会踩到「源码是 .ts / 产物是
8
+ * .js」这种路径差异。
9
+ */
10
+ import { parentPort, workerData } from "node:worker_threads";
11
+ import { DatabaseSync } from "node:sqlite";
12
+ import { mkdirSync } from "node:fs";
13
+ import { dirname } from "node:path";
14
+ const port = parentPort;
15
+ if (!port)
16
+ throw new Error("dbWorker 只能由 worker_threads 启动");
17
+ const { dbPath } = workerData;
18
+ // 数据库文件所在目录可能还不存在(首次启动、测试用临时目录),开库前先补上
19
+ if (dbPath !== ":memory:")
20
+ mkdirSync(dirname(dbPath), { recursive: true });
21
+ const sqlite = new DatabaseSync(dbPath);
22
+ // WAL:读不被写阻塞、写不被读阻塞,崩溃后也更容易恢复。
23
+ // 读回来的模式必须真的是 "wal" —— 文件系统不支持时(网络盘、只读目录等)会静默留在
24
+ // delete 模式,那种情况下并发读写会互相卡住,与其带病运行不如让启动直接失败。
25
+ const journal = sqlite.prepare("PRAGMA journal_mode = WAL").get();
26
+ if (journal?.journal_mode !== "wal") {
27
+ throw new Error(`无法切换到 WAL 日志模式(当前为 ${String(journal?.journal_mode)}),数据库路径:${dbPath}`);
28
+ }
29
+ // 外键约束在 SQLite 里默认是关的,必须显式打开,级联删除才会生效
30
+ sqlite.exec("PRAGMA foreign_keys = ON");
31
+ const foreignKeys = sqlite.prepare("PRAGMA foreign_keys").get();
32
+ if (foreignKeys?.foreign_keys !== 1) {
33
+ throw new Error("无法开启 foreign_keys,级联删除不会生效");
34
+ }
35
+ /**
36
+ * 语句缓存:SQL 全部来自仓储里的固定语句,种类有限,缓存下来省掉热路径上重复编译。
37
+ * 若某次迁移改动了表结构,SQLite 会在下次执行时自动重新编译,不需要手动失效。
38
+ */
39
+ const statementCache = new Map();
40
+ function prepare(sql) {
41
+ const cached = statementCache.get(sql);
42
+ if (cached)
43
+ return cached;
44
+ const compiled = sqlite.prepare(sql);
45
+ statementCache.set(sql, compiled);
46
+ return compiled;
47
+ }
48
+ /** 执行一条写语句;`changes` / `lastInsertRowid` 统一转成 number,保证能结构化克隆 */
49
+ function runStatement(sql, params) {
50
+ const result = prepare(sql).run(...params);
51
+ return {
52
+ changes: Number(result.changes),
53
+ lastInsertRowid: Number(result.lastInsertRowid),
54
+ };
55
+ }
56
+ /** 按语句自己声明的种类执行:读的回行,写的回 `changes` / `lastInsertRowid` */
57
+ function executeStatement(statement) {
58
+ switch (statement.kind) {
59
+ case "all":
60
+ return prepare(statement.sql).all(...statement.params);
61
+ case "get":
62
+ return prepare(statement.sql).get(...statement.params);
63
+ case "run":
64
+ return runStatement(statement.sql, statement.params);
65
+ }
66
+ }
67
+ /**
68
+ * 一次消息内串行执行一批语句:`BEGIN IMMEDIATE` 先拿写锁,中途任何一条失败就整批
69
+ * ROLLBACK。主线程的 `Database.tx` 就是靠这个保证「要么全成、要么全不成」,也是它能让
70
+ * 回调里「先读再写」而不拆成两次往返的原因(读与写在同一个事务、同一条消息里跑完)。
71
+ */
72
+ function runBatch(statements) {
73
+ sqlite.exec("BEGIN IMMEDIATE");
74
+ try {
75
+ const results = statements.map((statement) => executeStatement(statement));
76
+ sqlite.exec("COMMIT");
77
+ return results;
78
+ }
79
+ catch (error) {
80
+ try {
81
+ sqlite.exec("ROLLBACK");
82
+ }
83
+ catch {
84
+ // 部分错误(如死锁/中断)下 SQLite 已经自己回滚了,这里的再次回滚失败可以忽略
85
+ }
86
+ throw error;
87
+ }
88
+ }
89
+ function describeError(error) {
90
+ if (error instanceof Error)
91
+ return error.message;
92
+ return String(error);
93
+ }
94
+ port.on("message", (request) => {
95
+ try {
96
+ switch (request.kind) {
97
+ case "all": {
98
+ const rows = prepare(request.sql).all(...request.params);
99
+ port.postMessage({ id: request.id, ok: true, result: rows });
100
+ break;
101
+ }
102
+ case "get": {
103
+ const row = prepare(request.sql).get(...request.params);
104
+ port.postMessage({ id: request.id, ok: true, result: row });
105
+ break;
106
+ }
107
+ case "run": {
108
+ port.postMessage({
109
+ id: request.id,
110
+ ok: true,
111
+ result: runStatement(request.sql, request.params),
112
+ });
113
+ break;
114
+ }
115
+ case "batch": {
116
+ port.postMessage({ id: request.id, ok: true, result: runBatch(request.statements) });
117
+ break;
118
+ }
119
+ case "close": {
120
+ // 先回执再收尾:主线程拿到回执就知道挂起表可以释放了
121
+ port.postMessage({ id: request.id, ok: true });
122
+ statementCache.clear();
123
+ sqlite.close();
124
+ port.close();
125
+ break;
126
+ }
127
+ }
128
+ }
129
+ catch (error) {
130
+ port.postMessage({ id: request.id, ok: false, error: describeError(error) });
131
+ }
132
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 存储层出口:`Database`(worker 里的 node:sqlite)+ 迁移 + 一表一仓储。
3
+ *
4
+ * 这里不导出任何单例:`Database` 由单根(`app.ts`)创建后注入给各仓储与服务,
5
+ * 谁创建谁负责 `close()`。这样做的好处是测试与多实例可以各开各的库,互不影响。
6
+ */
7
+ export { Database } from "./Database.js";
8
+ export type { RunResult, TxClient, TxKind, TxOutcome, TxPlan, TxStatement, TxValue, } from "./Database.js";
9
+ export { migrate, MIGRATIONS } from "./migrations.js";
10
+ export type { Migration } from "./migrations.js";
11
+ export { MetaRepository } from "./MetaRepository.js";
12
+ export { WorkspaceRepository } from "./WorkspaceRepository.js";
13
+ export { ConversationRepository } from "./ConversationRepository.js";
14
+ export { MessageRepository } from "./MessageRepository.js";
15
+ export { AssetRepository } from "./AssetRepository.js";
16
+ export { NoticeRepository } from "./NoticeRepository.js";
17
+ export { QuestionRepository } from "./QuestionRepository.js";
18
+ export { CredentialRepository, hashCredential } from "./CredentialRepository.js";
19
+ export type { QuestionPatch } from "./QuestionRepository.js";
20
+ export type * from "./types.js";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 存储层出口:`Database`(worker 里的 node:sqlite)+ 迁移 + 一表一仓储。
3
+ *
4
+ * 这里不导出任何单例:`Database` 由单根(`app.ts`)创建后注入给各仓储与服务,
5
+ * 谁创建谁负责 `close()`。这样做的好处是测试与多实例可以各开各的库,互不影响。
6
+ */
7
+ export { Database } from "./Database.js";
8
+ export { migrate, MIGRATIONS } from "./migrations.js";
9
+ export { MetaRepository } from "./MetaRepository.js";
10
+ export { WorkspaceRepository } from "./WorkspaceRepository.js";
11
+ export { ConversationRepository } from "./ConversationRepository.js";
12
+ export { MessageRepository } from "./MessageRepository.js";
13
+ export { AssetRepository } from "./AssetRepository.js";
14
+ export { NoticeRepository } from "./NoticeRepository.js";
15
+ export { QuestionRepository } from "./QuestionRepository.js";
16
+ export { CredentialRepository, hashCredential } from "./CredentialRepository.js";
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 建表与迁移。
3
+ *
4
+ * `meta.schemaVersion` 是唯一的版本真相:`migrate` 从当前版本往上逐级执行,
5
+ * 每一级的所有语句 + 版本号推进都在**同一个事务**里 —— 结构改了一半而版本号没动,
6
+ * 或者版本号动了而结构没改,都会让下次启动跑在错误的假设上。
7
+ *
8
+ * 不识别任何旧数据、不做数据搬迁(PRD L1:新库从空开始)。
9
+ */
10
+ import type { Database } from "./Database.js";
11
+ export interface Migration {
12
+ /** 目标版本号:跑完这一级后 `meta.schemaVersion` 即为此值 */
13
+ version: number;
14
+ /** 这一级要执行的语句,按顺序在同一事务内执行 */
15
+ statements: string[];
16
+ }
17
+ /**
18
+ * 迁移列表。眼下只有 v1:这套表结构还没随版本发出去,PM 在 W1 之后的裁定(cwd 唯一、
19
+ * 身份列非空)直接落进了 v1 的建表语句。再往后改结构时追加新条目 —— 已经装上升过级的库
20
+ * 不会重跑旧条目,改旧条目会让两台机器的表结构悄悄分叉。
21
+ */
22
+ export declare const MIGRATIONS: Migration[];
23
+ /**
24
+ * 把库升到最新版本。重复调用是幂等的:已经跑过的版本会跳过,不会再执行一遍语句。
25
+ */
26
+ export declare function migrate(db: Database): Promise<void>;