@barryfan2045/dsh-matrix 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 (206) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +286 -0
  3. package/THIRD_PARTY_NOTICES.md +53 -0
  4. package/assets/avatars/instance-01.png +0 -0
  5. package/assets/avatars/instance-02.png +0 -0
  6. package/assets/avatars/instance-03.png +0 -0
  7. package/assets/avatars/instance-04.png +0 -0
  8. package/assets/avatars/instance-05.png +0 -0
  9. package/assets/avatars/instance-06.png +0 -0
  10. package/assets/avatars/instance-07.png +0 -0
  11. package/assets/avatars/instance-08.png +0 -0
  12. package/assets/avatars/instance-09.png +0 -0
  13. package/assets/avatars/instance-10.png +0 -0
  14. package/assets/avatars/instance-11.png +0 -0
  15. package/assets/avatars/instance-12.png +0 -0
  16. package/assets/avatars/management.png +0 -0
  17. package/cordis.patch.yml +13 -0
  18. package/dist/auth/admin-commands.d.ts +47 -0
  19. package/dist/auth/admin-commands.d.ts.map +1 -0
  20. package/dist/auth/admin-commands.js +112 -0
  21. package/dist/auth/admin-commands.js.map +1 -0
  22. package/dist/auth/challenge.d.ts +93 -0
  23. package/dist/auth/challenge.d.ts.map +1 -0
  24. package/dist/auth/challenge.js +104 -0
  25. package/dist/auth/challenge.js.map +1 -0
  26. package/dist/auth/store.d.ts +55 -0
  27. package/dist/auth/store.d.ts.map +1 -0
  28. package/dist/auth/store.js +124 -0
  29. package/dist/auth/store.js.map +1 -0
  30. package/dist/bridge/agent-commands.d.ts +39 -0
  31. package/dist/bridge/agent-commands.d.ts.map +1 -0
  32. package/dist/bridge/agent-commands.js +243 -0
  33. package/dist/bridge/agent-commands.js.map +1 -0
  34. package/dist/bridge/attachments-ledger.d.ts +54 -0
  35. package/dist/bridge/attachments-ledger.d.ts.map +1 -0
  36. package/dist/bridge/attachments-ledger.js +68 -0
  37. package/dist/bridge/attachments-ledger.js.map +1 -0
  38. package/dist/bridge/commands.d.ts +28 -0
  39. package/dist/bridge/commands.d.ts.map +1 -0
  40. package/dist/bridge/commands.js +18 -0
  41. package/dist/bridge/commands.js.map +1 -0
  42. package/dist/bridge/crypto-health.d.ts +37 -0
  43. package/dist/bridge/crypto-health.d.ts.map +1 -0
  44. package/dist/bridge/crypto-health.js +38 -0
  45. package/dist/bridge/crypto-health.js.map +1 -0
  46. package/dist/bridge/group-gate.d.ts +38 -0
  47. package/dist/bridge/group-gate.d.ts.map +1 -0
  48. package/dist/bridge/group-gate.js +49 -0
  49. package/dist/bridge/group-gate.js.map +1 -0
  50. package/dist/bridge/instance-lock.d.ts +10 -0
  51. package/dist/bridge/instance-lock.d.ts.map +1 -0
  52. package/dist/bridge/instance-lock.js +82 -0
  53. package/dist/bridge/instance-lock.js.map +1 -0
  54. package/dist/bridge/interactions.d.ts +62 -0
  55. package/dist/bridge/interactions.d.ts.map +1 -0
  56. package/dist/bridge/interactions.js +237 -0
  57. package/dist/bridge/interactions.js.map +1 -0
  58. package/dist/bridge/management-room.d.ts +13 -0
  59. package/dist/bridge/management-room.d.ts.map +1 -0
  60. package/dist/bridge/management-room.js +27 -0
  61. package/dist/bridge/management-room.js.map +1 -0
  62. package/dist/bridge/multi-project.d.ts +34 -0
  63. package/dist/bridge/multi-project.d.ts.map +1 -0
  64. package/dist/bridge/multi-project.js +44 -0
  65. package/dist/bridge/multi-project.js.map +1 -0
  66. package/dist/bridge/pipeline.d.ts +151 -0
  67. package/dist/bridge/pipeline.d.ts.map +1 -0
  68. package/dist/bridge/pipeline.js +502 -0
  69. package/dist/bridge/pipeline.js.map +1 -0
  70. package/dist/bridge/pmctl.d.ts +50 -0
  71. package/dist/bridge/pmctl.d.ts.map +1 -0
  72. package/dist/bridge/pmctl.js +272 -0
  73. package/dist/bridge/pmctl.js.map +1 -0
  74. package/dist/bridge/power-levels.d.ts +57 -0
  75. package/dist/bridge/power-levels.d.ts.map +1 -0
  76. package/dist/bridge/power-levels.js +92 -0
  77. package/dist/bridge/power-levels.js.map +1 -0
  78. package/dist/bridge/power-sync.d.ts +64 -0
  79. package/dist/bridge/power-sync.d.ts.map +1 -0
  80. package/dist/bridge/power-sync.js +121 -0
  81. package/dist/bridge/power-sync.js.map +1 -0
  82. package/dist/bridge/projects.d.ts +97 -0
  83. package/dist/bridge/projects.d.ts.map +1 -0
  84. package/dist/bridge/projects.js +288 -0
  85. package/dist/bridge/projects.js.map +1 -0
  86. package/dist/bridge/quote-cache.d.ts +38 -0
  87. package/dist/bridge/quote-cache.d.ts.map +1 -0
  88. package/dist/bridge/quote-cache.js +56 -0
  89. package/dist/bridge/quote-cache.js.map +1 -0
  90. package/dist/bridge/reply.d.ts +31 -0
  91. package/dist/bridge/reply.d.ts.map +1 -0
  92. package/dist/bridge/reply.js +122 -0
  93. package/dist/bridge/reply.js.map +1 -0
  94. package/dist/bridge/space-identity.d.ts +43 -0
  95. package/dist/bridge/space-identity.d.ts.map +1 -0
  96. package/dist/bridge/space-identity.js +92 -0
  97. package/dist/bridge/space-identity.js.map +1 -0
  98. package/dist/bridge/space-sync.d.ts +94 -0
  99. package/dist/bridge/space-sync.d.ts.map +1 -0
  100. package/dist/bridge/space-sync.js +137 -0
  101. package/dist/bridge/space-sync.js.map +1 -0
  102. package/dist/bridge/turn-commands.d.ts +28 -0
  103. package/dist/bridge/turn-commands.d.ts.map +1 -0
  104. package/dist/bridge/turn-commands.js +97 -0
  105. package/dist/bridge/turn-commands.js.map +1 -0
  106. package/dist/cli/config-store.d.ts +32 -0
  107. package/dist/cli/config-store.d.ts.map +1 -0
  108. package/dist/cli/config-store.js +118 -0
  109. package/dist/cli/config-store.js.map +1 -0
  110. package/dist/cli/crypto-store.d.ts +42 -0
  111. package/dist/cli/crypto-store.d.ts.map +1 -0
  112. package/dist/cli/crypto-store.js +70 -0
  113. package/dist/cli/crypto-store.js.map +1 -0
  114. package/dist/cli/device-prune.d.ts +105 -0
  115. package/dist/cli/device-prune.d.ts.map +1 -0
  116. package/dist/cli/device-prune.js +163 -0
  117. package/dist/cli/device-prune.js.map +1 -0
  118. package/dist/cli/index.d.ts +4 -0
  119. package/dist/cli/index.d.ts.map +1 -0
  120. package/dist/cli/index.js +584 -0
  121. package/dist/cli/index.js.map +1 -0
  122. package/dist/cli/log-filter.d.ts +32 -0
  123. package/dist/cli/log-filter.d.ts.map +1 -0
  124. package/dist/cli/log-filter.js +40 -0
  125. package/dist/cli/log-filter.js.map +1 -0
  126. package/dist/cli/pnpm-builds.d.ts +32 -0
  127. package/dist/cli/pnpm-builds.d.ts.map +1 -0
  128. package/dist/cli/pnpm-builds.js +73 -0
  129. package/dist/cli/pnpm-builds.js.map +1 -0
  130. package/dist/cli/profile-patch.d.ts +26 -0
  131. package/dist/cli/profile-patch.d.ts.map +1 -0
  132. package/dist/cli/profile-patch.js +56 -0
  133. package/dist/cli/profile-patch.js.map +1 -0
  134. package/dist/cli/setup.d.ts +39 -0
  135. package/dist/cli/setup.d.ts.map +1 -0
  136. package/dist/cli/setup.js +234 -0
  137. package/dist/cli/setup.js.map +1 -0
  138. package/dist/cli/systemd-hint.d.ts +11 -0
  139. package/dist/cli/systemd-hint.d.ts.map +1 -0
  140. package/dist/cli/systemd-hint.js +46 -0
  141. package/dist/cli/systemd-hint.js.map +1 -0
  142. package/dist/cli/systemd.d.ts +32 -0
  143. package/dist/cli/systemd.d.ts.map +1 -0
  144. package/dist/cli/systemd.js +58 -0
  145. package/dist/cli/systemd.js.map +1 -0
  146. package/dist/config.d.ts +54 -0
  147. package/dist/config.d.ts.map +1 -0
  148. package/dist/config.js +107 -0
  149. package/dist/config.js.map +1 -0
  150. package/dist/dsh/backend.d.ts +110 -0
  151. package/dist/dsh/backend.d.ts.map +1 -0
  152. package/dist/dsh/backend.js +481 -0
  153. package/dist/dsh/backend.js.map +1 -0
  154. package/dist/dsh/interactions.d.ts +50 -0
  155. package/dist/dsh/interactions.d.ts.map +1 -0
  156. package/dist/dsh/interactions.js +141 -0
  157. package/dist/dsh/interactions.js.map +1 -0
  158. package/dist/dsh/stream.d.ts +32 -0
  159. package/dist/dsh/stream.d.ts.map +1 -0
  160. package/dist/dsh/stream.js +109 -0
  161. package/dist/dsh/stream.js.map +1 -0
  162. package/dist/formatting.d.ts +14 -0
  163. package/dist/formatting.d.ts.map +1 -0
  164. package/dist/formatting.js +63 -0
  165. package/dist/formatting.js.map +1 -0
  166. package/dist/index.d.ts +8 -0
  167. package/dist/index.d.ts.map +1 -0
  168. package/dist/index.js +232 -0
  169. package/dist/index.js.map +1 -0
  170. package/dist/logger.d.ts +9 -0
  171. package/dist/logger.d.ts.map +1 -0
  172. package/dist/logger.js +28 -0
  173. package/dist/logger.js.map +1 -0
  174. package/dist/matrix/matrix-events.d.ts +49 -0
  175. package/dist/matrix/matrix-events.d.ts.map +1 -0
  176. package/dist/matrix/matrix-events.js +89 -0
  177. package/dist/matrix/matrix-events.js.map +1 -0
  178. package/dist/matrix/transport.d.ts +192 -0
  179. package/dist/matrix/transport.d.ts.map +1 -0
  180. package/dist/matrix/transport.js +510 -0
  181. package/dist/matrix/transport.js.map +1 -0
  182. package/dist/ports/attachments.d.ts +51 -0
  183. package/dist/ports/attachments.d.ts.map +1 -0
  184. package/dist/ports/attachments.js +27 -0
  185. package/dist/ports/attachments.js.map +1 -0
  186. package/dist/ports/backend.d.ts +159 -0
  187. package/dist/ports/backend.d.ts.map +1 -0
  188. package/dist/ports/backend.js +25 -0
  189. package/dist/ports/backend.js.map +1 -0
  190. package/dist/ports/interactions.d.ts +65 -0
  191. package/dist/ports/interactions.d.ts.map +1 -0
  192. package/dist/ports/interactions.js +15 -0
  193. package/dist/ports/interactions.js.map +1 -0
  194. package/dist/ports/streaming.d.ts +45 -0
  195. package/dist/ports/streaming.d.ts.map +1 -0
  196. package/dist/ports/streaming.js +23 -0
  197. package/dist/ports/streaming.js.map +1 -0
  198. package/dist/ports/transport.d.ts +68 -0
  199. package/dist/ports/transport.d.ts.map +1 -0
  200. package/dist/ports/transport.js +2 -0
  201. package/dist/ports/transport.js.map +1 -0
  202. package/dist/types.d.ts +53 -0
  203. package/dist/types.d.ts.map +1 -0
  204. package/dist/types.js +2 -0
  205. package/dist/types.js.map +1 -0
  206. package/package.json +80 -0
@@ -0,0 +1,121 @@
1
+ import { DEMOTED_POWER_LEVEL, TRUSTED_POWER_LEVEL, bookElevation, bookedIn, planDemotion, planElevation, unbookElevation, } from "./power-levels.js";
2
+ function emptyResult() {
3
+ return { elevated: [], demoted: [], forgotten: [], failures: [] };
4
+ }
5
+ function describe(error) {
6
+ return error instanceof Error ? error.message : String(error);
7
+ }
8
+ function warnFailures(logger, what, failures) {
9
+ if (failures.length === 0)
10
+ return;
11
+ logger?.warn(`${what}有 ${failures.length} 处失败(保留记账,下次启动自愈重试): ${failures.map((f) => `${f.roomId}/${f.userId}: ${f.error}`).join('; ')}`);
12
+ }
13
+ /** 两个原语都在才做权限管理(否则调用方直接跳过,不报错)。 */
14
+ export function canManagePower(ops) {
15
+ return typeof ops.getUserPowerLevel === 'function' && typeof ops.setUserPowerLevel === 'function';
16
+ }
17
+ /**
18
+ * 在这个房间里把 trusted 用户提到 PL100。
19
+ *
20
+ * 先批量读当前等级(读失败的收进 failures、不参与计划),再用纯规划器决定谁要提 ——
21
+ * 已经 >= 100 的人不动、也不记账(那是管理员手工给的,不是我们的功劳)。
22
+ */
23
+ export async function elevateTrustedInRoom(roomId, trusted, ledger, ops, logger) {
24
+ const result = emptyResult();
25
+ if (!canManagePower(ops))
26
+ return result;
27
+ const current = {};
28
+ for (const userId of trusted) {
29
+ try {
30
+ current[userId] = await ops.getUserPowerLevel(roomId, userId);
31
+ }
32
+ catch (error) {
33
+ result.failures.push({ roomId, userId, error: describe(error) });
34
+ }
35
+ }
36
+ for (const userId of planElevation(current, trusted)) {
37
+ try {
38
+ await ops.setUserPowerLevel(roomId, userId, TRUSTED_POWER_LEVEL);
39
+ }
40
+ catch (error) {
41
+ result.failures.push({ roomId, userId, error: describe(error) });
42
+ continue; // 没写上就不记账
43
+ }
44
+ bookElevation(ledger, roomId, userId);
45
+ result.elevated.push({ roomId, userId });
46
+ }
47
+ warnFailures(logger, '提权', result.failures);
48
+ return result;
49
+ }
50
+ /**
51
+ * 把某用户**在所有记过账的房间里**降回普通成员。
52
+ *
53
+ * 只碰账本里有记账的房间(绝不盲扫全部房间,避免误伤管理员在别处的手工授权)。
54
+ * 当前已经低于 100 的只抹账、不写(别人先降过了)。
55
+ */
56
+ export async function demoteUserEverywhere(userId, ledger, ops, logger) {
57
+ const result = emptyResult();
58
+ if (!canManagePower(ops))
59
+ return result;
60
+ // 取快照:循环里会改账本
61
+ const rooms = Object.keys(ledger).filter((roomId) => bookedIn(ledger, roomId).includes(userId));
62
+ for (const roomId of rooms) {
63
+ let level;
64
+ try {
65
+ level = await ops.getUserPowerLevel(roomId, userId);
66
+ }
67
+ catch (error) {
68
+ result.failures.push({ roomId, userId, error: describe(error) });
69
+ continue;
70
+ }
71
+ // 降不降由纯规划器说了算(规则只有一份)。单用户也要过它。
72
+ if (planDemotion({ [userId]: level }, [userId]).length === 0) {
73
+ if (unbookElevation(ledger, roomId, userId))
74
+ result.forgotten.push({ roomId, userId });
75
+ continue;
76
+ }
77
+ try {
78
+ await ops.setUserPowerLevel(roomId, userId, DEMOTED_POWER_LEVEL);
79
+ }
80
+ catch (error) {
81
+ result.failures.push({ roomId, userId, error: describe(error) });
82
+ continue; // 保留记账:下次还能再降
83
+ }
84
+ unbookElevation(ledger, roomId, userId);
85
+ result.demoted.push({ roomId, userId });
86
+ }
87
+ warnFailures(logger, `降级 ${userId}`, result.failures);
88
+ return result;
89
+ }
90
+ /**
91
+ * 启动对账(幂等):
92
+ * 1. 先把"账本里记过、但现在不在 trusted 里"的用户在各处降掉并抹账
93
+ * (从信任名单里拿掉的人,不该继续持有权限);
94
+ * 2. 再给给定房间里的当前 trusted 提权(补上之前失败或被漏掉的)。
95
+ */
96
+ export async function healPowerLevels(rooms, trusted, ledger, ops, logger) {
97
+ const result = emptyResult();
98
+ if (!canManagePower(ops))
99
+ return result;
100
+ const trustedSet = new Set(trusted);
101
+ const stale = new Set();
102
+ for (const roomId of Object.keys(ledger)) {
103
+ for (const userId of bookedIn(ledger, roomId)) {
104
+ if (!trustedSet.has(userId))
105
+ stale.add(userId);
106
+ }
107
+ }
108
+ for (const userId of stale) {
109
+ const stripped = await demoteUserEverywhere(userId, ledger, ops, logger);
110
+ result.demoted.push(...stripped.demoted);
111
+ result.forgotten.push(...stripped.forgotten);
112
+ result.failures.push(...stripped.failures);
113
+ }
114
+ for (const roomId of rooms) {
115
+ const elevated = await elevateTrustedInRoom(roomId, trusted, ledger, ops, logger);
116
+ result.elevated.push(...elevated.elevated);
117
+ result.failures.push(...elevated.failures);
118
+ }
119
+ return result;
120
+ }
121
+ //# sourceMappingURL=power-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"power-sync.js","sourceRoot":"","sources":["../../src/bridge/power-sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ import type { PowerLedger } from './power-levels.ts';
2
+ import type { SpaceIdentityState } from './space-sync.ts';
3
+ export interface ProjectEntry {
4
+ name?: string;
5
+ workdir: string;
6
+ }
7
+ export interface ProjectsState {
8
+ projects: Record<string, ProjectEntry>;
9
+ managementRooms: string[];
10
+ /**
11
+ * T12(US37):提权账本 —— roomId → 我们**实际**给谁提过 PL100。
12
+ * 只记真正发生的提升,`/revoke` 据此降级,绝不误伤管理员手工授权的房间。
13
+ */
14
+ powerGrants: PowerLedger;
15
+ /**
16
+ * T12(US38):space 与头像的已认领状态 —— 哪个 space 是我们建的、内置头像池铺到
17
+ * 了哪一版。config 里的 space 字段只作 boot 种子,持久记录以这里为准。
18
+ */
19
+ spaceIdentity: SpaceIdentityState;
20
+ }
21
+ export declare function emptyProjectsState(): ProjectsState;
22
+ /** 宽容解析:留合法条目丢垃圾,永不抛(auth-store parseAuthState 同款策略)。 */
23
+ export declare function parseProjectsState(raw: unknown): ProjectsState;
24
+ /** 工作目录解析规则(纯函数,表驱动测试的单点)。 */
25
+ export declare function resolveWorkdir(spec: string, opts: {
26
+ projectRoot: string;
27
+ home?: string;
28
+ }): string;
29
+ /** 项目展示名规则(projectLabelOf):name 优先,缺省 workdir basename。 */
30
+ export declare function projectLabelOf(entry: ProjectEntry): string;
31
+ /**
32
+ * 校验用户给的项目名(移植 project-labels.ts validateProjectLabel):
33
+ * 非空 / 无方括号 / 无空白 / ≤30 字符 / 大小写不敏感唯一。
34
+ * 返回错误消息(中文,给用户看)或 null(可用)。
35
+ */
36
+ export declare function validateProjectLabel(candidate: string, existingLabels: string[]): string | null;
37
+ export interface ProjectMapOptions {
38
+ /** 落盘路径;undefined = 纯内存(inMemory 构造器用这条)。 */
39
+ filePath?: string;
40
+ /** 工程根:相对项目路径与缺省名的解析基准(通常 = cfg.workdir)。 */
41
+ projectRoot: string;
42
+ /** home 覆盖(测试用;生产缺省 os.homedir())。 */
43
+ home?: string;
44
+ /** 初始状态覆盖(测试 seed 用)。 */
45
+ initial?: ProjectsState;
46
+ /** 非致命 I/O 错误回调(接桥日志)。 */
47
+ onError?: (error: unknown) => void;
48
+ }
49
+ export declare class ProjectMap {
50
+ private state;
51
+ private pending;
52
+ /** 每次持久化点的状态快照(测试断言写路径;内存/文件版都记)。 */
53
+ readonly saves: ProjectsState[];
54
+ private readonly filePath;
55
+ private readonly projectRoot;
56
+ private readonly home;
57
+ private readonly onError;
58
+ constructor(opts: ProjectMapOptions);
59
+ /** 测试/内存版 fake:不落盘,但同样记 saves 快照。 */
60
+ static inMemory(opts?: {
61
+ projectRoot: string;
62
+ home?: string;
63
+ initial?: ProjectsState;
64
+ }): ProjectMap;
65
+ /** 解析相对/缺省工作目录(pi-courier 规则的唯一出口)。 */
66
+ resolvePath(spec: string): string;
67
+ load(): Promise<ProjectsState>;
68
+ /**
69
+ * T12(US37):提权账本的**活引用**(调用方就地增删后自行 save())。
70
+ * 刻意返回活引用而不是副本:账本由 power-sync 就地更新,拷出去记账就丢了。
71
+ */
72
+ powerLedger(): PowerLedger;
73
+ /** T12(US38):space 身份状态的**活引用**(调用方就地更新后自行 save)。 */
74
+ spaceIdentity(): SpaceIdentityState;
75
+ get(roomId: string): ProjectEntry | undefined;
76
+ list(): Array<[string, ProjectEntry]>;
77
+ /** 展示名规则(projectLabelOf):name 优先,缺省 workdir basename。 */
78
+ labelOf(roomId: string): string | undefined;
79
+ /** 注册项目:workdirSpec 走 resolvePath(缺省名 = 项目名目录)。 */
80
+ register(roomId: string, workdirSpec: string, name?: string): void;
81
+ unregister(roomId: string): void;
82
+ rename(roomId: string, name: string): boolean;
83
+ setWorkdir(roomId: string, workdirSpec: string): boolean;
84
+ getManagementRooms(): string[];
85
+ managementRoom(): string | undefined;
86
+ isManagementRoom(roomId: string): boolean;
87
+ isProjectRoom(roomId: string): boolean;
88
+ /** 收养:第一条即定,已有任何管理房就不再动(幂等)。 */
89
+ adoptManagementRoom(roomId: string): Promise<void>;
90
+ /** 装配层启动种子:config 值只补缺,持久层赢;管理房列表取并集。 */
91
+ seedFromConfig(projects?: Record<string, ProjectEntry>, managementRooms?: string[]): void;
92
+ /** Flush now(测试与收养路径 await 用)。 */
93
+ save(): Promise<void>;
94
+ private persist;
95
+ private write;
96
+ }
97
+ //# sourceMappingURL=projects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/bridge/projects.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,288 @@
1
+ /**
2
+ * T9: 房间→项目映射持久层(多项目的单一写路径)。
3
+ *
4
+ * DSH 桥的"项目"= 同一进程内不同 cwd 的会话映射(pi-courier 是每项目一个
5
+ * 子进程,这里只映射 cwd)。本模块只管映射与持久化:
6
+ * - projects: roomId → { name?, workdir }(US34/35)
7
+ * - managementRooms: 收养的管理房间(US35 门禁用,本票最小版)
8
+ * 落盘 ~/.dsh/dsh-matrix/projects.json(0600,tmp+rename 原子写,同 auth 存储);
9
+ * 测试用 ProjectMap.inMemory 的内存版。
10
+ *
11
+ * workdir 解析规则照 pi-courier pmctl-controller resolveProjectPath:
12
+ * `~`/`~/x` 展开 home;绝对路径原样;相对路径按工程根(projectRoot=cfg.workdir);
13
+ * `/pmctl new demo` 缺省路径 = <projectRoot>/demo。
14
+ */
15
+ import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
16
+ import { homedir } from 'node:os';
17
+ import { basename, dirname, isAbsolute, join, resolve as resolvePath } from 'node:path';
18
+ import { expandHome } from "../auth/store.js";
19
+ export function emptyProjectsState() {
20
+ return { projects: {}, managementRooms: [], powerGrants: {}, spaceIdentity: {} };
21
+ }
22
+ /** 宽容解析:留合法条目丢垃圾,永不抛(auth-store parseAuthState 同款策略)。 */
23
+ export function parseProjectsState(raw) {
24
+ const state = emptyProjectsState();
25
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
26
+ return state;
27
+ const v = raw;
28
+ if (typeof v.projects === 'object' && v.projects !== null && !Array.isArray(v.projects)) {
29
+ for (const [roomId, entry] of Object.entries(v.projects)) {
30
+ if (typeof entry !== 'object' || entry === null)
31
+ continue;
32
+ const e = entry;
33
+ if (typeof e.workdir !== 'string' || !e.workdir)
34
+ continue;
35
+ state.projects[roomId] = typeof e.name === 'string' && e.name
36
+ ? { name: e.name, workdir: e.workdir }
37
+ : { workdir: e.workdir };
38
+ }
39
+ }
40
+ if (Array.isArray(v.managementRooms)) {
41
+ state.managementRooms = v.managementRooms.filter((x) => typeof x === 'string' && x.length > 0);
42
+ }
43
+ state.powerGrants = parsePowerGrants(v.powerGrants);
44
+ state.spaceIdentity = parseSpaceIdentity(v.spaceIdentity);
45
+ return state;
46
+ }
47
+ /** T12: 宽容解析 space 身份 —— 只留合法字符串 roomId 与有限数字版本。 */
48
+ function parseSpaceIdentity(raw) {
49
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
50
+ return {};
51
+ const v = raw;
52
+ const out = {};
53
+ if (typeof v.roomId === 'string' && v.roomId)
54
+ out.roomId = v.roomId;
55
+ if (typeof v.avatarPoolVersion === 'number' && Number.isFinite(v.avatarPoolVersion)) {
56
+ out.avatarPoolVersion = v.avatarPoolVersion;
57
+ }
58
+ return out;
59
+ }
60
+ /** T12: 宽容解析提权账本 —— 只留"房间 ID → 非空用户 ID 数组",垃圾丢光,永不抛。 */
61
+ function parsePowerGrants(raw) {
62
+ const out = {};
63
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
64
+ return out;
65
+ for (const [roomId, users] of Object.entries(raw)) {
66
+ if (!roomId || !Array.isArray(users))
67
+ continue;
68
+ const kept = users.filter((u) => typeof u === 'string' && u.length > 0);
69
+ if (kept.length > 0)
70
+ out[roomId] = [...new Set(kept)];
71
+ }
72
+ return out;
73
+ }
74
+ /** 工作目录解析规则(纯函数,表驱动测试的单点)。 */
75
+ export function resolveWorkdir(spec, opts) {
76
+ const home = opts.home ?? homedir();
77
+ const projectRoot = expandHome(opts.projectRoot);
78
+ const p = spec.trim();
79
+ if (!p || p === '.')
80
+ return resolvePath(projectRoot);
81
+ if (p === '~')
82
+ return home;
83
+ if (p.startsWith('~/'))
84
+ return resolvePath(join(home, p.slice(2)));
85
+ if (isAbsolute(p))
86
+ return resolvePath(p);
87
+ return resolvePath(join(projectRoot, p));
88
+ }
89
+ /** 项目展示名规则(projectLabelOf):name 优先,缺省 workdir basename。 */
90
+ export function projectLabelOf(entry) {
91
+ const name = entry.name?.trim();
92
+ return name || basename(entry.workdir) || entry.workdir;
93
+ }
94
+ const MAX_LABEL_LENGTH = 30;
95
+ /**
96
+ * 校验用户给的项目名(移植 project-labels.ts validateProjectLabel):
97
+ * 非空 / 无方括号 / 无空白 / ≤30 字符 / 大小写不敏感唯一。
98
+ * 返回错误消息(中文,给用户看)或 null(可用)。
99
+ */
100
+ export function validateProjectLabel(candidate, existingLabels) {
101
+ const name = candidate.trim();
102
+ if (!name)
103
+ return '项目名不能为空';
104
+ if (/[[\]]/.test(name))
105
+ return '项目名不能包含方括号 [ ](会破坏日志格式)';
106
+ if (/\s/.test(name))
107
+ return '项目名不能包含空白字符';
108
+ if (name.length > MAX_LABEL_LENGTH)
109
+ return `项目名最长 ${MAX_LABEL_LENGTH} 字符(当前 ${name.length})`;
110
+ const lower = name.toLowerCase();
111
+ const clash = existingLabels.find((l) => l.toLowerCase() === lower);
112
+ if (clash)
113
+ return `项目名「${name}」与现有项目「${clash}」仅大小写不同(按名字匹配会混淆)`;
114
+ return null;
115
+ }
116
+ function cloneState(s) {
117
+ const projects = {};
118
+ for (const [roomId, e] of Object.entries(s.projects)) {
119
+ projects[roomId] = e.name !== undefined ? { name: e.name, workdir: e.workdir } : { workdir: e.workdir };
120
+ }
121
+ const powerGrants = {};
122
+ for (const [roomId, users] of Object.entries(s.powerGrants ?? {}))
123
+ powerGrants[roomId] = [...users];
124
+ return {
125
+ projects,
126
+ managementRooms: [...s.managementRooms],
127
+ powerGrants,
128
+ spaceIdentity: { ...(s.spaceIdentity ?? {}) },
129
+ };
130
+ }
131
+ export class ProjectMap {
132
+ state;
133
+ pending = Promise.resolve();
134
+ /** 每次持久化点的状态快照(测试断言写路径;内存/文件版都记)。 */
135
+ saves = [];
136
+ filePath;
137
+ projectRoot;
138
+ home;
139
+ onError;
140
+ constructor(opts) {
141
+ this.filePath = opts.filePath;
142
+ this.projectRoot = opts.projectRoot;
143
+ this.home = opts.home ?? homedir();
144
+ this.state = opts.initial ? cloneState(opts.initial) : emptyProjectsState();
145
+ this.onError = opts.onError;
146
+ }
147
+ /** 测试/内存版 fake:不落盘,但同样记 saves 快照。 */
148
+ static inMemory(opts = { projectRoot: '~' }) {
149
+ return new ProjectMap({ projectRoot: opts.projectRoot, ...(opts.home ? { home: opts.home } : {}), ...(opts.initial ? { initial: opts.initial } : {}) });
150
+ }
151
+ /** 解析相对/缺省工作目录(pi-courier 规则的唯一出口)。 */
152
+ resolvePath(spec) {
153
+ return resolveWorkdir(spec, { projectRoot: this.projectRoot, home: this.home });
154
+ }
155
+ async load() {
156
+ if (!this.filePath)
157
+ return cloneState(this.state);
158
+ try {
159
+ const raw = await readFile(expandHome(this.filePath), 'utf8');
160
+ this.state = parseProjectsState(JSON.parse(raw));
161
+ }
162
+ catch (error) {
163
+ // 首启 ENOENT 属正常;坏 JSON 降级空态,桥照常起。
164
+ if (error?.code !== 'ENOENT')
165
+ this.onError?.(error);
166
+ this.state = emptyProjectsState();
167
+ }
168
+ return cloneState(this.state);
169
+ }
170
+ // ── 映射读 ────────────────────────────────────────────────────────────────
171
+ /**
172
+ * T12(US37):提权账本的**活引用**(调用方就地增删后自行 save())。
173
+ * 刻意返回活引用而不是副本:账本由 power-sync 就地更新,拷出去记账就丢了。
174
+ */
175
+ powerLedger() {
176
+ return this.state.powerGrants;
177
+ }
178
+ /** T12(US38):space 身份状态的**活引用**(调用方就地更新后自行 save)。 */
179
+ spaceIdentity() {
180
+ return this.state.spaceIdentity;
181
+ }
182
+ get(roomId) {
183
+ return this.state.projects[roomId];
184
+ }
185
+ list() {
186
+ return Object.entries(this.state.projects);
187
+ }
188
+ /** 展示名规则(projectLabelOf):name 优先,缺省 workdir basename。 */
189
+ labelOf(roomId) {
190
+ const entry = this.state.projects[roomId];
191
+ if (!entry)
192
+ return undefined;
193
+ const name = entry.name?.trim();
194
+ return name || basename(entry.workdir) || entry.workdir;
195
+ }
196
+ // ── 映射写(单一写路径:改内存 → 快照 → 异步落盘) ──────────────────────
197
+ /** 注册项目:workdirSpec 走 resolvePath(缺省名 = 项目名目录)。 */
198
+ register(roomId, workdirSpec, name) {
199
+ const workdir = this.resolvePath(workdirSpec);
200
+ this.state.projects[roomId] = name ? { name, workdir } : { workdir };
201
+ this.persist();
202
+ }
203
+ unregister(roomId) {
204
+ delete this.state.projects[roomId];
205
+ this.persist();
206
+ }
207
+ rename(roomId, name) {
208
+ const entry = this.state.projects[roomId];
209
+ if (!entry)
210
+ return false;
211
+ this.state.projects[roomId] = { ...entry, name };
212
+ this.persist();
213
+ return true;
214
+ }
215
+ setWorkdir(roomId, workdirSpec) {
216
+ const entry = this.state.projects[roomId];
217
+ if (!entry)
218
+ return false;
219
+ this.state.projects[roomId] = { ...entry, workdir: this.resolvePath(workdirSpec) };
220
+ this.persist();
221
+ return true;
222
+ }
223
+ // ── 管理房间(US35 门禁的最小持久化) ─────────────────────────────────────
224
+ getManagementRooms() {
225
+ return [...this.state.managementRooms];
226
+ }
227
+ managementRoom() {
228
+ return this.state.managementRooms[0];
229
+ }
230
+ isManagementRoom(roomId) {
231
+ return this.state.managementRooms[0] === roomId;
232
+ }
233
+ isProjectRoom(roomId) {
234
+ return Boolean(this.state.projects[roomId]);
235
+ }
236
+ /** 收养:第一条即定,已有任何管理房就不再动(幂等)。 */
237
+ adoptManagementRoom(roomId) {
238
+ if (this.state.managementRooms.length > 0)
239
+ return Promise.resolve();
240
+ this.state.managementRooms = [roomId];
241
+ this.persist();
242
+ return this.save();
243
+ }
244
+ /** 装配层启动种子:config 值只补缺,持久层赢;管理房列表取并集。 */
245
+ seedFromConfig(projects, managementRooms) {
246
+ let touched = false;
247
+ if (projects) {
248
+ for (const [roomId, entry] of Object.entries(projects)) {
249
+ if (this.state.projects[roomId])
250
+ continue;
251
+ this.state.projects[roomId] = entry.name !== undefined ? { name: entry.name, workdir: entry.workdir } : { workdir: entry.workdir };
252
+ touched = true;
253
+ }
254
+ }
255
+ if (managementRooms) {
256
+ for (const roomId of managementRooms) {
257
+ if (!this.state.managementRooms.includes(roomId)) {
258
+ this.state.managementRooms.push(roomId);
259
+ touched = true;
260
+ }
261
+ }
262
+ }
263
+ if (touched)
264
+ this.persist();
265
+ }
266
+ // ── 持久化 ────────────────────────────────────────────────────────────────
267
+ /** Flush now(测试与收养路径 await 用)。 */
268
+ async save() {
269
+ this.pending = this.pending.then(() => this.write()).catch((error) => this.onError?.(error));
270
+ await this.pending;
271
+ }
272
+ persist() {
273
+ this.saves.push(cloneState(this.state));
274
+ if (!this.filePath)
275
+ return;
276
+ this.pending = this.pending.then(() => this.write()).catch((error) => this.onError?.(error));
277
+ }
278
+ async write() {
279
+ if (!this.filePath)
280
+ return;
281
+ const path = expandHome(this.filePath);
282
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
283
+ const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
284
+ await writeFile(tmp, JSON.stringify(this.state, null, 2), { mode: 0o600 });
285
+ await rename(tmp, path);
286
+ }
287
+ }
288
+ //# sourceMappingURL=projects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/bridge/projects.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAA;AACvF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAwB7C,MAAM,UAAU,kBAAkB;IAChC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,CAAA;AAClF,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,kBAAkB,CAAC,GAAY;IAC7C,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAA;IAClC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAA;IAC/E,MAAM,CAAC,GAAG,GAA8B,CAAA;IACxC,IAAI,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxF,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,QAAmC,CAAC,EAAE,CAAC;YACpF,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;gBAAE,SAAQ;YACzD,MAAM,CAAC,GAAG,KAAgC,CAAA;YAC1C,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,OAAO;gBAAE,SAAQ;YACzD,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI;gBAC3D,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;gBACtC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAA;QAC5B,CAAC;IACH,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC;QACrC,KAAK,CAAC,eAAe,GAAG,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAC7G,CAAC;IACD,KAAK,CAAC,WAAW,GAAG,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;IACnD,KAAK,CAAC,aAAa,GAAG,kBAAkB,CAAC,CAAC,CAAC,aAAa,CAAC,CAAA;IACzD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,oDAAoD;AACpD,SAAS,kBAAkB,CAAC,GAAY;IACtC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAA;IAC5E,MAAM,CAAC,GAAG,GAA8B,CAAA;IACxC,MAAM,GAAG,GAAuB,EAAE,CAAA;IAClC,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM;QAAE,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAA;IACnE,IAAI,OAAO,CAAC,CAAC,iBAAiB,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,EAAE,CAAC;QACpF,GAAG,CAAC,iBAAiB,GAAG,CAAC,CAAC,iBAAiB,CAAA;IAC7C,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,wDAAwD;AACxD,SAAS,gBAAgB,CAAC,GAAY;IACpC,MAAM,GAAG,GAAgB,EAAE,CAAA;IAC3B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAA;IAC7E,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAA8B,CAAC,EAAE,CAAC;QAC7E,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,SAAQ;QAC9C,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QACpF,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;IACvD,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,8BAA8B;AAC9B,MAAM,UAAU,cAAc,CAC5B,IAAY,EACZ,IAA4C;IAE5C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,OAAO,EAAE,CAAA;IACnC,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAChD,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;IACrB,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG;QAAE,OAAO,WAAW,CAAC,WAAW,CAAC,CAAA;IACpD,IAAI,CAAC,KAAK,GAAG;QAAE,OAAO,IAAI,CAAA;IAC1B,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAClE,IAAI,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,WAAW,CAAC,CAAC,CAAC,CAAA;IACxC,OAAO,WAAW,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAA;AAC1C,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,cAAc,CAAC,KAAmB;IAChD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,CAAA;IAC/B,OAAO,IAAI,IAAI,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,CAAA;AACzD,CAAC;AAED,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAE3B;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAiB,EAAE,cAAwB;IAC9E,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,EAAE,CAAA;IAC7B,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAA;IAC3B,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,yBAAyB,CAAA;IACxD,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,aAAa,CAAA;IACzC,IAAI,IAAI,CAAC,MAAM,GAAG,gBAAgB;QAAE,OAAO,SAAS,gBAAgB,UAAU,IAAI,CAAC,MAAM,GAAG,CAAA;IAC5F,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;IAChC,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAA;IACnE,IAAI,KAAK;QAAE,OAAO,OAAO,IAAI,UAAU,KAAK,mBAAmB,CAAA;IAC/D,OAAO,IAAI,CAAA;AACb,CAAC;AAeD,SAAS,UAAU,CAAC,CAAgB;IAClC,MAAM,QAAQ,GAAiC,EAAE,CAAA;IACjD,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrD,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAA;IACzG,CAAC;IACD,MAAM,WAAW,GAAgB,EAAE,CAAA;IACnC,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC;QAAE,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAA;IACnG,OAAO;QACL,QAAQ;QACR,eAAe,EAAE,CAAC,GAAG,CAAC,CAAC,eAAe,CAAC;QACvC,WAAW;QACX,aAAa,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,aAAa,IAAI,EAAE,CAAC,EAAE;KAC9C,CAAA;AACH,CAAC;AAED,MAAM,OAAO,UAAU;IACb,KAAK,CAAe;IACpB,OAAO,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAA;IAClD,qCAAqC;IAC5B,KAAK,GAAoB,EAAE,CAAA;IACnB,QAAQ,CAAoB;IAC5B,WAAW,CAAQ;IACnB,IAAI,CAAQ;IACZ,OAAO,CAAwC;IAEhE,YAAY,IAAuB;QACjC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAA;QAC7B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAA;QACnC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,OAAO,EAAE,CAAA;QAClC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,kBAAkB,EAAE,CAAA;QAC3E,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;IAC7B,CAAC;IAED,qCAAqC;IACrC,MAAM,CAAC,QAAQ,CAAC,OAAwE,EAAE,WAAW,EAAE,GAAG,EAAE;QAC1G,OAAO,IAAI,UAAU,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;IACzJ,CAAC;IAED,uCAAuC;IACvC,WAAW,CAAC,IAAY;QACtB,OAAO,cAAc,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;IACjF,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACjD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAA;YAC7D,IAAI,CAAC,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;QAClD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,kCAAkC;YAClC,IAAK,KAA+B,EAAE,IAAI,KAAK,QAAQ;gBAAE,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAA;YAC9E,IAAI,CAAC,KAAK,GAAG,kBAAkB,EAAE,CAAA;QACnC,CAAC;QACD,OAAO,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC/B,CAAC;IAED,0EAA0E;IAE1E;;;OAGG;IACH,WAAW;QACT,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAA;IAC/B,CAAC;IAED,qDAAqD;IACrD,aAAa;QACX,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAA;IACjC,CAAC;IAGD,GAAG,CAAC,MAAc;QAChB,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IACpC,CAAC;IAED,IAAI;QACF,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAC5C,CAAC;IAED,yDAAyD;IACzD,OAAO,CAAC,MAAc;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACzC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAA;QAC5B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,CAAA;QAC/B,OAAO,IAAI,IAAI,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,CAAA;IACzD,CAAC;IAED,uDAAuD;IAEvD,mDAAmD;IACnD,QAAQ,CAAC,MAAc,EAAE,WAAmB,EAAE,IAAa;QACzD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,CAAA;QAC7C,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAA;QACpE,IAAI,CAAC,OAAO,EAAE,CAAA;IAChB,CAAC;IAED,UAAU,CAAC,MAAc;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QAClC,IAAI,CAAC,OAAO,EAAE,CAAA;IAChB,CAAC;IAED,MAAM,CAAC,MAAc,EAAE,IAAY;QACjC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACzC,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAA;QACxB,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,CAAA;QAChD,IAAI,CAAC,OAAO,EAAE,CAAA;QACd,OAAO,IAAI,CAAA;IACb,CAAC;IAED,UAAU,CAAC,MAAc,EAAE,WAAmB;QAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACzC,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAA;QACxB,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,CAAA;QAClF,IAAI,CAAC,OAAO,EAAE,CAAA;QACd,OAAO,IAAI,CAAA;IACb,CAAC;IAED,+DAA+D;IAE/D,kBAAkB;QAChB,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAA;IACxC,CAAC;IAED,cAAc;QACZ,OAAO,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,CAAA;IACtC,CAAC;IAED,gBAAgB,CAAC,MAAc;QAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,KAAK,MAAM,CAAA;IACjD,CAAC;IAED,aAAa,CAAC,MAAc;QAC1B,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAA;IAC7C,CAAC;IAED,gCAAgC;IAChC,mBAAmB,CAAC,MAAc;QAChC,IAAI,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAA;QACnE,IAAI,CAAC,KAAK,CAAC,eAAe,GAAG,CAAC,MAAM,CAAC,CAAA;QACrC,IAAI,CAAC,OAAO,EAAE,CAAA;QACd,OAAO,IAAI,CAAC,IAAI,EAAE,CAAA;IACpB,CAAC;IAED,yCAAyC;IACzC,cAAc,CACZ,QAAuC,EACvC,eAA0B;QAE1B,IAAI,OAAO,GAAG,KAAK,CAAA;QACnB,IAAI,QAAQ,EAAE,CAAC;YACb,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACvD,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;oBAAE,SAAQ;gBACzC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAA;gBAClI,OAAO,GAAG,IAAI,CAAA;YAChB,CAAC;QACH,CAAC;QACD,IAAI,eAAe,EAAE,CAAC;YACpB,KAAK,MAAM,MAAM,IAAI,eAAe,EAAE,CAAC;gBACrC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;oBACjD,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;oBACvC,OAAO,GAAG,IAAI,CAAA;gBAChB,CAAC;YACH,CAAC;QACH,CAAC;QACD,IAAI,OAAO;YAAE,IAAI,CAAC,OAAO,EAAE,CAAA;IAC7B,CAAC;IAED,0EAA0E;IAE1E,kCAAkC;IAClC,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;QAC5F,MAAM,IAAI,CAAC,OAAO,CAAA;IACpB,CAAC;IAEO,OAAO;QACb,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;QACvC,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAM;QAC1B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IAC9F,CAAC;IAEO,KAAK,CAAC,KAAK;QACjB,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAM;QAC1B,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACtC,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QAC5D,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,MAAM,CAAA;QACtD,MAAM,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QAC1E,MAAM,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IACzB,CAAC;CACF"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * T8 (US33): reply-quote ring cache — ported from pi-courier src/quote-cache.ts
3
+ * (MIT). Pure, in-memory, no I/O.
4
+ *
5
+ * The bridge records every inbound message's event_id → excerpt (per room,
6
+ * FIFO eviction at MAX_ENTRIES_PER_ROOM) so a later reply-to a historical
7
+ * message can carry a short quote of the referenced text into the prompt.
8
+ * This is how "这条再改改" becomes resolvable for the agent.
9
+ *
10
+ * Deliberately memory-bounded: 50 entries/room, excerpt capped at 200 chars.
11
+ * Cache misses are a normal, silent downgrade — quotes are best-effort context.
12
+ */
13
+ /** A cleaned one-line excerpt of a quoted message plus its sender. */
14
+ export interface QuoteEntry {
15
+ username: string;
16
+ excerpt: string;
17
+ }
18
+ export interface QuoteCache {
19
+ /** Record a message for later quote lookups in that room. */
20
+ record(roomId: string, eventId: string, entry: QuoteEntry): void;
21
+ /** The recorded entry, or undefined when unknown (miss = silent no-quote). */
22
+ lookup(roomId: string, eventId: string): QuoteEntry | undefined;
23
+ }
24
+ /** Ring capacity per room; beyond this the oldest messages fall out. */
25
+ export declare const MAX_ENTRIES_PER_ROOM = 50;
26
+ /** Excerpt cap: enough context to identify a message, small enough for a prompt prefix. */
27
+ export declare const EXCERPT_MAX_CHARS = 200;
28
+ /** Single-line, trimmed excerpt capped at EXCERPT_MAX_CHARS ("…" marks truncation). */
29
+ export declare function toExcerpt(body: string, max?: number): string;
30
+ /** Extract the localpart from a full MXID like @user:matrix.org. */
31
+ export declare function extractUsername(userId: string): string;
32
+ /**
33
+ * Per-room FIFO cache. Insertion-ordered Maps give O(1) refresh/evict:
34
+ * re-recording an existing event id deletes then re-inserts it, so the ring
35
+ * evicts by last-seen order and a refresh never wastes a slot.
36
+ */
37
+ export declare function createQuoteCache(): QuoteCache;
38
+ //# sourceMappingURL=quote-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quote-cache.d.ts","sourceRoot":"","sources":["../../src/bridge/quote-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,sEAAsE;AACtE,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,6DAA6D;IAC7D,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IAChE,8EAA8E;IAC9E,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAA;CAChE;AAED,wEAAwE;AACxE,eAAO,MAAM,oBAAoB,KAAK,CAAA;AAEtC,2FAA2F;AAC3F,eAAO,MAAM,iBAAiB,MAAM,CAAA;AAEpC,uFAAuF;AACvF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAoB,GAAG,MAAM,CAGvE;AAED,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,UAAU,CAsB7C"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * T8 (US33): reply-quote ring cache — ported from pi-courier src/quote-cache.ts
3
+ * (MIT). Pure, in-memory, no I/O.
4
+ *
5
+ * The bridge records every inbound message's event_id → excerpt (per room,
6
+ * FIFO eviction at MAX_ENTRIES_PER_ROOM) so a later reply-to a historical
7
+ * message can carry a short quote of the referenced text into the prompt.
8
+ * This is how "这条再改改" becomes resolvable for the agent.
9
+ *
10
+ * Deliberately memory-bounded: 50 entries/room, excerpt capped at 200 chars.
11
+ * Cache misses are a normal, silent downgrade — quotes are best-effort context.
12
+ */
13
+ /** Ring capacity per room; beyond this the oldest messages fall out. */
14
+ export const MAX_ENTRIES_PER_ROOM = 50;
15
+ /** Excerpt cap: enough context to identify a message, small enough for a prompt prefix. */
16
+ export const EXCERPT_MAX_CHARS = 200;
17
+ /** Single-line, trimmed excerpt capped at EXCERPT_MAX_CHARS ("…" marks truncation). */
18
+ export function toExcerpt(body, max = EXCERPT_MAX_CHARS) {
19
+ const oneLine = body.replace(/\s+/g, ' ').trim();
20
+ return oneLine.length > max ? `${oneLine.slice(0, max)}…` : oneLine;
21
+ }
22
+ /** Extract the localpart from a full MXID like @user:matrix.org. */
23
+ export function extractUsername(userId) {
24
+ return userId.replace(/^@/, '').replace(/:.*$/, '');
25
+ }
26
+ /**
27
+ * Per-room FIFO cache. Insertion-ordered Maps give O(1) refresh/evict:
28
+ * re-recording an existing event id deletes then re-inserts it, so the ring
29
+ * evicts by last-seen order and a refresh never wastes a slot.
30
+ */
31
+ export function createQuoteCache() {
32
+ const rooms = new Map();
33
+ return {
34
+ record(roomId, eventId, entry) {
35
+ if (!eventId || !entry.excerpt)
36
+ return;
37
+ let room = rooms.get(roomId);
38
+ if (!room) {
39
+ room = new Map();
40
+ rooms.set(roomId, room);
41
+ }
42
+ room.delete(eventId);
43
+ room.set(eventId, entry);
44
+ while (room.size > MAX_ENTRIES_PER_ROOM) {
45
+ const oldest = room.keys().next().value;
46
+ if (oldest === undefined)
47
+ break;
48
+ room.delete(oldest);
49
+ }
50
+ },
51
+ lookup(roomId, eventId) {
52
+ return rooms.get(roomId)?.get(eventId);
53
+ },
54
+ };
55
+ }
56
+ //# sourceMappingURL=quote-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quote-cache.js","sourceRoot":"","sources":["../../src/bridge/quote-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAeH,wEAAwE;AACxE,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAA;AAEtC,2FAA2F;AAC3F,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAA;AAEpC,uFAAuF;AACvF,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,GAAG,GAAG,iBAAiB;IAC7D,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;IAChD,OAAO,OAAO,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAA;AACrE,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;AACrD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAmC,CAAA;IACxD,OAAO;QACL,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK;YAC3B,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO;gBAAE,OAAM;YACtC,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,IAAI,GAAG,IAAI,GAAG,EAAE,CAAA;gBAChB,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;YACzB,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YACpB,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;YACxB,OAAO,IAAI,CAAC,IAAI,GAAG,oBAAoB,EAAE,CAAC;gBACxC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAA;gBACvC,IAAI,MAAM,KAAK,SAAS;oBAAE,MAAK;gBAC/B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YACrB,CAAC;QACH,CAAC;QACD,MAAM,CAAC,MAAM,EAAE,OAAO;YACpB,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;QACxC,CAAC;KACF,CAAA;AACH,CAAC"}