@clowder-ai/connector-wecom-agent 0.0.0-stage → 0.1.0-alpha.5

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 (58) hide show
  1. package/README.en.md +56 -0
  2. package/README.md +24 -2
  3. package/assets/icon.svg +6 -0
  4. package/dist/WeComAgentAdapter.d.ts +149 -0
  5. package/dist/WeComAgentAdapter.d.ts.map +1 -0
  6. package/dist/WeComAgentAdapter.js +525 -0
  7. package/dist/WeComAgentAdapter.js.map +1 -0
  8. package/dist/collect-media.d.ts +12 -0
  9. package/dist/collect-media.d.ts.map +1 -0
  10. package/dist/collect-media.js +27 -0
  11. package/dist/collect-media.js.map +1 -0
  12. package/dist/command-ingress.d.ts +10 -0
  13. package/dist/command-ingress.d.ts.map +1 -0
  14. package/dist/command-ingress.js +13 -0
  15. package/dist/command-ingress.js.map +1 -0
  16. package/dist/inbound-download.d.ts +13 -0
  17. package/dist/inbound-download.d.ts.map +1 -0
  18. package/dist/inbound-download.js +70 -0
  19. package/dist/inbound-download.js.map +1 -0
  20. package/dist/inbound-media-source.d.ts +25 -0
  21. package/dist/inbound-media-source.d.ts.map +1 -0
  22. package/dist/inbound-media-source.js +111 -0
  23. package/dist/inbound-media-source.js.map +1 -0
  24. package/dist/index.d.ts +4 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +2 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/media-notice.d.ts +6 -0
  29. package/dist/media-notice.d.ts.map +1 -0
  30. package/dist/media-notice.js +50 -0
  31. package/dist/media-notice.js.map +1 -0
  32. package/dist/plugin-entrypoint.d.ts +7 -0
  33. package/dist/plugin-entrypoint.d.ts.map +1 -0
  34. package/dist/plugin-entrypoint.js +292 -0
  35. package/dist/plugin-entrypoint.js.map +1 -0
  36. package/dist/reply-sender-map.d.ts +10 -0
  37. package/dist/reply-sender-map.d.ts.map +1 -0
  38. package/dist/reply-sender-map.js +123 -0
  39. package/dist/reply-sender-map.js.map +1 -0
  40. package/dist/rich-block-plaintext.d.ts +5 -0
  41. package/dist/rich-block-plaintext.d.ts.map +1 -0
  42. package/dist/rich-block-plaintext.js +45 -0
  43. package/dist/rich-block-plaintext.js.map +1 -0
  44. package/dist/runtime.d.ts +65 -0
  45. package/dist/runtime.d.ts.map +1 -0
  46. package/dist/runtime.js +84 -0
  47. package/dist/runtime.js.map +1 -0
  48. package/dist/thread-mappings.d.ts +46 -0
  49. package/dist/thread-mappings.d.ts.map +1 -0
  50. package/dist/thread-mappings.js +62 -0
  51. package/dist/thread-mappings.js.map +1 -0
  52. package/dist/types.d.ts +15 -0
  53. package/dist/types.d.ts.map +1 -0
  54. package/dist/types.js +2 -0
  55. package/dist/types.js.map +1 -0
  56. package/npm-shrinkwrap.json +215 -0
  57. package/package.json +55 -4
  58. package/plugin.yaml +83 -0
@@ -0,0 +1,70 @@
1
+ const MEBIBYTE = 1024 * 1024;
2
+ /** Host import is capped at 64 MiB; reject before an adapter buffers beyond it. */
3
+ export const INBOUND_MEDIA_MAX_BYTES = 64 * MEBIBYTE;
4
+ export const INBOUND_MEDIA_TIMEOUT_MS = 30_000;
5
+ export class InboundMediaLimitError extends Error {
6
+ maxBytes;
7
+ constructor(maxBytes) {
8
+ super(`inbound media exceeds the plugin safety limit of ${maxBytes} bytes`);
9
+ this.maxBytes = maxBytes;
10
+ this.name = 'InboundMediaLimitError';
11
+ }
12
+ }
13
+ export async function collectBoundedInboundMedia(content, maxBytes = INBOUND_MEDIA_MAX_BYTES) {
14
+ const chunks = [];
15
+ let total = 0;
16
+ for await (const chunk of content) {
17
+ total += chunk.byteLength;
18
+ if (total > maxBytes)
19
+ throw new InboundMediaLimitError(maxBytes);
20
+ chunks.push(Buffer.from(chunk));
21
+ }
22
+ return Buffer.concat(chunks, total);
23
+ }
24
+ export async function fetchBoundedInboundMedia(fetchFn, input, init = {}, maxBytes = INBOUND_MEDIA_MAX_BYTES, timeoutMs = INBOUND_MEDIA_TIMEOUT_MS) {
25
+ const controller = new AbortController();
26
+ const timer = setTimeout(() => controller.abort(new Error('inbound media download timed out')), timeoutMs);
27
+ const signal = init.signal === null || init.signal === undefined
28
+ ? controller.signal
29
+ : AbortSignal.any([init.signal, controller.signal]);
30
+ try {
31
+ const response = await fetchFn(input, { ...init, signal });
32
+ if (!response.ok) {
33
+ await response.body?.cancel().catch(() => undefined);
34
+ return { response, bytes: Buffer.alloc(0) };
35
+ }
36
+ const contentLength = Number(response.headers.get('content-length'));
37
+ if (Number.isFinite(contentLength) && contentLength > maxBytes) {
38
+ controller.abort();
39
+ await response.body?.cancel().catch(() => undefined);
40
+ throw new InboundMediaLimitError(maxBytes);
41
+ }
42
+ if (response.body === null)
43
+ return { response, bytes: Buffer.alloc(0) };
44
+ const reader = response.body.getReader();
45
+ const chunks = [];
46
+ let total = 0;
47
+ try {
48
+ while (true) {
49
+ const item = await reader.read();
50
+ if (item.done)
51
+ break;
52
+ total += item.value.byteLength;
53
+ if (total > maxBytes) {
54
+ controller.abort();
55
+ await reader.cancel().catch(() => undefined);
56
+ throw new InboundMediaLimitError(maxBytes);
57
+ }
58
+ chunks.push(Buffer.from(item.value));
59
+ }
60
+ }
61
+ finally {
62
+ reader.releaseLock();
63
+ }
64
+ return { response, bytes: Buffer.concat(chunks, total) };
65
+ }
66
+ finally {
67
+ clearTimeout(timer);
68
+ }
69
+ }
70
+ //# sourceMappingURL=inbound-download.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound-download.js","sourceRoot":"","sources":["../src/inbound-download.ts"],"names":[],"mappings":"AAAA,MAAM,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC;AAE7B,mFAAmF;AACnF,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,GAAG,QAAQ,CAAC;AACrD,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC;AAE/C,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC1B;IAArB,YAAqB,QAAgB;QACnC,KAAK,CAAC,oDAAoD,QAAQ,QAAQ,CAAC,CAAC;QADzD,aAAQ,GAAR,QAAQ,CAAQ;QAEnC,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,OAAkC,EAClC,QAAQ,GAAG,uBAAuB;IAElC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAClC,KAAK,IAAI,KAAK,CAAC,UAAU,CAAC;QAC1B,IAAI,KAAK,GAAG,QAAQ;YAAE,MAAM,IAAI,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QACjE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,OAAqB,EACrB,KAAmB,EACnB,OAAoB,EAAE,EACtB,QAAQ,GAAG,uBAAuB,EAClC,SAAS,GAAG,wBAAwB;IAEpC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IAC3G,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;QAC9D,CAAC,CAAC,UAAU,CAAC,MAAM;QACnB,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3D,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACrD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9C,CAAC;QACD,MAAM,aAAa,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC;QACrE,IAAI,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,aAAa,GAAG,QAAQ,EAAE,CAAC;YAC/D,UAAU,CAAC,KAAK,EAAE,CAAC;YACnB,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACrD,MAAM,IAAI,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QAC7C,CAAC;QACD,IAAI,QAAQ,CAAC,IAAI,KAAK,IAAI;YAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QACxE,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;QACzC,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,CAAC;YACH,OAAO,IAAI,EAAE,CAAC;gBACZ,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;gBACjC,IAAI,IAAI,CAAC,IAAI;oBAAE,MAAM;gBACrB,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC;gBAC/B,IAAI,KAAK,GAAG,QAAQ,EAAE,CAAC;oBACrB,UAAU,CAAC,KAAK,EAAE,CAAC;oBACnB,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;oBAC7C,MAAM,IAAI,sBAAsB,CAAC,QAAQ,CAAC,CAAC;gBAC7C,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YACvC,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC;IAC3D,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { type FeatureContext, type PluginMessagingDraft } from '@clowder-ai/plugin-sdk';
2
+ export interface InboundMediaLocator {
3
+ readonly sourceEventId: string;
4
+ readonly type: 'image' | 'file' | 'audio' | 'video';
5
+ readonly platformKey: string;
6
+ readonly fileName?: string;
7
+ readonly duration?: number;
8
+ }
9
+ type DraftElement = PluginMessagingDraft['payload']['elements'][number];
10
+ export interface InboundMediaOwnership {
11
+ readonly entries: readonly {
12
+ readonly reference: string;
13
+ readonly revision: number;
14
+ }[];
15
+ }
16
+ export interface RetainedInboundMedia {
17
+ readonly elements: readonly DraftElement[];
18
+ readonly ownership: InboundMediaOwnership;
19
+ }
20
+ export declare function privateMediaReference(connectorId: string, sourceEventId: string, elementId: string): string;
21
+ export declare function retainInboundMedia(context: FeatureContext, connectorId: string, sourceId: string, sourceEventId: string, attachments: readonly Omit<InboundMediaLocator, 'sourceEventId'>[]): Promise<RetainedInboundMedia>;
22
+ export declare function releaseInboundMedia(context: FeatureContext, ownership: InboundMediaOwnership, sendError?: unknown): Promise<void>;
23
+ export declare function createInboundMediaSource(context: FeatureContext, download: (locator: InboundMediaLocator) => Promise<Uint8Array>): import('@clowder-ai/plugin-sdk').PluginStreams;
24
+ export {};
25
+ //# sourceMappingURL=inbound-media-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound-media-source.d.ts","sourceRoot":"","sources":["../src/inbound-media-source.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,oBAAoB,EAC1B,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IACpD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAQD,KAAK,YAAY,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,SAAS;QACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;KAC3B,EAAE,CAAC;CACL;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC;CAC3C;AAkBD,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG3G;AAED,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,SAAS,IAAI,CAAC,mBAAmB,EAAE,eAAe,CAAC,EAAE,GACjE,OAAO,CAAC,oBAAoB,CAAC,CAuD/B;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,qBAAqB,EAChC,SAAS,CAAC,EAAE,OAAO,GAClB,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,cAAc,EACvB,QAAQ,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,UAAU,CAAC,GAC9D,OAAO,wBAAwB,EAAE,aAAa,CAiBhD"}
@@ -0,0 +1,111 @@
1
+ import { createHash } from 'node:crypto';
2
+ const STATE_PREFIX = 'media-source/';
3
+ const stateKey = (reference) => `${STATE_PREFIX}${reference}`;
4
+ const DEFINITIVE_SEND_REJECTION_CODES = new Set([
5
+ 'VALIDATION', 'PERMISSION',
6
+ ]);
7
+ function isLocator(value) {
8
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
9
+ return false;
10
+ const candidate = value;
11
+ return typeof candidate.sourceEventId === 'string'
12
+ && ['image', 'file', 'audio', 'video'].includes(String(candidate.type))
13
+ && typeof candidate.platformKey === 'string';
14
+ }
15
+ function sameLocator(left, right) {
16
+ return left.sourceEventId === right.sourceEventId
17
+ && left.type === right.type
18
+ && left.platformKey === right.platformKey
19
+ && left.fileName === right.fileName
20
+ && left.duration === right.duration;
21
+ }
22
+ export function privateMediaReference(connectorId, sourceEventId, elementId) {
23
+ const digest = createHash('sha256').update(`${connectorId}\\0${sourceEventId}\\0${elementId}`).digest('hex');
24
+ return `pmr_${connectorId}_${digest}`;
25
+ }
26
+ export async function retainInboundMedia(context, connectorId, sourceId, sourceEventId, attachments) {
27
+ const retained = [];
28
+ try {
29
+ const elements = [];
30
+ for (const [index, attachment] of attachments.entries()) {
31
+ const elementId = `media-${index + 1}`;
32
+ const reference = privateMediaReference(connectorId, sourceEventId, elementId);
33
+ const locator = { sourceEventId, ...attachment };
34
+ const inserted = await context.storage.compareAndSet(stateKey(reference), null, locator);
35
+ if (inserted.applied) {
36
+ if (typeof inserted.revision !== 'number') {
37
+ throw new Error('Inbound media state did not return a revision fence');
38
+ }
39
+ retained.push({ reference, revision: inserted.revision });
40
+ }
41
+ else {
42
+ const existing = await context.storage.get(stateKey(reference));
43
+ if (existing === undefined || !isLocator(existing.value) || !sameLocator(existing.value, locator)) {
44
+ throw new Error('Inbound media reference conflicts with retained locator');
45
+ }
46
+ }
47
+ elements.push({
48
+ elementId,
49
+ kind: 'media_ref',
50
+ payload: {
51
+ type: attachment.type, reference, sourceId,
52
+ ...(attachment.fileName === undefined ? {} : { fileName: attachment.fileName }),
53
+ ...(attachment.duration === undefined ? {} : { duration: attachment.duration }),
54
+ },
55
+ });
56
+ }
57
+ return { elements, ownership: { entries: retained } };
58
+ }
59
+ catch (error) {
60
+ const cleanup = await Promise.allSettled(retained.map(({ reference, revision }) => (context.storage.delete(stateKey(reference), revision))));
61
+ context.log('warn', 'Inbound media locator retention failed', {
62
+ connectorId,
63
+ sourceEventId,
64
+ attachmentCount: attachments.length,
65
+ cleanupFailed: cleanup.some(result => result.status === 'rejected'),
66
+ errorName: error instanceof Error ? error.name : 'unknown',
67
+ });
68
+ return {
69
+ elements: attachments.map((attachment, index) => ({
70
+ elementId: `media-${index + 1}`,
71
+ kind: 'media_unavailable',
72
+ payload: {
73
+ type: attachment.type,
74
+ reason: 'unavailable',
75
+ ...(attachment.fileName === undefined ? {} : { fileName: attachment.fileName }),
76
+ },
77
+ })),
78
+ ownership: { entries: [] },
79
+ };
80
+ }
81
+ }
82
+ export async function releaseInboundMedia(context, ownership, sendError) {
83
+ if (sendError !== undefined) {
84
+ const code = sendError !== null && typeof sendError === 'object'
85
+ ? sendError.code
86
+ : undefined;
87
+ if (typeof code !== 'string' || !DEFINITIVE_SEND_REJECTION_CODES.has(code))
88
+ return;
89
+ }
90
+ await Promise.all(ownership.entries.map(({ reference, revision }) => (context.storage.delete(stateKey(reference), revision))));
91
+ }
92
+ export function createInboundMediaSource(context, download) {
93
+ return { read(reference, options = {}) {
94
+ return { async *[Symbol.asyncIterator]() {
95
+ options.signal?.throwIfAborted();
96
+ const entry = await context.storage.get(stateKey(reference));
97
+ if (!entry || !isLocator(entry.value))
98
+ throw new Error('plugin stream source is unavailable');
99
+ const bytes = await download(entry.value);
100
+ options.signal?.throwIfAborted();
101
+ // Provider adapters retain their existing bounded download checks. No base64 materialization.
102
+ for (let offset = 0; offset < bytes.length; offset += 524288) {
103
+ options.signal?.throwIfAborted();
104
+ // A retained iterator cannot outlive its current feature client.
105
+ await context.storage.get(stateKey(reference));
106
+ yield bytes.subarray(offset, Math.min(offset + 524288, bytes.length));
107
+ }
108
+ } };
109
+ } };
110
+ }
111
+ //# sourceMappingURL=inbound-media-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound-media-source.js","sourceRoot":"","sources":["../src/inbound-media-source.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { computeSignature, decryptMessage, encryptMessage, WeComAgentAdapter, } from './WeComAgentAdapter.js';
2
+ export type { WeComAgentAdapterOptions, WeComAgentAttachment, WeComAgentInboundMessage, } from './WeComAgentAdapter.js';
3
+ export type { ConnectorLogger, MessageEnvelope } from './types.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,wBAAwB,EACxB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { computeSignature, decryptMessage, encryptMessage, WeComAgentAdapter, } from './WeComAgentAdapter.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,iBAAiB,GAClB,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export interface TypedMediaNoticeOptions {
2
+ readonly elements?: readonly unknown[];
3
+ readonly warnInvalid?: (elementId: string) => void;
4
+ }
5
+ export declare function renderTypedMediaNotice(element: unknown, options?: TypedMediaNoticeOptions): string | undefined;
6
+ //# sourceMappingURL=media-notice.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-notice.d.ts","sourceRoot":"","sources":["../src/media-notice.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IACvC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACpD;AAoBD,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,uBAA4B,GACpC,MAAM,GAAG,SAAS,CAapB"}
@@ -0,0 +1,50 @@
1
+ import { isMediaUnavailableMessageElement, isMediaWarningMessageElement, } from '@clowder-ai/plugin-sdk';
2
+ const MEDIA_TYPE_LABEL = {
3
+ image: '图片',
4
+ file: '文件',
5
+ audio: '音频',
6
+ video: '视频',
7
+ };
8
+ const UNAVAILABLE_REASON_LABEL = {
9
+ source_expired: '来源已过期',
10
+ timeout: '处理超时',
11
+ unavailable: '无法获取',
12
+ };
13
+ const WARNING_STAGE_LABEL = {
14
+ transcription: '转写',
15
+ preview: '预览',
16
+ };
17
+ const WARNING_REASON_LABEL = {
18
+ timeout: '超时',
19
+ processing_failed: '处理失败',
20
+ };
21
+ function object(value) {
22
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
23
+ }
24
+ function referencedMediaLabel(elements, mediaElementId) {
25
+ const referenced = elements.find((candidate) => (object(candidate) && candidate.elementId === mediaElementId && candidate.kind === 'media_ref'));
26
+ if (!object(referenced) || !object(referenced.payload))
27
+ return undefined;
28
+ if (typeof referenced.payload.fileName === 'string' && referenced.payload.fileName.trim() !== '') {
29
+ return referenced.payload.fileName;
30
+ }
31
+ const type = referenced.payload.type;
32
+ return typeof type === 'string' && type in MEDIA_TYPE_LABEL
33
+ ? MEDIA_TYPE_LABEL[type]
34
+ : undefined;
35
+ }
36
+ export function renderTypedMediaNotice(element, options = {}) {
37
+ if (isMediaUnavailableMessageElement(element)) {
38
+ const label = element.payload.fileName ?? MEDIA_TYPE_LABEL[element.payload.type];
39
+ return `⚠️ 媒体不可用:${label}(${UNAVAILABLE_REASON_LABEL[element.payload.reason]})`;
40
+ }
41
+ if (isMediaWarningMessageElement(element)) {
42
+ const label = referencedMediaLabel(options.elements ?? [], element.payload.mediaElementId) ?? '媒体';
43
+ return `⚠️ 媒体处理警告:${label}(${WARNING_STAGE_LABEL[element.payload.stage]}${WARNING_REASON_LABEL[element.payload.reason]})`;
44
+ }
45
+ if (object(element) && (element.kind === 'media_unavailable' || element.kind === 'media_warning')) {
46
+ options.warnInvalid?.(typeof element.elementId === 'string' ? element.elementId : 'unknown');
47
+ }
48
+ return undefined;
49
+ }
50
+ //# sourceMappingURL=media-notice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-notice.js","sourceRoot":"","sources":["../src/media-notice.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { WeComAgentAdapter } from './WeComAgentAdapter.js';
2
+ import { type WeComAgentConnectorRuntime, type WeComAgentConnectorRuntimeOptions } from './runtime.js';
3
+ type RuntimeFactory = (options: WeComAgentConnectorRuntimeOptions<WeComAgentAdapter>) => WeComAgentConnectorRuntime<WeComAgentAdapter>;
4
+ export declare function createWeComAgentPluginModule(createRuntime?: RuntimeFactory): import("@clowder-ai/plugin-sdk").PluginModuleEntrypoint;
5
+ declare const _default: import("@clowder-ai/plugin-sdk").PluginModuleEntrypoint;
6
+ export default _default;
7
+ //# sourceMappingURL=plugin-entrypoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-entrypoint.d.ts","sourceRoot":"","sources":["../src/plugin-entrypoint.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAM3D,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,iCAAiC,EAIvC,MAAM,cAAc,CAAC;AAEtB,KAAK,cAAc,GAAG,CACpB,OAAO,EAAE,iCAAiC,CAAC,iBAAiB,CAAC,KAC1D,0BAA0B,CAAC,iBAAiB,CAAC,CAAC;AAmNnD,wBAAgB,4BAA4B,CAAC,aAAa,GAAE,cAAiD,2DA6E5G;;AAED,wBAA8C"}
@@ -0,0 +1,292 @@
1
+ import { isPluginDeliveryInput } from '@clowder-ai/plugin-contract';
2
+ import { definePluginModule } from '@clowder-ai/plugin-sdk';
3
+ import { threadMappings } from './thread-mappings.js';
4
+ import { createConnectorCommandBridge } from '@clowder-ai/plugin-sdk';
5
+ import { definePlugin, defineDeliveryAction, requireConnectorOutboundDelivery, } from '@clowder-ai/plugin-sdk';
6
+ import { renderTypedMediaNotice } from './media-notice.js';
7
+ import { createInboundMediaSource, releaseInboundMedia, retainInboundMedia } from './inbound-media-source.js';
8
+ import { createReplySenderMap } from './reply-sender-map.js';
9
+ import { connectorCommandFacts } from './command-ingress.js';
10
+ import { renderAllRichBlocksPlaintext } from './rich-block-plaintext.js';
11
+ import { createWeComAgentConnectorRuntime, requireWeComAgentWebhookInput, } from './runtime.js';
12
+ const CONNECTOR_ID = 'wecom-agent';
13
+ const IDENTITY_ID = 'wecom-agent';
14
+ function object(value) {
15
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
16
+ }
17
+ function requireDelivery(candidate) {
18
+ if (!isPluginDeliveryInput(candidate) || candidate.kind !== 'message')
19
+ throw new TypeError('wecom-agent requires a message delivery');
20
+ return structuredClone(candidate);
21
+ }
22
+ async function draft(context, message) {
23
+ const retained = await retainInboundMedia(context, CONNECTOR_ID, 'wecom-agent-media', message.providerMessageId, (message.attachments ?? []).map(attachment => ({
24
+ type: attachment.type, platformKey: attachment.platformKey,
25
+ ...(attachment.fileName === undefined ? {} : { fileName: attachment.fileName }),
26
+ })));
27
+ const messageDraft = {
28
+ idempotencyKey: message.providerMessageId,
29
+ sourceEventId: message.providerMessageId,
30
+ identity: IDENTITY_ID,
31
+ sender: { id: message.externalSenderId },
32
+ payload: {
33
+ provenance: {
34
+ origin: { kind: 'external', connectorId: CONNECTOR_ID, sourceAddress: { connectorId: CONNECTOR_ID, chatId: message.externalConversationId, messageId: message.providerMessageId } },
35
+ epistemicStatus: 'observation',
36
+ },
37
+ elements: [
38
+ { elementId: 'text-1', kind: 'text', payload: { text: message.text } },
39
+ ...retained.elements,
40
+ ],
41
+ },
42
+ };
43
+ return { messageDraft, ownership: retained.ownership };
44
+ }
45
+ async function createMessageBridge(context, reply) {
46
+ const replySenders = createReplySenderMap(context);
47
+ const subscriptions = new Map();
48
+ const subscribe = (threadId) => {
49
+ const current = subscriptions.get(threadId);
50
+ if (current !== undefined)
51
+ return current;
52
+ let pending;
53
+ pending = context.messaging.subscribe(threadId, { contributionId: CONNECTOR_ID }).catch((error) => {
54
+ if (subscriptions.get(threadId) === pending)
55
+ subscriptions.delete(threadId);
56
+ throw error;
57
+ });
58
+ subscriptions.set(threadId, pending);
59
+ return pending;
60
+ };
61
+ for (const binding of await threadMappings(context).listBindings())
62
+ await subscribe(binding.threadId);
63
+ const routeCommand = createConnectorCommandBridge(context, {
64
+ connectorId: CONNECTOR_ID, identityId: IDENTITY_ID, reply,
65
+ settle: async () => { },
66
+ async bindingChanged(change) {
67
+ if (change.threadId !== undefined)
68
+ await subscribe(change.threadId);
69
+ if (change.previousThreadId !== undefined && change.previousThreadId !== change.threadId &&
70
+ !(await threadMappings(context).listBindings()).some(binding => binding.threadId === change.previousThreadId)) {
71
+ await context.messaging.unsubscribe(change.previousThreadId);
72
+ subscriptions.delete(change.previousThreadId);
73
+ }
74
+ },
75
+ });
76
+ return {
77
+ async deliver(message) {
78
+ await routeCommand(connectorCommandFacts(message), async () => {
79
+ const thread = await threadMappings(context).ensureByKey(message.externalConversationId, { title: `WeCom ${message.externalConversationId}`.slice(0, 200) });
80
+ await subscribe(thread.id);
81
+ const prepared = await draft(context, message);
82
+ try {
83
+ const sent = await context.messaging.send(thread.id, prepared.messageDraft);
84
+ if (prepared.messageDraft.sender !== undefined) {
85
+ try {
86
+ await replySenders.record(sent.messageId, prepared.messageDraft.sender);
87
+ }
88
+ catch (error) {
89
+ context.log('warn', 'WeCom agent reply-sender mapping record failed', {
90
+ errorName: error instanceof Error ? error.name : 'unknown',
91
+ });
92
+ }
93
+ }
94
+ }
95
+ catch (error) {
96
+ await releaseInboundMedia(context, prepared.ownership, error);
97
+ throw error;
98
+ }
99
+ });
100
+ },
101
+ async outbound(candidate) {
102
+ const input = requireDelivery(candidate);
103
+ const bindings = (await threadMappings(context).listBindings()).filter(item => item.threadId === input.threadId);
104
+ if (bindings.length === 0)
105
+ throw new TypeError(`wecom-agent thread ${input.threadId} has no provider binding`);
106
+ const text = input.envelope.payload.elements.flatMap((element) => {
107
+ if (element.kind === 'text')
108
+ return [element.payload.text];
109
+ const notice = renderTypedMediaNotice(element, {
110
+ elements: input.envelope.payload.elements,
111
+ warnInvalid: elementId => context.log('warn', 'Invalid typed media notice ignored', { elementId }),
112
+ });
113
+ return notice === undefined ? [] : [notice];
114
+ }).join('\n\n');
115
+ const richBlocks = input.envelope.payload.elements.filter(element => element.kind === 'rich_block').map(element => element.payload);
116
+ const media = input.envelope.payload.elements.flatMap((element) => {
117
+ if (element.kind !== 'media_ref' || !object(element.payload))
118
+ return [];
119
+ const type = element.payload.type;
120
+ const reference = element.payload.reference;
121
+ if (!['image', 'file', 'audio', 'video'].includes(String(type)) || typeof reference !== 'string')
122
+ return [];
123
+ return [{ type, reference, ...(typeof element.payload.fileName === 'string' ? { fileName: element.payload.fileName } : {}) }];
124
+ });
125
+ const displayName = input.presentation?.actor.displayName || input.envelope.actor.id;
126
+ const replyPrefix = input.envelope.actor.kind === 'cat' ? `【${displayName}🐱】\n` : '';
127
+ const replyToSender = await replySenders.resolve(input.envelope.replyTo);
128
+ // One thread can carry several provider bindings (the Host allows
129
+ // rebinding different external chats onto the same thread), so the
130
+ // same Host delivery fans out to every binding.
131
+ return {
132
+ replyPrefix,
133
+ inputs: bindings.map(binding => requireConnectorOutboundDelivery({
134
+ deliveryId: input.deliveryId,
135
+ externalConversationId: binding.key,
136
+ presentation: { header: displayName, body: text, origin: input.envelope.actor.kind === 'cat' ? 'agent' : input.envelope.actor.kind === 'system' ? 'system' : 'direct' },
137
+ ...(replyToSender === undefined ? {} : { metadata: { replyToSender } }),
138
+ ...(richBlocks.length === 0 ? {} : { richBlocks }),
139
+ ...(media.length === 0 ? {} : { media }),
140
+ })),
141
+ };
142
+ },
143
+ };
144
+ }
145
+ function forwardedWebhookInput(candidate) {
146
+ if (!object(candidate) || !object(candidate.request)) {
147
+ throw new TypeError('wecom-agent webhook action requires a forwarded request');
148
+ }
149
+ if (Object.keys(candidate).some(key => key !== 'request')) {
150
+ throw new TypeError('wecom-agent webhook action contains an unsupported field');
151
+ }
152
+ const request = candidate.request;
153
+ if ((request.method !== 'GET' && request.method !== 'POST') || request.path !== 'connectors/wecom-agent') {
154
+ throw new TypeError('wecom-agent webhook request must match the declared GET or POST path');
155
+ }
156
+ return requireWeComAgentWebhookInput({ body: request.body, query: request.query });
157
+ }
158
+ function webhookHttpResponse(result) {
159
+ switch (result.kind) {
160
+ case 'challenge': return { status: 200, headers: { 'content-type': 'text/plain' }, body: result.response };
161
+ case 'processed': return { status: 200, headers: {}, body: { ok: true, messageId: result.messageId } };
162
+ case 'skipped': return { status: 200, headers: {}, body: { ok: true, skipped: result.reason } };
163
+ case 'error': return { status: result.status, headers: {}, body: { error: result.message } };
164
+ default: throw new TypeError('wecom-agent webhook runtime returned an invalid result');
165
+ }
166
+ }
167
+ async function deliverOutbound(outbound, input, context, replyPrefix) {
168
+ const blocks = [...(input.richBlocks ?? [])];
169
+ if (blocks.length > 0) {
170
+ const text = [input.presentation.subtitle, input.presentation.body, input.presentation.footer]
171
+ .filter((value) => value !== undefined && value.length > 0)
172
+ .join('\n\n');
173
+ await outbound.sendReply(input.externalConversationId, replyPrefix + text + '\n\n' + renderAllRichBlocksPlaintext(blocks));
174
+ }
175
+ else {
176
+ await outbound.sendFormattedReply(input.externalConversationId, {
177
+ header: input.presentation.header,
178
+ body: input.presentation.body,
179
+ origin: input.presentation.origin === 'callback' ? 'callback' : 'direct',
180
+ ...(input.presentation.subtitle === undefined ? {} : { subtitle: input.presentation.subtitle }),
181
+ ...(input.presentation.footer === undefined ? {} : { footer: input.presentation.footer }),
182
+ });
183
+ }
184
+ for (const media of input.media ?? []) {
185
+ if (media.type === 'video') {
186
+ await outbound.sendReply(input.externalConversationId, `${replyPrefix}⚠️ 视频附件暂不支持发送`, input.metadata);
187
+ continue;
188
+ }
189
+ if (!media.reference.startsWith('hmr_')) {
190
+ await outbound.sendReply(input.externalConversationId, `${replyPrefix}⚠️ 媒体不可用(旧引用无法读取)`, input.metadata);
191
+ continue;
192
+ }
193
+ try {
194
+ await outbound.sendMedia(input.externalConversationId, {
195
+ type: media.type,
196
+ content: context.streams.read(media.reference),
197
+ ...(media.fileName === undefined ? {} : { fileName: media.fileName }),
198
+ });
199
+ }
200
+ catch (error) {
201
+ context.log('warn', 'WeCom agent outbound media delivery failed', {
202
+ mediaType: media.type,
203
+ errorName: error instanceof Error ? error.name : 'unknown',
204
+ });
205
+ await outbound.sendReply(input.externalConversationId, `${replyPrefix}${error instanceof RangeError || (error instanceof Error && error.name === 'ProviderMediaLimitError')
206
+ ? '⚠️ 媒体过大,超过企业微信发送上限'
207
+ : '⚠️ 媒体不可用(读取或上传失败)'}`, input.metadata);
208
+ }
209
+ }
210
+ }
211
+ export function createWeComAgentPluginModule(createRuntime = createWeComAgentConnectorRuntime) {
212
+ return definePluginModule((manifest) => definePlugin({
213
+ manifest,
214
+ activate: {
215
+ 'wecom-agent-messaging': async (context) => {
216
+ let runtime;
217
+ const bridge = await createMessageBridge(context, (input, result) => runtime.outbound.sendFormattedReply(input.chatId, { header: 'Clowder AI', body: result.response, origin: 'callback' }));
218
+ const configure = async () => {
219
+ await runtime?.stop();
220
+ const [corpId, agentId, agentSecret, callbackToken, encodingAesKey] = await Promise.all([
221
+ context.config.get('corpId'),
222
+ context.config.get('agentId'),
223
+ context.secrets.get('agentSecret'),
224
+ context.secrets.get('callbackToken'),
225
+ context.secrets.get('encodingAesKey'),
226
+ ]);
227
+ if (typeof corpId !== 'string')
228
+ throw new TypeError('corpId must be a declared string');
229
+ if (typeof agentId !== 'string')
230
+ throw new TypeError('agentId must be a declared string');
231
+ if (typeof agentSecret !== 'string')
232
+ throw new TypeError('agentSecret must be a declared secret');
233
+ if (typeof callbackToken !== 'string')
234
+ throw new TypeError('callbackToken must be a declared secret');
235
+ if (typeof encodingAesKey !== 'string')
236
+ throw new TypeError('encodingAesKey must be a declared secret');
237
+ runtime = createRuntime({
238
+ config: { corpId, agentId, agentSecret, callbackToken, encodingAesKey },
239
+ host: { deliver: bridge.deliver },
240
+ logger: { debug: (...args) => context.log('debug', String(args[0]), { args }), info: (...args) => context.log('info', String(args[0]), { args }), warn: (...args) => context.log('warn', String(args[0]), { args }), error: (...args) => context.log('error', String(args[0]), { args }) },
241
+ });
242
+ await runtime.start();
243
+ };
244
+ await configure();
245
+ const mediaSource = createInboundMediaSource(context, locator => runtime.outbound.downloadMedia(locator.platformKey));
246
+ return {
247
+ configRefresh: configure,
248
+ streams: mediaSource,
249
+ actions: {
250
+ 'wecom-agent.outbound': defineDeliveryAction(async (candidate) => {
251
+ if (candidate.kind === 'execution_status') {
252
+ return { deliveryId: candidate.deliveryId };
253
+ }
254
+ const { inputs, replyPrefix } = await bridge.outbound(candidate);
255
+ // Per-binding isolation: one thread can fan out to several
256
+ // provider bindings, and a failure on one chat must not block
257
+ // or fail the others. A single binding keeps the old fail-fast
258
+ // behavior so the Host redelivers; with several bindings the
259
+ // action only rejects when every binding failed.
260
+ let firstFailure;
261
+ let delivered = 0;
262
+ for (const input of inputs) {
263
+ try {
264
+ await deliverOutbound(runtime.outbound, input, context, replyPrefix);
265
+ delivered += 1;
266
+ }
267
+ catch (error) {
268
+ if (firstFailure === undefined)
269
+ firstFailure = error;
270
+ context.log('warn', 'WeCom agent outbound delivery to one binding failed', {
271
+ externalConversationId: input.externalConversationId,
272
+ errorName: error instanceof Error ? error.name : 'unknown',
273
+ });
274
+ }
275
+ }
276
+ if (delivered === 0) {
277
+ if (firstFailure !== undefined)
278
+ throw firstFailure;
279
+ throw new Error('WeCom agent outbound delivery failed for all bindings (provider did not report an error)');
280
+ }
281
+ return { deliveryId: candidate.deliveryId };
282
+ }),
283
+ 'wecom-agent.webhook': async (candidate) => webhookHttpResponse(await runtime.handleWebhook(forwardedWebhookInput(candidate))),
284
+ },
285
+ dispose: () => runtime.stop(),
286
+ };
287
+ },
288
+ },
289
+ }));
290
+ }
291
+ export default createWeComAgentPluginModule();
292
+ //# sourceMappingURL=plugin-entrypoint.js.map