@zhin.js/host-http 1.2.2 → 1.4.0
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/access-control.d.ts +41 -3
- package/lib/access-control.js +112 -10
- package/lib/console-rest-pages-helpers.js +24 -2
- package/lib/console-rest-pages.d.ts +4 -1
- package/lib/console-rest-pages.js +49 -23
- package/lib/console-rpc-extended/contracts.d.ts +22 -0
- package/lib/console-rpc-extended/dispatcher.js +1 -1
- package/lib/console-rpc-extended/endpoint-rpc.js +21 -4
- package/lib/console-rpc-extended/index.d.ts +1 -1
- package/lib/console-rpc-extended/schedule-rpc.d.ts +1 -1
- package/lib/console-rpc-extended/schedule-rpc.js +78 -12
- package/lib/console-rpc-handlers.d.ts +9 -1
- package/lib/console-rpc-handlers.js +108 -12
- package/lib/console-rpc-types.d.ts +2 -0
- package/lib/console-rpc-types.js +43 -0
- package/lib/console-rpc.d.ts +29 -1
- package/lib/console-rpc.js +13 -5
- package/lib/host-installer.js +10 -5
- package/lib/http-host.js +5 -4
- package/lib/index.d.ts +6 -3
- package/lib/index.js +4 -1
- package/lib/node/listener.js +2 -1
- package/lib/port.d.ts +27 -0
- package/lib/port.js +68 -0
- package/lib/project-files.d.ts +7 -0
- package/lib/project-files.js +88 -11
- package/lib/unknown-rpc.d.ts +12 -0
- package/lib/unknown-rpc.js +33 -0
- package/package.json +4 -4
- package/src/access-control.ts +151 -11
- package/src/console-rest-pages-helpers.ts +27 -3
- package/src/console-rest-pages.ts +52 -15
- package/src/console-rpc-extended/contracts.ts +18 -0
- package/src/console-rpc-extended/dispatcher.ts +1 -1
- package/src/console-rpc-extended/endpoint-rpc.ts +28 -4
- package/src/console-rpc-extended/index.ts +1 -0
- package/src/console-rpc-extended/schedule-rpc.ts +67 -3
- package/src/console-rpc-handlers.ts +110 -11
- package/src/console-rpc-types.ts +45 -0
- package/src/console-rpc.ts +39 -5
- package/src/host-installer.ts +10 -4
- package/src/http-host.ts +6 -2
- package/src/index.ts +21 -0
- package/src/node/listener.ts +2 -1
- package/src/port.ts +60 -0
- package/src/project-files.ts +95 -11
- package/src/unknown-rpc.ts +44 -0
|
@@ -2,7 +2,33 @@ import { parseCron } from '@zhin.js/schedule';
|
|
|
2
2
|
import { errorMessage } from './rpc-values.js';
|
|
3
3
|
const CRON_NOT_WIRED = '持久化调度未接线:当前 Plugin Runtime ScheduleHost 仅支持插件注册的内存任务(list),' +
|
|
4
4
|
'不支持 add/remove/pause/resume(需要 @zhin.js/agent 持久化调度引擎)';
|
|
5
|
-
|
|
5
|
+
/** `titleI18n[locale]` → `titleI18n[language]` → `title`. */
|
|
6
|
+
function localizedTitle(job, locale) {
|
|
7
|
+
const map = job.titleI18n && typeof job.titleI18n === 'object' ? job.titleI18n : undefined;
|
|
8
|
+
if (locale && map) {
|
|
9
|
+
const exact = map[locale];
|
|
10
|
+
if (typeof exact === 'string' && exact.trim())
|
|
11
|
+
return exact.trim();
|
|
12
|
+
const language = locale.split('-')[0];
|
|
13
|
+
const byLanguage = map[language];
|
|
14
|
+
if (typeof byLanguage === 'string' && byLanguage.trim())
|
|
15
|
+
return byLanguage.trim();
|
|
16
|
+
}
|
|
17
|
+
return typeof job.title === 'string' && job.title.trim() ? job.title.trim() : undefined;
|
|
18
|
+
}
|
|
19
|
+
function lastRunFields(job) {
|
|
20
|
+
const run = job.lastRun;
|
|
21
|
+
if (!run || typeof run !== 'object' || typeof run.startedAt !== 'number')
|
|
22
|
+
return {};
|
|
23
|
+
return {
|
|
24
|
+
lastExecutedAt: run.startedAt,
|
|
25
|
+
...(typeof run.finishedAt === 'number' ? { lastFinishedAt: run.finishedAt, lastDurationMs: Math.max(0, run.finishedAt - run.startedAt) } : {}),
|
|
26
|
+
lastStatus: run.status === 'failed' ? 'failed' : 'succeeded',
|
|
27
|
+
...(run.status === 'failed' && typeof run.error === 'string' ? { lastError: run.error } : {}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function listSchedule(ctx, d = {}) {
|
|
31
|
+
const locale = typeof d.locale === 'string' && d.locale.trim() ? d.locale.trim() : undefined;
|
|
6
32
|
const host = ctx.scheduleHost;
|
|
7
33
|
if (!host || typeof host.list !== 'function') {
|
|
8
34
|
return { error: '调度服务未配置(scheduleHost 未挂载)' };
|
|
@@ -13,17 +39,26 @@ export function listSchedule(ctx) {
|
|
|
13
39
|
.filter((job) => !!job && typeof job === 'object'
|
|
14
40
|
&& typeof job.id === 'string'
|
|
15
41
|
&& typeof job.cron === 'string')
|
|
16
|
-
.map((job) =>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
? {
|
|
26
|
-
|
|
42
|
+
.map((job) => {
|
|
43
|
+
const title = localizedTitle(job, locale);
|
|
44
|
+
return {
|
|
45
|
+
id: job.id,
|
|
46
|
+
cron: job.cron,
|
|
47
|
+
...(typeof job.description === 'string' ? { description: job.description } : {}),
|
|
48
|
+
// #1250: plugin polling is not a user reminder; title is localized, displayName never falls back to a bare id when a title exists.
|
|
49
|
+
kind: job.kind === 'user-task' ? 'user-task' : 'plugin-internal',
|
|
50
|
+
...(title ? { title } : {}),
|
|
51
|
+
...(job.titleI18n && typeof job.titleI18n === 'object' ? { titleI18n: job.titleI18n } : {}),
|
|
52
|
+
displayName: title ?? (typeof job.description === 'string' && job.description ? job.description : job.id),
|
|
53
|
+
...lastRunFields(job),
|
|
54
|
+
// console cron 页渲染字段(由 schedule host 提供时透传)
|
|
55
|
+
expression: job.expression ?? job.cron,
|
|
56
|
+
running: job.running ?? true,
|
|
57
|
+
...(job.plugin ? { plugin: job.plugin } : {}),
|
|
58
|
+
...(job.nextExecution
|
|
59
|
+
? { nextExecution: job.nextExecution } : {}),
|
|
60
|
+
};
|
|
61
|
+
});
|
|
27
62
|
return listPersistentJobs(ctx).then((persistent) => ({ data: { memory, persistent, capabilities: { persistent: !!ctx.resolveScheduleEngine?.() && ctx.fullScope } } })).catch((error) => ({ error: errorMessage(error) }));
|
|
28
63
|
}
|
|
29
64
|
catch (error) {
|
|
@@ -44,10 +79,14 @@ async function listPersistentJobs(ctx) {
|
|
|
44
79
|
prompt: job.action?.prompt ?? '',
|
|
45
80
|
enabled: job.enabled !== false,
|
|
46
81
|
source: job.source ?? 'manual',
|
|
82
|
+
// Persistent jobs are created by a person or on their behalf by the Agent.
|
|
83
|
+
kind: 'user-task',
|
|
47
84
|
createdAt: job.createdAt,
|
|
48
85
|
nextExecution: job.state?.nextRunAtMs,
|
|
49
86
|
...(job.notify?.target
|
|
50
87
|
? { context: { target: job.notify.target } } : {}),
|
|
88
|
+
// #1250: the resolved delivery target; `resolved: false` = results cannot be delivered.
|
|
89
|
+
...(ctx.describeScheduleTarget ? { target: ctx.describeScheduleTarget(job.notify) } : {}),
|
|
51
90
|
lastExecutedAt: job.state?.lastExecutedAt,
|
|
52
91
|
lastStatus: job.state?.lastStatus,
|
|
53
92
|
}));
|
|
@@ -56,6 +95,23 @@ async function listPersistentJobs(ctx) {
|
|
|
56
95
|
throw new Error(`读取持久化任务失败: ${errorMessage(error)}`);
|
|
57
96
|
}
|
|
58
97
|
}
|
|
98
|
+
/** The account (`adapter` / `endpoint`) a schedule delivery target sends from, if it names one. */
|
|
99
|
+
function deliveryAccount(target) {
|
|
100
|
+
if (typeof target === 'string') {
|
|
101
|
+
// `<adapter>:<endpoint>:<kind>:<id>`
|
|
102
|
+
const [adapter, endpoint] = target.split(':');
|
|
103
|
+
return adapter && endpoint ? { adapter, endpoint } : undefined;
|
|
104
|
+
}
|
|
105
|
+
if (!target || typeof target !== 'object')
|
|
106
|
+
return undefined;
|
|
107
|
+
const scene = target.scene;
|
|
108
|
+
if (!scene || typeof scene !== 'object')
|
|
109
|
+
return undefined;
|
|
110
|
+
const { platform, endpointKey } = scene;
|
|
111
|
+
return typeof platform === 'string' && platform && typeof endpointKey === 'string' && endpointKey
|
|
112
|
+
? { adapter: platform, endpoint: endpointKey }
|
|
113
|
+
: undefined;
|
|
114
|
+
}
|
|
59
115
|
export async function addCron(d, ctx) {
|
|
60
116
|
const engine = ctx.resolveScheduleEngine?.();
|
|
61
117
|
if (!engine)
|
|
@@ -75,6 +131,16 @@ export async function addCron(d, ctx) {
|
|
|
75
131
|
const label = typeof d.label === 'string' && d.label.trim() ? d.label.trim() : undefined;
|
|
76
132
|
const context = (d.context ?? {});
|
|
77
133
|
const target = context.target ?? context.channel;
|
|
134
|
+
// A delivery target sends to that account on every run: `endpoint.operate` there, like
|
|
135
|
+
// `/message/send`, on top of `ops.schedule.write` (absent check = trusted in-process caller).
|
|
136
|
+
if (target && ctx.canOperateEndpoint) {
|
|
137
|
+
const account = deliveryAccount(target);
|
|
138
|
+
if (!account || !ctx.canOperateEndpoint(account.adapter, account.endpoint)) {
|
|
139
|
+
return { error: account
|
|
140
|
+
? `没有向该账号投递的权限:需要 ${account.adapter} / ${account.endpoint} 上的 endpoint.operate`
|
|
141
|
+
: '投递目标需要指明账号(scene.platform 与 scene.endpointKey)' };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
78
144
|
const job = {
|
|
79
145
|
id: `console-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`,
|
|
80
146
|
...(label ? { label } : {}),
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import { type ConsoleConfigApplicationStatus } from '@zhin.js/console-protocol';
|
|
1
|
+
import { type ConsoleConfigApplicationStatus, type ConsoleSendFailure } from '@zhin.js/console-protocol';
|
|
2
2
|
import type { ConversationAddress, RuntimeConsoleRpcContext, RuntimeConsoleRpcMessage, RuntimeConsoleRpcReply } from './console-rpc.js';
|
|
3
3
|
/** Canonical Console wire fields → runtime conversation address. */
|
|
4
4
|
export declare function wireConversation(channelType: string, channelId: string, parent: unknown): ConversationAddress;
|
|
5
|
+
/**
|
|
6
|
+
* Structural read of an outbound failure (#1250): Core's `EndpointDeliveryError` carries `code`,
|
|
7
|
+
* `disposition` and the canonical receipt; host/http must not import Core, so only those fields are
|
|
8
|
+
* read. `endpoint not found` is the Endpoint directory's plain error. An error counts as a send
|
|
9
|
+
* failure only when its `code` is a known delivery code or it carries a `disposition` / `receipt`;
|
|
10
|
+
* anything else (a Node `ENOENT`, a database error, …) is unclassified and keeps its own message.
|
|
11
|
+
*/
|
|
12
|
+
export declare function consoleSendFailure(error: unknown): ConsoleSendFailure | undefined;
|
|
5
13
|
/**
|
|
6
14
|
* Structural check for `config:patch-project` operations at the RPC boundary, so no project
|
|
7
15
|
* port implementation receives a malformed request. The store repeats the full validation.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CONFIG_PATCH_LIMITS, ENDPOINT_RPC, } from '@zhin.js/console-protocol';
|
|
1
|
+
import { CONFIG_PATCH_LIMITS, CONSOLE_SEND_FAILURE_MESSAGES, ENDPOINT_RPC, classifyConsoleSendFailure, endpointTransportLabel, } from '@zhin.js/console-protocol';
|
|
2
2
|
/** Canonical Console wire fields → runtime conversation address. */
|
|
3
3
|
export function wireConversation(channelType, channelId, parent) {
|
|
4
4
|
const kind = channelType === 'group' || channelType === 'channel' ? channelType : 'private';
|
|
@@ -13,6 +13,43 @@ export function wireConversation(channelType, channelId, parent) {
|
|
|
13
13
|
...(parentKind && parentId ? { parent: { kind: parentKind, id: parentId } } : {}),
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
+
const SEND_FAILURE_STATUSES = new Set(['failed', 'rejected', 'unsupported', 'suppressed']);
|
|
17
|
+
/**
|
|
18
|
+
* Structural read of an outbound failure (#1250): Core's `EndpointDeliveryError` carries `code`,
|
|
19
|
+
* `disposition` and the canonical receipt; host/http must not import Core, so only those fields are
|
|
20
|
+
* read. `endpoint not found` is the Endpoint directory's plain error. An error counts as a send
|
|
21
|
+
* failure only when its `code` is a known delivery code or it carries a `disposition` / `receipt`;
|
|
22
|
+
* anything else (a Node `ENOENT`, a database error, …) is unclassified and keeps its own message.
|
|
23
|
+
*/
|
|
24
|
+
export function consoleSendFailure(error) {
|
|
25
|
+
if (!(error instanceof Error))
|
|
26
|
+
return undefined;
|
|
27
|
+
const record = error;
|
|
28
|
+
if (typeof record.code !== 'string' || !record.code) {
|
|
29
|
+
return error.message === 'endpoint not found'
|
|
30
|
+
? { code: 'endpoint_not_found', reason: 'endpoint_not_found', status: 'failed' }
|
|
31
|
+
: undefined;
|
|
32
|
+
}
|
|
33
|
+
// Every known delivery code maps to a reason other than the catch-all `failed`.
|
|
34
|
+
const knownCode = classifyConsoleSendFailure(record.code) !== 'failed';
|
|
35
|
+
const delivery = typeof record.disposition === 'string' || (!!record.receipt && typeof record.receipt === 'object');
|
|
36
|
+
if (!knownCode && !delivery)
|
|
37
|
+
return undefined;
|
|
38
|
+
const receipt = record.receipt && typeof record.receipt === 'object'
|
|
39
|
+
? record.receipt
|
|
40
|
+
: undefined;
|
|
41
|
+
const status = record.disposition === 'unknown' || receipt?.failure?.deliveryUnknown === true
|
|
42
|
+
? 'unknown'
|
|
43
|
+
: typeof receipt?.status === 'string' && SEND_FAILURE_STATUSES.has(receipt.status)
|
|
44
|
+
? receipt.status
|
|
45
|
+
: record.disposition === 'rejected' ? 'rejected' : 'failed';
|
|
46
|
+
return {
|
|
47
|
+
code: record.code,
|
|
48
|
+
reason: classifyConsoleSendFailure(record.code, status),
|
|
49
|
+
status,
|
|
50
|
+
...(receipt?.failure?.retryable === true ? { retryable: true } : {}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
16
53
|
const FORBIDDEN_PATCH_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
17
54
|
/**
|
|
18
55
|
* Structural check for `config:patch-project` operations at the RPC boundary, so no project
|
|
@@ -129,12 +166,12 @@ export async function dispatchHostResourceConsoleRpc(type, message, ctx, request
|
|
|
129
166
|
emit({ requestId, error: 'filename is required' });
|
|
130
167
|
return true;
|
|
131
168
|
}
|
|
132
|
-
|
|
169
|
+
// Masked only (issue #1250): the file as written is never answered here.
|
|
170
|
+
if (!ctx.readEnvFileView) {
|
|
133
171
|
emit({ requestId, error: 'Env read is not configured' });
|
|
134
172
|
return true;
|
|
135
173
|
}
|
|
136
|
-
|
|
137
|
-
emit({ requestId, data: { content } });
|
|
174
|
+
emit({ requestId, data: await ctx.readEnvFileView(filename) });
|
|
138
175
|
}
|
|
139
176
|
catch (error) {
|
|
140
177
|
emit({
|
|
@@ -144,23 +181,71 @@ export async function dispatchHostResourceConsoleRpc(type, message, ctx, request
|
|
|
144
181
|
}
|
|
145
182
|
return true;
|
|
146
183
|
}
|
|
184
|
+
case 'env:reveal': {
|
|
185
|
+
try {
|
|
186
|
+
const filename = String(message.filename ?? '');
|
|
187
|
+
const key = message.key;
|
|
188
|
+
if (!filename) {
|
|
189
|
+
emit({ requestId, error: 'filename is required' });
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
if (typeof key !== 'string' || !key) {
|
|
193
|
+
emit({ requestId, error: 'key is required' });
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
if (!ctx.revealEnvVariable) {
|
|
197
|
+
emit({ requestId, error: 'Env reveal is not configured' });
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
emit({ requestId, data: await ctx.revealEnvVariable(filename, key) });
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
emit({
|
|
204
|
+
requestId,
|
|
205
|
+
error: `Failed to reveal env variable: ${error instanceof Error ? error.message : String(error)}`,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
147
210
|
case 'env:save': {
|
|
148
211
|
try {
|
|
149
212
|
const filename = String(message.filename ?? '');
|
|
150
|
-
const content = message.content;
|
|
151
213
|
if (!filename) {
|
|
152
214
|
emit({ requestId, error: 'filename is required' });
|
|
153
215
|
return true;
|
|
154
216
|
}
|
|
155
|
-
|
|
156
|
-
|
|
217
|
+
const hasChanges = message.changes !== undefined;
|
|
218
|
+
const hasContent = message.content !== undefined;
|
|
219
|
+
if (hasChanges && hasContent) {
|
|
220
|
+
emit({ requestId, error: 'changes 与 content 只能提供一个' });
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
if (hasChanges) {
|
|
224
|
+
if (!ctx.updateEnvVariables) {
|
|
225
|
+
emit({ requestId, error: 'Env write is not configured' });
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
const { changed } = await ctx.updateEnvVariables(filename, message.changes);
|
|
229
|
+
emit({ requestId, data: { success: true, message: `环境文件已保存: ${filename}`, changed } });
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
if (typeof message.content !== 'string') {
|
|
233
|
+
emit({ requestId, error: 'changes field is required' });
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
// The whole-file form must be asked for: an older Console sends back what `env:get` showed it.
|
|
237
|
+
if (message.mode !== 'replace') {
|
|
238
|
+
emit({
|
|
239
|
+
requestId,
|
|
240
|
+
error: '此 Host 只接受逐键保存(changes);整文件保存需显式声明 mode: "replace"。请升级 Console 后重试',
|
|
241
|
+
});
|
|
157
242
|
return true;
|
|
158
243
|
}
|
|
159
|
-
if (!ctx.
|
|
244
|
+
if (!ctx.replaceEnvFile) {
|
|
160
245
|
emit({ requestId, error: 'Env write is not configured' });
|
|
161
246
|
return true;
|
|
162
247
|
}
|
|
163
|
-
await ctx.
|
|
248
|
+
await ctx.replaceEnvFile(filename, message.content);
|
|
164
249
|
emit({ requestId, data: { success: true, message: `环境文件已保存: ${filename}` } });
|
|
165
250
|
}
|
|
166
251
|
catch (error) {
|
|
@@ -510,7 +595,7 @@ export async function dispatchHostResourceConsoleRpc(type, message, ctx, request
|
|
|
510
595
|
case 'endpoint.list': {
|
|
511
596
|
try {
|
|
512
597
|
const endpoints = ctx.listEndpoints ? await ctx.listEndpoints() : [];
|
|
513
|
-
emit({ requestId, data: { endpoints } });
|
|
598
|
+
emit({ requestId, data: { endpoints: endpoints.map(withTransportLabel) } });
|
|
514
599
|
}
|
|
515
600
|
catch (error) {
|
|
516
601
|
emit({
|
|
@@ -538,7 +623,7 @@ export async function dispatchHostResourceConsoleRpc(type, message, ctx, request
|
|
|
538
623
|
emit({ requestId, error: 'endpoint not found' });
|
|
539
624
|
return true;
|
|
540
625
|
}
|
|
541
|
-
emit({ requestId, data: endpoint });
|
|
626
|
+
emit({ requestId, data: withTransportLabel(endpoint) });
|
|
542
627
|
}
|
|
543
628
|
catch (error) {
|
|
544
629
|
emit({
|
|
@@ -628,9 +713,11 @@ export async function dispatchHostResourceConsoleRpc(type, message, ctx, request
|
|
|
628
713
|
emit({ requestId, data: { messageId: result.messageId } });
|
|
629
714
|
}
|
|
630
715
|
catch (error) {
|
|
716
|
+
const failure = consoleSendFailure(error);
|
|
631
717
|
emit({
|
|
632
718
|
requestId,
|
|
633
|
-
error: error instanceof Error ? error.message : String(error),
|
|
719
|
+
error: failure ? CONSOLE_SEND_FAILURE_MESSAGES[failure.reason] : error instanceof Error ? error.message : String(error),
|
|
720
|
+
...(failure ? { data: failure } : {}),
|
|
634
721
|
});
|
|
635
722
|
}
|
|
636
723
|
return true;
|
|
@@ -741,3 +828,12 @@ export function configurationSaveMessage(result) {
|
|
|
741
828
|
}
|
|
742
829
|
return result.restartRequired ? '配置已保存,需重启进程才能生效' : '配置已保存';
|
|
743
830
|
}
|
|
831
|
+
/** Adds the readable `transportLabel` next to the raw `transportState` of an Endpoint row (#1250). */
|
|
832
|
+
function withTransportLabel(endpoint) {
|
|
833
|
+
if (!endpoint || typeof endpoint !== 'object')
|
|
834
|
+
return endpoint;
|
|
835
|
+
const state = endpoint.transportState;
|
|
836
|
+
if (typeof state !== 'string')
|
|
837
|
+
return endpoint;
|
|
838
|
+
return { ...endpoint, transportLabel: endpointTransportLabel(state) };
|
|
839
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RPC types the runtime Console dispatcher of this package answers (`dispatchRuntimeConsoleRpc`:
|
|
3
|
+
* shell, configuration, plugin management, project files / environment / database, endpoints,
|
|
4
|
+
* schedules, inbox, login assist, workrooms). Used to tell an RPC this Host has but forgot to map
|
|
5
|
+
* (audited `unmapped`) from one it does not have at all (`unknown_rpc`, zhinjs/zhin#1250), and to
|
|
6
|
+
* build the Host's capability list. `console-rpc-types.test.ts` keeps it in step with the
|
|
7
|
+
* dispatchers' `case` labels.
|
|
8
|
+
*/
|
|
9
|
+
import { CONFIG_RPC, ENDPOINT_RPC, PLUGIN_RPC } from '@zhin.js/console-protocol';
|
|
10
|
+
export const RUNTIME_CONSOLE_RPC_TYPES = new Set([
|
|
11
|
+
// Shell
|
|
12
|
+
'ping', 'entries:get', 'pages:list',
|
|
13
|
+
// Configuration
|
|
14
|
+
...Object.values(CONFIG_RPC),
|
|
15
|
+
'workrooms:get', 'workrooms:set',
|
|
16
|
+
// Plugin management (the global switch is refused with a pointer to the gateway)
|
|
17
|
+
...Object.values(PLUGIN_RPC),
|
|
18
|
+
// Project files / environment / schemas / database / process
|
|
19
|
+
'files:tree', 'files:read', 'files:save',
|
|
20
|
+
'env:list', 'env:get', 'env:reveal', 'env:save',
|
|
21
|
+
'schema:get', 'schema:get-all',
|
|
22
|
+
'db:info', 'db:tables', 'db:select', 'db:query', 'db:execute',
|
|
23
|
+
'db:schema:describe', 'db:schema:preview', 'db:schema:apply',
|
|
24
|
+
'db:insert', 'db:update', 'db:delete', 'db:drop-table',
|
|
25
|
+
'db:kv:get', 'db:kv:set', 'db:kv:delete', 'db:kv:entries',
|
|
26
|
+
'system:restart',
|
|
27
|
+
// Endpoints
|
|
28
|
+
...Object.values(ENDPOINT_RPC),
|
|
29
|
+
// Schedules
|
|
30
|
+
'schedule:list', 'cron:list', 'cron:validate', 'cron:add', 'cron:remove', 'cron:pause', 'cron:resume',
|
|
31
|
+
// Requests / notices / inbox
|
|
32
|
+
'request.list', 'request.approve', 'request.reject', 'request.consumed', 'notice.consumed',
|
|
33
|
+
'inbox.requests', 'inbox.notices', 'inbox.messages', 'inbox.recent',
|
|
34
|
+
// Login assist
|
|
35
|
+
'login.list', 'login.submit', 'login.cancel', 'login.flow.list', 'login.flow.get', 'login.relogin', 'login.refresh',
|
|
36
|
+
// Workroom profiles / knowledge
|
|
37
|
+
'workroom.profile.status', 'workroom.profile.bootstrap', 'workroom.profile.pack.publish',
|
|
38
|
+
'workroom.profile.publish', 'workroom.profile.rollback', 'workroom.profile.policy.publish',
|
|
39
|
+
'workroom.knowledge.get', 'workroom.knowledge.publish', 'workroom.knowledge.rollback',
|
|
40
|
+
]);
|
|
41
|
+
export function isRuntimeConsoleRpcType(type) {
|
|
42
|
+
return typeof type === 'string' && RUNTIME_CONSOLE_RPC_TYPES.has(type);
|
|
43
|
+
}
|
package/lib/console-rpc.d.ts
CHANGED
|
@@ -101,9 +101,22 @@ export interface PluginVerificationResult {
|
|
|
101
101
|
readonly evidence: readonly string[];
|
|
102
102
|
readonly evidenceI18n?: import('@zhin.js/plugin-runtime').PluginVerificationOutcome['evidenceI18n'];
|
|
103
103
|
}
|
|
104
|
+
/** `inspect` options. `dependencies: 'skip'` leaves the project-wide graph check out (`dependencies: 'unchecked'`). */
|
|
105
|
+
export interface PluginInspectOptions {
|
|
106
|
+
readonly dependencies?: 'check' | 'skip';
|
|
107
|
+
}
|
|
108
|
+
/** Result of the project-wide dependency graph check (the same check `inspect` runs per plugin). */
|
|
109
|
+
export type PluginDependencyCheck = {
|
|
110
|
+
readonly status: 'passed';
|
|
111
|
+
} | {
|
|
112
|
+
readonly status: 'failed';
|
|
113
|
+
readonly error: string;
|
|
114
|
+
};
|
|
104
115
|
export interface PluginManagementPort {
|
|
105
116
|
explainConfiguration?(instanceKey: string, expectedRevision: string): Promise<ConsoleConfigExplanation>;
|
|
106
|
-
inspect?(packageName: string): Promise<PluginManagementState>;
|
|
117
|
+
inspect?(packageName: string, options?: PluginInspectOptions): Promise<PluginManagementState>;
|
|
118
|
+
/** Runs the project-wide dependency graph check once, for callers that inspect many plugins. */
|
|
119
|
+
checkDependencies?(): Promise<PluginDependencyCheck>;
|
|
107
120
|
apply?(packageName: string, expectedProjectRevision?: string): Promise<{
|
|
108
121
|
readonly requested: boolean;
|
|
109
122
|
readonly state: PluginManagementState;
|
|
@@ -193,8 +206,23 @@ export type RuntimeConsoleRpcContext = {
|
|
|
193
206
|
name: string;
|
|
194
207
|
exists: boolean;
|
|
195
208
|
}[]>;
|
|
209
|
+
/**
|
|
210
|
+
* @deprecated Not used any more: `env:get` answers {@link readEnvFileView} only, so the file as
|
|
211
|
+
* written never reaches the Console (issue #1250).
|
|
212
|
+
*/
|
|
196
213
|
readEnvFile?(filename: string): Promise<string>;
|
|
214
|
+
/** @deprecated Not used any more: `env:save` writes through {@link updateEnvVariables} / {@link replaceEnvFile}. */
|
|
197
215
|
writeEnvFile?(filename: string, content: string): Promise<void>;
|
|
216
|
+
/** `env:get`: masked view (key names, masks, lengths; comments and blank lines in place). */
|
|
217
|
+
readEnvFileView?(filename: string): Promise<import('@zhin.js/console-protocol').ConsoleEnvFileView>;
|
|
218
|
+
/** `env:reveal`: one variable as written. The route requires `ops.env.write` + `ops.env.read` and audits. */
|
|
219
|
+
revealEnvVariable?(filename: string, key: string): Promise<import('@zhin.js/console-protocol').ConsoleEnvRevealResult>;
|
|
220
|
+
/** `env:save { changes }`: validates and writes only the named keys (mask placeholders refused). */
|
|
221
|
+
updateEnvVariables?(filename: string, changes: unknown): Promise<{
|
|
222
|
+
readonly changed: readonly string[];
|
|
223
|
+
}>;
|
|
224
|
+
/** `env:save { content, mode: 'replace' }`: whole file; refused when it holds mask placeholders. */
|
|
225
|
+
replaceEnvFile?(filename: string, content: string): Promise<void>;
|
|
198
226
|
/** Optional schema accessors (plugin schema.json / Runtime schema registry). */
|
|
199
227
|
getSchema?(pluginName?: string): Promise<unknown>;
|
|
200
228
|
getAllSchemas?(): Promise<Record<string, unknown>>;
|
package/lib/console-rpc.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { CONFIG_PATCH_LIMITS, CONFIG_RPC, grantsAllow, } from '@zhin.js/console-protocol';
|
|
1
|
+
import { CONFIG_PATCH_LIMITS, CONFIG_RPC, CONSOLE_UNKNOWN_RPC_CODE, grantsAllow, } from '@zhin.js/console-protocol';
|
|
2
2
|
import { authorizeConsoleRpc, principalAllows } from './access-control.js';
|
|
3
|
+
import { isRuntimeConsoleRpcType } from './console-rpc-types.js';
|
|
3
4
|
import { dispatchExtendedConsoleRpc } from './console-rpc-extended/index.js';
|
|
4
5
|
import { dispatchPluginConsoleRpc } from './console-plugin-rpc.js';
|
|
5
6
|
import { configurationSaveMessage, dispatchHostResourceConsoleRpc, invalidProjectPatchOperations, } from './console-rpc-handlers.js';
|
|
@@ -34,9 +35,9 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
34
35
|
let canOperateEndpoint;
|
|
35
36
|
if (ctx.principal) {
|
|
36
37
|
const principal = ctx.principal;
|
|
37
|
-
const decision = authorizeConsoleRpc(principal, message);
|
|
38
|
+
const decision = authorizeConsoleRpc(principal, message, { registered: isRuntimeConsoleRpcType });
|
|
38
39
|
if (!decision.allowed) {
|
|
39
|
-
emit({ requestId, error: decision.error, code: decision.code });
|
|
40
|
+
emit({ requestId, error: decision.error, code: decision.code, ...(decision.reason ? { reason: decision.reason } : {}) });
|
|
40
41
|
return payloads;
|
|
41
42
|
}
|
|
42
43
|
authorizedEndpoint = scopedAuthorizedEndpoint(principal, decision);
|
|
@@ -282,7 +283,11 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
282
283
|
return payloads;
|
|
283
284
|
}
|
|
284
285
|
const projectPatch = message.type === CONFIG_RPC.PATCH_PROJECT;
|
|
285
|
-
const { instanceKey, expectedRevision, patches } = message;
|
|
286
|
+
const { instanceKey, expectedRevision, patches, secretStorage } = message;
|
|
287
|
+
if (secretStorage !== undefined && secretStorage !== 'env' && secretStorage !== 'plain') {
|
|
288
|
+
emit({ requestId, error: 'secretStorage 只能是 env 或 plain' });
|
|
289
|
+
return payloads;
|
|
290
|
+
}
|
|
286
291
|
if (projectPatch && instanceKey !== undefined) {
|
|
287
292
|
emit({ requestId, error: 'Project patches must not specify instanceKey' });
|
|
288
293
|
return payloads;
|
|
@@ -317,6 +322,7 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
317
322
|
...(instanceKey === undefined ? {} : { instanceKey }),
|
|
318
323
|
expectedRevision,
|
|
319
324
|
patches,
|
|
325
|
+
...(secretStorage === undefined ? {} : { secretStorage }),
|
|
320
326
|
});
|
|
321
327
|
}
|
|
322
328
|
catch (error) {
|
|
@@ -381,9 +387,11 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
381
387
|
});
|
|
382
388
|
}
|
|
383
389
|
catch (error) {
|
|
390
|
+
const code = error instanceof Error && 'code' in error && error.code === 'env-write-required' ? { code: 'env-write-required' } : {};
|
|
384
391
|
emit({
|
|
385
392
|
requestId,
|
|
386
393
|
error: `Failed to set config: ${error instanceof Error ? error.message : String(error)}`,
|
|
394
|
+
...code,
|
|
387
395
|
});
|
|
388
396
|
}
|
|
389
397
|
return payloads;
|
|
@@ -399,7 +407,7 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
399
407
|
});
|
|
400
408
|
return payloads;
|
|
401
409
|
}
|
|
402
|
-
emit({ requestId, error: `Unknown RPC type: ${type || '<empty>'}
|
|
410
|
+
emit({ requestId, error: `Unknown RPC type: ${type.slice(0, 64) || '<empty>'}`, code: CONSOLE_UNKNOWN_RPC_CODE });
|
|
403
411
|
return payloads;
|
|
404
412
|
}
|
|
405
413
|
}
|
package/lib/host-installer.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
3
3
|
import { isAbsolute, resolve } from 'node:path';
|
|
4
4
|
import { httpHostToken } from './http-host.js';
|
|
5
5
|
import { parseTrustProxy } from './trust-proxy.js';
|
|
6
|
+
import { parseHttpPort } from './port.js';
|
|
6
7
|
export async function resolveHttpConfig(config, envOverlay, projectRoot = process.cwd()) {
|
|
7
8
|
const document = await readConfigDocument(config);
|
|
8
9
|
if (!document || typeof document !== 'object')
|
|
@@ -11,18 +12,20 @@ export async function resolveHttpConfig(config, envOverlay, projectRoot = proces
|
|
|
11
12
|
if (!http || typeof http !== 'object')
|
|
12
13
|
return {};
|
|
13
14
|
const value = expandEnvironmentValue(http, (key) => envOverlay?.[key] ?? process.env[key]);
|
|
15
|
+
const stored = http;
|
|
14
16
|
if (value.tokens !== undefined && value.tokens !== null)
|
|
15
17
|
throw new Error(HTTP_TOKENS_REMOVED_MESSAGE);
|
|
16
18
|
const primary = Object.freeze({
|
|
17
19
|
host: typeof value.host === 'string' ? value.host : undefined,
|
|
18
|
-
|
|
20
|
+
// An invalid port is an error, never a silent fallback to the default (#1250).
|
|
21
|
+
port: parseHttpPort(value.port, 'http.port', stored.port),
|
|
19
22
|
token: typeof value.token === 'string' ? value.token : undefined,
|
|
20
23
|
corsOrigins: injectDesktopWebviewOrigins(parseStringList(value.corsOrigins), envOverlay),
|
|
21
24
|
apiBase: typeof value.base === 'string' ? value.base : undefined,
|
|
22
25
|
tls: await parseTlsConfig(value.tls, projectRoot),
|
|
23
26
|
trustProxy: parseTrustProxy(value.trustProxy),
|
|
24
27
|
});
|
|
25
|
-
const configuredListeners = await parseAdditionalListeners(value.listeners, primary, projectRoot);
|
|
28
|
+
const configuredListeners = await parseAdditionalListeners(value.listeners, primary, projectRoot, stored.listeners);
|
|
26
29
|
const listeners = await injectDesktopDeviceListener(configuredListeners, primary, envOverlay, projectRoot);
|
|
27
30
|
return Object.freeze({ ...primary, ...(listeners.length > 0 ? { listeners } : {}) });
|
|
28
31
|
}
|
|
@@ -71,7 +74,7 @@ async function injectDesktopDeviceListener(configured, primary, envOverlay, proj
|
|
|
71
74
|
}, projectRoot);
|
|
72
75
|
return Object.freeze([...configured, Object.freeze({ ...primary, host, port, tls })]);
|
|
73
76
|
}
|
|
74
|
-
async function parseAdditionalListeners(value, primary, projectRoot) {
|
|
77
|
+
async function parseAdditionalListeners(value, primary, projectRoot, stored) {
|
|
75
78
|
if (value === undefined)
|
|
76
79
|
return Object.freeze([]);
|
|
77
80
|
if (!Array.isArray(value))
|
|
@@ -87,13 +90,15 @@ async function parseAdditionalListeners(value, primary, projectRoot) {
|
|
|
87
90
|
if (typeof listener.host !== 'string' || !listener.host) {
|
|
88
91
|
throw new Error(`http.listeners[${index}].host is required`);
|
|
89
92
|
}
|
|
90
|
-
|
|
93
|
+
const storedPort = Array.isArray(stored) ? stored[index]?.port : undefined;
|
|
94
|
+
const port = parseHttpPort(listener.port, `http.listeners[${index}].port`, storedPort);
|
|
95
|
+
if (port === undefined) {
|
|
91
96
|
throw new Error(`http.listeners[${index}].port is required`);
|
|
92
97
|
}
|
|
93
98
|
listeners.push(Object.freeze({
|
|
94
99
|
...primary,
|
|
95
100
|
host: listener.host,
|
|
96
|
-
port
|
|
101
|
+
port,
|
|
97
102
|
tls: await parseTlsConfig(listener.tls, projectRoot),
|
|
98
103
|
}));
|
|
99
104
|
}
|
package/lib/http-host.js
CHANGED
|
@@ -12,7 +12,7 @@ import { createToken, generationAdmissionBinder, } from '@zhin.js/plugin-runtime
|
|
|
12
12
|
import { HttpBodyError, jsonResponse } from '@zhin.js/host-http-contract';
|
|
13
13
|
import { TokenRegistry, extractBearerToken, } from './token-registry.js';
|
|
14
14
|
import { buildOpenApiDocument, } from './openapi.js';
|
|
15
|
-
import { auditActor, authorizeConsoleHttp, authorizeConsoleWebSocket, requestMeta, } from './access-control.js';
|
|
15
|
+
import { auditActor, authorizeConsoleHttp, authorizeConsoleWebSocket, requestMeta, webSocketDenialReason, } from './access-control.js';
|
|
16
16
|
const logger = getLogger('http');
|
|
17
17
|
export const REMOTE_CONSOLE_ORIGIN = 'https://console.zhin.dev';
|
|
18
18
|
const DEFAULT_CORS_ORIGINS = Object.freeze([REMOTE_CONSOLE_ORIGIN]);
|
|
@@ -85,12 +85,12 @@ export function createHttpRouter(options = {}) {
|
|
|
85
85
|
if (!principal)
|
|
86
86
|
return undefined;
|
|
87
87
|
if (!authorizeConsoleWebSocket(principal, pathname)) {
|
|
88
|
-
audit(principal, remoteAddress, `ws:${pathname}`, 'denied');
|
|
88
|
+
audit(principal, remoteAddress, `ws:${pathname}`, 'denied', webSocketDenialReason(principal, pathname));
|
|
89
89
|
return undefined;
|
|
90
90
|
}
|
|
91
91
|
return principal;
|
|
92
92
|
}
|
|
93
|
-
function audit(principal, remoteAddress, action, outcome) {
|
|
93
|
+
function audit(principal, remoteAddress, action, outcome, reason) {
|
|
94
94
|
const sink = tokenRegistry.accessProvider?.audit;
|
|
95
95
|
if (!sink)
|
|
96
96
|
return;
|
|
@@ -99,6 +99,7 @@ export function createHttpRouter(options = {}) {
|
|
|
99
99
|
actor: auditActor(principal),
|
|
100
100
|
action,
|
|
101
101
|
outcome,
|
|
102
|
+
...(reason ? { detail: { reason } } : {}),
|
|
102
103
|
...(remoteAddress ? { ip: remoteAddress } : {}),
|
|
103
104
|
});
|
|
104
105
|
}
|
|
@@ -282,7 +283,7 @@ export function createHttpRouter(options = {}) {
|
|
|
282
283
|
const decision = authorizeConsoleHttp(principal, method, relative);
|
|
283
284
|
if (!decision.allowed) {
|
|
284
285
|
if (principal)
|
|
285
|
-
audit(principal, remoteAddress, `http:${method} ${relative}`, 'denied');
|
|
286
|
+
audit(principal, remoteAddress, `http:${method} ${relative}`, 'denied', decision.reason);
|
|
286
287
|
return { ok: false, status: decision.status, code: decision.code, error: decision.error };
|
|
287
288
|
}
|
|
288
289
|
return { ok: true, ...(principal ? { principal } : {}) };
|
package/lib/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export { createHttpRouter, httpHostToken, normalizePath, REMOTE_CONSOLE_ORIGIN, type HttpHandler, type HttpHost, type HttpRequestInfo, type HttpRouter, type HttpRouterOptions, type HttpRouteRegistration, type RouteContext, type WsConnection, type WsHandle, type WsRouteOptions, type WsTransport, type WsUpgradeDecision, type WsUpgradeRequest, type ListedWsRoute, } from './http-host.js';
|
|
2
2
|
export type { HttpHostAddress, HttpHostOptions, HttpHostTlsOptions } from './host-options.js';
|
|
3
3
|
export { listHttpHostAddresses } from './addresses.js';
|
|
4
|
+
export { DEFAULT_HTTP_PORT, ENVIRONMENT_REFERENCE_PATTERN, InvalidHttpPortError, describeInvalidPortValue, parseHttpPort, } from './port.js';
|
|
4
5
|
export { TokenRegistry, extractBearerToken, publicConsoleAccess, unrestrictedConsoleAccess, type AuthenticatedTokenPrincipal, type ConsoleAccess, type AuthScope, type TokenRegistryConfig, } from './token-registry.js';
|
|
5
|
-
export { auditActor, authorizeConsoleHttp, authorizeConsoleRpc, authorizeConsoleWebSocket, globalPermissions, isUserPrincipal, isExplicitConsolePrincipal, pluginRpcWriteAllowed, principalAllows, requestMeta, rpcAuditDetail, rpcAuditTarget, type RpcAuditOptions, type ConsoleAccessProvider, type ConsoleAuditEntry, type ConsoleBootstrapPrincipal, type ConsoleHttpDecision, type ConsolePrincipal, type ConsoleRequestMeta, type ConsoleRpcDecision, type ConsoleUserPrincipal, } from './access-control.js';
|
|
6
|
+
export { auditActor, authorizeConsoleHttp, authorizeConsoleRpc, authorizeConsoleWebSocket, globalPermissions, grantsDenialReason, isAuditedRpcDenial, unknownConsoleRpcError, webSocketDenialReason, type AuthorizeConsoleRpcOptions, isUserPrincipal, isExplicitConsolePrincipal, isGlobalOwnerPrincipal, isLocalSocketPrincipal, LOCAL_SOCKET_CREDENTIAL_ID, LOCAL_SOCKET_USER_ID, pluginRpcWriteAllowed, principalAllows, requestMeta, rpcAuditDetail, rpcAuditTarget, type RpcAuditOptions, type ConsoleAccessProvider, type ConsoleAuditEntry, type ConsoleBootstrapPrincipal, type ConsoleHttpDecision, type ConsolePrincipal, type ConsoleRequestMeta, type ConsoleRpcDecision, type ConsoleUserPrincipal, } from './access-control.js';
|
|
6
7
|
export { timingSafeEqualString } from './timing-safe-equal.js';
|
|
7
8
|
export { isLoopbackAddress, isLoopbackHost, verifyProtocolAccessToken, type ProtocolAuthRequest } from './protocol-auth.js';
|
|
8
9
|
export { compileTrustProxy, hasForwardingHeaders, parseTrustProxy, resolveClientAddress, TrustProxyConfigError, type TrustProxyPredicate, type TrustProxySetting, } from './trust-proxy.js';
|
|
@@ -10,9 +11,11 @@ export { createConsoleEventHub, consoleEventHubToken, type ConsoleEventHub, } fr
|
|
|
10
11
|
export { buildOpenApiDocument, patternToOpenApiPath, routeRequiresBearerAuth, type BuildOpenApiOptions, type ListedRoute, type OpenApiParameter, type RouteMeta, } from './openapi.js';
|
|
11
12
|
export { HttpBodyError, readBodyBytes, readJsonBody, readTextBody, type HttpBodyReadOptions, } from './json-body.js';
|
|
12
13
|
export { createResponseStream, emptyResponse, jsonResponse, SSE_HEADERS, type HttpResponseStream, type HttpResponseStreamOptions, } from '@zhin.js/host-http-contract';
|
|
13
|
-
export {
|
|
14
|
+
export { isRuntimeConsoleRpcType, RUNTIME_CONSOLE_RPC_TYPES } from './console-rpc-types.js';
|
|
15
|
+
export { createUnknownRpcLog, unknownRpcLog, type UnknownRpcLog, type UnknownRpcLogEntry } from './unknown-rpc.js';
|
|
16
|
+
export { dispatchRuntimeConsoleRpc, pickRpcReply, type RuntimeConsolePage, type RuntimeConsoleRpcContext, type RuntimeConsoleRpcMessage, type RuntimeConsoleRpcReply, type RuntimeEndpointSendInput, type PluginInstallPlan, type PluginUninstallPlan, type PluginUpdatePlan, type PluginManagementPort, type PluginManagementState, type PluginInspectOptions, type PluginDependencyCheck, type PluginVerificationResult, type PluginVerificationPlan, type PluginConfigValidation, } from './console-rpc.js';
|
|
14
17
|
export { dispatchPluginConsoleRpc, PLUGIN_SWITCH_REQUIRES_GATEWAY } from './console-plugin-rpc.js';
|
|
15
18
|
export { buildProjectFileTree, FILE_MANAGER_ALLOWED, FILE_MANAGER_BLOCKED, isProjectPathAllowed, listEnvFiles, readProjectFile, saveProjectFile, type FileTreeNode, } from './project-files.js';
|
|
16
19
|
export { registerConsoleRestPages, type ConsoleAgentIntrospection, type ConsoleAgentRuntime, type ConsoleAgentSessionTree, type ConsoleRestCtx, type ConsoleRestPagesOptions, } from './console-rest-pages.js';
|
|
17
|
-
export { dispatchExtendedConsoleRpc, type ConsoleRpcExtendedCtx, type ConsoleScheduleEngine, type ConsoleWorkroomProfileControlPort, type ConsoleWorkroomKnowledgeControlPort, type ExtendedRpcResult, type ConsoleLoginAssistPort, type ConsoleEndpointLoginAccess, loginAuditDetail, LOGIN_SUBMIT_WAIT_MS, } from './console-rpc-extended/index.js';
|
|
20
|
+
export { dispatchExtendedConsoleRpc, type ConsoleRpcExtendedCtx, type ConsoleScheduleEngine, type ConsoleScheduleTarget, type ConsoleWorkroomProfileControlPort, type ConsoleWorkroomKnowledgeControlPort, type ExtendedRpcResult, type ConsoleLoginAssistPort, type ConsoleEndpointLoginAccess, loginAuditDetail, LOGIN_SUBMIT_WAIT_MS, } from './console-rpc-extended/index.js';
|
|
18
21
|
export * from './host-installer.js';
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { createHttpRouter, httpHostToken, normalizePath, REMOTE_CONSOLE_ORIGIN, } from './http-host.js';
|
|
2
2
|
export { listHttpHostAddresses } from './addresses.js';
|
|
3
|
+
export { DEFAULT_HTTP_PORT, ENVIRONMENT_REFERENCE_PATTERN, InvalidHttpPortError, describeInvalidPortValue, parseHttpPort, } from './port.js';
|
|
3
4
|
export { TokenRegistry, extractBearerToken, publicConsoleAccess, unrestrictedConsoleAccess, } from './token-registry.js';
|
|
4
|
-
export { auditActor, authorizeConsoleHttp, authorizeConsoleRpc, authorizeConsoleWebSocket, globalPermissions, isUserPrincipal, isExplicitConsolePrincipal, pluginRpcWriteAllowed, principalAllows, requestMeta, rpcAuditDetail, rpcAuditTarget, } from './access-control.js';
|
|
5
|
+
export { auditActor, authorizeConsoleHttp, authorizeConsoleRpc, authorizeConsoleWebSocket, globalPermissions, grantsDenialReason, isAuditedRpcDenial, unknownConsoleRpcError, webSocketDenialReason, isUserPrincipal, isExplicitConsolePrincipal, isGlobalOwnerPrincipal, isLocalSocketPrincipal, LOCAL_SOCKET_CREDENTIAL_ID, LOCAL_SOCKET_USER_ID, pluginRpcWriteAllowed, principalAllows, requestMeta, rpcAuditDetail, rpcAuditTarget, } from './access-control.js';
|
|
5
6
|
export { timingSafeEqualString } from './timing-safe-equal.js';
|
|
6
7
|
export { isLoopbackAddress, isLoopbackHost, verifyProtocolAccessToken } from './protocol-auth.js';
|
|
7
8
|
export { compileTrustProxy, hasForwardingHeaders, parseTrustProxy, resolveClientAddress, TrustProxyConfigError, } from './trust-proxy.js';
|
|
@@ -9,6 +10,8 @@ export { createConsoleEventHub, consoleEventHubToken, } from './console-events.j
|
|
|
9
10
|
export { buildOpenApiDocument, patternToOpenApiPath, routeRequiresBearerAuth, } from './openapi.js';
|
|
10
11
|
export { HttpBodyError, readBodyBytes, readJsonBody, readTextBody, } from './json-body.js';
|
|
11
12
|
export { createResponseStream, emptyResponse, jsonResponse, SSE_HEADERS, } from '@zhin.js/host-http-contract';
|
|
13
|
+
export { isRuntimeConsoleRpcType, RUNTIME_CONSOLE_RPC_TYPES } from './console-rpc-types.js';
|
|
14
|
+
export { createUnknownRpcLog, unknownRpcLog } from './unknown-rpc.js';
|
|
12
15
|
export { dispatchRuntimeConsoleRpc, pickRpcReply, } from './console-rpc.js';
|
|
13
16
|
export { dispatchPluginConsoleRpc, PLUGIN_SWITCH_REQUIRES_GATEWAY } from './console-plugin-rpc.js';
|
|
14
17
|
export { buildProjectFileTree, FILE_MANAGER_ALLOWED, FILE_MANAGER_BLOCKED, isProjectPathAllowed, listEnvFiles, readProjectFile, saveProjectFile, } from './project-files.js';
|