@zhin.js/host-http 1.1.13 → 1.1.15

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.
@@ -1,17 +1,16 @@
1
+ /**
2
+ * Console REST pages — logs / marketplace / introspection / agent sessions。
3
+ *
4
+ * 统一提供日志、市场、Runtime snapshot 内省和 Agent session tree 页面。
5
+ *
6
+ * 数据源不可用时降级:读操作返回空数组 + `note` 说明,session tree 返回 503;
7
+ * 写操作要求 `ctx.fullScope && authScope === 'full'`,否则 403。
8
+ */
1
9
  import { readFile } from 'node:fs/promises';
2
10
  import { join } from 'node:path';
3
- import { HttpBodyError, readJsonBody } from './json-body.js';
11
+ import { readJsonBody } from './json-body.js';
4
12
  import { MarketplaceRegistry } from './marketplace-registry.js';
5
- const INTROSPECTION_PAGE_SIZES = {
6
- commands: 25,
7
- middlewares: 30,
8
- components: 30,
9
- tools: 15,
10
- 'prompt-sections': 20,
11
- endpoints: 30,
12
- bindings: 30,
13
- mcp: 30,
14
- };
13
+ import { clampPositiveInt, decodePathParam, normalizeBase, parsePositiveInt, registerAgentSessionRoutes, registerIntrospectionRoutes, requireWriteScope, trimTrailingSlashes, writeJson, } from './console-rest-pages-helpers.js';
15
14
  const PLUGINS_CACHE_TTL = 5 * 60 * 1000;
16
15
  /**
17
16
  * 注册 Console 页面 REST 路由。返回注销函数(注销全部已注册路由)。
@@ -399,562 +398,3 @@ async function listInstalledPluginPackages(projectRoot) {
399
398
  }
400
399
  }));
401
400
  }
402
- // ---------------------------------------------------------------------------
403
- // Runtime snapshot introspection
404
- // ---------------------------------------------------------------------------
405
- function registerIntrospectionRoutes(route, base, ctx, options) {
406
- const collectors = [
407
- {
408
- kind: 'commands',
409
- collect: (value) => ({ rows: value?.commands?.() ?? [] }),
410
- fields: [
411
- (c) => stringProp(c, 'pattern'),
412
- (c) => stringProp(c, 'desc'),
413
- (c) => stringProp(c, 'plugin'),
414
- ],
415
- },
416
- {
417
- kind: 'middlewares',
418
- collect: (value) => ({ rows: value?.middlewares?.() ?? [] }),
419
- fields: [
420
- (m) => stringProp(m, 'name'),
421
- (m) => stringProp(m, 'owner'),
422
- (m) => stringProp(m, 'phase'),
423
- (m) => stringProp(m, 'target'),
424
- (m) => stringProp(m, 'source'),
425
- ],
426
- },
427
- {
428
- kind: 'components',
429
- collect: (value) => ({ rows: value?.components?.() ?? [] }),
430
- fields: [
431
- (c) => stringProp(c, 'name'),
432
- (c) => stringProp(c, 'owner'),
433
- (c) => stringProp(c, 'source'),
434
- ],
435
- },
436
- {
437
- kind: 'endpoints',
438
- collect: () => ({
439
- rows: (ctx.getEndpoints?.() ?? []).map((endpoint) => ({
440
- adapter: endpoint.adapter,
441
- name: endpoint.name,
442
- online: endpoint.connected,
443
- status: endpoint.status,
444
- })),
445
- }),
446
- fields: [(b) => stringProp(b, 'adapter'), (b) => stringProp(b, 'name')],
447
- },
448
- {
449
- kind: 'bindings',
450
- collect: (value) => ({ rows: value?.bindings?.() ?? [] }),
451
- fields: [
452
- (a) => stringProp(a, 'name'),
453
- (a) => stringProp(a, 'provider'),
454
- (a) => stringProp(a, 'model'),
455
- ],
456
- },
457
- {
458
- kind: 'tools',
459
- collect: (value) => ({ rows: value?.tools?.() ?? [] }),
460
- fields: [
461
- (t) => stringProp(t, 'name'),
462
- (t) => stringProp(t, 'source'),
463
- (t) => stringProp(t, 'description'),
464
- ],
465
- },
466
- {
467
- kind: 'prompt-sections',
468
- collect: (value) => ({ rows: value?.promptSections?.() ?? [] }),
469
- fields: [
470
- (section) => stringProp(section, 'name'),
471
- (section) => stringProp(section, 'qualifiedName'),
472
- (section) => stringProp(section, 'title'),
473
- (section) => stringProp(section, 'owner'),
474
- (section) => stringProp(section, 'layer'),
475
- (section) => stringProp(section, 'retention'),
476
- (section) => stringProp(section, 'source'),
477
- ],
478
- },
479
- {
480
- kind: 'mcp',
481
- collect: (value) => {
482
- const result = value?.mcp?.();
483
- if (result == null)
484
- return { rows: [] };
485
- // readonly 数组不能用 Array.isArray 收窄,按对象形状判断
486
- if (typeof result === 'object' && 'rows' in result) {
487
- return { rows: result.rows, note: result.note };
488
- }
489
- return { rows: result };
490
- },
491
- fields: [(s) => stringProp(s, 'name')],
492
- },
493
- ];
494
- for (const { kind, collect, fields } of collectors) {
495
- route('GET', `${base}/introspection/${kind}`, async (_request, response, url) => {
496
- const lease = kind === 'endpoints' ? null : ctx.acquireAgentRuntime?.() ?? null;
497
- try {
498
- const introspection = lease?.value.introspection;
499
- const { rows, note } = collect(introspection);
500
- const query = url.searchParams;
501
- const filter = query.get('filter')?.trim() || undefined;
502
- const filtered = filterByFields([...rows], filter, fields);
503
- const slice = paginateItems(filtered, parsePositiveInt(query.get('page'), 1), parsePositiveInt(query.get('pageSize'), INTROSPECTION_PAGE_SIZES[kind]));
504
- const missing = kind === 'endpoints'
505
- ? !ctx.getEndpoints
506
- : !introspection;
507
- const degradedNote = note ?? (missing
508
- ? kind === 'endpoints'
509
- ? 'Endpoints 数据源未接线(ctx.getEndpoints 缺失)'
510
- : 'Agent runtime 未装配(basic/cli 未接线 acquireAgentRuntime)'
511
- : undefined);
512
- writeJson(response, 200, {
513
- success: true,
514
- data: { ...slice, filter, note: degradedNote },
515
- });
516
- }
517
- catch (err) {
518
- writeJson(response, 503, {
519
- success: false,
520
- data: { items: [], page: 1, pageSize: 0, total: 0, totalPages: 0 },
521
- error: err instanceof Error ? err.message : String(err),
522
- });
523
- }
524
- finally {
525
- lease?.release();
526
- }
527
- }, { summary: `Introspection: ${kind}`, tags: ['console', 'introspection'] });
528
- }
529
- let activeComponentPreviews = 0;
530
- route('POST', `${base}/introspection/components/render`, async (request, response, _url, authScope) => {
531
- if (!requireWriteScope(response, ctx, authScope))
532
- return;
533
- const body = (await readJsonBody(request)) ?? {};
534
- const requester = typeof body.requester === 'string' ? body.requester.trim() : '';
535
- const name = typeof body.name === 'string' ? body.name.trim() : '';
536
- if (!requester || !name || requester.length > 256 || name.length > 160) {
537
- writeJson(response, 400, { success: false, error: '请提供有效的 requester 与 component name' });
538
- return;
539
- }
540
- const maxConcurrent = clampPositiveInt(options.componentPreviewMaxConcurrent, 2, 8);
541
- if (activeComponentPreviews >= maxConcurrent) {
542
- writeJson(response, 503, { success: false, error: 'Component preview 正忙,请稍后重试' });
543
- return;
544
- }
545
- const lease = ctx.acquireAgentRuntime?.();
546
- const render = lease?.value.introspection?.renderComponent;
547
- const previewDelivery = lease?.value.introspection?.previewComponentDelivery;
548
- if (!lease || !render) {
549
- lease?.release();
550
- writeJson(response, 503, { success: false, error: 'Component Runtime 未就绪' });
551
- return;
552
- }
553
- const controller = new AbortController();
554
- const timeoutMs = clampPositiveInt(options.componentPreviewTimeoutMs, 5_000, 30_000);
555
- const timeout = setTimeout(() => controller.abort(new ComponentPreviewTimeoutError(`Component preview 超过 ${timeoutMs}ms`)), timeoutMs);
556
- activeComponentPreviews += 1;
557
- let released = false;
558
- const release = () => {
559
- if (released)
560
- return;
561
- released = true;
562
- activeComponentPreviews -= 1;
563
- lease.release();
564
- };
565
- const renderPromise = Promise.resolve().then(() => {
566
- const input = { requester, name, props: body.props ?? {}, signal: controller.signal };
567
- if (body.target === undefined)
568
- return render(input).then(output => ({ output }));
569
- if (!previewDelivery)
570
- throw new Error('Target-aware Component preview 未就绪');
571
- return previewDelivery({ ...input, target: body.target });
572
- });
573
- // A timed-out implementation may ignore AbortSignal. Keep its generation
574
- // lease until the real operation settles; the bounded concurrency gate
575
- // prevents non-cooperative previews from pinning unbounded generations.
576
- void renderPromise.then(release, release);
577
- try {
578
- const preview = await raceWithAbort(renderPromise, controller.signal);
579
- assertPreviewOutput(preview, options.componentPreviewMaxBytes ?? 256 * 1024);
580
- writeJson(response, 200, { success: true, data: { requester, name, ...preview } });
581
- }
582
- catch (error) {
583
- const status = error instanceof ComponentPreviewTimeoutError
584
- ? 408
585
- : error instanceof ComponentPreviewLimitError ? 413 : 422;
586
- writeJson(response, status, {
587
- success: false,
588
- error: error instanceof Error ? error.message : String(error),
589
- });
590
- }
591
- finally {
592
- clearTimeout(timeout);
593
- if (!controller.signal.aborted)
594
- controller.abort();
595
- }
596
- }, { summary: 'Render a Component preview', tags: ['console', 'introspection', 'component'] });
597
- }
598
- class ComponentPreviewTimeoutError extends Error {
599
- name = 'ComponentPreviewTimeoutError';
600
- }
601
- class ComponentPreviewLimitError extends Error {
602
- name = 'ComponentPreviewLimitError';
603
- }
604
- function clampPositiveInt(value, fallback, ceiling) {
605
- if (!Number.isFinite(value) || !value || value < 1)
606
- return fallback;
607
- return Math.min(Math.floor(value), ceiling);
608
- }
609
- async function raceWithAbort(promise, signal) {
610
- if (signal.aborted)
611
- throw abortReason(signal);
612
- return await new Promise((resolve, reject) => {
613
- const onAbort = () => reject(abortReason(signal));
614
- signal.addEventListener('abort', onAbort, { once: true });
615
- promise.then(resolve, reject).finally(() => signal.removeEventListener('abort', onAbort));
616
- });
617
- }
618
- function abortReason(signal) {
619
- return signal.reason instanceof Error
620
- ? signal.reason
621
- : new DOMException('Component preview aborted', 'AbortError');
622
- }
623
- function assertPreviewOutput(output, maxBytes) {
624
- const byteLimit = clampPositiveInt(maxBytes, 256 * 1024, 1024 * 1024);
625
- const seen = new WeakSet();
626
- let nodes = 0;
627
- const visit = (value, depth) => {
628
- nodes += 1;
629
- if (nodes > 10_000)
630
- throw new ComponentPreviewLimitError('Component preview 节点数超过 10000');
631
- if (depth > 24)
632
- throw new ComponentPreviewLimitError('Component preview 嵌套深度超过 24');
633
- if (!value || typeof value !== 'object')
634
- return;
635
- if (seen.has(value))
636
- throw new ComponentPreviewLimitError('Component preview 不能包含循环引用');
637
- seen.add(value);
638
- if (Array.isArray(value)) {
639
- for (const item of value)
640
- visit(item, depth + 1);
641
- }
642
- else {
643
- for (const item of Object.values(value))
644
- visit(item, depth + 1);
645
- }
646
- };
647
- visit(output, 0);
648
- let serialized;
649
- try {
650
- serialized = JSON.stringify(output);
651
- }
652
- catch (error) {
653
- throw new ComponentPreviewLimitError(`Component preview 无法序列化: ${error instanceof Error ? error.message : String(error)}`);
654
- }
655
- if (Buffer.byteLength(serialized ?? '') > byteLimit) {
656
- throw new ComponentPreviewLimitError(`Component preview 超过 ${byteLimit} bytes`);
657
- }
658
- }
659
- // ---------------------------------------------------------------------------
660
- // agent sessions(legacy agent-sessions-rest-api.ts;ADR 0010 D3)
661
- // ---------------------------------------------------------------------------
662
- function registerAgentSessionRoutes(route, base, ctx) {
663
- const prefix = `${base}/agent/sessions/`;
664
- const acquireSessionTree = () => {
665
- const lease = ctx.acquireAgentRuntime?.();
666
- const tree = lease?.value.sessionTree;
667
- if (!lease || !tree || typeof tree.resolveActiveSessionId !== 'function') {
668
- lease?.release();
669
- return null;
670
- }
671
- return { tree, release: () => lease.release() };
672
- };
673
- const unavailable = (response) => {
674
- writeJson(response, 503, {
675
- success: false,
676
- error: 'Agent session tree runtime 未就绪(新 Runtime 需经 acquireAgentRuntime 装配 sessionTree)',
677
- });
678
- };
679
- // HttpHost 无 `:param` 路由,用前缀路由匹配 `<sessionKey>/tree|message|leaf`。
680
- route('GET', `${base}/agent/sessions/*`, async (_request, response, url) => {
681
- const messageAction = parseSessionAction(url.pathname, prefix, 'message');
682
- if (messageAction)
683
- response.setHeader('Cache-Control', 'no-store');
684
- const parsed = messageAction ?? parseSessionAction(url.pathname, prefix, 'tree');
685
- if (!parsed) {
686
- writeJson(response, 404, { success: false, error: 'Not found' });
687
- return;
688
- }
689
- const messageId = Number(url.searchParams.get('messageId'));
690
- const expectedSessionId = url.searchParams.get('sessionId');
691
- if (messageAction && (!Number.isSafeInteger(messageId) || messageId < 1 || !expectedSessionId)) {
692
- writeJson(response, 400, { success: false, error: '需要 sessionId 和正安全整数 messageId' });
693
- return;
694
- }
695
- const lease = acquireSessionTree();
696
- if (!lease) {
697
- unavailable(response);
698
- return;
699
- }
700
- const runtime = lease.tree;
701
- try {
702
- if (messageAction && !runtime.readBranchMessage) {
703
- writeJson(response, 501, { success: false, error: '当前 Agent runtime 不支持读取完整消息' });
704
- return;
705
- }
706
- const sessionId = await runtime.resolveActiveSessionId(parsed.sessionKey);
707
- if (messageAction) {
708
- if (!sessionId) {
709
- writeJson(response, 404, { success: false, error: '未找到活跃会话' });
710
- return;
711
- }
712
- if (sessionId !== expectedSessionId) {
713
- writeJson(response, 409, { success: false, error: '会话已变化,请重新读取会话列表' });
714
- return;
715
- }
716
- const text = await runtime.readBranchMessage(sessionId, messageId);
717
- if (text === null) {
718
- writeJson(response, 404, { success: false, error: '未找到此会话的用户消息' });
719
- return;
720
- }
721
- writeJson(response, 200, { success: true, data: { messageId, text } });
722
- return;
723
- }
724
- if (!sessionId) {
725
- if (!isRuntimeImSessionKey(parsed.sessionKey)) {
726
- writeJson(response, 404, {
727
- success: false,
728
- error: `未找到会话:${parsed.sessionKey}`,
729
- });
730
- return;
731
- }
732
- const knownConversation = await ctx.isKnownConversationSession?.(parsed.sessionKey);
733
- if (knownConversation === false) {
734
- writeJson(response, 404, {
735
- success: false,
736
- error: `未找到会话:${parsed.sessionKey}`,
737
- });
738
- return;
739
- }
740
- // 渠道会话与 Agent 会话的生命周期不同:用户可以先在 Console
741
- // 选中一个真实渠道会话,而该会话尚未触发过 Agent。树查询是只读操作,
742
- // 因而将这种正常状态返回为可渲染空态,而不是误报资源不存在。
743
- writeJson(response, 200, {
744
- success: true,
745
- data: {
746
- state: 'not_started',
747
- sessionKey: parsed.sessionKey,
748
- sessionId: null,
749
- activeLeafMessageId: null,
750
- points: [],
751
- },
752
- });
753
- return;
754
- }
755
- const activeLeafMessageId = await runtime.getActiveLeafMessageId(sessionId);
756
- const points = await runtime.listBranchPoints(sessionId);
757
- writeJson(response, 200, {
758
- success: true,
759
- data: {
760
- state: 'active',
761
- sessionKey: parsed.sessionKey,
762
- sessionId,
763
- activeLeafMessageId,
764
- points,
765
- },
766
- });
767
- }
768
- finally {
769
- lease.release();
770
- }
771
- }, { summary: 'Get agent session tree or message', tags: ['console', 'agent'] });
772
- route('POST', `${base}/agent/sessions/*`, async (request, response, url, authScope) => {
773
- const parsed = parseSessionAction(url.pathname, prefix, 'leaf');
774
- if (!parsed) {
775
- writeJson(response, 404, { success: false, error: 'Not found' });
776
- return;
777
- }
778
- if (!requireWriteScope(response, ctx, authScope))
779
- return;
780
- let body;
781
- try {
782
- body = (await readJsonBody(request) ?? {});
783
- }
784
- catch (error) {
785
- if (error instanceof HttpBodyError) {
786
- writeJson(response, error.statusCode, { success: false, error: error.message });
787
- return;
788
- }
789
- throw error;
790
- }
791
- const messageIdRaw = body.messageId;
792
- const indexRaw = body.index;
793
- let messageId;
794
- let branchIndex;
795
- if (messageIdRaw != null && messageIdRaw !== '') {
796
- const value = Number(messageIdRaw);
797
- if (!Number.isFinite(value) || value < 1) {
798
- writeJson(response, 400, { success: false, error: 'messageId 须为正整数' });
799
- return;
800
- }
801
- messageId = value;
802
- }
803
- else if (indexRaw != null && indexRaw !== '') {
804
- const value = Number(indexRaw);
805
- if (!Number.isFinite(value) || value < 1) {
806
- writeJson(response, 400, { success: false, error: 'index 须为正整数' });
807
- return;
808
- }
809
- branchIndex = value;
810
- }
811
- else {
812
- writeJson(response, 400, { success: false, error: '需要 messageId 或 index 之一' });
813
- return;
814
- }
815
- const lease = acquireSessionTree();
816
- if (!lease) {
817
- unavailable(response);
818
- return;
819
- }
820
- const runtime = lease.tree;
821
- try {
822
- const sessionId = await runtime.resolveActiveSessionId(parsed.sessionKey);
823
- if (!sessionId) {
824
- writeJson(response, 404, {
825
- success: false,
826
- error: `未找到活跃会话:${parsed.sessionKey}`,
827
- });
828
- return;
829
- }
830
- if (branchIndex !== undefined) {
831
- const result = await runtime.jumpToBranchIndex(sessionId, branchIndex);
832
- const activeLeafMessageId = await runtime.getActiveLeafMessageId(sessionId);
833
- writeJson(response, result.ok ? 200 : 400, {
834
- success: result.ok,
835
- message: result.message,
836
- data: {
837
- sessionKey: parsed.sessionKey,
838
- sessionId,
839
- activeLeafMessageId,
840
- },
841
- });
842
- return;
843
- }
844
- const ok = await runtime.switchActiveLeaf(sessionId, messageId);
845
- const activeLeafMessageId = await runtime.getActiveLeafMessageId(sessionId);
846
- writeJson(response, ok ? 200 : 400, {
847
- success: ok,
848
- message: ok ? `已切换 active leaf 至消息 #${messageId}` : '切换失败',
849
- data: {
850
- sessionKey: parsed.sessionKey,
851
- sessionId,
852
- activeLeafMessageId,
853
- },
854
- });
855
- }
856
- finally {
857
- lease.release();
858
- }
859
- }, { summary: 'Switch agent session active leaf', tags: ['console', 'agent'] });
860
- }
861
- /** Runtime IM session keys are `<platform>:<endpoint>:<scope>:<sceneId>`. */
862
- function isRuntimeImSessionKey(value) {
863
- const first = value.indexOf(':');
864
- const second = value.indexOf(':', first + 1);
865
- const third = value.indexOf(':', second + 1);
866
- return first > 0
867
- && second > first + 1
868
- && third > second + 1
869
- && third < value.length - 1;
870
- }
871
- function parseSessionAction(pathname, prefix, action) {
872
- if (!pathname.startsWith(prefix))
873
- return null;
874
- const rest = pathname.slice(prefix.length).replace(/\/+$/u, '');
875
- const suffix = `/${action}`;
876
- if (!rest.endsWith(suffix))
877
- return null;
878
- const rawKey = rest.slice(0, -suffix.length);
879
- if (!rawKey)
880
- return null;
881
- return { sessionKey: decodePathParam(rawKey) };
882
- }
883
- // ---------------------------------------------------------------------------
884
- // 内部分页 / 过滤
885
- // ---------------------------------------------------------------------------
886
- function paginateItems(all, page, pageSize) {
887
- const total = all.length;
888
- const totalPages = Math.max(1, Math.ceil(total / pageSize) || 1);
889
- const safePage = Math.min(Math.max(1, page), totalPages);
890
- const start = (safePage - 1) * pageSize;
891
- return {
892
- items: all.slice(start, start + pageSize),
893
- page: safePage,
894
- pageSize,
895
- total,
896
- totalPages,
897
- };
898
- }
899
- function filterByFields(items, filter, fields) {
900
- if (!filter?.trim())
901
- return items;
902
- return items.filter((item) => {
903
- const blob = fields
904
- .map((f) => f(item) ?? '')
905
- .join(' ')
906
- .toLowerCase();
907
- return blob.includes(filter.trim().toLowerCase());
908
- });
909
- }
910
- function stringProp(value, key) {
911
- if (!value || typeof value !== 'object')
912
- return undefined;
913
- const prop = value[key];
914
- return typeof prop === 'string' ? prop : undefined;
915
- }
916
- function parsePositiveInt(raw, fallback) {
917
- if (raw && /^\d+$/u.test(raw))
918
- return Math.max(1, parseInt(raw, 10));
919
- return fallback;
920
- }
921
- // ---------------------------------------------------------------------------
922
- // helpers
923
- // ---------------------------------------------------------------------------
924
- /** 写操作门禁:demo 部署(ctx.fullScope=false)或 demo token 均 403。 */
925
- function requireWriteScope(response, ctx, authScope) {
926
- if (ctx.fullScope && authScope === 'full')
927
- return true;
928
- writeJson(response, 403, {
929
- success: false,
930
- error: 'Demo scope: 只读访问,写操作被拒绝',
931
- });
932
- return false;
933
- }
934
- function decodePathParam(raw) {
935
- try {
936
- return decodeURIComponent(raw);
937
- }
938
- catch {
939
- return raw;
940
- }
941
- }
942
- function trimTrailingSlashes(s) {
943
- let i = s.length;
944
- while (i > 0 && s[i - 1] === '/')
945
- i--;
946
- return s.slice(0, i);
947
- }
948
- function normalizeBase(value) {
949
- if (!value.startsWith('/'))
950
- return `/${value}`;
951
- return trimTrailingSlashes(value) || '/api';
952
- }
953
- function writeJson(response, status, body) {
954
- const payload = JSON.stringify(body);
955
- response.writeHead(status, {
956
- 'content-type': 'application/json; charset=utf-8',
957
- 'content-length': Buffer.byteLength(payload),
958
- });
959
- response.end(payload);
960
- }
@@ -0,0 +1,21 @@
1
+ import { type ConsoleConfigApplicationStatus } from '@zhin.js/console-protocol';
2
+ import type { ConversationAddress, RuntimeConsoleRpcContext, RuntimeConsoleRpcMessage, RuntimeConsoleRpcReply } from './console-rpc.js';
3
+ /** Canonical Console wire fields → runtime conversation address. */
4
+ export declare function wireConversation(channelType: string, channelId: string, parent: unknown): ConversationAddress;
5
+ /**
6
+ * Structural check for `config:patch-project` operations at the RPC boundary, so no project
7
+ * port implementation receives a malformed request. The store repeats the full validation.
8
+ */
9
+ export declare function invalidProjectPatchOperations(patches: readonly unknown[]): string | undefined;
10
+ export declare function dispatchHostResourceConsoleRpc(type: string, message: RuntimeConsoleRpcMessage, ctx: RuntimeConsoleRpcContext, requestId: number | string | undefined, payloads: RuntimeConsoleRpcReply[]): Promise<boolean>;
11
+ export declare function stringField(message: RuntimeConsoleRpcMessage, key: string): string;
12
+ export declare function recordField(value: unknown): Record<string, unknown> | undefined;
13
+ export declare function positiveInteger(value: unknown, fallback: number): number;
14
+ export declare function errorMessage(error: unknown): string;
15
+ export declare function errorReply(payloads: RuntimeConsoleRpcReply[], requestId: number | string | undefined, error: string): RuntimeConsoleRpcReply[];
16
+ /** HTTP paths under apiBase that demo scope may call (ADR 0016). */
17
+ export declare function isDemoHttpAllowed(method: string, pathname: string, apiBase: string): boolean;
18
+ export declare function configurationSaveMessage(result: {
19
+ restartRequired: boolean;
20
+ revision?: string;
21
+ } & ConsoleConfigApplicationStatus): string;