@zhin.js/host-http 1.0.13 → 1.0.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.
|
@@ -26,6 +26,8 @@ export interface ConsoleRestCtx {
|
|
|
26
26
|
get(name: string): unknown;
|
|
27
27
|
};
|
|
28
28
|
};
|
|
29
|
+
/** Distinguishes a real IM conversation with no Agent turn from a typo key. */
|
|
30
|
+
readonly isKnownConversationSession?: (sessionKey: string) => boolean | undefined | Promise<boolean | undefined>;
|
|
29
31
|
}
|
|
30
32
|
/** 内省数据门面 — 由 basic/cli 用 agent 包 collectIntrospection* 装配。 */
|
|
31
33
|
export interface ConsoleAgentIntrospection {
|
|
@@ -663,9 +663,33 @@ function registerAgentSessionRoutes(route, base, ctx) {
|
|
|
663
663
|
try {
|
|
664
664
|
const sessionId = await runtime.resolveActiveSessionId(parsed.sessionKey);
|
|
665
665
|
if (!sessionId) {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
666
|
+
if (!isRuntimeImSessionKey(parsed.sessionKey)) {
|
|
667
|
+
writeJson(response, 404, {
|
|
668
|
+
success: false,
|
|
669
|
+
error: `未找到会话:${parsed.sessionKey}`,
|
|
670
|
+
});
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const knownConversation = await ctx.isKnownConversationSession?.(parsed.sessionKey);
|
|
674
|
+
if (knownConversation === false) {
|
|
675
|
+
writeJson(response, 404, {
|
|
676
|
+
success: false,
|
|
677
|
+
error: `未找到会话:${parsed.sessionKey}`,
|
|
678
|
+
});
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
// 渠道会话与 Agent 会话的生命周期不同:用户可以先在 Console
|
|
682
|
+
// 选中一个真实渠道会话,而该会话尚未触发过 Agent。树查询是只读操作,
|
|
683
|
+
// 因而将这种正常状态返回为可渲染空态,而不是误报资源不存在。
|
|
684
|
+
writeJson(response, 200, {
|
|
685
|
+
success: true,
|
|
686
|
+
data: {
|
|
687
|
+
state: 'not_started',
|
|
688
|
+
sessionKey: parsed.sessionKey,
|
|
689
|
+
sessionId: null,
|
|
690
|
+
activeLeafMessageId: null,
|
|
691
|
+
points: [],
|
|
692
|
+
},
|
|
669
693
|
});
|
|
670
694
|
return;
|
|
671
695
|
}
|
|
@@ -674,6 +698,7 @@ function registerAgentSessionRoutes(route, base, ctx) {
|
|
|
674
698
|
writeJson(response, 200, {
|
|
675
699
|
success: true,
|
|
676
700
|
data: {
|
|
701
|
+
state: 'active',
|
|
677
702
|
sessionKey: parsed.sessionKey,
|
|
678
703
|
sessionId,
|
|
679
704
|
activeLeafMessageId,
|
|
@@ -774,6 +799,16 @@ function registerAgentSessionRoutes(route, base, ctx) {
|
|
|
774
799
|
}
|
|
775
800
|
}, { summary: 'Switch agent session active leaf', tags: ['console', 'agent'] });
|
|
776
801
|
}
|
|
802
|
+
/** Runtime IM session keys are `<platform>:<endpoint>:<scope>:<sceneId>`. */
|
|
803
|
+
function isRuntimeImSessionKey(value) {
|
|
804
|
+
const first = value.indexOf(':');
|
|
805
|
+
const second = value.indexOf(':', first + 1);
|
|
806
|
+
const third = value.indexOf(':', second + 1);
|
|
807
|
+
return first > 0
|
|
808
|
+
&& second > first + 1
|
|
809
|
+
&& third > second + 1
|
|
810
|
+
&& third < value.length - 1;
|
|
811
|
+
}
|
|
777
812
|
function parseSessionAction(pathname, prefix, action) {
|
|
778
813
|
if (!pathname.startsWith(prefix))
|
|
779
814
|
return null;
|
|
@@ -55,6 +55,18 @@ export interface ConsoleRpcExtendedCtx {
|
|
|
55
55
|
workroomKnowledgeControl?: ConsoleWorkroomKnowledgeControlPort;
|
|
56
56
|
}
|
|
57
57
|
export interface ConsoleWorkroomProfileControlPort {
|
|
58
|
+
getPlanningStatus(projectId: string, authenticatedPrincipal?: Readonly<{
|
|
59
|
+
principalId: string;
|
|
60
|
+
}>): Promise<unknown>;
|
|
61
|
+
bootstrapPlanning(command: Readonly<{
|
|
62
|
+
operationId: string;
|
|
63
|
+
projectId: string;
|
|
64
|
+
expectedRegistryRevision: number;
|
|
65
|
+
includeTools?: readonly string[];
|
|
66
|
+
includeSkills?: readonly string[];
|
|
67
|
+
}>, authenticatedPrincipal: Readonly<{
|
|
68
|
+
principalId: string;
|
|
69
|
+
}>): Promise<unknown>;
|
|
58
70
|
publishPack(command: Readonly<{
|
|
59
71
|
operationId: string;
|
|
60
72
|
pack: unknown;
|
|
@@ -95,6 +95,8 @@ export async function dispatchExtendedConsoleRpc(type, data, ctx) {
|
|
|
95
95
|
});
|
|
96
96
|
case 'endpoint.delete_friend':
|
|
97
97
|
return deleteFriend(d, ctx);
|
|
98
|
+
case 'workroom.profile.status':
|
|
99
|
+
case 'workroom.profile.bootstrap':
|
|
98
100
|
case 'workroom.profile.pack.publish':
|
|
99
101
|
case 'workroom.profile.publish':
|
|
100
102
|
case 'workroom.profile.rollback':
|
|
@@ -118,11 +120,31 @@ async function mutateWorkroomProfile(type, data, ctx) {
|
|
|
118
120
|
if (forbidden in data)
|
|
119
121
|
return { error: `Console Workroom Profile request cannot carry ${forbidden}` };
|
|
120
122
|
}
|
|
123
|
+
if (type === 'workroom.profile.status') {
|
|
124
|
+
try {
|
|
125
|
+
return { data: await control.getPlanningStatus(requiredRpcText(data.projectId, 'projectId'), ctx.authenticatedPrincipal) };
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
return { error: errorMessage(error) };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
121
131
|
const principal = ctx.authenticatedPrincipal;
|
|
122
132
|
if (!principal)
|
|
123
133
|
return { error: 'Workroom Profile control requires an authenticated principal' };
|
|
124
134
|
try {
|
|
125
135
|
const operationId = requiredRpcText(data.operationId, 'operationId');
|
|
136
|
+
if (type === 'workroom.profile.bootstrap') {
|
|
137
|
+
const projectId = requiredRpcText(data.projectId, 'projectId');
|
|
138
|
+
const includeTools = optionalRpcTextArray(data.includeTools, 'includeTools');
|
|
139
|
+
const includeSkills = optionalRpcTextArray(data.includeSkills, 'includeSkills');
|
|
140
|
+
return { data: await control.bootstrapPlanning(Object.freeze({
|
|
141
|
+
operationId,
|
|
142
|
+
projectId,
|
|
143
|
+
expectedRegistryRevision: requiredRpcInteger(data.expectedRegistryRevision, 'expectedRegistryRevision', -1),
|
|
144
|
+
...(includeTools ? { includeTools } : {}),
|
|
145
|
+
...(includeSkills ? { includeSkills } : {}),
|
|
146
|
+
}), principal) };
|
|
147
|
+
}
|
|
126
148
|
if (type === 'workroom.profile.pack.publish') {
|
|
127
149
|
return { data: await control.publishPack(Object.freeze({ operationId, pack: requiredRpcValue(data.pack, 'pack') }), principal) };
|
|
128
150
|
}
|
|
@@ -205,6 +227,16 @@ function requiredRpcText(value, name) {
|
|
|
205
227
|
function optionalRpcText(value, name) {
|
|
206
228
|
return value === undefined ? undefined : requiredRpcText(value, name);
|
|
207
229
|
}
|
|
230
|
+
function optionalRpcTextArray(value, name) {
|
|
231
|
+
if (value === undefined)
|
|
232
|
+
return undefined;
|
|
233
|
+
if (!Array.isArray(value))
|
|
234
|
+
throw new Error(`${name} is invalid`);
|
|
235
|
+
const result = value.map((item, index) => requiredRpcText(item, `${name}.${index}`));
|
|
236
|
+
if (new Set(result).size !== result.length)
|
|
237
|
+
throw new Error(`${name} contains duplicates`);
|
|
238
|
+
return Object.freeze(result);
|
|
239
|
+
}
|
|
208
240
|
function requiredRpcInteger(value, name, minimum = 1) {
|
|
209
241
|
if (!Number.isSafeInteger(value) || Number(value) < minimum)
|
|
210
242
|
throw new Error(`${name} is invalid`);
|
package/package.json
CHANGED
|
@@ -42,6 +42,10 @@ export interface ConsoleRestCtx {
|
|
|
42
42
|
readonly started: boolean;
|
|
43
43
|
readonly models: { get(name: string): unknown };
|
|
44
44
|
};
|
|
45
|
+
/** Distinguishes a real IM conversation with no Agent turn from a typo key. */
|
|
46
|
+
readonly isKnownConversationSession?: (
|
|
47
|
+
sessionKey: string,
|
|
48
|
+
) => boolean | undefined | Promise<boolean | undefined>;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
/** 内省数据门面 — 由 basic/cli 用 agent 包 collectIntrospection* 装配。 */
|
|
@@ -864,9 +868,33 @@ function registerAgentSessionRoutes(
|
|
|
864
868
|
try {
|
|
865
869
|
const sessionId = await runtime.resolveActiveSessionId(parsed.sessionKey);
|
|
866
870
|
if (!sessionId) {
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
871
|
+
if (!isRuntimeImSessionKey(parsed.sessionKey)) {
|
|
872
|
+
writeJson(response, 404, {
|
|
873
|
+
success: false,
|
|
874
|
+
error: `未找到会话:${parsed.sessionKey}`,
|
|
875
|
+
});
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
const knownConversation = await ctx.isKnownConversationSession?.(parsed.sessionKey);
|
|
879
|
+
if (knownConversation === false) {
|
|
880
|
+
writeJson(response, 404, {
|
|
881
|
+
success: false,
|
|
882
|
+
error: `未找到会话:${parsed.sessionKey}`,
|
|
883
|
+
});
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
// 渠道会话与 Agent 会话的生命周期不同:用户可以先在 Console
|
|
887
|
+
// 选中一个真实渠道会话,而该会话尚未触发过 Agent。树查询是只读操作,
|
|
888
|
+
// 因而将这种正常状态返回为可渲染空态,而不是误报资源不存在。
|
|
889
|
+
writeJson(response, 200, {
|
|
890
|
+
success: true,
|
|
891
|
+
data: {
|
|
892
|
+
state: 'not_started',
|
|
893
|
+
sessionKey: parsed.sessionKey,
|
|
894
|
+
sessionId: null,
|
|
895
|
+
activeLeafMessageId: null,
|
|
896
|
+
points: [],
|
|
897
|
+
},
|
|
870
898
|
});
|
|
871
899
|
return;
|
|
872
900
|
}
|
|
@@ -876,6 +904,7 @@ function registerAgentSessionRoutes(
|
|
|
876
904
|
writeJson(response, 200, {
|
|
877
905
|
success: true,
|
|
878
906
|
data: {
|
|
907
|
+
state: 'active',
|
|
879
908
|
sessionKey: parsed.sessionKey,
|
|
880
909
|
sessionId,
|
|
881
910
|
activeLeafMessageId,
|
|
@@ -975,6 +1004,17 @@ function registerAgentSessionRoutes(
|
|
|
975
1004
|
}, { summary: 'Switch agent session active leaf', tags: ['console', 'agent'] });
|
|
976
1005
|
}
|
|
977
1006
|
|
|
1007
|
+
/** Runtime IM session keys are `<platform>:<endpoint>:<scope>:<sceneId>`. */
|
|
1008
|
+
function isRuntimeImSessionKey(value: string): boolean {
|
|
1009
|
+
const first = value.indexOf(':');
|
|
1010
|
+
const second = value.indexOf(':', first + 1);
|
|
1011
|
+
const third = value.indexOf(':', second + 1);
|
|
1012
|
+
return first > 0
|
|
1013
|
+
&& second > first + 1
|
|
1014
|
+
&& third > second + 1
|
|
1015
|
+
&& third < value.length - 1;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
978
1018
|
function parseSessionAction(
|
|
979
1019
|
pathname: string,
|
|
980
1020
|
prefix: string,
|
|
@@ -66,6 +66,15 @@ export interface ConsoleRpcExtendedCtx {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export interface ConsoleWorkroomProfileControlPort {
|
|
69
|
+
getPlanningStatus(projectId: string,
|
|
70
|
+
authenticatedPrincipal?: Readonly<{ principalId: string }>): Promise<unknown>;
|
|
71
|
+
bootstrapPlanning(command: Readonly<{
|
|
72
|
+
operationId: string;
|
|
73
|
+
projectId: string;
|
|
74
|
+
expectedRegistryRevision: number;
|
|
75
|
+
includeTools?: readonly string[];
|
|
76
|
+
includeSkills?: readonly string[];
|
|
77
|
+
}>, authenticatedPrincipal: Readonly<{ principalId: string }>): Promise<unknown>;
|
|
69
78
|
publishPack(command: Readonly<{ operationId: string; pack: unknown }>,
|
|
70
79
|
authenticatedPrincipal: Readonly<{ principalId: string }>): Promise<unknown>;
|
|
71
80
|
publishProfile(command: Readonly<{
|
|
@@ -258,6 +267,8 @@ export async function dispatchExtendedConsoleRpc(
|
|
|
258
267
|
case 'endpoint.delete_friend':
|
|
259
268
|
return deleteFriend(d, ctx);
|
|
260
269
|
|
|
270
|
+
case 'workroom.profile.status':
|
|
271
|
+
case 'workroom.profile.bootstrap':
|
|
261
272
|
case 'workroom.profile.pack.publish':
|
|
262
273
|
case 'workroom.profile.publish':
|
|
263
274
|
case 'workroom.profile.rollback':
|
|
@@ -275,7 +286,8 @@ export async function dispatchExtendedConsoleRpc(
|
|
|
275
286
|
}
|
|
276
287
|
|
|
277
288
|
async function mutateWorkroomProfile(
|
|
278
|
-
type: 'workroom.profile.
|
|
289
|
+
type: 'workroom.profile.status' | 'workroom.profile.bootstrap'
|
|
290
|
+
| 'workroom.profile.pack.publish' | 'workroom.profile.publish'
|
|
279
291
|
| 'workroom.profile.rollback' | 'workroom.profile.policy.publish',
|
|
280
292
|
data: Record<string, unknown>,
|
|
281
293
|
ctx: ConsoleRpcExtendedCtx,
|
|
@@ -287,10 +299,36 @@ async function mutateWorkroomProfile(
|
|
|
287
299
|
]) {
|
|
288
300
|
if (forbidden in data) return { error: `Console Workroom Profile request cannot carry ${forbidden}` };
|
|
289
301
|
}
|
|
302
|
+
if (type === 'workroom.profile.status') {
|
|
303
|
+
try {
|
|
304
|
+
return { data: await control.getPlanningStatus(
|
|
305
|
+
requiredRpcText(data.projectId, 'projectId'),
|
|
306
|
+
ctx.authenticatedPrincipal,
|
|
307
|
+
) };
|
|
308
|
+
} catch (error) {
|
|
309
|
+
return { error: errorMessage(error) };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
290
312
|
const principal = ctx.authenticatedPrincipal;
|
|
291
313
|
if (!principal) return { error: 'Workroom Profile control requires an authenticated principal' };
|
|
292
314
|
try {
|
|
293
315
|
const operationId = requiredRpcText(data.operationId, 'operationId');
|
|
316
|
+
if (type === 'workroom.profile.bootstrap') {
|
|
317
|
+
const projectId = requiredRpcText(data.projectId, 'projectId');
|
|
318
|
+
const includeTools = optionalRpcTextArray(data.includeTools, 'includeTools');
|
|
319
|
+
const includeSkills = optionalRpcTextArray(data.includeSkills, 'includeSkills');
|
|
320
|
+
return { data: await control.bootstrapPlanning(Object.freeze({
|
|
321
|
+
operationId,
|
|
322
|
+
projectId,
|
|
323
|
+
expectedRegistryRevision: requiredRpcInteger(
|
|
324
|
+
data.expectedRegistryRevision,
|
|
325
|
+
'expectedRegistryRevision',
|
|
326
|
+
-1,
|
|
327
|
+
),
|
|
328
|
+
...(includeTools ? { includeTools } : {}),
|
|
329
|
+
...(includeSkills ? { includeSkills } : {}),
|
|
330
|
+
}), principal) };
|
|
331
|
+
}
|
|
294
332
|
if (type === 'workroom.profile.pack.publish') {
|
|
295
333
|
return { data: await control.publishPack(Object.freeze({ operationId, pack: requiredRpcValue(data.pack, 'pack') }), principal) };
|
|
296
334
|
}
|
|
@@ -374,6 +412,14 @@ function optionalRpcText(value: unknown, name: string): string | undefined {
|
|
|
374
412
|
return value === undefined ? undefined : requiredRpcText(value, name);
|
|
375
413
|
}
|
|
376
414
|
|
|
415
|
+
function optionalRpcTextArray(value: unknown, name: string): readonly string[] | undefined {
|
|
416
|
+
if (value === undefined) return undefined;
|
|
417
|
+
if (!Array.isArray(value)) throw new Error(`${name} is invalid`);
|
|
418
|
+
const result = value.map((item, index) => requiredRpcText(item, `${name}.${index}`));
|
|
419
|
+
if (new Set(result).size !== result.length) throw new Error(`${name} contains duplicates`);
|
|
420
|
+
return Object.freeze(result);
|
|
421
|
+
}
|
|
422
|
+
|
|
377
423
|
function requiredRpcInteger(value: unknown, name: string, minimum = 1): number {
|
|
378
424
|
if (!Number.isSafeInteger(value) || Number(value) < minimum) throw new Error(`${name} is invalid`);
|
|
379
425
|
return Number(value);
|