@zhin.js/host-http 1.0.6 → 1.0.9

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.
@@ -11,8 +11,8 @@
11
11
  * - inbox:`ctx.databaseHost.models`(unified_inbox_message/request/notice 三张表,
12
12
  * 表不存在或查询失败 → 空数组 + inboxEnabled:false;requestConsumed/noticeConsumed
13
13
  * 按行 id update consumed=1,表/model 缺失时报"未接线")。
14
- * - 社交/群管:`ctx.resolveEndpoint` 拿 live endpoint 实例,只消费其
15
- * `management` 语义端口;平台 SDK 兼容与字段归一化由 Adapter 负责。
14
+ * - 社交/群管:`ctx.withEndpointManagement` 在固定 generation lease 内
15
+ * 消费 `management` 语义端口;平台 SDK 字段归一化由 Adapter 负责。
16
16
  */
17
17
 
18
18
  /** 新 Runtime 统一收件箱表名(SSOT:@zhin.js/plugin-runtime inbox.js)。 */
@@ -31,13 +31,12 @@ export interface ConsoleRpcExtendedCtx {
31
31
  projectRoot: string;
32
32
  /** Plugin Runtime ScheduleHost(basic/cli schedule-host-installer 提供)。 */
33
33
  scheduleHost?: unknown;
34
- /** 经 ImRuntime / AdapterEndpointIndex 解析管理语义端口。 */
35
- resolveEndpointManagement?: (
34
+ /** 在固定 generation lease 内执行一次 Endpoint 管理操作。 */
35
+ withEndpointManagement?: <T>(
36
36
  adapter: string,
37
- endpointId: string,
38
- ) => EndpointManagementPort | null | undefined;
39
- /** @deprecated Use `resolveEndpointManagement`; accepted for one compatibility cycle. */
40
- resolveEndpoint?: (adapter: string, endpointId: string) => unknown;
37
+ endpointKey: string,
38
+ run: (management: EndpointManagementPort) => T | Promise<T>,
39
+ ) => Promise<T | null>;
41
40
  /** Plugin Runtime DatabaseHost 的 models 视图。 */
42
41
  databaseHost?: { models: { get(name: string): unknown } };
43
42
  /** Agent Host 持久化调度引擎(@zhin.js/agent getAssistantRuntime().engine),未 init 时返回 null。 */
@@ -340,13 +339,13 @@ async function listInbox(
340
339
  mapRow: (row: Record<string, unknown>) => Record<string, unknown>,
341
340
  ): Promise<ExtendedRpcResult> {
342
341
  const adapter = strField(d, '$adapter', 'adapter');
343
- const endpointId = strField(d, '$endpoint', 'endpointId', 'endpoint');
344
- if (!adapter || !endpointId) return { error: '$adapter and $endpoint required' };
342
+ const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
343
+ if (!adapter || !endpointKey) return { error: '$adapter and $endpoint required' };
345
344
  const limit = Math.min(numField(d, 30, '$limit', 'limit'), 100);
346
345
  const offset = Math.max(0, numField(d, 0, '$offset', 'offset'));
347
346
  const { rows, enabled } = await readInboxRows(ctx, table, {
348
347
  adapter,
349
- endpoint_id: endpointId,
348
+ endpoint_id: endpointKey,
350
349
  }, {
351
350
  // sort/limit 下推到 DB 侧(内存 sort/slice 保留,作为无下推能力模型的兜底)
352
351
  orderBy: { field: 'created_at', direction: 'DESC' },
@@ -366,11 +365,11 @@ async function listPendingRequests(
366
365
  ctx: ConsoleRpcExtendedCtx,
367
366
  ): Promise<ExtendedRpcResult> {
368
367
  const adapter = strField(d, '$adapter', 'adapter');
369
- const endpointId = strField(d, '$endpoint', 'endpointId', 'endpoint');
370
- if (!adapter || !endpointId) return { error: '$adapter and $endpoint required' };
368
+ const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
369
+ if (!adapter || !endpointKey) return { error: '$adapter and $endpoint required' };
371
370
  const { rows, enabled } = await readInboxRows(ctx, TABLE_REQUEST, {
372
371
  adapter,
373
- endpoint_id: endpointId,
372
+ endpoint_id: endpointKey,
374
373
  }, {
375
374
  orderBy: { field: 'created_at', direction: 'ASC' },
376
375
  });
@@ -386,10 +385,10 @@ async function listInboxMessages(
386
385
  ctx: ConsoleRpcExtendedCtx,
387
386
  ): Promise<ExtendedRpcResult> {
388
387
  const adapter = strField(d, '$adapter', 'adapter');
389
- const endpointId = strField(d, '$endpoint', 'endpointId', 'endpoint');
388
+ const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
390
389
  const channelId = strField(d, '$channel_id', 'channelId', 'channel_id');
391
390
  const channelType = strField(d, '$channel_type', 'channelType', 'channel_type');
392
- if (!adapter || !endpointId || !channelId || !channelType) {
391
+ if (!adapter || !endpointKey || !channelId || !channelType) {
393
392
  return { error: '$adapter, $endpoint, $channel_id, $channel_type required' };
394
393
  }
395
394
  const limit = Math.min(numField(d, 50, '$limit', 'limit'), 100);
@@ -399,7 +398,7 @@ async function listInboxMessages(
399
398
 
400
399
  const where: Record<string, unknown> = {
401
400
  adapter,
402
- endpoint_id: endpointId,
401
+ endpoint_id: endpointKey,
403
402
  channel_id: channelId,
404
403
  channel_type: channelType,
405
404
  };
@@ -525,31 +524,26 @@ async function actOnRequest(
525
524
  ctx: ConsoleRpcExtendedCtx,
526
525
  ): Promise<ExtendedRpcResult> {
527
526
  const adapter = strField(d, '$adapter', 'adapter');
528
- const endpointId = strField(d, '$endpoint', 'endpointId', 'endpoint');
527
+ const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
529
528
  const requestId = strField(d, '$id', 'id', 'platformRequestId', 'platform_request_id', 'requestId');
530
- if (!adapter || !endpointId || !requestId) {
529
+ if (!adapter || !endpointKey || !requestId) {
531
530
  return { error: '$adapter, $endpoint, $id required' };
532
531
  }
533
- const resolved = resolveLiveEndpoint(ctx, adapter, endpointId);
534
- if ('error' in resolved) return resolved;
535
- const management = resolved.management;
536
- const approve = type === 'request.approve';
537
- const method = approve ? management.approveRequest : management.rejectRequest;
538
- if (!method) {
539
- return {
540
- error: `请求审批未接线:当前平台(${adapter})endpoint 不支持 ${approve ? 'approveRequest' : 'rejectRequest'},` +
541
- '且新 Runtime 尚未挂载 pending request 注册表',
542
- };
543
- }
544
- try {
532
+ return withLiveEndpoint(ctx, adapter, endpointKey, async (management) => {
533
+ const approve = type === 'request.approve';
534
+ const method = approve ? management.approveRequest : management.rejectRequest;
535
+ if (!method) {
536
+ return {
537
+ error: `请求审批未接线:当前平台(${adapter})endpoint 不支持 ${approve ? 'approveRequest' : 'rejectRequest'},` +
538
+ '且新 Runtime 尚未挂载 pending request 注册表',
539
+ };
540
+ }
545
541
  const extra = approve
546
542
  ? strField(d, '$remark', 'remark')
547
543
  : strField(d, '$reason', 'reason');
548
544
  await method.call(management, requestId, extra || undefined);
549
545
  return { data: { success: true } };
550
- } catch (error) {
551
- return { error: errorMessage(error) };
552
- }
546
+ });
553
547
  }
554
548
 
555
549
  // ---------------------------------------------------------------- 社交读取
@@ -558,54 +552,45 @@ async function listFriends(
558
552
  d: Record<string, unknown>,
559
553
  ctx: ConsoleRpcExtendedCtx,
560
554
  ): Promise<ExtendedRpcResult> {
561
- const resolved = requireEndpoint(d, ctx);
562
- if ('error' in resolved) return resolved;
563
- const { management, adapter } = resolved;
564
- try {
555
+ const endpoint = requireEndpointAddress(d);
556
+ if ('error' in endpoint) return endpoint;
557
+ return withLiveEndpoint(ctx, endpoint.adapter, endpoint.endpointKey, async (management) => {
565
558
  if (management.listFriends) {
566
559
  const friends = [...await management.listFriends()];
567
560
  return { data: { friends, count: friends.length } };
568
561
  }
569
- return { error: `当前适配器(${adapter})不支持好友列表` };
570
- } catch (error) {
571
- return { error: errorMessage(error) };
572
- }
562
+ return { error: `当前适配器(${endpoint.adapter})不支持好友列表` };
563
+ });
573
564
  }
574
565
 
575
566
  async function listGroups(
576
567
  d: Record<string, unknown>,
577
568
  ctx: ConsoleRpcExtendedCtx,
578
569
  ): Promise<ExtendedRpcResult> {
579
- const resolved = requireEndpoint(d, ctx);
580
- if ('error' in resolved) return resolved;
581
- const { management, adapter } = resolved;
582
- try {
570
+ const endpoint = requireEndpointAddress(d);
571
+ if ('error' in endpoint) return endpoint;
572
+ return withLiveEndpoint(ctx, endpoint.adapter, endpoint.endpointKey, async (management) => {
583
573
  if (management.listGroups) {
584
574
  const groups = [...await management.listGroups()];
585
575
  return { data: { groups, count: groups.length } };
586
576
  }
587
- return { error: `当前适配器(${adapter})不支持群列表` };
588
- } catch (error) {
589
- return { error: errorMessage(error) };
590
- }
577
+ return { error: `当前适配器(${endpoint.adapter})不支持群列表` };
578
+ });
591
579
  }
592
580
 
593
581
  async function listChannels(
594
582
  d: Record<string, unknown>,
595
583
  ctx: ConsoleRpcExtendedCtx,
596
584
  ): Promise<ExtendedRpcResult> {
597
- const resolved = requireEndpoint(d, ctx);
598
- if ('error' in resolved) return resolved;
599
- const { management, adapter } = resolved;
600
- try {
585
+ const endpoint = requireEndpointAddress(d);
586
+ if ('error' in endpoint) return endpoint;
587
+ return withLiveEndpoint(ctx, endpoint.adapter, endpoint.endpointKey, async (management) => {
601
588
  if (management.listChannels) {
602
589
  const channels = [...await management.listChannels()];
603
590
  return { data: { channels, count: channels.length } };
604
591
  }
605
- return { error: `当前适配器(${adapter})不支持频道列表` };
606
- } catch (error) {
607
- return { error: errorMessage(error) };
608
- }
592
+ return { error: `当前适配器(${endpoint.adapter})不支持频道列表` };
593
+ });
609
594
  }
610
595
 
611
596
  async function listGroupMembers(
@@ -613,24 +598,19 @@ async function listGroupMembers(
613
598
  ctx: ConsoleRpcExtendedCtx,
614
599
  ): Promise<ExtendedRpcResult> {
615
600
  const adapter = strField(d, '$adapter', 'adapter');
616
- const endpointId = strField(d, '$endpoint', 'endpointId', 'endpoint');
601
+ const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
617
602
  const groupId = strField(d, '$group_id', 'groupId', 'group_id');
618
- if (!adapter || !endpointId || !groupId) {
603
+ if (!adapter || !endpointKey || !groupId) {
619
604
  return { error: '$adapter, $endpoint, $group_id required' };
620
605
  }
621
- const resolved = resolveLiveEndpoint(ctx, adapter, endpointId);
622
- if ('error' in resolved) return resolved;
623
- const { management } = resolved;
624
- try {
606
+ return withLiveEndpoint(ctx, adapter, endpointKey, async (management) => {
625
607
  const method = management.listGroupMembers;
626
608
  if (!method) {
627
609
  return { error: `当前适配器(${adapter})不支持群成员列表` };
628
610
  }
629
611
  const members = [...await method(groupId)];
630
612
  return { data: { members, count: members.length } };
631
- } catch (error) {
632
- return { error: errorMessage(error) };
633
- }
613
+ });
634
614
  }
635
615
 
636
616
  // ---------------------------------------------------------------- 群管/好友写操作
@@ -649,23 +629,20 @@ async function groupWriteOp(
649
629
  spec: GroupWriteSpec,
650
630
  ): Promise<ExtendedRpcResult> {
651
631
  const adapter = strField(d, '$adapter', 'adapter');
652
- const endpointId = strField(d, '$endpoint', 'endpointId', 'endpoint');
632
+ const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
653
633
  const groupId = strField(d, '$group_id', 'groupId', 'group_id');
654
634
  const userId = strField(d, '$user_id', 'userId', 'user_id');
655
- if (!adapter || !endpointId || !groupId) {
635
+ if (!adapter || !endpointKey || !groupId) {
656
636
  return { error: '$adapter, $endpoint, $group_id required' };
657
637
  }
658
638
  if (spec.requireUser && !userId) {
659
639
  return { error: '$user_id required' };
660
640
  }
661
- const resolved = resolveLiveEndpoint(ctx, adapter, endpointId);
662
- if ('error' in resolved) return resolved;
663
- const { management } = resolved;
664
- const method = management[spec.method];
665
- if (!method) {
666
- return { error: `当前适配器(${adapter})不支持${spec.unsupported}` };
667
- }
668
- try {
641
+ return withLiveEndpoint(ctx, adapter, endpointKey, async (management) => {
642
+ const method = management[spec.method];
643
+ if (!method) {
644
+ return { error: `当前适配器(${adapter})不支持${spec.unsupported}` };
645
+ }
669
646
  const durationRaw = d.$duration ?? d.duration;
670
647
  const enableRaw = d.$enable ?? d.enable;
671
648
  const args = spec.buildArgs(groupId, userId, {
@@ -676,9 +653,7 @@ async function groupWriteOp(
676
653
  });
677
654
  await (method as (...args: unknown[]) => Promise<void>).call(management, ...args);
678
655
  return { data: { success: true } };
679
- } catch (error) {
680
- return { error: errorMessage(error) };
681
- }
656
+ });
682
657
  }
683
658
 
684
659
  async function deleteFriend(
@@ -686,24 +661,19 @@ async function deleteFriend(
686
661
  ctx: ConsoleRpcExtendedCtx,
687
662
  ): Promise<ExtendedRpcResult> {
688
663
  const adapter = strField(d, '$adapter', 'adapter');
689
- const endpointId = strField(d, '$endpoint', 'endpointId', 'endpoint');
664
+ const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
690
665
  const userId = strField(d, '$user_id', 'userId', 'user_id');
691
- if (!adapter || !endpointId || !userId) {
666
+ if (!adapter || !endpointKey || !userId) {
692
667
  return { error: '$adapter, $endpoint, $user_id required' };
693
668
  }
694
- const resolved = resolveLiveEndpoint(ctx, adapter, endpointId);
695
- if ('error' in resolved) return resolved;
696
- const { management } = resolved;
697
- const method = management.deleteFriend;
698
- if (!method) {
699
- return { error: '当前适配器暂不支持删除好友' };
700
- }
701
- try {
669
+ return withLiveEndpoint(ctx, adapter, endpointKey, async (management) => {
670
+ const method = management.deleteFriend;
671
+ if (!method) {
672
+ return { error: '当前适配器暂不支持删除好友' };
673
+ }
702
674
  await method(userId);
703
675
  return { data: { success: true } };
704
- } catch (error) {
705
- return { error: errorMessage(error) };
706
- }
676
+ });
707
677
  }
708
678
 
709
679
  // ---------------------------------------------------------------- helpers
@@ -746,47 +716,30 @@ function numArrayField(d: Record<string, unknown>, ...keys: string[]): number[]
746
716
  return [];
747
717
  }
748
718
 
749
- type ResolveOk = {
750
- management: EndpointManagementPort;
751
- adapter: string;
752
- endpointId: string;
753
- };
754
-
755
- function resolveLiveEndpoint(
719
+ async function withLiveEndpoint(
756
720
  ctx: ConsoleRpcExtendedCtx,
757
721
  adapter: string,
758
- endpointId: string,
759
- ): ResolveOk | { error: string } {
760
- if (ctx.resolveEndpointManagement) {
761
- const management = ctx.resolveEndpointManagement(adapter, endpointId);
762
- if (!management) return { error: 'endpoint not found' };
763
- return { management, adapter, endpointId };
764
- }
765
- if (!ctx.resolveEndpoint) {
722
+ endpointKey: string,
723
+ run: (management: EndpointManagementPort) => ExtendedRpcResult | Promise<ExtendedRpcResult>,
724
+ ): Promise<ExtendedRpcResult> {
725
+ if (!ctx.withEndpointManagement) {
766
726
  return { error: 'Endpoint registry is not configured' };
767
727
  }
768
- const endpoint = ctx.resolveEndpoint(adapter, endpointId);
769
- if (!endpoint || typeof endpoint !== 'object') {
770
- return { error: 'endpoint not found' };
728
+ try {
729
+ return await ctx.withEndpointManagement(adapter, endpointKey, run)
730
+ ?? { error: 'endpoint not found' };
731
+ } catch (error) {
732
+ return { error: errorMessage(error) };
771
733
  }
772
- const management = (endpoint as { management?: unknown }).management;
773
- return {
774
- management: management && typeof management === 'object'
775
- ? management as EndpointManagementPort
776
- : {},
777
- adapter,
778
- endpointId,
779
- };
780
734
  }
781
735
 
782
- function requireEndpoint(
736
+ function requireEndpointAddress(
783
737
  d: Record<string, unknown>,
784
- ctx: ConsoleRpcExtendedCtx,
785
- ): ResolveOk | { error: string } {
738
+ ): { adapter: string; endpointKey: string } | { error: string } {
786
739
  const adapter = strField(d, '$adapter', 'adapter');
787
- const endpointId = strField(d, '$endpoint', 'endpointId', 'endpoint');
788
- if (!adapter || !endpointId) return { error: '$adapter and $endpoint required' };
789
- return resolveLiveEndpoint(ctx, adapter, endpointId);
740
+ const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
741
+ if (!adapter || !endpointKey) return { error: '$adapter and $endpoint required' };
742
+ return { adapter, endpointKey };
790
743
  }
791
744
 
792
745
  function normalizeParent(raw: unknown): { type: 'group' | 'guild'; id: string; name?: string } | undefined {
@@ -93,7 +93,7 @@ export type RuntimeConsoleRpcContext = {
93
93
  getAllSchemas?(): Promise<Record<string, unknown>>;
94
94
  /** Optional Adapter endpoint Console surface (Sandbox / Remote Console). */
95
95
  listEndpoints?(): Promise<readonly RuntimeEndpointSummary[]>;
96
- getEndpoint?(adapter: string, endpointId: string): Promise<RuntimeEndpointSummary | null>;
96
+ getEndpoint?(adapter: string, endpointKey: string): Promise<RuntimeEndpointSummary | null>;
97
97
  sendEndpointMessage?(input: RuntimeEndpointSendInput): Promise<{ messageId: string }>;
98
98
  /** Optional Database host surface (CLI wires DatabaseHost from plugin-runtime). */
99
99
  dbInfo?(): Promise<RuntimeDatabaseInfo> | RuntimeDatabaseInfo;
@@ -123,7 +123,7 @@ export type RuntimeDatabaseInfo = {
123
123
 
124
124
  export type RuntimeEndpointSendInput = {
125
125
  readonly adapter: string;
126
- readonly endpointId: string;
126
+ readonly endpointKey: string;
127
127
  readonly conversation: ConversationAddress;
128
128
  readonly content: unknown;
129
129
  };
@@ -630,8 +630,8 @@ export async function dispatchRuntimeConsoleRpc(
630
630
  try {
631
631
  const data = message as Record<string, unknown>;
632
632
  const adapter = String(data.$adapter ?? '');
633
- const endpointId = String(data.$endpoint ?? '');
634
- if (!adapter || !endpointId) {
633
+ const endpointKey = String(data.$endpoint ?? '');
634
+ if (!adapter || !endpointKey) {
635
635
  emit({ requestId, error: '$adapter and $endpoint required' });
636
636
  return payloads;
637
637
  }
@@ -639,7 +639,7 @@ export async function dispatchRuntimeConsoleRpc(
639
639
  emit({ requestId, error: 'Endpoint registry is not configured' });
640
640
  return payloads;
641
641
  }
642
- const endpoint = await ctx.getEndpoint(adapter, endpointId);
642
+ const endpoint = await ctx.getEndpoint(adapter, endpointKey);
643
643
  if (!endpoint) {
644
644
  emit({ requestId, error: 'endpoint not found' });
645
645
  return payloads;
@@ -657,12 +657,12 @@ export async function dispatchRuntimeConsoleRpc(
657
657
  try {
658
658
  const data = message as Record<string, unknown>;
659
659
  const adapter = String(data.$adapter ?? '');
660
- const endpointId = String(data.$endpoint ?? '');
660
+ const endpointKey = String(data.$endpoint ?? '');
661
661
  const channelId = String(data.$channel_id ?? '');
662
662
  const channelType = String(data.$channel_type ?? '');
663
663
  const content = data.$content;
664
664
  // channelType 可省:wireConversation 归一为 private(与旧 RPC 行为对齐)。
665
- if (!adapter || !endpointId || !channelId || content === undefined) {
665
+ if (!adapter || !endpointKey || !channelId || content === undefined) {
666
666
  emit({
667
667
  requestId,
668
668
  error: '$adapter, $endpoint, $channel_id, $content required',
@@ -675,7 +675,7 @@ export async function dispatchRuntimeConsoleRpc(
675
675
  }
676
676
  const result = await ctx.sendEndpointMessage({
677
677
  adapter,
678
- endpointId,
678
+ endpointKey,
679
679
  conversation: wireConversation(channelType, channelId, data.$parent),
680
680
  content,
681
681
  });