@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.
- package/README.md +5 -0
- package/lib/console-rest-pages.d.ts +5 -2
- package/lib/console-rest-pages.js +90 -67
- package/lib/console-rpc-extended.d.ts +4 -6
- package/lib/console-rpc-extended.js +73 -124
- package/lib/console-rpc.d.ts +2 -2
- package/lib/console-rpc.js +6 -6
- package/lib/http-host.d.ts +8 -5
- package/lib/http-host.js +141 -64
- package/lib/index.d.ts +1 -1
- package/package.json +5 -5
- package/src/console-rest-pages.ts +98 -73
- package/src/console-rpc-extended.ts +78 -125
- package/src/console-rpc.ts +8 -8
- package/src/http-host.ts +174 -72
- package/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -7,6 +7,11 @@ Console can register path-scoped WebSocket / HTTP routes without importing Koa o
|
|
|
7
7
|
Root installs the host via `installResources` (see `basic/cli/src/plugin-runtime/http-host-installer.ts`
|
|
8
8
|
and `console-host-installer.ts`).
|
|
9
9
|
|
|
10
|
+
The composition root owns the process listener through `ProcessHttpHost`. Generation scopes receive
|
|
11
|
+
only the `HttpHost` routing port: they may register HTTP/WS routes but cannot listen or close the
|
|
12
|
+
shared server. Registrations are bound to generation admission, so candidate routes stay invisible
|
|
13
|
+
until snapshot commit and retired routes stop matching immediately while their leases drain.
|
|
14
|
+
|
|
10
15
|
## Capabilities (this slice)
|
|
11
16
|
|
|
12
17
|
- Path-scoped WebSocket upgrades (`ws(path)`)
|
|
@@ -14,7 +14,10 @@ export interface ConsoleRestCtx {
|
|
|
14
14
|
* 可选:返回新 Runtime 的 agent 门面(见 {@link ConsoleAgentRuntime})。
|
|
15
15
|
* 未接线时 introspection 降级为空列表 + note,session tree 返回 503。
|
|
16
16
|
*/
|
|
17
|
-
readonly
|
|
17
|
+
readonly acquireAgentRuntime?: () => {
|
|
18
|
+
readonly value: ConsoleAgentRuntime;
|
|
19
|
+
release(): void;
|
|
20
|
+
} | null;
|
|
18
21
|
/** Database host(logs 页数据源:SystemLog 模型)。 */
|
|
19
22
|
readonly databaseHost?: {
|
|
20
23
|
readonly dialect: string;
|
|
@@ -49,7 +52,7 @@ export interface ConsoleAgentSessionTree {
|
|
|
49
52
|
message: string;
|
|
50
53
|
}>;
|
|
51
54
|
}
|
|
52
|
-
/** `ctx.
|
|
55
|
+
/** `ctx.acquireAgentRuntime()` 返回值的最小结构约定(全部可选,逐项降级)。 */
|
|
53
56
|
export interface ConsoleAgentRuntime {
|
|
54
57
|
readonly introspection?: ConsoleAgentIntrospection;
|
|
55
58
|
readonly sessionTree?: ConsoleAgentSessionTree;
|
|
@@ -448,7 +448,7 @@ function registerIntrospectionRoutes(route, base, ctx) {
|
|
|
448
448
|
const degradedNote = note ?? (missing
|
|
449
449
|
? kind === 'endpoints'
|
|
450
450
|
? 'Endpoints 数据源未接线(ctx.getEndpoints 缺失)'
|
|
451
|
-
: 'Agent runtime 未装配(basic/cli 未接线
|
|
451
|
+
: 'Agent runtime 未装配(basic/cli 未接线 acquireAgentRuntime)'
|
|
452
452
|
: undefined);
|
|
453
453
|
writeJson(response, 200, {
|
|
454
454
|
success: true,
|
|
@@ -466,27 +466,34 @@ function registerIntrospectionRoutes(route, base, ctx) {
|
|
|
466
466
|
}
|
|
467
467
|
}
|
|
468
468
|
function agentIntrospection(ctx) {
|
|
469
|
-
const
|
|
470
|
-
|
|
469
|
+
const lease = ctx.acquireAgentRuntime?.();
|
|
470
|
+
if (!lease)
|
|
471
|
+
return undefined;
|
|
472
|
+
try {
|
|
473
|
+
return lease.value.introspection;
|
|
474
|
+
}
|
|
475
|
+
finally {
|
|
476
|
+
lease.release();
|
|
477
|
+
}
|
|
471
478
|
}
|
|
472
479
|
// ---------------------------------------------------------------------------
|
|
473
480
|
// agent sessions(legacy agent-sessions-rest-api.ts;ADR 0010 D3)
|
|
474
481
|
// ---------------------------------------------------------------------------
|
|
475
482
|
function registerAgentSessionRoutes(route, base, ctx) {
|
|
476
483
|
const prefix = `${base}/agent/sessions/`;
|
|
477
|
-
const
|
|
478
|
-
const
|
|
479
|
-
const tree =
|
|
480
|
-
if (!tree)
|
|
484
|
+
const acquireSessionTree = () => {
|
|
485
|
+
const lease = ctx.acquireAgentRuntime?.();
|
|
486
|
+
const tree = lease?.value.sessionTree;
|
|
487
|
+
if (!lease || !tree || typeof tree.resolveActiveSessionId !== 'function') {
|
|
488
|
+
lease?.release();
|
|
481
489
|
return null;
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
return tree;
|
|
490
|
+
}
|
|
491
|
+
return { tree, release: () => lease.release() };
|
|
485
492
|
};
|
|
486
493
|
const unavailable = (response) => {
|
|
487
494
|
writeJson(response, 503, {
|
|
488
495
|
success: false,
|
|
489
|
-
error: 'Agent session tree runtime 未就绪(新 Runtime 需经
|
|
496
|
+
error: 'Agent session tree runtime 未就绪(新 Runtime 需经 acquireAgentRuntime 装配 sessionTree)',
|
|
490
497
|
});
|
|
491
498
|
};
|
|
492
499
|
// HttpHost 无 `:param` 路由,用前缀路由匹配 `<sessionKey>/tree|leaf`。
|
|
@@ -496,30 +503,36 @@ function registerAgentSessionRoutes(route, base, ctx) {
|
|
|
496
503
|
writeJson(response, 404, { success: false, error: 'Not found' });
|
|
497
504
|
return;
|
|
498
505
|
}
|
|
499
|
-
const
|
|
500
|
-
if (!
|
|
506
|
+
const lease = acquireSessionTree();
|
|
507
|
+
if (!lease) {
|
|
501
508
|
unavailable(response);
|
|
502
509
|
return;
|
|
503
510
|
}
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
511
|
+
const runtime = lease.tree;
|
|
512
|
+
try {
|
|
513
|
+
const sessionId = await runtime.resolveActiveSessionId(parsed.sessionKey);
|
|
514
|
+
if (!sessionId) {
|
|
515
|
+
writeJson(response, 404, {
|
|
516
|
+
success: false,
|
|
517
|
+
error: `未找到活跃会话:${parsed.sessionKey}`,
|
|
518
|
+
});
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const session = await runtime.agentSessionStore.getBySessionId(sessionId);
|
|
522
|
+
const points = await runtime.listBranchPoints(sessionId);
|
|
523
|
+
writeJson(response, 200, {
|
|
524
|
+
success: true,
|
|
525
|
+
data: {
|
|
526
|
+
sessionKey: parsed.sessionKey,
|
|
527
|
+
sessionId,
|
|
528
|
+
activeLeafMessageId: session?.active_leaf_message_id ?? null,
|
|
529
|
+
points,
|
|
530
|
+
},
|
|
509
531
|
});
|
|
510
|
-
return;
|
|
511
532
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
success: true,
|
|
516
|
-
data: {
|
|
517
|
-
sessionKey: parsed.sessionKey,
|
|
518
|
-
sessionId,
|
|
519
|
-
activeLeafMessageId: session?.active_leaf_message_id ?? null,
|
|
520
|
-
points,
|
|
521
|
-
},
|
|
522
|
-
});
|
|
533
|
+
finally {
|
|
534
|
+
lease.release();
|
|
535
|
+
}
|
|
523
536
|
}, { summary: 'Get agent session tree', tags: ['console', 'agent'] });
|
|
524
537
|
route('POST', `${base}/agent/sessions/*`, async (request, response, url, authScope) => {
|
|
525
538
|
const parsed = parseSessionAction(url.pathname, prefix, 'leaf');
|
|
@@ -529,19 +542,6 @@ function registerAgentSessionRoutes(route, base, ctx) {
|
|
|
529
542
|
}
|
|
530
543
|
if (!requireWriteScope(response, ctx, authScope))
|
|
531
544
|
return;
|
|
532
|
-
const runtime = getSessionTree();
|
|
533
|
-
if (!runtime) {
|
|
534
|
-
unavailable(response);
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
const sessionId = await runtime.resolveActiveSessionId(parsed.sessionKey);
|
|
538
|
-
if (!sessionId) {
|
|
539
|
-
writeJson(response, 404, {
|
|
540
|
-
success: false,
|
|
541
|
-
error: `未找到活跃会话:${parsed.sessionKey}`,
|
|
542
|
-
});
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
545
|
let body;
|
|
546
546
|
try {
|
|
547
547
|
body = (await readJsonBody(request) ?? {});
|
|
@@ -556,48 +556,71 @@ function registerAgentSessionRoutes(route, base, ctx) {
|
|
|
556
556
|
const messageIdRaw = body.messageId;
|
|
557
557
|
const indexRaw = body.index;
|
|
558
558
|
let messageId;
|
|
559
|
+
let branchIndex;
|
|
559
560
|
if (messageIdRaw != null && messageIdRaw !== '') {
|
|
560
|
-
const
|
|
561
|
-
if (!Number.isFinite(
|
|
561
|
+
const value = Number(messageIdRaw);
|
|
562
|
+
if (!Number.isFinite(value) || value < 1) {
|
|
562
563
|
writeJson(response, 400, { success: false, error: 'messageId 须为正整数' });
|
|
563
564
|
return;
|
|
564
565
|
}
|
|
565
|
-
messageId =
|
|
566
|
+
messageId = value;
|
|
566
567
|
}
|
|
567
568
|
else if (indexRaw != null && indexRaw !== '') {
|
|
568
|
-
const
|
|
569
|
-
if (!Number.isFinite(
|
|
569
|
+
const value = Number(indexRaw);
|
|
570
|
+
if (!Number.isFinite(value) || value < 1) {
|
|
570
571
|
writeJson(response, 400, { success: false, error: 'index 须为正整数' });
|
|
571
572
|
return;
|
|
572
573
|
}
|
|
573
|
-
|
|
574
|
+
branchIndex = value;
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
writeJson(response, 400, { success: false, error: '需要 messageId 或 index 之一' });
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
const lease = acquireSessionTree();
|
|
581
|
+
if (!lease) {
|
|
582
|
+
unavailable(response);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const runtime = lease.tree;
|
|
586
|
+
try {
|
|
587
|
+
const sessionId = await runtime.resolveActiveSessionId(parsed.sessionKey);
|
|
588
|
+
if (!sessionId) {
|
|
589
|
+
writeJson(response, 404, {
|
|
590
|
+
success: false,
|
|
591
|
+
error: `未找到活跃会话:${parsed.sessionKey}`,
|
|
592
|
+
});
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (branchIndex !== undefined) {
|
|
596
|
+
const result = await runtime.jumpToBranchIndex(sessionId, branchIndex);
|
|
597
|
+
const session = await runtime.agentSessionStore.getBySessionId(sessionId);
|
|
598
|
+
writeJson(response, result.ok ? 200 : 400, {
|
|
599
|
+
success: result.ok,
|
|
600
|
+
message: result.message,
|
|
601
|
+
data: {
|
|
602
|
+
sessionKey: parsed.sessionKey,
|
|
603
|
+
sessionId,
|
|
604
|
+
activeLeafMessageId: session?.active_leaf_message_id ?? null,
|
|
605
|
+
},
|
|
606
|
+
});
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const ok = await runtime.switchActiveLeaf(sessionId, messageId);
|
|
574
610
|
const session = await runtime.agentSessionStore.getBySessionId(sessionId);
|
|
575
|
-
writeJson(response,
|
|
576
|
-
success:
|
|
577
|
-
message:
|
|
611
|
+
writeJson(response, ok ? 200 : 400, {
|
|
612
|
+
success: ok,
|
|
613
|
+
message: ok ? `已切换 active leaf 至消息 #${messageId}` : '切换失败',
|
|
578
614
|
data: {
|
|
579
615
|
sessionKey: parsed.sessionKey,
|
|
580
616
|
sessionId,
|
|
581
617
|
activeLeafMessageId: session?.active_leaf_message_id ?? null,
|
|
582
618
|
},
|
|
583
619
|
});
|
|
584
|
-
return;
|
|
585
620
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
return;
|
|
621
|
+
finally {
|
|
622
|
+
lease.release();
|
|
589
623
|
}
|
|
590
|
-
const ok = await runtime.switchActiveLeaf(sessionId, messageId);
|
|
591
|
-
const session = await runtime.agentSessionStore.getBySessionId(sessionId);
|
|
592
|
-
writeJson(response, ok ? 200 : 400, {
|
|
593
|
-
success: ok,
|
|
594
|
-
message: ok ? `已切换 active leaf 至消息 #${messageId}` : '切换失败',
|
|
595
|
-
data: {
|
|
596
|
-
sessionKey: parsed.sessionKey,
|
|
597
|
-
sessionId,
|
|
598
|
-
activeLeafMessageId: session?.active_leaf_message_id ?? null,
|
|
599
|
-
},
|
|
600
|
-
});
|
|
601
624
|
}, { summary: 'Switch agent session active leaf', tags: ['console', 'agent'] });
|
|
602
625
|
}
|
|
603
626
|
function parseSessionAction(pathname, prefix, action) {
|
|
@@ -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.
|
|
15
|
-
* `management` 语义端口;平台 SDK
|
|
14
|
+
* - 社交/群管:`ctx.withEndpointManagement` 在固定 generation lease 内
|
|
15
|
+
* 消费 `management` 语义端口;平台 SDK 字段归一化由 Adapter 负责。
|
|
16
16
|
*/
|
|
17
17
|
export interface ConsoleRpcExtendedCtx {
|
|
18
18
|
/** full scope 才允许写操作;demo scope 只放行只读 RPC。 */
|
|
@@ -20,10 +20,8 @@ export interface ConsoleRpcExtendedCtx {
|
|
|
20
20
|
projectRoot: string;
|
|
21
21
|
/** Plugin Runtime ScheduleHost(basic/cli schedule-host-installer 提供)。 */
|
|
22
22
|
scheduleHost?: unknown;
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
/** @deprecated Use `resolveEndpointManagement`; accepted for one compatibility cycle. */
|
|
26
|
-
resolveEndpoint?: (adapter: string, endpointId: string) => unknown;
|
|
23
|
+
/** 在固定 generation lease 内执行一次 Endpoint 管理操作。 */
|
|
24
|
+
withEndpointManagement?: <T>(adapter: string, endpointKey: string, run: (management: EndpointManagementPort) => T | Promise<T>) => Promise<T | null>;
|
|
27
25
|
/** Plugin Runtime DatabaseHost 的 models 视图。 */
|
|
28
26
|
databaseHost?: {
|
|
29
27
|
models: {
|
|
@@ -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.
|
|
15
|
-
* `management` 语义端口;平台 SDK
|
|
14
|
+
* - 社交/群管:`ctx.withEndpointManagement` 在固定 generation lease 内
|
|
15
|
+
* 消费 `management` 语义端口;平台 SDK 字段归一化由 Adapter 负责。
|
|
16
16
|
*/
|
|
17
17
|
/** 新 Runtime 统一收件箱表名(SSOT:@zhin.js/plugin-runtime inbox.js)。 */
|
|
18
18
|
import { assertDemoConsoleRpcAllowed, normalizeConsoleRpcType, } from '@zhin.js/console-protocol';
|
|
@@ -225,14 +225,14 @@ async function readInboxRows(ctx, table, where, options) {
|
|
|
225
225
|
}
|
|
226
226
|
async function listInbox(d, ctx, table, key, mapRow) {
|
|
227
227
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
228
|
-
const
|
|
229
|
-
if (!adapter || !
|
|
228
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
229
|
+
if (!adapter || !endpointKey)
|
|
230
230
|
return { error: '$adapter and $endpoint required' };
|
|
231
231
|
const limit = Math.min(numField(d, 30, '$limit', 'limit'), 100);
|
|
232
232
|
const offset = Math.max(0, numField(d, 0, '$offset', 'offset'));
|
|
233
233
|
const { rows, enabled } = await readInboxRows(ctx, table, {
|
|
234
234
|
adapter,
|
|
235
|
-
endpoint_id:
|
|
235
|
+
endpoint_id: endpointKey,
|
|
236
236
|
}, {
|
|
237
237
|
// sort/limit 下推到 DB 侧(内存 sort/slice 保留,作为无下推能力模型的兜底)
|
|
238
238
|
orderBy: { field: 'created_at', direction: 'DESC' },
|
|
@@ -248,12 +248,12 @@ async function listInbox(d, ctx, table, key, mapRow) {
|
|
|
248
248
|
/** request.list —— 未处理的好友/群请求(unified_inbox_request 中 resolved=0)。 */
|
|
249
249
|
async function listPendingRequests(d, ctx) {
|
|
250
250
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
251
|
-
const
|
|
252
|
-
if (!adapter || !
|
|
251
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
252
|
+
if (!adapter || !endpointKey)
|
|
253
253
|
return { error: '$adapter and $endpoint required' };
|
|
254
254
|
const { rows, enabled } = await readInboxRows(ctx, TABLE_REQUEST, {
|
|
255
255
|
adapter,
|
|
256
|
-
endpoint_id:
|
|
256
|
+
endpoint_id: endpointKey,
|
|
257
257
|
}, {
|
|
258
258
|
orderBy: { field: 'created_at', direction: 'ASC' },
|
|
259
259
|
});
|
|
@@ -265,10 +265,10 @@ async function listPendingRequests(d, ctx) {
|
|
|
265
265
|
}
|
|
266
266
|
async function listInboxMessages(d, ctx) {
|
|
267
267
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
268
|
-
const
|
|
268
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
269
269
|
const channelId = strField(d, '$channel_id', 'channelId', 'channel_id');
|
|
270
270
|
const channelType = strField(d, '$channel_type', 'channelType', 'channel_type');
|
|
271
|
-
if (!adapter || !
|
|
271
|
+
if (!adapter || !endpointKey || !channelId || !channelType) {
|
|
272
272
|
return { error: '$adapter, $endpoint, $channel_id, $channel_type required' };
|
|
273
273
|
}
|
|
274
274
|
const limit = Math.min(numField(d, 50, '$limit', 'limit'), 100);
|
|
@@ -277,7 +277,7 @@ async function listInboxMessages(d, ctx) {
|
|
|
277
277
|
const parent = normalizeParent(d.$parent ?? d.parent);
|
|
278
278
|
const where = {
|
|
279
279
|
adapter,
|
|
280
|
-
endpoint_id:
|
|
280
|
+
endpoint_id: endpointKey,
|
|
281
281
|
channel_id: channelId,
|
|
282
282
|
channel_type: channelType,
|
|
283
283
|
};
|
|
@@ -393,126 +393,96 @@ async function markInboxConsumed(d, ctx, table) {
|
|
|
393
393
|
// ---------------------------------------------------------------- 请求审批
|
|
394
394
|
async function actOnRequest(type, d, ctx) {
|
|
395
395
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
396
|
-
const
|
|
396
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
397
397
|
const requestId = strField(d, '$id', 'id', 'platformRequestId', 'platform_request_id', 'requestId');
|
|
398
|
-
if (!adapter || !
|
|
398
|
+
if (!adapter || !endpointKey || !requestId) {
|
|
399
399
|
return { error: '$adapter, $endpoint, $id required' };
|
|
400
400
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
'且新 Runtime 尚未挂载 pending request 注册表',
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
try {
|
|
401
|
+
return withLiveEndpoint(ctx, adapter, endpointKey, async (management) => {
|
|
402
|
+
const approve = type === 'request.approve';
|
|
403
|
+
const method = approve ? management.approveRequest : management.rejectRequest;
|
|
404
|
+
if (!method) {
|
|
405
|
+
return {
|
|
406
|
+
error: `请求审批未接线:当前平台(${adapter})endpoint 不支持 ${approve ? 'approveRequest' : 'rejectRequest'},` +
|
|
407
|
+
'且新 Runtime 尚未挂载 pending request 注册表',
|
|
408
|
+
};
|
|
409
|
+
}
|
|
414
410
|
const extra = approve
|
|
415
411
|
? strField(d, '$remark', 'remark')
|
|
416
412
|
: strField(d, '$reason', 'reason');
|
|
417
413
|
await method.call(management, requestId, extra || undefined);
|
|
418
414
|
return { data: { success: true } };
|
|
419
|
-
}
|
|
420
|
-
catch (error) {
|
|
421
|
-
return { error: errorMessage(error) };
|
|
422
|
-
}
|
|
415
|
+
});
|
|
423
416
|
}
|
|
424
417
|
// ---------------------------------------------------------------- 社交读取
|
|
425
418
|
async function listFriends(d, ctx) {
|
|
426
|
-
const
|
|
427
|
-
if ('error' in
|
|
428
|
-
return
|
|
429
|
-
|
|
430
|
-
try {
|
|
419
|
+
const endpoint = requireEndpointAddress(d);
|
|
420
|
+
if ('error' in endpoint)
|
|
421
|
+
return endpoint;
|
|
422
|
+
return withLiveEndpoint(ctx, endpoint.adapter, endpoint.endpointKey, async (management) => {
|
|
431
423
|
if (management.listFriends) {
|
|
432
424
|
const friends = [...await management.listFriends()];
|
|
433
425
|
return { data: { friends, count: friends.length } };
|
|
434
426
|
}
|
|
435
|
-
return { error: `当前适配器(${adapter})不支持好友列表` };
|
|
436
|
-
}
|
|
437
|
-
catch (error) {
|
|
438
|
-
return { error: errorMessage(error) };
|
|
439
|
-
}
|
|
427
|
+
return { error: `当前适配器(${endpoint.adapter})不支持好友列表` };
|
|
428
|
+
});
|
|
440
429
|
}
|
|
441
430
|
async function listGroups(d, ctx) {
|
|
442
|
-
const
|
|
443
|
-
if ('error' in
|
|
444
|
-
return
|
|
445
|
-
|
|
446
|
-
try {
|
|
431
|
+
const endpoint = requireEndpointAddress(d);
|
|
432
|
+
if ('error' in endpoint)
|
|
433
|
+
return endpoint;
|
|
434
|
+
return withLiveEndpoint(ctx, endpoint.adapter, endpoint.endpointKey, async (management) => {
|
|
447
435
|
if (management.listGroups) {
|
|
448
436
|
const groups = [...await management.listGroups()];
|
|
449
437
|
return { data: { groups, count: groups.length } };
|
|
450
438
|
}
|
|
451
|
-
return { error: `当前适配器(${adapter})不支持群列表` };
|
|
452
|
-
}
|
|
453
|
-
catch (error) {
|
|
454
|
-
return { error: errorMessage(error) };
|
|
455
|
-
}
|
|
439
|
+
return { error: `当前适配器(${endpoint.adapter})不支持群列表` };
|
|
440
|
+
});
|
|
456
441
|
}
|
|
457
442
|
async function listChannels(d, ctx) {
|
|
458
|
-
const
|
|
459
|
-
if ('error' in
|
|
460
|
-
return
|
|
461
|
-
|
|
462
|
-
try {
|
|
443
|
+
const endpoint = requireEndpointAddress(d);
|
|
444
|
+
if ('error' in endpoint)
|
|
445
|
+
return endpoint;
|
|
446
|
+
return withLiveEndpoint(ctx, endpoint.adapter, endpoint.endpointKey, async (management) => {
|
|
463
447
|
if (management.listChannels) {
|
|
464
448
|
const channels = [...await management.listChannels()];
|
|
465
449
|
return { data: { channels, count: channels.length } };
|
|
466
450
|
}
|
|
467
|
-
return { error: `当前适配器(${adapter})不支持频道列表` };
|
|
468
|
-
}
|
|
469
|
-
catch (error) {
|
|
470
|
-
return { error: errorMessage(error) };
|
|
471
|
-
}
|
|
451
|
+
return { error: `当前适配器(${endpoint.adapter})不支持频道列表` };
|
|
452
|
+
});
|
|
472
453
|
}
|
|
473
454
|
async function listGroupMembers(d, ctx) {
|
|
474
455
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
475
|
-
const
|
|
456
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
476
457
|
const groupId = strField(d, '$group_id', 'groupId', 'group_id');
|
|
477
|
-
if (!adapter || !
|
|
458
|
+
if (!adapter || !endpointKey || !groupId) {
|
|
478
459
|
return { error: '$adapter, $endpoint, $group_id required' };
|
|
479
460
|
}
|
|
480
|
-
|
|
481
|
-
if ('error' in resolved)
|
|
482
|
-
return resolved;
|
|
483
|
-
const { management } = resolved;
|
|
484
|
-
try {
|
|
461
|
+
return withLiveEndpoint(ctx, adapter, endpointKey, async (management) => {
|
|
485
462
|
const method = management.listGroupMembers;
|
|
486
463
|
if (!method) {
|
|
487
464
|
return { error: `当前适配器(${adapter})不支持群成员列表` };
|
|
488
465
|
}
|
|
489
466
|
const members = [...await method(groupId)];
|
|
490
467
|
return { data: { members, count: members.length } };
|
|
491
|
-
}
|
|
492
|
-
catch (error) {
|
|
493
|
-
return { error: errorMessage(error) };
|
|
494
|
-
}
|
|
468
|
+
});
|
|
495
469
|
}
|
|
496
470
|
async function groupWriteOp(d, ctx, spec) {
|
|
497
471
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
498
|
-
const
|
|
472
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
499
473
|
const groupId = strField(d, '$group_id', 'groupId', 'group_id');
|
|
500
474
|
const userId = strField(d, '$user_id', 'userId', 'user_id');
|
|
501
|
-
if (!adapter || !
|
|
475
|
+
if (!adapter || !endpointKey || !groupId) {
|
|
502
476
|
return { error: '$adapter, $endpoint, $group_id required' };
|
|
503
477
|
}
|
|
504
478
|
if (spec.requireUser && !userId) {
|
|
505
479
|
return { error: '$user_id required' };
|
|
506
480
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
if (!method) {
|
|
513
|
-
return { error: `当前适配器(${adapter})不支持${spec.unsupported}` };
|
|
514
|
-
}
|
|
515
|
-
try {
|
|
481
|
+
return withLiveEndpoint(ctx, adapter, endpointKey, async (management) => {
|
|
482
|
+
const method = management[spec.method];
|
|
483
|
+
if (!method) {
|
|
484
|
+
return { error: `当前适配器(${adapter})不支持${spec.unsupported}` };
|
|
485
|
+
}
|
|
516
486
|
const durationRaw = d.$duration ?? d.duration;
|
|
517
487
|
const enableRaw = d.$enable ?? d.enable;
|
|
518
488
|
const args = spec.buildArgs(groupId, userId, {
|
|
@@ -523,33 +493,23 @@ async function groupWriteOp(d, ctx, spec) {
|
|
|
523
493
|
});
|
|
524
494
|
await method.call(management, ...args);
|
|
525
495
|
return { data: { success: true } };
|
|
526
|
-
}
|
|
527
|
-
catch (error) {
|
|
528
|
-
return { error: errorMessage(error) };
|
|
529
|
-
}
|
|
496
|
+
});
|
|
530
497
|
}
|
|
531
498
|
async function deleteFriend(d, ctx) {
|
|
532
499
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
533
|
-
const
|
|
500
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
534
501
|
const userId = strField(d, '$user_id', 'userId', 'user_id');
|
|
535
|
-
if (!adapter || !
|
|
502
|
+
if (!adapter || !endpointKey || !userId) {
|
|
536
503
|
return { error: '$adapter, $endpoint, $user_id required' };
|
|
537
504
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
if (!method) {
|
|
544
|
-
return { error: '当前适配器暂不支持删除好友' };
|
|
545
|
-
}
|
|
546
|
-
try {
|
|
505
|
+
return withLiveEndpoint(ctx, adapter, endpointKey, async (management) => {
|
|
506
|
+
const method = management.deleteFriend;
|
|
507
|
+
if (!method) {
|
|
508
|
+
return { error: '当前适配器暂不支持删除好友' };
|
|
509
|
+
}
|
|
547
510
|
await method(userId);
|
|
548
511
|
return { data: { success: true } };
|
|
549
|
-
}
|
|
550
|
-
catch (error) {
|
|
551
|
-
return { error: errorMessage(error) };
|
|
552
|
-
}
|
|
512
|
+
});
|
|
553
513
|
}
|
|
554
514
|
// ---------------------------------------------------------------- helpers
|
|
555
515
|
function strField(d, ...keys) {
|
|
@@ -592,35 +552,24 @@ function numArrayField(d, ...keys) {
|
|
|
592
552
|
}
|
|
593
553
|
return [];
|
|
594
554
|
}
|
|
595
|
-
function
|
|
596
|
-
if (ctx.
|
|
597
|
-
const management = ctx.resolveEndpointManagement(adapter, endpointId);
|
|
598
|
-
if (!management)
|
|
599
|
-
return { error: 'endpoint not found' };
|
|
600
|
-
return { management, adapter, endpointId };
|
|
601
|
-
}
|
|
602
|
-
if (!ctx.resolveEndpoint) {
|
|
555
|
+
async function withLiveEndpoint(ctx, adapter, endpointKey, run) {
|
|
556
|
+
if (!ctx.withEndpointManagement) {
|
|
603
557
|
return { error: 'Endpoint registry is not configured' };
|
|
604
558
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
559
|
+
try {
|
|
560
|
+
return await ctx.withEndpointManagement(adapter, endpointKey, run)
|
|
561
|
+
?? { error: 'endpoint not found' };
|
|
562
|
+
}
|
|
563
|
+
catch (error) {
|
|
564
|
+
return { error: errorMessage(error) };
|
|
608
565
|
}
|
|
609
|
-
const management = endpoint.management;
|
|
610
|
-
return {
|
|
611
|
-
management: management && typeof management === 'object'
|
|
612
|
-
? management
|
|
613
|
-
: {},
|
|
614
|
-
adapter,
|
|
615
|
-
endpointId,
|
|
616
|
-
};
|
|
617
566
|
}
|
|
618
|
-
function
|
|
567
|
+
function requireEndpointAddress(d) {
|
|
619
568
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
620
|
-
const
|
|
621
|
-
if (!adapter || !
|
|
569
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
570
|
+
if (!adapter || !endpointKey)
|
|
622
571
|
return { error: '$adapter and $endpoint required' };
|
|
623
|
-
return
|
|
572
|
+
return { adapter, endpointKey };
|
|
624
573
|
}
|
|
625
574
|
function normalizeParent(raw) {
|
|
626
575
|
if (!raw || typeof raw !== 'object')
|
package/lib/console-rpc.d.ts
CHANGED
|
@@ -68,7 +68,7 @@ export type RuntimeConsoleRpcContext = {
|
|
|
68
68
|
getAllSchemas?(): Promise<Record<string, unknown>>;
|
|
69
69
|
/** Optional Adapter endpoint Console surface (Sandbox / Remote Console). */
|
|
70
70
|
listEndpoints?(): Promise<readonly RuntimeEndpointSummary[]>;
|
|
71
|
-
getEndpoint?(adapter: string,
|
|
71
|
+
getEndpoint?(adapter: string, endpointKey: string): Promise<RuntimeEndpointSummary | null>;
|
|
72
72
|
sendEndpointMessage?(input: RuntimeEndpointSendInput): Promise<{
|
|
73
73
|
messageId: string;
|
|
74
74
|
}>;
|
|
@@ -96,7 +96,7 @@ export type RuntimeDatabaseInfo = {
|
|
|
96
96
|
};
|
|
97
97
|
export type RuntimeEndpointSendInput = {
|
|
98
98
|
readonly adapter: string;
|
|
99
|
-
readonly
|
|
99
|
+
readonly endpointKey: string;
|
|
100
100
|
readonly conversation: ConversationAddress;
|
|
101
101
|
readonly content: unknown;
|
|
102
102
|
};
|