@zhin.js/adapter-line 4.0.13 → 4.0.14

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/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @zhin.js/adapter-line
2
2
 
3
+ ## 4.0.14
4
+
5
+ ### Patch Changes
6
+
7
+ - 5969c5b: Add SideEventGateway so adapters forward notice/request/system into HandlerIndex. HandlerContext now exposes only generation-safe capabilities and prompt ports; live Endpoint escape hatches are removed.
8
+ - Updated dependencies [5969c5b]
9
+ - Updated dependencies [d336a3f]
10
+ - Updated dependencies [0c82a7e]
11
+ - Updated dependencies [b9217e4]
12
+ - Updated dependencies [5969c5b]
13
+ - Updated dependencies [5969c5b]
14
+ - Updated dependencies [974772e]
15
+ - Updated dependencies [5969c5b]
16
+ - Updated dependencies [5969c5b]
17
+ - Updated dependencies [2f786bd]
18
+ - Updated dependencies [63d89f9]
19
+ - Updated dependencies [71c7cdd]
20
+ - Updated dependencies [3cca0ea]
21
+ - Updated dependencies [1312ca0]
22
+ - Updated dependencies [985fa22]
23
+ - Updated dependencies [04b861d]
24
+ - Updated dependencies [a23d544]
25
+ - Updated dependencies [8cddabf]
26
+ - Updated dependencies [dbe5081]
27
+ - @zhin.js/im-contract@1.0.4
28
+ - @zhin.js/core@1.5.12
29
+ - @zhin.js/adapter@1.1.11
30
+ - @zhin.js/agent@1.1.14
31
+ - @zhin.js/host-http@1.0.11
32
+ - @zhin.js/command@1.0.15
33
+ - zhin.js@6.0.12
34
+
3
35
  ## 4.0.13
4
36
 
5
37
  ### Patch Changes
package/adapters/line.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * Convention entry: discover `adapters/line.ts` → defineAdapter.
4
4
  */
5
5
  import { defineAdapter } from 'zhin.js/adapter';
6
- import { messageGatewayToken } from '@zhin.js/core/runtime';
6
+ import { messageGatewayToken, sideEventGatewayToken } from '@zhin.js/core/runtime';
7
7
  import { httpHostToken } from '@zhin.js/host-http';
8
8
  import { LineEndpoint } from "../lib/endpoint.js";
9
9
  import { resolveLineConfig, } from "../lib/protocol.js";
@@ -26,6 +26,7 @@ export default defineAdapter({
26
26
  return new LineEndpoint({
27
27
  id: context.id,
28
28
  gateway: context.use(messageGatewayToken),
29
+ sideEvents: context.use(sideEventGatewayToken),
29
30
  http: context.use(httpHostToken),
30
31
  config,
31
32
  });
package/adapters/line.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Convention entry: discover `adapters/line.ts` → defineAdapter.
3
3
  */
4
4
  import { defineAdapter } from 'zhin.js/adapter';
5
- import { messageGatewayToken } from '@zhin.js/core/runtime';
5
+ import { messageGatewayToken, sideEventGatewayToken } from '@zhin.js/core/runtime';
6
6
  import { httpHostToken } from '@zhin.js/host-http';
7
7
  import { LineEndpoint } from '../src/endpoint.js';
8
8
  import {
@@ -31,6 +31,7 @@ export default defineAdapter<LineAdapterConfig>({
31
31
  return new LineEndpoint({
32
32
  id: context.id,
33
33
  gateway: context.use(messageGatewayToken),
34
+ sideEvents: context.use(sideEventGatewayToken),
34
35
  http: context.use(httpHostToken),
35
36
  config,
36
37
  });
package/lib/endpoint.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * LineEndpoint — lifecycle, outbound, admit, OpenAPI helpers for agent tools.
3
3
  */
4
4
  import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from 'zhin.js/adapter';
5
- import type { MessageGateway } from '@zhin.js/core/runtime';
5
+ import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
6
6
  import type { HttpHost } from '@zhin.js/host-http';
7
7
  import type { CapabilityId } from 'zhin.js';
8
8
  import { type LineEvent, type ResolvedLineConfig } from './protocol.js';
@@ -20,6 +20,7 @@ export type LineFetch = (url: string, init?: {
20
20
  export interface LineEndpointOptions {
21
21
  readonly id: CapabilityId;
22
22
  readonly gateway: MessageGateway;
23
+ readonly sideEvents?: SideEventGateway;
23
24
  readonly http: HttpHost;
24
25
  readonly config: ResolvedLineConfig;
25
26
  readonly fetch?: LineFetch;
package/lib/endpoint.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
2
2
  import { registerLineAgentEndpoint } from './line-agent-deps.js';
3
- import { formatInboundContent, formatOutboundMessages, generateMessageId, isMessageEvent, isValidLineRecipientId, lineInboundConversation, } from './protocol.js';
3
+ import { formatInboundContent, formatOutboundMessages, generateMessageId, isLineLifecycleEvent, isMessageEvent, isValidLineRecipientId, lineInboundConversation, } from './protocol.js';
4
4
  import { registerLineWebhookRoutes } from './webhook.js';
5
+ import { receiveLineSideEvent } from './side-event-dispatch.js';
5
6
  /** LINE replyToken 有效期短,过期后 reply 必 400;缓存带时间戳,超时弃用改走 push。 */
6
7
  const REPLY_TOKEN_TTL_MS = 60_000;
7
8
  /** 出站 HTTP 调用统一 30s 超时。 */
@@ -104,6 +105,10 @@ export class LineEndpoint {
104
105
  admit(event) {
105
106
  if (!this.#open)
106
107
  return;
108
+ if (isLineLifecycleEvent(event)) {
109
+ receiveLineSideEvent(this.#options.sideEvents, String(this.#options.id), this.#options.config.id, event, this.#logger);
110
+ return;
111
+ }
107
112
  const conversation = lineInboundConversation(String(this.#options.id), event.source);
108
113
  if ('replyToken' in event && typeof event.replyToken === 'string') {
109
114
  this.#replyTokenCache.set(conversation.id, { token: event.replyToken, timestamp: Date.now() });
package/lib/protocol.d.ts CHANGED
@@ -135,6 +135,7 @@ export interface LineChannel {
135
135
  export declare function resolveLineConfig(config?: LineAdapterConfig): ResolvedLineConfig;
136
136
  export declare function normalizeWebhookPath(path: string): string;
137
137
  export declare function isMessageEvent(event: LineEvent): event is LineMessageEvent;
138
+ export declare function isLineLifecycleEvent(event: LineEvent): boolean;
138
139
  export declare function isPostbackEvent(event: LineEvent): event is LinePostbackEvent;
139
140
  export declare function resolveChannel(source: LineSource): LineChannel;
140
141
  /**
package/lib/protocol.js CHANGED
@@ -39,6 +39,12 @@ export function normalizeWebhookPath(path) {
39
39
  export function isMessageEvent(event) {
40
40
  return event.type === 'message' && 'message' in event && event.message != null;
41
41
  }
42
+ export function isLineLifecycleEvent(event) {
43
+ return event.type === 'follow'
44
+ || event.type === 'unfollow'
45
+ || event.type === 'join'
46
+ || event.type === 'leave';
47
+ }
42
48
  export function isPostbackEvent(event) {
43
49
  return event.type === 'postback' && 'postback' in event;
44
50
  }
@@ -0,0 +1,4 @@
1
+ import type { SideEventGateway } from '@zhin.js/core/runtime';
2
+ import { type getAdapterLogger } from '@zhin.js/logger';
3
+ import { type LineEvent } from './protocol.js';
4
+ export declare function receiveLineSideEvent(sideEvents: SideEventGateway | undefined, endpointKey: string, configId: string, event: LineEvent, logger: ReturnType<typeof getAdapterLogger>): void;
@@ -0,0 +1,42 @@
1
+ import { buildNotice, senderFromId } from '@zhin.js/core';
2
+ import { formatCompact } from '@zhin.js/logger';
3
+ import { isLineLifecycleEvent, lineInboundConversation, } from './protocol.js';
4
+ function mapLineLifecycleParts(type) {
5
+ switch (type) {
6
+ case 'follow':
7
+ return { scene_type: 'friend', sub_type: 'increase' };
8
+ case 'unfollow':
9
+ return { scene_type: 'friend', sub_type: 'decrease' };
10
+ case 'join':
11
+ return { scene_type: 'group', sub_type: 'member_increase' };
12
+ case 'leave':
13
+ return { scene_type: 'group', sub_type: 'member_decrease' };
14
+ default:
15
+ return { scene_type: 'line', sub_type: type };
16
+ }
17
+ }
18
+ export function receiveLineSideEvent(sideEvents, endpointKey, configId, event, logger) {
19
+ if (!sideEvents || !isLineLifecycleEvent(event))
20
+ return;
21
+ const parts = mapLineLifecycleParts(event.type);
22
+ const conversation = lineInboundConversation(endpointKey, event.source);
23
+ const userId = event.source.userId || conversation.id;
24
+ void sideEvents.receiveNotice(buildNotice(event, {
25
+ $id: `line:${event.type}:${event.timestamp}:${userId}`,
26
+ $adapter: 'line',
27
+ $endpoint: configId,
28
+ $type: 'notice',
29
+ $scene_id: conversation.id,
30
+ $scene_type: parts.scene_type,
31
+ $sub_type: parts.sub_type,
32
+ $actor: senderFromId(userId),
33
+ $timestamp: event.timestamp,
34
+ })).catch((err) => {
35
+ logger.warn(formatCompact({
36
+ op: 'line_side_event_failed',
37
+ endpoint: configId,
38
+ event: event.type,
39
+ error: err instanceof Error ? err.message : String(err),
40
+ }));
41
+ });
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-line",
3
- "version": "4.0.13",
3
+ "version": "4.0.14",
4
4
  "description": "Zhin.js LINE Messaging API adapter for Plugin Runtime (HTTP webhook)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -32,20 +32,20 @@
32
32
  "directory": "plugins/adapters/line"
33
33
  },
34
34
  "dependencies": {
35
- "@zhin.js/adapter": "1.1.10",
36
- "@zhin.js/core": "1.5.11",
37
- "@zhin.js/host-http": "1.0.10",
38
- "@zhin.js/im-contract": "1.0.3",
35
+ "@zhin.js/adapter": "1.1.11",
36
+ "@zhin.js/core": "1.5.12",
37
+ "@zhin.js/host-http": "1.0.11",
38
+ "@zhin.js/im-contract": "1.0.4",
39
39
  "@zhin.js/logger": "1.0.76"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "zod": "^4.0.0",
43
- "@zhin.js/adapter": "1.1.10",
44
- "@zhin.js/agent": "1.1.13",
45
- "@zhin.js/command": "1.0.14",
46
- "@zhin.js/core": "1.5.11",
47
- "@zhin.js/host-http": "1.0.10",
48
- "zhin.js": "6.0.11"
43
+ "@zhin.js/adapter": "1.1.11",
44
+ "@zhin.js/agent": "1.1.14",
45
+ "@zhin.js/command": "1.0.15",
46
+ "@zhin.js/core": "1.5.12",
47
+ "@zhin.js/host-http": "1.0.11",
48
+ "zhin.js": "6.0.12"
49
49
  },
50
50
  "peerDependenciesMeta": {
51
51
  "@zhin.js/agent": {
@@ -66,8 +66,8 @@
66
66
  "typescript": "^6.0.3",
67
67
  "vitest": "^4.1.10",
68
68
  "zod": "^4.4.3",
69
- "@zhin.js/agent": "1.1.13",
70
- "zhin.js": "6.0.11"
69
+ "@zhin.js/agent": "1.1.14",
70
+ "zhin.js": "6.0.12"
71
71
  },
72
72
  "files": [
73
73
  "adapters",
package/src/endpoint.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * LineEndpoint — lifecycle, outbound, admit, OpenAPI helpers for agent tools.
3
3
  */
4
4
  import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from 'zhin.js/adapter';
5
- import type { MessageGateway } from '@zhin.js/core/runtime';
5
+ import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
6
6
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
7
7
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
8
8
  import type { CapabilityId } from 'zhin.js';
@@ -11,6 +11,7 @@ import {
11
11
  formatInboundContent,
12
12
  formatOutboundMessages,
13
13
  generateMessageId,
14
+ isLineLifecycleEvent,
14
15
  isMessageEvent,
15
16
  isValidLineRecipientId,
16
17
  lineInboundConversation,
@@ -19,6 +20,7 @@ import {
19
20
  type ResolvedLineConfig,
20
21
  } from './protocol.js';
21
22
  import { registerLineWebhookRoutes } from './webhook.js';
23
+ import { receiveLineSideEvent } from './side-event-dispatch.js';
22
24
 
23
25
  /** LINE replyToken 有效期短,过期后 reply 必 400;缓存带时间戳,超时弃用改走 push。 */
24
26
  const REPLY_TOKEN_TTL_MS = 60_000;
@@ -43,6 +45,7 @@ export type LineFetch = (
43
45
  export interface LineEndpointOptions {
44
46
  readonly id: CapabilityId;
45
47
  readonly gateway: MessageGateway;
48
+ readonly sideEvents?: SideEventGateway;
46
49
  readonly http: HttpHost;
47
50
  readonly config: ResolvedLineConfig;
48
51
  readonly fetch?: LineFetch;
@@ -155,6 +158,16 @@ export class LineEndpoint implements EndpointInstance {
155
158
  /** Test / internal: admit a parsed event when open (non-webhook path). */
156
159
  admit(event: LineEvent): void {
157
160
  if (!this.#open) return;
161
+ if (isLineLifecycleEvent(event)) {
162
+ receiveLineSideEvent(
163
+ this.#options.sideEvents,
164
+ String(this.#options.id),
165
+ this.#options.config.id,
166
+ event,
167
+ this.#logger,
168
+ );
169
+ return;
170
+ }
158
171
  const conversation = lineInboundConversation(String(this.#options.id), event.source);
159
172
  if ('replyToken' in event && typeof event.replyToken === 'string') {
160
173
  this.#replyTokenCache.set(conversation.id, { token: event.replyToken, timestamp: Date.now() });
package/src/protocol.ts CHANGED
@@ -201,6 +201,13 @@ export function isMessageEvent(event: LineEvent): event is LineMessageEvent {
201
201
  return event.type === 'message' && 'message' in event && (event as LineMessageEvent).message != null;
202
202
  }
203
203
 
204
+ export function isLineLifecycleEvent(event: LineEvent): boolean {
205
+ return event.type === 'follow'
206
+ || event.type === 'unfollow'
207
+ || event.type === 'join'
208
+ || event.type === 'leave';
209
+ }
210
+
204
211
  export function isPostbackEvent(event: LineEvent): event is LinePostbackEvent {
205
212
  return event.type === 'postback' && 'postback' in event;
206
213
  }
@@ -0,0 +1,54 @@
1
+ import { buildNotice, senderFromId } from '@zhin.js/core';
2
+ import type { SideEventGateway } from '@zhin.js/core/runtime';
3
+ import { formatCompact, type getAdapterLogger } from '@zhin.js/logger';
4
+ import {
5
+ isLineLifecycleEvent,
6
+ lineInboundConversation,
7
+ type LineEvent,
8
+ } from './protocol.js';
9
+
10
+ function mapLineLifecycleParts(type: LineEvent['type']): { scene_type: string; sub_type: string } {
11
+ switch (type) {
12
+ case 'follow':
13
+ return { scene_type: 'friend', sub_type: 'increase' };
14
+ case 'unfollow':
15
+ return { scene_type: 'friend', sub_type: 'decrease' };
16
+ case 'join':
17
+ return { scene_type: 'group', sub_type: 'member_increase' };
18
+ case 'leave':
19
+ return { scene_type: 'group', sub_type: 'member_decrease' };
20
+ default:
21
+ return { scene_type: 'line', sub_type: type };
22
+ }
23
+ }
24
+
25
+ export function receiveLineSideEvent(
26
+ sideEvents: SideEventGateway | undefined,
27
+ endpointKey: string,
28
+ configId: string,
29
+ event: LineEvent,
30
+ logger: ReturnType<typeof getAdapterLogger>,
31
+ ): void {
32
+ if (!sideEvents || !isLineLifecycleEvent(event)) return;
33
+ const parts = mapLineLifecycleParts(event.type);
34
+ const conversation = lineInboundConversation(endpointKey, event.source);
35
+ const userId = event.source.userId || conversation.id;
36
+ void sideEvents.receiveNotice(buildNotice(event, {
37
+ $id: `line:${event.type}:${event.timestamp}:${userId}`,
38
+ $adapter: 'line' as never,
39
+ $endpoint: configId,
40
+ $type: 'notice',
41
+ $scene_id: conversation.id,
42
+ $scene_type: parts.scene_type,
43
+ $sub_type: parts.sub_type,
44
+ $actor: senderFromId(userId),
45
+ $timestamp: event.timestamp,
46
+ })).catch((err) => {
47
+ logger.warn(formatCompact({
48
+ op: 'line_side_event_failed',
49
+ endpoint: configId,
50
+ event: event.type,
51
+ error: err instanceof Error ? err.message : String(err),
52
+ }));
53
+ });
54
+ }