@downcity/city 1.1.6 → 1.1.9
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/bin/cli/agent/AgentManager.js +1 -1
- package/bin/cli/agent/AgentManager.js.map +1 -1
- package/bin/cli/agent/AgentReset.js +1 -1
- package/bin/cli/agent/AgentReset.js.map +1 -1
- package/bin/cli/agent/Init.d.ts.map +1 -1
- package/bin/cli/agent/Init.js +4 -2
- package/bin/cli/agent/Init.js.map +1 -1
- package/bin/cli/agent/Run.d.ts.map +1 -1
- package/bin/cli/agent/Run.js +4 -1
- package/bin/cli/agent/Run.js.map +1 -1
- package/bin/cli/control-plane/ControlPlaneInit.js +1 -1
- package/bin/cli/control-plane/ControlPlaneInit.js.map +1 -1
- package/bin/cli/control-plane/ControlPlaneProcess.d.ts.map +1 -1
- package/bin/cli/control-plane/ControlPlaneProcess.js +2 -1
- package/bin/cli/control-plane/ControlPlaneProcess.js.map +1 -1
- package/bin/cli/model/ModelCommandShared.d.ts +1 -1
- package/bin/cli/model/ModelCommandShared.d.ts.map +1 -1
- package/bin/cli/model/ModelCommandShared.js +1 -1
- package/bin/cli/model/ModelCommandShared.js.map +1 -1
- package/bin/cli/model/ModelCreateCommand.js +1 -1
- package/bin/cli/model/ModelCreateCommand.js.map +1 -1
- package/bin/cli/model/ModelManager.js +1 -1
- package/bin/cli/model/ModelManager.js.map +1 -1
- package/bin/cli/model/ModelReadCommand.js +1 -1
- package/bin/cli/model/ModelReadCommand.js.map +1 -1
- package/bin/cli/service/ServiceCommandSupport.d.ts.map +1 -1
- package/bin/cli/service/ServiceCommandSupport.js +2 -1
- package/bin/cli/service/ServiceCommandSupport.js.map +1 -1
- package/bin/cli/shared/ChatAuth.d.ts.map +1 -1
- package/bin/cli/shared/ChatAuth.js +6 -5
- package/bin/cli/shared/ChatAuth.js.map +1 -1
- package/bin/cli/shared/ChatManager.d.ts.map +1 -1
- package/bin/cli/shared/ChatManager.js +11 -7
- package/bin/cli/shared/ChatManager.js.map +1 -1
- package/bin/cli/shared/Env.js +1 -1
- package/bin/cli/shared/Env.js.map +1 -1
- package/bin/cli/shared/Plugins.js +2 -1
- package/bin/cli/shared/Plugins.js.map +1 -1
- package/bin/cli/shared/PublicHostEnv.js +1 -1
- package/bin/cli/shared/PublicHostEnv.js.map +1 -1
- package/bin/config/Paths.d.ts +1 -5
- package/bin/config/Paths.d.ts.map +1 -1
- package/bin/config/Paths.js +2 -8
- package/bin/config/Paths.js.map +1 -1
- package/bin/control/ChannelAccountApiRoutes.d.ts.map +1 -1
- package/bin/control/ChannelAccountApiRoutes.js +2 -1
- package/bin/control/ChannelAccountApiRoutes.js.map +1 -1
- package/bin/control/EnvApiRoutes.js +1 -1
- package/bin/control/EnvApiRoutes.js.map +1 -1
- package/bin/control/ModelApiRoutes.js +1 -1
- package/bin/control/ModelApiRoutes.js.map +1 -1
- package/bin/control/ModelPoolService.js +1 -1
- package/bin/control/ModelPoolService.js.map +1 -1
- package/bin/control/PluginApiRoutes.d.ts.map +1 -1
- package/bin/control/PluginApiRoutes.js +2 -1
- package/bin/control/PluginApiRoutes.js.map +1 -1
- package/bin/control/gateway/AgentActions.d.ts.map +1 -1
- package/bin/control/gateway/AgentActions.js +4 -3
- package/bin/control/gateway/AgentActions.js.map +1 -1
- package/bin/control/gateway/AgentCatalog.js +4 -4
- package/bin/control/gateway/AgentCatalog.js.map +1 -1
- package/bin/http/auth/AuthService.d.ts.map +1 -1
- package/bin/http/auth/AuthService.js +1 -1
- package/bin/http/auth/AuthService.js.map +1 -1
- package/bin/http/auth/AuthStore.js +2 -2
- package/bin/http/auth/AuthStore.js.map +1 -1
- package/bin/platform/PluginLifecycle.d.ts +51 -0
- package/bin/platform/PluginLifecycle.d.ts.map +1 -0
- package/bin/platform/PluginLifecycle.js +98 -0
- package/bin/platform/PluginLifecycle.js.map +1 -0
- package/bin/platform/chatAuthorization/Store.d.ts +31 -0
- package/bin/platform/chatAuthorization/Store.d.ts.map +1 -0
- package/bin/platform/chatAuthorization/Store.js +145 -0
- package/bin/platform/chatAuthorization/Store.js.map +1 -0
- package/bin/platform/store/StoreChannelAccountRepository.d.ts +34 -0
- package/bin/platform/store/StoreChannelAccountRepository.d.ts.map +1 -0
- package/bin/platform/store/StoreChannelAccountRepository.js +198 -0
- package/bin/platform/store/StoreChannelAccountRepository.js.map +1 -0
- package/bin/platform/store/StoreEnvRepository.d.ts +98 -0
- package/bin/platform/store/StoreEnvRepository.d.ts.map +1 -0
- package/bin/platform/store/StoreEnvRepository.js +334 -0
- package/bin/platform/store/StoreEnvRepository.js.map +1 -0
- package/bin/platform/store/StoreModelRepository.d.ts +61 -0
- package/bin/platform/store/StoreModelRepository.d.ts.map +1 -0
- package/bin/platform/store/StoreModelRepository.js +278 -0
- package/bin/platform/store/StoreModelRepository.js.map +1 -0
- package/bin/platform/store/StoreSchema.d.ts +13 -0
- package/bin/platform/store/StoreSchema.d.ts.map +1 -0
- package/bin/platform/store/StoreSchema.js +319 -0
- package/bin/platform/store/StoreSchema.js.map +1 -0
- package/bin/platform/store/StoreSecureSettings.d.ts +33 -0
- package/bin/platform/store/StoreSecureSettings.d.ts.map +1 -0
- package/bin/platform/store/StoreSecureSettings.js +91 -0
- package/bin/platform/store/StoreSecureSettings.js.map +1 -0
- package/bin/platform/store/StoreShared.d.ts +44 -0
- package/bin/platform/store/StoreShared.d.ts.map +1 -0
- package/bin/platform/store/StoreShared.js +40 -0
- package/bin/platform/store/StoreShared.js.map +1 -0
- package/bin/platform/store/crypto.d.ts +24 -0
- package/bin/platform/store/crypto.d.ts.map +1 -0
- package/bin/platform/store/crypto.js +101 -0
- package/bin/platform/store/crypto.js.map +1 -0
- package/bin/platform/store/index.d.ts +230 -0
- package/bin/platform/store/index.d.ts.map +1 -0
- package/bin/platform/store/index.js +360 -0
- package/bin/platform/store/index.js.map +1 -0
- package/bin/platform/store/schema.d.ts +690 -0
- package/bin/platform/store/schema.d.ts.map +1 -0
- package/bin/platform/store/schema.js +81 -0
- package/bin/platform/store/schema.js.map +1 -0
- package/bin/process/registry/AgentHostRuntime.d.ts +9 -1
- package/bin/process/registry/AgentHostRuntime.d.ts.map +1 -1
- package/bin/process/registry/AgentHostRuntime.js +77 -3
- package/bin/process/registry/AgentHostRuntime.js.map +1 -1
- package/package.json +2 -2
- package/src/cli/agent/AgentManager.ts +1 -1
- package/src/cli/agent/AgentReset.ts +1 -1
- package/src/cli/agent/Init.ts +13 -8
- package/src/cli/agent/Run.ts +4 -1
- package/src/cli/control-plane/ControlPlaneInit.ts +1 -1
- package/src/cli/control-plane/ControlPlaneProcess.ts +2 -1
- package/src/cli/model/ModelCommandShared.ts +1 -1
- package/src/cli/model/ModelCreateCommand.ts +1 -1
- package/src/cli/model/ModelManager.ts +1 -1
- package/src/cli/model/ModelReadCommand.ts +1 -1
- package/src/cli/service/ServiceCommandSupport.ts +2 -1
- package/src/cli/shared/ChatAuth.ts +5 -6
- package/src/cli/shared/ChatManager.ts +12 -7
- package/src/cli/shared/Env.ts +1 -1
- package/src/cli/shared/Plugins.ts +1 -1
- package/src/cli/shared/PublicHostEnv.ts +1 -1
- package/src/config/Paths.ts +2 -9
- package/src/control/ChannelAccountApiRoutes.ts +2 -1
- package/src/control/EnvApiRoutes.ts +1 -1
- package/src/control/ModelApiRoutes.ts +1 -1
- package/src/control/ModelPoolService.ts +1 -1
- package/src/control/PluginApiRoutes.ts +4 -2
- package/src/control/gateway/AgentActions.ts +24 -17
- package/src/control/gateway/AgentCatalog.ts +4 -4
- package/src/http/auth/AuthService.ts +1 -1
- package/src/http/auth/AuthStore.ts +2 -2
- package/src/platform/PluginLifecycle.ts +132 -0
- package/src/platform/chatAuthorization/Store.ts +181 -0
- package/src/platform/store/StoreChannelAccountRepository.ts +269 -0
- package/src/platform/store/StoreEnvRepository.ts +452 -0
- package/src/platform/store/StoreModelRepository.ts +324 -0
- package/src/platform/store/StoreSchema.ts +344 -0
- package/src/platform/store/StoreSecureSettings.ts +126 -0
- package/src/platform/store/StoreShared.ts +67 -0
- package/src/platform/store/crypto.ts +112 -0
- package/src/platform/store/index.ts +497 -0
- package/src/platform/store/schema.ts +103 -0
- package/src/process/registry/AgentHostRuntime.ts +79 -3
|
@@ -10,10 +10,8 @@
|
|
|
10
10
|
import type { Hono } from "hono";
|
|
11
11
|
import {
|
|
12
12
|
findBuiltinPlugin,
|
|
13
|
-
isCityPluginEnabled,
|
|
14
13
|
listStaticPluginViews,
|
|
15
14
|
runLocalPluginAction,
|
|
16
|
-
setCityPluginEnabled,
|
|
17
15
|
} from "@downcity/agent";
|
|
18
16
|
import type { PlatformAgentOption } from "@downcity/agent";
|
|
19
17
|
import type {
|
|
@@ -24,6 +22,10 @@ import type {
|
|
|
24
22
|
PluginView,
|
|
25
23
|
} from "@downcity/agent";
|
|
26
24
|
import type { JsonValue } from "@downcity/agent";
|
|
25
|
+
import {
|
|
26
|
+
isCityPluginEnabled,
|
|
27
|
+
setCityPluginEnabled,
|
|
28
|
+
} from "@/platform/PluginLifecycle.js";
|
|
27
29
|
|
|
28
30
|
type PluginActionConfigItem = {
|
|
29
31
|
name: string;
|
|
@@ -33,6 +33,7 @@ import type { AgentProjectInitializationResult } from "@downcity/agent";
|
|
|
33
33
|
import type {
|
|
34
34
|
ExecutionBindingConfig,
|
|
35
35
|
} from "@downcity/agent";
|
|
36
|
+
import { createAgentPlatformRuntime } from "@/process/registry/AgentHostRuntime.js";
|
|
36
37
|
|
|
37
38
|
function resolveExecutionInput(params: {
|
|
38
39
|
modelId?: unknown;
|
|
@@ -56,14 +57,17 @@ export async function initializePlatformAgentProject(params: {
|
|
|
56
57
|
modelId?: unknown;
|
|
57
58
|
forceOverwriteShipJson?: unknown;
|
|
58
59
|
}): Promise<AgentProjectInitializationResult> {
|
|
59
|
-
return initializeAgentProject(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
return initializeAgentProject(
|
|
61
|
+
{
|
|
62
|
+
projectRoot: params.projectRoot,
|
|
63
|
+
agentName: String(params.agentName || "").trim() || undefined,
|
|
64
|
+
execution: resolveExecutionInput({
|
|
65
|
+
modelId: params.modelId,
|
|
66
|
+
}),
|
|
67
|
+
forceOverwriteShipJson: params.forceOverwriteShipJson === true,
|
|
68
|
+
},
|
|
69
|
+
createAgentPlatformRuntime(),
|
|
70
|
+
);
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
/**
|
|
@@ -253,14 +257,17 @@ export async function startManagedAgentByProjectRoot(params: {
|
|
|
253
257
|
`Project not ready: ${normalizedRoot}. Required files: PROFILE.md and downcity.json`,
|
|
254
258
|
);
|
|
255
259
|
}
|
|
256
|
-
await initializeAgentProject(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
260
|
+
await initializeAgentProject(
|
|
261
|
+
{
|
|
262
|
+
projectRoot: normalizedRoot,
|
|
263
|
+
agentName: String(params.initialization?.agentName || "").trim() || undefined,
|
|
264
|
+
execution: resolveExecutionInput({
|
|
265
|
+
modelId: params.initialization?.modelId,
|
|
266
|
+
}),
|
|
267
|
+
forceOverwriteShipJson: params.initialization?.forceOverwriteShipJson === true,
|
|
268
|
+
},
|
|
269
|
+
createAgentPlatformRuntime(),
|
|
270
|
+
);
|
|
264
271
|
} else {
|
|
265
272
|
const profilePath = getProfileMdPath(normalizedRoot);
|
|
266
273
|
const shipPath = getDowncityJsonPath(normalizedRoot);
|
|
@@ -271,7 +278,7 @@ export async function startManagedAgentByProjectRoot(params: {
|
|
|
271
278
|
}
|
|
272
279
|
}
|
|
273
280
|
|
|
274
|
-
ensureRuntimeExecutionBindingReady(normalizedRoot);
|
|
281
|
+
ensureRuntimeExecutionBindingReady(normalizedRoot, createAgentPlatformRuntime());
|
|
275
282
|
const args = await buildRunArgsFromOptions(normalizedRoot, {});
|
|
276
283
|
const started = await startDaemonProcess({
|
|
277
284
|
projectRoot: normalizedRoot,
|
|
@@ -18,9 +18,9 @@ import {
|
|
|
18
18
|
import {
|
|
19
19
|
getProfileMdPath,
|
|
20
20
|
getDowncityJsonPath,
|
|
21
|
-
getDowncityMemoryIndexPath,
|
|
22
21
|
getDowncitySchemaPath,
|
|
23
22
|
getSoulMdPath,
|
|
23
|
+
getDowncityMemoryLongTermPath,
|
|
24
24
|
} from "@/config/Paths.js";
|
|
25
25
|
import { isAgentProjectInitialized } from "@downcity/agent";
|
|
26
26
|
import { listManagedAgentEntries } from "@/process/registry/CityRegistry.js";
|
|
@@ -44,7 +44,7 @@ import type {
|
|
|
44
44
|
PlatformAgentShipJson,
|
|
45
45
|
} from "@downcity/agent";
|
|
46
46
|
import type { DowncityConfig } from "@downcity/agent";
|
|
47
|
-
import { PlatformStore } from "
|
|
47
|
+
import { PlatformStore } from "@/platform/store/index.js";
|
|
48
48
|
const DEFAULT_RUNTIME_HOST = "127.0.0.1";
|
|
49
49
|
const DEFAULT_RUNTIME_PORT = 5314;
|
|
50
50
|
|
|
@@ -596,8 +596,8 @@ export async function buildPlatformConfigStatusResponse(params: {
|
|
|
596
596
|
readPlatformConfigFileStatus({
|
|
597
597
|
key: "memory_index",
|
|
598
598
|
scope: "agent",
|
|
599
|
-
label: ".downcity/memory/
|
|
600
|
-
filePath:
|
|
599
|
+
label: ".downcity/memory/MEMORY.md",
|
|
600
|
+
filePath: getDowncityMemoryLongTermPath(cwd),
|
|
601
601
|
}),
|
|
602
602
|
]);
|
|
603
603
|
}
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
import type { AuthIssuedToken, AuthTokenSummary } from "@downcity/agent";
|
|
10
10
|
import type { AuthPrincipal, AuthTokenRecord, AuthUser } from "@downcity/agent";
|
|
11
|
-
import { optionalTrimmedText } from "@downcity/agent";
|
|
12
11
|
import { AuthError } from "./AuthError.js";
|
|
13
12
|
import { AuthStore, type AuthStoreOptions } from "./AuthStore.js";
|
|
14
13
|
import { extractBearerToken, generateAccessToken, hashAccessToken } from "./TokenService.js";
|
|
14
|
+
import { optionalTrimmedText } from "@/platform/store/StoreShared.js";
|
|
15
15
|
|
|
16
16
|
const LOCAL_CLI_USERNAME = "local-cli";
|
|
17
17
|
const LOCAL_CLI_DISPLAY_NAME = "Local CLI";
|
|
@@ -27,13 +27,13 @@ import type {
|
|
|
27
27
|
AuthTokenRecord,
|
|
28
28
|
AuthUser,
|
|
29
29
|
} from "@downcity/agent";
|
|
30
|
-
import { ensurePlatformStoreSchema } from "@downcity/agent";
|
|
31
30
|
import {
|
|
32
31
|
nowIso,
|
|
33
32
|
normalizeNonEmptyText,
|
|
34
33
|
optionalTrimmedText,
|
|
35
34
|
type PlatformStoreContext,
|
|
36
|
-
} from "
|
|
35
|
+
} from "@/platform/store/StoreShared.js";
|
|
36
|
+
import { ensurePlatformStoreSchema } from "@/platform/store/StoreSchema.js";
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* AuthStore 构造参数。
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* City 级 plugin 生命周期管理。
|
|
3
|
+
*
|
|
4
|
+
* 关键点(中文)
|
|
5
|
+
* - plugin 启用/关闭属于 city 全局配置,不应由 agent 自己写入。
|
|
6
|
+
* - 这里把状态落到平台安全配置中,由 city 统一读写。
|
|
7
|
+
* - 默认策略:未显式关闭时,一律视为启用。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { PlatformStore } from "@/platform/store/index.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 单个 plugin 的 city 级生命周期配置。
|
|
14
|
+
*/
|
|
15
|
+
export interface CityPluginLifecycleItem {
|
|
16
|
+
/**
|
|
17
|
+
* 当前 plugin 是否在 city 级被启用。
|
|
18
|
+
*/
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* 最近更新时间(ISO 字符串)。
|
|
22
|
+
*/
|
|
23
|
+
updatedAt: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* city 级 plugin 生命周期配置映射。
|
|
28
|
+
*/
|
|
29
|
+
export interface CityPluginLifecycleConfig {
|
|
30
|
+
/**
|
|
31
|
+
* 插件生命周期配置对象映射。
|
|
32
|
+
*/
|
|
33
|
+
[pluginName: string]: CityPluginLifecycleItem | undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const PLUGIN_LIFECYCLE_SETTING_KEY = "plugins.lifecycle";
|
|
37
|
+
|
|
38
|
+
function normalizeLifecycleItem(input: unknown): CityPluginLifecycleItem | null {
|
|
39
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return null;
|
|
40
|
+
const record = input as Record<string, unknown>;
|
|
41
|
+
if (typeof record.enabled !== "boolean") return null;
|
|
42
|
+
return {
|
|
43
|
+
enabled: record.enabled,
|
|
44
|
+
updatedAt: String(record.updatedAt || "").trim() || new Date().toISOString(),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeLifecycleConfig(input: unknown): CityPluginLifecycleConfig {
|
|
49
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return {};
|
|
50
|
+
const out: CityPluginLifecycleConfig = {};
|
|
51
|
+
for (const [pluginName, raw] of Object.entries(input as Record<string, unknown>)) {
|
|
52
|
+
const key = String(pluginName || "").trim();
|
|
53
|
+
if (!key) continue;
|
|
54
|
+
const item = normalizeLifecycleItem(raw);
|
|
55
|
+
if (!item) continue;
|
|
56
|
+
out[key] = item;
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 读取 city 级 plugin 生命周期配置。
|
|
63
|
+
*/
|
|
64
|
+
export function readCityPluginLifecycleConfig(): CityPluginLifecycleConfig {
|
|
65
|
+
const store = new PlatformStore();
|
|
66
|
+
try {
|
|
67
|
+
return normalizeLifecycleConfig(
|
|
68
|
+
store.getSecureSettingJsonSync<CityPluginLifecycleConfig>(
|
|
69
|
+
PLUGIN_LIFECYCLE_SETTING_KEY,
|
|
70
|
+
),
|
|
71
|
+
);
|
|
72
|
+
} finally {
|
|
73
|
+
store.close();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 写入完整 city 级 plugin 生命周期配置。
|
|
79
|
+
*/
|
|
80
|
+
export function writeCityPluginLifecycleConfig(
|
|
81
|
+
value: CityPluginLifecycleConfig,
|
|
82
|
+
): CityPluginLifecycleConfig {
|
|
83
|
+
const normalized = normalizeLifecycleConfig(value);
|
|
84
|
+
const store = new PlatformStore();
|
|
85
|
+
try {
|
|
86
|
+
store.setSecureSettingJsonSync(PLUGIN_LIFECYCLE_SETTING_KEY, normalized);
|
|
87
|
+
return normalized;
|
|
88
|
+
} finally {
|
|
89
|
+
store.close();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 读取单个 plugin 的 city 级生命周期状态。
|
|
95
|
+
*/
|
|
96
|
+
export function readCityPluginLifecycleItem(
|
|
97
|
+
pluginName: string,
|
|
98
|
+
): CityPluginLifecycleItem | null {
|
|
99
|
+
const key = String(pluginName || "").trim();
|
|
100
|
+
if (!key) return null;
|
|
101
|
+
return readCityPluginLifecycleConfig()[key] || null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 判断单个 plugin 是否启用。
|
|
106
|
+
*/
|
|
107
|
+
export function isCityPluginEnabled(pluginName: string): boolean {
|
|
108
|
+
const item = readCityPluginLifecycleItem(pluginName);
|
|
109
|
+
if (!item) return true;
|
|
110
|
+
return item.enabled === true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 设置单个 plugin 的启用态。
|
|
115
|
+
*/
|
|
116
|
+
export function setCityPluginEnabled(
|
|
117
|
+
pluginName: string,
|
|
118
|
+
enabled: boolean,
|
|
119
|
+
): CityPluginLifecycleConfig {
|
|
120
|
+
const key = String(pluginName || "").trim();
|
|
121
|
+
if (!key) {
|
|
122
|
+
throw new Error("pluginName is required");
|
|
123
|
+
}
|
|
124
|
+
const current = readCityPluginLifecycleConfig();
|
|
125
|
+
return writeCityPluginLifecycleConfig({
|
|
126
|
+
...current,
|
|
127
|
+
[key]: {
|
|
128
|
+
enabled,
|
|
129
|
+
updatedAt: new Date().toISOString(),
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* City 级 chat authorization 配置存储。
|
|
3
|
+
*
|
|
4
|
+
* 关键点(中文)
|
|
5
|
+
* - 静态 chat 授权配置属于 city 维护的项目侧状态。
|
|
6
|
+
* - 这里把配置落在项目 `.downcity/chat/authorization/config.json`。
|
|
7
|
+
* - agent 只消费注入进来的读写能力,不再自己管理这份配置。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from "fs-extra";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import type {
|
|
13
|
+
ChatAuthorizationChannel,
|
|
14
|
+
ChatAuthorizationConfig,
|
|
15
|
+
ChatAuthorizationRole,
|
|
16
|
+
ChatChannelAuthorizationConfig,
|
|
17
|
+
} from "@downcity/agent";
|
|
18
|
+
import {
|
|
19
|
+
CHAT_AUTHORIZATION_CHANNELS,
|
|
20
|
+
createDefaultChatAuthorizationRoles,
|
|
21
|
+
} from "@downcity/agent";
|
|
22
|
+
|
|
23
|
+
function normalizeText(value: unknown): string | undefined {
|
|
24
|
+
const text = String(value || "").trim();
|
|
25
|
+
return text || undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeRoleMap(input: unknown): Record<string, ChatAuthorizationRole> {
|
|
29
|
+
const defaultRoles = createDefaultChatAuthorizationRoles();
|
|
30
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return defaultRoles;
|
|
31
|
+
const roles: Record<string, ChatAuthorizationRole> = {};
|
|
32
|
+
for (const [rawRoleId, rawRole] of Object.entries(input as Record<string, unknown>)) {
|
|
33
|
+
const roleId = normalizeText(rawRoleId);
|
|
34
|
+
if (!roleId) continue;
|
|
35
|
+
const roleObj =
|
|
36
|
+
rawRole && typeof rawRole === "object" && !Array.isArray(rawRole)
|
|
37
|
+
? (rawRole as {
|
|
38
|
+
name?: unknown;
|
|
39
|
+
description?: unknown;
|
|
40
|
+
permissions?: unknown[];
|
|
41
|
+
})
|
|
42
|
+
: null;
|
|
43
|
+
if (!roleObj) continue;
|
|
44
|
+
const builtinRole = defaultRoles[roleId];
|
|
45
|
+
const permissions = Array.isArray(roleObj.permissions)
|
|
46
|
+
? [...new Set(roleObj.permissions.map((item) => normalizeText(item)).filter(Boolean))]
|
|
47
|
+
: [];
|
|
48
|
+
roles[roleId] = {
|
|
49
|
+
roleId,
|
|
50
|
+
name: normalizeText(roleObj.name) || builtinRole?.name || roleId,
|
|
51
|
+
...(normalizeText(roleObj.description) || builtinRole?.description
|
|
52
|
+
? {
|
|
53
|
+
description:
|
|
54
|
+
normalizeText(roleObj.description) || builtinRole?.description || undefined,
|
|
55
|
+
}
|
|
56
|
+
: {}),
|
|
57
|
+
permissions: permissions as ChatAuthorizationRole["permissions"],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (Object.keys(roles).length === 0) return defaultRoles;
|
|
61
|
+
if (!roles.default) roles.default = defaultRoles.default;
|
|
62
|
+
return roles;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeBindingMap(
|
|
66
|
+
input: unknown,
|
|
67
|
+
roles: Record<string, ChatAuthorizationRole>,
|
|
68
|
+
): Record<string, string> {
|
|
69
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return {};
|
|
70
|
+
const out: Record<string, string> = {};
|
|
71
|
+
for (const [rawId, rawRoleId] of Object.entries(input as Record<string, unknown>)) {
|
|
72
|
+
const entityId = normalizeText(rawId);
|
|
73
|
+
const roleId = normalizeText(rawRoleId);
|
|
74
|
+
if (!entityId || !roleId || !roles[roleId]) continue;
|
|
75
|
+
out[entityId] = roleId;
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function normalizeChannelConfig(
|
|
81
|
+
input: unknown,
|
|
82
|
+
roles: Record<string, ChatAuthorizationRole>,
|
|
83
|
+
): ChatChannelAuthorizationConfig {
|
|
84
|
+
const raw =
|
|
85
|
+
input && typeof input === "object" && !Array.isArray(input)
|
|
86
|
+
? (input as {
|
|
87
|
+
defaultUserRoleId?: unknown;
|
|
88
|
+
userRoles?: unknown;
|
|
89
|
+
})
|
|
90
|
+
: {};
|
|
91
|
+
const defaultUserRoleId = normalizeText(raw.defaultUserRoleId) || "default";
|
|
92
|
+
return {
|
|
93
|
+
defaultUserRoleId: roles[defaultUserRoleId] ? defaultUserRoleId : "default",
|
|
94
|
+
userRoles: normalizeBindingMap(raw.userRoles, roles),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function normalizeAuthorizationConfig(input: unknown): ChatAuthorizationConfig {
|
|
99
|
+
const raw =
|
|
100
|
+
input && typeof input === "object" && !Array.isArray(input)
|
|
101
|
+
? (input as { roles?: unknown; channels?: Record<string, unknown> })
|
|
102
|
+
: {};
|
|
103
|
+
const roles = normalizeRoleMap(raw.roles);
|
|
104
|
+
const channels: Partial<Record<ChatAuthorizationChannel, ChatChannelAuthorizationConfig>> = {};
|
|
105
|
+
for (const channel of CHAT_AUTHORIZATION_CHANNELS) {
|
|
106
|
+
channels[channel] = normalizeChannelConfig(raw.channels?.[channel], roles);
|
|
107
|
+
}
|
|
108
|
+
return { roles, channels };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getChatAuthorizationConfigPath(projectRoot: string): string {
|
|
112
|
+
return path.join(projectRoot, ".downcity", "chat", "authorization", "config.json");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function readConfigFile(projectRoot: string): ChatAuthorizationConfig {
|
|
116
|
+
const file = getChatAuthorizationConfigPath(projectRoot);
|
|
117
|
+
if (!fs.existsSync(file)) return normalizeAuthorizationConfig({});
|
|
118
|
+
try {
|
|
119
|
+
return normalizeAuthorizationConfig(fs.readJsonSync(file));
|
|
120
|
+
} catch {
|
|
121
|
+
return normalizeAuthorizationConfig({});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function writeConfigFile(projectRoot: string, config: ChatAuthorizationConfig): ChatAuthorizationConfig {
|
|
126
|
+
const file = getChatAuthorizationConfigPath(projectRoot);
|
|
127
|
+
fs.ensureDirSync(path.dirname(file));
|
|
128
|
+
fs.writeJsonSync(file, normalizeAuthorizationConfig(config), { spaces: 2 });
|
|
129
|
+
return normalizeAuthorizationConfig(config);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 同步读取 chat authorization 配置。
|
|
134
|
+
*/
|
|
135
|
+
export function readChatAuthorizationConfigSync(projectRoot: string): ChatAuthorizationConfig {
|
|
136
|
+
return readConfigFile(String(projectRoot || "").trim());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 读取 chat authorization 配置。
|
|
141
|
+
*/
|
|
142
|
+
export async function readChatAuthorizationConfig(
|
|
143
|
+
projectRoot: string,
|
|
144
|
+
): Promise<ChatAuthorizationConfig> {
|
|
145
|
+
return readChatAuthorizationConfigSync(projectRoot);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 覆盖写入 chat authorization 配置。
|
|
150
|
+
*/
|
|
151
|
+
export async function writeChatAuthorizationConfig(
|
|
152
|
+
projectRoot: string,
|
|
153
|
+
nextConfig: ChatAuthorizationConfig,
|
|
154
|
+
): Promise<ChatAuthorizationConfig> {
|
|
155
|
+
return writeConfigFile(String(projectRoot || "").trim(), nextConfig);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 设置单个用户角色。
|
|
160
|
+
*/
|
|
161
|
+
export async function setChatAuthorizationUserRole(params: {
|
|
162
|
+
projectRoot: string;
|
|
163
|
+
channel: ChatAuthorizationChannel;
|
|
164
|
+
userId: string;
|
|
165
|
+
roleId: string;
|
|
166
|
+
}): Promise<ChatAuthorizationConfig> {
|
|
167
|
+
const projectRoot = String(params.projectRoot || "").trim();
|
|
168
|
+
const userId = normalizeText(params.userId);
|
|
169
|
+
const roleId = normalizeText(params.roleId);
|
|
170
|
+
if (!projectRoot) throw new Error("projectRoot is required");
|
|
171
|
+
if (!userId || !roleId) throw new Error("userId and roleId are required");
|
|
172
|
+
const next = readConfigFile(projectRoot);
|
|
173
|
+
next.roles = normalizeRoleMap(next.roles);
|
|
174
|
+
if (!next.roles[roleId]) throw new Error(`Unknown roleId: ${roleId}`);
|
|
175
|
+
next.channels ??= {};
|
|
176
|
+
const channelConfig = normalizeChannelConfig(next.channels[params.channel], next.roles);
|
|
177
|
+
channelConfig.userRoles ??= {};
|
|
178
|
+
channelConfig.userRoles[userId] = roleId;
|
|
179
|
+
next.channels[params.channel] = channelConfig;
|
|
180
|
+
return writeConfigFile(projectRoot, next);
|
|
181
|
+
}
|