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.
- package/README.en.md +1 -2
- package/README.md +1 -2
- package/dist/bot-registry.d.ts +11 -0
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +1 -0
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli/bots-list-output.d.ts +8 -0
- package/dist/cli/bots-list-output.d.ts.map +1 -1
- package/dist/cli/bots-list-output.js +9 -0
- package/dist/cli/bots-list-output.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -1
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +169 -51
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts +2 -0
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +61 -2
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/role-resolver.d.ts +17 -1
- package/dist/core/role-resolver.d.ts.map +1 -1
- package/dist/core/role-resolver.js +64 -10
- package/dist/core/role-resolver.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +14 -9
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/trigger-session.d.ts +9 -0
- package/dist/core/trigger-session.d.ts.map +1 -0
- package/dist/core/trigger-session.js +158 -0
- package/dist/core/trigger-session.js.map +1 -0
- package/dist/core/worker-pool.d.ts +22 -0
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +54 -2
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +73 -44
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/connector-api.d.ts +3 -0
- package/dist/dashboard/connector-api.d.ts.map +1 -0
- package/dist/dashboard/connector-api.js +351 -0
- package/dist/dashboard/connector-api.js.map +1 -0
- package/dist/dashboard/federated-group-core.d.ts +54 -0
- package/dist/dashboard/federated-group-core.d.ts.map +1 -0
- package/dist/dashboard/federated-group-core.js +165 -0
- package/dist/dashboard/federated-group-core.js.map +1 -0
- package/dist/dashboard/federation-api.d.ts +42 -0
- package/dist/dashboard/federation-api.d.ts.map +1 -0
- package/dist/dashboard/federation-api.js +408 -0
- package/dist/dashboard/federation-api.js.map +1 -0
- package/dist/dashboard/federation-spoke-api.d.ts +76 -0
- package/dist/dashboard/federation-spoke-api.d.ts.map +1 -0
- package/dist/dashboard/federation-spoke-api.js +618 -0
- package/dist/dashboard/federation-spoke-api.js.map +1 -0
- package/dist/dashboard/team-group.d.ts +18 -0
- package/dist/dashboard/team-group.d.ts.map +1 -0
- package/dist/dashboard/team-group.js +7 -0
- package/dist/dashboard/team-group.js.map +1 -0
- package/dist/dashboard/trigger-api.d.ts +13 -0
- package/dist/dashboard/trigger-api.d.ts.map +1 -0
- package/dist/dashboard/trigger-api.js +77 -0
- package/dist/dashboard/trigger-api.js.map +1 -0
- package/dist/dashboard/web/app.js +8 -0
- package/dist/dashboard/web/app.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults.js +13 -0
- package/dist/dashboard/web/bot-defaults.js.map +1 -1
- package/dist/dashboard/web/connectors.d.ts +2 -0
- package/dist/dashboard/web/connectors.d.ts.map +1 -0
- package/dist/dashboard/web/connectors.js +187 -0
- package/dist/dashboard/web/connectors.js.map +1 -0
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +4 -0
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/team-federation.d.ts +3 -0
- package/dist/dashboard/web/team-federation.d.ts.map +1 -0
- package/dist/dashboard/web/team-federation.js +472 -0
- package/dist/dashboard/web/team-federation.js.map +1 -0
- package/dist/dashboard/webhook-routes.d.ts +19 -0
- package/dist/dashboard/webhook-routes.d.ts.map +1 -0
- package/dist/dashboard/webhook-routes.js +321 -0
- package/dist/dashboard/webhook-routes.js.map +1 -0
- package/dist/dashboard-web/app.js +512 -384
- package/dist/dashboard-web/index.html +2 -0
- package/dist/dashboard.js +153 -1
- package/dist/dashboard.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +31 -9
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +31 -9
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts +25 -2
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +141 -43
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +20 -4
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/client.d.ts +41 -0
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +174 -48
- package/dist/im/lark/client.js.map +1 -1
- package/dist/services/bot-owner-store.d.ts +28 -0
- package/dist/services/bot-owner-store.d.ts.map +1 -0
- package/dist/services/bot-owner-store.js +82 -0
- package/dist/services/bot-owner-store.js.map +1 -0
- package/dist/services/bot-profile-store.d.ts +16 -0
- package/dist/services/bot-profile-store.d.ts.map +1 -0
- package/dist/services/bot-profile-store.js +98 -0
- package/dist/services/bot-profile-store.js.map +1 -0
- package/dist/services/card-prefs-store.d.ts +2 -1
- package/dist/services/card-prefs-store.d.ts.map +1 -1
- package/dist/services/card-prefs-store.js +13 -4
- package/dist/services/card-prefs-store.js.map +1 -1
- package/dist/services/connector-store.d.ts +58 -0
- package/dist/services/connector-store.d.ts.map +1 -0
- package/dist/services/connector-store.js +79 -0
- package/dist/services/connector-store.js.map +1 -0
- package/dist/services/deployment-identity.d.ts +22 -0
- package/dist/services/deployment-identity.d.ts.map +1 -0
- package/dist/services/deployment-identity.js +67 -0
- package/dist/services/deployment-identity.js.map +1 -0
- package/dist/services/federation-membership-store.d.ts +23 -0
- package/dist/services/federation-membership-store.d.ts.map +1 -0
- package/dist/services/federation-membership-store.js +66 -0
- package/dist/services/federation-membership-store.js.map +1 -0
- package/dist/services/federation-roster.d.ts +54 -0
- package/dist/services/federation-roster.d.ts.map +1 -0
- package/dist/services/federation-roster.js +51 -0
- package/dist/services/federation-roster.js.map +1 -0
- package/dist/services/federation-store.d.ts +76 -0
- package/dist/services/federation-store.d.ts.map +1 -0
- package/dist/services/federation-store.js +133 -0
- package/dist/services/federation-store.js.map +1 -0
- package/dist/services/group-creator.d.ts +6 -0
- package/dist/services/group-creator.d.ts.map +1 -1
- package/dist/services/group-creator.js +10 -1
- package/dist/services/group-creator.js.map +1 -1
- package/dist/services/groups-store.d.ts +11 -0
- package/dist/services/groups-store.d.ts.map +1 -1
- package/dist/services/groups-store.js +42 -0
- package/dist/services/groups-store.js.map +1 -1
- package/dist/services/invite-store.d.ts +28 -0
- package/dist/services/invite-store.d.ts.map +1 -0
- package/dist/services/invite-store.js +85 -0
- package/dist/services/invite-store.js.map +1 -0
- package/dist/services/pairing-store.d.ts +47 -0
- package/dist/services/pairing-store.d.ts.map +1 -0
- package/dist/services/pairing-store.js +132 -0
- package/dist/services/pairing-store.js.map +1 -0
- package/dist/services/team-roster.d.ts +38 -0
- package/dist/services/team-roster.d.ts.map +1 -0
- package/dist/services/team-roster.js +82 -0
- package/dist/services/team-roster.js.map +1 -0
- package/dist/services/team-store.d.ts +54 -0
- package/dist/services/team-store.d.ts.map +1 -0
- package/dist/services/team-store.js +156 -0
- package/dist/services/team-store.js.map +1 -0
- package/dist/services/trigger-log-store.d.ts +46 -0
- package/dist/services/trigger-log-store.d.ts.map +1 -0
- package/dist/services/trigger-log-store.js +132 -0
- package/dist/services/trigger-log-store.js.map +1 -0
- package/dist/services/trigger-types.d.ts +57 -0
- package/dist/services/trigger-types.d.ts.map +1 -0
- package/dist/services/trigger-types.js +28 -0
- package/dist/services/trigger-types.js.map +1 -0
- package/dist/services/webhook-key.d.ts +16 -0
- package/dist/services/webhook-key.d.ts.map +1 -0
- package/dist/services/webhook-key.js +123 -0
- package/dist/services/webhook-key.js.map +1 -0
- package/dist/services/webhook-lifecycle-extractors.d.ts +15 -0
- package/dist/services/webhook-lifecycle-extractors.d.ts.map +1 -0
- package/dist/services/webhook-lifecycle-extractors.js +59 -0
- package/dist/services/webhook-lifecycle-extractors.js.map +1 -0
- package/dist/services/webhook-lifecycle-store.d.ts +45 -0
- package/dist/services/webhook-lifecycle-store.d.ts.map +1 -0
- package/dist/services/webhook-lifecycle-store.js +159 -0
- package/dist/services/webhook-lifecycle-store.js.map +1 -0
- package/dist/setup/verify-permissions.d.ts.map +1 -1
- package/dist/setup/verify-permissions.js +4 -0
- package/dist/setup/verify-permissions.js.map +1 -1
- package/dist/skills/definitions.d.ts.map +1 -1
- package/dist/skills/definitions.js +64 -10
- package/dist/skills/definitions.js.map +1 -1
- package/dist/workflows/events/payloads.d.ts +2 -2
- package/dist/workflows/events/schema.d.ts +8 -8
- package/dist/workflows/trigger-from-envelope.d.ts +13 -0
- package/dist/workflows/trigger-from-envelope.d.ts.map +1 -0
- package/dist/workflows/trigger-from-envelope.js +67 -0
- package/dist/workflows/trigger-from-envelope.js.map +1 -0
- 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,
|
|
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":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"group-creator.js","sourceRoot":"","sources":["../../src/services/group-creator.ts"],"names":[],"mappings":"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"}
|