@zhin.js/host-http 1.0.6 → 1.0.8
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/lib/console-rest-pages.d.ts +5 -2
- package/lib/console-rest-pages.js +90 -67
- package/lib/console-rpc-extended.d.ts +2 -2
- package/lib/console-rpc-extended.js +29 -29
- package/lib/console-rpc.d.ts +2 -2
- package/lib/console-rpc.js +6 -6
- package/lib/http-host.js +7 -9
- package/package.json +5 -5
- package/src/console-rest-pages.ts +98 -73
- package/src/console-rpc-extended.ts +32 -32
- package/src/console-rpc.ts +8 -8
- package/src/http-host.ts +11 -9
|
@@ -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) {
|
|
@@ -21,9 +21,9 @@ export interface ConsoleRpcExtendedCtx {
|
|
|
21
21
|
/** Plugin Runtime ScheduleHost(basic/cli schedule-host-installer 提供)。 */
|
|
22
22
|
scheduleHost?: unknown;
|
|
23
23
|
/** 经 ImRuntime / AdapterEndpointIndex 解析管理语义端口。 */
|
|
24
|
-
resolveEndpointManagement?: (adapter: string,
|
|
24
|
+
resolveEndpointManagement?: (adapter: string, endpointKey: string) => EndpointManagementPort | null | undefined;
|
|
25
25
|
/** @deprecated Use `resolveEndpointManagement`; accepted for one compatibility cycle. */
|
|
26
|
-
resolveEndpoint?: (adapter: string,
|
|
26
|
+
resolveEndpoint?: (adapter: string, endpointKey: string) => unknown;
|
|
27
27
|
/** Plugin Runtime DatabaseHost 的 models 视图。 */
|
|
28
28
|
databaseHost?: {
|
|
29
29
|
models: {
|
|
@@ -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,12 +393,12 @@ 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
|
-
const resolved = resolveLiveEndpoint(ctx, adapter,
|
|
401
|
+
const resolved = resolveLiveEndpoint(ctx, adapter, endpointKey);
|
|
402
402
|
if ('error' in resolved)
|
|
403
403
|
return resolved;
|
|
404
404
|
const management = resolved.management;
|
|
@@ -472,12 +472,12 @@ async function listChannels(d, ctx) {
|
|
|
472
472
|
}
|
|
473
473
|
async function listGroupMembers(d, ctx) {
|
|
474
474
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
475
|
-
const
|
|
475
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
476
476
|
const groupId = strField(d, '$group_id', 'groupId', 'group_id');
|
|
477
|
-
if (!adapter || !
|
|
477
|
+
if (!adapter || !endpointKey || !groupId) {
|
|
478
478
|
return { error: '$adapter, $endpoint, $group_id required' };
|
|
479
479
|
}
|
|
480
|
-
const resolved = resolveLiveEndpoint(ctx, adapter,
|
|
480
|
+
const resolved = resolveLiveEndpoint(ctx, adapter, endpointKey);
|
|
481
481
|
if ('error' in resolved)
|
|
482
482
|
return resolved;
|
|
483
483
|
const { management } = resolved;
|
|
@@ -495,16 +495,16 @@ async function listGroupMembers(d, ctx) {
|
|
|
495
495
|
}
|
|
496
496
|
async function groupWriteOp(d, ctx, spec) {
|
|
497
497
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
498
|
-
const
|
|
498
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
499
499
|
const groupId = strField(d, '$group_id', 'groupId', 'group_id');
|
|
500
500
|
const userId = strField(d, '$user_id', 'userId', 'user_id');
|
|
501
|
-
if (!adapter || !
|
|
501
|
+
if (!adapter || !endpointKey || !groupId) {
|
|
502
502
|
return { error: '$adapter, $endpoint, $group_id required' };
|
|
503
503
|
}
|
|
504
504
|
if (spec.requireUser && !userId) {
|
|
505
505
|
return { error: '$user_id required' };
|
|
506
506
|
}
|
|
507
|
-
const resolved = resolveLiveEndpoint(ctx, adapter,
|
|
507
|
+
const resolved = resolveLiveEndpoint(ctx, adapter, endpointKey);
|
|
508
508
|
if ('error' in resolved)
|
|
509
509
|
return resolved;
|
|
510
510
|
const { management } = resolved;
|
|
@@ -530,12 +530,12 @@ async function groupWriteOp(d, ctx, spec) {
|
|
|
530
530
|
}
|
|
531
531
|
async function deleteFriend(d, ctx) {
|
|
532
532
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
533
|
-
const
|
|
533
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
534
534
|
const userId = strField(d, '$user_id', 'userId', 'user_id');
|
|
535
|
-
if (!adapter || !
|
|
535
|
+
if (!adapter || !endpointKey || !userId) {
|
|
536
536
|
return { error: '$adapter, $endpoint, $user_id required' };
|
|
537
537
|
}
|
|
538
|
-
const resolved = resolveLiveEndpoint(ctx, adapter,
|
|
538
|
+
const resolved = resolveLiveEndpoint(ctx, adapter, endpointKey);
|
|
539
539
|
if ('error' in resolved)
|
|
540
540
|
return resolved;
|
|
541
541
|
const { management } = resolved;
|
|
@@ -592,17 +592,17 @@ function numArrayField(d, ...keys) {
|
|
|
592
592
|
}
|
|
593
593
|
return [];
|
|
594
594
|
}
|
|
595
|
-
function resolveLiveEndpoint(ctx, adapter,
|
|
595
|
+
function resolveLiveEndpoint(ctx, adapter, endpointKey) {
|
|
596
596
|
if (ctx.resolveEndpointManagement) {
|
|
597
|
-
const management = ctx.resolveEndpointManagement(adapter,
|
|
597
|
+
const management = ctx.resolveEndpointManagement(adapter, endpointKey);
|
|
598
598
|
if (!management)
|
|
599
599
|
return { error: 'endpoint not found' };
|
|
600
|
-
return { management, adapter,
|
|
600
|
+
return { management, adapter, endpointKey };
|
|
601
601
|
}
|
|
602
602
|
if (!ctx.resolveEndpoint) {
|
|
603
603
|
return { error: 'Endpoint registry is not configured' };
|
|
604
604
|
}
|
|
605
|
-
const endpoint = ctx.resolveEndpoint(adapter,
|
|
605
|
+
const endpoint = ctx.resolveEndpoint(adapter, endpointKey);
|
|
606
606
|
if (!endpoint || typeof endpoint !== 'object') {
|
|
607
607
|
return { error: 'endpoint not found' };
|
|
608
608
|
}
|
|
@@ -612,15 +612,15 @@ function resolveLiveEndpoint(ctx, adapter, endpointId) {
|
|
|
612
612
|
? management
|
|
613
613
|
: {},
|
|
614
614
|
adapter,
|
|
615
|
-
|
|
615
|
+
endpointKey,
|
|
616
616
|
};
|
|
617
617
|
}
|
|
618
618
|
function requireEndpoint(d, ctx) {
|
|
619
619
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
620
|
-
const
|
|
621
|
-
if (!adapter || !
|
|
620
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
621
|
+
if (!adapter || !endpointKey)
|
|
622
622
|
return { error: '$adapter and $endpoint required' };
|
|
623
|
-
return resolveLiveEndpoint(ctx, adapter,
|
|
623
|
+
return resolveLiveEndpoint(ctx, adapter, endpointKey);
|
|
624
624
|
}
|
|
625
625
|
function normalizeParent(raw) {
|
|
626
626
|
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
|
};
|
package/lib/console-rpc.js
CHANGED
|
@@ -542,8 +542,8 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
542
542
|
try {
|
|
543
543
|
const data = message;
|
|
544
544
|
const adapter = String(data.$adapter ?? '');
|
|
545
|
-
const
|
|
546
|
-
if (!adapter || !
|
|
545
|
+
const endpointKey = String(data.$endpoint ?? '');
|
|
546
|
+
if (!adapter || !endpointKey) {
|
|
547
547
|
emit({ requestId, error: '$adapter and $endpoint required' });
|
|
548
548
|
return payloads;
|
|
549
549
|
}
|
|
@@ -551,7 +551,7 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
551
551
|
emit({ requestId, error: 'Endpoint registry is not configured' });
|
|
552
552
|
return payloads;
|
|
553
553
|
}
|
|
554
|
-
const endpoint = await ctx.getEndpoint(adapter,
|
|
554
|
+
const endpoint = await ctx.getEndpoint(adapter, endpointKey);
|
|
555
555
|
if (!endpoint) {
|
|
556
556
|
emit({ requestId, error: 'endpoint not found' });
|
|
557
557
|
return payloads;
|
|
@@ -570,12 +570,12 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
570
570
|
try {
|
|
571
571
|
const data = message;
|
|
572
572
|
const adapter = String(data.$adapter ?? '');
|
|
573
|
-
const
|
|
573
|
+
const endpointKey = String(data.$endpoint ?? '');
|
|
574
574
|
const channelId = String(data.$channel_id ?? '');
|
|
575
575
|
const channelType = String(data.$channel_type ?? '');
|
|
576
576
|
const content = data.$content;
|
|
577
577
|
// channelType 可省:wireConversation 归一为 private(与旧 RPC 行为对齐)。
|
|
578
|
-
if (!adapter || !
|
|
578
|
+
if (!adapter || !endpointKey || !channelId || content === undefined) {
|
|
579
579
|
emit({
|
|
580
580
|
requestId,
|
|
581
581
|
error: '$adapter, $endpoint, $channel_id, $content required',
|
|
@@ -588,7 +588,7 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
588
588
|
}
|
|
589
589
|
const result = await ctx.sendEndpointMessage({
|
|
590
590
|
adapter,
|
|
591
|
-
|
|
591
|
+
endpointKey,
|
|
592
592
|
conversation: wireConversation(channelType, channelId, data.$parent),
|
|
593
593
|
content,
|
|
594
594
|
});
|
package/lib/http-host.js
CHANGED
|
@@ -6,7 +6,7 @@ import { TokenRegistry, extractBearerToken, isDemoWebSocketPath, } from './token
|
|
|
6
6
|
import { buildOpenApiDocument, } from './openapi.js';
|
|
7
7
|
import { isDemoHttpAllowed } from './console-rpc.js';
|
|
8
8
|
import { HttpBodyError } from './json-body.js';
|
|
9
|
-
const logger = getLogger('
|
|
9
|
+
const logger = getLogger('http');
|
|
10
10
|
export const REMOTE_CONSOLE_ORIGIN = 'https://console.zhin.dev';
|
|
11
11
|
const DEFAULT_CORS_ORIGINS = Object.freeze([REMOTE_CONSOLE_ORIGIN]);
|
|
12
12
|
export const httpHostToken = createToken('zhin.host.http');
|
|
@@ -290,15 +290,13 @@ export function createHttpHost(options = {}) {
|
|
|
290
290
|
const listenPort = typeof bound === 'object' && bound ? bound.port : port;
|
|
291
291
|
const publicHost = host === '0.0.0.0' || host === '::' ? '127.0.0.1' : host;
|
|
292
292
|
address = Object.freeze({ host: publicHost, port: listenPort });
|
|
293
|
-
logger.info(
|
|
294
|
-
|
|
295
|
-
token: tokenRegistry.hasAnyToken()
|
|
293
|
+
logger.info(`listening http://${publicHost}:${listenPort}`
|
|
294
|
+
+ ` | routes: ${httpRoutes.length}`
|
|
295
|
+
+ ` | token: ${tokenRegistry.hasAnyToken()
|
|
296
296
|
? `${tokenRegistry.primaryTokenPrefixForLog()}…`
|
|
297
|
-
: '(none)'
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
http_routes: httpRoutes.length,
|
|
301
|
-
}));
|
|
297
|
+
: '(none)'}`);
|
|
298
|
+
logger.debug(`listen detail | cors: ${corsOrigins.join(',')}`
|
|
299
|
+
+ ` | ws: ${[...wsRoutes.keys()].join(',') || '(none)'}`);
|
|
302
300
|
resolve(address);
|
|
303
301
|
});
|
|
304
302
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/host-http",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Minimal HTTP and WebSocket host for Plugin Runtime (no Koa)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"ws": "^8.21.1",
|
|
21
|
-
"@zhin.js/console-protocol": "1.1.
|
|
22
|
-
"@zhin.js/host-http-contract": "1.0.
|
|
23
|
-
"@zhin.js/logger": "1.0.
|
|
24
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
21
|
+
"@zhin.js/console-protocol": "1.1.1",
|
|
22
|
+
"@zhin.js/host-http-contract": "1.0.1",
|
|
23
|
+
"@zhin.js/logger": "1.0.76",
|
|
24
|
+
"@zhin.js/plugin-runtime": "1.1.5"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^26.1.2",
|
|
@@ -32,7 +32,10 @@ export interface ConsoleRestCtx {
|
|
|
32
32
|
* 可选:返回新 Runtime 的 agent 门面(见 {@link ConsoleAgentRuntime})。
|
|
33
33
|
* 未接线时 introspection 降级为空列表 + note,session tree 返回 503。
|
|
34
34
|
*/
|
|
35
|
-
readonly
|
|
35
|
+
readonly acquireAgentRuntime?: () => {
|
|
36
|
+
readonly value: ConsoleAgentRuntime;
|
|
37
|
+
release(): void;
|
|
38
|
+
} | null;
|
|
36
39
|
/** Database host(logs 页数据源:SystemLog 模型)。 */
|
|
37
40
|
readonly databaseHost?: {
|
|
38
41
|
readonly dialect: string;
|
|
@@ -65,7 +68,7 @@ export interface ConsoleAgentSessionTree {
|
|
|
65
68
|
): Promise<{ ok: boolean; message: string }>;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
/** `ctx.
|
|
71
|
+
/** `ctx.acquireAgentRuntime()` 返回值的最小结构约定(全部可选,逐项降级)。 */
|
|
69
72
|
export interface ConsoleAgentRuntime {
|
|
70
73
|
readonly introspection?: ConsoleAgentIntrospection;
|
|
71
74
|
readonly sessionTree?: ConsoleAgentSessionTree;
|
|
@@ -626,7 +629,7 @@ function registerIntrospectionRoutes(
|
|
|
626
629
|
const degradedNote = note ?? (missing
|
|
627
630
|
? kind === 'endpoints'
|
|
628
631
|
? 'Endpoints 数据源未接线(ctx.getEndpoints 缺失)'
|
|
629
|
-
: 'Agent runtime 未装配(basic/cli 未接线
|
|
632
|
+
: 'Agent runtime 未装配(basic/cli 未接线 acquireAgentRuntime)'
|
|
630
633
|
: undefined);
|
|
631
634
|
writeJson(response, 200, {
|
|
632
635
|
success: true,
|
|
@@ -644,8 +647,13 @@ function registerIntrospectionRoutes(
|
|
|
644
647
|
}
|
|
645
648
|
|
|
646
649
|
function agentIntrospection(ctx: ConsoleRestCtx): ConsoleAgentIntrospection | undefined {
|
|
647
|
-
const
|
|
648
|
-
|
|
650
|
+
const lease = ctx.acquireAgentRuntime?.();
|
|
651
|
+
if (!lease) return undefined;
|
|
652
|
+
try {
|
|
653
|
+
return lease.value.introspection;
|
|
654
|
+
} finally {
|
|
655
|
+
lease.release();
|
|
656
|
+
}
|
|
649
657
|
}
|
|
650
658
|
|
|
651
659
|
// ---------------------------------------------------------------------------
|
|
@@ -658,17 +666,22 @@ function registerAgentSessionRoutes(
|
|
|
658
666
|
ctx: ConsoleRestCtx,
|
|
659
667
|
): void {
|
|
660
668
|
const prefix = `${base}/agent/sessions/`;
|
|
661
|
-
const
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
669
|
+
const acquireSessionTree = (): {
|
|
670
|
+
readonly tree: ConsoleAgentSessionTree;
|
|
671
|
+
release(): void;
|
|
672
|
+
} | null => {
|
|
673
|
+
const lease = ctx.acquireAgentRuntime?.();
|
|
674
|
+
const tree = lease?.value.sessionTree;
|
|
675
|
+
if (!lease || !tree || typeof tree.resolveActiveSessionId !== 'function') {
|
|
676
|
+
lease?.release();
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
return { tree, release: () => lease.release() };
|
|
667
680
|
};
|
|
668
681
|
const unavailable = (response: ServerResponse) => {
|
|
669
682
|
writeJson(response, 503, {
|
|
670
683
|
success: false,
|
|
671
|
-
error: 'Agent session tree runtime 未就绪(新 Runtime 需经
|
|
684
|
+
error: 'Agent session tree runtime 未就绪(新 Runtime 需经 acquireAgentRuntime 装配 sessionTree)',
|
|
672
685
|
});
|
|
673
686
|
};
|
|
674
687
|
|
|
@@ -679,32 +692,36 @@ function registerAgentSessionRoutes(
|
|
|
679
692
|
writeJson(response, 404, { success: false, error: 'Not found' });
|
|
680
693
|
return;
|
|
681
694
|
}
|
|
682
|
-
const
|
|
683
|
-
if (!
|
|
695
|
+
const lease = acquireSessionTree();
|
|
696
|
+
if (!lease) {
|
|
684
697
|
unavailable(response);
|
|
685
698
|
return;
|
|
686
699
|
}
|
|
700
|
+
const runtime = lease.tree;
|
|
701
|
+
try {
|
|
702
|
+
const sessionId = await runtime.resolveActiveSessionId(parsed.sessionKey);
|
|
703
|
+
if (!sessionId) {
|
|
704
|
+
writeJson(response, 404, {
|
|
705
|
+
success: false,
|
|
706
|
+
error: `未找到活跃会话:${parsed.sessionKey}`,
|
|
707
|
+
});
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
687
710
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
writeJson(response,
|
|
691
|
-
success:
|
|
692
|
-
|
|
711
|
+
const session = await runtime.agentSessionStore.getBySessionId(sessionId);
|
|
712
|
+
const points = await runtime.listBranchPoints(sessionId);
|
|
713
|
+
writeJson(response, 200, {
|
|
714
|
+
success: true,
|
|
715
|
+
data: {
|
|
716
|
+
sessionKey: parsed.sessionKey,
|
|
717
|
+
sessionId,
|
|
718
|
+
activeLeafMessageId: session?.active_leaf_message_id ?? null,
|
|
719
|
+
points,
|
|
720
|
+
},
|
|
693
721
|
});
|
|
694
|
-
|
|
722
|
+
} finally {
|
|
723
|
+
lease.release();
|
|
695
724
|
}
|
|
696
|
-
|
|
697
|
-
const session = await runtime.agentSessionStore.getBySessionId(sessionId);
|
|
698
|
-
const points = await runtime.listBranchPoints(sessionId);
|
|
699
|
-
writeJson(response, 200, {
|
|
700
|
-
success: true,
|
|
701
|
-
data: {
|
|
702
|
-
sessionKey: parsed.sessionKey,
|
|
703
|
-
sessionId,
|
|
704
|
-
activeLeafMessageId: session?.active_leaf_message_id ?? null,
|
|
705
|
-
points,
|
|
706
|
-
},
|
|
707
|
-
});
|
|
708
725
|
}, { summary: 'Get agent session tree', tags: ['console', 'agent'] });
|
|
709
726
|
|
|
710
727
|
route('POST', `${base}/agent/sessions/*`, async (request, response, url, authScope) => {
|
|
@@ -714,21 +731,6 @@ function registerAgentSessionRoutes(
|
|
|
714
731
|
return;
|
|
715
732
|
}
|
|
716
733
|
if (!requireWriteScope(response, ctx, authScope)) return;
|
|
717
|
-
const runtime = getSessionTree();
|
|
718
|
-
if (!runtime) {
|
|
719
|
-
unavailable(response);
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
const sessionId = await runtime.resolveActiveSessionId(parsed.sessionKey);
|
|
724
|
-
if (!sessionId) {
|
|
725
|
-
writeJson(response, 404, {
|
|
726
|
-
success: false,
|
|
727
|
-
error: `未找到活跃会话:${parsed.sessionKey}`,
|
|
728
|
-
});
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
734
|
let body: Record<string, unknown>;
|
|
733
735
|
try {
|
|
734
736
|
body = (await readJsonBody(request) ?? {}) as Record<string, unknown>;
|
|
@@ -741,49 +743,72 @@ function registerAgentSessionRoutes(
|
|
|
741
743
|
}
|
|
742
744
|
const messageIdRaw = body.messageId;
|
|
743
745
|
const indexRaw = body.index;
|
|
744
|
-
|
|
745
746
|
let messageId: number | undefined;
|
|
747
|
+
let branchIndex: number | undefined;
|
|
746
748
|
if (messageIdRaw != null && messageIdRaw !== '') {
|
|
747
|
-
const
|
|
748
|
-
if (!Number.isFinite(
|
|
749
|
+
const value = Number(messageIdRaw);
|
|
750
|
+
if (!Number.isFinite(value) || value < 1) {
|
|
749
751
|
writeJson(response, 400, { success: false, error: 'messageId 须为正整数' });
|
|
750
752
|
return;
|
|
751
753
|
}
|
|
752
|
-
messageId =
|
|
754
|
+
messageId = value;
|
|
753
755
|
} else if (indexRaw != null && indexRaw !== '') {
|
|
754
|
-
const
|
|
755
|
-
if (!Number.isFinite(
|
|
756
|
+
const value = Number(indexRaw);
|
|
757
|
+
if (!Number.isFinite(value) || value < 1) {
|
|
756
758
|
writeJson(response, 400, { success: false, error: 'index 须为正整数' });
|
|
757
759
|
return;
|
|
758
760
|
}
|
|
759
|
-
|
|
761
|
+
branchIndex = value;
|
|
762
|
+
} else {
|
|
763
|
+
writeJson(response, 400, { success: false, error: '需要 messageId 或 index 之一' });
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const lease = acquireSessionTree();
|
|
768
|
+
if (!lease) {
|
|
769
|
+
unavailable(response);
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
const runtime = lease.tree;
|
|
773
|
+
try {
|
|
774
|
+
const sessionId = await runtime.resolveActiveSessionId(parsed.sessionKey);
|
|
775
|
+
if (!sessionId) {
|
|
776
|
+
writeJson(response, 404, {
|
|
777
|
+
success: false,
|
|
778
|
+
error: `未找到活跃会话:${parsed.sessionKey}`,
|
|
779
|
+
});
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (branchIndex !== undefined) {
|
|
784
|
+
const result = await runtime.jumpToBranchIndex(sessionId, branchIndex);
|
|
785
|
+
const session = await runtime.agentSessionStore.getBySessionId(sessionId);
|
|
786
|
+
writeJson(response, result.ok ? 200 : 400, {
|
|
787
|
+
success: result.ok,
|
|
788
|
+
message: result.message,
|
|
789
|
+
data: {
|
|
790
|
+
sessionKey: parsed.sessionKey,
|
|
791
|
+
sessionId,
|
|
792
|
+
activeLeafMessageId: session?.active_leaf_message_id ?? null,
|
|
793
|
+
},
|
|
794
|
+
});
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const ok = await runtime.switchActiveLeaf(sessionId, messageId!);
|
|
760
799
|
const session = await runtime.agentSessionStore.getBySessionId(sessionId);
|
|
761
|
-
writeJson(response,
|
|
762
|
-
success:
|
|
763
|
-
message:
|
|
800
|
+
writeJson(response, ok ? 200 : 400, {
|
|
801
|
+
success: ok,
|
|
802
|
+
message: ok ? `已切换 active leaf 至消息 #${messageId}` : '切换失败',
|
|
764
803
|
data: {
|
|
765
804
|
sessionKey: parsed.sessionKey,
|
|
766
805
|
sessionId,
|
|
767
806
|
activeLeafMessageId: session?.active_leaf_message_id ?? null,
|
|
768
807
|
},
|
|
769
808
|
});
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
writeJson(response, 400, { success: false, error: '需要 messageId 或 index 之一' });
|
|
773
|
-
return;
|
|
809
|
+
} finally {
|
|
810
|
+
lease.release();
|
|
774
811
|
}
|
|
775
|
-
|
|
776
|
-
const ok = await runtime.switchActiveLeaf(sessionId, messageId);
|
|
777
|
-
const session = await runtime.agentSessionStore.getBySessionId(sessionId);
|
|
778
|
-
writeJson(response, ok ? 200 : 400, {
|
|
779
|
-
success: ok,
|
|
780
|
-
message: ok ? `已切换 active leaf 至消息 #${messageId}` : '切换失败',
|
|
781
|
-
data: {
|
|
782
|
-
sessionKey: parsed.sessionKey,
|
|
783
|
-
sessionId,
|
|
784
|
-
activeLeafMessageId: session?.active_leaf_message_id ?? null,
|
|
785
|
-
},
|
|
786
|
-
});
|
|
787
812
|
}, { summary: 'Switch agent session active leaf', tags: ['console', 'agent'] });
|
|
788
813
|
}
|
|
789
814
|
|
|
@@ -34,10 +34,10 @@ export interface ConsoleRpcExtendedCtx {
|
|
|
34
34
|
/** 经 ImRuntime / AdapterEndpointIndex 解析管理语义端口。 */
|
|
35
35
|
resolveEndpointManagement?: (
|
|
36
36
|
adapter: string,
|
|
37
|
-
|
|
37
|
+
endpointKey: string,
|
|
38
38
|
) => EndpointManagementPort | null | undefined;
|
|
39
39
|
/** @deprecated Use `resolveEndpointManagement`; accepted for one compatibility cycle. */
|
|
40
|
-
resolveEndpoint?: (adapter: string,
|
|
40
|
+
resolveEndpoint?: (adapter: string, endpointKey: string) => unknown;
|
|
41
41
|
/** Plugin Runtime DatabaseHost 的 models 视图。 */
|
|
42
42
|
databaseHost?: { models: { get(name: string): unknown } };
|
|
43
43
|
/** Agent Host 持久化调度引擎(@zhin.js/agent getAssistantRuntime().engine),未 init 时返回 null。 */
|
|
@@ -340,13 +340,13 @@ async function listInbox(
|
|
|
340
340
|
mapRow: (row: Record<string, unknown>) => Record<string, unknown>,
|
|
341
341
|
): Promise<ExtendedRpcResult> {
|
|
342
342
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
343
|
-
const
|
|
344
|
-
if (!adapter || !
|
|
343
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
344
|
+
if (!adapter || !endpointKey) return { error: '$adapter and $endpoint required' };
|
|
345
345
|
const limit = Math.min(numField(d, 30, '$limit', 'limit'), 100);
|
|
346
346
|
const offset = Math.max(0, numField(d, 0, '$offset', 'offset'));
|
|
347
347
|
const { rows, enabled } = await readInboxRows(ctx, table, {
|
|
348
348
|
adapter,
|
|
349
|
-
endpoint_id:
|
|
349
|
+
endpoint_id: endpointKey,
|
|
350
350
|
}, {
|
|
351
351
|
// sort/limit 下推到 DB 侧(内存 sort/slice 保留,作为无下推能力模型的兜底)
|
|
352
352
|
orderBy: { field: 'created_at', direction: 'DESC' },
|
|
@@ -366,11 +366,11 @@ async function listPendingRequests(
|
|
|
366
366
|
ctx: ConsoleRpcExtendedCtx,
|
|
367
367
|
): Promise<ExtendedRpcResult> {
|
|
368
368
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
369
|
-
const
|
|
370
|
-
if (!adapter || !
|
|
369
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
370
|
+
if (!adapter || !endpointKey) return { error: '$adapter and $endpoint required' };
|
|
371
371
|
const { rows, enabled } = await readInboxRows(ctx, TABLE_REQUEST, {
|
|
372
372
|
adapter,
|
|
373
|
-
endpoint_id:
|
|
373
|
+
endpoint_id: endpointKey,
|
|
374
374
|
}, {
|
|
375
375
|
orderBy: { field: 'created_at', direction: 'ASC' },
|
|
376
376
|
});
|
|
@@ -386,10 +386,10 @@ async function listInboxMessages(
|
|
|
386
386
|
ctx: ConsoleRpcExtendedCtx,
|
|
387
387
|
): Promise<ExtendedRpcResult> {
|
|
388
388
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
389
|
-
const
|
|
389
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
390
390
|
const channelId = strField(d, '$channel_id', 'channelId', 'channel_id');
|
|
391
391
|
const channelType = strField(d, '$channel_type', 'channelType', 'channel_type');
|
|
392
|
-
if (!adapter || !
|
|
392
|
+
if (!adapter || !endpointKey || !channelId || !channelType) {
|
|
393
393
|
return { error: '$adapter, $endpoint, $channel_id, $channel_type required' };
|
|
394
394
|
}
|
|
395
395
|
const limit = Math.min(numField(d, 50, '$limit', 'limit'), 100);
|
|
@@ -399,7 +399,7 @@ async function listInboxMessages(
|
|
|
399
399
|
|
|
400
400
|
const where: Record<string, unknown> = {
|
|
401
401
|
adapter,
|
|
402
|
-
endpoint_id:
|
|
402
|
+
endpoint_id: endpointKey,
|
|
403
403
|
channel_id: channelId,
|
|
404
404
|
channel_type: channelType,
|
|
405
405
|
};
|
|
@@ -525,12 +525,12 @@ async function actOnRequest(
|
|
|
525
525
|
ctx: ConsoleRpcExtendedCtx,
|
|
526
526
|
): Promise<ExtendedRpcResult> {
|
|
527
527
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
528
|
-
const
|
|
528
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
529
529
|
const requestId = strField(d, '$id', 'id', 'platformRequestId', 'platform_request_id', 'requestId');
|
|
530
|
-
if (!adapter || !
|
|
530
|
+
if (!adapter || !endpointKey || !requestId) {
|
|
531
531
|
return { error: '$adapter, $endpoint, $id required' };
|
|
532
532
|
}
|
|
533
|
-
const resolved = resolveLiveEndpoint(ctx, adapter,
|
|
533
|
+
const resolved = resolveLiveEndpoint(ctx, adapter, endpointKey);
|
|
534
534
|
if ('error' in resolved) return resolved;
|
|
535
535
|
const management = resolved.management;
|
|
536
536
|
const approve = type === 'request.approve';
|
|
@@ -613,12 +613,12 @@ async function listGroupMembers(
|
|
|
613
613
|
ctx: ConsoleRpcExtendedCtx,
|
|
614
614
|
): Promise<ExtendedRpcResult> {
|
|
615
615
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
616
|
-
const
|
|
616
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
617
617
|
const groupId = strField(d, '$group_id', 'groupId', 'group_id');
|
|
618
|
-
if (!adapter || !
|
|
618
|
+
if (!adapter || !endpointKey || !groupId) {
|
|
619
619
|
return { error: '$adapter, $endpoint, $group_id required' };
|
|
620
620
|
}
|
|
621
|
-
const resolved = resolveLiveEndpoint(ctx, adapter,
|
|
621
|
+
const resolved = resolveLiveEndpoint(ctx, adapter, endpointKey);
|
|
622
622
|
if ('error' in resolved) return resolved;
|
|
623
623
|
const { management } = resolved;
|
|
624
624
|
try {
|
|
@@ -649,16 +649,16 @@ async function groupWriteOp(
|
|
|
649
649
|
spec: GroupWriteSpec,
|
|
650
650
|
): Promise<ExtendedRpcResult> {
|
|
651
651
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
652
|
-
const
|
|
652
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
653
653
|
const groupId = strField(d, '$group_id', 'groupId', 'group_id');
|
|
654
654
|
const userId = strField(d, '$user_id', 'userId', 'user_id');
|
|
655
|
-
if (!adapter || !
|
|
655
|
+
if (!adapter || !endpointKey || !groupId) {
|
|
656
656
|
return { error: '$adapter, $endpoint, $group_id required' };
|
|
657
657
|
}
|
|
658
658
|
if (spec.requireUser && !userId) {
|
|
659
659
|
return { error: '$user_id required' };
|
|
660
660
|
}
|
|
661
|
-
const resolved = resolveLiveEndpoint(ctx, adapter,
|
|
661
|
+
const resolved = resolveLiveEndpoint(ctx, adapter, endpointKey);
|
|
662
662
|
if ('error' in resolved) return resolved;
|
|
663
663
|
const { management } = resolved;
|
|
664
664
|
const method = management[spec.method];
|
|
@@ -686,12 +686,12 @@ async function deleteFriend(
|
|
|
686
686
|
ctx: ConsoleRpcExtendedCtx,
|
|
687
687
|
): Promise<ExtendedRpcResult> {
|
|
688
688
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
689
|
-
const
|
|
689
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
690
690
|
const userId = strField(d, '$user_id', 'userId', 'user_id');
|
|
691
|
-
if (!adapter || !
|
|
691
|
+
if (!adapter || !endpointKey || !userId) {
|
|
692
692
|
return { error: '$adapter, $endpoint, $user_id required' };
|
|
693
693
|
}
|
|
694
|
-
const resolved = resolveLiveEndpoint(ctx, adapter,
|
|
694
|
+
const resolved = resolveLiveEndpoint(ctx, adapter, endpointKey);
|
|
695
695
|
if ('error' in resolved) return resolved;
|
|
696
696
|
const { management } = resolved;
|
|
697
697
|
const method = management.deleteFriend;
|
|
@@ -749,23 +749,23 @@ function numArrayField(d: Record<string, unknown>, ...keys: string[]): number[]
|
|
|
749
749
|
type ResolveOk = {
|
|
750
750
|
management: EndpointManagementPort;
|
|
751
751
|
adapter: string;
|
|
752
|
-
|
|
752
|
+
endpointKey: string;
|
|
753
753
|
};
|
|
754
754
|
|
|
755
755
|
function resolveLiveEndpoint(
|
|
756
756
|
ctx: ConsoleRpcExtendedCtx,
|
|
757
757
|
adapter: string,
|
|
758
|
-
|
|
758
|
+
endpointKey: string,
|
|
759
759
|
): ResolveOk | { error: string } {
|
|
760
760
|
if (ctx.resolveEndpointManagement) {
|
|
761
|
-
const management = ctx.resolveEndpointManagement(adapter,
|
|
761
|
+
const management = ctx.resolveEndpointManagement(adapter, endpointKey);
|
|
762
762
|
if (!management) return { error: 'endpoint not found' };
|
|
763
|
-
return { management, adapter,
|
|
763
|
+
return { management, adapter, endpointKey };
|
|
764
764
|
}
|
|
765
765
|
if (!ctx.resolveEndpoint) {
|
|
766
766
|
return { error: 'Endpoint registry is not configured' };
|
|
767
767
|
}
|
|
768
|
-
const endpoint = ctx.resolveEndpoint(adapter,
|
|
768
|
+
const endpoint = ctx.resolveEndpoint(adapter, endpointKey);
|
|
769
769
|
if (!endpoint || typeof endpoint !== 'object') {
|
|
770
770
|
return { error: 'endpoint not found' };
|
|
771
771
|
}
|
|
@@ -775,7 +775,7 @@ function resolveLiveEndpoint(
|
|
|
775
775
|
? management as EndpointManagementPort
|
|
776
776
|
: {},
|
|
777
777
|
adapter,
|
|
778
|
-
|
|
778
|
+
endpointKey,
|
|
779
779
|
};
|
|
780
780
|
}
|
|
781
781
|
|
|
@@ -784,9 +784,9 @@ function requireEndpoint(
|
|
|
784
784
|
ctx: ConsoleRpcExtendedCtx,
|
|
785
785
|
): ResolveOk | { error: string } {
|
|
786
786
|
const adapter = strField(d, '$adapter', 'adapter');
|
|
787
|
-
const
|
|
788
|
-
if (!adapter || !
|
|
789
|
-
return resolveLiveEndpoint(ctx, adapter,
|
|
787
|
+
const endpointKey = strField(d, '$endpoint', 'endpointKey', 'endpoint');
|
|
788
|
+
if (!adapter || !endpointKey) return { error: '$adapter and $endpoint required' };
|
|
789
|
+
return resolveLiveEndpoint(ctx, adapter, endpointKey);
|
|
790
790
|
}
|
|
791
791
|
|
|
792
792
|
function normalizeParent(raw: unknown): { type: 'group' | 'guild'; id: string; name?: string } | undefined {
|
package/src/console-rpc.ts
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
|
634
|
-
if (!adapter || !
|
|
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,
|
|
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
|
|
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 || !
|
|
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
|
-
|
|
678
|
+
endpointKey,
|
|
679
679
|
conversation: wireConversation(channelType, channelId, data.$parent),
|
|
680
680
|
content,
|
|
681
681
|
});
|
package/src/http-host.ts
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
import { isDemoHttpAllowed } from './console-rpc.js';
|
|
25
25
|
import { HttpBodyError } from './json-body.js';
|
|
26
26
|
|
|
27
|
-
const logger = getLogger('
|
|
27
|
+
const logger = getLogger('http');
|
|
28
28
|
|
|
29
29
|
export const REMOTE_CONSOLE_ORIGIN = 'https://console.zhin.dev';
|
|
30
30
|
const DEFAULT_CORS_ORIGINS = Object.freeze([REMOTE_CONSOLE_ORIGIN]);
|
|
@@ -401,15 +401,17 @@ export function createHttpHost(options: HttpHostOptions = {}): HttpHost {
|
|
|
401
401
|
const listenPort = typeof bound === 'object' && bound ? bound.port : port;
|
|
402
402
|
const publicHost = host === '0.0.0.0' || host === '::' ? '127.0.0.1' : host;
|
|
403
403
|
address = Object.freeze({ host: publicHost, port: listenPort });
|
|
404
|
-
logger.info(
|
|
405
|
-
|
|
406
|
-
|
|
404
|
+
logger.info(
|
|
405
|
+
`listening http://${publicHost}:${listenPort}`
|
|
406
|
+
+ ` | routes: ${httpRoutes.length}`
|
|
407
|
+
+ ` | token: ${tokenRegistry.hasAnyToken()
|
|
407
408
|
? `${tokenRegistry.primaryTokenPrefixForLog()}…`
|
|
408
|
-
: '(none)'
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
409
|
+
: '(none)'}`,
|
|
410
|
+
);
|
|
411
|
+
logger.debug(
|
|
412
|
+
`listen detail | cors: ${corsOrigins.join(',')}`
|
|
413
|
+
+ ` | ws: ${[...wsRoutes.keys()].join(',') || '(none)'}`,
|
|
414
|
+
);
|
|
413
415
|
resolve(address);
|
|
414
416
|
});
|
|
415
417
|
});
|