@zhin.js/host-http 1.1.0 → 1.1.1

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 (46) hide show
  1. package/README.md +5 -0
  2. package/lib/console-plugin-rpc.d.ts +3 -0
  3. package/lib/console-plugin-rpc.js +130 -0
  4. package/lib/console-rest-pages.d.ts +1 -1
  5. package/lib/console-rest-pages.js +11 -20
  6. package/lib/{console-rpc-extended.d.ts → console-rpc-extended/contracts.d.ts} +22 -21
  7. package/lib/console-rpc-extended/contracts.js +4 -0
  8. package/lib/console-rpc-extended/dispatcher.d.ts +2 -0
  9. package/lib/console-rpc-extended/dispatcher.js +92 -0
  10. package/lib/console-rpc-extended/endpoint-rpc.d.ts +24 -0
  11. package/lib/console-rpc-extended/endpoint-rpc.js +137 -0
  12. package/lib/console-rpc-extended/inbox-rpc.d.ts +23 -0
  13. package/lib/console-rpc-extended/inbox-rpc.js +292 -0
  14. package/lib/console-rpc-extended/index.d.ts +2 -0
  15. package/lib/console-rpc-extended/index.js +1 -0
  16. package/lib/console-rpc-extended/login-rpc.d.ts +4 -0
  17. package/lib/console-rpc-extended/login-rpc.js +46 -0
  18. package/lib/console-rpc-extended/rpc-values.d.ts +16 -0
  19. package/lib/console-rpc-extended/rpc-values.js +87 -0
  20. package/lib/console-rpc-extended/schedule-rpc.d.ts +6 -0
  21. package/lib/console-rpc-extended/schedule-rpc.js +109 -0
  22. package/lib/console-rpc-extended/workroom-rpc.d.ts +3 -0
  23. package/lib/console-rpc-extended/workroom-rpc.js +104 -0
  24. package/lib/console-rpc.d.ts +71 -15
  25. package/lib/console-rpc.js +116 -82
  26. package/lib/index.d.ts +3 -2
  27. package/lib/index.js +2 -1
  28. package/lib/marketplace-registry.d.ts +12 -0
  29. package/lib/marketplace-registry.js +45 -0
  30. package/package.json +4 -4
  31. package/src/console-plugin-rpc.ts +142 -0
  32. package/src/console-rest-pages.ts +14 -26
  33. package/src/console-rpc-extended/contracts.ts +155 -0
  34. package/src/console-rpc-extended/dispatcher.ts +114 -0
  35. package/src/console-rpc-extended/endpoint-rpc.ts +174 -0
  36. package/src/console-rpc-extended/inbox-rpc.ts +342 -0
  37. package/src/console-rpc-extended/index.ts +8 -0
  38. package/src/console-rpc-extended/login-rpc.ts +50 -0
  39. package/src/console-rpc-extended/rpc-values.ts +104 -0
  40. package/src/console-rpc-extended/schedule-rpc.ts +117 -0
  41. package/src/console-rpc-extended/workroom-rpc.ts +116 -0
  42. package/src/console-rpc.ts +189 -97
  43. package/src/index.ts +7 -2
  44. package/src/marketplace-registry.ts +58 -0
  45. package/lib/console-rpc-extended.js +0 -916
  46. package/src/console-rpc-extended.ts +0 -1192
package/README.md CHANGED
@@ -54,3 +54,8 @@ http:
54
54
  The legacy Koa `@zhin.js/host-router` stack and the `@zhin.js/host-api` management-plane plugin have
55
55
  been removed; the management-plane REST/RPC/SSE is now assembled by `@zhin.js/cli` (Console Host) on
56
56
  top of this package — no Host plugins need to be installed or enabled.
57
+
58
+ Extended Console RPC uses `src/console-rpc-extended/index.ts` as its module boundary. The dispatcher
59
+ only maps protocol method names to schedule, inbox, login, Endpoint-management, and Workroom control
60
+ handlers. Shared request parsing and generation-leased Endpoint execution live behind internal helper
61
+ ports, so adding one RPC domain does not enlarge the central dispatcher or couple unrelated domains.
@@ -0,0 +1,3 @@
1
+ import type { RuntimeConsoleRpcContext, RuntimeConsoleRpcMessage, RuntimeConsoleRpcReply } from './console-rpc.js';
2
+ /** Dispatches the plugin-management slice; undefined means the type belongs elsewhere. */
3
+ export declare function dispatchPluginConsoleRpc(type: string, message: RuntimeConsoleRpcMessage, ctx: RuntimeConsoleRpcContext): Promise<RuntimeConsoleRpcReply | undefined>;
@@ -0,0 +1,130 @@
1
+ import { PLUGIN_RPC } from '@zhin.js/console-protocol';
2
+ /** Dispatches the plugin-management slice; undefined means the type belongs elsewhere. */
3
+ export async function dispatchPluginConsoleRpc(type, message, ctx) {
4
+ const requestId = message.requestId;
5
+ try {
6
+ switch (type) {
7
+ case PLUGIN_RPC.SET_ENABLED: {
8
+ const instanceKey = stringField(message, 'instanceKey');
9
+ const enabled = message.enabled;
10
+ if (!instanceKey || typeof enabled !== 'boolean') {
11
+ return { requestId, error: 'instanceKey and boolean enabled are required' };
12
+ }
13
+ if (!ctx.setPluginEnabled) {
14
+ return { requestId, error: 'Plugin lifecycle management is not configured' };
15
+ }
16
+ const lifecycle = await ctx.setPluginEnabled(instanceKey, enabled);
17
+ ctx.publishEvent?.('plugin:lifecycle-updated', { instanceKey, enabled });
18
+ if (ctx.requestRestart) {
19
+ setTimeout(() => void Promise.resolve(ctx.requestRestart?.()).catch(() => undefined), 500);
20
+ }
21
+ return {
22
+ requestId,
23
+ data: {
24
+ success: true,
25
+ instanceKey,
26
+ enabled,
27
+ disabled: lifecycle.disabled,
28
+ restartRequired: true,
29
+ message: `${instanceKey} 已${enabled ? '启用' : '停用'},Host 正在重启`,
30
+ },
31
+ };
32
+ }
33
+ case PLUGIN_RPC.PLAN_INSTALL: {
34
+ const packageName = stringField(message, 'packageName');
35
+ if (!packageName)
36
+ return { requestId, error: 'packageName is required' };
37
+ if (!ctx.pluginManagement)
38
+ return { requestId, error: 'Plugin management is not configured' };
39
+ return { requestId, data: await ctx.pluginManagement.planInstall(packageName) };
40
+ }
41
+ case PLUGIN_RPC.INSTALL: {
42
+ const packageName = stringField(message, 'packageName');
43
+ if (!packageName)
44
+ return { requestId, error: 'packageName is required' };
45
+ if (!ctx.pluginManagement?.install) {
46
+ return { requestId, error: 'Plugin installation is not configured' };
47
+ }
48
+ const result = await ctx.pluginManagement.install(packageName, optionalString(message.expectedRevision));
49
+ ctx.publishEvent?.('plugin:installed', { packageName, restartRequired: result.restartRequired });
50
+ return { requestId, data: { success: true, ...result } };
51
+ }
52
+ case PLUGIN_RPC.PLAN_UNINSTALL: {
53
+ const packageName = stringField(message, 'packageName');
54
+ if (!packageName)
55
+ return { requestId, error: 'packageName is required' };
56
+ if (!ctx.pluginManagement?.planUninstall) {
57
+ return { requestId, error: 'Plugin uninstall planning is not configured' };
58
+ }
59
+ return { requestId, data: await ctx.pluginManagement.planUninstall(packageName) };
60
+ }
61
+ case PLUGIN_RPC.UNINSTALL: {
62
+ const packageName = stringField(message, 'packageName');
63
+ if (!packageName || stringField(message, 'confirmation') !== packageName) {
64
+ return { requestId, error: 'confirmation must exactly match packageName' };
65
+ }
66
+ if (!ctx.pluginManagement?.uninstall) {
67
+ return { requestId, error: 'Plugin uninstall is not configured' };
68
+ }
69
+ const result = await ctx.pluginManagement.uninstall(packageName, optionalString(message.expectedRevision));
70
+ ctx.publishEvent?.('plugin:uninstalled', { packageName, restartRequired: result.restartRequired });
71
+ return { requestId, data: { success: true, ...result } };
72
+ }
73
+ case PLUGIN_RPC.PLAN_UPDATE: {
74
+ const packageName = stringField(message, 'packageName');
75
+ const targetVersion = stringField(message, 'targetVersion');
76
+ if (!packageName || !targetVersion) {
77
+ return { requestId, error: 'packageName and targetVersion are required' };
78
+ }
79
+ if (!ctx.pluginManagement?.planUpdate) {
80
+ return { requestId, error: 'Plugin update planning is not configured' };
81
+ }
82
+ return { requestId, data: await ctx.pluginManagement.planUpdate(packageName, targetVersion) };
83
+ }
84
+ case PLUGIN_RPC.UPDATE: {
85
+ const packageName = stringField(message, 'packageName');
86
+ const targetVersion = stringField(message, 'targetVersion');
87
+ if (!packageName || !targetVersion) {
88
+ return { requestId, error: 'packageName and targetVersion are required' };
89
+ }
90
+ if (!ctx.pluginManagement?.update)
91
+ return { requestId, error: 'Plugin update is not configured' };
92
+ const result = await ctx.pluginManagement.update(packageName, targetVersion, optionalString(message.expectedRevision));
93
+ ctx.publishEvent?.('plugin:updated', {
94
+ packageName,
95
+ version: result.installedVersion,
96
+ restartRequired: result.restartRequired,
97
+ });
98
+ return { requestId, data: { success: true, ...result } };
99
+ }
100
+ case PLUGIN_RPC.VALIDATE_CONFIG: {
101
+ const pluginName = stringField(message, 'pluginName');
102
+ if (!pluginName)
103
+ return { requestId, error: 'pluginName is required' };
104
+ if (!ctx.validatePluginConfig)
105
+ return { requestId, error: 'Config validation is not configured' };
106
+ return { requestId, data: await ctx.validatePluginConfig(pluginName, message.data) };
107
+ }
108
+ case PLUGIN_RPC.DIAGNOSE: {
109
+ const pluginName = stringField(message, 'pluginName');
110
+ if (!pluginName)
111
+ return { requestId, error: 'pluginName is required' };
112
+ if (!ctx.diagnosePlugin)
113
+ return { requestId, error: 'Plugin diagnostics are not configured' };
114
+ return { requestId, data: await ctx.diagnosePlugin(pluginName) };
115
+ }
116
+ default:
117
+ return undefined;
118
+ }
119
+ }
120
+ catch (error) {
121
+ return { requestId, error: error instanceof Error ? error.message : String(error) };
122
+ }
123
+ }
124
+ function stringField(message, key) {
125
+ const value = message[key];
126
+ return typeof value === 'string' ? value.trim() : '';
127
+ }
128
+ function optionalString(value) {
129
+ return typeof value === 'string' ? value : undefined;
130
+ }
@@ -29,7 +29,7 @@ export interface ConsoleRestCtx {
29
29
  /** Distinguishes a real IM conversation with no Agent turn from a typo key. */
30
30
  readonly isKnownConversationSession?: (sessionKey: string) => boolean | undefined | Promise<boolean | undefined>;
31
31
  }
32
- /** 内省数据门面 — 由 basic/cli 用 agent 包 collectIntrospection* 装配。 */
32
+ /** 内省数据门面 — 由 composition root 从当前 Runtime snapshot 装配。 */
33
33
  export interface ConsoleAgentIntrospection {
34
34
  commands?(): readonly unknown[];
35
35
  middlewares?(): readonly unknown[];
@@ -1,6 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
3
  import { HttpBodyError, readJsonBody } from './json-body.js';
4
+ import { MarketplaceRegistry } from './marketplace-registry.js';
4
5
  const INTROSPECTION_PAGE_SIZES = {
5
6
  commands: 25,
6
7
  middlewares: 30,
@@ -11,7 +12,6 @@ const INTROSPECTION_PAGE_SIZES = {
11
12
  bindings: 30,
12
13
  mcp: 30,
13
14
  };
14
- let pluginsCache = null;
15
15
  const PLUGINS_CACHE_TTL = 5 * 60 * 1000;
16
16
  /**
17
17
  * 注册 Console 页面 REST 路由。返回注销函数(注销全部已注册路由)。
@@ -175,20 +175,11 @@ function registerLogsRoutes(route, base, ctx) {
175
175
  // marketplace(legacy marketplace-rest-api.ts;plugins.json + npmmirror)
176
176
  // ---------------------------------------------------------------------------
177
177
  function registerMarketplaceRoutes(route, base, ctx, fetchFn, pluginRegistryUrl, npmRegistryUrl) {
178
- const fetchPluginRegistry = async () => {
179
- if (pluginsCache && Date.now() - pluginsCache.ts < PLUGINS_CACHE_TTL) {
180
- return pluginsCache.data;
181
- }
182
- const resp = await fetchFn(pluginRegistryUrl, {
183
- signal: AbortSignal.timeout(30_000),
184
- });
185
- if (!resp.ok)
186
- throw new Error(`plugins.json fetch failed: ${resp.status}`);
187
- const json = (await resp.json());
188
- const list = json.plugins || [];
189
- pluginsCache = { data: list, ts: Date.now() };
190
- return list;
191
- };
178
+ const registry = new MarketplaceRegistry({
179
+ fetchFn,
180
+ url: pluginRegistryUrl,
181
+ ttlMs: PLUGINS_CACHE_TTL,
182
+ });
192
183
  route('GET', '/pub/marketplace/search', async (_request, response, url) => {
193
184
  const q = url.searchParams.get('q') ?? url.searchParams.get('keyword') ?? '';
194
185
  const pageNum = Math.max(1, parseInt(url.searchParams.get('page') ?? '1', 10) || 1);
@@ -197,7 +188,7 @@ function registerMarketplaceRoutes(route, base, ctx, fetchFn, pluginRegistryUrl,
197
188
  const official = url.searchParams.get('official') ?? undefined;
198
189
  const searchKeyword = q.trim().toLowerCase();
199
190
  try {
200
- const allPlugins = (await fetchPluginRegistry());
191
+ const allPlugins = (await registry.list());
201
192
  let plugins = allPlugins.map((p) => ({
202
193
  name: p.name,
203
194
  displayName: p.displayName || '',
@@ -259,8 +250,8 @@ function registerMarketplaceRoutes(route, base, ctx, fetchFn, pluginRegistryUrl,
259
250
  try {
260
251
  let cachedDownloads = { weekly: 0, monthly: 0 };
261
252
  try {
262
- const registry = (await fetchPluginRegistry());
263
- const cached = registry.find((p) => p.name === pkgName);
253
+ const registryPlugins = (await registry.list());
254
+ const cached = registryPlugins.find((p) => p.name === pkgName);
264
255
  if (cached?.downloads && typeof cached.downloads === 'object') {
265
256
  cachedDownloads = cached.downloads;
266
257
  }
@@ -379,7 +370,7 @@ async function listInstalledPluginPackages(projectRoot) {
379
370
  }));
380
371
  }
381
372
  // ---------------------------------------------------------------------------
382
- // introspection(legacy introspection-rest-api.ts;分页形状对齐 IntrospectionJsonResponse)
373
+ // Runtime snapshot introspection
383
374
  // ---------------------------------------------------------------------------
384
375
  function registerIntrospectionRoutes(route, base, ctx, options) {
385
376
  const collectors = [
@@ -822,7 +813,7 @@ function parseSessionAction(pathname, prefix, action) {
822
813
  return { sessionKey: decodePathParam(rawKey) };
823
814
  }
824
815
  // ---------------------------------------------------------------------------
825
- // 内部分页 / 过滤(语义对齐 agent 包 introspection-pagination.ts)
816
+ // 内部分页 / 过滤
826
817
  // ---------------------------------------------------------------------------
827
818
  function paginateItems(all, page, pageSize) {
828
819
  const total = all.length;
@@ -1,21 +1,3 @@
1
- /**
2
- * Extended Console RPC: cron/schedule + endpoint social/inbox surface.
3
- *
4
- * 挂在 `dispatchRuntimeConsoleRpc`(console-rpc.ts)之后的第二级 dispatch:
5
- * 返回 `undefined` 表示不识别该 type,由调用方继续走默认分支;
6
- * 识别后返回 `{ data }` 或 `{ error }`(requestId 由调用方补齐)。
7
- *
8
- * 数据源约定(只读真实数据源,不做内存假数据):
9
- * - cron:`ctx.scheduleHost`(Plugin Runtime ScheduleHost,仅 register/list,
10
- * 无持久化引擎,故 cron:add/remove/pause/resume 报"未接线")。
11
- * - request.list:优先 `management.listRequests()`(live endpoint);否则回退
12
- * unified_inbox_request。审批始终走 approveRequest/rejectRequest。
13
- * - inbox(历史):`ctx.databaseHost.models`(unified_inbox_message/request/notice,
14
- * 表不存在或查询失败 → 空数组 + inboxEnabled:false;requestConsumed/noticeConsumed
15
- * 按行 id update consumed=1,表/model 缺失时报"未接线")。
16
- * - 社交/群管:`ctx.withEndpointManagement` 在固定 generation lease 内
17
- * 消费 `management` 语义端口;平台 SDK 字段归一化由 Adapter 负责。
18
- */
19
1
  export interface ConsoleRpcExtendedCtx {
20
2
  /** full scope 才允许写操作;demo scope 只放行只读 RPC。 */
21
3
  fullScope: boolean;
@@ -134,7 +116,28 @@ export type ExtendedRpcResult = {
134
116
  } | {
135
117
  error: string;
136
118
  };
137
- interface EndpointManagementPort {
119
+ export interface ScheduleJobRow {
120
+ id: string;
121
+ cron: string;
122
+ description?: string;
123
+ }
124
+ export interface InboxSelection {
125
+ where(query: Record<string, unknown>): InboxSelection;
126
+ /** 可选:DB 侧排序下推(wrapModel 链具备该能力,轻量 fake 可缺失)。 */
127
+ orderBy?(field: string, direction?: 'ASC' | 'DESC'): InboxSelection;
128
+ /** 可选:DB 侧 limit 下推。 */
129
+ limit?(count: number): InboxSelection;
130
+ /** 可选:DB 侧 offset 下推。 */
131
+ offset?(count: number): InboxSelection;
132
+ then<TResult1 = Record<string, unknown>[], TResult2 = never>(onfulfilled?: ((value: Record<string, unknown>[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
133
+ }
134
+ export interface InboxModel {
135
+ select(): InboxSelection;
136
+ update?(patch: Record<string, unknown>): {
137
+ where(query: Record<string, unknown>): Promise<unknown> | unknown;
138
+ };
139
+ }
140
+ export interface EndpointManagementPort {
138
141
  listFriends?(): Promise<readonly {
139
142
  user_id: number | string;
140
143
  nickname: string;
@@ -172,5 +175,3 @@ interface EndpointManagementPort {
172
175
  setGroupAdmin?(groupId: string, userId: string, enabled: boolean): Promise<void>;
173
176
  deleteFriend?(userId: string): Promise<void>;
174
177
  }
175
- export declare function dispatchExtendedConsoleRpc(type: string, data: Record<string, unknown> | undefined, ctx: ConsoleRpcExtendedCtx): Promise<ExtendedRpcResult | undefined>;
176
- export {};
@@ -0,0 +1,4 @@
1
+ const CRON_NOT_WIRED = '持久化调度未接线:当前 Plugin Runtime ScheduleHost 仅支持插件注册的内存任务(list),' +
2
+ '不支持 add/remove/pause/resume(需要 @zhin.js/agent 持久化调度引擎)';
3
+ const CONSUMED_NOT_WIRED = '收件箱已读标记未接线:unified_inbox_request/notice 表未注册或 DatabaseHost 未启动';
4
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { ConsoleRpcExtendedCtx, ExtendedRpcResult } from './contracts.js';
2
+ export declare function dispatchExtendedConsoleRpc(type: string, data: Record<string, unknown> | undefined, ctx: ConsoleRpcExtendedCtx): Promise<ExtendedRpcResult | undefined>;
@@ -0,0 +1,92 @@
1
+ import { assertDemoConsoleRpcAllowed } from '@zhin.js/console-protocol';
2
+ import { addCron, listSchedule, mutateCron } from './schedule-rpc.js';
3
+ import { actOnRequest, listInbox, listInboxMessages, listPendingRequests, listRecentInboxMessages, mapNoticeRow, mapRequestRow, markInboxConsumed, TABLE_NOTICE, TABLE_REQUEST, } from './inbox-rpc.js';
4
+ import { cancelLogin, listLoginPending, submitLogin } from './login-rpc.js';
5
+ import { deleteFriend, groupWriteOp, listChannels, listFriends, listGroupMembers, listGroups, } from './endpoint-rpc.js';
6
+ import { mutateWorkroomKnowledge, mutateWorkroomProfile } from './workroom-rpc.js';
7
+ export async function dispatchExtendedConsoleRpc(type, data, ctx) {
8
+ const d = data ?? {};
9
+ if (!ctx.fullScope) {
10
+ const denied = assertDemoConsoleRpcAllowed(type);
11
+ if (denied)
12
+ return { error: denied };
13
+ }
14
+ switch (type) {
15
+ case 'schedule:list':
16
+ case 'cron:list':
17
+ return listSchedule(ctx);
18
+ case 'cron:add':
19
+ return addCron(d, ctx);
20
+ case 'cron:remove':
21
+ case 'cron:pause':
22
+ case 'cron:resume':
23
+ return mutateCron(type, d, ctx);
24
+ case 'request.list':
25
+ return listPendingRequests(d, ctx);
26
+ case 'inbox.requests':
27
+ return listInbox(d, ctx, TABLE_REQUEST, 'requests', mapRequestRow);
28
+ case 'inbox.notices':
29
+ return listInbox(d, ctx, TABLE_NOTICE, 'notices', mapNoticeRow);
30
+ case 'inbox.messages':
31
+ return listInboxMessages(d, ctx);
32
+ case 'inbox.recent':
33
+ return listRecentInboxMessages(d, ctx);
34
+ case 'request.approve':
35
+ case 'request.reject':
36
+ return actOnRequest(type, d, ctx);
37
+ case 'login.list':
38
+ return listLoginPending(ctx);
39
+ case 'login.submit':
40
+ return submitLogin(d, ctx);
41
+ case 'login.cancel':
42
+ return cancelLogin(d, ctx);
43
+ case 'request.consumed':
44
+ return markInboxConsumed(d, ctx, TABLE_REQUEST);
45
+ case 'notice.consumed':
46
+ return markInboxConsumed(d, ctx, TABLE_NOTICE);
47
+ case 'endpoint.friends':
48
+ return listFriends(d, ctx);
49
+ case 'endpoint.groups':
50
+ return listGroups(d, ctx);
51
+ case 'endpoint.channels':
52
+ return listChannels(d, ctx);
53
+ case 'endpoint.group_members':
54
+ return listGroupMembers(d, ctx);
55
+ case 'endpoint.group_kick':
56
+ return groupWriteOp(d, ctx, {
57
+ method: 'kickGroupMember',
58
+ buildArgs: (gid, uid) => [gid, uid],
59
+ requireUser: true,
60
+ unsupported: '踢出群成员',
61
+ });
62
+ case 'endpoint.group_mute':
63
+ return groupWriteOp(d, ctx, {
64
+ method: 'muteGroupMember',
65
+ buildArgs: (gid, uid, extra) => [gid, uid, extra.duration ?? 600],
66
+ requireUser: true,
67
+ unsupported: '禁言群成员',
68
+ });
69
+ case 'endpoint.group_admin':
70
+ return groupWriteOp(d, ctx, {
71
+ method: 'setGroupAdmin',
72
+ buildArgs: (gid, uid, extra) => [gid, uid, extra.enable !== false],
73
+ requireUser: true,
74
+ unsupported: '设置群管理员',
75
+ });
76
+ case 'endpoint.delete_friend':
77
+ return deleteFriend(d, ctx);
78
+ case 'workroom.profile.status':
79
+ case 'workroom.profile.bootstrap':
80
+ case 'workroom.profile.pack.publish':
81
+ case 'workroom.profile.publish':
82
+ case 'workroom.profile.rollback':
83
+ case 'workroom.profile.policy.publish':
84
+ return mutateWorkroomProfile(type, d, ctx);
85
+ case 'workroom.knowledge.get':
86
+ case 'workroom.knowledge.publish':
87
+ case 'workroom.knowledge.rollback':
88
+ return mutateWorkroomKnowledge(type, d, ctx);
89
+ default:
90
+ return undefined;
91
+ }
92
+ }
@@ -0,0 +1,24 @@
1
+ import type { ConsoleRpcExtendedCtx, ExtendedRpcResult } from './contracts.js';
2
+ export declare function listFriends(d: Record<string, unknown>, ctx: ConsoleRpcExtendedCtx): Promise<ExtendedRpcResult>;
3
+ export declare function listGroups(d: Record<string, unknown>, ctx: ConsoleRpcExtendedCtx): Promise<ExtendedRpcResult>;
4
+ export declare function listChannels(d: Record<string, unknown>, ctx: ConsoleRpcExtendedCtx): Promise<ExtendedRpcResult>;
5
+ export declare function listGroupMembers(d: Record<string, unknown>, ctx: ConsoleRpcExtendedCtx): Promise<ExtendedRpcResult>;
6
+ interface GroupWriteSpec {
7
+ method: 'kickGroupMember' | 'muteGroupMember' | 'setGroupAdmin';
8
+ buildArgs(groupId: string, userId: string, extra: {
9
+ duration?: number;
10
+ enable?: boolean;
11
+ }): unknown[];
12
+ requireUser: boolean;
13
+ /** 中文操作名,用于"该平台不支持 xxx"。 */
14
+ unsupported: string;
15
+ }
16
+ export declare function groupWriteOp(d: Record<string, unknown>, ctx: ConsoleRpcExtendedCtx, spec: GroupWriteSpec): Promise<ExtendedRpcResult>;
17
+ export declare function deleteFriend(d: Record<string, unknown>, ctx: ConsoleRpcExtendedCtx): Promise<ExtendedRpcResult>;
18
+ export declare function normalizeParent(raw: unknown): {
19
+ type: 'group' | 'guild';
20
+ id: string;
21
+ name?: string;
22
+ } | undefined;
23
+ export declare function channelFromStoredRow(row: Record<string, unknown>): Record<string, unknown>;
24
+ export {};
@@ -0,0 +1,137 @@
1
+ import { strField, withLiveEndpoint } from './rpc-values.js';
2
+ export async function listFriends(d, ctx) {
3
+ const endpoint = requireEndpointAddress(d);
4
+ if ('error' in endpoint)
5
+ return endpoint;
6
+ return withLiveEndpoint(ctx, endpoint.adapter, endpoint.endpointKey, async (management) => {
7
+ if (management.listFriends) {
8
+ const friends = [...await management.listFriends()];
9
+ return { data: { friends, count: friends.length } };
10
+ }
11
+ return { error: `当前适配器(${endpoint.adapter})不支持好友列表` };
12
+ });
13
+ }
14
+ export async function listGroups(d, ctx) {
15
+ const endpoint = requireEndpointAddress(d);
16
+ if ('error' in endpoint)
17
+ return endpoint;
18
+ return withLiveEndpoint(ctx, endpoint.adapter, endpoint.endpointKey, async (management) => {
19
+ if (management.listGroups) {
20
+ const groups = [...await management.listGroups()];
21
+ return { data: { groups, count: groups.length } };
22
+ }
23
+ return { error: `当前适配器(${endpoint.adapter})不支持群列表` };
24
+ });
25
+ }
26
+ export async function listChannels(d, ctx) {
27
+ const endpoint = requireEndpointAddress(d);
28
+ if ('error' in endpoint)
29
+ return endpoint;
30
+ return withLiveEndpoint(ctx, endpoint.adapter, endpoint.endpointKey, async (management) => {
31
+ if (management.listChannels) {
32
+ const channels = [...await management.listChannels()];
33
+ return { data: { channels, count: channels.length } };
34
+ }
35
+ return { error: `当前适配器(${endpoint.adapter})不支持频道列表` };
36
+ });
37
+ }
38
+ export async function listGroupMembers(d, ctx) {
39
+ const adapter = strField(d, 'adapter');
40
+ const endpointKey = strField(d, 'endpointKey');
41
+ const groupId = strField(d, 'groupId');
42
+ if (!adapter || !endpointKey || !groupId) {
43
+ return { error: 'adapter, endpointKey, and groupId are required' };
44
+ }
45
+ return withLiveEndpoint(ctx, adapter, endpointKey, async (management) => {
46
+ const method = management.listGroupMembers;
47
+ if (!method) {
48
+ return { error: `当前适配器(${adapter})不支持群成员列表` };
49
+ }
50
+ const members = [...await method(groupId)];
51
+ return { data: { members, count: members.length } };
52
+ });
53
+ }
54
+ export async function groupWriteOp(d, ctx, spec) {
55
+ const adapter = strField(d, 'adapter');
56
+ const endpointKey = strField(d, 'endpointKey');
57
+ const groupId = strField(d, 'groupId');
58
+ const userId = strField(d, 'userId');
59
+ if (!adapter || !endpointKey || !groupId) {
60
+ return { error: 'adapter, endpointKey, and groupId are required' };
61
+ }
62
+ if (spec.requireUser && !userId) {
63
+ return { error: 'userId is required' };
64
+ }
65
+ return withLiveEndpoint(ctx, adapter, endpointKey, async (management) => {
66
+ const method = management[spec.method];
67
+ if (!method) {
68
+ return { error: `当前适配器(${adapter})不支持${spec.unsupported}` };
69
+ }
70
+ const durationRaw = d.duration;
71
+ const enableRaw = d.enable;
72
+ const args = spec.buildArgs(groupId, userId, {
73
+ duration: typeof durationRaw === 'number' && Number.isFinite(durationRaw)
74
+ ? durationRaw
75
+ : undefined,
76
+ enable: typeof enableRaw === 'boolean' ? enableRaw : undefined,
77
+ });
78
+ await method.call(management, ...args);
79
+ return { data: { success: true } };
80
+ });
81
+ }
82
+ export async function deleteFriend(d, ctx) {
83
+ const adapter = strField(d, 'adapter');
84
+ const endpointKey = strField(d, 'endpointKey');
85
+ const userId = strField(d, 'userId');
86
+ if (!adapter || !endpointKey || !userId) {
87
+ return { error: 'adapter, endpointKey, and userId are required' };
88
+ }
89
+ return withLiveEndpoint(ctx, adapter, endpointKey, async (management) => {
90
+ const method = management.deleteFriend;
91
+ if (!method) {
92
+ return { error: '当前适配器暂不支持删除好友' };
93
+ }
94
+ await method(userId);
95
+ return { data: { success: true } };
96
+ });
97
+ }
98
+ // ---------------------------------------------------------------- helpers
99
+ function requireEndpointAddress(d) {
100
+ const adapter = strField(d, 'adapter');
101
+ const endpointKey = strField(d, 'endpointKey');
102
+ if (!adapter || !endpointKey)
103
+ return { error: 'adapter and endpointKey are required' };
104
+ return { adapter, endpointKey };
105
+ }
106
+ export function normalizeParent(raw) {
107
+ if (!raw || typeof raw !== 'object')
108
+ return undefined;
109
+ const parent = raw;
110
+ const id = typeof parent.id === 'string' && parent.id.trim() ? parent.id.trim() : undefined;
111
+ if (!id)
112
+ return undefined;
113
+ let type;
114
+ if (parent.type === 'group' || parent.type === 'guild')
115
+ type = parent.type;
116
+ else if (parent.type === 'channel')
117
+ type = 'guild';
118
+ if (!type)
119
+ return undefined;
120
+ const name = typeof parent.name === 'string' && parent.name.trim() ? parent.name.trim() : undefined;
121
+ return name ? { type, id, name } : { type, id };
122
+ }
123
+ export function channelFromStoredRow(row) {
124
+ const parent = parentFromStoredRow(row);
125
+ return {
126
+ id: String(row.channel_id ?? ''),
127
+ type: String(row.channel_type ?? ''),
128
+ ...(row.channel_name != null ? { name: String(row.channel_name) } : {}),
129
+ ...(parent ? { parent } : {}),
130
+ };
131
+ }
132
+ function parentFromStoredRow(row) {
133
+ return normalizeParent({
134
+ type: row.channel_parent_type,
135
+ id: row.channel_parent_id,
136
+ });
137
+ }
@@ -0,0 +1,23 @@
1
+ import type { ConsoleRpcExtendedCtx, ExtendedRpcResult } from './contracts.js';
2
+ export declare const TABLE_MESSAGE = "unified_inbox_message";
3
+ export declare const TABLE_REQUEST = "unified_inbox_request";
4
+ export declare const TABLE_NOTICE = "unified_inbox_notice";
5
+ export declare function listInbox(d: Record<string, unknown>, ctx: ConsoleRpcExtendedCtx, table: string, key: 'requests' | 'notices', mapRow: (row: Record<string, unknown>) => Record<string, unknown>): Promise<ExtendedRpcResult>;
6
+ /** request.list —— 优先 live EndpointManagement.listRequests;否则回退 unified_inbox_request。 */
7
+ export declare function listPendingRequests(d: Record<string, unknown>, ctx: ConsoleRpcExtendedCtx): Promise<ExtendedRpcResult>;
8
+ export declare function listInboxMessages(d: Record<string, unknown>, ctx: ConsoleRpcExtendedCtx): Promise<ExtendedRpcResult>;
9
+ /**
10
+ * Cross-endpoint recent inbox used by paired Mobile Console clients.
11
+ * Unlike endpoint-detail `inbox.messages`, every row carries its complete
12
+ * routable address so selecting a conversation can safely call
13
+ * `endpoint.send_message` through the Device Protocol write allowlist.
14
+ */
15
+ export declare function listRecentInboxMessages(d: Record<string, unknown>, ctx: ConsoleRpcExtendedCtx): Promise<ExtendedRpcResult>;
16
+ export declare function mapRequestRow(row: Record<string, unknown>): Record<string, unknown>;
17
+ export declare function mapNoticeRow(row: Record<string, unknown>): Record<string, unknown>;
18
+ /**
19
+ * request.consumed / notice.consumed —— 按行 id 置 consumed=1。
20
+ * 表未注册(或 model 无 update)时保持"未接线"报错。
21
+ */
22
+ export declare function markInboxConsumed(d: Record<string, unknown>, ctx: ConsoleRpcExtendedCtx, table: string): Promise<ExtendedRpcResult>;
23
+ export declare function actOnRequest(type: 'request.approve' | 'request.reject', d: Record<string, unknown>, ctx: ConsoleRpcExtendedCtx): Promise<ExtendedRpcResult>;