@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.
@@ -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: withAgentIntrospection(ctx, (value) => value?.commands?.() ?? []) }),
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: withAgentIntrospection(ctx, (value) => value?.middlewares?.() ?? []) }),
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: withAgentIntrospection(ctx, (value) => value?.components?.() ?? []) }),
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: withAgentIntrospection(ctx, (value) => value?.bindings?.() ?? []) }),
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: withAgentIntrospection(ctx, (value) => value?.tools?.() ?? []) }),
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 = withAgentIntrospection(ctx, (value) => value?.mcp?.());
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 { rows, note } = collect();
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
- : !hasAgentIntrospection(ctx);
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
- writeJson(response, 404, {
662
- success: false,
663
- error: `未找到活跃会话:${parsed.sessionKey}`,
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;
@@ -722,5 +722,6 @@ const DEMO_INTROSPECTION_PATHS = new Set([
722
722
  '/introspection/endpoints',
723
723
  '/introspection/bindings',
724
724
  '/introspection/tools',
725
+ '/introspection/prompt-sections',
725
726
  '/introspection/mcp',
726
727
  ]);
@@ -38,16 +38,21 @@ export type HttpHandler = (request: IncomingMessage, response: ServerResponse, u
38
38
  export interface HttpRouteRegistration {
39
39
  (): void;
40
40
  }
41
- export interface HttpHost extends GenerationAdmissionBindable<HttpHost> {
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
- export interface ProcessHttpHost extends HttpHost {
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';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Plugin-facing HTTP Host contract exposed from `@zhin.js/host-http`.
3
+ * @module @zhin.js/host-http
4
+ */
5
+ export { httpHostToken, } from './http-host.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/host-http",
3
- "version": "1.0.12",
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.76",
24
- "@zhin.js/plugin-runtime": "1.1.7"
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: withAgentIntrospection(ctx, (value) => value?.commands?.() ?? []) }),
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: withAgentIntrospection(ctx, (value) => value?.middlewares?.() ?? []) }),
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: withAgentIntrospection(ctx, (value) => value?.components?.() ?? []) }),
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: withAgentIntrospection(ctx, (value) => value?.bindings?.() ?? []) }),
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: withAgentIntrospection(ctx, (value) => value?.tools?.() ?? []) }),
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 = withAgentIntrospection(ctx, (value) => value?.mcp?.());
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 { rows, note } = collect();
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
- : !hasAgentIntrospection(ctx);
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
- writeJson(response, 404, {
866
- success: false,
867
- error: `未找到活跃会话:${parsed.sessionKey}`,
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,
@@ -819,5 +819,6 @@ const DEMO_INTROSPECTION_PATHS: ReadonlySet<string> = new Set([
819
819
  '/introspection/endpoints',
820
820
  '/introspection/bindings',
821
821
  '/introspection/tools',
822
+ '/introspection/prompt-sections',
822
823
  '/introspection/mcp',
823
824
  ]);
package/src/http-host.ts CHANGED
@@ -81,7 +81,8 @@ export interface HttpRouteRegistration {
81
81
  (): void;
82
82
  }
83
83
 
84
- export interface HttpHost extends GenerationAdmissionBindable<HttpHost> {
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
- export interface ProcessHttpHost extends HttpHost {
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
- ): HttpHost {
457
- const bound: HttpHost = {
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';