botmux 2.43.0 → 2.45.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 (192) hide show
  1. package/README.en.md +1 -2
  2. package/README.md +1 -2
  3. package/dist/bot-registry.d.ts +11 -0
  4. package/dist/bot-registry.d.ts.map +1 -1
  5. package/dist/bot-registry.js +1 -0
  6. package/dist/bot-registry.js.map +1 -1
  7. package/dist/cli/bots-list-output.d.ts +8 -0
  8. package/dist/cli/bots-list-output.d.ts.map +1 -1
  9. package/dist/cli/bots-list-output.js +9 -0
  10. package/dist/cli/bots-list-output.js.map +1 -1
  11. package/dist/cli.d.ts.map +1 -1
  12. package/dist/cli.js +6 -0
  13. package/dist/cli.js.map +1 -1
  14. package/dist/core/command-handler.d.ts.map +1 -1
  15. package/dist/core/command-handler.js +169 -51
  16. package/dist/core/command-handler.js.map +1 -1
  17. package/dist/core/dashboard-ipc-server.d.ts +2 -0
  18. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  19. package/dist/core/dashboard-ipc-server.js +61 -2
  20. package/dist/core/dashboard-ipc-server.js.map +1 -1
  21. package/dist/core/role-resolver.d.ts +17 -1
  22. package/dist/core/role-resolver.d.ts.map +1 -1
  23. package/dist/core/role-resolver.js +64 -10
  24. package/dist/core/role-resolver.js.map +1 -1
  25. package/dist/core/session-manager.d.ts.map +1 -1
  26. package/dist/core/session-manager.js +14 -9
  27. package/dist/core/session-manager.js.map +1 -1
  28. package/dist/core/trigger-session.d.ts +9 -0
  29. package/dist/core/trigger-session.d.ts.map +1 -0
  30. package/dist/core/trigger-session.js +158 -0
  31. package/dist/core/trigger-session.js.map +1 -0
  32. package/dist/core/worker-pool.d.ts +22 -0
  33. package/dist/core/worker-pool.d.ts.map +1 -1
  34. package/dist/core/worker-pool.js +54 -2
  35. package/dist/core/worker-pool.js.map +1 -1
  36. package/dist/daemon.d.ts.map +1 -1
  37. package/dist/daemon.js +73 -44
  38. package/dist/daemon.js.map +1 -1
  39. package/dist/dashboard/connector-api.d.ts +3 -0
  40. package/dist/dashboard/connector-api.d.ts.map +1 -0
  41. package/dist/dashboard/connector-api.js +351 -0
  42. package/dist/dashboard/connector-api.js.map +1 -0
  43. package/dist/dashboard/federated-group-core.d.ts +54 -0
  44. package/dist/dashboard/federated-group-core.d.ts.map +1 -0
  45. package/dist/dashboard/federated-group-core.js +165 -0
  46. package/dist/dashboard/federated-group-core.js.map +1 -0
  47. package/dist/dashboard/federation-api.d.ts +42 -0
  48. package/dist/dashboard/federation-api.d.ts.map +1 -0
  49. package/dist/dashboard/federation-api.js +408 -0
  50. package/dist/dashboard/federation-api.js.map +1 -0
  51. package/dist/dashboard/federation-spoke-api.d.ts +76 -0
  52. package/dist/dashboard/federation-spoke-api.d.ts.map +1 -0
  53. package/dist/dashboard/federation-spoke-api.js +618 -0
  54. package/dist/dashboard/federation-spoke-api.js.map +1 -0
  55. package/dist/dashboard/team-group.d.ts +18 -0
  56. package/dist/dashboard/team-group.d.ts.map +1 -0
  57. package/dist/dashboard/team-group.js +7 -0
  58. package/dist/dashboard/team-group.js.map +1 -0
  59. package/dist/dashboard/trigger-api.d.ts +13 -0
  60. package/dist/dashboard/trigger-api.d.ts.map +1 -0
  61. package/dist/dashboard/trigger-api.js +77 -0
  62. package/dist/dashboard/trigger-api.js.map +1 -0
  63. package/dist/dashboard/web/app.js +8 -0
  64. package/dist/dashboard/web/app.js.map +1 -1
  65. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  66. package/dist/dashboard/web/bot-defaults.js +13 -0
  67. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  68. package/dist/dashboard/web/connectors.d.ts +2 -0
  69. package/dist/dashboard/web/connectors.d.ts.map +1 -0
  70. package/dist/dashboard/web/connectors.js +187 -0
  71. package/dist/dashboard/web/connectors.js.map +1 -0
  72. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  73. package/dist/dashboard/web/i18n.js +4 -0
  74. package/dist/dashboard/web/i18n.js.map +1 -1
  75. package/dist/dashboard/web/team-federation.d.ts +3 -0
  76. package/dist/dashboard/web/team-federation.d.ts.map +1 -0
  77. package/dist/dashboard/web/team-federation.js +472 -0
  78. package/dist/dashboard/web/team-federation.js.map +1 -0
  79. package/dist/dashboard/webhook-routes.d.ts +19 -0
  80. package/dist/dashboard/webhook-routes.d.ts.map +1 -0
  81. package/dist/dashboard/webhook-routes.js +321 -0
  82. package/dist/dashboard/webhook-routes.js.map +1 -0
  83. package/dist/dashboard-web/app.js +512 -384
  84. package/dist/dashboard-web/index.html +2 -0
  85. package/dist/dashboard.js +153 -1
  86. package/dist/dashboard.js.map +1 -1
  87. package/dist/i18n/en.d.ts.map +1 -1
  88. package/dist/i18n/en.js +31 -9
  89. package/dist/i18n/en.js.map +1 -1
  90. package/dist/i18n/zh.d.ts.map +1 -1
  91. package/dist/i18n/zh.js +31 -9
  92. package/dist/i18n/zh.js.map +1 -1
  93. package/dist/im/lark/card-builder.d.ts +25 -2
  94. package/dist/im/lark/card-builder.d.ts.map +1 -1
  95. package/dist/im/lark/card-builder.js +141 -43
  96. package/dist/im/lark/card-builder.js.map +1 -1
  97. package/dist/im/lark/card-handler.d.ts.map +1 -1
  98. package/dist/im/lark/card-handler.js +20 -4
  99. package/dist/im/lark/card-handler.js.map +1 -1
  100. package/dist/im/lark/client.d.ts +41 -0
  101. package/dist/im/lark/client.d.ts.map +1 -1
  102. package/dist/im/lark/client.js +174 -48
  103. package/dist/im/lark/client.js.map +1 -1
  104. package/dist/services/bot-owner-store.d.ts +28 -0
  105. package/dist/services/bot-owner-store.d.ts.map +1 -0
  106. package/dist/services/bot-owner-store.js +82 -0
  107. package/dist/services/bot-owner-store.js.map +1 -0
  108. package/dist/services/bot-profile-store.d.ts +16 -0
  109. package/dist/services/bot-profile-store.d.ts.map +1 -0
  110. package/dist/services/bot-profile-store.js +98 -0
  111. package/dist/services/bot-profile-store.js.map +1 -0
  112. package/dist/services/card-prefs-store.d.ts +2 -1
  113. package/dist/services/card-prefs-store.d.ts.map +1 -1
  114. package/dist/services/card-prefs-store.js +13 -4
  115. package/dist/services/card-prefs-store.js.map +1 -1
  116. package/dist/services/connector-store.d.ts +58 -0
  117. package/dist/services/connector-store.d.ts.map +1 -0
  118. package/dist/services/connector-store.js +79 -0
  119. package/dist/services/connector-store.js.map +1 -0
  120. package/dist/services/deployment-identity.d.ts +22 -0
  121. package/dist/services/deployment-identity.d.ts.map +1 -0
  122. package/dist/services/deployment-identity.js +67 -0
  123. package/dist/services/deployment-identity.js.map +1 -0
  124. package/dist/services/federation-membership-store.d.ts +23 -0
  125. package/dist/services/federation-membership-store.d.ts.map +1 -0
  126. package/dist/services/federation-membership-store.js +66 -0
  127. package/dist/services/federation-membership-store.js.map +1 -0
  128. package/dist/services/federation-roster.d.ts +54 -0
  129. package/dist/services/federation-roster.d.ts.map +1 -0
  130. package/dist/services/federation-roster.js +51 -0
  131. package/dist/services/federation-roster.js.map +1 -0
  132. package/dist/services/federation-store.d.ts +76 -0
  133. package/dist/services/federation-store.d.ts.map +1 -0
  134. package/dist/services/federation-store.js +133 -0
  135. package/dist/services/federation-store.js.map +1 -0
  136. package/dist/services/group-creator.d.ts +6 -0
  137. package/dist/services/group-creator.d.ts.map +1 -1
  138. package/dist/services/group-creator.js +10 -1
  139. package/dist/services/group-creator.js.map +1 -1
  140. package/dist/services/groups-store.d.ts +11 -0
  141. package/dist/services/groups-store.d.ts.map +1 -1
  142. package/dist/services/groups-store.js +42 -0
  143. package/dist/services/groups-store.js.map +1 -1
  144. package/dist/services/invite-store.d.ts +28 -0
  145. package/dist/services/invite-store.d.ts.map +1 -0
  146. package/dist/services/invite-store.js +85 -0
  147. package/dist/services/invite-store.js.map +1 -0
  148. package/dist/services/pairing-store.d.ts +47 -0
  149. package/dist/services/pairing-store.d.ts.map +1 -0
  150. package/dist/services/pairing-store.js +132 -0
  151. package/dist/services/pairing-store.js.map +1 -0
  152. package/dist/services/team-roster.d.ts +38 -0
  153. package/dist/services/team-roster.d.ts.map +1 -0
  154. package/dist/services/team-roster.js +82 -0
  155. package/dist/services/team-roster.js.map +1 -0
  156. package/dist/services/team-store.d.ts +54 -0
  157. package/dist/services/team-store.d.ts.map +1 -0
  158. package/dist/services/team-store.js +156 -0
  159. package/dist/services/team-store.js.map +1 -0
  160. package/dist/services/trigger-log-store.d.ts +46 -0
  161. package/dist/services/trigger-log-store.d.ts.map +1 -0
  162. package/dist/services/trigger-log-store.js +132 -0
  163. package/dist/services/trigger-log-store.js.map +1 -0
  164. package/dist/services/trigger-types.d.ts +57 -0
  165. package/dist/services/trigger-types.d.ts.map +1 -0
  166. package/dist/services/trigger-types.js +28 -0
  167. package/dist/services/trigger-types.js.map +1 -0
  168. package/dist/services/webhook-key.d.ts +16 -0
  169. package/dist/services/webhook-key.d.ts.map +1 -0
  170. package/dist/services/webhook-key.js +123 -0
  171. package/dist/services/webhook-key.js.map +1 -0
  172. package/dist/services/webhook-lifecycle-extractors.d.ts +15 -0
  173. package/dist/services/webhook-lifecycle-extractors.d.ts.map +1 -0
  174. package/dist/services/webhook-lifecycle-extractors.js +59 -0
  175. package/dist/services/webhook-lifecycle-extractors.js.map +1 -0
  176. package/dist/services/webhook-lifecycle-store.d.ts +45 -0
  177. package/dist/services/webhook-lifecycle-store.d.ts.map +1 -0
  178. package/dist/services/webhook-lifecycle-store.js +159 -0
  179. package/dist/services/webhook-lifecycle-store.js.map +1 -0
  180. package/dist/setup/verify-permissions.d.ts.map +1 -1
  181. package/dist/setup/verify-permissions.js +4 -0
  182. package/dist/setup/verify-permissions.js.map +1 -1
  183. package/dist/skills/definitions.d.ts.map +1 -1
  184. package/dist/skills/definitions.js +64 -10
  185. package/dist/skills/definitions.js.map +1 -1
  186. package/dist/workflows/events/payloads.d.ts +2 -2
  187. package/dist/workflows/events/schema.d.ts +8 -8
  188. package/dist/workflows/trigger-from-envelope.d.ts +13 -0
  189. package/dist/workflows/trigger-from-envelope.d.ts.map +1 -0
  190. package/dist/workflows/trigger-from-envelope.js +67 -0
  191. package/dist/workflows/trigger-from-envelope.js.map +1 -0
  192. package/package.json +1 -1
@@ -0,0 +1,22 @@
1
+ export interface DeploymentIdentity {
2
+ deploymentId: string;
3
+ name: string;
4
+ /** The owner's tenant-stable Feishu identity, bound once via /pair. Used as
5
+ * the "operator" when this deployment initiates 拉群 (so the operator is
6
+ * pulled into the group). Absent until bound. */
7
+ ownerUnionId?: string;
8
+ ownerName?: string;
9
+ }
10
+ /** Load (creating + persisting on first call) this deployment's identity. */
11
+ export declare function getDeploymentIdentity(dataDir: string): DeploymentIdentity;
12
+ /** Rename this deployment (owner-facing label). Returns the updated identity. */
13
+ export declare function setDeploymentName(dataDir: string, name: string): DeploymentIdentity;
14
+ /** Bind the owner's Feishu identity (via /pair or auto-bind). Also adopts the
15
+ * owner's Feishu name as the DEPLOYMENT name — the deployment is shown to the
16
+ * team by its owner's real name (no custom labels), so the roster reads
17
+ * naturally ("申晗 的部署" instead of a hostname). Returns the updated identity. */
18
+ export declare function setDeploymentOwner(dataDir: string, owner: {
19
+ unionId?: string;
20
+ name?: string;
21
+ }): DeploymentIdentity;
22
+ //# sourceMappingURL=deployment-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployment-identity.d.ts","sourceRoot":"","sources":["../../src/services/deployment-identity.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb;;sDAEkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAcD,6EAA6E;AAC7E,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,CAkBzE;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAKnF;AAED;;;gFAGgF;AAChF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,kBAAkB,CAUlH"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Deployment identity: a stable id + human-readable name for THIS botmux
3
+ * deployment (one install = one owner). Used by federation so a hub can tell
4
+ * member deployments apart and group their bots in the shared roster.
5
+ *
6
+ * Generated once and persisted to `{dataDir}/deployment-identity.json`; the name
7
+ * defaults to the machine hostname and can be renamed by the owner.
8
+ */
9
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { randomUUID } from 'node:crypto';
12
+ import { hostname } from 'node:os';
13
+ function filePath(dataDir) {
14
+ return join(dataDir, 'deployment-identity.json');
15
+ }
16
+ function write(dataDir, id) {
17
+ if (!existsSync(dataDir))
18
+ mkdirSync(dataDir, { recursive: true });
19
+ const fp = filePath(dataDir);
20
+ const tmp = `${fp}.${process.pid}.${randomUUID()}.tmp`;
21
+ writeFileSync(tmp, JSON.stringify(id, null, 2) + '\n', 'utf-8');
22
+ renameSync(tmp, fp);
23
+ }
24
+ /** Load (creating + persisting on first call) this deployment's identity. */
25
+ export function getDeploymentIdentity(dataDir) {
26
+ const fp = filePath(dataDir);
27
+ if (existsSync(fp)) {
28
+ try {
29
+ const p = JSON.parse(readFileSync(fp, 'utf-8'));
30
+ if (p && typeof p.deploymentId === 'string' && p.deploymentId) {
31
+ return {
32
+ deploymentId: p.deploymentId,
33
+ name: typeof p.name === 'string' && p.name ? p.name : 'botmux',
34
+ ownerUnionId: typeof p.ownerUnionId === 'string' ? p.ownerUnionId : undefined,
35
+ ownerName: typeof p.ownerName === 'string' ? p.ownerName : undefined,
36
+ };
37
+ }
38
+ }
39
+ catch { /* corrupt — regenerate */ }
40
+ }
41
+ const id = { deploymentId: `dep_${randomUUID().slice(0, 12)}`, name: hostname() || 'botmux' };
42
+ write(dataDir, id);
43
+ return id;
44
+ }
45
+ /** Rename this deployment (owner-facing label). Returns the updated identity. */
46
+ export function setDeploymentName(dataDir, name) {
47
+ const cur = getDeploymentIdentity(dataDir);
48
+ const next = { ...cur, name: name.trim() || cur.name };
49
+ write(dataDir, next);
50
+ return next;
51
+ }
52
+ /** Bind the owner's Feishu identity (via /pair or auto-bind). Also adopts the
53
+ * owner's Feishu name as the DEPLOYMENT name — the deployment is shown to the
54
+ * team by its owner's real name (no custom labels), so the roster reads
55
+ * naturally ("申晗 的部署" instead of a hostname). Returns the updated identity. */
56
+ export function setDeploymentOwner(dataDir, owner) {
57
+ const cur = getDeploymentIdentity(dataDir);
58
+ const next = {
59
+ ...cur,
60
+ ownerUnionId: owner.unionId || cur.ownerUnionId,
61
+ ownerName: owner.name || cur.ownerName,
62
+ name: owner.name || cur.name, // default the deployment label to the owner's Feishu name
63
+ };
64
+ write(dataDir, next);
65
+ return next;
66
+ }
67
+ //# sourceMappingURL=deployment-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployment-identity.js","sourceRoot":"","sources":["../../src/services/deployment-identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ export interface RemoteMembership {
2
+ hubUrl: string;
3
+ teamId: string;
4
+ teamName: string;
5
+ syncToken: string;
6
+ deploymentId: string;
7
+ joinedAt: number;
8
+ /** Token THIS spoke issued to the hub at join; the hub presents it on
9
+ * delegate-group calls so we can verify the request came from a hub we joined. */
10
+ delegationToken?: string;
11
+ }
12
+ /** Record (or replace) a remote-team membership. */
13
+ export declare function addMembership(dataDir: string, m: Omit<RemoteMembership, 'joinedAt'> & {
14
+ joinedAt?: number;
15
+ }, now?: number): RemoteMembership;
16
+ /** All remote teams this deployment has joined. */
17
+ export declare function listMemberships(dataDir: string): RemoteMembership[];
18
+ /** Find the membership whose delegationToken matches (verifies an incoming
19
+ * hub→spoke delegate call came from a hub this deployment actually joined). */
20
+ export declare function findMembershipByDelegationToken(dataDir: string, token: string): RemoteMembership | null;
21
+ /** Remove one membership. Returns true if removed. */
22
+ export declare function removeMembership(dataDir: string, hubUrl: string, teamId: string): boolean;
23
+ //# sourceMappingURL=federation-membership-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation-membership-store.d.ts","sourceRoot":"","sources":["../../src/services/federation-membership-store.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB;uFACmF;IACnF,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AA8BD,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,GAAE,MAAmB,GAAG,gBAAgB,CAMxJ;AAED,mDAAmD;AACnD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAEnE;AAED;gFACgF;AAChF,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAGvG;AAED,sDAAsD;AACtD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAOzF"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Federation membership store (SPOKE side): which remote teams THIS deployment
3
+ * has joined, and the syncToken/hub needed to push bots + pull the shared roster.
4
+ *
5
+ * Storage: `{dataDir}/federation-memberships.json`, atomic writes. Keyed by
6
+ * `${hubUrl}::${teamId}` so a deployment can join multiple teams / hubs.
7
+ */
8
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ import { randomUUID } from 'node:crypto';
11
+ function filePath(dataDir) {
12
+ return join(dataDir, 'federation-memberships.json');
13
+ }
14
+ function keyOf(hubUrl, teamId) {
15
+ return `${hubUrl}::${teamId}`;
16
+ }
17
+ function readFile(dataDir) {
18
+ const fp = filePath(dataDir);
19
+ if (!existsSync(fp))
20
+ return {};
21
+ try {
22
+ const parsed = JSON.parse(readFileSync(fp, 'utf-8'));
23
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
24
+ return parsed;
25
+ }
26
+ catch { /* corrupt */ }
27
+ return {};
28
+ }
29
+ function writeFileAtomic(dataDir, data) {
30
+ if (!existsSync(dataDir))
31
+ mkdirSync(dataDir, { recursive: true });
32
+ const fp = filePath(dataDir);
33
+ const tmp = `${fp}.${process.pid}.${randomUUID()}.tmp`;
34
+ writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', 'utf-8');
35
+ renameSync(tmp, fp);
36
+ }
37
+ /** Record (or replace) a remote-team membership. */
38
+ export function addMembership(dataDir, m, now = Date.now()) {
39
+ const data = readFile(dataDir);
40
+ const full = { ...m, joinedAt: m.joinedAt ?? now };
41
+ data[keyOf(m.hubUrl, m.teamId)] = full;
42
+ writeFileAtomic(dataDir, data);
43
+ return full;
44
+ }
45
+ /** All remote teams this deployment has joined. */
46
+ export function listMemberships(dataDir) {
47
+ return Object.values(readFile(dataDir));
48
+ }
49
+ /** Find the membership whose delegationToken matches (verifies an incoming
50
+ * hub→spoke delegate call came from a hub this deployment actually joined). */
51
+ export function findMembershipByDelegationToken(dataDir, token) {
52
+ if (!token)
53
+ return null;
54
+ return Object.values(readFile(dataDir)).find(m => m.delegationToken === token) ?? null;
55
+ }
56
+ /** Remove one membership. Returns true if removed. */
57
+ export function removeMembership(dataDir, hubUrl, teamId) {
58
+ const data = readFile(dataDir);
59
+ const key = keyOf(hubUrl, teamId);
60
+ if (!data[key])
61
+ return false;
62
+ delete data[key];
63
+ writeFileAtomic(dataDir, data);
64
+ return true;
65
+ }
66
+ //# sourceMappingURL=federation-membership-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation-membership-store.js","sourceRoot":"","sources":["../../src/services/federation-membership-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Aggregated cross-deployment roster (HUB side): the hub's own local bots
3
+ * ([[team-roster]]) merged with every member deployment's advertised bots
4
+ * ([[federation-store]]), each tagged with the deployment it belongs to so the
5
+ * UI can group by deployment (local first, then remote by name).
6
+ *
7
+ * Pure read from `{dataDir}` files — testable, no Lark API.
8
+ */
9
+ import { type LiveBot } from './team-roster.js';
10
+ /** A federated deployment is considered stale (likely offline) if it hasn't
11
+ * synced within this window — its bots are flagged so the UI can de-emphasize them. */
12
+ export declare const FEDERATION_STALE_MS: number;
13
+ export interface AggregatedRosterBot {
14
+ larkAppId: string;
15
+ name: string;
16
+ cliId: string;
17
+ capability: string | null;
18
+ hasTeamRole: boolean;
19
+ /** Tenant-stable bot id (kept now so P2 拉群 by union_id needs no schema change). */
20
+ botUnionId?: string;
21
+ /** Owner (person) of this bot — union_id is tenant-stable, used to pull the
22
+ * owner into a federated group regardless of app scope. */
23
+ owner?: {
24
+ unionId?: string;
25
+ name?: string;
26
+ };
27
+ deployment: {
28
+ id: string;
29
+ name: string;
30
+ local: boolean;
31
+ stale: boolean;
32
+ };
33
+ }
34
+ export interface AggregatedDeployment {
35
+ id: string;
36
+ name: string;
37
+ local: boolean;
38
+ botCount: number;
39
+ lastSeenAt?: number;
40
+ /** true when a remote deployment hasn't synced within FEDERATION_STALE_MS. */
41
+ stale: boolean;
42
+ }
43
+ export interface AggregatedRoster {
44
+ team: {
45
+ id: string;
46
+ name: string;
47
+ memberCount: number;
48
+ };
49
+ deployments: AggregatedDeployment[];
50
+ bots: AggregatedRosterBot[];
51
+ }
52
+ /** Hub's local bots + all member deployments' bots, tagged + grouped by deployment. */
53
+ export declare function buildFederatedRoster(dataDir: string, teamId?: string, configOrder?: string[], now?: number, liveBots?: LiveBot[]): AggregatedRoster;
54
+ //# sourceMappingURL=federation-roster.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation-roster.d.ts","sourceRoot":"","sources":["../../src/services/federation-roster.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAmB,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAKjE;wFACwF;AACxF,eAAO,MAAM,mBAAmB,QAAgB,CAAC;AAEjD,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,mFAAmF;IACnF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;gEAC4D;IAC5D,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,UAAU,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1E;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD,WAAW,EAAE,oBAAoB,EAAE,CAAC;IACpC,IAAI,EAAE,mBAAmB,EAAE,CAAC;CAC7B;AAED,uFAAuF;AACvF,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,MAAwB,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,GAAE,MAAmB,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,GAAG,gBAAgB,CAoChL"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Aggregated cross-deployment roster (HUB side): the hub's own local bots
3
+ * ([[team-roster]]) merged with every member deployment's advertised bots
4
+ * ([[federation-store]]), each tagged with the deployment it belongs to so the
5
+ * UI can group by deployment (local first, then remote by name).
6
+ *
7
+ * Pure read from `{dataDir}` files — testable, no Lark API.
8
+ */
9
+ import { buildTeamRoster } from './team-roster.js';
10
+ import { listFederatedDeployments } from './federation-store.js';
11
+ import { getDeploymentIdentity } from './deployment-identity.js';
12
+ import { getTeam, getDefaultTeam, DEFAULT_TEAM_ID } from './team-store.js';
13
+ /** A federated deployment is considered stale (likely offline) if it hasn't
14
+ * synced within this window — its bots are flagged so the UI can de-emphasize them. */
15
+ export const FEDERATION_STALE_MS = 5 * 60 * 1000;
16
+ /** Hub's local bots + all member deployments' bots, tagged + grouped by deployment. */
17
+ export function buildFederatedRoster(dataDir, teamId = DEFAULT_TEAM_ID, configOrder, now = Date.now(), liveBots) {
18
+ const team = getTeam(dataDir, teamId) ?? getDefaultTeam(dataDir);
19
+ const localId = getDeploymentIdentity(dataDir);
20
+ const local = buildTeamRoster(dataDir, teamId, configOrder, liveBots);
21
+ const deployments = [
22
+ { id: localId.deploymentId, name: localId.name, local: true, botCount: local.bots.length, stale: false },
23
+ ];
24
+ const bots = local.bots.map(b => ({
25
+ larkAppId: b.larkAppId,
26
+ name: b.name,
27
+ cliId: b.cliId,
28
+ capability: b.capability,
29
+ hasTeamRole: b.hasTeamRole,
30
+ owner: b.owner ? { unionId: b.owner.unionId, name: b.owner.name } : undefined,
31
+ deployment: { id: localId.deploymentId, name: localId.name, local: true, stale: false },
32
+ }));
33
+ for (const dep of listFederatedDeployments(dataDir, teamId)) {
34
+ const stale = now - dep.lastSeenAt > FEDERATION_STALE_MS;
35
+ deployments.push({ id: dep.deploymentId, name: dep.name, local: false, botCount: dep.bots.length, lastSeenAt: dep.lastSeenAt, stale });
36
+ for (const b of dep.bots) {
37
+ bots.push({
38
+ larkAppId: b.larkAppId,
39
+ name: b.botName,
40
+ cliId: b.cliId,
41
+ capability: b.capability ?? null,
42
+ hasTeamRole: !!b.hasTeamRole,
43
+ botUnionId: b.botUnionId,
44
+ owner: (b.ownerUnionId || b.ownerName) ? { unionId: b.ownerUnionId, name: b.ownerName } : undefined,
45
+ deployment: { id: dep.deploymentId, name: dep.name, local: false, stale },
46
+ });
47
+ }
48
+ }
49
+ return { team: { id: team.id, name: team.name, memberCount: team.members.length }, deployments, bots };
50
+ }
51
+ //# sourceMappingURL=federation-roster.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation-roster.js","sourceRoot":"","sources":["../../src/services/federation-roster.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ export interface FederatedBot {
2
+ larkAppId: string;
3
+ botName: string;
4
+ cliId: string;
5
+ /** Tenant-stable bot id (used by P2 拉群 to add the bot cross-app). */
6
+ botUnionId?: string;
7
+ capability?: string | null;
8
+ hasTeamRole?: boolean;
9
+ /** Owner (the person) of this bot, tenant-stable union_id — so 拉群 can pull
10
+ * the owner into the group by union_id (open_id is app-scoped, union_id not). */
11
+ ownerUnionId?: string;
12
+ ownerName?: string;
13
+ }
14
+ export interface FederatedDeployment {
15
+ deploymentId: string;
16
+ name: string;
17
+ syncToken: string;
18
+ bots: FederatedBot[];
19
+ joinedAt: number;
20
+ lastSeenAt: number;
21
+ /** The spoke deployment's OWNER (the person), tenant-stable union_id. Hub uses
22
+ * this — derived from syncToken — as the trusted operator identity when this
23
+ * spoke initiates 拉群 (so the operator is pulled into the group). */
24
+ ownerUnionId?: string;
25
+ ownerName?: string;
26
+ /** Spoke's dashboard base URL — so this hub can call back to delegate 拉群
27
+ * (hub→spoke) when no local online bot can be the group creator. */
28
+ callbackUrl?: string;
29
+ /** Token the SPOKE issued to THIS hub at join; the hub presents it on
30
+ * delegate calls so the spoke can verify the request is from its hub. */
31
+ delegationToken?: string;
32
+ }
33
+ export interface RegisterInput {
34
+ deploymentId: string;
35
+ name: string;
36
+ bots: FederatedBot[];
37
+ ownerUnionId?: string;
38
+ ownerName?: string;
39
+ callbackUrl?: string;
40
+ delegationToken?: string;
41
+ }
42
+ /**
43
+ * Register a NEW remote deployment under a team and issue a fresh syncToken.
44
+ *
45
+ * NOT idempotent on purpose: `deploymentId` is public (it appears in the roster),
46
+ * so re-registering an existing id must NOT hand back its long-lived syncToken —
47
+ * otherwise anyone with an invite could claim another deployment's token by
48
+ * guessing its id. A duplicate returns `{ created: false }` with NO token; the
49
+ * caller surfaces `deployment_already_joined`. Ongoing bot refresh is via
50
+ * `syncDeployment` (syncToken), not this. Re-binding/rotation is a future
51
+ * explicit reset proving the old syncToken.
52
+ */
53
+ export declare function registerDeployment(dataDir: string, teamId: string, input: RegisterInput, now?: number): {
54
+ syncToken: string;
55
+ created: boolean;
56
+ };
57
+ /** Resolve a syncToken to its {teamId, deployment}, or null. */
58
+ export declare function getDeploymentByToken(dataDir: string, syncToken: string): {
59
+ teamId: string;
60
+ deployment: FederatedDeployment;
61
+ } | null;
62
+ /** Refresh a deployment's advertised bots + heartbeat, by syncToken. Returns true if found. */
63
+ export declare function syncDeployment(dataDir: string, syncToken: string, bots: FederatedBot[], owner?: {
64
+ ownerUnionId?: string;
65
+ ownerName?: string;
66
+ name?: string;
67
+ }, now?: number): boolean;
68
+ /** Member deployments of a team (empty if none). */
69
+ export declare function listFederatedDeployments(dataDir: string, teamId: string): FederatedDeployment[];
70
+ /** Remove a deployment from a team (leave/kick). Returns true if removed. */
71
+ export declare function removeDeployment(dataDir: string, teamId: string, deploymentId: string): boolean;
72
+ /** Remove the deployment owning a syncToken (spoke-initiated leave/revoke). Returns true if removed. */
73
+ export declare function removeDeploymentByToken(dataDir: string, syncToken: string): boolean;
74
+ /** Drop all federation records for a team (e.g. when the team is deleted). */
75
+ export declare function removeTeamFederation(dataDir: string, teamId: string): void;
76
+ //# sourceMappingURL=federation-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation-store.d.ts","sourceRoot":"","sources":["../../src/services/federation-store.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;sFACkF;IAClF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB;;yEAEqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;yEACqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;8EAC0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AA8BD,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,GAAE,MAAmB,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAQ3J;AAED,gEAAgE;AAChE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,mBAAmB,CAAA;CAAE,GAAG,IAAI,CAQnI;AAED,+FAA+F;AAC/F,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,KAAK,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,GAAE,MAAmB,GAAG,OAAO,CAiBhM;AAED,oDAAoD;AACpD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAE/F;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAS/F;AAED,wGAAwG;AACxG,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAQnF;AAED,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAM1E"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Federation store (HUB side): which remote deployments have joined each team,
3
+ * and the bots they last advertised. The hub aggregates these with its own local
4
+ * roster so every member sees a shared cross-deployment roster.
5
+ *
6
+ * A deployment registers once via an invite (→ teamId) and is issued a long-lived
7
+ * `syncToken` (high-entropy, never logged) used for subsequent sync/roster pulls.
8
+ *
9
+ * Storage: `{dataDir}/federations.json`, atomic writes. Single dashboard writer
10
+ * (same assumption as invite-store): register/sync are read-modify-write.
11
+ */
12
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ import { randomBytes, randomUUID } from 'node:crypto';
15
+ function filePath(dataDir) {
16
+ return join(dataDir, 'federations.json');
17
+ }
18
+ function readFile(dataDir) {
19
+ const fp = filePath(dataDir);
20
+ if (!existsSync(fp))
21
+ return { version: 1, teams: {} };
22
+ try {
23
+ const parsed = JSON.parse(readFileSync(fp, 'utf-8'));
24
+ if (parsed && typeof parsed.teams === 'object' && parsed.teams)
25
+ return { version: 1, teams: parsed.teams };
26
+ }
27
+ catch { /* corrupt — fall through */ }
28
+ return { version: 1, teams: {} };
29
+ }
30
+ function writeFileAtomic(dataDir, data) {
31
+ if (!existsSync(dataDir))
32
+ mkdirSync(dataDir, { recursive: true });
33
+ const fp = filePath(dataDir);
34
+ const tmp = `${fp}.${process.pid}.${randomUUID()}.tmp`;
35
+ writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', 'utf-8');
36
+ renameSync(tmp, fp);
37
+ }
38
+ /**
39
+ * Register a NEW remote deployment under a team and issue a fresh syncToken.
40
+ *
41
+ * NOT idempotent on purpose: `deploymentId` is public (it appears in the roster),
42
+ * so re-registering an existing id must NOT hand back its long-lived syncToken —
43
+ * otherwise anyone with an invite could claim another deployment's token by
44
+ * guessing its id. A duplicate returns `{ created: false }` with NO token; the
45
+ * caller surfaces `deployment_already_joined`. Ongoing bot refresh is via
46
+ * `syncDeployment` (syncToken), not this. Re-binding/rotation is a future
47
+ * explicit reset proving the old syncToken.
48
+ */
49
+ export function registerDeployment(dataDir, teamId, input, now = Date.now()) {
50
+ const data = readFile(dataDir);
51
+ const list = data.teams[teamId] ?? (data.teams[teamId] = []);
52
+ if (list.some(d => d.deploymentId === input.deploymentId))
53
+ return { syncToken: '', created: false };
54
+ const syncToken = randomBytes(24).toString('base64url');
55
+ list.push({ deploymentId: input.deploymentId, name: input.name, syncToken, bots: input.bots, joinedAt: now, lastSeenAt: now, ownerUnionId: input.ownerUnionId, ownerName: input.ownerName, callbackUrl: input.callbackUrl, delegationToken: input.delegationToken });
56
+ writeFileAtomic(dataDir, data);
57
+ return { syncToken, created: true };
58
+ }
59
+ /** Resolve a syncToken to its {teamId, deployment}, or null. */
60
+ export function getDeploymentByToken(dataDir, syncToken) {
61
+ if (!syncToken)
62
+ return null;
63
+ const data = readFile(dataDir);
64
+ for (const [teamId, list] of Object.entries(data.teams)) {
65
+ const deployment = list.find(d => d.syncToken === syncToken);
66
+ if (deployment)
67
+ return { teamId, deployment };
68
+ }
69
+ return null;
70
+ }
71
+ /** Refresh a deployment's advertised bots + heartbeat, by syncToken. Returns true if found. */
72
+ export function syncDeployment(dataDir, syncToken, bots, owner, now = Date.now()) {
73
+ const data = readFile(dataDir);
74
+ for (const list of Object.values(data.teams)) {
75
+ const deployment = list.find(d => d.syncToken === syncToken);
76
+ if (deployment) {
77
+ deployment.bots = bots;
78
+ deployment.lastSeenAt = now;
79
+ // Owner + name can change after join (e.g. binding adopts the owner's
80
+ // Feishu name) — keep them fresh on sync, only overwriting when sent.
81
+ if (owner?.ownerUnionId !== undefined)
82
+ deployment.ownerUnionId = owner.ownerUnionId;
83
+ if (owner?.ownerName !== undefined)
84
+ deployment.ownerName = owner.ownerName;
85
+ if (owner?.name)
86
+ deployment.name = owner.name; // non-empty only → roster grouping follows the owner's Feishu name
87
+ writeFileAtomic(dataDir, data);
88
+ return true;
89
+ }
90
+ }
91
+ return false;
92
+ }
93
+ /** Member deployments of a team (empty if none). */
94
+ export function listFederatedDeployments(dataDir, teamId) {
95
+ return readFile(dataDir).teams[teamId] ?? [];
96
+ }
97
+ /** Remove a deployment from a team (leave/kick). Returns true if removed. */
98
+ export function removeDeployment(dataDir, teamId, deploymentId) {
99
+ const data = readFile(dataDir);
100
+ const list = data.teams[teamId];
101
+ if (!list)
102
+ return false;
103
+ const before = list.length;
104
+ data.teams[teamId] = list.filter(d => d.deploymentId !== deploymentId);
105
+ if (data.teams[teamId].length === before)
106
+ return false;
107
+ writeFileAtomic(dataDir, data);
108
+ return true;
109
+ }
110
+ /** Remove the deployment owning a syncToken (spoke-initiated leave/revoke). Returns true if removed. */
111
+ export function removeDeploymentByToken(dataDir, syncToken) {
112
+ if (!syncToken)
113
+ return false;
114
+ const data = readFile(dataDir);
115
+ for (const list of Object.values(data.teams)) {
116
+ const idx = list.findIndex(d => d.syncToken === syncToken);
117
+ if (idx >= 0) {
118
+ list.splice(idx, 1);
119
+ writeFileAtomic(dataDir, data);
120
+ return true;
121
+ }
122
+ }
123
+ return false;
124
+ }
125
+ /** Drop all federation records for a team (e.g. when the team is deleted). */
126
+ export function removeTeamFederation(dataDir, teamId) {
127
+ const data = readFile(dataDir);
128
+ if (data.teams[teamId]) {
129
+ delete data.teams[teamId];
130
+ writeFileAtomic(dataDir, data);
131
+ }
132
+ }
133
+ //# sourceMappingURL=federation-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation-store.js","sourceRoot":"","sources":["../../src/services/federation-store.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,10 @@ export interface CreateGroupOpts {
5
5
  larkAppIds: string[];
6
6
  name?: string;
7
7
  userOpenIds?: string[];
8
+ /** Users to add by union_id (tenant-stable) — used to pull bot OWNERS into a
9
+ * federated group regardless of which bot they paired through (open_id is
10
+ * app-scoped, union_id is not). Added after the chat is created. */
11
+ ownerUnionIds?: string[];
8
12
  transferOwnerTo?: string;
9
13
  notifyOwnerOpenId?: string;
10
14
  /** Optional working directory to bind the newly created chat to oncall for
@@ -18,6 +22,8 @@ export interface CreateGroupResult {
18
22
  creator: string;
19
23
  invalidBotIds: string[];
20
24
  invalidUserIds: string[];
25
+ /** Owner union_ids Lark could not add to the chat (best-effort). */
26
+ invalidOwnerUnionIds: string[];
21
27
  ownerTransferredTo: string | null;
22
28
  transferError: string | null;
23
29
  notifyMessageId: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"group-creator.d.ts","sourceRoot":"","sources":["../../src/services/group-creator.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,eAAe;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB;wEACoE;IACpE,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;2DAEuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;8EAC0E;IAC1E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACzF;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA0G3F"}
1
+ {"version":3,"file":"group-creator.d.ts","sourceRoot":"","sources":["../../src/services/group-creator.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,eAAe;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB;wEACoE;IACpE,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;yEAEqE;IACrE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;2DAEuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,oEAAoE;IACpE,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;8EAC0E;IAC1E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACzF;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAoH3F"}
@@ -17,7 +17,7 @@
17
17
  * `notifyOwnerOpenId` MUST be in `creatorLarkAppId`'s app scope. Enforcing
18
18
  * this is the decision layer's job — the service trusts its inputs.
19
19
  */
20
- import { createChat, transferChatOwner, getChatOwner, getChatShareLink } from './groups-store.js';
20
+ import { createChat, transferChatOwner, getChatOwner, getChatShareLink, addUsersToChatByUnionId } from './groups-store.js';
21
21
  import { sendMessage } from '../im/lark/client.js';
22
22
  import { bindOncall } from './oncall-store.js';
23
23
  export async function createGroupWithBots(opts) {
@@ -44,6 +44,14 @@ export async function createGroupWithBots(opts) {
44
44
  else
45
45
  shareLinkError = sl.error;
46
46
  }
47
+ // Pull bot owners into the chat by union_id (tenant-stable; the creator bot
48
+ // adds them). Best-effort — failures surface as invalidOwnerUnionIds, the chat
49
+ // still exists. The creator's own owner (if any) is harmless to re-add.
50
+ let invalidOwnerUnionIds = [];
51
+ if (opts.ownerUnionIds && opts.ownerUnionIds.length > 0) {
52
+ const ar = await addUsersToChatByUnionId(opts.creatorLarkAppId, r.chatId, opts.ownerUnionIds);
53
+ invalidOwnerUnionIds = ar.invalidUserIds;
54
+ }
47
55
  let ownerTransferredTo = null;
48
56
  let transferError = null;
49
57
  if (opts.transferOwnerTo) {
@@ -117,6 +125,7 @@ export async function createGroupWithBots(opts) {
117
125
  creator: opts.creatorLarkAppId,
118
126
  invalidBotIds: r.invalidBotIds,
119
127
  invalidUserIds: r.invalidUserIds,
128
+ invalidOwnerUnionIds,
120
129
  ownerTransferredTo,
121
130
  transferError,
122
131
  notifyMessageId,
@@ -1 +1 @@
1
- {"version":3,"file":"group-creator.js","sourceRoot":"","sources":["../../src/services/group-creator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"group-creator.js","sourceRoot":"","sources":["../../src/services/group-creator.ts"],"names":[],"mappings":"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"}