@zhin.js/host-http 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/console-rest-pages.d.ts +3 -0
- package/lib/console-rest-pages.js +66 -26
- package/lib/console-rpc.js +1 -0
- package/lib/http-host.d.ts +7 -2
- package/lib/http-host.js +2 -0
- package/lib/public-api.d.ts +7 -0
- package/lib/public-api.js +5 -0
- package/package.json +3 -3
- package/src/console-rest-pages.ts +72 -30
- package/src/console-rpc.ts +1 -0
- package/src/http-host.ts +11 -4
- package/src/public-api.ts +15 -0
|
@@ -26,6 +26,8 @@ export interface ConsoleRestCtx {
|
|
|
26
26
|
get(name: string): unknown;
|
|
27
27
|
};
|
|
28
28
|
};
|
|
29
|
+
/** Distinguishes a real IM conversation with no Agent turn from a typo key. */
|
|
30
|
+
readonly isKnownConversationSession?: (sessionKey: string) => boolean | undefined | Promise<boolean | undefined>;
|
|
29
31
|
}
|
|
30
32
|
/** 内省数据门面 — 由 basic/cli 用 agent 包 collectIntrospection* 装配。 */
|
|
31
33
|
export interface ConsoleAgentIntrospection {
|
|
@@ -34,6 +36,7 @@ export interface ConsoleAgentIntrospection {
|
|
|
34
36
|
components?(): readonly unknown[];
|
|
35
37
|
bindings?(): readonly unknown[];
|
|
36
38
|
tools?(): readonly unknown[];
|
|
39
|
+
promptSections?(): readonly unknown[];
|
|
37
40
|
mcp?(): {
|
|
38
41
|
rows: readonly unknown[];
|
|
39
42
|
note?: string;
|
|
@@ -6,6 +6,7 @@ const INTROSPECTION_PAGE_SIZES = {
|
|
|
6
6
|
middlewares: 30,
|
|
7
7
|
components: 30,
|
|
8
8
|
tools: 15,
|
|
9
|
+
'prompt-sections': 20,
|
|
9
10
|
endpoints: 30,
|
|
10
11
|
bindings: 30,
|
|
11
12
|
mcp: 30,
|
|
@@ -384,7 +385,7 @@ function registerIntrospectionRoutes(route, base, ctx, options) {
|
|
|
384
385
|
const collectors = [
|
|
385
386
|
{
|
|
386
387
|
kind: 'commands',
|
|
387
|
-
collect: () => ({ rows:
|
|
388
|
+
collect: (value) => ({ rows: value?.commands?.() ?? [] }),
|
|
388
389
|
fields: [
|
|
389
390
|
(c) => stringProp(c, 'pattern'),
|
|
390
391
|
(c) => stringProp(c, 'desc'),
|
|
@@ -393,7 +394,7 @@ function registerIntrospectionRoutes(route, base, ctx, options) {
|
|
|
393
394
|
},
|
|
394
395
|
{
|
|
395
396
|
kind: 'middlewares',
|
|
396
|
-
collect: () => ({ rows:
|
|
397
|
+
collect: (value) => ({ rows: value?.middlewares?.() ?? [] }),
|
|
397
398
|
fields: [
|
|
398
399
|
(m) => stringProp(m, 'name'),
|
|
399
400
|
(m) => stringProp(m, 'owner'),
|
|
@@ -404,7 +405,7 @@ function registerIntrospectionRoutes(route, base, ctx, options) {
|
|
|
404
405
|
},
|
|
405
406
|
{
|
|
406
407
|
kind: 'components',
|
|
407
|
-
collect: () => ({ rows:
|
|
408
|
+
collect: (value) => ({ rows: value?.components?.() ?? [] }),
|
|
408
409
|
fields: [
|
|
409
410
|
(c) => stringProp(c, 'name'),
|
|
410
411
|
(c) => stringProp(c, 'owner'),
|
|
@@ -425,7 +426,7 @@ function registerIntrospectionRoutes(route, base, ctx, options) {
|
|
|
425
426
|
},
|
|
426
427
|
{
|
|
427
428
|
kind: 'bindings',
|
|
428
|
-
collect: () => ({ rows:
|
|
429
|
+
collect: (value) => ({ rows: value?.bindings?.() ?? [] }),
|
|
429
430
|
fields: [
|
|
430
431
|
(a) => stringProp(a, 'name'),
|
|
431
432
|
(a) => stringProp(a, 'provider'),
|
|
@@ -434,17 +435,30 @@ function registerIntrospectionRoutes(route, base, ctx, options) {
|
|
|
434
435
|
},
|
|
435
436
|
{
|
|
436
437
|
kind: 'tools',
|
|
437
|
-
collect: () => ({ rows:
|
|
438
|
+
collect: (value) => ({ rows: value?.tools?.() ?? [] }),
|
|
438
439
|
fields: [
|
|
439
440
|
(t) => stringProp(t, 'name'),
|
|
440
441
|
(t) => stringProp(t, 'source'),
|
|
441
442
|
(t) => stringProp(t, 'description'),
|
|
442
443
|
],
|
|
443
444
|
},
|
|
445
|
+
{
|
|
446
|
+
kind: 'prompt-sections',
|
|
447
|
+
collect: (value) => ({ rows: value?.promptSections?.() ?? [] }),
|
|
448
|
+
fields: [
|
|
449
|
+
(section) => stringProp(section, 'name'),
|
|
450
|
+
(section) => stringProp(section, 'qualifiedName'),
|
|
451
|
+
(section) => stringProp(section, 'title'),
|
|
452
|
+
(section) => stringProp(section, 'owner'),
|
|
453
|
+
(section) => stringProp(section, 'layer'),
|
|
454
|
+
(section) => stringProp(section, 'retention'),
|
|
455
|
+
(section) => stringProp(section, 'source'),
|
|
456
|
+
],
|
|
457
|
+
},
|
|
444
458
|
{
|
|
445
459
|
kind: 'mcp',
|
|
446
|
-
collect: () => {
|
|
447
|
-
const result =
|
|
460
|
+
collect: (value) => {
|
|
461
|
+
const result = value?.mcp?.();
|
|
448
462
|
if (result == null)
|
|
449
463
|
return { rows: [] };
|
|
450
464
|
// readonly 数组不能用 Array.isArray 收窄,按对象形状判断
|
|
@@ -458,15 +472,17 @@ function registerIntrospectionRoutes(route, base, ctx, options) {
|
|
|
458
472
|
];
|
|
459
473
|
for (const { kind, collect, fields } of collectors) {
|
|
460
474
|
route('GET', `${base}/introspection/${kind}`, async (_request, response, url) => {
|
|
475
|
+
const lease = kind === 'endpoints' ? null : ctx.acquireAgentRuntime?.() ?? null;
|
|
461
476
|
try {
|
|
462
|
-
const
|
|
477
|
+
const introspection = lease?.value.introspection;
|
|
478
|
+
const { rows, note } = collect(introspection);
|
|
463
479
|
const query = url.searchParams;
|
|
464
480
|
const filter = query.get('filter')?.trim() || undefined;
|
|
465
481
|
const filtered = filterByFields([...rows], filter, fields);
|
|
466
482
|
const slice = paginateItems(filtered, parsePositiveInt(query.get('page'), 1), parsePositiveInt(query.get('pageSize'), INTROSPECTION_PAGE_SIZES[kind]));
|
|
467
483
|
const missing = kind === 'endpoints'
|
|
468
484
|
? !ctx.getEndpoints
|
|
469
|
-
: !
|
|
485
|
+
: !introspection;
|
|
470
486
|
const degradedNote = note ?? (missing
|
|
471
487
|
? kind === 'endpoints'
|
|
472
488
|
? 'Endpoints 数据源未接线(ctx.getEndpoints 缺失)'
|
|
@@ -484,6 +500,9 @@ function registerIntrospectionRoutes(route, base, ctx, options) {
|
|
|
484
500
|
error: err instanceof Error ? err.message : String(err),
|
|
485
501
|
});
|
|
486
502
|
}
|
|
503
|
+
finally {
|
|
504
|
+
lease?.release();
|
|
505
|
+
}
|
|
487
506
|
}, { summary: `Introspection: ${kind}`, tags: ['console', 'introspection'] });
|
|
488
507
|
}
|
|
489
508
|
let activeComponentPreviews = 0;
|
|
@@ -608,20 +627,6 @@ function assertPreviewOutput(output, maxBytes) {
|
|
|
608
627
|
throw new ComponentPreviewLimitError(`Component preview 超过 ${byteLimit} bytes`);
|
|
609
628
|
}
|
|
610
629
|
}
|
|
611
|
-
function withAgentIntrospection(ctx, operation) {
|
|
612
|
-
const lease = ctx.acquireAgentRuntime?.();
|
|
613
|
-
if (!lease)
|
|
614
|
-
return operation(undefined);
|
|
615
|
-
try {
|
|
616
|
-
return operation(lease.value.introspection);
|
|
617
|
-
}
|
|
618
|
-
finally {
|
|
619
|
-
lease.release();
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
function hasAgentIntrospection(ctx) {
|
|
623
|
-
return withAgentIntrospection(ctx, (value) => value !== undefined);
|
|
624
|
-
}
|
|
625
630
|
// ---------------------------------------------------------------------------
|
|
626
631
|
// agent sessions(legacy agent-sessions-rest-api.ts;ADR 0010 D3)
|
|
627
632
|
// ---------------------------------------------------------------------------
|
|
@@ -658,9 +663,33 @@ function registerAgentSessionRoutes(route, base, ctx) {
|
|
|
658
663
|
try {
|
|
659
664
|
const sessionId = await runtime.resolveActiveSessionId(parsed.sessionKey);
|
|
660
665
|
if (!sessionId) {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
666
|
+
if (!isRuntimeImSessionKey(parsed.sessionKey)) {
|
|
667
|
+
writeJson(response, 404, {
|
|
668
|
+
success: false,
|
|
669
|
+
error: `未找到会话:${parsed.sessionKey}`,
|
|
670
|
+
});
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const knownConversation = await ctx.isKnownConversationSession?.(parsed.sessionKey);
|
|
674
|
+
if (knownConversation === false) {
|
|
675
|
+
writeJson(response, 404, {
|
|
676
|
+
success: false,
|
|
677
|
+
error: `未找到会话:${parsed.sessionKey}`,
|
|
678
|
+
});
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
// 渠道会话与 Agent 会话的生命周期不同:用户可以先在 Console
|
|
682
|
+
// 选中一个真实渠道会话,而该会话尚未触发过 Agent。树查询是只读操作,
|
|
683
|
+
// 因而将这种正常状态返回为可渲染空态,而不是误报资源不存在。
|
|
684
|
+
writeJson(response, 200, {
|
|
685
|
+
success: true,
|
|
686
|
+
data: {
|
|
687
|
+
state: 'not_started',
|
|
688
|
+
sessionKey: parsed.sessionKey,
|
|
689
|
+
sessionId: null,
|
|
690
|
+
activeLeafMessageId: null,
|
|
691
|
+
points: [],
|
|
692
|
+
},
|
|
664
693
|
});
|
|
665
694
|
return;
|
|
666
695
|
}
|
|
@@ -669,6 +698,7 @@ function registerAgentSessionRoutes(route, base, ctx) {
|
|
|
669
698
|
writeJson(response, 200, {
|
|
670
699
|
success: true,
|
|
671
700
|
data: {
|
|
701
|
+
state: 'active',
|
|
672
702
|
sessionKey: parsed.sessionKey,
|
|
673
703
|
sessionId,
|
|
674
704
|
activeLeafMessageId,
|
|
@@ -769,6 +799,16 @@ function registerAgentSessionRoutes(route, base, ctx) {
|
|
|
769
799
|
}
|
|
770
800
|
}, { summary: 'Switch agent session active leaf', tags: ['console', 'agent'] });
|
|
771
801
|
}
|
|
802
|
+
/** Runtime IM session keys are `<platform>:<endpoint>:<scope>:<sceneId>`. */
|
|
803
|
+
function isRuntimeImSessionKey(value) {
|
|
804
|
+
const first = value.indexOf(':');
|
|
805
|
+
const second = value.indexOf(':', first + 1);
|
|
806
|
+
const third = value.indexOf(':', second + 1);
|
|
807
|
+
return first > 0
|
|
808
|
+
&& second > first + 1
|
|
809
|
+
&& third > second + 1
|
|
810
|
+
&& third < value.length - 1;
|
|
811
|
+
}
|
|
772
812
|
function parseSessionAction(pathname, prefix, action) {
|
|
773
813
|
if (!pathname.startsWith(prefix))
|
|
774
814
|
return null;
|
package/lib/console-rpc.js
CHANGED
package/lib/http-host.d.ts
CHANGED
|
@@ -38,16 +38,21 @@ export type HttpHandler = (request: IncomingMessage, response: ServerResponse, u
|
|
|
38
38
|
export interface HttpRouteRegistration {
|
|
39
39
|
(): void;
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
/** @public Stable HTTP and WebSocket Host contract resolved through `httpHostToken`. */
|
|
42
|
+
export interface HttpHost {
|
|
42
43
|
ws(path: string): WsHandle;
|
|
43
44
|
route(method: string, path: string, handler: HttpHandler, meta?: RouteMeta): HttpRouteRegistration;
|
|
44
45
|
listRoutes(): readonly ListedRoute[];
|
|
45
46
|
get address(): HttpHostAddress | undefined;
|
|
47
|
+
/** @internal Console authentication registry owned by the process Host. */
|
|
46
48
|
get tokenRegistry(): TokenRegistry;
|
|
47
49
|
}
|
|
48
|
-
|
|
50
|
+
/** @internal Process-root lifecycle and generation admission surface. */
|
|
51
|
+
export interface ProcessHttpHost extends HttpHost, GenerationAdmissionBindable<HttpHost> {
|
|
49
52
|
listen(): Promise<HttpHostAddress>;
|
|
50
53
|
close(): Promise<void>;
|
|
51
54
|
}
|
|
55
|
+
/** @public Stable Plugin Runtime HTTP Host token. */
|
|
52
56
|
export declare const httpHostToken: import("@zhin.js/plugin-runtime").Token<HttpHost>;
|
|
57
|
+
/** @internal CLI composition-root factory. */
|
|
53
58
|
export declare function createHttpHost(options?: HttpHostOptions): ProcessHttpHost;
|
package/lib/http-host.js
CHANGED
|
@@ -9,7 +9,9 @@ import { HttpBodyError } from './json-body.js';
|
|
|
9
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
|
+
/** @public Stable Plugin Runtime HTTP Host token. */
|
|
12
13
|
export const httpHostToken = createToken('zhin.host.http');
|
|
14
|
+
/** @internal CLI composition-root factory. */
|
|
13
15
|
export function createHttpHost(options = {}) {
|
|
14
16
|
const host = options.host ?? '127.0.0.1';
|
|
15
17
|
const port = options.port ?? 8086;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin-facing HTTP Host contract exposed from `@zhin.js/host-http`.
|
|
3
|
+
* @module @zhin.js/host-http
|
|
4
|
+
*/
|
|
5
|
+
export { httpHostToken, type HttpHandler, type HttpHost, type HttpHostAddress, type HttpRouteRegistration, type WsConnection, type WsHandle, } from './http-host.js';
|
|
6
|
+
export type { AuthenticatedTokenPrincipal, AuthScope } from './token-registry.js';
|
|
7
|
+
export type { ListedRoute, OpenApiParameter, RouteMeta } from './openapi.js';
|
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.14",
|
|
4
4
|
"description": "Minimal HTTP and WebSocket host for Plugin Runtime (no Koa)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"ws": "^8.21.1",
|
|
21
21
|
"@zhin.js/console-protocol": "1.1.4",
|
|
22
22
|
"@zhin.js/host-http-contract": "1.0.1",
|
|
23
|
-
"@zhin.js/logger": "1.0.
|
|
24
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
23
|
+
"@zhin.js/logger": "1.0.77",
|
|
24
|
+
"@zhin.js/plugin-runtime": "1.1.8"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^26.1.2",
|
|
@@ -42,6 +42,10 @@ export interface ConsoleRestCtx {
|
|
|
42
42
|
readonly started: boolean;
|
|
43
43
|
readonly models: { get(name: string): unknown };
|
|
44
44
|
};
|
|
45
|
+
/** Distinguishes a real IM conversation with no Agent turn from a typo key. */
|
|
46
|
+
readonly isKnownConversationSession?: (
|
|
47
|
+
sessionKey: string,
|
|
48
|
+
) => boolean | undefined | Promise<boolean | undefined>;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
/** 内省数据门面 — 由 basic/cli 用 agent 包 collectIntrospection* 装配。 */
|
|
@@ -51,6 +55,7 @@ export interface ConsoleAgentIntrospection {
|
|
|
51
55
|
components?(): readonly unknown[];
|
|
52
56
|
bindings?(): readonly unknown[];
|
|
53
57
|
tools?(): readonly unknown[];
|
|
58
|
+
promptSections?(): readonly unknown[];
|
|
54
59
|
mcp?(): { rows: readonly unknown[]; note?: string } | readonly unknown[];
|
|
55
60
|
renderComponent?(input: Readonly<{
|
|
56
61
|
requester: string;
|
|
@@ -122,6 +127,7 @@ const INTROSPECTION_PAGE_SIZES = {
|
|
|
122
127
|
middlewares: 30,
|
|
123
128
|
components: 30,
|
|
124
129
|
tools: 15,
|
|
130
|
+
'prompt-sections': 20,
|
|
125
131
|
endpoints: 30,
|
|
126
132
|
bindings: 30,
|
|
127
133
|
mcp: 30,
|
|
@@ -567,12 +573,12 @@ function registerIntrospectionRoutes(
|
|
|
567
573
|
): void {
|
|
568
574
|
const collectors: Array<{
|
|
569
575
|
kind: IntrospectionKind;
|
|
570
|
-
collect: () => { rows: readonly unknown[]; note?: string };
|
|
576
|
+
collect: (introspection: ConsoleAgentIntrospection | undefined) => { rows: readonly unknown[]; note?: string };
|
|
571
577
|
fields: Array<(item: unknown) => string | undefined>;
|
|
572
578
|
}> = [
|
|
573
579
|
{
|
|
574
580
|
kind: 'commands',
|
|
575
|
-
collect: () => ({ rows:
|
|
581
|
+
collect: (value) => ({ rows: value?.commands?.() ?? [] }),
|
|
576
582
|
fields: [
|
|
577
583
|
(c) => stringProp(c, 'pattern'),
|
|
578
584
|
(c) => stringProp(c, 'desc'),
|
|
@@ -581,7 +587,7 @@ function registerIntrospectionRoutes(
|
|
|
581
587
|
},
|
|
582
588
|
{
|
|
583
589
|
kind: 'middlewares',
|
|
584
|
-
collect: () => ({ rows:
|
|
590
|
+
collect: (value) => ({ rows: value?.middlewares?.() ?? [] }),
|
|
585
591
|
fields: [
|
|
586
592
|
(m) => stringProp(m, 'name'),
|
|
587
593
|
(m) => stringProp(m, 'owner'),
|
|
@@ -592,7 +598,7 @@ function registerIntrospectionRoutes(
|
|
|
592
598
|
},
|
|
593
599
|
{
|
|
594
600
|
kind: 'components',
|
|
595
|
-
collect: () => ({ rows:
|
|
601
|
+
collect: (value) => ({ rows: value?.components?.() ?? [] }),
|
|
596
602
|
fields: [
|
|
597
603
|
(c) => stringProp(c, 'name'),
|
|
598
604
|
(c) => stringProp(c, 'owner'),
|
|
@@ -613,7 +619,7 @@ function registerIntrospectionRoutes(
|
|
|
613
619
|
},
|
|
614
620
|
{
|
|
615
621
|
kind: 'bindings',
|
|
616
|
-
collect: () => ({ rows:
|
|
622
|
+
collect: (value) => ({ rows: value?.bindings?.() ?? [] }),
|
|
617
623
|
fields: [
|
|
618
624
|
(a) => stringProp(a, 'name'),
|
|
619
625
|
(a) => stringProp(a, 'provider'),
|
|
@@ -622,17 +628,30 @@ function registerIntrospectionRoutes(
|
|
|
622
628
|
},
|
|
623
629
|
{
|
|
624
630
|
kind: 'tools',
|
|
625
|
-
collect: () => ({ rows:
|
|
631
|
+
collect: (value) => ({ rows: value?.tools?.() ?? [] }),
|
|
626
632
|
fields: [
|
|
627
633
|
(t) => stringProp(t, 'name'),
|
|
628
634
|
(t) => stringProp(t, 'source'),
|
|
629
635
|
(t) => stringProp(t, 'description'),
|
|
630
636
|
],
|
|
631
637
|
},
|
|
638
|
+
{
|
|
639
|
+
kind: 'prompt-sections',
|
|
640
|
+
collect: (value) => ({ rows: value?.promptSections?.() ?? [] }),
|
|
641
|
+
fields: [
|
|
642
|
+
(section) => stringProp(section, 'name'),
|
|
643
|
+
(section) => stringProp(section, 'qualifiedName'),
|
|
644
|
+
(section) => stringProp(section, 'title'),
|
|
645
|
+
(section) => stringProp(section, 'owner'),
|
|
646
|
+
(section) => stringProp(section, 'layer'),
|
|
647
|
+
(section) => stringProp(section, 'retention'),
|
|
648
|
+
(section) => stringProp(section, 'source'),
|
|
649
|
+
],
|
|
650
|
+
},
|
|
632
651
|
{
|
|
633
652
|
kind: 'mcp',
|
|
634
|
-
collect: () => {
|
|
635
|
-
const result =
|
|
653
|
+
collect: (value) => {
|
|
654
|
+
const result = value?.mcp?.();
|
|
636
655
|
if (result == null) return { rows: [] as readonly unknown[] };
|
|
637
656
|
// readonly 数组不能用 Array.isArray 收窄,按对象形状判断
|
|
638
657
|
if (typeof result === 'object' && 'rows' in result) {
|
|
@@ -646,8 +665,10 @@ function registerIntrospectionRoutes(
|
|
|
646
665
|
|
|
647
666
|
for (const { kind, collect, fields } of collectors) {
|
|
648
667
|
route('GET', `${base}/introspection/${kind}`, async (_request, response, url) => {
|
|
668
|
+
const lease = kind === 'endpoints' ? null : ctx.acquireAgentRuntime?.() ?? null;
|
|
649
669
|
try {
|
|
650
|
-
const
|
|
670
|
+
const introspection = lease?.value.introspection;
|
|
671
|
+
const { rows, note } = collect(introspection);
|
|
651
672
|
const query = url.searchParams;
|
|
652
673
|
const filter = query.get('filter')?.trim() || undefined;
|
|
653
674
|
const filtered = filterByFields([...rows], filter, fields);
|
|
@@ -658,7 +679,7 @@ function registerIntrospectionRoutes(
|
|
|
658
679
|
);
|
|
659
680
|
const missing = kind === 'endpoints'
|
|
660
681
|
? !ctx.getEndpoints
|
|
661
|
-
: !
|
|
682
|
+
: !introspection;
|
|
662
683
|
const degradedNote = note ?? (missing
|
|
663
684
|
? kind === 'endpoints'
|
|
664
685
|
? 'Endpoints 数据源未接线(ctx.getEndpoints 缺失)'
|
|
@@ -674,6 +695,8 @@ function registerIntrospectionRoutes(
|
|
|
674
695
|
data: { items: [], page: 1, pageSize: 0, total: 0, totalPages: 0 },
|
|
675
696
|
error: err instanceof Error ? err.message : String(err),
|
|
676
697
|
});
|
|
698
|
+
} finally {
|
|
699
|
+
lease?.release();
|
|
677
700
|
}
|
|
678
701
|
}, { summary: `Introspection: ${kind}`, tags: ['console', 'introspection'] });
|
|
679
702
|
}
|
|
@@ -800,23 +823,6 @@ function assertPreviewOutput(output: unknown, maxBytes: number): void {
|
|
|
800
823
|
}
|
|
801
824
|
}
|
|
802
825
|
|
|
803
|
-
function withAgentIntrospection<T>(
|
|
804
|
-
ctx: ConsoleRestCtx,
|
|
805
|
-
operation: (value: ConsoleAgentIntrospection | undefined) => T,
|
|
806
|
-
): T {
|
|
807
|
-
const lease = ctx.acquireAgentRuntime?.();
|
|
808
|
-
if (!lease) return operation(undefined);
|
|
809
|
-
try {
|
|
810
|
-
return operation(lease.value.introspection);
|
|
811
|
-
} finally {
|
|
812
|
-
lease.release();
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
function hasAgentIntrospection(ctx: ConsoleRestCtx): boolean {
|
|
817
|
-
return withAgentIntrospection(ctx, (value) => value !== undefined);
|
|
818
|
-
}
|
|
819
|
-
|
|
820
826
|
// ---------------------------------------------------------------------------
|
|
821
827
|
// agent sessions(legacy agent-sessions-rest-api.ts;ADR 0010 D3)
|
|
822
828
|
// ---------------------------------------------------------------------------
|
|
@@ -862,9 +868,33 @@ function registerAgentSessionRoutes(
|
|
|
862
868
|
try {
|
|
863
869
|
const sessionId = await runtime.resolveActiveSessionId(parsed.sessionKey);
|
|
864
870
|
if (!sessionId) {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
871
|
+
if (!isRuntimeImSessionKey(parsed.sessionKey)) {
|
|
872
|
+
writeJson(response, 404, {
|
|
873
|
+
success: false,
|
|
874
|
+
error: `未找到会话:${parsed.sessionKey}`,
|
|
875
|
+
});
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
const knownConversation = await ctx.isKnownConversationSession?.(parsed.sessionKey);
|
|
879
|
+
if (knownConversation === false) {
|
|
880
|
+
writeJson(response, 404, {
|
|
881
|
+
success: false,
|
|
882
|
+
error: `未找到会话:${parsed.sessionKey}`,
|
|
883
|
+
});
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
// 渠道会话与 Agent 会话的生命周期不同:用户可以先在 Console
|
|
887
|
+
// 选中一个真实渠道会话,而该会话尚未触发过 Agent。树查询是只读操作,
|
|
888
|
+
// 因而将这种正常状态返回为可渲染空态,而不是误报资源不存在。
|
|
889
|
+
writeJson(response, 200, {
|
|
890
|
+
success: true,
|
|
891
|
+
data: {
|
|
892
|
+
state: 'not_started',
|
|
893
|
+
sessionKey: parsed.sessionKey,
|
|
894
|
+
sessionId: null,
|
|
895
|
+
activeLeafMessageId: null,
|
|
896
|
+
points: [],
|
|
897
|
+
},
|
|
868
898
|
});
|
|
869
899
|
return;
|
|
870
900
|
}
|
|
@@ -874,6 +904,7 @@ function registerAgentSessionRoutes(
|
|
|
874
904
|
writeJson(response, 200, {
|
|
875
905
|
success: true,
|
|
876
906
|
data: {
|
|
907
|
+
state: 'active',
|
|
877
908
|
sessionKey: parsed.sessionKey,
|
|
878
909
|
sessionId,
|
|
879
910
|
activeLeafMessageId,
|
|
@@ -973,6 +1004,17 @@ function registerAgentSessionRoutes(
|
|
|
973
1004
|
}, { summary: 'Switch agent session active leaf', tags: ['console', 'agent'] });
|
|
974
1005
|
}
|
|
975
1006
|
|
|
1007
|
+
/** Runtime IM session keys are `<platform>:<endpoint>:<scope>:<sceneId>`. */
|
|
1008
|
+
function isRuntimeImSessionKey(value: string): boolean {
|
|
1009
|
+
const first = value.indexOf(':');
|
|
1010
|
+
const second = value.indexOf(':', first + 1);
|
|
1011
|
+
const third = value.indexOf(':', second + 1);
|
|
1012
|
+
return first > 0
|
|
1013
|
+
&& second > first + 1
|
|
1014
|
+
&& third > second + 1
|
|
1015
|
+
&& third < value.length - 1;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
976
1018
|
function parseSessionAction(
|
|
977
1019
|
pathname: string,
|
|
978
1020
|
prefix: string,
|
package/src/console-rpc.ts
CHANGED
package/src/http-host.ts
CHANGED
|
@@ -81,7 +81,8 @@ export interface HttpRouteRegistration {
|
|
|
81
81
|
(): void;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
/** @public Stable HTTP and WebSocket Host contract resolved through `httpHostToken`. */
|
|
85
|
+
export interface HttpHost {
|
|
85
86
|
ws(path: string): WsHandle;
|
|
86
87
|
route(
|
|
87
88
|
method: string,
|
|
@@ -91,10 +92,12 @@ export interface HttpHost extends GenerationAdmissionBindable<HttpHost> {
|
|
|
91
92
|
): HttpRouteRegistration;
|
|
92
93
|
listRoutes(): readonly ListedRoute[];
|
|
93
94
|
get address(): HttpHostAddress | undefined;
|
|
95
|
+
/** @internal Console authentication registry owned by the process Host. */
|
|
94
96
|
get tokenRegistry(): TokenRegistry;
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
/** @internal Process-root lifecycle and generation admission surface. */
|
|
100
|
+
export interface ProcessHttpHost extends HttpHost, GenerationAdmissionBindable<HttpHost> {
|
|
98
101
|
listen(): Promise<HttpHostAddress>;
|
|
99
102
|
close(): Promise<void>;
|
|
100
103
|
}
|
|
@@ -109,13 +112,17 @@ interface HttpRoute {
|
|
|
109
112
|
readonly admission?: GenerationAdmissionGate;
|
|
110
113
|
}
|
|
111
114
|
|
|
115
|
+
interface AdmissionBoundHttpHost extends HttpHost, GenerationAdmissionBindable<HttpHost> {}
|
|
116
|
+
|
|
112
117
|
interface WsRoute {
|
|
113
118
|
readonly listener: (connection: WsConnection) => void;
|
|
114
119
|
readonly admission?: GenerationAdmissionGate;
|
|
115
120
|
}
|
|
116
121
|
|
|
122
|
+
/** @public Stable Plugin Runtime HTTP Host token. */
|
|
117
123
|
export const httpHostToken = createToken<HttpHost>('zhin.host.http');
|
|
118
124
|
|
|
125
|
+
/** @internal CLI composition-root factory. */
|
|
119
126
|
export function createHttpHost(options: HttpHostOptions = {}): ProcessHttpHost {
|
|
120
127
|
const host = options.host ?? '127.0.0.1';
|
|
121
128
|
const port = options.port ?? 8086;
|
|
@@ -453,8 +460,8 @@ export function createHttpHost(options: HttpHostOptions = {}): ProcessHttpHost {
|
|
|
453
460
|
|
|
454
461
|
function createBoundHost(
|
|
455
462
|
admission: GenerationAdmissionGate,
|
|
456
|
-
):
|
|
457
|
-
const bound:
|
|
463
|
+
): AdmissionBoundHttpHost {
|
|
464
|
+
const bound: AdmissionBoundHttpHost = {
|
|
458
465
|
[generationAdmissionBinder]: (next) => createBoundHost(next),
|
|
459
466
|
ws: (path) => registerWs(path, admission),
|
|
460
467
|
route: (method, path, handler, meta) => registerRoute(
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin-facing HTTP Host contract exposed from `@zhin.js/host-http`.
|
|
3
|
+
* @module @zhin.js/host-http
|
|
4
|
+
*/
|
|
5
|
+
export {
|
|
6
|
+
httpHostToken,
|
|
7
|
+
type HttpHandler,
|
|
8
|
+
type HttpHost,
|
|
9
|
+
type HttpHostAddress,
|
|
10
|
+
type HttpRouteRegistration,
|
|
11
|
+
type WsConnection,
|
|
12
|
+
type WsHandle,
|
|
13
|
+
} from './http-host.js';
|
|
14
|
+
export type { AuthenticatedTokenPrincipal, AuthScope } from './token-registry.js';
|
|
15
|
+
export type { ListedRoute, OpenApiParameter, RouteMeta } from './openapi.js';
|