@zhin.js/host-http 1.1.0 → 1.1.1
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/README.md +5 -0
- package/lib/console-plugin-rpc.d.ts +3 -0
- package/lib/console-plugin-rpc.js +130 -0
- package/lib/console-rest-pages.d.ts +1 -1
- package/lib/console-rest-pages.js +11 -20
- package/lib/{console-rpc-extended.d.ts → console-rpc-extended/contracts.d.ts} +22 -21
- package/lib/console-rpc-extended/contracts.js +4 -0
- package/lib/console-rpc-extended/dispatcher.d.ts +2 -0
- package/lib/console-rpc-extended/dispatcher.js +92 -0
- package/lib/console-rpc-extended/endpoint-rpc.d.ts +24 -0
- package/lib/console-rpc-extended/endpoint-rpc.js +137 -0
- package/lib/console-rpc-extended/inbox-rpc.d.ts +23 -0
- package/lib/console-rpc-extended/inbox-rpc.js +292 -0
- package/lib/console-rpc-extended/index.d.ts +2 -0
- package/lib/console-rpc-extended/index.js +1 -0
- package/lib/console-rpc-extended/login-rpc.d.ts +4 -0
- package/lib/console-rpc-extended/login-rpc.js +46 -0
- package/lib/console-rpc-extended/rpc-values.d.ts +16 -0
- package/lib/console-rpc-extended/rpc-values.js +87 -0
- package/lib/console-rpc-extended/schedule-rpc.d.ts +6 -0
- package/lib/console-rpc-extended/schedule-rpc.js +109 -0
- package/lib/console-rpc-extended/workroom-rpc.d.ts +3 -0
- package/lib/console-rpc-extended/workroom-rpc.js +104 -0
- package/lib/console-rpc.d.ts +71 -15
- package/lib/console-rpc.js +116 -82
- package/lib/index.d.ts +3 -2
- package/lib/index.js +2 -1
- package/lib/marketplace-registry.d.ts +12 -0
- package/lib/marketplace-registry.js +45 -0
- package/package.json +4 -4
- package/src/console-plugin-rpc.ts +142 -0
- package/src/console-rest-pages.ts +14 -26
- package/src/console-rpc-extended/contracts.ts +155 -0
- package/src/console-rpc-extended/dispatcher.ts +114 -0
- package/src/console-rpc-extended/endpoint-rpc.ts +174 -0
- package/src/console-rpc-extended/inbox-rpc.ts +342 -0
- package/src/console-rpc-extended/index.ts +8 -0
- package/src/console-rpc-extended/login-rpc.ts +50 -0
- package/src/console-rpc-extended/rpc-values.ts +104 -0
- package/src/console-rpc-extended/schedule-rpc.ts +117 -0
- package/src/console-rpc-extended/workroom-rpc.ts +116 -0
- package/src/console-rpc.ts +189 -97
- package/src/index.ts +7 -2
- package/src/marketplace-registry.ts +58 -0
- package/lib/console-rpc-extended.js +0 -916
- package/src/console-rpc-extended.ts +0 -1192
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { errorMessage, optionalRpcText, optionalRpcTextArray, requiredRpcBoolean, requiredRpcInteger, requiredRpcText, requiredRpcValue, } from './rpc-values.js';
|
|
2
|
+
export async function mutateWorkroomProfile(type, data, ctx) {
|
|
3
|
+
const control = ctx.workroomProfileControl;
|
|
4
|
+
if (!control)
|
|
5
|
+
return { error: 'Workroom Profile authority control is unavailable' };
|
|
6
|
+
for (const forbidden of [
|
|
7
|
+
'authenticatedPrincipalId', 'principalId', 'decision', 'governance', 'approval', 'authority',
|
|
8
|
+
]) {
|
|
9
|
+
if (forbidden in data)
|
|
10
|
+
return { error: `Console Workroom Profile request cannot carry ${forbidden}` };
|
|
11
|
+
}
|
|
12
|
+
if (type === 'workroom.profile.status') {
|
|
13
|
+
try {
|
|
14
|
+
return { data: await control.getPlanningStatus(requiredRpcText(data.projectId, 'projectId'), ctx.authenticatedPrincipal) };
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
return { error: errorMessage(error) };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const principal = ctx.authenticatedPrincipal;
|
|
21
|
+
if (!principal)
|
|
22
|
+
return { error: 'Workroom Profile control requires an authenticated principal' };
|
|
23
|
+
try {
|
|
24
|
+
const operationId = requiredRpcText(data.operationId, 'operationId');
|
|
25
|
+
if (type === 'workroom.profile.bootstrap') {
|
|
26
|
+
const projectId = requiredRpcText(data.projectId, 'projectId');
|
|
27
|
+
const includeTools = optionalRpcTextArray(data.includeTools, 'includeTools');
|
|
28
|
+
const includeSkills = optionalRpcTextArray(data.includeSkills, 'includeSkills');
|
|
29
|
+
return { data: await control.bootstrapPlanning(Object.freeze({
|
|
30
|
+
operationId,
|
|
31
|
+
projectId,
|
|
32
|
+
expectedRegistryRevision: requiredRpcInteger(data.expectedRegistryRevision, 'expectedRegistryRevision', -1),
|
|
33
|
+
...(includeTools ? { includeTools } : {}),
|
|
34
|
+
...(includeSkills ? { includeSkills } : {}),
|
|
35
|
+
}), principal) };
|
|
36
|
+
}
|
|
37
|
+
if (type === 'workroom.profile.pack.publish') {
|
|
38
|
+
return { data: await control.publishPack(Object.freeze({ operationId, pack: requiredRpcValue(data.pack, 'pack') }), principal) };
|
|
39
|
+
}
|
|
40
|
+
const projectId = requiredRpcText(data.projectId, 'projectId');
|
|
41
|
+
if (type === 'workroom.profile.policy.publish') {
|
|
42
|
+
const expectedPreviousDigest = optionalRpcText(data.expectedPreviousDigest, 'expectedPreviousDigest');
|
|
43
|
+
return { data: await control.publishPlanningPolicy(Object.freeze({
|
|
44
|
+
operationId,
|
|
45
|
+
projectId,
|
|
46
|
+
profileRevisionId: requiredRpcText(data.profileRevisionId, 'profileRevisionId'),
|
|
47
|
+
revision: requiredRpcInteger(data.revision, 'revision'),
|
|
48
|
+
...(expectedPreviousDigest ? { expectedPreviousDigest } : {}),
|
|
49
|
+
policy: requiredRpcValue(data.policy, 'policy'),
|
|
50
|
+
}), principal) };
|
|
51
|
+
}
|
|
52
|
+
const common = {
|
|
53
|
+
operationId,
|
|
54
|
+
projectId,
|
|
55
|
+
expectedRegistryRevision: requiredRpcInteger(data.expectedRegistryRevision, 'expectedRegistryRevision', -1),
|
|
56
|
+
overlay: requiredRpcValue(data.overlay, 'overlay'),
|
|
57
|
+
activate: requiredRpcBoolean(data.activate, 'activate'),
|
|
58
|
+
};
|
|
59
|
+
return type === 'workroom.profile.rollback'
|
|
60
|
+
? { data: await control.publishRollback(Object.freeze({
|
|
61
|
+
...common,
|
|
62
|
+
restoredFromRevisionId: requiredRpcText(data.restoredFromRevisionId, 'restoredFromRevisionId'),
|
|
63
|
+
}), principal) }
|
|
64
|
+
: { data: await control.publishProfile(Object.freeze(common), principal) };
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
return { error: errorMessage(error) };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export async function mutateWorkroomKnowledge(type, data, ctx) {
|
|
71
|
+
const control = ctx.workroomKnowledgeControl;
|
|
72
|
+
if (!control)
|
|
73
|
+
return { error: 'Workroom Knowledge control is unavailable' };
|
|
74
|
+
for (const forbidden of [
|
|
75
|
+
'authenticatedPrincipalId', 'principalId', 'ownerPrincipalId', 'source', 'decision',
|
|
76
|
+
'governance', 'approval', 'authority', 'body', 'content',
|
|
77
|
+
]) {
|
|
78
|
+
if (forbidden in data)
|
|
79
|
+
return { error: `Console Workroom Knowledge request cannot carry ${forbidden}` };
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const projectId = requiredRpcText(data.projectId, 'projectId');
|
|
83
|
+
if (type === 'workroom.knowledge.get')
|
|
84
|
+
return { data: await control.read(projectId) };
|
|
85
|
+
const principal = ctx.authenticatedPrincipal;
|
|
86
|
+
if (!principal)
|
|
87
|
+
return { error: 'Workroom Knowledge mutation requires an authenticated principal' };
|
|
88
|
+
const common = {
|
|
89
|
+
operationId: requiredRpcText(data.operationId, 'operationId'), projectId,
|
|
90
|
+
expectedRevision: requiredRpcInteger(data.expectedRevision, 'expectedRevision', -1),
|
|
91
|
+
};
|
|
92
|
+
if (type === 'workroom.knowledge.rollback') {
|
|
93
|
+
return { data: await control.rollback(Object.freeze({
|
|
94
|
+
...common, restoreRevision: requiredRpcInteger(data.restoreRevision, 'restoreRevision', 0),
|
|
95
|
+
}), principal) };
|
|
96
|
+
}
|
|
97
|
+
if (!Array.isArray(data.entries))
|
|
98
|
+
throw new Error('entries is invalid');
|
|
99
|
+
return { data: await control.publish(Object.freeze({ ...common, entries: structuredClone(data.entries) }), principal) };
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
return { error: errorMessage(error) };
|
|
103
|
+
}
|
|
104
|
+
}
|
package/lib/console-rpc.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AuthScope } from './token-registry.js';
|
|
2
2
|
import type { DatabaseHostConsole } from '@zhin.js/plugin-runtime';
|
|
3
|
-
import { type ConsoleEndpointSummary } from '@zhin.js/console-protocol';
|
|
4
|
-
import { type ConsoleRpcExtendedCtx } from './console-rpc-extended.js';
|
|
3
|
+
import { type ConsoleConfigSource, type ConsoleEndpointSummary } from '@zhin.js/console-protocol';
|
|
4
|
+
import { type ConsoleRpcExtendedCtx } from './console-rpc-extended/index.js';
|
|
5
5
|
/**
|
|
6
6
|
* Console RPC 请求结构:未锚定 endpoint 的会话地址(结构对齐
|
|
7
7
|
* `Omit<ConversationRef,'endpoint'>`;host/http 架构分层不允许依赖
|
|
@@ -22,6 +22,62 @@ export type RuntimeConsoleRpcMessage = {
|
|
|
22
22
|
readonly [key: string]: unknown;
|
|
23
23
|
};
|
|
24
24
|
export type RuntimeConsoleRpcReply = Record<string, unknown>;
|
|
25
|
+
export interface PluginInstallPlan {
|
|
26
|
+
readonly packageName: string;
|
|
27
|
+
readonly instanceKey: string;
|
|
28
|
+
readonly alreadyDeclared: boolean;
|
|
29
|
+
readonly alreadyInstalled: boolean;
|
|
30
|
+
readonly restartRequired: boolean;
|
|
31
|
+
readonly changes: Readonly<{
|
|
32
|
+
readonly packageManifest: 'unchanged' | 'add-plugin';
|
|
33
|
+
readonly config: 'unchanged' | 'create-entry' | 'schema-required';
|
|
34
|
+
}>;
|
|
35
|
+
readonly warnings: readonly string[];
|
|
36
|
+
}
|
|
37
|
+
export interface PluginUninstallPlan {
|
|
38
|
+
readonly packageName: string;
|
|
39
|
+
readonly instanceKey: string;
|
|
40
|
+
readonly installed: boolean;
|
|
41
|
+
readonly declared: boolean;
|
|
42
|
+
readonly hasConfig: boolean;
|
|
43
|
+
readonly restartRequired: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface PluginUpdatePlan {
|
|
46
|
+
readonly packageName: string;
|
|
47
|
+
readonly instanceKey: string;
|
|
48
|
+
readonly currentVersion: string | null;
|
|
49
|
+
readonly targetVersion: string;
|
|
50
|
+
readonly installed: boolean;
|
|
51
|
+
readonly declared: boolean;
|
|
52
|
+
readonly alreadyCurrent: boolean;
|
|
53
|
+
readonly restartRequired: boolean;
|
|
54
|
+
}
|
|
55
|
+
export interface PluginManagementPort {
|
|
56
|
+
planInstall(packageName: string): Promise<PluginInstallPlan>;
|
|
57
|
+
install?(packageName: string, expectedRevision?: string): Promise<Readonly<{
|
|
58
|
+
readonly plan: PluginInstallPlan;
|
|
59
|
+
readonly restartRequired: boolean;
|
|
60
|
+
}>>;
|
|
61
|
+
planUninstall?(packageName: string): Promise<PluginUninstallPlan>;
|
|
62
|
+
uninstall?(packageName: string, expectedRevision?: string): Promise<Readonly<{
|
|
63
|
+
readonly plan: PluginUninstallPlan;
|
|
64
|
+
readonly restartRequired: boolean;
|
|
65
|
+
}>>;
|
|
66
|
+
planUpdate?(packageName: string, targetVersion: string): Promise<PluginUpdatePlan>;
|
|
67
|
+
update?(packageName: string, targetVersion: string, expectedRevision?: string): Promise<Readonly<{
|
|
68
|
+
readonly plan: PluginUpdatePlan;
|
|
69
|
+
readonly installedVersion: string;
|
|
70
|
+
readonly restartRequired: boolean;
|
|
71
|
+
}>>;
|
|
72
|
+
}
|
|
73
|
+
export interface PluginConfigValidation {
|
|
74
|
+
readonly valid: boolean;
|
|
75
|
+
readonly errors: readonly {
|
|
76
|
+
readonly path: string;
|
|
77
|
+
readonly message: string;
|
|
78
|
+
}[];
|
|
79
|
+
readonly missingEnv: readonly string[];
|
|
80
|
+
}
|
|
25
81
|
export type RuntimeConsolePage = {
|
|
26
82
|
readonly id: string;
|
|
27
83
|
readonly localName: string;
|
|
@@ -35,17 +91,16 @@ export type RuntimeConsoleRpcContext = {
|
|
|
35
91
|
readonly authScope: AuthScope;
|
|
36
92
|
listPages(): Promise<readonly RuntimeConsolePage[]>;
|
|
37
93
|
/** Optional project config accessors for read-only config RPCs. */
|
|
38
|
-
|
|
39
|
-
listPluginKeys?(): Promise<readonly string[]>;
|
|
94
|
+
readConfigSource?(): Promise<ConsoleConfigSource>;
|
|
40
95
|
readConfigDocument?(): Promise<Record<string, unknown>>;
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
96
|
+
/** Full-scope, revision-checked replacement of the active Root config source. */
|
|
97
|
+
replaceConfigSource?(source: string, expectedRevision: string): Promise<{
|
|
98
|
+
readonly revision: string;
|
|
99
|
+
readonly restartRequired: boolean;
|
|
100
|
+
}>;
|
|
46
101
|
/**
|
|
47
102
|
* Full-scope write: set `document[pluginName] = data` and persist.
|
|
48
|
-
* Returns whether a process restart is required
|
|
103
|
+
* Returns whether a process restart is required.
|
|
49
104
|
*/
|
|
50
105
|
setConfigKey?(pluginName: string, data: unknown): Promise<{
|
|
51
106
|
restartRequired: boolean;
|
|
@@ -54,6 +109,10 @@ export type RuntimeConsoleRpcContext = {
|
|
|
54
109
|
setPluginEnabled?(instanceKey: string, enabled: boolean): Promise<Readonly<{
|
|
55
110
|
disabled: readonly string[];
|
|
56
111
|
}>>;
|
|
112
|
+
/** Read-only install planning shared by Console Web and CLI surfaces. */
|
|
113
|
+
pluginManagement?: PluginManagementPort;
|
|
114
|
+
validatePluginConfig?(pluginName: string, data: unknown): Promise<PluginConfigValidation>;
|
|
115
|
+
diagnosePlugin?(pluginName: string): Promise<unknown>;
|
|
57
116
|
/** Atomic, revision-checked runtime Workroom Catalog read/write. */
|
|
58
117
|
readWorkroomCatalog?(): Promise<Readonly<{
|
|
59
118
|
agents: Readonly<Record<string, unknown>>;
|
|
@@ -83,8 +142,8 @@ export type RuntimeConsoleRpcContext = {
|
|
|
83
142
|
getSchema?(pluginName?: string): Promise<unknown>;
|
|
84
143
|
getAllSchemas?(): Promise<Record<string, unknown>>;
|
|
85
144
|
/** Optional Adapter endpoint Console surface (Sandbox / Remote Console). */
|
|
86
|
-
listEndpoints?(): Promise<readonly
|
|
87
|
-
getEndpoint?(adapter: string, endpointKey: string): Promise<
|
|
145
|
+
listEndpoints?(): Promise<readonly ConsoleEndpointSummary[]>;
|
|
146
|
+
getEndpoint?(adapter: string, endpointKey: string): Promise<ConsoleEndpointSummary | null>;
|
|
88
147
|
sendEndpointMessage?(input: RuntimeEndpointSendInput): Promise<{
|
|
89
148
|
messageId: string;
|
|
90
149
|
}>;
|
|
@@ -102,9 +161,6 @@ export type RuntimeConsoleRpcContext = {
|
|
|
102
161
|
/** Extended RPC surface(cron/schedule、endpoint 社交/inbox),fullScope 由 authScope 推导。 */
|
|
103
162
|
extended?: Omit<ConsoleRpcExtendedCtx, 'fullScope'>;
|
|
104
163
|
};
|
|
105
|
-
export type RuntimeEndpointPhase = NonNullable<ConsoleEndpointSummary['phase']>;
|
|
106
|
-
/** @deprecated Prefer `ConsoleEndpointSummary` from `@zhin.js/console-protocol`. */
|
|
107
|
-
export type RuntimeEndpointSummary = ConsoleEndpointSummary;
|
|
108
164
|
export type RuntimeDatabaseInfo = {
|
|
109
165
|
readonly dialect: string | null;
|
|
110
166
|
readonly connected: boolean;
|
package/lib/console-rpc.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dispatchExtendedConsoleRpc } from './console-rpc-extended.js';
|
|
3
|
-
|
|
1
|
+
import { CONFIG_RPC, ENDPOINT_RPC, assertDemoConsoleRpcAllowed, } from '@zhin.js/console-protocol';
|
|
2
|
+
import { dispatchExtendedConsoleRpc } from './console-rpc-extended/index.js';
|
|
3
|
+
import { dispatchPluginConsoleRpc } from './console-plugin-rpc.js';
|
|
4
|
+
/** Canonical Console wire fields → runtime conversation address. */
|
|
4
5
|
function wireConversation(channelType, channelId, parent) {
|
|
5
|
-
|
|
6
|
-
const prefixed = /^(private|group|channel|direct|c2c|temp):(.+)$/iu.exec(channelId);
|
|
7
|
-
const rawKind = channelType || prefixed?.[1] || 'private';
|
|
8
|
-
const kind = rawKind === 'group' || rawKind === 'channel' ? rawKind : 'private';
|
|
6
|
+
const kind = channelType === 'group' || channelType === 'channel' ? channelType : 'private';
|
|
9
7
|
const wire = parent && typeof parent === 'object'
|
|
10
8
|
? parent
|
|
11
9
|
: undefined;
|
|
@@ -13,7 +11,7 @@ function wireConversation(channelType, channelId, parent) {
|
|
|
13
11
|
const parentId = typeof wire?.id === 'string' && wire.id ? wire.id : undefined;
|
|
14
12
|
return {
|
|
15
13
|
kind,
|
|
16
|
-
id:
|
|
14
|
+
id: channelId,
|
|
17
15
|
...(parentKind && parentId ? { parent: { kind: parentKind, id: parentId } } : {}),
|
|
18
16
|
};
|
|
19
17
|
}
|
|
@@ -28,7 +26,6 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
28
26
|
const emit = (payload) => {
|
|
29
27
|
payloads.push(payload);
|
|
30
28
|
};
|
|
31
|
-
message = normalizeConsoleRpcMessage(message);
|
|
32
29
|
const type = String(message.type ?? '');
|
|
33
30
|
const requestId = message.requestId;
|
|
34
31
|
if (ctx.authScope === 'demo') {
|
|
@@ -48,6 +45,11 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
48
45
|
return payloads;
|
|
49
46
|
}
|
|
50
47
|
}
|
|
48
|
+
const pluginReply = await dispatchPluginConsoleRpc(type, message, ctx);
|
|
49
|
+
if (pluginReply !== undefined) {
|
|
50
|
+
emit(pluginReply);
|
|
51
|
+
return payloads;
|
|
52
|
+
}
|
|
51
53
|
switch (type) {
|
|
52
54
|
case 'ping':
|
|
53
55
|
emit({ type: 'pong', requestId });
|
|
@@ -73,11 +75,12 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
73
75
|
emit({ requestId, data: pages });
|
|
74
76
|
return payloads;
|
|
75
77
|
}
|
|
76
|
-
case
|
|
78
|
+
case CONFIG_RPC.GET_SOURCE: {
|
|
77
79
|
try {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
const config = ctx.readConfigSource
|
|
81
|
+
? await ctx.readConfigSource()
|
|
82
|
+
: { source: '', format: 'yaml', revision: '', configKeys: [] };
|
|
83
|
+
emit({ requestId, data: config });
|
|
81
84
|
}
|
|
82
85
|
catch (error) {
|
|
83
86
|
emit({
|
|
@@ -87,7 +90,7 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
87
90
|
}
|
|
88
91
|
return payloads;
|
|
89
92
|
}
|
|
90
|
-
case
|
|
93
|
+
case CONFIG_RPC.GET_ALL: {
|
|
91
94
|
try {
|
|
92
95
|
const document = ctx.readConfigDocument ? await ctx.readConfigDocument() : {};
|
|
93
96
|
emit({ requestId, data: document });
|
|
@@ -100,11 +103,9 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
100
103
|
}
|
|
101
104
|
return payloads;
|
|
102
105
|
}
|
|
103
|
-
case
|
|
106
|
+
case CONFIG_RPC.GET: {
|
|
104
107
|
try {
|
|
105
|
-
|
|
106
|
-
const data = (message.data ?? {});
|
|
107
|
-
const key = String(message.key ?? message.pluginName ?? data.pluginName ?? data.plugin ?? data.key ?? '');
|
|
108
|
+
const key = String(message.pluginName ?? '');
|
|
108
109
|
const document = ctx.readConfigDocument ? await ctx.readConfigDocument() : {};
|
|
109
110
|
emit({ requestId, data: key ? document[key] : document });
|
|
110
111
|
}
|
|
@@ -151,22 +152,33 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
151
152
|
}
|
|
152
153
|
return payloads;
|
|
153
154
|
}
|
|
154
|
-
case
|
|
155
|
+
case CONFIG_RPC.REPLACE_SOURCE: {
|
|
155
156
|
try {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
const source = message.source;
|
|
158
|
+
const expectedRevision = message.expectedRevision;
|
|
159
|
+
if (typeof source !== 'string') {
|
|
160
|
+
emit({ requestId, error: 'source field is required' });
|
|
161
|
+
return payloads;
|
|
162
|
+
}
|
|
163
|
+
if (typeof expectedRevision !== 'string' || !/^[a-f0-9]{64}$/u.test(expectedRevision)) {
|
|
164
|
+
emit({ requestId, error: 'expectedRevision is required' });
|
|
159
165
|
return payloads;
|
|
160
166
|
}
|
|
161
|
-
if (!ctx.
|
|
167
|
+
if (!ctx.replaceConfigSource) {
|
|
162
168
|
emit({ requestId, error: 'Config write is not configured' });
|
|
163
169
|
return payloads;
|
|
164
170
|
}
|
|
165
|
-
await ctx.
|
|
171
|
+
const result = await ctx.replaceConfigSource(source, expectedRevision);
|
|
166
172
|
ctx.publishEvent?.('config:updated', { pluginName: null, keys: [] });
|
|
167
173
|
emit({
|
|
168
174
|
requestId,
|
|
169
|
-
data: {
|
|
175
|
+
data: {
|
|
176
|
+
success: true,
|
|
177
|
+
...result,
|
|
178
|
+
message: result.restartRequired
|
|
179
|
+
? '配置已保存,需重启进程才能生效'
|
|
180
|
+
: '配置已保存',
|
|
181
|
+
},
|
|
170
182
|
});
|
|
171
183
|
}
|
|
172
184
|
catch (error) {
|
|
@@ -177,7 +189,7 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
177
189
|
}
|
|
178
190
|
return payloads;
|
|
179
191
|
}
|
|
180
|
-
case
|
|
192
|
+
case CONFIG_RPC.SET: {
|
|
181
193
|
try {
|
|
182
194
|
const pluginName = message.pluginName;
|
|
183
195
|
if (typeof pluginName !== 'string' || !pluginName) {
|
|
@@ -188,6 +200,17 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
188
200
|
emit({ requestId, error: 'Config write is not configured' });
|
|
189
201
|
return payloads;
|
|
190
202
|
}
|
|
203
|
+
if (ctx.validatePluginConfig) {
|
|
204
|
+
const validation = await ctx.validatePluginConfig(pluginName, message.data);
|
|
205
|
+
if (!validation.valid) {
|
|
206
|
+
emit({
|
|
207
|
+
requestId,
|
|
208
|
+
error: '配置校验失败',
|
|
209
|
+
data: validation,
|
|
210
|
+
});
|
|
211
|
+
return payloads;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
191
214
|
const result = await ctx.setConfigKey(pluginName, message.data);
|
|
192
215
|
ctx.publishEvent?.('config:updated', {
|
|
193
216
|
pluginName,
|
|
@@ -340,10 +363,9 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
340
363
|
}
|
|
341
364
|
case 'schema:get': {
|
|
342
365
|
try {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const pluginName = typeof candidate === 'string' ? candidate : undefined;
|
|
366
|
+
const pluginName = typeof message.pluginName === 'string'
|
|
367
|
+
? message.pluginName
|
|
368
|
+
: undefined;
|
|
347
369
|
const schema = ctx.getSchema ? await ctx.getSchema(pluginName) : null;
|
|
348
370
|
emit({ requestId, data: schema });
|
|
349
371
|
}
|
|
@@ -575,11 +597,11 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
575
597
|
}
|
|
576
598
|
case 'endpoint.info': {
|
|
577
599
|
try {
|
|
578
|
-
const data = message;
|
|
579
|
-
const adapter = String(data
|
|
580
|
-
const endpointKey = String(data
|
|
600
|
+
const data = recordField(message.data) ?? message;
|
|
601
|
+
const adapter = String(data.adapter ?? '');
|
|
602
|
+
const endpointKey = String(data.endpointKey ?? '');
|
|
581
603
|
if (!adapter || !endpointKey) {
|
|
582
|
-
emit({ requestId, error: '
|
|
604
|
+
emit({ requestId, error: 'adapter and endpointKey are required' });
|
|
583
605
|
return payloads;
|
|
584
606
|
}
|
|
585
607
|
if (!ctx.getEndpoint) {
|
|
@@ -601,19 +623,68 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
601
623
|
}
|
|
602
624
|
return payloads;
|
|
603
625
|
}
|
|
626
|
+
case ENDPOINT_RPC.TEST: {
|
|
627
|
+
const data = recordField(message.data) ?? message;
|
|
628
|
+
const adapter = stringField(data, 'adapter');
|
|
629
|
+
const endpointKey = stringField(data, 'endpointKey');
|
|
630
|
+
if (!adapter || !endpointKey) {
|
|
631
|
+
emit({ requestId, error: 'adapter and endpointKey are required' });
|
|
632
|
+
return payloads;
|
|
633
|
+
}
|
|
634
|
+
if (!ctx.getEndpoint) {
|
|
635
|
+
emit({ requestId, error: 'Endpoint registry is not configured' });
|
|
636
|
+
return payloads;
|
|
637
|
+
}
|
|
638
|
+
const startedAt = Date.now();
|
|
639
|
+
try {
|
|
640
|
+
const endpoint = await ctx.getEndpoint(adapter, endpointKey);
|
|
641
|
+
if (!endpoint) {
|
|
642
|
+
emit({ requestId, data: {
|
|
643
|
+
reachable: false,
|
|
644
|
+
connected: false,
|
|
645
|
+
phase: 'unconfigured',
|
|
646
|
+
latencyMs: Date.now() - startedAt,
|
|
647
|
+
message: 'Endpoint 未注册到当前 generation',
|
|
648
|
+
} });
|
|
649
|
+
return payloads;
|
|
650
|
+
}
|
|
651
|
+
emit({ requestId, data: {
|
|
652
|
+
reachable: endpoint.connected && endpoint.status === 'online',
|
|
653
|
+
connected: endpoint.connected,
|
|
654
|
+
status: endpoint.status,
|
|
655
|
+
phase: endpoint.phase ?? (endpoint.connected ? 'online' : 'pending'),
|
|
656
|
+
pendingLogin: endpoint.pendingLogin ?? false,
|
|
657
|
+
latencyMs: Date.now() - startedAt,
|
|
658
|
+
message: endpoint.connected
|
|
659
|
+
? 'Endpoint 已连接'
|
|
660
|
+
: endpoint.pendingLogin
|
|
661
|
+
? 'Endpoint 等待登录操作'
|
|
662
|
+
: 'Endpoint 当前离线',
|
|
663
|
+
} });
|
|
664
|
+
}
|
|
665
|
+
catch (error) {
|
|
666
|
+
emit({ requestId, data: {
|
|
667
|
+
reachable: false,
|
|
668
|
+
connected: false,
|
|
669
|
+
phase: 'failed',
|
|
670
|
+
latencyMs: Date.now() - startedAt,
|
|
671
|
+
message: error instanceof Error ? error.message : String(error),
|
|
672
|
+
} });
|
|
673
|
+
}
|
|
674
|
+
return payloads;
|
|
675
|
+
}
|
|
604
676
|
case 'endpoint.send_message': {
|
|
605
677
|
try {
|
|
606
|
-
const data = message;
|
|
607
|
-
const adapter = String(data
|
|
608
|
-
const endpointKey = String(data
|
|
609
|
-
const channelId = String(data
|
|
610
|
-
const channelType = String(data
|
|
611
|
-
const content = data
|
|
612
|
-
|
|
613
|
-
if (!adapter || !endpointKey || !channelId || content === undefined) {
|
|
678
|
+
const data = recordField(message.data) ?? message;
|
|
679
|
+
const adapter = String(data.adapter ?? '');
|
|
680
|
+
const endpointKey = String(data.endpointKey ?? '');
|
|
681
|
+
const channelId = String(data.channelId ?? data.id ?? '');
|
|
682
|
+
const channelType = String(data.channelType ?? data.type ?? '');
|
|
683
|
+
const content = data.content;
|
|
684
|
+
if (!adapter || !endpointKey || !channelId || !channelType || content === undefined) {
|
|
614
685
|
emit({
|
|
615
686
|
requestId,
|
|
616
|
-
error: '
|
|
687
|
+
error: 'adapter, endpointKey, channelId, channelType, and content are required',
|
|
617
688
|
});
|
|
618
689
|
return payloads;
|
|
619
690
|
}
|
|
@@ -624,11 +695,10 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
624
695
|
const result = await ctx.sendEndpointMessage({
|
|
625
696
|
adapter,
|
|
626
697
|
endpointKey,
|
|
627
|
-
conversation: wireConversation(channelType, channelId, data
|
|
698
|
+
conversation: wireConversation(channelType, channelId, data.parent),
|
|
628
699
|
content,
|
|
629
700
|
});
|
|
630
|
-
|
|
631
|
-
emit({ requestId, data: endpointSendResult(result.messageId) });
|
|
701
|
+
emit({ requestId, data: { messageId: result.messageId } });
|
|
632
702
|
}
|
|
633
703
|
catch (error) {
|
|
634
704
|
emit({
|
|
@@ -661,42 +731,6 @@ export async function dispatchRuntimeConsoleRpc(message, ctx) {
|
|
|
661
731
|
}
|
|
662
732
|
return payloads;
|
|
663
733
|
}
|
|
664
|
-
case 'plugin:set-enabled': {
|
|
665
|
-
try {
|
|
666
|
-
const instanceKey = typeof message.instanceKey === 'string' ? message.instanceKey : '';
|
|
667
|
-
const enabled = message.enabled;
|
|
668
|
-
if (!instanceKey || typeof enabled !== 'boolean') {
|
|
669
|
-
emit({ requestId, error: 'instanceKey and boolean enabled are required' });
|
|
670
|
-
return payloads;
|
|
671
|
-
}
|
|
672
|
-
if (!ctx.setPluginEnabled) {
|
|
673
|
-
emit({ requestId, error: 'Plugin lifecycle management is not configured' });
|
|
674
|
-
return payloads;
|
|
675
|
-
}
|
|
676
|
-
const lifecycle = await ctx.setPluginEnabled(instanceKey, enabled);
|
|
677
|
-
ctx.publishEvent?.('plugin:lifecycle-updated', { instanceKey, enabled });
|
|
678
|
-
emit({
|
|
679
|
-
requestId,
|
|
680
|
-
data: {
|
|
681
|
-
success: true,
|
|
682
|
-
instanceKey,
|
|
683
|
-
enabled,
|
|
684
|
-
disabled: lifecycle.disabled,
|
|
685
|
-
restartRequired: true,
|
|
686
|
-
message: `${instanceKey} 已${enabled ? '启用' : '停用'},Host 正在重启`,
|
|
687
|
-
},
|
|
688
|
-
});
|
|
689
|
-
if (ctx.requestRestart) {
|
|
690
|
-
setTimeout(() => {
|
|
691
|
-
void Promise.resolve(ctx.requestRestart?.()).catch(() => undefined);
|
|
692
|
-
}, 500);
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
catch (error) {
|
|
696
|
-
emit({ requestId, error: error instanceof Error ? error.message : String(error) });
|
|
697
|
-
}
|
|
698
|
-
return payloads;
|
|
699
|
-
}
|
|
700
734
|
default:
|
|
701
735
|
if (type.startsWith('db:')) {
|
|
702
736
|
emit({
|
package/lib/index.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ export { timingSafeEqualString } from './timing-safe-equal.js';
|
|
|
5
5
|
export { createConsoleEventHub, consoleEventHubToken, type ConsoleEventHub, } from './console-events.js';
|
|
6
6
|
export { buildOpenApiDocument, patternToOpenApiPath, routeRequiresBearerAuth, type BuildOpenApiOptions, type ListedRoute, type OpenApiParameter, type RouteMeta, } from './openapi.js';
|
|
7
7
|
export { HttpBodyError, readJsonBody, } from './json-body.js';
|
|
8
|
-
export { dispatchRuntimeConsoleRpc, isDemoHttpAllowed, pickRpcReply, type RuntimeConsolePage, type RuntimeConsoleRpcContext, type RuntimeConsoleRpcMessage, type RuntimeConsoleRpcReply, type RuntimeEndpointSendInput, type
|
|
8
|
+
export { dispatchRuntimeConsoleRpc, isDemoHttpAllowed, pickRpcReply, type RuntimeConsolePage, type RuntimeConsoleRpcContext, type RuntimeConsoleRpcMessage, type RuntimeConsoleRpcReply, type RuntimeEndpointSendInput, type PluginInstallPlan, type PluginUninstallPlan, type PluginUpdatePlan, type PluginManagementPort, type PluginConfigValidation, } from './console-rpc.js';
|
|
9
|
+
export { dispatchPluginConsoleRpc } from './console-plugin-rpc.js';
|
|
9
10
|
export { buildProjectFileTree, FILE_MANAGER_ALLOWED, FILE_MANAGER_BLOCKED, isProjectPathAllowed, listEnvFiles, readProjectFile, saveProjectFile, type FileTreeNode, } from './project-files.js';
|
|
10
11
|
export { registerConsoleRestPages, type ConsoleAgentIntrospection, type ConsoleAgentRuntime, type ConsoleAgentSessionTree, type ConsoleRestCtx, type ConsoleRestPagesOptions, } from './console-rest-pages.js';
|
|
11
|
-
export { dispatchExtendedConsoleRpc, type ConsoleRpcExtendedCtx, type ConsoleScheduleEngine, type ConsoleWorkroomProfileControlPort, type ConsoleWorkroomKnowledgeControlPort, type ExtendedRpcResult, } from './console-rpc-extended.js';
|
|
12
|
+
export { dispatchExtendedConsoleRpc, type ConsoleRpcExtendedCtx, type ConsoleScheduleEngine, type ConsoleWorkroomProfileControlPort, type ConsoleWorkroomKnowledgeControlPort, type ExtendedRpcResult, } from './console-rpc-extended/index.js';
|
package/lib/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { createConsoleEventHub, consoleEventHubToken, } from './console-events.j
|
|
|
6
6
|
export { buildOpenApiDocument, patternToOpenApiPath, routeRequiresBearerAuth, } from './openapi.js';
|
|
7
7
|
export { HttpBodyError, readJsonBody, } from './json-body.js';
|
|
8
8
|
export { dispatchRuntimeConsoleRpc, isDemoHttpAllowed, pickRpcReply, } from './console-rpc.js';
|
|
9
|
+
export { dispatchPluginConsoleRpc } from './console-plugin-rpc.js';
|
|
9
10
|
export { buildProjectFileTree, FILE_MANAGER_ALLOWED, FILE_MANAGER_BLOCKED, isProjectPathAllowed, listEnvFiles, readProjectFile, saveProjectFile, } from './project-files.js';
|
|
10
11
|
export { registerConsoleRestPages, } from './console-rest-pages.js';
|
|
11
|
-
export { dispatchExtendedConsoleRpc, } from './console-rpc-extended.js';
|
|
12
|
+
export { dispatchExtendedConsoleRpc, } from './console-rpc-extended/index.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface MarketplaceRegistryOptions {
|
|
2
|
+
readonly fetchFn: typeof fetch;
|
|
3
|
+
readonly url: string;
|
|
4
|
+
readonly ttlMs: number;
|
|
5
|
+
readonly now?: () => number;
|
|
6
|
+
}
|
|
7
|
+
/** Host-owned plugin registry reader with TTL caching and request coalescing. */
|
|
8
|
+
export declare class MarketplaceRegistry {
|
|
9
|
+
#private;
|
|
10
|
+
constructor(options: MarketplaceRegistryOptions);
|
|
11
|
+
list(): Promise<readonly unknown[]>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Host-owned plugin registry reader with TTL caching and request coalescing. */
|
|
2
|
+
export class MarketplaceRegistry {
|
|
3
|
+
#fetch;
|
|
4
|
+
#url;
|
|
5
|
+
#ttlMs;
|
|
6
|
+
#now;
|
|
7
|
+
#snapshot;
|
|
8
|
+
#inflight;
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.#fetch = options.fetchFn;
|
|
11
|
+
this.#url = options.url;
|
|
12
|
+
this.#ttlMs = options.ttlMs;
|
|
13
|
+
this.#now = options.now ?? Date.now;
|
|
14
|
+
}
|
|
15
|
+
list() {
|
|
16
|
+
const snapshot = this.#snapshot;
|
|
17
|
+
if (snapshot && this.#now() < snapshot.expiresAt) {
|
|
18
|
+
return Promise.resolve(snapshot.plugins);
|
|
19
|
+
}
|
|
20
|
+
if (this.#inflight)
|
|
21
|
+
return this.#inflight;
|
|
22
|
+
const request = this.#load();
|
|
23
|
+
this.#inflight = request;
|
|
24
|
+
const clearInflight = () => {
|
|
25
|
+
if (this.#inflight === request)
|
|
26
|
+
this.#inflight = undefined;
|
|
27
|
+
};
|
|
28
|
+
void request.then(clearInflight, clearInflight);
|
|
29
|
+
return request;
|
|
30
|
+
}
|
|
31
|
+
async #load() {
|
|
32
|
+
const response = await this.#fetch(this.#url, {
|
|
33
|
+
signal: AbortSignal.timeout(30_000),
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok)
|
|
36
|
+
throw new Error(`plugins.json fetch failed: ${response.status}`);
|
|
37
|
+
const json = (await response.json());
|
|
38
|
+
const plugins = Object.freeze([...(json.plugins ?? [])]);
|
|
39
|
+
this.#snapshot = Object.freeze({
|
|
40
|
+
plugins,
|
|
41
|
+
expiresAt: this.#now() + this.#ttlMs,
|
|
42
|
+
});
|
|
43
|
+
return plugins;
|
|
44
|
+
}
|
|
45
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/host-http",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Minimal HTTP and WebSocket host for Plugin Runtime (no Koa)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"ws": "^8.21.1",
|
|
21
|
-
"@zhin.js/
|
|
21
|
+
"@zhin.js/logger": "1.1.1",
|
|
22
22
|
"@zhin.js/host-http-contract": "1.1.0",
|
|
23
|
-
"@zhin.js/
|
|
24
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
23
|
+
"@zhin.js/console-protocol": "1.1.6",
|
|
24
|
+
"@zhin.js/plugin-runtime": "1.1.10"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^26.1.2",
|