@krischoichoi/channel-control 0.6.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.md +67 -0
- package/lib/access/conversation-directory-store.d.ts +73 -0
- package/lib/access/conversation-directory-store.d.ts.map +1 -0
- package/lib/access/conversation-directory-store.js +66 -0
- package/lib/access/conversation-directory-store.js.map +1 -0
- package/lib/access/conversation-directory.d.ts +51 -0
- package/lib/access/conversation-directory.d.ts.map +1 -0
- package/lib/access/conversation-directory.js +118 -0
- package/lib/access/conversation-directory.js.map +1 -0
- package/lib/access/manager.d.ts +47 -0
- package/lib/access/manager.d.ts.map +1 -0
- package/lib/access/manager.js +124 -0
- package/lib/access/manager.js.map +1 -0
- package/lib/access/materialize.d.ts +33 -0
- package/lib/access/materialize.d.ts.map +1 -0
- package/lib/access/materialize.js +67 -0
- package/lib/access/materialize.js.map +1 -0
- package/lib/access/owner-claim.d.ts +91 -0
- package/lib/access/owner-claim.d.ts.map +1 -0
- package/lib/access/owner-claim.js +200 -0
- package/lib/access/owner-claim.js.map +1 -0
- package/lib/access/policy-store.d.ts +55 -0
- package/lib/access/policy-store.d.ts.map +1 -0
- package/lib/access/policy-store.js +88 -0
- package/lib/access/policy-store.js.map +1 -0
- package/lib/access/validation.d.ts +29 -0
- package/lib/access/validation.d.ts.map +1 -0
- package/lib/access/validation.js +89 -0
- package/lib/access/validation.js.map +1 -0
- package/lib/auth/sanitizer.d.ts +35 -0
- package/lib/auth/sanitizer.d.ts.map +1 -0
- package/lib/auth/sanitizer.js +52 -0
- package/lib/auth/sanitizer.js.map +1 -0
- package/lib/auth/session-manager.d.ts +38 -0
- package/lib/auth/session-manager.d.ts.map +1 -0
- package/lib/auth/session-manager.js +219 -0
- package/lib/auth/session-manager.js.map +1 -0
- package/lib/config.d.ts +20 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +15 -0
- package/lib/config.js.map +1 -0
- package/lib/credentials/manager.d.ts +44 -0
- package/lib/credentials/manager.d.ts.map +1 -0
- package/lib/credentials/manager.js +24 -0
- package/lib/credentials/manager.js.map +1 -0
- package/lib/definitions/registry.d.ts +24 -0
- package/lib/definitions/registry.d.ts.map +1 -0
- package/lib/definitions/registry.js +59 -0
- package/lib/definitions/registry.js.map +1 -0
- package/lib/errors.d.ts +20 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +45 -0
- package/lib/errors.js.map +1 -0
- package/lib/index.d.ts +31 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +31 -0
- package/lib/index.js.map +1 -0
- package/lib/plugin.d.ts +20 -0
- package/lib/plugin.d.ts.map +1 -0
- package/lib/plugin.js +81 -0
- package/lib/plugin.js.map +1 -0
- package/lib/runtime/manager.d.ts +73 -0
- package/lib/runtime/manager.d.ts.map +1 -0
- package/lib/runtime/manager.js +277 -0
- package/lib/runtime/manager.js.map +1 -0
- package/lib/runtime/mount-handle.d.ts +11 -0
- package/lib/runtime/mount-handle.d.ts.map +1 -0
- package/lib/runtime/mount-handle.js +2 -0
- package/lib/runtime/mount-handle.js.map +1 -0
- package/lib/service.d.ts +180 -0
- package/lib/service.d.ts.map +1 -0
- package/lib/service.js +432 -0
- package/lib/service.js.map +1 -0
- package/lib/types.d.ts +370 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/lib/update-check.d.ts +128 -0
- package/lib/update-check.d.ts.map +1 -0
- package/lib/update-check.js +380 -0
- package/lib/update-check.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [ChannelDefinitionRegistry] — registration-order store of ChannelDefinitions.
|
|
3
|
+
*
|
|
4
|
+
* Each channel plugin registers exactly one definition at activation time; the
|
|
5
|
+
* control plane drives setup/authorization/runtime purely through these
|
|
6
|
+
* definitions (doc §14). Iteration is in registration order.
|
|
7
|
+
*/
|
|
8
|
+
import { ChannelDuplicateError, ChannelError } from '@krischoichoi/channel-core';
|
|
9
|
+
export class ChannelDefinitionRegistry {
|
|
10
|
+
definitions = new Map();
|
|
11
|
+
listeners = new Set();
|
|
12
|
+
/**
|
|
13
|
+
* Subscribe to definition registrations. The returned disposer removes the
|
|
14
|
+
* listener. Fired synchronously after a successful register() — the control
|
|
15
|
+
* plane uses this to auto-start configured channels the moment their plugin
|
|
16
|
+
* registers the definition (doc §27), since channel plugins activate AFTER
|
|
17
|
+
* the channel-control plugin itself.
|
|
18
|
+
*/
|
|
19
|
+
onRegister(listener) {
|
|
20
|
+
this.listeners.add(listener);
|
|
21
|
+
return () => {
|
|
22
|
+
this.listeners.delete(listener);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Register a definition; throws ChannelDuplicateError on duplicate id. */
|
|
26
|
+
register(definition) {
|
|
27
|
+
const { id } = definition;
|
|
28
|
+
if (this.definitions.has(id)) {
|
|
29
|
+
throw new ChannelDuplicateError(`channel definition '${id}' is already registered`);
|
|
30
|
+
}
|
|
31
|
+
this.definitions.set(id, definition);
|
|
32
|
+
for (const listener of this.listeners) {
|
|
33
|
+
listener(definition);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
get(id) {
|
|
37
|
+
return this.definitions.get(id);
|
|
38
|
+
}
|
|
39
|
+
/** Require a definition, or raise a stable ChannelError. */
|
|
40
|
+
require(id) {
|
|
41
|
+
const definition = this.definitions.get(id);
|
|
42
|
+
if (!definition) {
|
|
43
|
+
throw new ChannelError('CHANNEL_ERROR', `channel definition '${id}' is not registered`);
|
|
44
|
+
}
|
|
45
|
+
return definition;
|
|
46
|
+
}
|
|
47
|
+
has(id) {
|
|
48
|
+
return this.definitions.has(id);
|
|
49
|
+
}
|
|
50
|
+
/** List definitions in registration order. */
|
|
51
|
+
list() {
|
|
52
|
+
return [...this.definitions.values()];
|
|
53
|
+
}
|
|
54
|
+
/** Remove a definition; returns whether it existed. */
|
|
55
|
+
unregister(id) {
|
|
56
|
+
return this.definitions.delete(id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/definitions/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAGjF,MAAM,OAAO,yBAAyB;IACnB,WAAW,GAAG,IAAI,GAAG,EAA6B,CAAC;IACnD,SAAS,GAAG,IAAI,GAAG,EAA2C,CAAC;IAEhF;;;;;;OAMG;IACH,UAAU,CAAC,QAAiD;QAC1D,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClC,CAAC,CAAC;IACJ,CAAC;IAED,2EAA2E;IAC3E,QAAQ,CAAC,UAA6B;QACpC,MAAM,EAAE,EAAE,EAAE,GAAG,UAAU,CAAC;QAC1B,IAAI,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,qBAAqB,CAC7B,uBAAuB,EAAE,yBAAyB,CACnD,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;QACrC,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACtC,QAAQ,CAAC,UAAU,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClC,CAAC;IAED,4DAA4D;IAC5D,OAAO,CAAC,EAAU;QAChB,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5C,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,YAAY,CACpB,eAAe,EACf,uBAAuB,EAAE,qBAAqB,CAC/C,CAAC;QACJ,CAAC;QACD,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClC,CAAC;IAED,8CAA8C;IAC9C,IAAI;QACF,OAAO,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC;IACxC,CAAC;IAED,uDAAuD;IACvD,UAAU,CAAC,EAAU;QACnB,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACrC,CAAC;CACF"}
|
package/lib/errors.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control-plane error hierarchy with stable machine-readable codes.
|
|
3
|
+
*
|
|
4
|
+
* Channel-level errors reuse [ChannelError] from @krischoichoi/channel-core (e.g.
|
|
5
|
+
* duplicate definition ids). The control plane additionally defines codes for
|
|
6
|
+
* its own security/session concerns (auth not supported, unknown/cancelled/
|
|
7
|
+
* expired sessions, rejected secret fields) that are NOT part of the core's
|
|
8
|
+
* fixed [ChannelErrorCode] union, so they live here without touching core.
|
|
9
|
+
*/
|
|
10
|
+
export type ControlErrorCode = 'CONTROL_ERROR' | 'CONTROL_DEFINITION_NOT_FOUND' | 'AUTH_NOT_SUPPORTED' | 'AUTH_SESSION_NOT_FOUND' | 'AUTH_SESSION_CANCELLED' | 'AUTH_SESSION_EXPIRED' | 'AUTH_NOT_READY' | 'ENABLE_NOT_SUPPORTED' | 'SECRET_FIELD_REJECTED' | 'NOT_A_SECRET_FIELD' | 'CREDENTIAL_NOT_SUPPORTED' | 'CREDENTIAL_READONLY' | 'INVALID_CREDENTIAL' | 'UNKNOWN_FIELD' | 'INVALID_ACCESS_POLICY' | 'CLAIM_NOT_FOUND' | 'CLAIM_EXPIRED' | 'CLAIM_CANCELLED' | 'CLAIM_NOT_SUPPORTED' | 'CLAIM_INVALID';
|
|
11
|
+
/** Base control-plane error. */
|
|
12
|
+
export declare class ControlError extends Error {
|
|
13
|
+
readonly code: ControlErrorCode;
|
|
14
|
+
constructor(code?: ControlErrorCode, message?: string, options?: ErrorOptions);
|
|
15
|
+
}
|
|
16
|
+
/** Whether a value is a control error with the given code. */
|
|
17
|
+
export declare function isControlError(error: unknown, code?: ControlErrorCode): error is ControlError;
|
|
18
|
+
/** Normalize an unknown thrown value into a ControlError with a stable code. */
|
|
19
|
+
export declare function toControlError(error: unknown, code: ControlErrorCode, fallback?: string): ControlError;
|
|
20
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GACxB,eAAe,GACf,8BAA8B,GAC9B,oBAAoB,GACpB,wBAAwB,GACxB,wBAAwB,GACxB,sBAAsB,GACtB,gBAAgB,GAChB,sBAAsB,GACtB,uBAAuB,GACvB,oBAAoB,GACpB,0BAA0B,GAC1B,qBAAqB,GACrB,oBAAoB,GACpB,eAAe,GACf,uBAAuB,GACvB,iBAAiB,GACjB,eAAe,GACf,iBAAiB,GACjB,qBAAqB,GACrB,eAAe,CAAC;AAyBpB,gCAAgC;AAChC,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;gBAEpB,IAAI,GAAE,gBAAkC,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAK/F;AAED,8DAA8D;AAC9D,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,KAAK,IAAI,YAAY,CAG7F;AAED,gFAAgF;AAChF,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,gBAAgB,EACtB,QAAQ,CAAC,EAAE,MAAM,GAChB,YAAY,CAId"}
|
package/lib/errors.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const MESSAGES = {
|
|
2
|
+
CONTROL_ERROR: 'channel control error',
|
|
3
|
+
CONTROL_DEFINITION_NOT_FOUND: 'channel definition is not registered',
|
|
4
|
+
AUTH_NOT_SUPPORTED: 'channel does not support interactive authorization',
|
|
5
|
+
AUTH_SESSION_NOT_FOUND: 'auth session not found',
|
|
6
|
+
AUTH_SESSION_CANCELLED: 'auth session was cancelled',
|
|
7
|
+
AUTH_SESSION_EXPIRED: 'auth session has expired',
|
|
8
|
+
AUTH_NOT_READY: 'channel is not mounted; start it before beginning auth',
|
|
9
|
+
ENABLE_NOT_SUPPORTED: 'channel does not support enable/disable',
|
|
10
|
+
SECRET_FIELD_REJECTED: 'secret fields must be saved through the credentials seam',
|
|
11
|
+
NOT_A_SECRET_FIELD: 'field is not a secret; save it through the config endpoint',
|
|
12
|
+
CREDENTIAL_NOT_SUPPORTED: 'field has no credential ref',
|
|
13
|
+
CREDENTIAL_READONLY: 'credential is read-only (not writable)',
|
|
14
|
+
INVALID_CREDENTIAL: 'credential value must be a string (empty string clears it)',
|
|
15
|
+
UNKNOWN_FIELD: 'channel has no such setup field',
|
|
16
|
+
INVALID_ACCESS_POLICY: 'access policy is invalid for this channel',
|
|
17
|
+
CLAIM_NOT_FOUND: 'owner claim not found',
|
|
18
|
+
CLAIM_EXPIRED: 'owner claim has expired',
|
|
19
|
+
CLAIM_CANCELLED: 'owner claim was cancelled',
|
|
20
|
+
CLAIM_NOT_SUPPORTED: 'channel does not support owner claim',
|
|
21
|
+
CLAIM_INVALID: 'owner claim is in an invalid state for this operation',
|
|
22
|
+
};
|
|
23
|
+
/** Base control-plane error. */
|
|
24
|
+
export class ControlError extends Error {
|
|
25
|
+
code;
|
|
26
|
+
constructor(code = 'CONTROL_ERROR', message, options) {
|
|
27
|
+
super(message ?? MESSAGES[code], options);
|
|
28
|
+
this.name = 'ControlError';
|
|
29
|
+
this.code = code;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Whether a value is a control error with the given code. */
|
|
33
|
+
export function isControlError(error, code) {
|
|
34
|
+
if (!(error instanceof ControlError))
|
|
35
|
+
return false;
|
|
36
|
+
return code === undefined || error.code === code;
|
|
37
|
+
}
|
|
38
|
+
/** Normalize an unknown thrown value into a ControlError with a stable code. */
|
|
39
|
+
export function toControlError(error, code, fallback) {
|
|
40
|
+
if (error instanceof ControlError)
|
|
41
|
+
return error;
|
|
42
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
43
|
+
return new ControlError(code, fallback ?? message);
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AA+BA,MAAM,QAAQ,GAAqC;IACjD,aAAa,EAAE,uBAAuB;IACtC,4BAA4B,EAAE,sCAAsC;IACpE,kBAAkB,EAAE,oDAAoD;IACxE,sBAAsB,EAAE,wBAAwB;IAChD,sBAAsB,EAAE,4BAA4B;IACpD,oBAAoB,EAAE,0BAA0B;IAChD,cAAc,EAAE,wDAAwD;IACxE,oBAAoB,EAAE,yCAAyC;IAC/D,qBAAqB,EAAE,0DAA0D;IACjF,kBAAkB,EAAE,4DAA4D;IAChF,wBAAwB,EAAE,6BAA6B;IACvD,mBAAmB,EAAE,wCAAwC;IAC7D,kBAAkB,EAAE,4DAA4D;IAChF,aAAa,EAAE,iCAAiC;IAChD,qBAAqB,EAAE,2CAA2C;IAClE,eAAe,EAAE,uBAAuB;IACxC,aAAa,EAAE,yBAAyB;IACxC,eAAe,EAAE,2BAA2B;IAC5C,mBAAmB,EAAE,sCAAsC;IAC3D,aAAa,EAAE,uDAAuD;CACvE,CAAC;AAEF,gCAAgC;AAChC,MAAM,OAAO,YAAa,SAAQ,KAAK;IAC5B,IAAI,CAAmB;IAEhC,YAAY,OAAyB,eAAe,EAAE,OAAgB,EAAE,OAAsB;QAC5F,KAAK,CAAC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,8DAA8D;AAC9D,MAAM,UAAU,cAAc,CAAC,KAAc,EAAE,IAAuB;IACpE,IAAI,CAAC,CAAC,KAAK,YAAY,YAAY,CAAC;QAAE,OAAO,KAAK,CAAC;IACnD,OAAO,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;AACnD,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,cAAc,CAC5B,KAAc,EACd,IAAsB,EACtB,QAAiB;IAEjB,IAAI,KAAK,YAAY,YAAY;QAAE,OAAO,KAAK,CAAC;IAChD,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,IAAI,YAAY,CAAC,IAAI,EAAE,QAAQ,IAAI,OAAO,CAAC,CAAC;AACrD,CAAC"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @krischoichoi/channel-control — universal Channel Control Plane.
|
|
3
|
+
*
|
|
4
|
+
* Exports the control-plane types (doc §14–§18), the sub-managers
|
|
5
|
+
* (definition registry, credential manager, auth session manager, runtime
|
|
6
|
+
* manager), the [ChannelControlService] (Cordis Service at ctx.channelControl)
|
|
7
|
+
* and the control-plane error hierarchy.
|
|
8
|
+
*
|
|
9
|
+
* This package is adapter-agnostic: it never imports channel-weixin/qq/
|
|
10
|
+
* dingtalk/lark/web. Platform differences are sealed behind ChannelDefinition.
|
|
11
|
+
*
|
|
12
|
+
* The Cordis plugin entry lives in ./plugin.ts (package export "./plugin").
|
|
13
|
+
*/
|
|
14
|
+
export * from './types.js';
|
|
15
|
+
export * from './errors.js';
|
|
16
|
+
export * from './config.js';
|
|
17
|
+
export * from './definitions/registry.js';
|
|
18
|
+
export * from './credentials/manager.js';
|
|
19
|
+
export * from './auth/sanitizer.js';
|
|
20
|
+
export * from './auth/session-manager.js';
|
|
21
|
+
export * from './runtime/manager.js';
|
|
22
|
+
export * from './runtime/mount-handle.js';
|
|
23
|
+
export * from './access/policy-store.js';
|
|
24
|
+
export * from './access/conversation-directory.js';
|
|
25
|
+
export * from './access/conversation-directory-store.js';
|
|
26
|
+
export * from './access/validation.js';
|
|
27
|
+
export * from './access/materialize.js';
|
|
28
|
+
export * from './access/manager.js';
|
|
29
|
+
export * from './update-check.js';
|
|
30
|
+
export * from './service.js';
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,oCAAoC,CAAC;AACnD,cAAc,0CAA0C,CAAC;AACzD,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @krischoichoi/channel-control — universal Channel Control Plane.
|
|
3
|
+
*
|
|
4
|
+
* Exports the control-plane types (doc §14–§18), the sub-managers
|
|
5
|
+
* (definition registry, credential manager, auth session manager, runtime
|
|
6
|
+
* manager), the [ChannelControlService] (Cordis Service at ctx.channelControl)
|
|
7
|
+
* and the control-plane error hierarchy.
|
|
8
|
+
*
|
|
9
|
+
* This package is adapter-agnostic: it never imports channel-weixin/qq/
|
|
10
|
+
* dingtalk/lark/web. Platform differences are sealed behind ChannelDefinition.
|
|
11
|
+
*
|
|
12
|
+
* The Cordis plugin entry lives in ./plugin.ts (package export "./plugin").
|
|
13
|
+
*/
|
|
14
|
+
export * from './types.js';
|
|
15
|
+
export * from './errors.js';
|
|
16
|
+
export * from './config.js';
|
|
17
|
+
export * from './definitions/registry.js';
|
|
18
|
+
export * from './credentials/manager.js';
|
|
19
|
+
export * from './auth/sanitizer.js';
|
|
20
|
+
export * from './auth/session-manager.js';
|
|
21
|
+
export * from './runtime/manager.js';
|
|
22
|
+
export * from './runtime/mount-handle.js';
|
|
23
|
+
export * from './access/policy-store.js';
|
|
24
|
+
export * from './access/conversation-directory.js';
|
|
25
|
+
export * from './access/conversation-directory-store.js';
|
|
26
|
+
export * from './access/validation.js';
|
|
27
|
+
export * from './access/materialize.js';
|
|
28
|
+
export * from './access/manager.js';
|
|
29
|
+
export * from './update-check.js';
|
|
30
|
+
export * from './service.js';
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,oCAAoC,CAAC;AACnD,cAAc,0CAA0C,CAAC;AACzD,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC"}
|
package/lib/plugin.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cordis plugin entry for @krischoichoi/channel-control (doc §12, §46).
|
|
3
|
+
*
|
|
4
|
+
* Instantiates the ChannelControlService, then auto-starts every configured
|
|
5
|
+
* channel (headless support, doc §25/§27). autoStartAll is guarded so a single
|
|
6
|
+
* misconfigured/broken channel can never crash profile activation. On fiber
|
|
7
|
+
* unload the plugin stops and disposes every runtime mount.
|
|
8
|
+
*
|
|
9
|
+
* The access manager is wired over the shared, durable channel-domain storage
|
|
10
|
+
* (`ctx.channels.resources.storage`) so policies written here and read by the
|
|
11
|
+
* harness resolver share one KV namespace. Owner identity resolution
|
|
12
|
+
* delegates to each registered definition's optional `resolveOwnerIdentity`.
|
|
13
|
+
*/
|
|
14
|
+
import { type Context } from '@deepseek-ai/cordis';
|
|
15
|
+
import { Config, type Config as ChannelControlConfig } from './config.js';
|
|
16
|
+
export declare const name = "channel-control";
|
|
17
|
+
export declare const inject: string[];
|
|
18
|
+
export { Config };
|
|
19
|
+
export declare function apply(ctx: Context, config: ChannelControlConfig): void;
|
|
20
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAMnD,OAAO,EAAE,MAAM,EAAE,KAAK,MAAM,IAAI,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAE1E,eAAO,MAAM,IAAI,oBAAoB,CAAC;AACtC,eAAO,MAAM,MAAM,EAAE,MAAM,EAAgC,CAAC;AAE5D,OAAO,EAAE,MAAM,EAAE,CAAC;AAElB,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAyEtE"}
|
package/lib/plugin.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cordis plugin entry for @krischoichoi/channel-control (doc §12, §46).
|
|
3
|
+
*
|
|
4
|
+
* Instantiates the ChannelControlService, then auto-starts every configured
|
|
5
|
+
* channel (headless support, doc §25/§27). autoStartAll is guarded so a single
|
|
6
|
+
* misconfigured/broken channel can never crash profile activation. On fiber
|
|
7
|
+
* unload the plugin stops and disposes every runtime mount.
|
|
8
|
+
*
|
|
9
|
+
* The access manager is wired over the shared, durable channel-domain storage
|
|
10
|
+
* (`ctx.channels.resources.storage`) so policies written here and read by the
|
|
11
|
+
* harness resolver share one KV namespace. Owner identity resolution
|
|
12
|
+
* delegates to each registered definition's optional `resolveOwnerIdentity`.
|
|
13
|
+
*/
|
|
14
|
+
import {} from '@deepseek-ai/cordis';
|
|
15
|
+
import { ChannelControlService } from './service.js';
|
|
16
|
+
import { ChannelStorageAccessPolicyStore } from './access/policy-store.js';
|
|
17
|
+
import { ChannelStorageConversationDirectoryStore } from './access/conversation-directory-store.js';
|
|
18
|
+
import { Config } from './config.js';
|
|
19
|
+
export const name = 'channel-control';
|
|
20
|
+
export const inject = ['channels', 'credentials'];
|
|
21
|
+
export { Config };
|
|
22
|
+
export function apply(ctx, config) {
|
|
23
|
+
const credentials = ctx.credentials;
|
|
24
|
+
const channels = ctx.channels;
|
|
25
|
+
// The resolver closure references `service`; annotate it explicitly so the
|
|
26
|
+
// self-reference resolves without circular-inference.
|
|
27
|
+
let service;
|
|
28
|
+
service = new ChannelControlService(ctx, {
|
|
29
|
+
credentials,
|
|
30
|
+
// Durable policy store over the same ChannelStorage the harness resolver reads.
|
|
31
|
+
accessStore: new ChannelStorageAccessPolicyStore(() => channels.resources.storage),
|
|
32
|
+
// Durable conversation-identity directory over the same shared storage.
|
|
33
|
+
conversationStore: new ChannelStorageConversationDirectoryStore(() => channels.resources.storage),
|
|
34
|
+
// Owner identity resolution delegates to the registered definition's hook.
|
|
35
|
+
resolveOwnerIdentity: async (channelId, accountId) => service.definitions.get(channelId)?.resolveOwnerIdentity?.(accountId),
|
|
36
|
+
// Prompt-only bundle update check over the same durable channel storage.
|
|
37
|
+
updateCheck: {
|
|
38
|
+
enabled: config.updateCheck.enabled,
|
|
39
|
+
intervalHours: config.updateCheck.intervalHours,
|
|
40
|
+
storage: () => channels.resources.storage,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
// Fire-and-forget startup check (never blocks activation; offline-tolerant;
|
|
44
|
+
// prompt-only — it never installs anything).
|
|
45
|
+
void service.updates.trigger();
|
|
46
|
+
// Headless auto-start: definitions registered BEFORE this plugin activates
|
|
47
|
+
// are swept here; definitions registered afterwards (channel plugins
|
|
48
|
+
// activate after channel-control) are auto-started by the service's
|
|
49
|
+
// onRegister hook (doc §27). Unconfigured channels are skipped silently and
|
|
50
|
+
// a failed start never crashes plugin activation.
|
|
51
|
+
void (async () => {
|
|
52
|
+
try {
|
|
53
|
+
await service.runtime.autoStartAll();
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
ctx.logger('channel-control').error('[channel-control] autoStartAll failed during activation', error);
|
|
57
|
+
}
|
|
58
|
+
})();
|
|
59
|
+
// On fiber unload, stop and dispose every mounted runtime adapter.
|
|
60
|
+
ctx.effect(() => {
|
|
61
|
+
return () => service.runtime.stopAll().catch(() => { });
|
|
62
|
+
});
|
|
63
|
+
// Event listeners for the owner-claim session manager and the conversation
|
|
64
|
+
// identity directory. Neither observe() ever throws intentionally (both catch
|
|
65
|
+
// and log internally), but wrap them anyway so a failure can never propagate
|
|
66
|
+
// back to the adapter inbound loop (which would treat the whole platform
|
|
67
|
+
// message as failed). The disposer is returned through ctx.effect teardown.
|
|
68
|
+
ctx.effect(() => {
|
|
69
|
+
const off = channels.on((event) => {
|
|
70
|
+
try {
|
|
71
|
+
service.ownerClaims.observe(event);
|
|
72
|
+
service.observeConversation(event);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
ctx.logger('channel-control').warn('[channel-control] channel event observe failed', error);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
return off;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [ChannelRuntimeManager] — start/stop/restart/status for mounted channel
|
|
3
|
+
* adapters (doc §21–§23, §25, §60; M2 Task 3 / M3 Task 6).
|
|
4
|
+
*
|
|
5
|
+
* Each mount lives in a Map keyed by `channelId:accountId` (default
|
|
6
|
+
* account 'main'). Starting an already-mounted key disposes the old mount
|
|
7
|
+
* first (restart semantics). The underlying mount is transactional — mirroring
|
|
8
|
+
* channel-core's mountChannelAdapter: register → start → rollback on failure
|
|
9
|
+
* (abort + stop + unregister + rethrow). Because the runtime must AWAIT
|
|
10
|
+
* startup and capture start errors (the opaque core handle does not expose a
|
|
11
|
+
* settle promise), the mount here drives the SAME Cordis effect directly and
|
|
12
|
+
* exposes a `ChannelMountHandle`-shaped disposer, so a failed start never
|
|
13
|
+
* leaves a residual registry entry and surfaces lastError via status().
|
|
14
|
+
*
|
|
15
|
+
* autoStartAll() mounts every registered definition whose autoStart is not
|
|
16
|
+
* false and whose dynamic configured state is configured; unconfigured
|
|
17
|
+
* channels are skipped silently (doc §25/§27) and a failing definition is
|
|
18
|
+
* logged, never allowed to crash plugin activation.
|
|
19
|
+
*/
|
|
20
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
21
|
+
import type { ChannelAdapter } from '@krischoichoi/channel-core';
|
|
22
|
+
import { ChannelDefinitionRegistry } from '../definitions/registry.js';
|
|
23
|
+
import type { CredentialSeam } from '../credentials/manager.js';
|
|
24
|
+
import type { ChannelRuntimeStatus } from '../types.js';
|
|
25
|
+
export interface ChannelRuntimeManagerOptions {
|
|
26
|
+
ctx: Context;
|
|
27
|
+
registry: ChannelDefinitionRegistry;
|
|
28
|
+
credentials: CredentialSeam;
|
|
29
|
+
}
|
|
30
|
+
export declare class ChannelRuntimeManager {
|
|
31
|
+
private readonly mounts;
|
|
32
|
+
private readonly errors;
|
|
33
|
+
private readonly ctx;
|
|
34
|
+
private readonly registry;
|
|
35
|
+
private readonly credentials;
|
|
36
|
+
constructor(options: ChannelRuntimeManagerOptions);
|
|
37
|
+
key(channelId: string, accountId?: string): string;
|
|
38
|
+
start(channelId: string, accountId?: string): Promise<void>;
|
|
39
|
+
stop(channelId: string, accountId?: string): Promise<void>;
|
|
40
|
+
restart(channelId: string, accountId?: string, rollback?: () => Promise<void> | void): Promise<void>;
|
|
41
|
+
status(channelId: string, accountId?: string): Promise<ChannelRuntimeStatus>;
|
|
42
|
+
isRunning(channelId: string, accountId?: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* The currently mounted adapter for a channel (doc §43: the Weixin
|
|
45
|
+
* definition delegates beginAuth/pollAuth/submitAuthInput to the mounted
|
|
46
|
+
* adapter so the M1 auth flow itself is unchanged). Returns undefined when
|
|
47
|
+
* not mounted.
|
|
48
|
+
*/
|
|
49
|
+
adapter(channelId: string, accountId?: string): ChannelAdapter | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Auto-start ONE definition when it is registered (doc §27). Skips
|
|
52
|
+
* silently when autoStart is false or the channel is not configured; a
|
|
53
|
+
* failing start is logged and never thrown (registration must not crash
|
|
54
|
+
* the profile). Returns true when mounted.
|
|
55
|
+
*/
|
|
56
|
+
autoStartOne(channelId: string): Promise<boolean>;
|
|
57
|
+
/**
|
|
58
|
+
* Mount every enabled+configured registered definition (doc §25, §27).
|
|
59
|
+
* Unconfigured channels are skipped silently. A failing start is logged and
|
|
60
|
+
* never propagates, so an unconfigured/broken channel cannot crash the
|
|
61
|
+
* profile. Returns { started, skipped } counts.
|
|
62
|
+
*/
|
|
63
|
+
autoStartAll(): Promise<{
|
|
64
|
+
started: number;
|
|
65
|
+
skipped: number;
|
|
66
|
+
failed: number;
|
|
67
|
+
}>;
|
|
68
|
+
/** Dispose and forget every mounted adapter. */
|
|
69
|
+
stopAll(): Promise<void>;
|
|
70
|
+
private mountTransactional;
|
|
71
|
+
private mount;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/runtime/manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,4BAA4B,CAAC;AAChF,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGxD,MAAM,WAAW,4BAA4B;IAC3C,GAAG,EAAE,OAAO,CAAC;IACb,QAAQ,EAAE,yBAAyB,CAAC;IACpC,WAAW,EAAE,cAAc,CAAC;CAC7B;AAUD,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAU;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IACrD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAiB;gBAEjC,OAAO,EAAE,4BAA4B;IAMjD,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM;IAI5C,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB3D,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY1D,OAAO,CACX,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GACpC,OAAO,CAAC,IAAI,CAAC;IA2CV,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA4ClF,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO;IAIzD;;;;;OAKG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAI1E;;;;;OAKG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAmBvD;;;;;OAKG;IACG,YAAY,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAyCnF,gDAAgD;IAC1C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAW9B,OAAO,CAAC,kBAAkB;YAgDZ,KAAK;CAUpB"}
|