@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,31 @@
1
+ import type { Transport } from '../ports/transport.ts';
2
+ import type { TurnStreamSource } from '../ports/streaming.ts';
3
+ /** Default edit throttle (ticket T7: ~1.5s between Matrix edits). */
4
+ export declare const STREAM_THROTTLE_MS = 1500;
5
+ /** Body shown when a turn ends with nothing streamed and no reply text. */
6
+ export declare const NO_OUTPUT_BODY = "(\u672C\u8F6E\u6CA1\u6709\u6587\u672C\u8F93\u51FA)";
7
+ export interface TurnResponderOptions {
8
+ transport: Transport;
9
+ roomId: string;
10
+ /** Hard ceiling for any single Matrix body (placeholder clip + final split). */
11
+ maxChunk: number;
12
+ /** Streaming source; with `sessionId` present = stream mode. */
13
+ stream?: TurnStreamSource;
14
+ /** DSH session whose stream drives the placeholder. */
15
+ sessionId?: string;
16
+ /** Injectable clock (ms) so throttle windows are test-deterministic. */
17
+ now?: () => number;
18
+ throttleMs?: number;
19
+ /** Placeholder body sent at start. */
20
+ placeholder?: string;
21
+ }
22
+ export interface TurnResponder {
23
+ /** Stream mode: send the placeholder, then subscribe. Otherwise a no-op. */
24
+ start(): Promise<void>;
25
+ /** Turn settled: land the complete reply and release everything. */
26
+ finalize(fullText: string | undefined): Promise<void>;
27
+ /** Detach the stream without landing anything (idempotent). */
28
+ dispose(): void;
29
+ }
30
+ export declare function createTurnResponder(opts: TurnResponderOptions): TurnResponder;
31
+ //# sourceMappingURL=reply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reply.d.ts","sourceRoot":"","sources":["../../src/bridge/reply.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAEjF,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,OAAO,CAAA;AAEtC,2EAA2E;AAC3E,eAAO,MAAM,cAAc,uDAAe,CAAA;AAE1C,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,SAAS,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,QAAQ,EAAE,MAAM,CAAA;IAChB,gEAAgE;IAChE,MAAM,CAAC,EAAE,gBAAgB,CAAA;IACzB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wEAAwE;IACxE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,4EAA4E;IAC5E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrD,+DAA+D;IAC/D,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,oBAAoB,GAAG,aAAa,CA+F7E"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * T7 turn responder: how one agent answer lands in the room.
3
+ *
4
+ * Non-stream mode (no source): byte-for-byte the T1 behavior - finalize sends
5
+ * the splitMessage chunks as fresh messages, nothing happens while the turn
6
+ * runs.
7
+ *
8
+ * Stream mode: start() posts a '…' placeholder (m.text, kept editable) and
9
+ * subscribes to the session's TurnStreamSource. Each text delta accumulates
10
+ * into a local buffer; at most one edit per throttle window renders it
11
+ * (tail-clipped to maxChunk + '…'). Turn completion is the pipeline's
12
+ * whenIdle - the stream's end frames settle single model ATTEMPTS, not the
13
+ * turn - and an abandoned attempt rewinds the buffer so retry content never
14
+ * stays in the room. finalize() retracts everything the stream guessed: the
15
+ * first split chunk EDITS the placeholder, any rest follows as new messages.
16
+ */
17
+ import { splitMessage } from "../formatting.js";
18
+ /** Default edit throttle (ticket T7: ~1.5s between Matrix edits). */
19
+ export const STREAM_THROTTLE_MS = 1500;
20
+ /** Body shown when a turn ends with nothing streamed and no reply text. */
21
+ export const NO_OUTPUT_BODY = '(本轮没有文本输出)';
22
+ export function createTurnResponder(opts) {
23
+ const now = opts.now ?? (() => Date.now());
24
+ const throttleMs = opts.throttleMs ?? STREAM_THROTTLE_MS;
25
+ const placeholder = opts.placeholder ?? '…';
26
+ const streaming = opts.stream !== undefined && opts.sessionId !== undefined;
27
+ let buffer = '';
28
+ /** buffer.length at the current attempt's start - the abandoned rewind point. */
29
+ let attemptMark = 0;
30
+ let editId;
31
+ /** Seeded so the FIRST delta edits immediately; later ones wait out the window. */
32
+ let lastEditAt = now() - throttleMs;
33
+ let rendered;
34
+ /** Serialized edit chain: no streaming edit may land after the final one. */
35
+ let chain = Promise.resolve();
36
+ let unsubscribe;
37
+ let done = false;
38
+ function clip(text) {
39
+ if (text.length <= opts.maxChunk)
40
+ return text;
41
+ return `${text.slice(0, Math.max(1, opts.maxChunk - 1))}…`;
42
+ }
43
+ function queueEdit(body) {
44
+ if (done || editId === undefined)
45
+ return;
46
+ const shown = body.length > 0 ? clip(body) : placeholder;
47
+ if (shown === rendered)
48
+ return; // identical render → skip the redundant edit
49
+ rendered = shown;
50
+ lastEditAt = now();
51
+ const id = editId;
52
+ chain = chain
53
+ .then(() => opts.transport.edit(opts.roomId, id, shown))
54
+ .catch(() => undefined); // edit is best-effort (port contract)
55
+ }
56
+ const handlers = {
57
+ onTextDelta(text) {
58
+ if (done || !text)
59
+ return;
60
+ buffer += text;
61
+ if (now() - lastEditAt >= throttleMs)
62
+ queueEdit(buffer);
63
+ },
64
+ onTurnEnd() {
65
+ queueEdit(buffer); // turn's last word from the stream side: flush
66
+ },
67
+ onAttemptStart() {
68
+ if (!done)
69
+ attemptMark = buffer.length;
70
+ },
71
+ onAttemptAbandoned() {
72
+ if (done)
73
+ return;
74
+ buffer = buffer.slice(0, attemptMark);
75
+ queueEdit(buffer); // retract the retry text from the room right away
76
+ },
77
+ };
78
+ return {
79
+ async start() {
80
+ if (!streaming || done)
81
+ return;
82
+ try {
83
+ editId = await opts.transport.send(opts.roomId, placeholder);
84
+ rendered = placeholder;
85
+ }
86
+ catch {
87
+ editId = undefined; // placeholder failed → degrade to plain sends
88
+ }
89
+ if (!done) {
90
+ unsubscribe = opts.stream.subscribe(opts.sessionId, handlers);
91
+ }
92
+ },
93
+ async finalize(fullText) {
94
+ if (done)
95
+ return;
96
+ done = true;
97
+ unsubscribe?.();
98
+ unsubscribe = undefined;
99
+ const body = fullText ?? NO_OUTPUT_BODY;
100
+ const chunks = splitMessage(body, opts.maxChunk);
101
+ if (editId !== undefined) {
102
+ const id = editId;
103
+ await chain; // land all streamed edits before overwriting with the truth
104
+ await opts.transport.edit(opts.roomId, id, chunks[0] ?? body).catch(() => undefined);
105
+ for (const chunk of chunks.slice(1)) {
106
+ await opts.transport.send(opts.roomId, chunk);
107
+ }
108
+ }
109
+ else {
110
+ for (const chunk of chunks) {
111
+ await opts.transport.send(opts.roomId, chunk);
112
+ }
113
+ }
114
+ },
115
+ dispose() {
116
+ done = true;
117
+ unsubscribe?.();
118
+ unsubscribe = undefined;
119
+ },
120
+ };
121
+ }
122
+ //# sourceMappingURL=reply.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reply.js","sourceRoot":"","sources":["../../src/bridge/reply.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /** 共享头像池的图片数量(instance-01 … instance-12)。 */
2
+ export declare const AVATAR_POOL_SIZE = 12;
3
+ /**
4
+ * 内置头像池的版本标记。**只在整个池子换风格时**才 +1:配置里记的版本落后于它时,
5
+ * 启动自愈会把受管房间的头像重铺一次,然后记下新版本、回到"只补缺"模式。
6
+ * 本仓库从 1 起步(没有需要迁就的历史标记;上游的 3 是它自己的迁移账)。
7
+ */
8
+ export declare const AVATAR_POOL_VERSION = 1;
9
+ /** space 展示名:实例名前面加一个 π 前缀。 */
10
+ export declare function spaceDisplayName(instanceName: string): string;
11
+ /**
12
+ * 旧模板名字的精确匹配。只认**完全相等**,所以用户手打的同名之外的名字永远不会
13
+ * 被启动自愈改掉。保留 pi-courier 的旧模板,是为了让从上游迁过来的部署能认出并
14
+ * 复用/改名它原来的 space,而不是又建一个。
15
+ */
16
+ export declare function isLegacySpaceName(name: string, instanceName: string): boolean;
17
+ /** djb2 over UTF-8 bytes —— 极小、跨进程与运行时稳定(纯 u32 运算,不依赖 locale)。 */
18
+ export declare function nameHash(name: string): number;
19
+ /** 按名字挑一张池内图片:同一个名字永远同一张。 */
20
+ export declare function pickPoolAvatarFile(name: string): string;
21
+ /** 管理房间的专属图片(固定一张,不与池子撞)。 */
22
+ export declare function managementAvatarFile(): string;
23
+ /** 是否开启了空间模式(缺省 false:不建 space、不挂头像)。 */
24
+ export declare function isSpaceMode(space: {
25
+ enabled: boolean;
26
+ } | undefined): boolean;
27
+ /** 头像池默认目录:`<包根>/assets/avatars`(src/ 与 dist/ 下都成立)。 */
28
+ export declare function avatarDir(): string;
29
+ /**
30
+ * 读一张内置头像。文件名先过白名单(只允许 `[\w.-]+.png`)——这一层是**防穿越**的
31
+ * 关键:头像文件名来自配置/名字哈希,但白名单保证它永远出不了 assets/avatars。
32
+ * `dir` 可注入,便于测试用夹具目录。
33
+ */
34
+ export declare function readAvatarBundled(file: string, dir?: string): Buffer;
35
+ /**
36
+ * m.room.avatar 的 `info` 子块。
37
+ *
38
+ * **尺寸从 PNG 的 IHDR 里读**(固定在第 16..24 字节,大端 u32),不硬编码:
39
+ * 我们的头像池是缩过图的(128×128),照抄上游的 512 会给出**假的元数据** ——
40
+ * 客户端据此排版会错,而且这是实打实的谎报。非 PNG 则只报 mimetype/size。
41
+ */
42
+ export declare function avatarInfo(data: Buffer): Record<string, unknown>;
43
+ //# sourceMappingURL=space-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"space-identity.d.ts","sourceRoot":"","sources":["../../src/bridge/space-identity.ts"],"names":[],"mappings":"AAqBA,6CAA6C;AAC7C,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAElC;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAEpC,+BAA+B;AAC/B,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAE7E;AAED,kEAAkE;AAClE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI7C;AAED,6BAA6B;AAC7B,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED,6BAA6B;AAC7B,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,yCAAyC;AACzC,wBAAgB,WAAW,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,GAAG,OAAO,CAE5E;AAED,wDAAwD;AACxD,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,MAAoB,GAAG,MAAM,CAGjF;AAKD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOhE"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * T12 (US37/38):房间身份 —— 桥怎么给自己的房间命名与挂头像。
3
+ *
4
+ * 移植自 pi-courier `src/space-identity.ts`(MIT,见 THIRD_PARTY_NOTICES.md),
5
+ * 按 DSH 语境取用:
6
+ *
7
+ * - space 名 `π <实例名>`:短品牌前缀;旧模板只在**精确匹配**时才改名(见
8
+ * isLegacySpaceName),用户自己起的名字永不被启动自愈覆盖。
9
+ * - 每个受管房间挂一张内置头像:space 按**实例名**哈希、项目房按**项目名**哈希,
10
+ * 所以同名永远同一张(重建/重启都不变),不同实例/项目通常不同;管理房固定用
11
+ * management.png。
12
+ * - 头像池落在包根的 `assets/avatars/`(12 张 instance-NN.png + management.png,
13
+ * 512×512)。用户想换成自己的图,同名覆盖即可,不用改代码。
14
+ *
15
+ * 这里的函数都是**纯的**(除了 readAvatarBundled 读文件),因此可以在没有真实
16
+ * Matrix 连接的情况下把命名与选图规则钉死。
17
+ */
18
+ import { readFileSync } from 'node:fs';
19
+ import * as path from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+ /** 共享头像池的图片数量(instance-01 … instance-12)。 */
22
+ export const AVATAR_POOL_SIZE = 12;
23
+ /**
24
+ * 内置头像池的版本标记。**只在整个池子换风格时**才 +1:配置里记的版本落后于它时,
25
+ * 启动自愈会把受管房间的头像重铺一次,然后记下新版本、回到"只补缺"模式。
26
+ * 本仓库从 1 起步(没有需要迁就的历史标记;上游的 3 是它自己的迁移账)。
27
+ */
28
+ export const AVATAR_POOL_VERSION = 1;
29
+ /** space 展示名:实例名前面加一个 π 前缀。 */
30
+ export function spaceDisplayName(instanceName) {
31
+ return `π ${instanceName}`;
32
+ }
33
+ /**
34
+ * 旧模板名字的精确匹配。只认**完全相等**,所以用户手打的同名之外的名字永远不会
35
+ * 被启动自愈改掉。保留 pi-courier 的旧模板,是为了让从上游迁过来的部署能认出并
36
+ * 复用/改名它原来的 space,而不是又建一个。
37
+ */
38
+ export function isLegacySpaceName(name, instanceName) {
39
+ return name === `pi-courier · ${instanceName}`;
40
+ }
41
+ /** djb2 over UTF-8 bytes —— 极小、跨进程与运行时稳定(纯 u32 运算,不依赖 locale)。 */
42
+ export function nameHash(name) {
43
+ let hash = 5381;
44
+ for (const byte of Buffer.from(name, 'utf8'))
45
+ hash = (hash * 33 + byte) >>> 0;
46
+ return hash;
47
+ }
48
+ /** 按名字挑一张池内图片:同一个名字永远同一张。 */
49
+ export function pickPoolAvatarFile(name) {
50
+ const index = (nameHash(name) % AVATAR_POOL_SIZE) + 1;
51
+ return `instance-${String(index).padStart(2, '0')}.png`;
52
+ }
53
+ /** 管理房间的专属图片(固定一张,不与池子撞)。 */
54
+ export function managementAvatarFile() {
55
+ return 'management.png';
56
+ }
57
+ /** 是否开启了空间模式(缺省 false:不建 space、不挂头像)。 */
58
+ export function isSpaceMode(space) {
59
+ return space?.enabled === true;
60
+ }
61
+ /** 头像池默认目录:`<包根>/assets/avatars`(src/ 与 dist/ 下都成立)。 */
62
+ export function avatarDir() {
63
+ return path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'assets', 'avatars');
64
+ }
65
+ /**
66
+ * 读一张内置头像。文件名先过白名单(只允许 `[\w.-]+.png`)——这一层是**防穿越**的
67
+ * 关键:头像文件名来自配置/名字哈希,但白名单保证它永远出不了 assets/avatars。
68
+ * `dir` 可注入,便于测试用夹具目录。
69
+ */
70
+ export function readAvatarBundled(file, dir = avatarDir()) {
71
+ if (!/^[\w.-]+\.png$/.test(file))
72
+ throw new Error(`非法的头像文件名: ${file}`);
73
+ return readFileSync(path.join(dir, file));
74
+ }
75
+ /** PNG 文件头(用来判"这是不是 PNG",以及读 IHDR 里的真实尺寸)。 */
76
+ const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
77
+ /**
78
+ * m.room.avatar 的 `info` 子块。
79
+ *
80
+ * **尺寸从 PNG 的 IHDR 里读**(固定在第 16..24 字节,大端 u32),不硬编码:
81
+ * 我们的头像池是缩过图的(128×128),照抄上游的 512 会给出**假的元数据** ——
82
+ * 客户端据此排版会错,而且这是实打实的谎报。非 PNG 则只报 mimetype/size。
83
+ */
84
+ export function avatarInfo(data) {
85
+ const info = { mimetype: 'image/png', size: data.length };
86
+ if (data.length >= 24 && data.subarray(0, 8).equals(PNG_MAGIC)) {
87
+ info.width = data.readUInt32BE(16);
88
+ info.height = data.readUInt32BE(20);
89
+ }
90
+ return info;
91
+ }
92
+ //# sourceMappingURL=space-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"space-identity.js","sourceRoot":"","sources":["../../src/bridge/space-identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * T12 (US38):space 归集与头像自愈。
3
+ *
4
+ * 规则(移植自 pi-courier src/space.ts 的 identity 部分,MIT,按本仓库语境收紧):
5
+ *
6
+ * - **space**:开启空间模式时,给实例建一个私有 space `π <实例名>`;已认领过就复用
7
+ * (不重复建),然后把受管房间(管理房 + 项目房)都挂进去(`m.space.child`)。
8
+ * - **头像**:space 按**实例名**哈希、项目房按**项目名**哈希、管理房用专属图;
9
+ * 同名永远同一张(重建/重启都不变)。
10
+ * - **永不覆盖用户手动设置的头像**:只在"房间还没有头像"时补挂;唯一的例外是
11
+ * 内置头像池**整体换风格**(配置里记的版本落后于 AVATAR_POOL_VERSION),那时
12
+ * 按上游语义重铺一次,铺完把版本记上,之后回到"只补缺"。
13
+ * - **版本只在整趟都没失败时才记**:否则部分房间没铺上,却因为版本已记而永远
14
+ * 不再重铺。
15
+ *
16
+ * 本模块不碰持久层:它就地更新传入的 `state`,由调用方决定何时存盘(与 power-sync
17
+ * 同一分工)。头像字节通过注入的 `readAvatar` 读取,便于测试用夹具、也让"池子缺图"
18
+ * 这条失败路径可测。
19
+ */
20
+ import type { Logger } from '../types.ts';
21
+ /** space 与头像的持久状态(调用方负责存盘)。 */
22
+ export interface SpaceIdentityState {
23
+ /** 已认领的 space 房间 ID;复用而不是重复建。 */
24
+ roomId?: string;
25
+ /** 已铺过的内置头像池版本;落后于 AVATAR_POOL_VERSION 时重铺一次。 */
26
+ avatarPoolVersion?: number;
27
+ }
28
+ /** 空间操作需要的原语(Transport 上都是可选的;缺关键项就整体跳过)。 */
29
+ export interface SpaceOps {
30
+ createSpace?(opts: {
31
+ name: string;
32
+ inviteUserId?: string;
33
+ }): Promise<string>;
34
+ getRoomAvatar?(roomId: string): Promise<{
35
+ url?: string;
36
+ info?: Record<string, unknown>;
37
+ } | undefined>;
38
+ setRoomAvatar?(roomId: string, png: Buffer, info?: Record<string, unknown>): Promise<void>;
39
+ addRoomToSpace?(spaceId: string, roomId: string, via?: string): Promise<void>;
40
+ /** 读内置头像(缺省走包内 assets/avatars;测试注入夹具目录)。 */
41
+ readAvatar?(file: string): Buffer;
42
+ }
43
+ export interface SpaceFailure {
44
+ what: 'space' | 'avatar' | 'child';
45
+ roomId?: string;
46
+ error: string;
47
+ }
48
+ export interface SpaceSyncResult {
49
+ /** 本次是否新建了 space(复用时为 false)。 */
50
+ spaceCreated: boolean;
51
+ spaceId?: string;
52
+ avatarsSet: string[];
53
+ avatarsKept: string[];
54
+ childrenAdded: string[];
55
+ failures: SpaceFailure[];
56
+ }
57
+ export type AvatarTarget = {
58
+ kind: 'management';
59
+ } | {
60
+ kind: 'pool';
61
+ name: string;
62
+ };
63
+ /** 头像文件名:管理房专属图,其它按名字哈希进池子。 */
64
+ export declare function avatarFileFor(target: AvatarTarget): string;
65
+ /** space 是否还能建(缺 createSpace 就跳过)。 */
66
+ export declare function canManageSpace(ops: SpaceOps): boolean;
67
+ /**
68
+ * 确保有一个可用的 space:已认领过就复用(哪怕它已不存在 —— 那是自愈下一趟的事,
69
+ * 不该在本趟重复建房),否则建一个并就地记账。
70
+ */
71
+ export declare function ensureSpace(instanceName: string, state: SpaceIdentityState, ops: SpaceOps, logger?: Logger): Promise<SpaceSyncResult>;
72
+ /**
73
+ * 确保房间头像正确。返回 'set'(补挂/重铺)或 'kept'(已有且不需要重铺)。
74
+ *
75
+ * 重铺的唯一触发条件是**池子版本落后**:用户手动换过的头像在那之前一直会被保住。
76
+ */
77
+ export declare function ensureRoomAvatar(roomId: string, target: AvatarTarget, state: SpaceIdentityState, ops: SpaceOps, logger?: Logger): Promise<'set' | 'kept' | 'failed'>;
78
+ /** 把房间挂进 space(盲写 m.space.child;重复写是幂等的,不额外读状态)。 */
79
+ export declare function ensureRoomInSpace(spaceId: string, roomId: string, ops: SpaceOps, logger?: Logger): Promise<boolean>;
80
+ /**
81
+ * 启动对账:建/复用 space → 给每个房间补头像 → 把每个房间挂进 space。
82
+ *
83
+ * `targets` 给出每个房间该用哪张图(管理房 / 项目名)。池子版本**只在整趟零失败时**
84
+ * 才记上,否则部分房间没铺到却再也不会重铺。
85
+ */
86
+ export declare function healSpaceIdentity(input: {
87
+ instanceName: string;
88
+ /** 受管房间:roomId → 该用哪张头像。 */
89
+ rooms: ReadonlyArray<{
90
+ roomId: string;
91
+ target: AvatarTarget;
92
+ }>;
93
+ }, state: SpaceIdentityState, ops: SpaceOps, logger?: Logger): Promise<SpaceSyncResult>;
94
+ //# sourceMappingURL=space-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"space-sync.d.ts","sourceRoot":"","sources":["../../src/bridge/space-sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,137 @@
1
+ import { AVATAR_POOL_VERSION, avatarInfo, managementAvatarFile, pickPoolAvatarFile, readAvatarBundled, spaceDisplayName, } from "./space-identity.js";
2
+ function emptyResult() {
3
+ return { spaceCreated: false, avatarsSet: [], avatarsKept: [], childrenAdded: [], failures: [] };
4
+ }
5
+ function describe(error) {
6
+ return error instanceof Error ? error.message : String(error);
7
+ }
8
+ function readAvatar(ops, file) {
9
+ return (ops.readAvatar ?? ((f) => readAvatarBundled(f)))(file);
10
+ }
11
+ /** 头像文件名:管理房专属图,其它按名字哈希进池子。 */
12
+ export function avatarFileFor(target) {
13
+ return target.kind === 'management' ? managementAvatarFile() : pickPoolAvatarFile(target.name);
14
+ }
15
+ /** space 是否还能建(缺 createSpace 就跳过)。 */
16
+ export function canManageSpace(ops) {
17
+ return typeof ops.createSpace === 'function';
18
+ }
19
+ /**
20
+ * 确保有一个可用的 space:已认领过就复用(哪怕它已不存在 —— 那是自愈下一趟的事,
21
+ * 不该在本趟重复建房),否则建一个并就地记账。
22
+ */
23
+ export async function ensureSpace(instanceName, state, ops, logger) {
24
+ const result = emptyResult();
25
+ if (state.roomId) {
26
+ result.spaceId = state.roomId;
27
+ return result;
28
+ }
29
+ if (!canManageSpace(ops))
30
+ return result;
31
+ const name = spaceDisplayName(instanceName);
32
+ try {
33
+ const roomId = await ops.createSpace({ name });
34
+ state.roomId = roomId;
35
+ result.spaceId = roomId;
36
+ result.spaceCreated = true;
37
+ logger?.info(`T12 space 已创建:${name} (${roomId})`);
38
+ }
39
+ catch (error) {
40
+ result.failures.push({ what: 'space', error: describe(error) });
41
+ logger?.warn(`T12 space 创建失败(${name}),下次启动重试:${describe(error)}`);
42
+ }
43
+ return result;
44
+ }
45
+ /**
46
+ * 确保房间头像正确。返回 'set'(补挂/重铺)或 'kept'(已有且不需要重铺)。
47
+ *
48
+ * 重铺的唯一触发条件是**池子版本落后**:用户手动换过的头像在那之前一直会被保住。
49
+ */
50
+ export async function ensureRoomAvatar(roomId, target, state, ops, logger) {
51
+ if (typeof ops.getRoomAvatar !== 'function' || typeof ops.setRoomAvatar !== 'function')
52
+ return 'kept';
53
+ const rebrand = state.avatarPoolVersion !== AVATAR_POOL_VERSION;
54
+ let current;
55
+ try {
56
+ current = await ops.getRoomAvatar(roomId);
57
+ }
58
+ catch (error) {
59
+ logger?.warn(`T12 读房间头像失败 ${roomId}:${describe(error)}`);
60
+ return 'failed';
61
+ }
62
+ // 已经有头像、且不是"整池换风格" → 一律不碰(用户手动设置的尤其不能动)
63
+ if (current && !rebrand)
64
+ return 'kept';
65
+ const file = avatarFileFor(target);
66
+ let data;
67
+ try {
68
+ data = readAvatar(ops, file);
69
+ }
70
+ catch (error) {
71
+ logger?.warn(`T12 头像文件读不到(${file}),下次启动重试:${describe(error)}`);
72
+ return 'failed';
73
+ }
74
+ try {
75
+ await ops.setRoomAvatar(roomId, data, avatarInfo(data));
76
+ return 'set';
77
+ }
78
+ catch (error) {
79
+ logger?.warn(`T12 设置头像失败 ${roomId}:${describe(error)}`);
80
+ return 'failed';
81
+ }
82
+ }
83
+ /** 把房间挂进 space(盲写 m.space.child;重复写是幂等的,不额外读状态)。 */
84
+ export async function ensureRoomInSpace(spaceId, roomId, ops, logger) {
85
+ if (typeof ops.addRoomToSpace !== 'function')
86
+ return false;
87
+ try {
88
+ await ops.addRoomToSpace(spaceId, roomId);
89
+ return true;
90
+ }
91
+ catch (error) {
92
+ logger?.warn(`T12 挂房间进 space 失败 ${roomId}:${describe(error)}`);
93
+ return false;
94
+ }
95
+ }
96
+ /**
97
+ * 启动对账:建/复用 space → 给每个房间补头像 → 把每个房间挂进 space。
98
+ *
99
+ * `targets` 给出每个房间该用哪张图(管理房 / 项目名)。池子版本**只在整趟零失败时**
100
+ * 才记上,否则部分房间没铺到却再也不会重铺。
101
+ */
102
+ export async function healSpaceIdentity(input, state, ops, logger) {
103
+ const result = await ensureSpace(input.instanceName, state, ops, logger);
104
+ const spaceId = result.spaceId;
105
+ // space 自己也是一间桥建的房:按**实例名**哈希挂头像(与房间同一套规则:
106
+ // 已有且不需要重铺就不碰,用户自己换过的不会被覆盖)。
107
+ if (spaceId) {
108
+ const spaceAvatar = await ensureRoomAvatar(spaceId, { kind: 'pool', name: input.instanceName }, state, ops, logger);
109
+ if (spaceAvatar === 'set')
110
+ result.avatarsSet.push(spaceId);
111
+ else if (spaceAvatar === 'kept')
112
+ result.avatarsKept.push(spaceId);
113
+ else
114
+ result.failures.push({ what: 'avatar', roomId: spaceId, error: '见日志' });
115
+ }
116
+ for (const { roomId, target } of input.rooms) {
117
+ const avatar = await ensureRoomAvatar(roomId, target, state, ops, logger);
118
+ if (avatar === 'set')
119
+ result.avatarsSet.push(roomId);
120
+ else if (avatar === 'kept')
121
+ result.avatarsKept.push(roomId);
122
+ else
123
+ result.failures.push({ what: 'avatar', roomId, error: '见日志' });
124
+ if (spaceId && typeof ops.addRoomToSpace === 'function') {
125
+ const added = await ensureRoomInSpace(spaceId, roomId, ops, logger);
126
+ if (added)
127
+ result.childrenAdded.push(roomId);
128
+ else
129
+ result.failures.push({ what: 'child', roomId, error: '见日志' });
130
+ }
131
+ }
132
+ // 版本只在整趟都没失败时记:否则没铺到的房间会永远失去重铺机会
133
+ if (result.failures.length === 0)
134
+ state.avatarPoolVersion = AVATAR_POOL_VERSION;
135
+ return result;
136
+ }
137
+ //# sourceMappingURL=space-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"space-sync.js","sourceRoot":"","sources":["../../src/bridge/space-sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * T3 回合控制命令表:/stop /queue /interrupt /last。
3
+ *
4
+ * UX 移植自 pi-courier src/rpc/command-map.ts(🛑→⏹️ 换 DSH cancel 语义),
5
+ * 独立成文件避免与 T2 的桥命令表(src/bridge/commands.ts)冲突;主代理合并时
6
+ * 可把本表的 switch 并进总表,`handleTurnCommand(name, args, ctx)` 即入口契约。
7
+ *
8
+ * 返回约定:
9
+ * string → 命令已处理,调用方(pipeline)把文本作为回帖发出
10
+ * undefined → 不是回合命令(未知斜杠/普通文本),走既有消息流(steer/prompt)
11
+ */
12
+ import type { SessionHandle } from '../ports/backend.ts';
13
+ export interface TurnCommandContext {
14
+ session: SessionHandle;
15
+ }
16
+ export interface ParsedCommand {
17
+ /** 含前导 '/',全小写化前的原样名字 */
18
+ name: string;
19
+ /** 首个空格之后的参数(已 trim) */
20
+ args: string;
21
+ }
22
+ /** "/name args…" 的极简解析;非斜杠开头的文本返回 undefined。 */
23
+ export declare function parseTurnCommand(text: string): ParsedCommand | undefined;
24
+ /** Names owned by this table (pipeline uses it to keep the T4 router from
25
+ * claiming /stop etc.). Values keep the leading slash, matching parseTurnCommand. */
26
+ export declare const TURN_COMMANDS: ReadonlySet<string>;
27
+ export declare function handleTurnCommand(name: string, args: string, ctx: TurnCommandContext): Promise<string | undefined>;
28
+ //# sourceMappingURL=turn-commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn-commands.d.ts","sourceRoot":"","sources":["../../src/bridge/turn-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAc,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEpE,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,aAAa,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,gDAAgD;AAChD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAOxE;AAyCD;qFACqF;AACrF,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,MAAM,CAAiE,CAAA;AAE/G,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,kBAAkB,GACtB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA0C7B"}